在将自定义模型包装器传递给SHAP KernelExplainer时,如何保持特征对齐?
我正在进行一个可解释性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导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。