看到了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
下面的代码重点就在于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-->
2----:-->
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)
'''
导入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))
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()
)
)