可解释 AI
这一课你将能做到
- 用 SHAP 解释树模型:单条预测的贡献分解(waterfall)与全局特征重要性(beeswarm)
- 用 LIME 做「模型无关」的局部解释,并与 SHAP 结论对比
- 把数值解释翻译成业务语言,产出可归档的离线解释报告
为什么要学
「这个提交为什么被判成大概率超时?」——运营会问,出题人也会问。黑盒预测在需要信任与问责的场景(误判申诉、模型验收、合规审计)站不住脚。可解释性(XAI)就是把模型的判断依据翻译成人话的技术。
两个事实标准各有分工:SHAP 基于博弈论 Shapley 值,把预测拆成每个特征的「贡献额」,全局加和严格成立,树模型有极速实现;LIME 在预测点附近扰动采样、拟合局部线性代理模型,任何模型都能用但解释是近似的。人话:SHAP 是精确到分的账单,LIME 是「在附近问一圈」的民意调查。
先建立图像
单条预测的 SHAP 分解(waterfall 思路):
基准(全体平均超时概率)= 0.15
+ code_lines = 280 → +0.52 (代码很长,强烈推高)
+ problem_tle_hist = 0.4 → +0.30 (该题历史 TLE 率高)
+ loop_depth = 2 → -0.06 (循环不深,略微拉低)
+ lang_java = 1 → -0.01
────────────────────────
最终预测 = 0.90 (每个特征贡献严格可加)
全局视角:对全部样本的 |SHAP| 求均值 → 哪些特征整体上最有话语权
跟着做
步骤 1:准备数据与已训练模型
pip install shap lime matplotlib
python src/train.py # 训练并落盘 MLflow run(沿用第 4 课脚本)
步骤 2:SHAP 解释单条预测与全局
# src/explain_shap.py
import matplotlib
matplotlib.use("Agg") # 无界面环境必须先设后端,否则画图报错
import pandas as pd
import shap
import joblib
from sklearn.model_selection import train_test_split
import sys; sys.path.insert(0, ".")
from src.train import load_data, FEATURES
model = joblib.load("models/timeout-v1.joblib")
df = load_data()
split = int(len(df) * 0.8)
X_te = df[FEATURES][split:]
explainer = shap.TreeExplainer(model) # 树模型专用,秒级出结果
sv = explainer(X_te)
# ① 全局:哪些特征最有话语权
shap.plots.beeswarm(sv, max_display=8, show=False)
import matplotlib.pyplot as plt
plt.savefig("shap_beeswarm.png", bbox_inches="tight", dpi=150)
# ② 局部:第 0 号样本为什么被判超时
shap.plots.waterfall(sv[0], max_display=8, show=False)
plt.savefig("shap_waterfall.png", bbox_inches="tight", dpi=150)
# ③ 数值形式(报告/落库用):贡献值表
print(pd.DataFrame({
"feature": FEATURES,
"value": X_te.iloc[0].values,
"shap": sv.values[0].round(3),
}).sort_values("shap", key=abs, ascending=False))
python src/explain_shap.py
# 看 shap_beeswarm.png:横轴是 SHAP 值,红色高值点落在哪边,特征就把预测推向哪边
步骤 3:LIME 局部解释与对比
# src/explain_lime.py
import numpy as np
import pandas as pd
import joblib
from lime.lime_tabular import LimeTabularExplainer
import sys; sys.path.insert(0, ".")
from src.train import load_data, FEATURES
model = joblib.load("models/timeout-v1.joblib")
df = load_data()
split = int(len(df) * 0.8)
X_tr, X_te = df[FEATURES][:split], df[FEATURES][split:]
explainer = LimeTabularExplainer(
X_tr.values, feature_names=FEATURES,
class_names=["normal", "timeout"], mode="classification",
discretize_continuous=True) # 把连续特征离散成规则更易读
exp = explainer.explain_instance(
X_te.iloc[0].values, model.predict_proba, num_features=4)
for feat, weight in exp.as_list():
print(f"{weight:+.3f} {feat}")
exp.save_to_file("lime_report.html")
python src/explain_lime.py
# 输出示意:
# +0.28 code_lines > 195.5 (代码行数超阈值,推高超时概率)
# +0.19 0.35 < problem_tle_hist ≤ 0.45
# -0.05 loop_depth ≤ 2.0
步骤 4:翻译成业务语言
# src/report_one.py —— 给运营/出题人的解释话术生成器(示意)
# 读 shap 贡献值表,按 |shap| 排序取前 3:
# 「该提交被判为高超时风险(0.90),主要依据:
# ① 代码长度 280 行,远高于全站平均,贡献最大;
# ② 所属题目近期 TLE 率 40%,属于易超时题目;
# ③ 循环深度仅 2 层,略微缓解了风险。」
# 同样的三句话可自动渲染进挑战申诉工单,人工复核时有据可依
讲清楚要点
- SHAP 的两个直觉:基准值是「什么都不知道时」的平均预测;每个特征的 SHAP 值是「把该特征纳入后预测移动了多少」,正推高风险、负推低风险,全部加总恰好等于预测值与基准之差。
- TreeExplainer 与 KernelExplainer 差一个数量级:树模型(XGBoost/LightGBM/sklearn 树)用 TreeExplainer 精确且快;任意黑盒只能 KernelExplainer,慢到只能采样少量样本。选模型时就该想到「以后要不要解释」。
- LIME 的本质是局部线性近似:在预测点附近扰动生成「伪样本」→ 拿原模型打分 → 拟合一个可读的线性/规则模型。它的解释是「这一片的行为」,不是全局规律,且受扰动策略影响不稳定。
- 解释的两大用途:对内——验收与排障(发现模型学到可疑特征,如「user_id 尾号越大越超时」这种泄漏信号);对外——申诉、合规、建立业务信任。判题超时误判申诉就是典型场景。
- 解释要离线做:SHAP/LIME 都不便宜,放到离线批处理(每日对抽检样本出报告、申诉工单触发的按需解释),不要阻塞毫秒级在线推理链路。
你可能会懵的地方
- SHAP 值的正负和单位是什么?
与预测同单位。分类模型解释的是概率(或 logit,取决于实现),回归模型解释的就是预测值本身。
explainer(X)返回的base_values + values.sum(axis=1)应等于逐条预测,可用这步自检。 - LIME 和 SHAP 对同一样本给出不同解释,谁对? 都是「对」的近似,但口径不同:SHAP 是满足可加性公理的精确贡献,LIME 是固定扰动策略下的局部近似,结论对超参敏感。工程约定:树模型以 SHAP 为准,LIME 用于给非树模型补位与交叉验证。
- 深度学习模型怎么解释? 文本/图像用 SHAP 的 GradientExplainer/DeepExplainer 或注意力可视化、Grad-CAM;表格深度模型用 SHAP 采样实现。共同点是更慢更近似,更依赖抽样验证稳定性。
- SHAP 排名能直接用来删特征吗? 能提供线索,不能直接执行。全局重要性低 ≠ 可删(可能对少数关键样本至关重要),且删特征要考虑业务口径与上游数据稳定性。正确流程:SHAP 定候选 → 特征消融实验验证 → 业务确认后下线。
- 解释结果会被「对抗利用」吗? 会。若「代码行数 > 195 就显著推高超时概率」被公开,用户可以故意写短代码绕过预测。策略:解释只对内部与申诉工单开放,对外只暴露结论性话术。
错法对照
| 错法 | 问题 | 正解 |
|---|---|---|
| 把 SHAP 当因果解释 | 贡献是「相关性归因」,非因果 | 表述为「模型依据」而非「因为所以」 |
| 只看全局重要性不看单条 | 关键个例的异常依据被平均掩盖 | 全局定位 + waterfall 逐单复核 |
| 在线推理链路同步算 SHAP | 延迟从 5ms 涨到秒级 | 离线批量出报告,线上只存预测 |
| 特征离散化阈值随数据变 | 同一提交两次解释结论不同 | 解释代码与阈值也进版本控制 |
| 用解释「自证模型正确」 | 解释只能发现可疑,不能证明正确 | 解释 + 消融实验 + 人工抽检三角验证 |
卡点急救
- 画图报错 "no display":脚本开头先
matplotlib.use("Agg")再 import pyplot,配show=False后savefig。 - TreeExplainer 不支持该模型:换
shap.KernelExplainer(model.predict_proba, X背景样本)(取几百条背景即可);或确认模型在 SHAP 支持列表内。 - LIME 输出的是区间规则不是原始特征名:
discretize_continuous=True把数值切成可读区间,属正常行为;要原始连续权重设为 False。
面试常问
- 讲讲 SHAP 的原理。 Shapley 值来自合作博弈论:把「预测」看成各特征合作产出的收益,计算每个特征在所有可能「入场顺序」下带来的边际贡献的期望,满足可加性、对称性、零贡献等公理。树模型用 TreeShap 算法把指数级遍历降为多项式级,工程上可用。
- LIME 和 SHAP 的区别与选型? SHAP:公理化、精确可加、树模型快,但任意模型慢;LIME:模型无关、输出可读规则,但局部近似、对扰动敏感、不稳定。选型:树模型默认 SHAP;黑盒(深度网络/外部模型)用 LIME 或 SHAP 采样实现;重要决策场景两者交叉验证。
- 如何向业务方解释一条预测? 模板:「结论 + 前三依据(特征名+取值+方向)+ 不确定度」。禁止输出 SHAP 数值本身,翻译成「代码行数远高于平均」这类业务语言;同时主动说明「这是模型依据,不构成因果结论」。
自测三问
- waterfall 图里的 base value 是什么?SHAP 值如何加总回预测?
- LIME 为什么结论可能不稳定?它适合什么场景?
- 发现「user_id 尾号」SHAP 重要性异常高,说明什么?该怎么处理?
小结一句话
XAI 用 SHAP 的精确贡献分解与 LIME 的局部近似规则,把黑盒预测翻译成可审计、可沟通的依据,服务于排障、申诉与合规。