2019-07-16
深度学习里的 Norm 家族很热闹:BN、LN、IN、GN、SN,看名字像一串缩写暗号。 但它们的出发点并不玄乎:网络越深,中间层的数据分布越容易乱跑。分布一乱,后面的层就要一边学习任务,一边适应前面层不断变化的输出,训练自然会变慢、变抖,甚至变得难收敛。
假设你要判断一个学生是否优秀,有几组指标:
文化课成绩
体育测试
身高体重
文化课满分可能是 100,体育可能是另一套分数,身高体重又是完全不同的量纲。
如果直接把这些数混在一起比较,就会很别扭。
一个自然的做法是:把它们拉到相近的尺度里,再比较。
神经网络里也类似。每一层都会把输入变成新的特征。如果这些特征的均值、方差、尺度一直乱飘,后面的层就会很难受。
单个特征的波动,我们可以用方差描述:
Var(X) = E[(X - μ)²]
如果有多个特征,只看每个特征自己的方差还不够,还要看不同特征之间的关系。
这就会用到协方差:
Cov(X, Y) = E[(X - μx)(Y - μy)]
如果把很多特征两两之间的协方差放在一起,就得到协方差矩阵。
直观地说:
方差:自己和自己的波动
协方差:自己和别人的联动
深层网络训练时,前面层的参数一直在更新,后面层看到的输入分布也会跟着变化。
这类分布变化会让优化变得更麻烦。Norm 系列方法的核心想法就是:别让中间层输出太放飞,先拉回一个更稳定的分布,再继续往后传。
大多数归一化方法都绕不开这几步:
算均值
算方差
标准化
再用可学习参数缩放和平移
公式可以写成:
x_hat = (x - μ) / sqrt(σ² + ε)
然后:
y = γx_hat + β
其中:
μ 是均值σ² 是方差ε 用来避免除零γ 是可学习缩放β 是可学习平移为什么标准化后还要加 γ 和 β?
因为完全把数据拉成固定分布,可能会限制模型表达能力。γ 和 β 相当于告诉模型:我先帮你整理一下分布,但你觉得哪里需要放大、缩小、平移,可以自己学回来。
BN 是最常见的归一化方法之一。
对于 CNN 输入:
N × C × H × W
BN 通常对每个通道 C 分别统计均值和方差,统计范围覆盖:
N、H、W
也就是说,同一个通道里,整个 batch 的所有空间位置一起参与统计。
PyTorch 用法:
import torch
import torch.nn as nn
bn = nn.BatchNorm2d(num_features=64)
x = torch.randn(8, 64, 32, 32)
y = bn(x)
print(y.shape)
BN 的优点:
BN 的问题也很明确:
训练时,BN 使用当前 batch 的均值和方差;推理时,使用训练过程中累计的 running mean 和 running var。
这也是为什么验证或推理时要调用:
model.eval()
LN 不依赖 batch 维度。
它通常对单个样本内部的特征做归一化。
如果输入是:
batch × hidden_dim
LN 会对每个样本的 hidden_dim 做统计。
PyTorch 用法:
ln = nn.LayerNorm(normalized_shape=128)
x = torch.randn(32, 128)
y = ln(x)
print(y.shape)
LN 很适合序列模型和 Transformer 类结构,因为这些场景里 batch 大小可能变化,而且不同样本长度、结构差异较大。
LN 的特点:
IN 常见于图像生成、风格迁移等任务。
对于输入:
N × C × H × W
IN 对每个样本、每个通道分别统计 H × W 上的均值和方差。
也就是:
每张图,每个通道,自己归一化
PyTorch 用法:
inorm = nn.InstanceNorm2d(num_features=64)
x = torch.randn(8, 64, 32, 32)
y = inorm(x)
print(y.shape)
为什么风格迁移里喜欢 IN?
因为图像风格往往和通道统计量有关。IN 会弱化单张图里的对比度、亮度、风格统计,让模型更关注内容结构。
它的特点:
GN 可以看成在 LN 和 IN 之间找了一个折中。
它把通道分成若干组,每组内部做归一化。
对于:
N × C × H × W
如果分成 G 组,每组有 C / G 个通道。GN 会在每个样本内部,对每组的通道和空间位置一起统计。
PyTorch 用法:
gn = nn.GroupNorm(num_groups=8, num_channels=64)
x = torch.randn(4, 64, 32, 32)
y = gn(x)
print(y.shape)
GN 的优势是:不依赖 batch 统计。
所以在目标检测、分割这类显存吃紧、batch 较小的任务里,GN 经常比 BN 更稳。
它的特点:
SN 的思路更像“别急着站队,让模型自己选”。
BN、LN、IN 各有优势,那能不能把它们混合起来?
SN 会把多种归一化统计方式组合起来,并给它们学习权重。
可以粗略理解成:
SN = α_BN × BN + α_LN × LN + α_IN × IN
这里的权重不是手写死的,而是让模型学习。
它的目标是:不同任务、不同层,可能适合不同的归一化方式,那就让网络自己决定比例。
这类方法实现会比 BN、LN、IN、GN 更复杂,但思想很直观:
归一化也可以自适应
简单记法:
BN:看一个 batch
LN:看一个样本的一层
IN:看一张图的一个通道
GN:看一个样本的一组通道
SN:把几种方式混起来学
如果你只记一句话:
Norm 方法的核心差别,是统计均值和方差时,到底沿哪些维度统计。
下面用同一个输入跑几个 Norm:
import torch
import torch.nn as nn
x = torch.randn(4, 8, 16, 16)
layers = {
"BatchNorm2d": nn.BatchNorm2d(8),
"InstanceNorm2d": nn.InstanceNorm2d(8),
"GroupNorm": nn.GroupNorm(num_groups=4, num_channels=8),
}
for name, layer in layers.items():
y = layer(x)
print(name, y.shape, y.mean().item(), y.std().item())
它们输出形状都一样:
batch × channel × height × width
但统计均值和方差的维度不一样,所以数值行为也不一样。
Keras 中常见的是 BatchNormalization 和 LayerNormalization:
import tensorflow as tf
from tensorflow.keras import layers, models
model = models.Sequential([
layers.Conv2D(32, 3, padding="same", input_shape=(32, 32, 3)),
layers.BatchNormalization(),
layers.ReLU(),
layers.Conv2D(64, 3, padding="same"),
layers.BatchNormalization(),
layers.ReLU(),
layers.GlobalAveragePooling2D(),
layers.Dense(10, activation="softmax"),
])
model.summary()
LayerNormalization:
x = layers.Input(shape=(128,))
y = layers.LayerNormalization()(x)
model = tf.keras.Model(x, y)
如果需要 GN、IN 或 SN,通常要使用扩展库,或者自己写自定义层。
用 PyTorch 手写一个简化版 LN:
import torch
import torch.nn as nn
class SimpleLayerNorm(nn.Module):
def __init__(self, hidden_dim, eps=1e-5):
super().__init__()
self.gamma = nn.Parameter(torch.ones(hidden_dim))
self.beta = nn.Parameter(torch.zeros(hidden_dim))
self.eps = eps
def forward(self, x):
mean = x.mean(dim=-1, keepdim=True)
var = x.var(dim=-1, keepdim=True, unbiased=False)
x_hat = (x - mean) / torch.sqrt(var + self.eps)
return self.gamma * x_hat + self.beta
x = torch.randn(32, 128)
ln = SimpleLayerNorm(128)
y = ln(x)
print(y.shape)
这个小例子能看出 Norm 的本质:选定统计维度,算均值方差,标准化,再缩放平移。
没有一个 Norm 能通吃所有任务,可以按场景先做保守选择。
图像分类、batch 比较正常:
优先试 BN
NLP、Transformer、序列建模:
优先试 LN
图像生成、风格迁移:
可以关注 IN
目标检测、分割、小 batch:
可以试 GN
不确定哪种统计最合适,并且愿意接受更复杂实现:
可以了解 SN
Norm 不是简单把数据缩到 0 到 1。
很多 Norm 做的是均值方差标准化,不是 min-max 缩放。
BN 不等于所有场景最优。
batch 太小的时候,BN 的统计量可能不稳,这时 GN 或 LN 可能更舒服。
γ 和 β 不是多余的。
它们让模型在标准化之后还能学回需要的尺度和偏移。
训练和推理模式要分清。
BN 在训练和推理时行为不同,忘记 model.eval() 会让验证结果变得很奇怪。
Norm 不是让模型变聪明的魔法,但它能让训练环境更干净、更稳定。把数据分布收拾利索,后面的优化器才好干活。