第3章 PAE的数学推导:变分推断、ELBO及重参数化技巧

好,咱们进入硬核部分。PAE(Probabilistic Autoencoder)的核心不是编解码结构,而是它背后那套概率图模型——尤其是变分推断那一套。

说实话,我第一次看变分推断时,满脑子都是「这玩意儿到底凭什么能近似后验?」后来在项目中踩过几次坑,才算真正理解了它的套路。今天我把这套逻辑掰开揉碎,结合PAE一起讲明白。

3.1 从贝叶斯的困境说起

在PAE里,我们希望学到数据x的隐变量z的后验分布 p(z|x)。但真实后验往往算不出来——积分太复杂。

举个例子:假设z是10维隐变量,x是图片,那积分 ∫ p(x|z)p(z) dz 你根本没法解析算。我当年第一次跑高维数据时就卡在这儿,想着「是不是我数学太差」,后来发现大家都这样。

于是我们用变分推断:找一个容易计算的分布 q(z|x) 去逼近真实后验 p(z|x)。

核心目标: 最小化 q(z|x) 与 p(z|x) 之间的KL散度。

好,问题来了——KL散度本身还带着p(z|x),而p(z|x)正是我们求不到的。怎么办?

3.2 ELBO登场——等价转换

我们写一下KL散度公式:

KL(q(z|x) || p(z|x)) = ∫ q(z|x) log[ q(z|x) / p(z|x) ] dz

展开 p(z|x) = p(x|z)p(z)/p(x),得到:

KL = ∫ q(z|x) log[ q(z|x) p(x) / (p(x|z)p(z)) ] dz
   = log p(x) + ∫ q(z|x) log[ q(z|x) / p(x|z)p(z) ] dz
   = log p(x) - ( ∫ q(z|x) log p(x|z) dz - KL(q(z|x) || p(z)) )

移项后得到:

log p(x) - KL = ELBO
其中 ELBO = E_{z~q}[log p(x|z)] - KL(q(z|x) || p(z))

这个ELBO就是证据下界(Evidence Lower BOund)。

我的理解: 最大化ELBO等价于最小化KL散度。而ELBO里每一项都能算——第一项是重建对数似然,第二项是隐变量分布的KL惩罚项。说白了,ELBO就是「重建质量 - 分布距离」。

3.3 变分自编码器与PAE的联系

PAE直接套用了VAE的变分框架:编码器输出 q(z|x)(通常是高斯分布的均值和方差),解码器负责 p(x|z)。但PAE在损失函数里引入了异常值惩罚项,这个咱们后面再细讲。

你先记着这结构:

组件作用
编码器 (Encoder)将x映射到近似后验 q(z|x) 的参数(μ, σ)
隐变量采样从q(z|x)中采样一个z(需要重参数化)
解码器 (Decoder)根据z重建x,计算 log p(x|z)
ELBO损失最大化 ELBO = E[log p(x|z)] - KL(q||p)

3.4 重参数化技巧——为什么你需要它?

你想想看,我们要求梯度来训练网络,但采样这个操作是不可导的。假设q(z|x)是正态分布 N(μ, σ²),你直接采样z ~ N(μ, σ²),那z这个随机节点的梯度没法传回μ和σ。

我曾栽跟头的地方: 第一次实现时我没用重参数化,直接采样,反向传播死活不更新编码器的方差参数,训练半天重建效果烂得一塌糊涂。后来才发现采样操作挡住了梯度。

重参数化的解决方式很简单——把随机性分离出来:

z = μ + σ * ε,    其中 ε ~ N(0, 1)

现在z的可导部分来自μ和σ,随机性全由固定的ε提供。这样梯度可以顺利流过μ和σ节点。

具体做法:

  • 编码器输出μ和logσ(取log是为了保证σ正数)
  • 从标准正态分布采样ε
  • 计算 z = μ + exp(logσ) * ε
  • 把z传给解码器

代码片段看起来像这样:

def reparameterize(mu, logvar):
    std = torch.exp(0.5 * logvar)
    eps = torch.randn_like(std)
    return mu + eps * std

这个技巧让整个PAE的ELBO损失变得可微,梯度才能往下传。

3.5 完整的训练损失

PAE的损失函数在ELBO基础上多了一个异常值约束项,但核心还是ELBO。咱们先写出标准ELBO损失(取负号变成最小化问题):

L_ELBO = - E_{z~q}[log p(x|z)] + KL(q(z|x) || p(z))

第一项是重建损失——对于连续数据常用MSE,对于离散数据用交叉熵。

第二项KL散度,在p(z)=N(0,1)且q(z|x)=N(μ, σ²)时,有闭合解:

KL = -0.5 * sum( 1 + log(σ²) - μ² - σ² )

这个公式你们可以手推一遍,挺有意思的。我在项目里直接写了这个函数:

def kl_divergence(mu, logvar):
    return -0.5 * torch.sum(1 + logvar - mu.pow(2) - logvar.exp(), dim=1)

整体损失加上PAE的异常值项(后面章节再展开),就是完整的训练目标。

3.6 一张图串起整个推导

下面我把变分推断→ELBO→重参数化这条线画成结构图,你一眼就能看清逻辑链。

变分推断 → ELBO → 重参数化 核心流程 真实后验 p(z|x) 难以计算 引入 q(z|x) 近似 p(z|x) 最小化 KL(q||p) = log p(x) - ELBO ELBO = E[log p(x|z)] − KL(q(z|x) || p(z)) (重建对数似然 − 隐变量分布正则) 重参数化: z = μ + σ · ε, ε~N(0,1) 关键思想 用可计算的下界 替代不可计算的后验 通过重参数化 让采样节点可导 PAE 沿用同样的框架 额外加异常惩罚

3.7 实操中你需要注意什么

我把自己踩过的坑列出来,你少走弯路。

  • KL退火: 训练初期KL项太强,模型直接忽略重建——我遇到过,重建出来的全是均值。解决办法:让KL权重从0慢慢升到1。
  • logvar初始值: 千万别把logvar初始设成很大的正数,否则σ巨大,采样噪声过强。我一般初始成 -3 ~ -1。
  • 数值稳定性: 计算KL时用 logvar.exp() 可能溢出,建议用 torch.exp(logvar).clamp(min=1e-8)。
一个小技巧: 如果你发现训练不稳定,先固定方差(让σ=1只学μ)跑几个epoch,再放开方差参数。这相当于先学位置再学尺度,收敛快很多。

3.8 小结

PAE的数学根基就是这三个东西:变分推断、ELBO、重参数化。变分推断给了我们一个可优化的下界;ELBO把优化目标拆成重建+正则两项;重参数化让整个流程可微、可学。