摘要: 在本文中,我想帶領你們看一看最近在Keras中實現的體系結構中一系列重要的卷積組塊。git
做爲一名計算機科學家,我常常在翻閱科學技術資料或者公式的數學符號時碰壁。我發現經過簡單的代碼來理解要容易得多。所以,在本文中,我想帶領你們看一看最近在Keras中實現的體系結構中一系列重要的卷積組塊。github
當你在GitHub網站上尋找經常使用架構實現時,必定會對它們裏面的代碼量感到驚訝。若是標有足夠多的註釋並使用額外的參數來改善模型,將會是一個很是好的作法,但與此同時這也會分散體系結構的本質。爲了進一步簡化和縮短代碼,我將使用一些別名函數:算法
defconv(x, f, k=3, s=1, p='same', d=1, a='relu'): return Conv2D(filters=f, kernel_size=k, strides=s, padding=p, dilation_rate=d, activation=a)(x) def dense(x, f, a='relu'): return Dense(f, activation=a)(x) defmaxpool(x, k=2, s=2, p='same'): return MaxPooling2D(pool_size=k, strides=s, padding=p)(x) defavgpool(x, k=2, s=2, p='same'): return AveragePooling2D(pool_size=k, strides=s, padding=p)(x) defgavgpool(x): return GlobalAveragePooling2D()(x) defsepconv(x, f, k=3, s=1, p='same', d=1, a='relu'): return SeparableConv2D(filters=f, kernel_size=k, strides=s, padding=p, dilation_rate=d, activation=a)(x)
在刪除模板代碼以後的代碼更易讀。固然,這隻有在你理解個人首字母縮寫後纔有效。網絡
defconv(x, f, k=3, s=1, p='same', d=1, a='relu'): return Conv2D(filters=f, kernel_size=k, strides=s, padding=p, dilation_rate=d, activation=a)(x) def dense(x, f, a='relu'): return Dense(f, activation=a)(x) defmaxpool(x, k=2, s=2, p='same'): return MaxPooling2D(pool_size=k, strides=s, padding=p)(x) defavgpool(x, k=2, s=2, p='same'): return AveragePooling2D(pool_size=k, strides=s, padding=p)(x) defgavgpool(x): return GlobalAveragePooling2D()(x) defsepconv(x, f, k=3, s=1, p='same', d=1, a='relu'): return SeparableConv2D(filters=f, kernel_size=k, strides=s, padding=p, dilation_rate=d, activation=a)(x)
瓶頸(Bottleneck)組塊架構
一個卷積層的參數數量取決於卷積核的大小、輸入過濾器的數量和輸出過濾器的數量。你的網絡越寬,3x3卷積耗費的成本就越大。app
def bottleneck(x, f=32, r=4): x = conv(x, f//r, k=1) x = conv(x, f//r, k=3) return conv(x, f, k=1)
瓶頸組塊背後的思想是,使用一個低成本的1x1卷積,按照必定比率r將通道的數量下降,以便隨後的3x3卷積具備更少的參數。最後,咱們用另一個1x1的卷積來拓寬網絡。ide
Inception模塊函數
模塊提出了經過並行的方式使用不一樣的操做而且合併結果的思想。經過這種方式網絡能夠學習不一樣類型的過濾器。性能
defnaive_inception_module(x, f=32): a = conv(x, f, k=1) b = conv(x, f, k=3) c = conv(x, f, k=5) d = maxpool(x, k=3, s=1) return concatenate([a, b, c, d])
在這裏,咱們將使用卷積核大小分別爲一、3和5的卷積層與一個MaxPooling層進行合併。這段代碼顯示了Inception模塊的原始實現。實際的實現結合了上述的瓶頸組塊思想,這使它稍微的複雜了一些。學習
definception_module(x, f=32, r=4): a = conv(x, f, k=1) b = conv(x, f//3, k=1) b = conv(b, f, k=3) c = conv(x, f//r, k=1) c = conv(c, f, k=5) d = maxpool(x, k=3, s=1) d = conv(d, f, k=1) return concatenate([a, b, c, d])
Inception模塊
剩餘組塊(ResNet)
ResNet是由微軟的研究人員提出的一種體系結構,它容許神經網絡具備任意多的層數,同時還提升了模型的準確度。如今你可能已經習慣使用它了,但在ResNet以前,狀況並不是如此。
defresidual_block(x, f=32, r=4): m = conv(x, f//r, k=1) m = conv(m, f//r, k=3) m = conv(m, f, k=1) return add([x, m])
ResNet的思路是將初始的激活添加到卷積組塊的輸出結果中。利用這種方式,網絡能夠經過學習過程決定用於輸出的新卷積的數量。值得注意的是,Inception模塊鏈接這些輸出,而剩餘組塊是用於求和。
ResNeXt組塊
根據它的名稱,你能夠猜到ResNeXt與ResNet是密切相關的。做者們將術語「基數(cardinality)」引入到卷積組塊中,做爲另外一個維度,如寬度(通道數量)和深度(網絡層數)。
基數是指在組塊中出現的並行路徑的數量。這聽起來相似於以並行的方式出現的4個操做爲特徵的Inception模塊。然而,基數4不是指的是並行使用不一樣類型的操做,而是簡單地使用相同的操做4次。
它們作的是一樣的事情,那麼爲何你還要把它們並行放在一塊兒呢?這個問題問得好。這個概念也被稱爲分組卷積,能夠追溯到最先的AlexNet論文。儘管當時它主要用於將訓練過程劃分到多個GPU上,而ResNeXt則使用ResNeXt來提升參數的效率。
defresnext_block(x, f=32, r=2, c=4): l = [] for i in range(c): m = conv(x, f//(c*r), k=1) m = conv(m, f//(c*r), k=3) m = conv(m, f, k=1) l.append(m) m = add(l) return add([x, m])
這個想法是把全部的輸入通道分紅一些組。卷積將只會在其專用的通道組內進行操做,而不會影響其它的。結果發現,每組在提升權重效率的同時,將會學習不一樣類型的特徵。
想象一個瓶頸組塊,它首先使用一個爲4的壓縮率將256個輸入通道減小到64個,而後將它們再恢復到256個通道做爲輸出。若是想引入爲32的基數和2的壓縮率,那麼咱們將使用並行的32個1x1的卷積層,而且每一個卷積層的輸出通道是4(256/(32*2))個。隨後,咱們將使用32個具備4個輸出通道的3x3的卷積層,而後是32個1x1的卷積層,每一個層則有256個輸出通道。最後一步包括添加這32條並行路徑,在爲了建立剩餘鏈接而添加初始輸入以前,這些路徑會爲咱們提供一個輸出。
左側: ResNet組塊 右側: 參數複雜度大體相同的RexNeXt組塊
這有很多的東西須要消化。用上圖能夠很是直觀地瞭解都發生了什麼,而且能夠經過複製這些代碼在Keras中本身建立一個小型網絡。利用上面9行簡單的代碼能夠歸納出這些複雜的描述,這難道不是很好嗎?
順便提一下,若是基數與通道的數量相同,咱們就會獲得一個叫作深度可分卷積(depthwise separable convolution)的東西。自從引入了Xception體系結構以來,這些技術獲得了普遍的應用。
密集(Dense)組塊
密集組塊是剩餘組塊的極端版本,其中每一個卷積層得到組塊中以前全部卷積層的輸出。咱們將輸入激活添加到一個列表中,而後輸入一個能夠遍歷塊深度的循環。每一個卷積輸出還會鏈接到這個列表,以便後續迭代得到愈來愈多的輸入特徵映射。這個方案直到達到了所須要的深度纔會中止。
defdense_block(x, f=32, d=5): l = x for i in range(d): x = conv(l, f) l = concatenate([l, x]) return l
儘管須要數月的研究才能獲得一個像DenseNet這樣出色的體系結構,可是實際的構建組塊其實就這麼簡單。
SENet(Squeeze-and-Excitation)組塊
SENet曾經在短時間內表明着ImageNet的較高水平。它是創建在ResNext的基礎之上的,主要針對網絡通道信息的建模。在常規的卷積層中,每一個通道對於點積計算中的加法操做具備相同的權重。
Squeezeand Excitation組塊
SENet引入了一個很是簡單的模塊,能夠添加到任何現有的體系結構中。它建立了一個微型神經網絡,學習如何根據輸入對每一個過濾器進行加權。正如你看到的那樣,SENet自己不是一個卷積組塊,可是由於它能夠被添加到任何卷積組塊中,而且可能會提升它的性能,所以我想將它添加到混合體中。
defse_block(x, f, rate=16): m = gavgpool(x) m = dense(m, f // rate) m = dense(m, f, a='sigmoid') return multiply([x, m])
每一個通道被壓縮爲一個單值,並被饋送到一個兩層的神經網絡裏。根據通道的分佈狀況,這個網絡將根據通道的重要性來學習對其進行加權。最後,再用這個權重跟卷積激活相乘。
SENets只用了很小的計算開銷,同時還可能會改進卷積模型。在我看來,這個組塊並無獲得應有的重視。
NASNet標準單元
這就是事情變得醜陋的地方。咱們正在遠離人們提出的簡捷而有效的設計決策的空間,並進入了一個設計神經網絡體系結構的算法世界。NASNet在設計理念上是使人難以置信的,但實際的體系結構是比較複雜的。咱們所瞭解的是,它在ImageNet上表現的很優秀。
經過人工操做,做者們定義了一個不一樣類型的卷積層和池化層的搜索空間,每一個層都具備不一樣的可能性設置。他們還定義瞭如何以並行的方式、順序地排列這些層,以及這些層是如何被添加的或鏈接的。一旦定義完成,他們會創建一個基於遞歸神經網絡的強化學習(Reinforcement Learning,RL)算法,若是一個特定的設計方案在CIFAR-10數據集上表現良好,就會獲得相應的獎勵。
最終的體系結構不只在CIFAR-10上表現良好,並且在ImageNet上也得到了至關不錯的結果。NASNet是由一個標準單元(Normal Cell)和一個依次重複的還原單元(Reduction Cell)組成。
defnormal_cell(x1, x2, f=32): a1 = sepconv(x1, f, k=3) a2 = sepconv(x1, f, k=5) a = add([a1, a2]) b1 = avgpool(x1, k=3, s=1) b2 = avgpool(x1, k=3, s=1) b = add([b1, b2]) c2 = avgpool(x2, k=3, s=1) c = add([x1, c2]) d1 = sepconv(x2, f, k=5) d2 = sepconv(x1, f, k=3) d = add([d1, d2]) e2 = sepconv(x2, f, k=3) e = add([x2, e2]) return concatenate([a, b, c, d, e])
這就是如何在Keras中實現一個標準單元的方法。除了這些層和設置結合的很是有效以外,就沒有什麼新的東西了。
倒置剩餘(Inverted Residual)組塊
到如今爲止,你已經瞭解了瓶頸組塊和可分離卷積。如今就把它們放在一塊兒。若是你作一些測試,就會注意到,由於可分離卷積已經減小了參數的數量,所以進行壓縮可能會損害性能,而不是提升性能。
做者們提出了與瓶頸組塊和剩餘組塊相反的想法。他們使用低成本的1x1卷積來增長通道的數量,由於隨後的可分離卷積層已經大大減小了參數的數量。在把通道添加到初始激活以前,下降了通道的數量。
definv_residual_block(x, f=32, r=4): m = conv(x, f*r, k=1) m = sepconv(m, f, a='linear') return add([m, x])
問題的最後一部分是在可分離卷積以後沒有激活函數。相反,它直接被添加到了輸入中。這個組塊被證實當被放到一個體繫結構中的時候是很是有效的。
AmoebaNet標準單元
AmoebaNet的標準單元
利用AmoebaNet,咱們在ImageNet上達到了當前的最高水平,而且有可能在通常的圖像識別中也是如此。與NASNet相似,AmoebaNet是經過使用與前面相同的搜索空間的算法設計的。惟一的糾結是,他們放棄了強化學習算法,而是採用了一般被稱爲「進化」的遺傳算法。可是,深刻了解其工做方式的細節超出了本文的範疇。故事的結局是,經過進化,做者們可以找到一個比NASNet的計算成本更低的更好的解決方案。這在ImageNet-A上得到了名列前五的97.87%的準確率,也是第一次針對單個體繫結構的。
結論
我但願本文能讓你對這些比較重要的卷積組塊有一個深入的理解,而且可以認識到實現起來可能比想象的要容易。要進一步瞭解這些體系結構,請查看相關的論文。你會發現,一旦掌握了一篇論文的核心思想,就會更容易理解其他的部分了。另外,在實際的實現過程當中一般將批量規範化添加到混合層中,而且隨着激活函數應用的的地方會有所變化。