TensorFlow | TF修鍊手冊(六)——批標準化與梯度剪裁
批標準化
綜合前文所述,為解決/緩解反向傳播過程中出現的梯度爆炸或梯度消失問題,我們可以使用的方法主要有——
- 使用Xavier或He參數初始化策略;
- 使用ELU或ReLU及其變種激活函數。
但是人們發現這並不能完全解決這一問題,2015年Sergey Ioffe 和 Christian Szegedy提出了一種新的策略,稱為Batch Normalization(批標準化)。這一技術在每一層的輸入進入激活函數以前加入了一個標準化的操作,使其標準正態化。然後依據兩個參數來放縮並平移結果。換句話說,這種操作使得模型中的每一層都可以學習理想分布的輸入。
這一操作可以總結為以下幾步——
需要解釋的是,ε代表了一個極小的數字,通常是10的-3次方,目的是為了防止方差為0.
演算法的提出者認為這一技術可以顯著提高各種類型的深層神經網路的效果,梯度消失的問題被大大緩解了,甚至可以使用tanh或logistic作為激活函數了,神經網路對連接權重的初始化也不再十分敏感,更高的學習率可以被使用,這極大地加速了訓練進程。此外,它還自帶了正則化的效果。當然了,由於批標準化需要做的計算操作多了不少,因此訓練時間將會多一些。所以如果你需要一個快捷反應的神經網路,不妨先試試ELU + 何式初始化。
在TensorFlow中使用批標準化
TensorFlow提供了一個batch_normalization()的功能,它可以簡化標準正態化輸入的過程,但是你必須自行計算上述最後一步的γ、ε和β,並將它們作為參數傳給這個函數。
或者你也可以使用batch_norm()函數,它可以為你完成上述所有工作。
import tensorflow as tffrom tensorflow.contrib.layers import batch_normfrom tensorflow.contrib.layers import fully_connectedn_inputs = 28*28n_hidden1 = 300n_hidden2 = 100n_outputs = 10X = tf.placeholder(tf.float32, shape=(None, n_inputs), name="X")is_training = tf.placeholder(tf.bool, shape=(), name="is_training")bn_params = { is_training : is_training, decay : 0.99, updates_collections : None }hidden1 = fully_connected(X, n_hidden1, scope=hidden1, normalizer_fn=batch_norm, normalizer_params=bn_params)hidden2 = fully_connected(hidden1, n_hidden2, scope="hidden2", normalizer_fn=batch_norm, normalizer_params=bn_params)logits = fully_connected(hidden2, n_outputs, activation_fn=None, scope="outputs",normalizer_fn=batch_norm, normalizer_params=bn_params)
代碼中定義了「is_training」這個布爾型的佔位符用來表示該過程是否在訓練中,這是由於在訓練過程中需要使用當前批量的均值和標準差,而在test過程中則要使用移動平均。這種指數平均使用的是指數式衰減,這也就是為什麼我們需要decay這個參數的原因了。移動平均的具體做法是(其中箭頭左側的v(hat)代表的是新的移動平均值,右側的第一個v(hat)是原移動平均值,v是新的數值)——
updates_collections設置為None,這樣移動平均值會在進行批標準化之前更新。否則默認情況下你需要手動進行該操作。
最後我們像之前一樣使用fully_connected()構建了一層神經網路,只不過我們加入了normalizer_fn來確定批標準化的輸入標準化方式。
需要注意的是默認情況下batchnorm()並不進行放縮(即γ被設置為1),這對於沒有激活函數或激活函數為ReLU的層是沒有問題的,但是對於其它類型的激活函數,就需要在bn_params中加入"scale":"True"。
你可能已經發現了,在之前的代碼中我們幾乎重複了三次相同的神經網路層構建語句,為了防止這種重複,我們可以使用arg_scope()創建一個參數域(Argument Scope),第一個參數是一個函數的list,而其它的參數就會自動地被傳入這些函數中。
with tf.contrib.framework.arg_scope([fully_connected], normalizer_fn=batch_norm, normalizer_params=bn_params): hidden1 = fully_connected(X, n_hidden1, scope="hidden1") hidden2 = fully_connected(hidden1, n_hidden2, scope="hidden2") logits = fully_connected(hidden2, n_outputs, scope="outputs", activation_fn=None)
流程圖創建的剩餘部分和之前是相同的,定義代價函數,創建一個Optimizer並令它最小化代價函數,定義評價的操作,然後創建Saver。
執行部分也大致相同,不同的是在feed_dict時需要根據情況設置is_training為True或False.
需要說明的是,在這個只有兩個隱層的神經網路中,批量標準化不一定會有很好的效果,但是對於更深層的神經網路,它可以造成很大的影響。
梯度剪裁(Gradient Clipping)
剃度剪裁就是在反向傳播過程中對梯度設置一個閾值,這一方法通常用於RNN。當前人們更喜歡上述批標準化的方法。
由於我們此前使用的minimize()函數會計算梯度以後直接應用,因此為了進行梯度剪裁我們需要使用compute_gradients(),然後對梯度進行剪裁,然後應用梯度為剪裁後的梯度。
threshold = 1.0optimizer = tf.train.GradientDescentOptimizer(learning_rate)grads_and_vars = optimizer.compute_gradients(loss)capped_gvs = [(tf.clip_by_value(grad, -threshold, threshold), var) for gard, var in grads_and_vars]training_op = optimizer.apply_gradients(capped_gvs)
閾值是一個可以調節的超參數。
總結——梯度問題的解決方法
- 使用Xavier或He參數初始化策略;
- 使用ELU或ReLU及其變種激活函數;
- 批標準化;
- 梯度裁剪。
推薦閱讀:
※使用Python操作機器人聊天
※字元編碼的奧秘
※Python中實現iterator
※利用Python讀取外部數據文件
TAG:深度學習DeepLearning | Python | TensorFlow |