網(wǎng)站首頁 編程語言 正文
YOLOv5的Backbone設(shè)計(jì)
在上一篇文章《YOLOV5的anchor設(shè)定》中我們討論了anchor的產(chǎn)生原理和檢測過程,對YOLOv5的網(wǎng)絡(luò)結(jié)構(gòu)有了大致的了解。接下來,我們將聚焦于YOLOv5的Backbone,深入到底層源碼中體會v5的Backbone設(shè)計(jì)。
1 Backbone概覽及參數(shù)
# Parameters nc: 80 # number of classes depth_multiple: 0.33 # model depth multiple width_multiple: 0.50 # layer channel multiple # YOLOv5 v6.0 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 ]
yolov5s的backbone部分如上,其網(wǎng)絡(luò)結(jié)構(gòu)使用yaml文件配置,通過./models/yolo.py解析文件加了一個輸入構(gòu)成的網(wǎng)絡(luò)模塊。與v3和v4所使用的config設(shè)置的網(wǎng)絡(luò)不同,yaml文件中的網(wǎng)絡(luò)組件不需要進(jìn)行疊加,只需要在配置文件中設(shè)置number即可。
1.1 Param
# Parameters nc: 80 # number of classes depth_multiple: 0.33 # model depth multiple width_multiple: 0.50 # layer channel multiple
nc: 8
代表數(shù)據(jù)集中的類別數(shù)目,例如MNIST中含有0-9共10個類.
depth_multiple: 0.33
用來控制模型的深度,僅在number≠1時啟用。 如第一個C3層(c3具體是什么后續(xù)介紹)的參數(shù)設(shè)置為[-1, 3, C3, [128]]
,其中number=3,表示在v5s中含有1個C3(3*0.33);同理,v5l中的C3個數(shù)就是3(v5l的depth_multiple參數(shù)為1)。
width_multiple: 0.50
用來控制模型的寬度,主要作用于args中的ch_out。如第一個Conv層,ch_out=64,那么在v5s實(shí)際運(yùn)算過程中,會將卷積過程中的卷積核設(shè)為64x0.5,所以會輸出32通道的特征圖。
1.2 backbone
# YOLOv5 v6.0 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 ]
- from:-n代表是從前n層獲得的輸入,如-1表示從前一層獲得輸入
- number:表示網(wǎng)絡(luò)模塊的數(shù)目,如[-1, 3, C3, [128]]表示含有3個C3模塊
- model:表示網(wǎng)絡(luò)模塊的名稱,具體細(xì)節(jié)可以在./models/common.py查看,如Conv、C3、SPPF都是已經(jīng)在common中定義好的模塊
- args:表示向不同模塊內(nèi)傳遞的參數(shù),即[ch_out, kernel, stride, padding, groups],這里連ch_in都省去了,因?yàn)檩斎攵际巧蠈拥妮敵觯ǔ跏糲h_in為3)。為了修改過于麻煩,這里輸入的獲取是從./models/yolo.py的def parse_model(md, ch)函數(shù)中解析得到的。
1.3 Exp
[-1, 1, Conv, [64, 6, 2, 2]], # 0-P1/2
input:3x640x640
[ch_out, kernel, stride, padding]=[64, 6, 2, 2]
故新的通道數(shù)為64x0.5=32
根據(jù)特征圖計(jì)算公式:Feature_new=(Feature_old-kernel+2xpadding)/stride+1可得:
新的特征圖尺寸為:Feature_new=(640-6+2x2)/2+1=320
[-1, 1, Conv, [128, 3, 2]], # 1-P2/4
input:32x320x320
[ch_out, kernel, stride]=[128, 3, 2]
同理可得:新的通道數(shù)為64,新的特征圖尺寸為160
2 Backbone組成
v6.0版本的Backbone去除了Focus模塊(便于模型導(dǎo)出部署),Backbone主要由CBL、BottleneckCSP/C3以及SPP/SPPF等組成,具體如下圖所示:
3.1 CBS
CBS模塊其實(shí)沒什么好稀奇的,就是Conv+BatchNorm+SiLU,這里著重講一下Conv的參數(shù),就當(dāng)復(fù)習(xí)pytorch的卷積操作了,先上CBL源碼:
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) #其中nn.Identity()是網(wǎng)絡(luò)中的占位符,并沒有實(shí)際操作,在增減網(wǎng)絡(luò)過程中,可以使得整個網(wǎng)絡(luò)層數(shù)據(jù)不變,便于遷移權(quán)重?cái)?shù)據(jù);nn.SiLU()一種激活函數(shù)(S形加權(quán)線性單元)。 self.act = nn.SiLU() if act is True else (act if isinstance(act, nn.Module) else nn.Identity()) def forward(self, x):#正態(tài)分布型的前向傳播 return self.act(self.bn(self.conv(x))) def forward_fuse(self, x):#普通前向傳播 return self.act(self.conv(x))
由源碼可知:Conv()包含7個參數(shù),這些參數(shù)也是二維卷積Conv2d()中的重要參數(shù)。ch_in, ch_out, kernel, stride沒什么好說的,展開說一下后三個參數(shù):
padding
從我現(xiàn)在看到的主流卷積操作來看,大多數(shù)的研究者不會通過kernel來改變特征圖的尺寸,如googlenet中3x3的kernel設(shè)定了padding=1,所以當(dāng)kernel≠1時需要對輸入特征圖進(jìn)行填充。當(dāng)指定p值時按照p值進(jìn)行填充,當(dāng)p值為默認(rèn)時則通過autopad函數(shù)進(jìn)行填充:
def autopad(k, p=None): # kernel, padding # Pad to 'same' if p is None: p = k // 2 if isinstance(k, int) else [x // 2 for x in k] # auto-pad #如果k是整數(shù),p為k與2整除后向下取整;如果k是列表等,p對應(yīng)的是列表中每個元素整除2。 return p
這里作者考慮到對不同的卷積操作使用不同大小的卷積核時padding也需要做出改變,所以這里在為p賦值時會首先檢查k是否為int,如果k為列表則對列表中的每個元素整除。
groups
代表分組卷積,如下圖所示
groups – Number of blocked connections from input channels to output
- At groups=1, all inputs are convolved to all outputs.
- At groups=2, the operation becomes equivalent to having two conv layers side by side, each seeing half the input channels, and producing half the output channels, and both subsequently concatenated.
- At groups= in_channels, each input channel is convolved with its own set of filters, of size: ?(out_channels)/(in_channels)?.
act
決定是否對特征圖進(jìn)行激活操作,SiLU表示使用Sigmoid進(jìn)行激活。
one more thing:dilation
Conv2d中還有一個重要的參數(shù)就是空洞卷積dilation,通俗解釋就是控制kernel點(diǎn)(卷積核點(diǎn))間距的參數(shù),通過改變卷積核間距實(shí)現(xiàn)特征圖及特征信息的保留,在語義分割任務(wù)中空洞卷積比較有效。
3.2 CSP/C3
CSP即backbone中的C3,因?yàn)樵赽ackbone中C3存在shortcut,而在neck中C3不使用shortcut,所以backbone中的C3層使用CSP1_x表示,neck中的C3使用CSP2_x表示。
3.2.1 CSP結(jié)構(gòu)
接下來讓我們來好好梳理一下backbone中的C3層的模塊組成。先上源碼:
class C3(nn.Module): # CSP Bottleneck with 3 convolutions def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5): # ch_in, ch_out, number, shortcut, groups, expansion super().__init__() c_ = int(c2 * e) # hidden channels self.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))
從源碼中可以看出:輸入特征圖一條分支先經(jīng)過.cv1,再經(jīng)過.m,得到子特征圖1;另一分支經(jīng)過.cv2后得到子特征圖2。最后將子特征圖1和子特征圖2拼接后輸入.cv3得到C3層的輸出,如下圖所示。 這里的CV操作容易理解,就是前面的Conv2d+BN+SiLU,關(guān)鍵是.m操作。
.m操作使用nn.Sequential將多個Bottleneck(圖示中我以Resx命名)串接到網(wǎng)絡(luò)中,for loop中的n即網(wǎng)絡(luò)配置文件args中的number,也就是將number×depth_multiple個Bottleneck串接到網(wǎng)絡(luò)中。那么,Bottleneck又是個什么玩意呢?
3.2.2 Bottleneck
要想了解Bottleneck,還要從Resnet說起。在Resnet出現(xiàn)之前,人們的普遍為網(wǎng)絡(luò)越深獲取信息也越多,模型泛化效果越好。然而隨后大量的研究表明,網(wǎng)絡(luò)深度到達(dá)一定的程度后,模型的準(zhǔn)確率反而大大降低。這并不是過擬合造成的,而是由于反向傳播過程中的梯度爆炸和梯度消失。也就是說,網(wǎng)絡(luò)越深,模型越難優(yōu)化,而不是學(xué)習(xí)不到更多的特征。
為了能讓深層次的網(wǎng)絡(luò)模型達(dá)到更好的訓(xùn)練效果,殘差網(wǎng)絡(luò)中提出的殘差映射替換了以往的基礎(chǔ)映射。對于輸入x,期望輸出H(x),網(wǎng)絡(luò)利用恒等映射將x作為初始結(jié)果,將原來的映射關(guān)系變成F(x)+x。與其讓多層卷積去近似估計(jì)H(x) ,不如近似估計(jì)H(x)-x,即近似估計(jì)殘差F(x)。因此,ResNet相當(dāng)于將學(xué)習(xí)目標(biāo)改變?yōu)槟繕?biāo)值H(x)和x的差值,后面的訓(xùn)練目標(biāo)就是要將殘差結(jié)果逼近于0。
殘差模塊有什么好處呢?
1.梯度彌散方面。加入ResNet中的shortcut結(jié)構(gòu)之后,在反傳時,每兩個block之間不僅傳遞了梯度,還加上了求導(dǎo)之前的梯度,這相當(dāng)于把每一個block中向前傳遞的梯度人為加大了,也就會減小梯度彌散的可能性。
2.特征冗余方面。正向卷積時,對每一層做卷積其實(shí)只提取了圖像的一部分信息,這樣一來,越到深層,原始圖像信息的丟失越嚴(yán)重,而僅僅是對原始圖像中的一小部分特征做提取。這顯然會發(fā)生類似欠擬合的現(xiàn)象。加入shortcut結(jié)構(gòu),相當(dāng)于在每個block中又加入了上一層圖像的全部信息,一定程度上保留了更多的原始信息。
在resnet中,人們可以使用帶有shortcut的殘差模塊搭建幾百層甚至上千層的網(wǎng)絡(luò),而淺層的殘差模塊被命名為Basicblock(18、34),深層網(wǎng)絡(luò)所使用的的殘差模塊,就被命名為了Bottleneck(50+)。
Bottleneck與Basicblock最大的區(qū)別是卷積核的組成。 Basicblock由兩個3x3的卷積層組成,Bottleneck由兩個1x1卷積層夾一個3x3卷積層組成:其中1x1卷積層降維后再恢復(fù)維數(shù),讓3x3卷積在計(jì)算過程中的參數(shù)量更少、速度更快。
第一個1x1的卷積把256維channel降到64維,然后在最后通過1x1卷積恢復(fù),整體上用的參數(shù)數(shù)目:1x1x256x64 + 3x3x64x64 + 1x1x64x256 = 69632,而不使用bottleneck的話就是兩個3x3x256的卷積,參數(shù)數(shù)目: 3x3x256x256x2 = 1179648,差了16.94倍。
Bottleneck減少了參數(shù)量,優(yōu)化了計(jì)算,保持了原有的精度。
說了這么多,都是為了給CSP中的Bottleneck做前情提要,我們再回頭看CSP中的Bottleneck其實(shí)就更清楚了:
class Bottleneck(nn.Module): # Standard bottleneck def __init__(self, c1, c2, shortcut=True, g=1, e=0.5): # ch_in, ch_out, shortcut, groups, expansion super().__init__() c_ = int(c2 * e) # hidden channels self.cv1 = Conv(c1, c_, 1, 1) self.cv2 = Conv(c_, c2, 3, 1, g=g) self.add = shortcut and c1 == c2 def forward(self, x): return x + self.cv2(self.cv1(x)) if self.add else self.cv2(self.cv1(x))
可以看到,CSP中的Bottleneck同resnet模塊中的類似,先是1x1的卷積層(CBS),然后再是3x3的卷積層,最后通過shortcut與初始輸入相加。但是這里與resnet的不通點(diǎn)在于:CSP將輸入維度減半運(yùn)算后并未再使用1x1卷積核進(jìn)行升維,而是將原始輸入x也降了維,采取concat的方法進(jìn)行張量的拼接,得到與原始輸入相同維度的輸出。其實(shí)這里能區(qū)分一點(diǎn)就夠了:resnet中的shortcut通過add實(shí)現(xiàn),是特征圖對應(yīng)位置相加而通道數(shù)不變;而CSP中的shortcut通過concat實(shí)現(xiàn),是通道數(shù)的增加。二者雖然都是信息融合的主要方式,但是對張量的具體操作又不相同.
其次,對于shortcut是可根據(jù)任務(wù)要求設(shè)置的,比如在backbone中shortcut=True,neck中shortcut=False。
當(dāng)shortcut=True時,Resx如圖:
當(dāng)shortcut=False時,Resx如圖:
這其實(shí)也是YOLOv5為人稱贊的地方,代碼更體系、代碼冗余更少,僅需要指定一個參數(shù)便可以將Bottleneck和普通卷積聯(lián)合在一起使用,減少了代碼量的同時也使整體感觀得到提升。
3.3 SSPF
class SPPF(nn.Module): # Spatial Pyramid Pooling - Fast (SPPF) layer for YOLOv5 by Glenn Jocher def __init__(self, c1, c2, k=5): # equivalent to SPP(k=(5, 9, 13)) super().__init__() c_ = c1 // 2 # hidden channels self.cv1 = Conv(c1, c_, 1, 1) self.cv2 = Conv(c_ * 4, c2, 1, 1) self.m = nn.MaxPool2d(kernel_size=k, stride=1, padding=k // 2) def forward(self, x): x = self.cv1(x) with warnings.catch_warnings(): warnings.simplefilter('ignore') # suppress torch 1.9.0 max_pool2d() warning y1 = self.m(x) y2 = self.m(y1) return self.cv2(torch.cat([x, y1, y2, self.m(y2)], 1))
SSPF模塊將經(jīng)過CBS的x、一次池化后的y1、兩次池化后的y2和3次池化后的self.m(y2)先進(jìn)行拼接,然后再CBS提取特征。 仔細(xì)觀察不難發(fā)現(xiàn),雖然SSPF對特征圖進(jìn)行了多次池化,但是特征圖尺寸并未發(fā)生變化,通道數(shù)更不會變化,所以后續(xù)的4個輸出能夠在channel維度進(jìn)行融合。這一模塊的主要作用是對高層特征進(jìn)行提取并融合,在融合的過程中作者多次運(yùn)用最大池化,盡可能多的去提取高層次的語義特征。
YOLOv5s的Backbone總覽
最后,結(jié)合上述的講解應(yīng)該就不難理解v5s的backbone了
總結(jié)
原文鏈接:https://blog.csdn.net/weixin_43427721/article/details/123613944
相關(guān)推薦
- 2022-11-12 C語言字符串與字符數(shù)組面試題中最易錯考點(diǎn)詳解_C 語言
- 2022-05-21 C++多線程互斥鎖和條件變量的詳解_C 語言
- 2023-01-01 C語言用fun函數(shù)實(shí)現(xiàn)兩個數(shù)的交換方式_C 語言
- 2024-03-13 Linux 安裝RabbitMQ
- 2021-12-11 Linux環(huán)境下查看日志文件命令詳解_Linux
- 2022-06-13 ASP.NET?Core中的靜態(tài)文件介紹_實(shí)用技巧
- 2022-09-19 ASP.NET?Core模仿中間件方式實(shí)現(xiàn)列表過濾功能_實(shí)用技巧
- 2022-04-11 Android中圖片圓角三種實(shí)現(xiàn)方法_Android
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運(yùn)行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運(yùn)算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實(shí)現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡單動態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊(duì)列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支