輕量級CNN架構設計
GiantPandaCV導語
卷積神經網絡架構設計,又指backbone設計,主要是根據具體任務的數據集特點以及相關的評價指標來確定一個網絡結構的輸入圖像分辨率,深度,每一層寬度,拓撲結構等細節(jié)。公開發(fā)表的論文大多都是基于ImageNet這種大型的公開數據集來進行的通用結構設計,早期只以其分類精度來證明設計的優(yōu)劣,后來也慢慢開始對比參數量(Params)和計算量(FLOPs),由于ImageNet的數據量十分巨大且豐富,所以通常在該數據集上獲得很好精度的網絡結構泛化到其他任務性能也都不會差。但在很多特定任務中,這種通用的結構雖然效果還可以,卻并不算最好,所以一般在實際應用時通常是基于已公開發(fā)表的優(yōu)秀網絡結構再根據任務特點進行適當修改得到自己需要的模型結構。
目前人工智能技術應用的一個趨勢是在端側平臺上部署高性能的神經網絡模型并能在真實場景中實時(大于30幀)運行,如移動端/嵌入式端設備。這些平臺的特點是內存資源少,處理器性能不高,功耗受限,這使得目前精度最高的模型由于對內存和計算資源的超額要求使得根本無法在上面部署且達到實時性的要求,雖然可以通過知識蒸餾,通道剪枝,低比特量化等一系列手段來降低模型參數量和計算量,但仍然遠遠不夠,且在精度和幀率之間各種trade-off也非常繁瑣,所以直接設計輕量級的架構,然后結合剪枝量化是最有效的解決辦法,本文將結合自己看的論文和參加項目比賽的經驗講述輕量級CNN的發(fā)展以及一些設計總結,如有不對之處請不吝賜教。
基本概念
感受野(Receptive Field)
感受野指的是卷積神經網絡每一層輸出的特征圖(feature map)上每個像素點映射回輸入圖像上的區(qū)域大小,神經元感受野的范圍越大表示其能接觸到的原始圖像范圍就越大,也意味著它能學習更為全局,語義層次更高的特征信息,相反,范圍越小則表示其所包含的特征越趨向局部和細節(jié)。因此感受野的范圍可以用來大致判斷每一層的抽象層次,并且我們可以很明顯地知道網絡越深,神經元的感受野越大。

分辨率(Resolution)
分辨率指的是輸入模型的圖像尺寸,即長寬大小。通常情況會根據模型下采樣次數n和最后一次下采樣后feature map的分辨率k×k來決定輸入分辨率的大小,即:
從輸入r×r到最后一個卷積特征feature map的k×k,整個過程是一個信息逐漸抽象化的過程,即網絡學習到的信息逐漸由低級的幾何信息轉變?yōu)楦呒壍恼Z義信息,這個feature map的大小可以是3×3,5×5,7×7,9×9等等,k太大會增加后續(xù)的計算量且信息抽象層次不夠高,影響網絡性能,k太小會造成非常嚴重的信息丟失,如原始分辨率映射到最后一層的feature map有效區(qū)域可能不到一個像素點,使得訓練無法收斂。
在ImageNet分類任務中,通常設置的5次下采樣,并且考慮到其原始圖像大多數在300分辨率左右,所以把最后一個卷積特征大小設定為7×7,將輸入尺寸固定為224×224×3。在目標檢測任務中,很多采用的是416×416×3的輸入尺寸,當然由于很多目標檢測模型是全卷積的結構,通常可以使用多尺寸訓練的方式,即每次輸入只需要保證是32×的圖像尺寸大小就行,不固定具體數值。但這種多尺度訓練的方式在圖像分類當中是不通用的,因為分類模型最后一層是全連接結構,即矩陣乘法,需要固定輸入數據的維度。
深度(Depth)
神經網絡的深度決定了網絡的表達能力,它有兩種計算方法,早期的backbone設計都是直接使用卷積層堆疊的方式,它的深度即神經網絡的層數,后來的backbone設計采用了更高效的module(或block)堆疊的方式,每個module是由多個卷積層組成,它的深度也可以指module的個數,這種說法在神經架構搜索(NAS)中出現的更為頻繁。通常而言網絡越深表達能力越強,但深度大于某個值可能會帶來相反的效果,所以它的具體設定需要不斷調參得到。
寬度(Width)
寬度決定了網絡在某一層學到的信息量,但網絡的寬度時指的是卷積神經網絡中最大的通道數,由卷積核數量最多的層決定。通常的結構設計中卷積核的數量隨著層數越來越多的,直到最后一層feature map達到最大,這是因為越到深層,feature map的分辨率越小,所包含的信息越高級,所以需要更多的卷積核來進行學習。通道越多效果越好,但帶來的計算量也會大大增加,所以具體設定也是一個調參的過程,并且各層通道數會按照8×的倍數來確定,這樣有利于GPU的并行計算。

下采樣(Down-Sample)
下采樣層有兩個作用,一是減少計算量,防止過擬合,二是增大感受野,使得后面的卷積核能夠學到更加全局的信息。下采樣的設計有兩種:
采用stride為2的池化層,如Max-pooling或Average-pooling,目前通常使用Max-pooling,因為它計算簡單且最大響應能更好保留紋理特征; 采用stride為2的卷積層,下采樣的過程是一個信息損失的過程,而池化層是不可學習的,用stride為2的可學習卷積層來代替pooling可以得到更好的效果,當然同時也增加了一定的計算量。
(突然想到為啥不使用雙線性插值,向下插值來代替Pooling,這個雖然比MaxPooling計算量更大,但是保留的信息應該更豐富才是)
上采樣(Up-Sampling)
在卷積神經網絡中,由于輸入圖像通過卷積神經網絡(CNN)提取特征后,輸出的尺寸往往會變小,而有時我們需要將圖像恢復到原來的尺寸以便進行進一步的計算(如圖像的語義分割),這個使圖像由小分辨率映射到大分辨率的操作,叫做上采樣,它的實現一般有三種方式:
插值,一般使用的是雙線性插值,因為效果最好,雖然計算上比其他插值方式復雜,但是相對于卷積計算可以說不值一提; 轉置卷積又或是說反卷積,通過對輸入feature map間隔填充0,再進行標準的卷積計算,可以使得輸出feature map的尺寸比輸入更大; Max Unpooling,在對稱的max pooling位置記錄最大值的索引位置,然后在unpooling階段時將對應的值放置到原先最大值位置,其余位置補0;

參數量(Params)
參數量指的網絡中可學習變量的數量,包括卷積核的權重weight,批歸一化(BN)的縮放系數γ,偏移系數β,有些沒有BN的層可能有偏置bias,這些都是可學習的參數 ,即在模型訓練開始前被賦予初值,在訓練過程根據鏈式法則中不斷迭代更新,整個模型的參數量主要由卷積核的權重weight的數量決定,參數量越大,則該結構對運行平臺的內存要求越高,參數量的大小是輕量化網絡設計的一個重要評價指標。
計算量(FLOPs)
神經網絡的前向推理過程基本上都是乘累加計算,所以它的計算量也是指的前向推理過程中乘加運算的次數,通常用FLOPs來表示,即floating point operations(浮點運算數)。計算量越大,在同一平臺上模型運行延時越長,尤其是在移動端/嵌入式這種資源受限的平臺上想要達到實時性的要求就必須要求模型的計算量盡可能地低,但這個不是嚴格成正比關系,也跟具體算子的計算密集程度(即計算時間與IO時間占比)和該算子底層優(yōu)化的程度有關。
卷積計算類型
標準卷積 (Convolution)
在神經網絡架構設計中,標準卷積是最常見的結構,假設其輸入feature map的維度是(1, iC, iH, iW),每個卷積核的維度是(1, iC, k, k),一次卷積濾波得到一層feature map的維度為(1,1, oH, oW),一共有oC個卷積核,則輸出feature map的維度是(1, oC, oH, oW),計算量為iC×k×k×oC×oH×oW,計算過程如下圖所示,由于太過基礎,故不贅述。

深度卷積 (Depthwise Convolution)
深度卷積與標準卷積相比,顧名思義是在深度上做了文章,而這里的深度跟網絡的深度無關,它指的通道,標準卷積中每個卷積核都需要與feature map的所有層進行計算,所以每個卷積核的通道數等于輸入feature map的通道數,通過設定卷積核的數量可以控制輸出feature map的通道數。而深度卷積每個卷積核都是單通道的,維度為(1,1,k,k) ,卷積核的個數為iC,即第i個卷積核與feature map第i個通道進行二維的卷積計算,最后輸出維度為(1,iC,oH,oW),它不能改變輸出feature map的通道數,所以通常會在深度卷積后面接上一個(oC,iC,1,1)的標準卷積來代替3×3或更大尺寸的標準卷積,總的計算量為iC×k×k×oH×oW+iC×oH×oW×oC,是普通卷積的1/oC+1/(k×k),大大減少了計算量和參數量,又可以達到相同的效果,這種結構被稱為深度可分離卷積(Depthwise Separable Convolution),在MobileNet V1被提出,后來漸漸成為輕量化結構設計的標配。

深卷積之前一直被吐槽在GPU上運行速度還不如一般的標準卷積,因為depthwise 的卷積核復用率比普通卷積要小很多,計算和內存訪問的比值比普通卷積更小,因此會花更多時間在內存開銷上,而且per-channel的矩陣計算很小不容易并行導致的更慢,但理論上計算量和參數量都是大大減少的,只是底層優(yōu)化的問題。
分組卷積 (Group Convolution)
分組卷積最早在AlexNet中出現,當時作者在訓練模型時為了減少顯存占用而將feature map分組然后給多個GPU進行處理,最后把多個輸出進行融合。具體計算過程是,分組卷積首先將輸入feature map分成g個組,每個組的大小為(1, iC/g, iH, iW),對應每組中一個卷積核的大小是(1,iC/g,k,k),每組有oC/g個卷積核,所以每組輸出feature map的尺寸為(1,oC/g,oH,oW),最終g組輸出拼接得到一個(1,oC,oH,oW)的大feature map,總的計算量為iC/g×k×k×oC×oH×oW,是標準卷積的1/g,參數量也是標準卷積的1/g。

但由于feature map組與組之間相互獨立,存在信息的阻隔,所以ShuffleNet提出對輸出的feature map做一次channel shuffle的操作,即通道混洗,打亂原先的順序,使得各個組之間的信息能夠交互起來。
空洞卷積 (Dilated Convolution)
空洞卷積是針對圖像語義分割問題中下采樣會降低圖像分辨率、丟失信息而提出的一種卷積思路。通過間隔取值擴大感受野,讓原本3x3的卷積核,在相同參數量和計算量下擁有更大的感受野。這里面有個擴張率(dilation rate)的系數,這個系數定義了這個間隔的大小,標準卷積相當于dilation rate為1的空洞卷積,下圖展示的是dilation rate為2的空洞卷積計算過程,可以看出3×3的卷積核可以感知標準的5×5卷積核的范圍,還有一種理解思路就是先對3×3的卷積核間隔補0,使它變成5×5的卷積,然后再執(zhí)行標準卷積的操作。

轉置卷積 (Transposed Convolutions)
轉置卷積又稱反卷積(Deconvolution),它和空洞卷積的思路正好相反,是為上采樣而生,也應用于語義分割當中,而且他的計算也和空洞卷積正好相反,先對輸入的feature map間隔補0,卷積核不變,然后使用標準的卷積進行計算,得到更大尺寸的feature map。

可變形卷積 (deformable convolution)
以上的卷積計算都是固定的,每次輸入不同的圖像數據,卷積計算的位置都是完全固定不變,即使是空洞卷積/轉置卷積,0填充的位置也都是事先確定的。而可變性卷積是指卷積核上對每一個元素額外增加了一個h和w方向上偏移的參數,然后根據這個偏移在feature map上動態(tài)取點來進行卷積計算,這樣卷積核就能在訓練過程中擴展到很大的范圍。而顯而易見的是可變性卷積雖然比其他卷積方式更加靈活,可以根據每張輸入圖片感知不同位置的信息,類似于注意力,從而達到更好的效果,但是它比可行變卷積在增加了很多計算量和實現難度,目前感覺只在GPU上優(yōu)化的很好,在其他平臺上還沒有見到部署。

其他算子
池化(pooling)
池化這個操作比較簡單,一般在上采樣和下采樣的時候用到,沒有參數,不可學習,但操作極為簡單,和depthwise卷積類似,只是把乘累加操作替換成取最大/取平均操作。
最大池化和平均池化

全局平均池化 全局平均池化的操作是對一個維度為(C,H,W)的feature map,在HW方向整個取平均,然后輸出一個長度為C的向量,這個操作一般在分類模型的最后一個feature map之后出現,然后接一個全連接層就可以完成分類結果的輸出了。早期的分類模型都是把最后一個feature map直接拉平成C×H×W的向量,然后再接全連接層,但是顯然可以看出來這個計算量極大,甚至有的模型最后一個全連接層占了整個模型計算量的50%以上,之后由研究人員發(fā)現對這個feature map做一個全局平均池化,然后再加全連接層可以達到相似的效果,且計算量降低到了原來的1/HW。 最大向上池化 這個操作在前面基本概念一節(jié)上采樣段落中有描述,故不贅述。 全連接計算(Full Connected)
這個本質其實就是矩陣乘法,輸入一個(B, iC)的數據,權重為(iC, oC),那么輸出為(B, oC),在多層感知機和分類模型最后一層常常見到。

Addition / Concatenate分支
Addition和Concatenate分支操作統(tǒng)稱為shortcut,如下圖所示,操作極為簡單。Addition是在ResNet中提出,兩個相同維度的feature map相同位置點的值直接相加,得到新的相同維度feature map,這個操作可以融合之前的特征,增加信息的表達,Concatenate操作是在Inception中首次使用,被DenseNet發(fā)揚光大,和addition不同的是,它只要求兩個feature map的HW相同,通道數可以不同,然后兩個feature map在通道上直接拼接,得到一個更大的feature map,它保留了一些原始的特征,增加了特征的數量,使得有效的信息流繼續(xù)向后傳遞。

Channel shuffle
channel shuffle是ShuffleNet中首次提出,主要是針對分組卷積中不同組之間信息不流通,對不同組的feature map進行混洗的一個操作,如下圖所示,假設原始的feature map維度為(1,9,H,W),被分成了3個組,每個組有三個通道,那么首先將這個feature map進行reshape操作,得到(1,3,3,H,W),然后對中間的兩個大小為3的維度進行轉置,依然是(1,3,3,H,W),最后將通道拉平,變回(1,9,H,W),就完成了通道混洗,使得不同組的feature map間隔保存,增強了信息的交互。

常用激活函數
激活函數的非線性是神經網絡發(fā)揮作用最重要的因素之一,而對于實際部署,激活函數的實現也是很重要的一個方面,實現的不好對加速效果影響很大,這里主要講幾個部署當中常見的激活函數。
ReLU系列
這里主要指常用的ReLU,ReLU6和leaky ReLU。ReLU比較好部署,小于0的部分為0,大于0的部分為原始值,只需要判斷一下符號位就行;ReLU6與ReLU相比也只是在正向部分多了個閾值,大于6的值等于6,在實現時多了個比較也不算麻煩;而leaky ReLU和ReLU正向部分一樣,都是大于0等于原始值,但負向部分卻是等于原始值的1/10,浮點運算的話乘個0.1就好了,如果因為量化要實現整數運算,這塊可以做個近似,如0.1用13>>7來代替,具體實現方法多種多樣 ,還算簡單。

Sigmoid系列
這里主要指sigmoid,還有和他相關的swish:
可以看出,如果按照公式來實現sigmoid對低性能的硬件來說非常不友好,因為涉及到大量的exp指數運算和除法運算,于是有研究人員針對此專門設計了近似的硬件友好的函數h-sigmoid和h-swish函數,這里的h指的就是hardware的意思:
可視化的對比如下圖所示,可以看出在保證精度的同時又能大大方便硬件的實現,當然要直接實現sigmoid也是可以的,畢竟sigmoid是有限輸出,當輸入小于-8或大于8的時候,輸出基本上接近于-1和1,可以根據這個特點設計一個查找表,速度也超快,且我們實測對精度沒啥影響。

經典輕量化模型
早期比較經典的卷積神經網絡,如AlexNet,VGG,GoogleNet(或Inception),ResNet,DenseNet都是以提升模型在ImageNet數據集上的分類精度為主了,很少考慮參數量和計算量的問題,他們的主要結構解析起來也比較簡單,基本都是由標準卷積(7×7,5×5,3×3和1×1),Pooling和shortcut操作(Addition / Concatenate)構成,而且以3×3及其以上的卷積核為主,通道數也是動輒上千,所以參數量和計算量巨大。后續(xù)研究人員慢慢發(fā)現兩個3×3卷積可以代替一個5×5卷積的效果,三個3×3卷積可以代替一個7×7卷積的效果,大量使用1×1卷積,使用3×3 depthwise conv + pointwise conv(1×1標準卷積)可以代替3×3普通卷積......一系列操作可以減少參數量和計算量,所以下面講述一下一些輕量級神經網絡發(fā)展的歷史,因為這塊很多人都講過,所以我會簡單一些,挑重點說說。
SqueezeNet
SqueezeNet是公認的輕量級模型設計最早期的工作之一,作者提出了三種策略來實現在保持精度的情況下大大減少當時主流模型(以AlexNet為例)的計算量和參數量:
將模型中一部分的3×3卷積用1×1來代替,1×1卷積是3×3參數量和計算量的1/9,所以可以大大減少參數量和計算量; 減少3×3卷積的輸入通道數,這個可以通過在進入3×3卷積之前加一個1×1卷積來實現通道數量的減少; 將下采樣層的位置往后推,使得模型可以在更大的feature map上進行更多的學習,這一步雖然會在增加計算量,但是和上面兩個策略結合可以在維持模型精度的情況下仍大大減少參數量和計算量;

根據上面的策略,作者提出了fire module的子結構,如下圖所示,然后整個模型由這樣的子結構堆疊而成。這個fire module由squeeze部分和expand部分構成,squeeze部分是1×1的卷積層,而expand部分是1×1的卷積和3×3的卷積拼接起來的,每次feature map輸入這個fire module會在squeeze層降低通道數,然后在expand通道增加通道數,從而在參數量更少的情況下仍然可以得到充分的學習。最后結合一些模型壓縮的方法可以使得SqueezeNet在達到AlexNet同等精度的情況下,參數量減少到后者的1/50,計算量減少到后者的1/510。
這篇論文使用大量1×1的卷積核代替3×3卷積,并且利用1×1卷積改變大尺度卷積層輸入feature map的通道數從而減少計算量的思想是非常有意義的,后續(xù)的很多輕量級網路的論文都沿用了這種套路。
MobileNet系列
MobileNet系列一共有V1,V2和V3三篇論文,簡要的講:
MobileNet V1主要思想是提出了一種新的結構—深度可分離卷積(Depthwise Separable Convolution)來代替標準3×3卷積,從而大大減少模型的參數量和計算量; MobileNet V2在V1的基礎上提出了一種倒置殘差的模塊,這個模塊有三個卷積,第一個部分是一個1×1標準卷積,用來升維,第二個部分是由3×3深度卷積+1×1標準卷積構成的深度分離卷積,用來學習特征和降維,模塊的輸出和輸入再進行一個Addition的操作,由于和ResNet中維度升降方式相反,所以稱為倒置殘差。中間升維的作用是讓深度可分離卷積得到更充分的學習,計算量相對于標準卷積來說也不大,而且這種升降維的方式非常靈活,可以大大減少計算量。本文還從流形學的角度探究了輸入深度可分離卷積上一層的ReLU6對信息傳遞的影響,理論證明去掉上一個1×1標準卷積的ReLU激活函數能更有利于后面的深度可分離卷積對特征的學習。

MobileNet V3感覺相對于前兩篇沒有那么大的結構創(chuàng)新了,主要思想是神經架構搜索(NAS)和硬件友好結構,總的來看V3的結構是在V2的基礎上進行了一些修改,如增加了SE block這種已被提出的注意力機制,激活函數換成了H-swish,last stage減少了幾層計算,針對語義分割提出了Lite R-ASPP的head(不在討論之列),整個論文看著像是堆tricks,重點不是很突出,有點年底沖業(yè)績的嫌疑。

根據我自己的比賽和項目經驗來看,還是MobileNet V1和V2的結構比較實用,參數量和計算量小,可拓展性好,SE block這種模塊對延時影響還是不小,而且我們發(fā)現其他各種花里胡哨的激活函數跟ReLU/ReLU6相比都差不多,對精度沒有很大的影響,還不如直接部署ReLU/ReLU6來的方便。
ShuffleNet系列 曠視出品的ShuffleNet系列有兩篇論文,且后一篇在打前一篇的臉,很有意思。
ShuffleNet V1是在MobileNet V1后MobileNet V2前提出的,說實話結構上和MobileNet V2還挺像,大家可以上下兩張圖片對比一下。兩者都想到了學習ResNet的殘差結構,區(qū)別在于ShuffleNet V1覺得block當中的1×1標準卷積也非常耗時,于是用1×1的分組卷積外加channel shuffle的操作給替換了,然后MobileNet V2會先升維讓深度可分離卷積得到充分的學習再降維回來,ShuffleNet V1中stride為2的模塊也有自己的特色,雖然看著MobileNet V2的結構更簡潔一些,但ShuffleNet V1創(chuàng)新也是不少,尤其那個用channel shuffle增強不同組之間信息交互的操作。

ShuffleNet ?V2論文是一篇誠意滿滿之作,作者通過分析ShuffleNet v1與MobileNet v2這兩個移動端網絡在GPU/ARM兩種平臺下的時間消耗分布,看出Conv等計算密集型操作占了絕大多數時間,但其它像Elemwise和IO等內存讀寫密集型操作也占了相當比例的時間,因此像以往那樣僅以FLOPs來作為指導準則來設計CNN網絡是不完備的,雖然它可以反映出占大比例時間的Conv操作,但不夠準確。于是作者提出了高效網絡設計的四個指導原則:
當輸入和輸出的通道數相同時,conv計算所需的MAC(memory access cost)最??; 大量的分組卷積會增加MAC開銷; 網絡結構的碎片化會減少其可并行優(yōu)化的程度,GoogleNet系列和NASNet中很多分支進行不同的卷積/pool計算非常碎片,對硬件運行很不友好; Element-wise操作不可忽視,對延時影響很大,包括ReLU,Addition,AddBias等,主要是因為這些操作計算與內存訪問的占比太??;
基于此,作者提出了ShuffleNet V2的blocks,如下所示,與V1相比,去掉了分組卷積的操作,去掉了Add操作,換成了Concat,stride為2的block的旁路把平均池化換成了深度可分離卷積,為了繼續(xù)延續(xù)channel shuffle的操作,作者在block進去的地方做了個split的操作,最后再concat+channel shuffle,這里是為了替換掉之前的Add,同時也可以減少計算量。

Shift: A Zero FLOP, Zero Parameter Alternative to Spatial Convolutions
這是一篇很有意思的論文,主要是提出了一種無參數,無計算的移位算子來代替ResNet中計算量占比很高的3×3卷積,這種算子稱為shift kernel,如下圖所示,只需要根據kernel上的shift信息對feature map進行位置上的上下左右偏移即可得到新的feature map,沒有計算,僅僅是訪存操作。詳細一點說就是如下面Shift框里面的第一個卷積核,它只在黃色的區(qū)域為1,其他白色的區(qū)域為0,在做卷積計算的時候其實就相當于把輸入feature map中間偏左邊的那個點的值平移到輸出feature map中間的地方,正如作者標注的向右的箭頭所示。而且這個操作都是per-channel的,所以每個卷積核只有k×k種可能性,當通道數大于k×k時,就需要將所有通道分成iC/(k×k)組,剩下的channel設置為center,即中間為1,其余為0,然后怎么去選取每個卷積核的類型呢,論文在兩個shift kernel中間夾了一個1×1的標準卷積,要保證第二次shift操作之后數據與第一次輸入shift kernel之前順序一致,并且這個shift操作可以通過SGD的方式進行端到端訓練,再具體的細節(jié)論文其實也沒有闡述的很清楚,而且我目前也沒有看到作者公布源代碼,不過這篇論文看起來還是很有意思的,論文中還分析了depthwise計算不高效的原因在于計算/IO時間導致運行更慢的問題,其實這個shift kernel的作用并沒有產生新的信息或者去進行特征的學習(畢竟連參數都沒有),而是對feature map空域的信息做了個混洗,使得與他相接的1×1卷積可以學到更豐富的特征,在我看來有點類似于在網絡內部做數據增強的感覺。

GhostNet
GhostNet也是一篇很有意思且簡潔的架構設計的論文,作者在可視化一些訓練好的神經網絡中間feature map時發(fā)現它們通常會包含一些相似且冗余的特征圖,使得神經網絡能得到更充分的學習。基于這個想法,作者通過設定一系列廉價的線性運算操作來代替部分卷積計算,以此來產生更多的特征圖,僅僅這么一個簡單的操作就可以減少模型的參數量和計算量,而且在幾個視覺公開數據集上取得了很不錯的效果,甚至超越了MobileNet V3,感覺非常的大道至簡,這也是我比較喜歡的原因。

基于特定硬件的神經架構搜索
另見 :https://zhuanlan.zhihu.com/p/320290820
設計方法總結
接下來我將結合自己看過的論文,還有這一年多的項目比賽經歷談一談我所理解的圖像分類和目標檢測相關輕量級模型設計,本文思想還比較淺薄,主要是給自己的工作做個總結,有不正確的地方希望大家能共同討論。
設計之前
通常我們都是基于已有的硬件架構去進行模型的部署,這個時候就需要確定這個架構下能部署什么算子,可以通過已有的接口自己拓展哪些算子,并且哪些算子不是很高效。就拿我去年參加的某視覺加速比賽來說,當時初出茅廬,不太懂硬件底層,頭鐵要在Xilinx ultra96 V1板子 + 我們組自研的硬件架構上部署當時最新,準確率最高的EfficientNet,因為準確率確實高,老板就欽定必須使用這個模型,并且選擇了比較合適的EfficientNet-B4,輸入分辨率由384改成256。后來一頓開搞發(fā)現有很多h-sigmoid操作,這個雖然之前還沒有經驗,但是還好很快想到用查找表去實現了,并且還發(fā)現我們的架構尚且不能實現全連接層(勿噴,之前都是在搞全卷積網絡),所以里面的SE block還有最后一層都無法用我們的架構部署,然后博士師兄就想到了利用ultra96板子上的ARM+NEON加速技術去實現這一部分,每次PS和PL交互數據,當時只有一個月的開發(fā)時間,因為這個模型比較大且當時我們開發(fā)模式的問題,連續(xù)熬夜差點沒把整個組人的命搭進去,最后上板跑幀率也只有6幀(師兄最開始預估能達到80幀,所以大家一直不停的往下做hhhhh),在ImageNet驗證集上純浮點準確率是0.805,INT8準確率是0.793,幀率低的原因有兩點,一個是模型確實很大,另一個是因為SE block在每一個stride為1的module中都出現了,整個模型PS和PL交互十分頻繁,而我們當時間很緊剛剛完成一版就必須提交了,所以這塊也根本沒有優(yōu)化,導致了延時很高,并且當時我們的架構只跑了100M的頻率,更高頻率會有一些bug(貌似是板子的問題),所以幀率非常低,這個真的是血的教訓:一定要量力而行,仔細研究評價指標,在硬件友好程度和精度上做一個trade-off,一味片面地追求精度真的要命呀。
輕量級CNN架構設計
總的思路:選定合適結構 + 通道剪枝 + 量化
訓練 :ImageNet pretrain model + Data Normalization(統(tǒng)計自己數據集的均值和方差) + Batch Normlization + 大batch size + 一堆數據增強tricks + 嘗試各種花里胡哨的loss function和optimizer
(再次說明這部分只討論圖像分類和目標檢測兩種任務,目前的視覺加速比賽基本都是基于這兩個任務做的,按照計算資源和內存從小到大排列,不用問,沒有劃分原則)
1.絕對貧窮人口
輸入分辨率要小,如128,160,192或256; 下采樣使用MaxPooling; 特征學習層使用Depthwise Separable Convolution,即一層3×3的Depthwise + 一層pointwise(1×1標準卷積)堆疊; 激活函數用ReLU;
另外這里推薦看看MCUNet,感覺非常呦西,MIT韓松團隊yyds!
2. 相對貧窮人口
輸入分辨率依舊要小,記住分辨率對計算量的影響都是翻倍的; 下采樣可以使用MaxPooling或者stride為2的Depthwise Separable Convolution; 特征學習層使用Depthwise Separable Convolution,或者MobileNet V2的倒置殘差結構,又或是ShuffleNet V2的unit,1×1的Group convolution能處理的比較好的話其實也推薦使用(主要是計算/訪存); 激活函數用ReLU或者ReLU6;
3. 低收入人口
輸入數據選用小分辨率,如果對精度要求高一些可以適當增大; 下采樣可以使用MaxPooling或者stride為2的Depthwise Separable Convolution; 特征學習層可以使用MobileNet V2的倒置殘差結構,又或是ShuffleNet V2的unit,也可以使用通道數小一點的3×3標準卷積; 激活函數用ReLU,ReLU6或者leaky ReLU,看效果了;
4.一般收入人口
輸入數據可以稍微大一些,288,320,384啥的可以考慮上了; 下采樣使用stride為2的Depthwise Separable Convolution或者stride為2的3×3卷積; 特征學習層既可以使用上述的,也可以使用3×3標準卷積 + 1×1標準卷積的堆疊形式,SE block這種硬件支持的還不錯的可以嘗試加上; 激活函數除了上述的可以試試H-sigmoid和H-swish,不過據我經驗效果基本和ReLU差不多;
5.高收入人口
輸入數據的分辨率可以往500-600靠攏了; 下采樣和上述一樣; 特征學習層可以上ResNet + SE block的配置,ResNet是真的牛逼,5×5的卷積啥的也可以整上,第一層直接上7×7的標準卷積也不是不可以,資源再多了可以增加通道數和深度 或者 上多核并行計算; 激活函數可以用H-swish;
番外
目標檢測任務中感覺Tiny YOLO V3非常受歡迎,建議嘗試!計算量太大可以換更輕量的backbone或者改輸入分辨率,輕量級的backbone+FPN的結構也很棒,且推薦使用商湯開源的mmdetection,訓練調參當場起飛。
另外之前閱讀MIT HAN lab基于特定硬件的神經架構搜索相關文章時發(fā)現他們設計模型常用的一個子結構:MobileNetV2的倒置殘差模塊 + SE block + H-swish,看他們在很多算法加速比賽上拿了冠軍,感覺百試不爽呀,且根據硬件資源進行拓展的靈活度也很高,具體可以參見他們今年發(fā)表的OnceForAll論文中的模型,源代碼都在Github上能找到,MIT HAN lab真學術界良心,再喊一句MIT韓松團隊yyds!
最后總結
過去的一年被導師安排著參加各種比賽,去年啥也不懂的時候還能拿幾個不錯的獎,今年感覺學了很多懂了很多,使用的模型也都對硬件比較友好,量化后幾乎無損(一個點以內),反倒連連受挫,心情非常沮喪,而且總被奇奇怪怪的模型打敗,總有一種自己學了一身正派功夫,最后反倒被野路子出招一擊即潰的感覺,然后論文也被拒了,沒心思改投,回想一下碰上疫情的這一年真的好失落,可能還是我太菜了吧。
馬上也要開始投入找實習(希望老板能放我)刷題找工作的階段了,最近也在培養(yǎng)下一屆,把工作慢慢移交給他們,一想到找工作,整個人的心態(tài)都不一樣了,無心科研,這篇文章就算是對我過去一年多的工作做個總結吧,同時也希望我們課題組能夠發(fā)展的越來越好,多多拿比賽大獎,多多發(fā)論文。
歡迎關注GiantPandaCV, 在這里你將看到獨家的深度學習分享,堅持原創(chuàng),每天分享我們學習到的新鮮知識。( ? ?ω?? )?
有對文章相關的問題,或者想要加入交流群,歡迎添加BBuf微信:
為了方便讀者獲取資料以及我們公眾號的作者發(fā)布一些Github工程的更新,我們成立了一個QQ群,二維碼如下,感興趣可以加入。
