批標准化(Bactch Normalization,BN)是為了克服神經網絡加深導致難以訓練而誕生的,隨着神經網絡深度加深,訓練起來就會越來越困難,收斂速度回很慢,常常會導致梯度彌散問題(Vanishing Gradient Problem)。
統計機器學習中有一個經典的假設:Source Domain 和 Target Domain的數據分布是一致的。也就是說,訓練數據和測試數據是滿足相同分布的。這是通過訓練數據獲得的模型能夠在測試集上獲得好的效果的一個基本保障。
Convariate Shift是指訓練集的樣本數據和目標樣本集分布不一致時,訓練得到的模型無法很好的Generalization。它是分布不一致假設之下的一個分支問題,也就是指Sorce Domain和Target Domain的條件概率一致的,但是其邊緣概率不同。的確,對於神經網絡的各層輸出,在經過了層內操作后,各層輸出分布就會與對應的輸入信號分布不同,而且差異會隨着網絡深度增大而加大了,但每一層所指向的Label仍然是不變的。
解決辦法:一般是根據訓練樣本和目標樣本的比例對訓練樣本做一個矯正。所以,通過引入Bactch Normalization來標准化某些層或者所有層的輸入,從而固定每層輸入信息的均值和方差。
方法:Bactch Normalization一般用在非線性映射(激活函數)之前,對x=Wu+b做標准化,是結果(輸出信號各個維度)的均值為0,方差為1。讓每一層的輸入有一個穩定的分布會有利於網絡的訓練。
優點:Bactch Normalization通過標准化讓激活函數分布在線性區間,結果就是加大了梯度,讓模型更大膽的進行梯度下降,具有如下優點:
- 加大搜索的步長,加快收斂的速度;
- 更容易跳出局部最小值;
- 破壞原來的數據分布,一定程度上緩解了過擬合;
因此,在遇到神經網絡收斂速度很慢或梯度爆炸(Gradient Explore)等無法訓練的情況系啊,都可以嘗試用Bactch Normalization來解決。
梯度爆炸:梯度非常大,鏈式求導后乘積就變得很大,使權重變得非常大,產生指數級爆炸。
Batch Normalization原理及其TensorFlow實現
Batch Normalization原理及其TensorFlow實現
本文參考文獻
Ioffe S, Szegedy C. Batch normalization: Accelerating deep network training by reducing internal covariate shift[J]. arXiv preprint arXiv:1502.03167, 2015.
被引次數:1658
目前主流的訓練深度神經網絡的算法是梯度下降算法,簡而言之該過程就是通過將網絡輸出值與真實值之間的誤差信號逐層傳遞至神經網絡的每個節點,進而更新節點與節點之間的參數。
盡管在大數據時代,采取深度神經網絡進行數據建模會變得非常優越,深度神經網絡的調參過程一直是受人詬病的地方。論文中通常不會直接給出一組性能較好的參數是怎么得到的,而是直接給出模型的結果。這就使得深度神經網絡的調參過程在沒有經驗的人看來是一個black box。
隨着梯度下降算法的不斷改進,已經有越來越多的算法嘗試減少調參的工作量,比如減小學習率、選取合適的權重初始化函數、應用Dropout等等,而我們今天要介紹的Batch Normalization也是一個加速神經網絡訓練過程的算法,幫助減少調參的彎路。這個算法在2015年由Google提出,一提出便被廣泛接納采用,可以發現,現如今基本上所有的深度神經網絡模型中都會加入Batch Normalization技巧。短短兩年,這篇文章就已經被引用了1658次,實屬2015年深度學習領域的頂級優質文章。
1. 為什么要提出Batch Normalization
首先論文中給出了一個名詞covariate shift,文中指出,在深度神經網絡里面,正是covariate shift導致了神經網絡訓練起來比較緩慢,而ReLU的發明也是為了減少covariate shift,較少covariate shift就可以提高神經網絡的訓練速度。
covariate shift簡單來說就是當你訓練好了一個函數,輸入的分布變了,這個函數就無法處理了。舉個例子,現在想建立一個可以用來預測癌症的分類器模型,首先我們從醫院里患有癌症的老年人中抽取血液,但是僅有這些樣本肯定不行,因為我們缺少健康的樣本,於是我們去學校里找了一些年輕的學生志願獻血得到了健康樣本的模型,這樣我們就可以利用邏輯回歸進行分類了是不是?訓練好的模型真的就可以直接投放市場應用到病人身上了嗎?
顯然不是的,很大概率上這個模型會表現不好,原因就在於我們病人的樣本和學生的樣本分布根本不同,也就是說訓練集樣本的分布跟測試集樣本的分布不同,即樣本之間存在了covariate shift。
雖然我們本意是要學習P(癌症|血液)的概率分布,但是我們無法真正學習到這個分布,而在很大程度上我們學習的是P(癌症|血液,學生),也就是說我們這個模型是依賴於輸入樣本的分布的。
那么在深度神經網絡中,所謂的internal covariate shift該如何理解呢?
如果神經網絡的層數很淺的話,其實這個問題不大,因為采取隨機的mini-batch訓練方法,mini-batch之間的樣本分布差別不會太大。但是對於深度神經網絡而言(比如微軟提出152層的residual network),由於其參數是逐層啟發性的結構,因此,internal covariate shift的問題就不應該忽視了。在深度神經網絡中,由於第一層參數的改變,導致了傳遞給第二層的輸入的分布也會發生改變,也就是說在更新參數的過程中,無形中發生了internal covariate shift。因此,這樣網絡就很難較好的范化,訓練起來也就非常緩慢了。
2. 什么是Batch Normalization
為了減少深度神經網絡中的internal covariate shift,論文中提出了Batch Normalization算法,首先是對每一層的輸入做一個Batch Normalization 變換,該變換的操作如下圖所示。
直觀上理解,就是首先對每層的輸入進行常規的歸一化,然后再在此基礎上添加一個仿射變換,容許輸入變換到原來的規模。這里需要說明兩點:
第一,這里的常規歸一化實際上就是改變了一個mini-batch中樣本的分本,由原來的某個分布轉化成均值為0方差為1的標准分布;
第二,僅轉化了分布還不行,因為轉化過后可能改變了輸入的取值范圍,因此需要賦予一定的放縮和平移能力,即將歸一化后的輸入通過一個仿射變換的子網絡。這里仿射變換中的gamma和beta都是可以學習的參數,不難發現如果gamma取輸入的標准差,beta取輸入的均值,Batch Normalization變換就回到了恆等變換。
第三,這里的所有操作都是可微分的,也就使得了梯度后向傳播算法在這里變得可行;
跳過文中的梯度的推導公式,我們直接來看整個Batch Normalization算法,如下圖所示,首先是訓練Batch Normalized網絡,然后再將其應用於推理階段。
關於上圖,有必要說明一個困惑的地方。細心的讀者一定會發現上圖中的第10步中Var[x]不是直接等於方差的期望,而是在前面添加了分數m/(m-1),這是為什么呢?
原因就在於我們的初衷是希望網絡的輸出只依賴於輸入,而不是依賴於mini-batch的划分,之所以划分成mini-batch來進行訓練,是因為這樣做考慮到了實際情況,如果采取完整的batch訓練,那是不現實的。
基於此,算法1中的方差只是一個mini-batch樣本方差,但是在算法2中的推理階段,我們要使用的應該是樣本全局的方差,而不是拿一個部分來預測,而實際上我們也不知道整個數據集的期望值是多少,所以就拿mini-batch的均值來代替期望值,這樣簡單粗暴的做法就會使得我們估計的樣本過於集中,也就是說我們估計的方差要比真實方差小一點,因此mini-batch中的方差實際上是低估了整個數據集的方差,我們要得到無偏估計,所以這里要乘以m/(m-1)稍稍放大一下方差的值,至於為什么不放大到其他值,為什么分母不是m-2或m-3,可以查閱更嚴格的數學證明。
原理部分就介紹到這里了,論文中給出了結合了Batch Normalization的CNN網絡用於圖像分類的效果,感興趣的讀者可以去閱讀一下,至於Batch Normalization在RNN中的應用效果以及相應改進,我們后期會繼續推送。
接下來動手在TensorFlow中實現Batch Normalization,由於TensorFlow中提供了非常方便的Batch Normalization的API,因此這里我只簡單演示一下Batch Normalization變換的寫法:
importtensorflow astf
defbn_transform(x):batch_mean, batch_var = tf.nn.moments(x,[ 0]) z_hat = (x - batch_mean) / tf.sqrt(batch_var + epsilon) gamma = tf.Variable(tf.ones([ 100])) beta = tf.Variable(tf.zeros([ 100])) bn = gamma * z_hat + beta y = tf.nn.sigmoid(bn)
returny
在TensorFlow中,我們可以直接在層與層之間調用tf.layers.batch_normalization函數,該函數參數調用如下:
batch_normalization( inputs, axis=- 1, momentum= 0.99, epsilon= 0.001, center= True, scale= True, beta_initializer=tf.zeros_initializer(), gamma_initializer=tf.ones_initializer(), moving_mean_initializer=tf.zeros_initializer(), moving_variance_initializer=tf.ones_initializer(), beta_regularizer= None, gamma_regularizer= None, training= False, trainable= True, name= None, reuse= None)
在訓練階段我們只用設定training=True即可,在推理階段,我們設置training=False即可,感興趣的讀者可以看看這個函數的內部實現源碼。