大改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代碼實現,非常的簡單,就是按照上面的思路實現一遍。
-
class SeAttention(nn.Module):
-
def __init__(self, channel_num, r=4):
-
""" Constructor
-
"""
-
super(SeAttention, self).__init__()
-
self.channel_num = channel_num
-
self.r = r
-
self.inter_channel = int( float(self.channel_num) / self.r)
-
self.fc_e1 = torch.nn.Linear(channel_num, self.inter_channel)
-
self.relu_e1 = nn.ReLU(inplace=True)
-
self.fc_e2 = torch.nn.Linear(self.inter_channel, channel_num)
-
def forward(self, x):
-
y = torch.nn.functional.adaptive_avg_pool2d(x, (1, 1)).squeeze()
-
y = self.fc_e1(y)
-
y = self.relu_e1(y)
-
y = self.fc_e2(y)
-
y = torch.sigmoid(y).unsqueeze(-1).unsqueeze(-1)
-
return x*y
shufflenet_sk網絡
主要是借鑑了sknet中的注意力機制,用於選擇大卷積核的輸出還是小卷積核的輸出。其原理圖如下所示:
將特徵X分別經過小卷積核(3*3)得到新的特徵U3*3(圖中黃色部分);另外一路經過大卷積核(5*5)得到新的特徵U5*5(圖中綠色部分)。然後加兩個新的特徵U相加,然後經過SE相同的squeeze和excitation得到權重向量,注意這裏的權重向量有兩個,一個是針對U3*3的權重向量,另一個是針對U5*5的權重向量。最後用權重向量分別與對應的U相乘,最後相加得到新的特徵輸出V。主要代碼如下:
-
class SKConv(nn.Module):
-
def __init__(self, in_channels):
-
""" Constructor
-
Args:
-
in_channels: input channel dimensionality.
-
M: the number of branchs.
-
"""
-
super(SKConv, self).__init__()
-
r = 2.0
-
L = 32
-
d = max(int(in_channels / r), L)
-
self.in_channels = in_channels
-
self.conv1 = nn.Sequential(
-
self.depthwise_conv(in_channels, in_channels, kernel_size=3, dilation=1, padding=1),
-
nn.BatchNorm2d(in_channels),
-
#nn.ReLU(inplace=False)
-
)
-
self.conv2 = nn.Sequential(
-
self.depthwise_conv(in_channels, in_channels, kernel_size=3, dilation=2, padding=2),
-
nn.BatchNorm2d(in_channels),
-
# nn.ReLU(inplace=False)
-
)
-
self.fc1 = nn.Linear(in_channels, d)
-
self.fc2 = nn.Linear(d, in_channels*2)
-
self.softmax = nn.Softmax(dim=1)
-
def forward(self, x):
-
U1 = self.conv1(x)
-
U2 = self.conv2(x)
-
U = U1 + U2
-
S = U.mean(-1).mean(-1)
-
Z1 = self.fc1(S)
-
Z2 = self.fc2(Z1)
-
A = self.softmax(Z2)
-
V = U1 * A[:,:self.in_chanels].unsqueeze(-1).unsqueeze(-1) + \
-
U2 * A[:,self.in_channels:].unsqueeze(-1).unsqueeze(-1)
-
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卷積效果是最好的。