批量归一化(Batch Normalization)

我们知道网络开始训练之后,每一层网络的参数都会发生变化。又由于每一层网络的输入是上一层网络的输出,上一层网络参数的变化,就导致当前层的输入分布发生变化,这个问题我们称之为 Internal Convariate Shift。

BN 的出现就是试图去解决该问题。我们在每一层的网络输出之后,将输出数据经过 BN 层,来实现控制数据的分布。BN 中的 Batch 指的是以 Batch 为单位,对输入的批次样本的同一特征进行 Normalization。很显然,BN 的 batch size 不同,也会影响到网络的收敛。一般而言,我们希望 batch 的样本计算得到均值和方差能够更加接近真实分布的均值和方差,所以在设置 batch size 时会选择较大。

1. 批量归一化公式

  1. λ 和 β 是可学习的参数,它相当于对标准化后的值做了一个线性变换,λ 为系数,β 为偏置;
  2. eps 通常指为 1e-5,避免分母为 0;
  3. E(x) 表示变量的均值;
  4. Var(x) 表示变量的方差;

数据在经过 BN 层之后,无论数据以前的分布是什么,都会被归一化成均值为 β,标准差为 γ 的分布。

注意:BN 层不会改变输入数据的维度,只改变输入数据的的分布. 在实际使用过程中,BN 常常和卷积神经网络结合使用,卷积层的输出结果后接 BN 层,即:BN 层在激活函数之前。

关于 BN 的一些有趣探讨:https://stackoverflow.com/questions/45493384/is-it-normal-to-use-batch-normalization-in-rnn-lstm

2. BN 层的接口

torch.nn.BatchNorm2d(num_features, eps=1e-05, momentum=0.9, affine=True)
  1. 由于每次使用的 mini batch 的数据集,所以 BN 使用移动加权平均来近似计算均值和方差,而 momentum 参数则调节移动加权平均值的计算;
  2. affine = False 表示 γ=1,β=0,反之,则表示 γ 和 β 要进行学习;
  3. BatchNorm2d 适用于输入的数据为 4D,输入数据的形状 [N,C,H,W]

均值使用的是移动加权平均:

running_mean = (1 - momentum) * running_mean + momentum * batch_mean
running_var = (1 - momentum) * running_var + momentum * batch_var

示例代码:

import torch
import torch.nn as nn


def test01():

    torch.manual_seed(0)
    batch_input = torch.randint(0, 10, [2, 2, 2, 2]).float()
    print(batch_input)

    # num_features 表示每个样本特征图的数量,通道数量
    # affine 为 False 表示不带 gama 和 beta 两个学习参数
    # eps 小常数,避免分母为 0
    # momentum 移动加权平均系数:
    # running_mean = (1 - momentum) * running_mean + momentum * batch_mean
    # running_var = (1 - momentum) * running_var + momentum * batch_var
    batch_norm = nn.BatchNorm2d(num_features=2, affine=False, eps=1e-5, momentum=0.9)
    output = batch_norm(batch_input)
    # 均值是批次相同通道的均值
    # 方差是批次相同通道的均值

    # tensor([3.7125, 5.7375])
    # 3.7125 表示第一个通道的均值
    # 5.7375 表示第二个通道的均值
    print('批次均值:', batch_norm.running_mean)

    # tensor([8.4411, 5.2268])
    # 8.4411 表示第一个通道的方差
    # 5.2268 表示第二个通道的方差
    # print(batch_norm.running_var)


def test02():

    torch.manual_seed(0)
    batch_input = torch.randint(0, 10, [2, 2, 2, 2]).float()

    # 取出第一个通道所有的值
    channel = batch_input[:, 0, :, :]
    # 计算第一个通道元素均值
    batch_mean = torch.sum(channel) / channel.numel()
    # 计算第一个通道移动加权均值
    momentum = 0.9
    print('动作分解:', (1 - momentum) * 0 + momentum * batch_mean)


if __name__ == '__main__':
    test01()
    test02()

程序执行结果:

tensor([[[[4., 9.],
          [3., 0.]],

         [[3., 9.],
          [7., 3.]]],


        [[[7., 3.],
          [1., 6.]],

         [[6., 9.],
          [8., 6.]]]])
批次均值: tensor([3.7125, 5.7375])
动作分解: tensor(3.7125)

当我们进行预测时,可以使用该移动均值和方差对输入数据进行批次归一化。

3. 贝塞尔校正

贝塞尔校正是一个与统计学的方法和标准差相关的修正方法,在计算样本的方差和标准差时,将分母中的 N 替换成 N-1,这种修正方法得到的方差和标准差,更近似总体集合分布中的方差和标准差。

未经允许不得转载:一亩三分地 » 批量归一化(Batch Normalization)