笔者在之前的一篇博客《谈谈机器学习模型的可解释性》介绍了机器学习可解释性的基本概念,那么今天我们就来看看如何具体的利用这些可解释的工具来对一个真实的模型进行可解释性的分析。
本文所有代码参见https://github.com/gangtao/ml-Interpretability
首先我们获得了FIFA 2018年俄罗斯世界杯比赛的所有数据,我们利用这些数据来构建机器学习的模型。
import numpy as np
import pandas as pd
import warnings
warnings.filterwarnings('ignore')
data = pd.read_csv('./FIFA 2018 Statistics.csv')
data.head()
该数据集记录了俄罗斯世界杯每一场比赛的数据,包含日期,本队,对手,得分,控球,射门,射中,射偏等等统计数据,其中有一个字段‘Man of the Match’是本队是否获得了当场比赛的最佳球员。我们利用这些数据来构建一个预测模型,利用所有的数据来预测本队是否获得了赛后评比的当场最佳球员。
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.tree import DecisionTreeClassifier
y = (data['Man of the Match'])
feature_names = [i for i in data.columns if data[i].dtype in [np.int64]]
X = data[feature_names]
train_X, val_X, train_y, val_y = train_test_split(X, y, random_state=1)
my_model_1 = RandomForestClassifier(random_state=0).fit(train_X, train_y)
my_model_2 = DecisionTreeClassifier(random_state=0).fit(train_X, train_y)
我们利用随机森林和决策树算法训练了两个分类模型,用于预测是否本队获得了最佳球员。
对于决策树模型,我们可以利用graphviz可视化该模型。
from sklearn import tree
import graphviz
tree_graph = tree.export_graphviz(my_model_2, out_file=None, feature_names=feature_names)
graphviz.Source(tree_graph)
对于一个模型的解释,我们可以从特征开始,首先分析那些特征是最重要的,对越策的结果影响最大。在之前我们看到的决策树模型中,我们看到SKlearn计算了一个我们称之为基尼指数(gini)或者信息熵的值,用于反应特征的重要性。另外我们也可以从特征在树中的的层级来推断出特征的重要性,一般来说,出现在里根结点越近的层级,表示该特征越重要。在我们之前的决策树模型中,得分位于根结点,显然这个特征是影响是否获得当场最佳球员的重要特征。
对于利用基尼指数来计算特征重要性的方法不具有通用性,我们需要一个与模型无关的方法来计算。
Permutation Importance提供了一个和模型无关的计算特征重要性的方法。Permutation的中文含义是排列的意思,该算法基本思路如下:
我们利用eli5提供的PermutationImportance方法来计算特征重要性。
import eli5
from eli5.sklearn import PermutationImportance
perm = PermutationImportance(my_model_1, random_state=1).fit(val_X, val_y)
eli5.show_weights(perm, feature_names = val_X.columns.tolist())
下图是随即森林模型和决策树模型的特征重要性结果
我们看到对于最总要的特征,两个模型的意见是一致的,那就是进球数,然而对于第二重要的特征,随机森林认为射中目标最为重要,然而决策树认为,发更多的角球才是王道,坚决放弃控球,虽然随即森林认为发角球鸟用都没有,是最不重要的。
在了解了特征重要性后,我们还想了解每一个特征具体是如果影响模型越策的,这个时候就可以利用PDP或者ICE来进一步分析。
PDP的基本思路就是控制其它所有特征不变,改变要分析的特征,看看它对预测结果的影响。ICE和PDP类似,ICE会显示所有实例上的分析结果。
首先我们分析进球数对预测的影响。
from matplotlib import pyplot as plt
from pdpbox import pdp, get_dataset, info_plots
pdp_goals = pdp.pdp_isolate(model=my_model_1, dataset=val_X, model_features=feature_names, feature='Goal Scored')
pdp.pdp_plot(pdp_goals, 'Goal Scored', plot_pts_dist=True)
plt.show()
从PDP图中我们可以看出从不进球到近一个球,对预测的结果又一个明显的上升的影响趋势。然而进一个球之后,进太多的球对预测结果影响很低。
然后我们分析跑动距离对预测结果的影响。
feature_to_plot = 'Distance Covered (Kms)'
pdp_dist = pdp.pdp_isolate(model=my_model_1, dataset=val_X, model_features=feature_names, feature=feature_to_plot)
pdp.pdp_plot(pdp_dist, feature_to_plot, plot_pts_dist=True)
plt.show()
我们可以看出,当全队的跑动距离在100KM的时候,获得最佳球员的概率最大,然而跑动更多,概率反而下降了。
Sklearn也提供了PDP的支持
import matplotlib.pyplot as plt
from sklearn.inspection import plot_partial_dependence
plot_partial_dependence(my_model_1, train_X,
['Goal Scored','Ball Possession %', 'Distance Covered (Kms)' , 'Corners', (0,1)],
feature_names, grid_resolution=50)
fig = plt.gcf()
fig.set_figheight(10)
fig.set_figwidth(10)
fig.suptitle('Partial dependence')
plt.subplots_adjust(top=0.9, bottom = 0.1, wspace = 0.8) # tight_layout causes overlap with suptitle
PDP最多可以分析两个特征。上图中的最后一个PDP图就是包含了对进球数和控球两个特征的分析。
PDP一般只针对某一个特征进行分析,最多两个,我们可以看出当分析两个特征的时候,PDP图已经不是一目了然的清楚了。Sharpley Value可以针对某一个数据实例,对所有的特征对预测的贡献作出分析。
我们现选取某一个训练数据,假定为第五个训练数据。该数据的具体内容如下:
Goal Scored 2
Ball Possession % 38
Attempts 13
On-Target 7
Off-Target 4
Blocked 2
Corners 6
Offsides 1
Free Kicks 18
Saves 1
Pass Accuracy % 69
Passes 399
Distance Covered (Kms) 148
Fouls Committed 25
Yellow Card 1
Yellow & Red 0
Red 0
Goals in PSO 3
Name: 118, dtype: int64
row_to_show = 5
data_for_prediction = val_X.iloc[row_to_show] # use 1 row of data here. Could use multiple rows if desired
data_for_prediction_array = data_for_prediction.values.reshape(1, -1)
pred_1 = my_model_1.predict_proba(data_for_prediction_array)
pred_2 = my_model_2.predict_proba(data_for_prediction_array)
pred_1,pred_2
(array([[0.3, 0.7]]), array([[0., 1.]]))
两个模型都认为该比赛本队获得最佳球员的可能性很大,其中决策树更是拍胸脯给出了100%的预测概率。那么我们就来看看利用Sharpley Value,每一个特征的具贡献。
import shap # package used to calculate Shap values
explainer = shap.TreeExplainer(my_model_2)
shap_values = explainer.shap_values(data_for_prediction)
shap.initjs()
shap.force_plot(explainer.expected_value[1], shap_values[1], data_for_prediction)
利用TreeExplainer我们可以分析决策树模型中,对该条数据的解释。红色部分表示正面的影响,蓝色是负面影响,Base Value 0.5是基准值,进球,射门数,犯规,角球和控球率等都提供了正分数,进球影响最大。而控球率等其它指标则提供了负分。总共一起,贡献了最终的输出值1。
KernelExplainer可以针对一般的模型进行Sharpley Value的分析,但是运算要慢一些。
k_explainer = shap.KernelExplainer(my_model_1.predict_proba, train_X)
k_shap_values = k_explainer.shap_values(data_for_prediction)
shap.initjs()
shap.force_plot(k_explainer.expected_value[1], k_shap_values[1], data_for_prediction)
上图是利用KernelExplainer,针对同一场比赛,对于随即森林模型的解释。
Summary Plot给出了所有数据点的分析汇总
shap_values = k_explainer.shap_values(val_X)
shap.initjs()
shap.summary_plot(shap_values[1], val_X)
横坐标表示改值对于预测是正面的还是负面的,颜色标志了该值的大小。对于进球,我们发现,当进球数高的时候(红色),多为正面影响,而进球数低的时候,多为负面影响。有意思的黄牌数,大部分情况无论颜色,所有的点都集中在中间,也就是说得多少黄牌对于是否能评选本场最佳影响不大,然而有一场比赛,有一个特例,高的黄牌数对是否获得本场最佳产生了很大的负面影响。
我们还可以利用shap.dependence_plot来分析两个特征之间的相互影响。
explainer = shap.TreeExplainer(my_model_1)
# Calculate Shap values
shap_values = explainer.shap_values(X)
# make plot.
shap.dependence_plot('Ball Possession %', shap_values[1], X, interaction_index="Goal Scored")
上图是控球和进球数两个指标对Sharpley Value的影响。同样的,当出现位置,颜色两个可视化属性来提供分析的时候,我们人类是很难很好的读出图中的含义的。我们把焦点放在右下角的两个点。这两个点是所有比赛中控球率最高的两场比赛,但是右边这个点的Sharpley Value更低,从这个角度来看更多的控球对于本队获得最佳球员,更为不利。
LIME 全称是 local interpretable model-agnostic explanations 直译是局部可解释的模型无关的解释,非常拗口。LIME针对某个实例,假定在局部,模型是简单的线性模型,对该数据点作出解释。
import lime
import lime.lime_tabular
explainer = lime.lime_tabular.LimeTabularExplainer(train_X, feature_names=feature_names,
class_names=['No','Yes'],
discretize_continuous=False)
train_sample = train_X.sample(n=1)
pred_p_1 = my_model_1.predict_proba(train_sample.values)
pred_p_2 = my_model_2.predict_proba(train_sample.values)
pred_1 = my_model_1.predict(train_sample.values)
pred_2 = my_model_2.predict(train_sample.values)
pred_p_1,pred_1,pred_p_2,pred_2
类似的我们随机从训练集中取出一个点,用LIME来进行解释。该场比赛的数据如下:
利用LIME,我们可以给出LIME对于决策树模型下,给场比赛的解释。
exp = explainer.explain_instance(train_sample.values[0],
my_model_2.predict_proba,
num_features=len(feature_names),
top_labels=1)
exp.show_in_notebook(show_table=True, show_all=False)
比起本文中的表数据,LIME跟擅长对于文本数据和深度神经网络进行解释。
本文利用俄罗斯世界杯数据,构建了两个模型来预测本场最佳球员,并利用Permutation Importance/PDP/Sharpley Value/LIME等工具来对这两个模型进行解释。希望给大家一些如何解释机器学习模型的实际操作的例子。大家可以看出,虽然有诸多的工具,但是对于模型的解释还是需要我这个老球迷利用自己对足球的理解和知识来阐述(瞎掰)的,所有提供解释不可避免的需要领域专家的介入。
另外本文的代码可以在Binder上运行,Binder能将你在github上的notebook,构建一个容器,运行在google cloud上,非常方便。不知道国内能不能访问。