机器学习中的变分推理:从KL散度到ELBO的直观理解(附Python代码示例)
从直觉到实践:拆解变分推理的KL散度与ELBO核心
如果你接触过概率图模型、深度生成模型(比如VAE),或者任何需要处理复杂后验分布的场景,大概率会碰到“变分推理”这个词。它听起来很高深,充斥着KL散度、ELBO、证据下界这些术语,让很多开发者望而却步,觉得这是一块纯理论的数学硬骨头。但事实上,它的核心思想非常直观:用一个我们熟悉的、简单的分布,去逼近一个我们搞不定的、复杂的分布。今天,我们不打算堆砌复杂的公式推导,而是换个角度,从“为什么需要它”以及“它到底在干什么”入手,用代码和图像把KL散度和ELBO这两个核心概念掰开揉碎,让你获得一种“原来如此”的直觉理解。
想象一下,你手里有一个复杂的黑盒系统,输入一些潜在因素(比如光照、角度),它能生成一张图片。现在给你一堆图片(观测数据),让你反推每张图片对应的潜在因素是什么(后验分布)。直接计算这个“反推”的解析解几乎不可能(证据项难以计算)。变分推理的聪明之处在于,它不硬算,而是说:“我们找一个形式简单的分布家族(比如高斯分布),然后在这个家族里,找一个和那个复杂后验分布最‘像’的成员。” 衡量“像不像”的尺子,就是KL散度;而找到这个成员的过程,就转化为最大化ELBO这个更易处理的目标。接下来,我们就沿着这把“尺子”和这个“目标”,一步步构建起可操作的认知。
1. 重新认识KL散度:不只是距离,更是一种权衡
在信息论中,KL散度(Kullback-LeLeibler Divergence)常被描述为两个概率分布P和Q之间的“距离”或“差异”。但严格来说,它并不满足距离度量的对称性和三角不等式,所以称之为“散度”更准确。它的定义式对于离散分布是:
$$ D_{KL}(P || Q) = \sum_{x} P(x) \log \frac{P(x)}{Q(x)} $$
对于连续分布,求和换成积分即可。这个公式的直观含义是什么?我们可以把它拆解来看:
- $\log \frac{P(x)}{Q(x)}$: 这衡量了在某个具体点x上,真实分布P与近似分布Q的“惊奇度”差异。如果Q(x)很小(即Q认为x几乎不可能发生),而P(x)较大,那么这个比值会很大,取对数后就是一个很大的正数,表示Q在这里错得离谱。
- $P(x)$: 这是权重。KL散度是用真实分布P作为权重,对所有点上的“惊奇度差异”进行加权平均。这意味着,KL散度更关心在P的高概率区域,Q是否也给出了高概率。
所以,$D_{KL}(P || Q)$ 的核心是:站在分布P的视角,去评价Q作为近似的糟糕程度。
1.1 前向KL与反向KL:两种截然不同的逼近哲学
KL散度的非对称性($D_{KL}(P || Q) \neq D_{KL}(Q || P)$)绝非数学上的瑕疵,而是赋予了它两种不同的逼近模式,对应着不同的风险偏好。
前向KL:$D_{KL}(P || Q)$ 这是我们刚才讨论的标准形式,“以P为基准看Q”。最小化前向KL会导致一种被称为 “零避免” 的行为。因为公式中P(x)是权重,一旦在某个x点有P(x) > 0而Q(x) → 0,那么$\log(P(x)/Q(x))$会趋向无穷大,导致KL散度爆炸。因此,为了最小化损失,Q会被迫在P所有可能出现的区域(即P(x)>0的地方)都赋予非零的概率。结果就是,Q会倾向于“覆盖”或“抹平”P的所有模式,哪怕有些模式概率很低,Q也不敢完全忽略。
反向KL:$D_{KL}(Q || P)$ 这次我们调换了位置,“以Q为基准看P”。最小化反向KL会导致 “零强迫” 行为。此时,权重变成了Q(x)。如果Q(x) > 0而P(x) → 0,同样会导致散度很大。因此,Q会倾向于只在P的高概率区域集中概率质量,而主动忽略P的那些低概率区域(即使P在那里有非零值)。结果就是,Q会倾向于“锁定”P的某一个主要模式(比如一个峰值),而忽略其他次要模式。
提示:你可以把P想象成一片有着多个山峰(模式)的山脉。最小化前向KL,相当于要求你的近似分布Q是一片高原,必须覆盖所有山峰所在的位置;而最小化反向KL,则允许你的Q是一座单独的山峰,但它必须稳稳地坐在某一个真实山峰的顶上。
让我们用代码生成一个双峰分布的例子,来可视化这两种差异。
import numpy as np
import matplotlib.pyplot as plt
from scipy import stats, optimize
# 1. 定义一个双峰的真实分布 P (两个高斯分布的混合)
def true_distribution(x):
# 混合两个高斯分布,权重各0.5
peak1 = stats.norm.pdf(x, loc=-1.5, scale=0.6)
peak2 = stats.norm.pdf(x, loc=1.5, scale=0.8)
return 0.5 * peak1 + 0.5 * peak2
# 2. 我们的近似分布族 Q:单高斯分布,由均值mu和标准差sigma参数化
def approx_distribution(x, mu, sigma):
return stats.norm.pdf(x, loc=mu, scale=sigma)
# 3. 定义KL散度(离散近似,在采样点上计算)
def kl_divergence(p_probs, q_probs):
# 避免log(0)的情况,加一个极小值
eps = 1e-10
p_probs = np.clip(p_probs, eps, 1)
q_probs = np.clip(q_probs, eps, 1)
return np.sum(p_probs * np.log(p_probs / q_probs))
# 4. 在定义域上采样点,用于计算和绘图
x = np.linspace(-5, 5, 1000)
p_vals = true_distribution(x)
p_vals_normalized = p_vals / np.sum(p_vals) # 离散概率归一化
# 5. 优化:找到最佳的单高斯分布来近似双峰分布
# 目标函数:KL散度
def objective_forward(params):
mu, sigma = params
q_vals = approx_distribution(x, mu, sigma)
q_vals_normalized = q_vals / np.sum(q_vals)
return kl_divergence(p_vals_normalized, q_vals_normalized)
def objective_reverse(params):
mu, sigma = params
q_vals = approx_distribution(x, mu, sigma)
q_vals_normalized = q_vals / np.sum(q_vals)
# 注意这里顺序调换了
return kl_divergence(q_vals_normalized, p_vals_normalized)
# 初始猜测
initial_guess = [0.0, 2.0]
# 使用优化器寻找最小化KL散度的参数
result_forward = optimize.minimize(objective_forward, initial_guess, bounds=[(-5,5), (0.1, 5)])
result_reverse = optimize.minimize(objective_reverse, initial_guess, bounds=[(-5,5), (0.1, 5)])
mu_forward, sigma_forward = result_forward.x
mu_reverse, sigma_reverse = result_reverse.x
print(f"前向KL最优近似: 均值(mu)={mu_forward:.3f}, 标准差(sigma)={sigma_forward:.3f}")
print(f"反向KL最优近似: 均值(mu)={mu_reverse:.3f}, 标准差(sigma)={sigma_reverse:.3f}")
# 6. 绘图
fig, axes = plt.subplots(1, 3, figsize=(15, 4))
# 子图1:真实分布P
axes[0].plot(x, p_vals, 'b-', linewidth=3, label='真实分布 P (双峰)')
axes[0].fill_between(x, p_vals, alpha=0.3, color='blue')
axes[0].set_title('真实分布 P')
axes[0].legend()
axes[0].grid(True, alpha=0.3)
# 子图2:最小化前向KL得到的Q
q_vals_forward = approx_distribution(x, mu_forward, sigma_forward)
axes[1].plot(x, p_vals, 'b-', linewidth=2, label='真实 P', alpha=0.7)
axes[1].plot(x, q_vals_forward, 'r--', linewidth=3, label=f'近似 Q (前向KL)\n$\mu$={mu_forward:.2f}, $\sigma$={sigma_forward:.2f}')
axes[1].fill_between(x, q_vals_forward, alpha=0.3, color='red')
axes[1].set_title('最小化 D_KL(P || Q) (前向KL)')
axes[1].legend()
axes[1].grid(True, alpha=0.3)
# 子图3:最小化反向KL得到的Q
q_vals_reverse = approx_distribution(x, mu_reverse, sigma_reverse)
axes[2].plot(x, p_vals, 'b-', linewidth=2, label='真实 P', alpha=0.7)
axes[2].plot(x, q_vals_reverse, 'g--', linewidth=3, label=f'近似 Q (反向KL)\n$\mu$={mu_reverse:.2f}, $\sigma$={sigma_reverse:.2f}')
axes[2].fill_between(x, q_vals_reverse, alpha=0.3, color='green')
axes[2].set_title('最小化 D_KL(Q || P) (反向KL)')
axes[2].legend()
axes[2].grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
运行这段代码,你会看到三张图。中间那张图(前向KL)中的红色虚线高斯分布,会试图变得“又矮又胖”,去覆盖蓝色双峰分布的两个鼓包。而右边那张图(反向KL)中的绿色虚线高斯分布,则会“又高又瘦”地锁定在左边或右边的某一个峰上(具体锁定哪个取决于优化初始值)。这个直观对比清晰地展示了两种KL散度引导出的不同逼近策略。
在变分推理中,我们几乎总是使用反向KL $D_{KL}(Q || P)$。为什么呢?因为我们的目标分布P是复杂的后验 $p(z|x)$,而Q是我们选择的简单分布(如高斯分布)。使用反向KL,允许我们的近似分布Q“大胆地”忽略后验分布中那些概率极低的区域,从而得到一个更简洁、更易于处理的近似,即使它可能丢失了一些细节(多峰信息)。这在计算上是友好的,也符合我们寻找一个代表性“总结”的初衷。
2. 变分推理框架:将推断转化为优化
理解了KL散度,尤其是反向KL,我们就可以正式进入变分推理的框架了。我们的核心问题是:给定观测数据 $x$,我们想推断潜在变量 $z$ 的后验分布 $p(z|x)$,但这个分布太复杂,无法直接计算或采样。
变分推理的思路是:
- 选择一个简单的分布族 $Q$: 比如所有对角协方差的高斯分布,或所有因子分解的分布(平均场假设)。这个族由一组参数 $\phi$ 控制,记作 $q_{\phi}(z)$。
- 在这个族里找一个最好的: 如何定义“最好”?就是让 $q_{\phi}(z)$ 与真实后验 $p(z|x)$ 的KL散度最小。
- 将推断问题转化为优化问题: 我们的目标变成了: $$\phi^* = \arg\min_{\phi} D_{KL}\big( q_{\phi}(z) \ || \ p(z|x) \big)$$
然而,直接优化这个目标有个致命问题:计算 $D_{KL}$ 需要知道 $p(z|x)$,而这正是我们不知道的!这就陷入了一个循环。变分推理的巧妙之处在于,它通过数学变换,绕开了这个难题。
2.1 ELBO的登场:一个可优化的替代目标
我们对目标进行拆解: $$ \begin{aligned} D_{KL}(q_{\phi}(z) || p(z|x)) &= \mathbb{E}{z \sim q{\phi}} \left[ \log \frac{q_{\phi}(z)}{p(z|x)} \right] \ &= \mathbb{E}{z \sim q{\phi}} [\log q_{\phi}(z)] - \mathbb{E}{z \sim q{\phi}} [\log p(z|x)] \end{aligned} $$
将第二项中的 $p(z|x)$ 用贝叶斯公式 $p(z|x) = \frac{p(x, z)}{p(x)}$ 替换: $$ \begin{aligned} ... &= \mathbb{E}{z \sim q{\phi}} [\log q_{\phi}(z)] - \mathbb{E}{z \sim q{\phi}} [\log p(x, z)] + \mathbb{E}{z \sim q{\phi}} [\log p(x)] \ &= \mathbb{E}{z \sim q{\phi}} [\log q_{\phi}(z)] - \mathbb{E}{z \sim q{\phi}} [\log p(x, z)] + \log p(x) \quad (\text{因为} p(x) \text{与} z \text{无关}) \end{aligned} $$
移项后,我们得到: $$ \log p(x) = D_{KL}(q_{\phi}(z) || p(z|x)) + \underbrace{\mathbb{E}{z \sim q{\phi}} [\log p(x, z)] - \mathbb{E}{z \sim q{\phi}} [\log q_{\phi}(z)]}_{\text{证据下界 (ELBO)}} $$
由于KL散度总是非负的,我们立刻得到一个重要结论: $$ \log p(x) \ge \text{ELBO}(\phi) $$ 也就是说,ELBO是数据对数似然 $\log p(x)$(又称证据)的一个下界。这就是它名字“Evidence Lower BOund”的由来。
观察上式,为了最小化 $D_{KL}$,由于 $\log p(x)$ 是固定值(只与数据和真实模型有关),最大化ELBO就等价于最小化KL散度。于是,我们无法直接优化的KL散度最小化问题,就转化为了可以操作的ELBO最大化问题: $$\phi^* = \arg\max_{\phi} \text{ELBO}(\phi) = \arg\max_{\phi} \left{ \mathbb{E}{z \sim q{\phi}} [\log p(x, z)] - \mathbb{E}{z \sim q{\phi}} [\log q_{\phi}(z)] \right}$$
这个新的目标函数美妙在哪里?
- 不再需要 $p(z|x)$: 它只涉及联合分布 $p(x, z) = p(z)p(x|z)$(先验和似然)以及我们设定的变分分布 $q_{\phi}(z)$。这两者通常是我们能够定义和计算的。
- 物理意义清晰: ELBO可以进一步写为: $$\text{ELBO}(\phi) = \mathbb{E}{z \sim q{\phi}} [\log p(x|z)] - D_{KL}(q_{\phi}(z) || p(z))$$ 第一项是重构项:期望在近似后验 $q_{\phi}(z)$ 下,数据 $x$ 的似然尽可能大,这促使模型更好地拟合数据。 第二项是正则化项:惩罚近似后验 $q_{\phi}(z)$ 偏离先验 $p(z)$ 的程度,防止过拟合,并鼓励学习到有意义的隐表示。
这个形式在变分自编码器(VAE)中得到了经典的应用。最大化ELBO,就是在数据拟合和后验对齐先验之间做一个权衡。
3. 实战:用PyTorch实现一个简单的变分推断
理论说得再多,不如动手写一行代码。我们来实现一个最简单的场景:用变分推断估计一维高斯混合模型的后验。假设我们观测到一些数据 $x$,它们来自一个由潜在变量 $z$(指示来自哪个高斯成分)控制的混合分布。我们的目标是推断 $z$ 的后验分布。
import torch
import torch.nn as nn
import torch.distributions as dist
import matplotlib.pyplot as plt
import numpy as np
# 设置随机种子以确保结果可复现
torch.manual_seed(42)
np.random.seed(42)
# 1. 生成模拟数据
# 真实模型:两个一维高斯分布混合,z=0或1决定来自哪个成分
true_mu = torch.tensor([-2.0, 2.0]) # 两个高斯成分的均值
true_sigma = torch.tensor([0.7, 1.0]) # 两个高斯成分的标准差
true_pi = torch.tensor([0.4, 0.6]) # 混合权重
num_samples = 500
# 生成 latent z
true_z = torch.multinomial(true_pi, num_samples, replacement=True)
# 根据z生成x
true_x = torch.zeros(num_samples)
for i in range(num_samples):
true_x[i] = torch.normal(true_mu[true_z[i]], true_sigma[true_z[i]])
# 2. 定义变分分布 q_phi(z | x)
# 这里我们假设 q 是一个伯努利分布,参数 phi 由数据 x 通过一个神经网络决定
class VariationalDistribution(nn.Module):
def __init__(self):
super().__init__()
# 一个非常简单的网络:x (标量) -> 隐层 -> logits (标量,用于伯努利参数)
self.net = nn.Sequential(
nn.Linear(1, 10),
nn.ReLU(),
nn.Linear(10, 1) # 输出 logit,通过sigmoid得到概率
)
def forward(self, x):
# x shape: (batch_size, 1)
logits = self.net(x) # (batch_size, 1)
# 定义变分分布 q(z|x) 为伯努利分布
q_z_given_x = dist.Bernoulli(logits=logits.squeeze(-1))
return q_z_given_x
# 3. 定义模型联合分布 p(x, z) = p(z) * p(x|z)
# 先验 p(z): 伯努利,这里我们假设混合权重为先验知识(或可学习)
prior_pi = torch.tensor([0.5, 0.5]) # 均匀先验
prior_dist = dist.Bernoulli(probs=prior_pi[1]) # p(z=1) = 0.5
# 似然 p(x|z): 高斯分布,参数需要学习或设定
# 为了简化,我们假设已知两个高斯成分的均值和方差,但不知道每个数据点属于哪个成分。
# 在实际VI中,这些也可以是模型参数。这里我们固定为真实值(或接近的值)。
likelihood_mu = nn.Parameter(torch.tensor([-1.8, 2.2]), requires_grad=True) # 可学习的均值
likelihood_sigma = nn.Parameter(torch.tensor([1.0, 1.0]), requires_grad=True) # 可学习的标准差
def log_joint(x, z):
"""
计算 log p(x, z) = log p(z) + log p(x|z)
x: 观测数据 (batch_size, 1)
z: 隐变量 (batch_size,), 取值为0或1
"""
# log p(z)
log_p_z = prior_dist.log_prob(z) # (batch_size,)
# log p(x|z)
# 为每个样本选择对应的高斯参数
mu_z = likelihood_mu[z.long()] # (batch_size,)
sigma_z = likelihood_sigma[z.long()] # (batch_size,)
log_p_x_given_z = dist.Normal(mu_z, sigma_z).log_prob(x.squeeze(-1)) # (batch_size,)
return log_p_z + log_p_x_given_z
# 4. 定义ELBO计算和优化
variational_net = VariationalDistribution()
optimizer = torch.optim.Adam(list(variational_net.parameters()) + [likelihood_mu, likelihood_sigma], lr=0.01)
num_epochs = 2000
elbo_history = []
for epoch in range(num_epochs):
optimizer.zero_grad()
# 重参数化采样:从 q(z|x) 采样,同时保持梯度可传
q_dist = variational_net(true_x.unsqueeze(-1)) # q(z | x)
# 使用重参数化技巧采样(对于伯努利,可以使用Gumbel-Softmax松弛或直接score function estimator)
# 这里为了简单,我们使用score function estimator (REINFORCE) 的变体,但更常见的是用连续分布的重参数化。
# 我们改用分析计算ELBO,因为对于离散隐变量,ELBO有时可以解析计算或使用其他技巧。
# 但为了教学演示,我们假设z是连续的并用重参数化。这里我们调整问题:假设q是连续分布(如Logistic)来近似离散后验。
# 让我们调整方法,使用连续松弛(Gumbel-Softmax)或直接计算ELBO的蒙特卡洛估计。
# 更简单直接的方法:对于伯努利q,ELBO中的期望可以近似为对z的采样。
# 我们使用简单的蒙特卡洛估计,采样一个z
z_sample = q_dist.sample() # (batch_size,) 0或1
# 计算 log p(x, z_sample)
log_p_xz = log_joint(true_x.unsqueeze(-1), z_sample)
# 计算 log q(z_sample | x)
log_q_z_given_x = q_dist.log_prob(z_sample)
# 蒙特卡洛估计的ELBO
elbo_sample = log_p_xz - log_q_z_given_x
# 对batch求平均作为目标(最大化平均ELBO)
loss = -elbo_sample.mean() # 负ELBO作为损失
loss.backward()
optimizer.step()
elbo_history.append(-loss.item())
if epoch % 200 == 0:
print(f'Epoch {epoch:4d}, Loss (负ELBO): {loss.item():.4f}')
# 5. 训练后,查看学到的变分分布参数
print(f"\n学习到的似然参数:")
print(f" 成分0: 均值={likelihood_mu[0].item():.3f}, 标准差={likelihood_sigma[0].item():.3f}")
print(f" 成分1: 均值={likelihood_mu[1].item():.3f}, 标准差={likelihood_sigma[1].item():.3f}")
# 6. 可视化结果
fig, axes = plt.subplots(1, 2, figsize=(12, 4))
# 子图1:ELBO训练曲线
axes[0].plot(elbo_history)
axes[0].set_xlabel('训练轮次')
axes[0].set_ylabel('ELBO (估计值)')
axes[0].set_title('ELBO在训练过程中的变化')
axes[0].grid(True, alpha=0.3)
# 子图2:真实数据分布与学到的后验推断
axes[1].hist(true_x.numpy(), bins=30, density=True, alpha=0.6, label='观测数据 x', color='gray')
# 绘制真实混合分布
x_range = np.linspace(-6, 6, 300)
true_pdf = (true_pi[0] * dist.Normal(true_mu[0], true_sigma[0]).log_prob(torch.tensor(x_range)).exp() +
true_pi[1] * dist.Normal(true_mu[1], true_sigma[1]).log_prob(torch.tensor(x_range)).exp()).numpy()
axes[1].plot(x_range, true_pdf, 'b-', linewidth=2, label='真实数据分布 p(x)')
# 绘制学到的两个高斯成分
learned_pdf = (0.5 * dist.Normal(likelihood_mu[0], likelihood_sigma[0]).log_prob(torch.tensor(x_range)).exp() +
0.5 * dist.Normal(likelihood_mu[1], likelihood_sigma[1]).log_prob(torch.tensor(x_range)).exp()).detach().numpy()
axes[1].plot(x_range, learned_pdf, 'r--', linewidth=2, label='学到的模型 p(x|z)先验混合')
axes[1].set_xlabel('x')
axes[1].set_ylabel('概率密度')
axes[1].set_title('数据分布与学到的模型对比')
axes[1].legend()
axes[1].grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
# 7. 对几个样本点,查看其变分后验 q(z|x)
test_x = torch.tensor([[-3.0], [0.0], [3.0]])
with torch.no_grad():
for x_val in test_x:
q_dist = variational_net(x_val.unsqueeze(0))
prob_z1 = q_dist.probs.item() # q(z=1 | x) 的概率
print(f"对于 x = {x_val.item():.1f}, q(z=1|x) = {prob_z1:.4f} (倾向于成分 {1 if prob_z1 > 0.5 else 0})")
这段代码虽然简化了很多细节(例如对离散隐变量更精确的梯度估计),但它清晰地勾勒出了变分推断的实践流程:
- 定义变分分布族: 我们用一个小型神经网络将观测数据 $x$ 映射到隐变量 $z$ 的分布参数(这里是伯努利概率)。
- 构建联合分布模型: 定义先验 $p(z)$ 和似然 $p(x|z)$。
- 计算并最大化ELBO: 通过采样估计期望,计算ELBO并取其负值作为损失进行梯度下降优化。
- 推断: 训练完成后,对于新的 $x$,我们可以直接通过
variational_net(x)得到其后验近似 $q(z|x)$,而无需进行复杂的积分计算。
运行代码,你会看到ELBO随着训练逐渐上升(损失下降),学到的似然参数会逼近真实值,并且对于处于不同区间的 $x$,网络给出的 $q(z=1|x)$ 概率也符合直觉(例如 $x=3$ 更可能来自成分1)。
4. 超越基础:现代变分推理的演进与挑战
我们上面介绍的是变分推理最经典和基础的形式。在实际研究和应用中,为了提升其性能和适用性,衍生出了许多重要的变体和技巧。
1. 平均场变分推断 这是最常用的假设之一,它假设变分分布 $q(z)$ 可以完全分解为各个隐变量分量独立的乘积: $$q(z) = \prod_{i=1}^{d} q_i(z_i)$$ 这个假设极大地简化了计算,使得我们可以轮流优化每一个 $q_i(z_i)$(坐标上升法)。但它也带来了一个明显的限制:忽略了隐变量之间的相关性。在很多复杂模型中,这可能导致近似效果不佳。
2. 随机梯度变分推断与重参数化技巧 对于大数据集和复杂模型,传统的优化方法(如坐标上升)效率低下。随机梯度变分推断通过从变分分布中采样来蒙特卡洛估计ELBO的梯度,并利用随机梯度下降进行优化。这里的关键是重参数化技巧。
假设 $z \sim q_{\phi}(z) = \mathcal{N}(\mu_{\phi}, \sigma_{\phi}^2)$,我们可以将其重参数化为: $$z = \mu_{\phi} + \sigma_{\phi} \cdot \epsilon, \quad \epsilon \sim \mathcal{N}(0, 1)$$ 这样,采样过程从依赖参数 $\phi$ 的分布中直接采样,变成了从一个固定分布中采样 $\epsilon$ 再进行确定性变换。这使得梯度 $\nabla_{\phi} \mathbb{E}{z \sim q{\phi}}[f(z)]$ 可以顺利地通过 $z$ 传递到 $\phi$ 上,大大降低了梯度的方差,使得基于梯度的优化稳定可行。这正是VAE能够成功训练的核心。
3. 归一化流 为了克服平均场假设过于简单的缺点,归一化流通过一系列可逆变换,将一个简单的初始分布(如标准高斯)转换为一个复杂、富有表现力的分布。设 $z_0 \sim p_0(z_0)$(简单分布),经过 $K$ 次可逆变换 $f_k$,得到 $z_K = f_K \circ ... \circ f_1(z_0)$。那么 $z_K$ 的分布为: $$\log q_K(z_K) = \log p_0(z_0) - \sum_{k=1}^{K} \log \left| \det \frac{\partial f_k}{\partial z_{k-1}} \right|$$ 通过精心设计 $f_k$ 使其雅可比行列式易于计算,我们可以用 $q_K(z_K)$ 作为变分分布,它能够拟合复杂的多峰后验,同时仍然允许精确的密度计算和采样。下表对比了几种变分分布族的特点:
| 变分分布族 | 表达能力 | 计算复杂度 | 主要优点 | 主要缺点 |
|---|---|---|---|---|
| 平均场 | 弱 | 低 | 简单,可解析更新 | 忽略变量间相关性 |
| 全协方差高斯 | 中等 | 中 | 能捕获相关性 | 参数量大 ($O(d^2)$) |
| 归一化流 | 强 | 高 | 极其灵活,可拟合复杂分布 | 变换设计复杂,计算成本高 |
4. 挑战与应对 变分推理并非银弹,它面临一些固有挑战:
- 近似偏差: 变分族的选择决定了近似的上限。如果真实后验非常复杂,而变分族很简单,无论怎么优化,KL散度也不会降到零。
- 优化困难: ELBO目标可能是非凸的,容易陷入局部最优。初始化和优化器的选择很重要。
- 方差与估计: 基于采样的梯度估计可能存在高方差问题,影响收敛稳定性。除了重参数化,控制变量、Rao-Blackwellization等技术也被用来降低方差。
在实践中,选择哪种变分推理方法,需要在模型复杂度、计算资源和对近似精度的要求之间做出权衡。对于许多现代深度学习模型(如VAE、扩散模型中的部分步骤),变分推理提供了一种将贝叶斯推断与端到端梯度学习相结合的强大范式。
理解变分推理,关键在于把握其“用优化做近似”的核心思想,以及ELBO如何巧妙地绕开计算难题。从KL散度的直观图像,到ELBO的推导与分解,再到一行行代码的实现,我们希望这条路径能帮你建立起牢固的直觉。当你下次在论文中看到“我们采用变分推理来近似后验”时,脑海中浮现的不再是一堆晦涩的符号,而是一个分布试图用简单的形状去捕捉另一个复杂分布轮廓的生动画面,以及一个通过最大化下界来不断调整自身参数的优化过程。
更多推荐
所有评论(0)