【TensorFlow-windows】MobileNet理论概览与实现
前言
輕量級神經網絡中,比較重要的有MobileNet和ShuffleNet,其實還有其它的,比如SqueezeNet、Xception等。
本博客為MobileNet的前兩個版本的理論簡介與Keras中封裝好的模塊的對應實現方案。
國際慣例,參考博客:
縱覽輕量化卷積神經網絡:SqueezeNet、MobileNet、ShuffleNet、Xception
為什么MobileNet及其變體如此之快?
【Tensorflow】tf.nn.depthwise_conv2d如何實現深度卷積?
Keras-application中的實現
MobileNetV1論文
MobileNetV2論文
MobileNetV3論文
MobileNetV1
理論
利用深層分離式卷積(Depthwise Separable Convolution)替換傳統的卷積操作,深層分離式卷積是由深層卷積(depthwise convolution)和大小為1的卷積核(pointwise convolution)組合,
正常的卷積過程為:設有M個大小為(P,P)(P,P)(P,P)的輸入特征圖,使用N個大小為(S,S)(S,S)(S,S)的卷積核進行卷積操作,可得到N個大小為(Q,Q)(Q,Q)(Q,Q)的輸出特征圖。計算量是:
M×N×S2×Q2M\times N \times S^2\times Q^2 M×N×S2×Q2
MobileNet的卷積過程:M個大小為(P,P)(P,P)(P,P)的輸入特征圖,先與MMM個大小為(3,3)(3,3)(3,3)的卷積核分別卷積,也就是M個特征圖與M個卷積核一一對應,第M個卷積核對除第M個特征圖外的特征圖無其它操作,就得到了M個大小為(Q,Q)(Q,Q)(Q,Q)的特征圖。隨后使用N個大小為(1,1)(1,1)(1,1)的卷積核對這M個特征圖卷積,得到N個大小為(Q,Q)(Q,Q)(Q,Q)的特征圖。計算量是:
M×S2×Q2+M×N×Q2M\times S^2\times Q^2 + M\times N\times Q^2 M×S2×Q2+M×N×Q2
上圖左邊就是常規的卷積操作,右邊就是MobileNet對應的卷積操作。
此外論文還提供兩個控制模型參數或者計算量的操作:
- Width Multiplier:將模型變得更瘦,意思就是最終通道數減少,在pointwise convolution層操作,比如原來N個(1,1)(1,1)(1,1)個卷積核,現在變成了N×WidthMultiplierN \times WidthMultiplierN×WidthMultiplier
- Resolution Multiplier:將模型的每個特征圖變得更小,比如某層是240×240240\times240240×240的特征圖,經過Resolution Multiplier=0.5調整后,就變成了120×120120\times120120×120大小的特征圖,論文中是通過這個來調整輸入大小。
In practice we implicitly set ρ by setting the input resolution
經評論區提醒,關于Resolution Multiplier的描述有所變動。之前描述的是tensorflow里面的驗證結果:它沒有調整特征圖大小,而是提升了depth wise后的卷積特征圖通道數。具體看下面的問題描述與驗證。
問題
關于Resolution Multiplier,如果按照第三個參考博客計算,那么這個值必須是正整數,不可能是小于1的小數,剛好與tensorflow的實現對應,但是論文描述是為了降低計算量,一般是小于1的小數,所以不可能擴大。這兩個理論剛好相反。
我個人理解是這個Resolution Multiplier是降低depthwise卷積得到的每個特征圖的大小,比如輸入10個大小為(224,224)(224,224)(224,224)的特征圖,經過第一步深度卷積后得到的是10個(112,112)(112,112)(112,112)大小的特征圖,而非5個(224,224)(224,224)(224,224)大小的特征圖。從文章中的公式就能看出來:
DK?DK?αM?ρDF?ρDF+αM?αN?ρDF?ρDFD_K\cdot D_K\cdot \alpha M \cdot \rho D_F\cdot \rho D_F+\alpha M\cdot \alpha N\cdot \rho D_F\cdot \rho D_F DK??DK??αM?ρDF??ρDF?+αM?αN?ρDF??ρDF?
其中α\alphaα代表輸出特征圖減少了,除了最開始的輸入層外的層輸入和輸出特征圖都變成了 α\alphaα 倍,所以第二項MMM和NNN都乘了α\alphaα ,分別表示輸入特征圖與輸出特征圖個數;ρ\rhoρ乘在了DFD_FDF?,而DFD_FDF?代表特征圖的大小而非個數,所以應該是降低了每層depthwise conv后的特征圖大小,類似于卷積核變大了,輸出特征圖就變小了。
但是Tensorflow代碼中驗證的結果又和自己的想法不同。驗證如下:
代碼
在Keras中,實現此過程的代碼如下:
def _depthwise_conv_block(inputs, pointwise_conv_filters, alpha,depth_multiplier=1, strides=(1, 1), block_id=1):channel_axis = 1 if backend.image_data_format() == 'channels_first' else -1pointwise_conv_filters = int(pointwise_conv_filters * alpha)if strides == (1, 1):x = inputselse:x = layers.ZeroPadding2D(((0, 1), (0, 1)),name='conv_pad_%d' % block_id)(inputs)x = layers.DepthwiseConv2D((3, 3),padding='same' if strides == (1, 1) else 'valid',depth_multiplier=depth_multiplier,strides=strides,use_bias=False,name='conv_dw_%d' % block_id)(x)x = layers.BatchNormalization(axis=channel_axis, name='conv_dw_%d_bn' % block_id)(x)x = layers.ReLU(6., name='conv_dw_%d_relu' % block_id)(x)x = layers.Conv2D(pointwise_conv_filters, (1, 1),padding='same',use_bias=False,strides=(1, 1),name='conv_pw_%d' % block_id)(x)x = layers.BatchNormalization(axis=channel_axis,name='conv_pw_%d_bn' % block_id)(x)return layers.ReLU(6., name='conv_pw_%d_relu' % block_id)(x)很清晰的發現,是經過DepthWise卷積、BN、Relu、pointwise卷積、BN、Relu。
而且寬度因子alpha起作用的是pointwise卷積的時候,而分辨率因子depth_multiplier是對DepthwiseConv起作用的。下面能驗證。
然后按照論文Table1復現結構的時候,要注意卷積步長,剛開始的正常卷積,步長(2,2)(2,2)(2,2),后面依次為不使用步長和使用步長的深層分離網絡結構。
x = _conv_block(img_input, 32, alpha, strides=(2, 2))x = _depthwise_conv_block(x, 64, alpha, depth_multiplier, block_id=1)x = _depthwise_conv_block(x, 128, alpha, depth_multiplier,strides=(2, 2), block_id=2)x = _depthwise_conv_block(x, 128, alpha, depth_multiplier, block_id=3)x = _depthwise_conv_block(x, 256, alpha, depth_multiplier,strides=(2, 2), block_id=4)x = _depthwise_conv_block(x, 256, alpha, depth_multiplier, block_id=5)x = _depthwise_conv_block(x, 512, alpha, depth_multiplier,strides=(2, 2), block_id=6)x = _depthwise_conv_block(x, 512, alpha, depth_multiplier, block_id=7)x = _depthwise_conv_block(x, 512, alpha, depth_multiplier, block_id=8)x = _depthwise_conv_block(x, 512, alpha, depth_multiplier, block_id=9)x = _depthwise_conv_block(x, 512, alpha, depth_multiplier, block_id=10)x = _depthwise_conv_block(x, 512, alpha, depth_multiplier, block_id=11)x = _depthwise_conv_block(x, 1024, alpha, depth_multiplier,strides=(2, 2), block_id=12)x = _depthwise_conv_block(x, 1024, alpha, depth_multiplier, block_id=13)驗證問題
上面說了關于resolution multiplier的理解論文和tf官方實現,不清楚原因所在。
先來驗證一下論文3.3節的width Multiplier,直接用Keras自帶的模型:
import tensorflow as tf mobilenet=tf.keras.applications.MobileNet(alpha=0.25,depth_multiplier=1,weights=None) #mobilenet=tf.keras.applications.MobileNet(alpha=1,depth_multiplier=1,weights=None) mobilenet.summary()可以發現在第一次卷積減少了1/41/41/4的特征圖,后面每次pwpwpw的時候也減少了1/41/41/4的特征圖,這里的pw就是point wise卷積,所以width multiplier是在(1,1)(1,1)(1,1)卷積的時候減少卷積核數量
再來看看resolution multiplier
-
輸入小數的時候
mobilenet=tf.keras.applications.MobileNet(alpha=1,depth_multiplier=0.5,weights=None)直接報錯:
TypeError: Value passed to parameter 'shape' has DataType float32 not in list of allowed values: int32, int64 -
對比1和2的時候:
mobilenet=tf.keras.applications.MobileNet(alpha=1,depth_multiplier=1,weights=None) #mobilenet=tf.keras.applications.MobileNet(alpha=1,depth_multiplier=2,weights=None) model.summary()
可以發現在dw的時候通道數增大了一倍,原理就是在(3,3)(3,3)(3,3)的深度卷積的時候,增加了通道數。這讓我很郁悶啊,論文和tf的代碼不一樣哎,原文是減少,是小數。
這個在Keras中也有對應描述:
depth_multiplier: depth multiplier for depthwise convolution. This is called the resolution multiplier in the MobileNet paper
貌似Resolution Multiplier就是針對depthwise卷積的
如果有大佬能幫忙解惑,將不甚感激昂。
------------------更新日志2019-8-7---------------------------------
與評論區討論了一下,這個Resolution Multiplier姑且就認為是改變輸入大小吧,Keras官方的MobileNet不是默認支持四種輸入大小么:
MobileNetV2
理論
文章首先提出了一個概念Linear Bottlenecks,這個Bottolenecks在ResNet中出現過,戳這篇博客看,講道理就是shortcut連接,說明在MobileNetV2引入了ResNet的思想。
作者做了一個假設:神經網絡中的興趣流行(manifold of interest)可以被嵌入到低維子空間中,個人覺得像是PCA的理論,提取主分量就是降維到低維子空間了。比如某卷積層的第ddd個通道的像素所編碼的信息,實際就存在某個特定的流行空間中,而這個流行空間可以嵌入到低維子空間。
基于這個假設,降低每一層的維度就能降低操作空間的維度,這在MobileNetV1中使用過,利用width multiplier減少每個block輸出的特征圖數目。但是,神經網絡中的非線性變換打破了這個想法,比如Relu經常導致某些神經元壞死,將會損失信息。
隨后又做了個假設,既然Relu在某個通道會丟失信息,搞不好在其它通道仍然保存著信息。
總結起來就是,作者認為,感興趣的流行,一定存在于高維激活空間的一個低維子空間中:
- 如果在Relu激活后仍然保持著非零,那就是線性變換。
- Relu可以保存輸入流行的完整信息,但是僅僅是在輸入流行存在于輸入空間的一個低維子空間的前提下。
這樣就可以優化網絡結構:假設興趣流行在低維空間中,我們就可以通過插入linear bottleneck到卷積模塊中,進而捕捉到這種低維信息。具體插入到哪里,看下面右圖中帶斜線的塊,就是線性激活塊。
左圖是標準的Resnet模塊,先(1,1)(1,1)(1,1)的卷積核卷積,然后Relu,再用(3,3)(3,3)(3,3)的卷積核卷積,然后Relu,再用(1,1)(1,1)(1,1)的卷積核卷積,再Relu,將最終的結果與輸入相加。
右圖是文章的Inverted Residual,帶虛線的是不適用非線性激活。先(1,1)(1,1)(1,1)的卷積核卷積,注意這里要用大量的卷積核增加特征圖數目,再用Relu激活,再用(3,3)(3,3)(3,3)的深層卷積depthwise conv卷積,使用Relu激活,再使用(1,1)(1,1)(1,1)的卷積核卷積,這里使用較少的卷積核,降低輸出特征圖的數目,最后來一次shortcut連接輸入和輸出特征圖。
從上圖可以發現,傳統的Resnet輸入和輸出通道數都比較大,而Invert residual的通道數都比較小,只有中間極大增加了通道數,是想用大量的通道來確保流行信息被保存下來,后面用了深層卷積,確保了參數與計算量的降低。
Resnet通常稱沙漏形狀,先減少通道數,再增加通道數。Invert Residual剛好相反,先增加通道數,再降維;這就是為啥稱為逆Res
初步總結,MobilenetV2的特點:
- 依舊使用MobileNetV1的特點,使用depthwise和pointwise的卷積,但是增加了殘差結構連接
- 與殘差網的漏斗形狀(兩邊粗,中間細)相反,MobileNetv2在中間增加了通道數,呈現兩邊細中間粗的形狀,因為作者相信大量的低維空間能夠捕捉到高維特征中有用的信息,所以要擴充通道數
- 在Block最后一層,為了不破壞提取的信息,去掉了Relu激活,而直接shortcut連接
結構總結:
- (1,1)(1,1)(1,1)的pointwise卷積,提高通道數,結果Relu
- (3,3)(3,3)(3,3)的depthwise卷積,提特征,結果Relu
- (1,1)(1,1)(1,1)的pointwise卷積,單純降維,不Relu
- 輸入與輸出的shortcut
代碼
在Keras中做了一下簡單的處理:
先做常規卷積,卷積核大小(3,3)(3,3)(3,3),步長(2,2)(2,2)(2,2),使用valid的卷積方式,降低特征圖大小:
x = layers.ZeroPadding2D(padding=correct_pad(backend, img_input, 3),name='Conv1_pad')(img_input) x = layers.Conv2D(first_block_filters,kernel_size=3,strides=(2, 2),padding='valid',use_bias=False,name='Conv1')(x) x = layers.BatchNormalization(axis=channel_axis,epsilon=1e-3,momentum=0.999,name='bn_Conv1')(x) x = layers.ReLU(6., name='Conv1_relu')(x)其中correct_pad是為了讓卷積能夠正常進行,也就是下式能整除,避免特征圖變成小數寬度:
n?m+paddingstride+1\frac{n-m+padding}{stride}+1 striden?m+padding?+1
接下來便是使用Invert Residual模型結構:
其中alpha是最后一層pointwise的(1,1)(1,1)(1,1)卷積的輸出特征圖數目,expansion是invert residual的第一層pointwise卷積對原始通道數擴充的維度。
注意這里的卷積步長有變化,有(1,1)(1,1)(1,1)和(2,2)(2,2)(2,2),所以在步長位(2,2)(2,2)(2,2)的時候,要注意使用correct_pad填充特征圖,避免卷積出小數寬高特征圖的情況。
還有一點是,Keras實現的時候,第1個block并沒有做通道數的擴充:
if block_id:# Expandx = layers.Conv2D(expansion * in_channels,kernel_size=1,padding='same',use_bias=False,activation=None,name=prefix + 'expand')(x)x = layers.BatchNormalization(axis=channel_axis,epsilon=1e-3,momentum=0.999,name=prefix + 'expand_BN')(x)x = layers.ReLU(6., name=prefix + 'expand_relu')(x)else:prefix = 'expanded_conv_'隨后進入(3,3)(3,3)(3,3)的depthwise卷積提取特征,注意如果步長是(2,2)(2,2)(2,2),要核對是否能夠滿足步長卷積:
if stride == 2:x = layers.ZeroPadding2D(padding=correct_pad(backend, x, 3),name=prefix + 'pad')(x)x = layers.DepthwiseConv2D(kernel_size=3,strides=stride,activation=None,use_bias=False,padding='same' if stride == 1 else 'valid',name=prefix + 'depthwise')(x)x = layers.BatchNormalization(axis=channel_axis,epsilon=1e-3,momentum=0.999,name=prefix + 'depthwise_BN')(x)x = layers.ReLU(6., name=prefix + 'depthwise_relu')(x)隨后直接做投影,也就是使用pointwise卷積降低通道數,不用Relu:
# Projectx = layers.Conv2D(pointwise_filters,kernel_size=1,padding='same',use_bias=False,activation=None,name=prefix + 'project')(x)x = layers.BatchNormalization(axis=channel_axis,epsilon=1e-3,momentum=0.999,name=prefix + 'project_BN')(x)最后如果輸入輸出的通道數相同,就shortcut:
if in_channels == pointwise_filters and stride == 1:return layers.Add(name=prefix + 'add')([inputs, x]) return x為了更清晰理解每層的作用,我這里對部分層的特征圖大小做了一下輸出截圖:
from keras.applications.mobilenetv2 import MobileNetV2 model = MobileNetV2(weights='imagenet') model.summary()剛開始的卷積->BN->Relu:
隨后是最開始的invert residual 塊,沒有維度擴充,只有depthwise卷積和pointwise映射:
可以發現輸出維度由32降低成16。
再看第二塊invert residual塊:
上來就把維度由輸入的16個通道提升為96個通道,然后使用(3,3)(3,3)(3,3)的卷積,步長為(2,2)(2,2)(2,2),得到特征圖大小是
113?32+1=56\frac{113-3}{2}+1=56 2113?3?+1=56
最后投影的時候,又將通道數降低了4倍,此時沒有shorcut,因為輸入輸出的特征圖通道和大小都不同。
再看第三個invert block
同樣,一上來就把通道數由24擴充6倍到144,然后步長為(1,1)(1,1)(1,1)的(3,3)(3,3)(3,3)卷積,最后降低通道數,此時輸入和輸出的特征圖數目與大小都相同,為(56,56,24)(56,56,24)(56,56,24),直接shortcut加起來。
第四個invert block:
老樣子:24擴充6倍到144,用(3,3)(3,3)(3,3)的卷積核步長位(2,2)(2,2)(2,2)卷積,最后降低通道數。
總結
主要稍微總結了一下MobileNet的兩個版本的設計思路
MobileNetV1:使用depthwise卷積和(1,1)(1,1)(1,1)的卷積核代替傳統的卷積算法
MobileNetV2:在版本1的基礎上加入了殘差網的思想,傳統殘差網是先將特征圖數目變小,然后再變大,最后shortcut,而MobileNetV2之所以稱為invert residual就是因為反過來,先將特征圖數目變大,再變小,這是因為作者感覺特征圖太少會受到Relu的影響,表達能力不強,那么就使用一堆的特征圖去提取特征,總有一個能提取到有用信息,最后為了避免信息被破話,就不用Relu,直接shortcut了。
戳這里是V1的實現,戳這里是V2的實現,都是官方的,具體使用方法,可以戳Keras官方文檔的這里仿寫。
后續會繼續學習其它的輕量級網絡設計方法,包括Inception、Xception、ShuflleNet等
總結
以上是生活随笔為你收集整理的【TensorFlow-windows】MobileNet理论概览与实现的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 腾讯合作生存手游《代号:生机》公布 血战
- 下一篇: FGO最新女帝节奏榜 五星从者排行榜