大改ShuffleNetV2網絡,注意力機制,csp,卷積裁剪

大改ShuffleNetV2網絡,注意力機制,csp,卷積裁剪

1.背景

在移動端場景中,目前有很多不錯的輕量級網絡可以選擇,例如google的mobilenet系列,efficient lite系列,曠世的shufflenet系列,華爲的ghostnet等。在這些模型中,我尤其喜歡shufflenetV2,因爲它結構清晰,簡潔(如下圖所示爲shufflenetv2 1.0x的結構圖:紅色標註爲各個模塊的flops),並且在設計之初就考慮了MAC(訪存代價),使得在移動端部署的時候具有很低的延時。在多個數據集上的測試也驗證了其較好的泛化性能。不過有時候爲了實現在更低計算資源的嵌入式場景中使用shufflenet,或者在輕量級檢測框架中使用shufflenet作爲bakbone,那麼仍然需要對shufflenet做一些優化。

本次實踐將圍繞對shufflenetV2 1.0X版本進行改進,包括提升精度降低計算量兩個維度。看看能否創造出一個比基線版本更優的shufflenet結構。在不加註釋的情況下,下文的shufflenet默認指的是shufflenet v2 1.0x版本。

2、提升精度措施

結合目前比較火的注意力機制,例如senet,sknet等,這裏分別設計四個網絡:

增加se注意力機制的:shufflenet_se網絡

增加sk注意力機制的:shufflenet_sk網絡

將3*3的depthwise卷積擴大爲5*5:shufflenet_k5網絡

3、降低計算量

使用卷積裁剪以及csp技術,分別設計兩個網絡 :

裁剪block中不重要的1*1卷積,這裏設計一個shufflenet_liteconv網絡

使用csp對網絡進行重組,這裏設計一個shufflenet_csp網絡

shufflenet_se網絡

主要是借鑑senet中的通道注意力機制,將其應用到shufflenet中。下面簡單回顧一下senet中的注意力機制:

主要有兩個操作:squeeze和excitation。squeeze指的是將特徵通過GAP壓縮空間信息,將原來的c*h*w維度信息壓縮爲c*1*1. Excitation使用了兩個全連接層,第一個降低維度,將c*1*1降維爲c/r *1*1(帶有relu激活),第二個fc層將特徵重新映射回c*1*1(沒有relu激活),然後經過sigmoid之後得到各個通道的權重係數。然後將權重係數與原來的特徵相乘,從而得到一個新的特徵。下面是seattention的pytroch代碼實現,非常的簡單,就是按照上面的思路實現一遍。

 
  1. class SeAttention(nn.Module):

  2. def __init__(self, channel_num, r=4):

  3. """ Constructor

  4. """

  5. super(SeAttention, self).__init__()

  6. self.channel_num = channel_num

  7. self.r = r

  8. self.inter_channel = int( float(self.channel_num) / self.r)

  9. self.fc_e1 = torch.nn.Linear(channel_num, self.inter_channel)

  10. self.relu_e1 = nn.ReLU(inplace=True)

  11. self.fc_e2 = torch.nn.Linear(self.inter_channel, channel_num)

  12.  
  13. def forward(self, x):

  14. y = torch.nn.functional.adaptive_avg_pool2d(x, (1, 1)).squeeze()

  15. y = self.fc_e1(y)

  16. y = self.relu_e1(y)

  17. y = self.fc_e2(y)

  18. y = torch.sigmoid(y).unsqueeze(-1).unsqueeze(-1)

  19. return x*y

  20.  
  21.  

shufflenet_sk網絡

主要是借鑑了sknet中的注意力機制,用於選擇大卷積核的輸出還是小卷積核的輸出。其原理圖如下所示:

將特徵X分別經過小卷積核(3*3)得到新的特徵U3*3(圖中黃色部分);另外一路經過大卷積核(5*5)得到新的特徵U5*5(圖中綠色部分)。然後加兩個新的特徵U相加,然後經過SE相同的squeeze和excitation得到權重向量,注意這裏的權重向量有兩個,一個是針對U3*3的權重向量,另一個是針對U5*5的權重向量。最後用權重向量分別與對應的U相乘,最後相加得到新的特徵輸出V。主要代碼如下:

 
  1. class SKConv(nn.Module):

  2. def __init__(self, in_channels):

  3. """ Constructor

  4. Args:

  5. in_channels: input channel dimensionality.

  6. M: the number of branchs.

  7.         """

  8. super(SKConv, self).__init__()

  9. r = 2.0

  10. L = 32

  11. d = max(int(in_channels / r), L)

  12. self.in_channels = in_channels

  13. self.conv1 = nn.Sequential(

  14. self.depthwise_conv(in_channels, in_channels, kernel_size=3, dilation=1, padding=1),

  15. nn.BatchNorm2d(in_channels),

  16. #nn.ReLU(inplace=False)

  17. )

  18. self.conv2 = nn.Sequential(

  19. self.depthwise_conv(in_channels, in_channels, kernel_size=3, dilation=2, padding=2),

  20. nn.BatchNorm2d(in_channels),

  21. # nn.ReLU(inplace=False)

  22.             )

  23.         self.fc1 = nn.Linear(in_channels, d)

  24. self.fc2 = nn.Linear(d, in_channels*2)

  25. self.softmax = nn.Softmax(dim=1)

  26.  
  27.  
  28.    def forward(self, x):

  29.     U1 = self.conv1(x)

  30.     U2 = self.conv2(x)

  31.        U = U1 + U2

  32.        S = U.mean(-1).mean(-1)

  33.        Z1 = self.fc1(S)

  34.        Z2 = self.fc2(Z1)

  35.        A = self.softmax(Z2)

  36.        V = U1 * A[:,:self.in_chanels].unsqueeze(-1).unsqueeze(-1) + \

  37.            U2 * A[:,self.in_channels:].unsqueeze(-1).unsqueeze(-1)

  38.        return V

shufflenet_liteconv網絡

通過觀察shufflenet的block,可以分爲兩種結構,一種是每個stage的第一個block,該block由於需要降採樣,升維度,所以對輸入直接複製成兩份,經過branch1,和branch2之後再concat到一起,通道翻倍,如下圖中的降採樣block所示。另外一種普通的block將輸入split成兩部分,一部分經過branch2的卷積提取特徵後直接與branch1的部分進行concat。如下圖中的普通block所示:

一般在DW卷積(depthwise卷積)的前或後使用1*1的卷積處於兩種目的,一種是融合通道間的信息,彌補dw卷積對通道間信息融合功能的缺失。另一種是爲了降維升維,例如mobilenet v2中的inverted reddual模塊。而shufflenet中的block,在branch2中用了2個1*1卷積,實際上有一些多餘,因爲此處不需要進行升維降維的需求,那麼只是爲了融合dw卷積的通道間信息。實際上有一個1*1卷積就夠了。爲了剪裁的方便,將上圖中的紅色虛線框中的1*1卷積刪除。實現shufflenet_liteconv版本。

shufflenet_k5網絡

進一步觀察shufflenet的計算量分佈,實際上在dw卷積上的計算量佔比是很小的,主要的計算量都在1*1的卷積上面。因此對dw卷積進行一個卷積核的擴張,既不會增加太多的計算比重,又能提升效果,何樂而不爲呢。Shufflenet_k5將所有的3*3dw卷積替換成5*5的dw卷積,注意在pytroch實現中,需要將padding從1修改爲2,這樣輸出的特徵圖才能保持與原來一致的分辨率。

shufflenet_csp網絡

csp在大型網絡上取得了很大的成功。它在每個stage,將輸入split成兩部分,一部分經過原來的路徑,另一部分直接shortcut到stage的尾部,然後concat到一起。這既降低了計算量,又豐富了梯度信息,減少了梯度的重用,是一個非常不錯的trip。在yolov4,yolov5的目標檢測中,也引入了csp機制,使用了csp_darknet。此處將csp引入到shufflenet中。並且對csp做了一定的精簡,最終使用csp stage精簡版本作爲最終的網絡結構。

4、實驗測試

對上述:shufflenet_se、shufflenet_sk、shufflenet_liteconv、shufflenet_k5、shufflenet_csp在cifar10數據集上進行訓練,統計其最終的精度和flops,並與baseline的shufflenet進行對比,結果如下表所示:

網絡結構

Top1

實測flops

(M)

參數規模

(M)

模型文件大小

shufflenet(baseline)

0.880

147.79

1.263

5116K

shufflenet_se

0.884

147.93

1.406

5676K

shufflenet_sk

0.885

152.34

1.555

6872K

shufflenet_liteconv

0.879

104.77

0.923

3736K

shufflenet_k5

0.891

155.18

1.303

5268K

shufflenet_csp

0.862

101.96

0.924

3776K

對其中最重要的指標flops與top1精度進行可視化:

5、總結

從提升精度的角度看,shufflenet_k5的效果是最好的,比注意力機制的se和sk都要好。說明在輕量級網絡中,注意力機制的收益並沒有那麼明顯,沒有直接提升dw卷積的卷積核來的收益高。最降低計算量方面,精簡1*1卷積在幾乎不降低精度的情況下,大約降低了約30%的計算量。Shufflenet_csp網絡雖然能大幅降低計算量,但是精度降低的也很明顯。分析原因,主要有兩個,一是shufflenetv2本身已經使用了在輸入通道split,然後concat的blcok流程,與csp其實是一樣的,知識csp是基於一個stage,shufflenetv2是基於一個block,另外csp本來就是在densenet這種密集連接的網絡上使用有比較好的效果,在輕量級網絡上不見得效果會好。綜上所述,如果爲了提升精度,將卷積核擴大是最簡單也最有效的,並且計算量只提升了5%。如果爲了降低計算量,那麼通過剪裁1*1卷積效果是最好的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章