从yaml文件中创建网络模型

看到了yolov5创建网络的方法,从yaml文件中创建,记录一下。

#cfg是yaml文件的内容,ch=3表示input channel是3通道,nc=80,表示分类类别是80个类别
model = Model(cfg or ckpt['model'].yaml, ch=3, nc=nc, anchors=hyp.get('anchors')).to(device)  # create

为了简化,将代码写成如下格式,这样便可以不断运行Model来理解代码

from models.yolo import Model
import yaml  # for torch hub
from pathlib import Path
#cfg路径定位到自己的yolov5s.yaml路径
cfg='../../models/yolov5s.yaml'
yaml_file = Path(cfg).name
print(f'yaml.name:{yaml_file}')
with open(cfg, encoding='ascii', errors='ignore') as f:
    yaml_f = yaml.safe_load(f)  # model dict
model = Model(cfg=yaml_f,ch=3)  # create

1.parse_model

下面的代码重点就在于parse_model函数。


class Model(nn.Module):
    def __init__(self, cfg='yolov5s.yaml', ch=3, nc=None, anchors=None):  # model, input channels, number of classes
        super().__init__()
        if isinstance(cfg, dict):
            self.yaml = cfg  # model dict
        else:  # is *.yaml
            import yaml  # for torch hub
            self.yaml_file = Path(cfg).name
            print(f'yaml.name:{self.yaml_file}')
            with open(cfg, encoding='ascii', errors='ignore') as f:
                self.yaml = yaml.safe_load(f)  # model dict

        # Define model
        # ch参数通过self.yaml.get('ch', ch) 方法送入
        # self.yaml.get('ch', ch):有ch参数取出,没有则以__init__的ch传入
        ch = self.yaml['ch'] = self.yaml.get('ch', ch)  # input channels
        if nc and nc != self.yaml['nc']:
            LOGGER.info(f"Overriding model.yaml nc={self.yaml['nc']} with nc={nc}")
            self.yaml['nc'] = nc  # override yaml value
        if anchors:
            LOGGER.info(f'Overriding model.yaml anchors with anchors={anchors}')
            self.yaml['anchors'] = round(anchors)  # override yaml value
        '''
            解析模型:
            parse_model:通过yaml文件建立模型
            nc: number of classes:80
        '''
        self.model, self.save = parse_model(deepcopy(self.yaml), ch=[ch])  # model, savelist

所以我们下面来看parse_model函数内部


'''
    parse_model(self.yaml, ch=[ch])
    anchors:anchor 
    nc: number of classes
    #通过深度参数 depth gain 在搭建每一层的时候,实际深度 = 理论深度( 每一层的参数n) * depth_multiple,这样就可以起到一个动态调整模型深度的作用。
	#第三个参数是width_multiple,用于控制模型的宽度。在模型中间层的每一层的实际输出channel = 理论channel(每一层的参数c2) * width_multiple,这样也可以起到一个动态调整模型宽度的作用
    gd: 深度控制参数
    gw: 宽度控制参数
'''
def parse_model(d, ch):  # model_dict, input_channels(3)
    print(f'送进来的ch:{ch}')#3
    LOGGER.info(f"\n{'':>3}{'from':>18}{'n':>3}{'params':>10}  {'module':<40}{'arguments':<30}")
    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
    no = na * (nc + 5)  # number of outputs = anchors * (classes + 5)

    layers, save, c2 = [], [], ch[-1]  # layers, savelist, ch out
    # 解析循环开始
    for i, (f, n, m, args) in enumerate(d['backbone'] + d['head']):  # from, number, module, args
        '''              上一层传入,只迭代一次,卷积层(自己写的Conv),[输出通道,kernel_size,stride,padding]
        	yolov5s.yaml:[-1, 1, Conv, [64, 6, 2, 2]]
        	f:from,每一层的输入,-1表示由上一层的输出输入
        	n:表示这个层要循环几次,类似于Inception结构
        	m:module:表示这个层是什么模块,可以通过判断然后搭建出来,详情见1.1
        	args:保存每一层的参数,例如
            循环来看
            1----:Conv--><class 'str'>
            2----:<class 'models.common.Conv'>--><class 'type'>
            eval函数可以将一个str字符串解析成某个类型,详见1.1
        '''
        print(f'初始的m:{m},初始的n:{n}')
        print(f'1----:{m}-->{type(m)}')
        m = eval(m) if isinstance(m, str) else m  # eval strings
        print(f'2----:{m}-->{type(m)}')
        #将参数保存在args里面
        for j, a in enumerate(args):
            try:
                args[j] = eval(a) if isinstance(a, str) else a  # eval strings
            except NameError:
                pass

        '''
            [from, number, module, args]
            number : module的数量
            n,n_ 深度控制
            n * gd : module实际上应该有多少层(实际层数根据gd大小变化)
            if n>1:
                n,n_ = max(层数)
        '''
        print(f'ch更新:{ch}')
        n = n_ = max(round(n * gd), 1) if n > 1 else n  # depth gain
        # 如果module在下面:
        if m in [Conv, GhostConv, Bottleneck, GhostBottleneck, SPP, SPPF, DWConv, MixConv2d, Focus, CrossConv,
                 BottleneckCSP, C3, C3TR, C3SPP, C3Ghost]:
            # c1为上一层的输出channel,这一层的输入channel
            # c2为这一层的输出channel
            # args为:[输出通道,kernel_size,stride,padding]
            #ch 是每一层output的集合 ,此时的args是[output,kernel_size,stride,padding]
            c1, c2 = ch[f], args[0]#本层的输入通道,输出通道
            print(f'ch是什么:{ch},args是什么:{args},c1:{c1},c2:{c2}')
            if c2 != no:  # if not output
                # 宽度控制
                # 要么为c2 * gw, 要么为8 ,看哪个大
                c2 = make_divisible(c2 * gw, 8)

            '''
                一个是数组,一个是取出数组的东西
                args:[1, 2, 3, 4, 5, 6, 7]
                *args:2 3 4 5 6 7
                此时的args:
                args: [输入,输出,kernel_size,stride,padding]
            '''
            args = [c1, c2, *args[1:]]
            if m in [BottleneckCSP, C3, C3TR, C3Ghost]:
                # 在2的位置添加一个n,即重复模块数量
                args.insert(2, n)  # number of repeats
                n = 1
        elif m is nn.BatchNorm2d:
            # 对于BatchNorm2d,输入输出等同,且无多余参数,就参数只需要一个输入ch[f]
            print(f'batchnorm2d:{ch},:{f}::{ch[f]}')
            args = [ch[f]]
        elif m is Concat:
            # 对于Concat: 输出为输入的所有层数和
            c2 = sum(ch[x] for x in f)
        elif m is Detect:
            # 对于检测层,args添加这一层的所有输入
            args.append([ch[x] for x in f])
            print('---------Detect--------------')
            for x in f:
                print(x,'----',ch[x])
            print(f'args:{args}')
            if isinstance(args[1], int):  # number of anchors
                args[1] = [list(range(args[1] * 2))] * len(f)
        elif m is Contract:
            c2 = ch[f] * args[0] ** 2
        elif m is Expand:
            c2 = ch[f] // args[0] ** 2
        else:
            # f->from 
            c2 = ch[f]

		#下面这句详见1.2
        m_ = nn.Sequential(*(m(*args) for _ in range(n))) if n > 1 else m(*args)  # module
        #t表示m的module模块名字
        t = str(m)[8:-2].replace('__main__.', '')  # module type 如果有__main__ 替换''
        #np是这个m_模块的参数数量
        np = sum(x.numel() for x in m_.parameters())  # number params numel()函数:返回数组中元素的个数
        #将序号,from,模块类型,参数数量加入参数
        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
        #extend() 函数用于在列表末尾一次性追加另一个序列中的多个值
        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列表
        ch.append(c2)
    return nn.Sequential(*layers), sorted(save)

1.1 eval

'''
导入nn库,nn库里有nn.Conv2d这个模块,我就可以使用eval来将字符串转成这个模块
'''
import torch.nn as nn
m='nn.Conv2d'
print(f'此时的m类型:{type(m)}')
m_=eval(m)
print(f'此时的m_类型:{type(m_)}')
print('对m_模块赋值')
print(m_(2,2,3,1))
输出:
此时的m类型:<class 'str'>
此时的m_类型:<class 'type'>
Conv2d(2, 2, kernel_size=(3, 3), stride=(1, 1))

但是 yolov5s 的网络都是自己写的,例如在common.py中定义了Conv网络,包括了nn.Conv2d, nn.BatchNorm2d, nn.SiLU(),其余网络不再介绍。

class Conv(nn.Module):
    # Standard convolution
    def __init__(self, c1, c2, k=1, s=1, p=None, g=1, act=True):  # ch_in, ch_out, kernel, stride, padding, groups
        super().__init__()
        self.conv = nn.Conv2d(c1, c2, k, s, autopad(k, p), groups=g, bias=False)
        self.bn = nn.BatchNorm2d(c2)
        self.act = nn.SiLU() if act is True else (act if isinstance(act, nn.Module) else nn.Identity())

    def forward(self, x):
        return self.act(self.bn(self.conv(x)))

    def forward_fuse(self, x):
        return self.act(self.conv(x))

1.2

from models.common import *#导入yolov5的网络
m='Conv'
m = eval(m) if isinstance(m, str) else m  # eval strings
# args=[输入通道,输出通道,kernel_size,stride,padding]
#*args表示取出args列表中的值
args=[3,32,6,2,2]
m_=nn.Sequential(m(*args))
print(m_)
输出:
Sequential(
  (0): Conv(
    (conv): Conv2d(3, 32, kernel_size=(6, 6), stride=(2, 2), padding=(2, 2), bias=False)
    (bn): BatchNorm2d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    (act): SiLU()
  )
)
from models.common import *#导入yolov5的网络
m='Conv'
m_= eval(m) if isinstance(m, str) else m  # eval strings
# args=[输入通道,输出通道,kernel_size,stride,padding]
#*args表示取出args列表中的值
args=[3,32,6,2,2]

# nn.Sequential(*(m(*args) for _ in range(n)))
# 改写这句话
n=3#重复3遍
m_m_=[]
for i in range(n):
    m_m_.append(m_(*args))#添加每个模块
# *m_m_:取出每个模块,装入nn.Sequential
m=nn.Sequential(*m_m_)
print(m)
输出:
Sequential(
  (0): Conv(
    (conv): Conv2d(3, 32, kernel_size=(6, 6), stride=(2, 2), padding=(2, 2), bias=False)
    (bn): BatchNorm2d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    (act): SiLU()
  )
  (1): Conv(
    (conv): Conv2d(3, 32, kernel_size=(6, 6), stride=(2, 2), padding=(2, 2), bias=False)
    (bn): BatchNorm2d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    (act): SiLU()
  )
  (2): Conv(
    (conv): Conv2d(3, 32, kernel_size=(6, 6), stride=(2, 2), padding=(2, 2), bias=False)
    (bn): BatchNorm2d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    (act): SiLU()
  )
)
  • 2
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值