第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)$ 取的。
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 贝叶斯线性回归的变分推断
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)}$)
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})$$
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,加速收敛
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
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用于贝叶斯推断
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 模型比较
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比较采样效率。