参考链接

  1. https://tangshusen.me/Dive-into-DL-PyTorch/#/chapter05_CNN/5.10_batch-norm

Batch Normalization(BN)的作用

通常来说,数据标准化预处理对于浅层模型就足够有效了:处理后的任意一个特征在数据集中所有样本上的均值为0、标准差为1。标准化处理输入数据使各个特征的分布相近:这往往更容易训练出有效的模型。但对深层神经网络来说,即使输入数据已做标准化,训练中模型参数的更新依然很容易造成靠近输出层输出的剧烈变化。这种计算数值的不稳定性通常令我们难以训练出有效的深度模型。

批量归一化利用小批量上的均值和标准差,不断调整神经网络中间输出,从而使整个神经网络在各层的中间输出的数值更稳定。

对全连接层做归一化

批量归一化层置于全连接层中的仿射变换和激活函数之间。设全连接层的输入为 u u u,权重参数和偏差参数分别为 W W W b b b,激活函数为 ϕ \phi ϕ。设批量归一化的运算符为 B N BN BN。那么,使用批量归一化的全连接层的输出为

ϕ ( B N ( x ) ) \phi \left ( BN\left ( x\right )\right ) ϕ(BN(x))

其中, x x x为全连接层的输出。 x = W u + b x=Wu+b x=Wu+b

考虑一个由 m m m个样本组成的小批量,仿射变换的输出 B = { x ( 1 ) , ⋅ ⋅ ⋅ , x ( m ) } B=\left \{x^{\left ( 1\right )},\cdot \cdot \cdot ,x^{\left ( m\right )}\right \} B={x(1),,x(m)}。它们正是批量归一化层的输入。对于小批量 B B B中任意样本 x ( i ) ∈ R d , 1 ⩽ i ⩽ m x^{\left ( i\right )}\in R^{d},1\leqslant i\leqslant m x(i)Rd,1im批量归一化层的输出同样是 d d d维向量。

首先求小批量的均值和方差:

μ B = 1 m ∑ i = 1 m x ( i ) \mu _{B}=\frac{1}{m}\sum_{i=1}^{m}x^{\left ( i\right )} μB=m1i=1mx(i)

σ B 2 = 1 m ∑ i = 1 m ( x ( i ) − μ B ) 2 \sigma _{B}^{2}=\frac{1}{m}\sum_{i=1}^{m}\left ( x^{\left ( i\right )}-\mu _{B}\right )^{2} σB2=m1i=1m(x(i)μB)2

再标准化全连接层的输出:

x ^ ( i ) = x ( i ) − μ B σ B 2 + ϵ \hat{x}^{\left ( i\right )}=\frac{x^{\left ( i\right )}-\mu _{B}}{\sqrt{\sigma _{B}^{2}+\epsilon }} x^(i)=σB2+ϵ x(i)μB

引入了两个可以学习的模型参数,拉伸(scale)参数 γ \gamma γ和偏移(shift)参数 β \beta β。这两个参数和 x ( i ) x^{\left ( i\right )} x(i)形状相同,皆为 d d d维向量。它们与 x ( i ) x^{\left ( i\right )} x(i)分别做按元素乘法和加法计算:

y ( i ) = γ ⊙ x ^ ( i ) + β y^{\left ( i\right )}=\gamma \odot \hat{x}^{\left ( i\right )}+\beta y(i)=γx^(i)+β

至此,我们得到了 x ( i ) x^{\left ( i\right )} x(i)的批量归一化的输出 y ( i ) y^{\left ( i\right )} y(i)。值得注意的是,可学习的拉伸和偏移参数保留了不对 x ( i ) x^{\left ( i\right )} x(i)做批量归一化的可能

对卷积层做批量归一化

对卷积层来说,批量归一化发生在卷积计算之后、应用激活函数之前。如果卷积计算输出多个通道,我们需要对这些通道的输出分别做批量归一化,且每个通道都拥有独立的拉伸和偏移参数,并均为标量。设小批量中有 m m m个样本。在单个通道上,假设卷积计算输出的高和宽分别为 p p p q q q。我们需要对该通道中 m × p × q m×p×q m×p×q个元素同时做批量归一化。对这些元素做标准化计算时,我们使用相同的均值和方差,即该通道中 m × p × q m×p×q m×p×q个元素的均值和方差。

预测时的批量归一化

将训练好的模型用于预测时,我们希望模型对于任意输入都有确定的输出。因此,单个样本的输出不应取决于批量归一化所需要的随机小批量中的均值和方差。一种常用的方法是通过移动平均估算整个训练数据集的样本均值和方差,并在预测时使用它们得到确定的输出。

自定义实现

def batch_norm(is_training, X, gamma, beta, moving_mean, moving_var, eps, momentum):
    # 判断当前模式是训练模式还是预测模式
    if not is_training:
        # 如果是在预测模式下,直接使用传入的移动平均所得的均值和方差
        X_hat = (X - moving_mean) / torch.sqrt(moving_var + eps)
    else:
        assert len(X.shape) in (2, 4)
        if len(X.shape) == 2:
            # 使用全连接层的情况,计算特征维上的均值和方差
            mean = X.mean(dim=0)
            var = ((X - mean) ** 2).mean(dim=0)
        else:
            # 使用二维卷积层的情况,计算通道维上(axis=1)的均值和方差。这里我们需要保持
            # X的形状以便后面可以做广播运算
            mean = X.mean(dim=0, keepdim=True).mean(dim=2, keepdim=True).mean(dim=3, keepdim=True)
            var = ((X - mean) ** 2).mean(dim=0, keepdim=True).mean(dim=2, keepdim=True).mean(dim=3, keepdim=True)
        # 训练模式下用当前的均值和方差做标准化
        X_hat = (X - mean) / torch.sqrt(var + eps)
        # 更新移动平均的均值和方差
        moving_mean = momentum * moving_mean + (1.0 - momentum) * mean
        moving_var = momentum * moving_var + (1.0 - momentum) * var
    Y = gamma * X_hat + beta  # 拉伸和偏移
    return Y, moving_mean, moving_var

class BatchNorm(nn.Module):
    def __init__(self, num_features, num_dims):
        super(BatchNorm, self).__init__()
        if num_dims == 2:
            shape = (1, num_features)
        else:
            shape = (1, num_features, 1, 1)
        # 参与求梯度和迭代的拉伸和偏移参数,分别初始化成0和1
        self.gamma = nn.Parameter(torch.ones(shape))
        self.beta = nn.Parameter(torch.zeros(shape))
        # 不参与求梯度和迭代的变量,全在内存上初始化成0
        self.moving_mean = torch.zeros(shape)
        self.moving_var = torch.zeros(shape)

    def forward(self, X):
        # 如果X不在内存上,将moving_mean和moving_var复制到X所在显存上
        if self.moving_mean.device != X.device:
            self.moving_mean = self.moving_mean.to(X.device)
            self.moving_var = self.moving_var.to(X.device)
        # 保存更新过的moving_mean和moving_var, Module实例的traning属性默认为true, 调用.eval()后设成false
        Y, self.moving_mean, self.moving_var = batch_norm(self.training, 
            X, self.gamma, self.beta, self.moving_mean,
            self.moving_var, eps=1e-5, momentum=0.9)
        return Y

PyTorch的BatchNorm

Pytorch中nn模块定义的BatchNorm1d和BatchNorm2d类分别用于全连接层和卷积层,都需要指定输入的num_features参数值。

net = nn.Sequential(
            nn.Conv2d(1, 6, 5), # in_channels, out_channels, kernel_size
            nn.BatchNorm2d(6),
            nn.Sigmoid(),
            nn.MaxPool2d(2, 2), # kernel_size, stride
            nn.Conv2d(6, 16, 5),
            nn.BatchNorm2d(16),
            nn.Sigmoid(),
            nn.MaxPool2d(2, 2),
            d2l.FlattenLayer(),
            nn.Linear(16*4*4, 120),
            nn.BatchNorm1d(120),
            nn.Sigmoid(),
            nn.Linear(120, 84),
            nn.BatchNorm1d(84),
            nn.Sigmoid(),
            nn.Linear(84, 10)
        )

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐