码到成功

BN、LN、IN:它们到底在“归一化”什么


## 1. 为什么深度学习里总在谈“归一化”

训练神经网络时,特征分布经常会发生漂移。某一层输出可能整体偏大、偏小,或者不同维度之间量纲差异很大。结果就是:

- 梯度不稳定,训练过程忽快忽慢
- 某些激活值很容易进入饱和区
- 学习率稍微大一点就容易震荡
- 网络对初始化和 batch 组织方式过于敏感


常见形式可以写成:

```text
y = gamma * (x - mean) / sqrt(var + eps) + beta
```

其中:

- `mean` 和 `var` 决定“怎么统计”
- `gamma` 和 `beta` 负责把标准化后的分布重新拉回模型需要的表达空间

BN、LN、IN 真正的区别,不在公式,而在“均值和方差是沿着哪些维度算出来的”。

## 2. 先抓住核心:差别只在统计轴

如果把卷积特征图记成 `[N, C, H, W]`:

- `N` 是 batch size
- `C` 是通道数
- `H, W` 是空间尺寸

那么三者的直觉差别可以先记成一句话:

- `BN`:同一通道,跨样本一起统计
- `LN`:每个样本自己统计,不看别的样本
- `IN`:每个样本的每个通道再单独统计

如果你只记一件事,就记这件事。

## 3. Batch Normalization:让整批样本在同一通道上对齐

### 3.1 它在做什么

BN 的典型思路是:

- 对每个通道分别处理
- 统计时把 batch 维度一起算进去
- 对卷积特征来说,通常还会把空间维度一起纳入统计

也就是说,对某个通道 `c`,会使用整批样本在该通道上的值来计算均值和方差。

这意味着:

- 当前样本的归一化结果,会受到同 batch 其他样本影响
- batch 越稳定,统计值通常越稳定
- batch 太小时,估计的均值和方差可能比较抖

### 3.2 直觉理解

可以把 BN 想成:

“同一个通道里,大家先站到一条统一的标尺上。”

比如卷积层的某个通道专门检测纹理边缘,那么这个通道会把这一批样本里所有对应响应拉到一个相对统一的尺度上,再由 `gamma` 和 `beta` 去决定最终保留多少幅度和偏移。

### 3.3 它适合什么场景

- 卷积网络
- batch size 足够大且稳定的训练过程
- 对吞吐和收敛速度比较敏感的场景

### 3.4 它的限制

- 小 batch 时效果可能不稳定
- 训练和推理阶段使用的统计量不同
- 序列任务里,样本长度变化和小 batch 往往让 BN 不够顺手

## 4. Layer Normalization:每个样本自己完成归一化

### 4.1 它在做什么

LN 的关键点是:

- 不跨 batch 统计
- 每个样本独立完成归一化
- 归一化的范围是该样本内部的一组特征维

在序列任务中,最常见的理解方式是:

- 对一个 token 的隐藏向量沿特征维做归一化

在卷积任务里,如果把某个空间位置上的通道向量看成一组特征,也可以得到非常直观的理解:

- 同一样本的同一位置,跨通道完成归一化

### 4.2 直觉理解

LN 更像是在说:

“别拿别的样本做参照,我只把当前样本内部调整平衡。”

这样做的好处是,它完全不依赖 batch size。哪怕 batch 为 1,依然可以正常工作。

### 4.3 它适合什么场景

- RNN
- Transformer
- 小 batch 训练
- batch 组成不稳定,或者推理时需要和训练阶段行为尽量一致的场景

### 4.4 它的特点

- 不依赖 batch 统计,鲁棒性更强
- 在序列建模里非常自然
- 对卷积网络来说,不一定总比 BN 更优,但在某些小 batch 设定下很实用

## 5. Instance Normalization:每个样本、每个通道自己校准

### 5.1 它在做什么

IN 可以理解为:

- 不跨 batch
- 也不把不同通道混在一起
- 对每个样本的每个通道,单独沿空间维度 `H, W` 计算均值和方差

如果张量是 `[N, C, H, W]`,那么对于某个样本的某个通道,IN 只看这个二维特征图内部的空间分布。

### 5.2 直觉理解

它像是在说:

“这个通道就处理这个通道的事,不要和其他样本、其他通道互相干扰。”

因此它在图像风格相关任务中很常见,因为它比较擅长把样本内部某些整体风格差异削弱掉,同时保住通道级的语义分工。

### 5.3 它适合什么场景

- 风格迁移
- 图像生成
- 某些需要弱化实例风格偏移的视觉任务

### 5.4 它和 LN 的差别

两者都不依赖 batch,但统计方式不同:

- `LN` 更关注一个样本内部的一组特征整体
- `IN` 更关注一个样本内部、每个通道各自的空间分布

## 6. 一张表看懂三者区别

| 方法 | 是否跨 batch | 典型统计范围 | 常见场景 | 直觉 |
| --- | --- | --- | --- | --- |
| BN | 是 | 单通道下的 `N + H + W` | CNN | 同一通道整批对齐 |
| LN | 否 | 单样本内部的特征维 | Transformer / RNN | 每个样本自己平衡 |
| IN | 否 | 单样本单通道的 `H + W` | 风格迁移 / 生成任务 | 每个通道单独校准 |

## 7. 用 Python 把三种归一化“手算”一遍

下面用 `NumPy` 做一个最小示例。为了方便说明,输入张量使用 `[N, C, H, W]`。

```python
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 channel`
- `LN mean by sample`
- `IN mean by sample/channel`

它们分别会非常接近 0,这正好对应三种方法各自的统计单位。

## 8. 如果你在 PyTorch 里直接用

```python
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,更常见的写法会是:

```python
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 用法。

## 9. 一个工程化选择建议

如果你在选型时不想每次都从头分析,可以先按下面的经验走:

- 做常规 CNN,并且 batch 足够大:优先试 `BN`
- 做 Transformer、RNN,或者 batch 很小:优先试 `LN`
- 做风格迁移、图像生成,或者想削弱实例风格差异:优先试 `IN`

当然,这不是硬规则。最稳妥的做法仍然是结合:

- 任务类型
- batch size
- 模型结构
- 推理与训练的一致性要求

一起判断。