Bootstrap

理解BatchNorm和LayerNorm的区别和共同点,以及代码解释

BatchNorm通常是在每个特征通道上,对同一个batch中的所有样本进行归一化。比如,对于一个形状为(N, C, H, W)的输入,BatchNorm会对每个通道c,计算该batch中所有N个样本在H和W维度上的均值和方差,然后用这些统计量来归一化。这样,每个通道都有自己的均值和方差,参数也是按通道的。这有助于减少内部协变量偏移,加速训练,并且有一定的正则化效果,因为每个batch的统计量会有噪声。

LayerNorm是针对每个样本单独进行归一化,比如在NLP中,对于每个token的特征,或者是在某个层内。比如,输入形状如果是(N, C),LayerNorm会在C的维度上计算均值和方差,然后归一化。这样,每个样本都有自己的均值和方差,不受batch内其他样本的影响。这对于处理变长序列或者batch size较小的情况可能更好,比如Transformer中就用了LayerNorm。

共同点:两者都是对数据进行归一化,减去均值,除以方差,然后应用缩放和平移参数。这样可以使得数据分布稳定,加速训练。两者的参数都是可学习的,即gamma和beta。

不同点:主要在于归一化的维度。BatchNorm依赖于batch的大小,如果batch较小的话,统计量的估计可能不准确,而LayerNorm不依赖batch维度,因此在batch size变化时更稳定。此外,BatchNorm在训练和测试时的行为不同,测试时使用移动平均的均值和方差,而LayerNorm在训练和测试时的计算方式是一样的,因为都是基于当前样本的数据。

应用场景不同。
图像处理(CNN): 通常使用BatchNorm,因为图像数据具有空间不变性,且Batch统计稳定。
序列模型(RNN/Transformer): 使用LayerNorm,因为序列长度可变,且需要单样本独立性。
小批量训练: LayerNorm更鲁棒,BatchNorm在小Batch时统计量不准确。

BatchNorm的作用:

  1. 加速训练收敛: 通过对每个mini-batch的数据进行归一化,使得每一层的输入分布更加稳定,从而允许使用更大的学习率,加快训练速度。
  2. 缓解梯度消失/爆炸: 归一化后的数据分布在激活函数的敏感区域(如Sigmoid的线性区域),有助于梯度传播。
  3. 正则化效果:由于每个mini-batch的均值和方差不同,引入了一定的噪声,可以起到轻微的正则化作用,减少对Dropout的依赖。
  4. 减少对参数初始化的依赖: 归一化后的数据分布更稳定,降低了对权重初始化的敏感性。

LayerNorm的作用:

  1. 适用于动态网络结构:在处理变长序列(如RNN、Transformer)时,每个样本的长度可能不同,BatchNorm难以处理,而LayerNorm在特征维度归一化,不受Batch大小影响。
  2. 独立于Batch统计: 每个样本独立归一化,适合小批量或单样本训练场景(如在线学习)。
  3. 稳定训练过程:通过归一化每个样本的特征,减少层间输入的分布变化,提升训练稳定性。
  4. 适用于不同模态数据:在自然语言处理、语音识别等领域,LayerNorm表现更好,因为这些任务中特征维度(如词向量)的归一化更合理。

1. BatchNorm(批归一化)

核心思想

每个特征通道,在**批量维度(Batch)**上计算均值和方差,用于归一化。

操作步骤

  1. 统计量计算:沿Batch维度计算当前批次数据的均值和方差。
  2. 归一化:减去均值,除以标准差(加小常数防除零)。
  3. 缩放与平移:应用可学习的参数 gamma(缩放因子)和 beta(平移因子)。

数学公式

x ^ i = x i − μ B σ B 2 + ϵ , y i = γ x ^ i + β \hat{x}_i = \frac{x_i - \mu_B}{\sqrt{\sigma_B^2 + \epsilon}}, \quad y_i = \gamma \hat{x}_i + \beta x^i=σB2+ϵ xiμB,yi=γx^i+β
其中:

  • μ B , σ B 2 \mu_B, \sigma_B^2 μB,σB2:沿Batch维度的均值和方差。
  • ϵ \epsilon ϵ:防止除零的小常数。

特点

  • 依赖Batch大小:小Batch可能导致统计量估计不准确。
  • 训练与测试差异:训练时用当前Batch统计量,测试时用全局移动平均。
  • 适用场景:CNN等固定深度的静态网络(如ResNet)。

2. LayerNorm(层归一化)

核心思想

每个样本,在特征维度上计算均值和方差,独立于其他样本。

操作步骤

  1. 统计量计算:沿特征维度计算单个样本的均值和方差。
  2. 归一化:减去均值,除以标准差。
  3. 缩放与平移:应用 gammabeta

数学公式

x ^ i = x i − μ L σ L 2 + ϵ , y i = γ x ^ i + β \hat{x}_i = \frac{x_i - \mu_L}{\sqrt{\sigma_L^2 + \epsilon}}, \quad y_i = \gamma \hat{x}_i + \beta x^i=σL2+ϵ xiμL,yi=γx^i+β
其中:

  • μ L , σ L 2 \mu_L, \sigma_L^2 μL,σL2:沿特征维度的均值和方差。

特点

  • 独立于Batch:统计量仅依赖单个样本,适合变长数据(如文本、语音)。
  • 训练与测试一致:无需区分模式,始终用当前输入计算统计量。
  • 适用场景:RNN、Transformer等动态网络。

3. 异同对比

特性BatchNormLayerNorm
统计量维度沿Batch维度计算(跨样本)沿特征维度计算(单样本内)
Batch依赖依赖Batch大小,小Batch效果差与Batch无关,适合小或动态Batch
训练/测试测试时用移动平均统计量训练和测试行为一致
适用场景CNN(图像)RNN、Transformer(序列数据)
参数每特征通道的 gammabeta同左

4. 代码实现

BatchNorm手动实现

import torch

def manual_batchnorm(x, gamma, beta, eps=1e-5):
    """
    BatchNorm手动实现
    输入:
        x: 输入数据,形状为 [batch_size, features]
        gamma: 缩放参数,形状为 [features]
        beta: 平移参数,形状为 [features]
        eps: 防止除零的小常数
    """
    # 沿Batch维度计算均值和方差(跨样本统计)
    mean = x.mean(dim=0)                 # 形状 [features]
    var = x.var(dim=0, unbiased=False)   # 形状 [features](无偏估计设为False,与PyTorch一致)

    # 归一化:x_hat = (x - mean) / sqrt(var + eps)
    x_hat = (x - mean) / torch.sqrt(var + eps)

    # 应用缩放和平移:y = gamma * x_hat + beta
    y = gamma * x_hat + beta             # gamma和beta广播到[batch_size, features]

    return y

# 测试代码
batch_size = 2
features = 3
x = torch.tensor([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]])  # 输入数据 [2, 3]
gamma = torch.ones(features)              # 初始化为全1 [3]
beta = torch.zeros(features)              # 初始化为全0 [3]

# 手动实现结果
manual_output = manual_batchnorm(x, gamma, beta)
print("Manual BatchNorm Output:\n", manual_output)

LayerNorm手动实现

def manual_layernorm(x, gamma, beta, eps=1e-5):
    """
    LayerNorm手动实现
    输入:
        x: 输入数据,形状为 [batch_size, features]
        gamma: 缩放参数,形状为 [features]
        beta: 平移参数,形状为 [features]
        eps: 防止除零的小常数
    """
    # 沿特征维度计算均值和方差(单样本统计)
    mean = x.mean(dim=1, keepdim=True)    # 形状 [batch_size, 1]
    var = x.var(dim=1, keepdim=True, unbiased=False)  # 形状 [batch_size, 1]

    # 归一化:x_hat = (x - mean) / sqrt(var + eps)
    x_hat = (x - mean) / torch.sqrt(var + eps)

    # 应用缩放和平移:y = gamma * x_hat + beta
    y = gamma * x_hat + beta              # gamma和beta广播到[batch_size, features]

    return y

# 手动实现结果
manual_output = manual_layernorm(x, gamma, beta)
print("Manual LayerNorm Output:\n", manual_output)

不同点:

1. BatchNorm:跨样本归一化(dim=0)(同一特征在不同样本中的值)
dim=0:沿行方向操作(保留列)
输入形状假设:假设输入数据为 [batch_size, features](二维)
核心目标:对每个特征通道(如RGB图像的红色通道),跨所有样本计算统计量,使该特征在不同样本中的分布一致。
计算逻辑
沿着 dim=0(Batch维度) 计算均值和方差,即对每个特征(或通道)在所有样本上求统计量。
例如,输入形状为 [4, 3](4个样本,3个特征)时,dim=0 表示对4个样本的同一特征值计算均值和方差。
目的:稳定同一特征在不同样本中的分布

# 输入形状 [batch_size, features] = [4, 3]
x = torch.tensor([[1.0, 2.0, 3.0],
                  [4.0, 5.0, 6.0],
                  [7.0, 8.0, 9.0],
                  [10.0, 11.0, 12.0]])

# 对每个特征(列)跨样本计算均值和方差
mean = x.mean(dim=0)  # 形状 [3],如 [5.5, 6.5, 7.5]
var = x.var(dim=0)    # 对每个特征列求方差

2. LayerNorm:单样本内归一化(dim=1)(同一样本内所有特征的值)
dim=1:沿列方向操作(保留行)
核心目标:对每个样本的所有特征进行归一化,使同一样本内不同特征的分布一致。
输入形状假设:输入数据通常为 [batch_size, features](如自然语言处理中的词向量序列)。
计算逻辑:
沿着 dim=1(特征维度) 计算均值和方差,即对每个样本的所有特征求统计量。
例如,输入形状为 [4, 3] 时,dim=1 表示对每个样本的3个特征值计算均值和方差。
**目的:**消除样本内不同特征的量纲差异,提升模型对动态数据(如变长文本)的适应性。

# 输入形状 [batch_size, features] = [4, 3]
x = torch.tensor([[1.0, 2.0, 3.0],
                  [4.0, 5.0, 6.0],
                  [7.0, 8.0, 9.0],
                  [10.0, 11.0, 12.0]])

# 对每个样本的所有特征计算均值和方差
mean = x.mean(dim=1, keepdim=True)  # 形状 [4, 1],如 [2.0, 5.0, 8.0, 11.0]
var = x.var(dim=1, keepdim=True)     # 对每个样本行求方差

BatchNorm处理图像(4D输入)
输入形状为 [batch_size, channels, height, width] 时,BatchNorm对每个通道(dim=1)在 batch_size, height, width 维度上计算统计量:

mean = x.mean(dim=[0, 2, 3], keepdim=True)  # 沿Batch和空间维度

或者

# 对每个通道 c ∈ [1, C]
mean_c = 均值(x[:, c, :, :])  # 计算所有样本和空间位置的均值
var_c = 方差(x[:, c, :, :])  # 计算所有样本和空间位置的方差

dim=[0, 2, 3] 表示沿 Batch、高度、宽度三个维度聚合(压缩),最终保留通道维度 C。
结果形状为 (C,),每个元素对应一个通道的均值。

LayerNorm处理序列(3D输入)
输入形状为 [batch_size, seq_length, features] 时,LayerNorm对每个样本的所有特征和序列位置求统计量:

mean = x.mean(dim=[1, 2], keepdim=True)  # 沿序列长度和特征维度

5. 简单实现的 Batch Normalization(批量归一化)用于神经网络训练中

import torch
import torch.nn as nn
import torch.nn.functional as F

class BatchNormalization(nn.Module):
    def __init__(self, num_features, eps=1e-5, momentum=0.1):
        super(BatchNormalization, self).__init__()
        self.num_features = num_features
        self.eps = eps
        self.momentum = momentum

        # 可学习的参数 gamma 和 beta
        self.gamma = nn.Parameter(torch.ones(num_features))
        self.beta = nn.Parameter(torch.zeros(num_features))

        # 存储均值和方差,用于推理阶段
        self.running_mean = torch.zeros(num_features)
        self.running_var = torch.ones(num_features)

    def forward(self, x):
        if self.training:
            # 在训练过程中计算每个批次的均值和方差
            batch_mean = x.mean(dim=0)
            batch_var = x.var(dim=0, unbiased=False)

            # 规范化输入
            x_hat = (x - batch_mean) / torch.sqrt(batch_var + self.eps)

            # 更新均值和方差的移动平均
            self.running_mean = self.momentum * batch_mean + (1 - self.momentum) * self.running_mean
            self.running_var = self.momentum * batch_var + (1 - self.momentum) * self.running_var

            # 应用可学习的参数 gamma 和 beta
            out = self.gamma * x_hat + self.beta
        else:
            # 在推理阶段使用全局均值和方差
            x_hat = (x - self.running_mean) / torch.sqrt(self.running_var + self.eps)
            out = self.gamma * x_hat + self.beta

        return out

class SimpleNN(nn.Module):
    def __init__(self):
        super(SimpleNN, self).__init__()
        self.fc1 = nn.Linear(128, 64)
        self.bn1 = BatchNormalization(64)  # 添加 Batch Normalization
        self.fc2 = nn.Linear(64, 10)

    def forward(self, x):
        x = self.fc1(x)
        x = self.bn1(x)  # 使用 Batch Normalization
        x = F.relu(x)
        x = self.fc2(x)
        return x

# 示例:创建并测试模型
model = SimpleNN()
input_data = torch.randn(32, 128)  # 假设我们有32个样本,每个样本128维
output = model(input_data)
print(output.shape)  # 输出的形状应该是 (32, 10)

代码解释:
1. 初始化参数:
• num_features:输入特征的数量(通常是神经网络中某一层的输出通道数或特征数量)。
• eps:为了数值稳定性,防止除以零的一个小常数。
• momentum:用于控制均值和方差的移动平均的权重。
• gamma 和 beta:这两个参数是可学习的,分别用于缩放和偏移。它们帮助网络恢复原有的表达能力。
• running_mean 和 running_var:这两个是用于推理阶段的全局均值和方差,它们是在训练时根据每一批次的统计信息通过移动平均计算得到的。
2. 前向传播(forward):
• 训练阶段:计算当前批次的均值和方差,然后对输入进行标准化。接着应用 gamma 和 beta 参数来进行缩放和偏移。
• 推理阶段:使用训练期间累积的均值和方差进行标准化,而不是当前批次的均值和方差。


6. 总结

BatchNorm:通过跨样本归一化稳定训练,适合图像等静态数据。
LayerNorm:通过单样本归一化处理动态数据,适合序列模型。
共同目标:加速收敛,减少对初始化的敏感度。

【深度学习】BatchNorm、LayerNorm

;