公众号:尤而小屋 作者: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])