利用SHAP对XGBoost解释
该篇文章是学习笔记原来在这里利用SHAP解释Xgboost模型大家可以见原文。1. Feature importance在SHAP被广泛使用之前我们通常用feature importance或者partial dependence plot来解释xgboost。 feature importance是用来衡量数据集中每个特征的重要性。简单来说每个特征对于提升整个模型的预测能力的贡献程度就是特征的重要性。Feature importance可以直观地反映出特征的重要性看出哪些特征对最终的模型影响较大。但是无法判断特征与最终预测结果的关系是如何的。下面这个例子中我们用2018年足球球员身价数据请在SofaSofa数据竞赛页面进行数据下载下载解压后只需要train.csv这个文件来具体阐述。importseaborn# 加载模块importxgboostasxgbimportpandasaspdimportnumpyasnpimportmatplotlib.pyplotasplt plt.style.use(seaborn-v0_8)# 读取数据目标变量y是球员的身价万欧元datapd.read_csv(../data/train.csv)# 获得当时球员年龄todaypd.to_datetime(2018-01-01)data[birth_date]pd.to_datetime(data[birth_date])data[age]np.round((today-data[birth_date]).apply(lambdax:x.days)/365.,1)# 选择特征这里只是举例未必是最佳组合# 特征依次为身高厘米、潜力、速度、射门、传球、带球、防守、体格、国际知名度、年龄cols[height_cm,potential,pac,sho,pas,dri,def,phy,international_reputation,age]# 训练xgboost回归模型modelxgb.XGBRegressor(max_depth4,learning_rate0.05,n_estimators150)model.fit(data[cols],data[y].values)# 获取feature importanceplt.figure(figsize(15,5))plt.bar(range(len(cols)),model.feature_importances_)plt.xticks(range(len(cols)),cols,rotation-45,fontsize14)plt.title(Feature importance,fontsize14)plt.show()上图中我们可以看出国际知名度、潜力和年龄是影响球员身价最重要的三个因素。但是这些因素和身价是正相关、负相关还是其他更复杂的相关性我们无法从上图得知。我们也无法解读每个特征对每个个体的预测值的影响。2. SHAP valueSHAP的名称来源于SHapley Additive exPlanation。Shapley value起源于合作博弈论。比如说甲乙丙丁四个工人一起打工甲和乙完成了价值100元的工件甲、乙、丙完成了价值120元的工件乙、丙、丁完成了价值150元的工件甲、丁完成了价值90元的工件那么该如何公平、合理地分配这四个人的工钱呢Shapley提出了一个合理的计算方法有兴趣地可以查看原论文我们称每个参与者分配到的数额为Shapley value。SHAP是由Shapley value启发的可加性解释模型。对于每个预测样本模型都产生一个预测值SHAP value就是该样本中每个特征所分配到的数值。 假设第i个样本为xi第i个样本的第j个特征为xi,j模型对第i个样本的预测值为yi整个模型的基线通常是所有样本的目标变量的均值为ybase那么SHAP value服从以下等式。yiybasef(xi,1)f(xi,2)⋯f(xi,k)其中f(xi,1)为xi,j的SHAP值。直观上看f(xi,1)就是第i个样本中第1个特征对最终预测值yi的贡献值当f(xi,1)0说明该特征提升了预测值也正向作用反之说明该特征使得预测值降低有反作用。很明显可以看出与上一节中feature importance相比SHAP value最大的优势是SHAP能对于反映出每一个样本中的特征的影响力而且还表现出影响的正负性。3. SHAP的Python实现Python中SHAP值的计算由shap这个package实现可以通过pip install shap安装。下面我们针对第1节中训练出的模型model计算其SHAP值。引用package并且获得解释器explainer。importshap# model是在第1节中训练的模型explainershap.TreeExplainer(model)获取训练集data各个样本各个特征的SHAP值。因为data中有10441个样本以及10个特征我们得到的shap_values的维度是10441×10。shap_valuesexplainer.shap_values(data[cols])print(shap_values.shape)我们也可以获得在第2节中提到的模型的基线ybase。通过对比发现我们可以确认基线值就是训练集的目标变量的拟合值的均值。在这里例子中目标变量是球员的身价万欧元也就是球员的平均身价为229万欧元。y_baseexplainer.expected_value print(y_base)data[pred]model.predict(data[cols])print(data[pred].mean())3.1 单个样本的SHAP值我们可以随机检查其中一位球员身价的预测值以及其特征对预测值的影响。下面的数据框中第一列是特征名称第二列是特征的数值第三列是各个特征在该样本中对应的SHAP值。# 比如我们挑选数据集中的第30位j30player_explainerpd.DataFrame()player_explainer[feature]cols player_explainer[feature_value]data[cols].iloc[j].values player_explainer[shap_value]shap_values[j]player_explainer我们知道一个样本中各特征SHAP值的和加上基线值应该等于该样本的预测值。我们可以做如下的验证。print(y_base sum_of_shap_values: %.2f%(y_base player_explainer[shap_value].sum()))print(y_pred: %.2f%(data[pred].iloc[j]))shap还提供极其强大的数据可视化功能。下图是对上面数据框的可视化。蓝色表示该特征的贡献是负数红色则表示该特征的贡献是正数。最长的红色条是潜力值球员的潜力值很高而他的身价也因此增加了1092万最长的蓝色条是年龄这个球员年龄较小才20岁出头尚未到职业巅峰未来也有诸多不确定性身价也因此降低了180万元。shap.initjs()shap.force_plot(explainer.expected_value, shap_values[j], data[cols].iloc[j])3.2 对特征的总体分析除了能对单个样本的SHAP值进行可视化之外还能对特征进行整体的可视化。下图中每一行代表一个特征横坐标为SHAP值。一个点代表一个样本颜色越红说明特征本身数值越大颜色越蓝说明特征本身数值越小。我们可以直观地看出潜力potential是一个很重要的特征而且基本上是与身价成正相关的。年龄age也会明显影响身价蓝色点主要集中在SHAP小于0的区域也有一部分是红色的点这可能是由于交互作用可见年纪小会降低身价估值另一方面如果年纪很大也会降低估值甚至降低得更明显因为age这一行最左端的点基本上都是红色的。shap.summary_plot(shap_values, data[cols])我们也可以把一个特征对目标变量影响程度的绝对值的均值作为这个特征的重要性。因为SHAP和feature_importance的计算方法不同所以我们这里也得到了与第1节不同的重要性排序。shap.summary_plot(shap_values, data[cols],plot_typebar)3.3 部分依赖图Partial Dependence PlotSHAP也提供了部分依赖图的功能与传统的部分依赖图不同的是这里纵坐标不是目标变量y的数值而是SHAP值。比如下图中年纪大概呈现出金字塔分布也就是24到31岁这个年纪对球员的身价是拉抬作用小于24以及大于31岁的球员身价则会被年纪所累。shap.dependence_plot(age, shap_values, data[cols],interaction_indexNone,showFalse)3.4 对多个变量的交互进行分析我们也可以多个变量的交互作用进行分析。一种方式是采用summary_plot描绘出散点图如下shap_interaction_valuesshap.TreeExplainer(model).shap_interaction_values(data[cols])# shap.summary_plot(shap_interaction_values, data[cols], max_display 4) # 展示的列数# 对每一个变量绘制交互作用影响的图fori, colinenumerate(cols): print(fInteraction for {col})shap.summary_plot(shap_interaction_values[:, i, :], data[cols],max_display10# 列数保留10列)我们也可以用dependence_plot描绘两个变量交互下变量对目标值的影响。shap.dependence_plot(potential, shap_values, data[cols],interaction_indexinternational_reputation,showFalse)也可以不显示交互只看其单独的效果。数据和代码放在我的资源里可以直接下载。