微信公号:ilulaoshi / 原文发表在我的个人网站:https://lulaoshi.info/machine-learning/convolutional/batch-normalization.html
转载请引用我的个人网站链接,谢谢!
深度神经网络很难训练。因为深度神经网络中网络层数太多,在训练过程中,模型参数的更新会引起靠近输出侧各层的输出结果发生剧烈的变化。Google 将这一现象总结为Internal Covariate Shift(ICS),具体而言,有如下表现:
- 靠近输出侧的各层需要不断去重新适应参数更新,学习速度慢。
- 每层的更新都会影响到其他层,为了训好模型,我们需要非常谨慎地去设定学习率、初始化权重、以及尽可能细致的参数更新策略。
从上图可以看到,靠近输入侧的层稍有更新,靠近输出侧的层都需要重新寻找更优参数。这种计算数值的不稳定性通常令我们难以训练出有效的深度模型。对数据进行归一化是解决这类问题的一种思路。
批量归一化的数学表示
归一化是机器学习特征工程常用的技巧,一般是减去均值然后除以标准差。
浅层的模型,比如线性回归,逻辑回归等,需要对输入特征进行归一化。假设我们有两个特征,分别为年龄和价格,这两个特征的度量单位不同,表征含义不同,直接用来作为机器学习算法的特征,分布不均匀。我们需要使用归一化,将这两个特征转成均值为0的分布,机器学习的效果会好很多。
基于这种归一化的思想,我们可以在深度神经网络的层与层之间使用归一化,Google 提出了批量归一化。
批量归一化(Batch Normalization)主要是在一个批次(Batch)上进行归一化。给定一个批次
B
\mathcal{B}
B,批次内包含多个样本,
x
∈
B
\boldsymbol{x} \in \mathcal{B}
x∈B表示
x
\boldsymbol{x}
x属于这个批次。Batch Normalization的公式如下:
B
N
(
x
)
=
γ
⊙
x
−
μ
^
B
σ
^
B
+
β
.
\mathrm{BN}(\boldsymbol{x}) = \boldsymbol{\gamma} \odot \frac{\boldsymbol{x} - \hat{\boldsymbol{\mu}}_\mathcal{B}}{\hat{\boldsymbol{\sigma}}_\mathcal{B}} + \boldsymbol{\beta}.
BN(x)=γ⊙σ^Bx−μ^B+β.
在这个公式中,
μ
^
B
\hat{ \boldsymbol{\mu}}_\mathcal{B}
μ^B是这个批次所有样本的均值,
σ
^
B
\hat{\boldsymbol{\sigma}}_\mathcal{B}
σ^B是这个批次所有样本的标准差,
x
−
μ
^
B
σ
^
B
\frac{\boldsymbol{x} - \hat{\boldsymbol{\mu}}_\mathcal{B}}{\hat{\boldsymbol{\sigma}}_\mathcal{B}}
σ^Bx−μ^B是一个对输入数据进行归一化的过程,即减去均值然后除以标准差。数据经过
x
−
μ
^
B
σ
^
B
\frac{\boldsymbol{x} - \hat{\boldsymbol{\mu}}_\mathcal{B}}{\hat{\boldsymbol{\sigma}}_\mathcal{B}}
σ^Bx−μ^B的归一化后,得到一个均值为0,方差为单位误差的分布。
接着,还要对这个归一化的数值进行伸缩(Scale)和偏移(Shift)。 γ \boldsymbol{\gamma} γ是一个伸缩系数,可以对数据扩大或缩小,它和 x \boldsymbol{x} x的维度相同, ⊙ \odot ⊙表示 γ \boldsymbol{\gamma} γ中的每个元素与归一化之后的 x \boldsymbol{x} x按元素(Element-wise)做乘法。 β \boldsymbol{\beta} β是一个偏移系数,它和 x \boldsymbol{x} x的维度相同,按元素做加法。 γ \boldsymbol{\gamma} γ和 β \boldsymbol{\beta} β是两个需要学习的参数。对于一个输入 x \boldsymbol{x} x,经过 B N ( x ) \mathrm{BN}(\boldsymbol{x}) BN(x)后,得到的数据符合均值为 β \boldsymbol{\beta} β,方差为 γ 2 \boldsymbol{\gamma}^2 γ2的分布。
一个疑问是,明明是为了进行归一化,我们得到了均值为0,方差为单位误差的分布了,为什么又在归一化的基础上加了伸缩和平移?怎么又变回去了?答案是:为了保障模型的表达能力不因为归一化而下降。直观上来讲,神经网络某一层的在努力地学习,如果只做 x − μ ^ B σ ^ B \frac{\boldsymbol{x} - \hat{\boldsymbol{\mu}}_\mathcal{B}}{\hat{\boldsymbol{\sigma}}_\mathcal{B}} σ^Bx−μ^B,那么这一层的输出会被粗暴地限制到某个固定的范围,后续层输入都是这样一个分布的数据。有了伸缩和平移,而且这两个参数是需要学习的参数,那么这一层的输出就不再是被粗暴归一化的了。
如果不使用Batch Normalization,某一层的输入均值取决于前序层神经网络的复杂的前向传播;使用了Batch Normalization,某一层的输入均值仅由 β \boldsymbol{\beta} β来确定,去除了与前序网络前向传播计算的紧耦合。
在计算
μ
^
B
\hat{ \boldsymbol{\mu}}_\mathcal{B}
μ^B和
σ
^
B
\hat{\boldsymbol{\sigma}}_\mathcal{B}
σ^B时,需要使用下面的公式:
μ
^
B
=
1
m
∑
i
=
1
m
x
(
i
)
,
σ
^
B
2
=
1
m
∑
i
=
1
m
(
x
(
i
)
−
μ
^
B
)
2
+
ϵ
.
\begin{aligned} \hat{\boldsymbol{\mu}}_\mathcal{B} &= \frac{1}{m}\sum_{i = 1}^{m} \boldsymbol{x^{(i)}},\\ \hat{\boldsymbol{\sigma}}_\mathcal{B}^2 &= \frac{1}{m}\sum_{i = 1}^{m} (\boldsymbol{x^{(i)}} - \hat{\boldsymbol{\mu}}_{\mathcal{B}})^2 + \epsilon.\end{aligned}
μ^Bσ^B2=m1i=1∑mx(i),=m1i=1∑m(x(i)−μ^B)2+ϵ.
其中,
m
m
m为批次中的样本数量。另外,
ϵ
>
0
\epsilon > 0
ϵ>0是一个很小的常数,因为Batch Normalization在计算过程中,
σ
^
B
\hat{\boldsymbol{\sigma}}_\mathcal{B}
σ^B在分母上,需要保证分母大于0。
全连接和卷积的批量归一化
对于深度卷积神经网络,全连接层和卷积层的批量归一化实现起来稍有差别。Batch Normalization主要使用一个批次的样本数据,在之前的各个卷积神经网络的讨论中,我们其实一直没有关注批次维度,实际上,模型训练一般(batch_size, features)
的形式,批次中的样本数量是一个重要的维度。
全连接层的批量归一化
卷积层的一个批次的数据一般遵循(batch_size, features)
的模式,第一维是批次中样本数量,第二维是全连接层输入
x
\boldsymbol{x}
x的维度。
原始论文将Batch Normalization放在了仿射变换(
W
x
+
b
\mathbf{W}\boldsymbol{x} + \mathbf{b}
Wx+b)的后面,非线性激活函数的前面。后续一些应用也将Batch Normalization放在非线性激活函数的后面。如果按照原始论文,首先经过仿射变换
W
x
+
b
\mathbf{W}\boldsymbol{x} + \mathbf{b}
Wx+b,进行批量归一化,再使用非线性激活函数
g
\mathbf{g}
g,输出为
h
\mathbf{h}
h完整的公式为:
h
=
g
(
B
N
(
W
x
+
b
)
)
.
\mathbf{h} = \mathbf{g}(\mathrm{BN}(\mathbf{W}\boldsymbol{x} + \mathbf{b}) ).
h=g(BN(Wx+b)).
在进行Batch Normalization时,均值和方差都是基于一个批次内的数据。
卷积层的批量归一化
一般将Batch Normalization放在卷积层后,非线性激活函数前。
区别于全连接层,卷积层一个批次的数据一般遵循(batch_size, channels, height, width)
的模式,第一维是批次中样本数量,第二维是频道数,第三维和第四维是高和宽。我们需要对这些通道的输出分别做批量归一化,且每个通道都拥有独立的伸缩和偏移参数,并均为标量。设批次中有
m
m
m个样本。在单个通道上,卷积层输出的高和宽分别为
h
h
h和
w
w
w。我们需要对该通道中
m
×
h
×
w
m \times h \times w
m×h×w个元素同时做批量归一化。具体的计算均值和方差时,我们使用该通道中
m
×
h
×
w
m \times h \times w
m×h×w个元素的均值和方差。如果有
c
c
c个通道,要对这
c
c
c个通道都执行一次批量归一化。
Batch Normalization在一定程度上解决了计算不稳定的问题,可以加速训练的过程,原始论文提到:
We apply Batch Normalization to the best-performing ImageNet classification network, and show that we can match its performance using only 7% of the training steps, and can further exceed its accuracy by a substantial margin.
预测时的批量归一化
使用批量归一化训练时,我们可以将批量大小设得大一点,从而使批量内样本的均值和方差的计算都较为准确。将训练好的模型用于预测时,一般输入都是单个样本,没办法计算归一化的均值和方差。我们可以计算整个训练样本的均值和方差,使用这个均值和方差。这种方法计算量很大,另一种常用的方法是在训练时,通过移动平均估算整个训练数据集的样本均值和方差,并在预测时使用它们得到确定的输出。下面公式计算了移动平均值。
μ
m
o
v
=
α
×
μ
m
o
v
+
(
1
−
α
)
×
μ
^
B
,
σ
m
o
v
2
=
α
×
σ
m
o
v
2
+
(
1
−
α
)
×
σ
^
B
2
.
\begin{aligned} \boldsymbol{\mu}_{mov} = \alpha \times \boldsymbol{\mu}_{mov} + (1 - \alpha) \times \hat{\boldsymbol{\mu}}_\mathcal{B},\\ \boldsymbol{\sigma}_{mov}^2 = \alpha \times \boldsymbol{\sigma}_{mov}^2 + (1 - \alpha) \times \hat{\boldsymbol{\sigma}}^2_\mathcal{B}. \end{aligned}
μmov=α×μmov+(1−α)×μ^B,σmov2=α×σmov2+(1−α)×σ^B2.
在这个批次内,我们先计算批次内均值和方差,另外有变量
μ
m
o
v
\boldsymbol{\mu}_{mov}
μmov和
σ
m
o
v
2
\boldsymbol{\sigma}_{mov}^2
σmov2存储着批次之间移动平均值。这里的
α
\alpha
α是一个系数,又被称为Momentum。计算时会跨多个批次,Momentum可以设置为0.9,那么移动平均会保留原来移动平均值的0.9,并将本批次平均值的0.1更新到移动平均值里。
需要注意的是,这里的Momentum与优化器中的Momentum不同。另外,移动平均值是在训练过程中,在遍历所有训练样本时计算得到的,训练结束后保留下来,在预测时使用。
框架实现
如前文所述,计算的过程没有那么复杂,深度学习框架PyTorch和TensorFlow已经帮我们实现了。比如,PyTorch中torch.nn.BatchNorm2d
,TensorFlow的tf.keras.layers.BatchNormalization
。
小结
- 深度神经网络训练难,Batch Normalization在层与层之间进行归一化,经过归一化后,输出结果更稳定,可以提升训练速度。
- 批量归一化基于一个批次的数据,计算批次均值和方差,进行伸缩和偏移。
- 全连接层和卷积层的批量归一化略有不同。
- 训练和预测时,批量归一化的计算方式不同。
参考资料
-
Ioffe, S., & Szegedy, C. (2015). Batch normalization: accelerating deep network training by reducing internal covariate shift.