10模式识别与机器学习

近似推断

复杂模型的求解

变分推断MCMC采样方法

🎯 AI 时代的学习建议:不要只读理论。理解这一章的设计动机(为什么需要这个概念?)和边界条件(什么情况下会失效?)。当 AI 生成的代码涉及这些概念时,你能判断它是否用对了场景。AI 能帮你总结内容,但无法替代你亲手阅读、思考、建立直觉的过程。

第10章:近似推断(Approximate Inference)

一、导读

10.1 本章学习目标

第10章是全书的理论高峰,系统介绍了当精确推断不可行时的近似推断方法。在复杂的概率模型(如深层图模型、大规模混合模型)中,精确推断通常是NP难的。近似推断提供了实用的替代方案,使我们能够在合理时间内获得足够精确的结果。

本章的核心学习目标包括:

  • 理解近似推断的动机:为什么精确推断不可行?在什么情况下需要近似方法?
  • 掌握变分推断:变分推断的核心思想、ELBO的推导、平均场近似。
  • 掌握MCMC采样方法:Metropolis-Hastings算法、Gibbs采样的原理和实现。
  • 理解变分推断与EM的关系:变分EM如何将EM推广到含有复杂隐变量的模型?
  • 掌握近似推断的比较:变分推断和MCMC各自的优缺点是什么?如何选择?
  • 了解现代近似推断方法:随机变分推断、黑箱变分推断等最新进展。

10.2 为什么本章如此重要

近似推断是现代机器学习的核心计算引擎。在深度学习中,变分推断是训练变分自编码器(VAE)的基础;在贝叶斯深度学习中,近似推断提供了不确定性量化的能力;在大语言模型中,采样方法是生成文本的关键技术。

变分推断和MCMC是两大近似推断范式。变分推断将推断转化为优化问题,速度快但结果有偏;MCMC通过采样逼近真实后验,理论上无偏但计算成本高。理解这两种范式,就掌握了现代概率编程和贝叶斯推断的核心工具。

10.3 核心问题引导

在阅读本章时,请带着以下问题思考:

精确推断为什么是NP难的?困难具体来自哪里?

变分推断如何将推断问题转化为优化问题?ELBO的几何直觉是什么?

平均场近似的核心假设是什么?这个假设在什么情况下会失效?

MCMC采样为什么能逼近目标分布?细致平衡条件的作用是什么?

变分推断和MCMC各有什么优缺点?在什么场景下应该选择哪种方法?

随机变分推断如何使变分方法适用于大规模数据?


二、核心概念详解

10.1 近似推断的动机

10.1.1 精确推断的困难

在概率模型中,推断任务通常涉及计算后验分布 $p(\mathbf{Z}|\mathbf{X})$ 或边缘似然 $p(\mathbf{X})$:

$$p(\mathbf{Z}|\mathbf{X}) = \frac{p(\mathbf{X}, \mathbf{Z})}{p(\mathbf{X})} = \frac{p(\mathbf{X}, \mathbf{Z})}{\int p(\mathbf{X}, \mathbf{Z}) d\mathbf{Z}}$$

困难来自分母中的积分(或求和):

  • 当 $\mathbf{Z}$ 是高维连续变量时,积分是多重积分,维度灾难
  • 当 $\mathbf{Z}$ 是离散变量时,求和需要遍历所有可能的赋值,复杂度为指数级
  • 即使图模型有简单的局部结构,全局推断也可能是NP难的

10.1.2 近似方法分类

近似推断方法分为两大类:

确定性方法(变分推断):

  • 将推断转化为优化问题
  • 用简单的分布 $q(\mathbf{Z})$ 近似后验 $p(\mathbf{Z}|\mathbf{X})$
  • 最小化 $q$ 和真实后验之间的KL散度
  • 速度快,但结果有偏

随机方法(MCMC):

  • 通过采样逼近目标分布
  • 构造马尔可夫链使其平稳分布为目标分布
  • 理论上渐近无偏,但收敛可能很慢

10.2 变分推断

10.2.1 变分下界(ELBO)

变分推断的核心是引入一个简单的分布族 $\mathcal{Q}$,用 $q(\mathbf{Z}) \in \mathcal{Q}$ 近似后验 $p(\mathbf{Z}|\mathbf{X})$。

对数边缘似然可以分解为:

$$\ln p(\mathbf{X}) = \mathcal{L}(q) + KL(q || p)$$

其中:

  • $\mathcal{L}(q) = \int q(\mathbf{Z}) \ln \frac{p(\mathbf{X}, \mathbf{Z})}{q(\mathbf{Z})} d\mathbf{Z}$ 是证据下界(ELBO)
  • $KL(q || p) = -\int q(\mathbf{Z}) \ln \frac{p(\mathbf{Z}|\mathbf{X})}{q(\mathbf{Z})} d\mathbf{Z} \geq 0$

由于 $KL \geq 0$,有 $\mathcal{L}(q) \leq \ln p(\mathbf{X})$。

变分推断的目标:最大化 $\mathcal{L}(q)$,等价于最小化 $KL(q || p)$。

10.2.2 平均场近似

平均场近似假设后验可以分解为各隐变量的独立乘积:

$$q(\mathbf{Z}) = \prod_{i=1}^{M} q_i(\mathbf{Z}_i)$$

这个假设大大简化了优化问题。通过固定其他因子 $q_j (j \neq i)$,对 $q_i$ 优化ELBO,可以得到坐标上升的更新公式:

$$\ln q_i^*(\mathbf{Z}_i) = \mathbb{E}_{j \neq i}[\ln p(\mathbf{X}, \mathbf{Z})] + \text{const}$$

其中期望是关于其他因子 $q_j (j \neq i)$ 取的。

python
import numpy as np
import matplotlib.pyplot as plt

def variational_inference_demo():
    """变分推断演示:一维高斯后验的近似"""
    np.random.seed(42)
    
    # 真实后验:混合高斯
    # p(z|x) ∝ 0.4 * N(z|-2, 0.5) + 0.6 * N(z|2, 1.0)
    
    z = np.linspace(-6, 6, 500)
    true_posterior = 0.4 * np.exp(-0.5 * ((z + 2) / 0.5)**2) / (0.5 * np.sqrt(2*np.pi)) + \
                     0.6 * np.exp(-0.5 * ((z - 2) / 1.0)**2) / (1.0 * np.sqrt(2*np.pi))
    true_posterior /= np.trapz(true_posterior, z)
    
    # 变分近似:单高斯(最小化KL(q||p))
    # 最优q是p的"模式覆盖"近似
    q_mean = np.trapz(z * true_posterior, z)
    q_var = np.trapz((z - q_mean)**2 * true_posterior, z)
    q_approx = np.exp(-0.5 * ((z - q_mean) / np.sqrt(q_var))**2) / (np.sqrt(q_var) * np.sqrt(2*np.pi))
    
    # 可视化
    fig, axes = plt.subplots(1, 2, figsize=(14, 5))
    
    axes[0].plot(z, true_posterior, 'b-', linewidth=2, label='True posterior')
    axes[0].plot(z, q_approx, 'r--', linewidth=2, label='Variational approx (mean-field)')
    axes[0].set_xlabel('z')
    axes[0].set_ylabel('p(z)')
    axes[0].set_title('Variational Inference: KL(q||p) minimization')
    axes[0].legend()
    axes[0].grid(True, alpha=0.3)
    
    # ELBO的几何直觉
    # KL(q||p) = ∫q log(q/p) dz
    kl = np.trapz(q_approx * np.log((q_approx + 1e-300) / (true_posterior + 1e-300)), z)
    elbo = -kl  # 简化版本(忽略常数项)
    
    axes[1].bar(['KL(q||p)', '-ELBO'], [kl, -elbo], color=['red', 'blue'], alpha=0.7)
    axes[1].set_ylabel('Value')
    axes[1].set_title('ELBO and KL Divergence')
    axes[1].grid(True, alpha=0.3, axis='y')
    
    plt.tight_layout()
    plt.savefig('variational_inference.png', dpi=150)
    plt.close()
    
    print(f"KL散度: {kl:.4f}")
    print(f"变分近似均值: {q_mean:.3f}")
    print(f"变分近似标准差: {np.sqrt(q_var):.3f}")

variational_inference_demo()

10.2.3 变分推断的性质

KL(q||p) vs KL(p||q):

  • $KL(q||p)$(变分推断使用):$q$ 倾向于覆盖 $p$ 的所有模式,但可能过度扩散(模式覆盖)
  • $KL(p||q)$:$q$ 倾向于拟合 $p$ 的某个模式,可能忽略其他模式(模式寻求)

平均场近似的局限:

  • 忽略了隐变量之间的相关性
  • 倾向于低估后验方差
  • 对于强相关的隐变量,近似效果差

10.3 变分推断实例

10.3.1 贝叶斯线性回归的变分推断

python
def variational_bayesian_linear_regression():
    """变分贝叶斯线性回归"""
    np.random.seed(42)
    
    # 生成数据
    N = 50
    X_vb = np.sort(np.random.uniform(-3, 3, N))
    t_vb = 2 * X_vb + 1 + np.random.randn(N) * 0.5
    
    Phi = np.column_stack([np.ones(N), X_vb])
    D = Phi.shape[1]
    
    # 变分推断
    # 后验近似: q(w) = N(w|m, S), q(alpha) = Gamma(alpha|a, b)
    
    # 初始化
    m = np.zeros(D)
    S = np.eye(D)
    a_alpha = 1.0
    b_alpha = 1.0
    
    # 先验参数
    a0 = 1.0
    b0 = 1.0
    
    elbos = []
    
    for iteration in range(100):
        # 更新 q(w)
        E_alpha = a_alpha / b_alpha
        S_inv = E_alpha * np.eye(D) + Phi.T @ Phi
        S = np.linalg.inv(S_inv)
        m = S @ Phi.T @ t_vb
        
        # 更新 q(alpha)
        a_alpha = a0 + D / 2
        b_alpha = b0 + 0.5 * (m @ m + np.trace(S))
        
        # 计算ELBO
        E_w_S_inv_w = m @ S_inv @ m
        elbo = -0.5 * N * np.log(2 * np.pi) + 0.5 * D * np.log(E_alpha) - \
               0.5 * np.sum((t_vb - Phi @ m)**2) - 0.5 * E_alpha * (m @ m + np.trace(S))
        elbos.append(elbo)
    
    # 可视化
    fig, axes = plt.subplots(1, 2, figsize=(14, 5))
    
    # 左图:回归结果
    X_plot = np.linspace(-4, 4, 100)
    Phi_plot = np.column_stack([np.ones(100), X_plot])
    
    mean_pred = Phi_plot @ m
    # 预测方差(近似)
    var_pred = np.array([Phi_plot[i] @ S @ Phi_plot[i] for i in range(100)])
    std_pred = np.sqrt(var_pred)
    
    axes[0].scatter(X_vb, t_vb, c='red', s=30, zorder=5, label='Data')
    axes[0].plot(X_plot, mean_pred, 'b-', linewidth=2, label='VB prediction')
    axes[0].fill_between(X_plot, mean_pred - 2*std_pred, mean_pred + 2*std_pred, 
                          alpha=0.2, color='blue', label='95% CI')
    axes[0].plot(X_plot, 2*X_plot + 1, 'g--', linewidth=2, label='True function')
    axes[0].set_xlabel('x')
    axes[0].set_ylabel('y')
    axes[0].set_title('Variational Bayesian Linear Regression')
    axes[0].legend()
    axes[0].grid(True, alpha=0.3)
    
    # 右图:ELBO收敛
    axes[1].plot(elbos)
    axes[1].set_xlabel('Iteration')
    axes[1].set_ylabel('ELBO')
    axes[1].set_title('ELBO Convergence')
    axes[1].grid(True, alpha=0.3)
    
    plt.tight_layout()
    plt.savefig('variational_bayesian_lr.png', dpi=150)
    plt.close()
    
    print(f"变分后验均值: w = {np.round(m, 3)}")
    print(f"真实参数: w = [1, 2]")
    print(f"最终ELBO: {elbos[-1]:.2f}")

variational_bayesian_linear_regression()

10.4 MCMC采样

10.4.1 基本思想

MCMC(Markov Chain Monte Carlo)通过构造马尔可夫链来从目标分布中采样。链的平稳分布等于目标分布 $p(\mathbf{Z}|\mathbf{X})$。

关键性质:

  • 细致平衡条件:$p(\mathbf{z}) T(\mathbf{z}'|\mathbf{z}) = p(\mathbf{z}') T(\mathbf{z}|\mathbf{z}')$
  • 遍历性:链可以到达状态空间的所有部分
  • 渐近无偏:当采样数足够大时,样本均值收敛到期望值

10.4.2 Metropolis-Hastings算法

Metropolis-Hastings(MH)是最通用的MCMC算法:

从当前状态 $\mathbf{z}^{(t)}$ 出发,从提议分布 $q(\mathbf{z}^|\mathbf{z}^{(t)})$ 采样候选 $\mathbf{z}^$

计算接受概率:

$$A = \min\left(1, \frac{p(\mathbf{z}^) q(\mathbf{z}^{(t)}|\mathbf{z}^)}{p(\mathbf{z}^{(t)}) q(\mathbf{z}^*|\mathbf{z}^{(t)})}\right)$$

以概率 $A$ 接受候选($\mathbf{z}^{(t+1)} = \mathbf{z}^*$),否则拒绝($\mathbf{z}^{(t+1)} = \mathbf{z}^{(t)}$)

python
def metropolis_hastings():
    """Metropolis-Hastings采样"""
    np.random.seed(42)
    
    # 目标分布:双峰分布
    def log_target(z):
        p1 = np.exp(-0.5 * ((z + 3) / 0.8)**2) / (0.8 * np.sqrt(2*np.pi))
        p2 = np.exp(-0.5 * ((z - 3) / 1.2)**2) / (1.2 * np.sqrt(2*np.pi))
        return np.log(0.4 * p1 + 0.6 * p2 + 1e-300)
    
    # MH采样
    n_samples = 5000
    burn_in = 1000
    samples = np.zeros(n_samples + burn_in)
    z_current = 0.0
    
    accepted = 0
    for i in range(1, n_samples + burn_in):
        # 提议分布:高斯随机游走
        z_proposal = z_current + np.random.randn() * 1.0
        
        # 接受概率
        log_alpha = log_target(z_proposal) - log_target(z_current)
        
        if np.log(np.random.random()) < log_alpha:
            z_current = z_proposal
            accepted += 1
        
        samples[i] = z_current
    
    # 去除burn-in
    samples = samples[burn_in:]
    
    # 可视化
    fig, axes = plt.subplots(1, 3, figsize=(15, 5))
    
    # 左图:采样轨迹
    axes[0].plot(samples[:200])
    axes[0].set_xlabel('Iteration')
    axes[0].set_ylabel('Sample')
    axes[0].set_title(f'MH Sampling Trace (acceptance rate: {accepted/(n_samples+burn_in):.2%})')
    axes[0].grid(True, alpha=0.3)
    
    # 中图:样本直方图
    axes[1].hist(samples, bins=50, density=True, alpha=0.7, label='MH samples')
    z_grid = np.linspace(-8, 8, 200)
    target_pdf = np.exp(np.array([log_target(z) for z in z_grid]))
    axes[1].plot(z_grid, target_pdf, 'r-', linewidth=2, label='Target distribution')
    axes[1].set_xlabel('z')
    axes[1].set_ylabel('Density')
    axes[1].set_title('Sample Distribution')
    axes[1].legend()
    axes[1].grid(True, alpha=0.3)
    
    # 右图:自相关
    max_lag = 50
    autocorr = np.array([np.corrcoef(samples[:-lag], samples[lag:])[0, 1] 
                          for lag in range(1, max_lag+1)])
    axes[2].plot(range(1, max_lag+1), autocorr, 'o-')
    axes[2].set_xlabel('Lag')
    axes[2].set_ylabel('Autocorrelation')
    axes[2].set_title('Autocorrelation')
    axes[2].grid(True, alpha=0.3)
    axes[2].axhline(y=0, color='k', linestyle='--', alpha=0.3)
    
    plt.tight_layout()
    plt.savefig('metropolis_hastings.png', dpi=150)
    plt.close()
    
    print(f"接受率: {accepted/(n_samples+burn_in):.2%}")
    print(f"样本均值: {np.mean(samples):.3f}")
    print(f"样本标准差: {np.std(samples):.3f}")

metropolis_hastings()

10.4.3 Gibbs采样

Gibbs采样是MH的特殊情况,当提议分布为条件分布时,接受率恒为1。

对于 $\mathbf{Z} = (Z_1, \ldots, Z_M)$,Gibbs采样依次从条件分布中采样:

$$Z_i^{(t+1)} \sim p(Z_i | Z_1^{(t+1)}, \ldots, Z_{i-1}^{(t+1)}, Z_{i+1}^{(t)}, \ldots, Z_M^{(t)}, \mathbf{X})$$

python
def gibbs_sampling():
    """Gibbs采样:从二元高斯分布中采样"""
    np.random.seed(42)
    
    # 目标分布:二元高斯
    mu = np.array([0, 0])
    Sigma = np.array([[1, 0.8], [0.8, 1]])
    Sigma_inv = np.linalg.inv(Sigma)
    
    # 条件分布参数
    # p(z1|z2) = N(mu1 + Sigma12/Sigma22 * (z2 - mu2), Sigma11 - Sigma12^2/Sigma22)
    cond_mean_1_given_2 = lambda z2: mu[0] + Sigma[0,1]/Sigma[1,1] * (z2 - mu[1])
    cond_var_1_given_2 = Sigma[0,0] - Sigma[0,1]**2/Sigma[1,1]
    
    cond_mean_2_given_1 = lambda z1: mu[1] + Sigma[1,0]/Sigma[0,0] * (z1 - mu[0])
    cond_var_2_given_1 = Sigma[1,1] - Sigma[1,0]**2/Sigma[0,0]
    
    # Gibbs采样
    n_samples = 3000
    burn_in = 500
    samples = np.zeros((n_samples + burn_in, 2))
    z = np.array([0.0, 0.0])
    
    for i in range(n_samples + burn_in):
        # 更新 z1
        z[0] = np.random.normal(cond_mean_1_given_2(z[1]), np.sqrt(cond_var_1_given_2))
        # 更新 z2
        z[1] = np.random.normal(cond_mean_2_given_1(z[0]), np.sqrt(cond_var_2_given_1))
        samples[i] = z.copy()
    
    samples = samples[burn_in:]
    
    # 可视化
    fig, axes = plt.subplots(1, 2, figsize=(14, 5))
    
    # 左图:采样点
    axes[0].scatter(samples[:, 0], samples[:, 1], alpha=0.1, s=5, label='Gibbs samples')
    
    # 绘制真实等密度椭圆
    eigenvalues, eigenvectors = np.linalg.eigh(Sigma)
    angle = np.degrees(np.arctan2(eigenvectors[1, 0], eigenvectors[0, 0]))
    for n_std in [1, 2, 3]:
        width = 2 * n_std * np.sqrt(eigenvalues[0])
        height = 2 * n_std * np.sqrt(eigenvalues[1])
        ellipse = plt.Ellipse(mu, width, height, angle=angle, fill=False, 
                               linewidth=2-n_std*0.5, color='red')
        axes[0].add_patch(ellipse)
    
    axes[0].set_xlabel('z1')
    axes[0].set_ylabel('z2')
    axes[0].set_title('Gibbs Sampling from Bivariate Gaussian')
    axes[0].set_xlim(-4, 4)
    axes[0].set_ylim(-4, 4)
    axes[0].set_aspect('equal')
    axes[0].grid(True, alpha=0.3)
    
    # 右图:边缘分布
    axes[1].hist(samples[:, 0], bins=40, density=True, alpha=0.5, label='z1 samples')
    z1_grid = np.linspace(-4, 4, 100)
    axes[1].plot(z1_grid, np.exp(-0.5 * z1_grid**2) / np.sqrt(2*np.pi), 'r-', 
                  linewidth=2, label='N(0,1)')
    axes[1].set_xlabel('z1')
    axes[1].set_ylabel('Density')
    axes[1].set_title('Marginal Distribution of z1')
    axes[1].legend()
    axes[1].grid(True, alpha=0.3)
    
    plt.tight_layout()
    plt.savefig('gibbs_sampling.png', dpi=150)
    plt.close()
    
    print(f"Gibbs采样统计:")
    print(f"  z1: 均值={np.mean(samples[:, 0]):.3f}, 标准差={np.std(samples[:, 0]):.3f}")
    print(f"  z2: 均值={np.mean(samples[:, 1]):.3f}, 标准差={np.std(samples[:, 1]):.3f}")
    print(f"  相关系数: {np.corrcoef(samples.T)[0, 1]:.3f}")

gibbs_sampling()

10.5 变分推断 vs MCMC

10.5.1 比较

特性变分推断MCMC
方法类型优化采样
速度快慢
结果有偏(近似)渐近无偏
收敛保证收敛到局部最优收敛到真实后验
可扩展性好(随机变分推断)差(需要大量采样)
适用场景大规模数据、实时应用精确推断、小模型

10.5.2 选择策略

  • 选择变分推断:数据量大、需要快速推断、可以接受近似结果
  • 选择MCMC:数据量小、需要精确推断、计算资源充足
  • 混合使用:用变分推断初始化MCMC,加速收敛
python
def compare_vi_mcmc():
    """比较变分推断和MCMC"""
    np.random.seed(42)
    
    # 目标分布:相关二元高斯
    mu = np.array([0, 0])
    Sigma = np.array([[1, 0.9], [0.9, 1]])
    
    # 变分推断(平均场)
    # 平均场假设 q(z1, z2) = q(z1)q(z2)
    # 最优 q(z1) = N(z1 | mu1, Sigma11)
    vi_mean = mu.copy()
    vi_var = np.diag(Sigma)  # 忽略相关性
    
    # MCMC
    n_samples = 5000
    burn_in = 1000
    samples = np.zeros((n_samples + burn_in, 2))
    z = np.array([0.0, 0.0])
    
    cond_var = 1 - 0.9**2
    
    for i in range(n_samples + burn_in):
        z[0] = np.random.normal(0.9 * z[1], np.sqrt(cond_var))
        z[1] = np.random.normal(0.9 * z[0], np.sqrt(cond_var))
        samples[i] = z.copy()
    samples = samples[burn_in:]
    
    # 可视化
    fig, axes = plt.subplots(1, 2, figsize=(14, 5))
    
    # 变分推断
    z1 = np.linspace(-4, 4, 100)
    z2 = np.linspace(-4, 4, 100)
    Z1, Z2 = np.meshgrid(z1, z2)
    
    # 真实分布
    diff = np.stack([Z1 - mu[0], Z2 - mu[1]], axis=-1)
    true_log_pdf = -0.5 * np.einsum('...i,...ij,...j->...', diff, np.linalg.inv(Sigma), diff)
    true_pdf = np.exp(true_log_pdf)
    true_pdf /= true_pdf.sum()
    
    # 变分近似
    vi_pdf = np.exp(-0.5 * (Z1 - vi_mean[0])**2 / vi_var[0]) / np.sqrt(2*np.pi*vi_var[0]) * \
             np.exp(-0.5 * (Z2 - vi_mean[1])**2 / vi_var[1]) / np.sqrt(2*np.pi*vi_var[1])
    
    axes[0].contourf(Z1, Z2, true_pdf, levels=15, cmap='Blues', alpha=0.7)
    axes[0].contour(Z1, Z2, vi_pdf, levels=10, colors='red', linestyles='--')
    axes[0].set_title('VI (red dashed) vs True (blue contours)')
    axes[0].set_xlabel('z1')
    axes[0].set_ylabel('z2')
    axes[0].set_xlim(-4, 4)
    axes[0].set_ylim(-4, 4)
    axes[0].grid(True, alpha=0.3)
    
    # MCMC
    axes[1].scatter(samples[:, 0], samples[:, 1], alpha=0.05, s=3, c='green')
    axes[1].contour(Z1, Z2, true_pdf, levels=10, colors='blue')
    axes[1].set_title('MCMC Samples vs True Contours')
    axes[1].set_xlabel('z1')
    axes[1].set_ylabel('z2')
    axes[1].set_xlim(-4, 4)
    axes[1].set_ylim(-4, 4)
    axes[1].grid(True, alpha=0.3)
    
    plt.tight_layout()
    plt.savefig('vi_vs_mcmc.png', dpi=150)
    plt.close()
    
    # 比较
    print("变分推断 vs MCMC:")
    print(f"  VI方差(z1): {vi_var[0]:.3f} (真实: {Sigma[0,0]:.3f})")
    print(f"  VI方差(z2): {vi_var[1]:.3f} (真实: {Sigma[1,1]:.3f})")
    print(f"  VI忽略了相关性: 真实 ρ={Sigma[0,1]:.3f}")
    print(f"  MCMC估计 ρ={np.corrcoef(samples.T)[0,1]:.3f}")

compare_vi_mcmc()

三、实战案例

10.1 变分GMM

python
def variational_gmm():
    """变分高斯混合模型"""
    np.random.seed(42)
    
    # 生成数据
    N = 300
    X_vgmm = np.vstack([
        np.random.multivariate_normal([0, 0], [[0.5, 0], [0, 0.5]], N//3),
        np.random.multivariate_normal([3, 3], [[0.3, 0.1], [0.1, 0.3]], N//3),
        np.random.multivariate_normal([0, 4], [[0.4, -0.1], [-0.1, 0.4]], N//3)
    ])
    
    K = 5  # 使用比真实组件数更多的组件
    
    # 简化的变分GMM(使用sklearn的BayesianGaussianMixture)
    from sklearn.mixture import BayesianGaussianMixture
    
    bgmm = BayesianGaussianMixture(n_components=K, weight_concentration_prior_type='dirichlet_process',
                                     weight_concentration_prior=0.01, max_iter=200, random_state=42)
    bgmm.fit(X_vgmm)
    
    # 有效的组件数
    effective_components = np.sum(bgmm.weights_ > 0.01)
    
    print(f"设定的组件数: {K}")
    print(f"有效的组件数: {effective_components}")
    print(f"组件权重: {np.round(bgmm.weights_, 3)}")
    
    # 可视化
    fig, ax = plt.subplots(figsize=(8, 6))
    labels = bgmm.predict(X_vgmm)
    ax.scatter(X_vgmm[:, 0], X_vgmm[:, 1], c=labels, cmap='viridis', alpha=0.5, s=10)
    ax.scatter(bgmm.means_[:, 0], bgmm.means_[:, 1], c='red', s=200, marker='*', 
               edgecolors='black', zorder=5)
    ax.set_title(f'Variational GMM (effective K={effective_components})')
    ax.set_xlabel('x1')
    ax.set_ylabel('x2')
    ax.grid(True, alpha=0.3)
    plt.tight_layout()
    plt.savefig('variational_gmm.png', dpi=150)
    plt.close()

variational_gmm()

10.2 MCMC用于贝叶斯推断

python
def mcmc_bayesian_inference():
    """MCMC用于贝叶斯参数推断"""
    np.random.seed(42)
    
    # 数据:泊松分布的观测
    N = 50
    true_lambda = 3.0
    data = np.random.poisson(true_lambda, N)
    
    # 贝叶斯推断:lambda的后验
    # 先验: lambda ~ Gamma(1, 1)
    # 后验: lambda | data ~ Gamma(1 + sum(data), 1 + N)
    
    # MCMC采样
    n_samples = 5000
    burn_in = 1000
    samples = np.zeros(n_samples + burn_in)
    lambda_current = 2.0
    
    for i in range(1, n_samples + burn_in):
        # 提议分布:对数正态随机游走
        lambda_proposal = lambda_current * np.exp(np.random.randn() * 0.2)
        
        # 对数后验
        def log_posterior(lam):
            if lam <= 0:
                return -np.inf
            # 先验: Gamma(1, 1)
            log_prior = 0  # Gamma(1,1) = Exp(1)
            # 似然: Poisson
            log_lik = np.sum(data * np.log(lam) - lam - np.log(np.arange(1, max(data)+2).cumprod()))
            return log_prior + log_lik
        
        log_alpha = log_posterior(lambda_proposal) - log_posterior(lambda_current)
        log_alpha += np.log(lambda_proposal) - np.log(lambda_current)  # Jacobian
        
        if np.log(np.random.random()) < log_alpha:
            lambda_current = lambda_proposal
        
        samples[i] = lambda_current
    
    samples = samples[burn_in:]
    
    # 解析后验
    alpha_post = 1 + np.sum(data)
    beta_post = 1 + N
    
    # 可视化
    fig, axes = plt.subplots(1, 2, figsize=(14, 5))
    
    # 左图:MCMC采样
    axes[0].hist(samples, bins=50, density=True, alpha=0.7, label='MCMC samples')
    lam_grid = np.linspace(0, 8, 200)
    analytical_pdf = lam_grid**(alpha_post-1) * np.exp(-beta_post*lam_grid) * beta_post**alpha_post / np.math.factorial(int(alpha_post)-1) if alpha_post == int(alpha_post) else lam_grid**(alpha_post-1) * np.exp(-beta_post*lam_grid)
    from scipy.stats import gamma as gamma_dist
    analytical_pdf = gamma_dist.pdf(lam_grid, alpha_post, scale=1/beta_post)
    axes[0].plot(lam_grid, analytical_pdf, 'r-', linewidth=2, label='Analytical posterior')
    axes[0].axvline(x=true_lambda, color='g', linestyle='--', label=f'True λ={true_lambda}')
    axes[0].set_xlabel('λ')
    axes[0].set_ylabel('Density')
    axes[0].set_title('Bayesian Inference for Poisson Rate')
    axes[0].legend()
    axes[0].grid(True, alpha=0.3)
    
    # 右图:收敛诊断
    axes[1].plot(np.cumsum(samples) / np.arange(1, len(samples)+1))
    axes[1].axhline(y=true_lambda, color='g', linestyle='--', label='True λ')
    axes[1].axhline(y=np.mean(samples), color='r', linestyle='-', label='MCMC mean')
    axes[1].set_xlabel('Number of samples')
    axes[1].set_ylabel('Running mean')
    axes[1].set_title('Convergence Diagnostic')
    axes[1].legend()
    axes[1].grid(True, alpha=0.3)
    
    plt.tight_layout()
    plt.savefig('mcmc_bayesian.png', dpi=150)
    plt.close()
    
    print(f"真实 λ: {true_lambda}")
    print(f"MCMC估计: {np.mean(samples):.3f} ± {np.std(samples):.3f}")
    print(f"解析后验均值: {alpha_post/beta_post:.3f}")

mcmc_bayesian_inference()

10.3 模型比较

python
def model_comparison_waic():
    """使用WAIC进行模型比较"""
    np.random.seed(42)
    
    # 生成数据
    N = 100
    X_mc = np.random.randn(N, 3)
    true_w = np.array([1, -0.5, 0])
    t_mc = X_mc @ true_w + np.random.randn(N) * 0.5
    
    # 模型1:完整模型(3个特征)
    # 模型2:简化模型(2个特征)
    
    from sklearn.linear_model import BayesianRidge
    
    # 完整模型
    br_full = BayesianRidge()
    br_full.fit(X_mc, t_mc)
    
    # 简化模型
    br_simple = BayesianRidge()
    br_simple.fit(X_mc[:, :2], t_mc)
    
    # 计算对数似然
    from scipy.stats import norm
    
    def compute_waic(model, X, t):
        pred_mean, pred_std = model.predict(X, return_std=True)
        log_lik = norm.logpdf(t, loc=pred_mean, scale=pred_std)
        
        # WAIC = -2 * (lppd - p_waic)
        lppd = np.sum(np.log(np.mean(np.exp(log_lik))))  # 简化
        p_waic = np.sum(np.var(log_lik, axis=0))
        
        return -2 * (np.sum(log_lik) - p_waic)
    
    waic_full = compute_waic(br_full, X_mc, t_mc)
    waic_simple = compute_waic(br_simple, X_mc[:, :2], t_mc)
    
    print(f"完整模型 WAIC: {waic_full:.2f}")
    print(f"简化模型 WAIC: {waic_simple:.2f}")
    print(f"ΔWAIC: {abs(waic_full - waic_simple):.2f}")
    
    if waic_full < waic_simple:
        print("→ 完整模型更优")
    else:
        print("→ 简化模型更优(奥卡姆剃刀)")

model_comparison_waic()

四、本章小结

第10章系统介绍了近似推断的两大范式:

变分推断:将推断转化为优化问题,通过最大化ELBO来寻找最优的近似分布。平均场近似假设后验因子分解,简化了优化但忽略了相关性。变分推断速度快但结果有偏。

MCMC采样:通过构造马尔可夫链从目标分布中采样。Metropolis-Hastings是最通用的算法,Gibbs采样在条件分布已知时更高效。MCMC渐近无偏但收敛可能很慢。

方法选择:变分推断适合大规模数据和实时应用,MCMC适合需要精确推断的小模型。两者可以混合使用。

现代发展:随机变分推断使变分方法适用于大规模数据;黑箱变分推断(BBVI)减少了模型特定的推导;哈密顿蒙特卡洛(HMC)提高了MCMC的采样效率。

近似推断是现代概率机器学习的计算引擎。从变分自编码器到贝叶斯深度学习,从概率编程到因果推断,近似推断的思想无处不在。掌握变分推断和MCMC,就掌握了处理复杂概率模型的核心能力。


五、思考与练习

思考题

变分推断中为什么使用 $KL(q||p)$ 而不是 $KL(p||q)$?两者的行为有什么不同?

平均场近似在什么情况下会严重失真?请举一个具体的例子。

MCMC的burn-in阶段为什么需要丢弃?如何判断链是否已经收敛?

随机变分推断如何使变分方法适用于大规模数据?它与批量变分推断有什么区别?

在什么场景下你会选择变分推断?什么场景下选择MCMC?

编程练习

变分逻辑回归:实现贝叶斯逻辑回归的变分推断,使用平均场近似。比较变分推断与MCMC的结果。

Gibbs采样用于GMM:实现GMM的Gibbs采样,与EM算法比较聚类结果。

HMC采样:实现哈密顿蒙特卡洛(HMC)算法,与标准MH比较采样效率。


10/10近似推断
0%