<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
在上一篇文章《YOLOV5的anchor設定》中我們討論了anchor的產生原理和檢測過程,對YOLOv5的網路結構有了大致的瞭解。接下來,我們將聚焦於YOLOv5的Backbone,深入到底層原始碼中體會v5的Backbone設計。
# 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部分如上,其網路結構使用yaml檔案設定,通過./models/yolo.py解析檔案加了一個輸入構成的網路模組。與v3和v4所使用的config設定的網路不同,yaml檔案中的網路元件不需要進行疊加,只需要在組態檔中設定number即可。
# Parameters nc: 80 # number of classes depth_multiple: 0.33 # model depth multiple width_multiple: 0.50 # layer channel multiple
nc: 8
代表資料集中的類別數目,例如MNIST中含有0-9共10個類.
depth_multiple: 0.33
用來控制模型的深度,僅在number≠1時啟用。 如第一個C3層(c3具體是什麼後續介紹)的引數設定為[-1, 3, C3, [128]]
,其中number=3,表示在v5s中含有1個C3(3*0.33);同理,v5l中的C3個數就是3(v5l的depth_multiple引數為1)。
width_multiple: 0.50
用來控制模型的寬度,主要作用於args中的ch_out。如第一個Conv層,ch_out=64,那麼在v5s實際運算過程中,會將折積過程中的折積核設為64x0.5,所以會輸出32通道的特徵圖。
# 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 ]
[-1, 1, Conv, [64, 6, 2, 2]], # 0-P1/2
input:3x640x640
[ch_out, kernel, stride, padding]=[64, 6, 2, 2]
故新的通道數為64x0.5=32
根據特徵圖計算公式: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]
同理可得:新的通道數為64,新的特徵圖尺寸為160
v6.0版本的Backbone去除了Focus模組(便於模型匯出部署),Backbone主要由CBL、BottleneckCSP/C3以及SPP/SPPF等組成,具體如下圖所示:
CBS模組其實沒什麼好稀奇的,就是Conv+BatchNorm+SiLU,這裡著重講一下Conv的引數,就當複習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()是網路中的預留位置,並沒有實際操作,在增減網路過程中,可以使得整個網路層資料不變,便於遷移權重資料;nn.SiLU()一種啟用函數(S形加權線性單元)。 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))
由原始碼可知:Conv()包含7個引數,這些引數也是二維折積Conv2d()中的重要引數。ch_in, ch_out, kernel, stride沒什麼好說的,展開說一下後三個引數:
padding
從我現在看到的主流折積操作來看,大多數的研究者不會通過kernel來改變特徵圖的尺寸,如googlenet中3x3的kernel設定了padding=1,所以當kernel≠1時需要對輸入特徵圖進行填充。當指定p值時按照p值進行填充,當p值為預設時則通過autopad函數進行填充:
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是整數,p為k與2整除後向下取整;如果k是列表等,p對應的是列表中每個元素整除2。 return p
這裡作者考慮到對不同的折積操作使用不同大小的折積核時padding也需要做出改變,所以這裡在為p賦值時會首先檢查k是否為int,如果k為列表則對列表中的每個元素整除。
groups
代表分組折積,如下圖所示
groups – Number of blocked connections from input channels to output
act
決定是否對特徵圖進行啟用操作,SiLU表示使用Sigmoid進行啟用。
one more thing:dilation
Conv2d中還有一個重要的引數就是空洞折積dilation,通俗解釋就是控制kernel點(折積核點)間距的引數,通過改變折積核間距實現特徵圖及特徵資訊的保留,在語意分割任務中空洞折積比較有效。
CSP即backbone中的C3,因為在backbone中C3存在shortcut,而在neck中C3不使用shortcut,所以backbone中的C3層使用CSP1_x表示,neck中的C3使用CSP2_x表示。
接下來讓我們來好好梳理一下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))
從原始碼中可以看出:輸入特徵圖一條分支先經過.cv1,再經過.m,得到子特徵圖1;另一分支經過.cv2後得到子特徵圖2。最後將子特徵圖1和子特徵圖2拼接後輸入.cv3得到C3層的輸出,如下圖所示。 這裡的CV操作容易理解,就是前面的Conv2d+BN+SiLU,關鍵是.m操作。
.m操作使用nn.Sequential將多個Bottleneck(圖示中我以Resx命名)串接到網路中,for loop中的n即網路組態檔args中的number,也就是將number×depth_multiple個Bottleneck串接到網路中。那麼,Bottleneck又是個什麼玩意呢?
要想了解Bottleneck,還要從Resnet說起。在Resnet出現之前,人們的普遍為網路越深獲取資訊也越多,模型泛化效果越好。然而隨後大量的研究表明,網路深度到達一定的程度後,模型的準確率反而大大降低。這並不是過擬合造成的,而是由於反向傳播過程中的梯度爆炸和梯度消失。也就是說,網路越深,模型越難優化,而不是學習不到更多的特徵。
為了能讓深層次的網路模型達到更好的訓練效果,殘差網路中提出的殘差對映替換了以往的基礎對映。對於輸入x,期望輸出H(x),網路利用恆等對映將x作為初始結果,將原來的對映關係變成F(x)+x。與其讓多層折積去近似估計H(x) ,不如近似估計H(x)-x,即近似估計殘差F(x)。因此,ResNet相當於將學習目標改變為目標值H(x)和x的差值,後面的訓練目標就是要將殘差結果逼近於0。
殘差模組有什麼好處呢?
1.梯度彌散方面。加入ResNet中的shortcut結構之後,在反傳時,每兩個block之間不僅傳遞了梯度,還加上了求導之前的梯度,這相當於把每一個block中向前傳遞的梯度人為加大了,也就會減小梯度彌散的可能性。
2.特徵冗餘方面。正向折積時,對每一層做折積其實只提取了影象的一部分資訊,這樣一來,越到深層,原始影象資訊的丟失越嚴重,而僅僅是對原始影象中的一小部分特徵做提取。這顯然會發生類似欠擬合的現象。加入shortcut結構,相當於在每個block中又加入了上一層影象的全部資訊,一定程度上保留了更多的原始資訊。
在resnet中,人們可以使用帶有shortcut的殘差模組搭建幾百層甚至上千層的網路,而淺層的殘差模組被命名為Basicblock(18、34),深層網路所使用的的殘差模組,就被命名為了Bottleneck(50+)。
Bottleneck與Basicblock最大的區別是折積核的組成。 Basicblock由兩個3x3的折積層組成,Bottleneck由兩個1x1折積層夾一個3x3折積層組成:其中1x1折積層降維後再恢復維數,讓3x3折積在計算過程中的引數量更少、速度更快。
第一個1x1的折積把256維channel降到64維,然後在最後通過1x1折積恢復,整體上用的引數數目:1x1x256x64 + 3x3x64x64 + 1x1x64x256 = 69632,而不使用bottleneck的話就是兩個3x3x256的折積,引數數目: 3x3x256x256x2 = 1179648,差了16.94倍。
Bottleneck減少了引數量,優化了計算,保持了原有的精度。
說了這麼多,都是為了給CSP中的Bottleneck做前情提要,我們再回頭看CSP中的Bottleneck其實就更清楚了:
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的不通點在於:CSP將輸入維度減半運算後並未再使用1x1折積核進行升維,而是將原始輸入x也降了維,採取concat的方法進行張量的拼接,得到與原始輸入相同維度的輸出。其實這裡能區分一點就夠了:resnet中的shortcut通過add實現,是特徵圖對應位置相加而通道數不變;而CSP中的shortcut通過concat實現,是通道數的增加。二者雖然都是資訊融合的主要方式,但是對張量的具體操作又不相同.
其次,對於shortcut是可根據任務要求設定的,比如在backbone中shortcut=True,neck中shortcut=False。
當shortcut=True時,Resx如圖:
當shortcut=False時,Resx如圖:
這其實也是YOLOv5為人稱讚的地方,程式碼更體系、程式碼冗餘更少,僅需要指定一個引數便可以將Bottleneck和普通折積聯合在一起使用,減少了程式碼量的同時也使整體感觀得到提升。
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模組將經過CBS的x、一次池化後的y1、兩次池化後的y2和3次池化後的self.m(y2)先進行拼接,然後再CBS提取特徵。 仔細觀察不難發現,雖然SSPF對特徵圖進行了多次池化,但是特徵圖尺寸並未發生變化,通道數更不會變化,所以後續的4個輸出能夠在channel維度進行融合。這一模組的主要作用是對高層特徵進行提取並融合,在融合的過程中作者多次運用最大池化,儘可能多的去提取高層次的語意特徵。
最後,結合上述的講解應該就不難理解v5s的backbone了
到此這篇關於通過底層原始碼理解YOLOv5中Backbone的文章就介紹到這了,更多相關YOLOv5 Backbone詳解內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45