假设我们有一个二元分类问题,我们的目标是分为1和0两个类别。我打算使用树分类器根据特征来预测1和0。此外,我可以使用SHAP值来对预测1和0的特征重要性进行排序。到目前为止,一切顺利!
现在假设我想知道仅预测1的特征的重要性,有什么推荐的方法吗?我可以将数据分成两部分(名义上:df_tot = df_zeros + df_ones
),然后在分类器中使用df_ones
,并提取其SHAP值,但这样做的话,目标只会包含1,因此模型实际上并不会学习如何分类。所以我想知道如何处理这样的问题?
回答:
让我们准备一些二元分类数据:
from seaborn import load_datasetfrom sklearn.model_selection import train_test_splitfrom lightgbm import LGBMClassifierimport shaptitanic = load_dataset("titanic")X = titanic.drop(["survived","alive","adult_male","who",'deck'],1)y = titanic["survived"]features = X.columnscat_features = []for cat in X.select_dtypes(exclude="number"): cat_features.append(cat)# think about meaningful ordering instead X[cat] = X[cat].astype("category").cat.codes.astype("category")X_train, X_val, y_train, y_val = train_test_split(X,y,train_size=.8, random_state=42)clf = LGBMClassifier(max_depth=3, n_estimators=1000, objective="binary")clf.fit(X_train,y_train, eval_set=(X_val,y_val), early_stopping_rounds=100, verbose=100)
为了回答你的问题,要按类别提取SHAP值,可以按类别标签进行子集划分:
explainer = shap.TreeExplainer(clf)shap_values = explainer.shap_values(X_train)sv = np.array(shap_values)y = clf.predict(X_train).astype("bool")# shap values for survivalsv_survive = sv[:,y,:]# shap values for dyingsv_die = sv[:,~y,:]
然而,一个更有趣的问题是你可以用这些值做什么。
一般来说,通过查看summary_plot
(针对整个数据集)可以获得有价值的见解:
shap.summary_plot(shap_values[1], X_train.astype("float"))
解释(全局):
- 性别、舱位等级和年龄是决定结果的最具影响力的特征
- 男性、较不富裕、年龄较大的人生存机会减少
可以按以下方式提取前三个全局最具影响力的特征:
idx = np.abs(sv[1,:,:]).mean(0).argsort()features[idx[:-4:-1]]# Index(['sex', 'pclass', 'age'], dtype='object')
如果你想按类别进行分析,可以单独为幸存者(sv[1,y,:]
)这样做:
# top3 features for probability of survivalidx = sv[1,y,:].mean(0).argsort()features[idx[:-4:-1]]# Index(['sex', 'pclass', 'age'], dtype='object')
对于未幸存者(sv[0,~y,:]
)也是如此:
# top3 features for probability of dieingidx = sv[0,~y,:].mean(0).argsort()features[idx[:3]]# Index(['alone', 'embark_town', 'parch'], dtype='object')
注意,这里我们使用了平均SHAP值,并且表示我们对幸存者的最大值和未幸存者的最小值感兴趣(最低值,接近0,也可能意味着没有恒定的、单向的影响)。使用绝对值的平均值也可能有意义,但解释将是最具影响力的,无论方向如何。
为了做出明智的选择,是否更喜欢平均值还是绝对值的平均值,必须了解以下事实:
- SHAP值可以是正的也可以是负的
- SHAP值是对称的,增加/减少一个类别的概率会以相同的量减少/增加另一个类别的概率(由于p₁ = 1 – p₀)
证明:
#shap valuessv = np.array(shap_values)#base valuesev = np.array(explainer.expected_value)sv_died, sv_survived = sv[:,0,:] # + constantprint(sv_died, sv_survived, sep="\n")# [-0.73585563 1.24520748 0.70440429 -0.15443337 -0.01855845 -0.08430467 0.02916375 -0.04846619 0. -0.01035171]# [ 0.73585563 -1.24520748 -0.70440429 0.15443337 0.01855845 0.08430467 -0.02916375 0.04846619 0. 0.01035171]
你很可能会发现性别和年龄对幸存者和非幸存者都起到了最具影响力的作用;因此,与其分析每个类别中最具影响力的特征,不如看看是什么让两个性别和年龄相同的乘客一个幸存,另一个未幸存(提示:在数据集中找到这样的案例,将一个作为背景,并分析另一个的SHAP值,或者尝试分析一个类别与另一个类别作为背景)。
你可以使用dependence_plot
进行进一步的分析(在全局或按类别基础上):
shap.dependence_plot("sex", shap_values[1], X_train)
解释(全局):
- 男性生存的概率较低(较低的SHAP值)
- 舱位等级(财富)是下一个最具影响力的因素:较高的舱位等级(较少的财富)降低了女性生存的几率,反之亦然,对男性来说