詳解殘差網路

詳解殘差網路

來自專欄大師兄學習深度學習3 人贊了文章

在VGG中,卷積網路達到了19層,在GoogLeNet中,網路史無前例的達到了22層。那麼,網路的精度會隨著網路的層數增多而增多嗎?在深度學習中,網路層數增多一般會伴著下面幾個問題

  1. 計算資源的消耗
  2. 模型容易過擬合
  3. 梯度消失/梯度爆炸問題的產生

問題1可以通過GPU集群來解決,對於一個企業資源並不是很大的問題;問題2的過擬合通過採集海量數據,並配合Dropout正則化等方法也可以有效避免;問題3通過Batch Normalization也可以避免。貌似我們只要無腦的增加網路的層數,我們就能從此獲益,但實驗數據給了我們當頭一棒。

作者發現,隨著網路層數的增加,網路發生了退化(degradation)的現象:隨著網路層數的增多,訓練集loss逐漸下降,然後趨於飽和,當你再增加網路深度的話,訓練集loss反而會增大。注意這並不是過擬合,因為在過擬合中訓練loss是一直減小的。

當網路退化時,淺層網路能夠達到比深層網路更好的訓練效果,這時如果我們把低層的特徵傳到高層,那麼效果應該至少不比淺層的網路效果差,或者說如果一個VGG-100網路在第98層使用的是和VGG-16第14層一模一樣的特徵,那麼VGG-100的效果應該會和VGG-16的效果相同。所以,我們可以在VGG-100的98層和14層之間添加一條直接映射(Identity Mapping)來達到此效果。

從資訊理論的角度講,由於DPI(數據處理不等式)的存在,在前向傳輸的過程中,隨著層數的加深,Feature Map包含的圖像信息會逐層減少,而ResNet的直接映射的加入,保證了 l+1 層的網路一定比 l 層包含更多的圖像信息。

基於這種使用直接映射來連接網路不同層直接的思想,殘差網路應運而生。

1. 殘差網路

1.1 殘差塊

殘差網路是由一系列殘差塊組成的(圖1)。一個殘差塊可以用表示為:

x_{l+1}= x_l+mathcal{F}(x_l, {W_l})	ag{1}

殘差塊分成兩部分直接映射部分和殘差部分。 h(x_l) 是直接映射,反應在圖1中是左邊的曲線; mathcal{F}(x_l, {W_l}) 是殘差部分,一般由兩個或者三個卷積操作構成,即圖1中右側包含卷積的部分。

圖1:殘差塊

圖1中的Weight在卷積網路中是指卷積操作,addition是指單位加操作。

在卷積網路中, x_l 可能和 x_{l+1} 的Feature Map的數量不一樣,這時候就需要使用 1	imes1 卷積進行升維或者降維(圖2)。這時,殘差塊表示為:

x_{l+1}= h(x_l)+mathcal{F}(x_l, {W_l})	ag{2}

其中 h(x_l) = W_lx 。其中 W_l1	imes1 卷積操作,但是實驗結果 1	imes1 卷積對模型性能提升有限,所以一般是在升維或者降維時才會使用。

圖2:1*1殘差塊

一般,這種版本的殘差塊叫做resnet_v1,keras代碼實現如下:

def res_block_v1(x, input_filter, output_filter): res_x = Conv2D(kernel_size=(3,3), filters=output_filter, strides=1, padding=same)(x) res_x = BatchNormalization()(res_x) res_x = Activation(relu)(res_x) res_x = Conv2D(kernel_size=(3,3), filters=output_filter, strides=1, padding=same)(res_x) res_x = BatchNormalization()(res_x) if input_filter == output_filter: identity = x else: #需要升維或者降維 identity = Conv2D(kernel_size=(1,1), filters=output_filter, strides=1, padding=same)(x) x = keras.layers.add([identity, res_x]) output = Activation(relu)(x) return output

1.2 殘差網路

殘差網路的搭建分為兩步:

  1. 使用VGG公式搭建Plain VGG網路
  2. 在Plain VGG的卷積網路之間插入Identity Mapping,注意需要升維或者降維的時候加入 1	imes1 卷積。

在實現過程中,一般是直接stack殘差塊的方式。

def resnet_v1(x): x = Conv2D(kernel_size=(3,3), filters=16, strides=1, padding=same, activation=relu)(x) x = res_block_v1(x, 16, 16) x = res_block_v1(x, 16, 32) x = Flatten()(x) outputs = Dense(10, activation=softmax, kernel_initializer=he_normal)(x) return outputs

1.3 為什麼叫殘差網路

在統計學中,殘差和誤差是非常容易混淆的兩個概念。誤差是衡量觀測值和真實值之間的差距,殘差是指預測值和觀測值之間的差距。對於殘差網路的命名原因,作者給出的解釋是,網路的一層通常可以看做 y=H(x) , 而殘差網路的一個殘差塊可以表示為 H(x)=F(x)+x ,也就是 F(x) = H(x)-x ,在單位映射中, y=x 便是觀測值,而 H(x) 是預測值,所以 F(x) 便對應著殘差,因此叫做殘差網路。

2. 殘差網路的背後原理

殘差塊一個更通用的表示方式是

y_l= h(x_l)+mathcal{F}(x_l, {W_l})	ag{3}

x_{l+1} = f(y_l)	ag{4}

現在我們先不考慮升維或者降維的情況,那麼在[1]中, h(cdot) 是直接映射, f(cdot) 是激活函數,一般使用ReLU。我們首先給出兩個假設:

  • 假設1: h(cdot) 是直接映射;
  • 假設2: f(cdot) 是直接映射。

那麼這時候殘差塊可以表示為:

x_{l+1} = x_l + mathcal{F}(x_l, {W_l})	ag{5}

對於一個更深的層 L ,其與 l 層的關係可以表示為

x_L = x_l + sum_{i=1}^{L-1}mathcal{F}(x_i, {W_i})	ag{6}

這個公式反應了殘差網路的兩個屬性:

  1. L 層可以表示為任意一個比它淺的l層和他們之間的殘差部分之和;
  2. x_L= x_0 + sum_{i=0}^{L-1}mathcal{F}(x_i, {W_i})L 是各個殘差塊特徵的單位累和,而MLP是特徵矩陣的累積。

根據BP中使用的導數的鏈式法則,損失函數 varepsilon 關於 x_l 的梯度可以表示為

frac{partial varepsilon}{partial x_l} = frac{partial varepsilon}{partial x_L}frac{partial x_L}{partial x_l} = frac{partial varepsilon}{partial x_L}(1+frac{partial }{partial x_l}sum_{i=1}^{L-1}mathcal{F}(x_i, {W_i})) = frac{partial varepsilon}{partial x_L}+frac{partial varepsilon}{partial x_L} frac{partial }{partial x_l}sum_{i=1}^{L-1}mathcal{F}(x_i, {W_i}) 	ag{7}

上面公式反映了殘差網路的兩個屬性:

  1. 在整個訓練過程中, frac{partial }{partial x_l}sum_{i=1}^{L-1}mathcal{F}(x_i, {W_i}) 不可能一直為 -1 ,也就是說在殘差網路中不會出現梯度消失的問題。
  2. frac{partial varepsilon}{partial x_L} 表示 L 層的梯度可以直接傳遞到任何一個比它淺的 l 層。

通過分析殘差網路的正向和反向兩個過程,我們發現,當殘差塊滿足上面兩個假設時,信息可以非常暢通的在高層和低層之間相互傳導,說明這兩個假設是讓殘差網路可以訓練深度模型的充分條件。那麼這兩個假設是必要條件嗎?

2.1 直接映射是最好的選擇

對於假設1,我們採用反證法,假設 h(x_l) = lambda_l x_l ,那麼這時候,殘差塊(圖3.b)表示為

x_{l+1} = lambda_lx_l + mathcal{F}(x_l, {W_l})	ag{8}

對於更深的L層

x_{L} = (prod_{i=l}^{L-1}lambda_l)x_l + sum_{i=l}^{L-1}((prod_{i=l}^{L-1})mathcal{F}(x_l, {W_l})	ag{9}

為了簡化問題,我們只考慮公式的左半部分 x{L} = (prod{i=l}^{L-1}lambda_l)x_l ,損失函數 varepsilonx_l 求偏微分得

frac{partialvarepsilon}{partial x_l} = frac{partialvarepsilon}{partial xL} (prod{i=l}^{L-1}lambda_i)	ag{10}

上面公式反映了兩個屬性:

  1. lambda>1 時,很有可能發生梯度爆炸;
  2. lambda<1 時,梯度變成0,會阻礙殘差網路信息的反向傳遞,從而影響殘差網路的訓練。

所以 lambda 必須等1。同理,其他常見的激活函數都會產生和上面的例子類似的阻礙信息反向傳播的問題。

對於其它不影響梯度的 h(cdot) ,例如LSTM中的門機制(圖3.c,圖3.d)或者Dropout(圖3.f)以及[1]中用於降維的 1	imes1 卷積(圖3.e)也許會有效果,作者採用了實驗的方法進行驗證,實驗結果見圖4

圖3:直接映射的變異模型

圖4:變異模型(均為110層)在Cifar10數據集上的表現

從圖4的實驗結果中我們可以看出,在所有的變異模型中,依舊是直接映射的效果最好。下面我們對圖3中的各種變異模型的分析

  1. Exclusive Gating:在LSTM的門機制中,絕大多數門的值為0或者1,幾乎很難落到0.5附近。當 g(x)
ightarrow0 時,殘差塊變成只有直接映射組成,阻礙卷積部分特徵的傳播;當 g(x)
ightarrow1 時,直接映射失效,退化為普通的卷積網路;
  2. Short-cut only gating: g(x)
ightarrow0 時,此時網路便是[1]提出的直接映射的殘差網路; g(x)
ightarrow1 時,退化為普通卷積網路;
  3. Dropout:類似於將直接映射乘以 1-p ,所以會影響梯度的反向傳播;
  4. 1	imes1 conv: 1	imes1 卷積比直接映射擁有更強的表示能力,但是實驗效果卻不如直接映射,說明該問題更可能是優化問題而非模型容量問題。

所以我們可以得出結論:假設1成立,即

y_l = x_l + mathcal{F}(x_l, w_l) 	ag{11}

y_{l+1} = x_{l+1} + mathcal{F}(x_{l+1}, w_{l+1}) = f(y_l) + mathcal{F}(f(y_l), w_{l+1}) 	ag{12}

2.2 激活函數的位置

[1] 提出的殘差塊可以詳細展開如圖5.a,即在卷積之後使用了BN做歸一化,然後在和直接映射單位加之後使用了ReLU作為激活函數。

圖5:激活函數在殘差網路中的使用

在2.1節中,我們得出假設「直接映射是最好的選擇」,所以我們希望構造一種結構能夠滿足直接映射,即定義一個新的殘差結構 hat{f}(cdot)

y_{l+1} = y_l + mathcal{F}(hat{f}(y_l), w_{l+1}) 	ag{13}

上面公式反應到網路里即將激活函數移到殘差部分使用,即圖5.c,這種在卷積之後使用激活函數的方法叫做post-activation。然後,作者通過調整ReLU和BN的使用位置得到了幾個變種,即5.d中的ReLU-only pre-activation和5.d中的 full pre-activation。作者通過對照試驗對比了這幾種變異模型,結果見圖6。

圖6:基於激活函數位置的變異模型在Cifar10上的實驗結果

而實驗結果也表明將激活函數移動到殘差部分可以提高模型的精度。

該網路一般就在resnet_v2,keras實現如下:

def res_block_v2(x, input_filter, output_filter): res_x = BatchNormalization()(x) res_x = Activation(relu)(res_x) res_x = Conv2D(kernel_size=(3,3), filters=output_filter, strides=1, padding=same)(res_x) res_x = BatchNormalization()(res_x) res_x = Activation(relu)(res_x) res_x = Conv2D(kernel_size=(3,3), filters=output_filter, strides=1, padding=same)(res_x) if input_filter == output_filter: identity = x else: #需要升維或者降維 identity = Conv2D(kernel_size=(1,1), filters=output_filter, strides=1, padding=same)(x) output= keras.layers.add([identity, res_x]) return outputdef resnet_v2(x): x = Conv2D(kernel_size=(3,3), filters=16 , strides=1, padding=same, activation=relu)(x) x = res_block_v2(x, 16, 16) x = res_block_v2(x, 16, 32) x = BatchNormalization()(x) y = Flatten()(x) outputs = Dense(10, activation=softmax, kernel_initializer=he_normal)(y) return outputs

3.殘差網路與模型集成

Andreas Veit等人的論文[3]指出殘差網路可以從模型集成的角度理解。如圖7所示,對於一個3層的殘差網路可以展開成一棵含有8個節點的二叉樹,而最終的輸出便是這8個節點的集成。而他們的實驗也驗證了這一點,隨機刪除殘差網路的一些節點網路的性能變化較為平滑,而對於VGG等stack到一起的網路來說,隨機刪除一些節點後,網路的輸出將完全隨機。

圖7:殘差網路展開成二叉樹

Reference

[1] He K, Zhang X, Ren S, et al. Deep residual learning for image recognition[C]//Proceedings of the IEEE conference on computer vision and pattern recognition. 2016: 770-778.

[2] He K, Zhang X, Ren S, et al. Identity mappings in deep residual networks[C]//European Conference on Computer Vision. Springer, Cham, 2016: 630-645.

[3] Veit A, Wilber M J, Belongie S. Residual networks behave like ensembles of relatively shallow networks[C]//Advances in Neural Information Processing Systems. 2016: 550-558.


推薦閱讀:

圖像處理項目部署1:so部署
ACMMM2016_UnitBox
視覺偉業VMFace團隊攻克群體骨架多粒度實時提取與追蹤技術
探索 YOLO v3 實現細節 - 第1篇 訓練
CS231n 2017 Lecture 1: Course Introduction 隨堂筆記

TAG:深度學習DeepLearning | 計算機視覺 |