1. 完整的网络结构
以下是参考b站上作者以及yolov5官方代码画出的yolov5l v6.0版本的模型结构,v6.0版本的模型结构是目前yolov5版本的稳定版本,想必以后也不会有什么改变。l,m,n,s,x只是有些层以及输出通道数变化,整体架构是完全一样的,因此我们只拿一个l模型来说。其他的就不在一一介绍了。
https://www.bilibili.com/video/BV1zB4y1L7Ay/?p=2&vd_source=f2dd6b8571e0ba116897678fab0fb319
从上图可以看出,模型主要分为三个部分:
backbone:主要用于图片的特征提取;
neck:主要对特征图进行多尺度融合,传给detect层;
detect:用来预测最终结果。
其中neck和detect部分被统一称作为head模块。
接下来对图中各个标识做一下说明:
**0,1,2,…,22,23:**每一层编号;
**P1,P2,P3,P4,P5:代表不同特征金字塔层,特征图相对于原图尺寸缩减了多少倍,比如P4就是24 = 16倍,在yaml文件里用P4/16;
**ConvBNSiLU:**代表conv2d + batchnorm + silu;
k1,s1,p0,c64:代表conv2d的参数,kernelsize,stride,padding,out_channels;
C3: 代表C3层,csp的变形,是整个yolov5的核心层,可以提升网络的深度(残差结构),减少模型参数(我的理解是在不改变输入输出通道的状态下,通过使用卷积将通道数减半分两条分支,一条分支做特征提取,另一条分支保留原来信息,然后contact来实现);
bottleneck: C3中重复的模块,分为残差结构(shortcut=True),非残差结构(shortcut=False);
SPPF:是SPP(空间金字塔池化)的升级版,保留了SPP的性能,运算效率得到提升,所以叫SPP-Faster,yolo中的作用主要是实现局部特征和全局特征的featherMap级别的融合,还是为了不同尺度的特征融合;
PAN: PAN结构为自下而上到自上而下的特征金字塔,目的不同尺度的特征融合。
2 不同模块的代码
在yolov5中不同模块的结构代码定义在common.py中
- ConvBNSiLU 模块
这里代码很简单,没有什么要解释的
# 1. ConvBNSiLU module
class Conv(nn.Module):
""" yolov5 中最常见的卷积模块,也就是ConvBNSiLU 模块 """
def __init__(self, c1, c2, k=1, s=1, p=None, g=1, act=True): # ch_in, ch_out, kernel, stride, padding, groups
super().__init__()
self.conv = nn.Conv2d(c1, c2, k, s, autopad(k, p), groups=g, bias=False)
self.bn = nn.BatchNorm2d(c2)
self.act = nn.SiLU() if act is True else (act if isinstance(act, nn.Module) else nn.Identity())
def forward(self, x):
return self.act(self.bn(self.conv(x)))
def forward_fuse(self, x):
return self.act(self.conv(x))
- C3, bottleneck 模块
对着模型结构图看,也很简单,主要解释一下C3层重复的是其中bottleneck层
# 2. C3, bottleneck
class Bottleneck(nn.Module):
# Standard bottleneck
def __init__(self, c1, c2, shortcut=True, g=1, e=0.5): # ch_in, ch_out, shortcut, groups, expansion
super().__init__()
c_ = int(c2 * e) # hidden channels
self.cv1 = Conv(c1, c_, 1, 1)
self.cv2 = Conv(c_, c2, 3, 1, g=g)
self.add = shortcut and c1 == c2
def forward(self, x):
return x + self.cv2(self.cv1(x)) if self.add else self.cv2(self.cv1(x))
class C3(nn.Module):
""" 贯彻整个模型的最主要的结构,有两种C3 结构,一个是shortcut=True的形式,一个是为False的形式,具体看一下模型结构示意图 """
def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5): # ch_in, ch_out, number, shortcut, groups, expansion
super().__init__()
c_ = int(c2 * e) # hidden channels
self.cv1 = Conv(c1, c_, 1, 1)
self.cv2 = Conv(c1, c_, 1, 1)
self.cv3 = Conv(2 * c_, c2, 1) # act=FReLU(c2)
self.m = nn.Sequential(*[Bottleneck(c_, c_, shortcut, g, e=1.0) for _ in range(n)])
# self.m = nn.Sequential(*[CrossConv(c_, c_, 3, 1, g, 1.0, shortcut) for _ in range(n)])
def forward(self, x):
return self.cv3(torch.cat((self.m(self.cv1(x)), self.cv2(x)), dim=1))
- SPPF
不做过多解释
# 3. SPPF
class SPPF(nn.Module):
# Spatial Pyramid Pooling - Fast (SPPF) layer for YOLOv5 by Glenn Jocher
def __init__(self, c1, c2, k=5): # equivalent to SPP(k=(5, 9, 13))
super().__init__()
c_ = c1 // 2 # hidden channels
self.cv1 = Conv(c1, c_, 1, 1)
self.cv2 = Conv(c_ * 4, c2, 1, 1)
self.m = nn.MaxPool2d(kernel_size=k, stride=1, padding=k // 2)
def forward(self, x):
x = self.cv1(x)
with warnings.catch_warnings():
warnings.simplefilter('ignore') # suppress torch 1.9.0 max_pool2d() warning
y1 = self.m(x)
y2 = self.m(y1)
return self.cv2(torch.cat([x, y1, y2, self.m(y2)], 1))
- Concat
# 4. Concat
class Concat(nn.Module):
# Concatenate a list of tensors along dimension
""" 模型结构中的concat层,基本是把通道拼接在一起"""
def __init__(self, dimension=1):
super().__init__()
self.d = dimension
def forward(self, x):
return torch.cat(x, self.d)
3 备注
完整的代码https://github.com/ideal-ai-mu/deeplearning/blob/main/yolo/yolov5/common.py
模型结构已经画的很详细了,按照结构去一步步实现即可,没什么难的。