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
posted @ 2019-04-21 15:36  youngliu91  阅读(212)  评论(0)    收藏  举报