Batch Normalization
为什么需要 Normalization
独立同分布与白化
独立同分布并非所有机器学习模型的必然要求(比如 Naive Bayes 模型就建立在特征彼此独立的基础之上,而Logistic Regression 和神经网络则在非独立的特征数据上依然可以训练出很好的模型),但独立同分布的数据可以简化常规机器学习模型的训练、提升机器学习模型的预测能力,已经是一个共识。
因此,在把数据喂给机器学习模型之前,“白化(whitening)”是一个重要的数据预处理步骤。白化一般包含两个目的:
- 去除特征之间的相关性 —> 独立
- 使得所有特征具有相同的均值和方差—> 同分布
白化最典型的方法就是PCA。
深度学习中的 Internal Covariate Shift
深度神经网络模型的训练为什么会很困难?其中一个重要的原因是,深度神经网络涉及到很多层的叠加,而每一层的参数更新会导致下一层的输入数据分布发生变化,通过层层叠加,深层的输入分布变化会非常剧烈,这就使得深层需要不断去重新适应前面层的参数更新,此现象称之为 Internal Covariate Shift ,简称ICS。
ICS 会导致什么问题?
简而言之,每个神经元的输入数据不再是“独立同分布”。
其一,上层参数需要不断适应新的输入数据分布,降低学习速度。
其二,下层输入的变化可能趋向于变大或者变小,导致上层落入饱和区,使得学习过早停止。
其三,每层的更新都会影响到其它层,因此每层的参数更新策略需要尽可能的谨慎。
为了训好模型,我们需要非常谨慎地去设定学习率、初始化权重、以及尽可能细致的参数更新策略。
什么时候使用BN
在神经网络训练时遇到收敛速度很慢,或梯度爆炸等无法训练的状况时可以尝试BN来解决。
另外,在一般使用情况下也可以加入BN来加快训练速度,提高模型精度。
Batch Normalization原理
假设将每一层输出后的数据都归一化到0均值,1方差,满足正太分布,但是,此时有一个问题,每一层的数据分布都是标准正太分布,导致其完全学习不到输入数据的特征,因此,直接对每一层做归一化显然是不合理的。
顾名思义,Batch Normalization嘛,就是“批规范化”,即在每次SGD时,通过mini-batch来对相应的activation做规范化操作,使得结果(输出信号各个维度)的均值为0,方差为1。而最后的“scale and shift”操作则是为了让因训练所需而“刻意”加入的BN能够有可能还原最初的输入(即当 \(\gamma^{(k)}=\sqrt{\operatorname{Var}\left[x^{(k)}\right]}\),\(\beta^{(k)}=E\left[x^{(k)}\right]\)),从而保证整个network的capacity。(有关capacity的解释:实际上BN可以看作是在原模型上加入的“新操作”,这个新操作很大可能会改变某层原来的输入。当然也可能不改变,不改变的时候就是“还原原来输入”。如此一来,既可以改变同时也可以保持原输入,那么模型的容纳能力(capacity)就提升了。)

之所以称之为batch norm是因为所 norm 的数据是一个 batch 的,假设输入数据是 \(\beta=x_{1 \dots m}\) 共m个数据,输出是 \(y_{i}=B N(x)\),batch norm的步骤如下:
- 先求出此次批量数据\(x\)的均值,\(\mu_{\beta}=\frac{1}{m} \sum_{i=1}^{m} x_{i}\)
- 求出此次batch的方差,\(\sigma_{\beta}^{2}=\frac{1}{m} \sum_{i=1} m\left(x_{i}-\mu_{\beta}\right)^{2}\)
- 接下来就是对\(x\)做归一化,得到 \(x_{i}^{-}\)
- 最重要的一步,引入缩放和平移变量 \(\gamma\) 和 \(\beta\) ,计算归一化后的值:\(y_{i}=\gamma x_{i}^{-}+\beta\)
如果直接做归一化不做其他处理,神经网络是学不到任何东西的,但是加入这两个参数后,事情就不一样了,先考虑特殊情况下,如果 \(\gamma\) 和 \(\beta\) 分别等于此batch的标准差和均值,那么\(y_{i}\)就还原到归一化前的\(x\),也即是缩放平移到了归一化前的分布,相当于batch norm没有起作用。加入 \(\gamma\) 和 \(\beta\) 参数,样就保证了每一次数据经过归一化后还保留的有学习来的特征,同时又能完成归一化这个操作,加速训练。
Batch Normalization 的优点
- 没有它之前,需要小心的调整学习率和权重初始化,但是有了BN可以放心的使用大学习率,但是使用了BN,就不用小心的调参了,较大的学习率极大的提高了学习速度,
- Batchnorm本身上也是一种正则的方式,可以代替其他正则方式如dropout等,因为在mini-batch上计算的均值和方差,而不是在整个数据集上,均值和方差有一些小的噪声,所以和dropout相似,它往每个隐藏层的激活值上增加了噪音。
- Batch归一化减少了输入值改变的问题,它的确使这些值变得更稳定,神经网络的之后层就会有更坚实的基础。即使使输入分布改变了一些,它会改变得更少。它做的是当前层保持学习,当改变时,迫使后层适应的程度减小了,你可以这样想,它减弱了前层参数的作用与后层参数的作用之间的联系,它使得网络每层都可以自己学习,稍稍独立于其它层,这有助于加速整个网络的学习。
代码示例
训练时:
def Batchnorm_simple_for_train(x, gamma, beta, bn_param):
"""
param:x : 输入数据,设shape(B,L)
param:gama : 缩放因子 γ
param:beta : 平移因子 β
param:bn_param : batchnorm所需要的一些参数
eps : 接近0的数,防止分母出现0
momentum : 动量参数,一般为0.9, 0.99, 0.999
running_mean :滑动平均的方式计算新的均值,训练时计算,为测试数据做准备
running_var : 滑动平均的方式计算新的方差,训练时计算,为测试数据做准备
"""
running_mean = bn_param['running_mean'] #shape = [B]
running_var = bn_param['running_var'] #shape = [B]
results = 0. # 建立一个新的变量
x_mean=x.mean(axis=0) # 计算x的均值
x_var=x.var(axis=0) # 计算方差
x_normalized=(x-x_mean)/np.sqrt(x_var+eps) # 归一化
results = gamma * x_normalized + beta # 缩放平移
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
但是在测试的时候可不是这样,测试的时候每次只输入一张图片,这怎么计算批量的均值和方差,于是,就有了代码中下面两行,在训练的时候实现计算好 mean var 测试的时候直接拿来用就可以了,不用计算均值和方差。
running_mean = momentum * running_mean + (1 - momentum) * x_mean
running_var = momentum * running_var + (1 - momentum) * x_var
所以,测试的时候是这样的:
def Batchnorm_simple_for_test(x, gamma, beta, bn_param):
"""
param:x : 输入数据,设shape(B,L)
param:gama : 缩放因子 γ
param:beta : 平移因子 β
param:bn_param : batchnorm所需要的一些参数
eps : 接近0的数,防止分母出现0
momentum : 动量参数,一般为0.9, 0.99, 0.999
running_mean :滑动平均的方式计算新的均值,训练时计算,为测试数据做准备
running_var : 滑动平均的方式计算新的方差,训练时计算,为测试数据做准备
"""
running_mean = bn_param['running_mean'] #shape = [B]
running_var = bn_param['running_var'] #shape = [B]
results = 0. # 建立一个新的变量
x_normalized=(x-running_mean )/np.sqrt(running_var +eps) # 归一化
results = gamma * x_normalized + beta # 缩放平移
return results , bn_param

浙公网安备 33010602011771号