2019-02-13
归一化层做的事情,本质上就是先把一组值拉回到更稳定的分布附近,再交给可学习参数重新调整。
训练神经网络时,特征分布经常会发生漂移。某一层输出可能整体偏大、偏小,或者不同维度之间量纲差异很大。结果就是:
常见形式可以写成:
y = gamma * (x - mean) / sqrt(var + eps) + beta
其中:
mean 和 var 决定“怎么统计”gamma 和 beta 负责把标准化后的分布重新拉回模型需要的表达空间BN、LN、IN 真正的区别,不在公式,而在“均值和方差是沿着哪些维度算出来的”。
如果把卷积特征图记成 [N, C, H, W]:
N 是 batch sizeC 是通道数H, W 是空间尺寸那么三者的直觉差别可以先记成一句话:
BN:同一通道,跨样本一起统计LN:每个样本自己统计,不看别的样本IN:每个样本的每个通道再单独统计如果你只记一件事,就记这件事。
BN 的典型思路是:
也就是说,对某个通道 c,会使用整批样本在该通道上的值来计算均值和方差。
这意味着:
可以把 BN 想成:
“同一个通道里,大家先站到一条统一的标尺上。”
比如卷积层的某个通道专门检测纹理边缘,那么这个通道会把这一批样本里所有对应响应拉到一个相对统一的尺度上,再由 gamma 和 beta 去决定最终保留多少幅度和偏移。
LN 的关键点是:
在序列任务中,最常见的理解方式是:
在卷积任务里,如果把某个空间位置上的通道向量看成一组特征,也可以得到非常直观的理解:
LN 更像是在说:
“别拿别的样本做参照,我只把当前样本内部调整平衡。”
这样做的好处是,它完全不依赖 batch size。哪怕 batch 为 1,依然可以正常工作。
IN 可以理解为:
H, W 计算均值和方差如果张量是 [N, C, H, W],那么对于某个样本的某个通道,IN 只看这个二维特征图内部的空间分布。
它像是在说:
“这个通道就处理这个通道的事,不要和其他样本、其他通道互相干扰。”
因此它在图像风格相关任务中很常见,因为它比较擅长把样本内部某些整体风格差异削弱掉,同时保住通道级的语义分工。
两者都不依赖 batch,但统计方式不同:
LN 更关注一个样本内部的一组特征整体IN 更关注一个样本内部、每个通道各自的空间分布| 方法 | 是否跨 batch | 典型统计范围 | 常见场景 | 直觉 |
|---|---|---|---|---|
| BN | 是 | 单通道下的 N + H + W |
CNN | 同一通道整批对齐 |
| LN | 否 | 单样本内部的特征维 | Transformer / RNN | 每个样本自己平衡 |
| IN | 否 | 单样本单通道的 H + W |
风格迁移 / 生成任务 | 每个通道单独校准 |
下面用 NumPy 做一个最小示例。为了方便说明,输入张量使用 [N, C, H, W]。
import numpy as np
np.random.seed(7)
x = np.random.randn(2, 3, 2, 2).astype(np.float32)
eps = 1e-5
def batch_norm_numpy(x, eps=1e-5):
# 对每个通道,跨 N/H/W 统计
mean = x.mean(axis=(0, 2, 3), keepdims=True)
var = x.var(axis=(0, 2, 3), keepdims=True)
return (x - mean) / np.sqrt(var + eps)
def layer_norm_numpy(x, eps=1e-5):
# 这里演示卷积特征上的一种直观写法:每个样本在 C/H/W 上整体归一化
mean = x.mean(axis=(1, 2, 3), keepdims=True)
var = x.var(axis=(1, 2, 3), keepdims=True)
return (x - mean) / np.sqrt(var + eps)
def instance_norm_numpy(x, eps=1e-5):
# 每个样本的每个通道,沿 H/W 统计
mean = x.mean(axis=(2, 3), keepdims=True)
var = x.var(axis=(2, 3), keepdims=True)
return (x - mean) / np.sqrt(var + eps)
bn_out = batch_norm_numpy(x, eps)
ln_out = layer_norm_numpy(x, eps)
in_out = instance_norm_numpy(x, eps)
print("input shape:", x.shape)
print("BN mean by channel:", bn_out.mean(axis=(0, 2, 3)))
print("LN mean by sample:", ln_out.mean(axis=(1, 2, 3)))
print("IN mean by sample/channel:", in_out.mean(axis=(2, 3)))
你可以重点观察这三行输出:
BN mean by channelLN mean by sampleIN mean by sample/channel它们分别会非常接近 0,这正好对应三种方法各自的统计单位。
import torch
import torch.nn as nn
x = torch.randn(8, 64, 32, 32)
bn = nn.BatchNorm2d(64)
ln = nn.LayerNorm([64, 32, 32]) # 对整个样本的 C/H/W 做归一化
inn = nn.InstanceNorm2d(64)
y_bn = bn(x)
y_ln = ln(x)
y_in = inn(x)
如果是 Transformer,更常见的写法会是:
import torch
import torch.nn as nn
x = torch.randn(4, 128, 512) # [batch, seq_len, hidden]
ln = nn.LayerNorm(512)
y = ln(x)
这里 LayerNorm(512) 表示沿最后一维隐藏特征做归一化,这也是 NLP 场景里最典型的 LN 用法。
如果你在选型时不想每次都从头分析,可以先按下面的经验走:
BNLNIN当然,这不是硬规则。最稳妥的做法仍然是结合:
一起判断。