【深度学习八股总结】Batch/Layer/Instance/Group Normalization

部署运行你感兴趣的模型镜像

Batch Normalization

对一个batch内的数据计算均值和方差,将数据归一化为均值为0、方差为1的正态分布数据,最后用对数据进行缩放和偏移来还原数据本身的分布:

BN测试和训练时的差异

核心区别在于统计量(均值和方差)的来源与计算方式,训练时计算Batch内数据的均值 (μbatchμ_{batch}μbatch) 和方差 (σbatch2σ²_{batch}σbatch2),测试时使用全局训练数据的滑动平均计算均值和方差。

目的:保证输出的确定性、一致性处理任意大小输入的能力。

以下是主要差异的详细对比:

特征训练阶段 (Training)测试阶段 (Inference/Evaluation)
统计量来源当前 Mini-Batch 的样本固定的全局统计量 (训练阶段估计的)
均值 (μ) 计算μbatch=1m∑i=1mxi\mu_{\text{batch}} = \frac{1}{m} \sum_{i=1}^{m} x_iμbatch=m1i=1mxi使用训练阶段估计的 E[x]E[x]E[x] (滑动平均)
方差 (σ²) 计算σbatch2=1m∑i=1m(xi−μbatch)2\sigma^2_{\text{batch}} = \frac{1}{m} \sum_{i=1}^{m} (x_i - \mu_{\text{batch}})^2σbatch2=m1i=1m(xiμbatch)2使用训练阶段估计的 Var[x]\text{Var}[x]Var[x] (滑动平均)
归一化公式x^i=xi−μbatchσbatch2+ε\hat{x}_i = \frac{x_i - \mu_{\text{batch}}}{\sqrt{\sigma^2_{\text{batch}} + \varepsilon}}x^i=σbatch2+εxiμbatchx^i=xi−E[x]Var[x]+ε\hat{x}_i = \frac{x_i - E[x]}{\sqrt{\text{Var}[x] + \varepsilon}}x^i=Var[x]+εxiE[x]
可学习参数 (γ, β)更新 (通过反向传播)固定 (使用训练好的值)
目的1. 加速收敛
2. 缓解内部协变量偏移
3. 提供正则化效果
提供稳定的、确定性的输出
依赖 Batch Size (统计量依赖当前Batch) (使用固定统计量,单样本也可运行)
随机性 (不同Batch统计量不同) (输出是确定的)
  1. 训练阶段:

    • 动态统计量: 对于每个输入 Mini-Batch,BN层计算 该Batch内数据 的均值 (μbatchμ_{batch}μbatch) 和方差 (σbatch2σ²_{batch}σbatch2)。
    • 归一化: 使用μbatchμ_{batch}μbatchσbatch2σ²_{batch}σbatch2对 Batch 内的每个样本进行归一化:μbatch=1m∗∑i=1mxiμ_{batch} = \frac{1}{m} * \sum_{i=1}^{m}x_iμbatch=m1i=1mxi(其中 ε 是一个很小的数,防止除以零)。
    • 缩放与偏移: 应用可学习的缩放因子 γ 和偏移因子 β:yi=γ∗x^i+ββ:y_i = γ * x̂_i + ββyi=γx^i+β。这赋予了网络在必要时恢复原始表示的能力。
    • 更新全局统计量: 训练过程中会持续估计整个训练数据集的均值和方差的指数移动平均 (Exponential Moving Average - EMA) 或简单平均:
      • E[x]=momentum∗E[x]+(1−momentum)∗μbatchE[x] = momentum * E[x] + (1 - momentum) * μ_{batch}E[x]=momentumE[x]+(1momentum)μbatch
      • Var[x]=momentum∗Var[x]+(1−momentum)∗σbatch2Var[x] = momentum * Var[x] + (1 - momentum) * σ²_{batch}Var[x]=momentumVar[x]+(1momentum)σbatch2
    • 这里的 momentummomentummomentum 通常接近 1,例如 0.9, 0.99,表示更依赖历史估计值)。这个 E[x]Var[x] 就是最终用于测试阶段的固定统计量。
  2. 测试阶段:

    • 固定统计量: 不再使用当前输入数据计算均值和方差 而是使用在整个训练过程结束后确定下来的固定值 E[x]Var[x] (即训练阶段通过EMA计算得到的全局估计值)。
    • 归一化: 使用固定的 E[x]Var[x] 对每个测试样本进行归一化: x^i=(xi−E[x])Var[x]+εx̂_i =\frac{(x_i - E[x])}{\sqrt{Var[x] + ε}}x^i=Var[x]+ε(xiE[x])
    • 缩放与偏移: 使用训练好的、固定的参数 γβyi=γ∗x^i+βy_i = γ * x̂_i + βyi=γx^i+β
    • 目的: 确保网络在处理单个样本任意大小的 Batch (甚至 Batch Size=1) 时,其行为是确定性的一致的。输出不依赖于测试时其他样本的统计量。

为什么测试阶段不能使用 Batch 统计量?

  1. 一致性要求: 训练好的模型在部署时,对于相同的输入,应该总是产生相同的输出。如果在测试时使用当前 Batch 的统计量,那么:
    • 同一个样本在不同 Batch 中(例如和不同的其他样本组合在一起)会得到不同的归一化结果,导致模型输出不同。这是不可接受的。
    • 无法处理 Batch Size=1 的情况(因为无法计算有意义的均值和方差)。
  2. 全局最优估计: 训练阶段积累的 E[x]Var[x] 是对整个训练数据集统计特性的最优估计,比单个测试 Batch 的统计量更能代表数据的真实分布。使用它们能得到更稳定、更泛化的结果。

在代码中如何体现?

现代深度学习框架 (PyTorch, TensorFlow/Keras) 通过 train()eval() 模式自动处理这种差异:

  • model.train(): 将模型置于训练模式。BN 层使用当前 Batch 的统计量计算 μ_batchσ²_batch,并更新其内部的 running_mean (E[x]) 和 running_var (Var[x])。
  • model.eval(): 将模型置于评估/测试模式。BN 层停止更新 running_meanrunning_var,并直接使用它们存储的固定值 E[x]Var[x] 进行归一化计算。
# PyTorch 示例
model.train()  # 训练模式
# 进行训练迭代... BN层会更新running_mean/running_var

model.eval()   # 评估/测试模式
with torch.no_grad():  # 通常同时禁用梯度计算
    # 进行测试或推理... BN层使用固定的running_mean/running_var

Batch Norm 1d

用于 2D 输入(如全连接层的输出或时序数据),输入形状为b*c, 即在特征维度c上进行归一化:

import torch
import torch.nn as nn

class MyBatchNorm1d:
    def __init__(self, num_features, eps=1e-5, momentum=0.1, affine=True):
        self.num_features = num_features
        self.eps = eps
        self.momentum = momentum
        self.affine = affine
        
        # 可学习参数
        if self.affine:
            self.gamma = torch.ones(num_features)
            self.beta = torch.zeros(num_features)
        else:
            self.gamma = None
            self.beta = None
        
        # 运行时统计量
        self.running_mean = torch.zeros(num_features)
        self.running_var = torch.ones(num_features)
        
        # 训练模式标志
        self.training = True

    def forward(self, x):
        if self.training:
            # 计算当前批次的均值和方差
            batch_mean = x.mean(dim=0)
            batch_var = x.var(dim=0, unbiased=False)  # 使用有偏估计
            
            # 更新全局统计量
            self.running_mean = (1 - self.momentum) * self.running_mean + self.momentum * batch_mean
            self.running_var = (1 - self.momentum) * self.running_var + self.momentum * batch_var
        else:
            # 推理时使用保存的统计量
            batch_mean = self.running_mean
            batch_var = self.running_var
        
        # 归一化
        x_hat = (x - batch_mean) / torch.sqrt(batch_var + self.eps)
        
        # 缩放和偏移(如果启用)
        if self.affine:
            x_hat = self.gamma * x_hat + self.beta
        
        return x_hat

# 测试代码
if __name__ == "__main__":
    # 模拟输入 (batch_size=3, num_features=5)
    x = torch.randn(3, 5)

    bn_custom = MyBatchNorm1d(5, affine=True)
    bn_custom.gamma = bn_native.weight.clone().detach()
    bn_custom.beta = bn_native.bias.clone().detach()
    bn_custom.running_mean = bn_native.running_mean.clone()
    bn_custom.running_var = bn_native.running_var.clone()
    output_custom = bn_custom.forward(x)

Batch Norm 2d

用于 4D 输入(如卷积层的输出)输入形状为b*c*h*w,即在每个通道上进行normalization,求b*h*w内的像素求均值和方差。

class MyBatchNorm2d:
    def __init__(self, num_features, eps=1e-5, momentum=0.1, affine=True):
        self.num_features = num_features
        self.eps = eps
        self.momentum = momentum
        self.affine = affine
        
        # 可学习参数
        if self.affine:
            self.gamma = torch.ones(num_features)
            self.beta = torch.zeros(num_features)
        else:
            self.gamma = None
            self.beta = None
        
        # 运行时统计量
        self.running_mean = torch.zeros(num_features)
        self.running_var = torch.ones(num_features)
        
        # 训练模式标志
        self.training = True

    def forward(self, x):
        # 输入形状: (B, C, H, W)
        if self.training:
            # 计算当前批次的均值和方差(沿 B, H, W 维度)
            batch_mean = x.mean(dim=(0, 2, 3))  # 形状变为 [C]
            batch_var = x.var(dim=(0, 2, 3), unbiased=False)  # 有偏估计
            
            # 更新全局统计量
            self.running_mean = (1 - self.momentum) * self.running_mean + self.momentum * batch_mean
            self.running_var = (1 - self.momentum) * self.running_var + self.momentum * batch_var
        else:
            batch_mean = self.running_mean
            batch_var = self.running_var
        
        # 归一化(需要保持维度对齐)
        x_hat = (x - batch_mean[None, :, None, None]) / torch.sqrt(batch_var[None, :, None, None] + self.eps)
        
        # 缩放和偏移(如果启用)
        if self.affine:
            x_hat = self.gamma[None, :, None, None] * x_hat + self.beta[None, :, None, None]
        
        return x_hat

# 测试代码
if __name__ == "__main__":
    # 模拟输入 (batch_size=2, channels=3, height=4, width=4)
    x = torch.randn(2, 3, 4, 4)

    # 手动实现
    bn_custom = MyBatchNorm2d(3, affine=True)
    bn_custom.gamma = bn_native.weight.clone().detach()
    bn_custom.beta = bn_native.bias.clone().detach()
    bn_custom.running_mean = bn_native.running_mean.clone()
    bn_custom.running_var = bn_native.running_var.clone()
    output_custom = bn_custom.forward(x)

作用

a、防止过拟合

b、加速网络的收敛,internal covariate shift导致上层网络需要不断适应底层网络带来的分布变化

c、缓解梯度爆炸和梯度消失

局限 依赖于batch size,适用于batch size较大的情况

Layer normalization

与Batch Normalization不同,Layer Normalization在每个样本上独立进行,而不是在每个批次上进行。对每个样本的所有特征进行归一化,如N*C*H*W,对每个C*H*W进行归一化,得到N个均值和方差。

对于一个给定的输入张量 XXX(假设是二维的,即 X∈Rn×dX \in \mathbb{R}^{n \times d}XRn×d),其中 nnn 是样本数量,ddd 是特征维度(即C*H*W),Layer Normalization的步骤如下:

  1. 计算均值:对于每个样本 xix_ixi(其中 xi∈Rdx_i \in \mathbb{R}^dxiRd),计算其均值 μi\mu_iμi
    μi=1d∑j=1dxij \mu_i = \frac{1}{d} \sum_{j=1}^d x_{ij} μi=d1j=1dxij

  2. 计算方差:对于每个样本 xix_ixi,计算其方差 σi2\sigma_i^2σi2
    σi2=1d∑j=1d(xij−μi)2 \sigma_i^2 = \frac{1}{d} \sum_{j=1}^d (x_{ij} - \mu_i)^2 σi2=d1j=1d(xijμi)2

  3. 归一化:对于每个样本 xix_ixi,使用其均值和方差进行归一化:
    x^ij=xij−μiσi2+ϵ \hat{x}_{ij} = \frac{x_{ij} - \mu_i}{\sqrt{\sigma_i^2 + \epsilon}} x^ij=σi2+ϵxijμi
    其中 ϵ\epsilonϵ 是一个很小的常数,用于避免除以零。

  4. 仿射变换:对于归一化后的样本 x^i\hat{x}_ix^i,使用可学习的参数 γ\gammaγβ\betaβ 进行缩放和偏移:
    yi=γx^i+β y_i = \gamma \hat{x}_i + \beta yi=γx^i+β
    其中 γ\gammaγβ\betaβddd维向量。

代码实现

import torch
import torch.nn as nn

class LayerNorm(nn.Module):
    def __init__(self, features, eps=1e-5):
        super().__init__()
        self.gamma = nn.Parameter(torch.ones(features))  # 缩放参数
        self.beta = nn.Parameter(torch.zeros(features))  # 平移参数
        self.eps = eps  # 数值稳定项

    def forward(self, x):
        # 在最后一个维度计算均值和方差
        mean = x.mean(dim=-1, keepdim=True)
        var = x.var(dim=-1, keepdim=True, unbiased=False)
        
        # 归一化
        x_normalized = (x - mean) / torch.sqrt(var + self.eps)
        
        # 仿射变换
        return self.gamma * x_normalized + self.beta

Instance normalization

Instance normalization对每个样本的每个通道特征进行归一化,输入张量维度为(N,C,H,W)(N, C, H, W)(N,C,H,W)NNN:批大小,CCC:通道数,HHH:高度,WWW:宽度。Instance normalization对每个H*W进行归一化,得到N*C个均值和方差。
均值与方差计算
对每个样本nnn和通道ccc单独计算: μnc=1HW∑h=1H∑w=1Wxnchw \mu_{nc} = \frac{1}{HW}\sum_{h=1}^{H}\sum_{w=1}^{W}x_{nchw} μnc=HW1h=1Hw=1Wxnchw σnc2=1HW∑h=1H∑w=1W(xnchw−μnc)2 \sigma_{nc}^2 = \frac{1}{HW}\sum_{h=1}^{H}\sum_{w=1}^{W}(x_{nchw}-\mu_{nc})^2 σnc2=HW1h=1Hw=1W(xnchwμnc)2
归一化
x^nchw=xnchw−μncσnc2+ϵ \hat{x}_{nchw} = \frac{x_{nchw} - \mu_{nc}}{\sqrt{\sigma_{nc}^2 + \epsilon}} x^nchw=σnc2+ϵxnchwμnc
仿射变换
ynchw=γcx^nchw+βc y_{nchw} = \gamma_c \hat{x}_{nchw} + \beta_c ynchw=γcx^nchw+βc 其中γc\gamma_cγcβc\beta_cβc为可学习参数,ϵ\epsilonϵ为极小常数防止除零

import torch
import torch.nn as nn

class InstanceNorm(nn.Module):
    def __init__(self, num_features, eps=1e-5):
        super().__init__()
        self.eps = eps
        self.gamma = nn.Parameter(torch.ones(1, num_features, 1, 1))
        self.beta = nn.Parameter(torch.zeros(1, num_features, 1, 1))

    def forward(self, x):
        # 输入x形状: (N, C, H, W)
        mean = torch.mean(x, dim=[2,3], keepdim=True)  # 计算均值
        var = torch.var(x, dim=[2,3], keepdim=True, unbiased=False)  # 计算方差
        
        # 归一化
        x_normalized = (x - mean) / torch.sqrt(var + self.eps)
        
        # 缩放平移
        return self.gamma * x_normalized + self.beta

Group normalization

输入特征张量:设输入特征张量为X∈RN×C×H×WX \in \mathbb{R}^{N \times C \times H \times W}XRN×C×H×W,其中:NNN: 批量大小;CCC: 通道数;H,WH,WH,W: 空间维度

分组处理:每个样本按通道分组进行归一化,将通道分为GGG组,每组含C/GC/GC/G个通道,定义组索引g∈[1,...,G]g \in [1,...,G]g[1,...,G],则每个组的特征张量为Xn,g∈R1×(C/G)×H×WX_{n,g} \in \mathbb{R}^{1 \times (C/G) \times H \times W}Xn,gR1×(C/G)×H×W

**均值与方差计算** 对每个样本$n$和组$g$计算统计量:

μng=1(C/G)HW∑c=(g−1)(C/G)g(C/G)−1∑h=1H∑w=1WXnchw \mu_{ng} = \frac{1}{(C/G)HW} \sum_{c=(g-1)(C/G)}^{g(C/G)-1} \sum_{h=1}^{H} \sum_{w=1}^{W} X_{nchw} μng=(C/G)HW1c=(g1)(C/G)g(C/G)1h=1Hw=1WXnchw

σng2=1(C/G)HW∑c=(g−1)(C/G)g(C/G)−1∑h=1H∑w=1W(Xnchw−μng)2 \sigma_{ng}^2 = \frac{1}{(C/G)HW} \sum_{c=(g-1)(C/G)}^{g(C/G)-1} \sum_{h=1}^{H} \sum_{w=1}^{W} (X_{nchw} - \mu_{ng})^2 σng2=(C/G)HW1c=(g1)(C/G)g(C/G)1h=1Hw=1W(Xnchwμng)2

标准化处理
对原始值进行标准化:

X^nchw=Xnchw−μngσng2+ϵ \hat{X}{nchw} = \frac{X{nchw} - \mu_{ng}}{\sqrt{\sigma_{ng}^2 + \epsilon}} X^nchw=σng2+ϵXnchwμng

仿射变换
加入可学习参数γc\gamma_cγcβc\beta_cβc

Ynchw=γcX^nchw+βc Y_{nchw} = \gamma_c \hat{X}_{nchw} + \beta_c Ynchw=γcX^nchw+βc

您可能感兴趣的与本文相关的镜像

TensorFlow-v2.9

TensorFlow-v2.9

TensorFlow

TensorFlow 是由Google Brain 团队开发的开源机器学习框架,广泛应用于深度学习研究和生产环境。 它提供了一个灵活的平台,用于构建和训练各种机器学习模型

Batch Normalization(批归一化)、Layer Normalization(层归一化)、Instance Normalization(实例归一化)、Group Normalization(组归一化)是常用的归一化技术,用于在神经网络中提升训练效果和稳定性。它们的区别如下: 1. Batch Normalization(批归一化): - 对每个批次的样本进行归一化,即在每个批次上计算均值和方差。 - 在训练过程中,使用当前批次的均值和方差来进行归一化;在测试过程中,使用整个训练集的均值和方差来进行归一化。 - 常用于卷积神经网络中,对每个通道进行归一化。 2. Layer Normalization(层归一化): - 对每个样本的特征维度进行归一化,即在每个样本上计算均值和方差。 - 在训练和测试过程中都使用当前样本的均值和方差来进行归一化。 - 主要应用于循环神经网络(RNN)等不同长度序列数据中。 3. Instance Normalization(实例归一化): - 对每个样本的每个通道进行归一化,即在每个样本的每个通道上计算均值和方差。 - 在训练和测试过程中都使用当前样本的均值和方差来进行归一化。 - 主要应用于图像风格迁移等任务。 4. Group Normalization(组归一化): - 将通道分成多个组,然后在每个组内对每个样本进行归一化,即在每个样本的每个组上计算均值和方差。 - 在训练和测试过程中都使用当前样本的均值和方差来进行归一化。 - 主要用于通道数较少的情况,例如小型网络或者输入数据通道较少的情况。 总结起来,这些归一化方法的区别在于对数据的归一化范围和维度的不同处理方式,适用于不同类型的神经网络和数据。具体选择哪种归一化方法取决于具体的应用场景和网络结构。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值