目标检测|YOLOv5网络结构学习

最近在学习yolov5的代码(因为项目需要),其实陆陆续续接触yolov5已经半年左右了,用yolov5也跑过了自己的数据集,但是一直没有上手对代码进行修改,主要还是因为用到了很多工程性的技术和代码,之前做科研的时候没有接触到,改代码方式如果不正确会引发很多BUG。
我们就先从yolov5的网络结构开始讲起吧~
一、Focus层 Focus层的代码如下:

class Focus(nn.Module): # Focus wh information into c-space def __init__(self, c1, c2, k=1, s=1, p=None, g=1, act=True):# ch_in, ch_out, kernel, stride, padding, groups super(Focus, self).__init__() self.conv = Conv(c1 * 4, c2, k, s, p, g, act) # self.contract = Contract(gain=2)def forward(self, x):# x(b,c,w,h) -> y(b,4c,w/2,h/2) return self.conv(torch.cat([x[..., ::2, ::2], x[..., 1::2, ::2], x[..., ::2, 1::2], x[..., 1::2, 1::2]], 1)) # return self.conv(self.contract(x))

输入图片大小为[3,640,640],首先先对其进行切片操作,然后通过concat操作连接到一起变成[12,320,320],最后进行卷积。
目标检测|YOLOv5网络结构学习
文章图片

具体切片是如何操作的,可以参考下图。从图中或者代码中可以看出,切片操作是分别从(0,0),(0,1),(1,0),(1,1)这四个点开始,每隔两个步长取图片中的一个像素点形成的。
目标检测|YOLOv5网络结构学习
文章图片

Focus层虽然是yolov5中首先提出来的,但是这个操作非常类似于yolov2中的PassThrough层:将w-h平面上的信息转换到通道维度,再通过卷积的方式提取不同特征。采用Focus层的目的应该是下采样(下采样在神经网络中主要是为了减少参数量达到降维的作用,同时还能增加局部感受野),但是相比于使用使用步长为2的卷积层或者池化层,Focus层能够有效减少下采样带来的信息损失,同时减少计算量。说到这里,有必要提一下空洞卷积,操作过程也非常类似,在设计卷积神经网络的时候可以考虑替换使用看看哪个效果更好啊~
二、Bottleneck模块 Bottleneck结构就很简单了,一个1×1的卷积后接一个3×3的卷积,其中1×1的卷积将通道数减半,3×3的卷积将通道数加倍,然后加上输入(注意这里是add操作,不是concat操作)。所以经过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(Bottleneck, self).__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 == c2def forward(self, x): return x + self.cv2(self.cv1(x)) if self.add else self.cv2(self.cv1(x))

目标检测|YOLOv5网络结构学习
文章图片

三、BottleneckCSP模块 由于yolov5一直在更新,上次我看的时候它使用的是BottleneckCSP模块,这次看它已经改成了C3,其实结构是一样的,写法略微有差异。BottleneckCSP中cv2和cv3调用的是系统的卷积层,使用concat连接之后加上BN层和激活函数;C3则直接使用了作者自己定义的卷积层(conv+batchnorm+SiLU),这里激活函数也有修改。(yolov5作为一个工程性的项目,作者一直在维护和修改,所以经常会有一些细节性的调整)
class BottleneckCSP(nn.Module): # CSP Bottleneck https://github.com/WongKinYiu/CrossStagePartialNetworks def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5):# ch_in, ch_out, number, shortcut, groups, expansion super(BottleneckCSP, self).__init__() c_ = int(c2 * e)# hidden channels self.cv1 = Conv(c1, c_, 1, 1) self.cv2 = nn.Conv2d(c1, c_, 1, 1, bias=False) self.cv3 = nn.Conv2d(c_, c_, 1, 1, bias=False) self.cv4 = Conv(2 * c_, c2, 1, 1) self.bn = nn.BatchNorm2d(2 * c_)# applied to cat(cv2, cv3) self.act = nn.LeakyReLU(0.1, inplace=True) self.m = nn.Sequential(*[Bottleneck(c_, c_, shortcut, g, e=1.0) for _ in range(n)])def forward(self, x): y1 = self.cv3(self.m(self.cv1(x))) y2 = self.cv2(x) return self.cv4(self.act(self.bn(torch.cat((y1, y2), dim=1))))class C3(nn.Module): # CSP Bottleneck with 3 convolutions def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5):# ch_in, ch_out, number, shortcut, groups, expansion super(C3, self).__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))

这个BottleneckCSP模块使用netron画出来非常大,放上来效果不太好,我就直接在网上扒图了。左边分支一就是使用了三个Bottleneck模块串联到一起,分支二就是输入经过一个conv卷积,然后两个分支cancat到一起,简单来说也是一个shortcut残差连接的思想。这个结构也是不改变输入尺寸的大小的。
目标检测|YOLOv5网络结构学习
文章图片

四、SPP模块 SPP模块非常经典了,从yolov3中开始使用到现在,yolo系列基本上都用到了。
class SPP(nn.Module): # Spatial pyramid pooling layer used in YOLOv3-SPP def __init__(self, c1, c2, k=(5, 9, 13)): super(SPP, self).__init__() c_ = c1 // 2# hidden channels self.cv1 = Conv(c1, c_, 1, 1) self.cv2 = Conv(c_ * (len(k) + 1), c2, 1, 1) self.m = nn.ModuleList([nn.MaxPool2d(kernel_size=x, stride=1, padding=x // 2) for x in k])def forward(self, x): x = self.cv1(x) return self.cv2(torch.cat([x] + [m(x) for m in self.m], 1))

【目标检测|YOLOv5网络结构学习】首先使用一个卷积使通道数减半1024->512,然后将输入经过三个不同尺寸大小的最大池化层,连同输入一起concat(四个部分512*4=2048),最后再经过一个卷积层使通道数减半2048->1024,所以SPP模块也是不改变输入尺寸大小的。
目标检测|YOLOv5网络结构学习
文章图片

五、整体架构 yolov5中所有网络结构相关的定义都在common.py文件里面,通过执行yolo.py文件可以查看整个网络结构(修改网络结构的话在对应的yaml里面,之后可能会出一个修改yolov5网络架构的文章,敬请期待~)
目标检测|YOLOv5网络结构学习
文章图片

网上关于yolov5的网络结构图太多了,但是我觉得都不太好,不适合新手理解。在Github看评论的时候,我看到不少大佬画了一些yolov5的结构图,感觉很不错,就都拿过来了。我一直看的都是第一个图,感觉这个画的比较详细且易懂(可能是因为很像yolov4,之前看yolov4看习惯了~)
目标检测|YOLOv5网络结构学习
文章图片

目标检测|YOLOv5网络结构学习
文章图片

目标检测|YOLOv5网络结构学习
文章图片

六、Write in the end 最后讲一讲自己对yolov5模型的看法吧。众所周知,yolo系列的作者从v3之后就退出CV界了,随后的yolov4、yolov5等模型都不再是原作者进行开发的了。yolov5作为一个工程性的项目,代码里面包含了大量工程上使用到的技巧,对于初学者甚至有一定python/pytorch基础的人来阅读或者修改代码都是比较困难的,因为改代码的时候很可能会导致一些未知的错误。但是你单纯地使用,训练/测试自己的数据集还是非常方便的,而且用过yolov5的人反响都还不错,最后贴上一张网上的评论吧~
目标检测|YOLOv5网络结构学习
文章图片

参考网址:
[1] 深度学习之Focus层
[2] yolov5中的Focus模块的理解
[3] yolov5网络结构梳理详解
[4]Github中各位大佬画的网络结构图

    推荐阅读