一.概述
MobileNetV1網絡是一條路的單通道結構,沒有feature map的複用。ResNet
和DenseNet
等網絡的提出,也驗證了feature map複用對提升網絡性能的有效性,MobileNetV2便應運而生。MobileNetV2提出使用 inverted residual with linear bottleneck,有很大residual block的影子在裏面。接下來便介紹一下MobileNetV2Linear Bottlenecks
和Inverted residuals
這兩個核心創新點。
當我們單獨去看Feature Map的每個通道的像素的值的時候,其實這些值代表的特徵可以映射到一個低維子空間的一個流形區域上。在進行完卷積操作之後往往會接一層激活函數來增加特徵的非線性性,一個最常見的激活函數便是ReLU。根據我們在殘差網絡中介紹的數據處理不等式(DPI),ReLU一定會帶來信息損耗,而且這種損耗是沒有辦法恢復的,ReLU的信息損耗是當通道數非常少的時候更爲明顯。爲什麼這麼說呢?我們看圖6中這個例子,其輸入是一個表示流形數據的矩陣,和卷機操作類似,他會經過 n 個ReLU的操作得到 n 個通道的Feature Map,然後我們試圖通過這 n 個Feature Map還原輸入數據,還原的越像說明信息損耗的越少。從下圖中我們可以看出,當 n 的值比較小時,ReLU的信息損耗非常嚴重,但是當 n 的值比較大的時候,輸入流形就能還原的很好了。
使用ReLU激活函數的通道數和信息損耗之間的關係
根據對上面提到的信息損耗問題分析,我們可以有兩種解決方案:
- 既然是ReLU導致的信息損耗,那麼我們就將ReLU替換成線性激活函數;
- 如果比較多的通道數能減少信息損耗,那麼我們就使用更多的通道。
二.網絡結構詳解
1.Linear Bottlenecks
假設某層的輸出的feature map大小爲HxWxD,經過激活層後稱之爲manifold of interest
,可以理解爲感興趣流形或有用的信息,大小仍爲HxWxD,經驗證明manifold of interest
完全可以壓縮到低維子空間,在V1版本中便可以通過width multiplier parameter
來降低激活空間的維數使得manifold of interest
充滿整個空間。問題就來了,在使用ReLU函數進行激活時,負數直接變爲0,這樣就會導致失去較多有用的信息(這在manifold of interest
佔激活空間較小時不是一個問題)。
總結一下,有以下兩點:
- 如果
manifold of interest
經過ReLU後均爲非零,意味着只經過了一個線性變換 - 除非
input manifold
位於輸入空間的低維子空間,經過ReLU後才能保持完整的信息
因此,論文中使用了linear bottleneck
來解決由於非線性激活函數造成的信息損失問題。linear bottleneck
本質上是不帶ReLU的1x1的卷積層。
我們當然不能把ReLU全部換成線性激活函數,不然網絡將會退化爲單層神經網絡,一個折中方案是在輸出Feature Map的通道數較少的時候也就是bottleneck部分使用線性激活函數,其它時候使用ReLU。代碼片段如下:
def _bottleneck(inputs, nb_filters, t):
x = Conv2D(filters=nb_filters * t, kernel_size=(1,1), padding='same')(inputs)
x = Activation(relu6)(x)
x = DepthwiseConv2D(kernel_size=(3,3), padding='same')(x)
x = Activation(relu6)(x)
x = Conv2D(filters=nb_filters, kernel_size=(1,1), padding='same')(x)
# do not use activation function
if not K.get_variable_shape(inputs)[3] == nb_filters:
inputs = Conv2D(filters=nb_filters, kernel_size=(1,1), padding='same')(inputs)
outputs = add([x, inputs])
return outputs
這裏使用了MobileNet中介紹的ReLU6激活函數,它是對ReLU在6上的截斷,數學形式爲:
下圖便是結合了殘差網絡和線性激活函數的MobileNet v2的一個block。
特別的,針對stride=1 和stride=2,在block上有稍微不同,主要是爲了與shortcut的維度匹配,因此,stride=2時,不採用shortcut。
2.Inverted Residual
當激活函數使用ReLU時,我們可以通過增加通道數來減少信息的損耗,使用參數 t 來控制,該層的通道數是輸入Feature Map的 t 倍。傳統的殘差塊的 t 一般取小於1的小數,常見的取值爲0.1,而在v2中這個值一般是介於 5-10 之間的數,在作者的實驗中, t=6 。考慮到殘差網絡和v2的 t 的不同取值範圍,他們分別形成了錐子形(兩頭小中間大)和沙漏形(兩頭大中間小)的結構,如下圖所示,其中斜線Feature Map表示使用的是線性激活函數。這也就是爲什麼這種形式的卷積block被叫做Interved Residual block,因爲他把short-cut轉移到了bottleneck層。
殘差網絡的的Residual block和v2的Inverted Residual block卷積對比
創新點:
1. Inverted residuals,通常的residuals block是先經過一個1*1的Conv layer,把feature map的通道數“壓”下來,再經過3*3 Conv layer,最後經過一個1*1 的Conv layer,將feature map 通道數再“擴張”回去。即先“壓縮”,最後“擴張”回去。
而 inverted residuals就是 先“擴張”,最後“壓縮”。2.Linear bottlenecks,爲了避免Relu對特徵的破壞,在residual block的Eltwise sum之前的那個 1*1 Conv 不再採用Relu。
主要是兩點:
- Depth-wise convolution之前多了一個1*1的“擴張”層,目的是爲了提升通道數,獲得更多特徵;
- 最後不採用Relu,而是Linear,目的是防止Relu破壞特徵。這個線性是通過1*1的卷積實現的。
再看看MobileNetV2的block 與ResNet 的block:
主要不同之處就在於,ResNet是:壓縮”→“卷積提特徵”→“擴張”,MobileNetV2則是Inverted residuals,即:“擴張”→“卷積提特徵”→ “壓縮”。
MobileNet-V1 最大的特點就是採用depth-wise separable convolution來減少運算量以及參數量,而在網絡結構上,沒有采用shortcut的方式。
Resnet及Densenet等一系列採用shortcut的網絡的成功,表明了shortcut是個非常好的東西,於是MobileNet-V2就將這個好東西拿來用。拿來主義,最重要的就是要結合自身的特點,MobileNet的特點就是depth-wise separable convolution,但是直接把depth-wise separable convolution應用到 residual block中,會碰到如下問題:
1.DWConv layer層提取得到的特徵受限於輸入的通道數,若是採用以往的residual block,先“壓縮”,再卷積提特徵,那麼DWConv layer可提取得特徵就太少了,因此一開始不“壓縮”,MobileNetV2反其道而行,一開始先“擴張”,本文實驗“擴張”倍數爲6。 通常residual block裏面是 “壓縮”→“卷積提特徵”→“擴張”,MobileNetV2就變成了 “擴張”→“卷積提特徵”→ “壓縮”,因此稱爲Inverted residuals
2.當採用“擴張”→“卷積提特徵”→ “壓縮”時,在“壓縮”之後會碰到一個問題,那就是Relu會破壞特徵。爲什麼這裏的Relu會破壞特徵呢?這得從Relu的性質說起,Relu對於負的輸入,輸出全爲零;而本來特徵就已經被“壓縮”,再經過Relu的話,又要“損失”一部分特徵,因此這裏不採用Relu,實驗結果表明這樣做是正確的,這就稱爲Linear bottlenecks
綜上我們可以得到MobileNet v2的一個block的詳細參數,Conv2d 和avgpool和傳統CNN裏的操作一樣;最大的特點是bottleneck,一個bottleneck由如下三個部分構成,如下圖所示,其中 s 代表步長:
MobileNet v2的實現可以通過堆疊bottleneck的形式實現,如下面代碼片段
def MobileNetV2_relu(input_shape, k):
inputs = Input(shape = input_shape)
x = Conv2D(filters=32, kernel_size=(3,3), padding='same')(inputs)
x = _bottleneck_relu(x, 8, 6)
x = MaxPooling2D((2,2))(x)
x = _bottleneck_relu(x, 16, 6)
x = _bottleneck_relu(x, 16, 6)
x = MaxPooling2D((2,2))(x)
x = _bottleneck_relu(x, 32, 6)
x = GlobalAveragePooling2D()(x)
x = Dense(128, activation='relu')(x)
outputs = Dense(k, activation='softmax')(x)
model = Model(inputs, outputs)
return model
網絡結構如下:
其中:t表示“擴張”倍數,c表示輸出通道數,n表示重複次數,s表示步長stride。
可以發現,除了最後的avgpool,整個網絡並沒有採用pooling進行下采樣,而是利用stride=2來下采樣,此法已經成爲主流。
三.實驗
看看MobileNet-V2 分類時,inference速度: