Skip to content

第 11 章 BatchNorm:训练时看一批,推理时用固定统计量 ​

初始化只安排了出发时的尺度。训练更新参数以后,中间信号仍会改变。Batch Normalization,简称 BatchNorm 或 BN,在网络中增加一项操作:按特征收集一组观测,减去它们的均值,再除以尺度,最后允许网络学习一次缩放和平移。

它不是把整个矩阵统一压成一个标准正态分布,也不会让数据自动变成正态分布。本章要弄清三件事:统计哪些数,谁通过梯度学习,以及一个样本单独推理时该用谁的统计量。原论文提供训练与推理算法;课程则用手写层把它们拆开。BatchNorm 原论文

11.1 一列一列地算,而不是一行一行地算 ​

设激活前矩阵 A∈RB×H。B 是批次中的样本数,H 是隐藏特征数。对第 j 列,定义均值、方差和归一化输出:

μj=1B∑b=1BAbj,vj=1B−c∑b=1B(Abj−μj)2,A^bj=Abj−μjvj+ε,Ybj=γjA^bj+βj.

这里 c 是方差的校正数,本书用它明确区分分母为 B 的 correction=0 与分母为 B−1 的 correction=1。ε>0 是防止零方差除法的稳定项,例子取 10−5。γ,β∈RH 是可训练参数。均值与方差来自本批激活,不是梯度优化器直接维护的自由参数。

拿三行两列来算:

A=[110314518].

均值是 [3,14]。减去均值后,两列分别为 [−2,0,2] 与 [−4,0,4]。平方和是 8 与 32。除以 B=3,方差为 [8/3,32/3];除以 B−1=2,得到 [4,16]。若采用后一种且暂时忽略很小的 eps,两列的归一化结果都成为 [−1,0,1]。这个例子还说明:归一化只调整位置和尺度,三个离散点并没有变成钟形曲线。

A.mean(0, keepdim=True) 的结果形状是 1×H。广播时,它沿样本轴复制成 B×H,每一行减去同一份列均值。keepdim=True 把被消去的维度留为 1,使这层意图直接写在形状里。若误写 mean(1),你是在每个样本内部混合不同特征,算法已经变了。

11.2 Bessel 校正与原生 PyTorch 的区别 ​

均值是用同一批数据估计的,中心化残差有一个约束:它们之和为零。估计总体方差时,除以 B−1 可得到通常所说的无偏样本方差;这就是 Bessel 校正。它并不意味着训练 BN 时“必须”用 B−1。训练前向可以有意使用分母 B,而运行统计量采用另一种估计。

课程手写实现与 torch.nn.BatchNorm1d 不完全一致。 第 4、5、6 讲 notebook 使用 std() 或 var() 的默认样本校正;第 5 讲更明确写出 1/(B−1)。本书的 BatchNorm(correction=1) 保留这个教学约定,所以后面手推梯度会出现 B−1。原生 PyTorch 2.6 训练前向使用 correction=0 的批方差,写入 running_var 的批方差估计则使用 correction=1。

配套实验直接用上面的三行矩阵检查已安装的原生层:设 momentum=1, affine=False,前向输出与除以 B 的手算一致,running_var 却等于 [4,16]。这比只记“BatchNorm 用哪种方差”可靠:必须同时说清前向还是运行统计量。PyTorch v2.6.0 BatchNorm 源码及类文档

11.3 为什么还要 gamma 和 beta ​

如果 BN 永远强制每列均值为零、尺度固定,网络就失去了一部分表达选择。后接 tanh 时,缩放决定多少输入进入饱和区,平移决定工作区间。可训练的 γj 与 βj 把这些选择交还给优化器:默认从 γ=1,β=0 开始,再按任务需要改变。

BN 前面的线性偏置通常可以删掉。假设 A=XW+b,训练批均值为 μA=μXW+b,则

A−μA=XW+b−(μXW+b)=XW−μXW.

同一个偏置被减掉了。BN 自己已有 beta 可负责输出平移。这个消去关系也解释了第 14 章里 BN 前偏置的梯度为何理论上为零;浮点计算可能留下很小的残差。它不表示任意位置的偏置都可以删除:最后一个没有后接 BN 的分类层偏置仍然有作用。

11.4 训练会让同批样本彼此影响 ​

把批次中的一个数从 1 改成 100,会同时改变列均值与列方差,因此同列其他样本的归一化结果也会变化。BN 不是逐样本独立的函数。这既带来一定的随机扰动,也制造了使用限制:小批次、彼此高度相关的样本、训练与部署数据分布差异,都可能使统计量不可靠。

课程 notebook 的附加 widget 演示了这个现象:只拖动五个输入中的一个,五个输出都会重新移动。这部分在 notebook 中标为视频之外的 bonus。本书用前面的三点例子即可检查:把第一列的 1 改成 9,新的列为 [9,3,5],均值从 3 变成 17/3,原来为 0 的中间残差不再是 0。不要把“标准化”误解成每个输入各自除以自己的大小。

另一个直接后果是:用校正数 1 训练时,每个通道必须至少有两个观测。只有一个观测,分母为零。即便使用校正数 0,单观测的中心化结果仍为零,网络也无法靠它恢复有用的相对差异。配套层默认使用 correction=1,会明确拒绝每通道仅一个观测的训练输入。

11.5 推理不该随着同行请求改变 ​

假设线上一次请求只生成一个名字。若还使用训练分支,它既缺少可靠批统计量,也会让预测受一起入批的其他请求影响。推理时应冻结统计量,使用训练期间积累的均值与方差:

Y=γA−μrunvrun+ε+β.

常见积累方式是指数移动平均。设本批均值为 μbatch,更新比例为 m,则

μrun←(1−m)μrun+mμbatch.

方差同理。若旧均值为 10、新批均值为 20、m=0.1,更新后为 11。这里的 momentum 指新统计量占多少,与 SGD 优化器中动量的参数含义不同。

running_mean 和 running_var 是缓冲状态,不放进 parameters(),不由损失梯度更新。更新时放进 torch.no_grad(),避免为移动平均不断连接历史计算图。eval() 切换 BN 使用的分支;no_grad() 关闭自动求导记录。两者做的事不同,推理一般需要同时使用。

第 4 讲早期代码对标准差直接做移动平均;后来的手写层对方差做移动平均并在使用时开方。两种量不能互换:EMA(v) 一般不等于 EMA(v)。迁移或恢复模型时,必须知道保存的是哪一种。本书统一保存方差。

另一种教学做法是在训练结束后,固定权重,用训练数据重新估计统计量。第 5 讲对单隐藏层这样校准。不能用验证集或测试集来“帮忙校准”,否则评估已经使用了待评估数据。多层网络的校准还涉及前层状态如何影响后层分布,不能简单把单层做法不加说明地复制到所有层。

11.6 官方 E02:把 BN 折进线性层 ​

推理统计量固定后,BN 是逐通道仿射变换。设上一层 A=XW+b,令

sj=γjvrun,j+ε.

展开后得到

Y=(XW+b−μrun)⊙s+β=XW′+b′,

其中 Wij′=Wijsj,bj′=(bj−μrun,j)sj+βj。s∈RH 沿权重的输入轴广播,也就是缩放 W∈RF×H 的每一列。若采用原生 Linear 的权重方向,则对应缩放每一行。

例如单输出层 W=[2,3]T,b=1,μ=5,v=4,γ=2,β=7,忽略 eps 时 s=1,折叠后 W′=W,b′=3。原来先算 2x1+3x2+1,再减 5、加 7,显然与一次线性计算一致。

配套 fold() 实现完整公式,包括 eps 和“前层无偏置”的情况。batchnorm_tests() 先训练一个包含三层 Linear、两层 BN 的小网络,再删除两个 BN、把参数折进相邻 Linear。在九个新输入上的最大输出误差为 4.44×10−16。这是 float64 舍入范围内的一致,训练模式则不能这样固定折叠,因为每个批次会产生不同的均值和方差。

11.7 练习、提示与答案 ​

  1. 官方 E02。 先不看 fold(),实现三层 MLP 的推理折叠。验证随机输入、单样本输入、批次输入,并检查删除 BN 前后参数数目如何变化。
  2. 补充。 为什么 with torch.no_grad(): model(x) 仍可能修改 running_mean?
  3. 补充。 一列观测数为 4,用 B−1 标准化且忽略 eps 后,这列以分母 B 计算的方差是多少?

第一题答案就是上一节的列缩放公式;必须先 eval(),测试时比较 logits 而非只比较 argmax,因为很大的数值误差也可能保留同一个最高类别。BN 的 gamma、beta 被吸收到新权重与偏置,不再单列为独立参数。第二题中 no_grad() 不会改变 training=True,训练分支仍会执行缓冲更新。第三题是 3/4,因为标准化时使用的分母与再次测量时不同;这不是实现坏了。

完整实现见 examples/part2/layers.py,验证见 examples/part2/experiments.py 的 batchnorm_tests(),实测记录见 qa/part2/experiments.json。

来源与范围 ​

  • 课程第 4 讲与固定 notebook:手写归一化、运行统计量、校准、推理、bonus widget 与 E02。
  • BatchNorm 论文:原始算法与推理变换;本章公式逐项展开及数值例子为原创教学。
  • PyTorch v2.6.0 源码:训练/推理状态与方差约定。课程对 ResNet-50 的代码浏览由第 12 章补足,不在这里冒充复现整网训练。

保留各书来源与许可 · 示例运行条件以正文为准