第六章 变分推断(优化版)

Chapter 6   Variational Inference (Optimized)


本章导航 / Chapter Navigator

一句话总结(TL;DR)

MAP 只找后验分布的”顶点”;GVI(高斯变分推断)用一个高斯分布去最好地”覆盖”整个后验,同时还能自动学习模型参数。

阅读路径建议:

  • 🟢 快速浏览:读完”直觉图解”即可把握核心思想
  • 🔵 理解算法:阅读”概念层”的公式和解释
  • 深度推导:展开”推导层”查看完整数学

6.1 为什么 MAP 不够用?

🎯 直觉:山谷的顶点 vs. 山谷的形状

想象你要研究一个山谷(后验分布 ):

  • MAP 的做法:找到谷底最低点。只告诉你”最可能在哪里”。
  • GVI 的做法:用一个椭球(高斯分布)去最好地覆盖整个山谷。不仅告诉你”最可能在哪里”,还告诉你”周围有多大范围都是可能的”。

MAP 的两个局限:

  1. 非线性模型下,后验不是高斯的,MAP 的拉普拉斯协方差只是粗糙近似
  2. MAP 无法自动学习噪声协方差等模型参数

GVI 解决了这两个问题。


🔑 概念层:三种方法的精度阶梯

方法用几个点近似期望从哪里取样精度
MAP / EKF1 个(仅均值处)最低
SPKF(UKF) 个 sigma 点先验
GVI 个 sigma 点后验 (迭代更新)最高

关键区别:GVI 的 sigma 点从当前估计的后验中取,而 SPKF 从先验取。后验通常比先验更窄,对非线性的近似更精确。


6.2 KL 散度:如何度量”两个分布有多接近”

🎯 直觉:单向测量

KL 散度衡量”用 近似 有多大的信息损失”。它不是对称的!

为什么选 而不是

  • :期望对 (我们自己的高斯) 取 → 可计算
  • :期望对 (真实后验) 取 → 不可计算(真实后验本来就是未知的!)

🔑 概念层:损失函数

展开 ,去掉常数项,得到我们实际要最小化的目标

其中 是联合分布的负对数似然。

两项的直觉:

  • 第一项:鼓励高斯 集中在数据似然高的区域(往真实后验靠近)
  • 第二项:防止 变成”无限窄的尖峰”(防止过度自信)
  • 两者的平衡 = 最优高斯近似

这个 等于负 ELBO(证据下界)。最小化 等价于最大化 ELBO。


📐 推导层:KL 散度展开

展开:从 KL 散度到损失函数

其中 是常数(与 无关)。去掉常数,最小化:

对于高斯 ,其熵为:

因此


6.3 GVI 优化:迭代牛顿法

🎯 直觉:比 MAP 更聪明的迭代

MAP 的牛顿迭代:每步在当前均值点处计算梯度和 Hessian。

GVI 的迭代:每步在当前后验分布的多个 sigma 点处计算期望,得到更准确的梯度和 Hessian。

两者的更新形式相同,差别只在于”用一个点”还是”用多个点”来近似期望。


🔑 概念层:GVI 更新公式

关于均值 和精度矩阵 求导,令其为零:

新的精度矩阵(= 期望 Hessian):

均值更新(解线性方程组):

与 MAP 的联系:若用 的均值处的函数值近似期望(即只用 1 个点),则 GVI 退化为 MAP 的牛顿迭代。MAP 是 GVI 的极端退化情形。


🧮 数值示例:立体相机测距

场景:立体相机观测距离 米处的目标,观测模型非线性:

真实后验均值 ≈ 24.777 m(通过 Monte Carlo 计算)

方法估计均值偏差
MAP / EKF24.569 m-33 cm(大偏差)
ISPKF24.741 m-3.8 cm
GVI24.779 m+0.3 cm(极小!)

结论:同样的数据,GVI 的偏差比 MAP 小 100 倍以上。


6.4 ESGVI:大规模问题的稀疏性

🎯 直觉:时间链的局部依赖

对于一条机器人轨迹:时刻 只和时刻 直接相关(马尔可夫性)。

这意味着:

  • 联合似然可以因子化
  • 精度矩阵 块三对角(大部分是零!)
  • 我们不需要计算完整的 协方差矩阵

🔑 概念层:精确稀疏 GVI(ESGVI)

关键性质:当似然可以因子化时,期望计算只需要局部边缘分布

其中 是局部边缘高斯——比全局 维数小得多!

ESGVI 算法流程:

初始化 μ, Σ⁻¹(可由 MAP 提供)
重复直到收敛:
  ① 对每个因子 k:
     - 从局部边缘 q_k = N(μ_k, Σ_kk) 生成 sigma 点
     - 计算局部期望(φ_k 的一阶、二阶期望)
  ② 组装全局精度矩阵 Σ⁻¹(块三对角)
  ③ LDL^T 分解 Σ⁻¹
  ④ 前后向代入求解 Σ⁻¹ δμ = -梯度
  ⑤ Takahashi 后向代入恢复所需的 Σ 子块
  ⑥ 更新 μ ← μ + δμ

计算复杂度:与 MAP 同阶 ,只有常数倍差距。


📐 推导层:Takahashi 算法提取协方差子块

展开:如何从稀疏 Σ⁻¹ 高效恢复协方差子块

分解后,可以用后向代换提取 的非零子块,无需显式求逆整个大矩阵。

从最后一个块开始:

然后向前递推():

“四角法则”保证所有需要的子块都可以被完整计算:若 ,则


6.5 Sigma 点:无导数计算期望

🎯 直觉:用有限个代表点近似积分

计算 本来需要对所有可能的 积分。Sigma 点用少数几个”代表性采样点”来近似这个积分:

sigma 点的生成,维度 ):

生成 个点(含中心点),权重


🔑 概念层:Stein 引理——避免显式计算导数

利用 Stein 引理,可以完全避免显式计算 的导数

实际意义:只需要能调用”给定 ,计算 的值”,不需要推导 的解析导数!这极大简化了实现。


6.6 参数估计:EM 算法

🎯 直觉:鸡和蛋的问题

问题:如果我们不知道模型参数(如噪声协方差 ),如何估计状态?反之,如果不知道状态,如何学习参数?

答案:EM 算法——轮流做两件事:

重复直到收敛:
  E 步:固定参数 θ,用 ESGVI 估计状态轨迹 x
  M 步:固定状态估计 q(x),更新参数 θ

两步都让目标函数(负对数似然)单调不增,保证收敛(到局部最优)。


🔑 概念层:M 步的闭合解

固定状态估计后,参数更新有解析解。以噪声协方差为例:

其中 是运动残差。

重要细节:这里的期望包含了状态估计不确定性的修正项(区别于简单地用平均残差平方)。


🧮 数值示例:同时估计轨迹和噪声协方差

场景:机器人沿直线运动,我们不知道过程噪声有多大。

初始猜测(真实值

EM 迭代 估计位置估计误差
0(初值)0.10大(过度相信运动模型)
10.42
30.87
50.98很小(接近真实值)

EM 迭代同时改善了参数估计和状态估计,两者相互促进。


6.7 方法对比:升级版精度阶梯

🔑 概念层:完整比较表

方法本质计算 sigma 点后验质量计算代价
MAPGVI 的 1 点近似1 个(均值)仅均值精确最低
EKF在线 MAP1 个偏差大
SPKF / UKF先验 sigma 点 个(先验)均值+协方差近似
ISPKF迭代先验 sigma 个(先验,迭代)更好中高
ESGVI后验 sigma 点 个(后验,迭代)最接近真实后验

核心规律:越多 sigma 点 + 越靠近真实后验取点 → 越好的近似质量。


📐 推导层:GVI 与线性高斯情形的一致性

展开:线性高斯下 GVI 一步收敛到批量 MAP

对于线性高斯系统 ,期望 Hessian 等于 Hessian 本身(因为是二次函数):

这是一个常数,与 无关!因此 GVI 在一步迭代后就精确收敛,且结果与批量 MAP(Cholesky 平滑器)完全一致。

结论:GVI 是批量线性高斯估计的严格推广。在线性情形下它们等价;在非线性情形下 GVI 更准确。


本章自检清单

阅读完本章,你应该能回答:

  • 为什么选 KL 而不是 KL?(期望的可计算性)
  • GVI 与 MAP 的本质区别是什么?(用多个 sigma 点 vs. 用 1 个点近似期望)
  • 为什么 ESGVI 可以处理大规模轨迹问题?(因子化 → 精确稀疏 → 块三对角)
  • Stein 引理的作用是什么?(将导数期望化为函数值期望,避免显式求导)
  • EM 算法的 E 步和 M 步分别做什么?(E 步=估计状态,M 步=更新参数)

如果以上都清楚,你掌握了变分推断的核心框架!


延伸阅读与代码

Python 实现(ESGVI 核心)

import numpy as np
 
def sigma_points(mu, Sigma, kappa=0):
    """生成 UKF sigma 点"""
    n = len(mu)
    L = np.linalg.cholesky((n + kappa) * Sigma)
    points = [mu]
    weights = [kappa / (n + kappa)]
    for i in range(n):
        points.append(mu + L[:, i])
        points.append(mu - L[:, i])
        weights.extend([1/(2*(n+kappa)), 1/(2*(n+kappa))])
    return np.array(points), np.array(weights)
 
 
def gvi_update_step(mu, Sigma_inv, phi_func, Sigma_blocks):
    """
    ESGVI 单次迭代
    phi_func: 接收 x,返回 phi(x) 的值(无需导数!)
    Sigma_blocks: 当前协方差子块(用于 sigma 点生成)
    """
    # 生成 sigma 点
    n = len(mu)
    Sigma = np.linalg.inv(Sigma_inv)  # 简化;实际用 Takahashi 提取子块
    pts, wts = sigma_points(mu, Sigma)
 
    # 计算期望(用 Stein 引理,只需 phi 值)
    phi_vals = np.array([phi_func(x) for x in pts])
 
    E_phi = np.sum(wts * phi_vals)
 
    # 期望梯度(Stein 引理)
    deviations = pts - mu  # (2n+1) x n
    E_grad = Sigma_inv @ np.sum(
        wts[:, None] * deviations * phi_vals[:, None], axis=0
    )
 
    # 期望 Hessian(Stein 引理)
    outer = deviations[:, :, None] * deviations[:, None, :]  # (2n+1) x n x n
    E_hess = (Sigma_inv @
              np.sum(wts[:, None, None] * outer * phi_vals[:, None, None], axis=0)
              @ Sigma_inv - Sigma_inv * E_phi)
 
    # 更新精度矩阵和均值
    Sigma_inv_new = E_hess
    delta_mu = np.linalg.solve(Sigma_inv_new, -E_grad)
    mu_new = mu + delta_mu
 
    return mu_new, Sigma_inv_new
 
 
def esgvi(mu0, Sigma_inv0, phi_func, n_iter=10):
    """完整 ESGVI 迭代"""
    mu = mu0.copy()
    Sigma_inv = Sigma_inv0.copy()
 
    for i in range(n_iter):
        Sigma_blocks = None  # 实际实现用 Takahashi 提取
        mu_new, Sigma_inv_new = gvi_update_step(mu, Sigma_inv, phi_func, Sigma_blocks)
 
        if np.linalg.norm(mu_new - mu) < 1e-6:
            break
 
        mu = mu_new
        Sigma_inv = Sigma_inv_new
 
    return mu, Sigma_inv
 
 
def em_step_covariance(residuals, state_covariances):
    """
    EM M 步:更新噪声协方差
    residuals: 每个时刻的残差期望 [K x m]
    state_covariances: 每个时刻的状态协方差 [K x n x n]
    """
    K = len(residuals)
    # Q_new = (1/K) Σ_k [e_k e_k^T + 协方差修正项]
    Q = np.mean([
        residuals[k:k+1].T @ residuals[k:k+1] + state_covariances[k]
        for k in range(K)
    ], axis=0)
    return Q

下一章转向三维几何:旋转矩阵和位姿不是普通向量,需要特殊的数学工具(Lie 群)。