深度学习|表示学习|Batch Normalization 详解:数学、代码与经验总结|22
如是我闻: 在深度学习模型中,Batch Normalization(简称 BN)是一种常用的技术,能有效加速训练并提高模型的稳定性。BN 通过对 mini-batch 数据进行归一化,使每层的输入数据分布保持稳定,从而缓解梯度消失/爆炸问题,同时减少对权重初始化的敏感性。
在本篇文章中,我们将从 数学推导、代码实现 和 经验总结 三个方面,详细探讨 Batch Normalization 的工作原理,并分析为什么 BN 应该放在线性层(Linear)和激活函数(ReLU)之间,而不是之后。

1. 数学推导:BN 的计算过程
假设某层的输入数据为 XXX,它的形状是 (batch_size, num_features),其中每一列代表某个特征在 mini-batch 内的分布,BN 的计算过程如下:
Step 1: 计算 mini-batch 内的均值
对 batch 内所有样本的某个特征维度 jjj 计算均值:
μj=1m∑i=1mxi,j\mu_j = \frac{1}{m} \sum_{i=1}^{m} x_{i,j}μj=m1i=1∑mxi,j
其中:
- mmm 是 mini-batch 内的样本数
- xi,jx_{i,j}xi,j 是第 iii 个样本在特征维度 jjj 的值
Step 2: 计算 mini-batch 内的方差
σj2=1m∑i=1m(xi,j−μj)2\sigma_j^2 = \frac{1}{m} \sum_{i=1}^{m} (x_{i,j} - \mu_j)^2σj2=m1i=1∑m(xi,j−μj)2
为了防止除零,加上一个很小的数 ϵ\epsilonϵ:
σj2^=1m∑i=1m(xi,j−μj)2+ϵ\hat{\sigma_j^2} = \frac{1}{m} \sum_{i=1}^{m} (x_{i,j} - \mu_j)^2 + \epsilonσj2^=m1i=1∑m(xi,j−μj)2+ϵ
Step 3: 归一化
x^i,j=xi,j−μjσj2+ϵ\hat{x}_{i,j} = \frac{x_{i,j} - \mu_j}{\sqrt{\sigma_j^2 + \epsilon}}x^i,j=σj2+ϵxi,j−μj
这样,x^\hat{x}x^ 具有均值 0、方差 1 的特性。
Step 4: 重新缩放和平移(学习参数)
BN 不是单纯地归一化,而是 允许网络学习合适的缩放和平移参数,以恢复模型的表达能力:
yi,j=γjx^i,j+ηjy_{i,j} = \gamma_j \hat{x}_{i,j} +\eta_jyi,j=γjx^i,j+ηj
其中:
- γj\gamma_jγj 是缩放参数
- ηj\eta_jηj 是平移参数
- 这两个参数都是可学习的
总结:BN 公式
yi,j=γjxi,j−μjσj2+ϵ+ηjy_{i,j} = \gamma_j \frac{x_{i,j} - \mu_j}{\sqrt{\sigma_j^2 + \epsilon}} +\eta_jyi,j=γjσj2+ϵxi,j−μj+ηj
这些步骤的作用是:
- 让输入保持稳定的均值和方差,避免梯度消失/爆炸问题。
- 允许 BN 学习合适的缩放和平移,以保持模型的表达能力。
2. 代码实现:BN 在 MLP 里的使用
我们用 PyTorch 搭建一个简单的 MLP(多层感知机),并在隐藏层之间加入 Batch Normalization:
import torch
import torch.nn as nn
class SimpleMLP(nn.Module):
def __init__(self):
super(SimpleMLP, self).__init__()
self.fc1 = nn.Linear(2, 4) # 输入层 -> 隐藏层 1
self.bn1 = nn.BatchNorm1d(4) # BN 归一化
self.fc2 = nn.Linear(4, 3) # 隐藏层 1 -> 隐藏层 2
self.bn2 = nn.BatchNorm1d(3) # BN 归一化
self.fc3 = nn.Linear(3, 2) # 隐藏层 2 -> 输出层
def forward(self, x):
x = self.fc1(x)
x = self.bn1(x) # 先归一化
x = torch.relu(x) # 再激活
x = self.fc2(x)
x = self.bn2(x) # 先归一化
x = torch.relu(x) # 再激活
x = self.fc3(x) # 最后输出层不使用 BN
return x
# 创建模型
model = SimpleMLP()
# 生成一个 batch 的数据,假设 batch_size=5,每个样本有 2 个特征
x = torch.randn(5, 2)
# 运行前向传播
output = model(x)
print(output)
关键点:
- BN 层
nn.BatchNorm1d(隐藏层大小)直接加在Linear层之后。 BatchNorm1d作用于 mini-batch 的每个特征维度,而不是单个样本。- BN 放在 ReLU 之前,而不是之后,这一点非常重要。
3. 经验分析:为什么 BN 应该放在 ReLU 之前?
很多人会问:BN 是否应该放在激活函数之后?
答案是:不应该,BN 应该放在 Linear 层之后,ReLU 之前,主要有以下几个原因:
(1) BN 后再 ReLU,可以稳定数据分布
Linear -> BN之后,数据已经被标准化成 均值 0,方差 1。- 如果 BN 放在 ReLU 之后,ReLU 会把负数变成 0,这样会改变均值和方差,使 BN 失效。
(2) 让梯度更稳定
- BN 使输入数据分布平稳,梯度不会太大也不会太小,从而加快收敛速度。
- 如果 BN 在 ReLU 之后,负值部分会变成 0,这部分的梯度直接消失,影响训练效果。
(3) 从大数定律来看
- BN 计算的是 mini-batch 内的均值和方差。
- 由于大数定律,BN 能够在 batch 里提供稳定的估计,使得梯度更加平稳。
- 但如果 BN 在 ReLU 之后,负值部分全变成 0,均值和方差的估计会偏向正数部分,导致训练不稳定。
| 放在 ReLU 之前(正确) | 放在 ReLU 之后(错误) | |
|---|---|---|
| 数据分布 | 均值 0,方差 1 | 均值会偏移,方差变小 |
| 梯度流 | 让梯度更稳定 | 负值部分梯度消失 |
| 收敛速度 | 收敛更快 | 训练可能不稳定 |
因此,在 MLP 或 CNN 里,Batch Normalization 一般放在线性层(Linear/Conv)之后,ReLU 之前。
4. 总的来说
- Batch Normalization 通过对 mini-batch 归一化,使数据分布更稳定,提高训练效率。
- 数学上,BN 计算均值和方差,然后用缩放因子 γ\gammaγ 和偏移量 η\etaη 进行调整。
- 代码上,BN 层应该加在
Linear层后,ReLU之前。 - 经验上,BN 使梯度更稳定,并减少梯度消失问题,加快收敛速度。
- 大数定律解释了 BN 在 batch 内的统计计算是合理的,但如果 BN 放在 ReLU 之后,会破坏这个平稳性。
以上
更多推荐



所有评论(0)