PyMC模型平均:ensemble方法贝叶斯版

PyMC模型平均:ensemble方法贝叶斯版

【免费下载链接】pymc Python 中的贝叶斯建模和概率编程。 【免费下载链接】pymc 项目地址: https://gitcode.com/GitHub_Trending/py/pymc

引言:当单一模型不足以解决问题

你是否曾在多个贝叶斯模型之间犹豫不决?是否担心选择错误模型导致决策偏差?在机器学习中,集成(ensemble)方法通过组合多个模型来提高预测性能,而贝叶斯模型平均(Bayesian Model Averaging, BMA)则为这一理念提供了概率框架。本文将深入探讨如何在PyMC中实现模型平均,通过数学严谨性与工程实践相结合,帮助你构建更稳健的预测系统。

读完本文后,你将能够:

  • 理解贝叶斯模型平均的核心原理与数学基础
  • 使用PyMC实现基于WAIC/LOO的模型权重计算
  • 构建加权预测分布进行模型平均
  • 解决高维模型空间中的计算挑战
  • 应用模型平均处理实际业务问题

理论基础:从模型比较到模型平均

贝叶斯模型平均的数学框架

贝叶斯模型平均通过对所有可能模型的预测进行加权平均,其中权重对应模型的后验概率。对于预测变量$y$和新数据点$\tilde{x}$,模型平均的预测分布为:

$$ p(\tilde{y}|\mathcal{D}) = \sum_{m=1}^M p(\tilde{y}|\mathcal{M}_m, \mathcal{D}) p(\mathcal{M}_m|\mathcal{D}) $$

其中$\mathcal{M}_m$表示第$m$个模型,$p(\mathcal{M}_m|\mathcal{D})$为模型后验概率,通常通过贝叶斯因子计算。但在高维模型空间中,这一计算面临严峻挑战,实践中常采用近似方法。

信息准则与近似权重

当模型空间庞大时,我们使用信息准则近似模型后验概率。PyMC集成了ArviZ库,提供两种主要近似方法:

  1. Widely Applicable Information Criterion (WAIC)

    • 基于留一交叉验证(LOO-CV)的解析近似
    • 计算公式:$WAIC = -2(\hat{l}{WAIC} - p{WAIC})$
    • 其中$\hat{l}{WAIC}$为预期对数点预测密度,$p{WAIC}$为有效参数数量
  2. Leave-One-Out Cross-Validation (LOO)

    • 通过重要性采样估计留一交叉验证得分
    • 提供更稳健的模型比较,但计算成本更高

两种方法均返回信息准则值和模型权重,权重计算公式为:

$$ w_m = \frac{\exp(-\frac{1}{2}\Delta IC_m)}{\sum_{k=1}^M \exp(-\frac{1}{2}\Delta IC_k)} $$

其中$\Delta IC_m$是模型$m$与最佳模型的信息准则差值。

PyMC实现:模型平均工作流

步骤1:准备环境与数据

首先导入必要库并准备示例数据(以8 schools问题为例):

import arviz as az
import numpy as np
import pymc as pm
import matplotlib.pyplot as plt
from pymc import Model

# 8 schools数据集
y = np.array([28, 8, -3, 7, -1, 1, 18, 12])
sigma = np.array([15, 10, 16, 11, 9, 11, 10, 18])
schools = ["A", "B", "C", "D", "E", "F", "G", "H"]

步骤2:构建候选模型集

创建多个竞争模型,涵盖不同假设。以下构建三个模型:

  1. 完全池化模型:假设所有学校效果相同
  2. 分层模型:假设学校效果来自共同分布
  3. 混合效应模型:部分池化,引入学校特定协变量
def create_pooled_model():
    """完全池化模型:单一总体均值"""
    with Model() as model:
        mu = pm.Normal("mu", mu=0, sigma=1e6)
        obs = pm.Normal("obs", mu=mu, sigma=sigma, observed=y)
    return model

def create_hierarchical_model():
    """分层模型:随机效应模型"""
    with Model() as model:
        mu = pm.Normal("mu", mu=0, sigma=10)
        tau = pm.HalfNormal("tau", sigma=10)
        theta = pm.Normal("theta", mu=mu, sigma=tau, shape=len(y))
        obs = pm.Normal("obs", mu=theta, sigma=sigma, observed=y)
    return model

def create_mixed_model():
    """混合效应模型:部分池化+学校规模协变量"""
    # 模拟学校规模数据(实际应用中应使用真实数据)
    school_size = np.array([120, 150, 80, 200, 100, 140, 90, 180])
    
    with Model() as model:
        mu = pm.Normal("mu", mu=0, sigma=10)
        tau = pm.HalfNormal("tau", sigma=10)
        beta = pm.Normal("beta", mu=0, sigma=5)  # 学校规模系数
        theta = pm.Normal("theta", mu=mu + beta*(school_size/100), sigma=tau, shape=len(y))
        obs = pm.Normal("obs", mu=theta, sigma=sigma, observed=y)
    return model

# 创建模型列表
models = {
    "Pooled": create_pooled_model(),
    "Hierarchical": create_hierarchical_model(),
    "Mixed": create_mixed_model()
}

步骤3:模型拟合与诊断

对每个模型进行采样并检查收敛性:

# 存储结果
traces = {}
idatas = {}

# 拟合所有模型
for name, model in models.items():
    with model:
        print(f"拟合模型: {name}")
        trace = pm.sample(2000, tune=1000, chains=4, target_accept=0.95)
        idata = az.from_pymc3(trace=trace, model=model)
        traces[name] = trace
        idatas[name] = idata
        
        # 收敛诊断
        az.plot_trace(idata, var_names=["~obs"])
        plt.suptitle(f"{name}模型诊断", y=1.02)
        plt.show()
        
        # 检查Rhat和ESS
        summary = az.summary(idata)
        print(f"{name}模型收敛统计:\n", summary[["r_hat", "ess_bulk"]])

步骤4:模型比较与权重计算

使用WAIC和LOO进行模型比较并计算权重:

# 计算WAIC和LOO
waic_results = {}
loo_results = {}

for name, idata in idatas.items():
    # 计算WAIC
    waic = az.waic(idata)
    waic_results[name] = waic
    
    # 计算LOO
    loo = az.loo(idata)
    loo_results[name] = loo

# 模型比较表
comparison_waic = az.compare(waic_results)
comparison_loo = az.compare(loo_results)

print("WAIC模型比较:\n", comparison_waic[["elpd_waic", "p_waic", "waic", "weight"]])
print("\nLOO模型比较:\n", comparison_loo[["elpd_loo", "p_loo", "loo", "weight"]])

# 可视化模型权重
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))
az.plot_weights(comparison_waic, ax=ax1)
ax1.set_title("WAIC模型权重")
az.plot_weights(comparison_loo, ax=ax2)
ax2.set_title("LOO模型权重")
plt.tight_layout()
plt.show()

步骤5:实现模型平均预测

基于LOO/WAIC权重构建加权预测分布:

def model_averaged_prediction(models, idatas, weights, new_data=None):
    """
    计算模型平均预测分布
    
    参数:
    - models: 模型字典
    - idatas: 包含后验样本的InferenceData字典
    - weights: 模型权重(来自az.compare)
    - new_data: 新数据点(如适用)
    
    返回:
    - 综合预测分布数组
    """
    predictions = []
    weights_norm = weights / weights.sum()  # 确保权重归一化
    
    for name, weight in weights_norm.items():
        if weight < 1e-6:  # 忽略权重过小的模型
            continue
            
        model = models[name]
        idata = idatas[name]
        
        # 生成后验预测
        with model:
            ppc = pm.sample_posterior_predictive(idata, samples=1000)
            y_pred = ppc.posterior_predictive["obs"].values  # 形状: (chains, draws, ...)
            
            # 展平链和样本维度
            y_pred_flat = y_pred.reshape(-1, *y_pred.shape[2:])
            
            # 应用权重
            predictions.append(weight * y_pred_flat)
    
    # 加权求和得到平均预测
    averaged_pred = np.sum(np.stack(predictions), axis=0)
    return averaged_pred

# 获取LOO权重
loo_weights = comparison_loo["weight"]

# 计算模型平均预测
averaged_pred = model_averaged_prediction(models, idatas, loo_weights)

# 可视化预测结果
fig, ax = plt.subplots(figsize=(10, 6))

# 绘制原始数据
ax.errorbar(range(len(y)), y, yerr=sigma, fmt="o", label="观测数据", color="black", capsize=5)

# 绘制各模型预测
for name, idata in idatas.items():
    with models[name]:
        ppc = pm.sample_posterior_predictive(idata, samples=1000)
        pred_mean = ppc.posterior_predictive["obs"].mean(dim=["chain", "draw"]).values
        ax.plot(pred_mean, label=f"{name}模型", linestyle="--", alpha=0.7)

# 绘制模型平均预测
avg_mean = averaged_pred.mean(axis=0)
avg_hdi = az.hdi(averaged_pred)
ax.plot(avg_mean, label="模型平均", color="red", linewidth=2)
ax.fill_between(range(len(y)), avg_hdi[0], avg_hdi[1], color="red", alpha=0.2)

ax.set_xticks(range(len(y)))
ax.set_xticklabels(schools)
ax.set_ylabel("SAT分数提升")
ax.legend()
plt.title("各模型预测 vs 模型平均预测")
plt.tight_layout()
plt.show()

步骤6:不确定性量化与决策支持

模型平均不仅提供点预测,还能量化预测不确定性:

# 计算预测分布的统计量
mean_pred = averaged_pred.mean(axis=0)
hdi_94 = az.hdi(averaged_pred, hdi_prob=0.94)
std_pred = averaged_pred.std(axis=0)

# 组织结果为表格
results_table = {
    "学校": schools,
    "观测值": y,
    "平均预测": np.round(mean_pred, 2),
    "94% HDI下限": np.round(hdi_94[0], 2),
    "94% HDI上限": np.round(hdi_94[1], 2),
    "预测标准差": np.round(std_pred, 2)
}

# 打印结果表格
import pandas as pd
pd.DataFrame(results_table)

# 决策风险评估示例
# 计算"真实效果为正"的后验概率
positive_effect_prob = (averaged_pred > 0).mean(axis=0)

fig, ax = plt.subplots(figsize=(10, 5))
bars = ax.bar(schools, positive_effect_prob)
for bar, prob in zip(bars, positive_effect_prob):
    if prob > 0.9:
        bar.set_color("green")
    elif prob < 0.1:
        bar.set_color("red")
    else:
        bar.set_color("gray")
ax.axhline(0.9, color="black", linestyle="--", alpha=0.3)
ax.axhline(0.1, color="black", linestyle="--", alpha=0.3)
ax.set_ylabel("效果为正的后验概率")
ax.set_title("各学校辅导效果为正的概率")
plt.tight_layout()
plt.show()

高级主题:超越基础模型平均

处理高维模型空间

当候选模型超过10个时,建议采用以下策略减少计算负担:

  1. 分层模型平均:先对模型进行聚类,在每层内进行平均
  2. 稀疏权重正则化:使用尖峰-平板先验(Spike-and-Slab)收缩权重
  3. 随机模型采样:从模型空间随机采样子集进行平均
def sparse_model_averaging(models, idatas, prior_strength=1.0):
    """带尖峰-平板先验的稀疏模型平均"""
    # 计算LOO分数
    loo_dict = {name: az.loo(idata) for name, idata in idatas.items()}
    comparison = az.compare(loo_dict)
    
    # 尖峰-平板先验权重调整
    log_weights = comparison["weight"].values
    n_models = len(models)
    
    # 添加稀疏性惩罚(简化版实现)
    penalty = prior_strength * np.log(n_models) * np.ones(n_models)
    sparse_weights = np.exp(log_weights - penalty)
    sparse_weights /= sparse_weights.sum()
    
    # 应用稀疏权重进行预测(复用之前的预测函数)
    return model_averaged_prediction(models, idatas, sparse_weights)

动态模型平均(DMA)

对于时间序列数据,模型权重可能随时间变化:

def dynamic_model_averaging(models, idatas, time_series_data, window_size=10):
    """动态模型平均实现(简化版)"""
    n_time_points = len(time_series_data)
    predictions = []
    
    for t in range(window_size, n_time_points):
        # 使用滚动窗口数据重新拟合模型
        window_data = time_series_data[t-window_size:t]
        
        # 更新模型权重(实际应用中应重新计算LOO/WAIC)
        # 此处简化为使用近期预测误差加权
        recent_errors = np.array([...])  # 计算各模型近期预测误差
        dynamic_weights = np.exp(-recent_errors) / np.sum(np.exp(-recent_errors))
        
        # 计算当前时间点的模型平均预测
        pred = model_averaged_prediction(models, idatas, dynamic_weights)
        predictions.append(pred)
    
    return np.array(predictions)

模型平均的局限性与解决方案

挑战解决方案实现复杂度
计算成本高1. 模型剪枝 2. 随机近似 3. 分布式计算★★☆
模型相关性1. 分层聚类平均 2. 贝叶斯堆叠★★★
先验敏感性1. 先验鲁棒性检验 2. 超先验调整★★☆
解释性下降1. SHAP值分解 2. 变量重要性加权★★★

实际应用案例:客户流失预测

业务背景

某电信公司希望预测客户流失风险,现有多个竞争模型:

  • 逻辑回归模型(可解释性强)
  • 贝叶斯神经网络(复杂模式捕捉)
  • 生存分析模型(考虑时间因素)

模型平均实现

# 伪代码展示实际业务应用
def churn_model_averaging():
    # 1. 数据准备(客户特征、历史行为、流失标签)
    X, y = load_churn_data()
    
    # 2. 构建模型集
    models = {
        "Logistic": build_logistic_model(X, y),
        "BNN": build_bayesian_nn(X, y),
        "Survival": build_survival_model(X, y, time_feature)
    }
    
    # 3. 模型拟合与验证(使用时间分割验证集)
    idatas = fit_models_with_time_split(models, X, y)
    
    # 4. 计算业务导向的模型权重
    # 考虑:1. 预测准确率 2. 解释性需求 3. 计算效率
    business_weights = {
        "Logistic": 0.4,  # 高解释性权重
        "BNN": 0.4,       # 高准确率权重
        "Survival": 0.2   # 时间因素权重
    }
    
    # 5. 生成客户级预测与干预优先级
    customer_scores = model_averaged_prediction(models, idatas, business_weights)
    
    # 6. 业务价值计算
    intervention_value = calculate_intervention_roi(customer_scores, customer_value)
    
    return intervention_value

总结与未来展望

贝叶斯模型平均通过数学严谨的加权机制,有效解决了单一模型选择的风险,特别适合:

  1. 存在多个合理建模假设的场景
  2. 需要量化预测不确定性的决策问题
  3. 模型性能随时间或环境变化的动态系统

PyMC虽未直接提供模型平均API,但通过与ArviZ的集成,结合后验预测采样,可灵活实现这一强大技术。未来发展方向包括:

  • 自动化模型空间探索
  • 神经网络与概率模型的混合平均
  • 因果推断中的模型不确定性量化

实践建议

  • 始终从简单模型开始,逐步增加复杂度
  • 使用WAIC和LOO双重验证权重稳定性
  • 结合业务知识调整权重(如解释性要求)
  • 定期重新校准模型权重以适应数据漂移

通过本文介绍的方法,你可以在PyMC中构建稳健的模型平均系统,为关键决策提供更全面的概率支持。

【免费下载链接】pymc Python 中的贝叶斯建模和概率编程。 【免费下载链接】pymc 项目地址: https://gitcode.com/GitHub_Trending/py/pymc

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值