二分类任务中CatBoost模型的SHAP值获取与解读问题
我来帮你解决这个用SHAP解释CatBoost二分类模型的问题,这是处理不平衡分类时很常见的困惑点,咱们一步步拆解:
为什么你得到的是二维SHAP值和单个基准值?
在二分类任务中,SHAP的TreeExplainer默认只返回正类的SHAP贡献(二维数组:样本数×特征数),对应的expected_value是正类的基准预测值(比如训练集上正类的平均概率或对数几率)。这是因为二分类的两类预测是互补的(0类的预测结果可以通过1类推导),所以SHAP默认只输出一类的结果来节省空间。
1. 构造三维(类别数×样本数×特征数)的SHAP值
有两种简单可靠的方法实现你的需求:
方法一:手动互补生成两类SHAP值
因为0类的特征贡献和1类完全相反,你可以直接用正类SHAP值的相反数作为0类的SHAP值,组合成三维数组:
import numpy as np # 先获取正类的SHAP值(原有代码的逻辑) explainer = shap.TreeExplainer(catboost_clf) shap_values_pos = explainer.shap_values(X) # 组合成三维数组:[0类SHAP值, 1类SHAP值] shap_values_both = np.array([-shap_values_pos, shap_values_pos]) # 此时维度为 (2, 1747360, 13),完全符合你的期望
方法二:用CatBoost原生API获取SHAP值
CatBoost本身支持直接输出SHAP值,通过get_feature_importance指定类型即可,再提取需要的部分:
# 使用CatBoost原生方法获取SHAP相关结果 shap_raw = catboost_clf.get_feature_importance(Pool(X, y), type="ShapValues") # 返回的数组是 (样本数, 特征数+1),最后一列是模型的原始预测值(对数几率) shap_values_pos = shap_raw[:, :-1] # 提取前13列作为正类SHAP值 # 同样组合成三维数组 shap_values_both = np.array([-shap_values_pos, shap_values_pos])
2. 提取类别级SHAP值并分析模型
得到三维数组后,你可以针对每个类别做针对性分析:
计算类别专属的特征重要性
通过平均绝对SHAP值衡量每个特征对某一类预测的整体影响:
import pandas as pd # 1类的特征重要性(平均绝对SHAP值) class1_importance = np.mean(np.abs(shap_values_both[1]), axis=0) # 0类的特征重要性 class0_importance = np.mean(np.abs(shap_values_both[0]), axis=0) # 把重要性和特征名对应起来排序查看 feature_names = X.columns class1_importance_df = pd.DataFrame( {"feature": feature_names, "importance": class1_importance} ).sort_values(by="importance", ascending=False)
绘制类别专属的SHAP可视化图
用SHAP的绘图工具直观分析,比如1类的汇总图(展示特征对预测的正负影响):
# 1类的SHAP汇总图 shap.summary_plot(shap_values_both[1], X, title="SHAP Summary for Class 1 (Positive Class)") # 0类的SHAP汇总图 shap.summary_plot(shap_values_both[0], X, title="SHAP Summary for Class 0 (Negative Class)")
单个样本的类别级贡献分析
查看某个样本中,每个特征对两类预测的具体影响:
sample_idx = 0 # 选第一个样本举例 # 分析该样本对1类的贡献 shap.waterfall_plot(shap.Explanation( values=shap_values_both[1][sample_idx], base_values=explainer.expected_value, data=X.iloc[sample_idx], feature_names=X.columns ), title="Feature Contributions to Class 1 Prediction (Sample {})".format(sample_idx)) # 分析该样本对0类的贡献(注意基准值要换成1 - 正类基准值,概率场景下) shap.waterfall_plot(shap.Explanation( values=shap_values_both[0][sample_idx], base_values=1 - explainer.expected_value, data=X.iloc[sample_idx], feature_names=X.columns ), title="Feature Contributions to Class 0 Prediction (Sample {})".format(sample_idx))
3. 关于单个expected_value的问题
你看到的expected_value是正类的基准预测值:
- 如果模型用
Logloss损失,默认输出是对数几率,此时expected_value是训练集上正类的平均对数几率 - 如果你设置
model_output="probability",expected_value就是训练集上正类的平均概率
对应0类的基准值可以直接推导:
- 对数几率场景:
-explainer.expected_value - 概率场景:
1 - explainer.expected_value
如果需要两类的基准值数组,可以手动构造:
# 概率场景下的两类基准值 base_values_both = np.array([1 - explainer.expected_value, explainer.expected_value])
小细节优化
- 你代码里的
trainx_preds = catboost_clf.predict(X_test)变量名有点混淆,建议改成test_preds更清晰 - 使用SHAP时不需要把X包装成CatBoost的
Pool,直接传入DataFrame或numpy数组即可,SHAP会自动处理
内容的提问来源于stack exchange,提问作者Dhvani Shah
相关产品推荐
相关产品推荐

