欢迎光临散文网 会员登陆 & 注册

28 批量归一化【动手学深度学习v2】

2023-07-27 15:54 作者:月芜SA  | 我要投稿

批量归一化层:一种新兴的,在如今流行的网络中都很常见的一种层。

在常见网络中,数据在底层,损失函数在上层。在进行反向传播的过程中,梯度往往在上层会比较大,但到了底层就会变得很小。

所以在训练过程中,往往上层的参数会收敛的比较快,下层参数却变化得很慢,但是一旦下层参数发生变化,整个网络中的参数都得跟着变,导致网络整体收敛变慢。

*一个脑洞:能不能控制网络下层的学习率较大。上层的学习率较小?


对于一个可学习的特征,都有一个可学习的参数γ和β。

对于全连接层来说,就是一列一特征,一行一数据,批量归一化层的可训练参数数量=特征数=列数。

对于卷积层来说,训练参数数量=通道数。类比来说,卷积层处理的每一个像素都相当于一个样本,组成每个像素的通道数据相当于这个像素的特征向量。

批量归一化层需设置在一层输出之后,激活函数之前(因为批量归一化属于线性变换,激活函数为非线性变换。激活函数会严重影响数据的平均值和方差值,比如会把所有负数数据变为正数。)


批量归一化本质?:通过随机取样获取随机的数据平均值与数据方差值来对数据增加噪音。

总结:为了防止不同的小批量,在层数过多时,出现分布剧烈变化,导致收敛速度下降,所以要用BATCH norm。注意是不同的小批量


代码实现

import torch
from torch import nn
from d2l import torch as d2l


def batch_norm(X, gamma, beta, moving_mean, moving_var, eps, momentum):
    # 通过is_grad_enabled来判断当前模式是训练模式还是预测模式,gamma和beta为待训练的批量归一化层参数,moving_mean和moving_var是动态均值和标准差
    if not torch.is_grad_enabled():
        # 如果是在预测模式下,直接使用传入的移动平均所得的均值和方差
        X_hat = (X - moving_mean) / torch.sqrt(moving_var + eps)
    else:
        assert len(X.shape) in (2, 4)
        # 如果是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, 2, 3), keepdim=True)
                    var = ((X - mean) ** 2).mean(dim=(0, 2, 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.data, moving_var.data

创建一个正确的BatchNorm层。 这个层将保持适当的参数:拉伸gamma和偏移beta,这两个参数将在训练过程中更新。 此外,我们的层将保存均值和方差的移动平均值,以便在模型预测期间随后使用。

class BatchNorm(nn.Module):
    # num_features:完全连接层的输出数量或卷积层的输出通道数。
    # num_dims:2表示完全连接层,4表示卷积层
    def __init__(self, num_features, num_dims):
        super().__init__()
        if num_dims == 2:
            shape = (1, num_features)
        else:
            shape = (1, num_features, 1, 1)
        # 参与求梯度和迭代的拉伸和偏移参数,分别初始化成1和0
        self.gamma = nn.Parameter(torch.ones(shape))
        self.beta = nn.Parameter(torch.zeros(shape))
        # 非模型参数的变量初始化为0和1
        self.moving_mean = torch.zeros(shape)
        self.moving_var = torch.ones(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
        Y, self.moving_mean, self.moving_var = batch_norm(
            X, self.gamma, self.beta, self.moving_mean,
            self.moving_var, eps=1e-5, momentum=0.9)
        return Y


批量规范化是在卷积层或全连接层之后、相应的激活函数之前应用的。


net = nn.Sequential(
    nn.Conv2d(1, 6, kernel_size=5), BatchNorm(6, num_dims=4), nn.Sigmoid(),
    nn.AvgPool2d(kernel_size=2, stride=2),
    nn.Conv2d(6, 16, kernel_size=5), BatchNorm(16, num_dims=4), nn.Sigmoid(),
    nn.AvgPool2d(kernel_size=2, stride=2), nn.Flatten(),
    nn.Linear(16*4*4, 120), BatchNorm(120, num_dims=2), nn.Sigmoid(),
    nn.Linear(120, 84), BatchNorm(84, num_dims=2), nn.Sigmoid(),
    nn.Linear(84, 10))


和以前一样,在Fashion-MNIST数据集上训练网络。 这个代码与我们第一次训练LeNet( 6.6节)时几乎完全相同,主要区别在于学习率大得多。

lr, num_epochs, batch_size = 1.0, 10, 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
d2l.train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu())


loss 0.267, train acc 0.900, test acc 0.860
40878.1 examples/sec on cuda:0


简明实现

除了使用我们刚刚定义的BatchNorm,我们也可以直接使用深度学习框架中定义的BatchNorm。 该代码看起来几乎与我们上面的代码相同。


net = nn.Sequential(
    nn.Conv2d(1, 6, kernel_size=5), nn.BatchNorm2d(6), nn.Sigmoid(),
    nn.AvgPool2d(kernel_size=2, stride=2),
    nn.Conv2d(6, 16, kernel_size=5), nn.BatchNorm2d(16), nn.Sigmoid(),
    nn.AvgPool2d(kernel_size=2, stride=2), nn.Flatten(),
    nn.Linear(256, 120), nn.BatchNorm1d(120), nn.Sigmoid(),
    nn.Linear(120, 84), nn.BatchNorm1d(84), nn.Sigmoid(),
    nn.Linear(84, 10))


下面,我们使用相同超参数来训练模型。 请注意,通常高级API变体运行速度快得多,因为它的代码已编译为C++或CUDA,而我们的自定义代码由Python实现。

d2l.train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu())


loss 0.263, train acc 0.902, test acc 0.862
71480.6 examples/sec on cuda:0


知识补充:

·前面课程中讲过的Xavier,是对数据初始化进行标准化处理,而BN是在训练过程中对每个层之间的数据进行标准化处理。本质上二者没有区别。

·BN只有在较深的深度学习网络中才有较好的效果(较浅网络不会出现顶层参数随底层参数微变而不断变动的情况)

·有一种解释,认为BN之后地形图更加平坦,容易收敛。此外使用BN后可以搭配更大的学习率,加快学习速度。

·除了Batch Normalization,还有在不同维度上使用Normalization的方法




28 批量归一化【动手学深度学习v2】的评论 (共 条)

分享到微博请遵守国家法律