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即可。

  • 相关阅读:
    MongoDB 释放磁盘空间 db.runCommand({repairDatabase: 1 })
    RK 调试笔记
    RK Android7.1 拨号
    RK Android7.1 移植gt9271 TP偏移
    RK Android7.1 定制化 itvbox 盒子Launcher
    RK Android7.1 双屏显示旋转方向
    RK Android7.1 设置 内存条作假
    RK Android7.1 设置 蓝牙 已断开连接
    RK Android7.1 进入Camera2 亮度会增加
    RK 3128 调触摸屏 TP GT9XX
  • 原文地址:https://www.cnblogs.com/qizhou/p/13504172.html
Copyright © 2011-2022 走看看