虽然在训练初期使用 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})