【CV基石】Batch Normalization

BN作用

首先,在进行训练之前,一般要对数据做归一化,使其分布一致,但是在深度神经网络训练过程中,通常以送入网络的每一个batch训练,这样每个batch具有不同的分布;此外,为了解决batch normalizaiton论文中提出的internal covarivate shift问题,因为在训练过程中,数据分布会发生变化,对下一层网络的学习带来困难

Internal Covariate Shift
深度神经网络具有如下特性:
网络中层与层之间有着高度关联性和耦合性。前一层的输出是后一层的输入。随着训练的进行,网络中的参数也随着梯度下降在不停更新。

  • 一方面,当底层网络中参数发生微弱变化时,由于每一层中的线性变换与非线性激活映射,这些微弱变化随着网络层数的加深而被放大;
  • 另一方面,参数的变化导致每一层的输入分布会发生改变,进而上层的网络需要不停地去适应这些分布变化,使得我们的模型训练变得困难。

随着梯度下降,每一层的参数都在更新,这就导致逐层的分布会不断发生变化,那么每层的参数就要不断适应这种输入分布的变化,这就导致模型收敛的速度非常低。
同时当激活函数选择了sigmoid,tanh等激活函数时,很容易使模型陷入梯度饱和区,这就会导致梯度消失。一种思路是替换激活函数,如ReLU函数,另一种思路是,如果我们可以将输入控制在一个稳定的范围,如:均值为0,方差为1的范围。这样就能避免它们陷入了梯度饱和区。
白化是ML常用的规范化数据的方法,常见的是PCA和ZCA。但是如果每层都做这种操作的话,首先计算成本很高,其次它会改变网络每一次的分布,把网络本身的特性抹去了。

所以batch normalization就是强行将数据拉回到均值为0,方差为1的正太分布上,这样不仅数据分布一致,而且避免发生梯度消失。但是network不同层学习到的数据特征,归一化到正太分布后所有层学习到的特征会趋于相似,相当于没有学习。于是引入了两个可以学习的参数γ、β,加入了这两个参数之后,网络可以更加容易的学习到更多的东西。比如极端情况,当缩放变量γ和平移变量β分别等于batch数据的方差和均值时,最后得到的yi就和原来的xi一模一样了,相当于batch normalization没有起作用了。这样就保证了每一次数据经过归一化后还保留的有学习来的特征,同时又能完成归一化这个操作,加速训练。

1. 使得模型训练收敛速度更快

  • 没有BN之前,需要小心的调整学习率和权重初始化,但是有了BN可以放心的使用大学习率,但是使用了BN,就不用小心的调参了,较大的学习率极大的提高了学习速度

2. 使得模型隐藏层输出的特征分布更稳定,更利于模型的学习

  • BN层使网络中每层输入数据分布比较稳定,从而可以加速模型的学习速率(后一层网络不必去不断适应前面网络输出的分布变化,可以做到独立训练)
  • BN层使模型对网络参数不那么敏感,简化调参过程,使得网络更为稳定。当学习率设置比较高的时候,参数变化的步伐会比较大,容易出现震荡和不收敛,但是使用BN将不会受到参数值大小的影响。

3. 缓解梯度消失的问题

  • 通过BN操作可以让激活函数的输入数据落在梯度非饱和区,缓解梯度消失的问题

4. 起到一定正则化作用

  • 通过自适应学习 γ γ γ β β β数据不仅可以保留更多的原始信息,同时因为每一个batch的 γ γ γ β β β都会不同,这就为学习过程增加了随机噪音,一定程度上起到了正则化作用,可以代替其他正则方式如dropout等。

5. 起到去相关的作用

  • BN降低了数据之间的绝对差异,有一个去相关的性质,更多的考虑相对差异性,因此在分类任务上具有更好的效果(个人见解)

前向传播

batchnorm forward

反向传播

∂ ℓ ∂ x i = ∂ ℓ ∂ x ^ i ⋅ 1 σ B 2 + ϵ + ∂ ℓ ∂ σ B 2 ⋅ 2 ( x i − μ B ) m + ∂ ℓ ∂ μ B ⋅ 1 m \frac{\partial \ell}{\partial x_{i}}=\frac{\partial \ell}{\partial \widehat{x}_{i}} \cdot \frac{1}{\sqrt{\sigma_{\mathcal{B}}^{2}+\epsilon}}+\frac{\partial \ell}{\partial \sigma_{\mathcal{B}}^{2}} \cdot \frac{2\left(x_{i}-\mu_{\mathcal{B}}\right)}{m}+\frac{\partial \ell}{\partial \mu_{\mathcal{B}}} \cdot \frac{1}{m} xi=x iσB2+ϵ 1+σB2m2(xiμB)+μBm1
∂ ℓ ∂ γ = ∑ i = 1 m ∂ ℓ ∂ y i ⋅ x ^ i \frac{\partial \ell}{\partial \gamma}=\sum_{i=1}^{m} \frac{\partial \ell}{\partial y_{i}} \cdot \widehat{x}_{i} γ=i=1myix i
∂ ℓ ∂ β = ∑ i = 1 m ∂ ℓ ∂ y i \frac{\partial \ell}{\partial \beta}=\sum_{i=1}^{m} \frac{\partial \ell}{\partial y_{i}} β=i=1myi


Python实现代码如下:

import numpy as np

def batchnorm_forward(x, gamma, beta, bn_param):
    """Forward pass for batch normalization: y = gamma * x_hat + beta.
    Parameters
    ----------
    x: Data of shape (N, D)
    gamma: Scale parameter of shape (D,)
    beta: Shift paremeter of shape (D,)
    bn_param: Dictionary with the following keys:
        mode: 'train' or 'test', required
        eps: Constant for numeric stability
        momentum: Constant for running mean / variance.
        running_mean: Array of shape (D,) giving running mean of features
        running_var: Array of shape (D,) giving running variance of features
    Returns
    -------
    y: bn result, an array of shape (N, D)
    cache: A tuple of values needed in the backward pass
    """
    mode = bn_param['mode']
    eps = bn_param.get('eps', 1e-5)
    momentum = bn_param.get('momentum', 0.9)
    
    x = np.array(x)
    N, D = x.shape
    running_mean = bn_param.get('running_mean', np.zeros(D, dtype=x.dtype))
    running_var = bn_param.get('running_var', np.zeros(D, dtype=x.dtype))

    y, cache = None, None
    
    if mode == 'train':
        sample_mean = np.mean(x, axis=0)
        sample_var = np.var(x, axis=0)
        x_norm = (x - sample_mean) / np.sqrt(sample_var + eps)

        running_mean = momentum * running_mean + (1 - momentum) * sample_mean
        running_var = momentum * running_var + (1 - momentum) * sample_var

        y = gamma * x_norm + beta
        cache = (x_norm, x, sample_mean, sample_var, eps, gamma, beta)
    elif mode == 'test':
        scale = gamma / np.sqrt(running_var + eps)
        y = scale * x + (beta - running_mean * scale)
    else:
        raise ValueError('Invalid forward batchnorm mode "%s"' % mode)

    # Store the updated running mean and var back into bn_param
    bn_param['running_mean'] = running_mean
    bn_param['running_var'] = running_var

    return y, cache

def batchnorm_backward(dy, cache):
    """Backward pass for batch normalization.
    Parameters
    ----------
    dy: Upstream derivatives, of shape (N, D)
    cache: Variable of intermediates from batchnorm_forward

    Returns
    -------
    dx: An array of gradient with respect to inputs x, of shape (N, D)
        dx = dx_norm * dx_norm_dx + dvar * dvar_dx + dmean * dmean_dx
    dgamma: An array of gradient with respect to scale parameter gamma, of shape (D,)
        dgamma = dy * x_norm
    dbeta: An array of gradient with respect to shift parameter beta, of shape (D,)
        dbeta = dy
    """
    dx, dgamma, dbeta = None, None, None
    x_norm, x, sample_mean, sample_var, eps, gamma, beta = cache
    N = x.shape[0]

    dx_norm = gamma * dy
    dx_norm_dx = 1 / np.sqrt(sample_var + eps)
    dvar = np.sum(dx_norm * (x - sample_mean) * -0.5 * (sample_var + eps) ** -1.5, axis=0)    
    dvar_dx = 2 * (x - sample_mean) / N
    dx_norm_dmean = -1 / np.sqrt(sample_var + eps)
    dvar_dmean = -2 * np.sum(x - sample_mean,  axis=0) / N
    dmean = np.sum(dx_norm * dx_norm_dmean + dvar * dvar_dmean , axis=0)
    dmean_dx = np.ones_like(x) / N

    dx = dx_norm * dx_norm_dx + dvar * dvar_dx + dmean * dmean_dx
    dgamma = np.sum(dy * x_norm, axis=0)
    dbeta = np.sum(dy, axis=0)

    return dx, dgamma, dbeta

def main():
    x = ([1.0, 0.2, 1.2, -0.2, 3.1, 2.6, 2.1, 0.9, 5.0, 0.02],
         [-1.1, 2.4, 3.1, 5.1, 9.5, -3.2, 0.45, 3.12, 2.9, 1.4],
         [5.5, 2.5, 3.1, 0.9, 2.1, 1.1, 0.5, -5.1, 2.9, 1.11])
    gamma = [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0]
    beta = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
    bn_param = {'mode' : 'train'}

    y, cache = batchnorm_forward(x, gamma, beta, bn_param)
    dy = y
    dx, dgamma, dbeta = batchnorm_backward(dy, cache)

    x -= dx
    gamma -= dgamma
    beta -= dbeta
    print('x:', x)
    print('gamma:', gamma)
    print('beta:', beta)

if __name__ == "__main__":
    main()

参考

Batch Normalization学习笔记及其实现
batch normalization原理以及反向传播公式
BatchNormalization、LayerNormalization、InstanceNorm、GroupNorm、SwitchableNorm总结


Technical Exchange

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值