在神经网络建模的良好实践中被认为是关键要素之一的技术是一种称为批量归一化的技术。通过让你的神经网络在所有层中使用归一化的输入,这种技术可以确保模型更快地收敛,因此训练所需的计算资源更少。
在本文中,我们将专注于用PyTorch实现的批量归一化。
什么是批量归一化?
训练神经网络是根据高层次的监督式机器学习过程进行的。一批数据通过模型,然后将其预测与输入的实际或真实值进行比较。
这种差异导致了所谓的损失值,可以用于随后的误差反向传播和模型优化。
优化模型涉及稍微调整模型中可训练层的权重。到目前为止一切都很好。但是,现在假设你有以下情况:
你可以想象,考虑到模型的权重,它在处理第二批数据时将相对较差 - 因此,权重变化显著。因此,你的模型在处理第三批数据时也会比它本可以的更差,仅仅因为它已经适应了显著偏离的情况。
而且,尽管它可以随着时间学会恢复到更通用的过程,你可以看到,在你的数据集中存在相对不稳定性时(甚至在相对归一化的数据集中,由于此类效应在下游层中发生),模型优化将大幅震荡。这很糟糕,因为它放慢了训练过程。
批量归一化是一种可以在层级上应用的归一化技术。简单来说,它规范了“每层的输入到一个学习到的表示,可能接近(μ = 0.0, σ = 1.0)。因此,所有层的输入都被规范化了,显著的异常值不太可能以负面方式影响训练过程。即便如此,它们的影响也将比不使用批量归一化时小很多。
训练深度神经网络的复杂性在于,由于先前层的参数改变,每一层的输入分布在训练过程中会发生变化。这通过要求较低的学习率和小心的参数初始化来减慢训练,并使得难以训练具有饱和非线性的模型闻名。我们将这种现象称为内部协变量偏移,并通过规范层输入来解决这个问题。我们的方法得力于使规范化成为模型架构的一部分,并对每个训练小批次执行规范化。批量归一化使我们可以使用更高的学习率,对初始化不那么挑剔,在某些情况下消除了对Dropout的需要。应用于最先进的图像分类模型,批量归一化以14倍更少的训练步骤达到了相同的准确性,并以显著的优势击败了原始模型。使用一组批量归一化的网络,我们改善了在ImageNet分类上公布的最佳结果:达到4.82%的前5测试误差,超过了人类评估员的准确性。
用PyTorch进行批量归一化
现在我们已经对批量归一化有了基本的了解,以及为什么需要它,我们将继续用深度学习库PyTorch来实现它。
BatchNorm2d和BatchNorm1d之间的差异
首先,PyTorch中的二维批量归一化和一维批量归一化之间的差异。
PyTorch 网站上一维 Batch Normalization 的定义如下:
在2D或3D输入上应用批量归一化(具有可选附加通道维度的小批量 1D 输入)(…)
PyTorch(n.d.)
…这是二维批量归一化被描述的方式:
对 4D 输入应用批量归一化(具有附加通道维度的小批量 2D 输入)(...)
PyTorch(nd)
让我们总结:
批量归一化的4D、3D和2D输入
那么,什么是“4D输入”?PyTorch如此描述:(N, C, H, W)
换句话说,一个nn.BatchNorm2d层的4D输入代表了一组N个对象,每个对象都有一个高度和一个宽度,始终具有大于等于1的频道数量。
nn.BatchNorm1d代表了低维度输入:许多输入,可能是频道数,以及每个对象的内容。就像神经网络中密集层产生的那些规则的一维数组。
我们现在知道我们必须将nn.BatchNorm2d应用于处理图像的层。主要的是卷积层,它们在图像上滑动,以生成它们更抽象的表示。nn.BatchNorm1d可以用于堆叠在卷积层之上的密集层,以生成分类。
在神经网络中使用批量归一化
现在我们知道了在神经网络的哪种类型层中必须应用哪种类型的批量归一化,我们可以思考一下在哪里应用批量归一化在我们的神经网络中。
编写神经网络 + 训练循环
声明导入
首先,我们要声明我们的导入。
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)
编写训练循环
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年提出的技术,用于规范化神经网络每一层的输入数据。这可以确保你的神经网络更快地训练,从而更早收敛,为你节省宝贵的计算资源。