YOLOv5:YOLO目标检测模型的第五次迭代.
YOLOv5是一个面向实时工业应用而开源的目标检测算法,其优异性在于开源库的实用和鲁棒性。YOLOv5开源库的主要特点为:
- 友好和完善的部署支持
- 算法训练速度极快,在 300 epoch 情况下训练时长和大部分算法在 12 epoch 的训练时间接近
- 框架进行了非常多的特殊情况(corner case)优化,功能和文档也比较丰富
YOLOv5 有 P5 和 P6 两个不同训练输入尺度的模型:P6 即为 1280x1280 输入的大模型;P5 是输入尺寸是 640x640的常规模型。本文主要介绍 P5 模型结构。
1. 数据增强模块
YOLOv5 目标检测算法中使用的数据增强比较多,包括:
- Mosaic 马赛克
- RandomAffine 随机仿射变换
- MixUp
- 图像模糊等采用 Albu 库实现的变换
- HSV 颜色空间增强
- 随机水平翻转
其中 Mosaic 数据增强概率为 1,表示一定会触发;而对于 small 和 nano 两个版本的模型不使用 MixUp,其他的 l/m/x 系列模型则采用了 0.1 的概率触发 MixUp。
其核心的 Mosaic + RandomAffine + MixUp 过程如下:
(1)Mosaic 马赛克
Mosaic 属于混合类数据增强,因为它在运行时候需要 4 张图片拼接,变相的相当于增加了训练的 batch size。其运行过程简要概况为:
- 随机生成拼接后 4 张图的交接中心点坐标,此时就相当于确定了 4 张拼接图片的交接点
- 随机选出另外 3 张图片的索引以及读取对应的标注
- 对每张图片采用保持宽高比的 resize 操作将其缩放到指定大小
- 按照上下左右规则,计算每张图片在待输出图片中应该放置的位置,因为图片可能出界故还需要计算裁剪坐标
- 利用裁剪坐标将缩放后的图片裁剪,然后贴到前面计算出的位置,其余位置全部补 114 像素值
- 对每张图片的标注也进行相应处理
由于拼接了 4 张图,所以输出图片面积会扩大 4 倍,从 640x640 变成 1280x1280,因此要想恢复为 640x640, 必须要再接一个 RandomAffine 随机仿射变换。
(2)RandomAffine 随机仿射变换
随机仿射变换有两个目的:
- 对图片进行随机几何仿射变换
- 将 Mosaic 输出的扩大 4 倍的图片还原为 640x640 尺寸
随机仿射变换包括平移、旋转、缩放、错切等几何增强操作,同时由于 Mosaic 和 RandomAffine 属于比较强的增强操作,会引入较大噪声,因此需要对增强后的标注进行处理,过滤规则为:
- 增强后的 gt bbox 宽高要大于 wh_thr
- 增强后的 gt bbox 面积和增强前的 gt bbox 面积比要大于 ar_thr,防止增强太严重
- 最大宽高比要小于 area_thr,防止宽高比改变太多
- 由于旋转后标注框会变大导致不准确,因此目标检测里面很少会使用旋转数据增强。
(3)MixUp
MixUp 和 Mosaic 类似也属于混合图片类增强方法。随机选出另外一张图后将两图再随机混合。具体实现方法有多种,常见的做法是要么将 label 直接拼接起来,要么将 label 也采用 alpha 方法混合。
YOLOv5 实现的 MixUp 对 label 直接拼接,而图片通过分布采样混合。随机出来的另一张图也需要经过 Mosaic 马赛克 + RandomAffine 随机仿射变换 的增强后才能混合。
2. 网络结构
YOLOv5 网络结构是标准的 CSPDarknet + PAFPN + 非解耦 Head。
YOLOv5 网络结构大小由 deepen_factor 和 widen_factor 两个参数决定。其中 deepen_factor 控制网络结构深度,即 CSPLayer 中 DarknetBottleneck 模块堆叠的数量;widen_factor 控制网络结构宽度,即模块输出特征图的通道数。以 YOLOv5-l 为例,其 deepen_factor = widen_factor = 1.0。
(1)backbone
CSPDarknet 整体结构和 ResNet 类似。P5 模型共 5 层结构,包含 1 个 Stem Layer 和 4 个 Stage Layer:
- Stem Layer 是 1 个 6x6 的 ConvModule。
- 除了最后一个 Stage Layer,其他均由 1 个 ConvModule 和 1 个 CSPLayer 组成。其中 ConvModule 为 3x3的 Conv2d + BatchNorm + SiLU 激活函数。CSPLayer 由 3 个 ConvModule + n 个 DarknetBottleneck(带残差连接) 组成。
- 最后一个 Stage Layer 在最后增加了 SPPF 模块。SPPF 模块是将输入串行通过多个 5x5 大小的 MaxPool2d 层,与 SPP 模块效果相同,但速度更快。
- P5 模型会在 Stage Layer 2-4 之后分别输出一个特征图进入 Neck 结构。以 640x640 输入图片为例,其输出特征为 (B,256,80,80)、(B,512,40,40) 和 (B,1024,20,20),对应的 stride 分别为 8/16/32。
- P6 模型会在 Stage Layer 2-5 之后分别输出一个特征图进入 Neck 结构。以 1280x1280 输入图片为例,其输出特征为 (B,256,160,160)、(B,512,80,80)、(B,768,40,40) 和 (B,1024,20,20),对应的 stride 分别为 8/16/32/64。
(2)Neck
YOLOv5 Neck采用PAFPN结构,Neck 模块输出的特征图和 Backbone 完全一致。即 P5 模型为 (B,256,80,80)、(B,512,40,40) 和 (B,1024,20,20);P6 模型为 (B,256,160,160)、(B,512,80,80)、(B,768,40,40) 和 (B,1024,20,20)。
(3)Head
YOLOv5 Head 结构和 YOLOv3 完全一样,为非解耦Head。Head 模块只包括 3 个不共享权重的卷积,用于将输入特征图进行变换。由于 YOLOv5 是非解耦输出,即分类和 bbox 检测等都是在同一个卷积的不同通道中完成。以 COCO 80 类为例:
- P5 模型在输入为 640x640 分辨率情况下,其 Head 模块输出的 shape 分别为 (B, 3x(4+1+80),80,80), (B, 3x(4+1+80),40,40) 和 (B, 3x(4+1+80),20,20)。
- P6 模型在输入为 1280x1280 分辨率情况下,其 Head 模块输出的 shape 分别为 (B, 3x(4+1+80),160,160), (B, 3x(4+1+80),80,80), (B, 3x(4+1+80),40,40) 和 (B, 3x(4+1+80),20,20)。
- 其中 3 表示 3 个 anchor,4 表示 bbox 预测分支,1 表示 obj 预测分支,80 表示 COCO 数据集类别预测分支。
3. 正负样本匹配策略
正负样本匹配策略的核心是确定预测特征图的所有位置中哪些位置应该是正样本,哪些是负样本,甚至有些是忽略样本。 匹配策略是目标检测算法的核心,一个好的匹配策略可以显著提升算法性能。
YOLOV5 的匹配策略简单总结为:采用了 anchor 和 gt_bbox 的形状匹配度作为划分规则,同时引入跨邻域网格策略来增加正样本。 其主要包括如下两个核心步骤:
- 对于任何一个输出层,抛弃了常用的基于 Max IoU 匹配的规则,而是直接采用形状规则匹配,也就是该 GT Bbox 和当前层的 Anchor 计算宽高比,如果宽高比例大于设定阈值,则说明该 GT Bbox 和 Anchor 匹配度不够,将该 GT Bbox 暂时丢掉,在该层预测中该 GT Bbox 对应的网格内的预测位置认为是负样本
- 对于匹配上的 GT Bbox,计算其落在哪个网格内,同时利用四舍五入规则,额外找出最近的两个网格,将这三个网格都认为是负责预测该 GT Bbox 的,可以粗略估计正样本数相比之前的 YOLO 系列,至少增加了三倍。
(1)Anchor设置
YOLOv5 是 Anchor-based 的目标检测算法,其 Anchor size 的获取方式与 YOLOv3 类似,也是使用聚类获得,其不同之处在于聚类使用的标准不再是基于 IoU 的,而是使用形状上的宽高比作为聚类准则(即 shape-match)。
默认 Anchor size:
anchors = [[(10, 13), (16, 30), (33, 23)], [(30, 61), (62, 45), (59, 119)],
[(116, 90), (156, 198), (373, 326)]]
(2)Bbox 编解码过程
在 Anchor-based 算法中,预测框通常会基于 Anchor 进行变换,然后预测变换量,这对应 GT Bbox 编码过程,而在预测后需要进行 Pred Bbox 解码,还原为真实尺度的 Bbox,这对应 Pred Bbox 解码过程。
在 YOLOv3 中,回归公式为:
\[\begin{aligned} b_x&=\sigma(t_x)+c_x \\ b_y&=\sigma(t_y)+c_y \\ b_w&=a_w\cdot e^{t_w} \\ b_h&=a_h\cdot e^{t_h} \\ \end{aligned}\]而在 YOLOv5 中,回归公式为:
\[\begin{aligned} b_x&=(2\cdot\sigma(t_x)-0.5)+c_x \\ b_y&=(2\cdot\sigma(t_y)-0.5)+c_y \\ b_w&=a_w\cdot(2\cdot\sigma(t_w))^2 \\ b_h&=a_h\cdot(2\cdot\sigma(t_h))^2 \end{aligned}\]改进之处主要有以下两点:
- 中心点坐标范围从 $(0, 1)$ 调整至 $(-0.5, 1.5)$
- 宽高范围从$(0,+\infty)$ 调整至 $(0,4a_{wh})$
这个改进具有以下好处:
- 新的中心点设置能更好的预测到 0 和 1。这有助于更精准回归出 box 坐标。
- 宽高回归公式中 $exp(x)$ 是无界的,这会导致梯度失去控制,造成训练不稳定。YOLOv5 中改进后的宽高回归公式优化了此问题。
(3)匹配策略
YOLOv5的正样本匹配策略采用“比例”比较,即将 GT Bbox 的 WH 与 Anchor 的 WH 分别进行比例比较,要求最大比例$r^{max}$不超过给定阈值才算成功匹配。
\[\begin{aligned} r_w &= w_{gt} / w_{pt} \\ r_h & = h_{gt} / h_{pt} \\ r_w^{max}&=\max(r_w, 1/r_w) \\ r_h^{max}&=\max(r_h, 1/r_h) \\ r^{max}&=\max(r_w^{max}, r_h^{max}) \\ \end{aligned}\]GT Bbox成功匹配之后,会将其相近的两个邻域位置也设置为正样本:
YOLOv5 的 Assign 方式带来了以下改进:
- 一个 GT Bbox 能够匹配多个 Anchor
- 一个 GT Bbox 和一个Anchor 匹配时,能分配 1-3 个正样本
- 以上策略能适度缓解目标检测中常见的正负样本不均衡问题。
4. 训练策略
(1)损失函数
YOLOv5 中总共包含 3 个 Loss,分别为:
- Classes loss:使用的是 BCE loss
- Objectness loss:使用的是 BCE loss
- Location loss:使用的是 CIoU loss
三个 loss 按照一定比例汇总:
\[Loss=\lambda_1L_{cls}+\lambda_2L_{obj}+\lambda_3L_{loc}\]P3、P4、P5 层对应的 Objectness loss 按照不同权重进行相加,默认的设置是:
\[L_{obj}=4.0\cdot L_{obj}^{small}+1.0\cdot L_{obj}^{medium}+0.4\cdot L_{obj}^{large}\](2)优化策略
将优化参数分成 Conv/Bias/BN 三组,在 WarmUp 阶段,不同组采用不同的学习率以及 momentum 更新曲线。 同时在 WarmUp 阶段采用的是 iter-based 更新策略,而在非 WarmUp 阶段则变成 epoch-based 更新策略。
针对不同的 batch size 采用了不同的 weight decay 策略,具体来说为:
- 当训练 batch size $<= 64$ 时,weight decay 不变
- 当训练 batch size $> 64$ 时,weight decay 会根据总 batch size 进行线性缩放
为了最大化不同 batch size 情况下的性能,设置总 batch size 小于 64 时候会自动开启梯度累加功能。
(3)推理过程
YOLOv5 输出特征图尺度为 80x80、40x40 和 20x20 的三个特征图,每个位置共 3 个 anchor,因此输出特征图通道为 $3\times(5+80)=255$。 YOLOv5 是非解耦输出头,因此首先提前将其进行解耦,分成了类别预测分支、bbox 预测分支和 obj 预测分支。
YOLOv5的推理过程如下:
- 将三个不同尺度的类别预测分支、bbox 预测分支和 obj 预测分支进行拼接,并进行维度变换。为了后续方便处理,会将原先的通道维度置换到最后,类别预测分支、bbox 预测分支和 obj 预测分支的形状分别为 (b, 3x80x80+3x40x40+3x20x20, 80)=(b,25200,80),(b,25200,4),(b,25200,1)。
- 分类预测分支和 obj 分支需要进行 sigmoid 计算,而 bbox 预测分支需要进行解码,还原为真实的原图解码后 xyxy 格式。
- 遍历 batch 中的每张图,然后用 score_thr 对类别预测分值进行阈值过滤,去掉低于 score_thr 的预测结果
- 将 obj 预测分值和过滤后的类别预测分值相乘,然后依然采用 score_thr 进行阈值过滤,确保过滤后的检测框数目不会多于 nms_pre。
- 基于前处理过程,将剩下的检测框还原到网络输出前的原图尺度,然后进行 nms 即可。最终输出的检测框不能多于 max_per_img。
(4)batch shape 策略
为了加速验证集的推理过程,作者提出了 batch shape 策略,其核心原则是:确保在 batch 推理过程中同一个 batch 内的图片 pad 像素最少,不要求整个验证过程中所有 batch 的图片尺度一样。
其大概流程是:将整个测试或者验证数据的宽高比进行排序,然后依据 batch 设置将排序后的图片组成一个 batch, 同时计算这个 batch 内最佳的 batch shape,防止 pad 像素过多。最佳 batch shape 计算原则为在保持宽高比的情况下进行 pad,不追求正方形图片输出。
image_shapes = []
for data_info in data_list:
image_shapes.append((data_info['width'], data_info['height']))
image_shapes = np.array(image_shapes, dtype=np.float64)
n = len(image_shapes) # number of images
batch_index = np.floor(np.arange(n) / self.batch_size).astype(
np.int64) # batch index
number_of_batches = batch_index[-1] + 1 # number of batches
aspect_ratio = image_shapes[:, 1] / image_shapes[:, 0] # aspect ratio
irect = aspect_ratio.argsort()
data_list = [data_list[i] for i in irect]
aspect_ratio = aspect_ratio[irect]
# Set training image shapes
shapes = [[1, 1]] * number_of_batches
for i in range(number_of_batches):
aspect_ratio_index = aspect_ratio[batch_index == i]
min_index, max_index = aspect_ratio_index.min(
), aspect_ratio_index.max()
if max_index < 1:
shapes[i] = [max_index, 1]
elif min_index > 1:
shapes[i] = [1, 1 / min_index]
batch_shapes = np.ceil(
np.array(shapes) * self.img_size / self.size_divisor +
self.pad).astype(np.int64) * self.size_divisor
for i, data_info in enumerate(data_list):
data_info['batch_shape'] = batch_shapes[batch_index[i]]