You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 09:10:23