SHAP图介绍
参考帖子:SHAP 可视化解释机器学习模型简介_shap图-CSDN博客
今日作业偏思考类型,有一定难度
- 参考上述文档补全剩余的几个图
- 尝试确定一下shap各个绘图函数对于每一个参数的尺寸要求,如shap.force_plot力图中的数据需要满足什么形状?
- 确定分类问题和回归问题的数据如何才能满足尺寸,分类采取信贷数据集,回归采取单车数据集。
SHAP库的 Shapley 值
SHAP库是用来计算显示特征(输入)对标签(输出)的影响,核心在于 Shapley value。为了计算一个特定特征(比如“特征 A”)对某个预测的贡献(它的 Shapley 值),SHAP 会考虑:
1. 所有可能的特征组合(子集/联盟):从没有特征开始,到包含所有特征。
2. 特征 A 的边际贡献:对于每一个特征组合,比较“包含特征 A 的组合的预测值”与“不包含特征 A 但包含其他相同特征的组合的预测值”之间的差异。这个差异就是特征 A 在这个特定组合下的“边际贡献”。
3. 加权平均: Shapley 值是该特征在所有可能的特征组合中边际贡献的加权平均值。权重确保了分配的公平性。
那举个例子解释Shapley值:想象你是一个足球队教练,队里有5个球员(特征)。现在要评估梅西(特征A)对比赛胜利(预测结果)的贡献。Shapley值的计算就像这样:
- 先看没有梅西时其他球员组合的表现(比如只上C罗+内马尔)
- 再看加上梅西后这个组合的表现(梅西+C罗+内马尔)
- 两者的差值就是梅西在这个组合中的"边际贡献"
- 最后把所有可能的球员组合(共2^5=32种)中梅西的贡献求平均
SHAP库的加性特性
SHAP 的一个重要特性是加性。这意味着:
基准值: 这是模型在整个训练(或背景)数据集上的平均预测输出。可以理解为没有任何特征信息时的“默认”预测。
SHAP 值之和: 对于任何一个样本的预测,所有特征的 SHAP 值加起来,再加上基准值,就精确地等于该样本的模型预测值:模型预测值(样本 X) = 基准值 + SHAP值(特征1) + SHAP值(特征2) + ... + SHAP值(特征N)
在用上面的例子解释一下:
假设你要评估一支足球队(模型)的战斗力(预测得分),队里有5名球星(特征):
- 基准值(60分):这是球队没有明星球员时的基础战斗力(相当于训练数据的平均预测值)
- 球星贡献(SHAP值):每个球员的贡献值可正可负(范戴克专注防守会降低进攻评分)
- 梅西:+25分
- C罗:+20分
- 姆巴佩:+15分
- 德布劳内:+10分
- 范戴克:-5分(防守型球员可能略微降低进攻评分)
- 最终战斗力 = 基准值 + 所有球星贡献 = 60 + 25 + 20 + 15 + 10 - 5 = 125分
每个球员的贡献值可正可负(范戴克专注防守会降低进攻评分),即使换替补球员组合,这个加法规则也永远成立
'shap_values' 数组
对于回归问题:
- 模型只有一个输出
- 对 'n_samples' 个样本中的每一个样本都要计算 'n_features' 个特征各自的 SHAP 值
- 这就自然形成了形状为 '(n_samples, n_features)' 的数组。 'shap_values[i, j]' 代表第 `i` 个样本的第 `j` 个特征对该样本预测值的贡献
对于分类问题:
- 模型通常为每个类别输出一个分数或概率
- SHAP 需要解释模型是如何得到每个类别的分数的,对 'n_samples' 个样本中的每一个样本,分别为每个类别计算 'n_features' 个特征各自的 SHAP 值
-
最常见的组织方式是返回一个列表,列表长度等于类别数。列表的第 `k` 个元素是一个 '(n_samples, n_features)' 的数组,表示所有样本的所有特征对预测类别 `k` 的贡献。 `shap_values[k, i, j]` 代表第 `i` 个样本的第 `j` 个特征对该样本预测类别 `k`的贡献。
总之,分类问题和回归问题输出的 shap_values 的形状不同。
- 分类问题:shap_values.shape = (类别数量, 样本数量, 特征数量) 多了个类别
- 回归问题:shap_values.shape = (样本数量, 特征数量)
要点:这里提一下二分类问题,一个样本要么属于类别 A,要么属于类别 B,预测概率之和为 1。具体来说,如果某个特征对预测为类别 A 有正向贡献,那么它对预测为类别 B 就会有反向贡献,二者相互关联。所以,一般来说只需要计算一组 SHAP 值来表示特征对其中一个类别的贡献,就可以推断出对另一个类别的贡献,是个二维数组。但这里还是用的三维数组哈
# 导入相关的库之类的不提了,选用的之前处理好的数据,不重复说了
# --- 1. 默认参数的随机森林 ---
import time
start_time = time.time() # 记录开始时间
rf_model = RandomForestClassifier(random_state=42)
rf_model.fit(X_train, y_train) # 在训练集上训练
rf_pred = rf_model.predict(X_test) # 在测试集上预测
end_time = time.time() # 记录结束时间
print(f"训练与预测耗时: {end_time - start_time:.4f} 秒")
print("\n默认随机森林 在测试集上的分类报告:")
print(classification_report(y_test, rf_pred))
print("默认随机森林 在测试集上的混淆矩阵:")
print(confusion_matrix(y_test, rf_pred))
# --- 2. 用SHAP进行模型的特征解释 ---
import shap
# 创建 SHAP 解释器
explainer = shap.TreeExplainer(rf_model)
# 计算 SHAP 值(基于测试集),对于二分类问题,这里shap_values是一个numpy数组
# 这里大家先知道这是个numpy数组即可,我们后面学习完numpy在来回头解读这个值
shap_values = explainer.shap_values(X_test) # 这个计算很耗时
# 搞清楚数据维度
print("shap_values shape:", shap_values.shape)
print("shap_values[0] shape:", shap_values[0].shape) # 表示第一个样本的SHAP值,形状为(特征数量,2)
print("shap_values[:, :, 0] shape:", shap_values[:, :, 0].shape) # 这里使用了切片操作 [:, :, 0],意味着选取所有样本、所有特征在第三维上索引为0的元素,即分类为0
print("X_test shape:", X_test.shape)
结果如下,可以详细比对一下看看数据维度
shap_values shape: (1500, 31, 2)
shap_values[0] shape: (31, 2)
shap_values[:, :, 0] shape: (1500, 31)
X_test shape: (1500, 31)
开始用SHAP库进行可视化:
# --- 1. SHAP 特征重要性条形图 (Summary Plot - Bar) ---
print("--- 1. SHAP 特征重要性条形图 ---")
shap.summary_plot(shap_values[:, :, 0], X_test, plot_type="bar",show=False) # 这里的show=False表示不直接显示图形,这样可以继续用plt来修改元素,不然就直接输出了
plt.title("SHAP Feature Importance (Bar Plot)")
plt.show()
# --- 2. SHAP 特征重要性蜂巢图 (Summary Plot - Violin) ---
print("--- 2. SHAP 特征重要性蜂巢图 ---")
shap.summary_plot(shap_values[:, :, 0], X_test,plot_type="violin",show=False,max_display=10) # 这里的show=False表示不直接显示图形,这样可以继续用plt来修改元素,不然就直接输出了
plt.title("SHAP Feature Importance (Violin Plot)")
plt.show()
# 注意下上面几个参数,plot_type可以是bar和violin,max_display表示显示前多少个特征,默认是20个
收获心得:
最麻烦的是数据维度结构要搞清楚,神经网络里面这种类似的更多,头疼捏