Skip to content

第 3 章:微积分、变分推断与采样方法

梯度是深度学习的通用语言,而"采样"是这门语言中最难翻译的词。本章围绕一个核心矛盾展开:我们需要对含有随机采样步骤的目标函数求梯度,但采样操作本身不可导。Score Function 和 Path Derivative 是解决这一矛盾的两条路径,分别催生了策略梯度和重参数化技巧。在此基础上,我们深入变分推断与 VAE 的数学结构,剖析 PyTorch autograd 机制,推导常见函数的梯度,分析不同 token-level 归一化策略的梯度效应,并将视野扩展到 Neural ODE、能量模型、流形约束优化与因果推断。


3.1 Score Function 与 Path Derivative

两种梯度估计器的本质区别在于:Score Function 把采样当作不可控的"黑盒",在外部用奖励加权概率梯度;Path Derivative 打开采样的"黑盒",将随机性剥离为外部噪声,让梯度直接穿过确定性变换流回参数。

3.1.1 问题的提出

生成模型和强化学习的通用优化目标是最小化期望损失:

J(θ)=Expθ(x)[f(x)]

θ 求梯度 θJ(θ) 时面临核心障碍:x 是从 pθ(x)采样得到的,而采样操作不可导——概率分布改变了,采样结果是离散跳变的,没有平滑的梯度可传递。

两种绕过障碍的方法各有哲学:

方法别名隐喻适用场景
Score Function (SF)REINFORCE 估计器"蒙眼射击":不知道弹道如何,但记住高分时的姿势f(x) 不可导、离散 token、复杂 RL 环境
Path Derivative (PD)Reparameterization Trick"拆解枪支":把手抖(噪声)和瞄准(参数)分开f(x) 可导、连续潜变量(VAE、Diffusion)

3.1.2 Score Function 估计器

对数导数技巧(Log-Derivative Trick) 是 SF 估计器的数学基础。核心恒等式:

θpθ(x)=pθ(x)θlogpθ(x)

利用此技巧,将梯度从"对分布求导"转化为"在分布下求期望":

θJ(θ)=θpθ(x)f(x)dx=pθ(x)θlogpθ(x)f(x)dxθJ(θ)=Expθ(x)[f(x)θlogpθ(x)]

直觉解释f(x) 充当权重系数——如果某次采样的结果很好(f(x) 小,即 Loss 低),就增大产生该结果的概率(沿 θlogpθ(x) 方向更新)。θlogpθ(x) 指示的正是"怎样调参数使 x 出现的概率更高"。

Score Function 期望为零(重要性质):

Exp(x;θ)[θlogp(x;θ)]=p(x;θ)θp(x;θ)p(x;θ)dx=θp(x;θ)dx=θ(1)=0

这意味着减去任何与 x 无关的 baseline b 都不会引入偏差:E[(f(x)b)θlogpθ(x)] 仍然是 θJ(θ) 的无偏估计,但可以大幅降低方差。

SF 的特点

  • 优点f(x) 可以完全不可导,甚至可以是离散的(如 NLP 中离散 token 的奖励,或复杂 RL 环境中不可导的 Reward)
  • 缺点:方差极高——f(x) 的量级直接放大梯度估计的噪声,通常需要 baseline 减方差

3.1.3 Path Derivative 估计器

重参数化技巧(Reparameterization Trick):将随机变量 x 分解为确定性变换 g 和外部噪声 ϵ

x=g(θ,ϵ),ϵp(ϵ)(与 θ 无关)

经典示例:正态分布 xN(μ,σ2) 可重写为 x=μ+σϵ,其中 ϵN(0,1)

根据无意识统计学家定律(Law of the Unconscious Statistician),期望可转化为对噪声的期望:

J(θ)=Expθ(x)[f(x)]=Eϵp(ϵ)[f(g(θ,ϵ))]

现在 p(ϵ) 不含 θ,可以直接交换积分与微分,再用链式法则:

θJ(θ)=Eϵp(ϵ)[θf(g(θ,ϵ))]=Eϵp(ϵ)[xf(x)|x=g(θ,ϵ)Loss 对 x 的梯度θg(θ,ϵ)变换对 θ 的梯度]θJ(θ)=Eϵp(ϵ)[xf(g(θ,ϵ))θg(θ,ϵ)]

直觉解释:梯度通过链式法则,从 Loss f 传回 x,再穿过确定性变换 g 传回 θ。整条路径都是可导的,因为随机性已经被"外包"给了 ϵ

PD 的特点

  • 优点:直接利用 f(x) 的局部几何信息(斜率),方差极小,收敛极快
  • 缺点:要求 f(x)g(θ,ϵ) 处处可导,不适用于离散采样

3.1.4 计算图对比

两种估计器的梯度流可以从计算图直观理解:

Score Function:   θ → p_θ → [Sample x] ···✕ f(x)
                                 ↑ 梯度在采样处断裂
                  SF 绕道:直接问 p_θ "怎样让 x 更可能",用 f(x) 加权

Path Derivative:  ε → [Transform g(θ, ε)] → x → f(x)
                         ↑ θ 是变换的参数
                  梯度流畅通:θ ← g ← x ← f

SF 的梯度流到 x 就断了,SF 重建了一条"旁路";PD 将随机性剥离到 ϵ,让 θ 成为确定性变换的参数,梯度流一路畅通。

3.1.5 实验对比

用一个简单问题对比两种估计器:寻找高斯分布的最优均值 μ,使采样出的 x 最小化 L(x)=(xtarget)2(target = 3.0)。

python
import torch
import matplotlib.pyplot as plt

torch.manual_seed(42)
TARGET, SIGMA, LR, ITERATIONS, BATCH_SIZE = 3.0, 1.0, 0.1, 500, 64

def run_optimization(method='path_derivative'):
    mu = torch.tensor([0.0], requires_grad=True)
    optimizer = torch.optim.Adam([mu], lr=LR)
    loss_history, grad_var_history, mu_history = [], [], []

    for _ in range(ITERATIONS):
        optimizer.zero_grad()
        dist = torch.distributions.Normal(mu, SIGMA)

        if method == 'path_derivative':
            x = dist.rsample((BATCH_SIZE,))     # rsample() 保持梯度流
            loss = ((x - TARGET) ** 2).mean()
            loss_mean = loss
            loss.backward()
            grads = 2 * (x - TARGET)             # 单样本梯度: d/dmu (x-T)^2 = 2(x-T)

        elif method == 'score_function':
            x = dist.sample((BATCH_SIZE,))       # sample() 切断梯度流
            loss_val = (x - TARGET) ** 2
            log_prob = dist.log_prob(x)
            surrogate = (log_prob * loss_val.detach()).mean()  # 代理损失
            surrogate.backward()
            loss_mean = loss_val.mean()
            grads = loss_val * (x - mu.detach())  # SF 单样本梯度: f(x) * (x-mu)/sigma^2

        loss_history.append(loss_mean.item())
        mu_history.append(mu.item())
        grad_var_history.append(torch.var(grads).item())
        optimizer.step()

    return loss_history, grad_var_history, mu_history

pd_loss, pd_var, pd_mu = run_optimization('path_derivative')
sf_loss, sf_var, sf_mu = run_optimization('score_function')

实验结果解读

  • 收敛速度:Path Derivative 的 μ 在约 50 步内逼近 target = 3.0;Score Function 需要数百步,且曲线抖动明显
  • 梯度方差(核心差异):PD 的梯度方差比 SF 低 1--2 个数量级。SF 的 f(x)logp 中,f(x)=(x3)2 的量级直接叠加到梯度上,导致方差爆炸;PD 直接用 xf=2(x3) 这一精确斜率信息,方差天然受控
  • Loss 曲线:PD 平滑单调下降;SF 在前期剧烈波动,需要更大 batch 或 baseline 才能稳定

3.2 变分推断:Reverse KL、Forward KL 与 VAE

变分推断的核心思想是"既然积不出来,就猜一个"——用一个简单分布族中的最优成员去近似复杂的真实后验,将推断问题转化为优化问题。ELBO 是这一转化的数学桥梁。

3.2.1 变分推断(Variational Inference)

贝叶斯统计的终极目标是计算后验分布——透过现象(数据 X)看本质(隐变量 Z):

P(Z|X)=P(X|Z)P(Z)P(X)=P(X|Z)P(Z)P(X|Z)P(Z)dZ

分母 P(X)=P(X|Z)P(Z)dZ(Evidence)需要对所有可能的 Z 积分。对于高维潜变量空间(如神经网络的参数空间),这个积分是难解的(Intractable)

变分推断的思路:找一个形状简单、易于计算的分布族 Q(比如高斯),在其中找参数 θ 使 Qθ(Z) 最像真实后验 P(Z|X)。"最像"的衡量标准是 KL 散度。这把积分问题转化为了优化问题

3.2.2 为什么选 Reverse KL

标准变分推断最小化 Reverse KL

KL(Q(z)P(z|x))=EzQ[logQ(z)P(z|x)]

关键观察:期望是对 Q 求的。因为 Q 是我们自己设定的分布(如高斯),可以轻松采样和求导。

若改用 Forward KLKL(PQ)=EzP[logP(z|x)logQ(z)],期望对真实后验 P(z|x) 求——而 P(z|x) 正是我们算不出来的东西。这构成了循环论证:为了近似 P 需要从 P 采样,而 P 就是想近似的目标。

Reverse KL vs. Forward KL 的行为差异

类型名称性质行为
KL(QP)(Reverse)Mode-SeekingZero-ForcingQ 选择 P 的一个峰并紧密贴合,忽略其他峰
KL(PQ)(Forward)Mean-SeekingMass-CoveringQ 扩散覆盖 P 的所有质量区域

直觉:Reverse KL 中,当 P(z|x)=0Q(z)>0 时,log(Q/P),惩罚极大。因此 Q 被迫只在 P 有质量的地方放质量——这就是 Zero-Forcing,结果是 Q 精准但不全面。

3.2.3 ELBO 推导

直接最小化 KL(QP) 需要知道 P(z|x),而这恰恰是未知的。ELBO(Evidence Lower BOund)提供了一个等价但可计算的目标。

从 KL 散度出发展开:

KL(Q(z)P(z|x))=EzQ[logQ(z)P(z|x)]=EzQ[logQ(z)logP(x,z)P(x)]=EzQ[logQ(z)]EzQ[logP(x,z)]+logP(x)

重新整理得到核心等式:

logP(x)=EzQ[logP(x,z)logQ(z)]ELBO+KL(Q(z)P(z|x))

因为 KL0,所以 ELBO logP(x),这就是"Evidence Lower Bound"名称的由来。

最大化 ELBO 最小化 KL(QP)(因为 logP(x) 是关于 Q 的常数)。

从 ELBO 到 VAE Loss 的桥接推导:将联合概率 P(x,z)=P(z)P(xz) 代入 ELBO 的定义:

ELBO=EzQ[logP(x,z)logQ(z)]=EzQ[logP(z)+logP(xz)logQ(z)]=EzQ[logP(xz)]+EzQ[logP(z)logQ(z)]=EzQ[logP(xz)]EzQ[logQ(z)P(z)]=EzQ[logP(xz)]重构项KL(Q(z)P(z))先验正则项

这一分解将 ELBO 从抽象的联合概率形式变成了两个直觉清晰的部分:第一项要求从 Q 采样的 z 能大概率重构出 x;第二项要求 Q(z) 不要偏离先验 P(z) 太远。这正是 VAE 损失函数的来源。

ELBO 的计算优势在于它避开了难以计算的 P(x,z)dz

  • Q(z) 是我们自己设定的简单分布,密度和采样都已知
  • P(x,z)=P(z)P(xz):先验 P(z) 由我们设定,似然 P(xz) 由模型定义——两者都是点对点计算,不需要积分

3.2.4 VAE:ELBO 的神经网络实现

VAE(Variational AutoEncoder)用神经网络参数化变分推断的两个组件:

  • Encoder qϕ(z|x):输入数据 x,输出后验近似的参数(均值 μ 和对数方差 logσ2
  • Decoder pθ(x|z):输入潜变量 z,输出重构数据

VAE 的损失函数是负 ELBO,可分解为两个直觉清晰的部分:

LVAE=ELBO=Eqϕ(z|x)[logpθ(x|z)]重构误差(Reconstruction Loss)+KL(qϕ(z|x)p(z))正则项(KL Divergence)
  • 重构误差:要求 Decoder 能从 z 大概率还原出 x。若假设 p(x|z) 为高斯分布,此项等价于 MSE;若为伯努利分布,等价于 Binary Cross Entropy
  • KL 正则项:要求 Encoder 输出的后验 qϕ(z|x) 尽量接近先验 p(z)=N(0,I)。这防止了潜在空间"记住"个别数据点,保证了生成时的平滑性

当先验为标准正态分布时,KL 散度有解析解

KL(N(μ,σ2)N(0,1))=12j=1d(1+logσj2μj2σj2)

完整的 VAE 实现:

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

class VAE(nn.Module):
    def __init__(self, input_dim=784, hidden_dim=400, latent_dim=20):
        super().__init__()
        # Encoder: x → h → (mu, log_var)
        self.fc1   = nn.Linear(input_dim, hidden_dim)
        self.fc_mu = nn.Linear(hidden_dim, latent_dim)
        self.fc_lv = nn.Linear(hidden_dim, latent_dim)
        # Decoder: z → h → x_recon
        self.fc3 = nn.Linear(latent_dim, hidden_dim)
        self.fc4 = nn.Linear(hidden_dim, input_dim)

    def encode(self, x):
        h = F.relu(self.fc1(x))
        return self.fc_mu(h), self.fc_lv(h)

    def reparameterize(self, mu, log_var):
        """重参数化技巧核心:z = mu + sigma * epsilon"""
        std = torch.exp(0.5 * log_var)     # log_var → std
        eps = torch.randn_like(std)         # epsilon ~ N(0,1),与 phi 无关
        return mu + eps * std               # 确定性变换,梯度可流过

    def decode(self, z):
        return torch.sigmoid(self.fc4(F.relu(self.fc3(z))))

    def forward(self, x):
        mu, log_var = self.encode(x.view(-1, 784))
        z = self.reparameterize(mu, log_var)  # 梯度通过这里流回 mu, log_var
        return self.decode(z), mu, log_var

def vae_loss(recon_x, x, mu, log_var):
    """负 ELBO = 重构误差 + KL 散度"""
    BCE = F.binary_cross_entropy(recon_x, x.view(-1, 784), reduction='sum')
    KLD = -0.5 * torch.sum(1 + log_var - mu.pow(2) - log_var.exp())
    return BCE + KLD, BCE, KLD

训练结果(MNIST,10 epochs)

Epoch总 Loss重构误差KL 散度
1162.70146.8115.88
5109.5084.5524.95
10105.9680.5825.38

观察:随着训练进行,重构误差持续下降(Decoder 越来越好),KL 散度先升后趋于稳定(Encoder 学到的后验结构在与先验约束之间找到平衡)。训练完成后,从 N(0,I) 随机采样 z 并通过 Decoder,可以生成清晰可辨的手写数字。

3.2.5 知识蒸馏中的 Forward KL

知识蒸馏(Knowledge Distillation)是 Forward KL 的成功应用场景,值得与 VI 对比理解。

LKD=KL(PT(y|x)PS(y|x))=i=1CPT(yi|x)logPT(yi|x)PS(yi|x)

为何这里可以用 Forward KL?

  • PT(Teacher)是已知的、完全透明的——做一次前向传播就能得到完整的概率向量
  • 积分域是有限的类别集合(如 10 类或 1000 类), 变成了 ,可以精确计算
  • 这与 VI 面对的不可计算高维连续后验是完全不同的处境

因为 Teacher 是固定的(detach),PT 对优化来说是常数:KL(PTPS)=H(PT)+H(PT,PS)。最小化 Forward KL 等价于最小化交叉熵——这就是 KD 代码中直接写 CrossEntropyLoss(student_logits, teacher_probs) 的原因。


3.3 梯度无法通过"采样"传播:Policy Gradient 与 Reparameterization Trick

采样是计算图中的"断桥"。对于离散采样(token 生成),只能绕道——用 Policy Gradient;对于连续采样(潜变量),可以修桥——用 Reparameterization Trick。

3.3.1 问题的根源

LLM 对齐的 RLHF 目标函数:

maxπθExD,yπθ(y|x)[rϕ(x,y)]βDKL[πθ(y|x)πref(y|x)]

计算图:

θ参数化概率分布 P采样动作 y计算奖励 R
  • Ry:奖励随动作的变化,可计算
  • yP不可导y(token)是离散的——概率从 0.49 变到 0.51,输出从 "A" 瞬间跳变为 "B",没有平滑梯度

后果:梯度无法穿过"采样"这个操作反向传播回参数 θ

3.3.2 解决方案一:Policy Gradient(Score Function 的应用)

不尝试通过采样反向传播,而是用第 3.1 节的 Score Function 技巧绕过:

θJ(θ)=E[R(y)奖励值(标量常数)θlogπθ(y|x)动作的对数概率]

关键设计:

  • R(y) 变成了权重系数(detach):不需要对 R 求关于 y 的导数,也不需要对 y 求关于 θ 的导数
  • θlogπθ(y|x)πθ(y|x) 是神经网络直接输出的概率,完全可导
  • 数据 y 不再必须从当前 πθ 采样——可以用旧策略的数据(Off-Policy)

Policy Gradient 的工作流程

  1. 采样(Rollout):让模型运行,记录"状态 x、动作 y、奖励 R"。此时不计算梯度
  2. 更新:如果 R 高,计算 logπ(y|x) 让模型更可能选 y;如果 R 低,则降低 y 的概率

PPO 在此基础上引入重要性采样(Importance Sampling)和 Clip 机制:

LPPO=min(πθ(y|x)πold(y|x)A,clip(πθπold,1ϵ,1+ϵ)A)

3.3.3 解决方案二:Reparameterization Trick(连续情形)

对于连续潜变量(VAE 的 z、Diffusion 的 xt),重参数化将采样改写为确定性变换:

z=μϕ(x)+σϕ(x)ϵ,ϵN(0,I)

梯度路径完全畅通:ϕμϕ,σϕzLoss,全程可导。

3.3.4 Vanilla PG vs. PPO 的梯度行为

对 logit z(经 Softmax 后概率为 πa),两种方法的梯度有质的差异:

Vanilla PG(使用 logπa):

zJ=(logπa)πaπazaA=1πaπa(1πa)A=(1πa)A

πa0 时,梯度 1A=A——恒定的强推力,即使概率已经极低也不放弃。

PPO(使用比率 r=πa/πold):

zJ=1πoldπa(1πa)A

πa0 时,πa(1πa)0,整个梯度 0——自然熄灭,避免对低概率动作的极端更新。

这解释了 PPO 为什么比 Vanilla PG 更稳定:它自带"安全阀",不会对已经被放弃的动作施加不合理的大梯度。


3.4 PyTorch Autograd 机制

PyTorch 的动态计算图是"用时建、用后拆"——每次前向传播即时构建图,反向传播沿图求完梯度后立即释放。理解这一机制是正确调试梯度问题的基础。

PyTorch 动态计算图示意:前向构建、反向求导

3.4.1 动态计算图

PyTorch 使用动态计算图(define-by-run):

  • requires_grad=True:标记需要计算梯度的叶节点(通常是模型参数 nn.Parameter
  • .backward():从标量 loss 出发,沿计算图反向传播,把每个叶节点的梯度累加到 .grad 属性中
  • optimizer.zero_grad():清空累积梯度。不清空则多次 backward 的梯度会叠加(这在梯度累积策略中是有意为之,但通常需要显式管理)

3.4.2 梯度通过形状变换的传播

一个揭示 autograd 行为的经典示例:

python
import torch
from torch import nn

a = nn.Parameter(torch.rand(1, 4))    # shape: (1, 4),叶节点
b = a.unsqueeze(0)                     # shape: (1, 1, 4)
c = b.tile(2, 1, 1)                    # shape: (2, 1, 4),a 被复制了 2 次
d = torch.rand(2, 1, 4)               # 随机数据

loss = torch.mean(d - c)
loss.backward()

print(a.grad)  # tensor([[-0.2500, -0.2500, -0.2500, -0.2500]])

梯度分析

  1. loss = mean(d - c),总共 2×1×4=8 个元素
  2. losscijk=18(mean 中每个元素的贡献相等,负号来自 dc
  3. tile(2, 1, 1) 使得 a 的每个元素在 c 中出现 2 次
  4. 梯度从两个副本累加回原参数:2×(18)=14=0.25

核心原则tileexpandrepeat 等操作相当于参数共享——梯度从所有"副本"处累加回源参数。这与卷积中权重共享的梯度传播机制完全一致。


3.5 常见函数的梯度推导

深度学习中反复出现的几个函数——Softmax、Cross Entropy、Sigmoid——它们的梯度公式简洁优雅,但推导需要商法则和链式法则的配合。掌握这些推导是理解后续章节(如 PPO 梯度分析)的前提。

3.5.1 商法则

(f(x)g(x))=f(x)g(x)f(x)g(x)[g(x)]2

3.5.2 Softmax 梯度

πa=Softmax(z)a=ezakezk

对角元素i=a):

令分子 f=eza,分母 g=kezk,则 fza=ezagza=eza

πaza=ezakezkezaeza(kezk)2=ezakezkkezkezakezk=πa(1πa)

非对角元素ia):

分子 eza 不含 zi,故 fzi=0,而 gzi=ezi

πazi=0ezaezi(kezk)2=πaπi

紧凑形式:πazi=πa(δaiπi),其中 δai 是 Kronecker delta。

3.5.3 Cross Entropy 对 Logits 的梯度

CE Loss(one-hot 标签 y,目标类别 c):

LCE=ayalogπa=logπc

对 logit zi 求导(链式法则 + Softmax 导数):

LCEzi=aya1πaπazi=1πcπc(δciπi)=πiyiLCEz=πy

这个优雅的结果——"预测概率减去真实标签"——是 Softmax 和 Log 的链式法则恰好相互抵消的结果。它也解释了为什么 Cross Entropy + Softmax 在实践中是天作之合。

3.5.4 Sigmoid 梯度与饱和问题

σ(z)=11+ez,σ(z)=σ(z)(1σ(z))

z+σ1σ0;当 zσ0σ0。两端都是饱和区,梯度消失。

这正是 ReLU (max(0,z)) 取代 Sigmoid 作为隐藏层激活函数的主要原因:ReLU 在 z>0 时梯度恒为 1,不存在饱和问题。

3.5.5 二分类 Cross Entropy 的梯度

假设标签 y=1,模型输出 logit z,经 Sigmoid 得概率 p=σ(z)

LCE=logp,Lz=p1

当 Loss 0(即 p1)时,梯度 p10——Loss 和梯度渐近地同时趋向零。这是凸损失函数的典型行为,与下一节讨论的 PPO 反常行为形成鲜明对比。


3.6 Loss 与梯度分析(Token-Level Mean)

"Loss 是高度,Gradient 是坡度"——高度为零不意味着坡度为零。理解两者的非对称关系是调试训练问题的关键。

3.6.1 Loss 与 Gradient 的非对称关系

一个常见误解是"Loss 降到 0,梯度自然为 0"。这在 MSE、CE 这类以 0 为最小值的凸函数上成立,但在更一般的情形下不成立。

MSE(完美抛物线)L=12(y^y)2,梯度 =(y^y)θf。Loss = 0 时 y^=y,梯度恰好为零。

Cross Entropy(渐近收敛)L=logp,梯度 =py。Loss 0 意味着 p1,梯度 0。两者同向趋零但不是恒等关系。

Plateau(饱和区)——Loss 大但梯度为零

L(θ)=(σ(θ)1)2

θ 时,σ(θ)0,Loss 1(最大值),但梯度 =2(σ1)σ(1σ)0。模型"困"在高 Loss 的平原上,完全无法学习——这就是 Sigmoid 饱和的典型表现。

陡峭峡谷(Steep Valley)——Loss 小但梯度巨大

L(θ)=12kθ2

在最优解附近的微小偏移 ϵ:Loss =12kϵ2=O(ϵ2) 极小,但梯度 =kϵ。当 k 极大(Hessian 特征值大,曲率陡峭)时,即使 ϵ 很小,梯度也可以很大——这是梯度爆炸和训练震荡的根源。

PPO Clip——Loss 0 但梯度 = 0

当概率比 r=πθ/πold 超出 [1ϵ,1+ϵ] 区间时,PPO 取 clip 后的值作为损失,梯度被人为置零。反过来,当 r=0πθ0)时,Loss = 0,但梯度 =A0(因为 0 不是最优点,负无穷才是)。

场景LossGradient机制
MSE 最优点00凸函数全局最小值
CE 接近收敛00渐近趋零
Sigmoid 饱和区接近最大值0梯度消失
陡峭峡谷极小极大Hessian 特征值大
PPO Clip 区0=0人为截断保护
PPO r=0=00最优点在负无穷
python
import numpy as np
import matplotlib.pyplot as plt

fig, axes = plt.subplots(1, 3, figsize=(18, 5))

# Plateau: High Loss, Low Gradient
x1 = np.linspace(-10, 5, 200)
sigmoid = 1 / (1 + np.exp(-x1))
loss1 = (sigmoid - 1)**2
grad1 = 2 * (sigmoid - 1) * (sigmoid * (1 - sigmoid))
axes[0].plot(x1, loss1, label=r'Loss: $(\sigma(x)-1)^2$', color='blue', linewidth=2)
axes[0].plot(x1, np.abs(grad1), label='|Gradient|', color='red', linestyle='--', linewidth=2)
axes[0].set_title('Plateau: High Loss, Zero Gradient')

# Steep Valley: Low Loss, High Gradient
x2 = np.linspace(-0.5, 0.5, 200)
axes[1].plot(x2, 20*x2**2, label='Loss: $20x^2$', color='blue', linewidth=2)
axes[1].plot(x2, np.abs(40*x2), label='|Gradient|', color='red', linestyle='--', linewidth=2)
axes[1].set_title('Steep Valley: Low Loss, Huge Gradient')

# PPO Clip: Loss != 0, Gradient = 0
r = np.linspace(0, 2.0, 400)
A, epsilon = 1.0, 0.2
objective = np.minimum(r * A, np.clip(r, 1-epsilon, 1+epsilon) * A)
ppo_loss = -objective
ppo_grad = np.where(r < (1+epsilon), -A, 0)
axes[2].plot(r, ppo_loss, label='PPO Loss', color='blue', linewidth=2)
axes[2].plot(r, np.abs(ppo_grad), label='|Gradient|', color='red', linestyle='--', linewidth=2)
axes[2].axvspan(1+epsilon, 2.0, color='yellow', alpha=0.2, label='Clipped Region')
axes[2].set_title('PPO Clip: Loss!=0, Gradient=0')

for ax in axes:
    ax.grid(True, alpha=0.3); ax.legend()
plt.tight_layout()

三联图输出解读

左图(Plateau):蓝色 Loss 曲线在 θ<5 的区域维持在接近 1 的高位,而红色梯度曲线在同一区域已经衰减到接近零。这就是 Sigmoid 饱和区的典型表现——模型被困在高 Loss 的平原上,Loss 告诉你"离目标很远",但梯度却说"我不知道往哪走"。只有在 θ 进入 [2,2] 的过渡区时,梯度才出现明显的峰值,此时 Sigmoid 的斜率最大,优化才真正生效。

中图(Steep Valley):Loss =20x2x=0 附近极小(接近零),但梯度 =40x 的绝对值依然可观。k=20 对应极高的曲率(Hessian 特征值),这意味着即使参数已经非常接近最优点,一步梯度更新仍然可能"跨过"最优解跳到对面——这正是高曲率损失面导致训练震荡的几何原因。实践中的应对策略包括降低学习率或使用 Adam 等自适应优化器来抑制大梯度方向的步长。

右图(PPO Clip):当概率比 r>1+ϵ=1.2 时,蓝色 Loss 曲线仍然非零(黄色阴影区),但红色梯度曲线骤降为零。这是 PPO 的"安全阀"设计:一旦新策略偏离旧策略超过阈值,梯度被人为截断,阻止策略做出过于激进的更新。与前两个图的"自然现象"不同,PPO Clip 是刻意的工程设计——它牺牲了一部分优化信号,换取了训练的稳定性。

3.6.2 Token-Level 归一化策略

在 RLHF 训练中,不同长度回答的 token-level 归一化方式直接影响每个 token 的梯度权重:

GRPO(Naive)——Per-Sample Mean

L=1Gi=1G1|oi|t=1|oi|(logπ(oi,t)Ai)

每个样本先内部取均值,再跨样本平均。效果:不管回答有 10 tokens 还是 100 tokens,每条回答的总投票权相同(1/G),长回答中每个 token 的贡献被严重稀释。

DAPO——Global Token Mean

L=1i|oi|i=1Gt=1|oi|(logπ(oi,t)Ai)

所有 token 放入同一个池子,除以总 token 数。消除了长度歧视,但梯度量级随 batch 内总 token 数变化,可能需要调整学习率。

Dr. GRPO——Sum over Group

L=1Gi=1Gt=1|oi|(logπ(oi,t)A~i)

只除以组数 G,不除以回答长度。长回答包含更多决策步骤,总梯度贡献自然更大。

数值对比(10-token 短回答 + 100-token 长回答,G=2):

python
import torch

def analyze_gradient_attenuation():
    len_short, len_long, G = 10, 100, 2
    total_tokens = len_short + len_long

    print(f"{'Method':<15} | {'Short Grad':<15} | {'Long Grad':<15}")
    print("-" * 50)
    print(f"{'GRPO (Naive)':<15} | {1.0/G:<15.4f} | {1.0/G:<15.4f}")
    print(f"{'DAPO':<15} | {len_short/total_tokens:<15.4f} | {len_long/total_tokens:<15.4f}")
    print(f"{'Dr. GRPO':<15} | {len_short/G:<15.4f} | {len_long/G:<15.4f}")

analyze_gradient_attenuation()

输出:

Method          | Short Grad      | Long Grad
--------------------------------------------------
GRPO (Naive)    | 0.5000          | 0.5000
DAPO            | 0.0909          | 0.9091
Dr. GRPO        | 5.0000          | 50.0000

解读

  • GRPO (Naive) 完全抹平了长度差异,长句子每 token 的贡献被稀释 10 倍,导致模型偏好短回答
  • DAPO 消除了长度歧视,但梯度量级(0.09 和 0.91)远小于 Dr. GRPO,需要更大学习率补偿
  • Dr. GRPO 梯度量级只与 G 有关,长推理链做对了理应获得更强正反馈,最符合 Policy Gradient 定理的原始形式

3.7 Neural ODE

如果残差网络的层数可以是实数而非整数,每一层变成了微分方程中无穷小的一步,我们就得到了 Neural ODE——一个把"深度"从离散跳跃推向连续极限的框架。

Neural ODE 的连续轨迹:从离散残差跳跃到连续流

3.7.1 残差连接即欧拉离散化

残差块的更新公式:

x+1=x+F(x)

把层数 视为时间 t,步长为 1,这恰好是 ODE 的欧拉离散化(Euler discretization):

x+1x1=F(x)dxdt=F(x,t)

这不只是形式上的巧合。它意味着:增加残差网络的层数相当于在数值积分中缩小步长,而层数趋于无穷的极限就是连续的 ODE。

3.7.2 从 ResNet 到 Neural ODE

Neural ODE(Chen et al., 2018)将这一洞察推向极致:直接用神经网络 fθ 参数化 ODE 的右端项:

dh(t)dt=fθ(h(t),t)

给定初始状态 h(0)=x(输入),终态 h(T)(输出)通过 ODE solver 数值积分得到:

h(T)=h(0)+0Tfθ(h(t),t)dt

不再有离散的"第 1 层、第 2 层",取而代之的是一个连续的时间参数 t[0,T]

3.7.3 优势与训练

连续深度:层数不再是需要预先设定的超参数,而是由 ODE solver 自适应决定积分步数。

内存高效:训练时使用**伴随法(Adjoint Method)**反向传播——不需要存储中间时刻的所有激活值(不像 ResNet 需要存每一层的激活),内存开销与"深度"无关,只与状态维度和 ODE 参数量有关。

自适应精度:ODE solver(如 Dormand-Prince)根据误差容限自动调整步长——在状态变化剧烈的区域用小步长(等价于更多"层"),在平稳区域用大步长。

3.7.4 与生成模型的联系

Neural ODE 是现代生成模型中多个框架的理论基石:

  • 扩散模型(Diffusion):前向加噪/反向去噪过程本质上是 SDE(随机微分方程)。去掉随机项后的确定性版本——概率流 ODE(Probability Flow ODE)——就是 Neural ODE
  • Flow Matching:将从先验分布到数据分布的变换视为向量场,用 Neural ODE 来参数化和求解这个向量场
  • 连续 Normalizing Flows:利用 ODE 的可逆性和 Jacobian 的 trace 公式实现精确的密度估计

3.8 能量模型(Energy-Based Methods)

能量模型的哲学:不直接建模概率,而是学一个"打分函数"——分数越低越自然,分数越高越违和。概率通过 Boltzmann 分布自动从能量中涌现。

3.8.1 能量函数与 Boltzmann 分布

EBM 的核心公式:

Pθ(x)=eEθ(x)Z(θ),Z(θ)=eEθ(x)dx

直觉——能量景观(Energy Landscape):

想象一个起伏不平的山地地形:

  • 山谷底(低能量):小球放在这里是稳定的,不想动。对应"自然的"、"合理的"数据点,概率高
  • 山顶/半山腰(高能量):小球不稳定,会自然滚落。对应"不自然的"、"异常的"数据点,概率低

在 EBM 中,Eθ(x) 不是物理学中的焦耳,而是一个**"惩罚分数"(Penalty Score)**或"不兼容度"——任何能输出标量的神经网络都可以充当能量函数,不需要归一化。

3.8.2 训练困难:配分函数

训练 EBM 需要最大化对数似然:

θlogPθ(x)=θEθ(x)+ExPθ[θEθ(x)]

第一项(正相)简单:降低训练数据的能量。第二项(负相)困难:需要从模型自身的分布 Pθ 中采样,而 Pθ 的配分函数 Z(θ) 不可计算。

对比散度(Contrastive Divergence, CD):Hinton 提出的实用方案——不做完整的 MCMC 采样,只跑几步(通常 1 步)从数据点出发的 Gibbs 采样,用得到的"负样本"近似负相梯度。

3.8.3 Score Matching:绕过配分函数

Score(得分函数,注意与第 3.1 节的 Score Function 估计器区分)是对数概率密度对数据 x 的梯度:

sθ(x)=xlogPθ(x)=xEθ(x)

配分函数 Z(θ) 是关于 x 的常数,在对 x 求梯度时消失——这是 Score Matching 的核心优势。

Score Matching 的目标:

Epdata[sθ(x)xlogpdata(x)2]

直接匹配模型的 score 和数据分布的 score,完全避免了 Z 的计算。

3.8.4 与扩散模型的联系

扩散模型本质上是在学习加噪数据的 score function(xlogpt(x))。去噪过程可以理解为沿 score 方向的 Langevin Dynamics 迭代:

xt1=xt+δ2sθ(xt,t)+δϵ,ϵN(0,I)

从 EBM 的视角看:score 指向能量景观的"下坡方向",Langevin Dynamics 就是在能量景观上做带噪声的梯度下降,最终收敛到低能量区域(高概率数据)。


3.9 流形约束优化

当参数天然活在弯曲的空间上——如旋转矩阵、单位球面、正定矩阵——在欧氏空间做梯度下降再投影回去既粗糙又低效。流形约束优化的目标是让优化过程"原住民化":直接在弯曲空间上行走。

3.9.1 流形与约束

许多 AI 问题的参数天然存在于某个低维流形上,而非整个欧氏空间:

  • 旋转矩阵 SO(n)RTR=I,行列式为 1。3D 视觉、机器人姿态估计中无处不在
  • 单位球面 Sn1x=1。归一化嵌入(如对比学习中的特征向量)
  • 正定矩阵流形 S++n:协方差矩阵的参数空间。高斯过程、变分推断的参数

如果忽视约束,在欧氏空间做无约束优化后投影回流形,会导致:更新方向被投影歪曲、投影步骤本身的计算开销、收敛到约束边界附近的振荡。

3.9.2 三种约束处理策略

投影法(Projection):每步梯度更新后投影回流形。简单直接,但投影步骤可能代价高昂(如投影到 SO(n) 需要 SVD),且投影方向可能与优化方向冲突。

参数化法(Parameterization):用满足约束的参数化消除约束。例如正交矩阵可以参数化为 R=exp(A),其中 A 是反对称矩阵(AT=A),对 A 做无约束优化即可保证 R 始终正交。

Riemannian 梯度下降:在流形上定义度量张量,计算流形上的梯度(将欧氏梯度投影到切空间),沿测地线(geodesic)更新参数。这是理论上最"原住民"的方法,但实现复杂度较高。

3.9.3 在 AI 中的应用

  • Attention 正交基:对注意力头施加正交约束,使不同头关注不同子空间,提高多样性
  • 归一化嵌入优化:对比学习中特征向量在训练过程中始终保持在单位球面上,避免 collapse
  • Flow Matching 与向量场:将从先验到数据分布的变换视为流形上的向量场,Neural ODE 在流形上求解

3.10 因果推断入门

"冰淇淋销量与溺水事故高度相关"——但没有人会因此禁止卖冰淇淋。传统 ML 只能学到相关性,因果推断要回答的问题是:如果我做了 XY 会如何变化?

3.10.1 相关性 vs. 因果性

传统 ML 的局限:学习的是关联性(Association)P(Y|X)——观测到 XY 的条件概率。无法区分"X 导致 Y"与"XY 共同被 Z 导致"。

因果 ML 的目标:学习干预效果(Intervention)P(Y|do(X=x))——强行将 X 设为 x(do-operator)时 Y 如何变化。

Pearl 的因果层级(Ladder of Causation)

层级问题数学例子
L1: 关联观测到什么?P(YX)看到冰淇淋销量高,溺水事故多
L2: 干预如果做了 XP(Ydo(X))强制增加冰淇淋生产,溺水率变吗?
L3: 反事实如果当时做了 XP(YxX=x)那位病人如果接受了治疗,会活吗?

每个层级严格包含下方的信息,但无法仅从下方的数据推出上方的结论。

3.10.2 因果图(DAG)

因果关系用**有向无环图(DAG)**表示:

  • 节点:变量
  • 有向边 XYXY 的直接原因

关键概念:

  • 混淆变量(Confounder)ZXZYZ 同时影响 XY,制造虚假关联(如"天气热"同时导致"冰淇淋销量高"和"溺水多")
  • 后门标准(Backdoor Criterion):如果能找到一组变量 S 阻断 XY 的所有后门路径,则条件化 S 后的条件概率 P(Y|X,S) 等于因果效应 P(Y|do(X))

3.10.3 因果推断与 AI 的交汇

治疗效果预测:在临床医疗中预测"给患者用药 A vs. 药 B"的效果差异,不能仅靠观测数据——因为医生开药的决策本身就是有偏的(混淆变量:病情严重程度同时影响用药选择和治疗结果)。需要因果推断工具来去混淆。

LLM 中的因果思维

  • Causal Masking:Transformer 的因果掩码保证 token t 只能看到之前的上下文——这是自回归生成的"时间因果性"
  • 反事实数据增强:通过干预因果图中的变量生成对抗样本,提高鲁棒性

奖励模型的混淆问题:RLHF 中,如果人类偏好数据存在混淆变量(如回答长度偏差),直接训练奖励模型会学到"长回答更好"的伪相关而非真正的质量信号。这正是第 3.6 节中 GRPO/DAPO/Dr. GRPO 长度偏差问题的深层根源——需要因果推断的视角来识别和消除这类偏差。


3.11 交叉熵方法(Cross-Entropy Method)

交叉熵方法(CEM)是一种基于采样的零阶优化算法:不需要梯度,只需要反复采样、评估、筛选、拟合。它的核心思想极其朴素——在好的样本上重新拟合分布,让下一轮采样更可能落在高分区域。

3.11.1 算法思想

考虑一个黑盒优化问题:寻找 x=argmaxxf(x),其中 f(x) 可能不可导、不连续、甚至没有解析表达式。梯度方法在此完全失效。

CEM 的策略是维护一个搜索分布 pθ(x)(通常是高斯分布),通过以下循环迭代逼近最优解:

  1. 采样:从当前分布 pθ(x) 中采 N 个候选解 {x1,,xN}
  2. 评估:计算每个候选解的目标值 f(xi)
  3. 筛选:选取目标值最高的前 K 个样本(称为 elite set),其中 K=ρNρ 为 elite 比例(通常 10%--20%)
  4. 更新:用 elite 样本重新拟合分布参数 θ(对高斯分布即重新计算均值和协方差)
μt+1=1Ki=1Kxielite,Σt+1=1Ki=1K(xieliteμt+1)(xieliteμt+1)T

重复直到分布收敛(方差足够小)。

3.11.2 为什么叫"交叉熵"

CEM 的名字来源于其理论基础。将 elite 筛选理解为重要性采样:我们希望找到一个新分布 pθ 使得采样尽量集中在高分区域。形式上,这等价于最小化以下交叉熵:

θt+1=argminθExpθt[1f(x)γtlogpθ(x)]

其中 γt 是第 t 轮 elite 的分数阈值,1f(x)γt 是指示函数。对高斯族求解这个优化问题,解析解恰好就是 elite 样本的经验均值和协方差——这就是上述更新公式的由来。

3.11.3 与策略优化的联系

CEM 与强化学习中的策略搜索方法密切相关。在 RL 场景中:

  • 搜索分布 pθ 对应策略参数的分布(而非策略本身)
  • 目标值 f(x) 对应一次 rollout 的累积奖励
  • elite 筛选 对应只保留高回报的 episode

这使 CEM 成为一种简单有效的进化策略(Evolution Strategy)。与 Policy Gradient 相比:

特性CEMPolicy Gradient
梯度需求零阶,无需梯度一阶,需要 logπ
样本效率较低(大量采样仅保留 top-K较高(所有样本都贡献梯度)
适用场景低维参数空间、不可导目标高维参数空间、可导策略
方差天然低(只用好样本)较高(需 baseline 减方差)

在低维连续控制任务(如 CartPole、MountainCar 等经典环境)中,CEM 凭借实现简单、无需调学习率等优点,常常是首选的 baseline 方法。

3.11.4 实现示例

以下代码用 CEM 求解一个简单的多峰函数优化问题:

python
import numpy as np

def cem_optimize(f, dim, n_samples=100, elite_frac=0.2, n_iters=50):
    """交叉熵方法优化 f(x) 的最大值"""
    mu = np.zeros(dim)
    sigma = np.ones(dim) * 5.0  # 初始标准差较大,覆盖广
    n_elite = int(n_samples * elite_frac)

    for t in range(n_iters):
        # 1. 采样
        samples = np.random.randn(n_samples, dim) * sigma + mu
        # 2. 评估
        scores = np.array([f(x) for x in samples])
        # 3. 筛选 elite
        elite_idx = scores.argsort()[-n_elite:]
        elite_samples = samples[elite_idx]
        # 4. 更新分布参数
        mu = elite_samples.mean(axis=0)
        sigma = elite_samples.std(axis=0)

    return mu

# 测试:优化 Rastrigin 函数的负值(寻找全局最小值)
def neg_rastrigin(x):
    return -(10 * len(x) + sum(xi**2 - 10 * np.cos(2 * np.pi * xi) for xi in x))

best = cem_optimize(neg_rastrigin, dim=2, n_samples=200, n_iters=100)
print(f"Found optimum at: {best}")  # 应接近 [0, 0]

CEM 的简洁性是它的优势——整个算法不过十几行,没有学习率、没有梯度计算、没有反向传播。但这种简洁也决定了它的局限:在高维空间中,覆盖搜索空间所需的样本数呈指数增长,使得 CEM 难以扩展到上千维的参数空间。


本章小结

主题核心公式/思想应用场景
Score Function 估计器J=E[f(x)logp(x)]Policy Gradient、REINFORCE
Path Derivative 估计器x=g(θ,ϵ),梯度直接流过VAE、扩散模型训练
Reverse KL / ELBOmax ELBO min KL(QP)VAE、变分推断
Reparameterization Trickz=μ+σϵVAE Encoder、连续控制
Loss vs. Gradient非单调关系(PPO Clip、Sigmoid 饱和)PPO 调试、训练诊断
Token-Level 归一化GRPO / DAPO / Dr. GRPO 三种策略RLHF 长度偏差修正
Neural ODE残差 = 欧拉步,极限为 ODE连续深度、Flow Matching
能量模型P(x)eE(x),Score Matching扩散模型(score-based)
流形约束投影 / 参数化 / Riemannian 梯度正交约束、归一化嵌入
因果推断do-calculus,DAG,后门标准治疗效果、奖励去偏
交叉熵方法(CEM)采样→筛选 elite→重新拟合分布零阶优化、低维策略搜索

三条核心直觉

  1. 采样是不可导的屏障——所有绕过它的方法(Score Function 或 Reparameterization)本质上都是将随机性"移出计算图"或"转换视角"。离散采样只能用 SF 绕道(REINFORCE/PPO),连续采样可以用 PD 修桥(Reparameterization)。

  2. ELBO 是推断与生成的桥梁——通过最大化 ELBO,VAE 同时学到了如何压缩数据(Encoder,近似后验 q(z|x))和如何生成数据(Decoder,似然 p(x|z)),两者通过潜在空间的概率结构耦合。ELBO 把一个不可解的积分问题变成了可以用 SGD 优化的目标。

  3. 能量视角统一了大多数生成模型——扩散模型学习 score(能量曲面的梯度),EBM 直接在能量曲面上爬坡,Flow Matching 找一条从先验到数据的最优路径。它们是在同一个能量景观上的不同"行走策略",Neural ODE 为这些策略提供了连续时间的数学框架。


延伸阅读

  • 变分推断:Blei, Kucukelbir & McAuliffe (2017), "Variational Inference: A Review for Statisticians", JASA
  • VAE:Kingma & Welling (2014), "Auto-Encoding Variational Bayes", arXiv:1312.6114
  • Policy Gradient:Williams (1992), "Simple Statistical Gradient-Following Algorithms for Connectionist Reinforcement Learning";Schulman et al. (2017), "Proximal Policy Optimization Algorithms"
  • Neural ODE:Chen et al. (2018), "Neural Ordinary Differential Equations", NeurIPS 2018 Best Paper
  • EBM 与 Score Matching:Song & Ermon (2019), "Generative Modeling by Estimating Gradients of the Data Distribution"
  • 因果推断:Pearl (2018), The Book of Why;Peters, Janzing & Scholkopf (2017), Elements of Causal Inference(开源)
  • Token-Level 归一化:Liu et al. (2025), "Understanding the Length Bias of GRPO"(Dr. GRPO);Yu et al. (2025), "DAPO: An Open-Source LLM Reinforcement Learning System"