前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >YOLO系列介绍(二)

YOLO系列介绍(二)

作者头像
算法之名
发布2022-05-06 10:27:32
9990
发布2022-05-06 10:27:32
举报
文章被收录于专栏:算法之名算法之名

YOLO系列介绍

YOLOV4

上图是YOLOV4的网络结构。

通过上图,我们可以看到V4比V3无论在准确率上还是检测速度上都有了一个很大的提升,在准确率上提升了10%,在速度上提升了12%。

YOLOV4并不是YOLO系列原作者写的,是Alexey Bochkovskiy发表于2020年的CVPR中,并且被原作者认可。相比于YOLOV3,V4使用了CSPDarknet53的网络结构

CSP结构是在CSPDenseNet中被提出。它能够增强CNN学习的能力;移除了计算瓶颈;降低显存的使用。使用CSP结构后能够加快网络的推理速度。

在上图中可以看到,对于输入的特征层会分成两部分——Part1和Part2。对于Part2分支是通过一系列的Dense Block,再通过一个Transition与Part1融合,有关DenseNet的内容可以参考Tensorflow深度学习算法整理 中的DenseNet。在标准的CSPDenseNet中,Part1和Part2是按照通道来进行一个均分操作,但是在YOLOV4中并不是如此分割的。在YOLOV4中是将特征层经过两个1*1的卷积层,它们的通道数都是原特征层通道数的一半。其中的一个分支经过了一系列的Res Block后再通过一个1*1的卷积层,再与另一个分支进行拼接后再通过一个1*1的卷积层来最后的输出。

我们来看一下它的BackBone(主干网络)这一块的网络结构

这个DownSample1跟上面提到的CSP结构略有不同,就是在两个分支中,它们的通道数没有减半,而是保持了特征层相同的通道数。与YOLOV3不同,V4的每一个卷积层使用的激活函数不再是LeakRelu而是Mish激活函数。在DownSample1中的Res Block也不是一个标准的Res Block,而是一个类似的结构,先通过一个1*1的卷积层变成32通道,再经过一个3*3的卷积核变回64通道再做残差连接。

Mish激活函数的公示为

图像如下

从图中可以看出它在负值的时候并不是完全截断,而是允许比较小的负梯度流入,从而保证信息流动。

DownSample2到后面的DownSample5就跟之前所说的CSP的结构是一样的了。

图像特征经过了主干网络之后会经过一个SPP(Spatial Pyramid Pooling)的网络结构作为最后的输出到Neck中

对于输入的特征层,它会依次通过一个5*5、9*9、13*13的最大池化层,它们的步距都为1,通过不同的padding之后,它们输出的大小和输入的特征层大小是相同的,再将这三个池化层的输出以及原输入进行cancat拼接。通过SPP结构,它能在一定程度上解决多尺度的问题。

现在我们来看一下YOLOV4的Neck(使用BackBone提取的特征)部分

在上图的a的右半部分以及b的部分合称Neck,它使用的是PAN(Path Aggregation NetWork)的网络结构。在YOLOV3中没有b的部分,只有对主干网络从深层向浅层进行连接,分层提取3种形状的单元格的坐标、置信度以及分类信息,这一部分我们称为FPN。在YOLOV4中,b的部分刚好与之相反,从浅层向深层进行连接并提取。

这里需要注意的是,在原始的PAN网络中关于特征层与特征层融合的部分是采用相加的策略,但是在YOLOV4中是采用concat的策略,将两个特征层在深度方向进行拼接。

我们再来看一下YOLOV4的整体结构

在上图中,一个416*416*3的彩色图像经过CSPDarknet53的主干网络,首先会通过一个ConvSet1的网络结构,该结构是一个1*1、3*3、1*1的三层卷积核的网络结构,再通过一个SPP结构,再通过一个ConvSet2的网络结构,它的结构和ConvSet1是一样的。通过ConvSet2后就得到了一个13*13*512的feature map。此时我们接上一个1*1的卷积层,通过Upsample1上采样将feature map的高和宽翻倍,从13*13变成26*26。此时我们将DownSample4的输出通过一个1*1的卷积层调整它的通道,然后再和Upsample1的输出进行concat拼接。再通过ConvSSet3得到一个26*26*256的feature map。ConvSet3是5个卷积层堆叠得到的。这里需要注意的是在CSPDarknet53中的激活函数为Mish,但是在搭建PAN模块中所采用的激活函数都是LeakReLu。通过ConvSet3经过一个1*1的卷积层再通过Upsample2将feature map的尺寸再翻倍变成52*52.此时将DownSample3的输出通过一个1*1的卷积层调整通道数然后再与Upsample2的输出进行一个cancat拼接再接上一个ConvSet4,ConvSet4与ConvSet3的结构是一样的,得到一个52*52*128的feature map。将该feature map通过一个3*3的卷积层和1*1的卷积层得到一个最后的输出,没有bn以及激活函数,它的通道数为(4+1+numclass)*3。然后将该52*52*128的feature map通过一个3*3的卷积层,步长为2的卷积层进行下采样变回26*26*256的featue map再和ConvSet3产生的26*26*256的feature map进行cancat的拼接,再通过一个ConvSet5,再经过一个3*3、1*1的卷积层得到一个26*26*256尺寸的预测输出。再将ConvSet5的输出经过一个3*3,步长为2的卷积层进行下采样,将尺寸从26*26变成13*13,再与ConvSet2所产生的13*13*512的featue map进行一个cancat的拼接,经过ConvSet6,经过3*3、1*1的卷积层,得到一个预测输出。

YOLOV4和YOLOV3最大的不同就是V3是只有主干网络+FPN就进行三层的输出,没有V4的反卷积过程。V4的FPN会与反卷积的各层进行融合,然后再进行输出。当然最原始的YOLOV3也没有SPP网络,但是YOLOV3 SPP版本是有的。

优化策略

首先就是消除Grid网格的敏感程度。在YOLOV3中,我们都是通过1*1的卷积层来预测,每滑动到一个单元格中,就会预测当前的单元格中,它所对应的3种Anchor的一系列输出值。假设当这个1*1的卷积核滑动到上图的最中间的单元格的时候,它会针对每一个Anchor回归出相应的目标边界框回归参数、置信度以及类别的score分数。针对每个Anchor,它的回归参数有tx、ty、tw和th。上图中bx是预测目标边界框(上图中的蓝色框)的中心点的x坐标,它的计算方法就是将tx传入sigmoid函数

,再加上cx(当前单元格左上角的x坐标)。by就是目标边界框中心点的y坐标,它的计算方法就是将ty传入sigmoid函数再加上cy(当前单元格左上角的y坐标)。bw是目标边界框宽度,它的计算方式是将pw(Anchoor边框的宽度,上图中的虚线框)乘以e^tw;bn是目标边界框高度,它的计算方式是将ph(Anchoor边框的高度)乘以e^th。由于sigmoid函数是在0~1之间,所以目标边界框的中心点是被限制在当前的单元格内部,这样就会产生一个问题,比如groun truth边框的中心点落在当前的单元格的边界的时候,更极端一点是在当前单元格的左上角的时候,此时我们希望σ(tx)和σ(ty)都为0,但是对sigmoid函数来看,只有当x->-∞时才能取到0

对于这种极端的数值,我们的网络一般是无法达到的,为了解决这个问题,YOLOV4的作者引入了一个缩放因子scale,这样目标边界框中心点的坐标公式就变为

一般我们会把scale设成2,那么上面的式子就变成

这样我们可以对该函数和sigmoid函数进行一个函数图像上的对比

通过上图,我们可以看到,在相同的x缩放范围之内,包含scale的式子能取到y的范围更广,故y对x变的更加的敏感,并且值域由原来的0~1变成了-0.5~1.5之间。所以改变了目标边界框中心点的坐标公式之后,我们的预测值也就可以到达0和1这些点了,就解决了这个问题了。

Mosaic data augmentation,将四张不同的图片按照一定的规则给拼接到一起,拼接好之后得到一张新的图片,能够扩张训练的样本多样性。

IoU threshold(match posotive samples),匹配正样本所用的阈值。在YOLOV3中,对于每一个ground truth(浅蓝色块),去和每一个anchor(黄色块)进行一个匹配(match)。由于每一输出特征层有3种Anchor,我们会将同一个ground truth与这三种Anchor的左上角对齐去计算一个IoU。譬如上图中,ground truth与第一种Anchor的IoU为0.25,与第二种Anchor的IoU为0.7,与第三种Anchor的IoU为0.2。假设我们设置的IoU阈值为0.3,那么满足要求的就是第二种Anchor——AT2,那么我们就会采用Anchor 2作为正样本。首先我们会将ground truth给缩放到feature map上,上图中,我们可以看到该ground truth的中心点在正中间的单元格内,那么这个单元格的Anchor 2就会作为正样本。如果有多个Anchor的IoU都大于0.3,那么这多个Anchor都会作为正样本。当然在原始的YOLOV3中,作者只会给一个Anchor赋予正样本,那么这样就会导致正样本数量过少,不如前面的方法。

在YOLOV4中

我们不仅会把中间的单元格的Anchor 2作为正样本,还会把该单元格上面的单元格和左边的单元格的Anchor 2都作为正样本,之所以能够这么做是因为YOLOV4的目标边界框中心点的坐标公式发生了改变,它的取值范围从0~1扩展到了-0.5~1.5之间了。对于当前单元格上面的单元格,它左上角的坐标到该中心点从x轴上看是小于0.5的,从y轴上看是小于1.5的,所以该中心点满足-0.5到1.5的范围内。对于当前单元格左边的单元格,它左上角的坐标到该中心点在x轴上是小于1.5的,在y轴上是小于0.5的,所以这个ground truth中心到左边单元格的范围也是在-0.5~1.5之间。通过这样的策略,它又能扩充正样本的数量。

上图是更多的情况。在第一幅图中,当ground truth的中心点在当前单元格的左上角的话,我们会取上面的左边的单元格的Anchor 2作为正样本;当ground truth的中心点在当前单元格的右下角的话,我们会取下面的右边的单元格的Anchor 2作为正样本;当ground truth的中心点在当前单元格的右上角的话,我们会取上面的右边的单元格的Anchor 2作为正样本;当ground truth的中心点在当前单元格的左下角的话,我们会取下面的左边的单元格的Anchor 2作为正样本;当ground truth的中心点刚好落在当前单元格的中心点的时候,我们只会利用当前的单元格的Anchor 2作为正样本。

Optimizered Anchors,在YOLOV3中,针对不同尺度都提供了不同的Anchor尺寸,它这个尺寸是通过聚类得到的。在YOLOV4中,作者针对512*512的尺寸又重新优化了Anchor。

YOLOV5

YOLOV5的项目作者是Glenn Jocher,也不是YOLO的原作者,并且YOLOV5没有发表论文,只是一个工程实现。YOLOV5的网络结构如下

  • Backbone: New CSP-Darknet53
  • Neck: SPPFNew CSP-PAN
  • Head: YOLOv3 Head

总体来说,V5和V4差不多,YOLOv5针对不同大小(n, s, m, l, x)的网络整体架构都是一样的,只不过会在每个子模块中采用不同的深度和宽度,分别应对yaml文件中的depth_multiple和width_multiple参数。

yolov5s.yaml

代码语言:javascript
复制
depth_multiple: 0.33  # model depth multiple
width_multiple: 0.50  # layer channel multiple

还需要注意一点,官方除了n, s, m, l, x版本外还有n6, s6, m6, l6, x6,区别在于后者是针对更大分辨率的图片比如1280x1280,当然结构上也有些差异,后者会下采样64倍,采用4个预测特征层,而前者只会下采样到32倍且采用3个预测特征层。YOLOV5的整体结构图如下

在YOLOV5的backbone卷积层中使用的是分组卷积,它的激活函数由V4的Mish改成了SiLU。

代码语言:javascript
复制
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)
        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))

分组卷积主要是减少参数降低计算量用,其计算逻辑大概为假设输入的通道数为12,输出通道数为6,卷积核为3*3,分组group=3,bias=Fasle。

传统卷积每个卷积核大小为3*3*12,总参数量为6*3*3*12;分组后每个通道的输入为12/3=4,每个group的输出通道为6/3=2,每个group里面卷积核的大小为3*3*4,总的参数量为6*3*3*4。可以看出分组后参数量为传统卷积的1/group。不过在最新的YOLOV5 6.0中没有做这个分组。当我们跑一些大型模型的时候,当我们的GPU比较多,可以把卷积层的不同通道放到不同的GPU上跑的时候,就可以采用这种分组卷积。另外就是当我们只有一个GPU,而且显存不太够的情况下,为了减少参数量,也可以使用这种分组卷积。

SiLu的激活函数跟Mish是很像的,公式为

图像为

另外YOLOV5的backbone依然是一个CSP结构的,这跟YOLOV4是一样的。

代码语言:javascript
复制
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 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().__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.SiLU()
        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), 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().__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)  # optional 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)), 1))

但是这里需要注意的是在YOLOV5 6.0版本之前一直使用的是BottleneckCSP结构,但是到了6.0的时候改成了C3结构,这两个结构都是CSP结构,它们的不同之处就在于C3比BottleneckCSP少了一个卷积层。而在中间的部分也不再是V4的Res Block,改成了Bottleneck,而Bottleneck也是一种类似的Res Block,就是把一层卷积改成了两层卷积。

这个是BottleneckCSP的网络结构,而红色虚线框的卷积层就是比C3多出来的部分,C3的网络结构如下

SPPF

在YOLOV4的SPP中是将输入的特征层并行输入各个大小的最大池化层,然后再去融合。但是在YOLOV5中,是将特征层串行的依次通过5*5的最大池化层,然后再将它们的输出以及特征层本身的输入进行concat的拼接。将两个5*5的最大池化层串行等同于一个9*9的最大池化层;而将三个5*5的最大池化层串行等同于一个13*13的最大池化层。但是采用SPPF的结构效率要更高,因为特征层在依次串行通过5*5的最大池化层的过程中,它比直接通过一个9*9或者13*13的最大池化层的计算量要小.

代码语言:javascript
复制
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))

除了SPPF和SPP的不同外,在PAN网络中,总体结构V5和V4也是相同的,但是V4的卷积层只是普通的带LeakyReLu激活函数的普通卷积层,而在V5中都调整成了带CiLu激活函数的C3结构,而C3结构也是CSP结构的,这是跟V4比较大的区别。

数据增强

Mosaic

这个跟V4是一样的,将4张图片给拼成一张图片,增加数据的多样性。

Copy paste

将不同图像中的目标给复制粘贴一下,将一张图像的目标实例分割给扣下来再放到其他图像中去。要使用这种数据增强必须在数据中有目标分割的标签,否则无法使用该增强方式。

Random affine(随机仿射变换)

这里的仿射变换指的是旋转、缩放、平移和错切。源码中只启用了随机的缩放和平移。在配置文件hyp.scratch-high.yaml中

代码语言:javascript
复制
degrees: 0.0  # image rotation (+/- deg)
translate: 0.1  # image translation (+/- fraction)
scale: 0.9  # image scale (+/- gain)
shear: 0.0  # image shear (+/- deg)

MixUp

将两张图片按一定透明程度混合成一张新的图片。这种数据增强的方式被启用的概率只有10%。

Augment HSV

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

Random horizontal flip

随机的水平翻转

rectangular

同个batch里做rectangle宽高等比变换,加快训练。在我们对图像进行训练的时候,由于每张图片的宽高都可能不相同,通常我们会把图片给reshape到一个固定的大小。但是有一些图片的宽很窄,高很小,由此会产生两边的黑边就会很大。这种大黑边就会影响我们训练的速度。在YOLOV5中改进了该方法,在同一个batch中会尽可能的拥有自己单独的shape,满足这个batch中所有的图片都能有更小的黑边。

K-Means聚类Anchors

有关K-Means聚类的内容请参考聚类

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2022-05-05,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • YOLOV4
  • YOLOV5
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档