深度学习:批归一化Batch Normalization

        深度神经网络模型训练难,其中一个重要的现象就是 Internal Covariate Shift. Batch Norm 自 2015 年由Google 提出之后, Layer Norm / Weight Norm / Cosine Norm 等也横空出世。BN,Batch Normalization,就是在深度神经网络训练过程中使得每一层神经网络的输入保持相近的分布。

BN的来源Motivation

1.1 独立同分布与白化

1.2 深度学习中的 Internal Covariate Shift

        作者认为:网络训练过程中参数不断改变导致后续每一层输入的分布也发生变化,而学习的过程又要使每一层适应输入的分布,因此我们不得不降低学习率、小心地初始化。作者将分布发生变化称之为 internal covariate shift。

        大家应该都知道,我们一般在训练网络的时会将输入减去均值,还有些人甚至会对输入做白化等操作,目的是为了加快训练。为什么减均值、白化可以加快训练呢,这里做一个简单地说明:首先,图像数据是高度相关的,假设其分布如下图a所示(简化为2维)。由于初始化的时候,我们的参数一般都是0均值的,因此开始的拟合y=Wx+b,基本过原点附近,如图b红色虚线。因此,网络需要经过多次学习才能逐步达到如紫色实线的拟合,即收敛的比较慢。如果我们对输入数据先作减均值操作,如图c,显然可以加快学习。更进一步的,我们对数据再进行去相关操作,使得数据更加容易区分,这样又会加快训练,如图d。
这里写图片描述

        白化的方式有好几种,常用的有PCA白化:即对数据进行PCA操作之后,在进行方差归一化。这样数据基本满足0均值、单位方差、弱相关性。作者首先考虑,对每一层数据都使用白化操作,但分析认为这是不可取的。因为白化需要计算协方差矩阵、求逆等操作,计算量很大,此外,反向传播时,白化操作不一定可导。于是,作者采用下面的Normalization方法。

Normalization via Mini-Batch Statistics

        数据归一化方法很简单,就是要让数据具有0均值和单位方差,如下式:
这里写图片描述
        但是如果简单的这么干,会降低层的表达能力。比如下图,在使用sigmoid激活函数的时候,如果把数据限制到0均值单位方差,那么相当于只使用了激活函数中近似线性的部分,这显然会降低模型表达能力。
这里写图片描述

为此,作者又为BN增加了2个参数,用来保持模型的表达能力:
这里写图片描述
上述公式中用到了均值E和方差Var,需要注意的是理想情况下E和Var应该是针对整个数据集的,但显然这是不现实的。因此,作者做了简化,用一个Batch的均值和方差作为对整个数据集均值和方差的估计。
整个BN的算法如下:
这里写图片描述
求导的过程也非常简单,有兴趣地可以自己再推导一遍或者直接参见原文。

[Batch Normalization详解]

总结一下:

Normalization 的通用公式

深度学习:批归一化Batch Normalization_第1张图片

BN与常用的数据归一化最大的区别就是加了后面这两个参数,这两个参数主要作用是在加速收敛和表征破坏之间做的trade off

BN训练和测试时的差异

        对于BN,在训练时,是对每一批的训练数据进行归一化,也即用每一批数据的均值和方差。而在测试时,比如进行一个样本的预测,就并没有batch的概念,因此用的是全量训练数据的均值和方差,可以通过移动平均法求得。

BN训练时为什么不用全量训练集的均值和方差呢?

        因为用全量训练集的均值和方差容易过拟合,对于BN,其实就是对每一批数据进行归一化到一个相同的分布,而每一批数据的均值和方差会有一定的差别,而不是用固定的值,这个差别实际上能够增加模型的鲁棒性,也会在一定程度上减少过拟合。也正是因此,BN一般要求将训练集完全打乱,并用一个较大的batch值,否则,一个batch的数据无法较好得代表训练集的分布,会影响模型训练的效果。

        具体在tf的解释看下面tf函数说明。

[BN和Dropout在训练和测试时的差别]

BN的dropout共同使用时的要注意的问题

[深度学习:正则化]

Batch Normalized的作用

1)改善流经网络的梯度

2)允许更大的学习率,大幅提高训练速度:你可以选择比较大的初始学习率,让你的训练速度飙涨。以前还需要慢慢调整学习率,甚至在网络训练到一半的时候,还需要想着学习率进一步调小的比例选择多少比较合适,现在我们可以采用初始很大的学习率,然后学习率的衰减速度也很大,因为这个算法收敛很快。当然这个算法即使你选择了较小的学习率,也比以前的收敛速度快,因为它具有快速训练收敛的特性;

3)减少对初始化的强烈依赖

4)改善正则化策略:作为正则化的一种形式,轻微减少了对dropout的需求你再也不用去理会过拟合中drop out、L2正则项参数的选择问题,采用BN算法后,你可以移除这两项了参数,或者可以选择更小的L2正则约束参数了,因为BN具有提高网络泛化能力的特性;

5)再也不需要使用使用局部响应归一化层了(局部响应归一化是Alexnet网络用到的方法,搞视觉的估计比较熟悉),因为BN本身就是一个归一化网络层;

6)可以把训练数据彻底打乱(防止每批训练的时候,某一个样本都经常被挑选到,文献说这个可以提高1%的精度)。

[cs231n学习笔记-激活函数-BN-参数优化]

Batch-normalized对于优化的作用

How Does Batch Normalization Help Optimization?

深度学习:批归一化Batch Normalization_第2张图片

[https://arxiv.org/pdf/1805.11604.pdf]
BN解决梯度消失的原因:

考虑一些具体的激活函数。我们看到,无论是tanh还是sigmoid

深度学习:批归一化Batch Normalization_第3张图片

深度学习:批归一化Batch Normalization_第4张图片

函数图像的两端,相对于x的变化,y的变化都很小(这其实很正常,毕竟tanh就是拉伸过的sigmoid),也就是说,容易出现梯度衰减的问题。那么,如果在tanh或sigmoid之前,进行一些normalization处理,就可以缓解梯度衰减的问题。所以最初的BN论文选择把BN层放在非线性激活之前。

但是ReLU的画风和它们完全不一样啊。

深度学习:批归一化Batch Normalization_第5张图片

实际上,最初的BN论文虽然也在使用ReLU的Inception上进行了试验,但首先研究的是sigmoid激活。因此,试验ReLU的,我猜想作者可能就顺便延续了之前把BN放前面的配置,而没有单独针对ReLU进行处理。

[Batch-normalized 应该放在非线性激活层的前面还是后面?]

所以Batch-normalized 应该放在非线性激活层的前面还是后面?

You have the choice of applying batch normalization either before or after the non-linearity, depending on your definition of the “activation distribution of interest” that you wish to normalize. It will probably end up being a hyperparameter that you’ll just have to tinker with.BN放在非线性函数前还是后取决于你想要normalize的对象,更像一个超参数。

[Batch-normalized 应该放在非线性激活层的前面还是后面?]

-柚子皮-

 

 

Batch Normalization —— 纵向规范化

Batch Normalization 针对单个神经元进行,利用网络训练时一个 mini-batch 的数据来计算该神经元 x_i 的均值和方差,因而称为 Batch Normalization。

深度学习:批归一化Batch Normalization_第6张图片

相对上面的 Normalization 通用公式:

BN 比较适用的场景是:每个 mini-batch 比较大,数据分布比较接近。在进行训练之前,要做好充分的 shuffle,否则效果会差很多。

另外,由于 BN 需要在运行过程中统计每个 mini-batch 的一阶统计量和二阶统计量,因此不适用于 动态的网络结构 和 RNN 网络。不过,也有研究者专门提出了适用于 RNN 的 BN 使用方法,这里先不展开了。

-柚子皮-

Layer Normalization —— 横向规范化

层规范化就是针对 BN 的上述不足而提出的。与 BN 不同,LN 是一种横向的规范化,如图所示。它综合考虑一层所有维度的输入,计算该层的平均输入值和输入方差,然后用同一个规范化操作来转换各个维度的输入。

深度学习:批归一化Batch Normalization_第7张图片

相对上面的 Normalization 通用公式:

LN 针对单个训练样本进行,不依赖于其他数据,因此可以避免 BN 中受 mini-batch 数据分布影响的问题,可以用于 小mini-batch场景、动态网络场景和 RNN,特别是自然语言处理领域。此外,LN 不需要保存 mini-batch 的均值和方差,节省了额外的存储空间。

但是,BN 的转换是针对单个神经元可训练的——不同神经元的输入经过再平移和再缩放后分布在不同的区间,而 LN 对于一整层的神经元训练得到同一个转换——所有的输入都在同一个区间范围内。如果不同输入特征不属于相似的类别(比如颜色和大小),那么 LN 的处理可能会降低模型的表达能力。

-柚子皮-

 

 

TensorFlow批归一化函数

tensorflow中batch normalization的实现主要有下面三个:

tf.nn.batch_normalization

tf.layers.batch_normalization

tf.contrib.layers.batch_norm

封装程度逐个递进,建议使用tf.layers.batch_normalization或tf.contrib.layers.batch_norm,因为在tensorflow官网的解释比较详细。

tf.layers.batch_normalization公式如下:

y=γ(x−μ)/σ+β

其中x是输入,y是输出,μ是均值,σ是方差,γ和β是缩放(scale)、偏移(offset)系数。

tf.keras.layers.BatchNormalization(...):

使用keras的话,是不需且不能In particular, tf.control_dependencies(tf.GraphKeys.UPDATE_OPS) should not be used。

tf.layers.batch_normalization(  #将被tf.keras.layers.BatchNormalization(...)取代
    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
)

参数:

inputs:张量输入。
axis:一个int,应该被规范化的轴(通常是特征轴)。例如,在使用data_format=“channels_first”的Convolution2D层之后,在BatchNormalization中设置axis=1。  理解的话可能参考示例及tf.contrib.layers.layer_norm中参数inputs的说明。
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,则无法指定。

训练的时候需要特别注意的两点

(1)输入参数设置training=True。同使用tf.layers.dropout时一样!

(2)计算loss时,要添加以下代码(即添加update_ops到最后的train_op中)。

net_optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate)
with tf.control_dependencies(tf.get_collection(tf.GraphKeys.UPDATE_OPS)):
    train_op = net_optimizer.minimize(loss)

或者等价地:

  x_norm = tf.compat.v1.layers.batch_normalization(x, training=training)

  # ...

  update_ops = tf.compat.v1.get_collection(tf.GraphKeys.UPDATE_OPS)
  train_op = optimizer.minimize(loss)
  train_op = tf.group([train_op, update_ops])

其原因:

        这样才能计算μ和σ的滑动平均(训练时,需要更新滑动平均值(moving_mean)和滑动方差(moving_variance)。默认情况下,更新操作放置在tf.GraphKeys.UPDATE_OPS中,因此需要将它们作为对train_ops的依赖项添加。此外,在获取update_ops集合之前,请确保添加任何批处理标准化(batch_normalization)操作。否则,update_ops将为空,训练 / 验证将无法正常工作。)

根据前面的理论,BN在training和inference时使用的方法是不一样的:

training时:

我们需要逐个神经元逐个样本地来计算,这个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一同被保存下来。

inference时:

output = (input - running_mean) / running_std
output = trained_gamma * output + trained_beta
也就是说,在inference时,BN对应的操作不再是公式里提到的那样,计算该batch的各种统计量,而是直接使用在训练时保存下来的population均值和方差,进行一次线性变换。这样效率提升了很多。但是缺点也显而易见,如果训练集和验证集不平衡的时候,验证的效果会一直一直很差。

[TensorFlow中批归一化的实现——tf.layers.batch_normalization()函数]

[由training参数想到的]

tf.layers.batch_normalization示例

output = tf.Variable([[[0.3, 0.0, 0.5, 0.2],
                       [0.44, 0.32, 0.23, 0.01],
                       [-0.2, 0.6, 0.5, 0.1]],
                      [[0.4, 0.0, 0.0, 0.2],
                       [0.4, 0.2, 0.3, 0.01],
                       [0.2, -0.6, -0.5, 0.15]]])
print(output.shape)

axis = [0, 1]
output_n = tf.layers.batch_normalization(output, axis=axis, training=True, trainable=False)
print(output_n.shape)
output_n_mean = tf.reduce_mean(output_n, axis=[2])with tf.Session() as sess:

    sess.run(tf.global_variables_initializer())
    print('\n')
    print(output_n.eval())
    print('\n')
    print(output_n_mean.eval())

(2, 3, 4)

(2, 3, 4)
[[[ 0.2731793  -1.365896    1.365896   -0.27317917]
  [ 1.1840361   0.43622375 -0.12463534 -1.4956245 ]
  [-1.3987572   1.0879223   0.77708733 -0.4662524 ]]

 [[ 1.4808724  -0.8885234  -0.8885234   0.29617447]
  [ 1.1691556  -0.18638718  0.49138427 -1.4741528 ]
  [ 1.0586927  -1.1269956  -0.85378444  0.92208725]]]


[[ 2.9802322e-08  0.0000000e+00  1.4901161e-08]
 [ 1.4901161e-08 -2.9802322e-08 -1.4901161e-08]] #误差范围内可以认为是0

如果batch_nomalization中axis改成axis = [2],且output_n_mean = tf.reduce_mean(output_n, axis=[0,1]),则输出:

[[[ 0.19564903 -0.23395906  0.9464483   1.0328814 ]
  [ 0.8277463   0.6298898   0.16815075 -1.1887878 ]
  [-2.061841    1.3857577   0.9464483  -0.13641822]]

 [[ 0.64714706 -0.23395906 -0.49484357  1.0328814 ]
  [ 0.64714706  0.30594647  0.3699316  -1.1887878 ]
  [-0.255849   -1.8536758  -1.9361355   0.4482317 ]]]


[-9.934107e-08  0.000000e+00  0.000000e+00  9.934107e-08] #误差范围内可以认为是0

也就是说batch_nomalization中axis是指定哪几个维度,则对另外几个维度进行nomalization。

其它示例:

cnn后加batch_normalization[使用tf.layers高级函数来构建带有BatchNormalization的神经网络]

TensorFlow层归一化函数

tf.contrib.layers.layer_norm(
    inputs,
    center=True,
    scale=True,
    activation_fn=None,
    reuse=None,
    variables_collections=None,
    outputs_collections=None,
    trainable=True,
    begin_norm_axis=1,
    begin_params_axis=-1,
    scope=None
)

By default, begin_norm_axis = 1 and begin_params_axis = -1, meaning that normalization is performed over all but the first axis (the HWC if inputs is NHWC), while the beta and gamma trainable parameters are calculated for the rightmost axis (the C if inputs is NHWC). Scaling and recentering is performed via broadcast of the beta and gamma parameters with the normalized tensor.

参数:

inputs: A tensor having rank R. The normalization is performed over axes begin_norm_axis ... R - 1 and centering and scaling parameters are calculated over begin_params_axis ... R - 1.

lstm中使用layer_norm

[tf.contrib.rnn.LayerNormBasicLSTMCell]

直接在lstm后面使用layer_norm或者batch_normalization还不清楚怎么搞。

from: -柚子皮-

ref: [详解深度学习中的Normalization,BN/LN/WN]**

 

你可能感兴趣的:(深度学习:批归一化Batch Normalization)