各种Normalization
# 1 Batch Normalization(BN)
# 1.1 原理简介
Batch Normalization 是最早也是最常见的一种归一化方式,它在每一个小批次上对特征进行标准化,使其均值为 0,方差为 1,从而缓解内部协变量偏移。(对每一批数据(batch)中的每一列(特征)进行标准化,让它们变成均值为0、方差为1的分布。)
对一个特征维度 ,计算如下:
其中:
- 是 batch size
- 和 是可学习参数(用于恢复模型表达能力)
# 1.2 PyTorch 示例
import torch
import torch.nn as nn
# 一个 batch 有 2 个样本,每个样本有 3 个特征
x = torch.tensor([[1.0, 2.0, 3.0],
[2.0, 4.0, 6.0]])
# 转成 float32 类型
x = x.float()
# 创建 BatchNorm1d 对象,对每个“列”做归一化(因为是3个特征)
bn = nn.BatchNorm1d(num_features=3)
# 在训练模式下进行前向传播
bn.train()
out = bn(x)
print("输入:\n", x)
print("归一化输出:\n", out)
# 计算均值和方差
mean = x.mean(dim=0, keepdim=True)
std = x.std(dim=0, keepdim=True, unbiased=False)
print("均值:\n", mean)
print("方差:\n", std)
# 计算归一化输出
normalized_output = (x - mean) / (std + 1e-5) # 加上一个小常数以避免除零错误
print("手动归一化输出:\n", normalized_output)
'''
输入:
tensor([[1., 2., 3.],
[2., 4., 6.]])
归一化输出:
tensor([[-1.0000, -1.0000, -1.0000],
[ 1.0000, 1.0000, 1.0000]], grad_fn=<NativeBatchNormBackward0>)
均值:
tensor([[1.5000, 3.0000, 4.5000]])
方差:
tensor([[0.5000, 1.0000, 1.5000]])
手动归一化输出:
tensor([[-1.0000, -1.0000, -1.0000],
[ 1.0000, 1.0000, 1.0000]])
'''
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
输出说明(假设 gamma=1,beta=0):
输入:
[[1.0, 2.0, 3.0],
[2.0, 4.0, 6.0]]
2
3
- 第1列均值是 1.5,方差是 0.25
- 第2列均值是 3.0,方差是 1.0
- 第3列均值是 4.5,方差是 2.25
归一化后:
[[-1, -1, -1],
[ 1, 1, 1]]
2
(实际输出会稍微不同,因为加了一个小常数 epsilon 来避免除以 0)
# 1.3 适用场景
- CNN 中用于特征图的每个通道(使用
BatchNorm2d
) - MLP 中用于全连接层(使用
BatchNorm1d
)
# 2 Layer Normalization(LN)
# 2.1 作用
LayerNorm 和 BatchNorm 最大的区别是:
- BatchNorm:对同一特征维度、跨多个样本归一化(对列)
- LayerNorm:对每个样本自身的所有特征归一化(对行)
这使得 LayerNorm 更适用于 RNN、Transformer、小 batch 情况
# 2.2 举个最简单的例子(2个样本,每个有3个特征)
import torch
import torch.nn as nn
# 输入:2个样本,每个样本有3个特征
x = torch.tensor([[1.0, 2.0, 3.0],
[2.0, 4.0, 6.0]])
x = x.float()
# LayerNorm 对每个样本的全部3个特征归一化
ln = nn.LayerNorm(normalized_shape=3)
out = ln(x)
print("输入:\n", x)
print("归一化输出:\n", out)
# 计算均值和方差
mean = x.mean(dim=1, keepdim=True)
std = x.std(dim=1, keepdim=True, unbiased=False)
print("均值:\n", mean)
print("方差:\n", std)
# 计算归一化输出
normalized_output = (x - mean) / (std + 1e-5) # 加上一个小常数以避免除零错误
print("手动归一化输出:\n", normalized_output)
'''
输入:
tensor([[1., 2., 3.],
[2., 4., 6.]])
归一化输出:
tensor([[-1.2247, 0.0000, 1.2247],
[-1.2247, 0.0000, 1.2247]], grad_fn=<NativeLayerNormBackward0>)
均值:
tensor([[2.],
[4.]])
方差:
tensor([[0.8165],
[1.6330]])
手动归一化输出:
tensor([[-1.2247, 0.0000, 1.2247],
[-1.2247, 0.0000, 1.2247]])
'''
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# 2.3 输出说明:
对于第一行 [1.0, 2.0, 3.0]
:
- 均值 = 2.0,方差 = 1.0
- 归一化结果 =
[-1.2247, 0.0, 1.2247]
(近似)
对于第二行 [2.0, 4.0, 6.0]
:
- 均值 = 4.0,方差 = 2
- 归一化结果 =
[-1.2247, 0.0, 1.2247]
(近似)
# 2.4 总结
项目 | BatchNorm | LayerNorm |
---|---|---|
归一化维度 | 跨样本、每个特征(列) | 每个样本内部所有特征(行) |
应用场景 | CNN、图像 | NLP、RNN、Transformer、小batch |
输入形状 | [B, C] / [B, C, H, W] | 通常是 [B, L]、[B, L, D] 等 |
# 3 Instance Normalization(IN)
# 3.1 作用
Instance Normalization 是对每个样本的每个通道分别归一化。和前两种的区别:
名称 | 归一化维度 |
---|---|
BatchNorm | 每个通道、跨整个 batch |
LayerNorm | 每个样本、所有特征(向量层) |
InstanceNorm | 每个样本的每个通道(图像每张图的每个通道) |
InstanceNorm 常用于图像风格迁移(style transfer),因为它去除了样本间的统计特征,只保留个体结构。
# 3.2 举个简单例子:图像 [N, C, H, W] 形状,做每个样本每个通道归一化
我们用一个更真实的张量,形状为 [batch_size=1, channels=2, height=2, width=2]
,表示 1 张图像,2 个通道,每个通道是 2×2 的像素块。
import torch
import torch.nn as nn
# 创建一个样本,2个通道,每个通道是 2x2 像素
x = torch.tensor([[
[[1.0, 2.0], # 第一个通道
[3.0, 4.0]],
[[10.0, 20.0], # 第二个通道
[30.0, 40.0]]
]])
x = x.float() # 形状:[1, 2, 2, 2]
# InstanceNorm2d: 对每个样本的每个通道归一化(独立处理)
inorm = nn.InstanceNorm2d(num_features=2, affine=False)
# 归一化输出
out = inorm(x)
print("输入:\n", x)
print("归一化输出:\n", out)
# 计算均值和方差
mean = x.mean(dim=(2, 3), keepdim=True)
std = x.std(dim=(2, 3), keepdim=True, unbiased=False)
print("均值:\n", mean)
print("方差:\n", std)
print("手动归一化输出:\n", (x - mean) / (std + 1e-5)) # 加上一个小常数以避免除零错误
'''
输入:
tensor([[[[ 1., 2.],
[ 3., 4.]],
[[10., 20.],
[30., 40.]]]])
归一化输出:
tensor([[[[-1.3416, -0.4472],
[ 0.4472, 1.3416]],
[[-1.3416, -0.4472],
[ 0.4472, 1.3416]]]])
均值:
tensor([[[[ 2.5000]],
[[25.0000]]]])
方差:
tensor([[[[ 1.1180]],
[[11.1803]]]])
手动归一化输出:
tensor([[[[-1.3416, -0.4472],
[ 0.4472, 1.3416]],
[[-1.3416, -0.4472],
[ 0.4472, 1.3416]]]])
'''
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# 3.3 总结一句话:
InstanceNorm:适合图像风格迁移,归一化的是每个样本的每个通道的所有像素。
# 4 Group Normalization(GN)
# 4.1 作用与背景
Group Normalization(组归一化)是为了解决 BatchNorm 在小 batch size 下效果不佳 的问题。它的归一化策略:
把通道(Channel)分成几组,每组内部进行归一化。
适用于图像、3D、NLP 等多种任务,尤其是:
- Batch 很小(比如只有1或2)时
- 模型很深、需要稳定训练时
# 4.2 举个例子:
我们还是用一个张量 [1, 4, 2, 2]
:
- batch_size = 1
- channel = 4
- 每个通道是 2×2 像素
我们将通道分成 2组,每组2个通道。
# 4.3 示例代码
import torch
import torch.nn as nn
# 输入张量:[B=1, C=4, H=2, W=2]
x = torch.tensor([[
[[1.0, 2.0], [3.0, 4.0]], # 通道1
[[5.0, 6.0], [7.0, 8.0]], # 通道2
[[10.0, 20.0], [30.0, 40.0]], # 通道3
[[50.0, 60.0], [70.0, 80.0]] # 通道4
]])
x = x.float() # [1, 4, 2, 2]
# 将4个通道分成2组(每组2个通道)
gn = nn.GroupNorm(num_groups=2, num_channels=4, affine=False)
out = gn(x)
print("输入:\n", x)
print("归一化输出:\n", out)
# 分组归一化的均值和方差计算
# 手动计算
group1 = x[0, 0:2].reshape(-1) # 通道0和1
group2 = x[0, 2:4].reshape(-1) # 通道2和3
mean1 = group1.mean()
std1 = group1.std(unbiased=False)
print("组1均值:\n", mean1)
print("组1方差:\n", std1)
group1_norm = (x[0, 0:2] - mean1) / std1
mean2 = group2.mean()
std2 = group2.std(unbiased=False)
print("组2均值:\n", mean2)
print("组2方差:\n", std2)
group2_norm = (x[0, 2:4] - mean2) / std2
manual = torch.cat([group1_norm, group2_norm], dim=0).unsqueeze(0)
print("手动归一化结果:\n", manual)
'''
输入:
tensor([[[[ 1., 2.],
[ 3., 4.]],
[[ 5., 6.],
[ 7., 8.]],
[[10., 20.],
[30., 40.]],
[[50., 60.],
[70., 80.]]]])
归一化输出:
tensor([[[[-1.5275, -1.0911],
[-0.6547, -0.2182]],
[[ 0.2182, 0.6547],
[ 1.0911, 1.5275]],
[[-1.5275, -1.0911],
[-0.6547, -0.2182]],
[[ 0.2182, 0.6547],
[ 1.0911, 1.5275]]]])
组1均值:
tensor(4.5000)
组1方差:
tensor(2.2913)
组2均值:
tensor(45.)
组2方差:
tensor(22.9129)
手动归一化结果:
tensor([[[[-1.5275, -1.0911],
[-0.6547, -0.2182]],
[[ 0.2182, 0.6547],
[ 1.0911, 1.5275]],
[[-1.5275, -1.0911],
[-0.6547, -0.2182]],
[[ 0.2182, 0.6547],
[ 1.0911, 1.5275]]]])
'''
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# 4.4 手动说明下怎么归一化
- Group 1 = 通道 1 和 2,共 8 个值
- Group 2 = 通道 3 和 4,共 8 个值
对每组内所有像素值做均值方差归一化。
🚨 关键点:跨通道归一化,但不跨 batch,不跨组。
# 4.5 输出说明(近似):
输出中:
- Group1(前2通道)会被归一化成均值0,标准差1
- Group2(后2通道)也独立归一化
# 4.6 总结对比
名称 | 归一化范围 | 是否依赖 batch size |
---|---|---|
BatchNorm | 每个通道(跨样本) | ✅ 是 |
LayerNorm | 每个样本自身所有特征 | ❌ 否 |
InstanceNorm | 每个样本的每个通道 | ❌ 否 |
GroupNorm | 每个样本的每组通道 | ❌ 否 |
# 5 RMSNorm(Root Mean Square Normalization)
# 5.1 作用与背景
RMSNorm 是一种更简洁、更稳定的归一化方法,常见于 Transformer 变种中,比如 GPT 系列和一些轻量模型。它与 LayerNorm 非常相似,但有两点区别:
对比项 | LayerNorm | RMSNorm |
---|---|---|
是否减均值 | ✅ 是 | ❌ 否 |
是否只用方差 | ❌ 均值和方差都用 | ✅ 只用 RMS(均方根) |
公式是否更简单 | ❌ | ✅ 更简单计算、更稳定、更高效 |
# 5.2 归一化公式
RMSNorm 公式如下:
其中:
- 是一个样本向量
- 是维度长度
- 是可学习缩放因子(没有偏移量 )
- 没有减去均值
# 5.3 示例代码
import torch
import torch.nn as nn
class RMSNorm(nn.Module):
def __init__(self, dim, eps=1e-8):
super().__init__()
self.eps = eps
self.gamma = nn.Parameter(torch.ones(dim)) # 可学习缩放
def forward(self, x):
rms = torch.sqrt(torch.mean(x ** 2, dim=-1, keepdim=True))
return self.gamma * x / (rms + self.eps)
# 输入是2个样本,每个有4个特征
x = torch.tensor([[1.0, 2.0, 3.0, 4.0],
[2.0, 4.0, 6.0, 8.0]])
x = x.float()
rmsnorm = RMSNorm(dim=4)
out = rmsnorm(x)
print("输入:\n", x)
print("归一化输出:\n", out)
'''
输入:
tensor([[1., 2., 3., 4.],
[2., 4., 6., 8.]])
归一化输出:
tensor([[0.3651, 0.7303, 1.0954, 1.4606],
[0.3651, 0.7303, 1.0954, 1.4606]], grad_fn=<DivBackward0>)
'''
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 5.4 输出说明(近似):
对第一行:
- 均方根 = √[(1² + 2² + 3² + 4²)/4] = √7.5 ≈ 2.7386
- 每个数除以 2.7386 → 归一化成相对比例结构
优点:
- 更高效(少一步减均值)
- 更稳定(避免减均值带来的波动)
- 不依赖 batch
# 5.5 总结
名称 | 是否减均值 | 是否标准差 | 是否依赖 batch | 常用于 |
---|---|---|---|---|
LayerNorm | ✅ | ✅ | ❌ | Transformer/LLM |
RMSNorm | ❌ | ✅ (RMS) | ❌ | GPT-Neo, LLaMA |
# 6 ScaleNorm(比例缩放归一化)
# 6.1 作用与背景
ScaleNorm 是一种极简的归一化方式,用于替代 LayerNorm,在某些 Transformer 模型中被使用(如 T5 的某些变体)。它的思路很简单:
不去计算均值或方差,仅通过一个固定比例来控制输出大小。
# 6.2 公式定义
其中:
- 是一个向量
- 是其 L2 范数(向量长度)
- 是一个可学习的标量参数
它不会做标准化,只是缩放向量到固定长度(类似单位向量再乘一个比例)
# 6.3 简单实现(PyTorch)
import torch
import torch.nn as nn
class ScaleNorm(nn.Module):
def __init__(self, scale, eps=1e-5):
super().__init__()
self.scale = nn.Parameter(torch.tensor(scale))
self.eps = eps
def forward(self, x):
norm = x.norm(dim=-1, keepdim=True)
return self.scale * x / (norm + self.eps)
# 示例:2个样本,每个4维特征
x = torch.tensor([[1.0, 2.0, 3.0, 4.0],
[2.0, 4.0, 6.0, 8.0]])
x = x.float()
scalenorm = ScaleNorm(scale=1.0)
out = scalenorm(x)
print("输入:\n", x)
print("归一化输出:\n", out)
'''
输入:
tensor([[1., 2., 3., 4.],
[2., 4., 6., 8.]])
归一化输出:
tensor([[0.1826, 0.3651, 0.5477, 0.7303],
[0.1826, 0.3651, 0.5477, 0.7303]], grad_fn=<DivBackward0>)
'''
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 6.4 输出解释
对第一行:
- L2 范数 = √(1²+2²+3²+4²) = √30 ≈ 5.477
- 归一化为单位向量再乘上可学习的 γ
最终输出就变成“长度为 γ”的向量,方向不变。
# 6.5 特点总结
特点 | 是否有均值/方差 | 是否可学习 | 复杂度 | 是否依赖 batch |
---|---|---|---|---|
LayerNorm | ✅ 有 | ✅ | 中 | ❌ |
RMSNorm | ❌ 无均值 | ✅ | 中 | ❌ |
ScaleNorm | ❌ 无均值/方差 | ✅(仅缩放) | ✅ 最快 | ❌ |
适用场景:
- 大模型训练中追求极致简洁与速度
- 对规范化要求不高但要控制数值范围
# 7 深度学习常见 Normalization 方法对比表
名称 | 是否减均值 | 是否标准差 / RMS | 是否使用偏度 | 归一化维度 | 依赖 batch? | 常见用法 / 模型 |
---|---|---|---|---|---|---|
BatchNorm | ✅ 是 | ✅ 是(标准差) | ❌ 否 | 每个通道(跨 batch) | ✅ 是 | CNN, ResNet, MLP |
LayerNorm | ✅ 是 | ✅ 是(标准差) | ❌ 否 | 每个样本自身所有特征 | ❌ 否 | NLP, Transformer, LLM |
InstanceNorm | ✅ 是 | ✅ 是(标准差) | ❌ 否 | 每个样本的每个通道 | ❌ 否 | 风格迁移,图像生成 |
GroupNorm | ✅ 是 | ✅ 是(标准差) | ❌ 否 | 每个样本每组通道(按组划分) | ❌ 否 | 小 batch 图像模型 |
RMSNorm | ❌ 否 | ✅ 是(RMS) | ❌ 否 | 每个样本所有特征(类似 LayerNorm) | ❌ 否 | LLaMA, GPT-Neo, Transformer |
ScaleNorm | ❌ 否 | ❌ 否(只用 L2 范数) | ❌ 否 | 每个样本整体缩放 | ❌ 否 | T5 变体,轻量 Transformer |
# 7.1 归一化在哪儿:
输入 shape | BatchNorm | LayerNorm | InstanceNorm | GroupNorm |
---|---|---|---|---|
[B, C, H, W] | 对每个通道 跨 batch | 对每个样本的全特征 | 每张图每个通道独立 | 每张图通道分组归一化 |
# 7.2 使用建议:
情况 | 推荐归一化方法 |
---|---|
CNN 图像 + batch 较大 | BatchNorm |
CNN 图像 + batch 很小 | GroupNorm / InstanceNorm |
NLP / Transformer | LayerNorm / RMSNorm / PowerNorm |
图像风格迁移、图像生成模型 | InstanceNorm |
追求极简、快速推理 | ScaleNorm |
# 8 序列特征如何 Normalization
(batchsize,seq,feature_dim),这种序列特征layernorm和RMSNorm是怎么归一化的?
现在用具体维度为 [batch_size, seq_len, feature_dim]
的张量来说明 LayerNorm 和 RMSNorm 是如何归一化的。
x.shape = [B, L, D]
# B: batch_size,例如 2
# L: 序列长度,例如 5
# D: 特征维度,例如 4
2
3
4
# 8.1 LayerNorm 是怎么归一化的?
# 8.1.1 原则:
对每个样本的每个时间步(token)单独做归一化:
沿着最后一个维度 D(feature_dim)做归一化
也就是说,对于 x[b, l, :]
这一个 token,它是一个 [D]
的向量,LayerNorm 会:
- 减去它自己的均值
- 除以它自己的标准差
- 再乘上 γ,加上 β(可学习)
# 8.1.2 举例讲解
import torch
import torch.nn as nn
# 手动创建一个固定值的张量,方便观察
x = torch.tensor([
[ # 第一句话,共3个token
[1.0, 2.0, 3.0, 4.0], # token 1
[2.0, 4.0, 6.0, 8.0], # token 2
[0.5, 1.0, 1.5, 2.0] # token 3
],
[ # 第二句话
[10.0, 20.0, 30.0, 40.0],
[5.0, 5.0, 5.0, 5.0],
[-1.0, 0.0, 1.0, 2.0]
]
]) # shape = [2, 3, 4]
x = x.float()
print("原始输入 shape:", x.shape)
ln = nn.LayerNorm(normalized_shape=4)
out_ln = ln(x)
print("\nLayerNorm 输出:\n", out_ln)
'''
原始输入 shape: torch.Size([2, 3, 4])
LayerNorm 输出:
tensor([[[-1.3416, -0.4472, 0.4472, 1.3416],
[-1.3416, -0.4472, 0.4472, 1.3416],
[-1.3416, -0.4472, 0.4472, 1.3416]],
[[-1.3416, -0.4472, 0.4472, 1.3416],
[ 0.0000, 0.0000, 0.0000, 0.0000],
[-1.3416, -0.4472, 0.4472, 1.3416]]],
grad_fn=<NativeLayerNormBackward0>)
'''
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
每个 token 如 [1, 2, 3, 4]
会被:
- 减去自身均值
2.5
- 除以标准差
~1.118
- 得到输出
[-1.3416, -0.4472, 0.4472, 1.3416]
# 8.2 RMSNorm 是怎么归一化的?
RMSNorm 的原理一样,只不过:
- 不减均值
- 只除以 RMS(均方根)
也就是对每个 x[b, l, :]
:
# 8.2.1 举例
class RMSNorm(nn.Module):
def __init__(self, dim, eps=1e-8):
super().__init__()
self.eps = eps
self.gamma = nn.Parameter(torch.ones(dim)) # 可学习缩放
def forward(self, x):
rms = torch.sqrt(torch.mean(x ** 2, dim=-1, keepdim=True))
return self.gamma * x / (rms + self.eps)
import torch
import torch.nn as nn
# 手动创建一个固定值的张量,方便观察
x = torch.tensor([
[ # 第一句话,共3个token
[1.0, 2.0, 3.0, 4.0], # token 1
[2.0, 4.0, 6.0, 8.0], # token 2
[0.5, 1.0, 1.5, 2.0] # token 3
],
[ # 第二句话
[10.0, 20.0, 30.0, 40.0],
[5.0, 5.0, 5.0, 5.0],
[-1.0, 0.0, 1.0, 2.0]
]
]) # shape = [2, 3, 4]
rmsnorm = RMSNorm(dim=4)
out_rms = rmsnorm(x)
print("\nRMSNorm 输出:\n", out_rms)
'''
RMSNorm 输出:
tensor([[[ 0.3651, 0.7303, 1.0954, 1.4606],
[ 0.3651, 0.7303, 1.0954, 1.4606],
[ 0.3651, 0.7303, 1.0954, 1.4606]],
[[ 0.3651, 0.7303, 1.0954, 1.4606],
[ 1.0000, 1.0000, 1.0000, 1.0000],
[-0.8165, 0.0000, 0.8165, 1.6330]]], grad_fn=<DivBackward0>)
'''
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# 8.3 小结对比
项目 | LayerNorm | RMSNorm |
---|---|---|
归一化维度 | 每个 token 的特征维度 D | 同上 |
是否减均值 | ✅ 是 | ❌ 否 |
是否除 RMS | ✅ 用标准差(含均值) | ✅ 用 RMS |
参数维度 | γ, β 是 [D] 向量(每个特征一组) | γ 是 [D] 向量(β 可无) |
输入形状支持 | [B, L, D] ,自动按 D 归一化 | 同上 |
常见场景 | BERT, GPT, Transformer | GPT-Neo, LLaMA, Falcon, Mamba |