• 动手学深度学习—批量规范化(代码详解)



    批量规范化(batch normalization),可持续加速深层网络的收敛速度。

    1. 训练深层网络

    1. 数据预处理的方式通常会对最终结果产生巨大影响。
    2. 对于典型的多层感知机或卷积神经网络。训练时,中间层中的变量(例如,多层感知机中的仿射变换输出)可能具有更广的变化范围。
    3. 更深层的网络很复杂,容易过拟合。 这意味着正则化变得更加重要。

    批量规范化:

    • 每次训练迭代中,首先规范输入,即减去均值并除以其标准差,其中两者均基于当前小批量处理。
    • 接下来,应用比例系数和偏移系数。
    • 因为是基于批量统计的标准化,才有了批量规范化的名称。
      在这里插入图片描述
      γ和β是需要与其他模型参数一起学习的参数
      均值和方差如下图公式所示
      在这里插入图片描述

    2. 批量规范化层

    全连接层和卷积层的批量规范化实现略有不同。

    2.1 全连接层

    对于全连接层,将批量规范化层置于全连接层中的仿射变换和激活函数之间。
    在这里插入图片描述

    2.2 卷积层

    对于卷积层,在卷积层之后和非线性激活函数之前应用批量规范化。

    假设我们的小批量包含m个样本,并且对于每个通道,卷积的输出具有高度p和宽度q。
    那么对于卷积层,我们在每个输出通道的mpq个元素上同时执行每个批量规范化。

    3. 从零实现批量规范化层

    从头开始实现一个具有张量的批量规范化层。

    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方法来判断当前模式是训练模式还是预测模式
        if not torch.is_grad_enabled():
            # eps->方差估计值添加一个小的常量ε>0,以确保永远不会尝试除以0
            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:
                # 卷积层:(batch_size, in_channels, height, weight)
                # 使用卷积层的情况,计算通道维上(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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    4. 使用批量规范化层的 LeNet

    在LeNet模型上使用BatchNorm。

    # 将其应用于LeNet模型
    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))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    定义精度评估函数

    """
        定义精度评估函数:
        1、将数据集复制到显存中
        2、通过调用accuracy计算数据集的精度
    """
    def evaluate_accuracy_gpu(net, data_iter, device=None): #@save
        # 判断net是否属于torch.nn.Module类
        if isinstance(net, nn.Module):
            net.eval()
            
            # 如果不在参数选定的设备,将其传输到设备中
            if not device:
                device = next(iter(net.parameters())).device
        
        # Accumulator是累加器,定义两个变量:正确预测的数量,总预测的数量。
        metric = d2l.Accumulator(2)
        with torch.no_grad():
            for X, y in data_iter:
                # 将X, y复制到设备中
                if isinstance(X, list):
                    # BERT微调所需的(之后将介绍)
                    X = [x.to(device) for x in X]
                else:
                    X = X.to(device)
                y = y.to(device)
                
                # 计算正确预测的数量,总预测的数量,并存储到metric中
                metric.add(d2l.accuracy(net(X), y), y.numel())
        return metric[0] / metric[1]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    定义GPU训练函数

    """
        定义GPU训练函数:
        1、为了使用gpu,首先需要将每一小批量数据移动到指定的设备(例如GPU)上;
        2、使用Xavier随机初始化模型参数;
        3、使用交叉熵损失函数和小批量随机梯度下降。
    """
    #@save
    def train_ch6(net, train_iter, test_iter, num_epochs, lr, device):
        """用GPU训练模型(在第六章定义)"""
        # 定义初始化参数,对线性层和卷积层生效
        def init_weights(m):
            if type(m) == nn.Linear or type(m) == nn.Conv2d:
                nn.init.xavier_uniform_(m.weight)
        net.apply(init_weights)
        
        # 在设备device上进行训练
        print('training on', device)
        net.to(device)
        
        # 优化器:随机梯度下降
        optimizer = torch.optim.SGD(net.parameters(), lr=lr)
        
        # 损失函数:交叉熵损失函数
        loss = nn.CrossEntropyLoss()
        
        # Animator为绘图函数
        animator = d2l.Animator(xlabel='epoch', xlim=[1, num_epochs],
                                legend=['train loss', 'train acc', 'test acc'])
        
        # 调用Timer函数统计时间
        timer, num_batches = d2l.Timer(), len(train_iter)
        
        for epoch in range(num_epochs):
            
            # Accumulator(3)定义3个变量:损失值,正确预测的数量,总预测的数量
            metric = d2l.Accumulator(3)
            net.train()
            
            # enumerate() 函数用于将一个可遍历的数据对象
            for i, (X, y) in enumerate(train_iter):
                timer.start() # 进行计时
                optimizer.zero_grad() # 梯度清零
                X, y = X.to(device), y.to(device) # 将特征和标签转移到device
                y_hat = net(X)
                l = loss(y_hat, y) # 交叉熵损失
                l.backward() # 进行梯度传递返回
                optimizer.step()
                with torch.no_grad():
                    # 统计损失、预测正确数和样本数
                    metric.add(l * X.shape[0], d2l.accuracy(y_hat, y), X.shape[0])
                timer.stop() # 计时结束
                train_l = metric[0] / metric[2] # 计算损失
                train_acc = metric[1] / metric[2] # 计算精度
                
                # 进行绘图
                if (i + 1) % (num_batches // 5) == 0 or i == num_batches - 1:
                    animator.add(epoch + (i + 1) / num_batches,
                                 (train_l, train_acc, None))
                    
            # 测试精度
            test_acc = evaluate_accuracy_gpu(net, test_iter) 
            animator.add(epoch + 1, (None, None, test_acc))
            
        # 输出损失值、训练精度、测试精度
        print(f'loss {train_l:.3f}, train acc {train_acc:.3f},'
              f'test acc {test_acc:.3f}')
        
        # 设备的计算能力
        print(f'{metric[2] * num_epochs / timer.sum():.1f} examples/sec'
              f'on {str(device)}')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70

    在这里插入图片描述

    训练LeNet模型

    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())
    
    • 1
    • 2
    • 3

    在这里插入图片描述
    在模型训练过程中,批量规范化利用小批量的均值和标准差,不断调整神经网络的中间输出,使整个神经网络各层的中间输出值更加稳定。

  • 相关阅读:
    解决VSCODE 终端中显示中文乱码的问题
    io多路复用
    深入理解nginx一致性哈希负载均衡模块[上]
    Python GUI编程之PyQt5入门到实战
    Spark和Hadoop的区别和比较
    赖迪思软件 lattice Diamond
    在基于ABP框架的前端项目Vue&Element项目中采用日期格式处理,对比Moment.js和day.js的处理
    Android Kotlin 协程初探 | 京东物流技术团队
    K8S集群中Pod挂载Storageclass存储卷异常排查思路
    ChatGPT研究论文提示词集合3-【数据收集】、【数据分析】和【解释与讨论】
  • 原文地址:https://blog.csdn.net/qq_38473254/article/details/134037120