ShuffleNet總結

在2017年底,Face++發了一篇論文ShuffleNet: An Extremely Efficient Convolutional Neural Network for Mobile Devices討論了一個極有效率且能夠運行在手機等移動設備上的網絡結構——ShuffleNet。這個英文名我更願意翻譯成「重組通道網絡」,ShuffleNet經過分組卷積與\(1 \times 1\)的卷積核來下降計算量,經過重組通道來豐富各個通道的信息。這個論文的mxnet源碼的開源地址爲:MXShuffleNethtml

分組卷積與核大小對計算量的影響

論文說中到「We propose using pointwise group convolutions to reduce computation complexity of 1 × 1 convolutions」,那麼爲何用分組卷積與小的卷積核會減小計算的複雜度呢?先來看看卷積在編程中是如何實現的,Caffe與mxnet的CPU版本都是用差很少的方法實現的,但Caffe的計算代碼會更加簡潔。python

不分組且只有一個樣本

在不分組與輸入的樣本量爲1(batch_size=1)的條件下,輸出一個通道上的一個點是卷積核會與全部的通道卷積之積,如圖1所示:git

conv1

圖1 輸入層(第一層)只有一個通道,那個第二層一個通道上的點是第一層通道相應區域與相應卷積核的卷積,第三層一個通道上的點是與第二層全部通道上相應區域與相應卷積核的卷積,並且對於輸出通道每一個輸入通道對應的卷積核是不同的,不一樣的輸出通道也有不一樣的卷積核,因此說卷積核的參數量是$C_{out} \times C_{in} \times K_h \times K_w$

在Caffe的計算方法中,先要將輸入張量爲\(n \times C_{in} \times H_{in} \times W_{in}\)(n是batch_size)轉化爲一個$ \left(C_{in} \times K_h \times K_w\right) \times \left(H_{in} \times W_{in}\right)\(的矩陣,這個過程叫**im2col**。最後獲得的輸出張量爲\)n \times C_{out} \times H_{in} \times W_{in}$。github

conv2

圖2 輸入的全部通道按卷積核的大小提取出來排列成一行,要注意的是在這只是示意圖,在實際的程序中,通常會排成一列,由於在防問數據時會一個通道一個通道地訪問的。輸出一個點要輸出的數據有$C_{in} \times K_h \times K_w$個。

conv3

圖3 輸出一個通道就有$H_{out} \times W_{out}$個點,並且在程序中同一個通道(圖中的同一個顏色)的內容是按行排列的,因此說轉換出來的的矩陣是圖中$ \left(C_{in} \times K_h \times K_w\right) \times \left(H_{out} \times W_{out}\right)$矩陣的轉置。

conv4

圖4 一樣卷積核(Filter)也要Reshape成$C_{out} \times \left( C_{in} \times K_h \times K_w \right)$ 的矩陣

獲得的兩個矩陣Feature與Filter相乘獲得輸出矩陣Output,再Reshape成\(C_{out} \times C_{in} \times H_{out} \times W_{out}\)張量:
\[ Filter_{C_{out} \times \left( C_{in} \times K_h \times K_w \right)} \times Feature_{\left(C_{in} \times K_h \times K_w\right) \times \left(H_{out} \times W_{out}\right)} = Output_{C_{out} \times (H_{out} \times W_{out})} \tag{1.1} \]編程

如今的計算技術中,對方長度爲\(n\)的方陣,計算量能從\(n^3\)代碼到\(n^{2.376}\),最小的複雜度如今仍然未知,本文爲了方便計算量就以\(n^3\)爲基準。因此式(1.1)的矩陣計算最普通的計算量\(Computation\)是:
\[ Computation=C_{out} \times H_{out} \times W_{out} \times \left( C_{in} \times K_h \times K_w \right)^2 \tag{1.2} \]
從式(1.2)中能夠看出來,卷積核的大小對計算量影響是很大的,\(3 \times 3\)的卷積核比\(1 \times 1\)的計算量要大\(3^4=81\)倍。網絡

分組且只有一個樣本

什麼叫作分組,就是將輸入與輸出的通道分紅幾組,好比輸出與輸入的通道數都是4個且分紅2組,那第一、2通道的輸出只使用第一、2通道的輸入,一樣那第三、4通道的輸出只使用第一、2通道的輸入。也就是說,不一樣組的輸出與輸入沒有關係了,減小聯繫必然會使計算量減少,但同時也會致使信息的丟失ide

當分紅g組後,一層參數量的大小由\(Filter_{C_{out} \times \left( C_{in} \times K_h \times K_w \right)}\)變成\(Filter_{C_{out} \times \left( C_{in} \times K_h \times K_w / g \right)}\)。Feature Matrix的大小雖然沒發生變化,可是每一組的使用量是原來的$1/g,Filter也只用到全部參數的\(1/g\)\(。而後再循環計算\)g$次(同時FeatureMatrix與FilterMatrix要有地址偏移),那麼計算公式與計算量的大小爲:
\[ Filter_{C_{out}/g \times \left( C_{in} \times K_h \times K_w /g \right)} \times Feature_{\left(C_{in} \times K_h \times K_w /g\right) \times \left(H_{out} \times W_{out}\right)} = Output_{C_{out}/g \times (H_{out} \times W_{out})} \tag{1.3} \]
\[ Computation=C_{out} \times H_{out} \times W_{out} \times \left( C_{in} \times K_h \times K_w /g \right)^2 \tag{1.4} \]函數

因此,分紅\(g\)組可使參數量變成原來的\(1/g\),計算量是原來的\(1/g^2\)優化

多個樣本輸入

爲了節省內存,多個樣本輸入的時候,上述的全部過程都不會改變,而是每個樣本都運行一次上述的過程。this

以上只是最簡單、粗略的分析,實際上計算效率的提高並不會有上述這麼多,一方面由於im2col會消耗與矩陣運算差很少的時間,另外一方面由於現代的blas庫優化了矩陣運算,複雜度並無上述分析的那麼多,還有計算過程for循環是比較耗時的指令,即便用openmp也不能優化卷積的計算過程。

交換通道(Shuffle Channels)

在上面我提到過,分組會致使信息的丟失,那麼有沒有辦法來解決這個問題呢?這個論文給出的方法就是交換通道,由於在同一組中不一樣的通道蘊含的信息多是相同的,若是在不一樣的組以後交換一些通道,那麼就能交換信息,使得各個組的信息更豐富,能提取到的特徵天然就更多,這樣是有利於獲得更好的結果。

shufleChannels

圖5 分組交換通道的示意圖,a)是不交換通道可是分紅3組了,要吧看到,不一樣的組是徹底獨立的;b)每組內又分紅3組,不分別交換到其它組中,這樣信息就發生了交換,c)這個是與b)是等價的。

ShuffleUnit

ShuffleUnit的設計參考了ResNet,總有兩個基本單元,兩人個基本單元功能不同,將他們組合起來就能夠獲得ShuffleNet。這樣的設計能夠在增長網絡的深度(比mobilenet深約一倍)的同時,減小參數總量與計算量(本人運行Cifar10時,速度大約是molibenet的10倍)。

shufleunit

圖6 b)與c)是兩人個ShuffleNet的基本單元,這兩個單元是參考了a)的設計,單元b)輸出與輸入的Shape一致,只是豐富了每一個通道的信息,單元c)增長了一倍的通道數且輸出的$H_{out}$、$W_{out}$ 比$H_{in}$、$W_{in}$減小了一倍

源碼解讀

def combine(residual, data, combine):
    if combine == 'add':
        return residual + data
    elif combine == 'concat':
        return mx.sym.concat(residual, data, dim=1)
    return None

add是表明圖6中的單元b),concat是表明圖6中的單元c)。

def channel_shuffle(data, groups):
    data = mx.sym.reshape(data, shape=(0, -4, groups, -1, -2))
    data = mx.sym.swapaxes(data, 1, 2)
    data = mx.sym.reshape(data, shape=(0, -3, -2))
    return data

這個函數就是交換通道的函數,函數的第一行data = mx.sym.reshape(data, shape=(0, -4, groups, -1, -2))是將輸入爲\(n \times C_{in} \times H_{in} \times W_{in}\)reshape成\(n \times (C_{in}/g) \times g\times H_{in} \times W_{in}\),要注意的是mxnet中reshape不會改變張量在內存中的排列順序。至於要mxnet中的0,-1,-2,-3,-4的具體意義能夠這樣看到:

import mxnet as mx
print(help(mx.sym.reshape))

能夠看到輸出如下(只提取出一小部分,其他的可用上述方法查看),這裏有各個參數的具體意義:

- ``0``  copy this dimension from the input to the output shape.
- ``-1`` infers the dimension of the output shape by using the remainder of the input dimensions
- ``-2`` copy all/remainder of the input dimensions to the output shape.
- ``-3`` use the product of two consecutive dimensions of the input shape as the output dimension.
- ``-4`` split one dimension of the input into two dimensions passed subsequent to -4 in shape (can contain -1).

函數的第二行是交換第一與第二個維度,那麼如今這個symbol的符號的shape就變成了\(n \times g \times (C_{in}/g) \times H_{in} \times W_{in}\)。這裏的第零個維度是\(n\)要注意的是交換維度改變了張量在內存中的排列順序,改變了內存中的順序實現上就是完成了圖5c)中的Channel Shuffle操做,不一樣的顏色代碼數據在原來內存中的位置。
函數的最後一行合併了第一與第二個維度,輸出的張量與輸入的張量shape都是\(n \times C_{in} \times H_{in} \times W_{in}\)

def shuffleUnit(residual, in_channels, out_channels, combine_type, groups=3, grouped_conv=True):

    if combine_type == 'add':
        DWConv_stride = 1
    elif combine_type == 'concat':
        DWConv_stride = 2
        out_channels -= in_channels

    first_groups = groups if grouped_conv else 1

    bottleneck_channels = out_channels // 4

    data = mx.sym.Convolution(data=residual, num_filter=bottleneck_channels, 
                      kernel=(1, 1), stride=(1, 1), num_group=first_groups)
    data = mx.sym.BatchNorm(data=data)
    data = mx.sym.Activation(data=data, act_type='relu')

    data = channel_shuffle(data, groups)

    data = mx.sym.Convolution(data=data, num_filter=bottleneck_channels, kernel=(3, 3), 
                       pad=(1, 1), stride=(DWConv_stride, DWConv_stride), num_group=groups)
    data = mx.sym.BatchNorm(data=data)

    data = mx.sym.Convolution(data=data, num_filter=out_channels, 
                       kernel=(1, 1), stride=(1, 1), num_group=groups)
    data = mx.sym.BatchNorm(data=data)

    if combine_type == 'concat':
        residual = mx.sym.Pooling(data=residual, kernel=(3, 3), pool_type='avg', 
                              stride=(2, 2), pad=(1, 1))

    data = combine(residual, data, combine_type)

    return data

ShuffleUnit這個函數實現上是實現圖6的b)與c),add對應成b),comcat對應於c)。

def make_stage(data, stage, groups=3):
    stage_repeats = [3, 7, 3]

    grouped_conv = stage > 2

    if groups == 1:
        out_channels = [-1, 24, 144, 288, 567]
    elif groups == 2:
        out_channels = [-1, 24, 200, 400, 800]
    elif groups == 3:
        out_channels = [-1, 24, 240, 480, 960]
    elif groups == 4:
        out_channels = [-1, 24, 272, 544, 1088]
    elif groups == 8:
        out_channels = [-1, 24, 384, 768, 1536]
       
    data = shuffleUnit(data, out_channels[stage - 1], out_channels[stage], 
                       'concat', groups, grouped_conv)

    for i in range(stage_repeats[stage - 2]):
        data = shuffleUnit(data, out_channels[stage], out_channels[stage], 
                           'add', groups, True)

    return data

def get_shufflenet(num_classes=10):
    data = mx.sym.var('data')
    data = mx.sym.Convolution(data=data, num_filter=24, 
                              kernel=(3, 3), stride=(2, 2), pad=(1, 1))
    data = mx.sym.Pooling(data=data, kernel=(3, 3), pool_type='max', 
                          stride=(2, 2), pad=(1, 1))
    
    data = make_stage(data, 2)
    
    data = make_stage(data, 3)
    
    data = make_stage(data, 4)
     
    data = mx.sym.Pooling(data=data, kernel=(1, 1), global_pool=True, pool_type='avg')
    
    data = mx.sym.flatten(data=data)
    
    data = mx.sym.FullyConnected(data=data, num_hidden=num_classes)
    
    out = mx.sym.SoftmaxOutput(data=data, name='softmax')

    return out

這兩個函數能夠直接獲得做者在論文中的表:

table

圖7

結果比較

論文後面用了種實驗證實這兩個技術的有效性,且證明了ShuffleNet的優秀,這裏就不細說,看論文後面的表就能一目瞭然。

【防止爬蟲轉載而致使的格式問題——連接】:
http://www.cnblogs.com/heguanyou/p/8087422.html

相關文章
相關標籤/搜索