批量规范化|现代卷积神经网络|动手学深度学习

16 阅读7分钟

1. 在使用批量规范化之前,我们是否可以从全连接层或卷积层中删除偏置参数?为什么?

在使用批量规范化(Batch Normalization,BN)之前,我们可以从全连接层或卷积层中删除偏置参数。这是因为批量规范化层本身已经包含了偏置(偏移)和缩放参数,用来规范化和调整输入的分布。

理由解释

  1. 批量规范化的原理:

    • 批量规范化的过程包括两个步骤:规范化和重新调整。
    • 规范化:将输入数据 xx 规范化为均值为 0,方差为 1 的数据: x^=xμσ2+ϵ\hat{x} = \frac{x - \mu}{\sqrt{\sigma^2 + \epsilon}} 其中,μ\muσ\sigma 分别是小批量数据的均值和标准差,ϵ\epsilon 是一个小常数,用于数值稳定性。
    • 重新调整:引入可学习的参数 γ\gamma(缩放)和 β\beta(偏移)对规范化后的数据进行重新调整: y=γx^+βy = \gamma \hat{x} + \beta 这里,γ\gammaβ\beta 是批量规范化层的可学习参数。
  2. 偏置参数的冗余:

    • 全连接层和卷积层中的偏置参数主要作用是对输出进行平移(偏移)。
    • 批量规范化层在重新调整步骤中已经有了 β\beta 参数,用于对规范化后的数据进行偏移。因此,全连接层和卷积层中的偏置参数变得冗余。

实际操作

假设我们有一个全连接层或者卷积层,使用 PyTorch 实现如下:

import torch.nn as nn

# 带偏置参数的卷积层
conv_with_bias = nn.Conv2d(in_channels=64, out_channels=128, kernel_size=3, padding=1, bias=True)
# 带偏置参数的全连接层
fc_with_bias = nn.Linear(in_features=1024, out_features=512, bias=True)

在使用批量规范化时,我们可以删除这些层中的偏置参数:

# 不带偏置参数的卷积层
conv_without_bias = nn.Conv2d(in_channels=64, out_channels=128, kernel_size=3, padding=1, bias=False)
# 不带偏置参数的全连接层
fc_without_bias = nn.Linear(in_features=1024, out_features=512, bias=False)

# 添加批量规范化层
bn_conv = nn.BatchNorm2d(num_features=128)
bn_fc = nn.BatchNorm1d(num_features=512)

完整的神经网络模块

下面是一个带有批量规范化的卷积块的示例,其中删除了卷积层的偏置参数:

class ConvBlock(nn.Module):
    def __init__(self, in_channels, out_channels, kernel_size, padding):
        super(ConvBlock, self).__init__()
        self.conv = nn.Conv2d(in_channels, out_channels, kernel_size=kernel_size, padding=padding, bias=False)
        self.bn = nn.BatchNorm2d(out_channels)
        self.relu = nn.ReLU()

    def forward(self, x):
        x = self.conv(x)
        x = self.bn(x)
        x = self.relu(x)
        return x

# 实例化并应用卷积块
conv_block = ConvBlock(64, 128, kernel_size=3, padding=1)

总结

在使用批量规范化之前,从全连接层或卷积层中删除偏置参数是可行且有益的。这是因为批量规范化层自带的 β\beta 参数已经实现了偏移操作,保留卷积层和全连接层的偏置参数只会造成冗余。因此,通过删除这些偏置参数,可以简化模型并减少不必要的计算。

2. 比较LeNet在使用和不使用批量规范化情况下的学习率。

  1. 绘制训练和测试准确度的提高。
  2. 学习率有多高?
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))

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.270, train acc 0.900, test acc 0.841
209880.1 examples/sec on cuda:0

image.png

net = nn.Sequential(
    nn.Conv2d(1, 6, kernel_size=5), nn.Sigmoid(),
    nn.AvgPool2d(kernel_size=2, stride=2),
    nn.Conv2d(6, 16, kernel_size=5), nn.Sigmoid(),
    nn.AvgPool2d(kernel_size=2, stride=2), nn.Flatten(),
    nn.Linear(256, 120), nn.Sigmoid(),
    nn.Linear(120, 84), nn.Sigmoid(),
    nn.Linear(84, 10))
d2l.train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu())
loss 0.485, train acc 0.811, test acc 0.787
268592.6 examples/sec on cuda:0

image.png

3. 我们是否需要在每个层中进行批量规范化?尝试一下?

4. 可以通过批量规范化来替换暂退法吗?行为会如何改变?

net = nn.Sequential(
    nn.Conv2d(1, 6, kernel_size=5), nn.Sigmoid(), nn.Dropout(p=0.5),
    nn.AvgPool2d(kernel_size=2, stride=2),
    nn.Conv2d(6, 16, kernel_size=5), nn.Sigmoid(), nn.Dropout(p=0.5),
    nn.AvgPool2d(kernel_size=2, stride=2), nn.Flatten(),
    nn.Linear(256, 120), nn.Sigmoid(),
    nn.Linear(120, 84), nn.Sigmoid(),
    nn.Linear(84, 10))
loss 0.586, train acc 0.769, test acc 0.778
260383.2 examples/sec on cuda:0

image.png

5. 确定参数 betagamma,并观察和分析结果。

print(*[(name, param.shape) for name, param in net.named_parameters()])
('0.weight', torch.Size([6, 1, 5, 5])) ('0.bias', torch.Size([6])) ('1.weight', torch.Size([6])) ('1.bias', torch.Size([6])) ('4.weight', torch.Size([16, 6, 5, 5])) ('4.bias', torch.Size([16])) ('5.weight', torch.Size([16])) ('5.bias', torch.Size([16])) ('9.weight', torch.Size([120, 256])) ('9.bias', torch.Size([120])) ('10.weight', torch.Size([120])) ('10.bias', torch.Size([120])) ('12.weight', torch.Size([84, 120])) ('12.bias', torch.Size([84])) ('13.weight', torch.Size([84])) ('13.bias', torch.Size([84])) ('15.weight', torch.Size([10, 84])) ('15.bias', torch.Size([10]))

6. 查看高级API中有关BatchNorm的在线文档,以查看其他批量规范化的应用。

image.png

7. 研究思路:可以应用的其他“规范化”转换?可以应用概率积分变换吗?全秩协方差估计可以么?

批量规范化(Batch Normalization, BN)已经证明在深度神经网络训练中具有显著效果,但还有其他“规范化”方法可以尝试。研究思路可以从以下几个方面展开,包括概率积分变换和全秩协方差估计等方法。

1. 概率积分变换(Probability Integral Transform)

概念

  • 概率积分变换将数据映射到其累积分布函数(CDF)上,通常用于将数据变换为均匀分布。

应用于神经网络

  • 优点:概率积分变换可以将数据标准化,消除异常值的影响,提高模型训练的稳定性。
  • 实施方法:可以在输入数据或者中间层输出上应用概率积分变换,将数据变换为标准正态分布(或其他目标分布)。

研究思路

  • 实现和实验:尝试在神经网络的输入和隐藏层上应用概率积分变换,观察其对训练稳定性和模型性能的影响。
  • 对比分析:与传统的批量规范化、层规范化等方法进行对比,评估其优缺点。

2. 全秩协方差估计(Full-Rank Covariance Estimation)

概念

  • 全秩协方差估计涉及对数据进行中心化,并使用样本协方差矩阵对数据进行白化(whitening),使数据在新坐标系下具有单位方差和零协方差。

应用于神经网络

  • 优点:全秩协方差估计可以去除数据中的线性相关性,减少内在冗余,提高模型训练的有效性。
  • 实施方法:在每个迷你批次上计算协方差矩阵并进行白化处理,将数据投影到无关的主成分空间。

研究思路

  • 实现和实验:在神经网络的输入和隐藏层上实现全秩协方差估计,评估其对模型收敛速度和最终性能的影响。
  • 计算复杂度:分析这种方法的计算复杂度和实际应用中的可行性,考虑通过近似方法降低计算开销。

其他规范化方法

1. 层规范化(Layer Normalization)

  • 概念:在每个神经元的层级上进行规范化,不依赖于批量数据。
  • 应用:适用于 RNN 等对序列数据敏感的模型。

2. 实例规范化(Instance Normalization)

  • 概念:在每个样本的基础上进行规范化,常用于图像风格转换。
  • 应用:图像生成任务中常用。

3. 组规范化(Group Normalization)

  • 概念:将通道分成若干组,在每组内进行规范化。
  • 应用:适用于小批量或单样本训练的情况。

实验设计与比较

在研究这些规范化方法时,可以设计以下实验步骤:

  1. 数据预处理和规范化应用

    • 在训练开始前,对数据进行相应的规范化处理。
  2. 模型训练和测试

    • 在标准数据集(如CIFAR-10,MNIST等)上训练和测试不同规范化方法的模型。
  3. 性能评估

    • 对比不同规范化方法对训练速度、收敛稳定性、最终测试准确度和计算开销的影响。
  4. 参数敏感性分析

    • 研究不同参数设置对规范化方法性能的影响,找出最优参数组合。
  5. 鲁棒性测试

    • 在不同数据集和不同网络架构上验证规范化方法的鲁棒性和通用性。

结论

通过上述研究,可以深入了解不同规范化方法的优缺点,并根据实际应用需求选择最合适的方法。此外,结合多个规范化方法,可能会得到更优的效果,为神经网络训练提供更好的工具和技术支持。