在之前的文章中,我们以YOLOv5为对象,详细解剖了一只麻雀的内部构造,包括anchor机制、backbone的结构、neck的结构和head的结构。在本篇文章中,我们将以YOLOv7v0.1版本的代码为目标,结合作者团队的YOLOv7原文,详细介绍一下其骨架网络的整体架构及各部分的实现原理,并结合网络配置文件yolov7.yaml以及common.py中网络组件进行细节剖析。
首先解读一下网络架构图。
1-P1/2;16-P3/8:这个是在画结构图过程中为了避免标错中间特征尺寸而做的标记。第一个数字代表当前模块的索引;Pn是表示当前模块下采样的次数,所以会看到有Pn出现的地方特征图尺寸就会改变;Pn之后的数字则是下采样的倍数
CBS:蓝色的CBS模块就是Conv+BatchNorm+SiLU的集成模块,主要负责进行特征提取。其参数主要是kernel尺寸和stride步长,kernel的尺寸不会影响输出特征图长宽的变化,因为在yolo中会使用autopid这个函数进行图像的自动填充( p = k / / 2 p=k//2 p=k//2);改变特征图大小的操作只有stride步长,具体而言 w o u t = w i n / s , h o u t = h i n / s w_out=w_in /s,h_out=h_in /s wout=win/s,hout=hin/s
ELAN1:YOLOv7的Backbone中特有的聚合网络,受DenseNet与ResNet启发而设计的模块,聚合过程中特征图的通道数和尺寸都不会发生改变。
MPConv:一种比较特别的池化结构,主要目的还是有效缩减特征图的尺寸,减少运算量和参数量,加快计算速度并防止过拟合。
总体来说,YOLOv7的backbone整体网络架构与v5的差别还是挺大的(集成度越来越高,同时也越来越麻烦,小白读起来越来越费劲),所以咱们还是按照老套路,将其中的模块拆解开来步步为营。
其实一个大厦无论多么辉煌瑰丽,都需要使用水泥和砖头一点一点堆砌,而CBS模块就是YOLOv7的水泥和砖头了,这里我直接把YOLOv5中对CBS的解释搬过来,并更新一些新的观点。
CBS模块其实没什么好稀奇的,就是Conv+BatchNorm+SiLU,这里着重讲一下Conv的参数,就当复习pytorch的卷积操作了,先上CBL源码:
class Conv(nn.Module):
# Standard convolution
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)
#其中nn.Identity()是网络中的占位符,并没有实际操作,在增减网络过程中,可以使得整个网络层数据不变,便于迁移权重数据;nn.SiLU()一种激活函数(S形加权线性单元)。
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))
由源码可知:Conv()包含7个参数,这些参数也是二维卷积Conv2d()中的重要参数。ch_in, ch_out, kernel, stride没什么好说的,展开说一下后三个参数:
从我现在看到的主流卷积操作来看,大多数的研究者不会通过kernel来改变特征图的尺寸,如googlenet中3x3的kernel设定了padding=1,所以当kernel≠1时需要对输入特征图进行填充。当指定p值时按照p值进行填充,当p值为默认时则通过autopad函数进行填充:
def autopad(k, p=None): # kernel, padding
# Pad to 'same'
if p is None:
p = k // 2 if isinstance(k, int) else [x // 2 for x in k] # auto-pad
#如果k是整数,p为k与2整除后向下取整;如果k是列表等,p对应的是列表中每个元素整除2。
return p
这里作者考虑到对不同的卷积操作使用不同大小的卷积核时padding也需要做出改变,所以这里在为p赋值时会首先检查k是否为int,如果k为列表则对列表中的每个元素整除。
决定是否对特征图进行激活操作,SiLU表示使用Sigmoid进行激活。
这部分结构其实并不复杂,但是为了读者对这部分结构有更深刻的认识,我还是打算沿着YOLOv7的原文中的描述,对其进行系统地介绍,首先让我们来看看作者在论文中提出的聚合模块的演变历程。
VoVNet是2019年的文献中提出的一种方法,对比了当时主要的聚合网络结构DenseNet和ResNet,并提出DenseNet的聚合方式要优于ResNet的结论。因为 DenseNet 利用 concat的方式相比于 ResNet利用sum 的方式,能够使网络特征图获得多个感知域(尺度)内的信息,对于下游的检测、分割任务是极为有用的。
其实Concat也是现在使用比较广泛、作用机制清晰、实现方便的一种信息聚合方式,在single-stream的多模态模型结构中,大多数情况下都是直接concat image embedding和text embedding,反而要比单独处理文本图像信息效果要好,其更底层的原理之前在知乎一个回答下看到过,如果有时间整理一下,这里先占个坑。
但是在实际应用中呢,作者发现DenseNet并没有理论上说的那么efficient,作者总结有以下几点原因:
因此VoVNet中中提出了One-Shot Aggregation的方法,如上图b所示,亦如本节最开始的(a)VoVNet所示。
其实图注里已经写的很清楚了,就是只在每一个module的最后阶段进行特征聚合,这样既保留了DenseNet中concat的优势,又能够优化对GPU的利用方式,缓解了MAC的问题。
再往后的PRN和CSPVoVNet等结构是从梯度优化的角度来考虑模型设计的,因为梯度得到优化之后,网络可以在更少参数量的基础上获得更稳定的表现。
CSP其实要基于PRN(short connection的过程中只选取部分channel,能够获得更丰富的梯度信息,取得更好的训练效果),PRN 证明了丰富的梯度对于训练更好的模型有帮助,以此推理,让所有的 channel 都接收不同的梯度信息能够让模型训练的最好,但是这样并不高效,因此兼顾性能要求,就进一步设计出 CSPNet:
CSPDenseNet 一方面可以和 DenseNet 一样可以获得更丰富的梯度信息,另一方面也通过 cross stage partial operation 操作避免了过多重复的梯度信息。
ELAN这篇文章是今年6月份左右刚挂到arxiv上的,比较新,我主要看了一下作者第三章的分析部分,谈一下自己的理解。
文章中首先指出DenseNet效果比ResNet好的原因就在于DenseNet使用的是相同时间戳下的不同梯度源,而非ResNet中相同时间戳下的相同输入源,并且认为文中提出的PRN结构相较于残差网络只选取部分通道进行残差链接,其实就相当于Dropout的机制(感兴趣的小伙伴可以看我之前写的dropout机制的那篇博客),也就增加了原始的梯度源信息,提高了网络的泛化性
其次,CSPNet仅使用了简单的通道分割、跨阶段连接和少量额外的过渡层,在不改变原有网络计算单元的情况下,成功地完成了预定的目标。也就是说,CSPNet通过channel split的手段,一方面增加了梯度源的信息,另一方面又减少了计算量,一举两得。
最后就是属于比较工程化的经验了:搭建网络使不仅要考虑最短的梯度路径,而且要保证每一层的最短梯度路径都能得到有效的训练。至于整个网络的最长梯度路径的长度,它将大于或等于任意一层的最长梯度路径的长度。因此,在实施网络级梯度路径设计策略时,需要考虑网络中所有层的最长最短梯度路径长度,以及整个网络的最长梯度路径长度。
说了这么多,其实最主要的一点就是ELAN这样的结构能够提升整个网络的鲁棒性、能够减少网络的参数量加速计算过程,所以ELAN用在这里最主要的一点是因为它足够高效,下面再看一下ELAN的代码实现,首先看一下ELAN的网络结构。
这个网络结构其实跟论文中的©ELAN含义是相同的,只不过画法不同而已,结合下面的ELAN网络配置进行解读:
# ELAN1
[-1, 1, Conv, [64, 1, 1]],# -6
[-2, 1, Conv, [64, 1, 1]],# -5
[-1, 1, Conv, [64, 3, 1]],
[-1, 1, Conv, [64, 3, 1]],# -3
[-1, 1, Conv, [64, 3, 1]],
[-1, 1, Conv, [64, 3, 1]],# -1
[[-1, -3, -5, -6], 1, Concat, [1]],
[-1, 1, Conv, [256, 1, 1]], # 11
首先看Concat这部分,可以确定最后是有4个特征图进行拼接的,其输入来源分别是从concat往前数第一个(-1)一直到从concat往前数第六个(-6),这部分参数如果不理解可以去YOLOv5Backbone详解看一下。确定了concat的特征图来源,再看其特征图尺寸。虽然这些特征图的kernel size不都相同,但是根据yolo的autopad机制,可以确定其特征图输出都跟输入的尺寸相同(只有s改变特征图尺寸),所以这里其实就是作不同深度不同语义上的信息融合,融合之后只有通道数变为原来四倍(64->256),也就是ELAN之前的128通道变成了ELAN模块之后的256通道。**结论:ELAN不改变图像的尺寸,只会将channel数double。**这部分看起来挺麻烦,但是讲代码的话其实没那么难理解,几句话就说完了。
先贴一下模块的网络结构图:
再贴一下模块的网络配置:
# MPConv
[-1, 1, MP, []],# maxpooling:k=2 s=2
[-1, 1, Conv, [128, 1, 1]],
[-3, 1, Conv, [128, 1, 1]],
[-1, 1, Conv, [128, 3, 2]],
[[-1, -3], 1, Concat, [1]], # 16-P3/8
还是先看concat,按图索骥,找到concat的两个输入,得出结论:MPConv模块不改变输入尺寸,只会增加channel数目,将其double
先写到这,等以后想起什么来再补充。
[1] Lee Y, Hwang J, Lee S, et al. An energy and GPU-computation efficient backbone network for real-time object detection[C]//Proceedings of the IEEE/CVF conference on computer vision and pattern recognition workshops. 2019: 0-0.
[2]He K, Zhang X, Ren S, et al. Deep residual learning for image recognition[C]//Proceedings of the IEEE conference on computer vision and pattern recognition. 2016: 770-778.
[3]Huang G, Liu Z, Van Der Maaten L, et al. Densely connected convolutional networks[C]//Proceedings of the IEEE conference on computer vision and pattern recognition. 2017: 4700-4708.
[4]Wang C Y, Mark Liao H Y, Chen P Y, et al. Enriching variety of layer-wise learning information by gradient combination[C]//Proceedings of the IEEE/CVF International Conference on Computer Vision Workshops. 2019: 0-0.
[5]Wang C Y, Liao H Y M, Wu Y H, et al. CSPNet: A new backbone that can enhance learning capability of CNN[C]//Proceedings of the IEEE/CVF conference on computer vision and pattern recognition workshops. 2020: 390-391.
[6]Wang C Y, Liao H Y M, Yeh I H. Designing Network Design Strategies Through Gradient Path Analysis[J]. arXiv preprint arXiv:2211.04800, 2022.
[7] https://blog.csdn.net/weixin_43427721/article/details/123613944?spm=1001.2014.3001.5501