深度学习之批归一化--BN详解

论文《Batch Normalization: Accelerating Deep Network Training by Reducing Internal Covariate Shift

1、为什么需要BN?

深层神经网络中,中间某一层的输入是其之前的神经层的输出。因此,其之前的神经层的参数变化会导致其输入的分布发生较大的差异。利用随机梯度下降更新参数时,每次参数更新都会导致网络中间每一层的输入的分布发生改变。越深的层,其输入分布会改变的越明显。

内部协变量偏移(Internal Covariate Shift):每一层的参数在更新过程中,会改变下一层输入的分布,神经网络层数越多,表现得越明显,(就比如高层大厦底部发生了微小偏移,楼层越高,偏移越严重。)

为了解决内部协变量偏移问题,就要使得每一个神经层的输入的分布在训练过程要保持一致。

为 了降低分布变化的影响,可使用归一化策略Normalization,把数据分布映射到一个确定的区间。

2、BN的原理

2.1 数学原理如下:

y = x − m e a n ( x ) V a r ( x ) + e p s × γ + β y=\frac {x-mean(x)} {\sqrt{Var(x)}+eps}\times \gamma + \beta y=Var(x) +epsxmean(x)×γ+β

eps的作用是保持数值稳定性,防止除0的操作。

2.2 卷积层的BN,

BN是在channel上进行,且每个channel有独立的scale和shift;比如输入是 n × c × h × w n\times c\times h\times w n×c×h×w,则mean和var是在每个channel上进行计算,即在 n × h × w n\times h\times w n×h×w上;同样 γ \gamma γ β \beta β也是在channel上的可学习的参数。所以,BN共有 2 × c 2\times c 2×c个参数

2.3 scale和shift的作用

  • 提升网络的表达能力

因为BN在归一化时会破坏参数分布,这样的normalization可能会改变这一层网络的表达能力,比如对于一个sigmoid激活函数,可能就把限制在了它的线性区域,失去了非线性激活的能力。因此我们要赋予normalization能够进行单位变换(identity transform)的能力。这可以通过在后面再加一层缩放和平移实现。

  • 保证了模型的capacity

γ和β作为调整参数可以调整被BN刻意改变过后的输入,即能够保证能够还原成原始的输入分布。BN对每一层输入分布的调整有可能改变某层原来的输入,当然有了这两个参数,经过调整也可以不发生改变,既可以改变同时也可以保持原输入,那么模型的容纳能力(capacity)就提升了。

  • 适应激活函数

如果是sigmoid函数,那么BN后的分布在0-1之间,由于sigmoid在接近0的地方趋于线性,非线性表达能力可能会降低,因此通过γ和β可以自动调整输入分布,使得非线性表达能力增强。
如果激活函数为ReLU,0均值1方差意味着将有一半的数值在负半轴失活无法使用,那么通过β可以进行调整参与激活的数据的比例,防止dead-Relu问题。

2.4 训练和测试时的区别

  • 训练时的BN

1)沿着通道计算每个batch的均值μ 和 方差σ² ,做归一化

mean(x)是均值μ , 是NHW的均值, V a r ( x ) \sqrt{Var(x)} Var(x) 是方差σ²,是NHW的方差。

2)加入缩放和平移变量 γ 和 β(为了恢复原分布)

  • 测试时的BN

在训练时,是对每一批的训练数据进行归一化,也即用每一批数据的均值和方差。

而在测试时,比如进行一个样本的预测,就并没有batch的概念,因此,这个时候用的均值和方差是全量训练数据的均值和方差,这个可以通过移动平均法求得。

running_mean = momentum * running_mean + (1 - momentum) * x_mean
running_var = momentum * running_var + (1 - momentum) * x_var

2.5 BN与卷积、激活函数的使用

一般BN用在卷积之后,激活函数之前,这样得到的分布会更加稳定。

如果放在激活函数之后,有可能会导致稀疏激活(sparser activations)。

we apply it before the nonlinearity since that is where matching the first and second moments is more likely to result in a stable distribution. On the contrary, apply the standardization layer to the output of the nonlinearity, which results in sparser activations.

3、BN的作用

1)BN使得网络中每层输入数据的分布相对稳定,加速模型学习速度

解决“internal covariate shift”的问题,神经网络目的就是通过训练模型,可以让模型能够最好的拟合数据的真实分布。
在训练时,训练数据经过层间操作,会出现数据分布的微小偏移,随着网络层数的加深,这种偏移量与label的差别逐渐扩大,导致模型需要学习调整样本的分布,训练效率低下
BN将每层的数据进行标准化,并通过可学习参数γ和β两个学习参数来调整这种分布。这样会缓解内部协变量偏移的问题,大大增加了训练速度。

2)BN使得模型对网络中的参数不那么敏感,简化调参过程,使得网络学习更加稳定

3)BN允许网络使用饱和性激活函数(例如sigmoid,tanh等),缓解梯度消失问题

避免了梯度弥散和爆炸,BN可以控制数据的分布范围,在遇到sigmoid或者tanh等激活函数时,
不会使数据落在饱和区域导致梯度弥散。并且BN可以避免ReLU激活函数数据死亡的问题。

4)BN具有一定的正则化效果

在正则化方面,一般全连接层用dropout,卷积层用BN。

5)防止过拟合

6)降低权重初始化的困难,

在深度网络中,网络的最终训练效果也受到初始化的影响,初始化决定了神经网络最终会收敛到哪个局部最小值中,具有随机性。通过BN来标准化分布,可以降低初始化权重的影响。

4、BN存在的问题

(1)每次是在一个batch上计算均值、方差,如果batch size太小,则计算的均值、方差不足以代表整个数据分布。

(2)batch size太大:会超过内存容量;需要跑更多的epoch,导致总训练时间变长;会直接固定梯度下降的方向,导致很难更新。

5、BN在PyTorch的实现

PyTorch的实现,以torch.nn.BatchNorm2d为例。

torch.nn.BatchNorm2d(num_features, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)

一般来说pytorch中的模型都是继承nn.Module类的,都有一个属性trainning指定是否是训练状态,训练状态与否将会影响到某些层的参数是否是固定的,比如BN层或者Dropout层。通常用model.train()指定当前模型model为训练状态,model.eval()指定当前模型为测试状态。

  • 其内部的参数如下:

1、num_features:一般输入参数为NCH*W,即为其中特征的数量
2、eps:分母中添加的一个值,目的是为了计算的稳定性,默认为:1e-5
3、momentum:一个用于计算均值和方差的滑动平均的参数
4、affine:是否需要仿射变换,即γ和β的设置,默认为true。如果affine=False则γ=1,β=0,并且不能学习被更新。
5、track_running_stats:默认true,表示跟踪整个训练过程中的batch的统计特性,得到滑动平均的方差和均值,而不只是仅仅依赖与当前输入的batch的统计特性。如果track_running_stats=False,那么就只是计算当前输入的batch的统计特性中的均值和方差了。
当在推理阶段的时候,如果track_running_stats=False,此时如果batch_size比较小,那么其统计特性就会和全局统计特性有着较大偏差,可能导致糟糕的效果,而且会导致每次模型测试测试集的结果时都不一样;track_running_stats设置为True时,每次得到的结果都一样。

  • shape:

input:(N,C,H,W)
output: (N,C,H,W)

  • 属性:

num_batches_tracked属性(Parameter类):

训练时用来统计训练时的forward过的min-batch数目,每经过一个min-batch, track_running_stats+=1
如果没有指定momentum, 则使用1/num_batches_tracked 作为因数来计算均值和方差(running mean and variance).

Parameters 是 Variable 的子类。

Variable 与 Parameter的另一个不同之处在于,Parameter不能被 volatile(即:无法设置volatile=True)而且默认requires_grad=True。Variable默认requires_grad=False。

Paramenters和Modules一起使用的时候会有一些特殊的属性,即:当Paramenters赋值给Module的属性的时候,他会自动的被加到 Module的 参数列表中(即:会出现在 parameters() 迭代器中)。将Varibale赋值给Module属性则不会有这样的影响。 这样做的原因是:我们有时候会需要缓存一些临时的状态(state), 比如:模型中RNN的最后一个隐状态。如果没有Parameter这个类的话,那么这些临时变量也会注册成为模型变量。

  • 注意:

**BN层中的running_mean和running_var的更新是在forward()操作时进行的,**而不是在optimizer.step()中进行的,因此如果处于训练中,就算不进行手动step(),BN的统计特性也会变化。

**用model.eval()转到测试阶段,才能固定住running_mean和running_var,**有时候如果是先预训练模型然后加载模型,重新跑测试数据的时候,结果不同,有一点性能上的损失,这个时候基本上是training和track_running_stats设置的不对。

  • 源码
class BatchNorm2d(_BatchNorm):
    def _check_input_dim(self, input):
        if input.dim() != 4:
            raise ValueError('expected 4D input (got {}D input)'
                             .format(input.dim()))

nn.BatchNorm2d继承_BatchNorm,BatchNorm2d仅仅负责查看tensor的尺寸是否符合要求。直接跳到_BatchNorm中。

构造函数中running_mean,running_var就是用来记录均值和方差的滑动平均值的。都是用buffer来申请储存空间不是用parameter,是因为这不参与训练。weight和bias就是γ和β,是训练参数。

class _BatchNorm(_NormBase):

    def __init__(self, num_features, eps=1e-5, momentum=0.1, affine=True,
                 track_running_stats=True):
        super(_BatchNorm, self).__init__(
            num_features, eps, momentum, affine, track_running_stats)

    def forward(self, input: Tensor) -> Tensor:
        self._check_input_dim(input)

        # exponential_average_factor is set to self.momentum
        # (when it is available) only so that it gets updated
        # in ONNX graph when this node is exported to ONNX.
        if self.momentum is None:
            exponential_average_factor = 0.0
        else:
            exponential_average_factor = self.momentum

        if self.training and self.track_running_stats:
            # TODO: if statement only here to tell the jit to skip emitting this when it is None
            if self.num_batches_tracked is not None:
                self.num_batches_tracked = self.num_batches_tracked + 1
                if self.momentum is None:  # use cumulative moving average
                    exponential_average_factor = 1.0 / float(self.num_batches_tracked)
                else:  # use exponential moving average
                    exponential_average_factor = self.momentum

        """ Decide whether the mini-batch stats should be used for normalization rather than the buffers.
        Mini-batch stats are used in training mode, and in eval mode when buffers are None.
        """
        if self.training:
            bn_training = True
        else:
            bn_training = (self.running_mean is None) and (self.running_var is None)

        """Buffers are only updated if they are to be tracked and we are in training mode. Thus they only need to be
        passed when the update should occur (i.e. in training mode when they are tracked), or when buffer stats are
        used for normalization (i.e. in eval mode when buffers are not None).
        """
        return F.batch_norm(
            input,
            # If buffers are not to be tracked, ensure that they won't be updated
            self.running_mean if not self.training or self.track_running_stats else None,
            self.running_var if not self.training or self.track_running_stats else None,
            self.weight, self.bias, bn_training, exponential_average_factor, self.eps)

最后,附上两篇参考。

论文阅读笔记:看完也许能进一步了解Batch Normalization

深入解读Inception V2之Batch Normalization(附源码)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值