從DensNet到CliqueNet,解讀北大在卷積架構上的探索
來自專欄機器之心128 人贊了文章
機器之心原創,作者:蔣思源。
卷積神經網路架構一直是計算機視覺領域的研究重點,很多分類、檢測和分割等任務都依賴於基本架構提供更好的性能。本文先概覽了經典的卷積網路架構及它們的優缺點,其次重點分析了 CVPR 去年的最佳論文 DenseNet 與今年北大等研究機構提出的 CliqueNet,這篇論文接收為 CVPR 2018 的 Orals/Spotlights。
近日,微軟亞洲研究院主辦了一場關於 CVPR 2018 中國論文分享會,機器之心在分享會中發現了一篇非常有意思的論文,它介紹了一種新型卷積網路架構,並且相比於 DenseNet 能抽取更加精鍊的特徵。北大楊一博等研究者提出的這種 CliqueNet 不僅有前向的密集型連接,同時還有反向的密集型連接來精鍊前面層級的信息。根據楊一博向機器之心介紹的 CliqueNet 以及我們對卷積架構的了解,我們將與各位讀者縱覽卷積網路的演變。
卷積架構
很多讀者其實已經對各種卷積架構非常熟悉了,因此這一部分只簡要介紹具有代表性的卷積結構與方法。首先第一種當然是 Yann LeCun 在 1998 年提出的 LeNet-5,它奠定了整個卷積神經網路的基礎。正如 LeCun 在 LeNet-5 原論文中所說,卷積網路結合了三種關鍵性思想來確保模型對圖像的平移、縮放和扭曲具有一定程度的不變性,這三種關鍵思想即局部感受野、權重共享和空間/時間子採樣。其中局部感受野表示卷積核只關注圖像的局部特徵,而權重共享表示一個卷積核在整張圖像上都使用相同的權值,最後的子採樣即我們常用的池化操作,它可以精鍊抽取的特徵。
上圖是 LeNet-5 架構,它疊加了兩個卷積層與池化層來抽取圖像特徵,然後再將抽取的特徵傳入兩個全連接層以組合所有特徵並識別圖像類別。雖然 LeNet-5 很早就提出來了,且架構和現在很多簡單的卷積網路非常相似,但當時可能限於計算資源和數據集等條件並沒有得到廣泛應用。
第一個得到廣泛關注與應用的卷積神經網路是 2012 年提出來的 AlexNet,它相比於 LeNet-5 最大的特點是使用更深的卷積網路和 GPU 進行並行運算。如下所示,AlexNet 有 5 個卷積層和 3 個最大池化層,它可分為上下兩個完全相同的分支,這兩個分支在第三個卷積層和全連接層上可以相互交換信息。AlexNet 將卷積分為兩個分支主要是因為需要在兩塊老式 GTX580 GPU 上加速訓練,且單塊 GPU 無法為深度網路提供足夠的內存,因此研究者將網路分割為兩部分,並饋送到兩塊 GPU 中。
AlexNet 還應用了非常多的方法來提升模型性能,包括第一次使用 ReLU 非線性激活函數、第一次使用 Dropout 以及大量數據增強而實現網路的正則化。除此之外,AlexNet 還使用了帶動量的隨機梯度下降、L2 權重衰減以及 CNN 的集成方法,這些方法現在都成為了卷積網路不可缺少的模塊。
到了 2014 年,牛津大學提出了另一種深度卷積網路 VGG-Net,它相比於 AlexNet 有更小的卷積核和更深的層級。AlexNet 前面幾層用了 11*11 和 5*5 的卷積核以在圖像上獲取更大的感受野,而 VGG 採用更小的卷積核與更深的網路提升參數效率。一般而言,疊加幾個小的卷積核可以獲得與大卷積核相同的感受野,而疊加小卷積核使用的參數明顯要少於一個大卷積核。此外,疊加小卷積核因為加深了卷積網路,能引入更強的非線性。
VGG-Net 的泛化性能較好,常用於圖像特徵的抽取目標檢測候選框生成等。VGG 最大的問題就在於參數數量,VGG-19 基本上是參數量最多的卷積網路架構。VGG-Net 的參數主要出現在後面兩個全連接層,每一層都有 4096 個神經元,可想而至這之間的參數會有多麼龐大。
同樣在 2014 年,谷歌提出了 GoogLeNet(或 Inception-v1)。該網路共有 22 層,且包含了非常高效的 Inception 模塊,它沒有如同 VGG-Net 那樣大量使用全連接網路,因此參數量非常小。GoogLeNet 最大的特點就是使用了 Inception 模塊,它的目的是設計一種具有優良局部拓撲結構的網路,即對輸入圖像並行地執行多個卷積運算或池化操作,並將所有輸出結果拼接為一個非常深的特徵圖。因為 1*1、3*3 或 5*5 等不同的卷積運算與池化操作可以獲得輸入圖像的不同信息,並行處理這些運算並結合所有結果將獲得更好的圖像表徵。
一般 Inception 模塊的參數雖然少,但很多並行的卷積運算需要很多計算量。直接拼接不同的卷積運算會產生巨量的運算,因此修正的 Inception 模塊每一個卷積分支都會先執行一個 1*1 的卷積將通道數大大減少,這也相當於對輸入數據進行降維而簡化運算。此外,GoogLeNet 中間多出來的兩個分類網路主要是為了向前面的卷積與 Inception 模塊提供額外的梯度進行訓練。因為隨著網路的加深,梯度無法高效地由後向前傳,網路參數也就得不到更新。這樣的分支則能減輕深度網路的梯度傳播問題,但這種修補並不優美,也不能解決更深網路的學習問題。
最後,何愷明等人於 2015 年提出來的深度殘差網路驟然將網路深度由十幾二十層提升到上百層。ResNet 最大的特點即解決了反向傳播過程中的梯度消失問題,因此它可以訓練非常深的網路而不用像 GoogLeNet 那樣在中間添加分類網路以提供額外的梯度。根據 ResNet 原論文,設計的出發點即更深的網路相對於較淺的網路不能產生更高的訓練誤差,因此研究者引入了殘差連接以實現這樣的能力。
如上黑色曲線,ResNet 引入了殘差連接。在每一個殘差模塊上,殘差連接會將該模塊的輸入與輸出直接相加。因此在反向傳播中,根據殘差連接傳遞的梯度就可以不經過殘差模塊內部的多個卷積層,因而能為前一層保留足夠的梯度信息。此外,每一個殘差模塊還可以如同 Inception 模塊那樣添加 1×1 卷積而形成瓶頸層。這樣的瓶頸結構對輸入先執行降維再進行卷積運算,運算完後對卷積結果升維以恢復與輸入相同的維度,這樣在低維特徵上進行計算能節省很多計算量。
DenseNet
ResNet 雖然非常高效,但如此深的網路並不是每一層都是有效的。最近一些研究表明 ResNet 中的很多層級實際上對整體的貢獻非常小,即使我們在訓練中隨機丟棄一些層級也不會有很大的影響。這種卷積層和特徵圖的冗餘將降低模型的參數效率,並加大計算力的需求。為此,Gao Huang 等研究者提出了 DenseNet,該論文獲得了 CVPR 2017 的最佳論文。
DenseNet 的目標是提升網路層級間信息流與梯度流的效率,並提高參數效率。它也如同 ResNet 那樣連接前層特徵圖與後層特徵圖,但 DenseNet 並不會像 ResNet 那樣對兩個特徵圖求和,而是直接將特徵圖按深度相互拼接在一起。DenseNet 最大的特點即每一層的輸出都會作為後面所有層的輸入,這樣最後一層將拼接前面所有層級的輸出特徵圖。這種結構確保了每一層能從損失函數直接訪問到梯度,因此可以訓練非常深的網路。
如上所示為 Dense Block 的結構,特徵圖 X_2 在輸入 X_0 與 X_1 的條件下執行卷積運算得出,同時 X_2 會作為計算 X_3 和 X_4 的輸入。一般對於 L 層的 DenseNet,第 l 層有 l 個輸入特徵圖和 L-l 個輸出特徵圖,因此它一共擁有 L(L+1)/2 個連接。這種密集連接型的網路也就形象地稱為 DenseNet 了。
此外,Gao Huang 等研究者在原論文表示 DenseNet 所需要的參數更少,因為它相對於傳統卷積網路不需要重新學習冗餘的特徵圖。具體來說,DenseNet 的每一層都有非常淺的特徵圖或非常少的卷積核,例如 12、24、32 等,因此能節省很多網路參數。
又因為每一層輸出的特徵圖都比較淺,所以每一層都能將前面所有層級的特徵圖拼接為一個較深的特徵圖而作為輸入,這樣每一層也就復用了前面的特徵圖。特徵圖的復用能產生更緊湊的模型,且拼接由不同層產生的特徵圖能提升輸入的方差和效率。
形式化來說,若給定一張圖像 x_0 饋送到 L 層的 DenseNet 中,且 H_l() 表示第 l 層包含卷積、池化、BN 和激活函數等的非線性變換,那麼第 l 層的輸出可以表示為 X_l。對於 DenseNet 的密集型連接,第 l 層的輸出特徵圖可以表示為:
其中 [x_0, x_1, . . . , x_{?1}] 表示從 0 到 l-1 層產生的特徵圖,為了簡化計算,它們會按深度拼接為單個張量。在原論文的實現中,H_l() 為包含了三個模塊的複合函數,即先執行批量歸一化和 ReLU 激活函數,再執行 1 個 3×3 的卷積。
上述方程若想將特徵圖按深度拼接,那麼所有特徵圖的尺寸就需要相等。一般常用的方法是控制卷積核的移動步幅或添加池化運算,DenseNet 將網路分為不同的 Dense Block,並在 Dense Block 之間調整特徵圖的大小。
如上所示,密集連接塊之間的轉換層會通過卷積改變特徵圖深度,通過池化層改變特徵圖尺寸。在原論文的實現中,轉換層先後使用了批量歸一化、1×1 的逐點卷積和 2×2 的平均池化。
此外,若 Dense Block 中的 H_l 輸出 k 張特徵圖,那麼第 l 層的輸入特徵圖就有 k_0 + k × (l - 1) 張,其中 k_0 為輸入圖像的通道數。由於輸入特徵圖的深度或張數取決於 k,DenseNet 每一個卷積都有比較淺的特徵圖,即 k 值很小。在 ImageNet 中,DenseNet 的 k 值設為了 32。
不過儘管 k 值比較小,但在後面層級的卷積運算中,輸入的特徵圖深度還是非常深的。因此與 ResNet 的殘差模塊和 GoogLeNet 的 Inception 模塊一樣,DenseNet 同樣在 3×3 卷積運算前加入了 1×1 逐點卷積作為瓶頸層來減少輸入特徵圖的深度。因此我們可以將 H_l 由原版的 BN-ReLU-Conv(3×3) 修正為:BN-ReLU-Conv(1× 1)-BN-ReLU-Conv(3×3)。除了加入瓶頸層,DenseBlock 間的轉換層也可以通過 1×1 卷積減低特徵圖的維度。
以下表格是 DenseNet 在 ImageNet 數據集上所採用的架構,其中每個卷積層的卷積核數 k=32,「conv」層對應於原版 H_l 或添加了瓶頸層的 H_l。
在實踐中,很多研究者都表示 DenseNet 的參數雖然少,但顯存佔用非常恐怖。這主要是因為大量的特徵圖拼接操作和 BN 運算結果都會佔用新的顯存,不過現在已經有研究者修改代碼而降低顯存佔用。此外,DenseNet 的計算量也非常大,很難做到實時語義分割等任務。
CliqueNet
DenseNet 通過復用不同層級的特徵圖,減少了不同層間的相互依賴性,且最終的預測會利用所有層的信息而提升模型魯棒性。但是 Yunpeng Chen 等研究者在論文 Dual Path Networks 中表示隨著網路深度的增加,DenseNet 中的密集型連接路徑會線性地增加,因此參數會急劇地增加。這就導致了在不特定優化實現代碼的情況下需要消耗大量的 GPU 顯存。而在北大楊一博等研究者提出來的 CliqueNet 中,每個 Clique Block 只有固定通道數的特徵圖會饋送到下一個 Clique Block,這樣就大大增加了參數效率。
CliqueNet 最大的特點是其不僅有前傳的部分,同時還能根據後面層級的輸出對前面層級的特徵圖做優化。這種網路架構受到了循環結構與注意力機制的啟發,即卷積輸出的特徵圖可重複使用,經過精鍊的特徵圖將注意更重要的信息。在同一個 Clique 模塊內,任意兩層間都有前向和反向連接,這也就提升了深度網路中的信息流。
- 論文:Convolutional Neural Networks with Alternately Updated Clique
- 論文地址:https://arxiv.org/abs/1802.10419
- 實現地址:https://github.com/iboing/CliqueNet
CliqueNet 的每一個模塊可分為多個階段,但更高的階段需要更大的計算成本,因此該論文只討論兩個階段。第一個階段如同 DenseNet 那樣傳播,這可以視為初始化過程。而第二個階段如下圖所示每一個卷積運算的輸入不僅包括前面所有層的輸出特徵圖,同樣還包括後面層級的輸出特徵圖。第二階段中的循環反饋結構會利用更高級視覺信息精鍊前面層級的卷積核,因而能實現空間注意力的效果。
如上所示在第一階段中,若輸入 0、1、2 號特徵圖的拼接張量,卷積運算可得出特徵圖 3。在第二階段中,特徵圖 1 會根據第一階段輸出的 2、3、4 號拼接特徵圖計算得出,我們可將其稱為已更新特徵圖 1。第二階段的特徵圖 3 在輸入第一階段的輸出特徵圖 4 和已更新特徵圖 1、2 的情況下得出。
由此可以看出,每一步更新時,都利用最後得到的幾個特徵圖去精鍊相對最早得到的特徵圖。因為最後得到的特徵圖相對包含更高階的視覺信息,所以該方法用交替的方式,實現了對各個層級特徵圖的精鍊。
Clique Block 中的第一階段比較好理解,與上文 Dense Block 的前向傳播相同。可能讀者對第二階段的傳播過程仍然有些難以理解,不過原論文中給出了一個很好的表達式來描述第 2 階段。對於第二階段中的第 i 層和第 k 個循環,交替更新的表達式為:
其中 k >= 2,上面 Clique Block 的展開圖 k=2。W * X 表示卷積核在輸入特徵圖上做卷積運算,g 為非線性激活函數。若 k=2,那麼第 i 層更新後的特徵圖
,可通過拼接前面層級(l<i)已更新的特徵圖
和後面層級(m>i)前一個循環的特徵圖
得出。在這個式子中,加號和累加都表示按特徵圖深度的拼接運算。
如上所示有兩套卷積核,即階段 2 中的前向卷積運算與反向卷積運算。每一套卷積核參數在不同階段中是共享的。
對於有五個卷積層的 Clique Block,傳播方式如下表所示。其中 W_ij 表示 X_i 到 X_j 的參數,它在不同階段可以重複利用和更新,「{}」表示拼接操作。
如上所示,第一階段的輸出特徵圖都是拼接前面層級的特徵圖 X^1 和輸入數據 X_0 ,並做對應的卷積運算而得出。第二階段的輸出特徵圖會拼接前面層級更新過的特徵圖 X^2 與第一階段的特徵圖 X^1 而得出。
除了特徵圖的復用與精鍊外,CliqueNet 還採用了一種多尺度的特徵策略來避免參數的快速增長。具體如下所示我們將每一個 Block 的輸入特徵圖與輸出特徵圖拼接在一起,並做全局池化以得到一個向量。將所有 Block 的全局池化結果拼接在一起就能執行最後的預測。由於損失函數是根據所有 Block 結果計算得出,那麼各個 Block 就能直接訪問梯度信息。
此外,由於每一個 Block 只有第二階段的輸出會作為下一個 Block 的輸入,因此 Block 的特徵圖維度也不會超線性的增加,從而具有參數量和計算量上的優勢。
如下所示為 CliqueNet 的關鍵代碼,build_model 函數會先構建 3 個 Clique Block,並將它們全局池化的結果傳入列表中。最終的特徵即使用 tf.concat() 函數將所有池化結果拼接在一起,且在進行一個線性變換後可傳入 Softmax 函數獲得類別的預測概率。
import tensorflow as tffrom utils import *block_num=3def build_model(input_images, k, T, label_num, is_train, keep_prob, if_a, if_b, if_c): current=first_transit(input_images, channels=64, strides=1, with_biase=False) current_list=[] ## build blocks for i in range(block_num): block_feature, transit_feature = loop_block_I_II(current, if_b, channels_per_layer=k, layer_num=T/3, is_train=is_train, keep_prob=keep_prob, block_name=b+str(i)) if if_c==True: block_feature=compress(block_feature, is_train=is_train, keep_prob=keep_prob, name=com+str(i)) current_list.append(global_pool(block_feature, is_train)) if i==block_num-1: break current=transition(transit_feature, if_a, is_train=is_train, keep_prob=keep_prob, name=tran+str(i)) ## final feature final_feature=current_list[0] for block_id in range(len(current_list)-1): final_feature=tf.concat((final_feature, current_list[block_id+1]), axis=3) feature_length=final_feature.get_shape().as_list()[-1] print final feature length:,feature_length feature_flatten=tf.reshape(final_feature, [-1, feature_length]) ## final_fc Wfc=tf.get_variable(name=FC_W, shape=[feature_length, label_num], initializer=tf.contrib.layers.xavier_initializer()) bfc=tf.get_variable(name=FC_b, initializer=tf.constant(0.0, shape=[label_num])) logits=tf.matmul(feature_flatten, Wfc)+bfc prob=tf.nn.softmax(logits) return logits, prob
以上的 CliqueNet 就是北京大學和上海交通大學在 CVPR 2018 對卷積網路架構的探索,也許還有更多更高效的結構,但這需要研究社區長期的努力。
推薦閱讀: