首页 / PyTorch 入门教程 / 归一化层:BatchNorm 与 LayerNorm

PyTorch 入门教程

归一化层:BatchNorm 与 LayerNorm

本教程共 60 篇 · 第 28 篇 · 更新于 2026-08-17 · 约 4 分钟阅读

PyTorchBatchNormLayerNorm归一化批归一化深度学习入门

本节目标:搞懂归一化层为什么能让训练更稳,会用 BatchNorm 和 LayerNorm,并分清它们各自的适用场景。

为什么需要归一化

想象你在搭积木塔,越搭越高,手一抖整座塔就晃。深层网络训练也有类似的烦恼:前面层参数一更新,后面层收到的输入分布就跟着变,后面层刚学会的规律又失效了。这个现象叫内部协变量偏移(Internal Covariate Shift),层数越深越严重。

分布漂移还会连累数值稳定性。激活值一会儿大一会儿小,梯度也跟着忽大忽小,训练像踩在摇晃的船板上。归一化层的思路很简单:不管输入怎么漂,先把它按统一的均值和方差「整容」一遍,再交给下一层。每层都站在稳定的起跑线上,学习率可以开大,训练又快又稳。

归一化还有一层好处:它把每层输入的尺度限制在稳定范围内,梯度的大小也跟着稳,深层网络才训得动。可以说,没有归一化,就没有今天的深度网络。

BatchNorm:按批次归一化

批归一化(Batch Normalization,简称 BatchNorm)是卷积网络的标配。它对每个通道做两步:

  1. 算出当前 batch 内、同一通道上的均值 μ 和方差 σ²
  2. 每个值减去均值、除以标准差,再乘 γ、加 β

γ 和 β 是可学习的参数,给网络留了「反悔」的空间。如果原始分布本来就好,网络自己会把它学回来,不会白白抹掉信息。

PyTorch 按数据维度分了三个版本:全连接层和序列用 BatchNorm1d,图像用 BatchNorm2d,视频和医学影像用 BatchNorm3d。用法完全一样,只是输入形状不同:

import torch
import torch.nn as nn

# 2D:图像 (batch, channels, H, W)
bn = nn.BatchNorm2d(64)
x = torch.randn(32, 64, 28, 28)
out = bn(x)
print(out.shape)   # torch.Size([32, 64, 28, 28])

# 1D:全连接 (batch, features)
bn1d = nn.BatchNorm1d(128)
x = torch.randn(32, 128)
print(bn1d(x).shape)   # torch.Size([32, 128])

每个通道有独立的 γ、β、running_mean、running_var,形状都是 [64]。BatchNorm 摆在哪里?最主流的顺序是「卷积 → BatchNorm → 激活函数」,也就是 Conv → BN → ReLU。

训练和推理:两套统计量

BatchNorm 最迷惑人的地方,是它训练和推理时行为不一样:

模式用谁的统计量
model.train()当前 batch 的均值/方差,并更新 running_mean/var
model.eval()训练中攒下的 running_mean/var,固定不动

推理时往往只有一张图进来,batch 统计量没法算,所以要用训练时积累的滑动平均。两个统计量按 momentum 混合:

running_mean = (1 - momentum) × 旧值 + momentum × batch均值

注意 PyTorch 的 momentum 和直觉相反:默认 0.1 表示新 batch 只占 10% 权重,历史值占 90%。batch 很小时统计量波动大,可以把 momentum 调小(比如 0.01)。调 momentum 的经验:batch 越大统计量越稳,保持默认就行;batch 越小,越要调小 momentum,让统计量跟得上数据变化。

Warning

忘了 model.eval() 是高频事故。评估时模型还在训练模式,会用 batch 统计量、还顺手更新 running 统计量,结果忽高忽低。上一章的 Dropout 也吃这个亏。

LayerNorm:不依赖批次的另一个选择

BatchNorm 有个软肋:batch 太小(比如只有 1 或 2 个样本)时,batch 统计量不可靠,甚至没法训练。层归一化(Layer Normalization,简称 LayerNorm)换个维度做归一化——对单个样本的所有特征求均值方差,完全不看 batch。

ln = nn.LayerNorm(512)
x = torch.randn(32, 10, 512)   # (batch, seq_len, features)
out = ln(x)                    # 对最后一维 512 个特征归一化
print(out.shape)               # torch.Size([32, 10, 512])

LayerNorm 是 Transformer 的标配,BERT、GPT 里到处都是。因为 Transformer 训练时序列长度常变、batch 大小不固定,BatchNorm 撑不住。LayerNorm 和 BatchNorm 不冲突,各管一摊:图像任务里 CNN 爱用 BatchNorm,文本任务里 Transformer 标配 LayerNorm,有些模型也会两者都用、各取所长。

其他归一化:一张表看懂

归一化家族的差异,只在「对哪几个维度求均值方差」:

方法归一化范围依赖 batch典型场景
BatchNorm同一通道、跨样本大 batch 的 CNN
LayerNorm单样本、跨特征Transformer、NLP
GroupNorm单样本、通道分组内小 batch 的检测、分割
InstanceNorm单样本单通道的空间维风格迁移、GAN

记两条就够用:CNN 图像任务、batch 不小于 16,用 BatchNorm2d;Transformer 或 batch 不固定,用 LayerNorm。小 batch 的检测分割场景,GroupNorm 是 BatchNorm 的直接替代。InstanceNorm 则常用于风格迁移:每张图只按自己的统计量归一化,batch 里其他图的信息不会串味。它的参数也好懂:把通道分成几组,组内做归一化:

gn = nn.GroupNorm(num_groups=32, num_channels=256)
x = torch.randn(4, 256, 56, 56)   # batch 只有 4,照样正常
print(gn(x).shape)                # torch.Size([4, 256, 56, 56])

一个完整的小模型

把学到的拼起来,一个典型的卷积块长这样:

class ConvBlock(nn.Module):
    def __init__(self, in_ch, out_ch):
        super().__init__()
        self.block = nn.Sequential(
            nn.Conv2d(in_ch, out_ch, 3, padding=1, bias=False),
            nn.BatchNorm2d(out_ch),
            nn.ReLU(inplace=True),
        )

    def forward(self, x):
        return self.block(x)

注意卷积层的 bias=False:BN 的 β 已经承担了偏置的角色,卷积的 bias 纯属白占内存。这个块的顺序也有讲究:先卷积提特征,再归一化稳住分布,最后激活函数引入非线性。

三个常见坑

  1. batch=1 时 BatchNorm 训练会报错。每个通道只有一个值,方差没法算,PyTorch 直接抛「Expected more than 1 value per channel」的异常。换 GroupNorm、LayerNorm,或推理时切 eval()。还有个反直觉的点:同样的代码在 eval 模式下跑单张图完全正常,因为用的是 running 统计量。
  2. 卷积接了 BatchNorm,就别再要 bias。原因上面刚说过,省内存也少一份参数。
  3. 迁移学习冻结主干,别忘冻结 BN。只设 requires_grad=False 拦不住 running 统计量更新,预训练统计会被新数据污染。正确做法是让主干的 BN 层保持 eval() 状态。

排查训练异常时,如果验证 loss 忽高忽低,先怀疑 model.eval() 是不是漏了;训练出现 NaN,先查 batch size 是不是 1。这两条能省下不少调试时间。

想确认模式切对了,可以跑两遍同样的输入:训练模式下两次输出应该不同(Dropout 在捣乱),评估模式下两次输出应该完全一样。如果反过来了,就是模式没切对。

顺带一提:推理时的 Conv-BN 融合

模型训完做推理时,BatchNorm 只剩乘乘加加,数学上可以把它「融」进前一层的卷积权重里,等价转换、白赚速度。torch.compile 现在会自动做这种融合,你不用手写。哪天在推理代码里找不到 BN 层了,别慌,它只是被优化掉了。融合之后模型文件更小,推理也更快。

到这里,训练稳定性的两大支柱——正则化和归一化——你都掌握了。下一章聊聊参数初始化,看看模型的「起跑姿势」有多重要。