現在的神經網絡通常都特別深,在輸出層向輸入層傳播導數的過程中,梯度很容易被激活函數或是權重以指數級的規模縮小或放大,從而產生“梯度消失”或“梯度爆炸”的現象,造成訓練速度下降和效果不理想。
如何避免或者減輕這一現象的發生呢?歸一化就是方法的一種。歸一化將網絡中層與層之間傳遞的數據限制在一定范圍內,從而避免了梯度消失和爆炸的發生。下面介紹一種最基本的歸一化:批量歸一化(BN, Batch Normalization)。另外還有層歸一化(LN, Layer Normalization)和權重歸一化(WN, Weight Normalization),和BN大同小異。
批量歸一化
批量歸一化層的是這樣定義的,當使用批量梯度下降(或小批量)時,對前一層的輸出在批量的維度上進行歸一化,即
\begin{align} &\hat{X}_i^t=\frac{X_i^{t-1}-E(X^{t-1})}{\sqrt{D(X^{t-1})+\varepsilon}} \\ \text{where}\;\; &E(X^{t-1}) = \frac{1}{n}\sum\limits_{i=1}^nX_i^{t-1}\notag\\ &D(X^{t-1}) = \frac{1}{n-1}\sum\limits_{i=1}^n\left[X_i^{t-1}-E(X^{t-1})\right]^2\notag \end{align}
其中$n$是輸入批量,$X_i^{t-1}$是前一層輸出批量中的第$i$個,$\varepsilon$是為避免0除而設置的較小數。以上都是按元素進行的操作。這樣做的顯式優點在於,大部分的輸出都被映射到了-1和1之間,而諸如sigmoid激活函數,在這個區間內的梯度是最大的,從而避免因激活函數值的飽和而產生的梯度消失。並且由於層輸出的歸一化約束,反向傳播的累積不會特別顯著,梯度爆炸也得以避免。
但是,如果僅僅進行以上操作,網絡的擬合能力就會下降。這是因為,神經網絡強大的擬合能力在於激活函數的非線性。經過以上操作,激活函數的輸入通常都集中在-1和1之間,而sigmoid函數在這區間內的導數變化率是比較低的,或者說是比較線性的。為了防止這一點,BN在這基礎上再加一個“反向”操作,將權重輸出再乘上自學習的標准差和均值,映射到激活函數曲率(或者說二階導數絕對值、導數變化率)相對更大的位置,在獲得較大導數的同時,保留激活非線性。公式如下:
$ \begin{aligned} &X_i^t= \gamma^t\hat{X}_i^t+\beta^t\\ \end{aligned} $
與$(1)$式聯合得到:
$ \begin{aligned} &X_i^t= \frac{\gamma^t}{\sqrt{D(X^{t-1})+\varepsilon}}X_i^{t-1} + \left(\beta^t-\frac{E(X^{t-1})\gamma^t}{\sqrt{D(X^{t-1})+\varepsilon}}\right) \\ \end{aligned} $
其中$\gamma,\beta$都是模型中用反向傳播學習的參數。這樣一來,BN層可以自己“決定”將輸出映射到合適位置。
另外,在訓練結束進行推理時,我們輸入模型的通常都是單個樣本,畢竟一個樣本是不能求樣本方差的。所以BN使用滑動平均(moving average)來保存所有輸入的均值和方差,以用於對單一輸入的歸一化。
Keras中BN的使用
Keras中已經實現了BN層可以直接使用,而不用我們自己重新寫這個輪子。使用方式如下:
x = keras.layers.BatchNormalization(axis=-1,#對輸入的哪個軸執行BN momentum=0.99,#滑動平均和方差的動量 epsilon=0.001,#防止0除的較小值 center=True,#是否使用beta調整歸一化后的輸出均值 scale=True,#是否使用gamma調整歸一化后的輸出方差 trainable=True)(x)
其中要注意axis,歸一化操作是針對axis維度指定的向量進行的。比如當BN層的前一層是二維卷積層,輸出的第一維是批量,然后是圖像寬高,最后一維是通道。假如BN層axis=-1,均值就是整個批量的所有像素對應的通道向量的平均,方差的計算也是以這個維度進行。也可以傳入列表,假如axis=[1,2,3],那么進行的規范化就是原理中所介紹的那樣,均值的規模就是CxHxW。對於下面的代碼:
from keras import layers,Model,Input Input_img = Input(shape = [320,320,3]) x = layers.BatchNormalization(axis=-1, momentum=0.99, epsilon=0.001, center=True, scale=True)(Input_img) model = Model(Input_img,x) model.summary()
summary()輸出可訓練參數和不可訓練參數各6個。可訓練參數就是$\gamma,\beta$,不可訓練參數是滑動平均所保存的均值和方差。另外,如果將BN層的traninable標記設置為False,那么$\gamma,\beta$就會被固定,不會被訓練;而如果設置為True,則只有$\gamma,\beta$會被訓練,另外6個不可訓練參數依然是不可訓練狀態,因為它們是通過滑動平均而不是反向傳播來更新的。
Pytorch中的BN
在Pytorch中使用BN的代碼如下(和上面一樣,也是2維BN):
from torch import nn bn = nn.BatchNorm2d(num_features = 6,# 特征數 eps = 0.00001, # 防止0除 momentum = 0.1, # 滑動平均與方差的動量(與Keras中相反),如果設為None,momentum = 1/i,i是輸入的次數 affine = True, # 是否使用gamma與beta再次映射,也就是會多出幾個訓練參數 track_running_stats = True)# 是否累計每次計算的均值與方差 for i in bn.state_dict(): print(i,bn.state_dict()[i])
與Keras中類似,但這里直接默認對特征維度進行規范化,特征維度在pytorch中是倒數第3維。需要注意的是,track_running_stats,也就是累計每次輸入計算的均值與方差,只要你傳入一個輸入,累計的均值與方差就會被修改。因此,如果你在推理時不想修改這兩個累計值,就要將track_running_stats設置為False。而在訓練時,只需再修改回True即可。