关于shap对特征排序和可视化的一些笔记
shap的核心原则是:需要模型的连续输出值(概率或者置信度分数),而非离散的类别标签,否则会出错,比如我在对SVM定义explaier的时候如果explainer = shap.KernelExplainer(best_svm, background_summary)就会出错,最后shap值算出来全是0(还是通过printshap值才发现的)。询问ds得到如图下回答:
1. **支持向量机(SVM)**:线性SVM和核SVM通常使用decision_function()返回到超平面的距离,这适用于SHAP。分类概率可以用predict_proba(),但有些实现可能没有,比如SVC默认可能需要设置probability=True。
-
SVM的特殊性:
-
若使用
SVC(probability=True)启用了概率估计,则也可用.predict_proba(),但计算开销较大 -
未设置
probability=True时,.predict_proba()不可用,必须用.decision_function()
-
2. **逻辑回归**:虽然属于线性模型,但通常使用predict_proba()返回概率,不过某些情况下decision_function()也能用,返回线性部分的得分。需要说明两者都可以,但根据需求选择。
3. **决策树和随机森林**:这些基于树的分类器一般用predict_proba()获取概率,作为连续输出。如果用户使用类别标签,SHAP值可能不正确,需要提醒他们使用概率。
4. **梯度提升机(如XGBoost、LightGBM)**:同样使用predict_proba(),尤其是分类任务。回归任务则直接用predict()。
5. **神经网络(如Keras、PyTorch)**:需要自定义输出函数,获取最后一层的激活值,比如logits,而不是最终的分类结果。
| 模型类型 | 应使用的输出函数 | 原因 | 示例代码 |
|---|---|---|---|
| 线性SVM | .decision_function() | 返回样本到超平面的距离(连续值),反映分类置信度 | explainer = shap.KernelExplainer(svm.decision_function, data) |
| 核SVM(非线性SVM) | .decision_function() | 同上 | 同上 |
| 逻辑回归 | .predict_proba() 或 .decision_function() | .predict_proba()返回概率,.decision_function()返回线性得分(两者均可) | shap.KernelExplainer(lr.predict_proba, data) |
| 决策树分类器 | .predict_proba() | 返回类别概率(如0.2,0.80.2,0.8) | shap.TreeExplainer(rf.predict_proba) |
| 随机森林/XGBoost分类器 | .predict_proba() | 同上 | 同上 |
| 神经网络分类器 | 自定义输出层logits | 需要获取未归一化的原始输出(如softmax前的logits) | 见下方详细说明 |
| 线性回归 | .predict() | 回归任务本身输出连续值 | shap.LinearExplainer(lm.predict, data) |
| 梯度提升回归器 | .predict() | 同上 | shap.TreeExplainer(gb.predict) |
关于神经网络用keras举例如下:
# 定义输出logits的模型(假设原始模型输出softmax)
logit_model = keras.Model(inputs=model.inputs, outputs=model.layers[-2].output)
# 创建explainer时使用logits
explainer = shap.KernelExplainer(logit_model, background_data)
#验证输出值是否连续:
# 检查输出是否为连续值(非整数)
print(model.predict_proba(X_test[:1])) # 应该输出类似 [[0.3, 0.7]]
print(model.decision_function(X_test[:1])) # 应该输出浮点数(如 1.24)
下面是关于2个输出例子:
# 错误:直接使用返回类别标签的predict()
explainer = shap.KernelExplainer(svm.predict, data) # 输出全0/1导致SHAP失效
# 正确:使用连续输出
explainer = shap.KernelExplainer(svm.decision_function, data)
快速测试方法:
运行以下代码检查模型输出是否符合SHAP要求:
sample = X_test[:1]
print("Predict label :", model.predict(sample)) # 应该输出 0/1
print("Predict proba :", model.predict_proba(sample))# 应该输出概率数组
print("Decision function :", model.decision_function(sample)) # 应该输出浮点数
如果只有 predict() 输出有效值,说明该模型不适合直接使用SHAP(需修改输出方式或换模型)
接下来是对自己的几个模型进行特征排序可视化
在针对树的特征可视化的时候也遇到一些问题,主要还是因为shap_values()的shape问题导致,
我用print查看shap_values_class1.shape的时候发现输出是(10,10,2)
-
shap_values_class1.shape = (10, 10, 2):这表示 SHAP 值被计算成了一个三维张量,不符合二分类任务的预期结构(预期应为二维矩阵(n_samples, n_features))。以至于后续mean_shap_values.shape = (10, 2)。 -
mean_shap_values.shape = (10, 2):直接原因是三维 SHAP 值取平均后保留了多余的维度。 -
所以用如下代码:if isinstance(shap_values, list):
# 结构1:列表 [class_0_matrix, class_1_matrix] (正确结构)
shap_values_class1 = shap_values[1] # 二分类任务选择索引1
else:
# 结构2:三维数组 (n_samples, n_features, n_classes)
shap_values_class1 = shap_values[:,:,1] -
之前的代码只是取了shape_values[1]现在增加了一个else多了一个判断。
# 假设 best_model_names、best_models 和 results_path 已定义
models_dict = dict(zip(best_model_names, best_models))
# 若需要保证每次采样相同,可设置随机种子
np.random.seed(42)
# 选择100个样本作为背景数据(根据实际情况调整)
background_summary = shap.sample(x_train_scaled, x_test_scaled.shape[0])
for model_name, model in models_dict.items():
print(f"正在处理 {model_name} ...")
# 选择合适的解释器
if model_name in ["Decision Tree", "Random Forest"]:
explainer = shap.TreeExplainer(model, background_summary)
shap_values = explainer.shap_values(x_test_scaled, check_additivity=False)
# 提取正类(class 1)的SHAP值
if isinstance(shap_values, list):
# 结构1:列表 [class_0_matrix, class_1_matrix] (正确结构)
shap_values_class1 = shap_values[1] # 二分类任务选择索引1
else:
# 结构2:三维数组 (n_samples, n_features, n_classes)
shap_values_class1 = shap_values[:,:,1]
#计算特征的平局shaa值用于后续排序
mean_shap_values = np.abs(shap_values_class1).mean(axis=0)
feature_names = x_test_scaled.columns
feature_importance_df = pd.DataFrame({
'Feature': feature_names,
'MeanImportance': mean_shap_values
}).sort_values(by='MeanImportance', ascending=True)
# ----- 绘制水平柱形图 -----
plt.figure(figsize=(12, 8))
plt.barh(feature_importance_df['Feature'], feature_importance_df['MeanImportance'])
plt.xlabel('SHAP Feature Importance')
plt.title(f"{model_name} Feature Importance")
plt.savefig(f'{results_path}shap_bar_{model_name}.png', bbox_inches='tight')
plt.close()
# ----- 绘制散点图(beeswarm图) -----
#使用 shap.summary_plot 绘制所有样本的 SHAP 值分布
plt.figure()
shap.summary_plot(
shap_values_class1,
x_test_scaled,
feature_names=feature_names.tolist(), # 显式传递特征名
show=False
)
plt.savefig(f'{results_path}shap_scatter_{model_name}.png', bbox_inches='tight')
plt.close()
elif model_name in ["Logistic Regression", "SVM"]:
explainer = shap.KernelExplainer(model.decision_function, background_summary)
shap_values = explainer.shap_values(x_test_scaled)
# 计算每个特征的平均绝对 SHAP 值
mean_shap_values = np.abs(shap_values).mean(axis=0)
feature_names = x_test_scaled.columns
# 创建包含特征名称和平均 SHAP 值的 DataFrame,并按照重要性排序
feature_importance_df = pd.DataFrame({
'Feature': feature_names,
'MeanImportance': mean_shap_values
}).sort_values(by='MeanImportance', ascending=True)
# ----- 绘制水平柱形图 -----
plt.figure(figsize=(12, 8))
plt.barh(feature_importance_df['Feature'], feature_importance_df['MeanImportance'])
plt.xlabel('SHAP Feature Importance')
plt.title(f"{model_name} Feature Importance")
plt.savefig(f'{results_path}shap_bar_{model_name}.png', bbox_inches='tight')
plt.close()
# ----- 绘制散点图(beeswarm图) -----
# 使用 shap.summary_plot 绘制所有样本的 SHAP 值分布
plt.figure()
shap.summary_plot(shap_values, x_test_scaled, feature_names=feature_names.tolist(),show=False)
plt.title(f"{model_name} SHAP Summary Scatter")
plt.savefig(f'{results_path}shap_scatter_{model_name}.png', bbox_inches='tight')
plt.close()
print("所有模型的 SHAP 特征重要性图均已保存。")
更多推荐

所有评论(0)