机器学习可解释性神器shap入门

2023-11-09 00:05:16 浏览数 (2)

公众号:尤而小屋 作者:Peter 编辑:Peter

大家好,我是Peter~

今天给大家介绍一个机器学习模型可解释性神器:shap。

以前写过一篇文章里面用过shap,请参考:

shap

shap(SHapley Additive exPlanations)是一个用于解释机器学习模型输出的模型解释包。

它的核心思想是计算特征对模型输出的边际贡献,并从全局和局部两个层面对模型进行解释。

数学原理

SHAP的数学原理是基于博弈论中的Shapley值,用于衡量每个特征对模型预测的贡献。对于每个预测样本,SHAP通过计算每个特征的Shapley值,将模型输出的预测值分解为每个特征的贡献,从而帮助人们理解模型是如何做出决策的。

Shapley值是一种基于博弈论的方法,用于解决合作博弈中的公平分配问题。在机器学习领域中,SHAP将机器学习模型看作是一个合作博弈,每个特征看作是一个合作的参与者。通过计算每个特征的Shapley值,可以衡量每个特征对模型预测的贡献,从而对模型进行解释。

SHAP值计算

SHAP的计算方法如下:

  • 首先,对于每个预测样本,将模型预测值减去所有特征的平均影响估计(即全部样本该特征的均值),得到每个特征对预测的边际贡献;
  • 然后,根据每个特征的边际贡献和特征的出现次数,计算每个特征的Shapley值;
  • 最后,将每个特征的Shapley值相加,得到该样本的SHAP值。

可以上面的描述中能够看到SHAP值的计算是属于加性回归思想。

XGBoost模型可视化解释

导入库

In 1:

代码语言:txt复制
import xgboost
import numpy as np
import shap

import warnings
warnings.filterwarnings("ignore")

建立xgboost树模型

In 2:

代码语言:txt复制
X, y = shap.datasets.california()  # 导入数据
X.head()

Out2:

MedInc

HouseAge

AveRooms

AveBedrms

Population

AveOccup

Latitude

Longitude

0

8.3252

41.0

6.984127

1.023810

322.0

2.555556

37.88

-122.23

1

8.3014

21.0

6.238137

0.971880

2401.0

2.109842

37.86

-122.22

2

7.2574

52.0

8.288136

1.073446

496.0

2.802260

37.85

-122.24

3

5.6431

52.0

5.817352

1.073059

558.0

2.547945

37.85

-122.25

4

3.8462

52.0

6.281853

1.081081

565.0

2.181467

37.85

-122.25

In 3:

代码语言:txt复制
y

Out3:

代码语言:txt复制
array([4.526, 3.585, 3.521, ..., 0.923, 0.847, 0.894])

In 4:

代码语言:txt复制
model = xgboost.XGBRegressor().fit(X, y)  # 建立模型

创建可解释器

基于xgboost模型创建可解释器

In 5:

代码语言:txt复制
explainer = shap.Explainer(model)   # 基于模型创建可解释器 
shap_values = explainer(X)  # 基于解释器对特征矩阵X进行解释,算出shap值

每个特征的shap值构成的矩阵:

In 6:

代码语言:txt复制
shap_values

Out6:

代码语言:txt复制
.values =
array([[ 1.7081785 ,  0.09363674,  0.19277047, ...,  0.01571906,
        -0.39385185,  0.55515116],
       [ 1.426717  ,  0.03108795,  0.00601703, ...,  0.2112088 ,
        -0.36280793,  0.5884698 ],
       [ 1.3600677 ,  0.16082455,  0.47361216, ..., -0.02257477,
        -0.5582292 ,  0.5463798 ],
       ...,
       [-0.5842778 ,  0.01744973, -0.0949486 , ...,  0.10111337,
        -0.9798146 ,  0.3479332 ],
       [-0.6035651 ,  0.03118367, -0.05752674, ...,  0.23118298,
        -1.051862  ,  0.32962263],
       [-0.44976887,  0.02051439, -0.12479055, ..., -0.00343278,
        -0.85543966,  0.33553985]], dtype=float32)

.base_values =
array([2.0684865, 2.0684865, 2.0684865, ..., 2.0684865, 2.0684865,
       2.0684865], dtype=float32)

.data =
array([[   8.3252    ,   41.        ,    6.98412698, ...,    2.55555556,
          37.88      , -122.23      ],
       [   8.3014    ,   21.        ,    6.23813708, ...,    2.10984183,
          37.86      , -122.22      ],
       [   7.2574    ,   52.        ,    8.28813559, ...,    2.80225989,
          37.85      , -122.24      ],
       ...,
       [   1.7       ,   17.        ,    5.20554273, ...,    2.3256351 ,
          39.43      , -121.22      ],
       [   1.8672    ,   18.        ,    5.32951289, ...,    2.12320917,
          39.43      , -121.32      ],
       [   2.3886    ,   16.        ,    5.25471698, ...,    2.61698113,
          39.37      , -121.24      ]])

单个样本(瀑布图)

在每个样本实例中,每个特征的shap值可视化:

In 7:

代码语言:txt复制
shap_values[0]

Out7:

代码语言:txt复制
.values =
array([ 1.7081785 ,  0.09363674,  0.19277047,  0.01245449, -0.05390611,
        0.01571906, -0.39385185,  0.55515116], dtype=float32)

.base_values =
2.0684865

.data =
array([   8.3252    ,   41.        ,    6.98412698,    1.02380952,
        322.        ,    2.55555556,   37.88      , -122.23      ])

第一个样本实例的信息:shap_values、基线值base_values、原始样本数据值data

In 8:

代码语言:txt复制
shap.plots.waterfall(shap_values[0])

In 9:

代码语言:python代码运行次数:0复制
shap.plots.waterfall(shap_values[100])

红色表示特征对预测的贡献值大,蓝色表示贡献值小

单个样本可视化(力图)

In 10:

代码语言:txt复制
shap.initjs()  # 需要这段代码

shap.plots.force(shap_values[0])

多个样本可视化(力图)

In 11:

代码语言:txt复制
shap.initjs()  # 需要这段代码
shap.plots.force(shap_values[:300])

单个特征可视化(全部样本)

查看单个特征在全部样本数据上的表现:

In 12:

代码语言:txt复制
shap.plots.scatter(shap_values[:, "Latitude"], color=shap_values)

全部特征可视化(蜜蜂图)

针对全部特征的可视化,使用蜜蜂图beeswarm

In 13:

代码语言:txt复制
shap.plots.beeswarm(shap_values)

全部特征可视化(柱状图)

shap值的平均绝对值:

In 14:

代码语言:txt复制
shap.plots.bar(shap_values)

基于KernelExplainer可视化

In 15:

代码语言:txt复制
import sklearn
import shap
from sklearn.model_selection import train_test_split

# js初始化
shap.initjs()

X, y = shap.datasets.iris()

# 切分数据集合
X_train,X_test,Y_train,Y_test = train_test_split(X,y, test_size=0.2, random_state=0)

# 建立模型
svm = sklearn.svm.SVC(kernel='rbf', probability=True)
svm.fit(X_train, Y_train)

建立解释器并可视化:

In 16:

代码语言:txt复制
# 建立解释器
explainer = shap.KernelExplainer(svm.predict_proba, X_train, link="logit")
shap_values = explainer.shap_values(X_test, nsamples=100)

shap.initjs()
shap.force_plot(explainer.expected_value[0], shap_values[0][0,:], X_test.iloc[0,:],link="logit")

In 17:

代码语言:python代码运行次数:0复制
shap.force_plot(explainer.expected_value[0], shap_values[0], X_test, link="logit")

基于DeepExplainer可视化(TensorFlow、Keras)

使用DeepExplainer类对深度学习模型(主要是基于TensorFlow和Keras)进行可视化:

In 18:

代码语言:txt复制
import shap
import numpy as np  
import tensorflow as tf  
from tensorflow.keras import datasets, layers, models

from keras.datasets import cifar10,mnist

导入数据

In 19:

代码语言:txt复制
(train_data, train_labels), (test_data, test_labels) = cifar10.load_data()

In 20:

代码语言:txt复制
train_data.shape[1:]

Out20:

代码语言:txt复制
(32, 32, 3)

数据归一化

In 21:

代码语言:txt复制
# 将数值值标准化至 0 到 1 的区间内  

train_data, test_data = train_data / 255.0, test_data / 255.0

定义模型

In 22:

代码语言:txt复制
# 定义模型  
model = models.Sequential()  
model.add(layers.Conv2D(32, (3, 3), activation='relu', input_shape=train_data.shape[1:]))  
model.add(layers.MaxPooling2D((2, 2)))  
model.add(layers.Conv2D(64, (3, 3), activation='relu'))  
model.add(layers.MaxPooling2D((2, 2)))  
model.add(layers.Conv2D(64, (3, 3), activation='relu'))

添加全连接层

In 23:

代码语言:txt复制
model.add(layers.Flatten())  
model.add(layers.Dense(64, activation='relu'))  
model.add(layers.Dense(10))

模型编译

In 24:

代码语言:txt复制
# 编译模型,指定优化器、损失函数和评价指标  

model.compile(optimizer='adam',  
              loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),  # 多分类使用损失
              metrics=['accuracy'])

训练模型

In 25:

代码语言:txt复制
# 训练模型,指定训练数据、批次大小和训练轮次(epoch)  
history = model.fit(train_data, 
                    train_labels, 
                    epochs=10,   
                    validation_data=(test_data, test_labels))
Epoch 1/10
1563/1563 [==============================] - 21s 13ms/step - loss: 1.5149 - accuracy: 0.4482 - val_loss: 1.2923 - val_accuracy: 0.5376
Epoch 2/10
1563/1563 [==============================] - 21s 13ms/step - loss: 1.1512 - accuracy: 0.5934 - val_loss: 1.0384 - val_accuracy: 0.6317
......
Epoch 9/10
1563/1563 [==============================] - 21s 13ms/step - loss: 0.6220 - accuracy: 0.7811 - val_loss: 0.8401 - val_accuracy: 0.7213
Epoch 10/10
1563/1563 [==============================] - 21s 14ms/step - loss: 0.5819 - accuracy: 0.7955 - val_loss: 0.8959 - val_accuracy: 0.6996

shap可视化

In 26:

代码语言:txt复制
# 随机无放回抽样
background = train_data[np.random.choice(train_data.shape[0], 100, replace=False)]  

# 建立解释器
exp = shap.DeepExplainer(model, background)
shap_values = exp.shap_values(test_data[1:5])
shap.image_plot(shap_values, -test_data[1:5])

0 人点赞