解释随机森林和其他黑盒子模型,如XGBoost
在机器学习中,在性能和解释之间有一个反复出现的困境。通常,模型越好,越复杂,越难理解。
一般来说,有两种解释模型的方法:
- 总体解释(Overall interpretation):确定哪些变量(或变量的组合)具有最大的预测能力,哪些变量具有最小的预测能力
- 局部解释(Local interpretation):对于给定的数据点和相关的预测,确定哪些变量(或变量的组合)解释这个特定的预测
根据您使用的模型的类型,可能存在解释模型的特定方法。例如,决策树模型可以简单地通过绘制这棵树来解释,看看如何分割和叶子的成分是什么。
然而,对于RandomForest或XGBoost,并没有特定的方法来实现这一点,因为它们通常更善于进行预测。
总体解释
在Python的大多数模型中,总体解释已经开箱即用,具有“feature_importances_”属性。示例如下:
from sklearn.ensemble import RandomForestClassifier # from xgboost import XGBClassifier
model = RandomForestClassifier() # XGBClassifier()
model.fit(X, y)
pd.DataFrame({'Variable':X.columns,
'Importance':model.feature_importances_}).sort_values('Importance', ascending=False)
对于给定模型的特性导入示例,我在本文中称之为“特性导入表”(按特性重要性按降序排序)
解释这个输出是非常简单的:根据模型,越重要,变量就越相关。这是一个很好的方法。
- 确定具有最佳预测能力的变量
- 提出问题/纠正错误:与其他变量相比,变量太重要了。示例:在以前的项目中,我们处理有偏差的数据:类1的数据在变量中有很多缺失值,而类0的数据则没有。直到我们查看特性重要性表时才意识到这一点。模型了解到,如果数据缺失,则属于类1。我们通过从0类的数据中抽取缺失值来解决它。
- 用新的变量更新模型。要查看一个新变量是否与您的模型相关,请在(不包含新变量)之前计算模型的特性输入值,以及(使用新变量)之后的模型。分析新变量在特征导入表中产生的移位。示例:在进行特性工程时,您可以提出一个更相关的特性,但是在数据中引入它可能会减少与新特性直接相关的特性的输入。
- 比较不同的模型:比较两个不同模型(例如RandomForest和XGBoost)的特征输入,比较变量的重要性。它可以帮助我们看到一个模型是否掌握了一个变量的预测能力。示例:将XGBoost模型与不同的深度进行比较,可以帮助您理解当您使用特定的深度时,特定的变量将变得有用。
到目前为止,这对于全面理解这个模型是有好处的。
局部解释
在这里,我将定义什么是局部解释,并提出一种方法,使用任何模型都可以轻松地解决这个问题。
如何定义局部解释?
这里启发我的是我从DataRobot得到的一个演示,在那里他们想要预测贷款违约。在他们的演示中,对于每个人的预测,他们还输出了增加最多默认概率的前3个变量,以及最大的3个变量。
让我们保持这个示例(使用模拟数据),为了更容易阅读,让我们用3个变量表示局部解释,如下所示:
局部解释的说明:对于每个数据点,我们确定对默认预测影响最大的3个变量。变量Var_1增加了前2个预测中的违约概率(分别为+ 34%和+ 25%),但在第3个预测中减少了(-12%)
从这种解释中得出的最有趣的结论
解释每一个预测可以用来:
- 理解个别情况下预测的原因。例子:两个个体可能有很高的违约概率,但由于完全不同的原因(即不同的变量)
- 了解一个过滤过的种群最常见的预测原因。例子:对于所有违约概率超过50%的人,解释违约的最常见变量是什么
对于所有默认预测(概率> 50%),我们对前3个变量中最常见的变量进行排名,以了解哪些变量解释最多的默认值。变量Var_1在43个案例中是预测默认值的最大贡献变量,15个案例中第二个,12个案例中第3个
在Python中实现
Python中的treeinterpreter 库允许我们准确计算每个特征对随机森林模型的影响。
对于其他模型,我们将做一个快速解决方案:运行随机森林模型,并进行局部解释,其中模型与随机森林模型之间的预测匹配(当它们同时预测默认或非默认时)。这是我在客户端项目中选择的解决方案,其中我有一个XGBoost模型。在这种情况下,来自Random Forest的局部解释很有意义,但是如果没有专门的XGBoost专用框架,仍然是一个令人沮丧的解决方法。
由于计算需要一些时间(取决于随机森林模型中的树数量),我建议您使用预测的子集进行此练习。例如,根据模型最有可能违约的10个人。
from treeinterpreter import treeinterpreter as ti
for i,row in X.iterrows():
data_point = pd.DataFrame([row])
data_point.set_axis(['value_variable']) # Once transposed, it will be the column name
prediction, bias, contributions = ti.predict(rf_model, data_point)
local_interpretation = data_point.append(
pd.DataFrame([[round(c[1],3) for c in contributions[0]]], columns=data_point.columns.tolist(), index=['contribution_variable'])
).T.sort_values('contribution_variable', ascending=False)
# print(local_interpretation)
对于每个单独的预测,我们使用treeinterpreter包计算预测中每个变量的个体贡献
请注意,treeinterpreter具有可变贡献和整体偏差。
有一件事要记住
假设你有一个变量“age”,它的贡献足够高,可以排在最前面的3个变量中,从而对一个特定的预测做出贡献。你可能会感兴趣知道age是什么,因为你(或专家)会对18或35岁的人有不同的理解。因此,查看一个变量的贡献和它的值是一个好习惯(比如上面的代码,其中有两列“value_variable”和“contribution_variable”)
最后
解释黑盒子模型一直是许多研究论文的主题,目前尤其在深入学习解释方面。
对于树解释器来说,最好有其他树基模型,比如XGBoost、LightGBM、CatBoost或其他梯度增强方法。