实现层归一化
五、Layer Normalization
层归一化介绍
-
层归一化(LayerNorm)通过对同一层的神经元输出进行归一化处理,有效提升模型训练稳定性。与BatchNorm不同,LayerNorm对单个样本的所有特征维度进行归一化,使其对序列长度变化具有更好的适应性。
当处理变长序列数据时,LayerNorm保持每个时间步的独立计算特性,避免不同序列长度带来的统计量偏差。该操作通过可学习的缩放参数gamma和平移参数beta保留模型的表达能力。
数学公式
-
层归一化对输入张量最后一个维度进行标准化处理:
μ = 1 d m o d e l ∑ i = 1 d m o d e l x i σ 2 = 1 d m o d e l ∑ i = 1 d m o d e l ( x i − μ ) 2 out = γ ⋅ x − μ σ 2 + ϵ + β \mu = \frac{1}{d_{model}}\sum_{i=1}^{d_{model}}x_i \\ \sigma^2 = \frac{1}{d_{model}}\sum_{i=1}^{d_{model}}(x_i - \mu)^2 \\ \text{out} = \gamma \cdot \frac{x - \mu}{\sqrt{\sigma^2 + \epsilon}} + \beta μ=dmodel1i=1∑dmodelxiσ2=dmodel1i=1∑dmodel(xi−μ)2out=γ⋅σ2+ϵx−μ+β
其中:- γ ∈ R d _ m o d e l \gamma \in \mathbb{R}^{d\_{model}} γ∈Rd_model:可学习缩放参数(初始化为1)
- β ∈ R d _ m o d e l \beta \in \mathbb{R}^{d\_{model}} β∈Rd_model:可学习平移参数(初始化为0)
- ϵ \epsilon ϵ:数值稳定系数(默认1e-12)
d _ m o d e l d\_model d_model 为模型维度。
代码实现
-
层归一化代码实现
import torch from torch import nnclass LayerNorm(nn.Module):def __init__(self, d_model, eps=1e-12):super(LayerNorm, self).__init__()self.d_model = d_modelself.eps = eps# 可学习参数初始化self.gamma = nn.Parameter(torch.ones(d_model)) # 缩放参数self.beta = nn.Parameter(torch.zeros(d_model)) # 平移参数def forward(self, x):"""shape of x: [batch_size,seq_len,d_model]shape of mean and var : [batch_size,seq_len,1]shape of gamma and beta: [d_model]"""# 步骤1:计算最后一个维度的均值和方差mean = x.mean(dim=-1, keepdim=True)var = x.var(dim=-1, unbiased=False, # 使用有偏方差估计(与PyTorch官方实现保持一致)keepdim=True # 保持维度对其)# 步骤2:标准化计算normalized = (x - mean) / torch.sqrt(var + self.eps)# 步骤3:仿射变换out = self.gamma * normalized + self.betareturn out
-
注意
-
在统计学中,“unbiased”(无偏)通常指的是一个估计量,其期望值等于所估计的参数值。对于方差的计算,有两种常用的方法:无偏估计和最大似然估计。
无偏估计(Bessel’s Correction):在计算样本方差时,通常使用无偏估计,其公式为:
Var = 1 N − 1 ∑ i = 1 N ( x i − x ˉ ) 2 \text{Var} = \frac{1}{N-1} \sum_{i=1}^{N} (x_i - \bar{x})^2 Var=N−11i=1∑N(xi−xˉ)2
这里我们除以的是 (N-1),而不是样本数量 (N)。这是因为用 (N-1) 除法能够补偿样本方差相对于总体方差的系统性低估,也就是所谓贝塞尔校正(Bessel’s correction)。有偏估计(或最大似然估计,MLE):这是普遍用于深度学习中的方法,就是直接使用:
Var = 1 N ∑ i = 1 N ( x i − x ˉ ) 2 \text{Var} = \frac{1}{N} \sum_{i=1}^{N} (x_i - \bar{x})^2 Var=N1i=1∑N(xi−xˉ)2其中 N 为样本总量,延伸到我们transformer中就是 d _ m o d e l d\_model d_model 。
-
使用有偏估计可以减小计算的偏差,提升计算的效率和速度,这个同事也在深度学习过程中允许更快的梯度更新,虽然有轻微的偏差,但通常在大量数据下这并不显著影响模型的训练效果。
-
unbiased=False
是指定要使用有偏估计来计算方差。这默认为False
,偏向于加速计算并使得在深度学习环境下的表现更加稳定。
-
-
维度计算流程
操作步骤 张量形状变化示例 输入数据 [batch_size, seq_len, d_model] 步骤1 - 计算均值(dim=-1) [batch_size, seq_len, 1] 步骤1 - 计算方差(dim=-1) [batch_size, seq_len, 1] 步骤2 - 标准化计算 [batch_size, seq_len, d_model] 步骤3 - 线性变换(gamma/beta) [batch_size, seq_len, d_model]
使用示例
-
测试代码
if __name__ == "__main__":# 参数配置batch_size = 4seq_len = 100d_model = 512# 生成测试数据x = torch.randn(batch_size, seq_len, d_model)# 实例化层归一化模块layer_norm = LayerNorm(d_model=d_model)# 前向传播out = layer_norm(x)# 验证输出print("输入形状:", x.shape) # torch.Size([4, 100, 512])print("输出形状:", out.shape) # torch.Size([4, 100, 512])print("参数gamma形状:", layer_norm.gamma.shape) # torch.Size([512])print("参数beta形状:", layer_norm.beta.shape) # torch.Size([512])