
上周在整理伯克利 2026 春季深度强化学习课程笔记时第 11 讲“变分推断Variational Inference”整整卡了两天。不是因为代码难写而是数学符号、强化学习目标、概率图模型三条线交织在一起很容易让人迷失方向。这门课程本身的安排很有意思前面几讲还在讲如何用神经网络拟合 Q 函数、如何估计策略梯度到了这一讲却突然切换到概率推断的视角把强化学习重新表述成一个“推断问题”。这篇博文会把这一讲的来龙去脉整理成一条完整的学习路线先讲清楚变分推断到底在解决什么问题再推导它的核心数学框架 ELBOEvidence Lower Bound然后把它与控制即推断Control as Inference的思路打通看它是怎么落到深度强化学习算法里的。最后我会用 PyTorch 实现一个最经典的变分自编码器VAE一方面验证 ELBO 的推导另一方面为理解潜在变量世界模型比如 Dreamer做好铺垫。适合的读者有两类一是正在系统学习深度强化学习、发现“熵正则项”出现得很突然的同学二是想补概率推断基础、但不想只看纯数学教材的工程师。学完本文后你应该能独立推导 ELBO能解释 PPO/A3C 里的熵正则项与控制即推断的关系并且能手写一个可运行的 VAE 来验证这些概念。1. 为什么要理解变分推断1.1 从贝叶斯后验说起我们先回到一个基本的概率推断问题。假设有一个观测变量x一个隐变量z我们希望知道在看到x之后z的条件分布是什么也就是后验分布p(z|x)。按照贝叶斯公式p(z|x) p(x,z) / p(x) p(x|z) * p(z) / p(x)其中p(x)是边缘似然Marginal Likelihood也叫证据Evidence。在很多实际问题里p(x)并不容易计算因为需要把z的所有可能取值都积分一遍p(x) ∫ p(x|z) p(z) dz如果z的维度很高比如是多层神经网络的全部参数或者是一个图片潜在表征这个积分在数学上和计算上都是极其困难的。即使z只有几十维离散化之后的状态空间也大得无法穷举。这其实就是变分推断出现的核心动机当精确后验不可解时我们用一族简单的、参数化的分布q(z)去近似真正的后验p(z|x)。近似得好不好用 KL 散度来衡量。变分推断的目标就是找到一组参数使得q(z)尽可能接近p(z|x)。1.2 为什么精确推断不可行很多第一次接触变分推断的人会产生一个疑问既然贝叶斯公式看起来这么简洁直接代公式不就行了嘛问题出在分母p(x)上。以高斯混合模型为例如果隐变量是离散的簇标签p(x)是有限项求和但如果隐变量是连续的高维向量p(x)就是一个高维积分。绝大多数情况下这个积分没有解析解数值积分在高维空间里又会遇到“维度灾难”。还有一个更实际的问题我们往往不只是想求一个后验分布还要在这个后验分布下计算期望比如预测新数据的概率或者计算某个函数的期望值。这又是一层积分。精确推断在高维模型、复杂似然函数面前基本上是走不通的。因此现实中的深度强化学习和深度生成模型都采用了“近似推断”的思路用神经网络去参数化一个简单的分布族然后通过梯度下降优化参数让这个简单分布逼近真实后验。1.3 从 MLE 到近似推断在传统的监督学习中我们经常使用最大似然估计MLE或最大后验估计MAP。它们是点估计只给出参数的最优取值不给出参数的不确定性。但在很多场景里点估计是不够的。比如在强化学习中我们不仅想知道当前最优策略是什么还想保证探索的充分性在贝叶斯神经网络中我们希望预测结果带不确定性而不是只输出一个自信的标签。变分推断提供了从点估计走向分布估计的一条可行路径。这也是伯克利深度强化学习课程把变分推断放进“现代深度强化学习算法”前的原因很多算法表面上是梯度下降底层其实是把强化学习建模成概率图模型然后用变分推断去求解最优策略分布。2. 控制即推断强化学习的问题转化2.1 最优变量建模控制即推断Control as Inference是理解“强化学习为什么要用变分推断”的关键桥梁。它的基本思想是把强化学习中的“获得高回报”建模成一个概率事件然后要求解的事件就变成了一个条件概率。具体做法是引入一个二值变量O_t表示在时刻t状态s_t和动作a_t是否“最优”。我们定义p(O_t 1 | s_t, a_t) exp(r(s_t, a_t))也就是说奖励越大O_t 1的概率越接近 1。这里其实隐含了一个要求奖励函数的值应该落在合适的范围内因为概率不能大于 1。如果奖励是一个很大的正数exp(r)会超过 1如果奖励是负数exp(r)会很小。实际使用中通常会对奖励做一个平移或者缩放保证概率有意义。有了O_t之后强化学习的目标就可以重新表述为在给定“每一时刻都最优”这个事件发生的条件下求动作的后验分布p(a_t | s_t, O_t1)。这个后验分布就是最优策略的另一种表达。2.2 从概率联合到策略如果我们把马尔可夫决策过程MDP当作一个概率图模型那么整个轨迹的联合概率可以写成p(τ, O1) ∝ p(s_0) * ∏_t p(s_{t1}|s_t, a_t) * π(a_t|s_t) * exp(r(s_t, a_t))其中π(a_t|s_t)是先验策略exp(r(s_t, a_t))是最优性变量带来的似然项。现在问题变成我们希望找一个策略分布q(a_t|s_t)使得它接近真正的后验p(a_t|s_t, O_t1)。你看这已经不是一个“逐步改进策略”的视角而是变成了一个“推断后验分布”的视角。当我们使用变分推断来求解这个后验时目标函数中会非常自然地出现熵项。我们可以直观理解最优策略不仅要去追求高奖励还要保持足够的随机性因为 KL 散度会惩罚“分布过于尖锐”的情况。2.3 标准化的必要性上述模型里有一个工程上的细节必须说明直接定义p(O_t1|s_t,a_t) exp(r(s_t,a_t))会让奖励的绝对大小直接影响概率。如果奖励整体平移概率会指数级变化导致后验分布完全不同。这意味着在实际的深度强化学习算法中如果我们想让控制即推断的模型成立往往需要对奖励做标准化或者对熵项设置一个温度系数。这个系数控制着“最大化奖励”和“保持探索”之间的平衡。理解了这一点再看很多算法里的熵正则项就不只是“为了增加探索”这样一个辅助解释了而可以看作对控制即推断这个概率模型做变分推断后天然需要的组成部分。3. 变分推断的数学基础从 KL 散度到 ELBO3.1 KL 散度与 ELBO 推导现在正式推导变分推断的核心公式。设q(z)是我们用来近似后验p(z|x)的分布我们希望最小化 KL 散度KL(q(z) || p(z|x)) E_{q(z)}[log q(z) - log p(z|x)]直接把p(z|x) p(x,z) / p(x)代入KL(q(z) || p(z|x)) E_{q(z)}[log q(z)] - E_{q(z)}[log p(x,z)] log p(x)把log p(x)移到等式左边log p(x) KL(q(z) || p(z|x)) E_{q(z)}[log p(x,z) - log q(z)]最后一项通常被称为 ELBO也就是证据下界。因为 KL 散度恒大于等于 0所以 ELBO 是log p(x)的一个下界。这也是它名字的由来。3.2 最大化 ELBO 的含义通过上面的推导可以知道最大化 ELBO 等价于最小化 KL 散度因为log p(x)对于模型参数来说是一个常数。所以变分推断的核心工作就变成了调整q(z)的参数让 ELBO 尽可能大。ELBO 有一个非常有用的展开形式ELBO E_{q(z)}[log p(x|z)] - KL(q(z) || p(z))这个形式的信息量很大。第一项是重构似然可以理解为“从隐变量 z 生成数据 x 的能力”第二项让q(z)不要偏离先验p(z)太远。这正好对应了深度生成模型里的两股力量既要拟合数据又要保持分布的规律性。不少初学者会把“证据下界”理解成一个固定值其实它会在训练过程中不断变化。训练神经网络时ELBO 的数值通常是负数因为对数概率最大也到不了 0加上 KL 散度的约束之后负值很常见。千万不要看到损失为负就认为程序写错了这在变分推断里是完全正常的。3.3 参数化与优化在实际计算中q(z)通常被参数化为一个简单的分布族比如对角高斯分布。如果z的维度是d那么q(z)由d个均值μ和d个标准差σ决定。神经网络的任务就是根据输入x输出这些μ和σ。有了参数化之后ELBO 可以通过蒙特卡洛采样来估计从q(z)中采样若干z计算log p(x|z)和 KL 项。但是直接采样会带来一个问题采样操作是不可导的梯度无法从采样结果回流到编码器网络的参数上。这个问题由重参数化技巧Reparameterization Trick解决。基本思路是采样一个标准高斯噪声ε然后通过线性变换生成zz μ(x) σ(x) ⊙ εε ~ N(0, I)这样求导路径里就只剩确定性变换随机性被转移到了与参数无关的ε上。这个技巧是整个深度变分推断能够训练起来的基石。4. MCMC 与变分推断两条近似路径的对比学习变分推断时经常会遇到另一个名词MCMC马尔可夫链蒙特卡洛。它们都用来近似后验分布但思路完全不同。MCMC 的思路是采样构造一条马尔可夫链让链的平稳分布等于目标后验分布。只要链跑得足够长采样出来的样本就可以用来估计期望。它的优点是理论上有无偏性缺点是收敛速度难以判断高维空间里可能需要数百万次采样才能得到可靠结果。变分推断的思路是优化先假设一个简单的分布族q(z)然后调整参数让这个分布尽可能靠近目标后验。它的缺点是会有近似偏差因为真实后验可能根本不在你假设的分布族里优点是非常适合与神经网络、随机梯度下降结合能够处理大规模数据。对比维度MCMC变分推断核心思路采样近似优化近似理论保证渐近无偏有偏偏差取决于分布族选择计算开销通常很大相对较小可扩展到大数据适合场景低维、精度要求高高维、与深度学习结合是否容易并行较难容易类似一般神经网络训练在伯克利深度强化学习课程的语境里选择变分推断是很自然的事。因为深度强化学习算法面对的状态空间和参数空间都非常大而且我们更关心“如何把神经网络的目标函数写出来然后反向传播更新参数”而不是“如何精确采样一个后验分布”。这也是为什么现代深度强化学习算法里出现频率更高的不是 MCMC而是各种基于变分推断的近似方法。5. 变分推断在深度强化学习算法中的落点5.1 从熵正则项说起如果你用过 PPO、A3C 或者 SAC对下面这个目标函数一定不陌生J(θ) E_{s, a ~ π_θ}[Q(s, a)] α * H(π_θ(·|s))其中H(π_θ(·|s))是当前策略在状态s下的熵α是熵系数。很多资料会告诉你这个熵项是为了鼓励探索防止策略过早收敛到局部最优。这个解释没有错但不够本质。回到控制即推断和变分推断的视角。我们在第 2 节说过强化学习可以建模成求p(a_t|s_t, O_t1)这个后验分布。用变分推断求解时我们希望最小化q(a_t|s_t)与后验之间的 KL 散度。这个目标函数展开后除了奖励项之外还会出现-KL(q(a|s) || p(a|s))这种正则项。如果我们选择先验策略p(a|s)是均匀分布那么-KL(q || uniform)等价于“最大化 q 的熵”。所以熵正则项并不是谁拍脑袋加进去的而是变分推断目标函数的一个自然组成部分。理解这一点之后你再调整 SAC 的熵系数时会清楚自己到底在调节什么你在调节“策略保留多少随机性”与“策略有多贪心”之间的权衡。5.2 策略梯度与变分目标的关系为了更明确地看到变分推断怎么指导算法设计可以写出一个简化版本的变分目标。假设我们要优化策略π_θ(a|s)来逼近最优后验一个典型的梯度形式是∇θ J(θ) E_{a~π_θ}[∇θ log π_θ(a|s) * (Q(s,a) - α log π_θ(a|s))]这个式子跟普通的策略梯度只差了一个α log π_θ(a|s)项。从变分推断的视角看-α log π_θ(a|s)这一项就是在调整策略本身的熵如果一个动作在当前策略下概率很高它就会受到一定的惩罚避免策略变得过于集中。这给我们的启发是策略搜索不只是“找到最优动作”而是“找到一个分布这个分布既要覆盖高价值动作又不能完全坍缩成一个点”。变分推断提供了这个目标函数的形式化来源。5.3 从策略后验到深度强化学习算法在实际的深度强化学习算法中变分推断思想最常见的三种落点包括基于最大熵的策略优化如 SAC、Soft Q Learning把熵正则项纳入目标本质是在做带熵约束的变分策略推断。潜在变量策略Latent Variable Policies用隐变量z建模多模态动作分布通过变分推断学习后验p(z|s, a)解决高斯策略表达力不足的问题。世界模型中的潜在表征学习如 Dreamer用变分自编码器结构学习状态表征让模型在低维潜在空间里做预测和规划。理解了变分推断你会突然发现这些深度强化学习算法之间不再是孤立的技巧堆叠而是共享同一个概率推断骨架。6. 用 PyTorch 实现一个 VAE 验证 ELBO6.1 环境准备先用一个最经典的例子来巩固上面的数学推导变分自编码器VAE。它把输入图片x编码成潜在表示z然后从z解码重建x。训练目标正是最大化 ELBO。环境方面本文示例以常见环境为例请根据你的实际版本调整。建议使用较新的 PyTorch 稳定版本2.xPython 使用 3.9 及以上另外需要安装 torchvision 和 matplotlib。pip install torch torchvision matplotlib6.2 网络结构与重参数化下面代码包含三个部分编码器、解码器、重参数化采样。编码器接收 MNIST 图片28x28 像素输出潜在变量z的均值和方差。解码器从z重建图片。import torch import torch.nn as nn import torch.nn.functional as F class Encoder(nn.Module): def __init__(self, input_dim784, hidden_dim256, latent_dim32): super().__init__() self.fc1 nn.Linear(input_dim, hidden_dim) self.fc_mean nn.Linear(hidden_dim, latent_dim) self.fc_logvar nn.Linear(hidden_dim, latent_dim) def forward(self, x): h F.relu(self.fc1(x)) mean self.fc_mean(h) logvar self.fc_logvar(h) return mean, logvar class Decoder(nn.Module): def __init__(self, latent_dim32, hidden_dim256, output_dim784): super().__init__() self.fc1 nn.Linear(latent_dim, hidden_dim) self.fc2 nn.Linear(hidden_dim, hidden_dim) self.fc3 nn.Linear(hidden_dim, output_dim) def forward(self, z): h F.relu(self.fc1(z)) h F.relu(self.fc2(h)) out torch.sigmoid(self.fc3(h)) return out def reparameterize(mean, logvar): # logvar 是 log(σ^2)方差需要先转成标准差再采样 std torch.exp(0.5 * logvar) eps torch.randn_like(std) return mean eps * std这里的关键就是reparameterize函数。如果直接从编码器输出的分布里采样采样操作不可导反向传播会断掉通过eps引入随机性之后梯度可以正常回流到mean和logvar上。6.3 损失函数与训练循环VAE 的损失函数就是负 ELBO包含两部分重建误差和 KL 散度。重建误差我用二元交叉熵因为我们用 sigmoid 输出像素概率KL 散度用高斯分布之间的闭式解def vae_loss(decoded, x, mean, logvar): reconstruction_loss F.binary_cross_entropy(decoded, x, reductionsum) kl_loss -0.5 * torch.sum(1 logvar - mean.pow(2) - logvar.exp()) return reconstruction_loss kl_loss下面是一个完整的训练循环跑在 MNIST 数据集上。为了收敛更快这里用了 Adam 优化器和 256 的批大小。import torch.optim as optim from torch.utils.data import DataLoader from torchvision import datasets, transforms batch_size 256 latent_dim 32 epochs 20 device cuda if torch.cuda.is_available() else cpu transform transforms.Compose([ transforms.ToTensor(), transforms.Lambda(lambda x: x.view(-1)) ]) train_dataset datasets.MNIST(root./data, trainTrue, downloadTrue, transformtransform) train_loader DataLoader(train_dataset, batch_sizebatch_size, shuffleTrue) encoder Encoder().to(device) decoder Decoder().to(device) optimizer optim.Adam(list(encoder.parameters()) list(decoder.parameters()), lr1e-3) for epoch in range(epochs): total_loss 0.0 for x, _ in train_loader: x x.to(device) mean, logvar encoder(x) z reparameterize(mean, logvar) decoded decoder(z) loss vae_loss(decoded, x, mean, logvar) optimizer.zero_grad() loss.backward() optimizer.step() total_loss loss.item() print(fEpoch {epoch1}/{epochs}, loss{total_loss / len(train_loader.dataset):.4f})训练 20 个 epoch 之后loss 通常会从最初的超过 300 降到 110 左右。如果你注意到 loss 是负数通常说明代码里 KL 项或重建项的数值表达出了问题MNIST VAE 的 loss 一般是正数因为重建误差占主导。6.4 可视化验证为了直观验证模型学到了有意义的潜在空间可以用标准正态分布采样一批z然后喂给解码器生成图片。import matplotlib.pyplot as plt z torch.randn(16, latent_dim).to(device) with torch.no_grad(): generated decoder(z).cpu().view(16, 1, 28, 28) fig, axes plt.subplots(4, 4, figsize(6, 6)) for i, ax in enumerate(axes.flatten()): ax.imshow(generated[i, 0], cmapgray) ax.axis(off) plt.tight_layout() plt.show()如果训练正常你会看到生成的手写数字虽然不够完美但已经具备数字的结构特征。这说明编码器确实把 784 维的图片压缩到了一个 32 维的潜在空间里并且解码器能够从这个潜在空间恢复图片。这个“压缩-重建”过程本质上就是变分推断在深度生成模型中的一次完整落地。7. 进阶应用潜在变量世界模型7.1 从 VAE 到世界模型VAE 解决了“如何学习一个潜在表征”的问题而这个能力在深度强化学习里正好被世界模型World Model需要。代表性的工作如 Dreamer会先学习一个潜在动力学模型然后在潜在空间里做想象 rollout最后用这些想象轨迹来训练策略。Dreamer 的核心组件包括表征模型Representation Model从观测o_t和隐状态h_t推断潜在状态z_t。转移模型Transition Model从h_t和动作a_t预测下一个潜在状态z_{t1}的分布。奖励预测器Reward Predictor从z_t预测即时奖励。这些组件的训练目标可以写成E[log p(o_t|z_t)] - β * KL(q(z_t|o_t, h_t) || p(z_t|h_t))看到没有这个目标和 VAE 的 ELBO 在结构上几乎一模一样第一项是解码重建第二项是 KL 正则。所以如果你已经能实现 VAE再去看 Dreamer 的代码会觉得很多模块都是熟悉的。世界模型不过是在 VAE 的基础上加了一个时序结构让潜在空间能够跨越多个时间步保持一致。7.2 变分推断在基于模型的强化学习中的作用基于模型的强化学习有一个经典问题如果模型预测误差累积策略可能会在幻觉轨迹上过度优化导致在真实环境里表现很差。变分推断在这里提供的帮助是让模型不仅预测一个未来状态而是预测一个状态分布。策略在规划时需要考虑多种可能的发展方向而不是死板地依赖单一预测。从实现角度来说这通常意味着世界模型不会直接输出一个确定性的z_{t1}而是输出一个高斯分布的均值和方差然后采样出z_{t1}再继续往后预测。这种随机性让模型保留了不确定性也让策略的探索更有依据。把这套思路和伯克利课程第 11 讲结合起来看你会发现变分推断并不仅仅是一个“概率图模型理论”而是深度强化学习里许多前沿算法的实际训练目标。8. 常见问题与排查思路学习变分推断以及把它应用到深度强化学习时下面几个问题出现频率很高。问题现象常见原因解决思路生成效果模糊数字轮廓模糊不清VAE 本身倾向生成均值附近的数据是对“模糊”的天然解释改用更强大的解码器、增加潜在维度或尝试 Flow-based 方法KL 项消失所有样本集中在先验附近后验坍缩Posterior Collapse解码器太强导致隐变量被忽略降低解码器容量、增加 KL 项权重、使用 KL 退火训练 loss 一开始就是负数且波动大对数概率计算单位混乱或 KL 项为负检查logvar是否误用为方差KL 闭式解里1 logvar - mean^2 - exp(logvar)写全PPO/SAC 熵系数调大后奖励崩掉熵项与奖励项失衡策略过于随机减小α或使用自适应温度调节让目标熵随训练动态变化把 MCMC 直接用到深度 RL 中无法收敛高维连续动作空间采样开销过大优先选择变分推断近似而不是无偏采样重参数化后梯度为 NaN标准差过小或过大导致数值溢出对logvar做数值裁剪比如限制在[-10, 10]区间内第一个问题需要多说一句VAE 生成图片模糊并不是代码写错了而是最大似然在像素级别上进行平均后产生的必然结果。做图像生成时更推荐生成对抗网络GAN或扩散模型但如果你的目标是学习潜在表征VAE 的模糊性并不影响表征质量甚至可能说明隐变量足够鲁棒。后验坍缩是变分自编码器家族里一个真正需要留意的问题。当你发现训练过程中 KL 项快速降到接近 0大概率是解码器太强大可以不依赖隐变量完成重建。这时隐变量就退化成白噪声网络没有动力从输入里提取有用的结构。常见的修复手段是 KL 退火训练初期让 KL 项权重从 0 慢慢升到 1逼着网络先建立“编码器到解码器”的信息通路再逐步强化正则约束。9. 最佳实践与工程建议9.1 数学推导顺序如果你是从零学习这一讲建议不要直接看论文里的复杂推导而是按下面顺序逐步建立直觉第一步先手推一个离散隐变量的例子。假设z只有两个取值写出 KL 散度的每一项理解为什么 ELBO 是下界。第二步再推高斯隐变量。这时候你会遇到多元高斯 KL 闭式解顺便理解重参数化要解决什么问题。第三步回到控制即推断把“奖励指数化”代入 ELBO 公式把熵项找出来。这一步做完了你再看 SAC 论文会非常顺畅。9.2 代码调试顺序写 VAE 或者变分策略模型时按下面顺序检查代码先跑通一个极小的数据集比如只有几十个样本的 Toy Dataset确认 loss 能下降。再检查重建输出。如果重建都是 0.5 左右的灰块说明解码器或损失函数有问题。打印 KL 项和重建项的数值。如果 KL 项占比太小说明模型把信息都放在“直接重建”上没有压缩到潜在空间。最后再上完整数据集通过可视化 latent space 的聚类效果来判断表征是否合理。9.3 强化学习工程中的实际建议在真正把变分推断的思想引入强化学习时有几个工程建议值得长期保留熵系数不要拍脑袋定。可以先让策略在没有熵正则的情况下训练若干轮观察策略熵的自然下降速度再设定一个目标熵值。注意奖励量纲。控制即推断模型中exp(r)依赖奖励的绝对大小实际工程里奖励缩放往往比调大α更有效。潜在空间维度不宜过大。世界模型和 VAE 里把latent_dim从 32 加到 128并不一定会提升效果有时反而让 KL 项难以平衡导致训练不稳定。涉及生产环境或真实物理系统时任何策略参数更新都必须经过仿真验证、灰度环境和最小权限原则的约束不要在未验证模型可靠性的情况下直接上真实系统。10. 总结与下一步学习路线变分推断这一讲看起来是纯数学内容但它的价值在于提供了一个统一的视角无论是 VAE、熵正则化的策略优化还是潜在变量世界模型本质上都是在做同一件事——用一个可优化的简单分布去近似一个难解的后验分布。今天这篇文章的核心内容包括变分推断解决了精确后验不可计算的问题。ELBO 的推导过程和最大化它的实际含义。控制即推断如何把强化学习变成概率推断问题。PPO、A3C 等算法里的熵正则项与变分推断的关系。用 PyTorch 实现 VAE验证 ELBO 的完整流程。Dreamer 等世界模型如何沿用了变分推断的训练目标。下一步的学习路线我建议你先回到伯克利课程的第 10 讲和第 12 讲把“策略梯度”和“价值函数”部分重新读一遍带着“熵正则项其实是变分目标的一部分”这个新视角去理解。然后可以打开 Dreamer 的官方实现找到表征模型和转移模型的 loss 函数对比一下它和本文 VAE 代码的相似之处。如果这两步你都能完成变分推断这一讲的知识就不再是一个孤立的公式而会成为你真的能用来设计新算法的工具。最后给你一个操作性很强的建议不要只看推导去跑一次代码。把第 6 节里的 VAE 代码复制进 Jupyter Notebook 或者脚本文件先跑通 MNIST再把latent_dim改成 2 并用scatter可视化数字类别在潜在平面上的分布。你会看到 0 和 1 这类形状差异大的数字自然分开而 3、5、8 这些结构相近的数字靠得很近。这种直观感受比记住 ELBO 公式更宝贵也会让你在之后学习深度强化学习的世界模型时少走很多弯路。