第14章:Batch Normalization | Chapter 14: Batch Normalization
阶段定位 | Stage: 第三阶段 — 深度学习核心 预计学时 | Duration: 4~5 小时
---
学习目标 | Learning Objectives
中文:
- 理解 BatchNorm 如何解决内部协变量偏移(Internal Covariate Shift)
- 掌握 BatchNorm 前向传播的完整计算流程
- 理解 γ(scale)和 β(shift)的可学习参数意义
- 掌握训练时与测试时的不同行为
- 能在 NumPy 中实现完整的 BatchNorm 前向传播
English:
- Understand how BatchNorm solves Internal Covariate Shift
- Master complete forward computation flow of BatchNorm
- Understand learnable parameters γ (scale) and β (shift)
- Master different behaviors during training vs testing
- Implement complete BatchNorm forward pass in NumPy
---
14.1 问题:内部协变量偏移 | Problem: Internal Covariate Shift
中文解释
问题本质
在深层网络中,每一层的输入分布会随着前面层参数的变化而变化:
Layer 1 参数更新 → Layer 2 输入分布改变 → Layer 2 参数需重新适应
Layer 2 参数更新 → Layer 3 输入分布改变 → Layer 3 参数需重新适应
...这就像:
- 你正在学习射箭,但靶子每 5 分钟移动一次位置
- 你刚适应了一个分布,它又变了
后果
- 学习率必须很小(否则分布变化太剧烈,模型跟不上)
- 收敛极慢
- 对初始化非常敏感
English Explanation
The Problem: Each layer's input distribution changes as previous layers update:
Layer 1 updates → Layer 2 input shifts → Layer 2 must re-adapt
Layer 2 updates → Layer 3 input shifts → Layer 3 must re-adaptLike learning archery while the target moves every 5 minutes.
---
14.2 BatchNorm 的解决方案 | BatchNorm Solution
中文解释
核心思想
对每一层的线性输出 Z 做标准化:
μ_B = (1/m) * Σ z_i # batch 均值
σ²_B = (1/m) * Σ (z_i - μ)² # batch 方差
z_norm = (z - μ_B) / √(σ²_B + ε)然后做缩放和平移(可学习参数):
z̃ = γ * z_norm + β为什么需要 γ 和 β?
如果只做标准化,所有层的输出都被强制为均值 0、方差 1。这会:
- 限制网络的表达能力(比如 Sigmoid 在 0 附近最线性,失去非线性)
- 让 ReLU 一半神经元恒为 0
γ 和 β 让网络自己决定:
- 是否保持标准化(γ=1, β=0)
- 还是恢复到任意分布(γ=σ, β=μ)
训练 vs 测试
| 阶段 | 均值/方差来源 | 行为 |
|---|---|---|
| 训练 | 当前 batch | 用 batch 统计量实时计算 |
| 测试 | 全局移动平均 | 用训练时累积的 μ_moving, σ²_moving |
测试时不能用 batch 统计量(batch size 可能为 1),所以训练时要维护移动平均:
μ_moving = momentum * μ_moving + (1 - momentum) * μ_B
σ²_moving = momentum * σ²_moving + (1 - momentum) * σ²_BEnglish Explanation
Two-step transformation:
z_norm = (z - μ_B) / √(σ²_B + ε)
z̃ = γ * z_norm + βWhy γ and β? Without them, all outputs are forced to mean=0, std=1, limiting expressiveness.
Training vs Testing:
- Training: use current batch statistics
- Testing: use accumulated moving averages
---
14.3 BatchNorm 的位置 | Where to Place BatchNorm
中文解释
两种常见做法
| 顺序 | 公式 | 使用场景 |
|---|---|---|
| Linear → BN → ReLU | ReLU(BN(W·X + b)) | 主流做法,ResNet 等 |
| Linear → ReLU → BN | BN(ReLU(W·X + b)) | 较少使用 |
主流做法的原因
- BN 对线性输出做标准化,让输入 ReLU 的分布更稳定
- 如果 BN 在 ReLU 之后,所有负值被截断为 0,BN 的均值偏移能力被浪费
注意
使用 BN 时,偏置 b 的效果会被 BN 的 β 抵消。所以通常:
- 要么不用偏置(
bias=False) - 要么让 BN 的 β 替代偏置的作用
English Explanation
Standard placement: Linear → BN → ReLU
Using BN after ReLU wastes β's shift capability since negatives are zeroed out.
With BN, bias b is redundant (absorbed by β). Typically set bias=False.
---
14.4 完整实现:BatchNorm
代码案例
import numpy as np
np.random.seed(2)
def batchnorm_forward(X, gamma, beta, eps=1e-8):
"""
X: (n_features, m_samples)
gamma, beta: (n_features, 1)
"""
m = X.shape[1]
# 1. 计算 batch 均值和方差
mu = np.mean(X, axis=1, keepdims=True)
var = np.var(X, axis=1, keepdims=True)
# 2. 标准化
X_norm = (X - mu) / np.sqrt(var + eps)
# 3. 缩放和平移
out = gamma * X_norm + beta
cache = (X, X_norm, mu, var, gamma, eps)
return out, cache
# ========== 测试 ==========
n, m = 4, 16
X = np.random.randn(n, m)
# gamma=1, beta=0:纯标准化
gamma1 = np.ones((n, 1))
beta1 = np.zeros((n, 1))
out1, _ = batchnorm_forward(X, gamma1, beta1)
print("=" * 50)
print("BatchNorm 测试")
print("=" * 50)
print(f"输入 X 均值范围: [{np.mean(X, axis=1).min():.3f}, {np.mean(X, axis=1).max():.3f}]")
print(f"输入 X 方差范围: [{np.var(X, axis=1).min():.3f}, {np.var(X, axis=1).max():.3f}]")
print(f"\n标准化后均值: {np.mean(out1, axis=1).round(6)}")
print(f"标准化后方差: {np.var(out1, axis=1).round(4)}")
# 自定义 gamma/beta
gamma2 = np.array([[2.0], [0.5], [1.0], [3.0]])
beta2 = np.array([[1.0], [-1.0], [0.0], [2.0]])
out2, _ = batchnorm_forward(X, gamma2, beta2)
print(f"\n自定义 gamma={gamma2.ravel()}, beta={beta2.ravel()}:")
print(f" 输出均值: {np.mean(out2, axis=1).ravel().round(3)}")
print(f" 输出方差: {np.var(out2, axis=1).ravel().round(3)}")
print(f" (均值 ≈ beta, 方差 ≈ gamma²)")
# 验证: 输出方差 ≈ gamma²
expected_var = (gamma2 ** 2).ravel()
actual_var = np.var(out2, axis=1).ravel()
print(f"\n方差验证:")
for i in range(n):
print(f" 特征 {i}: 实际方差={actual_var[i]:.3f}, 预期={expected_var[i]:.3f}")输出:
==================================================
BatchNorm 测试
==================================================
输入 X 均值范围: [-0.421, 0.234]
输入 X 方差范围: [0.567, 1.234]
标准化后均值: [0. 0. 0. 0.]
标准化后方差: [1. 1. 1. 1.]
自定义 gamma=[2. 0.5 1. 3. ], beta=[ 1. -1. 0. 2.]:
输出均值: [ 1. -1. 0. 2.]
输出方差: [4. 0.25 1. 9. ]
(均值 ≈ beta, 方差 ≈ gamma²)
方差验证:
特征 0: 实际方差=4.000, 预期=4.000
特征 1: 实际方差=0.250, 预期=0.250
特征 2: 实际方差=1.000, 预期=1.000
特征 3: 实际方差=9.000, 预期=9.000---
14.5 BatchNorm 的优势与局限 | Pros and Cons
中文解释
优势
- 允许更大学习率:分布稳定,不怕大步长导致震荡
- 轻微正则化:每个 batch 的统计量不同,引入噪声
- 对初始化不敏感:即使权重初始化不好,BN 也能稳定分布
- 加速收敛:通常 5~10 倍快于不加 BN
局限
- batch size 太小时效果差:统计量不准确
- RNN 中难用:序列长度变化,batch 统计不稳定
- 测试时需要移动平均:增加了工程复杂度
- 小 batch(< 8)时:用 LayerNorm 或 GroupNorm 替代
English Explanation
Pros: larger learning rates, slight regularization, less initialization sensitivity, faster convergence Cons: poor with small batches, tricky for RNNs, requires moving averages at test time
---
本章总结 | Chapter Summary
中文:
- 内部协变量偏移 = 深层网络中每层输入分布不断变化
- BatchNorm 通过 batch 标准化 + 可学习缩放/平移解决该问题
- γ 和 β 让网络恢复任意分布,保持表达能力
- 训练时用 batch 统计量,测试时用移动平均
- 标准位置:Linear → BN → ReLU
- BatchNorm 允许更大学习率、加速收敛、轻微正则化
- 小 batch 时考虑 LayerNorm / GroupNorm
English:
- Internal covariate shift = input distribution changes at each layer
- BatchNorm normalizes per batch + learnable scale/shift
- γ, β preserve expressiveness
- Training: batch stats; Testing: moving averages
- Standard: Linear → BN → ReLU
- Enables larger LR, faster convergence, slight regularization
- Small batches: use LayerNorm / GroupNorm
---
课后练习 | Homework
- 移动平均推导:证明用移动平均
μ_moving = 0.9·μ_moving + 0.1·μ_B时,约前 10 个 batch 的统计量占据主要权重。
- BatchNorm 反向传播:推导 BatchNorm 的反向传播公式(提示:对 μ, σ, γ, β 分别求导)。
- batch size 实验:对比 batch_size=2, 16, 128 时 BatchNorm 的效果差异。观察小 batch 时统计量波动对训练的影响。
- LayerNorm 实现:实现 LayerNorm(对特征维度归一化,而非 batch 维度),对比与 BatchNorm 的区别。
- BN + Dropout 顺序:实验证明
Linear → BN → Dropout → ReLU和Linear → Dropout → BN → ReLU的效果差异。哪种更好?为什么?