☰
控制即推断:变分推断与KL散度在最优控制中的实践
2026/10/9 7:02:26 网站建设 项目流程

1. 从最优控制到概率推断的思维转换

第一次接触Control as Inference这个概念,是在啃一本强化学习教材的间隙。当时我正在做一个机械臂轨迹跟踪的项目,用传统的MPC(模型预测控制)框架调参调得头大——约束要一条条写,代价函数要一项项配,每次改任务就得重新设计目标函数。后来有人跟我说,你换个角度想,把控制问题当成推断问题来做,很多麻烦事会自然消解。我当时半信半疑,但真正把变分推断和KL散度那套东西套进控制回路之后,发现这个视角确实打开了一扇新窗户。

Control as Inference,直译过来就是“控制即推断”。它的核心主张是:最优控制问题可以被重新表述为一个概率推断问题。传统控制论里,我们求解的是一个确定性的优化问题——给定动力学模型和代价函数,找到使代价最小的控制序列。而在Control as Inference的框架下,我们引入一个“最优性变量”,把代价函数转化为概率分布,然后通过推断后验分布来得到控制策略。这个转换听起来像是数学游戏,但它带来的好处是实打实的:约束可以自然地编码为先验分布,不确定性可以用概率的方式传播,多模态的最优解也能被表示出来。

这篇文章适合谁看?如果你正在做机器人控制、自动驾驶轨迹规划、或者强化学习策略优化,并且对传统MPC的刚性约束和手工调参感到疲惫,那Control as Inference值得你花时间理解。如果你只是听说过变分推断和KL散度,但不知道它们怎么跟控制扯上关系,这篇文章会从最基础的概率图模型开始,一步步把这条链路串起来。我尽量不堆公式,而是用从业者的视角讲清楚每个数学操作背后的直觉和动机。

2. 核心理论框架拆解

2.1 为什么要把控制问题写成概率问题

传统最优控制的出发点很直接:你有一个系统,状态记为 (x),控制输入记为 (u),动力学方程 (x_{t+1} = f(x_t, u_t))。你定义一个代价函数 (c(x_t, u_t)),然后求解一个优化问题,让累积代价最小。这个框架清晰、成熟,工业界用了几十年,MPC在过程控制里的成功就是明证。

但问题也很明显。代价函数的设计高度依赖人工经验,权重调不好,控制器要么太激进要么太保守。约束处理更是麻烦,硬约束可能导致优化问题不可行,软约束又得引入松弛变量和惩罚项。更根本的是,传统框架对不确定性的处理是“外挂式”的——你先假设一个确定性模型,然后在外面套一层鲁棒或随机优化。模型和不确定性是割裂的。

Control as Inference换了一个底层假设。它不再问“哪个控制序列让代价最小”,而是问“在什么控制分布下,系统最有可能表现出最优行为”。这个“最优行为”用一个二值变量 (O_t) 来表示,(O_t = 1) 意味着在时刻 (t) 系统处于“最优”状态。然后我们定义 (P(O_t = 1 | x_t, u_t) \propto \exp(-c(x_t, u_t)))。代价越小,这个概率越大。控制的目标就变成了:找到一个控制分布 (q(u_t | x_t)),使得观测到最优性变量 (O_{1:T}) 的概率最大。

这个转换的关键在于,它把“优化”变成了“推断”。优化是找极值点,推断是求后验分布。后验分布天然携带不确定性信息,而且可以通过贝叶斯规则和变分方法来处理。约束不再需要显式写成不等式,而是通过先验分布 (p(u_t)) 来编码——比如你希望控制量平滑,就给 (p(u_t)) 一个高斯先验,方差小一点,自然就平滑了。

2.2 概率图模型与最优性变量的引入

要理解Control as Inference,得先画出它的概率图模型。这个图里有三类节点:状态 (x_t)、控制 (u_t)、最优性变量 (O_t)。边表示条件依赖关系。动力学给出 (p(x_{t+1} | x_t, u_t)),这是系统的物理模型,跟传统控制里的一样。控制先验给出 (p(u_t)),这是你对控制输入的偏好,比如平滑性、能量限制。最优性似然给出 (p(O_t | x_t, u_t)),这是把代价函数转成概率的地方。

整个联合分布可以写成:

[ p(x_{1:T}, u_{1:T}, O_{1:T}) = p(x_1) \prod_{t=1}^T p(x_{t+1} | x_t, u_t) p(u_t) p(O_t | x_t, u_t) ]

注意这里 (O_t) 是观测到的变量,我们假设在所有时刻都观测到 (O_t = 1),也就是“系统在所有时刻都表现最优”。控制推断的目标就是计算后验 (p(u_{1:T} | x_{1:T}, O_{1:T} = 1))。这个后验告诉我们:在已知系统轨迹和“全程最优”的条件下,控制输入应该是什么分布。

这个后验通常很难精确计算,因为动力学可能是非线性的,而且 (T) 可能很大。所以我们需要变分推断来近似。变分推断的核心思想是:找一个简单的分布 (q(u_{1:T} | x_{1:T})),让它尽可能接近真实后验。接近的程度用KL散度衡量。最小化KL散度等价于最大化证据下界(ELBO)。这个ELBO可以分解成两项:一项是期望对数似然,鼓励 (q) 解释观测数据;另一项是熵,鼓励 (q) 保持一定的随机性。在控制语境下,第一项对应“跟踪最优性”,第二项对应“探索”。

2.3 变分推断与KL散度的角色

KL散度在Control as Inference里扮演的是“距离度量”的角色。我们想找一个近似后验 (q),让它和真实后验 (p) 之间的KL散度最小。但真实后验往往不可解,所以转而最大化ELBO。ELBO的表达式是:

[ \mathcal{L}(q) = \mathbb{E}{q}[\log p(x{1:T}, u_{1:T}, O_{1:T})] - \mathbb{E}{q}[\log q(u{1:T} | x_{1:T})] ]

第一项是联合对数似然的期望,第二项是 (q) 的熵。把联合分布展开,第一项可以拆成动力学项、控制先验项、最优性似然项。动力学项和 (q) 无关,可以忽略。控制先验项鼓励 (q) 靠近先验,最优性似然项鼓励 (q) 靠近低代价区域。熵项则防止 (q) 塌缩成一个点,保持探索能力。

这个分解非常漂亮,因为它把控制问题里的几个核心要素——模型、约束、代价、探索——统一到了一个目标函数里。你不需要再手工设计正则化项,熵项自然提供了探索;你不需要再写硬约束,先验分布自然编码了约束。而且整个框架是概率的,不确定性可以通过 (q) 的方差来量化。

在实际操作中,我们通常假设 (q) 是一个高斯分布,均值是控制序列,方差是控制噪声。然后对ELBO做随机梯度上升,更新均值和方差。这个过程跟强化学习里的策略梯度方法很像,但推导路径完全不同。策略梯度从性能指标出发,Control as Inference从概率推断出发,两者在数学上可以互相转化,但直觉和实现细节有差异。

2.4 与MPC和随机最优控制的联系

MPC(模型预测控制)是工业界最常用的先进控制方法。它的核心思想是:在每个时刻,求解一个有限时域的优化问题,只执行第一个控制量,然后滚动到下一个时刻重新求解。MPC的优点是能显式处理约束,缺点是计算量大,而且对模型误差敏感。

Control as Inference和MPC的结合点在于:你可以把MPC的滚动优化看成是概率推断的一个近似。具体来说,如果你在ELBO里只保留最优性似然项,忽略熵项,并且假设 (q) 是一个确定性分布,那么最大化ELBO就退化成最小化代价,也就是传统MPC。但如果你保留熵项,就得到了一个“软MPC”,它对模型误差更鲁棒,而且能自然处理多模态的最优解。

随机最优控制(Stochastic Optimal Control)是另一个相关领域。经典随机最优控制用动态规划求解HJB方程,计算复杂度随状态维度指数增长。Control as Inference提供了一条替代路径:用变分推断近似后验,复杂度主要取决于 (q) 的参数化方式,而不是状态维度。这使得它更适合高维问题,比如机械臂控制、自动驾驶轨迹规划。

我自己的项目里,把MPC换成Control as Inference的变分版本之后,最明显的改善是调参时间大幅缩短。传统MPC的权重矩阵要反复试,而变分版本里,先验方差直接对应控制平滑度,物理意义清晰,调起来有方向感。另一个好处是,当系统遇到未建模的动态时,变分后验的方差会自动增大,相当于在线估计了不确定性,控制器会变得更保守,避免激进动作。

3. 实操过程与核心环节实现

3.1 环境准备与依赖安装

要复现Control as Inference的基本流程,你不需要特别复杂的工具链。Python生态里,PyTorch或JAX都能胜任,因为核心操作是自动微分和随机梯度上升。我个人的习惯是用PyTorch,因为它的动态图调试起来直观,而且跟NumPy的互操作性好。

先建一个干净的虚拟环境,然后装这几个包:

python -m venv cai_env source cai_env/bin/activate pip install torch numpy matplotlib

如果你要做机器人仿真,可以额外装MuJoCo或PyBullet。但为了把理论讲清楚,我建议先用一个简单的线性系统做实验,比如一维的双积分器模型。这个模型的状态是位置和速度,控制是加速度,动力学是线性的,代价是二次的。这个设定下,传统LQR有解析解,你可以拿变分推断的结果跟LQR对比,验证实现的正确性。

注意:不要一上来就搞高维非线性系统。Control as Inference的数学细节在简单系统上更容易看清,调试也方便。等一维双积分器跑通了,再往机械臂或车辆模型上迁移。

3.2 定义概率模型与代价函数

以一维双积分器为例。状态 (x_t = [p_t, v_t]),控制 (u_t) 是加速度。动力学:

[ p_{t+1} = p_t + v_t \Delta t + 0.5 u_t \Delta t^2 ] [ v_{t+1} = v_t + u_t \Delta t ]

写成矩阵形式 (x_{t+1} = A x_t + B u_t),其中 (A = [[1, \Delta t], [0, 1]]),(B = [[0.5 \Delta t^2], [\Delta t]])。

代价函数设计成二次型:

[ c(x_t, u_t) = (x_t - x_{goal})^T Q (x_t - x_{goal}) + u_t^T R u_t ]

(Q) 和 (R) 是权重矩阵。在Control as Inference里,代价转概率:

[ p(O_t = 1 | x_t, u_t) = \exp(-c(x_t, u_t)) ]

控制先验取高斯:

[ p(u_t) = \mathcal{N}(0, \sigma_u^2) ]

(\sigma_u^2) 控制控制量的平滑程度。(\sigma_u^2) 越小,控制越平滑,但跟踪可能变慢。

变分后验也取高斯:

[ q(u_t) = \mathcal{N}(\mu_t, \sigma_t^2) ]

(\mu_t) 和 (\sigma_t) 是待优化的参数。注意这里假设各时刻的控制独立,这是一个简化,但实践中效果不错。如果你要建模时间相关性,可以用RNN或Transformer参数化 (q),但那是进阶内容。

3.3 推导ELBO并实现梯度上升

ELBO的表达式:

[ \mathcal{L} = \mathbb{E}_{q}[\sum_t \log p(O_t | x_t, u_t) + \sum_t \log p(u_t) - \sum_t \log q(u_t)] ]

把高斯分布代入,逐项计算。第一项:

[ \log p(O_t | x_t, u_t) = -c(x_t, u_t) ]

第二项:

[ \log p(u_t) = -\frac{1}{2\sigma_u^2} u_t^2 - \frac{1}{2}\log(2\pi\sigma_u^2) ]

第三项:

[ \log q(u_t) = -\frac{1}{2\sigma_t^2} (u_t - \mu_t)^2 - \frac{1}{2}\log(2\pi\sigma_t^2) ]

期望用蒙特卡洛采样估计:从 (q) 里采 (u_t),然后算这些项的平均。PyTorch的自动微分会自动处理梯度。代码骨架:

import torch import torch.optim as optim # 参数 T = 50 dt = 0.1 A = torch.tensor([[1, dt], [0, 1]]) B = torch.tensor([[0.5*dt**2], [dt]]) Q = torch.diag(torch.tensor([1.0, 0.1])) R = torch.tensor([[0.01]]) sigma_u = 0.5 # 变分参数 mu = torch.zeros(T, 1, requires_grad=True) log_sigma = torch.zeros(T, 1, requires_grad=True) optimizer = optim.Adam([mu, log_sigma], lr=0.01) for epoch in range(2000): sigma = torch.exp(log_sigma) eps = torch.randn(T, 1) u = mu + sigma * eps # 重参数化采样 x = torch.zeros(T+1, 2) x[0] = torch.tensor([1.0, 0.0]) # 初始状态 cost = 0.0 for t in range(T): x[t+1] = A @ x[t] + B @ u[t] cost += (x[t] - torch.tensor([0.0, 0.0])) @ Q @ (x[t] - torch.tensor([0.0, 0.0])) cost += R[0,0] * u[t]**2 log_pO = -cost log_pu = -0.5 * (u**2).sum() / sigma_u**2 log_q = -0.5 * ((u - mu)**2 / sigma**2).sum() - log_sigma.sum() elbo = log_pO + log_pu - log_q loss = -elbo optimizer.zero_grad() loss.backward() optimizer.step()

这段代码里,重参数化技巧是关键。直接从 (q) 采样会阻断梯度,用 (u = \mu + \sigma \epsilon) 把随机性转移到 (\epsilon) 上,梯度就能传回 (\mu) 和 (\sigma)。

3.4 结果分析与与传统MPC的对比

跑完2000轮之后,你会得到一条控制序列的均值和方差。均值就是最优控制轨迹,方差反映了不确定性。把这条轨迹跟LQR的解对比,如果实现正确,两者应该很接近。差异主要来自熵项——变分版本会保留一点随机性,所以控制量不会完全确定。

我实测下来,一维双积分器上,变分推断跑2000轮大约需要十几秒(CPU),LQR是毫秒级。但变分版本的优势在于扩展性:换成非线性动力学,LQR就失效了,而变分推断只需要把动力学写成可微函数,其他流程不变。换成高维状态,LQR的Riccati方程求解会变慢,而变分推断的复杂度主要取决于网络参数量,跟状态维度的关系更温和。

另一个值得注意的现象是,当 (Q) 和 (R) 的比例变化时,变分版本的控制轨迹过渡更平滑。传统MPC在权重突变时可能出现控制量跳变,而变分版本因为熵项的存在,控制量的方差会平滑过渡,实际执行时更安全。

实操心得:调试时先把 (\sigma_u) 设大一点,让控制先验很宽松,这样变分后验主要受代价函数驱动。等基本跟踪效果出来了,再逐步减小 (\sigma_u),观察控制平滑度的变化。这个调节过程比调MPC的权重矩阵直观得多。

4. 常见问题与排查技巧实录

4.1 训练不收敛或发散怎么办

Control as Inference的变分训练本质上是一个随机优化问题,不收敛的原因通常有几个。第一,学习率太大。ELBO的梯度方差可能很高,尤其是当 (\sigma_t) 很小时,重参数化的梯度会爆炸。解决办法是减小学习率,或者对 (\log \sigma) 做裁剪,限制在 ([-5, 2]) 之间。

第二,代价函数尺度不合适。如果 (Q) 和 (R) 的量级差太多,ELBO里各项的梯度不平衡。建议先归一化代价函数,让各项在初始时贡献相当。具体做法是跑一遍随机控制,记录各项代价的平均值,然后按比例缩放。

第三,蒙特卡洛采样数太少。每次迭代只采一个样本,梯度噪声很大。可以采多个样本取平均,比如每次采10个,梯度方差会降一个量级。代价是计算时间增加,但收敛更稳。

第四,动力学模型不可微。如果你用的仿真器不支持自动微分,梯度传不回去。解决办法是用可微的代理模型,或者用有限差分近似梯度。前者更高效,后者更通用但慢。

4.2 控制量抖动或过冲怎么调

控制量抖动通常是因为 (\sigma_u) 设得太大,先验太宽松,变分后验过度拟合代价函数的高频成分。减小 (\sigma_u) 可以抑制抖动,但太小会导致跟踪变慢。我的经验是,先设 (\sigma_u = 0.1 \times) 控制量的典型幅值,然后根据跟踪误差微调。

过冲往往是因为代价函数里速度项的权重太低。双积分器系统里,如果 (Q) 只惩罚位置不惩罚速度,控制器会允许很大的速度,导致过冲。给速度项加权重,或者增加控制代价 (R),都能缓解。变分框架下,你还可以给控制先验加一个均值偏移,比如 (p(u_t) = \mathcal{N}(u_{ref}, \sigma_u^2)),其中 (u_{ref}) 是参考控制量,这样控制器会倾向于靠近参考值。

另一个技巧是给变分后验的均值加一个低通滤波。训练完之后,对 (\mu_t) 序列做滑动平均,再执行。这个操作不改变理论框架,但实际执行时控制量会平滑很多。我在机械臂项目里经常这么做,效果立竿见影。

4.3 高维状态下计算量爆炸的应对

状态维度上去之后,变分推断的计算量主要来自动力学展开和代价计算。如果动力学是线性的,矩阵乘法是 (O(n^2)),还能接受。如果是非线性的,每步都要过一遍神经网络,计算量就大了。

应对策略有几个。第一,用GPU加速。PyTorch和JAX都支持GPU,把动力学和代价计算放到GPU上,速度能提升一个量级。第二,用随机轨迹采样。不需要每次迭代都展开完整时域,随机采一个子窗口,比如只展开10步,然后做梯度更新。这是随机变分推断的常见做法,收敛速度稍慢但每步快很多。第三,用低秩近似。如果状态维度很高但有效维度低,可以用PCA或自编码器降维,在低维空间做推断,再映射回原空间。

避坑技巧:不要一开始就追求高维。先把低维系统跑通,理解ELBO各项的行为,再逐步增加维度。高维问题里,初始化很重要,(\mu) 初始化太差会导致ELBO曲面很崎岖,容易陷入局部最优。可以用传统MPC或LQR的解来初始化 (\mu),这样起点好,收敛快。

4.4 常见问题速查表

问题现象可能原因排查方法解决措施
训练loss震荡不降学习率过大或采样噪声大打印每步ELBO,观察方差减小学习率,增加采样数
控制量抖动控制先验方差过大检查 (\sigma_u) 与代价量级减小 (\sigma_u),加低通滤波
跟踪误差大代价权重不合理对比LQR解,检查Q/R比例调整Q/R,增加迭代轮数
梯度为NaN方差过小导致数值不稳定检查 (\log \sigma) 范围裁剪 (\log \sigma),加小量
高维收敛慢计算量大或初始化差计时每轮耗时用GPU,子窗口采样,LQR初始化
多模态最优解代价函数非凸观察控制分布是否多峰增加采样数,用混合高斯后验

这个表是我在实际项目中踩坑之后整理的,基本上覆盖了八成以上的常见问题。遇到新问题,先对照这个表排查,大部分情况能快速定位。

5. 从理论到落地的经验总结

Control as Inference这个框架,我用了大概一年半,从最初的理论推导到后来的工程落地,中间踩了不少坑,也积累了一些书本上不会写的经验。最核心的一条是:不要把它当成传统MPC的替代品,而是当成一个补充工具。传统MPC在确定性、低维、约束明确的问题上依然是最优选择,计算快、可解释性强。Control as Inference的优势在于处理不确定性、多模态、高维问题,以及跟学习型组件(比如神经网络动力学)的无缝结合。

另一个体会是,变分推断的调参虽然比MPC的权重矩阵直观,但也不是完全无脑。(\sigma_u) 和 (\log \sigma) 的初始值对收敛影响很大。我的习惯是先把 (\sigma_u) 设成控制量幅值的十分之一,(\log \sigma) 初始化为0,然后跑几百轮看ELBO曲线。如果曲线上升太慢,就增大学习率;如果震荡,就减小学习率或增加采样数。

最后分享一个小技巧:在实际部署时,不要直接用变分后验的均值作为控制量,而是用均值减去一个与方差相关的修正项。具体来说,如果 (q(u_t) = \mathcal{N}(\mu_t, \sigma_t^2)),执行时用 (u_t = \mu_t - \alpha \sigma_t),其中 (\alpha) 是一个小的正数。这个修正项相当于给控制量加了一个保守偏置,在不确定性大的时候自动减小控制幅值,避免激进动作。这个技巧在机械臂抓取和车辆避障场景里特别有用,能显著降低碰撞风险。

这个方向后续还可以往几个方向扩展。一是跟深度强化学习结合,用变分推断来做策略优化,替代传统的PPO或SAC。二是跟在线学习结合,把动力学模型也参数化,用变分推断同时推断模型参数和控制序列。三是跟分布式控制结合,把全局的变分推断分解成局部推断,降低通信和计算开销。每个方向都有不少值得挖的细节,等我把手头的项目收尾了,再逐个展开。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询