這天,老板跟你說,希望能在手機上跑深度神經網絡,並且准確率要和 VGG、GoogleNet 差不多。
接到這個任務后你有點懵逼,這些網絡別說計算量大,就連網絡參數也要 100MB 的空間才存得下,放在手機上跑?開玩笑唄。
老板又說,怎么實現是你的事,我要的只是這個功能。
你默默地點了點頭。
初步嘗試:MobileNet v1
問題出在哪
要在手機上跑深度網絡,需要在模型參數和計算量上進行優化。
那深度神經網絡的計算量和參數量主要體現在哪呢?這里以 VGG16 為例:

第一層卷積: [224 x 224 x 3] --> [224 x 224 x 64],卷積核大小為 3 x 3(簡單起見,這里計算量的計算忽略激活函數)
計算量為:\(3 \times 3 \times 3 \times 224 \times 224 \times 64 \approx 8.7 \times 10^7\)
參數量為:\(3 \times 3 \times 3 \times 64 = 1728\)
第二層卷積:[112 x 112 x 64] --> [112 x 112 x 128],卷積核大小為 3 x 3。
計算量為:\(3 \times 3 \times 64 \times 112 \times 112 \times 128 \approx 9.2 \times 10^8\)
參數量為:\(3 \times 3 \times 64 \times 128 = 73728\)
......
第一層全連接層:[14 x 14 x 512] --> [4096]。
計算量為:\(14 \times 14 \times 512 \times 4096 \approx 4.1 \times 10^8\)
參數量為:\(4096 \times 1000 = 4096000\)
......
兩相對比,同時考慮到網絡中卷積層比全連層多,就不難發現深度卷積網絡中的計算量主要由卷積層承包,而參數則集中在全鏈接層。因此,要想對模型做優化,可以在卷積層的計算上做點手腳,同時減小全連接層的維度。
Separable Convolution
雖然找到了問題所在,但具體要如何優化卷積層的計算量呢?幸運的是,你在搜索的過程中發現已經有人針對這個問題給出了解決方案:Separable Convolution。這是一種對卷積運算進行分解的方法。
以下例子摘自文末鏈接:卷積神經網絡中的Separable Convolution
假設現在需要做這樣一個卷積操作:[64 x 64 x 3] --> [64 x 64 x 4],那么通常的操作是這樣的(假設卷積核大小為 3 x 3):

這種做法的計算量為:\(3 \times 3 \times 3 \times 64 \times 64 \times 4 = 442368\),
參數量為:\(3 \times 3 \times 3 \times 4 = 108\)。
而 Separable Convolution 會將該操作分解為兩步:Depthwise Convolution 和 Pointwise Convolution。
Depthwise Convolution 的過程其實非常簡單,顧名思義,Depthwise 就是每個通道單獨做一遍卷積:

這種做法的效果是:[64 x 64 x 3] --> [64 x 64 x 3],由於是 Depthwise 的,所以只需要三個 [3 x 3 x 1] 的 filter 即可。
因此計算量為:\(3 \times 3 \times 64 \times 64 \times 3=110592\),
參數量為:\(3 \times 3 \times 3 = 27\)。
不過 Depthwise 將不同通道之間的聯系斷開了,而且輸出的通道數與輸入是一樣的。為了得到 [64 x 64 x 4] 的輸出,還需要經過 Pointwise Convolution。
Pointwise Convolution 的過程在 Depthwise 之后進行,它是用一個 [1 x 1] 的卷積核把 [64 x 64 x 3] 的 feature map 轉換為 [64 x 64 x 4]:

計算量為:\(1 \times 1 \times 64 \times 64 \times 3 \times 4=49152\),
參數量為:\(1 \times 1 \times 3 \times 4 = 12\)。
我們發現,通過 Separable Convolution 這種分解的方法也可以拼湊出一個 [64 x 64 x 4] 的 feature map,
而這種方法的計算量為:\(110592 + 49152=159744\),而總的參數量為:\(27 + 12 = 39\)。
對比原先的 442368 (計算量) 和 108 (參數量),簡直實惠了好多。
於是,你通過這種套路構造出了一個適合手機端運行的深度網絡,並簡化了全連接層的參數:

圖中的 Conv dw 指的就是 Depthwise Convolution。由於是為手機設計的網絡,因此你取了個形象的名字:Mobilenet。
不過,這個網絡的精度會不會下降呢?你趕緊在 ImageNet 數據集上做了實驗:

這個結果實在是太感人了,精度幾乎和 GoogleNet 相當,但計算量卻只有后者的三分之一,參數量也減少了三分之一(當然也可能是圖像分類這個問題相對簡單)。
為了方便對模型大小的進一步調整,你提供了兩個額外的參數: \(\alpha\)、 \(\rho\)。\(\alpha\) 又稱為 Width Multiplier,主要用來控制 feature map 的 channel 數目,因為在某些任務中,很多 feature map 的 channel 可能包含很少的信息,因此少一些,而有些情況可能需要更多的 channel。\(\alpha=1\) 時就是上文中提出的基准網絡。\(\rho\) 則是圖像的分辨率,由它控制輸入圖片的大小。
進階:ShuffleNet v1
Separable Convolution 其實就是 MobileNet v1 的精華了,個人認為,MobileNet v1 能取得成功主要還是那些大網絡在處理簡單任務時存在大量的冗余,所以 MobileNet v1 用更少的參數量拼湊出同樣大小的 feature map 時,性能並沒有明顯下降。
而 ShuffleNet v1 則是在此基礎上進一步壓榨卷積操作,它的重點放在了 Pointwise Convolution 上。Pointwise Convolution 的作用是把 feature map 的所有 channel 信息聯系起來,但這種聯系可能本身就存在冗余。舉個例子,一個 [64 x 64 x 4] 的 feature map,通過 [1 x 1 x 4] 的卷積核后,可以得到 [64 x 64 x 1] 的輸出,這個 [1 x 1 x 4] 的卷積核其實就是把原來 feature map 上每個位置的所有 channel 信息(一個 [1 x 1 x 4] 的通道向量)進行加權求和,得到下一層 feature map 上的一個點。不過,真的有必要融和整個通道向量的信息嗎?如果只對兩個通道的信息進行相加,得到的結果會比四個通道差嗎?為了探究這個問題,煉丹師們把原來的 Pointwise Convolution 改造成了 Group Convolution,這個 Group Convolution 其實也不是什么新鮮玩意,當年 AlexNet 剛出來的時候,由於顯存不足,就曾將卷積操作分為兩組,用兩張顯卡來裝 feature map,這種做法導致更少的參數量和計算量,而且在某些任務中並不會對性能產生很大影響。ShuffleNet v1 的煉丹師顯然發現了這一點。
Group Convolution 的操作非常簡單,還是舉之前的例子:一個 [64 x 64 x 4] 的 feature map,要想進一步得到 [64 x 64 x 2] 的 feature map,直接用 Pointwise Convolution 處理的話,需要一個 [1 x 1 x 4 x 2] 的卷積張量。但用上 Group Convolution 后,我們可以這樣操作,用一個 [1 x 1 x 2 x 1] 的卷積張量對原來 feature map 四層通道中的前面兩層進行卷積操作,得到一個 [64 x 64 x 1] 的 feature map,之后,用另一個 [1 x 1 x 2 x 1] 的卷積張量繼續對后面兩層進行卷積操作,同樣得到一個 [64 x 64 x 1] 的 feature map,這兩塊 feature map 拼在一起,最終得到一個 [64 x 64 x 2] 的 feature map。
仔細數數,原來 Pointwise Convolution 的計算量為:\(1 \times 1 \times 64 \times 64 \times 4 \times 2=32768\),參數量為:\(1 \times 1 \times 4 \times 2=8\),而現在拆成 Group Convolution 后,計算量為:\(1 \times 1 \times 64 \times 64 \times 2 \times 2=16384\),參數量為:\(1 \times 1 \times 2 \times 2=4\),計算量和參數量都減少了一半。
雞賊的讀者可能還發現,如果把 Group Convolution 做到極致,每個 Group 只有一個 channel 的話,就變成 Depthwise + Pointwise Convolution 了,哈哈,原來又是拼湊游戲,笑出聲。
不過,僅僅用 Group Convolution,說性能不會影響很多人是不信的,畢竟本身就是 Pointwise Convolution,相鄰點之間的信息已經忽略了,要是通道上的信息也忽略太多,難免會存在問題。所以,ShuffleNet v1 的 Shuffle 該登場了。煉丹師為了增強 Group Convolution 的魯棒性,在對通道進行相加時,故意打亂了通道順序。這樣一來,在上面的例子中,本來是 1、2 通道結合得到一個新的點,就變成了 1、3 通道結合,2、4 通道結合了。
這也就是這篇論文的精華所在:

當然啦,估計是考慮到 Group Convolution 本身損失的信息有點嚴重,論文又特意加了 ResNet 中的短路連接,算是彌補了一點信息:

下圖給出的是論文中關於 Shuffle 操作的實驗:

Cls err 是 ImageNet 數據集上的錯誤分類率,數值越小證明結果越好,g 則表示 group 的數量。實驗結果給出這樣一個信息:當 group 的數量越多時,shuffle 的作用也越明顯。這一點也很好理解,因為 group 越多,丟失的信息也越多,這時如果能把 channel 打散,那么不同組之間的 channel 信息就有了交流的通道,能在一定程度上增加魯棒性。
總結
總的來說,MobileNet v1 作為第一個進行手機端優化的工作,其亮點主要是 Depthwise Convolution 和 Pointwise Convolution。ShuffleNet v1 則是在 MobileNet v1 的基礎上加入了 Group Convolution,並通過 Shuffle 的方法提高魯棒性,同時加入短路連接保持網絡的表達能力。