2. 先验参数越界导致采样失败的排查
问题定位:先验参数越界如何让采样器崩溃
在 MCMC 采样中,先验分布的超参数(如正态先验的 sigma、Gamma 先验的 alpha/beta、Beta 先验的 a/b)必须落在各自分布的合法定义域内。一旦把 sigma 误设为负值,采样器在计算对数先验密度时会得到 NaN 或直接抛出定义域异常,整条采样链在初始化阶段就中断。排查的关键不是急着改采样器参数,而是先确认先验参数本身是否满足约束。
复现:构造一个会失败的采样模型
下面用 PyMC 构造一个最小可复现模型:观测数据为 5 个样本,先验均值 mu 用正态先验,先验标准差 sigma 被误设为 -0.5。运行采样时,采样器在计算对数先验密度时遇到负的标准差,抛出定义域异常。
import numpy as np
import pymc as pm
# 观测数据(示例)
y_obs = np.array([1.2, 0.8, 1.5, 0.9, 1.1])
# 错误配置:sigma 被误设为负值
with pm.Model() as broken_model:
mu = pm.Normal("mu", mu=0, sigma=10)
sigma = pm.HalfNormal("sigma", sigma=-0.5) # 越界:HalfNormal 的 sigma 必须 > 0
y = pm.Normal("y", mu=mu, sigma=sigma, observed=y_obs)
try:
trace = pm.sample(draws=500, tune=500, chains=2, random_seed=42)
except Exception as e:
print("采样失败:", type(e).__name__, e)
预期现象:HalfNormal 的 sigma 参数为负时,PyMC 在构建分布对象阶段就会抛出 ValueError,提示参数必须为正。若换成自定义先验或某些后端,错误可能延迟到 logp 求值时以 NaN 形式出现,表现为采样链全部拒绝、r_hat 异常或直接报 NaN 相关错误。
修复:参数合法性校验 + 修正先验
修复分两层:第一层在模型构建前对先验超参数做显式校验,把错误尽早暴露;第二层把 sigma 修正为合法值。校验函数应覆盖常见先验的约束:正态/半正态的 sigma > 0,Gamma 的 alpha > 0, beta > 0,Beta 的 a > 0, b > 0。
def validate_prior_params(params: dict):
"""检查先验超参数是否落在合法定义域内。"""
rules = {
"sigma": lambda v: v > 0,
"alpha": lambda v: v > 0,
"beta": lambda v: v > 0,
"a": lambda v: v > 0,
"b": lambda v: v > 0,
}
for name, value in params.items():
if name in rules and not rules[name](value):
raise ValueError(
f"先验参数 {name}={value} 越界,必须满足约束"
)
return True
# 修正后的配置
prior_params = {"mu": 0.0, "sigma": 0.5}
validate_prior_params(prior_params) # 通过
with pm.Model() as fixed_model:
mu = pm.Normal("mu", mu=0, sigma=10)
sigma = pm.HalfNormal("sigma", sigma=prior_params["sigma"])
y = pm.Normal("y", mu=mu, sigma=sigma, observed=y_obs)
trace = pm.sample(draws=500, tune=500, chains=2, random_seed=42)
验证与判读
修复后应确认三件事:采样无异常抛出;pm.summary(trace) 中 r_hat 接近 1(经验上小于 1.01);sigma 的后验样本全部为正。若仍出现 NaN 或链不收敛,说明问题不在先验参数越界,应转向其他排查方向(如数据尺度、采样器步长)。
把
validate_prior_params 放在模型构建之前调用,可以让越界错误在采样开始前就暴露,避免浪费采样时间,也让错误信息直接指向具体参数名和非法值。不要用
abs() 或 max(sigma, 1e-6) 之类的方式“静默修正”越界参数。这会掩盖配置错误,使模型在不知情的情况下使用与预期不同的先验,导致后验解释失真。正确做法是显式报错并修正配置源头。