初始化概念
初始化參數指的是在網絡模型訓練之前,對各個節點的權重和偏置進行初始化賦值的過程。
在深度學習中,神經網絡的權重初始化方法(weight initialization)對模型的收斂速度和性能有着至關重要的影響。模型的訓練,簡而言之,就是對權重參數$W$的不停迭代更新,以期達到更好的性能。而隨着網絡深度(層數)的增加,訓練中極易出現梯度消失或者梯度爆炸等問題。因此,對權重$W$的初始化顯得至關重要,一個好的權重初始化雖然不能完全解決梯度消失或梯度爆炸的問題,但是對於處理這兩個問題是有很大幫助的,並且十分有利於提升模型的收斂速度和性能表現。
初始化原則
一些基本的儲備知識
在總結參數初始化的原則之前,先簡單看一下網絡模型運行的過程,參數初始化的目的是使網絡模型能夠更好地進行訓練。現在大部分的網絡訓練依然采用誤差的反向傳播算法,誤差反向傳播分為正反兩個過程,這里就不再贅述了,先引入幾個概念。下面這幅圖是一個神經網絡的某一個層:
由圖可知,每一個層內部的組成主要有:
輸入$X/h^i$:來自原始樣本$X$的輸入($i = 0$)或上一層(第$i - 1層$)的輸出$h^i$。
權重$W$:網絡模型訓練的主體對象,第$i$層的權重參數$w^i$。
狀態值$z$:作為每一層激活函數$f$的輸入,處於網絡層的內部,所以稱之為狀態值。
激活值$h$:狀態值$z^i$經過了激活函數$f$后的輸出,也就是第$i$層的最終輸出$h^i$;
數據在網絡模型中流動的時候,則會有(這里默認沒有偏置項$B$):$$\begin{align}z^i &= w^i \cdot h^{i - 1} \\ h^i &= f(z^i)\end{align}$$
然后在反向傳播的過程中,由於是復合函數的求導,根據鏈式法則,會有兩組導數,一個是損失函數$Cost$對$z$的導數,一個是損失函數$Cost$對$W$的導數,(詳細過程這里不推導),這里再引入兩個概念:
1)損失函數$Cost$關於狀態$z$的梯度:即$\frac{\partial Cost}{\partial z}$
2)損失函數$Cost$關於權重參數$W$的梯度:即$\frac{\partial Cost}{\partial W}$
參數初始化的幾個基本條件
什么樣的初始化參數才是最好的呢?
需要牢記參數初始化的目的是為了讓神經網絡在訓練過程中學習到有用的信息,這意味着參數梯度不應該為0。而我們知道在全連接的神經網絡中,參數梯度和反向傳播得到的狀態梯度以及入激活值有關——激活值飽和會導致該層狀態梯度信息為0,然后導致下面所有層的參數梯度為0;入激活值為0會導致對應參數梯度為0。所以如果要保證參數梯度不等於0,那么參數初始化應該使得各層激活值不會出現飽和現象且激活值不為0。我們把這兩個條件總結為參數初始化條件:
- 初始化必要條件一:各層激活值不會出現飽和現象。
- 初始化必要條件二:各層激活值不為$0$。
下面從最常見的全0初始化和標准隨機初始化展開來討論。
全0初始化的可行性
在線性回歸,logistics回歸的時候,基本上都是把參數初始化為0,我們的模型也能夠很好的工作(可初始化為0的模型見這篇文章)。然后在神經網絡中,把$W$初始化為0是不可以的。這是因為如果把$W$初始化$0$,那么在前向傳播過程中,每一層的神經元學到的東西都是一樣的(激活值均為0),而在bp的時候,不同維度的參數會得到相同的更新,因為他們的gradient相同,稱之為“對稱失效”(具體可看這篇文章)。下面用一段代碼來演示,當把$W$初始化為$0$:
def initialize_parameters_zeros(layers_dims): """ Arguments: layer_dims -- python array (list) containing the size of each layer. Returns: parameters -- python dictionary containing your parameters "W1", "b1", ..., "WL", "bL": W1 -- weight matrix of shape (layers_dims[1], layers_dims[0]) b1 -- bias vector of shape (layers_dims[1], 1) ... WL -- weight matrix of shape (layers_dims[L], layers_dims[L-1]) bL -- bias vector of shape (layers_dims[L], 1) """ parameters = {} np.random.seed(3) L = len(layers_dims) # number of layers in the network for l in range(1, L): parameters['W' + str(l)] = np.zeros((layers_dims[l], layers_dims[l - 1])) parameters['b' + str(l)] = np.zeros((layers_dims[l], 1)) return parameters

import numpy as np import matplotlib.pyplot as plt def initialize_parameters(layer_dims): """ :param layer_dims: list,每一層單元的個數(維度) :return:dictionary,存儲參數w1,w2,...,wL,b1,...,bL """ np.random.seed(3) L = len(layer_dims)#the number of layers in the network parameters = {} for l in range(1,L): parameters["W" + str(l)] = np.random.randn(layer_dims[l],layer_dims[l-1])*0.01 parameters["b" + str(l)] = np.zeros((layer_dims[l],1)) return parameters def forward_propagation(): data = np.random.randn(1000, 100000) # layer_sizes = [100 - 10 * i for i in range(0,5)] layer_sizes = [1000,800,500,300,200,100,10] num_layers = len(layer_sizes) parameters = initialize_parameters(layer_sizes) A = data for l in range(1,num_layers): A_pre = A W = parameters["W" + str(l)] b = parameters["b" + str(l)] z = np.dot(W,A_pre) + b #計算z = wx + b A = np.tanh(z) #畫圖 plt.subplot(2,3,l) plt.hist(A.flatten(),facecolor='g') plt.xlim([-1,1]) plt.yticks([]) plt.show()
我們可以看看cost function是如何變化的:
能夠看到代價函數降到0.64(迭代1000次)后,再迭代已經不起什么作用了。
標准隨機初始化
我們希望所有參數的期望接近$0$。遵循這個原則,可以將參數設置為接近$0$的很小的隨機數(有正有負),在實際中,隨機參數服從高斯分布/正態分布(Gaussian distribution / normal distribution)和均勻分布(uniform distribution)都是有效的初始化方法。還是用一段代碼來演示:
def initialize_parameters_random(layers_dims): """ Arguments: layer_dims -- python array (list) containing the size of each layer. Returns: parameters -- python dictionary containing your parameters "W1", "b1", ..., "WL", "bL": W1 -- weight matrix of shape (layers_dims[1], layers_dims[0]) b1 -- bias vector of shape (layers_dims[1], 1) ... WL -- weight matrix of shape (layers_dims[L], layers_dims[L-1]) bL -- bias vector of shape (layers_dims[L], 1) """ np.random.seed(3) # This seed makes sure your "random" numbers will be the as ours parameters = {} L = len(layers_dims) # integer representing the number of layers for l in range(1, L): parameters['W' + str(l)] = np.random.randn(layers_dims[l], layers_dims[l - 1])*0.01 parameters['b' + str(l)] = np.zeros((layers_dims[l], 1)) return parameters
【代碼注解:
1. 是一個均值為$04,方差為$1$的高斯分布采樣。
2. 乘$0.01$是因為要把$W$隨機初始化到一個相對較小的值,因為如果X很大的話,$W$又相對較大,會導致$Z$非常大,這樣如果激活函數是sigmoid,就會導致sigmoid的輸出值$1$或者$0$,然后會導致一系列問題(比如cost function計算的時候,log里是0,這樣會有點麻煩)。】
隨機初始化后,cost function隨着迭代次數的變化示意圖為:
能夠看出,cost function的變化是比較正常的。但是隨機初始化也有缺點:當神經網絡的層數增多時,會發現越往后面的層的激活函數(使用tanH)的輸出值幾乎都接近於0,如下圖所示:
而激活函數輸出值接近於0會導致梯度非常接近於$0$,因此會導致梯度消失。
總結來說,一般的隨機初始化存在后層網絡激活函數的輸出值趨近於0的問題,且網絡輸出數據分布的方差會受每層神經元個數的影響(隨之改變)。針對這些問題,Glorot 等人提出在初始化的同時加上對方差大小的規范化。這樣不僅獲得了更快的收斂速度,而且維持了輸入輸出數據分布方差的一致性,也避免了后面層的激活函數的輸出值趨向於0的問題。
Glorot條件
這里直接引入Glorot條件——優秀的初始化應該保證以下兩個條件:
(1)各個層的激活值$h$(輸出值)的方差要保持一致,即$\forall(i, j) : Var(h^i) = Var(h^j)$;
(2)各個層對狀態$z$的梯度的方差要保持一致,即$\forall(i, j) : Var(\frac{\partial Cost}{\partial z^i}) = Var(\frac{\partial Cost}{\partial z^i})$;
關於方差的三個事實
既然要保持上面的兩個方差在各個網絡層中不改變,那也就是它實際上是會改變的,關於為什么會改變的公式推導,這里不詳細說明了,直接引入三個基本的客觀事實(兩有關一無關):
(1)各個層激活值$h$(輸出值)的方差與網絡的層數有關;
(2)關於狀態$z$的梯度的方差與網絡的層數有關;
(3)各個層權重參數$W$的梯度的方差與層數無關;
關於上面幾個基本結論的詳情以及公式推導,我們可以參考Xavier的原論文:Understanding the difficulty of training deep feedforward neural networks以及Vic時代的專欄深度學習之參數初始化(一)——Xavier初始化(里面有較為詳實的公式推導)
借用論文中的幾幅圖,也可以看出這幾個結論。
(1)各層激活值$h$直方圖如下:
可以看出,激活值的方差逐層遞減。
(2)各層狀態Z的梯度的直方圖如下:
狀態的梯度在反向傳播過程中越往下梯度越小(因為方差越來越小)。
(3)各層參數W的梯度的直方圖如下:
參數梯度的方差和層數基本無關。
參數初始化的幾點要求
(1)參數不能全部初始化為$0$,也不能全部初始化同一個值;
(2)最好保證參數初始化的均值為$0$,正負交錯,正負參數大致上數量相等;
(3)初始化參數不能太大或者是太小,參數太小會導致特征在每層間逐漸縮小而難以產生作用,參數太大會導致數據在逐層間傳遞時逐漸放大而導致梯度消失發散,不能訓練;
(4)如果有可能滿足Glorot條件也是不錯的;
上面的幾點要求中,(1)(2)(3)基本上是硬性要求,這也就衍生出了一系列的參數初始化方法,什么正態標准化等諸如此類的標准化方法,關於各種參數初始化方法,會在后面繼續說明。
常見的參數初始化方法
我們常見的幾種初始化方法是按照“正態分布隨機初始化——對應為normal”和按照“均勻分布隨機初始化——對應為uniform”,這里就不再多說了,這里介紹幾種遇見較少的初始化方法。
Xavier初始化方法(又稱Glorot初始化)
正態化的Glorot初始化——glorot_normal
Glorot 正態分布初始化器,也稱為 Xavier 正態分布初始化器。它從以$0$為中心(均值),標准差為$stddev = sqrt(\frac{2}{fan_{in} + fan_{out}})$的截斷正態分布中抽取樣本, 其中$fan_{in}$ 和 $fan_{out}$是權重張量的扇入扇出(即輸入和輸出的單元數目):$$fan_in = chanels_{in} * kernels_{width} * kernels_{height} \\ fan_{out} = chanels_{out} * kernels_{width} * kernels_{height}$$
舉個栗子:假設輸入網絡的一個$28 * 28 * 1$的數據,卷積核$kernels$形狀為$3 * 3$,卷積核通道數$chanels$有$32$個(即輸出的通道數有$32$個),那么此時有$limit = sqrt(\frac{6}{1 * 3 * 3 + 32 * 3 * 3})$
在keras和tensorflow均有實現,以keras為例: keras.initializers.glorot_normal(seed=None)
標准化的Glorot初始化——glorot_uniform
Glorot 均勻分布初始化器,也稱為 Xavier 均勻分布初始化器。它從$[-limit, limit]$中的均勻分布中抽取樣本, 其中$limit = sqrt(\frac{6}{fan_{in}] + fan_{out}})$, $fan_{in}$是權值張量中的輸入神經元的數量,$fan_{out}$是權重張量的輸出神經元的數量。
【有關均勻分布的上下限$limit$取值的詳細推導過程請點擊:均勻分布】
以keras為例: keras.initializers.glorot_uniform(seed=None)
輸出值分布:tanh vs. ReLU(Xavier initialization)
左邊是tanh,右邊是ReLU。
可以看出Xavier搭配tanh,深層的激活函數輸出值還是非常漂亮得服從標准高斯分布,但是對於目前神經網絡中最常用的ReLU激活函數,還是無能能力,當達到5,6層后幾乎又開始趨向於0,更深層的話很明顯又會趨向於0。。
Xavier初始化器的缺點
Xavier的推導過程是基於幾個假設的,所以這限制了Xavier 初始化器的適用范圍。
比如,其中一個假設是激活函數趨近於線性,這並不適用於ReLU等非線性激活函數;
另一個是激活值關於0對稱,這個則不適用於sigmoid、ReLU等不是關於0對稱的激活函數。
可以實驗驗證sigmoid激活函數用Xavier初始化后的初始化激活值、反向梯度、參數梯度特性:以MNIST做訓練數據,發現標准初始化和Xavier初始化得到的初始激活、參數梯度特性是一樣的。激活值的方差逐層遞減,參數梯度的方差也逐層遞減。
Kaiming初始化(又稱He / MSRA初始化)
Kaiming初始化,也稱之為He初始化,又稱之為MSRA初始化,出自大神何凱明之手。
因為前面講了Glorot初始化不適合ReLU激活函數,所以殘差網絡(ResNet)的作者何凱明在Delving Deep into Rectifiers:Surpassing Human-Level Performance on ImageNet Classification文中提出了ReLU網絡的初始化方法:Kaming初始化。作者的推導過程針對的其實是卷積網絡的前向和反向過程。而為了和Xavier初始化方法保持一致,這里我們還是討論全連接的網絡結構。
關於期望、方差的性質,我們已經在Xavier初始化一節介紹過了,這里不再重復。
在Xavier論文中,作者給出的Glorot條件是:正向傳播時,激活值的方差保持不變;反向傳播時,關於狀態值的梯度的方差保持不變。
這在Kaiming論文中稍作變換:正向傳播時,狀態值的方差保持不變;反向傳播時,關於激活值的梯度的方差保持不變。
正態化的kaiming初始化——he_normal
he正態分布初始化器。它從以$0$為中心,標准差為$stddev = sqrt(\frac{2}{fan_{in}})$的截斷正態分布中抽取樣本, 其中$fan_{in}$是權值張量中的輸入神經元的數量。在keras中的實現為 keras.initializers.he_normal(seed=None)
標准化化的kaiming初始化——he_uniform
he均勻方差縮放初始化器。它從$[-limit, limit]$中的均勻分布中抽取樣本, 其中$limit = sqrt(\frac{6}{fan_{in}})$,其中$fan_{in}$是權值張量中的輸入神經元的數量。在keras中的實現為 keras.initializers.he_uniform(seed=None)
代碼演示
def initialize_parameters_he(layers_dims): """ Arguments: layer_dims -- python array (list) containing the size of each layer. Returns: parameters -- python dictionary containing your parameters "W1", "b1", ..., "WL", "bL": W1 -- weight matrix of shape (layers_dims[1], layers_dims[0]) b1 -- bias vector of shape (layers_dims[1], 1) ... WL -- weight matrix of shape (layers_dims[L], layers_dims[L-1]) bL -- bias vector of shape (layers_dims[L], 1) """ np.random.seed(3) parameters = {} L = len(layers_dims) # integer representing the number of layers for l in range(1, L): parameters['W' + str(l)] = np.random.randn(layers_dims[l], layers_dims[l - 1]) * np.sqrt(2 / layers_dims[l - 1]) parameters['b' + str(l)] = np.zeros((layers_dims[l], 1)) return parameters
經過he initialization后,當隱藏層使用ReLU時,激活函數的輸出值的分布情況:
效果是比Xavier initialization好很多。 現在神經網絡中,隱藏層常使用ReLU,權重初始化常用He initialization這種方法。
Lecun初始化
出自大神Lecun之手。
正態化的Lecun初始化——lecun_normal
LeCun 正態分布初始化器。它從以$0$為中心,標准差為$stddev = sqrt(\frac{1}{fan_{in}})$的截斷正態分布中抽取樣本, 其中 fan_in是權值張量中的輸入單位的數量。在keras中的實現為 keras.initializers.lecun_normal(seed=None)
標准化的Lecun初始化——lecun_uniform
LeCun 均勻初始化器。它從$[-limit, limit]$中的均勻分布中抽取樣本, 其中$limit = sqrt(\frac{3}{fan_{in}})$,$fan_{in}$是權值張量中的輸入神經元的數量。在keras中的實現為 keras.initializers.lecun_uniform(seed=None)
Batch Normalization
BN是將輸入的數據分布變成高斯分布,這樣可以保證每一層神經網絡的輸入保持相同分布。
優點
隨着網絡層數的增加,分布逐漸發生偏移,之所以收斂慢,是因為整體分布往非線性函數取值區間的上下限靠近。這會導致反向傳播時梯度消失。BN就是通過規范化的手段,把每層神經網絡任意神經元這個輸入值的分布強行拉回到均值0方差1的標准正態分布,使得激活輸入值落入非線性函數中比較敏感的區域。可以讓梯度變大,學習收斂速度快,能大大加快收斂速度。
Scale and Shift作用
$\gamma$和$\beta$是學習到的參數,他們可以讓標准正態分布變得更高/更胖和向左右偏移。
有關BN的詳細解讀見深度學習之解密Batch Normalization。
參數初始化方法總結
三種隨機初始化器公式匯總
初始化方案建議
文中代碼來自於compare_initialization,感謝作者分享!
參考資料:
https://keras.io/zh/initializers/
https://blog.csdn.net/siyue0211/article/details/80384951
https://blog.csdn.net/u012328159/article/details/80025785