使用PyTorch进行批量标准化

2024年01月16日 由 alex 发表 235 0

在神经网络建模的良好实践中被认为是关键要素之一的技术是一种称为批量归一化的技术。通过让你的神经网络在所有层中使用归一化的输入,这种技术可以确保模型更快地收敛,因此训练所需的计算资源更少。


在本文中,我们将专注于用PyTorch实现的批量归一化。


什么是批量归一化?


训练神经网络是根据高层次的监督式机器学习过程进行的。一批数据通过模型,然后将其预测与输入的实际或真实值进行比较。


这种差异导致了所谓的损失值,可以用于随后的误差反向传播和模型优化。


优化模型涉及稍微调整模型中可训练层的权重。到目前为止一切都很好。但是,现在假设你有以下情况:


  • 你用一个平均值为0.25,标准差为1.2的低维数据批来喂养模型,并且你调整你的模型。
  • 你的第二批数据平均值为13.2,标准差为33.9。
  • 你的第三批分别返回到0.35和1.9。


你可以想象,考虑到模型的权重,它在处理第二批数据时将相对较差 - 因此,权重变化显著。因此,你的模型在处理第三批数据时也会比它本可以的更差,仅仅因为它已经适应了显著偏离的情况。


而且,尽管它可以随着时间学会恢复到更通用的过程,你可以看到,在你的数据集中存在相对不稳定性时(甚至在相对归一化的数据集中,由于此类效应在下游层中发生),模型优化将大幅震荡。这很糟糕,因为它放慢了训练过程。


批量归一化是一种可以在层级上应用的归一化技术。简单来说,它规范了“每层的输入到一个学习到的表示,可能接近(μ = 0.0, σ = 1.0)。因此,所有层的输入都被规范化了,显著的异常值不太可能以负面方式影响训练过程。即便如此,它们的影响也将比不使用批量归一化时小很多。


训练深度神经网络的复杂性在于,由于先前层的参数改变,每一层的输入分布在训练过程中会发生变化。这通过要求较低的学习率和小心的参数初始化来减慢训练,并使得难以训练具有饱和非线性的模型闻名。我们将这种现象称为内部协变量偏移,并通过规范层输入来解决这个问题。我们的方法得力于使规范化成为模型架构的一部分,并对每个训练小批次执行规范化。批量归一化使我们可以使用更高的学习率,对初始化不那么挑剔,在某些情况下消除了对Dropout的需要。应用于最先进的图像分类模型,批量归一化以14倍更少的训练步骤达到了相同的准确性,并以显著的优势击败了原始模型。使用一组批量归一化的网络,我们改善了在ImageNet分类上公布的最佳结果:达到4.82%的前5测试误差,超过了人类评估员的准确性。


用PyTorch进行批量归一化


现在我们已经对批量归一化有了基本的了解,以及为什么需要它,我们将继续用深度学习库PyTorch来实现它。


BatchNorm2d和BatchNorm1d之间的差异


首先,PyTorch中的二维批量归一化和一维批量归一化之间的差异。


  1. 二维批量归一化可通过nn.BatchNorm2d获得。
  2. 对于一维批量归一化,你可以使用nn.BatchNorm1d。


PyTorch 网站上一维 Batch Normalization 的定义如下:


在2D或3D输入上应用批量归一化(具有可选附加通道维度的小批量 1D 输入)(…)
PyTorch(n.d.)


…这是二维批量归一化被描述的方式:


对 4D 输入应用批量归一化(具有附加通道维度的小批量 2D 输入)(...)
PyTorch(nd)


让我们总结:


  • 一维nn.BatchNorm1d在2D或3D输入上应用批量归一化。
  • 二维nn.BatchNorm2d在4D输入上应用它。


批量归一化的4D、3D和2D输入


那么,什么是“4D输入”?PyTorch如此描述:(N, C, H, W)


  • 这里,N代表批次中的样本数量。
  • C代表频道数。
  • H代表高度,W代表宽度。


换句话说,一个nn.BatchNorm2d层的4D输入代表了一组N个对象,每个对象都有一个高度和一个宽度,始终具有大于等于1的频道数量。


nn.BatchNorm1d代表了低维度输入:许多输入,可能是频道数,以及每个对象的内容。就像神经网络中密集层产生的那些规则的一维数组。


我们现在知道我们必须将nn.BatchNorm2d应用于处理图像的层。主要的是卷积层,它们在图像上滑动,以生成它们更抽象的表示。nn.BatchNorm1d可以用于堆叠在卷积层之上的密集层,以生成分类。


在神经网络中使用批量归一化


现在我们知道了在神经网络的哪种类型层中必须应用哪种类型的批量归一化,我们可以思考一下在哪里应用批量归一化在我们的神经网络中。


编写神经网络 + 训练循环


声明导入


首先,我们要声明我们的导入。


  • 我们需要基于os的定义来正确下载数据集。
  • 所有基于torch的导入对于PyTorch都是必须的:torch本身,即神经网络(即nn模块)和DataLoader用于加载我们将在今天的神经网络中使用的数据集。
  • 我们从torchvision加载CIFAR10数据集 - 以及一些变换(主要是图像归一化),在训练神经网络之前,我们将在数据集上应用这些变换。


import os
import torch
from torch import nn
from torchvision.datasets import CIFAR10
from torch.utils.data import DataLoader
from torchvision import transforms


定义带批量归一化的 nn.Module


接下来是定义 nn.Module。事实上,我们今天不使用卷积层——这很可能会提高你的神经网络性能。相反,我们直接将32x32x3的输入展平,然后将其进一步处理成10个类别的输出(因为CIFAR10有10个类别)。


正如你所见,我们在这里应用了 BatchNorm1d,因为我们使用的是密集连接的/全连接的(也就是线性的)层。注意,传给 BatchNorm 层的输入数量必须等于 Linear 层的输出数量。


这清楚地展示了如何在 PyTorch 中应用批量归一化。


class MLP(nn.Module):
  '''
    Multilayer Perceptron.
  '''
  def __init__(self):
    super().__init__()
    self.layers = nn.Sequential(
      nn.Flatten(),
      nn.Linear(32 * 32 * 3, 64),
      nn.BatchNorm1d(64),
      nn.ReLU(),
      nn.Linear(64, 32),
      nn.BatchNorm1d(32),
      nn.ReLU(),
      nn.Linear(32, 10)
    )

  def forward(self, x):
    '''Forward pass'''
    return self.layers(x)


编写训练循环


  • 首先,我们将随机数生成器的种子向量设置为一个固定的数字。这确保了任何差异都是由于数字生成过程的随机性质造成的,而不是由于数字生成器本身的伪随机性。
  • 然后我们准备 CIFAR-10 数据集,初始化 MLP 并定义损失函数和优化器。
  • 接着是对epochs进行迭代,在此过程中我们将当前损失设置为 0.0 并开始迭代数据加载器。我们将梯度置零,执行前向传递,计算损失,并执行反向传递,随后进行优化。的确,这就是在监督式机器学习过程中所发生的事情。
  • 我们打印每个通过模型前向传播的小批量数据的统计信息。


if __name__ == '__main__':
  
  # Set fixed random number seed
  torch.manual_seed(42)
  
  # Prepare CIFAR-10 dataset
  dataset = CIFAR10(os.getcwd(), download=True, transform=transforms.ToTensor())
  trainloader = torch.utils.data.DataLoader(dataset, batch_size=10, shuffle=True, num_workers=1)
  
  # Initialize the MLP
  mlp = MLP()
  
  # Define the loss function and optimizer
  loss_function = nn.CrossEntropyLoss()
  optimizer = torch.optim.Adam(mlp.parameters(), lr=1e-4)
  
  # Run the training loop
  for epoch in range(0, 5): # 5 epochs at maximum
    
    # Print epoch
    print(f'Starting epoch {epoch+1}')
    
    # Set current loss value
    current_loss = 0.0
    
    # Iterate over the DataLoader for training data
    for i, data in enumerate(trainloader, 0):
      
      # Get inputs
      inputs, targets = data
      
      # Zero the gradients
      optimizer.zero_grad()
      
      # Perform forward pass
      outputs = mlp(inputs)
      
      # Compute loss
      loss = loss_function(outputs, targets)
      
      # Perform backward pass
      loss.backward()
      
      # Perform optimization
      optimizer.step()
      
      # Print statistics
      current_loss += loss.item()
      if i % 500 == 499:
          print('Loss after mini-batch %5d: %.3f' %
                (i + 1, current_loss / 500))
          current_loss = 0.0
  # Process is complete.
  print('Training process has finished.')


完整代码


import os
import torch
from torch import nn
from torchvision.datasets import CIFAR10
from torch.utils.data import DataLoader
from torchvision import transforms
class MLP(nn.Module):
  '''
    Multilayer Perceptron.
  '''
  def __init__(self):
    super().__init__()
    self.layers = nn.Sequential(
      nn.Flatten(),
      nn.Linear(32 * 32 * 3, 64),
      nn.BatchNorm1d(64),
      nn.ReLU(),
      nn.Linear(64, 32),
      nn.BatchNorm1d(32),
      nn.ReLU(),
      nn.Linear(32, 10)
    )

  def forward(self, x):
    '''Forward pass'''
    return self.layers(x)
  
  
if __name__ == '__main__':
  
  # Set fixed random number seed
  torch.manual_seed(42)
  
  # Prepare CIFAR-10 dataset
  dataset = CIFAR10(os.getcwd(), download=True, transform=transforms.ToTensor())
  trainloader = torch.utils.data.DataLoader(dataset, batch_size=10, shuffle=True, num_workers=1)
  
  # Initialize the MLP
  mlp = MLP()
  
  # Define the loss function and optimizer
  loss_function = nn.CrossEntropyLoss()
  optimizer = torch.optim.Adam(mlp.parameters(), lr=1e-4)
  
  # Run the training loop
  for epoch in range(0, 5): # 5 epochs at maximum
    
    # Print epoch
    print(f'Starting epoch {epoch+1}')
    
    # Set current loss value
    current_loss = 0.0
    
    # Iterate over the DataLoader for training data
    for i, data in enumerate(trainloader, 0):
      
      # Get inputs
      inputs, targets = data
      
      # Zero the gradients
      optimizer.zero_grad()
      
      # Perform forward pass
      outputs = mlp(inputs)
      
      # Compute loss
      loss = loss_function(outputs, targets)
      
      # Perform backward pass
      loss.backward()
      
      # Perform optimization
      optimizer.step()
      
      # Print statistics
      current_loss += loss.item()
      if i % 500 == 499:
          print('Loss after mini-batch %5d: %.3f' %
                (i + 1, current_loss / 500))
          current_loss = 0.0
  # Process is complete.
  print('Training process has finished.')


结果


这些是我们在CIFAR-10数据集上,使用批量标准化训练我们的多层感知器(MLP)5个时期后的结果:


Starting epoch 5
Loss after mini-batch   500: 1.573
Loss after mini-batch  1000: 1.570
Loss after mini-batch  1500: 1.594
Loss after mini-batch  2000: 1.568
Loss after mini-batch  2500: 1.609
Loss after mini-batch  3000: 1.573
Loss after mini-batch  3500: 1.570
Loss after mini-batch  4000: 1.571
Loss after mini-batch  4500: 1.571
Loss after mini-batch  5000: 1.584


同样的,但现在没有批量归一化:


Starting epoch 5
Loss after mini-batch   500: 1.650
Loss after mini-batch  1000: 1.656
Loss after mini-batch  1500: 1.668
Loss after mini-batch  2000: 1.651
Loss after mini-batch  2500: 1.664
Loss after mini-batch  3000: 1.649
Loss after mini-batch  3500: 1.647
Loss after mini-batch  4000: 1.648
Loss after mini-batch  4500: 1.620
Loss after mini-batch  5000: 1.648


显然,但也不足为奇,基于批量标准化的模型表现得更好。


总结


在本文中,你了解了如何使用PyTorch深度学习库实现批量标准化(Batch Normalization)。批量标准化是一种于2015年提出的技术,用于规范化神经网络每一层的输入数据。这可以确保你的神经网络更快地训练,从而更早收敛,为你节省宝贵的计算资源。

文章来源:https://medium.com/@francescofranco_39234/batch-normalization-with-pytorch-20cdc205377f
欢迎关注ATYUN官方公众号
商务合作及内容投稿请联系邮箱:bd@atyun.com
评论 登录
热门职位
Maluuba
20000~40000/月
Cisco
25000~30000/月 深圳市
PilotAILabs
30000~60000/年 深圳市
写评论取消
回复取消