在将自定义模型包装器传递给SHAP KernelExplainer时,如何保持特征对齐?

人工智能 2026-07-12

我正在进行一个可解释性AI(XAI)项目,比较不同的模型无关框架(SHAP、LIME、DALEX)。我使用一个自定义包装器来统一我的模型输出(类似于Scikit-Learn的管道),以确保在这些库之间的兼容性。

然而,当我把包装好的模型传给 shap.KernelExplainer 时,在摘要图中遇到了特征名不匹配的问题。尽管输入的DataFrame拥有命名的列,但经过预测包装器后,SHAP值似乎失去了与特征名的关联。

import shap
import pandas as pd
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split

# Load a sample dataset
from sklearn.datasets import load_breast_cancer
data = load_breast_cancer()
X = pd.DataFrame(data.data, columns=data.feature_names)
y = data.target

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

model = RandomForestClassifier().fit(X_train, y_train)

# My Custom Wrapper (Simplified version of my project's Hermai-based wrapper)
def model_predict(data):
    # Standardizing output to return probabilities
    return model.predict_proba(data)

# SHAP Initialization
explainer = shap.KernelExplainer(model_predict, shap.sample(X_train, 10))
shap_values = explainer.shap_values(X_test.iloc[:5, :])

# The issue: Summary plot shows "Feature 1, Feature 2..." instead of actual names
shap.summary_plot(shap_values[1], X_test.iloc[:5, :])

解决方案

即使输入DataFrame有命名的列,经过预测包装器后,SHAP值似乎失去了与特征名的关联。

据我所知,这可能是因为没有设置 keep_index。尽管名字里有“索引”的含义,它似乎同时控制是否在数据框中保留索引(行名)以及是否保留列名。内部它将数据表示为DenseData或 DenseDataWithIndex;如果使用DenseDataWithIndex表示,那么它会保留特征名并将其绘制出来。

我还发现,为了让你的summary_plot() 函数在多类别问题上工作(从技术上讲,这也是model_predict的定义方式),我不得不把 shap_values[1] 改为 shap_values[:, :, 1]。我不确定这是因为它对你不起作用,还是因为我们使用了不同版本的SHAP。见注释。

完整代码:

import shap
import pandas as pd
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
import matplotlib.pyplot as plt

# Load a sample dataset
from sklearn.datasets import load_breast_cancer
data = load_breast_cancer()
X = pd.DataFrame(data.data, columns=data.feature_names)
y = data.target

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

model = RandomForestClassifier().fit(X_train, y_train)

# My Custom Wrapper (Simplified version of my project's Hermai-based wrapper)
def model_predict(data):
    # Standardizing output to return probabilities
    y = model.predict_proba(data)
    return y

# SHAP Initialization
explainer = shap.KernelExplainer(model_predict, shap.sample(X_train, 10), keep_index=True)
shap_values = explainer.shap_values(X_test)

for i, target in enumerate(data.target_names):
    # Note: Following code assumes shap version at least 0.45.0. In this version, this is array
    # of shape (n_examples, n_features, n_classes)
    # In prev versions, this has type list and shape [n_classes, n_examples, n_features]
    # See https://shap.readthedocs.io/en/latest/generated/shap.KernelExplainer.html#shap.KernelExplainer.shap_values
    plt.title(f"probabilities for class {target}")
    shap.summary_plot(shap_values[:, :, i], X_test)

注:keep_index 也会改变传给 model_predict() 的参数。它是一个DataFrame,而不是一个NumPy数组。

站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章