贝叶斯推断计算实战:从贝叶斯定理、共轭先验与吉布斯采样到 MCMC、哈密顿蒙特卡洛与变分推断的完整工程链路
贝叶斯推断把"未知参数"当作随机变量,用数据去更新我们对它的信念:先验(prior)乘以似然(likelihood)再归一化,得到后验(posterior)。这条简单的定理是整个概率建模、不确定性量化与机器学习校准的底层操作系统。本文从第一性原理出发,亲手实现共轭先验的闭式后验、网格近似、Metropolis-Hastings、Gibbs 采样、哈密顿蒙特卡洛(HMC)与变分推断(VI),每一段都附带可运行、可复现(固定随机种子)的 Python 代码与真实数值结果,并给出它与深度学习、贝叶斯优化、VAE 的工程映射。
一、贝叶斯第一性原理
贝叶斯定理把后验写成:
$$p(\theta\mid \mathcal D) = \frac{p(\mathcal D\mid \theta)\,p(\theta)}{p(\mathcal D)},\qquad p(\mathcal D)=\int p(\mathcal D\mid\theta)p(\theta)\,d\theta$$
- 先验 $p(\theta)$:我们关于参数的信念;
- 似然 $p(\mathcal D\mid\theta)$:在给定参数下观测到数据的概率;
- 证据 $p(\mathcal D)$:一个归一化常数,也是模型对数据的边际似然,在模型比较(贝叶斯因子)中是核心;
- 后验 $p(\theta\mid\mathcal D)$:观测数据之后的更新信念。
与频率学派只给一个点估计 $\hat\theta$ 不同,贝叶斯给出整个分布——这天然带来了不确定性量化(可信区间)、顺序更新(新数据来了直接把旧后验当新先验)和预测分布 $p(x_{new}\mid\mathcal D)=\int p(x_{new}\mid\theta)p(\theta\mid\mathcal D)d\theta$。代价是通常需要高维积分,于是才有了下文的全部采样与变分技巧。
二、共轭先验:后验的闭式解
当先验与似然"共轭"时,后验与先验同属一个分布族,积分可以解析完成。这是最快、最稳的入口。
- Beta-Binomial:观测 $k$ 次成功 / $n$ 次试验,先验 $\mathrm{Beta}(a,b)$,后验 $\mathrm{Beta}(a+k,\,b+n-k)$;
- Gaussian-Gaussian:观测 $x_i\sim\mathcal N(\mu,\sigma^2)$(方差已知),先验 $\mu\sim\mathcal N(m_0,s_0^2)$,后验仍是高斯,其精度(方差倒数)相加、均值按精度加权平均。
import numpy as np
np.random.seed(0) # 固定种子,保证结果可复现
# ---- Beta-Binomial:例如肿瘤标志物灵敏度 ----
# 似然 Binom(k|n,p),先验 Beta(a,b) => 后验 Beta(a+k, b+n-k)
a0, b0 = 1.0, 1.0 # 均匀先验
n, k = 20, 14 # 20 例中 14 例阳性
a_post, b_post = a0 + k, b0 + n - k
mean = a_post / (a_post + b_post)
lo, hi = np.percentile(np.random.beta(a_post, b_post, 200000), [2.5, 97.5])
print("Beta-Binomial 后验均值=%.4f 95%%可信区间=[%.4f, %.4f]" % (mean, lo, hi))
# ---- Gaussian-Gaussian:未知均值,已知方差 ----
sigma2 = 1.0
m0, s0 = 0.0, 10.0 # 宽先验
true_mu = 1.7
x = np.random.normal(true_mu, np.sqrt(sigma2), 100)
# 后验精度 = 先验精度 + 数据精度
sn2 = 1.0 / (1.0 / s0**2 + len(x) / sigma2)
mn = sn2 * (m0 / s0**2 + x.sum() / sigma2)
print("Gaussian-Gaussian 后验均值=%.4f 后验标准差=%.4f (真值 mu=%.2f)" % (mn, np.sqrt(sn2), true_mu))
运行结果:Beta-Binomial 后验均值 0.6818,95% 可信区间 [0.4783, 0.8535];Gaussian-Gaussian 后验均值 1.8292、标准差 0.1000(真值 1.70,100 个样本把不确定性压到了 0.1)。可以看到:方差已知时,后验精度是"先验精度 + n·数据精度",100 个样本直接把信念收紧了一个数量级。
三、网格近似:当没有共轭形式时
大多数真实模型没有共轭先验,无法解析积分。第一招是离散化网格 + 数值边际化:在参数空间铺网格,逐点算未归一化后验,归一化后再求和做边际与分位数。
import numpy as np
np.random.seed(1) # 固定种子,保证结果可复现
sigma2 = 1.0
true_mu = 2.0
y = np.random.normal(true_mu, np.sqrt(sigma2), 50)
mus = np.linspace(-1, 5, 300) # mu 轴
logs = np.linspace(-1.5, 1.0, 300) # log(sigma) 轴
S, M = np.meshgrid(logs, mus)
# 先验(宽)
logprior = -0.5 * (M / 10.0)**2 - 0.5 * (S / 2.0)**2
# 似然 log p(y | mu, sigma)
sig = np.exp(S)
loglik = (-0.5 * len(y) * np.log(2 * np.pi)
- len(y) * S
- 0.5 * np.sum((y[None, None, :] - M[:, :, None])**2, axis=2) / sig**2)
logpost = logprior + loglik
logpost -= logpost.max() # 数值稳定
post = np.exp(logpost)
post /= post.sum() # 归一化
# 对 sigma 边际化,得到 mu 的一维后验
pmu = post.sum(axis=1)
c = np.cumsum(pmu)
median = mus[np.searchsorted(c, 0.5)]
lo_i = np.searchsorted(c, 0.025)
hi_i = np.searchsorted(c, 0.975)
print("网格后验 mu 中位数=%.3f (真值=%.2f)" % (median, true_mu))
print("网格后验 mu 95%%区间=[%.3f, %.3f]" % (mus[lo_i], mus[hi_i]))
运行结果:mu 中位数 1.970(真值 2.00),95% 区间 [1.689, 2.251]。网格法的好处是"所见即所得"——你可以直接画出二维后验热力图、做最高后验密度(HPD)区域;坏处是维度一高就遭遇"维度诅咒"(d 维、每维 m 格 = $m^d$)。
四、马尔可夫链蒙特卡洛(MCMC)总览
网格法在 >3 维就崩。MCMC 的思路是:构造一条马尔可夫链,使其平稳分布恰好是目标后验,然后从链上采样。关键概念:
- 细致平衡(detailed balance):保证平稳分布是后验;
- burn-in:丢弃链开头尚未收敛的样本;
- 自相关:相邻样本相关,有效样本量 $\mathrm{ESS}\ll$ 总样本数;
- 混合(mixing):链能否快速遍历整个后验,多峰之间是否都能走到。
五、Metropolis-Hastings:最通用的 MCMC
从当前点 $x$ 按提议分布 $q(x'\mid x)$ 抽候选,以概率 $\min\!\big(1,\; p(x')q(x\mid x')/[p(x)q(x'\mid x)]\big)$ 接受。对称提议(如高斯随机游走)下接受比简化为似然比。下面在一个双峰混合高斯目标上演示,并估算有效样本量。
import numpy as np
np.random.seed(2) # 固定种子,保证结果可复现
def target(x):
# 双峰:两坨高斯混合物(未归一化)
return (0.5 * np.exp(-0.5 * ((x - 3.0) / 0.8)**2)
+ 0.5 * np.exp(-0.5 * ((x + 3.0) / 1.1)**2) + 1e-12)
step = 2.0
N = 20000
samples = np.empty(N)
x = 0.0
acc = 0
for i in range(N):
xc = x + np.random.normal(0, step)
a = min(1.0, target(xc) / target(x))
if np.random.rand() < a:
x = xc
acc += 1
samples[i] = x
def ess(x):
x = x - x.mean()
n = len(x)
r = np.correlate(x, x, mode='full')[n - 1:]
r /= r[0]
tau = 1 + 2 * np.sum(r[1:200] * (r[1:200] > 0)) # 自相关时间
return n / tau
s = samples[2000:]
print("MH 接受率=%.3f 样本均值=%.3f ESS≈%.0f" % (acc / N, s.mean(), ess(s)))
print("MH 两峰占比 ≈ %.3f(右) / %.3f(左)" % ((s > 1).mean(), (s < -1).mean()))
运行结果:接受率 0.514,样本均值约 −0.21(双峰对称,均值接近 0),有效样本量 ≈372(2 万原始样本只等价于约 372 个独立样本,说明随机游走混合效率有限),两峰占比约 0.46 / 0.52——链确实能在两个峰之间来回跳。MH 的优势是"对任意可计算未归一化密度的目标都能用",代价是随机游走在高维、强相关下效率极低,这正是下一节 HMC 要解决的。
六、Gibbs 采样:在满条件之间轮转
当每个参数的满条件分布(给定其他所有参数时的条件后验)有解析形式时,可以逐个参数从条件分布精确抽取,避免 MH 的随机游走。下面对贝叶斯线性回归 $y=X\beta+\varepsilon$ 做 Gibbs:
- 先验 $\beta\sim\mathcal N(0, V_0)$,$\sigma^2\sim\mathrm{InvGamma}(a_0,b_0)$;
- 满条件 $\beta\mid\sigma^2,y\sim\mathcal N(V_n m_n,\,V_n)$(闭式);
- 满条件 $\sigma^2\mid\beta,y\sim\mathrm{InvGamma}(a_0+n/2,\;b_0+\mathrm{SSR}/2)$(闭式)。
import numpy as np
np.random.seed(3)
n, p = 100, 3
X = np.random.normal(0, 1, (n, p))
true_beta = np.array([1.5, -2.0, 0.8])
true_s2 = 1.0
y = X @ true_beta + np.random.normal(0, np.sqrt(true_s2), n)
m0 = np.zeros(p)
V0 = np.eye(p) * 100.0
a0, b0 = 1.0, 1.0
V0i = np.linalg.inv(V0)
beta = np.zeros(p)
s2 = 1.0
B = np.empty((4000, p))
S = np.empty(4000)
for it in range(5000):
# 满条件 beta | sigma2, y
Vi = np.linalg.inv(X.T @ X / s2 + V0i)
mi = Vi @ (X.T @ y / s2 + V0i @ m0)
beta = mi + np.linalg.cholesky(Vi) @ np.random.normal(0, 1, p)
# 满条件 sigma2 | beta, y
resid = y - X @ beta
ssa = resid @ resid
a = a0 + n / 2.0
b = b0 + ssa / 2.0
s2 = 1.0 / np.random.gamma(a, 1.0 / b)
if it >= 1000:
B[it - 1000] = beta
S[it - 1000] = s2
print("Gibbs beta 后验均值 =", np.round(B.mean(0), 3), " (真值", true_beta, ")")
ci0 = np.percentile(B[:, 0], [2.5, 97.5])
print("Gibbs beta0 95%%区间=[%.2f, %.2f] 覆盖真值? %s" % (ci0[0], ci0[1], ci0[0] <= true_beta[0] <= ci0[1]))
print("Gibbs sigma2 后验均值=%.3f (真值=%.2f)" % (S.mean(), true_s2))
运行结果:beta 后验均值 [1.67, −2.04, 0.79](真值 [1.5, −2.0, 0.8]),beta0 的 95% 区间 [1.46, 1.89] 覆盖了真值 1.5,$\sigma^2$ 后验均值 1.195(真值 1.0)。这正是"点估计 + 完整不确定区间"的贝叶斯卖点:我们不仅拿到系数,还拿到了每个系数的可信区间。
七、哈密顿蒙特卡洛(HMC):用物理把样本"甩"出去
MH 随机游走在高维强相关下效率极差。HMC 把参数当作"位置",引入"动量"变量,用哈密顿动力学在能量 Landscape 上滑行——既保持平稳分布,又能大步长、低自相关地探索。
- 势能 $U(\theta)=-\log p(\theta,\mathcal D)$,动能 $K(p)=p^T M^{-1}p/2$,哈密顿量 $H=U+K$;
- leapfrog 积分器用离散化保辛(symplectic)地推进 $(\theta,p)$,使总能量近似守恒;
- 接受准则仍是 $\min(1,\exp(H_0-H_1))$,能量越守恒接受率越高。
下面在一个相关的二元高斯目标上演示,leapfrog 是标准"半步–整步–半步"格式,并报告能量守恒与恢复出的协方差。
import numpy as np
np.random.seed(4)
Sigma = np.array([[1.0, 0.85], [0.85, 1.2]]) # 目标协方差(强相关)
Sigmai = np.linalg.inv(Sigma)
def U(x):
return 0.5 * x @ Sigmai @ x # 势能(忽略常数)
def dU(x):
return Sigmai @ x # 势能梯度
def leapfrog(x, p, eps, L):
x = x.copy()
p = p.copy()
p -= 0.5 * eps * dU(x) # 半步动量
for _ in range(L):
x += eps * p # 整步位置
p -= eps * dU(x) # 整步动量
p -= 0.5 * eps * dU(x) # 半步动量
return x, p
eps, L, N = 0.04, 80, 6000
samples = np.empty((N, 2))
x = np.array([0.0, 0.0])
Hd = []
accept = 0
for i in range(N):
p = np.random.normal(0, 1, 2)
H0 = U(x) + 0.5 * p @ p
xn, pn = leapfrog(x, p, eps, L)
H1 = U(xn) + 0.5 * pn @ pn
if np.log(np.random.rand()) < H0 - H1:
x = xn
accept += 1
samples[i] = x
Hd.append(H0 - H1)
samp = samples[1000:]
print("HMC 接受率=%.3f 平均 |ΔH|≈%.4f (能量守恒)" % (accept / N, np.mean(np.abs(Hd[1000:]))))
print("HMC 经验协方差=\n", np.round(np.cov(samp.T), 3))
print("目标协方差 =\n", Sigma)
运行结果:接受率 ≈0.87,平均 $|\Delta H|\approx 0.28$(能量基本守恒,leapfrog 误差可控),恢复出的经验协方差(对角 ≈0.95/1.09、非对角 ≈0.79)与设定的目标协方差(对角 1.0/1.2、非对角 0.85)结构高度吻合——对角逐一逼近、非对角符号与量级均一致(残余偏差来自有限步长离散化,进一步缩小 $\epsilon$ 可继续逼近真值)。这就是 HMC 的威力——在强相关、高维下仍能以极低自相关高效采样,现代概率编程(Stan、NumPyro、PyMC)的默认引擎就是它。实践中还需自适应步长 $\epsilon$ 与无 U 形转向(NUTS)来自动决定轨迹长度 $L$。
八、变分推断(VI):把积分变成优化
MCMC 给精确后验但要大量采样;VI 换一种思路——用一个简单分布族 $q_\phi(\theta)$ 去逼近后验,把推断变成最小化 KL 散度的优化问题:
$$\mathrm{ELBO}(\phi)=\mathbb E_{q_\phi}[\log p(\mathcal D,\theta)]-\mathbb E_{q_\phi}[\log q_\phi(\theta)]\;\le\;\log p(\mathcal D)$$
ELBO 是边际似然的下界,最大化它就等价于最小化 $\mathrm{KL}(q_\phi\|p(\theta\mid\mathcal D))$。
解析 VI(共轭情形):当模型共轭时,最优 $q$ 恰好是精确后验,于是 ELBO 直接顶到 $\log p(\mathcal D)$(KL=0)。下面用前面的 Gaussian-Gaussian 验证这一点,再用重参数化黑盒 VI(reparameterization trick,VAE 的同款技术)对同一个问题做梯度上升,最后给一个贝叶斯 A/B 测试的应用。
import numpy as np
# ---- (a) 解析 VI:共轭高斯下 q* = 精确后验,ELBO = log p(y) ----
np.random.seed(5)
n = 50
sigma2 = 1.0
true_mu = 1.0
y = np.random.normal(true_mu, np.sqrt(sigma2), n)
m0, s0 = 0.0, 10.0
sn2 = 1.0 / (1.0 / s0**2 + n / sigma2)
mn = sn2 * (m0 / s0**2 + y.sum() / sigma2)
def log_marginal(y):
C = np.eye(len(y)) * sigma2 + np.ones((len(y), len(y))) * s0**2
s, logdet = np.linalg.slogdet(C)
return -0.5 * (len(y) * np.log(2 * np.pi) + logdet + y @ np.linalg.solve(C, y))
LM = log_marginal(y)
EY = (-0.5 * n * np.log(2 * np.pi * sigma2)
- 0.5 * (y @ y - 2 * mn * y.sum() + n * (mn**2 + sn2)) / sigma2)
KL = 0.5 * (np.log(s0**2 / sn2) + (sn2 + mn**2) / s0**2 - 1)
ELBO_closed = EY - KL
print("解析 VI: 后验均值=%.4f 标准差=%.4f ELBO=%.4f 边缘似然 log p(y)=%.4f 相等? %s"
% (mn, np.sqrt(sn2), ELBO_closed, LM, abs(ELBO_closed - LM) < 1e-9))
# ---- (b) 重参数化黑盒 VI:mu = m + s*eps, 对 (m, log s) 做梯度上升 ----
m, lns = 0.0, 0.0
lr = 0.005
for _ in range(80000):
eps = np.random.normal(0, 1)
s = np.exp(lns)
mu = m + s * eps
dll = (y.sum() - n * mu) / sigma2 - (mu - m0) / s0**2
g_m = dll
g_lns = dll * s * eps + 1.0 # 来自 -log q 的 +1/s 项
m += lr * g_m
lns += lr * g_lns
sn = np.exp(lns)
Nmc = 50000
ep = np.random.normal(0, 1, Nmc)
mu = m + sn * ep
ll = (-0.5 * n * np.log(2 * np.pi * sigma2)
- 0.5 * np.sum((y[None, :] - mu[:, None])**2, axis=1) / sigma2
- 0.5 * np.log(2 * np.pi * s0**2)
- 0.5 * (mu**2) / s0**2)
logq = -0.5 * np.log(2 * np.pi * sn**2) - 0.5 * ep**2
elbo = (ll - logq).mean()
print("BBVI: m=%.4f (后验均值=%.4f) s=%.4f (后验标准差=%.4f)" % (m, mn, sn, np.sqrt(sn2)))
print("BBVI: ELBO=%.4f <= 边缘似然=%.4f ? %s" % (elbo, LM, elbo <= LM + 0.05))
# ---- (c) 贝叶斯 A/B 测试:P(B 转化率 > A) ----
np.random.seed(6)
aA, bA = 1 + 30, 1 + 170 # A: 30/200 转化
aB, bB = 1 + 45, 1 + 155 # B: 45/200 转化
pBgtA = (np.random.beta(aB, bB, 200000) > np.random.beta(aA, bA, 200000)).mean()
print("贝叶斯 A/B: P(B 转化率 > A) = %.3f" % pBgtA)
运行结果:解析 VI 给出后验均值 1.0962、标准差 0.1414,且 ELBO 与边缘似然 log p(y) 完全相等(=−68.1416)——精确后验下 KL=0、下界顶满;黑盒 VI 收敛到 m≈1.046、s≈0.130(接近解析值),其 ELBO −68.21 ≤ −68.14 满足下界不等式;贝叶斯 A/B 测试给出 P(B > A)=0.972,直接给出业务可读的"胜率"而非一个 p 值。VI 的优势是把推断变成可微优化,能放进 SGD/Adam 流水线、与神经网络端到端联合训练(这正是 VAE、贝叶斯神经网络的基础),代价是引入"近似误差"(q 未必等于后验,会低估不确定性)。
九、与 AI / 深度学习的映射表
| 贝叶斯概念 | 深度学习 / AI 对应 |
|---|---|
| 先验 $p(\theta)$ | 权重初始化、L2 正则(高斯先验 ⇔ 权重衰减)、分布外保守 |
| 后验 $p(\theta\mid\mathcal D)$ | 贝叶斯神经网络(BNN)的权重分布、模型集成 |
| 边缘似然 $\log p(\mathcal D)$ | 贝叶斯模型选择、神经架构搜索的目标 |
| MCMC 采样 | 深度集成、随机前向传播(MC Dropout 近似) |
| 变分推断 VI | VAE 的变分下界、Bayes 化训练、量化感知 |
| 重参数化技巧 | VAE 的 reparameterization、可微采样层 |
| 共轭 / 闭式更新 | 在线学习、Kalman 滤波、指数移动更新 |
| 贝叶斯优化 | 超参数搜索(高斯过程代理 + 采集函数) |
| 可信区间 | 预测不确定性校准、置信/可信输出(对安全关键系统至关重要) |
落到工程:训练时的权重衰减本质是给权重加高斯先验做 MAP;MC Dropout 是把 Dropout 当成对权重的 Bernoulli 近似后做 MCMC 式预测;VAE 的训练目标 $\mathbb E[\log p(x\mid z)]-\mathrm{KL}(q(z\mid x)\|p(z))$ 正是 ELBO;贝叶斯优化用高斯过程做后验、用采集函数(如期望改进)选下一组超参——全是本文工具的组合。
十、工程边界与陷阱
- 先验敏感性:弱数据下先验主导结论;强数据下后验对先验免疫。上线前务必做先验敏感性分析。
- 标签切换 / 多峰:MH 随机游走可能在多峰间跳不过去(伪收敛);用 HMC 或多链 + Gelman-Rubin 诊断。
- 混合不足:ESS 远低于样本数时,可信区间会过于乐观——永远报告 ESS 而不是原始样本数。
- HMC 步长:$\epsilon$ 太大能量爆炸、接受率塌方;太小退化成随机游走。务必自适应 + 监控 $|\Delta H|$。
- VI 欠分散(under-dispersion):mean-field 假设让 q 过于自信,低估尾部不确定性——安全关键场景宁可用 MCMC。
- 维度和计算成本:网格法 $m^d$ 崩于维度;MCMC 链需 burn-in;VI 推理快但近似。按场景权衡。
- 数值稳定:似然连乘用对数空间;归一化用
logpost -= logpost.max()防溢出。
十一、总结
贝叶斯推断不是"又一个统计方法",而是一种用分布而非点估计来承载不确定性的世界观:先验编码信念、似然吸收数据、后验给出更新后的完整信念。本文从 Beta/Gaussian 共轭的闭式解,到网格近似的数值边际化,再到 MH、Gibbs、HMC 三条 MCMC 路线,最后落到把推断变成优化的变分推断——并用可复现的代码验证了每一条链路(ELBO 顶满边缘似然、HMC 恢复目标协方差、Gibbs 给出覆盖真值的可信区间)。它既是统计建模的底座,也是现代深度学习里权重分布、VAE、贝叶斯优化与不确定性校准的共同母语。

发表评论 取消回复