用LIME解释XGBoost和LightGBM结果


第一篇文章:angel_tone2:

背景

您想要进行的机器学习不是获得预测结果,而是根据预测结果做出一些决策。但是数据科学家并不处于决策位置(今天大部分时间),而是将决策权交给另一个人。
此时,数据科学家需要说明获得预测结果的原理,以便决策者可以信任预测结果并做出决策。

在许多机器学习的应用中,要求用户信任模型以帮助他们做出决策。医生肯定不会仅仅因为"模型如此说。"
局部可解释模型不可知解释(LIME)简介

我做了什么

LIME用于可视化和解释XGBoost和LightGBM的结果。

什么是LIME?
https://arxiv.org/abs/1602.04938
取得一个预测结果,并使用另一种可解释模型(例如线性模型)对它进行局部近似。根据此处获得的模型的偏回归系数,确定哪个特征量有助于预测结果以及影响程度。
本页上的解释非常容易理解。

使用数据

我们使用了UCI上发布的蘑菇数据集(该数据集根据蘑菇环的数量和伞的颜色等特征对可食用/不可食用蘑菇进行了分类)。

总结执行的代码和简短描述。

执行代码(GitHub)

https://github.com/qrlokki/learning/blob/master/LIME_test/notebooks/LIME_classification_with_XGBoost_and_LightGBM.ipynb

代码内容

图书馆,数据导入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import os, sys, math
import numpy as np
import pandas as pd

import seaborn as sns
import matplotlib.pyplot as plt

from sklearn.preprocessing import OneHotEncoder
from sklearn.preprocessing import LabelEncoder
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score

from plotting import plot_extension

import lightgbm as lgbm

### params

RAWDATA_DIR = "../data/raw"
RANDOM_STATE = 123

### read data
COLNAMES = [
   "edibility", "cap-shape", "cap-surface", "cap-color", "bruises", "odor",
   "gill-attachment", "gill-spacing", "gill-size", "gill-color",
   "stalk-shape", "stalk-root", "stalk-surface-above-ring",
   "stalk-surface-below-ring", "stalk-color-above-ring",
   "stalk-color-below-ring", "veil-type", "veil-color", "ring-number",
   "ring-type", "spore-print-color", "population", "habitat"
]
rawdata = pd.read_csv(os.path.join(RAWDATA_DIR,"agaricus-lepiota.data"), names=COLNAMES)

可视化

1
2
3
4
5
6
7
8
9
10
### visualize count of value each features
fig = plt.figure(figsize=(13,25))
for i, c in enumerate(rawdata.columns):
   ax = fig.add_subplot(
       math.ceil(len(rawdata.columns) / 3), 3, i + 1)
   # plot the continent on these axes
   sns.countplot(x=c, data=rawdata,  ax=ax)
   ax.set_title(c)
fig.tight_layout()
plt.show()

1.png

1
2
3
4
5
6
7
8
9
10
11
12
13
fig = plt.figure(figsize=(13,25))

for i, c in enumerate(rawdata.columns):
    ax = fig.add_subplot(
        math.ceil(len(rawdata.columns) / 3), 3, i + 1)

    # plot the continent on these axes
    sns.countplot(x=c, hue="edibility", data=rawdata, ax=ax)
    ax.set_title(c)


fig.tight_layout()
plt.show()

2.png

使用XGBoost

进行分类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
X = rawdata.iloc[:, 1:]
y = rawdata.iloc[:, 0]
# train/test split
X_train, X_test, y_train, y_test = train_test_split(
   X, y, test_size=0.2, shuffle=True, random_state=RANDOM_STATE)

# label encode
mle = MultiColumnLabelEncoder()
X_train = mle.fit_transform(X_train)
X_test = mle.transform(X_test)
le = LabelEncoder()
y_train = le.fit_transform(y_train)
y_test = le.transform(y_test)

# one-hot
cat_feats = X_train.columns
X_train = pd.get_dummies(X_train, columns=cat_feats)
X_test = pd.get_dummies(X_test, columns=cat_feats)
missing_cols = set(X_train.columns) - set(X_test.columns)
for c in missing_cols:
   X_test[c] = 0
X_test = X_test[X_train.columns]

import xgboost as xgbm
from sklearn import metrics

watchlist = [(X_train.values, y_train), (X_test.values, y_test)]
xgbm_classifier = xgbm.XGBClassifier(
   objectibe='binary:logistic',
   n_estimators=10000,
   learning_rate=0.01,
   reg_lambda=0.5,
   reg_alpha=0.5,
   colsample_bytree=0.8,
   subsample=0.8,
   seed=RANDOM_STATE)
xgbm_classifier.fit(
   X_train.values,
   y_train,
   eval_metric='auc',
   early_stopping_rounds=100,
   verbose=50,
   eval_set=watchlist,
)
y_test_pred_proba = xgbm_classifier.predict_proba(
   X_test.values, ntree_limit=xgbm_classifier.best_iteration)
print('auc : {0:.6f}'.format(metrics.roc_auc_score(y_test, y_test_pred_proba[:,1])))

使用LIME

1
2
3
4
5
6
7
8
9
10
11
12
13
import lime
import lime.lime_tabular
explainer = lime.lime_tabular.LimeTabularExplainer(
   X_train.values,
   mode='classification',
   feature_names=X_train.columns,
   class_names=["edible", "poisonous"],
   verbose=True
   )
i = 15
exp = explainer.explain_instance(X_test.values[i], xgbm_classifier.predict_proba, num_features=5)
exp.show_in_notebook(show_all=False)
print(y_test_pred_proba[i], exp.score, exp.intercept)

3.PNG

进行LightGBM分类器并使用LIME

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
dtrain = lgbm.Dataset(X_train, y_train)
dtest = lgbm.Dataset(X_test, y_test, reference=dtrain)
params = {
        'objective': 'binary',
        'metric': 'auc',
        'learning_rate': 0.01,
        'reg_lambda': 0.5,
        'reg_alpha': 0.5,
        'colsample_bytree': 0.8,
        'subsample': 0.8,
        'seed': RANDOM_STATE
        }
lgbm_classifier = lgbm.train(
   params,
   dtrain,
   valid_sets=dtest,
   num_boost_round=10000,
   early_stopping_rounds=50,
   verbose_eval=50,
   )
y_test_pred_proba = lgbm_classifier.predict(
   X_test.values, ntree_limit=lgbm_classifier.best_iteration)
print('\nauc : {0:.6f}'.format(metrics.roc_auc_score(y_test, y_test_pred_proba)))

# LIME
def predict_fn(x):
   preds = lgbm_classifier.predict(x).reshape(-1, 1)
   p0 = 1 - preds
   return np.hstack((p0, preds))

explainer = lime.lime_tabular.LimeTabularExplainer(
    X_train.values,
    mode='classification',
    feature_names=X_train.columns,
    class_names=["edible", "poisonous"],
    verbose=True
    )
i = 15
exp = explainer.explain_instance(X_test.values[i], predict_fn, num_features=5)
exp.show_in_notebook(show_all=False)
print(y_test_pred_proba[i], exp.score, exp.intercept)

4.PNG

结果

XGBoost:
3.PNG

LightGBM:
4.PNG

该图从左侧为

  • 近似后的分类器结果
  • 每个功能的权重
  • 每个功能的实际价值

它是


这些是预测同一样本的结果,但是LIME近似的模型的内容完全不同。出现的功能相似,但权重不同。要素odor_5出现在两个结果中,但其权重分别为0.56和0.02。在特征量odor_2中,权重分别为0.07和0.01。

以后要显示的功能数量可以通过explain_instancenum_features选项指定,这次将其设置为5。

警告

我觉得我必须要小心。

  • LightGBM无法按原样使用预测方法。由于LIME符合sklearn,因此我认为在二进制分类的结果中,它将以(2,)的形式出现。但是,LightGBM的预测仅返回1d的结果,因此我制作了predict_fn方法并在explain_instance中调用了它。
1
2
3
4
5
# LIME
def predict_fn(x):
   preds = lgbm_classifier.predict(x).reshape(-1, 1)
   p0 = 1 - preds
   return np.hstack((p0, preds))

  • 对可解释模型的局部逼近结果与原始模型获得的结果不同。这次我还没有真正看过有什么区别,但是最好在实际使用时看看它。

最后

人们倾向于接受他们可以解释和理解的内容(我认为),因此我认为从信任结果的angular解释此类内容非常有效。可视化的方式易于理解和交谈。
有几点需要注意,但是我想利用它们。

说到结果解释,关于结果解释的课程刚刚从kaggle开始。三个主要主题是哪些特征很重要,这些特征如何对某个预测结果有所贡献以及每个特征如何对所有数据的预测有所贡献,并使用了eli5和ppdbox。还有运动,这是相当不错的。
我已经在GitHub上发布了我所做的事情,所以我想在总结时总结一下。

第一次写起塔很困难。花了很长时间。
如果您发现有问题,请在评论中告知我们。

参考

  • https://github.com/marcotcr/lime
  • https://arxiv.org/abs/1602.04938
  • https://www.oreilly.com/learning/introduction-to-local-interpretable-model-agnostic-explanations-lime
  • http://pesuchin.hatenablog.com/entry/2017/01/07/170859