可解释 AI
LIME/SHAP 与合规沟通。
25 分钟
可解释 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")