BN层如何减少过拟合:从数学原理到PyTorch实战

1次阅读
没有评论

共计 2569 个字符,预计需要花费 7 分钟才能阅读完成。

image.webp

为什么我们需要 BN 层?

深度神经网络训练过程中,过拟合始终是个令人头疼的问题。过拟合的诱因很多,但最核心的两个问题是 参数协变偏移(Internal Covariate Shift) 梯度消失。随着网络层数加深,每层输入的分布会随着参数更新而不断变化,导致后续层需要不断适应新的数据分布,这种现象就是参数协变偏移。这会让训练过程变得不稳定,需要更小的学习率和更谨慎的参数初始化。

BN 层如何减少过拟合:从数学原理到 PyTorch 实战

Batch Normalization(BN)的提出正是为了解决这些问题。它通过对每一层的输入进行规范化,强制使其保持稳定的分布,从而减少内部协变量偏移的影响。同时,BN 还通过引入可学习的缩放参数 γ 和平移参数 β,保留了网络的表达能力。

BN 层的数学原理

BN 层的核心操作可以用以下公式表示:

对于一个小批量数据 $\mathcal{B} = {x_1, …, x_m}$,BN 层首先计算该批量的均值和方差:

$$\mu_\mathcal{B} = \frac{1}{m}\sum_{i=1}^m x_i$$

$$\sigma_\mathcal{B}^2 = \frac{1}{m}\sum_{i=1}^m (x_i – \mu_\mathcal{B})^2$$

然后用这些统计量对输入进行标准化:

$$\hat{x}i = \frac{x_i – \mu\mathcal{B}}{\sqrt{\sigma_\mathcal{B}^2 + \epsilon}}$$

最后引入可学习的参数 γ 和 β 进行缩放和偏移:

$$y_i = \gamma \hat{x}_i + \beta$$

其中 γ 和 β 是需要学习的参数,它们让网络可以自主决定是否使用标准化后的结果。

训练与推理阶段的差异

在训练阶段,BN 层使用当前小批量的统计量进行标准化。但在推理阶段,我们通常使用整个训练集的统计量。PyTorch 中通过 running_meanrunning_var来实现这一点,它们是在训练过程中通过指数移动平均(EMA)累积得到的:

running_mean = momentum * running_mean + (1 - momentum) * batch_mean
running_var = momentum * running_var + (1 - momentum) * batch_var

PyTorch 实现

下面是一个完整的 BN 层 PyTorch 实现:

import torch
import torch.nn as nn

class CustomBatchNorm1d(nn.Module):
    def __init__(self, num_features, eps=1e-5, momentum=0.1):
        super().__init__()
        self.num_features = num_features
        self.eps = eps
        self.momentum = momentum

        # 可训练参数
        self.gamma = nn.Parameter(torch.ones(num_features))
        self.beta = nn.Parameter(torch.zeros(num_features))

        # 非可训练参数
        self.register_buffer('running_mean', torch.zeros(num_features))
        self.register_buffer('running_var', torch.ones(num_features))

    def forward(self, x):
        if self.training:
            # 训练模式
            mean = x.mean(dim=0)
            var = x.var(dim=0, unbiased=False)

            # 更新 running_mean 和 running_var
            with torch.no_grad():
                self.running_mean = self.momentum * mean + (1 - self.momentum) * self.running_mean
                self.running_var = self.momentum * var + (1 - self.momentum) * self.running_var
        else:
            # 推理模式
            mean = self.running_mean
            var = self.running_var

        # 标准化
        x_hat = (x - mean) / torch.sqrt(var + self.eps)
        # 缩放和平移
        y = self.gamma * x_hat + self.beta
        return y

BN 如何减少过拟合

BN 减少过拟合主要通过两个机制:

  1. 内部协变量偏移的抑制:通过规范化每层的输入分布,BN 减少了网络对参数初始化和学习率的敏感性,使得训练更加稳定。

  2. 噪声注入效应:由于 BN 在训练时使用小批量统计量,这相当于给网络注入了噪声,起到了类似 Dropout 的正则化效果。

与 Dropout 和 L2 正则化相比,BN 的正则化效果更加 ” 智能 ”,它不会像 Dropout 那样直接丢弃部分神经元,也不会像 L2 那样对所有参数施加相同的惩罚。

生产环境中的注意事项

在小批量场景下,BN 的表现可能会变差。这时可以考虑使用 Group Normalization(GN)或 Weight Normalization(WN)作为替代:

# GroupNorm 示例
self.norm = nn.GroupNorm(num_groups=32, num_channels=128)

# WeightNorm 示例
self.conv = nn.utils.weight_norm(nn.Conv2d(3, 64, 3))

在模型导出时,为了提升推理效率,通常会将 Conv 层和 BN 层合并(称为 Conv-BN 折叠)。ONNX 格式的模型会自动完成这个优化:

torch.onnx.export(model, dummy_input, "model.onnx")

开放性问题

虽然 BN 在 CNN 中表现出色,但在 Transformer 架构中却很少使用。这主要是因为:

  1. Transformer 通常使用 LayerNorm,它在序列数据上表现更好
  2. Transformer 的自注意力机制本身已经具有一定的规范化效果

对于动态网络(如 NAS),BN 的适应性是一个挑战,因为网络结构可能会变化。解决方案包括:

  1. 使用可微分的 BN 参数
  2. 在网络结构变化时重新初始化 BN 层的统计量

BN 仍然是深度学习中的基石技术之一,理解它的原理和实现细节对于构建高效的神经网络至关重要。

正文完
 0
评论(没有评论)