卷積神經網路學習筆記——輕量化網路MobileNet系列(V1,V2,V3)

戰爭熱誠發表於2021-03-13

完整程式碼及其資料,請移步小編的GitHub地址

  傳送門:請點選我

  如果點選有誤:https://github.com/LeBron-Jian/DeepLearningNote

  這裡結合網路的資料和MobileNet論文,捋一遍MobileNet,基本程式碼和圖片都是來自網路,這裡表示感謝,參考連結均在後文。下面開始。

  MobileNet論文寫的很好,有想法的可以去看一下,我這裡提供翻譯地址:

深度學習論文翻譯解析(十七):MobileNets: Efficient Convolutional Neural Networks for Mobile Vision Applications

深度學習論文翻譯解析(十八):MobileNetV2: Inverted Residuals and Linear Bottlenecks

深度學習論文翻譯解析(十九):Searching for MobileNetV3

 

  卷積神經網路CNN已經普遍應用在計算機視覺領域,並且已經取得了不錯的效果,圖1為近年來CNN在ImageNet競賽的表現,可以看到為了追求分類準確度,模型深度越來越深,模型複雜度也越來越高,如深度殘差網路(ResNet)其層數已經多達152層。

   然而,在某些真實的應用場景如移動或者嵌入式裝置,如此大而複雜的模型時難以被應用的。首先是模型過於龐大,面臨著記憶體不足的問題,其次這些場景要求低延遲,或者說響應速度要快,想象一下自動駕駛汽車的行人檢測系統如果速度很慢會發生什麼可怕的事情。所以,研究小而高效的CNN模型在這些場景至關重要,至少目前是這樣,儘管未來硬體也會越來越快。

  目前的研究總結來看分為兩個方向:

  • 一是對訓練好的複雜模型進行壓縮得到小模型;
  • 二是直接設計小模型並進行訓練。

  不管如何,其目標在保持模型效能(accuracy)的前提下降低模型大小(parameters size),同時提升模型速度(speed, low latency)。本文的主角MobileNet屬於後者,其是Google最近提出的一種小巧而高效的CNN模型,其在accuracy和latency之間做了折中。

  MobileNet 需要儘可能維持其中發展較快的計算機視覺和深度學習領域與移動環境侷限性之間的平衡。因此,谷歌一直在定期對 MobileNets 架構進行更新,其中也加入了一些有關深度學習領域最新的想法。

1,深度可分離卷積(Depthwise separable convolution)

  自從2017年由谷歌公司提出,MobileNet可謂是輕量級網路中的 Inception,經歷了一代又一代的更新,成為了學習輕量級網路的必經之路。其實介紹 MobileNet V1只有一句話:MobileNet V1就是把VGG中的標準卷積層換成深度可分離卷積就可以了。那麼這個深度可分離卷積是什麼?

  MobileNet的基本單元是深度級可分離卷積(depthwise separable convolution——DSC),其實這種結構之前已經被使用在Inception模型中。根據史料記載,可追溯到2012年的論文Simplifying ConvNets for Fast Learning,作者提出了可分離卷積的概念(下圖(a)):

   Laurent Sifre博士2013年在谷歌實習期間,將可分離卷積擴充到了深度(depth),並且在他的博士論文Rigid-motion scattering for image classification中有詳細的描寫,感興趣的同學可以去看看論文。

  可分離卷積主要有兩種型別:空間可分離卷積和深度可分離卷積

1.1  空間可分離(略講)

  顧名思義,空間可分離就是將一個大的卷積核變成兩個小的卷積核,比如將一個3*3的核分成一個3*1 和一個 1*3 的核:

   由於空間可分離卷積不在 Mobilenet的範圍內,就不說了。

1.2  深度可分離卷積

   深度級可分離卷積其實是一種可分解卷積操作(factorized convolutions)。其可以分解為兩個更小的操作:depthwise  convolution 和 pointwise convolution。

   下面先學習標準的卷積操作:

   輸入一個12*12*3的一個輸入特徵圖,經過 5*5*3的卷積核得到一個8*8*3的輸出特徵圖。如果我們此時有256個特徵圖,我們將會得到一個8*8*256的輸出特徵圖。

  以上就是標準卷積做的活,那麼深度卷積和逐點卷積呢?

1.2.1  深度卷積

   與標準卷積網路不一樣的是,我們將卷積核拆分成單通道形式,在不改變輸入特徵影像的深度的情況下,對每一通道進行卷積操作,這樣就得到了和輸入特徵圖通道數一致的輸出特徵圖。如上圖,輸入12*12*3 的特徵圖,經過5*5*1*3的深度卷積之後,得到了8*8*3的輸出特徵圖。輸入和輸出的維度是不變的3,這樣就會有一個問題,通道數太少,特徵圖的維度太少,能獲得足夠的有效資訊嗎?

1.2.2  逐點卷積

  逐點卷積就是1*1卷積,主要作用就是對特徵圖進行升維和降維,如下圖:

   在深度卷積的過程中,我們得到了8*8*3的輸出特徵圖,我們用256個1*1*3的卷積核對輸入特徵圖進行卷積操作,輸出的特徵圖和標準的卷積操作一樣都是8*8*256了。

  標準卷積與深度可分離卷積的過程對比如下:

3,為什麼要深度可分離卷積?

  簡單來說,如果有一個方法能夠讓你用更少的引數,更少的運算,但是能達到差不多的結果,你會使用嗎?

  深度可分離卷積就是這樣的一個方法,我們首先來計算一下標準卷積的引數量和計算量(只考慮MAdd):

  這裡我們假設其輸入為Dk*Dk*M 的 feature map,這裡Dk為輸入 feature mpa的長,寬(簡單考慮假設長寬相同),M則為input channels 數目;N為output channels數目。這樣一個典型的conv結構的 kernel 通常為 Dk*Dk*M*N。

3.1  標準卷積的引數量

  卷積核的尺寸是Dk*Dk*M,一共有N個,所以標準卷積的引數量是:

3.2  標準卷積的計算量

  卷積核的尺寸是Dk*Dk*M,一共有N個,每一個都要進行Dw*Dh次運算,所以標準卷積的計算量是:

   標準卷積算完了,下面我們來計算深度可分離卷積的引數量和計算量:

  首先,Depthwise與Pointwise都是 conv操作,尤其是Pointwise更是典型的1*1 conv操作。Depthwise conv則是一種一個 input channel對應一個 conv filter進行卷積的操作,顯然它輸出的 output channels數目與 input channels數目也會相等。Pointwise conv 在Depthwise conv操作之後進行,它使用 1*1 的conv來將之前的 IC(input channels)個 feature maps進行融合,整理最終輸出 OC(output channels)個特徵的 feature maps。

3.3  深度可分離卷積的引數量

  深度可分離卷積的引數量由深度卷積和逐點卷積兩部分組成:

  深度卷積的卷積核尺寸Dk*Dk*M;逐點卷積的卷積核尺寸為1*1*M,一共有N個,所以深度可分離卷積的引數量是:

3.4  深度可分離卷積的計算量

  深度可分離卷積的計算量也是由深度卷積和逐點卷積兩部分組成:

  深度卷積的卷積核尺寸Dk×Dk×M,一共要做Dw×Dh次乘加運算;逐點卷積的卷積核尺寸為1×1×M,有N個,一共要做Dw×Dh次乘加運算,所以深度可分離卷積的計算量是:

3.5  總結

   可以引數數量和乘加操作的運算量均下降為原來的 1/N + 1/D2k

   我們通常所使用的時3*3的卷積核,也就是會下降到原來的九分之一到八分之一。

假設:輸出為一個224*224*3的影像,VGG網路某層卷積輸入的尺寸是112*112*64的特徵圖,卷積核為3*3*128,

  標準卷積的運算量是:3×3×128×64×112×112 = 924844032

  深度可分離卷積的運算量是:3×3×64×112×112+128×64×112×112 = 109985792

  這一層,Mobilenet V1所採用的深度可分離卷積計算量與標準卷積計算量的比值為:

109985792 /924844032 = 0.1189

  與我們所計算的九分之一到八分之一一致

  下面來學習 MobileNet 網路。

  MobileNet是谷歌提出來的移動端分類網路。在V1中MobileNet應用了深度可分離卷積(Depth-wise Seperable Convolution)並提出兩個超參來控制網路容量,這種卷積背後的假設是跨channel相關性 和跨spatial相關性的解耦。深度可分離卷積能夠節省引數量,在保持移動端可接受的模型複雜性的基礎上達到了相當的高精度。而在V2中,MobileNet應用了新的單元:Inverted residual with linear bottleneck,主要的改動是為 Bottleneck 新增了 linear 啟用輸出以及將殘差網路的 skip-connection 結構轉移到低維 Bottleneck 層。

2,MobileNet V1

  MobileNet  V1是一種基於流水線結構,使用深度可分離卷積構建的輕量級神經網路,並通過兩個超引數的引入使得開發人員可以基於自己的應用和資源限制選擇合適的模型。

  從概念上來說,MobileNetV1正試圖實現兩個基本目標,以構建移動第一計算視覺模型:1,較小的模型,引數數量更少;2,較小的複雜度,運算中乘法和加法更少。遵循這些原則,MobileNet V1 是一個小型,低延遲,低功耗的引數化模型,可以滿足各種用例的資源約束。它們可以用於實現:分類,檢測,嵌入和分割等功能。

2.1 MobileNet V1的創新點

2.1.1   depthwise後接BN層和RELU6,pointwise 後也接BN層和ReLU6

  如下圖所示(圖中應該是RELU6)。左圖是傳統卷積,右圖是深度可分離卷積。更多的ReLU6,增加了模型的非線性變化,增強了模型的泛化能力。

2.1.2  ReLU6啟用函式的作用

  V1中使用了ReLU6作為啟用函式,這個啟用函式在 float16/int8 的嵌入式裝置中效果很好,能較好的保持網路的魯棒性。

  ReLU6 就是普通的ReLU,但是限制最大輸出值為6(對輸出值做 clip),這是為了在移動端裝置float16的低精度的時候,也能有很好的數值解析度,如果對ReLU的啟用範圍不加限制,輸出範圍為0到正無窮,如果啟用值非常大,分佈在一個很大的範圍內,則低精度的float16無法很好地精確描述如此大範圍的數值,帶來精度損失。

   ReLU6函式與其導函式如下:

   對應的影像分別如下:

2.1.3  MobileNet V1給出兩個超引數(寬度乘子α 和 解析度乘子 rho)

  雖然MobileNet網路結構和延遲已經比較小了,但是很多時候在特定應用下還是需要更小更快的模型,為此引入了寬度因子alpha ,為了控制模型大小,我們引入了分辨因子 rho。

  寬度因子 alpha (Width Mutiplier)在每一層對網路的輸入輸出通道數進行縮減,輸出通道數由 M 到 alpha*M,輸出通道數由 N 到 alpha*N,變換後的計算量為:

  通常alpha 在(0,  1] 之間,比較典型的值由1, 0.75, 0.5, 0.25。計算量和引數數量減少程度與未使用寬度因子之前提高了 1/alpha**2倍。

  解析度因子 rho (resolution multiplier)用於控制輸入和內部層表示,即用解析度因子控制輸入的解析度,深度卷積和逐點卷積的計算量為:

  通常 rho 在(0,  1] 之間,比較典型的輸入分辨為 224, 192, 160, 128。計算量量減少程度與未使用寬度因子之前提高了 1/(alpha**2*rho*rho) 倍,引數量沒有影響。

  通過兩個超參,可以進一步縮減模型,文章中也給出了具體的實驗結果。此時,我們反過來看,擴大寬度和解析度,都能提高網路的準確率,但如果單一提升一個的話,準確率很快就會達到飽和,這就是2019年穀歌提出 efficientnet 的原因之一,動態提高深度,寬度,解析度來提高網路的準確率。

2.2  MobileNet V1 的網路架構

  MobileNet V1 的核心架構則基於一個流線型架構,該架構使用深度可分離卷積網路來構建了輕量化深度神經網路。就神經網路結構而言,深度可分類卷積將卷積核分為兩個單獨的卷積核,這兩個卷積核依次進行兩個卷積,即先是深度卷積,然後進行逐點卷積,如下圖所示:

   在MobileNetV1中,深度卷積網路的每個輸入通道都應用了單個濾波器。然後,逐點卷積應用 1*1 卷積網路來合併深度卷積的輸出。這種標準卷積方法既能濾波,又能一步將輸入合併成一組新的輸出。在這之中,深度可分離卷積將其分為兩次,一層用於濾波,另一層則用於合併。

  MobileNet的網路結構如下,一共由 28層構成(不包括AvgPool 和 FC 層,且把深度卷積和逐點卷積分開算),其除了第一層採用的是標準卷積核之外,剩下的卷積層都是用Depth Wise Separable  Convolution。

3, MobileNet V2

  MobileNet V2架構在 2018年初發布,MobileNet V2基於MobileNet V1的一些思想,並結合新的思想來優化。從架構上來看,MobileNet V2為架構增添了兩個新模組:1,引入了層與層之間的線性瓶頸;2,瓶頸之間的快捷連線。

   MobileNetV2之中的核心思想是,瓶頸對模型的中間輸入和輸出進行編碼,而內層則用於封裝模型從較低階別概念(如:畫素等)轉換到較高階別描述符(如:影像類別等)的能力。最後,與傳統的剩餘連線一樣,快捷方式能夠實現更快地訓練速度和更高的準確率。

3.1  MobileNet V1 VS MobileNet V2 

3.1.1  MobileNet V1 的問題

  MobileNet V1 的結構較為簡單,另外,主要的問題還是在Depthwise Convolution 之中,Depthwise Convolution 確實降低了計算量,但是Depthwise部分的 Kernel 訓練容易廢掉,即卷積核大部分為零,作者認為最終再經過 ReLU 出現輸出為 0的情況。

  V2 傳遞的思想只有一個,即ReLU 會對 channel 數較低的張量造成較大的資訊損耗,簡單來說,就是當低維資訊對映到高維,經過ReLU後再對映回低維時,若對映到的維度相對較高,則資訊變換回去的損失較小;若對映到的維度相對較低,則資訊變換回去後損失很大,如下圖所示:

   當原始輸入維度數增加到 15 以後再加 ReLU,基本不會丟失太多的資訊;但如果只把原始輸入維度增加到 2~5維度後再加 ReLU,則會出現較為嚴重的資訊丟失。因此,認為對低維度做ReLU運算,很容易造成資訊的丟失。而在高維度進行ReLU運算的話,資訊的丟失則會很少。另外一種解釋是,高維資訊變換回低維度資訊時,相當於做了一次特徵壓縮,會損失一部分資訊,而再進行過ReLU後,損失的部分就更大了。作者為了這個問題,就將ReLU替換成線性啟用函式。

  至於ReLU是如何損失特徵的,我的理解是:ReLU的特性使得對於負值輸入,其輸出為0,而且降維本身就是特徵壓縮的過程,這樣就使得特徵損失更為嚴重了

3.1.2  MobileNet  V1 和 V2 的對比

  相同點:都是採用 Depth-wise (DW)卷積搭配 Point-wise(PW)卷積的方式來提取特徵。這兩個操作合起來也叫 Depth-wise Separable Convolution,之前在 Xception中被廣泛使用。這麼做的好處是理論上可以成倍的減少卷積層的時間複雜度和空間複雜度,由下式可見,因為卷積核的尺寸K通常遠遠小於輸出通道數 Count,因此標準卷積的計算量複雜度近似為 DW+PW 組合卷積的 K2倍。

  不同點(Linear Bottleneck):V2在DW卷積之前新加了一個PW卷積,這麼做的原因是因為DW卷積由於本身的計算特性決定它自己沒有改變通道數的能力,上一層給他多少通道,他就只能輸出多少通道。所以如果上一層的通道數本身很少的話,DW也只能很委屈的低維空間提取特徵,因此效果不是很好,現在V2為了改善這個問題,給每個 DW 之前都配備了一個PW,專門用來升維,定義升維繫數為 t=6,這樣不管輸入通道數Cin 是多是少,經過第一個 PW 升維之後,DW都是在相對的更高維(t*Cin)是多是少,經過第一個 PW升維之後,DW 都是在相對的更高維(t*Cin)進行辛勤工作的。而且V2去掉了第二個PW的啟用函式,論文作者稱其為 Linear Bottleneck。這麼做的淵源,是因為作者認為啟用函式在高維空間能夠有效的增加非線性,而在低維空間時則會破壞特徵,不如線性的效果好。由於第二個PW的主要功能就是降維,因此按照上面的理論,降維之後就不宜再使用ReLU6了。

3.2  MobileNet V2 的創新點

  MobileNet V2  是對 MobileNet V1 的改進,同樣是一個輕量化卷積神經網路。MobileNet V2 釋出於2018年,時隔一年,谷歌的又一力作,V2在V1的基礎上,引入了Inverted Residuals和Linear Bottlenecks。

3.2.1 Inverted Residuals

  這個可以翻譯成“倒殘差模組”。什麼意思呢?我們來對比一下殘差模組和倒殘差模組的區別。

  • 殘差模組:輸入首先經過1*1的卷積進行壓縮,然後使用3*3的卷積進行特徵提取,最後在用1*1的卷積把通道數變換回去。整個過程是“壓縮-卷積-擴張”。這樣做的目的是減少3*3模組的計算量,提高殘差模組的計算效率。
  • 倒殘差模組:輸入首先經過1*1的卷積進行通道擴張,然後使用3*3的depthwise卷積,最後使用1*1的pointwise卷積將通道數壓縮回去。整個過程是“擴張-卷積-壓縮”。為什麼這麼做呢?因為depthwise卷積不能改變通道數,因此特徵提取受限於輸入的通道數,所以將通道數先提升上去。文中的擴充套件因子為6。

  用下圖表示再合適不過了。

 3.2.2 Linear  Bottleneck

  這個模組是為了解決一開始提出的那個低維-高維-低維的問題,即將最後一層的ReLU替換成線性啟用函式,而其它層的啟用函式依然是ReLU6。

  上面已經詳細說了,這裡不再贅述。

3.3  MobileNet V2 網路架構

   Mobilenet V2 的網路模組如下圖所示,當 stride=1時,輸入首先經過 1*1 卷積進行通道數的擴張,此時啟用函式為 ReLU6;然後經過3*3的depthwise卷積,啟用函式是ReLU6;接著經過1*1的pointwise卷積,將通道數壓縮回去,啟用函式是linear;最後使用shortcut,將兩者進行相加。而當stride=2時,由於input和output的特徵圖的尺寸不一致,所以就沒有shortcut了。

   最後,給出V2的網路結構。其中,t 為擴張稀疏,c 為輸出通道數,n 為該層重複的次數,s為步長。可以看出 V2 的網路比V1網路深了很多,V2有54層。

4,MobileNet V3

   MobileNet V3發表於2019年,Mobilenet-V3 提供了兩個版本,分別為 MobileNet-V3 Large以及 MobileNet-V3 Small,分別適用於對資源要求不同的情況。V3結合了v1的深度可分離卷積、v2的Inverted Residuals和Linear Bottleneck、SE模組,利用NAS(神經結構搜尋)來搜尋網路的配置和引數。這種方式已經遠遠超過了人工調參了,太恐怖了。

4.1  MobileNet V3 的創新點

4.1.1  修改尾部結構

  在MobileNetV2中,在Avg Pooling之前,存在一個 1*1 的卷積層,目的是提高特徵圖的維度,更有利於結構的預測,但是這其實帶來了一定的計算量了。所以這裡作者做了修改,將其放在 avg Pooling 的後面,首先利於 avg Pooling 將特徵圖的大小由 7*7 降到了 1*1,降到 1*1 後,然後再利用 1*1 提高維度,這樣就減少了 7*7 =49 倍的計算量。並且為了進一步的降低計算量,作者直接去掉了前面紡錘型卷積的 3*3 以及 1*1 卷積,進一步減少了計算量,就變成了如下圖第二行所示的結構,作者將其中的 3*3 以及 1*1 去掉後,精度並沒有得到損失,這裡降低了大約 10ms的延遲,提高了15%的運算速度,且幾乎沒有任何精度損失。其次,對於v2的輸入層,通過3*3卷積將輸入擴張成32維。作者發現使用ReLU或者switch啟用函式,能將通道數縮減到16維,且準確率保持不變。這又能節省3ms的延時。

4.1.2  非線性變換的改變

  由於嵌入式裝置計算sigmoid是會耗費相當大的計算資源的,特別是在移動端,因此作者提出了h-switch作為啟用函式。且隨著網路的加深,非線性啟用函式的成本也會隨之減少。所以只有在較深的層使用h-switch才能獲得更大的優勢。

  觀察上圖可以發現,其實相差不大(不過swish是谷歌自家的研究成果,h-swish 是在其基礎上,為速度進行了優化)。

  使用ReLU的好處:

  • 1,可以在任何軟硬體平臺進行計算
  • 2,量化的時候,它消除了潛在的精度損失,使用 h-swish 替換 swish,在量化模式下會提高大約 15%的效率

 4.1.3  引入 SE 結構

  在v2的 bottleneck 結構中引入SE模組,並且放在了 depthwise filter 之後,SE模組是一種輕量級的通道注意力模組,因為SE結構會消耗一定的時間,所以在depthwise之後,經過池化層,然後第一個fc層,通道數縮小4倍,再經過第二個fc層,通道數變換回去(擴大4倍),然後與depthwise進行按位相加,這樣作者發現,即提高了精度,同時還沒有增加時間消耗。

4.2  MobileNet V3 網路結構

  MobileNet V3 首先使用 MnasNet 進行粗略結構的搜尋,然後使用強化學習從一組離散的選擇中選擇最優配置。之後,MobileNet V3再使用 NatAdapt 對體系結構進行微調,這體現了  NetAdapt 的補充功能,它能夠以較小的降幅對未充分利用的啟用通道進行調整。

  除此之外,MobileNet 的另一個新穎的想法是在核心架構中加入一種名為“Squeeze-and-Excitation” 的神經網路(簡稱 SENet,也是ImageNet 2017 影像分類冠軍)。該神經網路的核心思想是通過顯式的建模網路卷積特徵通道之間的互相依賴關係,來提高網路所產生表示的質量。具體而言,就是通過學習來自動獲得到每個特徵的重要程度,然後依照這一結果去提升有用的特徵並抑制對當前任務用處不大的特徵。

  為此,開發者提出了一種允許網路進行特徵重新校準的機制。通過該機制,網路可以學習使用全域性資訊來選擇地強調資訊性特徵,並抑制不太有用的特徵。而在MobileNet V3的例子中,該架構擴充套件了 MobilenetV2 ,將 SENet 作為搜尋空間的一部分,最終得到了更穩定的架構。

  Mobilenet V3 還有一個有趣的優化,則是重新設計了體系結構中一些執行成本較高的層。第二代 MobilenetV2 中的一些層是模型準確性的基礎,但也引入了潛在變數。通過合併一些基本的優化功能。MobileNet V3 在能夠不犧牲準確率的情況下,刪除了 MobilenetV2 體系結構中三個執行成本較高的層。

   V3的結構如下圖,作者提供了兩個版本的V3,分別是large和small,對應與高資源和低資源的情況,兩者都是NAS進行搜尋出來的。

   重新回顧了mobilenet系列,可以看出,準確率在逐步提高,延時也不斷下降。雖然在imagenet上的準確率不能達到state-of-art,但在同等資源消耗下,其優勢就能大大體現出來。

 

5,MobileNet 的 python實現

5.1 Keras 實現 MobileNet V1

  上面有設計好的網路架構,使用Keras照著搭建就可以,

  部分程式碼如下:

from keras.applications.imagenet_utils import _obtain_input_shape
from keras import backend as K
from keras.layers import Input, Convolution2D, \
    GlobalAveragePooling2D, Dense, BatchNormalization, Activation
from keras.models import Model
from keras.engine.topology import get_source_inputs
from model.depthwise_conv2d import DepthwiseConvolution2D #debug
#from depthwise_conv2d import DepthwiseConvolution2D #release
from keras.utils import plot_model

'''Google MobileNet model for Keras.
# Reference:
- [MobileNets: Efficient Convolutional Neural Networks for Mobile Vision Applications](https://arxiv.org/pdf/1704.04861.pdf)
'''

def MobileNet(input_tensor=None, input_shape=(224,224,3), alpha=1, shallow=False, classes=1000):
    """Instantiates the MobileNet.Network has two hyper-parameters
        which are the width of network (controlled by alpha)
        and input size.
        
        # Arguments
            input_tensor: optional Keras tensor (i.e. output of `layers.Input()`)
                to use as image input for the model.
            input_shape: optional shape tuple, only to be specified
                if `include_top` is False (otherwise the input shape
                has to be `(224, 224, 3)` (with `channels_last` data format)
                or `(3, 224, 244)` (with `channels_first` data format).
                It should have exactly 3 inputs channels,
                and width and height should be no smaller than 96.
                E.g. `(200, 200, 3)` would be one valid value.
            alpha: optional parameter of the network to change the 
                width of model.
            shallow: optional parameter for making network smaller.
            classes: optional number of classes to classify images
                into.
        # Returns
            A Keras model instance.
        """

    input_shape = _obtain_input_shape(input_shape,
                                      default_size=224,
                                      min_size=96,
                                      data_format=K.image_data_format(),
                                      require_flatten=True)

    if input_tensor is None:
        img_input = Input(shape=input_shape)
    else:
        if not K.is_keras_tensor(input_tensor):
            img_input = Input(tensor=input_tensor, shape=input_shape)
        else:
            img_input = input_tensor

    x = Convolution2D(int(32 * alpha), (3, 3), strides=(2, 2), padding='same', use_bias=False)(img_input)
    x = BatchNormalization()(x)
    x = Activation('relu')(x)

    x = DepthwiseConvolution2D(int(32 * alpha), (3, 3), strides=(1, 1), padding='same', use_bias=False)(x)
    x = BatchNormalization()(x)
    x = Activation('relu')(x)
    x = Convolution2D(int(64 * alpha), (1, 1), strides=(1, 1), padding='same', use_bias=False)(x)
    x = BatchNormalization()(x)
    x = Activation('relu')(x)

    x = DepthwiseConvolution2D(int(64 * alpha), (3, 3), strides=(2, 2), padding='same', use_bias=False)(x)
    x = BatchNormalization()(x)
    x = Activation('relu')(x)
    x = Convolution2D(int(128 * alpha), (1, 1), strides=(1, 1), padding='same', use_bias=False)(x)
    x = BatchNormalization()(x)
    x = Activation('relu')(x)

    x = DepthwiseConvolution2D(int(128 * alpha), (3, 3), strides=(1, 1), padding='same', use_bias=False)(x)
    x = BatchNormalization()(x)
    x = Activation('relu')(x)
    x = Convolution2D(int(128 * alpha), (1, 1), strides=(1, 1), padding='same', use_bias=False)(x)
    x = BatchNormalization()(x)
    x = Activation('relu')(x)

    x = DepthwiseConvolution2D(int(128 * alpha), (3, 3), strides=(2, 2), padding='same', use_bias=False)(x)
    x = BatchNormalization()(x)
    x = Activation('relu')(x)
    x = Convolution2D(int(256 * alpha), (1, 1), strides=(1, 1), padding='same', use_bias=False)(x)
    x = BatchNormalization()(x)
    x = Activation('relu')(x)

    x = DepthwiseConvolution2D(int(256 * alpha), (3, 3), strides=(1, 1), padding='same', use_bias=False)(x)
    x = BatchNormalization()(x)
    x = Activation('relu')(x)
    x = Convolution2D(int(256 * alpha), (1, 1), strides=(1, 1), padding='same', use_bias=False)(x)
    x = BatchNormalization()(x)
    x = Activation('relu')(x)

    x = DepthwiseConvolution2D(int(256 * alpha), (3, 3), strides=(2, 2), padding='same', use_bias=False)(x)
    x = BatchNormalization()(x)
    x = Activation('relu')(x)
    x = Convolution2D(int(512 * alpha), (1, 1), strides=(1, 1), padding='same', use_bias=False)(x)
    x = BatchNormalization()(x)
    x = Activation('relu')(x)

    if not shallow:
        for _ in range(5):
            x = DepthwiseConvolution2D(int(512 * alpha), (3, 3), strides=(1, 1), padding='same', use_bias=False)(x)
            x = BatchNormalization()(x)
            x = Activation('relu')(x)
            x = Convolution2D(int(512 * alpha), (1, 1), strides=(1, 1), padding='same', use_bias=False)(x)
            x = BatchNormalization()(x)
            x = Activation('relu')(x)

    x = DepthwiseConvolution2D(int(512 * alpha), (3, 3), strides=(2, 2), padding='same', use_bias=False)(x)
    x = BatchNormalization()(x)
    x = Activation('relu')(x)
    x = Convolution2D(int(1024 * alpha), (1, 1), strides=(1, 1), padding='same', use_bias=False)(x)
    x = BatchNormalization()(x)
    x = Activation('relu')(x)

    x = DepthwiseConvolution2D(int(1024 * alpha), (3, 3), strides=(1, 1), padding='same', use_bias=False)(x)
    x = BatchNormalization()(x)
    x = Activation('relu')(x)
    x = Convolution2D(int(1024 * alpha), (1, 1), strides=(1, 1), padding='same', use_bias=False)(x)
    x = BatchNormalization()(x)
    x = Activation('relu')(x)

    x = GlobalAveragePooling2D()(x)
    out = Dense(classes, activation='softmax')(x)

    if input_tensor is not None:
        inputs = get_source_inputs(input_tensor)
    else:
        inputs = img_input

    model = Model(inputs, out, name='mobilenet')

    return model


if __name__ == '__main__':
    m = MobileNet(alpha=0.5)
    plot_model(m,'modela=0.5.png',show_shapes=True)
    print ("model ready")

 

5.2  Keras 實現 MobileNet V2

   基於論文給出的引數,使用Keras實現網路結構如下:

from keras.models import Model
from keras.layers import Input, Conv2D, GlobalAveragePooling2D, Dropout
from keras.layers import Activation, BatchNormalization, add, Reshape
from keras.applications.mobilenet import relu6, DepthwiseConv2D
from keras.utils.vis_utils import plot_model
 
from keras import backend as K
 
 
def _conv_block(inputs, filters, kernel, strides):
    """Convolution Block
    This function defines a 2D convolution operation with BN and relu6.
    # Arguments
        inputs: Tensor, input tensor of conv layer.
        filters: Integer, the dimensionality of the output space.
        kernel: An integer or tuple/list of 2 integers, specifying the
            width and height of the 2D convolution window.
        strides: An integer or tuple/list of 2 integers,
            specifying the strides of the convolution along the width and height.
            Can be a single integer to specify the same value for
            all spatial dimensions.
    # Returns
        Output tensor.
    """
 
    channel_axis = 1 if K.image_data_format() == 'channels_first' else -1
 
    x = Conv2D(filters, kernel, padding='same', strides=strides)(inputs)
    x = BatchNormalization(axis=channel_axis)(x)
    return Activation(relu6)(x)
 
 
def _bottleneck(inputs, filters, kernel, t, s, r=False):
    """Bottleneck
    This function defines a basic bottleneck structure.
    # Arguments
        inputs: Tensor, input tensor of conv layer.
        filters: Integer, the dimensionality of the output space.
        kernel: An integer or tuple/list of 2 integers, specifying the
            width and height of the 2D convolution window.
        t: Integer, expansion factor.
            t is always applied to the input size.
        s: An integer or tuple/list of 2 integers,specifying the strides
            of the convolution along the width and height.Can be a single
            integer to specify the same value for all spatial dimensions.
        r: Boolean, Whether to use the residuals.
    # Returns
        Output tensor.
    """
 
    channel_axis = 1 if K.image_data_format() == 'channels_first' else -1
    tchannel = K.int_shape(inputs)[channel_axis] * t
 
    x = _conv_block(inputs, tchannel, (1, 1), (1, 1))
 
    x = DepthwiseConv2D(kernel, strides=(s, s), depth_multiplier=1, padding='same')(x)
    x = BatchNormalization(axis=channel_axis)(x)
    x = Activation(relu6)(x)
 
    x = Conv2D(filters, (1, 1), strides=(1, 1), padding='same')(x)
    x = BatchNormalization(axis=channel_axis)(x)
 
    if r:
        x = add([x, inputs])
    return x
 
 
def _inverted_residual_block(inputs, filters, kernel, t, strides, n):
    """Inverted Residual Block
    This function defines a sequence of 1 or more identical layers.
    # Arguments
        inputs: Tensor, input tensor of conv layer.
        filters: Integer, the dimensionality of the output space.
        kernel: An integer or tuple/list of 2 integers, specifying the
            width and height of the 2D convolution window.
        t: Integer, expansion factor.
            t is always applied to the input size.
        s: An integer or tuple/list of 2 integers,specifying the strides
            of the convolution along the width and height.Can be a single
            integer to specify the same value for all spatial dimensions.
        n: Integer, layer repeat times.
    # Returns
        Output tensor.
    """
 
    x = _bottleneck(inputs, filters, kernel, t, strides)
 
    for i in range(1, n):
        x = _bottleneck(x, filters, kernel, t, 1, True)
 
    return x
 
 
def MobileNetv2(input_shape, k):
    """MobileNetv2
    This function defines a MobileNetv2 architectures.
    # Arguments
        input_shape: An integer or tuple/list of 3 integers, shape
            of input tensor.
        k: Integer, layer repeat times.
    # Returns
        MobileNetv2 model.
    """
 
    inputs = Input(shape=input_shape)
    x = _conv_block(inputs, 32, (3, 3), strides=(2, 2))
 
    x = _inverted_residual_block(x, 16, (3, 3), t=1, strides=1, n=1)
    x = _inverted_residual_block(x, 24, (3, 3), t=6, strides=2, n=2)
    x = _inverted_residual_block(x, 32, (3, 3), t=6, strides=2, n=3)
    x = _inverted_residual_block(x, 64, (3, 3), t=6, strides=2, n=4)
    x = _inverted_residual_block(x, 96, (3, 3), t=6, strides=1, n=3)
    x = _inverted_residual_block(x, 160, (3, 3), t=6, strides=2, n=3)
    x = _inverted_residual_block(x, 320, (3, 3), t=6, strides=1, n=1)
 
    x = _conv_block(x, 1280, (1, 1), strides=(1, 1))
    x = GlobalAveragePooling2D()(x)
    x = Reshape((1, 1, 1280))(x)
    x = Dropout(0.3, name='Dropout')(x)
    x = Conv2D(k, (1, 1), padding='same')(x)
 
    x = Activation('softmax', name='softmax')(x)
    output = Reshape((k,))(x)
 
    model = Model(inputs, output)
    plot_model(model, to_file='images/MobileNetv2.png', show_shapes=True)
 
    return model
 
 
if __name__ == '__main__':
    MobileNetv2((224, 224, 3), 1000)

 

 

 
 
MobileNet V1官方預訓練模型的使用:https://www.jianshu.com/p/fe0c1b10720b
https://blog.csdn.net/u011974639/article/details/79199306
 
https://www.jianshu.com/p/7f77faf1776d
https://zhuanlan.zhihu.com/p/58554116
完整實現可以參見GitHub(https://github.com/xiaohu2015/DeepLearning_tutorials/)
https://github.com/Hedlen/Mobilenet-Keras/blob/master/model/mobilenet.py
 https://www.jianshu.com/p/1cf3b543afff?utm_source=oschina-app
 
https://www.xianjichina.com/special/detail_433028.html

最後,給出3個版本的caffe模型:

mobilenet v1:https://github.com/shicai/MobileNet-Caffe/blob/master/mobilenet_deploy.prototxt

mobilenet v2:https://github.com/shicai/MobileNet-Caffe/blob/master/mobilenet_v2_deploy.prototxt

mobilenet v3:https://github.com/jixing0415/caffe-mobilenet-v3

相關文章