希望我之前的几篇文章——《SHAP教程-01 |解密“黑箱”模型的底层逻辑》——已经为您的数据科学实践带来了实质性的帮助。在本文中,我将带领大家探索 SHAP 可视化图表的更多创新应用,进一步提升模型解释的深度与广度。如果您尚未阅读前文,我建议您先行阅读,再来品味本文的精妙之处,定能收获更多。
序文:
我曾被一位‘绿中介’资深置业顾问的带看艺术所折服。他并未急于推介,而是引我先观其风骨——整栋建筑的气度,社区的曲径通幽,与那片如茵绿地所营造出的宁静致远。信步之间,他将周边的便利娓娓道来,恰到好处。而后,方入其内,逐一赏鉴。行至客堂,他示意我亲手触摸,去开启那些柜门与抽屉。当一束温润的嵌入式灯光悄然亮起时,我心中豁然一动。
那一刻,未来的生活画卷已在眼前徐徐展开:高朋满座,在此间品茶谈笑;家人闲坐,于庭院灯下共享天伦。 孩子的笑语在花木间回荡,伴着一轮明月……所谓岁月静好,大抵就是这般模样。
话说回来,我们在呈现机器学习模型的过程,与此颇有异曲同工之妙。我们首先需要向用户阐释模型的**全局可解释性 (global interpretability),即证明整个模型在宏观层面是合理且符合逻辑的。这意味着模型中预测变量与目标变量之间的关系,应与业务领域的既有认知和专业知识相吻合。在此基础上,我们还需进一步阐释模型的局部可解释性 (local interpretability)**,即证明模型对每一个独立样本的预测结果同样是清晰且可信的。我们需要能够清楚地解释,为何针对特定样本的特定预测变量取值,模型会给出相应的预测。而 SHAP (SHapley Additive exPlanations) 值,恰恰是这样一个强大的工具,能够同时胜任全局和局部两个层面的模型解释任务。
正所谓“细节决定成败,匠心铸就完美”。您或许曾为了打造一份引人入胜的演示文稿,花费数小时精心打磨幻灯片或图表。为了让您的听众更透彻地理解您的预测结果,并最终采纳您构建的模型,这份匠心投入无疑是值得的。在本章中,我们将进一步拓展 SHAP 可视化的知识边界,学习更多实用且有洞察力的图。这些图将按照由简入繁的顺序逐一呈现。
-
第一:在 全局可解释性方面,我们将深入探讨 (a)条形图(bar plot) 的进阶用法,(b) 精巧的队列图(cohort plot),以及 (c) 信息丰富的热力图(heatmap plot)。 -
第二:在 局部可解释性层面,我们将一同领略 (d) 直观的瀑布图(waterfall plot),(e) 聚焦单一样本的条形图,(f) 强大的力图(force plot),以及 (g) 清晰的决策图(decision plot)。 -
第三:此外,我还将演示如何巧妙运用 Matplotlib 库对 SHAP 图表进行个性化定制,满足您独特的展示需求。
最后,如果您正在构建一个目标变量包含多个类别的多分类模型,SHAP 值同样能够游刃有余地解释这类复杂模型。
0. 案例准备:构建 XGBoost 模型
为了确保读者能够方便地比较不同图表的效果,本文将继续沿用之前数篇文章中所使用的红酒品质数据集。该数据集的目标变量是红酒的品质评分,范围从 0 到 10(分数越高,品质越好)。输入特征则是构成每个红酒样本的各项理化指标,包括固定酸度、挥发性酸度、柠檬酸含量、残留糖分、氯化物含量、游离二氧化硫、总二氧化硫、密度、pH 值、硫酸盐含量以及酒精浓度。数据集中共包含 1,599 个红酒样本,我们将按照惯例将其划分为 1,279 个训练样本和 320 个测试样本,并基于此构建一个 XGBoost 模型作为后续 SHAP 分析的基础。
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
import xgboost as xgb
df = pd.read_csv('winequality-red')
features = ['fixed acidity', 'volatile acidity',
'citric acid', 'residual sugar', 'chlorides',
'free sulfur dioxide', 'total sulfur dioxide',
'density', 'pH', 'sulphates', 'alcohol']
Y = df['quality']
X = df[features]
X_train, X_test, Y_train, Y_test = train_test_split(X, Y,
test_size = 0.2, random_state = 1234)
xgb_model = xgb.XGBRegressor(random_state=42)
xgb_model.fit(X_train, Y_train)
# SHAP
import shap
explainer = shap.Explainer(xgb_model)
shap_values = explainer(X_test)
1. 洞察全局:全局可解释性图表深度解析
全局可解释性旨在揭示模型整体的决策模式和特征影响。
1.1 特征重要性条形图:聚焦核心,兼顾整体
当模型中包含大量预测变量时,传统的特征重要性条形图往往会显得冗长而杂乱,难以快速抓住重点,从而削弱其信息传递的效力。我们是否应该简单地截断图表,只显示最重要的几个特征?这样做固然简洁,但听众将无从得知那些被“舍弃”的次要特征的集体贡献——万一它们的累积重要性超过了顶部的几个核心特征呢?SHAP 的条形图巧妙地解决了这一难题。我们可以指定希望展示的核心预测变量数量,并能自动将那些未单独显示的次要特征的贡献汇总起来,作为一个整体进行呈现。这种处理方式的巧妙之处在于,它既突出了关键驱动因素,又客观地向听众展示其余特征的集体影响力,使得特征重要性的评估更为全面和公正。
shap.plots.bar(shap_values, max_display=10) # default is max_display=12
图 (1.1): SHAP 特征重要性条形图 (清晰展示了最重要的几个特征,并将其他特征的平均绝对SHAP值汇总为“剩余X个特征”,使得整体特征贡献一目了然
1.2 队列图 (Cohort Plot):深入剖析群体异质性
在很多实际场景中,将总体数据根据某一关键特征划分为不同的队列进行分析,往往能带来更深刻的洞察,揭示群体间的异质性。图 (1.2) 便是一个生动的例子:它将我们的红酒样本根据酒精含量分成了两个队列:酒精含量低于 11.15 的样本,以及酒精含量高于或等于 11.15 的样本。结合图 (1.1) 我们已知“酒精”是影响红酒品质最重要的特征,而图 (1.2) 则进一步告诉我们,“酒精”这个特征在第二个队列(高酒精含量组)中的重要性甚至更为突出。
SHAP 的队列图通过调用 .cohorts(N) 方法实现这一划分,它能将总体自动分割成 N 个队列。其内部巧妙地运用了 scikit-learn 的 DecisionTreeRegressor 算法来寻找最优的分割点。在我们的案例中,320 个测试样本被自动划分成了一个包含 237 个样本的队列和另一个包含 83 个样本的队列。
shap.plots.bar(shap_values.cohorts(2).abs.mean(0))
图 (1.2): SHAP 队列图 (实红色为酒精含量<11.15的队列,白格红为酒精含量>=11.15的队列。每个队列内部展示了各自的特征重要性条形图,突显了不同群体中特征影响的差异
这个最优分割点的阈值恰好是酒精含量 = 11.15。通过观察高酒精含量队列的条形图(如图中右侧部分),我们可以清晰地看到,一个红酒样本之所以被归入“酒精≥11.15”这个高品质潜力队列,主要是因为其较高的酒精含量(SHAP 值贡献约 0.5)、较高的硫酸盐含量(SHAP 值贡献约 0.2)以及较高的挥发性酸度(SHAP 值贡献约 0.18)等因素。
这样的洞察对于市场营销策略的制定极具启发性:例如,我们可以将第一个队列(低酒精含量,可能品质相对普通)定位为“优选”或“日常”系列,而将另一个队列(高酒精含量,可能品质更优)打造为“臻品”或“鉴赏级”系列,从而实现更精准的市场细分和产品推广。
图片:酒精含量>=11.15队列的特征重要性条形图,直观展示了该队列中各特征对模型输出的平均绝对影响
1.3 热力图 (Heatmap Plot):多维度信息聚合与模式发现
接下来,让我们随机选取 100 个红酒样本,并利用 SHAP 生成信息量极为丰富的热力图。如图 (1.3) 所示,这张图表蕴含了多重解读维度。
shap.plots.heatmap(shap_values[1:100])
首先,热力图的 Y 轴清晰地标注了各个预测变量,并按照其全局重要性从上到下进行了排序(重要性由右侧的水平条形图直观展示)。这种全局特征重要性的排序,揭示了我们的 XGBoost 模型在整体上认为“酒精”是决定红酒品质的首要因素,其次是“硫酸盐”,依此类推。
图 (1.3): SHAP 热力图 (I) (Y轴为按重要性排序的特征,X轴为经过层次聚类的样本实例。单元格颜色深浅和色调代表对应特征对该样本预测的SHAP值大小及方向。图顶部的f(x)曲线显示了每个样本的模型预测输出值
其次,热力图的 X 轴代表了我们选取的这 100 个样本实例。图中的颜色则直观地反映了每个特征对每个样本预测的 SHAP 值的大小和方向。以热力图最右侧的第 100 号葡萄酒样本为例,其“酒精”特征对应的单元格呈现深红色,这表明“酒精”含量对该特定样本的高品质预测贡献最大。
第三,请注意图 (1.3) 顶部的 f(x) 曲线,它描绘了模型对 X 轴上每个样本的预测输出值。可以看到,第 100 号样本的 f(x) 值较高,意味着模型预测其品质优良。结合其“酒精”特征的红色单元格,我们可以推断出,是较高的酒精含量极大地提升了该样本的品质预测。
第四,我们可能已经敏锐地观察到,X 轴上的样本并非随机排列,而是呈现出颜色聚集的模式。这是因为 SHAP 的热力图功能在生成图像前,默认会对样本实例进行层次聚类 (hierarchical clustering,通过 shap.order.hclust 实现),然后根据聚类结果对 X 轴上的样本进行排序。这样做有助于将具有相似 SHAP 值模式的样本聚集在一起,从而更容易发现潜在的群体特征和预测规律。
第五,二维热力图的视觉中心(通常用灰色或中性色表示SHAP值为零的区域)对应的是模型的基准值 (base value,通过 .base_value 获取),即所有训练样本预测值的平均。热力图清晰地揭示了:那些具有较高预测值(即 f(x) 曲线上处于高位的样本,通常位于图的右侧,取决于聚类结果)的葡萄酒,往往与较高的酒精含量和较高的硫酸盐含量(表现为深红色单元格)显著相关。
我特意强调之前是“任意”选取了 100 个数据样本来生成图 (1.3)。那么,如果我换一批样本,得到的解释模式会发生显著变化吗?为了验证这一点,我在图 (1.4) 中选取了另一组不同的样本生成了热力图。正如您所见,核心的解释结论依然稳固:具有较高预测输出(本图中 f(x) 曲线高值集中在左侧)的样本,依然与高酒精含量和高硫酸盐含量(表现为红色)紧密关联。这证明了热力图所揭示的模式具有一定的稳定性和代表性。
shap.plots.heatmap(shap_values[200:300])
图 (1.4): SHAP 热力图 (II) (展示了另一组样本的热力图,验证了之前观察到的模式的稳定性,即高预测值与高酒精、高硫酸盐含量相关
2. 洞察个体:局部可解释性图表深度解析
全局解释为我们描绘了模型的整体行为,而局部解释则聚焦于理解模型为何对单个样本做出特定预测。SHAP 提供了多种强大的可视化工具来实现这一点。
在本节中,我将重点演示四种常用的局部可解释性图表:瀑布图 (waterfall plot)、条形图 (bar plot)、力图 (force plot) 和决策图 (decision plot)。为了便于比较它们各自的特点和展现方式,我将针对两个相同的样本(观察样本 1 和观察样本 2)重复应用这四种图表。
2.1.1 观察样本 1 的瀑布图:层层剖析预测贡献
瀑布图是一种极具表现力的可视化工具,它清晰地展示了在给定样本的各个特征值条件下,模型是如何一步步“推导出”最终预测结果的。解读瀑布图时,我们从图的底部(基准值)开始,向上逐一累加(红色条,表示正向贡献)或 减去(蓝色条,表示负向贡献)各个特征的 SHAP 值,最终到达图顶部的模型预测输出。
下图展示了对测试集 (X_test) 中第一个观察样本的预测分析。整个“推导”过程始于底部的基准值 5.637,这个值代表了模型对所有训练样本的平均预测。而模型对观察样本 1 的最终预测值是 4.139,显示在图的顶端。那么,这个 4.139 是如何得到的呢?瀑布图清晰地揭示了这一过程:5.637 (基准值) – 0.04 (某特征的负贡献) – 0.04 (另一特征的负贡献) – 0.09 + 0.09 + 0.11 – 0.13 – 0.27 – 0.3 (总二氧化硫的负贡献) – 0.34 (挥发性酸度的负贡献) – 0.5 (酒精含量的负贡献) ≈ 4.139 请注意,由于浮点数运算,可能存在微小的舍入误差。
在瀑布图中,每个特征名称旁边都标注了该特征在该样本中的实际取值。例如,对于第一个观察样本,“酒精”含量的值是 9.4。那么,与所有其他葡萄酒相比,9.4 的酒精含量是高还是低呢?我们知道 SHAP 模型是基于训练数据集构建的,因此我们可以通过计算 X_train.mean() 来获取训练集中各特征的平均值。计算可得,所有葡萄酒的平均“酒精”含量约为 10.41。观察样本 1 的酒精含量 (9.4) 低于平均水平。鉴于较高的“酒精”含量通常对品质评分有积极贡献,而该样本的酒精含量偏低,因此“酒精”这一特征对该酒的品质预测产生了 -0.3 的负向SHAP贡献,正如在图 (2.1.1) 中所清晰展示的那样。
图片:测试集第一个观察样本的特征值
shap.plots.waterfall(shap_values[0])
图 (2.1.1): 观察样本 1 的 SHAP 瀑布图 (从底部的
即模型预测均值出发,每个特征的SHAP值(红色正向,蓝色负向)依次叠加,最终得到顶部的
即该样本的预测值。每个特征旁均标注了其原始值
2.1.2 观察样本 2 的瀑布图:异曲同工的个体解读
接下来,我们对测试集中的第二个观察样本进行同样的瀑布图分析。模型对该样本的最终预测值为 5.582。其推导过程如下:5.637 (基准值) + 0.01 + 0.03 – 0.03 + 0.04 + 0.1 + 0.12 – 0.12 – 0.15 + 0.24 (硫酸盐的正贡献) – 0.29 (酒精含量的负贡献) ≈ 5.582。
shap.plots.waterfall(shap_values[1]) # 第二个观察
图 (2.1.2): 观察样本 2 的 SHAP 瀑布图,与样本1类似,展示了各特征如何从基准值出发,逐步影响并最终形成样本2的预测值
2.2.1 观察样本 1 的条形图:聚焦个体特征贡献的另一种视角
与瀑布图逐步累加的展现方式不同,针对单个样本的 SHAP 条形图以零点为中心,直观地显示了各个特征对该样本预测结果的贡献大小和方向。如图 (2.2.1) 所示,红色条形代表正向贡献(推高预测值),蓝色条形代表负向贡献(拉低预测值),条形的长度则反映了贡献的强度。
shap.plots.bar(shap_values[0]) # 第一个样本
图 (2.2.1): 观察样本 1 的 SHAP 条形图 ,横轴为SHAP值,条形按绝对SHAP值大小降序排列,清晰展示了对该样本预测影响最大的几个特征及其贡献方向
2.2.2 观察样本 2 的条形图
同样地,图 (2.2.2) 展示了观察样本 2 的 SHAP 条形图,揭示了影响其预测结果的关键特征及其贡献。
shap.plots.bar(shap_values[1]) # 第二个样本
图 (2.2.2): 观察样本 2 的 SHAP 条形图
2.3.1 观察样本 1 的力图 (Force Plot):动态平衡的预测力量博弈
关于力图的详细解读,我在之前的文章《SHAP教程-01 |解密“黑箱”模型的底层逻辑》中已有深入阐述。简而言之,力图将模型的预测过程比作一场“博弈”。对于观察样本 1,我们的 XGBoost 模型给出的预测评分为 4.14。力图生动地展示了这个预测是如何形成的:它从一个代表模型平均预测的基准值 (5.647) 出发,图中红色的特征(如柠檬酸、pH值)如同“加速器”,将预测值向右(高分方向)推动;而蓝色的特征(如酒精含量、挥发性酸度、总二氧化硫)则扮演“减速器”的角色,将预测值向左(低分方向)拉拽。正是这些正负力量的相互作用与平衡,最终将预测稳定在了 4.14。
explainer = shap.TreeExplainer(xgb_model)
shap_values = explainer.shap_values(X_test)
shap.initjs()
def p(j):
return(shap.force_plot(explainer.expected_value, shap_values[j,:], X_test.iloc[j,:]))
p(0)
图 (2.3.1): 观察样本 1 的 SHAP 力图 (中间的粗黑线代表模型最终输出值4.14,基准值在左侧。红色区域的特征推动预测值升高,蓝色区域的特征则使其降低
2.3.2 观察样本 2 的力图
对于观察样本 2,模型预测其品质评分为 5.58。图 (C.3.2) 中的力图同样清晰地揭示了达成这一预测结果的关键驱动因素及其作用方向。
p(1)
图 (2.3.2): 观察样本 2 的 SHAP 力图
2.4.1 观察样本 1 的决策图 (Decision Plot):复杂预测路径的清晰呈现
当模型中涉及的预测变量数量非常多时,力图可能会因为信息过于密集而显得有些杂乱,不易解读。在这种情况下,决策图便成了一个更优的选择。图 (2.4.1) 展示了观察样本 1 的决策图。它清晰地表明,模型对该样本的最终预测评分为 4.139 (约等于 4.14)。图中央的垂直虚线代表了模型的基准预测值。每一条从底部基准值出发向上延伸的折线,代表了一个特征对预测路径的影响。括号中的数字是该特征在当前样本中的实际值。例如,观察样本 1 的“酒精”含量为 9.4。我们已经知道,较高的酒精含量通常会正向提升葡萄酒的品质。那么,9.4 的酒精含量算好吗?正如我们在 (2.1.1) 节中讨论过的,通过 X_train.mean() 我们可以得到训练集中所有葡萄酒的平均“酒精”含量约为 10.41。由于观察样本 1 的酒精含量 (9.4) 低于平均水平,因此“酒精”这个特征对该酒的品质预测贡献为负,导致预测路径向左偏折,如图 (2.4.1) 所示。决策图以这种逐级决策路径的方式,清晰地展现了各个特征如何共同作用,将预测从基准值一步步引导至最终的输出值。
expected_value = explainer.expected_value
print("The expected value is ", expected_value)
shap_values = explainer.shap_values(X_test)[0]
shap.decision_plot(expected_value, shap_values, X_test)
图 (2.4.1): 观察样本 1 的 SHAP 决策图,X轴为模型输出值,Y轴为特征(按SHAP值绝对大小排序。预测路径从底部的基准值开始,每个特征的SHAP值使其向左(负贡献)或向右(正贡献)偏折,最终到达顶部的模型预测值
2.4.2 观察样本 2 的决策图
我们同样为观察样本 2 生成决策图。XGBoost 模型预测其品质评分为 5.58。决策图将清晰地展示这一预测是如何通过各个特征的逐级影响而形成的。
shap_values = explainer.shap_values(X_test)[1]
shap.decision_plot(expected_value, shap_values, X_test)
图 (2.4.2): 观察样本 2 的 SHAP 决策图
3. 二分类目标的特殊考量:对数几率与概率的转换
在处理二分类问题时,我们通常期望模型的输出是介于 0 和 1 之间的概率值。通过在 XGBoost 的 XGBRegressor() (或更常用的 XGBClassifier()) 中指定目标函数为 reg:logistic (或 binary:logistic),我们可以构建一个二分类模型。然而,一个值得注意的细节是,在默认情况下,XGBoost 模型(尤其是其 predict 方法输出原始分数时或某些 SHAP 解释器直接作用于原始分数时)的 SHAP 瀑布图,其 X 轴呈现的单位是对数几率 (log-odds),而非人类直观理解的预测概率。
在本节中,我将详细阐述基于对数几率的瀑布图是如何解读的,以及如何将其巧妙地转换为以预测概率为尺度的瀑布图,使其更符合我们的直观认知。为了更好地对比,我会先展示一个基于概率输出的力图。
3.1 二分类模型的力图:概率视角的解释
首先,我们创建一个二分类的目标变量 y = np.where(df[‘quality’]>5, 1, 0) (即品质评分大于5的为优质酒,记为1,否则为0),并使用 binary:logistic 参数来构建一个 XGBoost 二分类模型。然后,我们为观察样本 1 生成力图。
from sklearn.model_selection import train_test_split
import xgboost as xgb
features = ['fixed acidity', 'volatile acidity', 'citric acid', 'residual sugar', 'chlorides',
'free sulfur dioxide', 'total sulfur dioxide', 'density', 'pH', 'sulphates', 'alcohol']
Y = np.where(df['quality']>5,1,0)
X = df[features]
X_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size = 0.2, random_state = 1234)
xgb_binary_model = xgb.XGBRegressor(objective='reg:logistic',random_state=42)
xgb_binary_model.fit(X_train, Y_train)
shap.initjs()
def p(j):
explainer = shap.TreeExplainer(xgb_binary_model)
xgb_binary_shap_values = explainer.shap_values(X_train)
return(shap.force_plot(explainer.expected_value, xgb_binary_shap_values[j,:], X_train.iloc[j,:], link='logit'))
p(0)
图 (3.1): 观察样本 1 的二分类模型力图,力图的输出值通常直接是概率,或者可以配置为概率
3.2 XGBoost 瀑布图:揭秘对数几率的“面纱”
下图是直接基于 XGBoost 二分类模型(可能输出原始分数)生成的观察样本 1 的瀑布图。可以看到,图中标注的最终预测值 。我们期望二分类模型的输出是一个介于 0 和 1 之间的概率,为何此处的预测值远大于 1.0 呢?原因就在于,这个瀑布图中 X 轴的单位是对数几率 (log-odds),而非概率。正如 SHAP 官方文档所解释的,XGBoost 分类器(在某些情况下)会输出经过逻辑链接函数(logistic link function)之前的边际值(即对数几率)。
import shap
explainer = shap.Explainer(xgb_binary_model)
xgb_binary_shap_values = explainer(X_train)
shap.plots.waterfall(xgb_binary_shap_values[0])
图 (3.2): 观察样本 1 的二分类模型瀑布图(基于对数odds)
要将对数几率转换为我们熟悉的 [0,1] 区间内的概率,我们需要使用 logistic S型函数 (logistic sigmoid function),其公式为 expit(x) = 1 / (1 + exp(-x)),它恰好是 logit 函数(对数几率函数)的逆函数。换言之,对于
,其对应的概率为 1 / (1 + exp(-4.894)) ≈ 0.992。这个 0.992 正是我们在前面 (3.1) 节力图中所看到的预测概率。
def xgb_shap_transform_scale(original_shap_values, Y_pred, which):
from scipy.special import expit
from scipy.special import expit
untransformed_base_value = original_shap_values.base_values[-1]
original_explanation_distance = np.sum(original_shap_values.values, axis=1)[which]
base_value = expit(untransformed_base_value )
distance_to_explain = Y_pred[which] - base_value
distance_coefficient = original_explanation_distance / distance_to_explain
shap_values_transformed = original_shap_values / distance_coefficient
shap_values_transformed.base_values = base_value
shap_values_transformed.data = original_shap_values.data
return shap_values_transformed
转换代码,核心逻辑是创建一个新的 Explanation 对象,其 base_values 和 values 都经过 expit 函数转换,同时更新 data 用于反映原始特征值。
应用这个转换逻辑后,我们再为同一个观察样本绘制瀑布图:
obs = 0
Y_pred = xgb_binary_model.predict(X_train)
print("The prediction is ", Y_pred[obs])
shap_values_transformed = xgb_shap_transform_scale(xgb_binary_shap_values, Y_pred, obs)
shap.plots.waterfall(shap_values_transformed[obs])
现在,如图所示,瀑布图的 X 轴已经清晰地以预测概率为尺度进行展示,使得解释更加直观易懂。
经过转换后,以概率为输出尺度的 SHAP 瀑布图,更易于直观理解
后面会更新一段更为通用的瀑布图绘制代码,它能够直接处理概率输出,无需进行上述的显式转换,并且支持绘制静态或交互式的瀑布图,功能更为强大。
4. 个性化你的 SHAP 图表:Matplotlib 定制技巧
正如前文提及,SHAP 的许多绘图函数都与强大的 Matplotlib 库紧密集成,这为我们提供了广阔的定制空间,可以随心所欲地调整图表的视觉风格,使其更符合特定的演示需求或审美偏好。关键的一步是,在调用 SHAP 的绘图函数时,通过设置参数 show=False 来阻止其立即显示默认图表,这样我们就能获得底层的 Matplotlib 图形对象,进而进行深度定制。
下面,我将通过一个实例来演示如何解决一个常见问题:图例 (legend) 意外遮挡了图表主体内容,我们需要将其移动到一个更合适的位置。这个例子将沿用 (1.2) 节中将总体划分为三个队列的场景。
4.1 调整图例位置、字体大小等
explainer = shap.Explainer(xgb_model)
shap_values = explainer(X_test)
shap.plots.bar(shap_values.cohorts(3).abs.mean(0))
图 (3.1.1): 默认图例位置不佳,遮挡了部分图表内容。
通过 Matplotlib 的 legend() 函数,我们可以利用 bbox_to_anchor 这个强大的关键字参数来精确控制图例的放置位置。这个参数赋予了我们极高的自由度,可以实现非常精细的手动布局。更多关于图例定制的技巧,推荐查阅 Matplotlib 官方的图例指南。
使用 plt.legend(bbox_to_anchor=(0., 1.02, 1., .102), loc='lower left', ncol=3, mode="expand", borderaxespad=0.) 等代码调整图例位置
shap.plots.bar(shap_values.cohorts(3).abs.mean(0),
show=False)
fig = plt.gcf() # gcf
fig.set_figheight(11)
fig.set_figwidth(9)
#plt.rcParams['font.size'] = '12'
ax = plt.gca() #gca
leg = ax.legend(bbox_to_anchor=(0., 1.02, 1., .102))
for l in leg.get_texts(): l.set_text(l.get_text().replace('Class', 'Klasse'))
plt.show()
图 (3.1.2): 经过精心调整后,图例被移至图表上方,视觉效果更佳。
4.2 在子图中并列展示多个 SHAP 图表
在某些情况下,您可能希望将多个相关的 SHAP 图表(例如,对比不同模型的解释,或展示同一模型不同方面的解释)水平或垂直地并列呈现在同一张画布上,以便于比较和综合分析。这可以通过 Matplotlib 经典的子图 (subplot) 功能轻松实现。
使用 plt.subplot() 或 fig, axes = plt.subplots() 创建子图,并在不同子图上调用 SHAP 绘图函数,同时传入 ax=axes[i] 参数的代码。
fig = plt.figure(figsize=(10,5))
ax1 = fig.add_subplot(121)
shap_values = explainer.shap_values(X_test)[0]
shap.decision_plot(expected_value, shap_values, X_test, show=False)
ax1.title.set_text('The First Observation')
ax2 = fig.add_subplot(122)
shap_values = explainer.shap_values(X_test)[1]
shap.decision_plot(expected_value, shap_values, X_test, show=False)
ax2.title.set_text('The Second Observation')
plt.tight_layout()
plt.show()
图 (3.2): 利用 Matplotlib 的子图功能,将两个 SHAP 图表(例如,两个不同队列的特征重要性图)优雅地并排展示。
5 多分类模型的 SHAP 图表诠释
当您的机器学习任务涉及到将样本划分到三个或更多个互斥类别时,您构建的便是多分类模型。SHAP 值同样能够为这类模型提供深刻的解释。在本节中,我将演示如何操作。首先,我们基于原始的品质评分创建一个新的多分类目标变量 ‘Multiclass’,它包含三个类别:‘Best’ (最优)、‘Premium’ (优质) 和 ‘Value’ (物超所值)。然后,我们通过在 XGBoost 的 XGBClassifier 中将 objective 参数设置为 multi:softprob (或者 multi:softmax 如果类别数量也作为参数) 来训练一个多分类模型。
创建多分类目标变量和训练多分类 XGBoost 模型的代码
# 多酚类
df['Multiclass'] = np.where(df['quality']>6, 'Best', # 2 = 'Best', 1 = 'Premium', 0 = 'Value'
np.where(df['quality']>5, 'Premium','Value'))
Y = df['Multiclass']
X = df[features]
X_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size = 0.2, random_state = 1234)
xgb_model = xgb.XGBClassifier(objective="multi:softprob", random_state=42)
xgb_model.fit(X_train, Y_train)
多分类模型的典型输出是每个样本属于各个类别的概率组成的矩阵。在我们的三分类案例中,模型会对每个样本输出其分别属于 ‘Best’、‘Premium’ 和 ‘Value’ 三个类别的概率,且这三个概率之和为 1.0。在 scikit-learn 框架下,分类器的 .predict_proba() 方法可以直接返回这个概率矩阵,如下面表格中的 “2-Best”、“1-Premium” 和 “0-Value” 列所示。而 .predict() 方法则直接输出模型预测的最可能的类别标签,对应表格中的 “Pred” 列。
multiclass_actual_pred = pd.DataFrame(xgb_model.predict_proba(X_test)).round(2)
multiclass_actual_pred['Actual'] = Y_test.values
multiclass_actual_pred['Pred'] = xgb_model.predict(X_test)
multiclass_actual_pred.columns = ['2 - Best','1 - Premium','0 - Value','Pred','Actual']
multiclass_actual_pred.head()
图片:多分类模型对部分样本的预测概率及最终预测类别
为了评估模型性能,我们可以生成一个混淆矩阵,直观地展示模型在各个类别上的预测准确情况:
pd.crosstab(multiclass_actual_pred['Actual'],multiclass_actual_pred['Pred'])
图片:多分类模型的混淆矩阵,清晰显示了真实类别与预测类别之间的对应关系
对于多分类模型,SHAP 的摘要图 (summary plot) 更强大,它可以按类别分别展示特征的重要性。这也就是说我们可以清晰地看到,对于每一个目标类别,哪些特征对模型将其预测为该类别贡献最大。以下是两种常见的展示方式:
为多分类模型生成 SHAP 摘要图的代码,通常 SHAP 会为每个类别生成一组 SHAP 值,然后可以分别对每个类别的 SHAP 值进行可视化。
import shap
explainer = shap.TreeExplainer(xgb_model)
shap_values = explainer.shap_values(X_test,approximate=True)
plt.title('The Summary Plot for the Multiclass Model'+'\n'+'Class 2 - Best, Class 1 - Premium, Class 0 - Value')
shap.summary_plot(shap_values, X_test, plot_type="bar")
图 (4.1.1): 多分类模型的 SHAP 摘要图(通常为 beeswarm 图形式),按类别分别展示。例如,上图可能分别展示了对预测为“Best”、“Premium”、“Value”贡献最大的特征及其SHAP值分布。
fig = plt.figure(figsize=(20,10))
ax0 = fig.add_subplot(131)
ax0.title.set_text('Class 2 - Best ')
shap.summary_plot(shap_values[2], X_test, plot_type="bar", show=False)
ax0.set_xlabel(r'SHAP values', fontsize=11)
plt.subplots_adjust(wspace = 5)
ax1 = fig.add_subplot(132)
ax1.title.set_text('Class 1 - Premium')
shap.summary_plot(shap_values[1], X_test, plot_type="bar", show=False)
plt.subplots_adjust(wspace = 5)
ax1.set_xlabel(r'SHAP values', fontsize=11)
ax2 = fig.add_subplot(133)
ax2.title.set_text('Class 0 - Value')
shap.summary_plot(shap_values[0], X_test, plot_type="bar", show=False)
ax2.set_xlabel(r'SHAP values', fontsize=11)
plt.show()
图 (4.1.2): 多分类模型的 SHAP 摘要图(条形图形式),同样按类别分别展示。这种方式更侧重于每个类别下特征的平均绝对SHAP值排序。
结语:用 SHAP 图表点亮模型解释之路
在本章中,我们一同探索了多种用于呈现模型预测解释的 SHAP 可视化方法,从全局洞察到个体剖析,从基本图表到高级定制,再到多分类模型的特殊应用。这些图表都为您提供了一套强有力的工具库,助您打造出既专业又具吸引力的模型解释演示。当您的听众能够更深刻、更直观地理解您的模型预测,并因此更愿意信任和采纳您的模型时,您在模型可解释性上投入的每一分努力都将获得丰厚的回报。
感谢阅读!
这是个系列:
-
SHAP教程-01 |解密“黑箱”模型的底层逻辑 -
SHAP教程-02 |如何更优雅的可视化 -
更多

