欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Batch Normalization

程序员文章站 2022-07-16 16:26:29
...

Batch Normalization原理:

概念的引入:

Internal Covariate Shift :

       其主要描述的是:训练深度网络的时候经常发生训练困难的问题,因为,每一次参数迭代更新后,上一层网络的输出数据经过这一层网络计算后,数据的分布会发生变化,为下一层网络的学习带来困难(神经网络本来就是要学习数据的分布,要是分布一直在变,学习就很难了),此现象称之为InternalInternal Covariate Shift。

 

Covariate Shift:

         Internal Covariate Shift 和Covariate Shift具有相似性,但并不是一个东西,前者发生在神经网络的内部,所以是InternalInternal,后者发生在输入数据上。Covariate Shift主要描述的是由于训练数据和测试数据存在分布的差异性,给网络的泛化性和训练速度带来了影响,我们经常使用的方法是做归一化或者白化。

IID独立同分布假设:

        机器学习领域有个很重要的假设:IID独立同分布假设,就是假设训练数据和测试数据是满足相同分布的,这是通过训练数据获得的模型能够在测试集获得好的效果的一个基本保障。

Batch Normalization:

        BatchNorm就是在深度神经网络训练过程中使得每一层神经网络的输入保持相同分布的。

原理:

        Internal Covariate Shift除了增加模型学习的难度之外,还会导致梯度消失的问题。深层神经网络在做非线性变换前的**输入值(就是那个x=WU+B,U是输入)随着网络深度加深或者在训练过程中,其分布逐渐发生偏移或者变动,之所以训练收敛慢,一般是整体分布逐渐往非线性函数的取值区间的上下限两端靠近(对于Sigmoid函数来说,意味着**输入值WU+B是大的负值或正值),所以这导致反向传播时低层神经网络的梯度消失,这是训练深层神经网络收敛越来越慢的本质原因。

        就是说Internal Covariate Shift 主要导致两个问题:1.中间层输入分布总是变化,增加了模型拟合的难度。2.中间层输入分布会使输出逐渐靠近**函数梯度较小的地方,导致梯度消失。

        所以解决Internal Covariate Shift 就可以提升模型的性能,BatchNorm就是通过一定的规范化手段,把每层神经网络任意神经元这个输入值的分布强行拉回到正态分布。这样使得**输入值落在非线性函数对输入比较敏感的区域,这样输入的小变化就会导致损失函数较大的变化,意思是这样让梯度变大,避免梯度消失问题产生,而且梯度变大意味着学习收敛速度快,能大大加快训练速度。

       对于每个隐层神经元,把逐渐向非线性函数映射后向取值区间极限饱和区靠拢的输入分布强制拉回到比较标准的正态分布,使得非线性变换函数的输入值落入对输入比较敏感的区域,以此避免梯度消失问题。因为梯度一直都能保持比较大的状态,所以很明显对神经网络的参数调整效率比较高,就是变动大,就是说向损失函数最优值迈动的步子大,也就是说收敛地快。

实现:

        为了减小Internal Covariate Shift,对神经网络的每一层做归一化不就可以了,假设将每一层输出后的数据都归一化到0均值,1方差,满足正态分布。但是,此时有一个问题,每一层的数据分布都是标准正太分布,导致其完全学习不到输入数据的特征,因为,费劲心思学习到的特征分布被归一化了,因此,直接对每一层做归一化显然是不合理的。
       但是如果稍作修改,加入可训练的参数做归一化,那就是Batch Norm实现的。

       Batch Normalization

           Batch Normalization

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

应用:

             根据前面的理论,Batch Norm主要是为了输入在**函数的敏感区。所以BatchNorm层要加在**函数前面。     

                 Batch Normalization

Batch Normalization

def __init__(self, num_classes, pretrained=False,
              bn_after_act=False, bn_before_act=False):
    super(Vgg19, self).__init__()
 
    self.pretrained = pretrained
    self.bn_before_act = bn_before_act
    self.bn_after_act = bn_after_act
 
    model = models.vgg19(pretrained = pretrained)
    self.features = model.features
 
 
    self.fc17 = nn.Linear(512 * 7 * 7, 4096)
    self.bn17 = nn.BatchNorm1d(4096)
    self.fc18 = nn.Linear(4096, 4096)
    self.bn18 = nn.BatchNorm1d(4096)
    self.fc19 = nn.Linear(4096, num_classes)
 
    self._initialize_weights()

nn.BatchNorm1d(dim),dim等于前一层输出的维度。BatchNorm层输出的维度也是dim。

优点:

①不仅仅极大提升了训练速度,收敛过程大大加快;

②还能增加分类效果,一种解释是这是类似于Dropout的一种防止过拟合的正则化表达方式,所以不用Dropout也能达到相当的效果;

③另外调参过程也简单多了,对于初始化要求没那么高,而且可以使用大的学习率等。