一. 简介
神经网络训练中, 低层参数更新后, 其输出也会有相应变化, 高层参数会疲于适应这些剧烈的变化, 难以有效收敛, 所以需要 normalization.
有了它, 可以使用更大的学习率,初值可以更随意。它起到了正则项的作用,在某些情况下,有它就不需要使用 dropout 了。
二. 原理
因为低层参数的更新, 高层网络要接收的 x 的分布可能在不同 batch 上相差很大, 不再是独立同分布. 要解决独立同分布的问题,“理论正确” 的方法就是对每一层的数据都进行白化操作。然而标准的白化操作代价高昂,特别是我们还希望白化操作是可微的,保证白化操作可以通过反向传播来更新梯度。
因此,以 BN 为代表的 Normalization 方法退而求其次, 将
x
\bold x
x 的分布规范化成在固定区间范围的标准分布。
h
=
f
(
γ
⋅
x
−
μ
σ
+
β
)
(1)
h=f(\bold \gamma\cdot\frac{\bold x-\mu}{\sigma}+\bold \beta) \tag 1
h=f(γ⋅σx−μ+β)(1)
注意式1中的参数均为黑体向量, 维度与低层神经元个数一致.
- μ =E[x], 是平移参数(moving_mean),变换后的数据均值为0.
- σ= ( v a r ( x ) ) \sqrt{(var(x))} (var(x)), 是缩放参数(scale parameter), 变换后的数据方差为1. 以上两个参数不用学习, 是统计出来的.
- beta , 再平移参数(re-shift parameter),变换后的数据均值为 beta.
- gamma, 再缩放参数(re-scale parameter)。变换后的数据方差为 gamma. 以上两个参数是 trainable的.
Q: 为啥要有再平移和再缩放?
A: 强制限定 x 的分布范围会削弱低层网络的表达效果, 所以引入可学习参数 gama,beta, 保证模型的表达能力不因为规范化而下降。
Q:经过这么的变回来再变过去,会不会跟没变一样?
A: 不会。因为,再变换引入的两个新参数 gama,beta, 可以表示旧参数作为输入的同一族函数,但是新参数有不同的学习动态。在旧参数中,x 的均值取决于下层神经网络的复杂关联;但在新参数中,仅由 b 来确定,去除了与下层计算的密切耦合。新参数很容易通过梯度下降来学习,简化了神经网络的训练。
2.1 Normalization 的权重伸缩不变性
权重伸缩不变性(weight scale invariance)指的是,当权重 W 按照常量 λ 进行伸缩时,得到的规范化后的值保持不变.
所以, 在 低层输出与Norm层之间认为插入乘法op是不起作用的.
三. BatchNormalization
Batch Normalization 于 2015 年由 Google 提出,开 Normalization 之先河。其规范化针对单个神经元进行,利用网络训练时一个 mini-batch 的数据来计算该神经元的均值和方差, 因而称为 Batch Normalization。
以输入shape=[N,T,d] 为例, 计算均值
μ
\mu
μ.shape=[1,T,d]
BN 需要在运行过程中统计每个 mini-batch 的一阶统计量和二阶统计量,因此不适用于 动态的网络结构 和 RNN 网络。
3.1 keras api
- calss
BatchNormalization(Layer)
- BatchNormalization.
__init__
(self,
axis=-1,
momentum=0.99,
epsilon=1e-3,
center=True,
scale=True,
beta_initializer=‘zeros’,
gamma_initializer=‘ones’,
moving_mean_initializer=‘zeros’,
moving_variance_initializer=‘ones’,
beta_regularizer=None,
gamma_regularizer=None,
beta_constraint=None,
gamma_constraint=None,
renorm=False,
renorm_clipping=None,
renorm_momentum=0.99,
fused=None,
trainable=True,
virtual_batch_size=None,
adjustment=None,
name=None,
**kwargs)- momentum, Momentum for the moving average.
- center, scale. 两个 bool 参数, 分别对应 beta, gamma 是否生效.
3.2 tf api
位于 tensorflow.contrib.layers.python.layers.layers.batch_norm()
. 一个 case 见下: 调用
layers.fully_connected(inputs, num_outputs=256, normalizer_fn=layers.batch_norm
, scope=‘Deep/Deep_Network/Dnn_HiddenLayer_1’,…) api, 会
对于一个 [512, 256] 的 variable tensor, 叠加 BN 后新增的 var 为:
Deep/Deep_Network/Dnn_HiddenLayer_1/BatchNorm/beta, [256],<dtype: 'float32'>
Deep/Deep_Network/Dnn_HiddenLayer_1/BatchNorm/gamma, [256],<dtype: 'float32'>
Deep/Deep_Network/Dnn_HiddenLayer_1/BatchNorm/moving_mean, [256],<dtype: 'float32'>
Deep/Deep_Network/Dnn_HiddenLayer_1/BatchNorm/moving_variance, [256],<dtype: 'float32'>
3.3 training:bool 的计算差异
- 训练时,均值、方差只基于该批次内数据做统计, 记为 mean, variance;
- 推理时,均值、方差是基于所有批次(整个数据集)的期望, 记为 moving_mean, moving_variance.
Q: moving_mean, moving_variance 怎么算呢?
A: 严格统计所有数据的均值不现实(内存限制及样本总数不确定), 所以退而采用 滑动平均的迭代方法, 见下:
m
o
v
i
n
g
_
m
e
a
n
(
t
)
=
m
o
v
i
n
g
_
m
e
a
n
(
t
−
1
)
∗
d
e
c
a
y
+
m
e
a
n
_
t
h
i
s
_
b
a
t
c
h
∗
(
1
−
d
e
c
a
y
)
(2)
moving\_mean^{(t)} = \\ moving\_mean^{(t-1)} * decay + mean\_this\_batch * (1 - decay) \tag 2
moving_mean(t)=moving_mean(t−1)∗decay+mean_this_batch∗(1−decay)(2)
mean_this_batch 与 variance_this_batch 通过 tf.nn.moments
(x) 同时算出.
式(2)的迭代通过 tensorflow.python.keras.layers.normalization.BatchNormalization._assign_moving_average
(self, variable, value, momentum) 调用.
3.4 moving_mean 的更新机制
moving_mean 在训练时不参与 loss 计算, 那么按照最小拓扑子图策略, 它相应的计算 op 也就得不到执行, 但为了 predict 阶段用, 却仍要更新, 那如何保障执行呢?
以 keras.BatchNormalization 为例:
- 其
_fused_batch_norm
() 方法中有
mean_update = self._assign_moving_average(self.moving_mean, mean,
momentum)
self.add_update(mean_update, inputs=True)
语句. - add_update() 方法是父类 Layer 的方法, 它会添加入参中的 op 到 self.
_updates
字段中, 可通过updates()
方法返回, 同时该方法具有 @property 注解, 所以也可当字段用. - 后续 optimize 阶段, 需要手动指定在计算 loss 前先执行这些 update op.
图. 清晰地看到两个相应的 update_op. 名字含 cond_
是因为区分 training 的 true/false, 用到了 tf.condition.
注意 tf/keras update_op 的差异
- tf.layers.Layer.call() 中有
_add_elements_to_collection(self.updates, ops.GraphKeys.UPDATE_OPS)
语句, 添加到了 graph_obj 的 相应 dict 中. - 但 keras 并没有做这件事, 所以需要手动明确地作维护.
四. LayerNormalization
以输入shape=[N,T,d] 为例, 计算均值 μ \mu μ.shape=[N,T,1]
4.1 对比
在输入x上计算均值的时候, BN在axis=0上计算, LN在axis=-1上计算.
可以类比一个N*d的矩阵, BN竖着算, LN 横着算.
4.2 keras / layer_norm
4.3 tf / layer_norm
def layer_norm(inputs, epsilon=1e-8, scope="layer_norm", params_shape=None):
'''Applies layer normalization. See https://arxiv.org/abs/1607.06450.
inputs: A tensor with 2 or more dimensions, where the first dimension has `batch_size`.
epsilon: A floating number. A very small number for preventing ZeroDivision Error.
scope: Optional scope for `variable_scope`.
Returns:
A tensor with the same shape and data dtype as `inputs`.
'''
with tf.variable_scope(scope, reuse=tf.AUTO_REUSE):
# inputs [N,T,d]
inputs_shape = inputs.get_shape()
if params_shape == None:
params_shape = inputs_shape[-1:] # [d]
# [N,T,1]
mean, variance = tf.nn.moments(inputs, [-1], keep_dims=True)
# [d,]
beta = tf.get_variable("beta", params_shape, initializer=tf.zeros_initializer())
gamma = tf.get_variable("gamma", params_shape, initializer=tf.ones_initializer())
utils.add_variable_for_rtp([beta, gamma])
# inputs - mean, shape is [N,T,d] - [N,T,1]
normalized = (inputs - mean) / ((variance + epsilon) ** 0.5)
# [d,] * [N,T,d] + [d,]
outputs = gamma * normalized + beta
return outputs