关于归一化的讲解的博客——【深度学习】Batch Normalization(批归一化)
tensorflow中的函数解析在这个博客——tf.nn.batch_normalization()函数解析
0 前言
关于批归一化的讲解我们在博客【深度学习】Batch Normalization(批归一化)中做了详细的讲解,不懂的同学可以看一下,下面我们来说一种TensorFlow中的批归一化的代码实现,主要使用的函数是tf.layers.batch_normalization()函数。
简单来说公式如下:
其中是输入,是输出,是均值,是方差,和是缩放(scale)、偏移(offset)系数。
1 函数
tf.layers.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,
beta_constraint=None,
gamma_constraint=None,
training=False,
trainable=True,
name=None,
reuse=None,
renorm=False,
renorm_clipping=None,
renorm_momentum=0.99,
fused=None,
virtual_batch_size=None,
adjustment=None
)
注意:训练时,需要更新滑动平均值(
moving_mean)和滑动方差(moving_variance)。默认情况下,更新操作放置在tf.GraphKeys.UPDATE_OPS中,因此需要将它们作为对train_ops的依赖项添加。此外,在获取update_ops集合之前,请确保添加任何批处理标准化(batch_normalization)操作。否则,update_ops将为空,训练 / 验证将无法正常工作。
参数:
inputs:张量输入。axis:一个int,应该被规范化的轴(通常是特征轴)。例如,在使用data_format=“channels_first”的Convolution2D层之后,在BatchNormalization中设置axis=1。momentum:滑动平均值的动量。epsilon:小浮点数加上方差以避免被零除。center:如果为True,则将beta的偏移量添加到标准化张量。如果为False,则忽略beta。scale:如果为True,则乘以gamma。如果为False,则不使用gamma。当下一层是线性的(例如,nn.relu)时,可以禁用此选项,因为可以由下一层进行缩放。beta_initializer:beta权重的初始值设定项。gamma_initializer:gamma权重的初始值设定项。moving_mean_initializer:滑动平均值的初始化器。moving_variance_initializer:滑动方差的初始值设定项。beta_regularizer:可选的beta权重正则化器。gamma_regularizer:gamma权重的可选调节器。beta_constraint:由Optimizer更新后应用于beta权重的可选投影函数(例如,用于实现层权重的规范约束或值约束)。函数必须将未投影的变量作为输入,并且必须返回投影的变量(必须具有相同的形状)。在进行异步分布式训练时,使用约束是不安全的。gamma_constraint:由Optimizer更新后应用于gamma权重的可选投影函数。- training:要么是Python布尔值,要么是TensorFlow布尔值标量张量(例如占位符)。是以训练模式(使用当前批的统计数据进行规范化)还是以推理模式(使用滑动统计数据进行规范化)返回输出。注意:请确保正确设置此参数,否则您的训练 / 验证将无法正常工作。
trainable:布尔值,如果为True,还将变量添加到图形集合GraphKeys.TRAINABLE_VARIABLES(请参见tf.variable)。name:字符串,层的名称。reuse:布尔值,是否以相同的名称重用前一层的权重。- `renorm:是否使用批量再规范化(https://arxiv.org/abs/1702.03275)。 这会在训练期间增加额外的变量。这个参数的任何一个值的推断都是相同的。
renorm_clipping:一种字典,可以将关键字“rmax”、“rmin”、“dmax”映射到用于剪裁renorm校正的标量张量。校正(r,d)用作corrected_value = normalized_value * r + d,其中r被剪裁为[rmin,rmax],d被剪裁为[-dmax,dmax]。缺少的rmax、rmin和dmax分别设置为inf、0和inf。renorm_momentum:用renorm更新滑动方式和标准偏差的动量。与动量不同,这会影响训练,既不应太小(会增加噪音),也不应太大(会给出过时的估计)。注意,momentum仍然被用来得到均值和方差来进行推理。fused:如果False或者True,尽可能使用更快的融合实现。如果为False,则使用系统建议的实现。virtual_batch_size:一个int。默认情况下,virtual_batch_size为None,这意味着在整个批次中执行批次规范化。当virtual_batch_size不是None时,改为执行“Ghost Batch Normalization”,创建每个单独规范化的虚拟子批(使用共享gamma、beta和滑动统计)。必须在执行期间划分实际批大小。adjustment:仅在训练期间,采用包含输入张量(动态)形状的张量并返回一对(scale、bias)以应用于标准化值(γ和β之前)的函数。例如,如果axis=-1,adjustment = lambda shape: ( tf.random_uniform(shape[-1:], 0.93, 1.07),tf.random_uniform(shape[-1:], -0.1, 0.1))将标准化值向上或向下缩放7%,然后将结果向上滑动0.1(每个功能都有独立的缩放和偏移,但在所有示例中都有共享),最后应用gamma和/或beta。如果没有,则不应用调整。如果指定了virtual_batch_size,则无法指定。
返回:
- 输出张量。
更多详细的内容可以去官方API文档查看:https://tensorflow.google.cn/versions/r1.12/api_docs/python/tf/layers/batch_normalization
2 训练
训练的时候需要特别注意的有两点:
(1)输入参数设置training=True,
(2)计算loss时,要添加以下代码(即添加update_ops到最后的train_op中)。
这样才能计算μ和σ的滑动平均(测试时会用到)
net_optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate)
update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS)
with tf.control_dependencies(update_ops):
train_op = net_optimizer.minimize(loss)
3 验证
测试的时候需要特别注意的只有一点:输入参数设置training=False。
训练时:
我们需要逐个神经元逐个样本地来计算,这个batch在某一层输出的均值和标准差,然后再对该层的输出进行标准化,同时还要学习gamma和beta两个参数。这是非常耗时的,显然,我们不能在inference的时候使用这种方法。
解决方案就是,在训练时使用滑动平均维护population均值和方差:
running_mean = momentum * running_mean + (1 - momentum) * sample_mean
running_std = momentum * running_std + (1 - momentum) * sample_std
在训练结束保存模型时,running_mean、running_var、trained_gamma和trained_beta一同被保存下来。
验证时:
output = (input - running_mean) / np.sqrt(running_std+eps)
output = trained_gamma * output + trained_beta
也就是说,在验证时,BN对应的操作不再是公式里提到的那样,计算该batch的各种统计量,而是直接使用在训练时保存下来的population均值和方差,进行一次线性变换。这样效率提升了很多。但是缺点也显而易见,如果训练集和验证集不平衡的时候,验证的效果会一直一直很差,所以这样看来深度学习还是数据的游戏,谁的数据质量高谁就是赢家罢了。
4 测试
测试是通过读取我们保存的模型,也就是从.checkpoint文件中读取模型参数从而进行测试。

我常用的保存参数的方式是最多保存几个:
var_list = tf.trainable_variables()
saver = tf.train.Saver(var_list=var_list, max_to_keep=5)
5 批归一化
在批归一化中有两个重要的参数γ和β,这是可训练参数,而μ和σ不是,它们是通过滑动平均计算出的,如果按照上面的方法保存模型,在读取模型预测时,会报错找不到μ和σ。
var_list = tf.trainable_variables()
g_list = tf.global_variables()
bn_moving_vars = [g for g in g_list if 'moving_mean' in g.name]
bn_moving_vars += [g for g in g_list if 'moving_variance' in g.name]
var_list += bn_moving_vars
按照上述写法,即可把μ和σ保存下来,读取模型预测时也不会报错。
6 总结
首先指定一个参数is_training,把is_training这个布尔值在构建计算图时就确定,从而更加清晰地调用batch normalization,确保不会出错。
is_training = tf.placeholder(tf.bool)
通过feed_dict进行is_training的赋值,再通过is_training对training进行赋值。
...
...
[train_batch_image, train_batch_label] = sess.run([train_images, train_labels])
train_feed_dict = {x: train_batch_image,
y_: train_batch_label,
is_training: True}
...
...
[val_batch_image, val_batch_label] = sess.run([val_images, val_labels])
val_feed_dict = {x: val_batch_image,
y_: val_batch_label,
is_training: False}
...
...
如果想要更多的资源,欢迎关注 @我是管小亮,文字强迫症MAX~
回复【福利】即可获取我为你准备的大礼,包括C++,编程四大件,NLP,深度学习等等的资料。
想看更多文(段)章(子),欢迎关注微信公众号「程序员管小亮」~

参考文章
- tensorflow中batch normalization的用法
- https://zhuanlan.zhihu.com/p/32846476