共计 1770 个字符,预计需要花费 5 分钟才能阅读完成。
背景痛点:内部协变量偏移
在深度神经网络的训练过程中,每一层的输入分布会随着前一层参数更新而不断变化,这种现象被称为内部协变量偏移(Internal Covariate Shift)。这会导致几个严重问题:

- 网络需要不断适应新的数据分布,导致收敛速度变慢
- 梯度更新变得不稳定,容易出现梯度消失或爆炸
- 需要使用较小的学习率,进一步拖慢训练速度
- 对参数初始化非常敏感
在 CNN 中,这些问题尤其明显,因为卷积层的输出分布会随着滤波器权重变化而剧烈波动。
技术对比:BN vs 其他归一化方法
- 批量归一化(BN):
- 按 batch 维度计算统计量
- 特别适合 CNN,因为保持空间位置间的统计一致性
-
训练时需要足够大的 batch size(通常 >16)
-
层归一化(Layer Norm):
- 按特征维度计算统计量
-
适合 RNN 和小 batch size 场景
-
实例归一化(Instance Norm):
- 对每个样本的每个通道单独归一化
- 常用于风格迁移任务
BN 在 CNN 中的优势在于它能保持卷积特有的平移不变性,同时稳定了中间特征的分布。
数学原理
BN 操作可以分为两个阶段:
-
标准化阶段:
$$
\hat{x} = \frac{x – \mu_\mathcal{B}}{\sqrt{\sigma_\mathcal{B}^2 + \epsilon}}
$$
其中 $\mu_\mathcal{B}$ 和 $\sigma_\mathcal{B}^2$ 是当前 batch 的均值和方差。 -
缩放平移阶段:
$$
y = \gamma \hat{x} + \beta
$$
可学习的参数 $\gamma$ 和 $\beta$ 让网络能够恢复原始特征的表达能力。
PyTorch 实战
基础实现
import torch
import torch.nn as nn
class ConvBNReLU(nn.Module):
def __init__(self, in_channels: int, out_channels: int, kernel_size: int):
super().__init__()
self.conv = nn.Conv2d(in_channels, out_channels, kernel_size, padding=kernel_size//2, bias=False)
self.bn = nn.BatchNorm2d(out_channels)
self.relu = nn.ReLU(inplace=True)
def forward(self, x: torch.Tensor) -> torch.Tensor:
return self.relu(self.bn(self.conv(x)))
推理模式下的 BN
在 model.eval() 模式下,BN 层会使用训练时计算的 running_mean 和 running_var,而不再更新这些统计量:
model = ConvBNReLU(3, 64, 3)
model.train() # 训练模式下会更新 running stats
# ... 训练过程...
model.eval() # 推理模式下使用固定的 running stats
性能考量
Batch Size 敏感性
根据原始论文 (Ioffe & Szegedy, 2015) 的实验结果:
- batch size=32 时,ImageNet 训练达到相同准确率所需迭代次数减少 14 倍
- batch size 过小 (如 <8) 时,统计量估计不准确,反而可能损害性能
多 GPU 训练
在分布式训练中,标准的 BN 在每个 GPU 上单独计算统计量,这会导致:
- 不同 GPU 看到的统计量不一致
- 等效 batch size 变小
解决方案是使用同步 BN(SyncBN),它会跨设备聚合统计量:
# PyTorch 中的实现
bn_layer = nn.SyncBatchNorm(num_features)
避坑指南
小 batch size 替代方案
- 使用 Group Normalization
- 结合 Layer Norm 和 Instance Norm
- 在多个 batch 上累积统计量
微调策略
当微调预训练模型时:
- 冻结 BN 层的 running_mean 和 running_var
- 保持 BN 的 $\gamma$ 和 $\beta$ 可训练
- 使用足够大的学习率(比随机初始化时小 10 倍)
思考题
在目标检测任务中,RoI 区域的特征归一化可以采用:
- RoI-Align 后对每个 RoI 单独计算统计量
- 使用全图的均值和方差进行归一化
- 引入可学习的归一化参数
哪种方法最好?这可能取决于具体的检测框架和任务需求。
