Yolov5(2):模型解析与构建源码逐行解析

目录

一、模型配置文件解读

二、 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:表示模块的参数

二、 parse_model函数解析

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)

你可能感兴趣的:(yolo,深度学习,pytorch,人工智能,目标检测)