汇总SHAP值以获得更详细的模型解释
我们从学习排列重要性和部分依赖图开始,以显示学习后的模型的内容。
然后我们学习了SHAP值来分解单个预测的组成部分。
现在我们将对SHAP值展开讨论,看看聚合许多SHAP值如何为排列重要性图和部分依赖图提供更详细的替代方案。
Shap 值显示了给定特征对我们预测的改变程度(与我们在该特征的某个基线值上进行预测相比)。
例如,考虑一个超简单的模型:
y = 4 ∗ x 1 + 2 ∗ x 2 y = 4* x1 + 2∗x2 y=4∗x1+2∗x2
如果 x 1 x1 x1 取值2,而不是基线值0,这样 x 1 x1 x1的SHAP值
应该是8(4乘以2)
我们在实践中使用的复杂模型很难计算这些。但通过一些聪明的算法,shap值允许我们将任何预测分解为每个特征值的效果总和,生成如下图:
除了每个预测的细分之外,Shap库还提供了Shap值组的可视化功能。我们将重点关注其中的两种可视化。这些可视化在概念上与排列重要性图和部分依赖图相似。因此,前面练习中的多个线索将在这里结合在一起。
排列重要性非常重要,因为它创建了简单的数字度量来查看哪些特征对模型重要。这有助于我们轻松地比较特性,并且您可以向非技术人员展示结果图。
但它并没有告诉你每个特性的重要性。如果一个特征具有中等排列重要性,那可能意味着它具有中等排列重要性
SHAP总结图可以让我们鸟瞰特征的重要性和驱动因素。我们将浏览一个足球数据的示例图:
有些东西你应该能够很容易地挑选出来:
Red
和Yellow & Red
特征。Yellow Card
(黄牌)不会影响预测,但有一种极端情况,高数值会导致低得多的预测。Goal Scored
越高,预测越高,得分越低,预测越低您已经看到了加载足球数据的代码:
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
data = pd.read_csv('../input/fifa-2018-match-statistics/FIFA 2018 Statistics.csv')
y = (data['Man of the Match'] == "Yes") # Convert from string "Yes"/"No" to binary
feature_names = [i for i in data.columns if data[i].dtype in [np.int64, np.int64]]
X = data[feature_names]
train_X, val_X, train_y, val_y = train_test_split(X, y, random_state=1)
my_model = RandomForestClassifier(random_state=0).fit(train_X, train_y)
我们使用以下代码获取所有验证数据的SHAP值。它足够短,我们在评论中解释它。
import shap # package used to calculate Shap values
# 创建计算 shap values 对象
explainer = shap.TreeExplainer(my_model)
# 计算 shap values 为画图作准备
# 计算所有val_X的shap values 而不是一行,这样有更多的数据用于作图shap_values = explainer.shap_values(val_X)
# 以索引1 的值来作图
shap.summary_plot(shap_values[1], val_X)
这里的代码并不太复杂。但也有一些需要注意的地方。
True
的SHAP值。这提供了对模型的一个很好的概述,但我们可能想要深入研究单个特性。这就是SHAP依赖性贡献图发挥作用的地方。
我们以前使用部分依赖图来显示单个特征如何影响预测。这些都是深刻的,并且与许多真实的用例相关。另外,只要稍加努力,它们就可以向非技术人员解释清楚。
但还有很多东西他们没有展示出来。例如,效果的分布是怎样的?某一特定值的影响是相当恒定的,还是取决于其他特征的值而变化很大?SHAP依赖性贡献图提供了与PDP相似的解释,但它们添加了更多细节。
先从形状开始,我们一会儿再回来讲颜色。每个点代表一行数据。水平位置是数据集的实际值,垂直位置显示该值对预测的影响。这个曲线向上倾斜的事实表明,你控球越多,模型对赢得本场最佳球员的预测就越高。
这一差异表明,其他特征必须与控球率相互作用。例如,这里我们突出了两个具有相似控球值的点。这个值导致一个预测增加,另一个预测减少。
这表明我们要深入研究相互作用,图中包含了颜色编码来帮助我们做到这一点。虽然主要趋势是向上的,但您可以直观地检查是否因网点颜色而变化。
考虑下面这个非常狭窄的具体例子。
这两点在空间上与上升趋势相去甚远。它们都是紫色的,表示该队进了一球。你可以这样理解:一般来说,拥有球权会增加球队球员赢得奖项的机会。但如果他们只进了一个球,这种趋势就会逆转,如果他们进的球那么少,裁判可能会因为他们控球太多而惩罚他们。
除了这几个异常值之外,颜色表示的相互作用在这里并不是很引人注目。但有时它会突然出现在你面前。
我们用下面的代码得到依赖性贡献图。与summary_plot
唯一不同的行是最后一行。
import shap # package used to calculate Shap values
# Create object that can calculate shap values
explainer = shap.TreeExplainer(my_model)
# calculate shap values. This is what we will plot.
shap_values = explainer.shap_values(X)
# make plot.
shap.dependence_plot('Ball Possession %', shap_values[1], X, interaction_index="Goal Scored")
如果您没有为interaction_index
提供一个参数,Shapley会使用一些逻辑来选择一个可能有趣的参数。
这不需要编写大量代码。但这些技术的诀窍在于批判性地思考结果,而不是编写代码本身。
用一些问题来测试自己,用这些技巧来提升你的技能。
我们再次提供了代码来进行基本的加载、审查和模型构建。运行下面的单元格以设置所有内容:
import numpy as np
import pandas as pd
from sklearn.ensemble import RandomForestRegressor
from sklearn.model_selection import train_test_split
import shap
# Environment Set-Up for feedback system.
from learntools.core import binder
binder.bind(globals())
from learntools.ml_explainability.ex5 import *
print("Setup Complete")
import pandas as pd
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
data = pd.read_csv('../input/hospital-readmissions/train.csv')
y = data.readmitted
base_features = ['number_inpatient', 'num_medications', 'number_diagnoses', 'num_lab_procedures',
'num_procedures', 'time_in_hospital', 'number_outpatient', 'number_emergency',
'gender_Female', 'payer_code_?', 'medical_specialty_?', 'diag_1_428', 'diag_1_414',
'diabetesMed_Yes', 'A1Cresult_None']
# Some versions of shap package error when mixing bools and numerics
X = data[base_features].astype(float)
train_X, val_X, train_y, val_y = train_test_split(X, y, random_state=1)
# For speed, we will calculate shap values on smaller subset of the validation data
small_val_X = val_X.iloc[:150]
my_model = RandomForestClassifier(n_estimators=30, random_state=1).fit(train_X, train_y)
这里用的还是前面用到医院再次入院的数据集
data.describe()
– | time_in_hospital | num_lab_procedures | num_procedures | num_medications | number_outpatient | number_emergency | number_inpatient | number_diagnoses | readmitted |
---|---|---|---|---|---|---|---|---|---|
count | 25000.000000 | 25000.00000 | 25000.000000 | 25000.000000 | 25000.000000 | 25000.000000 | 25000.00000 | 25000.000000 | 25000.000000 |
mean | 4.395640 | 42.96012 | 1.341080 | 15.988440 | 0.365920 | 0.203280 | 0.64300 | 7.420160 | 0.456400 |
std | 2.991165 | 19.76881 | 1.705398 | 8.107743 | 1.224419 | 0.982973 | 1.26286 | 1.940932 | 0.498105 |
min | 1.000000 | 1.00000 | 0.000000 | 1.000000 | 0.000000 | 0.000000 | 0.00000 | 1.000000 | 0.000000 |
25% | 2.000000 | 31.00000 | 0.000000 | 10.000000 | 0.000000 | 0.000000 | 0.00000 | 6.000000 | 0.000000 |
50% | 4.000000 | 44.00000 | 1.000000 | 15.000000 | 0.000000 | 0.000000 | 0.00000 | 8.000000 | 0.000000 |
75% | 6.000000 | 57.00000 | 2.000000 | 20.000000 | 0.000000 | 0.000000 | 1.00000 | 9.000000 | 1.000000 |
max | 14.000000 | 126.00000 | 6.000000 | 81.000000 | 36.000000 | 64.000000 | 21.00000 | 16.000000 | 1.000000 |
前几个问题需要检查每个特征的效果分布,而不仅仅是每个特征的平均效果。运行下面的单元格,以获得shap_values的总结图。运行大约需要20秒。
explainer = shap.TreeExplainer(my_model)
shap_values = explainer.shap_values(small_val_X)
shap.summary_plot(shap_values[1], small_val_X)
以下哪个特征对预测的影响范围更大(即最积极和最消极的影响之间的差异更大)
# 在以下填写变量 'diag_1_428' 或 'payer_code_?'
feature_with_bigger_range_of_effects = ____
# Check your answer
q_1.check()
答案:
feature_with_bigger_range_of_effects = ‘diag_1_428’
你认为效应大小的范围(最小效应和最大效应之间的距离)是一个很好的指标,表明哪个特征具有更高的排列重要性吗?为什么或者为什么不呢?
如果效应大小的范围测量的是与排列重要性不同的东西:对于“在讨论人群中的再入院风险时,模型认为这两个特征中哪一个对我们来说更重要”这个问题,哪个是更好的答案?
在你决定了你的答案之后,运行下面的代码行。
# Check your answer (Run this code cell to receive credit!)
q_2.solution()
结论:
没有。效应范围的宽度不是排列重要性的合理近似值。就此而言,范围的宽度并不能很好地映射到任何直观的“重要性”,因为它可以由几个异常值来确定。然而,如果图表上的所有点彼此之间分布广泛,这是一个合理的迹象,表明排列的重要性很高。由于影响的范围对异常值非常敏感,所以排列重要性是衡量对模型普遍重要的东西的更好方法。
diag_1_428
和payer_code_?
是二进制变量,取值为0或1。
从图表中,你认为哪一个通常会对预测的再入院风险产生更大的影响:
为了节省滚动时间,我们在下面添加了一个单元格来再次绘制图形(这个单元格运行得很快)。
shap.summary_plot(shap_values[1], small_val_X)
# Set following var to "diag_1_428" if changing it to 1 has bigger effect. Else set it to 'payer_code_?'
bigger_effect_when_changed = ____
# Check your answer
q_3.check()
答案:
bigger_effect_when_changed = “diag_1_428”
要获得结论和解释,运行下一行。
结论:
虽然diag_1_428
的大多数SHAP值很小,但少数粉点(变量的高值,对应于具有该诊断的人)具有较大的SHAP值。换句话说,这个变量的粉色点离0很远,让某人拥有更高的(粉色)值会显著增加他们的再入院风险。在现实世界中,这种诊断很罕见,但对患有这种疾病的人来说风险更大。相比之下,payer_code_?
有许多蓝色和粉红色的值,并且两者的SHAP值都与0有意义的不同。但是改变payer_code_?
从0(蓝色)到1(粉红色)的影响可能比更改diag_1_428
的影响要小。
一些特征(如number_inpatient
)在蓝色点和粉色点之间有相当清晰的分隔。num_lab_procedures
等其他变量将蓝色和粉红色的点混杂在一起,尽管SHAP值(或对预测的影响)并不都是0。
您认为您从num_lab_procedures
将蓝色和粉红色的点混在一起的事实中学到了什么? 为了得到答案,运行下面的行来验证您的结论。
结论:
这种混乱表明,有时增加该特征会导致更高的预测,有时会导致更低的预测。换句话说,特征值的高低对预测既有积极的影响,也有消极的影响。对于这种“混乱”的效果,最可能的解释是变量(在本例中为num_lab_procedures
)与其他变量具有交互作用。例如,可能有一些诊断需要进行许多实验室检查,而其他诊断则意味着风险增加。我们还不知道还有什么其他特性与num_lab_procedures
交互,尽管我们可以用SHAP贡献依赖图来研究它。
考虑下面的SHAP贡献依赖性图。
x轴显示feature_of_interest
,点根据other_feature
上色。
feature_of_interest
和other_feature
之间是否存在交互?如果是这样,当other_feature
值高或other_feature
值低时,feature_of_interest
是否对预测有更积极的影响?
当您准备好得到答案时,运行以下代码。
# Check your answer (Run this code cell to receive credit!)
q_5.solution()
结论:
首先,回想一下,SHAP值是对给定特征对预测的影响的估计。因此,如果点从左上角到右下角呈趋势,这意味着低的feature_of_interest
值导致更高的预测。
回到这个图表:
当other_feature
值较高时,Feature_of_interest
向下倾斜。要看到这一点,请将目光集中在粉色点上(other_feature
值较高的地方),并通过这些粉色点想象一条最适合的线。它向下倾斜,表明预测随着feature_of_interest
的增加而下降。
现在把你的眼睛集中在蓝色的点上,想象一下这些点之间的最佳拟合线。它通常是相当平坦的,甚至可能在图的右侧向上弯曲。因此,当other_feature
值较高时,增加feature_of_interest
会对预测产生更积极的影响。
通过运行以下单元格查看重新接收数据的总结图:
shap.summary_plot(shap_values[1], small_val_X)
num_drugs
和num_lab_procedures
都有粉红色和蓝色的点。
除了num_drugs
具有更大的影响(更积极和更消极)之外,很难看出这两个特征在影响再入院风险方面有什么有意义的区别。为每个变量创建SHAP依赖性贡献图,并描述您认为这两个变量对预测影响的不同之处。
提醒一下,这里是您之前看到的用于创建这种类型的图的代码。
shape.dependence_plot(feature_of_interest, shap_values[1], val_X)
回想一下,您的验证数据名为small_val_X
。
# Your code here
____
提示:这里需要填写 ‘num_lab_procedures’ 和 ‘num_medications’ 的
依赖性贡献图。
答案:
shap.dependence_plot(‘num_lab_procedures’, shap_values[1],
small_val_X) shap.dependence_plot(‘num_medications’, shap_values[1],
small_val_X)
粗略地说,num_lab_procedures
看起来像一个没有什么可识别模式的云。它在任何一点都不会陡然向上或向下倾斜。很难说我们从那个情节中学到了什么。同时,这些值并不都非常接近于0。所以这个模型似乎认为这是一个相关的特征。一个潜在的下一步将是通过给它涂上不同的其他特征来搜索交互来探索更多。
另一方面,num_drugs
明显向上倾斜,直到值大约为20,然后又向下倾斜。如果没有更多的医学背景,这似乎是一个令人惊讶的现象……你可以做一些探索看看这些病人是否在其他特征上也有不寻常的价值。但下一步最好是与领域专家(在本例中是医生)讨论这一现象。
就是这样!机器学习模型不应该再像黑盒子一样,因为你有工具来检查它们,并了解它们对世界的了解。
这是调试模型、建立信任和学习解释以做出更好决策的优秀技能。这些技术彻底改变了我做数据科学的方式,我希望它们也能对你产生同样的影响。
真正的数据科学包含探索的元素。我希望你能找到一个有趣的数据集来试用这些技术(Kaggle有很多免费的数据集可供试用)。如果你在这个世界上学到了一些有趣的东西,可以在这个论坛上分享你的作品。我很想看看你如何运用你的新技能。。