zoukankan      html  css  js  c++  java
  • 批量归一化(BN, Batch Normalization)

      现在的神经网络通常都特别深,在输出层向输入层传播导数的过程中,梯度很容易被激活函数或是权重以指数级的规模缩小或放大,从而产生“梯度消失”或“梯度爆炸”的现象,造成训练速度下降和效果不理想。

      如何避免或者减轻这一现象的发生呢?归一化就是方法的一种。归一化将网络中层与层之间传递的数据限制在一定范围内,从而避免了梯度消失和爆炸的发生。下面介绍一种最基本的归一化:批量归一化(BN, Batch Normalization)。另外还有层归一化(LN, Layer Normalization)和权重归一化(WN, Weight Normalization),和BN大同小异。

    批量归一化

      批量归一化层的是这样定义的,当使用批量梯度下降(或小批量)时,对前一层的输出在批量的维度上进行归一化,即

    egin{align} &hat{X}_i^t=frac{X_i^{t-1}-E(X^{t-1})}{sqrt{D(X^{t-1})+varepsilon}} \ ext{where};; &E(X^{t-1}) = frac{1}{n}sumlimits_{i=1}^nX_i^{t-1} otag\ &D(X^{t-1}) = frac{1}{n-1}sumlimits_{i=1}^nleft[X_i^{t-1}-E(X^{t-1}) ight]^2 otag end{align}

      其中$n$是输入批量,$X_i^{t-1}$是前一层输出批量中的第$i$个,$varepsilon$是为避免0除而设置的较小数。以上都是按元素进行的操作。这样做的显式优点在于,大部分的输出都被映射到了-1和1之间,而诸如sigmoid激活函数,在这个区间内的梯度是最大的,从而避免因激活函数值的饱和而产生的梯度消失。并且由于层输出的归一化约束,反向传播的累积不会特别显著,梯度爆炸也得以避免。

      但是,如果仅仅进行以上操作,网络的拟合能力就会下降。这是因为,神经网络强大的拟合能力在于激活函数的非线性。经过以上操作,激活函数的输入通常都集中在-1和1之间,而sigmoid函数在这区间内的导数变化率是比较低的,或者说是比较线性的。为了防止这一点,BN在这基础上再加一个“反向”操作,将权重输出再乘上自学习的标准差和均值,映射到激活函数曲率(或者说二阶导数绝对值、导数变化率)相对更大的位置,在获得较大导数的同时,保留激活非线性。公式如下:

    $ egin{aligned} &X_i^t= gamma^that{X}_i^t+eta^t\ end{aligned} $

      与$(1)$式联合得到:

    $ egin{aligned} &X_i^t= frac{gamma^t}{sqrt{D(X^{t-1})+varepsilon}}X_i^{t-1} + left(eta^t-frac{E(X^{t-1})gamma^t}{sqrt{D(X^{t-1})+varepsilon}} ight)  \ end{aligned} $

      其中$gamma,eta$都是模型中用反向传播学习的参数。这样一来,BN层可以自己“决定”将输出映射到合适位置。

      另外,在训练结束进行推理时,我们输入模型的通常都是单个样本,毕竟一个样本是不能求样本方差的。所以BN使用滑动平均(moving average)来保存所有输入的均值和方差,以用于对单一输入的归一化。

    Keras中BN的使用

      Keras中已经实现了BN层可以直接使用,而不用我们自己重新写这个轮子。使用方式如下:

    x = keras.layers.BatchNormalization(axis=-1,#对输入的哪个轴执行BN
                                        momentum=0.99,#滑动平均和方差的动量
                                        epsilon=0.001,#防止0除的较小值
                                        center=True,#是否使用beta调整归一化后的输出均值
                                        scale=True,#是否使用gamma调整归一化后的输出方差
                                        trainable=True)(x) 

      其中要注意axis,归一化操作是针对axis维度指定的向量进行的。比如当BN层的前一层是二维卷积层,输出的第一维是批量,然后是图像宽高,最后一维是通道。假如BN层axis=-1,均值就是整个批量的所有像素对应的通道向量的平均,方差的计算也是以这个维度进行。也可以传入列表,假如axis=[1,2,3],那么进行的规范化就是原理中所介绍的那样,均值的规模就是CxHxW。对于下面的代码:

    from keras import layers,Model,Input 
    
    Input_img = Input(shape = [320,320,3])  
    x = layers.BatchNormalization(axis=-1,
                                  momentum=0.99,
                                  epsilon=0.001,
                                  center=True,
                                  scale=True)(Input_img)  
    model = Model(Input_img,x)
    model.summary()

      summary()输出可训练参数和不可训练参数各6个。可训练参数就是$gamma,eta$,不可训练参数是滑动平均所保存的均值和方差。另外,如果将BN层的traninable标记设置为False,那么$gamma,eta$就会被固定,不会被训练;而如果设置为True,则只有$gamma,eta$会被训练,另外6个不可训练参数依然是不可训练状态,因为它们是通过滑动平均而不是反向传播来更新的。

    Pytorch中的BN

      在Pytorch中使用BN的代码如下(和上面一样,也是2维BN):

    from torch import nn
    
    bn = nn.BatchNorm2d(num_features = 6,# 特征数
                        eps = 0.00001,   # 防止0除
                        momentum = 0.1,  # 滑动平均与方差的动量(与Keras中相反),如果设为None,momentum = 1/i,i是输入的次数
                        affine = True,   # 是否使用gamma与beta再次映射,也就是会多出几个训练参数
                        track_running_stats = True)# 是否累计每次计算的均值与方差
    for i in bn.state_dict():
      print(i,bn.state_dict()[i])

      与Keras中类似,但这里直接默认对特征维度进行规范化,特征维度在pytorch中是倒数第3维。需要注意的是,track_running_stats,也就是累计每次输入计算的均值与方差,只要你传入一个输入,累计的均值与方差就会被修改。因此,如果你在推理时不想修改这两个累计值,就要将track_running_stats设置为False。而在训练时,只需再修改回True即可。

  • 相关阅读:
    1. 命令执行漏洞简介
    3. 从零开始学CSRF
    2. DVWA亲测CSRF漏洞
    使用pt-fifo-split 工具往mysql插入海量数据
    如何打印矩阵
    年轻人,你活着不是为了观察K线做布朗运动
    Python 之匿名函数和偏函数
    Python之闭包
    Python之装饰器
    Python之with语句
  • 原文地址:https://www.cnblogs.com/qizhou/p/13504172.html
Copyright © 2011-2022 走看看