机器学习——LightGBM模型的SHAP和LIME可解释性分析对比详解完整示例 --python(2)
这期主要就是对机器学习的结果查看,主要是用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)展示单个特征与目标变量的非线性关系及交互效应,同时通过局部解释图明确特定样本中各特征的正负向影响,既弥补了传统机器学习模型的“黑箱”缺陷,让特征重要性的解读更具科学性与可解释性,也为后续特征筛选、模型优化及结果验证提供了有力支撑,使机器学习结果更贴合实际分析需求。
更多推荐
所有评论(0)