原文鏈接:https://www.leiphone.com/news/201703/3qMp45aQtbxTdzmK.html
原文是谷歌大神工程師寫的一篇文章,看到之后覺得很不錯,能夠直觀地讓你深入理解權重初始化方式以及激活函數對模型訓練的影響。
本文是對原文的解讀,並附上了自己的理解以及代碼實現。
首先,一個好的權重初始化方法能夠幫助神經網絡更快的找到最優解決方案。
初始化權重的必要條件1:各網絡層激活值不會落在激活函數的飽和區域;
初始化權重的必要條件2:各網絡層激活值不會都非常接近0,也不會都遠離0,最好是均值為0(以0為中心分布)
1、初始化為0的可行性:
不可行,如果將所有的權重都初始化為0,那么所有神經元的輸出數值都是一樣的,那么反向傳播時,同一層的所有的梯度都是一樣的,權重更新也是一樣的,這樣的訓練是沒有意義的。
2、可行的幾種初始化方式:
pre-training:
即是利用訓練好的模型的參數進行初始化,然后再做fine-tuning。
Random initialization:
10層網絡,采用隨機初始化權重,每層輸出數據分布
import tensorflow as tf import numpy as np import matplotlib.pyplot as plt data = tf.constant(np.random.randn(2000, 800),dtype=tf.float32) layer_sizes = [800 - 50 * i for i in range(0,10)]#10層網絡,輸入和 num_layers = len(layer_sizes) fcs = [] # To store fully connected layers' output for i in range(0, num_layers-1): X = data if i == 0 else fcs[i - 1] node_in = layer_sizes[i] node_out = layer_sizes[i + 1] # W = tf.Variable(np.random.randn(node_in, node_out))# * 0.01 W = tf.Variable(np.random.randn(node_in, node_out),dtype=tf.float32)*0.01 fc = tf.matmul(X, W) fc = tf.nn.tanh(fc) fcs.append(fc) plt.figure() with tf.Session() as sess: sess.run(tf.global_variables_initializer()) for i in range(0,num_layers-1): plt.subplot(1,num_layers,i+1) x=fcs[i] x=np.array(x.eval()) x=x.flatten() plt.hist(x=x,bins=20,range=(-1,1)) plt.show()
創建10層神經網絡,激活函數為tanh,每層權重都采用隨機正態分布,均值0,標准差為0.01,以下為輸出分布:
由上圖可以看出,隨着網絡層數的增加,輸出數值分布逐漸往0靠近,后面幾層輸出都非常接近0。根據f=W.X+b可知反向傳播時對權重W求偏導時,當前層輸出的數值X即是反向傳播時計算的梯度中的乘積因子,導致梯度非常小,使得參數更新困難。
將以上正態分布初始化參數值調大,標准差變為1:
W = tf.Variable(np.random.randn(node_in, node_out))
再看看輸出分布:
可以看出輸出數值都集中在1和-1附近,激活函數用的tanh函數,可見已經落入激活函數的飽和區域,tanh函數1和-1附近的梯度為0,參數難以被更新。
- Xavier initialization
Xavier initialization 可以解決上面的問題,Xavier初始化是保持輸入和輸出的方差一致,這樣可以避免所有輸出值都趨向於0:
-
W = tf.Variable(np.random.randn(node_in, node_out)) / np.sqrt(node_in)
下面就是采用Xavier初始化之后的每層的數據分布直方圖:
哇,輸出很多層之后依然保持良好的分布,非常有利於我們優化神經網絡!
-
xavier initialization是在線性函數上推導出來,這說明它對非線性函數並不具有普遍適用性,這里僅僅是討論了tanh激活函數,下面討論ReLu激活函數的試驗
-
由上圖可以看出,由於relu函數特性導致各層網絡數據輸出都偏向0-1,輸出分布不是zero-centred,且到后面幾層,數據都在0附近 。
看來Xavier初始化對於Relu激活函數不是很適用。下面看看He initialization是否能解決relu初始化問題,He initialization的思想:在relu 網絡中,假定每一層有一半的神經元被激活,另一半為0,所以要保持方差不變,只需在Xavier的基礎上除以2。
-
W = tf.Variable(np.random.randn(node_in, node_out)) / np.sqrt(node_in/2)
......fc = tf.nn.relu(fc)
下面看看輸出分布,雖然不是zero-centred(以0為中心即0均值)但是起碼輸出數值分布很穩定都在0-1之間,不再像之前用xavier那樣在0附近,效果不錯,推薦在relu網絡中使用。
Batch Normalization Layer:
Batch Normalization是一種巧妙粗暴的方法來削弱不好的initialization帶來的影響,想要在非線性激活之前,輸出值應該有比較好的分布(例如高斯分布),以便於反向傳播計算梯度,更新權重。Batch Normalization將輸出值強行做了一次Gaussian Normalization和線性變換。
Batch Normalization中所有的操作都是平滑可導,這使得反向傳播時候可以有效學習到相應的參數Υβ,Bach Normalization 在train 和test時行為有所差別。訓練時的
μβ和σβ由當前batch計算得出;在testing時μβ和σβ應該使用訓練時保存的均值或類似的經過處理的值,而不是當前batch計算
Batch Normalization 試驗:
Relu激活,隨機初始化,無Batch Normal:
隨機初始化,有batchNormalization:
fc=tf.contrib.layers.batch_norm(fc,center=True,scale=True,is_training=True)
#這里注意需要將之前的輸入數據,輸出數據全部轉換成dtype=float32,batchnorm要求數據類型為float32,數據不一致會報錯哦
由圖上可以看出,加了batch Normalization之后,隨着網絡的加深,后面幾層的輸出數據分布仍然保持的很好,沒有趨向於0,效果不錯。
初始化推薦
·在ReLU activation function中推薦使用Xavier Initialization的變種,稱之為He Initialization:
- ·使用Batch Normalization layer 可以有效降低深度網絡對weight初始化的依賴:
總結:良好的權重初始化和激活函數可以讓數據在網絡中正常流動,權重正常更新,以達到學習的目的,通過以上實驗可以直觀的理解權重初始化和激活函數對輸出分布的影響。