1. 程式人生 > >TensorFlow 中的正則化(Batch Normalization)詳解和實現程式碼

TensorFlow 中的正則化(Batch Normalization)詳解和實現程式碼

        雖然在訓練初期使用 He 初始化方法初始ELU(或者其他派生的ReLU)能夠有效的防止梯度彌散、爆炸問題。但是這種方式無法保證梯度問題不會在訓練過程中產生。

        2015年的一篇paper( “Batch Normalization: Accelerating Deep Network Training by Reducing Internal Covariate Shift,” S. Ioffe and C. Szegedy (2015).)中, Sergey Ioffe and Christian Szegedy提出了一個Batch Normaalization(BN)技術去處理梯度彌散、爆炸問題。一般來說,在訓練過程中隨著前幾層引數的變化,每一層輸入的分佈都會發生改變(這杯稱作Internal Covariate Shift 問題)。

        該技術在模型每一層的啟用函式之前新增一個操作,以0為中心對輸入作正則化,在每一層使用兩個引數分別縮放與轉化輸出結果。換句話說,該操作在每一層上讓模型自動學習最優的尺度與均值。

        為了零中心化與正則化輸入,該演算法需要估計輸入的均值與標準差。它通過計算當前輸入最小批次的均值與標準差來實現(所以稱之為Batch Normalization).整個操作的描述見下列等式。

        在測試的時候,沒有mini-batch去計算經驗均值(empirical mean)和標準差,所以應該使用整個訓練集的均值和標準差。典型有效地在訓練時使用移動平均計算得到。因此,總體而言,每一個batch normalized 層都能學到四個引數:

作者證明了該技術明顯的提高了所有的神經網路的效能。能有效地控制梯度消失的問題,因此可以使用飽和的啟用函式比如tanh , 甚至是邏輯斯第啟用函式。同時,網路對權值的初始化也不敏感。也可以使用比較大的學習率,極大地提高學習速度。此外,他們指出:“應用到公認較好的影象分類模型中,batch mormal 達到了同樣的正確率,但只用了14此訓練步數,明顯打敗了原始模型。使用整合的batch-normal 網路,我們達到了最好的公共成績在ImageNet classification 上:4.9%的驗證誤差,(4.8%的測試誤差)超過人類分類的正確率。”最終, 就像一個不斷給與的禮物,batch normalization也像一個正則化,可以減少對其他正則化技術的需要(比如 dropout)

        然而,batch normalization 的確增加了模型的複雜程度(即使使用它可以免去對輸入資料的規則化,因為第一層隱藏層會對資料提供batch normal處理).此外,會有一個執行時的罰項:由於在每一層需要額外的計算,會讓神經網路在預測的時候變慢。因此,如果你需要預測變得輕快。你可能需要檢查一下普通的ELU+He initialization在使用batch normalization之前的表現如何。

  • 你可能會發現,剛開始的時候訓練是相當緩慢的, Gradient Descent正在為每個變數尋找最優的比例和偏移量在每一層中。但一旦找到了合理的合適的值,它就會加速執行。

使用TensorFlow 實現Batch Normalization

        Tensorflow 提供了 batch_normalization()方法方便地使輸入資料中心化與正則化,但是需要你親自計算均值和標準差(該值得計算基於訓練時的mini-batch 或測測試時的全量資料,)作為該函式的引數。這樣做是可行的,但不是最方便的方法,作為替代,你應該使用 batch_norm( )方法。這個函式為解決了這些計算。你可以直接或高數fully_connected()函式使用它,如下程式碼所示:

import tensorflow as tf

n_inputs = 28 * 28
n_hidden1 = 300
n_hidden2 = 100
n_outputs = 10

X = tf.placeholder(tf.float32, shape=(None, n_inputs), name="X")

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)

為了避免一遍又一遍得重複寫引數,我們可以使用python的 ‘partial()’函式:

X = tf.placeholder(tf.float32, shape=(None, n_inputs), name="X")
training = tf.placeholder_with_default(False, shape=(), name='training')

from functools import partial

my_batch_norm_layer = partial(tf.layers.batch_normalization,
training=training, momentum=0.9)

hidden1 = tf.layers.dense(X, n_hidden1, name="hidden1")
bn1 = my_batch_norm_layer(hidden1)
bn1_act = tf.nn.elu(bn1)
hidden2 = tf.layers.dense(bn1_act, n_hidden2, name="hidden2")
bn2 = my_batch_norm_layer(hidden2)
bn2_act = tf.nn.elu(bn2)
logits_before_bn = tf.layers.dense(bn2_act, n_outputs, name="outputs")
logits = my_batch_norm_layer(logits_before_bn)

下面,我們使用神經網路識別MNIST 的例子,其中,啟用函式使用ELU,並對每一層使用Batch Normalization:

batch_norm_momentum = 0.9

X = tf.placeholder(tf.float32, shape=(None, n_inputs), name="X")
y = tf.placeholder(tf.int32, shape=(None), name="y")
training = tf.placeholder_with_default(False, shape=(), name='training')

with tf.name_scope("dnn"):
he_init = tf.variance_scaling_initializer()

my_batch_norm_layer = partial(
tf.layers.batch_normalization,
training=training,
momentum=batch_norm_momentum)

my_dense_layer = partial(
tf.layers.dense,
kernel_initializer=he_init)

hidden1 = my_dense_layer(X, n_hidden1, name="hidden1")
bn1 = tf.nn.elu(my_batch_norm_layer(hidden1))
hidden2 = my_dense_layer(bn1, n_hidden2, name="hidden2")
bn2 = tf.nn.elu(my_batch_norm_layer(hidden2))
logits_before_bn = my_dense_layer(bn2, n_outputs, name="outputs")
logits = my_batch_norm_layer(logits_before_bn)

with tf.name_scope("loss"):
xentropy = tf.nn.sparse_softmax_cross_entropy_with_logits(labels=y, logits=logits)
loss = tf.reduce_mean(xentropy, name="loss")

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))
init = tf.global_variables_initializer()
saver = tf.train.Saver()
n_epochs = 20
batch_size = 20
extra_update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS)

with tf.Session() as sess:
       init.run()
       for epoch in range(n_epochs):
            for X_batch, y_batch in shuffle_batch(X_train, y_train, 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: X_valid, y: y_valid})
            print(epoch, "Validation accuracy:", accuracy_val)

       save_path = saver.save(sess, "./my_model_final.ckpt")

注意,你也能夠讓訓練操作依賴於更新操作,如下所示:

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)

這樣的話,你就只需要在訓練的時候執行 ‘training_op’ Tensorflow 將會自動執行更新操作:

sess.run(training_op, feed_dict={training: True, X: X_batch, y: y_batch})