网络可视化工具:netron
在线版本:https://lutzroeder.github.io/netron/
netron 对 pt 格式的权重文件兼容性不好,直接使用 netron 工具打开,无法显示整个网络。
可使用YOLOv5代码中models/export.py脚本将pt权重文件转换为onnx格式,再用netron工具打开,就可以看到YOLOv5网络的整体架构。
# 导出onnx文件 pip install onnx>=1.7.1 -i https://pypi.tuna.tsinghua.edu.cn/simple # for ONNX export pip install coremltools==4.0 -i https://pypi.tuna.tsinghua.edu.cn/simple # for CoreML export python models/export.py --weights weights/yolov5s.pt --img 640 --batch 1
yolov5s.yaml 解析(YOLOv5 的配置文件)
nc: 80 # number of classes, 数据集上的类别数
# 以下两个参数为缩放因子, 通过这两个参数就可以实现不同复杂度的模型设计
depth_multiple: 0.33 # model depth multiple, 控制网络深度(即控制 BottleneckCSP 的数目)
width_multiple: 0.50 # layer channel multiple, 控制网络宽度, 控制 Conv 通道个数(卷积核数量)
# depth_multiple 表示 BottleneckCSP 模块层的缩放因子,将所有的 BottleneckCSP 模块的 Bottleneck 乘上该参数得到最终个数。控制子模块数量=int(number*depth)
# width_multiple 表示卷积通道的缩放因子,就是将配置里面的 backbone 和 head 部分有关 Conv 通道的设置,全部乘以该系数。控制卷积核的数量=int(number*width)
以上三个参数,会用于模型搭建 yolo.py 文件中
# 读取 yaml 中的 anchors 和 parameters anchors, nc, gd, gw = d['anchors'], d['nc'], d['depth_multiple'], d['width_multiple']
通过深度参数 depth gain, 在搭建每层时, 实际深度 = 理论深度(每一层的参数n)* depth_multiple,起到动态调整模型深度的作用
n = max(round(n * gd), 1) if n > 1 else n # depth gain
在模型中间层的每一层的实际输出 channel = 理论channel(每一层的参数c2)* width_multiple,起到动态调整模型宽度的作用。
# 控制宽度(卷积核个数)的代码 c2 = make_divisible(c2 * gw, 8) if c2 != no else c2
# anchors 先验框的配置
anchors:
# 三个不同尺度的先验框
- [10,13, 16,30, 33,23] # P3/8 检测小目标,第一个先验框宽为10,高为13
- [30,61, 62,45, 59,119] # P4/16 检测中目标,P4即stride=16(经过了16倍的下采样以后尺度上的anchor的大小)
- [116,90, 156,198, 373,326] # P5/32 检测大目标,对应原图上 anchor 的大小也是最大的
yolov5 初始化了 9 个 anchors,在三个 Detect 层使用(3个feature map)中使用,每个 feature map 的每个 grid cell 都有三个 anchor 进行预测。
分配规则:
- 尺度越大的 feature map 越靠前,相对原图的下采样率越小,感受野越小,所以相对可以预测一些尺度比较小的物体,所以分配到的 anchors 越小;
- 尺度越小的 feature map 越靠后,相对原图的下采样率越大,感受野越大,所以可以预测一些尺度比较大的物体,所以分配到的 anchors 越大。
- 即在小特征图(feature map)上检测大目标,在大特征图上检测小目标。
YOLOv5 根据经验得到了这么 3 组 anchors,对于很多数据集而言确实挺合适的。但是也不能保证这 3 组 anchors 就适用于所有的数据集,所有 yolov5 还有一个anchor进化的策略:使用 k-means 和遗传进化算法,找到与当前数据集最吻合的 anchors。具体的代码细节见 autoanchor.py。
# YOLOv5 backbone
backbone:
# [from, number, module, args]
# from列参数:当前模块输入来自哪一层输入,-1 表示是从上一层获得的输入
# number列参数:模块重复的次数;1表示只有一个,3表示有三个相同的模块
# module列参数: 模块名
[[-1, 1, Focus, [64, 3]], # 0-P1/2
[-1, 1, Conv, [128, 3, 2]], # 1-P2/4 128表示128个卷积核,3表示3×3的卷积核,2表示步长为2,width_multiple: 0.50,128×0.5=64
[-1, 3, BottleneckCSP, [128]], # depth_multiple: 0.33,3×0.33≈1
[-1, 1, Conv, [256, 3, 2]], # 3-P3/8
[-1, 9, BottleneckCSP, [256]],
[-1, 1, Conv, [512, 3, 2]], # 5-P4/16
[-1, 9, BottleneckCSP, [512]],
[-1, 1, Conv, [1024, 3, 2]], # 7-P5/32
[-1, 1, SPP, [1024, [5, 9, 13]]],
[-1, 3, BottleneckCSP, [1024, False]], # 9
]
from:表示当前模块的输入来自那一层的输出,-1表示来自上一层的输出。
number:表示当前模块的理论重复次数,实际的重复次数还要由上面的参数 depth_multiple 共同决定,决定网络模型的深度。
module:模块类名,通过这个类名去 common.py 中寻找相应的类,进行模块化的搭建网络。
args:是一个 list,模块搭建所需参数,channel,kernel_size,stride,padding,bias等。会在网络搭建过程中根据不同层进行改变:
BottleneckCSP,C3,C3TR:# 在初始 arg 的基础上更新,加入当前层的输入 channel args = [c1, c2, *args[1:]] # [in_channel, out_channel, *args[1:]] # 如果当前层是 BottleneckCSP/C3/C3TR,则需要在 atgs 中加入 bottleneck 的个数 # [in_channel, out_channel, Bottleneck 的个数 n,bool(True 表示有 shortcut 默认,反之无)] if m in [BottleneckCSP, C3, C3RT]: args.insert(2, n) # number of repeats n = 1 # 恢复默认值 1
nn.BatchNorm2d:
elif m is nn.BatchNorm2d: # BN层只需要返回上一层的输出 channel args = [ch[f]]
Detect:
elif m is Detect: # Detect(YOLO Layer)层 # 在 args 中加入三个 Detect 层的输出 channel args.append([ch[x + 1] for x in f]) if isinstance(args[1], int): # number of anchors 不执行 args[1] = [list(range(args[1] * 2))] * len(f)
# YOLOv5 head
# 作者未区分 neck 模块,所以 head 部分包含了 PANet 和 Detect 部分
head:
[[-1, 1, Conv, [512, 1, 1]],
[-1, 1, nn.Upsample, [None, 2, 'nearest']],
[[-1, 6], 1, Concat, [1]], # cat backbone P4
[-1, 3, BottleneckCSP, [512, False]], # 13
[-1, 1, Conv, [256, 1, 1]],
[-1, 1, nn.Upsample, [None, 2, 'nearest']],
[[-1, 4], 1, Concat, [1]], # cat backbone P3
[-1, 3, BottleneckCSP, [256, False]], # 17 (P3/8-small)
[-1, 1, Conv, [256, 3, 2]],
[[-1, 14], 1, Concat, [1]], # cat head P4
[-1, 3, BottleneckCSP, [512, False]], # 20 (P4/16-medium)
[-1, 1, Conv, [512, 3, 2]],
[[-1, 10], 1, Concat, [1]], # cat head P5
[-1, 3, BottleneckCSP, [1024, False]], # 23 (P5/32-large)
[[17, 20, 23], 1, Detect, [nc, anchors]], # Detect(P3, P4, P5)
]
YOLOv5 Architecture Made by BAI Yong
YOLOv5s | YOLOv5m | YOLOv5l | YOLOv5x | |
depth_multiple | 0.33 | 0.67 | 1.0 | 1.33 |
width_multiple | 0.50 | 0.75 | 1.0 | 1.25 |
BottleneckCSP数 BCSPn(True) |
1,3,3 | 2,6,6 | 3,9,9 | 4,12,12 |
BottleneckCSP数BCSPn(False) |
1 | 2 | 3 | 4 |
Conv卷积核数量 | 32,64,128, 256,512 |
48,96,192, 384,768 |
64,128,256, 512,1024 |
80,160,320, 640,1280 |
models/yolov5s.yaml
depth_multiple: 0.33 # model depth multiple
width_multiple: 0.50 # layer channel multiple
models/yolov5m.yaml
depth_multiple: 0.67
width_multiple: 0.75
models/yolov5l.yaml
depth_multiple: 1.0
width_multiple: 1.0
models/yolov5x.yaml
depth_multiple: 1.33
width_multiple: 1.25
Focus
把数据切分为4份,每份数据都是相当于2倍下采样得到的,然后在channe维度进行拼接,最后进行卷积操作。
Focus() 模块的设计是为了减少 FLOPS、增加速度,而不增加 mAP。在 YOLOv5 中,作者希望降低 Conv2d 计算的成本,并使用张量 reshape 来减少空间(分辨率)并增加深度(通道数量)。 输入会被转换成 [ b,c,h,w ] —> [ b,c×4,h // 2,w // 2 ]
把宽度 w 和高度 h 的信息整合到 c 空间中。
以 YOLOv5s 的结构为例,原始 640×640×3 的图像输入 Focus 结构,采用切片操作,先变成 320×320×12 的特征图,再经过一次 32 个卷积核的卷积操作,最终变成 320×320×32 的特征图。
CSPNet
CSP(Cross Stage Parital Network) 跨阶段局部网络,主要从网络结构设计的角度解决推理中从计算量很大的问题。CSPNet 的作者认为推理计算过高的问题是由于网络优化中的梯度信息重复导致的。因此采用CSP模块先将基础层的特征映射划分为两部分,然后通过跨阶段层次结构将它们合并,在减少了计算量的同时可以保证准确率。
直接看网络结构可能更清晰,比如将 CSPNet 应用到 DarkNet53(CSPDarkNet-53)。上中左边是 DarkNet53 原本的结构,右边是应用 CSPNet 后的结构。
SPP(Spatial Pyramid Pooling) 空间金字塔池化
Add the SPP block over the CSP, since it significantly increase the receptive field, separates out the most significant context features and causes almost no reduction of the network operation speed.
PANet
Path-Aggregation Network 路径聚合网络
参考文章:
YOLOv5-5.0v-yaml 解析及模型构建(第二篇)_星魂非梦的博客-CSDN博客
Yolov5s.yaml_满船清梦压星河HK的博客-CSDN博客_yolov5s代码详解
YOLOv5.yaml文件 & 超参详细介绍_王吉吉丫的博客-CSDN博客_yolov5 超参数