做自媒體有哪些素材網(wǎng)站廣州品牌營銷服務(wù)
文章目錄
- 前言
- 一、yolov5文件說明
- 二、yolov5調(diào)用模型構(gòu)建位置
- 三、模型yaml文件解析
- 1、 yaml的backbone解讀
- Conv模塊參數(shù)解讀
- C3模塊參數(shù)解讀
- 2、yaml的head解讀
- Concat模塊參數(shù)解讀
- Detect模塊參數(shù)解讀
- 四、模型構(gòu)建整體解讀
- 五、構(gòu)建模型parse_model源碼解讀
前言
本文章記錄yolov5如何通過模型文件yaml搭建模型,從解析yaml參數(shù)用途,到parse_model模型構(gòu)建,最后到y(tǒng)olov5如何使用搭建模型實現(xiàn)模型訓(xùn)練過程。
`
一、yolov5文件說明
model/yolo.py文件:為模型構(gòu)建文件,主要為模型集成類class Model(nn.Module),模型yaml參數(shù)(如:yolov5s.yaml)構(gòu)建parse_model(d, ch)
model/common.py文件:為模型模塊(或叫模型組裝網(wǎng)絡(luò)模塊)
二、yolov5調(diào)用模型構(gòu)建位置
在train.py約113行位置,如下代碼:
if pretrained:with torch_distributed_zero_first(LOCAL_RANK):weights = attempt_download(weights) # download if not found locallyckpt = torch.load(weights, map_location=device) # load checkpointmodel = Model(cfg or ckpt['model'].yaml, ch=3, nc=nc, anchors=hyp.get('anchors')).to(device) # createexclude = ['anchor'] if (cfg or hyp.get('anchors')) and not resume else [] # exclude keys
三、模型yaml文件解析
以yolov5s.yaml文件作為參考,作為解析。
1、 yaml的backbone解讀
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]
Conv模塊參數(shù)解讀
backbone的[-1, 1, Conv, [128, 3, 2]]行作為解讀參考,在parse_model(d, ch)中表示,f, n, m, args=[-1, 1, Conv, [128, 3, 2]]。
f為取ch[f]通道(ch保存通道,-1取上次通道數(shù));
m為調(diào)用模塊函數(shù),通常在common.py中;
n為網(wǎng)絡(luò)深度depth,使用max[1,int(n*depth_multiple)]賦值,即m結(jié)構(gòu)循環(huán)次數(shù);
args對應(yīng)[128, 3, 2],表示通道數(shù)args[0],該值會根據(jù)math.ceil(args[0]/8)*8調(diào)整,args[1]表示kernel大小,args[2]表示stride,
args[-2:]后2位為m模塊傳遞參數(shù);
C3模塊參數(shù)解讀
backbone的[-1, 3, C3, [128]]行作為解讀參考,在parse_model(d, ch)中表示,f, n, m, args=[-1, 1, Conv, [128, 3, 2]]。
f為取ch[f]通道(ch保存通道,-1取上次通道數(shù));
m為調(diào)用模塊函數(shù),通常在common.py中;
n為網(wǎng)絡(luò)深度depth,使用max[1,int(n*depth_multiple)]賦值,即m結(jié)構(gòu)循環(huán)次數(shù);
args對應(yīng)[128],表示通道數(shù)args[0]為c2,該值會根據(jù)math.ceil(args[0]/8)*8調(diào)整,決定當前層輸出通道數(shù)量,而后在parse_model中被下面代碼直接忽略,會被插值n=1,在C3代碼中表示循環(huán)次數(shù),
順便說下args對應(yīng)[128,False],在在C3中的False表示是否需要shotcut。
c1, c2 = ch[f], args[0]
if c2 != no: # if not outputc2 = make_divisible(c2 * gw, 8)
args = [c1, c2, *args[1:]]
# 通過模塊,更換n值
if m in [BottleneckCSP, C3, C3TR, C3Ghost]:args.insert(2, n) # number of repeatsn = 1
C3模塊代碼如下:
class C3(nn.Module):# CSP Bottleneck with 3 convolutionsdef __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5): # ch_in, ch_out, number, shortcut, groups, expansionsuper().__init__()c_ = int(c2 * e) # hidden channelsself.cv1 = Conv(c1, c_, 1, 1)self.cv2 = Conv(c1, c_, 1, 1)self.cv3 = Conv(2 * c_, c2, 1) # act=FReLU(c2)self.m = nn.Sequential(*[Bottleneck(c_, c_, shortcut, g, e=1.0) for _ in range(n)])# self.m = nn.Sequential(*[CrossConv(c_, c_, 3, 1, g, 1.0, shortcut) for _ in range(n)])def forward(self, x):return self.cv3(torch.cat((self.m(self.cv1(x)), self.cv2(x)), dim=1))
2、yaml的head解讀
head參數(shù)
head:[[-1, 1, Conv, [512, 1, 1]],[-1, 1, nn.Upsample, [None, 2, 'nearest']],[[-1, 6], 1, Concat, [1]], # cat backbone P4[-1, 3, C3, [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, C3, [256, False]], # 17 (P3/8-small)[-1, 1, Conv, [256, 3, 2]],[[-1, 14], 1, Concat, [1]], # cat head P4[-1, 3, C3, [512, False]], # 20 (P4/16-medium)[-1, 1, Conv, [512, 3, 2]],[[-1, 10], 1, Concat, [1]], # cat head P5[-1, 3, C3, [1024, False]], # 23 (P5/32-large)[[17, 20, 23], 1, Detect, [nc, anchors]], # Detect(P3, P4, P5)]
Concat模塊參數(shù)解讀
head的[[-1, 6], 1, Concat, [1]]行作為解讀參考,在parse_model(d, ch)中表示,f, n, m, args=[[-1, 6], 1, Concat, [1]]。
f為取ch[-1]與ch[6]通道數(shù)和,且6會被保存到save列表中,在forward中該列表對應(yīng)層模塊輸出會被保存
;
m為調(diào)用模塊函數(shù),通常在common.py中;
n為網(wǎng)絡(luò)深度depth,使用max[1,int(n*depth_multiple)]賦值,即m結(jié)構(gòu)循環(huán)次數(shù),但這里必然為1;
args對應(yīng)[1],表示通cat維度,這里為1,表示通道疊加;
Detect模塊參數(shù)解讀
head的[[17, 20, 23], 1, Detect, [nc, anchors]]行作為解讀參考,在parse_model(d, ch)中表示,f, n, m, args=[[17, 20, 23], 1, Detect, [nc, anchors]]。
f表示需要使用的層,并分別在17,20,23層獲取對應(yīng)通道,可通過yaml從backbone開始從0開始數(shù)的那一行,如17對應(yīng)[-1, 3, C3, [256, False]], # 17 (P3/8-small),
同時,17、20、23也會被保存到save列表中
;
m為調(diào)用模塊函數(shù),通常在common.py中;
n為網(wǎng)絡(luò)深度depth,使用max[1,int(n*depth_multiple)]賦值,即m結(jié)構(gòu)循環(huán)次數(shù),但這里必然為1;
args對應(yīng)[nc, anchors],表示去nc數(shù)量與anchor三個列表,同時會將f找到的通道作為列表添加到args中,如下代碼示意,
最終args大致為[80, [[10, 13, 16, 30, 33, 23], [30, 61, 62, 45, 59, 119], [116, 90, 156, 198, 373, 326]], [128, 256, 512]],
80為類別nc,[128, 256, 512]為f對應(yīng)的通道,其它為anchor值;
elif m is Detect:args.append([ch[x] for x in f])if isinstance(args[1], int): # number of anchorsargs[1] = [list(range(args[1] * 2))] * len(f)
四、模型構(gòu)建整體解讀
yolov5模型集成網(wǎng)絡(luò)代碼如下,其重要解讀已在代碼中注釋。
class Model(nn.Module):def __init__(self, cfg='yolov5s.yaml', ch=3, nc=None, anchors=None): # model, input channels, number of classessuper().__init__()if isinstance(cfg, dict):self.yaml = cfg # model dictelse: # is *.yamlimport yaml # for torch hubself.yaml_file = Path(cfg).namewith open(cfg, errors='ignore') as f:self.yaml = yaml.safe_load(f) # model dict# Define modelch = self.yaml['ch'] = self.yaml.get('ch', ch) # input channelsif 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 valueif anchors:LOGGER.info(f'Overriding model.yaml anchors with anchors={anchors}')self.yaml['anchors'] = round(anchors) # override yaml valueself.model, self.save = parse_model(deepcopy(self.yaml), ch=[ch]) # model, savelistself.names = [str(i) for i in range(self.yaml['nc'])] # default names,將其對應(yīng)數(shù)字轉(zhuǎn)為字符串self.inplace = self.yaml.get('inplace', True)# Build strides, anchors 以下為detect模塊設(shè)置參數(shù)m = self.model[-1] # Detect()if isinstance(m, Detect):s = 256 # 2x min stridem.inplace = self.inplace# 通過給定假設(shè)輸入為torch.zeros(1, ch, s, s)獲得stridem.stride = torch.tensor([s / x.shape[-2] for x in self.forward(torch.zeros(1, ch, s, s))]) # forwardm.anchors /= m.stride.view(-1, 1, 1) # 變換獲得每一特征層的anchorcheck_anchor_order(m)self.stride = m.stride # [8,16,32]self._initialize_biases() # only run once,為檢測detect設(shè)置bias初始化# Init weights, biasesinitialize_weights(self)self.info()LOGGER.info('')def forward(self, x, augment=False, profile=False, visualize=False):if augment:return self._forward_augment(x) # augmented inference, Nonereturn self._forward_once(x, profile, visualize) # single-scale inference, train
以上代碼最重要網(wǎng)絡(luò)搭建模塊,如下代碼調(diào)用,我將在下一節(jié)解讀。
self.model, self.save = parse_model(deepcopy(self.yaml), ch=[ch]) # model, savelist
以上代碼最重要網(wǎng)絡(luò)運行forward,如下代碼調(diào)用,我將重點解讀。
模型在訓(xùn)練時候,是調(diào)用下面模塊,如下:
return self._forward_once(x, profile, visualize) # single-scale inference, train
m.f和m.i已在上面yaml中介紹,實際需重點關(guān)注y保存save列表對應(yīng)的特征層輸出,若沒有則保留為none占位,其代碼解讀已在有注釋,詳情如下:
def _forward_once(self, x, profile=False, visualize=False):y, dt = [], [] # outputsfor m in self.model:# 通過m.f確定改變m模塊輸入變量值,若為列表如[-1,6]一般為cat或detect,一般需要給定輸入什么特征if m.f != -1: # if not from previous layer# 若m.f為[-1,6]這種情況,則[x if j == -1 else y[j] for j in m.f]運行此塊,該塊將-1變成了上一層輸出x與對應(yīng)6的輸出x = y[m.f] if isinstance(m.f, int) else [x if j == -1 else y[j] for j in m.f] # from earlier layersif profile:self._profile_one_layer(m, x, dt)x = m(x) # run# 通過之前parse_model獲得save列表(已賦值給self.save),將其m模塊輸出結(jié)果保存到y(tǒng)列表中,否則使用none代替位置# 這里m.i是索引,是yaml每行的模塊索引y.append(x if m.i in self.save else None) # save outputif visualize:feature_visualization(x, m.type, m.i, save_dir=visualize)return x
五、構(gòu)建模型parse_model源碼解讀
該部分是yolov5根據(jù)對應(yīng)yaml模型文件搭建的網(wǎng)絡(luò),需結(jié)合對應(yīng)yaml文件一起解讀,我已在上面介紹了yaml文件,可自行查看。
同時,也需要重點關(guān)注 m_.i, m_.f, m_.type, m_.np = i, f, t, np,會在上面_forward_once函數(shù)中用到。
本模塊代碼解讀,也已在代碼注釋中,請查看源碼理解,代碼如下:
def parse_model(d, ch): # model_dict, input_channels(3)LOGGER.info('\n%3s%18s%3s%10s %-40s%-30s' % ('', 'from', 'n', 'params', 'module', 'arguments'))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,獲得每個特征點anchor數(shù)量,為3no = na * (nc + 5) # 最終預(yù)測輸出數(shù)量, number of outputs = anchors * (classes + 5)# layers保存yaml每一行處理作為一層,使用列表保存,最后輸出使用nn.Sequential(*layers)處理作為模型層連接# c2為yaml每一行通道輸出預(yù)定義數(shù)量,需與width_multiple參數(shù)共同決定layers, save, c2 = [], [], ch[-1] # layers, savelist, ch out # ch為channel數(shù)量,初始值為[3]for i, (f, n, m, args) in enumerate(d['backbone'] + d['head']): # from, number, module, args# eval這個函數(shù)會把里面的字符串參數(shù)的引號去掉,把中間的內(nèi)容當成Python的代碼# i為每一層附帶索引,相當于對yaml每一行的模塊設(shè)置編號m = eval(m) if isinstance(m, str) else m # eval stringsfor j, a in enumerate(args):try:args[j] = eval(a) if isinstance(a, str) else a # eval stringsexcept NameError:passn = n_ = max(round(n * gd), 1) if n > 1 else n # 獲得最終深度,循環(huán)次數(shù),depth gain# 不同網(wǎng)絡(luò)結(jié)構(gòu)模塊處理,同時會改變對應(yīng)c2通道if m in [Conv, GhostConv, Bottleneck, GhostBottleneck, SPP, SPPF, DWConv, MixConv2d, Focus, CrossConv,BottleneckCSP, C3, C3TR, C3SPP, C3Ghost]: # 是否在設(shè)定模塊內(nèi)c1, c2 = ch[f], args[0]if c2 != no: # if not outputc2 = make_divisible(c2 * gw, 8)args = [c1, c2, *args[1:]]# 通過模塊,更換n值if m in [BottleneckCSP, C3, C3TR, C3Ghost]:args.insert(2, n) # number of repeatsn = 1elif m is nn.BatchNorm2d:args = [ch[f]]elif m is Concat:c2 = sum([ch[x] for x in f]) # 將最后一層通道數(shù)與cancat通道疊加求和,如[[-1, 6], 1, Concat, [1]]將-1與第6通道求和elif m is Detect:args.append([ch[x] for x in f])if isinstance(args[1], int): # number of anchorsargs[1] = [list(range(args[1] * 2))] * len(f)elif m is Contract:c2 = ch[f] * args[0] ** 2elif m is Expand:c2 = ch[f] // args[0] ** 2else:c2 = ch[f]m_ = nn.Sequential(*[m(*args) for _ in range(n)]) if n > 1 else m(*args) # modulet = str(m)[8:-2].replace('__main__.', '') # module typenp = sum([x.numel() for x in m_.parameters()]) # number params,計算參數(shù)量m_.i, m_.f, m_.type, m_.np = i, f, t, np # attach index, 'from' index, type, number params ,將其賦給模型,后面forward會使用到LOGGER.info('%3s%18s%3s%10.0f %-40s%-30s' % (i, f, n_, np, t, args)) # printsave.extend(x % i for x in ([f] if isinstance(f, int) else f) if x != -1) # append to savelistlayers.append(m_) #if i == 0:ch = [] # 刪除 輸入的3 通道ch.append(c2) # 保存每個模塊的通道,即yaml的每行均保存,包含concat啥都保存return nn.Sequential(*layers), sorted(save)