这期主要就是对机器学习的结果查看,主要是用shap进行模型特征重要性的查看。

后面还有一个机器学习的 LIME,他的作用是跟shap差不多的,但是包含的含义还是有一点区别的

6: 最终模型评估

在找到最佳模型后,我们需要在从未参与过训练和调优的**测试集**上进行最后一次评估。这能反映出模型在真实世界新数据上的表现(泛化能力)。

# ===================================================================
# 6. 最终模型评估
# ===================================================================
print("\n--- 6. 最终模型评估 ---") # 打印阶段标题。
y_pred = final_model.predict(X_test) # 使用调优后的最终模型对测试集进行预测。

# --- 6.1 性能指标 ---
mae = mean_absolute_error(y_test, y_pred) # 计算平均绝对误差(MAE)。
mse = mean_squared_error(y_test, y_pred) # 计算均方误差(MSE)。
r2 = r2_score(y_test, y_pred) # 计算R²分数。

print(f"测试集 MAE: {mae:.4f}") # 打印MAE。
print(f"测试集 MSE: {mse:.4f}") # 打印MSE。
print(f"测试集 R²: {r2:.4f}") # 打印R²。

# --- 6.2 预测值 vs 真实值散点图 ---
plt.figure(figsize=(10, 8)) # 创建一个10x8英寸的图形窗口。
plt.scatter(y_test, y_pred, alpha=0.6, s=50) # 绘制真实值(y_test)与预测值(y_pred)的散点图。
plt.plot([y_test.min(), y_test.max()], [y_test.min(), y_test.max()], 'r--', lw=2) # 绘制一条y=x的红色虚线作为参考。
plt.xlabel('Actual Values') # 设置X轴标签。
plt.ylabel('Predicted Values') # 设置Y轴标签。
plt.title(f'LightGBM Final Model: Actual vs Predicted (R² = {r2:.4f})') # 设置标题,并动态显示R²分数。
plt.axis('equal') # 设置x,y轴的刻度单位长度相等。
plt.axis('square') # 设置绘图区域为正方形。
# 添加统计信息文本框
plt.text(0.05, 0.95, f'MAE: {mae:.4f}\nMSE: {mse:.4f}\nR²: {r2:.4f}', # 在图的左上角添加性能指标文本。
transform=plt.gca().transAxes, verticalalignment='top', # 设置文本框的位置和对齐方式。
bbox=dict(boxstyle='round', facecolor='wheat', alpha=0.8)) # 设置文本框的样式。
plt.grid(True, alpha=0.3) # 添加半透明的网格。
plt.savefig('LightGBM_真实值vs预测值.png', dpi=300) # 保存图像。
plt.show() # 显示图像。

# --- 6.3 残差分析 ---
residuals = y_test - y_pred # 计算残差(真实值 - 预测值)。
plt.figure(figsize=(15, 5)) # 创建一个15x5英寸的图形窗口。

plt.subplot(1, 3, 1) # 在1行3列的网格中选择第1个位置。
sns.scatterplot(x=y_pred, y=residuals) # 绘制预测值与残差的散点图。
plt.axhline(y=0, color='r', linestyle='--') # 绘制一条y=0的水平参考线。
plt.xlabel('Predicted Values') # 设置X轴标签。
plt.ylabel('Residuals') # 设置Y轴标签。
plt.title('Residual Scatter Plot') # 设置标题。

plt.subplot(1, 3, 2) # 在1行3列的网格中选择第2个位置。
sns.histplot(residuals, kde=True, bins=30) # 绘制残差的直方图。
plt.title('Residual Distribution') # 设置标题。
plt.xlabel('Residual') # 设置X轴标签。

plt.subplot(1, 3, 3) # 在1行3列的网格中选择第3个位置。
from scipy import stats # 导入scipy.stats模块,用于统计分析。
stats.probplot(residuals, dist="norm", plot=plt) # 绘制残差的正态概率图(Q-Q图)。
plt.title('Residual Q-Q Plot') # 设置标题。
plt.tight_layout() # 自动调整布局。
plt.savefig('LightGBM_残差分析.png', dpi=300) # 保存图像。
plt.show() # 显示图像。

7、计算 SHAP 值 (核心计算步骤)

#这是所有 SHAP 可视化分析的基础。在此阶段,我们为测试集中的每一个样本、每一个特征计算其对应的 SHAP 值。

# --- 7.1 计算SHAP值 ---
print("正在计算SHAP值...") # 打印提示信息,告知用户正在进行SHAP值的计算。
explainer = shap.TreeExplainer(final_model) # 创建一个针对树模型的SHAP解释器。`final_model`是我们已经训练好的模型(如XGBoost, LightGBM等)。
shap_values = explainer.shap_values(X_test) # 使用解释器计算测试集`X_test`中每个样本的SHAP值。

print(f"SHAP值形状: {shap_values.shape}") # 打印输出SHAP值数组的形状,方便我们理解其结构。
print(f"基准值(expected_value): {explainer.expected_value:.4f}") # 打印解释器的基准值,即模型在整个数据集上的平均预测输出。

# --- 7.2 SHAP摘要图系列 ---
fig, axes = plt.subplots(2, 2, figsize=(20, 16))  # 创建2x2子图网格

# 1. Beeswarm plot (蜂群图)
plt.subplot(2, 2, 1)
shap.summary_plot(
    shap_values, 
    X_test, 
    plot_type="dot", 
    show=False,
    title=""  # 关闭SHAP默认标题
)
plt.title("SHAP Summary Plot (Beeswarm)", fontsize=14, pad=20)  # pad增加标题与图的间距
plt.xticks(rotation=45, ha="right", fontsize=10)  # 旋转特征标签

# 2. Bar plot (条形图)
plt.subplot(2, 2, 2)
shap.summary_plot(
    shap_values, 
    X_test, 
    plot_type="bar", 
    show=False,
    title=""  # 关闭SHAP默认标题
)
plt.title("SHAP Global Feature Importance", fontsize=14, pad=20)
plt.xticks(rotation=45, ha="right", fontsize=10)

# 3. Violin plot (小提琴图)
plt.subplot(2, 2, 3)
shap.summary_plot(
    shap_values, 
    X_test, 
    plot_type="violin", 
    show=False,
    title=""  # 关闭SHAP默认标题
)
plt.title("SHAP Violin Plot", fontsize=14, pad=20)
plt.xticks(rotation=45, ha="right", fontsize=10)

# 4. Heatmap for top samples (热力图)
plt.subplot(2, 2, 4)
shap.plots.heatmap(
    shap.Explanation(
        values=shap_values[:20],  # 前20个样本
        base_values=explainer.expected_value,
        data=X_test.values[:20],
        feature_names=X_test.columns.tolist()
    ),
    show=False
    
)
plt.title("SHAP Heatmap (Top 20 Samples)", fontsize=14, pad=20)

plt.tight_layout()  # 自动调整布局
plt.savefig('SHAP_综合分析.png', dpi=300, bbox_inches='tight')  # 保存图像
plt.show()  # 显示图像

8、SHAP 依赖图 (特征交互分析)

依赖图展示了单个特征的取值如何影响其自身的 SHAP 值,并且能够通过颜色揭示它与其他特征的**交互效应**。

# --- 7.3 SHAP依赖图(交互效应分析)---
print("正在生成SHAP依赖图...")
import numpy as np
import shap
import matplotlib.pyplot as plt

# 计算每个特征的全局重要性(平均绝对SHAP值)
importances = np.abs(shap_values).mean(0)
n_top = 4  # 要分析的Top特征数

# 优化1:判断特征总数是否足够,避免报错
if len(importances) < n_top:
    top_features_indices = np.argsort(importances)[-len(importances):]
    print(f"特征总数不足{ n_top }个,将分析所有{len(importances)}个特征")
else:
    top_features_indices = np.argsort(importances)[-n_top:]

top_features = X_test.columns[top_features_indices].tolist()  # 获取Top特征名称

# 优化2:处理特征名特殊字符(避免保存失败)
def clean_feature_name(name):
    return str(name).replace(" ", "_").replace("(", "").replace(")", "").replace("/", "_")

for feature in top_features:
    clean_name = clean_feature_name(feature)  # 清洗特征名
    plt.figure(figsize=(12, 7))  # 适当放大图形
    # 绘制依赖图,保留自动交互特征识别
    shap.dependence_plot(
        feature, 
        shap_values, 
        X_test, 
        interaction_index="auto", 
        show=False,
        dot_size=30,  # 调整点大小,避免拥挤
        alpha=0.7     # 调整透明度,突出分布
    )
    # 优化3:放大标题和标签字体
    plt.title(f'SHAP Dependence Plot: {feature}', fontsize=16, pad=20)
    plt.xlabel(feature, fontsize=12)
    plt.ylabel('SHAP Value', fontsize=12)
    plt.xticks(fontsize=10)
    plt.yticks(fontsize=10)
    plt.tight_layout()
    # 用清洗后的名称保存文件
    plt.savefig(f'SHAP_依赖图_{clean_name}.png', dpi=300, bbox_inches='tight')
    plt.show()  # 若批量生成,可注释此行,仅保留保存

print("SHAP依赖图生成完成!")

9、SHAP 瀑布图 (单个样本预测分解)

瀑布图是解释**单个预测**的绝佳工具。它清晰地展示了从模型的基准值开始,每个特征是如何一步步将预测推向最终结果的。

# --- 7.4 SHAP瀑布图(单个预测详细分解)---
print("正在生成SHAP瀑布图...")  # 打印提示信息。
fig, axes = plt.subplots(1, 2, figsize=(24, 8))  # 创建一个1x2的子图网格,用于并排展示两个样本的瀑布图。

for i, idx in enumerate([0, len(X_test) // 2]):  # 选择第一个样本(索引0)和中间位置的样本进行分析。
    plt.subplot(1, 2, i + 1)  # 选择当前的子图位置。
    shap.plots.waterfall(shap.Explanation(values=shap_values[idx],  # 提取该样本的所有特征的SHAP值。
                                          base_values=explainer.expected_value,  # 传入模型的基准值。
                                          data=X_test.iloc[idx].values,  # 提取该样本的原始特征值。
                                          feature_names=X_test.columns.tolist()),  # 传入特征名称列表。
                         show=False)  # 绘制瀑布图。
    plt.title(f'SHAP Waterfall Plot - Sample {idx if idx >= 0 else len(X_test) + idx}')  # 设置子图标题,显示样本索引。
plt.tight_layout()  # 自动调整布局。
plt.savefig('SHAP_瀑布图.png', dpi=500, bbox_inches='tight')  # 保存图像。
plt.show()  # 显示图像。

10、SHAP 力图 (单个样本预测的另一种视角)

力图(Force Plot)提供了另一种紧凑的方式来可视化单个样本的预测分解,它形象地展示了各个特征是“推力”还是“拉力”。

代码及注释

# --- 7.5 SHAP力图 ---
print("正在生成SHAP力图...")  # 打印提示信息。
shap.initjs()  # 初始化JavaScript环境,用于在Jupyter等环境中渲染交互式力图(在此处非必需,但为好习惯)。
# 选择几个有代表性的样本
sample_indices = [0, len(X_test) // 4, len(X_test) // 2, 3 * len(X_test) // 4, -1]  # 选择分布在测试集中的5个样本。
for i, idx in enumerate(sample_indices):  # 遍历这5个样本。
    force_plot = shap.force_plot(explainer.expected_value,  # 传入基准值。
                                 shap_values[idx],  # 传入当前样本的SHAP值。
                                 X_test.iloc[idx],  # 传入当前样本的原始特征值。
                                 matplotlib=True, show=False)  # 指定使用Matplotlib后端绘图,并暂时不显示。
    plt.title(f"SHAP Force Plot - Sample {idx if idx >= 0 else len(X_test) + idx}")  # 为力图添加标题。
    plt.savefig(f'SHAP_力图_样本_{i}.png', bbox_inches='tight', dpi=300)  # 将每个力图保存为单独的文件。
    plt.show()  # 显示当前力图。

11、SHAP 部分依赖图 (PDP/ICE)

这部分代码使用了 Scikit-learn 的功能来绘制部分依赖图(PDP)和个体条件期望图(ICE)。虽然它们不直接来自 `shap` 库,但它们与 SHAP 提供的模型解释功能相辅相成,经常一起使用。

*   **PDP (Partial Dependence Plot)**: 显示一个特征对模型**平均**预测结果的影响。

*   **ICE (Individual Conditional Expectation)**: 显示一个特征对**每个单独样本**预测结果的影响。

# --- 7.6 SHAP部分依赖图 ---
print("正在生成SHAP部分依赖图...")  # 打印提示信息。
# 选择最重要的特征进行PDP分析
# 注意:这部分我们复用之前计算出的 top_features
from sklearn.inspection import PartialDependenceDisplay  # 从sklearn.inspection导入部分依赖图的绘图工具

top_pdp_features = top_features[:2]  # 从最重要的特征中选择前两个进行PDP分析。
for feature in top_pdp_features:  # 遍历这两个特征。
    plt.figure(figsize=(10, 6))  # 创建新的图形窗口。
    PartialDependenceDisplay.from_estimator(  # 使用from_estimator方法从模型直接创建PDP图。
        final_model,  # 我们训练好的模型。
        X_train,  # PDP/ICE通常在训练集上计算,以反映模型的学习行为。
        features=[feature],  # 指定要分析的特征。
        kind='average',  # 'average'表示只绘制平均的部分依赖图(PDP)。
        grid_resolution=50,  # 在特征值范围内取50个点进行计算。
        ax=plt.gca()  # 在当前Matplotlib坐标轴上绘图。
    )
    plt.title(f'Partial Dependence Plot: {feature}')  # 设置标题。
    plt.xlabel(feature)  # 设置X轴标签。
    plt.ylabel('Partial Dependence')  # 设置Y轴标签。
    plt.grid(True, alpha=0.3)  # 添加网格线。
    plt.tight_layout()  # 自动调整布局。
    plt.savefig(f'SHAP_PDP_{feature}.png', dpi=300, bbox_inches='tight')  # 保存PDP图。
    plt.show()  # 显示PDP图。

# 同时绘制部分依赖图和个体条件期望图
print("正在绘制部分依赖图和个体条件期望图...")  # 打印提示信息。
for feature in top_pdp_features:  # 再次遍历这两个特征。
    fig, ax = plt.subplots(figsize=(10, 6))  # 创建新的图形和坐标轴对象。
    PartialDependenceDisplay.from_estimator(  # 再次调用绘图函数。
        final_model,  # 我们训练好的模型。
        X_train,  # 同样使用训练集。
        features=[feature],  # 指定要分析的特征。
        kind='both',  # 'both'表示同时绘制PDP(平均线)和ICE(个体线)。
        grid_resolution=50,  # 同样设置网格分辨率。
        ax=ax  # 在指定的坐标轴上绘图。
    )
    ax.set_title(f'Partial Dependence and ICE Plot: {feature}')  # 设置标题。
    ax.set_xlabel(feature)  # 设置X轴标签。
    ax.set_ylabel('Partial Dependence / ICE')  # 设置Y轴标签。
    ax.grid(True, alpha=0.3)  # 添加网格线。
    plt.tight_layout()  # 自动调整布局。
    plt.savefig(f'SHAP_PDP_ICE_{feature}.png', dpi=300, bbox_inches='tight')  # 保存PDP/ICE图。
    plt.show()  # 显示PDP/ICE图。

其中重点通过SHAP(SHapley Additive exPlanations)方法开展模型特征重要性分析,该方法基于博弈论原理,能量化每个特征对预测结果的贡献度与影响方向,不仅可通过SHAP摘要图(Summary Plot)直观呈现全局特征重要性排序,清晰识别对模型预测起关键作用的核心变量,还能借助依赖图(Dependence Plot)展示单个特征与目标变量的非线性关系及交互效应,同时通过局部解释图明确特定样本中各特征的正负向影响,既弥补了传统机器学习模型的“黑箱”缺陷,让特征重要性的解读更具科学性与可解释性,也为后续特征筛选、模型优化及结果验证提供了有力支撑,使机器学习结果更贴合实际分析需求。

更多推荐