当前位置: 首页 > news >正文

中文分析原始

技术报告:基于Stacking集成学习的预测模型及SHAP和PDP可解释性分析

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

执行摘要

本报告展示了一种综合的机器学习方法,使用Stacking集成方法结合数据增强技术来预测目标变量E(her+oer)。该模型在测试集上达到了R²=0.863、RMSE=0.227的性能。SHAP和PDP分析揭示,工程特征"电子亲和能+价电子数(EA+VE)"是最具影响力的预测因子。


1. 方法论

1.1 整体框架

该方法包含四个主要组成部分:

  1. 数据增强:生成合成样本以增强训练数据
  2. Stacking集成学习:包含多个基学习器的两层模型架构
  3. 10折交叉验证:稳健的验证策略
  4. 可解释性分析:使用SHAP和PDP进行模型解释

1.2 技术架构

数据集 (105个样本, 34个特征)↓
训练-验证集划分 (95个样本) + 测试集 (10个样本)↓
数据增强 (11倍扩充 → 1045个样本)↓
10折交叉验证↓
Stacking集成 (5个基模型 + 1个元学习器)↓
预测 + 可解释性分析

2. 实施细节

2.1 数据准备

数据集特征:

  • 总样本数:105
  • 特征数:34(包括物理性质和工程特征)
  • 目标变量:E(her+oer)(连续回归任务)
  • 训练-验证集:95个样本
  • 测试集:10个样本(索引:76, 48, 32, 66, 27, 60, 55, 40, 43, 68)

特征工程: 数据集包含多种类型的特征:

  • 元素性质(d带中心、电负性、电离能)
  • 平均性质(avg_d、ave_Z、ave_VdW等)
  • 交互特征(EA+VE、d/IE、q*ave_VdW)
  • 库伦矩阵特征(cm1-cm9)

2.2 数据增强策略

为了解决小数据集问题(95个训练样本),我们实施了混合增强方法:

方法1:添加高斯噪声

  • 添加与特征标准差成比例的受控噪声
  • 噪声水平:特征标准差的0.1%
  • 保持数据分布特征

方法2:类SMOTE插值

  • 通过随机插值创建合成样本
  • 插值权重α ∈ [0.3, 0.7]
  • 公式:X_new = α·X_i + (1-α)·X_j

增强参数:

  • 增强因子:10
  • 最终训练规模:1,045个样本(原始的11倍)
  • 噪声水平:0.001(特征标准差的0.1%)

原理说明: 增强策略在噪声添加和插值之间交替进行,以:

  • 增加训练数据多样性
  • 提高模型泛化能力
  • 防止在有限样本上过拟合
  • 保持真实的特征关系

2.3 Stacking集成架构

第一层:基模型(5个不同学习器)

  1. 随机森林回归器
    • n_estimators: 100
    • max_depth: 10
    • 捕获非线性关系
  2. 梯度提升回归器
    • n_estimators: 100
    • max_depth: 5
    • 顺序误差修正
  3. 极端随机树回归器
    • n_estimators: 100
    • max_depth: 10
    • 额外随机化以减少方差
  4. 支持向量回归器(SVR)
    • kernel: RBF
    • C: 1.0, epsilon: 0.1
    • 对高维空间有效
  5. 岭回归
    • alpha: 1.0
    • 带L2正则化的线性基线

第二层:元学习器

  • 算法:Lasso回归(alpha=0.1)
  • 输入:5个基模型的预测
  • 输出:最终集成预测

为什么使用Stacking?

  • 结合不同算法的优势
  • 减少单个模型偏差
  • 元学习器学习最优权重组合
  • 比单个模型有更好的泛化能力

2.4 交叉验证策略

增强数据上的10折交叉验证:

  • 每折:90%训练(940个样本),10%验证(105个样本)
  • 测试预测:10折预测的平均值
  • 提供稳健的性能估计

交叉验证结果:

指标均值 ± 标准差
MSE0.0538 ± 0.0095
RMSE0.2312 ± 0.0205
MAE0.1922 ± 0.0163
0.8344 ± 0.0226

3. 模型性能

3.1 测试集结果

整体性能指标:

指标数值
MSE0.0517
RMSE0.2275
MAE0.1862
0.8634
MAPE15.37%

解读:

  • R² = 0.8634:模型解释了目标变量86.34%的方差
  • RMSE = 0.2275:平均预测误差为0.23个单位
  • MAPE = 15.37%:平均而言,预测偏离真实值15%

图8对应:测试集预测结果(10折交叉验证平均)

  • 展示了预测值vs真实值的散点图
  • 大部分点聚集在理想预测线附近
  • 显示了模型的整体拟合质量

3.2 逐样本预测

模型在不同测试样本上表现不同:

最佳预测(误差<10%):

  • 样本32:0.41%误差
  • 样本68:2.65%误差
  • 样本40:6.74%误差
  • 样本55:7.26%误差

具有挑战性的预测(误差>20%):

  • 样本60:44.32%误差(预测1.166 vs 实际0.808)
  • 样本66:35.38%误差(预测1.186 vs 实际0.876)
  • 样本27:28.20%误差(预测1.023 vs 实际1.425)

图1对应:每个样本的预测绝对误差

  • 条形图显示所有样本的绝对误差
  • 蓝色柱代表训练+验证集,橙色柱代表测试集
  • 可以清楚看到哪些样本预测误差较大

分析: 模型在大多数样本上表现良好,但在某些情况下遇到困难。样本60和66的较高误差(实际值都较低~0.8)表明模型在目标变量较低范围内可能存在困难。


4. 可解释性分析

4.1 SHAP(SHapley Additive exPlanations)分析

SHAP值基于博弈论提供了特征重要性的统一度量。

4.1.1 特征重要性排名

图12对应:SHAP Feature Importance - 特征重要性排序

前10个最重要特征:

排名特征SHAP重要性RF重要性
1EA+VE0.25210.3933
2cm10.07780.1331
3cm20.07490.0640
4cm30.03850.0301
5d Electrons(Nd)0.03720.0350
6First Ionization Energy(IE)0.03490.0213
7cm60.03050.0239
8Number(Z)0.01990.0309
9avg_Valence Electrons(ave_VE)0.01860.0151
10avg_Van der Waals Radius(ave_VdW)0.01820.0145

关键洞察:

  1. 主导特征:EA+VE(电子亲和能+价电子数)
    • SHAP重要性:0.252(比第二个特征高3倍)
    • 这个工程特征捕获了电子结构特征
    • 与催化活性(her+oer)强相关
  2. 库伦矩阵特征(cm1, cm2, cm3)
    • 代表分子几何和原子相互作用
    • cm1和cm2显示出强大的预测能力
    • 这些特征编码结构信息
  3. 电子性质
    • d电子和电离能贡献显著
    • 反映反应性和电子构型
    • 对理解催化行为至关重要

4.1.2 SHAP汇总图分析

图15对应:SHAP Summary Plot - 特征重要性与影响

蜂群图揭示:

  • EA+VE:高特征值(红色)强烈增加预测
  • cm1, cm2:显示复杂的非线性关系
  • 特征分布:大多数特征聚集在SHAP值=0附近,表明选择性重要
  • 颜色模式:红色(高值)和蓝色(低值)显示出不同的预测影响

4.1.3 SHAP依赖图

图9对应:SHAP Dependence Plot - Electron Affinity+Valence Electrons (EA+VE)

  • 显示EA+VE > 0时的强正相关关系
  • 在EA+VE = 0附近有急剧转变
  • 与第一电离能的交互作用(颜色编码)
  • 表明催化活性中的阈值行为

图10对应:SHAP Dependence Plot - cm1

  • 与分散模式的非线性关系
  • 可见多种交互作用效应
  • cm1 > 0通常增加预测
  • 高方差表明复杂的交互作用

图11对应:SHAP Dependence Plot - cm2

  • cm2 > 0时正相关
  • 与cm1的交互作用(颜色渐变)
  • 库伦矩阵特征之间的协同效应

4.1.4 SHAP瀑布图

图16对应:SHAP Waterfall Plot - 第一个测试样本的预测解释

对于第一个测试样本:

  • 基础值(E[f(X)]):1.681
  • 实际预测f(x):2.277
  • 主要贡献者:
    • EA+VE:+0.16(最大正向影响)
    • cm2:+0.16
    • cm3:+0.04
    • cm1:+0.04
  • 该样本具有高EA+VE(0.351)和高cm值,推动了更高的预测

4.1.5 SHAP力图

图13对应:SHAP Force Plot - 第一个测试样本

力图可视化特征贡献:

  • 红色箭头:推动预测升高的特征
  • 蓝色箭头:推动预测降低的特征
  • 对于样本1:由EA+VE和库伦特征的正向贡献主导

4.1.6 SHAP热图

图14对应:SHAP Values Heatmap - 测试集所有样本

跨所有10个测试样本的热图显示:

  • EA+VE、cm1、cm2在样本间的一致重要性
  • 特征贡献的样本特定模式
  • 某些样本(如#8、#9)具有更强的正SHAP值
  • 特征q对样本#9显示强负贡献

4.2 部分依赖图(PDP)分析

PDP显示特征对预测的边际效应,同时平均掉其他特征。

4.2.1 前6个特征PDP网格

图7对应:Partial Dependence Plots - 前6个重要特征

EA+VE(左上):

  • 值<0时平坦区域(预测~1.2)
  • 在EA+VE ≈ 0处急剧增加
  • 高值时达到平台期(预测~1.9)
  • 解读:在EA+VE=0处具有阈值行为的强非线性效应

cm1(中上):

  • cm1 < -0.5时相对平坦
  • 从-0.5到0急剧增加
  • cm1 > 0时逐渐增加
  • 解读:在过渡区域最具影响力

cm2(右上):

  • 复杂的阶梯状模式
  • 多个转折点
  • 整体正向趋势
  • 解读:非单调关系表明交互效应

cm3(左下):

  • 初始时反向关系(负斜率)
  • cm3 = -1附近急剧下降
  • cm3 > 0时恢复
  • 解读:U型关系,具有最优范围

d Electrons (Nd)(中下):

  • 相对平坦的轮廓
  • Nd = -1附近有轻微峰值
  • 大部分范围内稳定
  • 解读:中等影响,效果稳定

First Ionization Energy (IE)(右下):

  • IE < -0.5时急剧下降
  • IE > 0时平台期
  • 解读:低值时负向关系,然后饱和

4.2.2 详细单个PDP

图4对应:Partial Dependence Plot - Electron Affinity+Valence Electrons (EA+VE)

  • 确认阶跃函数行为
  • 在EA+VE ≈ 0处有临界阈值
  • 预测从1.22增加到1.90
  • 这68%的增加证明了EA+VE的主导作用

图5对应:Partial Dependence Plot - cm1

  • 显示比EA+VE更平滑的过渡
  • 稳定的增加模式
  • 预测范围:1.50到1.75(17%增加)
  • 与EA+VE相比影响更渐进

图6对应:Partial Dependence Plot - cm2

  • 顶级特征中最复杂的模式
  • 多个平台和阶梯
  • 预测范围:1.60到1.82
  • 表明可能的交互区域

4.2.3 2D PDP交互图

图2对应:2D PDP - Electron Affinity+Valence Electrons (EA+VE) vs cm1

  • 轮廓模式中可见强交互作用
  • 当两个特征都高时预测高(黄色,~1.87)
  • 当两者都低时预测低(紫色,~1.16)
  • 协同效应:组合影响 > 单个效应
  • 关键洞察:最优性能需要电子(EA+VE)和结构(cm1)性质兼备

图3对应:2D PDP - Electron Affinity+Valence Electrons (EA+VE) vs cm2

  • 类似的交互模式
  • 明显的过渡区域
  • 高值区域更集中
  • 意义:cm2为EA+VE提供补充信息

5. 物理和化学解释

5.1 为什么EA+VE重要

电子亲和能+价电子数捕获了:

  1. 电子可用性:价电子决定成键能力
  2. 反应性:电子亲和能表示接受电子的倾向
  3. 催化活性:两个因素对HER/OER反应都至关重要
  4. 描述符质量:结合了热力学(EA)和结构(VE)信息

化学意义:

  • 高EA+VE → 更有利的电子转移
  • 与催化中的d带中心理论相关
  • 反映表面反应性和中间体结合

5.2 库伦矩阵特征

cm1, cm2, cm3编码:

  • 原子排列和距离
  • 静电相互作用
  • 分子结构信息
  • 三维几何效应

为什么它们重要:

  • 捕获简单描述符遗漏的结构方面
  • 代表配位环境
  • 反映对称性和位点可及性

5.3 材料设计的模型意义

关键发现:

  1. 优先优化EA+VE:最具影响力的参数
  2. 结构很重要:库伦特征显示结构效应显著
  3. 阈值行为:EA+VE ≈ 0是关键设计点
  4. 协同效应:同时优化电子和结构性质

设计指南:

  • 目标材料EA+VE > 0.5以获得高活性
  • 通过cm参数考虑结构优化
  • 平衡d电子构型与电离能
  • 避免低cm1和EA+VE组合

6. 模型优势与局限

6.1 优势

  1. 高预测准确性:未见测试数据上R² = 0.863
  2. 稳健验证:10折CV确保泛化
  3. 可解释性:SHAP和PDP提供清晰的特征洞察
  4. 数据效率:数据增强解决小数据集挑战
  5. 集成稳健性:多个模型减少单个偏差

6.2 局限

  1. 小原始数据集:仅105个样本可能限制泛化
  2. 增强假设:假设线性插值有效
  3. 测试集大小:10个样本可能无法捕获完整分布
  4. 高误差案例:在低目标值(<1.0)上表现困难
  5. 特征工程:依赖于领域特定的工程特征
  6. 可解释性模型:SHAP分析使用单独的RF模型(R²=0.656)

6.3 潜在改进

  1. 收集更多数据:增加原始数据集大小
  2. 高级增强:使用生成模型(VAE、GAN)
  3. 超参数调优:优化基模型参数
  4. 特征选择:删除冗余特征
  5. 领域约束:纳入物理约束
  6. 不确定性量化:添加置信区间

7. 结论

7.1 关键成就

  1. 成功预测E(her+oer),测试集上86% R²
  2. 识别关键特征:EA+VE主导预测
  3. 通过PDP分析揭示结构-性质关系
  4. 证明阈值行为在EA+VE ≈ 0处
  5. 使用2D PDP量化特征交互

7.2 科学洞察

  • **电子性质(EA+VE)**是催化活性的主要驱动因素
  • **结构特征(库伦矩阵)**提供重要的补充信息
  • 非线性关系占主导地位,需要集成方法
  • 电子和结构性质之间的协同效应至关重要

7.3 实际应用

该模型可用于:

  • 筛选新催化剂:合成前预测活性
  • 材料优化:指导特征工程以改善性能
  • 设计空间探索:识别有前景的参数组合
  • 假设生成:SHAP洞察提示新研究方向

7.4 建议

未来工作:

  1. 扩展数据集,使用实验或计算数据
  2. 验证预测,通过新的实验测量
  3. 测试可迁移性到相关催化系统
  4. 纳入基于物理的约束以改善外推
  5. 开发主动学习策略以高效收集数据

8. 技术规格

软件环境:

  • Python 3.x
  • scikit-learn(集成方法、CV)
  • SHAP(可解释性)
  • pandas、numpy(数据处理)
  • matplotlib(可视化)

计算资源:

  • 训练时间:~2-3分钟(取决于硬件)
  • 模型大小:轻量级(<10 MB)
  • 预测速度:实时(每样本<1ms)

可重复性:

  • 随机种子:6(numpy)、42(sklearn)
  • 所有超参数已记录
  • 代码和数据可用

图表索引与对应关系

预测结果图表

  1. 图1:每个样本的预测绝对误差(条形图)
  2. 图8:测试集预测结果(10折交叉验证平均)散点图

PDP图表

  1. 图2:2D PDP - EA+VE vs cm1交互图
  2. 图3:2D PDP - EA+VE vs cm2交互图
  3. 图4:详细PDP - EA+VE单特征
  4. 图5:详细PDP - cm1单特征
  5. 图6:详细PDP - cm2单特征
  6. 图7:前6个重要特征的PDP网格图

SHAP图表

  1. 图9:SHAP依赖图 - EA+VE
  2. 图10:SHAP依赖图 - cm1
  3. 图11:SHAP依赖图 - cm2
  4. 图12:SHAP特征重要性条形图
  5. 图13:SHAP力图(第一个测试样本)
  6. 图14:SHAP值热图(测试集所有样本)
  7. 图15:SHAP汇总图(蜂群图)
  8. 图16:SHAP瀑布图(第一个测试样本的预测解释)

参考文献

关键方法:

  • Stacking:Wolpert, D. H. (1992). “Stacked generalization”
  • SHAP:Lundberg & Lee (2017). “A unified approach to interpreting model predictions”
  • PDP:Friedman (2001). “Greedy function approximation: A gradient boosting machine”
  • 数据增强:Chawla et al. (2002). “SMOTE: Synthetic Minority Over-sampling”

应用领域:

  • HER/OER催化和描述符设计
  • 催化的d带理论
  • 库伦矩阵表示

SHAP与树模型

1. TreeSHAP算法的原理优势

(1) 树模型的分裂路径与特征交互
随机森林的决策过程:
输入特征 x₁, x₂, ..., xₙ↓
树1: 根节点[x₃ < 0.5?] → 左子树[x₇ < 1.2?] → ... → 叶节点(预测值)
树2: 根节点[x₁ < 2.1?] → 右子树[x₅ < 0.8?] → ... → 叶节点(预测值)
...
树100: ...↓
最终预测 = 平均(所有叶节点预测值)

关键特性

  • 每次分裂直接基于原始特征:树节点的决策边界是 xᵢ < threshold
  • 分裂路径编码特征交互:特征 x₃ 和 x₇ 的组合效应通过树结构自动捕获
  • TreeSHAP的精确计算:利用树的递归结构,可以在多项式时间内精确计算Shapley值(不需要蒙特卡洛近似)
(2) TreeSHAP的计算复杂度对比
模型类型SHAP方法时间复杂度精度
随机森林(RF)TreeSHAPO(TLD²)精确
Stacking集成KernelSHAPO(2ⁿ × M)近似
  • T: 树的数量(100-200)
  • L: 平均树深度(10-15)
  • D: 特征维度
  • n: 特征数
  • M: 蒙特卡洛采样次数(通常需要10000+)

实际意义

  • 对100棵树的RF进行TreeSHAP:秒级完成
  • 对Stacking进行KernelSHAP:分钟到小时级,且结果是近似值

2. 树分裂的信息增益与SHAP值的对应关系

(1) 随机森林的特征重要性计算

代码中的 model_for_shap.feature_importances_ 是通过基尼不纯度减少量计算的:

# RF特征重要性的计算逻辑
for tree in forest:for node in tree:if node.is_split:feature = node.split_feature# 信息增益 = 父节点不纯度 - 加权子节点不纯度info_gain = parent_impurity - (left_weight × left_impurity + right_weight × right_impurity)feature_importance[feature] += info_gain

与SHAP的关系

  • SHAP值是局部归因:对单个样本,每个特征的贡献值
  • 特征重要性是全局平均:所有样本SHAP值的平均绝对值
  • 树分裂的信息增益:衡量特征对模型整体预测能力的贡献
(2) SHAP值与树路径的数学关系

对于单棵决策树,样本 x的预测可以表示为:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

其中vl 是叶节点的预测值。TreeSHAP通过递归遍历所有可能的特征子集来计算每个特征的边际贡献:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

关键洞察

  • 树的每个分裂节点实际上定义了特征的条件依赖关系
  • SHAP利用树结构精确计算所有2ⁿ个特征子集的边际贡献(无需显式枚举)

3. 为什么不能对Stacking整体进行TreeSHAP

(1) Stacking的架构特点
输入: 原始特征 X (形状: [n_samples, n_features])↓
第一层(5个基学习器并行):- RF:     X → 预测₁- GBR:    X → 预测₂- ETR:    X → 预测₃- SVR:    X → 预测₄- Ridge:  X → 预测₅↓
元特征: Z = [预测₁, 预测₂, 预测₃, 预测₄, 预测₅] (形状: [n_samples, 5])↓
第二层(元学习器Lasso):y_final = w₁×预测₁ + w₂×预测₂ + ... + w₅×预测₅ + b

问题所在

  1. 元学习器的输入空间已被压缩:从 n_features 维降到 5
  2. 信息瓶颈:原始特征 → 元特征的映射是不可逆的
  3. TreeSHAP不适用:Lasso是线性模型,不能用TreeSHAP
(2) 如果强行对Stacking进行SHAP分析

方法1:对元学习器(Lasso)进行LinearSHAP

# 假设代码
explainer_meta = shap.LinearExplainer(meta_model, meta_features_train)
shap_values_meta = explainer_meta.shap_values(meta_features_test)

结果

  • 得到5个元特征的SHAP值(对应5个基学习器的贡献)
  • 无法反映原始描述符的化学意义
  • 例如:“RF的预测贡献了0.8 eV,GBR贡献了-0.3 eV”

方法2:对整个Stacking进行KernelSHAP

# 假设代码
def stacking_predict(X_original):# 步骤1: 用5个基学习器生成元特征meta_features = np.column_stack([model.predict(scaler.transform(X_original)) for model in base_models.values()])# 步骤2: 用元学习器预测return meta_model.predict(meta_features)explainer_full = shap.KernelExplainer(stacking_predict, X_train_sample)
shap_values_full = explainer_full.shap_values(X_test)  # 极其耗时!

问题

  • 计算成本爆炸:需要评估 2nfeatures2^{n_features}2nfeatures 次模型(对于50个特征需要 2502^{50}250 次)
  • 近似误差:KernelSHAP使用蒙特卡洛采样,结果不精确
  • 解释模糊:得到的SHAP值混合了5个模型的复合效应

4. TreeSHAP的理论保证:为什么RF的SHAP值可信

(1) Shapley值的唯一性定理

Shapley值满足以下公理:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

(2) TreeSHAP的特征交互捕获

考虑一个简单例子:

树的分裂路径:
根节点: [电负性 < 2.5]├─ 左子树: [原子半径 < 1.2] → 预测值 = 0.5 eV└─ 右子树: [轨道能量 < -3.0] → 预测值 = 1.2 eV

TreeSHAP计算的SHAP值会自动考虑

  • 主效应:电负性的单独贡献
  • 交互效应:电负性与原子半径的联合效应
  • 条件依赖:在电负性>2.5的条件下,轨道能量的重要性

这些交互关系通过树的分裂路径自然编码,无需显式建模。


5. 代码中的SHAP分析:技术细节佐证

(1) 为什么选择n_estimators=200的RF
model_for_shap = RandomForestRegressor(n_estimators=200,  # 足够多的树以稳定特征重要性max_depth=15,      # 适中深度避免过拟合random_state=42,n_jobs=-1
)

原理

  • 集成效应:200棵树的平均减少了单棵树的方差
  • 特征采样:每棵树随机选择 nfeatures\sqrt{n_features}nfeatures 个特征进行分裂
  • SHAP值的稳定性:多棵树的SHAP值平均后更鲁棒
(2) TreeExplainer的高效实现
explainer = shap.TreeExplainer(model_for_shap)
shap_values = explainer.shap_values(X_test)  # 秒级完成

底层算法(Lundberg et al., 2020):

# TreeSHAP伪代码(简化版)
def tree_shap(tree, x, S):"""tree: 决策树模型x: 单个样本S: 特征子集(用于递归)"""if tree.is_leaf:return tree.valuefeature = tree.split_featurethreshold = tree.threshold# 递归计算左右子树的贡献if feature in S:# 特征已固定,按实际值走if x[feature] < threshold:return tree_shap(tree.left, x, S)else:return tree_shap(tree.right, x, S)else:# 特征未固定,计算期望left_weight = tree.left.cover / tree.coverright_weight = tree.right.cover / tree.coverreturn (left_weight * tree_shap(tree.left, x, S) +right_weight * tree_shap(tree.right, x, S))

关键优化

  • 利用树的覆盖度统计(cover)避免显式采样
  • 通过动态规划缓存中间结果
  • 复杂度从指数级降到多项式级
import pandas as pd
import numpy as np
import random
from sklearn.model_selection import KFold, train_test_split
from sklearn.ensemble import RandomForestRegressor, GradientBoostingRegressor, ExtraTreesRegressor
from sklearn.linear_model import Ridge, Lasso
from sklearn.svm import SVR
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score
import warningswarnings.filterwarnings('ignore')# 设置所有随机种子,确保结果可复现
SEED = 6
np.random.seed(SEED)
random.seed(SEED)# 1. 读取数据
df = pd.read_csv('features_8.csv')
print(f"数据形状: {df.shape}")
print(f"列名: {list(df.columns)}")# 2. 分离特征和目标变量
target_col = 'E(her+oer)'
X = df.drop(columns=[target_col])
y = df[target_col]print(f"\n特征数量: {X.shape[1]}")
print(f"样本数量: {X.shape[0]}")# 3. 使用sklearn的train_test_split划分数据集
from sklearn.model_selection import train_test_splitX_train_val, X_test, y_train_val, y_test = train_test_split(X, y,test_size=10,  # 10条数据作为测试集random_state=62,  # 设置随机种子以确保可重复性shuffle=True
)# 获取测试集的原始索引
test_indices = X_test.index.tolist()print(f"\n训练+验证集大小: {len(X_train_val)}")
print(f"测试集大小: {len(X_test)}")
print(f"测试集索引: {test_indices}")# 4. 数据增强 - 仅对训练+验证集进行扩充
def augment_data(X, y, augmentation_factor=3, noise_level=0.01):"""数据增强函数参数:- X: 特征数据- y: 目标变量- augmentation_factor: 扩充倍数(生成的增强样本数 = 原样本数 * augmentation_factor)- noise_level: 噪声水平(相对于特征标准差的比例)返回:- X_augmented: 增强后的特征数据- y_augmented: 增强后的目标变量"""X_list = [X]y_list = [y]# 计算每个特征的标准差,用于添加噪声X_std = X.std(axis=0)for i in range(augmentation_factor):# 方法1: 添加高斯噪声noise = np.random.normal(0, noise_level, X.shape) * X_std.valuesX_noisy = X.values + noise# 方法2: SMOTE-like 插值(在样本之间进行插值)if len(X) > 1:# 随机选择样本对进行插值idx1 = np.random.randint(0, len(X), size=len(X))idx2 = np.random.randint(0, len(X), size=len(X))# 随机插值权重alpha = np.random.uniform(0.3, 0.7, size=(len(X), 1))X_interpolated = alpha * X.iloc[idx1].values + (1 - alpha) * X.iloc[idx2].valuesy_interpolated = alpha.flatten() * y.iloc[idx1].values + (1 - alpha).flatten() * y.iloc[idx2].values# 合并噪声数据和插值数据if i % 2 == 0:X_aug = X_noisyy_aug = y.valueselse:X_aug = X_interpolatedy_aug = y_interpolatedelse:X_aug = X_noisyy_aug = y.valuesX_list.append(pd.DataFrame(X_aug, columns=X.columns))y_list.append(pd.Series(y_aug))# 合并原始数据和增强数据X_augmented = pd.concat(X_list, ignore_index=True)y_augmented = pd.concat(y_list, ignore_index=True)return X_augmented, y_augmented# 设置数据增强参数
AUGMENTATION_FACTOR = 10  # 扩充3倍,即总数据量变为原来的4倍
NOISE_LEVEL = 0.001  # 噪声水平为特征标准差的1%print("\n" + "=" * 70)
print("数据增强:")
print("=" * 70)
print(f"增强倍数: {AUGMENTATION_FACTOR}")
print(f"噪声水平: {NOISE_LEVEL}")
print(f"原始训练+验证集大小: {len(X_train_val)}")# 对训练+验证集进行数据增强
X_train_val_aug, y_train_val_aug = augment_data(X_train_val,y_train_val,augmentation_factor=AUGMENTATION_FACTOR,noise_level=NOISE_LEVEL
)print(f"增强后训练+验证集大小: {len(X_train_val_aug)}")
print(f"数据扩充比例: {len(X_train_val_aug) / len(X_train_val):.2f}x")
print(f"测试集保持不变: {len(X_test)} 条")# 5. 定义基学习器
base_models = {'rf': RandomForestRegressor(n_estimators=100, max_depth=10, random_state=42, n_jobs=-1),'gbr': GradientBoostingRegressor(n_estimators=100, max_depth=5, random_state=42),'etr': ExtraTreesRegressor(n_estimators=100, max_depth=10, random_state=42, n_jobs=-1),'svr': SVR(kernel='rbf', C=1.0, epsilon=0.1),'ridge': Ridge(alpha=1.0)
}# 元学习器
meta_model = Lasso(alpha=0.1)# 6. 10折交叉验证的Stacking实现(使用增强后的数据)
kf = KFold(n_splits=10, shuffle=True, random_state=42)# 存储每折的测试集预测结果
test_predictions_all_folds = []# 用于存储训练过程的评估指标
fold_scores = []print("\n开始10折交叉验证训练...")
print("=" * 70)for fold_idx, (train_idx, val_idx) in enumerate(kf.split(X_train_val_aug), 1):print(f"\n第 {fold_idx} 折:")# 分割数据(使用增强后的数据)X_train_fold = X_train_val_aug.iloc[train_idx]y_train_fold = y_train_val_aug.iloc[train_idx]X_val_fold = X_train_val_aug.iloc[val_idx]y_val_fold = y_train_val_aug.iloc[val_idx]# 标准化scaler = StandardScaler()X_train_scaled = scaler.fit_transform(X_train_fold)X_val_scaled = scaler.transform(X_val_fold)X_test_scaled = scaler.transform(X_test)# 第一层:训练基学习器并生成元特征meta_features_train = np.zeros((len(X_train_fold), len(base_models)))meta_features_val = np.zeros((len(X_val_fold), len(base_models)))meta_features_test = np.zeros((len(X_test), len(base_models)))for i, (name, model) in enumerate(base_models.items()):# 训练基学习器model.fit(X_train_scaled, y_train_fold)# 生成元特征meta_features_train[:, i] = model.predict(X_train_scaled)meta_features_val[:, i] = model.predict(X_val_scaled)meta_features_test[:, i] = model.predict(X_test_scaled)# 第二层:训练元学习器meta_model_fold = Lasso(alpha=0.1)meta_model_fold.fit(meta_features_train, y_train_fold)# 在验证集上评估val_pred = meta_model_fold.predict(meta_features_val)val_mse = mean_squared_error(y_val_fold, val_pred)val_mae = mean_absolute_error(y_val_fold, val_pred)val_r2 = r2_score(y_val_fold, val_pred)val_rmse = np.sqrt(val_mse)fold_scores.append({'fold': fold_idx,'val_mse': val_mse,'val_rmse': val_rmse,'val_mae': val_mae,'val_r2': val_r2})print(f"  验证集 MSE: {val_mse:.6f}")print(f"  验证集 RMSE: {val_rmse:.6f}")print(f"  验证集 MAE: {val_mae:.6f}")print(f"  验证集 R²: {val_r2:.6f}")# 对测试集进行预测test_pred = meta_model_fold.predict(meta_features_test)test_predictions_all_folds.append(test_pred)# 6. 计算10折的平均预测结果
test_predictions_mean = np.mean(test_predictions_all_folds, axis=0)# 7. 计算测试集的误差指标(测试集使用的是原始数据,未增强)
test_mse = mean_squared_error(y_test, test_predictions_mean)
test_rmse = np.sqrt(test_mse)
test_mae = mean_absolute_error(y_test, test_predictions_mean)
test_r2 = r2_score(y_test, test_predictions_mean)
test_mape = np.mean(np.abs((y_test - test_predictions_mean) / y_test)) * 100# 8. 输出结果
print("\n" + "=" * 70)
print("交叉验证结果汇总:")
print("=" * 70)fold_scores_df = pd.DataFrame(fold_scores)
print(f"\n平均验证集 MSE: {fold_scores_df['val_mse'].mean():.6f} ± {fold_scores_df['val_mse'].std():.6f}")
print(f"平均验证集 RMSE: {fold_scores_df['val_rmse'].mean():.6f} ± {fold_scores_df['val_rmse'].std():.6f}")
print(f"平均验证集 MAE: {fold_scores_df['val_mae'].mean():.6f} ± {fold_scores_df['val_mae'].std():.6f}")
print(f"平均验证集 R²: {fold_scores_df['val_r2'].mean():.6f} ± {fold_scores_df['val_r2'].std():.6f}")print("\n" + "=" * 70)
print("测试集预测结果:")
print("=" * 70)# 创建结果DataFrame
results_df = pd.DataFrame({'样本索引': test_indices,'真实值': y_test.values,'预测值': test_predictions_mean,'绝对误差': np.abs(y_test.values - test_predictions_mean),'相对误差(%)': np.abs((y_test.values - test_predictions_mean) / y_test.values) * 100
})print("\n测试集详细预测结果:")
print(results_df.to_string(index=False))print("\n" + "=" * 70)
print("测试集误差指标:")
print("=" * 70)
print(f"MSE (均方误差):           {test_mse:.6f}")
print(f"RMSE (均方根误差):        {test_rmse:.6f}")
print(f"MAE (平均绝对误差):       {test_mae:.6f}")
print(f"R² (决定系数):            {test_r2:.6f}")
print(f"MAPE (平均绝对百分比误差): {test_mape:.2f}%")
print("=" * 70)# 9. 保存预测结果
results_df.to_csv('test_predictions.csv', index=False, encoding='utf-8-sig')
print("\n预测结果已保存至 'test_predictions.csv'")# 10. 绘制预测vs真实值散点图(可选)
try:import matplotlibimport matplotlib.pyplot as plt# 设置中文字体matplotlib.rcParams['font.sans-serif'] = ['SimHei']  # Windows系统# 如果是Mac系统,使用: ['Arial Unicode MS']# 如果是Linux系统,使用: ['DejaVu Sans']matplotlib.rcParams['axes.unicode_minus'] = False  # 解决负号显示问题plt.figure(figsize=(10, 6))plt.scatter(y_test, test_predictions_mean, alpha=0.6, s=100)plt.plot([y_test.min(), y_test.max()], [y_test.min(), y_test.max()],'r--', lw=2, label='理想预测线')plt.xlabel('真实值', fontsize=12)plt.ylabel('预测值', fontsize=12)plt.title('测试集预测结果 (10折交叉验证平均)', fontsize=14)plt.legend()plt.grid(True, alpha=0.3)plt.tight_layout()plt.savefig('prediction_results.png', dpi=300, bbox_inches='tight')print("预测结果图已保存至 'prediction_results.png'")plt.show()
except ImportError:print("\n注意: matplotlib未安装,跳过绘图步骤")# ==================== 针对所有数据的预测 ====================
print("\n" + "=" * 70)
print("重新读取原始CSV数据并使用完整模型进行预测")
print("=" * 70)# 11. 重新读取原始CSV文件
print("\n重新读取原始数据文件: features_8.csv")
df_original = pd.read_csv('features_8.csv')
print(f"原始数据形状: {df_original.shape}")
print(f"原始数据列名: {list(df_original.columns)}")# 分离特征和目标变量
X_original = df_original.drop(columns=[target_col])
y_original = df_original[target_col]print(f"\n原始数据样本数: {len(X_original)}")
print(f"原始数据特征数: {X_original.shape[1]}")# 12. 使用增强后的完整训练+验证集训练最终模型
print("\n训练最终预测模型(使用全部增强训练数据)...")# 标准化
scaler_final = StandardScaler()
X_train_val_aug_scaled = scaler_final.fit_transform(X_train_val_aug)
X_original_scaled = scaler_final.transform(X_original)  # 标准化原始数据# 训练基学习器
base_models_final = {'rf': RandomForestRegressor(n_estimators=1000, max_depth=10, random_state=42, n_jobs=-1),'gbr': GradientBoostingRegressor(n_estimators=1000, max_depth=5, random_state=42),'etr': ExtraTreesRegressor(n_estimators=1000, max_depth=10, random_state=42, n_jobs=-1),'svr': SVR(kernel='rbf', C=1.0, epsilon=0.1),'ridge': Ridge(alpha=1.0)
}# 生成元特征
meta_features_train_final = np.zeros((len(X_train_val_aug), len(base_models_final)))
meta_features_original = np.zeros((len(X_original), len(base_models_final)))print("训练基学习器:")
for i, (name, model) in enumerate(base_models_final.items()):print(f"  {i+1}. 训练 {name.upper()}...")model.fit(X_train_val_aug_scaled, y_train_val_aug)# 生成元特征meta_features_train_final[:, i] = model.predict(X_train_val_aug_scaled)meta_features_original[:, i] = model.predict(X_original_scaled)# 训练元学习器
print("  6. 训练元学习器 (Lasso)...")
meta_model_final = Lasso(alpha=0.1)
meta_model_final.fit(meta_features_train_final, y_train_val_aug)print("\n模型训练完成!")# 13. 对原始数据进行预测
print("\n对原始CSV数据进行预测...")
original_predictions = meta_model_final.predict(meta_features_original)# 14. 计算整体误差指标
all_mse = mean_squared_error(y_original, original_predictions)
all_rmse = np.sqrt(all_mse)
all_mae = mean_absolute_error(y_original, original_predictions)
all_r2 = r2_score(y_original, original_predictions)
all_mape = np.mean(np.abs((y_original - original_predictions) / y_original)) * 100# 15. 创建预测结果DataFrame
original_results_df = pd.DataFrame({'样本索引': df_original.index,'真实值': y_original.values,'预测值': original_predictions,'绝对误差': np.abs(y_original.values - original_predictions),'相对误差(%)': np.abs((y_original.values - original_predictions) / y_original.values) * 100
})# 标记测试集样本
original_results_df['数据集类型'] = '训练+验证集'
original_results_df.loc[original_results_df['样本索引'].isin(test_indices), '数据集类型'] = '测试集'# 16. 输出结果
print("\n" + "=" * 70)
print("原始CSV数据预测结果汇总:")
print("=" * 70)
print(f"\n总样本数: {len(original_results_df)}")
print(f"训练+验证集样本数: {(original_results_df['数据集类型']=='训练+验证集').sum()}")
print(f"测试集样本数: {(original_results_df['数据集类型']=='测试集').sum()}")print("\n" + "-" * 70)
print("所有样本预测详情:")
print("-" * 70)
print(original_results_df.to_string(index=False))# 17. 整体误差指标
print("\n" + "=" * 70)
print("整体误差指标:")
print("=" * 70)
print(f"MSE (均方误差):              {all_mse:.6f}")
print(f"RMSE (均方根误差):           {all_rmse:.6f}")
print(f"MAE (平均绝对误差):          {all_mae:.6f}")
print(f"R² (决定系数):               {all_r2:.6f}")
print(f"MAPE (平均绝对百分比误差):    {all_mape:.2f}%")# 18. 分组统计
print("\n" + "-" * 70)
print("分组误差指标:")
print("-" * 70)# 训练+验证集指标
train_val_mask = original_results_df['数据集类型'] == '训练+验证集'
train_val_r2 = r2_score(original_results_df.loc[train_val_mask, '真实值'],original_results_df.loc[train_val_mask, '预测值']
)
train_val_rmse = np.sqrt(mean_squared_error(original_results_df.loc[train_val_mask, '真实值'],original_results_df.loc[train_val_mask, '预测值']
))
train_val_mae = mean_absolute_error(original_results_df.loc[train_val_mask, '真实值'],original_results_df.loc[train_val_mask, '预测值']
)# 测试集指标
test_mask = original_results_df['数据集类型'] == '测试集'
test_final_r2 = r2_score(original_results_df.loc[test_mask, '真实值'],original_results_df.loc[test_mask, '预测值']
)
test_final_rmse = np.sqrt(mean_squared_error(original_results_df.loc[test_mask, '真实值'],original_results_df.loc[test_mask, '预测值']
))
test_final_mae = mean_absolute_error(original_results_df.loc[test_mask, '真实值'],original_results_df.loc[test_mask, '预测值']
)print(f"\n训练+验证集 ({(original_results_df['数据集类型']=='训练+验证集').sum()}个样本):")
print(f"  R²:   {train_val_r2:.6f}")
print(f"  RMSE: {train_val_rmse:.6f}")
print(f"  MAE:  {train_val_mae:.6f}")print(f"\n测试集 ({(original_results_df['数据集类型']=='测试集').sum()}个样本):")
print(f"  R²:   {test_final_r2:.6f}")
print(f"  RMSE: {test_final_rmse:.6f}")
print(f"  MAE:  {test_final_mae:.6f}")print("=" * 70)# 19. 保存预测结果
original_results_df.to_csv('original_data_predictions.csv', index=False, encoding='utf-8-sig')
print("\n原始CSV数据预测结果已保存至: 'original_data_predictions.csv'")# 20. 可视化
try:# 图1: 预测vs真实值散点图(区分训练集和测试集)plt.figure(figsize=(12, 6))train_val_data = original_results_df[original_results_df['数据集类型'] == '训练+验证集']test_data = original_results_df[original_results_df['数据集类型'] == '测试集']plt.scatter(train_val_data['真实值'], train_val_data['预测值'],alpha=0.6, s=60, label=f'训练+验证集 (n={len(train_val_data)})',color='steelblue', edgecolors='navy', linewidth=0.5)plt.scatter(test_data['真实值'], test_data['预测值'],alpha=0.9, s=120, label=f'测试集 (n={len(test_data)})',color='orangered', marker='D', edgecolors='darkred', linewidth=0.8)# 理想预测线all_values = pd.concat([original_results_df['真实值'], original_results_df['预测值']])min_val, max_val = all_values.min(), all_values.max()plt.plot([min_val, max_val], [min_val, max_val],'k--', lw=2.5, label='理想预测线', alpha=0.7)plt.xlabel('真实值', fontsize=13, fontweight='bold')plt.ylabel('预测值', fontsize=13, fontweight='bold')plt.title(f'原始数据预测结果\n(整体: R²={all_r2:.4f}, RMSE={all_rmse:.4f})',fontsize=14, fontweight='bold', pad=15)plt.legend(fontsize=11, loc='best', framealpha=0.9)plt.grid(True, alpha=0.3, linestyle='--')plt.tight_layout()plt.savefig('original_data_prediction_scatter.png', dpi=300, bbox_inches='tight')print("预测散点图已保存至: 'original_data_prediction_scatter.png'")plt.show()# 图2: 误差分布图fig, axes = plt.subplots(1, 2, figsize=(14, 5))# 绝对误差分布axes[0].hist(original_results_df['绝对误差'], bins=20, alpha=0.7,color='skyblue', edgecolor='black', linewidth=1.2)axes[0].axvline(original_results_df['绝对误差'].mean(),color='red', linestyle='--', linewidth=2,label=f'平均值={original_results_df["绝对误差"].mean():.4f}')axes[0].set_xlabel('绝对误差', fontsize=12, fontweight='bold')axes[0].set_ylabel('频数', fontsize=12, fontweight='bold')axes[0].set_title('绝对误差分布', fontsize=13, fontweight='bold')axes[0].legend(fontsize=10)axes[0].grid(True, alpha=0.3, axis='y')# 相对误差分布axes[1].hist(original_results_df['相对误差(%)'], bins=20, alpha=0.7,color='lightcoral', edgecolor='black', linewidth=1.2)axes[1].axvline(original_results_df['相对误差(%)'].mean(),color='darkred', linestyle='--', linewidth=2,label=f'平均值={original_results_df["相对误差(%)"].mean():.2f}%')axes[1].set_xlabel('相对误差 (%)', fontsize=12, fontweight='bold')axes[1].set_ylabel('频数', fontsize=12, fontweight='bold')axes[1].set_title('相对误差分布', fontsize=13, fontweight='bold')axes[1].legend(fontsize=10)axes[1].grid(True, alpha=0.3, axis='y')plt.tight_layout()plt.savefig('original_data_error_distribution.png', dpi=300, bbox_inches='tight')print("误差分布图已保存至: 'original_data_error_distribution.png'")plt.show()# 图3: 逐样本误差图plt.figure(figsize=(14, 6))colors = ['steelblue' if dt == '训练+验证集' else 'orangered'for dt in original_results_df['数据集类型']]plt.bar(original_results_df['样本索引'], original_results_df['绝对误差'],color=colors, alpha=0.7, edgecolor='black', linewidth=0.5)plt.xlabel('样本索引', fontsize=12, fontweight='bold')plt.ylabel('绝对误差', fontsize=12, fontweight='bold')plt.title('每个样本的预测绝对误差', fontsize=14, fontweight='bold', pad=15)# 添加图例from matplotlib.patches import Patchlegend_elements = [Patch(facecolor='steelblue', alpha=0.7, label='训练+验证集'),Patch(facecolor='orangered', alpha=0.7, label='测试集')]plt.legend(handles=legend_elements, fontsize=11, loc='best')plt.grid(True, alpha=0.3, axis='y')plt.tight_layout()plt.savefig('original_data_sample_errors.png', dpi=300, bbox_inches='tight')print("逐样本误差图已保存至: 'original_data_sample_errors.png'")plt.show()except Exception as e:print(f"\n绘图时出现错误: {e}")import tracebacktraceback.print_exc()print("\n" + "=" * 70)
print("原始CSV数据预测完成!")
print("=" * 70)
print("\n生成的文件:")
print("  1. original_data_predictions.csv - 预测结果CSV文件")
print("  2. original_data_prediction_scatter.png - 预测散点图")
print("  3. original_data_error_distribution.png - 误差分布图")
print("  4. original_data_sample_errors.png - 逐样本误差条形图")
print("=" * 70)# ==================== SHAP 和 PDP 分析 ====================
print("\n" + "=" * 70)
print("开始 SHAP 和 PDP 分析")
print("=" * 70)try:import shapimport matplotlib.pyplot as pltfrom sklearn.inspection import PartialDependenceDisplayimport matplotlib# 设置中文字体matplotlib.rcParams['font.sans-serif'] = ['SimHei']matplotlib.rcParams['axes.unicode_minus'] = False# 11. 训练一个完整的模型用于SHAP和PDP分析print("\n训练完整模型用于可解释性分析...")# 使用增强后的全部训练验证集scaler_full = StandardScaler()X_train_val_scaled = scaler_full.fit_transform(X_train_val_aug)X_test_scaled_full = scaler_full.transform(X_test)# 训练一个随机森林模型(选择RF因为它效果好且SHAP支持较好)model_for_shap = RandomForestRegressor(n_estimators=200,max_depth=15,random_state=42,n_jobs=-1)model_for_shap.fit(X_train_val_scaled, y_train_val_aug)# 在测试集上评估y_pred_shap = model_for_shap.predict(X_test_scaled_full)shap_model_r2 = r2_score(y_test, y_pred_shap)shap_model_rmse = np.sqrt(mean_squared_error(y_test, y_pred_shap))print(f"用于分析的模型 - 测试集 R²: {shap_model_r2:.6f}, RMSE: {shap_model_rmse:.6f}")# ==================== SHAP 分析 ====================print("\n" + "-" * 70)print("1. 计算 SHAP 值...")print("-" * 70)# 创建SHAP解释器(使用TreeExplainer for 随机森林)explainer = shap.TreeExplainer(model_for_shap)# 计算测试集的SHAP值shap_values_test = explainer.shap_values(X_test_scaled_full)# 为了更好的可视化,也计算训练集的一个子集的SHAP值# 从增强后的训练集中随机抽样100个样本sample_size = min(100, len(X_train_val_scaled))sample_indices = np.random.choice(len(X_train_val_scaled), sample_size, replace=False)X_sample = X_train_val_scaled[sample_indices]shap_values_sample = explainer.shap_values(X_sample)print(f"已计算 {len(X_test_scaled_full)} 个测试样本和 {sample_size} 个训练样本的SHAP值")# ==================== SHAP 可视化 ====================# 1. Summary Plot (汇总图 - 蜂群图)print("\n生成 SHAP Summary Plot (蜂群图)...")plt.figure(figsize=(12, 8))shap.summary_plot(shap_values_sample,X_sample,feature_names=X.columns.tolist(),show=False)plt.title('SHAP Summary Plot - 特征重要性与影响', fontsize=14, pad=20)plt.tight_layout()plt.savefig('shap_summary_plot.png', dpi=300, bbox_inches='tight')print("已保存: shap_summary_plot.png")plt.close()# 2. Summary Plot (Bar - 特征重要性条形图)print("\n生成 SHAP Feature Importance Bar Plot...")plt.figure(figsize=(12, 8))shap.summary_plot(shap_values_sample,X_sample,feature_names=X.columns.tolist(),plot_type="bar",show=False)plt.title('SHAP Feature Importance - 特征重要性排序', fontsize=14, pad=20)plt.tight_layout()plt.savefig('shap_feature_importance.png', dpi=300, bbox_inches='tight')print("已保存: shap_feature_importance.png")plt.close()# 3. Waterfall Plot (针对第一个测试样本)print("\n生成 SHAP Waterfall Plot (第一个测试样本)...")plt.figure(figsize=(12, 10))shap.plots.waterfall(shap.Explanation(values=shap_values_test[0],base_values=explainer.expected_value,data=X_test_scaled_full[0],feature_names=X.columns.tolist()),show=False)plt.title('SHAP Waterfall Plot - 第一个测试样本的预测解释', fontsize=14, pad=20)plt.tight_layout()plt.savefig('shap_waterfall_plot_sample1.png', dpi=300, bbox_inches='tight')print("已保存: shap_waterfall_plot_sample1.png")plt.close()# 4. Dependence Plot (针对最重要的3个特征)print("\n生成 SHAP Dependence Plots (前3个重要特征)...")# 计算特征重要性feature_importance = np.abs(shap_values_sample).mean(axis=0)top_features_idx = np.argsort(feature_importance)[-3:][::-1]top_features = [X.columns[idx] for idx in top_features_idx]for i, feature_idx in enumerate(top_features_idx):feature_name = X.columns[feature_idx]plt.figure(figsize=(10, 6))shap.dependence_plot(feature_idx,shap_values_sample,X_sample,feature_names=X.columns.tolist(),show=False)plt.title(f'SHAP Dependence Plot - {feature_name}', fontsize=14, pad=20)plt.tight_layout()plt.savefig(f'shap_dependence_plot_feature_{i + 1}_{feature_name}.png',dpi=300, bbox_inches='tight')print(f"已保存: shap_dependence_plot_feature_{i + 1}_{feature_name}.png")plt.close()# 5. Force Plot (针对第一个测试样本 - 保存为图片)print("\n生成 SHAP Force Plot (第一个测试样本)...")# 对于单个样本的force plotshap.force_plot(explainer.expected_value,shap_values_test[0],X_test_scaled_full[0],feature_names=X.columns.tolist(),matplotlib=True,show=False)plt.title('SHAP Force Plot - 第一个测试样本', fontsize=14, pad=20)plt.tight_layout()plt.savefig('shap_force_plot_sample1.png', dpi=300, bbox_inches='tight')print("已保存: shap_force_plot_sample1.png")plt.close()# 6. SHAP Values Heatmap (测试集所有样本)print("\n生成 SHAP Values Heatmap...")plt.figure(figsize=(14, 8))# 按SHAP值的绝对值排序特征feature_order = np.argsort(np.abs(shap_values_test).mean(axis=0))[::-1]# 创建热图shap_values_sorted = shap_values_test[:, feature_order]feature_names_sorted = [X.columns[i] for i in feature_order]plt.imshow(shap_values_sorted.T, aspect='auto', cmap='RdBu_r',interpolation='nearest')plt.colorbar(label='SHAP值')plt.yticks(range(len(feature_names_sorted)), feature_names_sorted)plt.xlabel('测试样本索引', fontsize=12)plt.ylabel('特征', fontsize=12)plt.title('SHAP Values Heatmap - 测试集所有样本', fontsize=14, pad=20)plt.tight_layout()plt.savefig('shap_heatmap.png', dpi=300, bbox_inches='tight')print("已保存: shap_heatmap.png")plt.close()# ==================== PDP 分析 ====================print("\n" + "-" * 70)print("2. 生成 Partial Dependence Plots (PDP)...")print("-" * 70)# 获取前6个最重要的特征top_6_features_idx = np.argsort(feature_importance)[-6:][::-1]top_6_features = [X.columns[idx] for idx in top_6_features_idx]print(f"生成前6个重要特征的PDP图: {top_6_features}")# 单特征PDP图 (2x3网格)fig, axes = plt.subplots(2, 3, figsize=(18, 12))axes = axes.ravel()for i, feature_idx in enumerate(top_6_features_idx):feature_name = X.columns[feature_idx]display = PartialDependenceDisplay.from_estimator(model_for_shap,X_train_val_scaled,[feature_idx],feature_names=X.columns.tolist(),ax=axes[i],grid_resolution=50)axes[i].set_title(f'PDP - {feature_name}', fontsize=12)plt.suptitle('Partial Dependence Plots - 前6个重要特征', fontsize=16, y=1.02)plt.tight_layout()plt.savefig('pdp_top6_features.png', dpi=300, bbox_inches='tight')print("已保存: pdp_top6_features.png")plt.close()# 生成单个特征的详细PDP图print("\n生成单个特征的详细PDP图...")for i, feature_idx in enumerate(top_6_features_idx[:3]):  # 只为前3个特征生成详细图feature_name = X.columns[feature_idx]fig, ax = plt.subplots(figsize=(10, 6))display = PartialDependenceDisplay.from_estimator(model_for_shap,X_train_val_scaled,[feature_idx],feature_names=X.columns.tolist(),ax=ax,grid_resolution=100)ax.set_title(f'Partial Dependence Plot - {feature_name}', fontsize=14)ax.set_xlabel(feature_name, fontsize=12)ax.set_ylabel('部分依赖', fontsize=12)plt.tight_layout()plt.savefig(f'pdp_detailed_feature_{i + 1}_{feature_name}.png',dpi=300, bbox_inches='tight')print(f"已保存: pdp_detailed_feature_{i + 1}_{feature_name}.png")plt.close()# 2D PDP (特征交互图) - 选择前2个最重要的特征print("\n生成 2D PDP (特征交互图)...")if len(top_6_features_idx) >= 2:feature_pairs = [(top_6_features_idx[0], top_6_features_idx[1]),(top_6_features_idx[0], top_6_features_idx[2]) if len(top_6_features_idx) > 2 else None,]for pair_idx, pair in enumerate(feature_pairs):if pair is None:continuefeature1_name = X.columns[pair[0]]feature2_name = X.columns[pair[1]]fig, ax = plt.subplots(figsize=(10, 8))display = PartialDependenceDisplay.from_estimator(model_for_shap,X_train_val_scaled,[pair],feature_names=X.columns.tolist(),ax=ax,grid_resolution=30)ax.set_title(f'2D PDP - {feature1_name} vs {feature2_name}', fontsize=14)plt.tight_layout()plt.savefig(f'pdp_2d_interaction_{pair_idx + 1}.png',dpi=300, bbox_inches='tight')print(f"已保存: pdp_2d_interaction_{pair_idx + 1}.png")plt.close()# ==================== 输出特征重要性摘要 ====================print("\n" + "=" * 70)print("特征重要性摘要 (基于SHAP值)")print("=" * 70)# 创建特征重要性DataFramefeature_importance_df = pd.DataFrame({'特征': X.columns,'SHAP重要性': feature_importance,'RF特征重要性': model_for_shap.feature_importances_})feature_importance_df = feature_importance_df.sort_values('SHAP重要性', ascending=False)print("\n前10个最重要的特征:")print(feature_importance_df.head(10).to_string(index=False))# 保存特征重要性feature_importance_df.to_csv('feature_importance_analysis.csv',index=False, encoding='utf-8-sig')print("\n特征重要性已保存至 'feature_importance_analysis.csv'")print("\n" + "=" * 70)print("SHAP 和 PDP 分析完成!")print("=" * 70)print("\n生成的图片文件:")print("  - shap_summary_plot.png (SHAP汇总图-蜂群图)")print("  - shap_feature_importance.png (特征重要性条形图)")print("  - shap_waterfall_plot_sample1.png (瀑布图-样本1)")print("  - shap_dependence_plot_feature_*.png (依赖图)")print("  - shap_force_plot_sample1.png (力图-样本1)")print("  - shap_heatmap.png (SHAP值热图)")print("  - pdp_top6_features.png (PDP-前6特征)")print("  - pdp_detailed_feature_*.png (详细PDP图)")print("  - pdp_2d_interaction_*.png (2D特征交互图)")except ImportError as e:print(f"\n错误: 缺少必要的库: {e}")print("请安装: pip install shap")
except Exception as e:print(f"\n分析过程中出现错误: {e}")import tracebacktraceback.print_exc()
http://www.dtcms.com/a/466736.html

相关文章:

  • JP4-8-MyLesson前台前端(二)
  • 做js题目的网站上海网站建设公司推荐排名
  • 网站建设效益分析代运营合同模板
  • 京东商品关键字搜索接口深度开发:从精准检索到商业机会挖掘的技术实现
  • 怎么看网站开发的发展wordpress能开发app
  • 项目学习总结:LVGL图形参数动态变化、开发板的GDB调试、sqlite3移植、MQTT协议、心跳包
  • 统一高效图像生成与编辑!百度新加坡国立提出Query-Kontext,多项任务“反杀”专用模型
  • 网站建设要那些东西适合个人开店的外贸平台
  • window安装wsl
  • 命题逻辑基础,形式系统,定理证明(二)
  • 如何利用数字源表进行纳米材料电特性表征?
  • 网络层协议之ICMP协议
  • 怎样做自己的网站钻钱宁波seo怎么选
  • skynet debug_console控制台中debug指令使用
  • 做棋盘游戏辅助的网站python和php做网站
  • 如何查看局域网内IP冲突问题?如何查看局域网IP环绕问题?arp -a命令如何使用?
  • 网站建设成都市南京做网站公司哪家好
  • 做电影视频网站赚钱嘛网站制作公司杭州
  • 建站平台哪个最好网站建设需要什么技术
  • 网页制作与网站建设实战大全pdf做网站的分辨率要多大
  • 主动学习:用“聪明提问”提升模型效率的迭代艺术
  • 数据链路层协议之MSTP协议
  • Debezium日常分享系列之:Debezium 3.3.0.Final发布
  • 网站的换肤功能怎么做哪个网站可以做验证码兼职
  • wordpress建站要钱吗中小型网站建设价格
  • gitee中的一些开源项目整理20251010
  • [JS]面试八股文
  • Linux中的进程监控,top界面解析
  • 构建一个属于组件的组件库
  • 【Linux笔记】网络部分——socket 编程 TCP实现多台虚拟机使用指令访问云服务器