数学基础:均值与方差的计算

好,咱们进入正题。LayerNorm 的数学基础,说白了就是两个老朋友:均值和方差。你想想看,归一化操作的核心,不就是把数据拉回到一个稳定的分布上吗?那怎么描述分布?均值定中心,方差定范围。这两个概念搞不清楚,后面反向传播推导你肯定晕。

我个人习惯,讲数学之前先问个问题:为什么 LayerNorm 需要均值和方差? 因为神经网络里的数据,经过几层线性变换和激活函数后,分布早就歪了。有的神经元输出特别大,有的特别小。如果不做处理,梯度要么爆炸要么消失。嗯,这里要注意,均值和方差就是用来做「对齐」的尺子。

均值:数据的重心

均值,也叫期望。公式很简单:

μ = (1/N) * Σ x_i

就是把所有数加起来,除以个数。但我在项目中遇到过一个问题:均值对异常值非常敏感。比如你有一批特征,大部分在 0 到 1 之间,突然冒出来一个 1000。这个均值就会被拉得老高,归一化后正常数据反而被压扁了。所以实际工程中,有时候会用截断均值或者中位数替代,但 LayerNorm 里还是用标准均值,因为计算简单,反向传播好求导。

小技巧: 写代码实现时,注意数值稳定性。如果数据量很大,累加可能溢出。我建议用 np.mean(x, axis=-1, keepdims=True),框架已经帮你处理好了。

方差:数据的离散程度

方差衡量数据偏离均值的程度。公式有两个版本,你都得记住:

σ² = (1/N) * Σ (x_i - μ)²        # 总体方差
σ² = (1/(N-1)) * Σ (x_i - μ)²    # 样本方差(无偏估计)

LayerNorm 用的是总体方差,也就是除以 N 而不是 N-1。为什么?因为我们在做归一化,不是做统计推断。你想想看,我们关心的是当前这批数据本身的分布,而不是用它去估计总体的方差。所以直接用 N 除,计算简单,反向传播也干净。

我曾经踩过一个坑:在实现自定义 LayerNorm 时,用了 torch.var 函数,默认是除以 N-1。结果模型训练 loss 死活降不下去。查了半天才发现方差算错了。嗯,这里要提醒你:用框架自带函数时,一定要看清楚参数。PyTorch 里 torch.var 默认无偏,torch.var(unbiased=False) 才是我们要的。

计算中的维度问题

LayerNorm 和 BatchNorm 最大的区别,就是统计量计算的维度不同。BatchNorm 对 batch 维度算,LayerNorm 对特征维度算。举个例子:

假设输入形状是 (batch_size, seq_len, hidden_dim),比如 (2, 3, 4)。

  • BatchNorm:对每个特征位置,跨 batch 和 seq_len 算均值和方差。结果形状是 (1, 1, 4)。
  • LayerNorm:对每个样本的每个位置,跨 hidden_dim 算均值和方差。结果形状是 (2, 3, 1)。

说白了,LayerNorm 是每个 token 自己做归一化,跟其他样本无关。这在 NLP 任务里特别有用,因为句子长度不一样,batch 里样本之间差异大,BatchNorm 容易不稳定。

核心要点: LayerNorm 的均值和方差,是在最后一个维度(特征维度)上计算的。保持维度对齐,才能正确广播。

代码实现:手写均值与方差

咱们来写个简单的实现,加深理解。假设输入 x 形状是 (batch, seq_len, hidden_dim):

import numpy as np

def layer_norm_stats(x):
    # x: (batch, seq_len, hidden_dim)
    # 计算均值,沿着最后一个维度
    mean = np.mean(x, axis=-1, keepdims=True)  # (batch, seq_len, 1)
    
    # 计算方差,注意用总体方差
    var = np.mean((x - mean) ** 2, axis=-1, keepdims=True)  # (batch, seq_len, 1)
    
    return mean, var

# 测试
x = np.random.randn(2, 3, 4)
mean, var = layer_norm_stats(x)
print("均值形状:", mean.shape)  # (2, 3, 1)
print("方差形状:", var.shape)   # (2, 3, 1)

你看,输出形状里保留了 seq_len 维度,每个位置都有自己的均值和方差。这就是 LayerNorm 的「逐样本、逐位置」归一化。

反向传播中的均值和方差

这里要提前打个预防针:反向传播时,均值和方差不是常数。它们依赖于输入 x,所以求梯度时,要对均值和方差也求导。很多初学者在这里翻车,以为归一化后的输出对 x 求导就是简单的线性关系。其实不是。

举个例子,假设归一化后的输出是:

y_i = (x_i - μ) / √(σ² + ε)

那么 ∂y_i / ∂x_j 就不是简单的 1/σ,因为 μ 和 σ² 都依赖于 x_j。具体推导我们后面章节会详细讲,但你现在要记住:均值和方差引入了全局依赖,每个输出 y_i 对每个输入 x_j 都有梯度贡献。

避坑指南: 我曾经在实现自定义梯度时,忘了对均值求导,结果梯度算出来只有正确值的一半。检查了三天才发现。所以,永远不要把均值和方差当成常数,除非你用了 stop_gradient。

数值稳定性:ε 的作用

方差开根号后做分母,如果方差为 0,那就除零了。所以 LayerNorm 里会加一个小常数 ε,通常是 1e-5 或 1e-6。这个 ε 不能太大,否则归一化效果变差;也不能太小,否则数值不稳定。

我建议:ε 用 1e-5 起步。如果训练过程中出现 NaN,再调大。在混合精度训练(FP16)时,ε 可能需要调到 1e-3 甚至 1e-2,因为半精度浮点数的表示范围更小。

总结一下

均值和方差是 LayerNorm 的基石。你需要掌握:

  1. 均值:定中心,对异常值敏感
  2. 方差:定范围,用总体方差(除以 N)
  3. 维度:在最后一个特征维度上计算
  4. 反向传播:均值和方差也是变量,需要求导
  5. 数值稳定性:加 ε,注意精度

下一章,我们会把这些数学公式串起来,推导 LayerNorm 的完整前向和反向传播。准备好了吗?