前言
YOLOv5 是一系列在 COCO 数据集上预训练的对象检测架构和模型,结合了在数千小时的研究和开发中获得的经验教训和最佳实践。本章节主要以yolov5s为例介绍YOLOv5-v7.0版本的网络结构及初始化超参数。
网络结构主要分为以下方面:
特别注意:这是YOLOv5s的网络结构(因是自己画的图,难免会有疏漏,如有错误,望各位大佬在评论区指正!),想必很多小伙伴会有这样的疑惑,为什么我看到别人7.0版本的网络结构最后进入Head的是 80 ∗ 80 ∗ 256 80*80*256 80∗80∗256, 40 ∗ 40 ∗ 512 40*40*512 40∗40∗512和 20 ∗ 20 ∗ 1024 20*20*1024 20∗20∗1024,跟上面写的 80 ∗ 80 ∗ 128 80*80*128 80∗80∗128, 40 ∗ 40 ∗ 256 40*40*256 40∗40∗256和 20 ∗ 20 ∗ 512 20*20*512 20∗20∗512不一致,这个问题主要跟yolov5s.yaml
中的width_multiple
参数有关,本章后续会详细讲解。
--noautoanchor
参数设置True
default值即可关闭parser.add_argument('--noautoanchor', default=True, action='store_true', help=' 不自动调整anchor,默认为False')
在 YOLOv5 的配置文件model/*.yaml
中已经预设了一些针对 COCO数据集在 640 × 640 640×640 640×640图像大小下锚定框的尺寸:
anchors:
- [10,13, 16,30, 33,23] # P3/8
- [30,61, 62,45, 59,119] # P4/16
- [116,90, 156,198, 373,326] # P5/32
锚框核查函数 /utils/autoanchor.py 文件中:
@TryExcept(f'{PREFIX}ERROR')
def check_anchors(dataset, model, thr=4.0, imgsz=640):
# Check anchor fit to data, recompute if necessary
m = model.module.model[-1] if hasattr(model, 'module') else model.model[-1] # Detect()
shapes = imgsz * dataset.shapes / dataset.shapes.max(1, keepdims=True)
scale = np.random.uniform(0.9, 1.1, size=(shapes.shape[0], 1)) # augment scale
wh = torch.tensor(np.concatenate([l[:, 3:5] * s for s, l in zip(shapes * scale, dataset.labels)])).float() # wh
def metric(k): # compute metric
r = wh[:, None] / k[None]
x = torch.min(r, 1 / r).min(2)[0] # ratio metric
best = x.max(1)[0] # best_x
aat = (x > 1 / thr).float().sum(1).mean() # anchors above threshold
bpr = (best > 1 / thr).float().mean() # best possible recall
return bpr, aat
stride = m.stride.to(m.anchors.device).view(-1, 1, 1) # model strides
anchors = m.anchors.clone() * stride # current anchors
bpr, aat = metric(anchors.cpu().view(-1, 2))
s = f'\n{PREFIX}{aat:.2f} anchors/target, {bpr:.3f} Best Possible Recall (BPR). '
if bpr > 0.98: # threshold to recompute
LOGGER.info(f'{s}Current anchors are a good fit to dataset ✅')
else:
LOGGER.info(f'{s}Anchors are a poor fit to dataset ⚠️, attempting to improve...')
na = m.anchors.numel() // 2 # number of anchors
anchors = kmean_anchors(dataset, n=na, img_size=imgsz, thr=thr, gen=1000, verbose=False)
new_bpr = metric(anchors)[0]
if new_bpr > bpr: # replace anchors
anchors = torch.tensor(anchors, device=m.anchors.device).type_as(m.anchors)
m.anchors[:] = anchors.clone().view_as(m.anchors)
check_anchor_order(m) # must be in pixel-space (not grid-space)
m.anchors /= stride
s = f'{PREFIX}Done ✅ (optional: update model *.yaml to use these anchors in the future)'
else:
s = f'{PREFIX}Done ⚠️ (original anchors better than new anchors, proceeding with original anchors)'
LOGGER.info(s)
YOLOv5 在开始训练前会计算数据集标注信息针对默认锚定框的最佳召回率,如果最佳召回率大于或等于0.98,则不需要重新计算锚定框,使用默认锚框;如果最佳召回率小于0.98,则需要重新计算符合此数据集的锚框。
def metric(k): # compute metric
r = wh[:, None] / k[None]
x = torch.min(r, 1 / r).min(2)[0] # ratio metric
best = x.max(1)[0] # best_x
aat = (x > 1 / thr).float().sum(1).mean() # anchors above threshold
bpr = (best > 1 / thr).float().mean() # best possible recall
return bpr, aat
stride = m.stride.to(m.anchors.device).view(-1, 1, 1) # model strides
anchors = m.anchors.clone() * stride # current anchors
bpr, aat = metric(anchors.cpu().view(-1, 2))
其中,bpr(best possible recall)参数就是判断是否需要重新计算锚定框的依据(是否小于 0.98)
重新计算符合此数据集标注框的锚定框,是利用 k均值聚类算法(k-means clustering)和遗传算法(genetic algorithm)实现的
import utils.autoanchor as autoAC
# 对数据集重新计算 anchors
new_anchors = autoAC.kmean_anchors('./data/mydata.yaml', 9, 640, 5.0, 1000, True)
print(new_anchors)
最早出现于YOLOv4,YOLOv5也是延用了Mosaic数据增强。Mosaic数据增强的主要思想是将多张图片按一定比例组合成一张图片,实则是参考了CutMix数据增强方式,CutMix数据增强是将两张图片进行拼接,Mosaic数据增强则利用了四张图片,对四张图片进行拼接,每一张图片都有其对应的目标框,将四张图片拼接之后就获得一张新的图片,同时也获得这张图片对应的目标框,然后我们将这样一张新的图片传入到神经网络当中去学习,相当于一下子传入四张图片进行学习。
Mosaic数据增强的主要步骤:
Mosaic数据增强的优点:
⭐YOLOv5训练默认是开启了Mosaic数据增强的,可参考本章第六节超参数详解-(1)hpy超参数所以各位小伙伴可以放心训练!⭐
Backbone骨干网络的主要作用就是提取特征,并不断缩小特征图。Backbone中的主要结构有Conv模块、C3模块、SPPF模块。
由一个Conv2d、一个BatchNorm2d和SiLU激活函数构成。如下图所示:
Conv2d
的padding是自动计算的,通过修改stride来决定特征图缩小的倍数。 def autopad(k, p=None, d=1): # kernel, padding, dilation
# Pad to 'same' shape outputs
if d > 1:
k = d * (k - 1) + 1 if isinstance(k, int) else [d * (x - 1) + 1 for x in k] # actual kernel-size
if p is None:
p = k // 2 if isinstance(k, int) else [x // 2 for x in k] # auto-pad
return p
BatchNorm2d
为批归一化层,对每批的数据做归一化,其详细作用不在这里赘述。SiLU
激活函数,SiLU是Sigmoid和ReLU的改进版。SiLU具备无上界有下界、平滑、非单调的特性。SiLU在深层模型上的效果优于 ReLU。具有平滑性和非线性特性,有助于网络在训练过程中更快地收敛。C3由三个CBS模块和一个BottleNeck模块组成,得名C3。在Backbone中,C3是更为重要的提取特征的模块。其结构图如下:
add
操作是将主分支和残差分支的特征图进行直接相加,不会改变特征图的尺寸和维度,只是将对应位置的特征值进行相加。下述C3中带有False参数则表示不使用残差结构。通过残差结构,可以实现在深层网络中传递梯度和信息的快速传递,并有助于解决深层网络训练中的梯度消失问题。SPP是空间金字塔池化,采用 1 × 1 1×1 1×1, 5 × 5 5×5 5×5, 9 × 9 9×9 9×9, 13 × 13 13×13 13×13的最大池化的方式,进行多尺度融合。YOLOv5 6.0版本开始使用了在SPP基础上改进的SPPF。
注意:图像的尺度并非指图像的大小,而是指图像的模糊程度(σ),例如,人近距离看一个物体和远距离看一个物体模糊程度是不一样的,从近距离到远距离图像越来越模糊的过程,也是图像的尺度越来越大的过程。
Focus模块是对图片进行切片操作,具体操作是在一张图片中每隔一个像素拿到一个值,这样获得了四个独立的特征层,然后将四个独立的特征层进行堆叠,此时宽高信息就集中到了通道信息,输入通道扩充了四倍。拼接起来的特征层相对于原先的三通道变成了十二个通道,最后将得到的新图片再经过卷积操作,最终得到了没有信息丢失情况下的二倍下采样特征图。
YOLOv5 6.0开始将Focus模块替换成了一个 6 ∗ 6 6*6 6∗6的卷积层。两者的计算量是等价的,但使用 6 ∗ 6 6*6 6∗6的卷积会更加高效。
Neck的作用就是从Backbone中获取相对于较浅的特征,再与深层的语义特征Concat到一起。
FPN 结构通过自顶向下进行上采样,使得底层特征图包含更强的图像强语义信息
总结:FPN层自顶向下可以捕获强语义特征,而PAN则通过自底向上传达强定位特征。
Head层为Detect模块,Detect模块的网络结构很简单,仅由三个 1 ∗ 1 1*1 1∗1卷积构成,对应三个检测特征层。
(1)hpy超参数
data/hyps
文件夹下# Hyperparameters for VOC finetuning
# ython train.py --batch 64 --weights yolov5m.pt --data voc.yaml --img 512 --epochs 50
lr0: 0.01 # 学习率, SGD=1E-2, Adam=1E-3
lrf: 0.01 # 余弦退火超参数
momentum: 0.937 # 学习率动量
weight_decay: 0.0005 # 权重衰减系数
warmup_epochs: 3.0 # 预热学习epoch
warmup_momentum: 0.8 # 预热学习率动量
warmup_bias_lr: 0.1 # 预热学习率
box: 0.05 # Bounding Box Regeression 损失的系数
cls: 0.5 # 分类损失的系数
cls_pw: 1.0 # 分类BCELoss中正样本的权重
obj: 1.0 # 有无物体损失的系数
obj_pw: 1.0 # 有无物体BCELoss中正样本的权重
iou_t: 0.20 # 标签与anchors的iou阈值 iou training threshold
anchor_t: 4 # 标签的长h宽w/anchor的长h_a宽w_a阈值, 即h/h_a, w/w_a都要在(1/4, 4)之间anchor-multiple threshold
# anchors: 3.63
# 下面是一些数据增强的系数, 包括颜色空间和图片空间
fl_gamma: 0.0
hsv_h: 0.015 # 色调
hsv_s: 0.7 # 饱和度
hsv_v: 0.4 # 明度
degrees: 0.0 #旋转角度
translate: 0.1 # 水平和垂直平移
scale: 0.5 # 缩放
shear: 0.0 # 剪切
perspective: 0.0 # 透视变换参数
flipud: 0.0 # 上下翻转
fliplr: 0.5 # 左右翻转
mosaic: 1.0 #进行mosaic的概率
mixup: 0.0 #进行mixup的概率, 在mosaic启用时才可启用
copy_paste: 0.0 # segment copy-paste (probability), 在mosaic启用时, 才可以启用
(3)Acnhor
# anchors
anchors:
- [10,13, 16,30, 33,23] # P3/8 检测小目标 10,13是一组尺寸,一共三组
- [30,61, 62,45, 59,119] # P4/16
- [116,90, 156,198, 373,326] # P5/32 检测大目标
(4)Backbone
# YOLOv5s v6.0 backbone
backbone:
# YOLOv5 v6.0 backbone
backbone:
# from 第一列 输入来自哪一层 -1代表上一层, 4代表第4层
# number 第二列 卷积核的数量 最终数量需要乘上width
# module 第三列 模块名称 包括:Conv Focus BottleneckCSP SPP
# args 第四列 模块的参数
# [from, number, module, args]
[[-1, 1, Conv, [64, 6, 2, 2]], # 0-P1/2
[-1, 1, Conv, [128, 3, 2]], # 1-P2/4 卷积核的数量 = 128 * wedith = 128*0.5=64
[-1, 3, C3, [128]], # 模块数量 = 3 * depth =3*0.33=1
[-1, 1, Conv, [256, 3, 2]], # 3-P3/8
[-1, 6, C3, [256]], # 模块数量 = 6 * depth =6*0.33=2
[-1, 1, Conv, [512, 3, 2]], # 5-P4/16
[-1, 9, C3, [512]], # # 模块数量 = 9 * depth =9*0.33=3
[-1, 1, Conv, [1024, 3, 2]], # 7-P5/32
[-1, 3, C3, [1024]],
[-1, 1, SPPF, [1024, 5]], # 9
]
(5)网络结构参数
根据训练过程验证网络结构,结合训练时输出的网络结构信息
**backbone的前3个C3数量对应yolov5s.yaml的配置3,6,9分别除了3,变为1/3后的1,2,3,和模型深度参数有关depth_multiple: 0.33**
层数,第几层 from n params module arguments
ch[-1] 数量 参数量 模块名称(m) 网络结构参数:输入维度,输出维度,卷积核大小,卷积步长
0 -1 1 3520 models.common.Conv [3, 32, 6, 2, 2]
1 -1 1 18560 models.common.Conv [32, 64, 3, 2]
2 -1 1 18816 models.common.C3 [64, 64, 1]
3 -1 1 73984 models.common.Conv [64, 128, 3, 2]
4 -1 2 115712 models.common.C3 [128, 128, 2]
5 -1 1 295424 models.common.Conv [128, 256, 3, 2]
6 -1 3 625152 models.common.C3 [256, 256, 3]
7 -1 1 1180672 models.common.Conv [256, 512, 3, 2]
8 -1 1 1182720 models.common.C3 [512, 512, 1]
9 -1 1 656896 models.common.SPPF [512, 512, 5]
10 -1 1 131584 models.common.Conv [512, 256, 1, 1]
12 [-1, 6] 1 0 models.common.Concat [1]
13 -1 1 361984 models.common.C3 [512, 256, 1, False]
14 -1 1 33024 models.common.Conv [256, 128, 1, 1]
15 -1 1 0 torch.nn.modules.upsampling.Upsample [None, 2, 'nearest']
16 [-1, 4] 1 0 models.common.Concat [1]
17 -1 1 90880 models.common.C3 [256, 128, 1, False]
18 -1 1 147712 models.common.Conv [128, 128, 3, 2]
19 [-1, 14] 1 0 models.common.Concat [1]
20 -1 1 296448 models.common.C3 [256, 256, 1, False]
21 -1 1 590336 models.common.Conv [256, 256, 3, 2]
22 [-1, 10] 1 0 models.common.Concat [1]
23 -1 1 1182720 models.common.C3 [512, 512, 1, False]
24 [17, 20, 23] 1 229245 Detect [80, [[10, 13, 16, 30, 33, 23], [30, 61, 62, 45, 59, 119], [116, 90, 156, 198, 373, 326]], [128, 256, 512]]