目录
一、模型配置文件解读
二、 parse_model函数解析
YOLOv5系列:解析索引
一、模型配置文件解读
nc: 80 # number of classes
depth_multiple: 0.33 # model depth multiple
width_multiple: 0.50 # layer channel multiple
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
nc: 待检测目标的总类别数
depth_multiple:调整模型中模块深度的因子
width_multiple:调整模型中间通道数的因子
anchors:三层的预选框
backbone:
# [from, number, module, args]
[[-1, 1, Conv, [64, 6, 2, 2]], # 0-P1/2
[-1, 1, Conv, [128, 3, 2]], # 1-P2/4
[-1, 3, C3, [128]],
[-1, 1, Conv, [256, 3, 2]], # 3-P3/8
[-1, 6, C3, [256]],
[-1, 1, Conv, [512, 3, 2]], # 5-P4/16
[-1, 9, C3, [512]],
[-1, 1, Conv, [1024, 3, 2]], # 7-P5/32
[-1, 3, C3, [1024]],
[-1, 1, SPPF, [1024, 5]], # 9
]
backbone每一个元素包含了:[from, number, module, args]四个部分
from = -1 表示当前模块同前一个模块相连接
from != -1 表示当前模块同第from个模块相连接
number:表示当前此模块重复的数量
module:表示模块的名称
args:表示模块的参数
def parse_model(d, ch): # model_dict, input_channels(3)
# 打印模型中各层参数--表头
# LOGGER.info中 '':>3 表示靠右对齐,统一占位大于等于3
# 'module':<40 表示靠左对齐,统一占位大于等于40
LOGGER.info(f"\n{'':>3}{'from':>18}{'n':>3}{'params':>10} {'module':<40}{'arguments':<30}")
# anchors:预选框
# nc :类别数
# depth_multiple: 网络深度防缩因子
# width_multiple: 网络通道数防缩因子
anchors, nc, gd, gw = d['anchors'], d['nc'], d['depth_multiple'], d['width_multiple']
# 得到一层的预选框的个数
na = (len(anchors[0]) // 2) if isinstance(anchors, list) else anchors # number of anchors
# 每一个预选框包含 nc+5 个预测值,其中nc个值用来预测类别,2个用来预测位置调整,2个用来预测预测框尺寸调整,1个用来判别是否有目标
no = na * (nc + 5) # number of outputs = anchors * (classes + 5)
# layers:保存每一层
# save :为残差连接的concat暂存中间调整图
# c2 :当前模块的input_channel
layers, save, c2 = [], [], ch[-1] # layers, savelist, ch out
# 构建模型
# from:前一残差模块索引, number:深度, module:模块名, args:参数
for i, (f, n, m, args) in enumerate(d['backbone'] + d['head']):
# 实例化模块
m = eval(m) if isinstance(m, str) else m # eval strings
for j, a in enumerate(args):
try:
# 将参数字符串转换为参数数值
args[j] = eval(a) if isinstance(a, str) else a # eval strings
except NameError:
pass
# round(n * gd): 放缩模块深度,四舍五入
n = n_ = max(round(n * gd), 1) if n > 1 else n # depth gain
# 处理第一类模块,这一类模块的参数类似:
# 输入、输出通道数、
if m in [Conv, GhostConv, Bottleneck, GhostBottleneck, SPP, SPPF, DWConv, MixConv2d, Focus, CrossConv,
BottleneckCSP, C3, C3TR, C3SPP, C3Ghost]:
# 每处计算c2目的是作为下一层的输入通道数
c1, c2 = ch[f], args[0]
# 判断是否为最终输出,否则输出通道数要经过放缩为模型量级
if c2 != no:
# 将c2限定下界为 8 , make_divisible返回的是 max(ceil(c2 * gw / 8) * 8, 8)
c2 = make_divisible(c2 * gw, 8)
# 装入参数列表:输入通道数、输出通道数、其他参数
args = [c1, c2, *args[1:]]
# 处理第一类模块中的部分模块,这一类模块的参数:
# 第三个参数额外多一个深度
if m in [BottleneckCSP, C3, C3TR, C3Ghost]:
args.insert(2, n) # number of repeats
n = 1
# 处理第二类模块参数,仅仅需要一个通道数作为正则化参数
elif m is nn.BatchNorm2d:
args = [ch[f]]
# 处理第三类模块参数,计算出拼接后的通道数
elif m is Concat:
c2 = sum(ch[x] for x in f)
# 处理第四类模块参数,目标检测模块
elif m is Detect:
# 此模块参数为:[nc, anchors, [layer1_ch, layer2_ch, layer3_ch]]
# 三层特征图的通道数
args.append([ch[x] for x in f])
# 如果anchors为整数,表明传入的是预选框个数,则生成anchors数对表示预选框,len(f)表示层数
if isinstance(args[1], int): # number of anchors
args[1] = [list(range(args[1] * 2))] * len(f)
# 处理第五类模块参数, 图像切割,例如(1,3,20,20)-->(1,12,10,10),传入参数为切割比例
# 进而计算出输出的通道数为c2
elif m is Contract:
c2 = ch[f] * args[0] ** 2
# 处理第六类模块参数,是Contract的逆过程
elif m is Expand:
c2 = ch[f] // args[0] ** 2
else:
# 其他模块
c2 = ch[f]
# 逐个构建模块,对于多层的进行展开重构为整体,目的是提高效率,节省空间
m_ = nn.Sequential(*(m(*args) for _ in range(n))) if n > 1 else m(*args) # module
# 例如:"",目的为修改类型
t = str(m)[8:-2].replace('__main__.', '') # module type
# numel()获取tensor的元素总个数,np就是总参数量
np = sum(x.numel() for x in m_.parameters()) # number params
# 修改模块的信息,便于后续制表格式打印
m_.i, m_.f, m_.type, m_.np = i, f, t, np # attach index, 'from' index, type, number params
LOGGER.info(f'{i:>3}{str(f):>18}{n_:>3}{np:10.0f} {t:<40}{str(args):<30}') # print
# 为残差连接的concat暂存中间调整图,同时也为可视化提供帮助
save.extend(x % i for x in ([f] if isinstance(f, int) else f) if x != -1) # append to savelist
layers.append(m_)
# 处理边界
if i == 0:
ch = []
ch.append(c2)
return nn.Sequential(*layers), sorted(save)