
1. 为什么归一化层值得单独拎出来讲做深度学习的人几乎都绕不开归一化这个话题。BNBatch Normalization几乎是每个卷积网络的标配但当你真正开始做检测、分割、风格迁移、视频理解或者把模型部署到小批量甚至单样本推理的场景时BN 就开始掉链子了。这时候 GN、LN、IN 这三个兄弟就登场了。我最早接触 GN 是在做图像分割项目的时候当时显存吃紧batch size 只能开到 2BN 的统计量抖得厉害训练 loss 像心电图一样上下跳。后来换成 GN同样的网络结构同样的数据训练曲线立刻平滑了很多。从那以后我就养成了一个习惯只要 batch size 上不去优先考虑 GN 或者 LN而不是硬扛 BN。这篇内容主要面向已经了解 BN 基本概念、想搞清楚 GN、LN、IN 到底怎么选、怎么用、代码里怎么改的读者。我会从归一化的本质讲起把四种归一化的计算维度、适用场景、代码实现、踩坑经验全部拆开讲清楚。不管你是刚入门的新手还是已经调过几个模型的老手应该都能从中找到有用的东西。2. 归一化的本质到底在归一化什么2.1 从特征图的形状说起要理解 GN、LN、IN 的区别首先得把特征图的形状搞清楚。假设一个卷积层的输出是(N, C, H, W)其中 N 是 batch sizeC 是通道数H 和 W 是空间维度。这四维数据在内存里排列不同的归一化方法就是在这四个维度上做不同的“分组求均值和方差”。你可以把归一化想象成给一群人打分。BN 是每次只看一个特征通道把整个 batch 里所有样本在这个通道上的值放在一起算平均LN 是每个样本自己算自己的把所有通道和空间位置的值放在一起IN 是每个样本的每个通道单独算只在这个通道的 H×W 空间里算GN 则是把通道分成若干组每组内部算一个统计量。这个类比虽然粗糙但能帮你快速建立直觉。下面逐个拆解。2.2 四种归一化的计算维度对比先上一张表把核心差异列清楚方法计算维度统计量数量对 batch 的依赖BN(N, H, W)C 个强依赖LN(C, H, W)N 个无依赖IN(H, W)N×C 个无依赖GN(H, W) 按组N×G 个无依赖这张表是理解所有后续内容的钥匙。BN 在每个通道上跨样本统计所以 batch 越大越稳LN 在每个样本内部跨通道和空间统计跟 batch 完全无关IN 更细每个样本的每个通道单独统计GN 折中把通道分组组内统计。注意这里的“统计量数量”指的是训练时前向传播需要计算的均值和方差的对数。BN 有 C 个均值、C 个方差LN 有 N 个均值、N 个方差IN 有 N×C 个GN 有 N×G 个。2.3 为什么 batch 维度这么关键BN 的问题不在于它不好而在于它太依赖 batch。训练时用大 batch统计量估计得准效果很好但推理时用的是训练阶段累积的滑动平均如果训练和推理的数据分布差异大或者 batch 太小导致滑动平均本身就不准效果就会崩。更麻烦的是有些任务天然不适合大 batch。比如视频理解一个 clip 可能就占好几 G 显存比如医学图像分割3D 数据根本堆不了多少样本再比如风格迁移每个 batch 可能就一张内容图加一张风格图。这些场景下BN 的统计量噪声极大训练极不稳定。LN、IN、GN 的共同点就是它们都不跨 batch 统计。这意味着无论你的 batch size 是 1 还是 64归一化的行为是一致的。这个特性在部署和分布式训练时特别香因为你不用担心 batch 分布变化导致模型行为漂移。3. GN、LN、IN 各自的核心机制与适用场景3.1 Layer Normalization每个样本自己说了算LN 的计算方式很直接对于一个样本的所有通道和空间位置求一个全局均值和方差然后归一化。公式上对于第 n 个样本mu_n mean(x[n, :, :, :]) sigma_n std(x[n, :, :, :]) y[n, c, h, w] gamma[c] * (x[n, c, h, w] - mu_n) / sqrt(sigma_n^2 eps) beta[c]注意 gamma 和 beta 仍然是按通道来的所以 LN 并没有完全丢掉通道维度的可学习参数。LN 最早是在 RNN 和 Transformer 里大放异彩的。Transformer 的每个 token 表示维度是(batch, seq_len, d_model)LN 在 d_model 维度上归一化跟序列长度和 batch 都无关。这也是为什么 Transformer 能轻松处理变长序列和单样本推理。但在卷积网络里LN 的表现通常不如 BN 和 GN。原因在于卷积特征图的空间维度包含大量局部模式LN 把整个样本的所有空间位置混在一起算统计量会抹掉一些空间上的差异性。我试过在 ResNet 里直接把 BN 换成 LNImageNet 上的 top-1 掉了差不多 2 个点训练也更慢。实操心得LN 适合序列模型和 Transformer 类结构。如果你非要在 CNN 里用建议只在最后几层或者分类头之前用不要全网络替换。3.2 Instance Normalization风格迁移的标配IN 的计算粒度更细对每个样本的每个通道单独在 H×W 空间上算均值和方差。公式mu[n, c] mean(x[n, c, :, :]) sigma[n, c] std(x[n, c, :, :]) y[n, c, h, w] gamma[c] * (x[n, c, h, w] - mu[n, c]) / sqrt(sigma[n, c]^2 eps) beta[c]IN 最早是风格迁移任务里提出来的。风格迁移的核心需求是把内容图的“风格”去掉只保留结构然后注入目标风格。IN 恰好能做到这一点因为它把每个通道的全局统计量均值和方差归一化掉了而通道的均值和方差恰恰承载了图像的风格信息比如色调、对比度。但 IN 在分类任务上表现不好因为它丢掉了太多判别性信息。你想想把每个通道的均值方差都抹掉相当于把每个通道的“亮度”和“对比度”都统一了这对分类来说损失很大。注意IN 在训练和推理时行为完全一致不需要滑动平均也不需要 batch 统计。这是它比 BN 简单的地方。3.3 Group Normalization小 batch 场景的救星GN 是 2018 年提出的核心思想是既然 BN 依赖 batchLN 和 IN 又太极端那能不能在通道维度上分组组内做归一化具体做法是把 C 个通道分成 G 组每组有 C/G 个通道然后在每个样本的每组内部跨通道和空间位置计算均值和方差。mu[n, g] mean(x[n, g*C/G:(g1)*C/G, :, :]) sigma[n, g] std(x[n, g*C/G:(g1)*C/G, :, :])当 G1 时GN 退化成 LN当 GC 时GN 退化成 IN。所以 GN 其实是 LN 和 IN 的一般形式通过调节 G 来平衡“跨通道信息共享”和“通道独立性”。GN 在检测和分割任务上表现特别好。COCO 上的 Mask R-CNN把 BN 换成 GNbatch size 从 16 降到 2mAP 几乎不掉。这在显存受限的场景下简直是救命稻草。实操心得GN 的 G 一般取 32 或者 16。如果你的通道数小于 32G 取 8 或者 4。经验上G 取 32 在大多数视觉任务里都不会出错。4. 代码实现从零手写四种归一化4.1 PyTorch 内置模块的调用方式PyTorch 里这四种归一化都有现成的模块import torch.nn as nn bn nn.BatchNorm2d(num_features64) ln nn.LayerNorm(normalized_shape[64, 32, 32]) inorm nn.InstanceNorm2d(num_features64, affineTrue) gn nn.GroupNorm(num_groups32, num_channels64)注意 LN 在 PyTorch 里的normalized_shape参数需要指定归一化的维度。对于(N, C, H, W)的输入如果你想让 LN 在 C、H、W 上归一化就传[C, H, W]。但这样写有个问题H 和 W 是固定的如果输入尺寸变化LN 就会报错。所以 LN 在 CNN 里用得少在 Transformer 里因为序列长度可变通常只对最后一维归一化。4.2 手写 GN 的核心逻辑为了真正理解 GN我建议你手写一遍。下面是一个简化版的 GN 实现import torch import torch.nn as nn class GroupNormManual(nn.Module): def __init__(self, num_groups, num_channels, eps1e-5): super().__init__() self.num_groups num_groups self.num_channels num_channels self.eps eps self.weight nn.Parameter(torch.ones(num_channels)) self.bias nn.Parameter(torch.zeros(num_channels)) def forward(self, x): N, C, H, W x.shape G self.num_groups # reshape 成 (N, G, C//G * H * W) x x.view(N, G, -1) mean x.mean(dim2, keepdimTrue) var x.var(dim2, keepdimTrue, unbiasedFalse) x (x - mean) / torch.sqrt(var self.eps) # reshape 回 (N, C, H, W) x x.view(N, C, H, W) # 按通道缩放和平移 x x * self.weight.view(1, C, 1, 1) self.bias.view(1, C, 1, 1) return x这段代码的关键在于view(N, G, -1)这一步。它把每个样本的 C 个通道分成 G 组每组内的所有元素拉平成一个向量然后在这个向量上算均值和方差。算完之后再 reshape 回去最后用可学习的 gamma 和 beta 做仿射变换。注意var的unbiasedFalse很重要。归一化里用的是有偏方差除以 N 而不是 N-1因为我们要的是描述统计量不是无偏估计。4.3 四种归一化的统一实现框架其实这四种归一化可以用一个统一的框架来实现区别只在于“在哪些维度上算统计量”。下面这个函数可以模拟四种行为def normalize(x, modegn, num_groups32, eps1e-5): N, C, H, W x.shape if mode bn: # 跨 N, H, W 统计每个通道一个统计量 mean x.mean(dim(0, 2, 3), keepdimTrue) var x.var(dim(0, 2, 3), keepdimTrue, unbiasedFalse) elif mode ln: # 跨 C, H, W 统计每个样本一个统计量 mean x.mean(dim(1, 2, 3), keepdimTrue) var x.var(dim(1, 2, 3), keepdimTrue, unbiasedFalse) elif mode in: # 跨 H, W 统计每个样本每个通道一个统计量 mean x.mean(dim(2, 3), keepdimTrue) var x.var(dim(2, 3), keepdimTrue, unbiasedFalse) elif mode gn: # 分组后在组内跨通道和空间统计 x_reshaped x.view(N, num_groups, -1) mean x_reshaped.mean(dim2, keepdimTrue) var x_reshaped.var(dim2, keepdimTrue, unbiasedFalse) x_reshaped (x_reshaped - mean) / torch.sqrt(var eps) return x_reshaped.view(N, C, H, W) return (x - mean) / torch.sqrt(var eps)这个统一视角能帮你快速理解BN 的统计维度是(0, 2, 3)LN 是(1, 2, 3)IN 是(2, 3)GN 是先分组再在组内拉平。记住这个维度对应关系面试或者调参时都能快速反应。5. 实战选型什么场景用什么归一化5.1 图像分类BN 仍然是首选如果你做的是标准图像分类batch size 能开到 32 以上BN 仍然是最优解。ImageNet 上的各种 ResNet、EfficientNet、ConvNeXt默认都是 BN。BN 的统计量在大 batch 下估计得很准训练收敛快推理时滑动平均也稳定。但如果你因为显存限制只能开小 batch比如 8 或者 4那就要考虑 GN 了。我做过对比实验ResNet-50 在 batch size4 时BN 的 top-1 比 GN 低 1.5 个点左右而且训练曲线抖动明显。batch size2 时差距更大BN 甚至可能不收敛。5.2 目标检测与分割GN 的主场检测和分割任务的特征图分辨率高显存占用大batch size 通常很小。Mask R-CNN 原始论文用的是 batch size16但实际训练时很多人只能开到 2 或者 4。这时候 GN 就是标配。我自己的经验是检测头部分用 GNbackbone 部分如果预训练权重是 BN 的可以保留 BN 但冻结统计量或者全部换成 GN 然后重新训练。后者效果更好但需要更长的训练时间。实操心得从 BN 换到 GN 时学习率可以适当调大一点因为 GN 的梯度性质比 BN 更稳定。我通常会把学习率乘以 1.5 到 2 倍。5.3 风格迁移与生成模型IN 和 LN 的舞台风格迁移任务里IN 几乎是默认选择。AdaINAdaptive Instance Normalization就是 IN 的变体把风格图的均值和方差注入到内容图里。生成对抗网络里LN 和 IN 也很常见比如 StyleGAN 里用了 AdaINBigGAN 里用了 LN。如果你做的是视频生成或者时序生成LN 更合适因为它不依赖空间维度可以处理变长序列。5.4 Transformer 与序列模型LN 是唯一选择Transformer 里 LN 是标配因为序列长度可变BN 和 IN 都没法处理。LN 在d_model维度上归一化跟序列长度和 batch 都无关。这也是为什么 Transformer 能轻松处理单样本推理和变长输入。现在也有一些工作尝试在 Transformer 里用 GN 或者 RMSNorm但 LN 仍然是主流。RMSNorm 可以看作是 LN 的简化版去掉了均值中心化只保留方差缩放计算量更小效果在某些任务上还更好。6. 常见问题与排查技巧实录6.1 训练 loss 震荡严重是不是归一化的问题先检查 batch size。如果 batch size 小于 8BN 的统计量噪声很大loss 震荡是正常的。换成 GN 或者 LN 通常能缓解。如果换了之后还震荡那可能是学习率太大或者数据有问题。另一个常见原因是eps设置不当。BN 默认eps1e-5GN 也是。但如果你的特征值范围很小比如经过 sigmoid 之后都在 0 到 1 之间eps相对就太大了归一化后会引入偏差。这时候可以把eps调到1e-6甚至1e-7。6.2 推理时结果和训练时不一致BN 在训练和推理时的行为不同训练时用 batch 统计量推理时用滑动平均。如果你在推理时忘了调用model.eval()BN 会继续用 batch 统计量导致结果不一致。GN、LN、IN 没有这个问题因为它们不依赖 batch。但 GN 和 LN 在训练和推理时行为一致这既是优点也是缺点。优点是部署简单缺点是训练时没有 BN 那种“正则化”效果。BN 的 batch 统计量噪声其实起到了一定的正则化作用这也是为什么 BN 有时比 GN 泛化更好。6.3 GN 的组数怎么选GN 的组数 G 是一个超参数。经验上通道数 C推荐 G6432128322563251232328 或 16164 或 8G 取 32 在大多数情况下都工作得很好。如果 C 小于 32G 取 C 的约数比如 C16 时 G8 或 4。G 太小比如 1会退化成 LNG 太大比如 C会退化成 IN都不太适合视觉任务。6.4 预训练权重怎么迁移如果你有一个 BN 预训练的模型想换成 GN有两种做法第一种是直接替换然后重新训练。BN 的 gamma 和 beta 可以迁移到 GN 的 gamma 和 beta因为形状都是(C,)。但 running_mean 和 running_var 没法迁移因为 GN 不需要。这种做法的好处是简单坏处是需要重新训练。第二种是保留 BN 的 backbone只在新增的层或者检测头里用 GN。这种做法适合迁移学习但 backbone 的 BN 在小 batch 下仍然不稳定。我通常的做法是如果数据量足够直接全部换成 GN 重新训练如果数据量小保留 BN 但冻结统计量只训练 GN 部分。6.5 常见问题速查表问题现象可能原因解决方法训练 loss 震荡batch size 太小换 GN 或 LN推理结果异常忘了 model.eval()调用 eval()归一化后输出全零eps 太大或输入方差为零调小 eps检查输入GN 效果不如 BNbatch size 其实够大换回 BNLN 在 CNN 上效果差空间信息被抹掉只在最后几层用 LNIN 分类效果差风格信息被丢弃分类任务别用 IN7. 一些容易被忽略的细节7.1 归一化层的位置归一化层通常放在卷积层之后、激活函数之前。这个顺序是经过大量实验验证的。如果你把归一化放在激活之后效果通常会变差。原因在于激活函数比如 ReLU会引入非线性归一化放在激活之后会破坏这种非线性。但也有例外比如 Pre-activation ResNet 把 BN 放在卷积之前效果也不错。所以具体位置还是要根据网络结构来调。7.2 权重初始化与归一化的配合用了归一化之后权重初始化可以更激进一些。BN 论文里提到用了 BN 之后可以用更大的学习率和更简单的初始化。GN 也有类似的性质。我通常用 Kaiming 初始化配合 GN 效果很稳。但要注意归一化层的 gamma 和 beta 初始化很重要。gamma 初始化为 1beta 初始化为 0这是标准做法。如果你把 gamma 初始化得太小归一化的效果会被削弱。7.3 分布式训练下的归一化分布式训练时BN 的统计量是跨 GPU 同步的这需要额外的通信开销。GN、LN、IN 不需要跨 GPU 同步因为它们的统计量只在单个样本内部计算。这也是 GN 在分布式训练里更受欢迎的原因之一。如果你用 PyTorch 的 DistributedDataParallelBN 会自动同步统计量。但如果你用 GN就没有这个开销训练速度会更快。7.4 混合精度训练与归一化混合精度训练时BN 的统计量计算容易溢出因为 FP16 的范围有限。PyTorch 的 AMP 会自动把 BN 的统计量计算转成 FP32但 GN 和 LN 没有这个问题因为它们的计算量更小不容易溢出。如果你在用混合精度训练GN 和 LN 通常比 BN 更稳定。这也是我在小 batch 场景下优先选 GN 的原因之一。8. 从归一化看网络设计的取舍归一化层的选择本质上是一个权衡BN 依赖 batch但统计量准LN 和 IN 不依赖 batch但统计量粒度太粗或太细GN 折中通过分组来平衡。这个权衡背后反映的是深度学习里的一个核心问题如何在有限的计算资源下尽可能准确地估计数据分布。BN 用大 batch 来估计GN 用分组来估计LN 和 IN 用单样本来估计。没有哪种方法是万能的关键看你的场景。我个人的经验是能开大 batch 就用 BN开不了就用 GN序列模型用 LN风格迁移用 IN。这个规则覆盖了 90% 的场景。剩下的 10% 需要你根据具体任务去实验。最后分享一个小技巧如果你不确定用哪种归一化可以先用 GN 跑一遍因为 GN 在小 batch 下最稳而且 G 可以调节。如果 GN 效果不好再尝试 BN 或者 LN。这样能省下不少调参时间。