【大模型】归一化方法之Batch Norm, Layer Norm, RMS Norm

什么是 Normalization(归一化)

Normalization在统计学中一般翻译为归一化,现在已经成了神经网络中不可缺少的一个重要模块了。还有类似的是Standardization,一般翻译成标准化。这两个概念有什么区别呢?

  • 归一化是将数据缩放到0-1之间
  • 标准化是将数据缩放到均值为0,方差为1的正态分布。

在这里插入图片描述

有时候Normalization和Standardization会混淆,注意看清楚即可,不纠结细节。

注意:我们下面讲到的 Normalization归一化 严格讲应该称为 Standardization 标准化
下面讲到的Normalization归一化的本质都是减去均值除以方差,进行线性映射后,使得数据满足某个稳定分布。其描述了一种把样本调整到均值为 0,方差为 1 的缩放平移操作。使用这种方法可以消除输入数据的量纲,有利于随机初始化的网络训练。

1. Batch Norm(批归一化)

BatchNorm(Batch Normalization),批归一化,旨在提高神经网络的训练速度、稳定性和性能。它由 Sergey Ioffe 和 Christian Szegedy 于 2015 年提出。BatchNorm 主要解决的问题是在训练深度神经网络时出现的内部协变量偏移(Internal Covariate Shift),即网络中各层输入数据分布的变化。

内部协变量偏移是指,随着网络层次的加深,每一层的输入数据(即前一层的输出)的分布可能会发生变化,这可能会导致训练过程中的梯度问题,比如梯度消失或梯度爆炸,从而影响网络的收敛速度和稳定性。

在这里插入图片描述

工作原理

BatchNorm关注的是批次中每一列的数据,这些数据代表了 不同样本的同一个特征。 BatchNorm 的工作原理如下图所示:

在这里插入图片描述

  • 归一化:在训练过程中,BatchNorm 对每个小批量(mini-batch)的数据进行归一化处理,即计算该批量数据的均值和方差,并使用这些统计量将数据标准化,使得数据的均值为 0,方差为 1。

  • 缩放和偏移:归一化后的数据会经过两个可学习的参数(上图公式中的 γ \gamma γ β \beta β),即 缩放因子(gamma)和偏移因子(beta),这两个参数允许网络在训练过程中学习到最佳的归一化方式。

  • 应用:BatchNorm 通常在神经网络的层之间应用,特别是在卷积层或全连接层之后,激活函数之前

代码实现

class BatchNorm(nn.Module):
    # num_features:完全连接层的输出数量或卷积层的输出通道数。
    def __init__(self, num_features, num_dims):
        super().__init__()
        if num_dims == 2: # 全连接层
            shape = (1, num_features)
        else:             # 卷积层
            shape = (1, num_features, 1, 1)
        
        # 参与求梯度和迭代的拉伸和偏移参数,分别初始化成1和0
        self.gamma = nn.Parameter(torch.ones(shape))
        self.beta = nn.Parameter(torch.zeros(shape))
        
        # 非模型参数的变量初始化为0和1
        self.moving_mean = torch.zeros(shape)
        self.moving_var = torch.ones(shape)

    def batch_norm(self, X, gamma, beta, moving_mean, moving_var, eps, momentum):
        if not torch.is_grad_enabled():
            # 如果是在预测模式下,直接使用传入的移动平均所得的均值和方差
            X_hat = (X - moving_mean) / torch.sqrt(moving_var + eps)
        else:
            assert len(X.shape) in (2, 4)
            if len(X.shape) == 2:
                # 使用全连接层的情况,计算特征维上的均值和方差
                mean = X.mean(dim=0)                                       # (num_features,)
                var = ((X - mean) ** 2).mean(dim=0)                        # (num_features,)
            else:
                # 使用二维卷积层的情况,计算通道维上(axis=1)的均值和方差。
                mean = X.mean(dim=(0, 2, 3), keepdim=True)                # (1,num_features,1,1) 保持X的形状,以便后面可以做广播运算
                var = ((X - mean) ** 2).mean(dim=(0, 2, 3), keepdim=True) # (1,num_features,1,1)
                
            # 训练模式下,用当前的均值和方差做标准化
            X_hat = (X - mean) / torch.sqrt(var + eps)
            
            # 更新移动平均的均值和方差
            moving_mean = momentum * moving_mean + (1.0 - momentum) * mean
            moving_var = momentum * moving_var + (1.0 - momentum) * var
            
        Y = gamma * X_hat + beta  # 缩放和移位
        return Y, moving_mean.data, moving_var.data

    def forward(self, X):
        # 如果X不在内存上,将moving_mean和moving_var,复制到X所在显存上
        if self.moving_mean.device != X.device:
            self.moving_mean = self.moving_mean.to(X.device)
            self.moving_var = self.moving_var.to(X.device)
       
        # 保存更新过的moving_mean和moving_var
        Y, self.moving_mean, self.moving_var = self.batch_norm(
            X, self.gamma, self.beta, self.moving_mean,
            self.moving_var, eps=1e-5, momentum=0.9
        )
        return Y


2. Layer Norm(层归一化)

LayerNorm(Layer Normalization),层归一化是一种在深度学习中用于稳定神经网络训练和提高性能的技术。它是由 Jimmy Lei Ba、Jamie Ryan Kiros、Geoffrey E. Hinton 在 2016 年提出的。LayerNorm 与 Batch Normalization 类似,都旨在减少内部协变量偏移,但它们在归一化的具体实现上有所不同。

工作原理

LayerNorm的解决方案是对 每个样本的所有特征 进行单独归一化,而不是基于整个批次。

具体来说,LayerNorm 会计算单个样本在其所在层的所有激活值的均值和方差,并使用这些统计量来归一化该样本的激活值。这种方法不依赖于小批量统计量,因此可以减少 BatchNorm 中的噪声问题,并允许网络使用更小的小批量大小进行训练。

LayerNorm针对一个样本的 所有特征 计算均值和方差,然后使用这些来对样本进行归一化:

在这里插入图片描述

这里 x = ( x 1 , x 2 , ⋯   , x H ) x = ( x_1 , x_2 , ⋯   , x_H ) x=(x1,x2,,xH) 表示某个时间步LN层的输入向量表示,向量维度为 H H H h h h 是LN层的输出; g g g , b b b 是两个可学习的参数,分别代表缩放因子(gamma)和偏移因子(beta)。

LayerNorm 的工作原理如下:

  • 归一化:对于网络中的每一层,LayerNorm 会计算该层所有激活值的均值和方差。然后,使用这些统计量将激活值归一化,使得每个样本的激活值的均值为 0,方差为 1。

  • 缩放和偏移:与 BatchNorm 类似,归一化后的数据会经过两个可学习的参数(公式中的 g g g b b b),即缩放因子(gamma)和偏移因子(beta),这两个参数允许网络在训练过程中学习到最佳的归一化方式。

  • 应用:LayerNorm 可以应用于神经网络的任何层,包括卷积层和循环层,通常放在激活函数之前。

为什么层归一化有用?一些解释如下:

  1. 减少内部协变量偏移(Internal Covariate Shift): 内部协变量偏移是指在深度神经网络的训练过程中,每一层输入的分布会发生变化,导致网络的训练变得困难。层归一化通过对每一层的输入进行归一化处理,可以减少内部协变量偏移,使得每一层的输入分布更加稳定。
  2. 稳定化梯度: 层归一化有助于保持每一层输出的均值和方差稳定,从而使得梯度的传播更加稳定。这有助于减少梯度消失或梯度爆炸的问题,提高梯度在网络中的流动性,加快训练速度。
  3. 更好的参数初始化和学习率调整: 通过层归一化,每一层的输入分布被归一化到均值为0、方差为1的标准正态分布,这有助于更好地初始化网络参数和调整学习率。参数初始化与学习率调整的稳定性对模型的训练效果至关重要。
  4. 增强模型的泛化能力: 层归一化可以减少网络对训练数据分布的依赖,降低了过拟合的风险,从而提高模型的泛化能力。稳定的输入分布有助于模型更好地适应不同数据集和任务。

Batch Norm与Layer Norm的比较

Normalization技术旨在应对内部协变量偏移问题,它的核心在于将数据调整到一个统一的标准,以便进行有效的比较和处理。为了实现这一目标,我们需要确保参与归一化的数据点在本质上是可比的。

  • Batch Normalization(BatchNorm):这是一种对整个批次数据进行归一化的方法。具体来说,BatchNorm关注的是批次中每一列的数据,这些数据代表了不同样本的同一个特征。 因为这些特征在统计上是相关的,所以它们可以被合理地放在一起进行归一化处理。这就像是在同一个班级里,比较不同学生的同一科目成绩,因为这些成绩都在相同的评分标准下,所以可以直接比较。

BatchNorm常用于CV领域,其对整个 batch 样本内的每个特征做归一化,这消除了不同特征之间的大小关系。假设输入图像的尺寸为 b × c × h × w b\times c\times h\times w b×c×h×w (批量大小 * 通道 * 长 * 宽),图像的每个通道 c c c 看作一个特征,BN 可以把各通道特征图的数量级调整到差不多,同时保持不同图片相同通道特征图间的相对大小关系。

  • Layer Normalization(LayerNorm):与BatchNorm不同,LayerNorm适用于那些在不同样本之间难以直接比较的情况,如Transformer中的自注意力机制。在这些模型中,每个位置上的数据代表了不同的特征,因此直接归一化可能会失去意义。LayerNorm的解决方案是对每个样本的所有特征进行单独归一化,而不是基于整个批次。 这就像是评估每个学生在所有科目中的表现,而不是仅仅关注单一科目,这样可以更全面地理解每个学生的整体表现。

LayerNorm常用于NLP领域(适用于 Transformer 类模型),其对每个样本的所有特征做归一化,这消除了不同样本间的大小关系,但是保留了一个样本内不同特征之间的大小关系。

3. RMS Norm(均方根层归一化)

RMS Norm(Root MeanSquare Layer Norm),均方根层归一化 是 LayerNorm 的一个简单变体,来自 2019 年的论文 Root Mean Square Layer Normalization,被当前主流的大模型( LLama,Qwen, DeepSeek等 )所使用。

均方根层归一化认为,虽然LayerNorm很好,但是它每次需要计算均值和方差。实际上,Layer Norm成功的原因是re-scaling,因为方差Var计算的过程中使用了均值Mean。因此,RMS Norm不再使用均值Mean,而是构造了一个特殊的 统计量RMS 代替方差Var。

RMSNorm和LayerNorm的主要区别在于:RMSNorm不需要同时计算均值和方差两个统计量,而只需要计算均方根 Root Mean Square (RMS) 均方根 这一个统计量,公式如下:

在这里插入图片描述

这里, g i gi gi 为可学习的缩放因子,偏移参数 b i bi bi 移除掉了。

单看上式的话,相当于仅使用 x x x 的均方根来对输入进行归一化,它简化了层归一化的计算,使得计算量更小,同时还有可能带来性能上的提升。(实验表明:RMSNorm 性能和 LayerNorm 相当,但是可以节省7%到64%的运算量。)

LayerNorm与RMSNorm的比较

  • 计算复杂度:RMSNorm不计算均值,仅计算均方根(RMS),其归一化操作不减去均值,直接除以均方根,降低了整体计算量。
  • 数值稳定性:RMSNorm避免了方差接近零的情况,提升了数值稳定性。
  • 表现性能:在某些任务中,RMSNorm可以达到或超过LayerNorm的性能。

总结而言,RMSNorm通过简化归一化过程,降低计算复杂度,提供了一种有效的归一化方法。它在保持模型性能的同时,提高了计算效率,是LayerNorm的有力替代方案。对于需要高效归一化操作的深度学习模型,RMSNorm是一个值得考虑的选择。

代码实现

RMSNorm 在HuggingFace Transformer 库中代码实现如下所示:

class LlamaRMSNorm(nn.Module):
    def __init__(self, hidden_size, eps=1e-6):
        """
        LlamaRMSNorm is equivalent to T5LayerNorm
        """
        super().__init__()
        self.weight = nn.Parameter(torch.ones(hidden_size))
        self.variance_epsilon = eps # eps 防止取倒数之后分母为0
    def forward(self, hidden_states):
        input_dtype = hidden_states.dtype
        variance = hidden_states.to(torch.float32).pow(2).mean(-1, keepdim=True)
        hidden_states = hidden_states * torch.rsqrt(variance + self.variance_epsilon)
        # weight 是末尾乘的可训练参数, 即g_i
        return (self.weight * hidden_states).to(input_dtype)

参考资料

### RMSNorm 的反向传播与计算方法 #### RMSNorm 的定义及其前向传播 RMSNorm 是一种用于神经网络中的归一化技术,其核心思想在于通过去除均值统计量的影响而仅保留平方根平均(Root Mean Square, RMS),从而简化计算并提高效率。具体来说,在给定输入张量 \( \mathbf{x} \) 下,RMSNorm 输出可以表示为: \[ \text{RMSNorm}(x_i)=\frac{\mathbf{x}_i}{(\sqrt{\frac{1}{n}\sum_{j=1}^{n} (\mathbf{x}_{ij}-\mu)^2+\epsilon})}, \] 其中 \( n \) 表示特征维度大小;\( \mu \) 代表批处理样本的均值;\( \epsilon \) 则是一个很小的常数值用来防止除零错误[^2]。 然而实际上为了进一步简化运算过程以及加速收敛速度,通常会省略掉分子部分对于均值的操作,即直接采用未经中心化的版本: \[ y=\frac{\mathbf{x}}{\sigma + \epsilon }, \quad \sigma = \sqrt{\frac{1}{d}\sum_k x_k^2 }.\] 这里 \( d \) 表明的是沿着指定轴上的元素数量。 #### 反向传播推导 在进行反向传播时,目标是从损失函数 L 对权重 w 和偏置 b 进行更新操作之前先求得关于输入变量 x 的梯度 ∂L/∂x 。根据链式法则可得: \[ \delta_x := \frac{\partial L}{\partial \mathbf{x}} = \left( \frac{\partial L}{\partial \hat{\mathbf{x}}} \right)\odot \left[\frac{-0.5*(\mathbf{x}-\bar{\mathbf{x}})}{{((\sigma ^2)+\epsilon )}^\frac{3}{2}} * (2*\mathbf{x}/m-\frac{(2*\sum _k\mathbf{x}_k)}{m*m})\right],\] 此处 \( m=d \),并且注意到由于忽略了均值项因此有 \( \bar{\mathbf{x}}=0 \)[^4]。 上述表达式的含义是当前节点处误差信号 δx 应该等于后续层传递回来的误差乘上局部梯度的结果。这里的局部梯度由两部分组成:一是来自标准化因子 σ 的倒数变化率;二是来自于对原始输入 x 自身变动所引起的响应强度调整。 值得注意的是,在实际编程实践中往往不会严格按照这个公式逐条编写代码,而是借助自动微分工具如 PyTorch 或 TensorFlow 来完成这一工作。这些框架能够自动生成高效的 C++ 后端执行计划,并支持 GPU 加速等功能特性。 ```python import torch.nn as nn from typing import Optional class RMSNorm(nn.Module): def __init__(self, normalized_shape: int | tuple[int], eps: float = 1e-8, elementwise_affine: bool = True): super().__init__() self.normalized_shape = normalized_shape self.eps = eps self.elementwise_affine = elementwise_affine if self.elementwise_affine: self.weight = nn.Parameter(torch.ones(normalized_shape)) else: self.register_parameter('weight', None) def forward(self, input_tensor: torch.Tensor) -> torch.Tensor: variance = input_tensor.pow(2).mean(-1, keepdim=True) output = input_tensor / torch.sqrt(variance + self.eps) if self.weight is not None: output = output * self.weight return output def extra_repr(self) -> str: return f'normalized_shape={self.normalized_shape}, ' \ f'eps={self.eps}, ' \ f'elementwise_affine={self.elementwise_affine}' ``` 此段 Python 实现展示了如何构建一个简单的 RMSNorm 层类,它继承自 `torch.nn.Module` 并实现了正向传播逻辑。注意在这个例子中并没有显式地写出完整的反向传播路径,因为这一步骤是由 PyTorch 框架内部机制负责处理的。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值