BN的存在,主要起因于数据分布的问题。所谓数据分布,分为两种情况,一种在输入时数据分布不一样,称之为Covariate Shift,比如训练的数据和测试的数据本身分布就不一样,那么训练后的模型就很难泛化到测试集上。另一种分布不一样是指在输入数据经过网络内部计算后,分布发生了变化,使数据分布变得不稳定,从而导致网络寻找最优解的过程变得缓慢,训练速度会下降,这种称之为内部协方差偏移(Internal Covariate Shift)。
内部协方差偏移为什么会影响网络训练:训练深度网络时,神经网络隐层参数更新会导致网络输出层输出数据的分布(相对于输入数据分布)发生变化,而且随着层数的增加,根据链式规则,这种偏移现象会逐渐被放大。这对于网络参数学习来说是个问题:因为神经网络本质学习的就是数据分布(representation learning),如果数据分布变化了,神经网络又需要学习新的分布。
BN层的作用是对一个batch内的所有样本进行标准化,将不规范的样本分布变换为正态分布。处理后的样本数据分布于激活函数的敏感区域(梯度值较大的区域),因此在反向传播时能够加快误差的传播,加速网络训练。
目前主要有这几个方法:
Batch Normalization(2015年):
Ioffe S, Szegedy C. Batch normalization: Accelerating deep network training by reducing internal covariate shift[C]//International conference on machine learning. PMLR, 2015: 448-456.
Layer Normalization(2016年):
Ba J L, Kiros J R, Hinton G E. Layer normalization[J]. arXiv preprint arXiv:1607.06450, 2016.
Instance Normalization(2017年):
Ulyanov D, Vedaldi A, Lempitsky V. Instance normalization: The missing ingredient for fast stylization[J]. arXiv preprint arXiv:1607.08022, 2016.
Group Normalization(2018年):
Wu Y, He K. Group normalization[C]//Proceedings of the European conference on computer vision (ECCV). 2018: 3-19.
Switchable Normalization(2018年):
Luo P, Ren J, Peng Z, et al. Differentiable learning-to-normalize via switchable normalization[J]. arXiv preprint arXiv:1806.10779, 2018.
4种方法的示意图如下:
假设输入图像大小为 [ N , C , H , W ] [N, C, H, W] [N,C,H,W]简单来说,5种方法的主要区别在于:
BN:将一个batch内的样本( N N N个样本),按照 N , H , W N, H, W N,H,W三个维度进行标准化,也就是说,在每个通道上将所有的 N N N个样本的 H , W H, W H,W进行标准化操作。在batch较小时效果不好;
LN:对特定的单个样本,按照 C , H , W C, H, W C,H,W三个维度进行标准化,也就是说,对每个样本的所有通道上的 H , W H, W H,W进行标准化操作。对RNN作用明显;
IN:对特定的单个样本,按照 H , W H, W H,W两个维度进行标准化,也就是说,对每个样本的每个通道上的 H , W H, W H,W进行标准化操作。一般用于图像风格迁移任务中;
GN:对特定的单个样本,将 C C C分组,按照分组进行LN操作,也就是说,对每个样本特定通道上的 H , W H, W H,W进行标准化操作;
SN:将前面四种标准化方法结合,赋予权重,让网络自行学习归一化层如何选择。
Batch Normalization的目标是将特征进行标准化,即使网络输入成为0均值标准差为1的正态分布。BN可以看成是对一个Batch(比如 N N N)内的样本,按特征通道进行归一化(对每个通道上的样本进行归一化)。
网络在训练中,样本数据通常是以batch的方式传入网络,因此每个batch的样本分布可能出现差异,通过BN操作使每个batch中的样本分布均变为标准正态分布,可以加速网络的训练;
internal covarivate shift问题
BN的操作流程:(假设这里的样本 x i x_i xi均为单通道)
算法过程:
import numpy as np
def Batchnorm(x, gamma, beta, bn_param):
# x_shape:[B, C, H, W]
running_mean = bn_param['running_mean']
running_var = bn_param['running_var']
results = 0.
eps = 1e-5
x_mean = np.mean(x, axis=(0, 2, 3), keepdims=True)
x_var = np.var(x, axis=(0, 2, 3), keepdims=True0)
x_normalized = (x - x_mean) / np.sqrt(x_var + eps)
results = gamma * x_normalized + beta
# 因为在测试时是单个图片测试,这里保留训练时的均值和方差,用在后面测试时用
# 指数加权移动平均法(exponenentially weighted average)计算训练阶段中的均值和方差
running_mean = momentum * running_mean + (1 - momentum) * x_mean
running_var = momentum * running_var + (1 - momentum) * x_var
bn_param['running_mean'] = running_mean
bn_param['running_var'] = running_var
return results, bn_param
与Dropout类似,要保证训练阶段的输出和预测阶段的输出分布一致,那么就要进行一下处理(简单来说就是使用训练阶段的均值和方差的期望来对测试样本进行归一化)。
因为训练数据时一般会用batch对样本进行划分,但是在验证或测试时,一次只会输入一条数据,没有均值和方差,如何对单条数据进行批标准化呢?针对这种情况,可以利用训练集中的均值和方差,下图是BN论文中的原图,1-6步是训练过程,7-12步是推断过程:
在上述过程中,每个样本 x x x的通道数为 K K K,第 k k k个通道上的样本为 x ( k ) x^{(k)} x(k)。
训练阶段对在每个通道上对样本进行归一化操作,并加入偏移和缩放操作得到 y y y,通过损失函数优化原始的网络函数 Θ \Theta Θ和BN层的参数 { γ ( k ) , β ( k ) } k = 1 K \{\gamma^{(k)}, \beta^{(k)}\}^K_{k=1} {γ(k),β(k)}k=1K。
冻结BN层的参数,即 N BN inf ← N BN tr N^\text{inf}_\text{BN}\leftarrow N^\text{tr}_\text{BN} NBNinf←NBNtr,完成样本的预测任务。
求训练阶段每个batch的均值和方差的无偏估计,作为预测阶段的均值和方差,进行归一化操作,如:
y ← γ ( x − E [ x ] V a r [ x ] + ϵ ) + β = γ V a r [ x ] + ϵ x + [ β − γ E [ x ] V a r [ x ] + ϵ ] y\gets \gamma \left( \frac{x-E[x]}{\sqrt{Var[x] +\epsilon}} \right) +\beta =\frac{\gamma}{\sqrt{Var[x] +\epsilon}}x+\left[ \beta -\frac{\gamma E[x]}{\sqrt{Var[x] +\epsilon}} \right] y←γ(Var[x]+ϵx−E[x])+β=Var[x]+ϵγx+[β−Var[x]+ϵγE[x]]
无偏估计:无偏估计是用样本统计量来估计总体参数时的一种无偏推断。
首先解释下scale和shift的作用,以下图进行解释:
假设原始输入数据如图a所示,完成二分类任务时划分超平面如图b所示。对原始数据进行平移操作,处理后数据如图c所示。可以看出相对于原始数据,平移后的数据更容易确定最优超平面的参数b;对平移后的数据进行缩放操作,如图d所示,可以看出处理后的数据差异性更大,更容易确定最优划分超平面。
总得来说,平移和缩放操作是为了降低网络训练的难度,加速网络训练。
BN进行缩放和平移操作的另一个原因是:假设强制对网络的每一层输入作归一化操作得到标准正态分布,当输入数据原始分布不符合标准正态分布时,很容易导致后层的网络学习不到输入数据的分布。因此,暴力归一化显然是不合理的。而BN中引入可学习的缩放和平移参数 γ , β {\gamma, \beta} γ,β目的就是,不会强制使分布变为正态分布,保留网络学习到输入分布的部分特性。甚至预测阶段的两个参数是通过训练集学习得到,因此能够通过两个参数使预测阶段的样本向训练集的整体分布靠近。所以,当训练样本和预测样本分布一致时,效果提升明显。
另外一点,网络训练过程中,梯度受到输入的影响,假如输入 x x x的分布不稳定(例如方差较大),会导致梯度值太大或太小,导致参数更新过程的不稳定。BN可以缓解这种不稳定的情况。
上面提到BN可以使网络输出值变得较为稳定,从而使梯度变化较为稳定。也就是说,假设当输入缩放 α \alpha α倍后,通过BN层的输出分布大致相同。因为, x , W x, W x,W相对于 BN ( W x ) \text{BN}\left( Wx \right) BN(Wx)和缩放 α \alpha α倍后的权重输出 BN ( α W x ) \text{BN}\left( \alpha Wx \right) BN(αWx)相等,即 BN ( α W x ) = BN ( W x ) \text{BN}\left( \alpha Wx \right) = \text{BN}\left(Wx \right) BN(αWx)=BN(Wx),由此可以得出:
∂ BN ( ( α W ) x ) ∂ x = ∂ ( W x ) ∂ x , ∂ BN ( ( α W ) x ) ∂ α W = 1 α ∂ ( W x ) ∂ W \frac{\partial \text{BN}\left(( \alpha W)x \right)}{\partial x}= \frac{\partial \left( Wx \right)}{\partial x},\ \frac{\partial \text{BN}\left(( \alpha W)x \right)}{\partial \alpha W}= \frac{1}{\alpha}\frac{\partial \left( Wx \right)}{\partial W} ∂x∂BN((αW)x)=∂x∂(Wx), ∂αW∂BN((αW)x)=α1∂W∂(Wx)
可以看出,BN层可以减缓网络参数的梯度变化,因此可以使用更大的学习率加速训练。此外,BN的好处还有:
BN层的反向传播相比于普通层要略微复杂一些,首先给出论文中的公式,对其中省略的步骤在下面会给出推导过程。
由于网络正向传播是根据 γ , β \gamma ,\beta γ,β和均值、方差将 x i x_i xi变换为 y i y_i yi,那么反向传播则是根据 ∂ ℓ ∂ y i \frac{\partial \ell}{\partial y_i} ∂yi∂ℓ,计算出损失函数对于 γ , β \gamma ,\beta γ,β以及 x i x_i xi的偏导,其中对 x i x_i xi求偏导则是为了将误差继续传播。BN层的输出为: y i = BN γ , β ( x i ) = γ x ^ i + β y_i=\text{BN}_{\gamma ,\beta}\left( x_i \right) = \gamma \hat{x}_i + \beta yi=BNγ,β(xi)=γx^i+β 。
通过分析,我们需要得到的 ∂ ℓ ∂ x i , ∂ ℓ ∂ γ , ∂ ℓ ∂ β \frac{\partial \ell}{\partial x_i},\frac{\partial \ell}{\partial \gamma},\frac{\partial \ell}{\partial \beta} ∂xi∂ℓ,∂γ∂ℓ,∂β∂ℓ,而 x ^ i , μ B , σ B 2 \hat{x}_i,\mu _{\mathcal{B}},\sigma _{\mathcal{B}}^{2} x^i,μB,σB2与 x i x_i xi都有关系,即求解 ∂ ℓ ∂ x i \frac{\partial \ell}{\partial x_i} ∂xi∂ℓ可以分为:
∂ ℓ ∂ x i = ∂ ℓ ∂ x ^ i ∂ x ^ i ∂ x i + ∂ ℓ ∂ μ B ∂ μ B ∂ x i + ∂ ℓ ∂ σ B 2 ∂ σ B 2 ∂ x i \frac{\partial \ell}{\partial x_i}=\frac{\partial \ell}{\partial \hat{x}_i}\frac{\partial \hat{x}_i}{\partial x_i}+\frac{\partial \ell}{\partial \mu _{\mathcal{B}}}\frac{\partial \mu _{\mathcal{B}}}{\partial x_i}+\frac{\partial \ell}{\partial \sigma _{\mathcal{B}}^{2}}\frac{\partial \sigma _{\mathcal{B}}^{2}}{\partial x_i} ∂xi∂ℓ=∂x^i∂ℓ∂xi∂x^i+∂μB∂ℓ∂xi∂μB+∂σB2∂ℓ∂xi∂σB2
第一项中:
∂ ℓ ∂ x ^ i = ∂ ℓ ∂ y i ∂ y i ∂ x ^ i = ∂ ℓ ∂ y i γ \frac{\partial \ell}{\partial \hat{x}_i}=\frac{\partial \ell}{\partial y_i}\frac{\partial y_i}{\partial \hat{x}_i}=\frac{\partial \ell}{\partial y_i}\gamma ∂x^i∂ℓ=∂yi∂ℓ∂x^i∂yi=∂yi∂ℓγ
先求损失 ℓ \ell ℓ相对于参数 μ B , σ B 2 \mu_\mathcal{B}, \sigma^2_\mathcal{B} μB,σB2的梯度:
∂ ℓ ∂ σ B 2 = ∑ i = 1 m ∂ ℓ ∂ x ^ i ∂ x ^ i ∂ σ B 2 = ∑ i = 1 m ∂ ℓ ∂ x ^ i ⋅ ( x i − μ B ) ⋅ − 1 2 ( σ B 2 + ϵ ) − 3 2 \frac{\partial \ell}{\partial \sigma _{\mathcal{B}}^{2}}=\sum_{i=1}^m{\frac{\partial \ell}{\partial \hat{x}_i}\frac{\partial \hat{x}_i}{\partial \sigma _{\mathcal{B}}^{2}}}=\sum_{i=1}^m{\frac{\partial \ell}{\partial \hat{x}_i}\cdot \left( x_i-\mu _{\mathcal{B}} \right)}\cdot \frac{-1}{2}\left( \sigma _{\mathcal{B}}^{2}+\epsilon \right) ^{-\frac{3}{2}} ∂σB2∂ℓ=i=1∑m∂x^i∂ℓ∂σB2∂x^i=i=1∑m∂x^i∂ℓ⋅(xi−μB)⋅2−1(σB2+ϵ)−23
参数 σ B 2 \sigma^2_\mathcal{B} σB2与参数 μ B \mu_\mathcal{B} μB相关,因此其梯度需要分为两部分:
∂ ℓ ∂ μ B = ∑ i = 1 m ∂ ℓ ∂ x ^ i ∂ x ^ i ∂ μ B + ∂ ℓ ∂ σ B 2 ∂ σ B 2 ∂ μ B = ∑ i = 1 m ∂ ℓ ∂ x ^ i ⋅ − 1 σ B 2 + ϵ + ∂ ℓ ∂ σ B 2 ⋅ ∑ i = 1 m − 2 ( x i − μ B ) m \begin{aligned} \frac{\partial \ell}{\partial \mu _{\mathcal{B}}} &= \sum_{i=1}^m{\frac{\partial \ell}{\partial \hat{x}_i}\frac{\partial \hat{x}_i}{\partial \mu _{\mathcal{B}}}}+\frac{\partial \ell}{\partial \sigma _{\mathcal{B}}^{2}}\frac{\partial \sigma _{\mathcal{B}}^{2}}{\partial \mu _{\mathcal{B}}} \\ &= \sum_{i=1}^m{\frac{\partial \ell}{\partial \hat{x}_i}\cdot \frac{-1}{\sqrt{\sigma _{\mathcal{B}}^{2}+\epsilon}}}+\frac{\partial \ell}{\partial \sigma _{\mathcal{B}}^{2}}\cdot \frac{\sum_{i=1}^m{-2\left( x_i-\mu _{\mathcal{B}} \right)}}{m} \end{aligned} ∂μB∂ℓ=i=1∑m∂x^i∂ℓ∂μB∂x^i+∂σB2∂ℓ∂μB∂σB2=i=1∑m∂x^i∂ℓ⋅σB2+ϵ−1+∂σB2∂ℓ⋅m∑i=1m−2(xi−μB)
下面求解 ∂ ℓ ∂ x i \frac{\partial \ell}{\partial x_i} ∂xi∂ℓ:
∂ ℓ ∂ x i = ∂ ℓ ∂ x ^ i ∂ x ^ i ∂ x i + ∂ ℓ ∂ x ^ i ∂ x ^ i ∂ μ B ∂ μ B ∂ x i + ∂ ℓ ∂ x ^ i ∂ x ^ i ∂ σ B 2 ∂ σ B 2 ∂ x i = ∂ ℓ ∂ x ^ i ∂ x ^ i ∂ x i + ∂ ℓ ∂ μ B ∂ μ B ∂ x i + ∂ ℓ ∂ σ B 2 ∂ σ B 2 ∂ x i = ∂ ℓ ∂ x ^ i 1 σ B 2 + ϵ + ∂ ℓ ∂ μ B 1 m + ∂ ℓ ∂ σ B 2 ∂ σ B 2 ∂ x i \begin{aligned} \frac{\partial \ell}{\partial x_i} &= \frac{\partial \ell}{\partial \hat{x}_i}\frac{\partial \hat{x}_i}{\partial x_i}+\frac{\partial \ell}{\partial \hat{x}_i}\frac{\partial \hat{x}_i}{\partial \mu _{\mathcal{B}}}\frac{\partial \mu _{\mathcal{B}}}{\partial x_i}+\frac{\partial \ell}{\partial \hat{x}_i}\frac{\partial \hat{x}_i}{\partial \sigma _{\mathcal{B}}^{2}}\frac{\partial \sigma _{\mathcal{B}}^{2}}{\partial x_i} \\ &= \frac{\partial \ell}{\partial \hat{x}_i}\frac{\partial \hat{x}_i}{\partial x_i}+\frac{\partial \ell}{\partial \mu _{\mathcal{B}}}\frac{\partial \mu _{\mathcal{B}}}{\partial x_i}+\frac{\partial \ell}{\partial \sigma _{\mathcal{B}}^{2}}\frac{\partial \sigma _{\mathcal{B}}^{2}}{\partial x_i} \\ &= \frac{\partial \ell}{\partial \hat{x}_i}\frac{1}{\sqrt{\sigma _{\mathcal{B}}^{2}+\epsilon}}+\frac{\partial \ell}{\partial \mu _{\mathcal{B}}}\frac{1}{m}+\frac{\partial \ell}{\partial \sigma _{\mathcal{B}}^{2}}\frac{\partial \sigma _{\mathcal{B}}^{2}}{\partial x_i} \end{aligned} ∂xi∂ℓ=∂x^i∂ℓ∂xi∂x^i+∂x^i∂ℓ∂μB∂x^i∂xi∂μB+∂x^i∂ℓ∂σB2∂x^i∂xi∂σB2=∂x^i∂ℓ∂xi∂x^i+∂μB∂ℓ∂xi∂μB+∂σB2∂ℓ∂xi∂σB2=∂x^i∂ℓσB2+ϵ1+∂μB∂ℓm1+∂σB2∂ℓ∂xi∂σB2
注意:
∂ σ B 2 ∂ x i = 2 m ( x i − μ B ) ( 1 − 1 m ) + 2 m ∑ k = 1 , k ≠ i m ( x k − μ B ) ( − 1 m ) = 2 m ( x i − μ B ) + 2 m ( − 1 m ) ∑ k = 1 m ( x k − μ B ) = 2 m ( x i − μ B ) + 2 m ( − 1 m ) ( ∑ k = 1 m x k − m μ B ) = 2 m ( x i − μ B ) \begin{aligned} \frac{\partial \sigma _{\mathcal{B}}^{2}}{\partial x_i} &= \frac{2}{m}\left( x_i-\mu _{\mathcal{B}} \right) \left( 1-\frac{1}{m} \right) +\frac{2}{m}\sum_{k=1,k\ne i}^m{\left( x_k-\mu _{\mathcal{B}} \right) \left( -\frac{1}{m} \right)} \\ &= \frac{2}{m}\left( x_i-\mu _{\mathcal{B}} \right) +\frac{2}{m}\left( -\frac{1}{m} \right) \sum_{k=1}^m{\left( x_k-\mu _{\mathcal{B}} \right)} \\ &= \frac{2}{m}\left( x_i-\mu _{\mathcal{B}} \right) +\frac{2}{m}\left( -\frac{1}{m} \right) \left( \sum_{k=1}^m{x_k}-m\mu _{\mathcal{B}} \right) \\ &= \frac{2}{m}\left( x_i-\mu _{\mathcal{B}} \right) \end{aligned} ∂xi∂σB2=m2(xi−μB)(1−m1)+m2k=1,k=i∑m(xk−μB)(−m1)=m2(xi−μB)+m2(−m1)k=1∑m(xk−μB)=m2(xi−μB)+m2(−m1)(k=1∑mxk−mμB)=m2(xi−μB)
所以:
∂ ℓ ∂ x i = ∂ ℓ ∂ x ^ i 1 σ B 2 + ϵ + ∂ ℓ ∂ μ B 1 m + ∂ ℓ ∂ σ B 2 2 m ( x i − μ B ) \frac{\partial \ell}{\partial x_i}=\frac{\partial \ell}{\partial \hat{x}_i}\frac{1}{\sqrt{\sigma _{\mathcal{B}}^{2}+\epsilon}}+\frac{\partial \ell}{\partial \mu _{\mathcal{B}}}\frac{1}{m}+\frac{\partial \ell}{\partial \sigma _{\mathcal{B}}^{2}}\frac{2}{m}\left( x_i-\mu _{\mathcal{B}} \right) ∂xi∂ℓ=∂x^i∂ℓσB2+ϵ1+∂μB∂ℓm1+∂σB2∂ℓm2(xi−μB)
再加上对 γ , β \gamma ,\beta γ,β的偏导:
∂ ℓ ∂ γ = ∑ i = 1 m ∂ ℓ ∂ y i ∂ y i ∂ γ = ∑ i = 1 m ∂ ℓ ∂ y i x ^ i ∂ ℓ ∂ β = ∑ i = 1 m ∂ ℓ ∂ y i ∂ y i ∂ β = ∑ i = 1 m ∂ ℓ ∂ y i \begin{aligned} \frac{\partial \ell}{\partial \gamma} &= \sum_{i=1}^m{\frac{\partial \ell}{\partial y_i}\frac{\partial y_i}{\partial \gamma}}=\sum_{i=1}^m{\frac{\partial \ell}{\partial y_i}\hat{x}_i} \\ \frac{\partial \ell}{\partial \beta} &= \sum_{i=1}^m{\frac{\partial \ell}{\partial y_i}\frac{\partial y_i}{\partial \beta}}=\sum_{i=1}^m{\frac{\partial \ell}{\partial y_i}} \end{aligned} ∂γ∂ℓ∂β∂ℓ=i=1∑m∂yi∂ℓ∂γ∂yi=i=1∑m∂yi∂ℓx^i=i=1∑m∂yi∂ℓ∂β∂yi=i=1∑m∂yi∂ℓ
在Batch Normalization的论文中,作者将内部协方差偏移(Internal Covariate Shift)问题定义为由于训练期间网络参数的变化而导致网络激活值分布发生变化的现象。在 LeCun,Wiesler & Ney 等人的论文中可以知道,如果对神经网络的输入进行白化(whitened,zero means and unit variances)及去相关操作,那么可以消除Internal Covariate Shift的不良影响,从而加快神经网络的训练。而Batch Normalization的操作(还有之前Dropout中对参数或输出进行缩放scale等操作)和上述类似,也能起到消除Internal Covariate Shift的作用。
训练深度网络时,神经网络隐层参数更新会导致网络输出数据的分布发生变化,而且随着层数增加,根据链式求导规则,这种偏移现象会被逐渐放大。这使得网络学习成为问题:因为网络的本质就是学习数据的分布,如果输出分布发生变化,网络不得不学习新的分布。为了保证网络参数训练的稳定性和收敛性,往往需要选择较小的学习速率,同时参数初始化的好坏也明显影响最终模型的精度。为了对模型进行训练,我们需要非常谨慎地去设定学习率、初始化权重、以及尽可能细致的参数更新策略。
联系:输出分布发生变化,网络通过参数更新对分布进行拟合,若学习率选择不恰当,会使得网络始终无法拟合出新的分布,或者过程较慢,这就是训练中学习率不合适使得损失曲线发生抖动的原因吧。
Google 将这一现象总结为 Internal Covariate Shift,简称 ICS。
简单来说,将每一层的输入作为一个分布看待,由于深层的参数随着训练更新,导致相同输入分布得到的输出分布发生变化。
而机器学习中有个很重要的假设:IID独立同分布假设,就是假设训练数据和测试数据是满足相同分布且相互独立,这是通过训练数据获得的模型能够在测试集获得好的效果的一个基本保障。
那么,细化到神经网络的每一层间,每轮训练时分布都是不一致,那么相对的训练效果就得不到保障,所以称为层间的协方差偏移(covariate shift)。
源空间:这里指的是已知的训练样本空间,即包括训练样本及其样本标记;
目标空间:指未知的测试样本空间,我们要预测的是其样本标记。
即当源域数据和目标域数据分布一致时,满足源域和目标域的特征空间和标记空间相同。
在统计机器学习中的一个经典假设是“源空间(source domain)和目标空间(target domain)的数据分布(distribution)是一致的”。如果不一致,那么就出现了新的机器学习问题,如迁移学习和域自适应(transfer learning, domain adaptation)等。而covariate shift就是分布不一致假设之下的一个分支问题,它是指源空间和目标空间的条件概率是一致的,但是其边缘概率不同,即:
对所有 x ∈ X x\in \mathcal{X} x∈X ,有: P s ( Y ∣ X = x ) = P t ( Y ∣ X = x ) P_s\left( Y|X=x \right) =P_t\left( Y|X=x \right) Ps(Y∣X=x)=Pt(Y∣X=x) 。
但是, P s ( X ) ≠ P t ( X ) P_s\left( X \right) \ne P_t\left( X \right) Ps(X)=Pt(X) 。
对于神经网络的各层输出,由于相同分布的样本经过了层内操作作用(输入分布经过多次线性、非线性变换),原本的样本分布显然与各层对应的输入信号分布不同,而且差异会随着网络深度增大而增大,可是样本对应的样本标签(label)仍然是不变的,这便符合了covariate shift的定义,即条件概率 P ( Y ∣ X ) P\left( Y|X \right) P(Y∣X)一致,边缘概率 P ( X ) P\left( X \right) P(X)不同。由于是对层间信号的分析,也即是“internal”的来由。
因此,每个神经元的输入数据不再是“独立同分布”,导致了以下问题:
前面说到,出现上述问题的根本原因是神经网络每层之间,无法满足基本假设“独立同分布”,那么思路应该是怎么使得相同输入对应的输出分布满足独立同分布。
白化(Whitening)
白化,是机器学习里面常用的一种规范化数据分布的方法,主要是PCA白化与ZCA白化。在PCA中介绍过。
PCA白化大致过程为:第一步操作是PCA操作,求出新特征空间中的新坐标,第二步是对新的坐标进行方差归一化操作。
ZCA白化即通过投影矩阵将PCA白化操作后的归一化坐标重新投影至原特征空间中。
白化是对输入数据分布进行变换,进而达到以下两个目的:
1、使得输入特征分布具有相同的均值与方差,其中PCA白化保证了所有特征分布均值为0,方差为1;而ZCA白化则保证了所有特征分布均值为0,方差相同;
2、去除特征之间的相关性。协方差矩阵中非对角元素为零。
通过白化操作,我们可以减缓ICS的问题,进而固定了每一层网络输入分布,加速网络训练过程的收敛。
因为白化操作比较复杂,而BN论文作者认为BN操作可以缓解内部协方差偏移ICS问题。我们知道BN得到的是均值和方差相同的分布,但是保值均值和方差一致的分布也不一定是同分布。所以ICS问题是BN论文的一种升华方式。BN操作实际上并不是直接去解决ICS问题,更多的是解决了梯度消失等问题,以此来加速网络收敛的。
参考:https://www.zhihu.com/question/38102762/answer/85238569
BN代码实现:
下面给出BN层的前向算法和反向传播的Python实现。
前面说过,论文中采用的是指数加权移动平均法(exponenentially weighted average),也叫滑动平均(Moving Average)模型,最后利用无偏估计量进行预测。在这里介绍另一种方案(相当于累计更新),利用一个动量参数 θ \theta θ得到一个动态均值 r μ B r\mu _{\mathcal{B}} rμB 与动态方差 r σ B 2 r\sigma _{\mathcal{B}}^{2} rσB2,这样更方便简洁,torch7采用的也是这种方法,具体公式如下(其中 B i \mathcal{B}_i Bi表示的是第 i i i个mini-batch):
r μ B i = θ r μ B i − 1 + ( 1 − θ ) μ B r\mu _{\mathcal{B}_i}=\theta r\mu _{\mathcal{B}_{i-1}}+\left( 1-\theta \right) \mu _{\mathcal{B}} rμBi=θrμBi−1+(1−θ)μB
r r σ B i 2 = θ r σ B i − 1 2 + ( 1 − θ ) σ B 2 rr\sigma _{\mathcal{B}_i}^{2}=\theta r\sigma _{\mathcal{B}_{i-1}}^{2}+\left( 1-\theta \right) \sigma _{\mathcal{B}}^{2} rrσBi2=θrσBi−12+(1−θ)σB2
BN层前向传播:
def batchnorm_forward(x, gamma, beta, bn_param):
"""
Forward pass for batch normalization.
Input:
- x: Data of shape (N, D)
- gamma: Scale parameter of shape (D,)
- beta: Shift paremeter of shape (D,)
- bn_param: Dictionary with the following keys:
- mode: 'train' or 'test'; required
- eps: Constant for numeric stability
- momentum: Constant for running mean / variance. ## 计算均值、方差时用到的常数
- running_mean: Array of shape (D,) giving running mean of features
- running_var Array of shape (D,) giving running variance of features
Returns a tuple of:
- out: of shape (N, D)
- cache: A tuple of values needed in the backward pass
"""
mode = bn_param['mode']
eps = bn_param.get('eps', 1e-5)
momentum = bn_param.get('momentum', 0.9)
N, D = x.shape
running_mean = bn_param.get('running_mean', np.zeros(D, dtype=x.dtype))
running_var = bn_param.get('running_var', np.zeros(D, dtype=x.dtype))
out, cache = None, None
if mode == 'train':
sample_mean = np.mean(x, axis=0)
sample_var = np.var(x, axis=0)
out_ = (x - sample_mean) / np.sqrt(sample_var + eps)
# 更新均值和方差
running_mean = momentum * running_mean + (1 - momentum) * sample_mean
running_var = momentum * running_var + (1 - momentum) * sample_var
# BN层的输出
out = gamma * out_ + beta
cache = (out_, x, sample_var, sample_mean, eps, gamma, beta)
elif mode == 'test':
# 可以看出测试阶段,使用的是running_mean参数提供的均值
scale = gamma / np.sqrt(running_var + eps)
out = x * scale + (beta - running_mean * scale)
else:
raise ValueError('Invalid forward batchnorm mode "%s"' % mode)
# Store the updated running means back into bn_param
bn_param['running_mean'] = running_mean
bn_param['running_var'] = running_var
return out, cache
BN层的反向传播:
def batchnorm_backward(dout, cache):
"""
Backward pass for batch normalization.
Inputs:
- dout: Upstream derivatives, of shape (N, D)
- cache: Variable of intermediates from batchnorm_forward.
Returns a tuple of:
- dx: Gradient with respect to inputs x, of shape (N, D)
- dgamma: Gradient with respect to scale parameter gamma, of shape (D,)
- dbeta: Gradient with respect to shift parameter beta, of shape (D,)
"""
# 初始化偏导
dx, dgamma, dbeta = None, None, None
# 获得所需参数
out_, x, sample_var, sample_mean, eps, gamma, beta = cache
N = x.shape[0]
dout_ = gamma * dout # dl/d(hat(x))=dl/dy*gamma
dvar = np.sum(dout_ * (x - sample_mean) * -0.5 * (sample_var + eps) ** -1.5, axis=0)
dx_ = 1 / np.sqrt(sample_var + eps) # d(hat(x))/dx
dvar_ = 2 * (x - sample_mean) / N # dvar/dx
# intermediate for convenient calculation
di = dout_ * dx_ + dvar * dvar_ # dl/dx的前两项:dl/d(hat(x))* d(hat(x))/dx + dl/dvar* dvar/dx
dmean = -1 * np.sum(di, axis=0) # dl/dmean
dmean_ = np.ones_like(x) / N # dmean/dx
dx = di + dmean * dmean_ # dl/dx
dgamma = np.sum(dout * out_, axis=0) # dl/dgamma
dbeta = np.sum(dout, axis=0) # dl/dbeta
return dx, dgamma, dbeta
参考:https://zhuanlan.zhihu.com/p/26138673
Batch Normalizaiton存在以下缺点:
假设第 i i i个数据为 a i a^i ai其通道数为 L L L,对该数据进行层归一化时均值和标准差为:
μ i = 1 L ∑ l = 1 L a i l , σ i = 1 L ∑ l = 1 L ( a i l − μ l ) 2 \mu _i=\frac{1}{L}\sum_{l=1}^L{a_{i}^{l}}, \sigma _i=\sqrt{\frac{1}{L}\sum_{l=1}^L{\left( a_{i}^{l}-\mu ^l \right) ^2}} μi=L1l=1∑Lail,σi=L1l=1∑L(ail−μl)2
BN与Layer Normalization的区别在于:
所以,LN不依赖于Batch的大小和输入Sequence的深度(也就是通道数),因此可以用于batch size为1和输入长度变化sequence的正则化操作。LN用在RNN上效果比较明显。
BN注重对每个batch进行归一化,保证每个Batch内数据分布一致(均值和方差相同),因此模型的训练结果取决于每个Batch内数据的整体分布。
但是在图像风格化迁移问题中,生成结果主要依赖于某个图像实例,所以对整个batch进行归一化,不适合该问题,因而对单个实例进行归一化。可以加速模型收敛,并且保持每个图像实例之间的独立性。(以图像为例,便是对每个图像的像素进行归一化)
对当前batch内的第 t t t个样本的第 i i i个通道上的图像进行归一化,如下:
μ t i = 1 H W ∑ l = 1 W ∑ m = 1 H x t i l m , σ t i 2 = 1 H W ∑ l = 1 W ∑ m = 1 H ( x t i l m − μ t i ) 2 , y t i j k = x t i j k − μ t i σ t i 2 + ϵ \mu _{ti}=\frac{1}{HW}\sum_{l=1}^W{\sum_{m=1}^H{x_{tilm}}},\ \sigma _{ti}^{2}=\frac{1}{HW}\sum_{l=1}^W{\sum_{m=1}^H{\left( x_{tilm}-\mu _{ti} \right) ^2}}, \\ y_{tijk}=\frac{x_{tijk}-\mu _{ti}}{\sqrt{\sigma _{ti}^{2}+\epsilon}} μti=HW1l=1∑Wm=1∑Hxtilm, σti2=HW1l=1∑Wm=1∑H(xtilm−μti)2,ytijk=σti2+ϵxtijk−μti
Instance Normalization对于一些图片生成类的任务比如图片风格转换来说效果是明显优于BN的,但在很多其它图像类任务比如分类等场景效果不如BN。
BN的计算受其他样本影响的,由于每个batch的均值和标准差不稳定,对于单个数据而言,相对于是引入了噪声,但在分类这种问题上,结果和数据的整体分布有关系,因此需要通过BN获得数据的整体分布。而instance normalization的信息都是来自于自身的图片,相当于对全局信息做了一次整合和调整,在图像转换这种问题上,BN获得的整体信息不会带来任何收益,带来的噪声反而会弱化实例之间的独立性:这类生成式方法,每张图片自己的风格比较独立不应该与batch中其他的样本产生太大联系。
同样是解决BN对小batch效果较差的问题,Group Normalization将通道分组,然后对每个组内进行归一化,因此也与Batch大小无关。GN与Layer Norm类似,不过Group Norm将图像的特征通道分组,对每一组进行LN(即对单个样本的部分通道上的特征进行归一化。)
将样本的通道数进行分组,具体为: S i = { k ∣ k N = i N , ⌊ k C C G ⌋ = ⌊ i C C G ⌋ } S_i=\left\{ k|k_N=iN,\lfloor{\frac{k_C}{\frac{C}{G}}} \rfloor =\lfloor \frac{i_C}{\frac{C}{G}} \rfloor \right\} Si={k∣kN=iN,⌊GCkC⌋=⌊GCiC⌋},其中 ⌊ k C C G ⌋ = ⌊ i C C G ⌋ \lfloor \frac{k_C}{\frac{C}{G}} \rfloor =\lfloor \frac{i_C}{\frac{C}{G}} \rfloor ⌊GCkC⌋=⌊GCiC⌋是指:通道 k C , i C k_C, i_C kC,iC被分为同一组中; { k ∣ k N = i N } \{ k|k_N=i_N \} {k∣kN=iN}是指按照Batch的单个样本进行组归一化操作。
tf.nn.moments(x,[2, 3, 4], keep_dims=True):按维度[2,3,4]求解均值和方差,并保持维度不变。这里的输入样本矩阵为 [ N,C,H,W ] \left[ \text{N,C,H,W} \right] [N,C,H,W],首先进行变形 [ N , C , C// G , H,W ] \left[N,C,\text{C//}G,\text{H,W} \right] [N,C,C//G,H,W],然后按照维度 [ : , C// G , H,W ] \left[:,\text{C//}G,\text{H,W} \right] [:,C//G,H,W]求均值和方差。当Batch size不为0时,需要对batch内的每个样本按通道分组归一化。
论文作者认为:
因此作者提出自适应归一化方法——Switchable Normalization(SN)来解决上述问题。与强化学习不同,SN可以通过可微分学习,为深度网络中的每一个归一化层确定合适的归一化操作。
SN有一个直观表达:
通过网络学习各类归一化方法的权重 ,来结合BN,Layer Norm,Instance Norm等归一化操作。
h ^ n c i j = γ h n c i j − ∑ k ∈ Ω w k μ k ∑ k ∈ Ω w k ′ σ k 2 + ϵ + β w k = e λ k ∑ z ∈ { in, ln ,bn } e λ z , k ∈ { in, ln ,bn } \begin{aligned} \hat{h}_{ncij} &= \gamma \frac{h_{ncij}-\sum_{k\in \Omega}{w_k\mu _k}}{\sqrt{\sum_{k\in \Omega}{w_{k}^{'}\sigma _{k}^{2}}+\epsilon}}+\beta \\ w_k &=\frac{e^{\lambda _k}}{\sum_{z\in \left\{ \text{in,}\ln\text{,bn} \right\}}{e^{\lambda _z}}},\ k\in \left\{ \text{in,}\ln\text{,bn} \right\} \end{aligned} h^ncijwk=γ∑k∈Ωwk′σk2+ϵhncij−∑k∈Ωwkμk+β=∑z∈{in,ln,bn}eλzeλk, k∈{in,ln,bn}
μ in = 1 H W ∑ i , j H , W h n c i j , σ in 2 = 1 H W ∑ i , j H , W ( h n c i j − μ in ) 2 μ ln = 1 C ∑ c = 1 C μ in , σ ln 2 = 1 C ∑ c = 1 C ( σ in 2 + μ in 2 ) − μ ln 2 μ bn = 1 N ∑ n = 1 N μ in , σ bn 2 = 1 N ∑ n = 1 N ( σ in 2 + μ in 2 ) − μ bn 2 \mu _{\text{in}}=\frac{1}{HW}\sum_{i,j}^{H,W}{h_{ncij}},\,\,\sigma _{\text{in}}^{2}=\frac{1}{HW}\sum_{i,j}^{H,W}{\left( h_{ncij}-\mu _{\text{in}} \right) ^2} \\ \mu _{\ln}=\frac{1}{C}\sum_{c=1}^C{\mu _{\text{in}}},\,\,\sigma _{\ln}^{2}=\frac{1}{C}\sum_{c=1}^C{\left( \sigma _{\text{in}}^{2}+\mu _{\text{in}}^{2} \right) -\mu _{\ln}^{2}} \\ \mu _{\text{bn}}=\frac{1}{N}\sum_{n=1}^N{\mu _{\text{in}}},\ \sigma _{\text{bn}}^{2}=\frac{1}{N}\sum_{n=1}^N{\left( \sigma _{\text{in}}^{2}+\mu _{\text{in}}^{2} \right) -\mu _{\text{bn}}^{2}} μin=HW1i,j∑H,Whncij,σin2=HW1i,j∑H,W(hncij−μin)2μln=C1c=1∑Cμin,σln2=C1c=1∑C(σin2+μin2)−μln2μbn=N1n=1∑Nμin, σbn2=N1n=1∑N(σin2+μin2)−μbn2
其中, h n c i j h_{ncij} hncij表示当前样本(以图像为例)中,第 c c c个通道上位置 ( i , j ) (i,j) (i,j)处的像素值。因为归一化后的值 h ^ n c i j \hat{h}_{ncij} h^ncij
原文中作者进行的实验结果分析:
从图(a)可以看出,采用SN策略的不同任务,模型学习出的4种正则化策略的比重也不同。比如在图像风格迁移中由于单个样本实例影响较大,因此主要起作用的是IN;而LSTM中权重最大的则是LN。
从图(b)可以看出当batch size较小时(即图中所说每个GPU的样本数),当 N N N减小到一定程度时准确度急剧下降,当 N N N增大时准确度又能提升,而Instance Norm(Ideal BN)与 N N N无关,GN与 N N N也无关,这里结果出现波动可能是因为其他因素。而融合的SN也会受到batch size的影响。
优缺点: 简单总结下,SN方法通过训练得到针对于特定任务的归一化方法组合{BN, IN, LN},使网络不会受到某个方法缺陷所制约(如Batch太小时,BN效果不好,但是SN训练得到的BN权重较小,减缓了该问题)。此外,无须针对网络的特定层数来设计归一化方法。但是,缺陷之处在于增加了网络训练的难度。
来源:https://github.com/switchablenorms/Switchable-Normalization
class SwitchNorm2d(nn.Module):
def __init__(self, num_features, eps=1e-5, momentum=0.9, using_moving_average=True, using_bn=True,
last_gamma=False):
super(SwitchNorm2d, self).__init__()
self.eps = eps
self.momentum = momentum
self.using_moving_average = using_moving_average
self.using_bn = using_bn
self.last_gamma = last_gamma
self.weight = nn.Parameter(torch.ones(1, num_features, 1, 1))
self.bias = nn.Parameter(torch.zeros(1, num_features, 1, 1))
if self.using_bn:
self.mean_weight = nn.Parameter(torch.ones(3))
self.var_weight = nn.Parameter(torch.ones(3))
else:
self.mean_weight = nn.Parameter(torch.ones(2))
self.var_weight = nn.Parameter(torch.ones(2))
if self.using_bn:
self.register_buffer('running_mean', torch.zeros(1, num_features, 1))
self.register_buffer('running_var', torch.zeros(1, num_features, 1))
self.reset_parameters()
def reset_parameters(self):
if self.using_bn:
self.running_mean.zero_()
self.running_var.zero_()
if self.last_gamma:
self.weight.data.fill_(0)
else:
self.weight.data.fill_(1)
self.bias.data.zero_()
def _check_input_dim(self, input):
if input.dim() != 4:
raise ValueError('expected 4D input (got {}D input)'
.format(input.dim()))
def forward(self, x):
self._check_input_dim(x)
N, C, H, W = x.size()
x = x.view(N, C, -1) # [N, C, W*H]
mean_in = x.mean(-1, keepdim=True) # 根据W*H这一维度计算均值(IN)
var_in = x.var(-1, keepdim=True)
mean_ln = mean_in.mean(1, keepdim=True) # LN相对于IN需要对求出所有通道上的均值
temp = var_in + mean_in ** 2
var_ln = temp.mean(1, keepdim=True) - mean_ln ** 2
if self.using_bn:
if self.training:
mean_bn = mean_in.mean(0, keepdim=True) # 按照N求均值
var_bn = temp.mean(0, keepdim=True) - mean_bn ** 2
if self.using_moving_average:
self.running_mean.mul_(self.momentum)
self.running_mean.add_((1 - self.momentum) * mean_bn.data)
self.running_var.mul_(self.momentum)
self.running_var.add_((1 - self.momentum) * var_bn.data)
else:
self.running_mean.add_(mean_bn.data)
self.running_var.add_(mean_bn.data ** 2 + var_bn.data)
else:
mean_bn = torch.autograd.Variable(self.running_mean)
var_bn = torch.autograd.Variable(self.running_var)
softmax = nn.Softmax(dim=0) # 按列进行归一化
mean_weight = softmax(self.mean_weight) # 权重归一化
var_weight = softmax(self.var_weight)
if self.using_bn:
mean = mean_weight[0] * mean_in + mean_weight[1] * mean_ln + mean_weight[2] * mean_bn
var = var_weight[0] * var_in + var_weight[1] * var_ln + var_weight[2] * var_bn
else:
mean = mean_weight[0] * mean_in + mean_weight[1] * mean_ln
var = var_weight[0] * var_in + var_weight[1] * var_ln
x = (x-mean) / (var+self.eps).sqrt()
x = x.view(N, C, H, W)
return x * self.weight + self.bias