問題
訓練神經網絡是一個很復雜的過程,在前面提到了深度學習中常用的激活函數,例如ELU或者Relu的變體能夠在開始訓練的時候很大程度上減少梯度消失或者爆炸問題,但是卻不能保證在訓練過程中不出現該問題,例如在訓練過程中每一層輸入數據分布發生了改變了,那么我們就需要使用更小的learning rate去訓練,這一現象被稱為internal covariate shift,Batch Normalization能夠很好的解決這一問題。目前該算法已經被廣泛應用在深度學習模型中,該算法的強大至於在於:
- 可以選擇一個較大的學習率,能夠達到快速收斂的效果。
- 能夠起到Regularizer的效果,在一些情況下可以不使用Dropout,因為BN提高了模型的泛化能力
介紹
我們在將數據輸入到神經網絡中往往需要對數據進行歸一化,原因在於模型的目的就是為了學習模型的數據的分布,如果訓練集的數據分布和測試集的不一樣那么模型的泛化能力就會很差,另一方面如果模型的每一 batch的數據分布都不一樣,那么模型就需要去學習不同的分布,這樣模型的訓練速度會大大降低。
BN是一個獨立的步驟,被應用在激活函數之前,它簡單地對輸入進行零中心(zero-center)和歸一化(normalize),然后使用兩個新參數來縮放和移動結果(一個用於縮放,另一個用於縮放轉移)。 換句話說,BN讓模型學習最佳的尺度和 每層的輸入的平均值。
為了零中心和歸一化數據的分布,BN需要去估算輸入的mean和standard deviation,算法的計算過程如下:

其中:
- \(u_B\)是mini-btach \(B\)的均值,\(\sigma\)是mini-btach的標准差
- \(m_B\)是mini-batch中的樣本
- \(\hat{x}^{(i)}\) 是zero-center和normalize后的輸入
- 公式4是一個線性變換,是對數據分布的重構,\(z^{(i)}\)是算法對數據重構的output,\(\gamma\)和\(\beta\)分別代表的是對數據的
scale和shift,是我們需要學習的參數
應用
接下來我們就使用TensorFlow來實現帶有BN的神經網絡,步驟和前面講到的很多一樣,只是在輸入激活函數之前多處理了一部而已,在TF中我們使用的實現是tf.layers.batch_normalization。
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets("./") #自動下載數據到這個目錄
tf.reset_default_graph()
n_inputs = 28 * 28
n_hidden1 = 300
n_hidden2 = 100
n_outputs = 10
X = tf.placeholder(tf.float32, shape=(None, n_inputs), name="X")
y = tf.placeholder(tf.int64, shape=(None), name="y")
training = tf.placeholder_with_default(False, shape=(), name='training')
hidden1 = tf.layers.dense(X, n_hidden1, name="hidden1")
bn1 = tf.layers.batch_normalization(hidden1, training=training, momentum=0.9)
bn1_act = tf.nn.elu(bn1)
hidden2 = tf.layers.dense(bn1_act, n_hidden2, name="hidden2")
bn2 = tf.layers.batch_normalization(hidden2, training=training, momentum=0.9)
bn2_act = tf.nn.elu(bn2)
logits_before_bn = tf.layers.dense(bn2_act, n_outputs, name="outputs")
logits = tf.layers.batch_normalization(logits_before_bn, training=training,
momentum=0.9)
with tf.name_scope("loss"):
xentropy = tf.nn.sparse_softmax_cross_entropy_with_logits(labels=y,logits=logits)#labels允許的數據類型有int32, int64
loss = tf.reduce_mean(xentropy,name="loss")
learning_rate = 0.01
with tf.name_scope("train"):
optimizer = tf.train.GradientDescentOptimizer(learning_rate)
training_op = optimizer.minimize(loss)
with tf.name_scope("eval"):
correct = tf.nn.in_top_k(logits,y,1) #取值最高的一位
accuracy = tf.reduce_mean(tf.cast(correct,tf.float32)) #結果boolean轉為0,1
init = tf.global_variables_initializer()
saver = tf.train.Saver()
extra_update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS)
n_epochs = 20
batch_size = 200
with tf.Session() as sess:
init.run()
for epoch in range(n_epochs):
for iteration in range(mnist.train.num_examples // batch_size):
X_batch, y_batch = mnist.train.next_batch(batch_size)
sess.run([training_op, extra_update_ops],
feed_dict={training: True, X: X_batch, y: y_batch})
accuracy_val = accuracy.eval(feed_dict={X: mnist.test.images,
y: mnist.test.labels})
print(epoch, "Test accuracy:", accuracy_val)
在上面代碼中有一句需要解釋一下
extra_update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS)
這是因為在計算BN中需要計算moving_mean和moving_variance並且更新,所以在執行run的時候需要將其添加到執行列表中。我們還可以這樣寫
with tf.name_scope("train"):
optimizer = tf.train.GradientDescentOptimizer(learning_rate)
extra_update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS)
with tf.control_dependencies(extra_update_ops):
training_op = optimizer.minimize(loss)
在訓練的時候就只需要更新一個參數
sess.run(training_op, feed_dict={training: True, X: X_batch, y: y_batch})
此外,我們會發現在編寫神經網絡代碼中,很多代碼都是重復的可以將其模塊化,例如將構建每一層神經網絡的代碼封裝成一個function,不過這都是后話,看個人喜好吧。
