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。

  1. 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 特征重要性图均已保存。")

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐