使用LightGBMClassifier绘制SHAP瀑布图报错,求解决方案
如何生成单样本的SHAP瀑布图?
问题场景与初始报错
我使用以下代码绘制SHAP瀑布图:
explainer = shap.TreeExplainer(gbm, data=None) shap_values = explainer.shap_values(P) # 可视化第一个样本的预测解释 shap.waterfall_plot(explainer.expected_value[0], shap_values[0])
触发报错:
AttributeError: 'numpy.float64' object has no attribute 'base_values'
尝试的解决方法及后续报错
参考相关方案后,尝试手动封装对象适配瀑布图:
explainer = shap.TreeExplainer(gbm, data=None) shap_values = explainer(P) class helper_object(): """包装SHAP对象,接收样本索引作为输入""" def __init__(self, i): self.base_values = shap_values.base_values[i][0] self.data = P.loc[i] self.feature_names = P.columns.to_list() self.values = shap_values.values[i] # 可视化第6个样本的预测解释 shap.waterfall_plot(helper_object(5), len(shap_values[0]))
新报错:
AttributeError: 'list' object has no attribute 'base_values'
改用explainer(P)替代explainer.shap_values(P)后,仍报错:
AttributeError: 'helper_object' object has no attribute 'display_data'
打印值的发现
打印相关变量后,发现返回结果为数组而非单个常量:
print(shap_values[0].base_values) print(type(shap_values.base_values[0])) print(shap_values[0])
输出内容:
[ 4.03719405 -4.03719405] <class 'numpy.ndarray'> .values = array([[-5.19398412e-02, 5.19398412e-02], [-1.52522416e+00, 1.52522416e+00], [-7.06765115e-01, 7.06765115e-01], [-2.52875346e-01, 2.52875346e-01], [-1.54701093e-01, 1.54701093e-01], [ 6.35169405e-03, -6.35169405e-03], [ 6.57487803e-03, -6.57487803e-03], [ 3.70178479e-02, -3.70178479e-02], [ 0.00000000e+00, 0.00000000e+00], [-6.99857166e-02, 6.99857166e-02], [-1.38675779e-01, 1.38675779e-01], [-6.09464170e-02, 6.09464170e-02], [-1.94668294e-03, 1.94668294e-03], [-1.15005190e-03, 1.15005190e-03], [-1.11472815e+00, 1.11472815e+00], [-1.05449992e-01, 1.05449992e-01], [-1.82219843e-01, 1.82219843e-01], [ 1.58137725e-02, -1.58137725e-02], [-3.98713235e-01, 3.98713235e-01], [-9.45830700e-01, 9.45830700e-01], [-6.23312829e-02, 6.23312829e-02], [ 6.32792510e-02, -6.32792510e-02], [-6.01518308e-02, 6.01518308e-02], [ 1.87929746e-04, -1.87929746e-04], [-1.58341844e-03, 1.58341844e-03], [-5.08391166e-03, 5.08391166e-03], [ 0.00000000e+00, 0.00000000e+00], [ 1.05478554e-02, -1.05478554e-02], [ 2.37974651e-02, -2.37974651e-02], [ 5.65731935e-03, -5.65731935e-03], [ 2.05245700e-03, -2.05245700e-03]]) .base_values = array([ 4.03719405, -4.03719405]) .data = array([ 1.07000001e-01, 7.87079980e+03, 4.26199989e+01, 9.39999998e-01, 1.48000002e+01, 2.19000006e+00, 3.10000002e-01, 7.98212012e+03, 2.61899994e+02, 8.40000000e+01, 2.00000000e+01, 3.74000001e+00, -1.00000000e+00, -1.00000000e+00, 4.00000000e+00, 3.00000000e+00, 1.00000000e+00, -1.87000008e+01, 8.41299988e+02, 1.36000004e+01, -8.52000046e+00, 6.99999809e-01, -7.65000000e+02, 5.40000010e+00, 0.00000000e+00, -1.00000000e+00, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00])
完整可复现代码
A = [1,2,3,4,5,6,7,8,9,10] B = [21,5,7,8,15,36,20,18,15,13] W = [1,0,0,0,0,0,1,1,0,0] C = ["Adult", "Child", "Child", "Child", "Child", "Adult", "Adult", "Adult", "Child", "Child"] Number = pd.Series(A, name='Number') Age = pd.Series(B, name='Age') Car = pd.Series(W, name='Car') User_ages = pd.concat([Number, Age], axis=1) User_cars = pd.concat([User_ages, Car], axis=1) group = pd.Series(C, name='group') data = pd.concat([User_cars, group], axis=1) features = ['Age', 'Car'] X = data[features] y = data['group'] D = [11,12,13,14,15,16,17,18,19,20] E = [22,11,17,21,25,31,30,8,5,3] F = [1,0,0,0,1,0,1,0,0,0] Number = pd.Series(D, name='Number') Age = pd.Series(E, name='Age') Car = pd.Series(F, name='Car') data1 = pd.concat([Number, Age], axis=1) data2 = pd.concat([data1, Car], axis=1) P = data2[features] X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=22, test_size=0.1) params = { 'num_leaves': [20, 30, 40, 50, 60, 70], 'learning_rate': [0.05, 0.01], 'n_estimators': [100, 300, 500], 'subsample': [0.95], 'colsample_bytree': [0.95], 'n_jobs': [7], 'random_state': [22] } gcv = GridSearchCV(LGBMClassifier(), params, cv=2, verbose=1, error_score='raise').fit(X_train, y_train) gbm = gcv.best_estimator_ gbm_pred = gbm.predict(X_test) explainer = shap.TreeExplainer(gbm, data=None) shap_values = explainer.shap_values(P) # 可视化第一个样本的预测解释 shap.waterfall_plot(explainer.expected_value[0], shap_values[0])
解决方案
针对二分类模型,SHAP会返回两个类别的SHAP值数组,瀑布图需要针对单个类别和单样本进行处理,且需使用shap.Explanation对象规范输入格式:
import shap import pandas as pd from sklearn.model_selection import train_test_split from sklearn.model_selection import GridSearchCV from lightgbm import LGBMClassifier # (数据生成与模型训练代码与原代码一致,此处省略) explainer = shap.TreeExplainer(gbm, data=None) shap_values = explainer.shap_values(P) # 指定要解释的样本索引和类别索引 sample_idx = 0 # 第一个样本 class_idx = 0 # 第一个类别 # 创建符合要求的Explanation对象 exp = shap.Explanation( values=shap_values[class_idx][sample_idx], base_values=explainer.expected_value[class_idx], data=P.iloc[sample_idx], feature_names=P.columns ) # 绘制瀑布图 shap.waterfall_plot(exp)
关键说明
- 二分类模型中,
shap_values是长度为2的列表,分别对应两个类别的SHAP值,需明确指定要解释的类别。 - 使用
shap.Explanation封装单样本的SHAP值、基准值、特征数据和特征名,可避免手动封装类时的属性缺失问题,完全匹配瀑布图的输入要求。
内容的提问来源于stack exchange,提问作者Vui Chee Chang
相关产品推荐
相关产品推荐

