• [纯理论] YOLOv5


    YOLOv5

    作者: ultralytics

    论文源码: https://github.com/ultralytics/yolov5

    0. 引言

    YOLOv5 🚀 is a family of object detection architectures and models pretrained on the COCO dataset, and represents Ultralytics open-source research into future vision AI methods, incorporating lessons learned and best practices evolved over thousands of hours of research and development.

    YOLOv5🚀是一个在COCO数据集上预训练的物体检测架构和模型系列,它代表了Ultralytics对未来视觉AI方法的开源研究,包含了在数千小时的研究和开发中所获得的经验教训和最佳实践。


    YOLOv5仓库是在2020-05-18创建的,到今天已经迭代了很多个大版本了,现在已经迭代到v6.1了。下表是当前(v6.1)官网贴出的关于不同大小模型以及输入尺度对应的mAP、推理速度、参数数量以及理论计算量FLOPs。

    Modelsize(pixels)mAPval0.5:0.95mAPval0.5SpeedCPU b1(ms)SpeedV100 b1(ms)SpeedV100 b32(ms)params(M)FLOPs@640 (B)
    YOLOv5n64028.045.7456.30.61.94.5
    YOLOv5s64037.456.8986.40.97.216.5
    YOLOv5m64045.464.12248.21.721.249.0
    YOLOv5l64049.067.343010.12.746.5109.1
    YOLOv5x64050.768.976612.14.886.7205.7
    YOLOv5n6128036.054.41538.12.13.24.6
    YOLOv5s6128044.863.73858.23.612.616.8
    YOLOv5m6128051.369.388711.16.835.750.0
    YOLOv5l6128053.771.3178415.810.576.8111.4
    YOLOv5x6+ TTA1280153655.055.872.772.73136-26.2-19.4-140.7-209.8-

    1. 网络结构

    YOLOv5的网络结构主要由以下几部分组成:

    1. Backbone: New CSP-Darknet53
    2. Neck: SPPF, New CSP-PAN
    3. Head: YOLOv3 Head

    下面是霹雳吧啦Wz根据yolov5l.yaml绘制的网络整体结构,YOLOv5针对不同大小(n, s, m, l, x)的网络整体架构都是一样的,只不过会在每个子模块中采用不同的深度和宽度,分别应对yaml文件中的depth_multiplewidth_multiple参数。还需要注意一点,官方除了n, s, m, l, x版本外还有n6, s6, m6, l6, x6,区别在于后者是针对更大分辨率的图片比如1280x1280,当然结构上也有些差异,后者会下采样64倍,采用4个预测特征层,而前者只会下采样到32倍且采用3个预测特征层。本文只讨论前者。

    在这里插入图片描述

    1.1 Backbone

    通过和上篇博文讲的YOLOv4对比,其实YOLOv5在Backbone部分没太大变化。但是YOLOv5在v6.0版本后相比之前版本有一个很小的改动,把网络的第一层(原来是Focus模块)换成了一个6×6大小的卷积层。两者在理论上其实等价的,但是对于现有的一些GPU设备(以及相应的优化算法)使用6×6大小的卷积层比使用Focus模块更加高效。详情可以参考这个issue #4825 -> Is the Focus layer equivalent to a simple Conv layer?

    下图是原来的Focus模块(和之前Swin Transformer中的Patch Merging类似),将每个2×2的相邻像素划分为一个patch,然后将每个patch中相同位置(同一颜色)像素给拼在一起就得到了4个feature map,然后在接上一个3×3大小的卷积层。这和直接使用一个6×6大小的卷积层等效。

    在这里插入图片描述

    1.2 Neck

    YOLOv5在Neck部分的变化还是相对较大的,首先是将SPP换成成了SPPF(Spatial Pyramid Pooling - Fast, 是Glenn Jocher自己设计的),这个改动我个人觉得还是很有意思的,两者的作用是一样的,但后者效率更高

    1.2.1 SPP (Spatial Pyramid Pooling)

    SPP结构如下图所示,是将输入并行通过多个不同大小的MaxPool,然后做进一步融合,能在一定程度上解决目标多尺度问题。

    在这里插入图片描述

    每一个分支得到fmap的shape都一样, 最后会实现通道数×4

    1.2.2 SPPF (Spatial Pyramid Pooling Fast)

    而SPPF结构是将输入串行通过多个5×5大小的MaxPool层,这里需要注意的是串行两个5×5大小的MaxPool层是和一个9×9大小的MaxPool层计算结果是一样的,串行三个5×5大小的MaxPool层是和一个13×13大小的MaxPool层计算结果是一样的。

    在这里插入图片描述

    SPPF最后同样会实现通道数×4

    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)  # 先通过CBL进行通道数的减半
            with warnings.catch_warnings():
                warnings.simplefilter('ignore')  # suppress torch 1.9.0 max_pool2d() warning
                y1 = self.m(x)
                y2 = self.m(y1)
                # 上述两次最大池化
                # 将原来的x,一次池化后的y1,两次池化后的y2,3次池化的self.m(y2)先进行拼接,然后再CBL
                return self.cv2(torch.cat([x, y1, y2, self.m(y2)], 1))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    1.2.3 SPP和SPPF速度对比

    下面做个简单的小实验,对比下SPP和SPPF的计算结果以及速度,代码如下(注意这里将SPPF中最开始和结尾处的1×1卷积层给去掉了,只对比含有MaxPool的部分):

    import time
    import torch
    import torch.nn as nn
    
    
    class SPP(nn.Module):
        def __init__(self):
            super().__init__()
            self.maxpool1 = nn.MaxPool2d(5, 1, padding=2)
            self.maxpool2 = nn.MaxPool2d(9, 1, padding=4)
            self.maxpool3 = nn.MaxPool2d(13, 1, padding=6)
    
        def forward(self, x):
            o1 = self.maxpool1(x)
            o2 = self.maxpool2(x)
            o3 = self.maxpool3(x)
            return torch.cat([x, o1, o2, o3], dim=1)
    
    
    class SPPF(nn.Module):
        def __init__(self):
            super().__init__()
            self.maxpool = nn.MaxPool2d(5, 1, padding=2)
    
        def forward(self, x):
            o1 = self.maxpool(x)
            o2 = self.maxpool(o1)
            o3 = self.maxpool(o2)
            return torch.cat([x, o1, o2, o3], dim=1)
    
    
    def main():
        input_tensor = torch.rand(8, 32, 16, 16)
        spp = SPP()
        sppf = SPPF()
        output1 = spp(input_tensor)
        output2 = sppf(input_tensor)
    
        print(torch.equal(output1, output2))
    
        t_start = time.time()
        for _ in range(100):
            spp(input_tensor)
        print(f"spp time: {time.time() - t_start}")
    
        t_start = time.time()
        for _ in range(100):
            sppf(input_tensor)
        print(f"sppf time: {time.time() - t_start}")
    
    
    if __name__ == '__main__':
        main()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53

    终端输出结果:

    True
    spp time: 0.6333663463592529
    sppf time: 0.2547760009765625
    
    • 1
    • 2
    • 3

    通过对比可以发现,两者的计算结果是一模一样的,但SPPF比SPP计算速度快了不止两倍,快乐翻倍。

    1.2.4 CSP-PAN

    在Neck部分另外一个不同点就是New CSP-PAN了,在YOLOv4中,Neck的PAN结构是没有引入CSP结构的,但在YOLOv5中作者在PAN结构中加入了CSP。详情见上面的网络结构图,每个C3模块里都含有CSP结构。

    CSP结构是在CSPNet(Cross Stage Partial Network)论文中提出的,CSPNet作者说在目标检测任务中使用CSP结构有如下好处:

    • Strengthening learning ability of a CNN: 增强CNN的学习能力
    • Removing computational bottlenecks: 移除计算瓶颈
    • Reducing memory costs: 减少MACs

    CSP的加入可以: 减少网络的计算量以及对显存的占用,同时保证网络的能力不变或者略微提升。CSP结构的思想参考原论文中绘制的CSPDenseNet,进入每个stage(一般在下采样后)先将数据划分成俩部分,如下图左图所示的Part1和Part2。

    在这里插入图片描述

    左边的图是CSPDenseNet(来源于CSPNet论文)。CSP结构会将网络分为两个部分。Part2分支首先会经过一系列的Block(这里是DenseBlock),最后经过Transition,得到输出后再与Part1上的输出进行Transition融合。

    1.3 Head

    在Head部分,YOLOv3, v4, v5都是一样的。

    2. 数据增强策略

    在YOLOv5代码里,关于数据增强策略还是挺多的,这里简单罗列部分方法:

    2.1 Mosaic

    将四张图片拼成一张图片。

    在这里插入图片描述

    2.2 Copy Paste

    将部分目标随机的粘贴到图片中,前提是数据要有segments数据才行,即每个目标的实例分割信息。下面是Copy paste原论文中的示意图。

    在这里插入图片描述

    可以理解为是升级版的Mosaic

    2.3 Random affine(Rotation, Scale, Translation and Shear)

    随机进行仿射变换,但根据配置文件里的超参数发现只使用了Scale (缩放)和Translation (平移)。

    在这里插入图片描述

    数据增强中的仿射变换:旋转,缩放,平移以及错切(shear)

    2.4 MixUp

    Mixup就是将两张图片按照一定的透明度融合在一起,具体有没有用不太清楚,毕竟没有论文,也没有消融实验。代码中只有较大的模型才使用到了MixUp,而且每次只有 10 % 10\% 10% 的概率会使用到。

    在这里插入图片描述

    2.5 Albumentations

    主要是做些滤波、直方图均衡化以及改变图片质量等等,我看代码里写的只有安装了albumentations包才会启用,但在项目的requirements.txt文件中albumentations包是被注释掉了的,所以默认不启用


    Albumentations官方文档: https://albumentations.readthedocs.io/en/latest/

    albumentations 是一个给予 OpenCV 的快速训练数据增强库,拥有非常简单且强大的可以用于多种任务(分割、检测)的接口,易于定制且添加其他框架非常方便。

    它可以对数据集进行逐像素的转换,如:

    1. 模糊
    2. 下采样
    3. 高斯造点
    4. 高斯模糊
    5. 动态模糊
    6. RGB转换
    7. 随机雾化

    也可以进行空间转换(同时也会对目标进行转换),如:

    1. 裁剪
    2. 翻转
    3. 随机裁剪等。

    github及其示例地址如下:

    GitHub: https://github.com/albumentations-team/albumentations
    示例:https://github.com/albumentations-team/albumentations_examples

    参考: https://zhuanlan.zhihu.com/p/107399127/

    2.6 Augment HSV(Hue, Saturation, Value)

    随机调整色度,饱和度以及明度。

    在这里插入图片描述

    2.7 Random horizontal flip,随机水平翻转

    在这里插入图片描述

    3. 训练策略

    在YOLOv5源码中使用到了很多训练的策略,这里简单总结几个:

    1. Multi-scale training(0.5~1.5x)多尺度训练: 假设设置输入图片的大小为 640 × 640 640 \times 640 640×640,训练时采用尺寸是在 0.5 × 640 ∼ 1.5 × 640 0.5 \times 640 \sim 1.5 \times 640 0.5×6401.5×640 之间随机取值,注意取值时取得都是32的整数倍(因为网络会最大下采样32倍)。
    2. AutoAnchor(For training custom data): 训练自己数据集时可以根据自己数据集里的目标进行重新聚类生成Anchors模板。
    3. Warmup and Cosine LR scheduler: 训练前先进行Warmup热身,然后在采用Cosine学习率下降策略。
    4. EMA(Exponential Moving Average): 可以理解为给训练的参数加了一个动量,让它更新过程更加平滑。
    5. Mixed precision: 混合精度训练,能够减少显存的占用并且加快训练速度,前提是GPU硬件支持。
    6. Evolve hyper-parameters超参数优化: 没有炼丹经验的人勿碰,保持默认就好。

    4. 其他

    4.1 损失计算

    YOLOv5的损失主要由三个部分组成:

    1. Classes loss,分类损失: 采用的是BCE loss,注意只计算正样本的分类损失。
    2. Objectness loss,obj损失(置信度损失): 采用的依然是BCE loss,注意这里的obj指的是网络预测的目标边界框与GT Box的CIoU。这里计算的是font color=‘red’>所有样本的obj损失。
    3. Location loss,定位损失: 采用的是CIoU loss,注意只计算正样本的定位损失。

    L a l l = λ 1 L c l s + λ 2 L o b j + λ 3 L l o c {\mathcal L}_{all} = \lambda_1 {\mathcal L}_{\rm cls} + \lambda_2 {\mathcal L}_{\rm obj} + \lambda_3 {\mathcal L}_{\rm loc} Lall=λ1Lcls+λ2Lobj+λ3Lloc

    其中, λ 1 , λ 2 , λ 3 \lambda_1, \lambda_2, \lambda_3 λ1,λ2,λ3 为平衡系数。

    4.2 平衡不同尺度的损失

    这里是指针对三个预测特征层(P3, P4, P5)上的obj损失采用不同的权重。在源码中,针对预测小目标的预测特征层(P3)采用的权重是4.0,针对预测中等目标的预测特征层(P4)采用的权重是1.0,针对预测大目标的预测特征层(P5)采用的权重是0.4,作者说这是针对COCO数据集设置的超参数。

    L o b j = 4.0 ⋅ L o b j s m a l l + 1.0 ⋅ L o b j m e d i u m + 0.4 ⋅ L l a r g e s m a l l {\mathcal L}_{\rm obj} = 4.0 \cdot {\mathcal L}^{\rm small}_{\rm obj} + 1.0 \cdot {\mathcal L}^{\rm medium}_{\rm obj} + 0.4 \cdot {\mathcal L}^{\rm small}_{\rm large} Lobj=4.0Lobjsmall+1.0Lobjmedium+0.4Llargesmall

    4.3 消除Grid敏感度 (Eliminating grid sensitivity)

    在YOLOv4中有提到过,主要是调整预测目标中心点相对Grid网格的左上角偏移量。下图是YOLOv2,v3的计算公式。

    在这里插入图片描述

    其中:

    • t x t_x tx 是网络预测的目标中心 x x x 坐标偏移量(相对于网格的左上角)
    • t y t_y ty 是网络预测的目标中心 y y y 坐标偏移量(相对于网格的左上角)
    • c x c_x cx 是对应网格左上角的 x x x 坐标
    • c y c_y cy 是对应网格左上角的 y y y 坐标
    • σ \sigma σ 是sigmoid激活函数,将预测的偏移量限制在0到1之间,即预测的中心点不会超出对应的Grid Cell区域

    关于预测目标中心点相对Grid网格左上角 ( c x , c y ) (c_x, c_y) (cx,cy) 偏移量为 σ ( t x ) , σ ( t y ) \sigma(t_x), \sigma(t_y) σ(tx),σ(ty)。YOLOv4的作者认为这样做不太合理,比如当真实目标中心点非常靠近网格的左上角点 σ ( t x ) \sigma(t_x) σ(tx) σ ( t y ) \sigma(t_y) σ(ty) 应该趋近与0)或者右下角点( σ ( t x ) \sigma(t_x) σ(tx) σ ( t y ) \sigma(t_y) σ(ty) 应该趋近与1)时,网络的预测值需要负无穷或者正无穷时才能取到,而这种很极端的值网络一般无法达到。

    为了解决这个问题,作者对偏移量进行了缩放从原来的 ( 0 , 1 ) (0, 1) (0,1) 缩放到 ( − 0.5 , 1.5 ) (-0.5, 1.5) (0.5,1.5) 这样网络预测的偏移量就能很方便达到0或1,故最终预测的目标中心点 b x , b y b_x, b_y bx,by 的计算公式为:

    b x = ( 2 ⋅ σ ( t x ) − 0.5 ) + c x b y = ( 2 ⋅ σ ( t y ) − 0.5 ) + c y b_x = \left( 2 \cdot \sigma(t_x) - 0.5 \right) + c_x \\ b_y = \left( 2 \cdot \sigma(t_y) - 0.5 \right) + c_y bx=(2σ(tx)0.5)+cxby=(2σ(ty)0.5)+cy

    下面是霹雳吧啦Wz绘制的 y = σ ( x ) y = \sigma(x) y=σ(x) 对应sigma曲线和 y = 2 ⋅ σ ( x ) − 0.5 y = 2 \cdot \sigma(x) - 0.5 y=2σ(x)0.5 对应scale曲线。

    在这里插入图片描述

    很明显通过引入缩放系数scale以后, x x x 在同样的区间内, y y y 的取值范围更大,或者说 y y y x x x 更敏感了。并且偏移的范围由原来 ( 0 , 1 ) (0, 1) (0,1) 调整到了 ( − 0.5 , 1.5 ) (-0.5, 1.5) (0.5,1.5)

    b w , b h b_w, b_h bw,bh 保持YOLOv3的策略不变。


    在YOLOv5中除了调整预测Anchor相对Grid网格左上角 ( c x , c y ) (c_x, c_y) (cx,cy) 偏移量以外,还调整了预测目标高宽的计算公式,之前是:

    b w = p w ⋅ e t w b h = p h ⋅ e t h b_w = p_w \cdot e^{t_w} \\ b_h = p_h \cdot e^{t_h} bw=pwetwbh=pheth

    在YOLOv5中被作者调整为:

    b w = p w ⋅ ( 2 ⋅ σ ( t w ) ) 2 b h = p h ⋅ ( 2 ⋅ σ ( t h ) ) 2 b_w = p_w \cdot (2 \cdot \sigma(t_w))^2 \\ b_h = p_h \cdot (2 \cdot \sigma(t_h))^2 bw=pw(2σ(tw))2bh=ph(2σ(th))2

    作者Glenn Jocher对此修改的原话如下,也可以参考issue #471

    The original yolo/darknet box equations have a serious flaw. Width and Height are completely unbounded as they are simply out=exp(in), which is dangerous, as it can lead to runaway gradients, instabilities, NaN losses and ultimately a complete loss of training.

    原始的yolo/darknet盒子方程有一个严重的缺陷。宽度和高度是完全无界的,因为它们只是out=exp(in),这很危险,因为它可能导致梯度失控、不稳定、NaN损失,最终完全失去训练。

    作者的大致意思是,原来的计算公式并没有对预测目标宽高做限制,这样可能出现梯度爆炸,训练不稳定等问题。下图是修改前 y = e x y = e^x y=ex 和修改后 y = ( 2 ⋅ σ ( x ) ) 2 y = (2 \cdot \sigma(x))^2 y=(2σ(x))2 (相对Anchor宽高的倍率因子)的变化曲线, 很明显调整后倍率因子被限制在 ( 0 , 4 ) (0, 4) (0,4) 之间。

    在这里插入图片描述

    4.4 匹配张样本 (Build Targets)

    之前在YOLOv4介绍中有讲过该部分内容,其实YOLOv5也差不多。主要的区别在于GT Box与Anchor Templates模板的匹配方式。在YOLOv4中是直接将每个GT Box与对应的Anchor Templates模板计算IoU,只要IoU大于设定的阈值就算匹配成功。但在YOLOv5中,作者先去计算每个GT Box与对应的Anchor Templates模板的高宽比例,即:

    r w = w g t w a t r h = h g t h w a t r_w = \frac{w_{gt}}{w_{at}} \\ r_h = \frac{h_{gt}}{hw_{at}} rw=watwgtrh=hwathgt

    然后统计这些比例和它们倒数之间的最大值,这里可以理解成计算GT Box和Anchor Templates分别在宽度以及高度方向的最大差异(当相等的时候比例为1,差异最小):

    r w max ⁡ = max ⁡ ( r w , 1 r w ) r h max ⁡ = max ⁡ ( r h , 1 r h ) r^{\max}_w = \max(r_w, \frac{1}{r_w}) \\ r^{\max}_h = \max(r_h, \frac{1}{r_h}) rwmax=max(rw,rw1)rhmax=max(rh,rh1)

    接着统计 r w max ⁡ r_w^{\max} rwmax r h max ⁡ r_h^{\max} rhmax 之间的最大值,即宽度和高度方向差异最大的值:

    r max ⁡ = max ⁡ ( r w max ⁡ , r h max ⁡ ) r^{\max} = \max(r_w^{\max}, r_h^{\max}) rmax=max(rwmax,rhmax)

    如果GT Box和对应的Anchor Template的 r m a x r^{max} rmax 小于阈值anchor_t(在源码中默认设置为4.0),即GT Box和对应的Anchor Template的高、宽比例相差不算太大,则将GT Box分配给该Anchor Template模板。为了方便大家理解,可以看下图。

    在这里插入图片描述

    假设对某个GT Box而言,其实只要GT Box满足在某个Anchor Template宽和高的 × 0.25 \times 0.25 ×0.25 倍和 × 4.0 \times 4.0 ×4.0 倍之间就算匹配成功。

    剩下的步骤和YOLOv4中一致:

    将GT投影到对应预测特征层上,根据GT的中心点定位到对应Cell,注意图中有三个对应的Cell。因为网络预测中心点的偏移范围已经调整到了 ( − 0.5 , 1.5 ) (-0.5, 1.5) (0.5,1.5),所以按理说只要Grid Cell左上角点距离GT中心点在 ( − 0.5 , 1.5 ) (−0.5,1.5) (0.5,1.5) 范围内它们对应的Anchor都能回归到GT的位置处。这样会让正样本的数量得到大量的扩充。

    则这三个Cell对应的AT2和AT3都为正样本。

    在这里插入图片描述

    还需要注意的是,YOLOv5源码中扩展Cell时只会往上、下、左、右四个方向扩展,不会往左上、右上、左下、右下方向扩展。

    下面又给出了一些根据 G T x c e n t e r GT_x^{center} GTxcenter, G T y c e n t e r GT_y^{center} GTycenter 的位置扩展的一些Cell案例,其中%1表示取余并保留小数部分。

    在这里插入图片描述

    到此,YOLOv5相关理论的内容基本上都分析完了。

  • 相关阅读:
    发布版本自动化记录版本功能方法
    安全狗陈奋:数据安全需要建立在传统网络安全基础之上
    OpenCV自学笔记十六:直方图处理
    NLP 开源形近字算法之相似字列表(番外篇)
    OpenGLES:绘制一个混色旋转的3D圆锥
    [汇编语言实验]查看CPU和内存,用机器指令和汇编指令编程
    基于java雪花算法生成long类型无序ID实现
    仿游戏热血江湖游戏类22(得到物品基本攻击力2)
    【已解决】Qt发送信号后,槽函数没有响应
    密码学和Java加密与解密技术
  • 原文地址:https://blog.csdn.net/weixin_44878336/article/details/125994983