通俗講:端側神經網絡GhostNet(2019)

喜歡的話請關注我們的微信公衆號~《你好世界煉丹師》。

  • 公衆號主要講統計學,數據科學,機器學習,深度學習,以及一些參加Kaggle競賽的經驗。
  • 公衆號內容建議作爲課後的一些相關知識的補充,飯後甜點。
  • 此外,爲了不過多打擾,公衆號每週推送一次,每次4~6篇精選文章。

微信搜索公衆號:你好世界煉丹師。期待您的關注。


通俗講:端側神經網絡GhostNet(2019)

GhostNet是華爲諾亞方舟實驗室提出的一個新型神經網絡結構。目的類似Google提出的MobileNet,都是爲了硬件、移動端設計的輕小網絡,但是效果想擺MobileNet更好。

GhostNet基於Ghost模塊,這個特點是不改變卷積的輸出特徵圖的尺寸和通道大小,但是可以讓整個計算量和參數數量大幅度降低。簡單的說,GhostNet的主要貢獻就是減低計算量、提高運行速度的同時,精準度降低的更少了,而且這種改變,適用於任意的卷積網絡,因爲它不改變輸出特徵圖的尺寸。下面來具體看一看GhostNet到底是怎麼實現的吧。

GhostNet發現了一個這樣的問題:想象一張要進行分類的圖片,這個圖片經過卷積層,假設產生了16個通道的特徵圖,如果我們把16個通道的圖畫出來,變成16個黑白圖片,我們可以保證,每一個特徵圖都體現了原來圖片不同的特徵嗎?如圖19.9所示:
圖19.9 特徵圖比較
圖19.9是處理MNIST手寫數據集中第一層卷積層產生的32個特徵圖的圖像,你可以找到多少組重複的、極爲類似的特徵圖呢?不少吧。GhostNet就是覺得,既然有這麼多的特徵圖都是相似的,那麼生成相似的特徵圖的那部分計算量不就是多餘的可以節省的嘛。

注意:這個相似的特徵圖,叫做Ghost,鬼影。

GhostNet的整體結構是仿照MobileNet-v3的結構,只是用Ghost Module作爲基本組件,這裏也不多將MobileNet-v3的結構了,下面要講的就是我們能從GhostNet中到底學到什麼?學到怎麼用GhostNet的基本思想來降低我們模型的計算量。下面的內容就會圍繞着GhostNet的Ghost-Blockneck展開。

注意:這個Ghost-Blockneck是基於Ghost Module組件,還有SE Module和Depthwise卷積。

19.14.1 Ghost Module

Ghost Module就是GhostNet的主要貢獻了。來看代碼:

class GhostModule(nn.Module):
  def __init__(self, inp, oup, kernel_size=1, ratio=2, dw_size=3, stride=1, relu=True):
  super(GhostModule, self).__init__()
  self.oup = oup 
  init_channels = math.ceil(oup / ratio) 
  new_channels = init_channels*(ratio-1) 
  self.primary_conv = nn.Sequential( 
    nn.Conv2d(inp, init_channels, kernel_size, stride, padding=kernel_size//2, bias=False),
    nn.BatchNorm2d(init_channels),
    nn.ReLU(inplace=True) if relu else nn.Sequential(),
  )
  self.cheap_operation = nn.Sequential(  
    nn.Conv2d(init_channels, new_channels,dw_size,stride=1,
              padding=dw_size//2, groups=init_channels, bias=False),
    nn.BatchNorm2d(new_channels),
    nn.ReLU(inplace=True) if relu else nn.Sequential(),
  )
 
 def forward(self, x):
   x1 = self.primary_conv(x)
   x2 = self.cheap_operation(x1)
   out = torch.cat([x1,x2], dim=1) 
   return out[:,:self.oup,:,:] 

一個非常標準的PyTorch模型類的定義。代碼中主要有以下內容需要注意:

  • 看一下參數,inp就是輸入的通道數,oup就是輸出的通道數。這裏的ratio參數是一個重點,體現了特徵圖中,有多少的特徵圖不是Ghost的比例。比方說,生成16個特徵圖,然後ratio=2的話,就說明有8個特徵圖不是鬼影,如果ratio=4的話,那就是隻有4個特徵圖不是鬼影。
  • init_channels就是不是鬼影的特徵圖的通道數量,new_channels就是鬼影特徵圖的通道數量。兩者相加應該是等於oup的,但是因爲math.ceil(oup / ratio) 是向上取整,所以可能出現兩者相加大於oup的情況,所以在forward函數的return中,僅僅返回前oup個通道,來保證輸出特徵圖和預想的通道數是一致的。
  • 整個過程也很簡單,先用卷積生成init_channels通道數量的特徵圖,認爲這些是有效的、不重複的,然後再用這些init_channels通道特徵圖再通過卷積生成new_channels個鬼影通道特徵圖,是鬼影的和不是鬼影的在通道維度上拼接起來,就完事了。但是在生成鬼影特徵圖的過程中,卷積中出現了一個陌生的參數groups,這個就是分組卷積的知識,下一個小節會講解。
  • 這個Conv層+BN層+ReLU激活函數基本是標準配置了。

19.14.2 Group Convolution分組卷積

之前在MobileNet的深度可分離卷積中已經講了什麼是Depthwise,其實這就是分組卷積的一種形式。如果分的組數等於輸入特徵圖的通道數,那麼就是Depthwise了,如果分的組沒有那麼多,就是一般的分組卷積。具體區分如圖19.10所示:
在這裏插入圖片描述
在這裏介紹分組卷積只是因爲在代碼中使用到了這個概念。在代碼中可以看到生成鬼影特徵圖的時候,使用的是分組卷積(也是Depthwise):

self.cheap_operation = nn.Sequential(  
  nn.Conv2d(init_channels, new_channels,dw_size,stride=1,
            padding=dw_size//2, groups=init_channels, bias=False),
  nn.BatchNorm2d(new_channels),
  nn.ReLU(inplace=True) if relu else nn.Sequential(),
  )

有一個參數groups就是要分的組數,如果groups的數值等於輸入通道數,那麼就是Depthwise的方法。

注意:現在用分組卷積的話,一般就是用Depthwise的方法,所以要是看到Depthwise,實現的時候要想到用分組卷積groups這個參數。

19.14.3 SE Module

SE Module是SENet網絡提出的Module,這個網絡在2017年的ImageNet的圖像分類任務中拿到了冠軍。SENet是啥就不說了,2017年的冠軍現在在AI領域中現在已經有點過時了,但是SENet的核心SE Module保留了下來。

SE是Squeeze-and-Excitation,擠壓與刺激(這個SE的翻譯有點奇怪)。廢話不多說,直接看代碼來理解吧:

class SELayer(nn.Module):
  def __init__(self, channel, reduction=4):
  super(SELayer, self).__init__()
  self.avg_pool = nn.AdaptiveAvgPool2d(1)
  self.fc = nn.Sequential(
     nn.Linear(channel, channel // reduction), # //是求商,%是求餘數。8//2=4,8%2=0
     nn.ReLU(inplace=True),
     nn.Linear(channel // reduction, channel), 
  ) 
 
 def forward(self, x):
   b, c, _, _ = x.size()
   y = self.avg_pool(x).view(b, c)
   y = self.fc(y).view(b, c, 1, 1)
   y = torch.clamp(y, 0, 1)
   return x * y

這個自適應池化層,其實就是可以隨便設置池化之後的尺寸,然後這個都可以適應。比方說,輸入的維度是[16,64,7,7],這個batch中有16個特徵圖,然後每一個圖片有64個通道,特徵圖的尺寸是7*7的,然後假設設置的參數是(5,7),那麼可以得到[16,64,5,7]這樣的池化結果。具體過程就不講解了,在這裏只用知道怎麼用就行了。

這裏的參數是1,那麼就會產生[16,64,1]這樣的結果。說白了,就是一個全局平均池化層嘛。

然後繼續上面的例子,把這個[16,64,1]變成[16,64]之後輸入到全連接層,經過兩層全連接層後還是[16,64]的尺寸,然後x*y就是SE Module的最終返回值。

其實很好理解,相當於SE Module對通道進行了一個權重的評估。有的通道可能重要,有的通道可能不重要,所以經過這個過程讓特徵圖的每一個通道,得到了1個權重值。如何體現權重值的?就是讓那個通道的每一個值,都乘上這個權重值就行了,簡單粗暴,但是是有效果的。

  • 注意:SE Module使用全連接層,如果每一層都是用SE Module的話,可能增加10%左右的計算量。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章