最近,
Scaled-YOLOv4
的作者(也是后來的YOLOR的作者)和YOLOv4
的作者AB大佬再次聯手推出了YOLOv7,目前來看,這一版的YOLOv7
是一個比較正統的YOLO續作,畢竟有AB大佬在,得到了過YOLO原作的認可。
網上已經有了很多文章去從各個方面來測試YOLOv7,但關于YOLOv7到底長什么樣,似乎還沒有多少人做出介紹。由于YOLOv7再一次平衡好了參數量、計算量和性能之間的矛盾,所以,筆者也想嘗試YOLOv7的網絡結構來削減模型的大小,因此,通過查看YOLOv7的config文件,勾勒出了YOLOv7的網絡結構,故而新開此章,斗膽將v7的網絡結構介紹給各位讀者。請注意,本文只介紹YOLOv7的網絡結構,其余的技術點如Aux Head是不會涉及到。這一部分,筆者放在了自己的github上,鏈接如下,筆者暫且將YOLOv7的backbone命名為ELAN-Net
。
https://github.com/yjh0410/image_classification_pytorch
一、YOLOv7的backbone結構
我們可以打開官方源碼中的yolov7.yaml
文件,看到如圖1所示的網絡配置。YOLOv7的項目是繼承自YOLOv5,事實上,YOLOv7的第一作者為YOLO社區做的貢獻,如Pytorch_YOLOv4、caled-YOLOv4、YOLOR等都沿用了YOLOv5的項目,很多超參幾乎就是拿來用了,包括這次的YOLOv7,畢竟YOLOv5項目久經考驗,是很適合在它的基礎上做改進,省去了調參的麻煩。另外,在上圖中,我們還能看見anchor box,盡管YOLOv7采用的label assignment使用的是SimOTA,但bbox regression還是基于anchor box的,往往在實際任務中,使用anchor box等人工先驗會對實際任務帶來些好處。
![d161dc88-3359-11ed-ba43-dac502259ad0.png](https://file1.elecfans.com//web2/M00/96/B6/wKgaomTnH4mAa_JiAAMPORoB_LM258.png)
這里,我們主要看backbone的部分,熟悉YOLOv5的讀者應該不難理解這種寫法,我們順著該配置即可勾勒出YOLOv7的backbone。我們詳細地來說一下,筆者會配合由筆者自己寫的pytorch代碼來幫助讀者理解。畢竟,官方代碼的可讀性實在是一言難盡。
首先,是最開始的stem層
,如圖2所示,就是簡單地堆疊三層Conv卷積,每個Conv就是YOLOv5采用的標準的“卷積+BN+SiLU
”三件套。相應的代碼也展示在了下方,筆者將該層命名為“layer1”,輸出一個二倍降采樣的特征圖:
![d19a81be-3359-11ed-ba43-dac502259ad0.png](https://file1.elecfans.com//web2/M00/96/B6/wKgaomTnH4mACcpjAAFPKloI6EE673.png)
#ELANNetofYOLOv7
classELANNet(nn.Module):
"""
ELAN-NetofYOLOv7.
"""
def__init__(self,depthwise=False,num_classes=1000):
super(ELANNet,self).__init__()
self.layer_1=nn.Sequential(
Conv(3,32,k=3,p=1,depthwise=depthwise),
Conv(32,64,k=3,p=1,s=2,depthwise=depthwise),
Conv(64,64,k=3,p=1,depthwise=depthwise)
)
接下來,YOLOv7再用一層步長為2的卷積得到4倍降采樣圖,然后接了一連串卷積處理這個4被降采樣特征圖,而這些“一連串”的卷積便是就是YOLOv7論文中介紹的ELAN模塊了,官方給出的配置如下所示,我們可以對應著論文原圖來一起看:
![d1beeda6-3359-11ed-ba43-dac502259ad0.png](https://file1.elecfans.com//web2/M00/96/B6/wKgaomTnH4mAPnQhAAFcFklw_3s321.png)
按照上面的結構,我們便可以繪制出YOLOv7的核心模塊:ELAN
的具體網絡結構了,相應的代碼也展示在了下方。請注意,ELAN的這種結構的一個優勢就是每個branch的操作中,輸入通道都是和輸出通道保持一致的,僅僅是最開始的兩個1x1卷積
是有通道變化的。關于輸入輸出通道相等的優勢,這一點早在shufflenet-v2中就已經論證過了,是一條設計網絡的高效準則之一。
classELANBlock(nn.Module):
"""
ELANBLockofYOLOv7'sbackbone
"""
def__init__(self,in_dim,out_dim,expand_ratio=0.5,depthwise=False):
super(ELANBlock,self).__init__()
inter_dim=int(in_dim*expand_ratio)
self.cv1=Conv(in_dim,inter_dim,k=1)
self.cv2=Conv(in_dim,inter_dim,k=1)
self.cv3=nn.Sequential(
Conv(inter_dim,inter_dim,k=3,p=1,depthwise=depthwise),
Conv(inter_dim,inter_dim,k=3,p=1,depthwise=depthwise)
)
self.cv4=nn.Sequential(
Conv(inter_dim,inter_dim,k=3,p=1,depthwise=depthwise),
Conv(inter_dim,inter_dim,k=3,p=1,depthwise=depthwise)
)
assertinter_dim*4==out_dim
self.out=Conv(inter_dim*4,out_dim,k=1)
defforward(self,x):
"""
Input:
x:[B,C,H,W]
Output:
out:[B,2C,H,W]
"""
x1=self.cv1(x)
x2=self.cv2(x)
x3=self.cv3(x2)
x4=self.cv4(x3)
#[B,C,H,W]->[B,2C,H,W]
out=self.out(torch.cat([x1,x2,x3,x4],dim=1))
returnout
![d1e1f38c-3359-11ed-ba43-dac502259ad0.png](https://file1.elecfans.com//web2/M00/96/B6/wKgaomTnH4mAL17tAAHU9f0dtvo981.png)
不過,筆者好奇能不能直接拆分通道,一分為二呢?最開始的CSPNet就是這么干的,不過,到了YOLO這里,就換成了1x1卷積來壓縮。最后,ELAN模塊輸出的通道數是輸入的2倍。
于是,網絡的第二層也就搭建出來了,第二層輸出的就是4倍降采樣的特征圖了,如下方的代碼所示:
self.layer_2=nn.Sequential(
Conv(64,128,k=3,p=1,s=2,depthwise=depthwise),
ELANBlock(in_dim=128,out_dim=256,expand_ratio=0.5,depthwise=depthwise)
)
接下來,YOLOv7對該4倍降采樣的特征圖再進行降采樣操作
,不過,不同于以往的步長為2的卷積那么簡單,YOLOv7這里稍微設計得精細了一些,如下圖中的紅框部分所示,左邊的分支主要采用maxpooling(MP)來實現空間降采樣,并緊跟一個1x1卷積壓縮通道;右邊先用1x1卷積壓縮通道,然后再用步長為2的3x3卷積完成降采樣,最后,將兩個分支的結果合并,通道一個通道數等于輸入通道數,但空間分辨率縮小2倍的特征圖。筆者暫且將其命名為“DownSample
”層。相應代碼已展示在下方。
![d20747ea-3359-11ed-ba43-dac502259ad0.png](https://file1.elecfans.com//web2/M00/96/B6/wKgaomTnH4qASdzEAAGEgat74xI747.png)
def __init__(self, in_dim):
super().__init__()
inter_dim = in_dim // 2
self.mp = nn.MaxPool2d((2, 2), 2)
self.cv1 = Conv(in_dim, inter_dim, k=1)
self.cv2 = nn.Sequential(
Conv(in_dim, inter_dim, k=1),
Conv(inter_dim, inter_dim, k=3, p=1, s=2)
)
def forward(self, x):
"""
Input:
x: [B, C, H, W]
Output:
out: [B, C, H//2, W//2]
"""
# [B, C, H, W] -> [B, C//2, H//2, W//2]
x1 = self.cv1(self.mp(x))
x2 = self.cv2(x)
# [B, C, H//2, W//2]
out = torch.cat([x1, x2], dim=1)
return out
隨后,綠框部分就是上面已經介紹過的ELAN模塊,對被降采樣的特征圖進行處理。自此往后,就是重復堆疊這兩塊,直到最后。那么,backbone的整體我們就全部了解了,整個backbone的代碼如下:
#ELANNetofYOLOv7
classELANNet(nn.Module):
"""
ELAN-NetofYOLOv7.
"""
def__init__(self,depthwise=False,num_classes=1000):
super(ELANNet,self).__init__()
self.layer_1=nn.Sequential(
Conv(3,32,k=3,p=1,depthwise=depthwise),
Conv(32,64,k=3,p=1,s=2,depthwise=depthwise),
Conv(64,64,k=3,p=1,depthwise=depthwise)#P1/2
)
self.layer_2=nn.Sequential(
Conv(64,128,k=3,p=1,s=2,depthwise=depthwise),
ELANBlock(in_dim=128,out_dim=256,expand_ratio=0.5,depthwise=depthwise)#P2/4
)
self.layer_3=nn.Sequential(
DownSample(in_dim=256),
ELANBlock(in_dim=256,out_dim=512,expand_ratio=0.5,depthwise=depthwise)#P3/8
)
self.layer_4=nn.Sequential(
DownSample(in_dim=512),
ELANBlock(in_dim=512,out_dim=1024,expand_ratio=0.5,depthwise=depthwise)#P4/16
)
self.layer_5=nn.Sequential(
DownSample(in_dim=1024),
ELANBlock(in_dim=1024,out_dim=1024,expand_ratio=0.25,depthwise=depthwise)#P5/32
)
self.avgpool=nn.AdaptiveAvgPool2d((1,1))
self.fc=nn.Linear(1024,num_classes)
defforward(self,x):
x=self.layer_1(x)
x=self.layer_2(x)
x=self.layer_3(x)
x=self.layer_4(x)
x=self.layer_5(x)
#[B,C,H,W]->[B,C,1,1]
x=self.avgpool(x)
#[B,C,1,1]->[B,C]
x=x.flatten(1)
x=self.fc(x)
returnx
當然,以上代碼中的avgpool和fc兩個層請忽略,在檢測任務里我們是不需要這兩部分的。這里需要說一下的是layer5,按照前面幾層的配置,layer5應該順其自然地輸出一個通道數為2048的32倍降采樣的圖,但這樣似乎會引來過多的計算量,YOLOv7就將其通道數還是控制在了1024。所以,C3、C4和C5的通道數就分別是512、1024和1024,不再是以往常見的256、512、1024了。Backbone的整體結構展示在了圖6中。
![d22bca3e-3359-11ed-ba43-dac502259ad0.png](https://file1.elecfans.com//web2/M00/96/B6/wKgaomTnH4qAGDGoAAED848OWMY285.png)
由于整個YOLOv7是采用了train from scratch
策略,超參沿用久經考驗的YOLOv5的,所以,backbone這一部門是沒有imagenet pre-trained的。不過,筆者在搭建了這個backbone后,在ImageNet上進行了預訓練,感興趣的讀者可以筆者提供的github的README中獲得預訓練模型的下載鏈接。
二、YOLOv7的PaFPN結構
接下來,我們介紹一下YOLOv7的FPN
結構。和之前的YOLOv4、YOLOv5一樣,YOLOv7仍采用PaFPN
結構。有了之前backbone的經驗,這一部分也就容易多了,我們先看一下官方給出的配置文件,如下圖所示:
![d24a0972-3359-11ed-ba43-dac502259ad0.png](https://file1.elecfans.com//web2/M00/96/B6/wKgaomTnH4qAeriGAAJ2MCIaeVs510.png)
首先,對于backbone最后輸出的32倍降采樣特征圖C5,我們先使用SPP
處理一下。這部分的SPP是由Scaled-YOLOv4提出的SPP-CSP
,在YOLOv5中已經被用到了,沒有變化。經過SPP的處理后,C5的通道數從1024縮減到512。隨后的過程和YOLOv5是一樣的,依循top down的路線,先后和C4、C3去融合,得到P3、P4和P5;再按照bottom-up的路線,再去和P4、P5做融合。唯一與YOLOv5不同的地方就是原先YOLOv5使用的BottleneckCSP被換成了YOLOv7的ELAN模塊。原先YOLOv5所使用的步長為2的下采樣卷積也換成了上面的YOLOv7設計的DownSample層。不過,Head中的ELAN和DownSample兩部分與Backbone中的這兩塊有些細微差別,具體結構下面的兩圖所示。相應的代碼筆者也給出了。
![d27e35c6-3359-11ed-ba43-dac502259ad0.png](https://file1.elecfans.com//web2/M00/96/B6/wKgaomTnH4qAGxWMAAHzGvyh0gg890.png)
![d2a01506-3359-11ed-ba43-dac502259ad0.png](https://file1.elecfans.com//web2/M00/96/B6/wKgaomTnH4qAM_PZAADcwFIn31E503.png)
classELANBlock(nn.Module):
"""
ELANBLockofYOLOv7'shead
"""
def__init__(self,in_dim,out_dim,expand_ratio=0.5,depthwise=False,act_type='silu',norm_type='BN'):
super(ELANBlock,self).__init__()
inter_dim=int(in_dim*expand_ratio)
inter_dim2=int(inter_dim*expand_ratio)
self.cv1=Conv(in_dim,inter_dim,k=1,act_type=act_type,norm_type=norm_type)
self.cv2=Conv(in_dim,inter_dim,k=1,act_type=act_type,norm_type=norm_type)
self.cv3=Conv(inter_dim,inter_dim2,k=3,p=1,act_type=act_type,norm_type=norm_type,depthwise=depthwise)
self.cv4=Conv(inter_dim2,inter_dim2,k=3,p=1,act_type=act_type,norm_type=norm_type,depthwise=depthwise)
self.cv5=Conv(inter_dim2,inter_dim2,k=3,p=1,act_type=act_type,norm_type=norm_type,depthwise=depthwise)
self.cv6=Conv(inter_dim2,inter_dim2,k=3,p=1,act_type=act_type,norm_type=norm_type,depthwise=depthwise)
self.out=Conv(inter_dim*2+inter_dim2*4,out_dim,k=1)
defforward(self,x):
"""
Input:
x:[B,C_in,H,W]
Output:
out:[B,C_out,H,W]
"""
x1=self.cv1(x)
x2=self.cv2(x)
x3=self.cv3(x2)
x4=self.cv4(x3)
x5=self.cv5(x4)
x6=self.cv6(x5)
#[B,C_in,H,W]->[B,C_out,H,W]
out=self.out(torch.cat([x1,x2,x3,x4,x5,x6],dim=1))
returnout
classDownSample(nn.Module):
def__init__(self,in_dim,depthwise=False,act_type='silu',norm_type='BN'):
super().__init__()
inter_dim=in_dim
self.mp=nn.MaxPool2d((2,2),2)
self.cv1=Conv(in_dim,inter_dim,k=1,act_type=act_type,norm_type=norm_type)
self.cv2=nn.Sequential(
Conv(in_dim,inter_dim,k=1,act_type=act_type,norm_type=norm_type),
Conv(inter_dim,inter_dim,k=3,p=1,s=2,act_type=act_type,norm_type=norm_type,depthwise=depthwise)
)
defforward(self,x):
"""
Input:
x:[B,C,H,W]
Output:
out:[B,2C,H//2,W//2]
"""
#[B,C,H,W]->[B,C//2,H//2,W//2]
x1=self.cv1(self.mp(x))
x2=self.cv2(x)
#[B,C,H//2,W//2]
out=torch.cat([x1,x2],dim=1)
returnout
整個PaFPN的融合過程如下圖所示,筆者在途中標記出了通道的變化,在PaFPN的最后,YOLOv7使用了兩層RepConv去調整最終輸出的P3、P4和P5的通道數。在最后,YOLOv7還是一如既往地使用三層1x1卷積去預測objectness、class和bbox三部分。注意,YOLOv7還是一如既往地采用coupled head,而非YOLOX中的decoupled head。因為decoupled head會帶來過多的參數量和計算量,性能提升很微小,性價比不高。Head部分的代碼,筆者也在下方給出了,感興趣的讀者可以查看。
![d2b2e4e2-3359-11ed-ba43-dac502259ad0.png](https://file1.elecfans.com//web2/M00/96/B6/wKgaomTnH4qADtouAAFMUyIY-9w060.png)
#PaFPN-ELAN(YOLOv7's)
classPaFPNELAN(nn.Module):
def__init__(self,
in_dims=[256,512,512],
out_dim=[256,512,1024],
depthwise=False,
norm_type='BN',
act_type='silu'):
super(PaFPNELAN,self).__init__()
self.in_dims=in_dims
self.out_dim=out_dim
c3,c4,c5=in_dims
#topdwon
##P5->P4
self.cv1=Conv(c5,256,k=1,norm_type=norm_type,act_type=act_type)
self.cv2=Conv(c4,256,k=1,norm_type=norm_type,act_type=act_type)
self.head_elan_1=ELANBlock(in_dim=256+256,
out_dim=256,
depthwise=depthwise,
norm_type=norm_type,
act_type=act_type)
#P4->P3
self.cv3=Conv(256,128,k=1,norm_type=norm_type,act_type=act_type)
self.cv4=Conv(c3,128,k=1,norm_type=norm_type,act_type=act_type)
self.head_elan_2=ELANBlock(in_dim=128+128,
out_dim=128,#128
depthwise=depthwise,
norm_type=norm_type,
act_type=act_type)
#bottomup
#P3->P4
self.mp1=DownSample(128,act_type=act_type,norm_type=norm_type,depthwise=depthwise)
self.head_elan_3=ELANBlock(in_dim=256+256,
out_dim=256,#256
depthwise=depthwise,
norm_type=norm_type,
act_type=act_type)
#P4->P5
self.mp2=DownSample(256,act_type=act_type,norm_type=norm_type,depthwise=depthwise)
self.head_elan_4=ELANBlock(in_dim=512+512,
out_dim=512,#512
depthwise=depthwise,
norm_type=norm_type,
act_type=act_type)
#RepConv
self.repconv_1=RepConv(128,out_dim[0],k=3,s=1,p=1)
self.repconv_2=RepConv(256,out_dim[1],k=3,s=1,p=1)
self.repconv_3=RepConv(512,out_dim[2],k=3,s=1,p=1)
defforward(self,features):
c3,c4,c5=features
#Topdown
##P5->P4
c6=self.cv1(c5)
c7=F.interpolate(c6,scale_factor=2.0)
c8=torch.cat([c7,self.cv2(c4)],dim=1)
c9=self.head_elan_1(c8)
##P4->P3
c10=self.cv3(c9)
c11=F.interpolate(c10,scale_factor=2.0)
c12=torch.cat([c11,self.cv4(c3)],dim=1)
c13=self.head_elan_2(c12)
#Bottomup
#p3->P4
c14=self.mp1(c13)
c15=torch.cat([c14,c9],dim=1)
c16=self.head_elan_3(c15)
#P4->P5
c17=self.mp2(c16)
c18=torch.cat([c17,c5],dim=1)
c19=self.head_elan_4(c18)
#RepCpnv
c20=self.repconv_1(c13)
c21=self.repconv_2(c16)
c22=self.repconv_3(c19)
out_feats=[c20,c21,c22]#[P3,P4,P5]
returnout_feats
三、結束語
那么,至此,YOLOv7的網絡結構就全部繪制出了,本文的目的也達到了。當然,YOLOv7還有E-ELAN
結構,這一點,筆者暫且不做介紹了,有了相關基礎,相信讀者們也不難自行分析YOLOv7的其他網絡結構。對于YOLOv7的其他技術點,如Aux Head、RepConv的設計、YOLOR中的隱性知識(Implicit knowledge)等,筆者就不做介紹了。
最后,感謝各位讀者的支持。對于YOLOv7的網絡結構,讀者有任何問題都可以在評論區留言,僅憑筆者的一己之見,不免會落入某種獨斷與偏見之中。
審核編輯:湯梓紅
-
網絡結構
+關注
關注
0文章
48瀏覽量
11202
原文標題:長文詳解YOLOv7的網絡結構
文章出處:【微信號:zenRRan,微信公眾號:深度學習自然語言處理】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
神經網絡結構搜索有什么優勢?
yolov7 onnx模型在NPU上太慢了怎么解決?
無法使用MYRIAD在OpenVINO trade中運行YOLOv7自定義模型怎么解決?
環形網絡,環形網絡結構是什么?
YOLOv7訓練自己的數據集包括哪些
![<b class='flag-5'>YOLOv7</b>訓練自己的數據集包括哪些](https://file1.elecfans.com/web2/M00/88/D0/wKgaomR0Ua2AUJLtAAA4xM8F8GY592.png)
一文徹底搞懂YOLOv8【網絡結構+代碼+實操】
![一文徹底搞懂<b class='flag-5'>YOLOv</b>8【<b class='flag-5'>網絡結構</b>+代碼+實操】](https://file1.elecfans.com/web2/M00/89/CA/wKgaomSK1wqAawBpAAABOfRW73Q341.png)
一文徹底搞懂YOLOv8(網絡結構+代碼+實操)
![一文徹底搞懂<b class='flag-5'>YOLOv</b>8(<b class='flag-5'>網絡結構</b>+代碼+實操)](https://file1.elecfans.com/web2/M00/8A/14/wKgaomSQGHOAGjVCAABhhez6Euw079.png)
yolov5和YOLOX正負樣本分配策略
![<b class='flag-5'>yolov</b>5和YOLOX正負樣本分配策略](https://file1.elecfans.com/web2/M00/90/65/wKgZomTZpAGAL2OrAAALvAFuRhg212.jpg)
使用OpenVINO優化并部署訓練好的YOLOv7模型
![使用OpenVINO優化并部署訓練好的<b class='flag-5'>YOLOv7</b>模型](https://file1.elecfans.com/web2/M00/9E/9E/wKgZomToHDqANerlAAA53hyTfak487.png)
深度學習YOLOv3 模型設計的基本思想
![<b class='flag-5'>深度</b>學習<b class='flag-5'>YOLOv</b>3 模型設計的基本思想](https://file1.elecfans.com/web2/M00/AA/44/wKgZomUt8xKATr0UAAAO3MiYG44917.png)
詳細解讀YOLOV7網絡架構設計
![詳細解讀<b class='flag-5'>YOLOV7</b><b class='flag-5'>網絡</b>架構設計](https://file1.elecfans.com/web2/M00/B3/18/wKgZomVkA2-AJ88FAABcE9s2k-s437.png)
評論