可解释 AI:SHAP 与 LIME¶
一句话说明¶
SHAP 和 LIME 让黑盒机器学习模型"说出"它为什么做出某个预测——告诉你哪些特征(基因/指标)对预测结果贡献最大,这在生信中对发现生物标志物至关重要。
核心知识点¶
为什么需要可解释性?¶
- 黑盒模型(随机森林、神经网络)预测准但不知道原因
- 生信研究需要知道"哪些基因重要"而不只是"预测结果"
- 临床应用中需要医生理解模型判断依据
- 监管要求(FDA 要求 AI 辅助诊断可解释)
SHAP(SHapley Additive exPlanations)¶
核心思想:博弈论 Shapley 值——公平分配每个特征对预测结果的"功劳"
直觉:把特征看作合作游戏的玩家,SHAP 值是每个玩家的公平报酬
| SHAP 类型 | 适用模型 | 速度 |
|---|---|---|
| TreeSHAP | 树模型(XGBoost、RF) | 极快 |
| KernelSHAP | 任何模型 | 慢 |
| DeepSHAP | 神经网络 | 中等 |
| LinearSHAP | 线性模型 | 极快 |
LIME(Local Interpretable Model-agnostic Explanations)¶
核心思想:在待解释样本附近局部扰动,拟合简单线性模型 1. 对样本特征做局部扰动 2. 用原始模型预测扰动后的结果 3. 用线性模型拟合这些扰动数据 4. 线性模型系数 = 特征重要性
SHAP vs LIME 对比¶
| 维度 | SHAP | LIME |
|---|---|---|
| 理论基础 | 博弈论,有公理保证 | 局部线性近似 |
| 一致性 | 全局一致 | 局部,可能不一致 |
| 速度 | TreeSHAP 快 | 较慢 |
| 适用范围 | 任何模型 | 任何模型 |
| 首选 | 生信推荐用 SHAP | 快速探索时用 |
实战代码¶
import numpy as np
import pandas as pd
from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
# ===== 1. 准备:训练一个预测 2 型糖尿病的随机森林 =====
np.random.seed(42)
# 模拟基因表达 + 临床特征数据
n_samples = 500
feature_names = [
'gene_PPARG', 'gene_TCF7L2', 'gene_KCNJ11', # 已知糖尿病相关基因
'BMI', 'age', 'fasting_glucose', 'HbA1c', # 临床指标
'gene_FTO', 'gene_SLC30A8', 'gene_IGF2BP2' # 更多基因
]
X, y = make_classification(
n_samples=n_samples,
n_features=len(feature_names),
n_informative=6, # 6 个真正有信息量的特征
n_redundant=2,
random_state=42
)
X_df = pd.DataFrame(X, columns=feature_names)
X_train, X_test, y_train, y_test = train_test_split(X_df, y, test_size=0.2, random_state=42)
# 训练随机森林
rf_model = RandomForestClassifier(n_estimators=100, random_state=42)
rf_model.fit(X_train, y_train)
print(f'模型准确率: {rf_model.score(X_test, y_test):.3f}')
# ===== 2. SHAP 分析 =====
# pip install shap
import shap
# TreeExplainer:专为树模型优化,速度极快
explainer = shap.TreeExplainer(rf_model)
# 计算测试集的 SHAP 值
# shap_values 形状: [类别数, 样本数, 特征数]
shap_values = explainer.shap_values(X_test)
# 对二分类问题,取类别1(患病)的 SHAP 值
shap_values_class1 = shap_values[1] # [样本数, 特征数]
print(f'\nSHAP 值形状: {shap_values_class1.shape}')
# --- 全局特征重要性 ---
# 每个特征的平均绝对 SHAP 值 = 全局重要性
mean_shap = np.abs(shap_values_class1).mean(axis=0)
feature_importance_df = pd.DataFrame({
'feature': feature_names,
'mean_abs_shap': mean_shap
}).sort_values('mean_abs_shap', ascending=False)
print('\n全局特征重要性(SHAP):')
print(feature_importance_df.to_string(index=False))
# --- 单样本解释 ---
sample_idx = 0 # 第一个测试样本
sample_shap = shap_values_class1[sample_idx] # 该样本每个特征的 SHAP 值
base_value = explainer.expected_value[1] # 基准预测值(所有样本的平均预测)
print(f'\n样本 {sample_idx} 的预测解释:')
print(f'基准值 (base value): {base_value:.3f}')
for feat, shap_val in zip(feature_names, sample_shap):
direction = '↑' if shap_val > 0 else '↓' # 正值增加患病概率,负值降低
print(f' {feat}: SHAP={shap_val:+.3f} {direction}')
print(f'最终预测值: {base_value + sample_shap.sum():.3f}')
# 可视化(需要 matplotlib)
try:
import matplotlib.pyplot as plt
# 蜂巢图:展示所有样本所有特征的 SHAP 值分布
shap.summary_plot(shap_values_class1, X_test, feature_names=feature_names, show=False)
plt.tight_layout()
plt.savefig('/tmp/shap_summary.png', dpi=100, bbox_inches='tight')
print('\nSHAP summary plot 已保存到 /tmp/shap_summary.png')
plt.close()
except Exception as e:
print(f'可视化跳过: {e}')
# ===== 3. LIME 分析 =====
# pip install lime
try:
import lime
import lime.lime_tabular
# 创建 LIME 解释器
lime_explainer = lime.lime_tabular.LimeTabularExplainer(
X_train.values, # 训练数据(用于估计特征分布)
feature_names=feature_names,
class_names=['正常', '糖尿病'],
mode='classification'
)
# 解释单个样本
sample = X_test.iloc[0].values # 取第一个测试样本
explanation = lime_explainer.explain_instance(
sample,
rf_model.predict_proba, # 模型预测函数
num_features=5 # 展示最重要的 5 个特征
)
print('\nLIME 解释(前5个重要特征):')
for feat, weight in explanation.as_list():
print(f' {feat}: {weight:+.4f}')
except ImportError:
print("需要安装 lime: pip install lime")
面试常问点¶
Q: SHAP 和特征重要性(Feature Importance)有什么区别? A: 随机森林的默认特征重要性基于 Gini 不纯度减少,有偏差(偏爱高基数特征),且没有方向性。SHAP 更准确、有理论保证,且能告诉你每个特征增加还是降低预测值。
Q: SHAP 的 base value 是什么? A: 所有训练样本的平均预测值,相当于"如果什么特征信息都不知道"时的预测。每个样本的预测 = base value + 所有 SHAP 值的加和。
Q: 生信中 SHAP 怎么帮助发现生物标志物? A: 训练分类模型(如癌症 vs 正常),用 SHAP 找出对预测贡献最大的基因,这些基因是候选生物标志物,再做实验验证。
Q: LIME 的局限性是什么? A: ①局部线性假设不一定成立;②扰动采样方式影响结果;③不同次运行结果可能不一致(随机性)。
速查表¶
| 术语 | 解释 |
|---|---|
| SHAP 值 | 特征对预测值贡献的公平度量 |
| base value | 平均预测值(无任何特征信息时) |
| TreeSHAP | 树模型的精确快速 SHAP 实现 |
| KernelSHAP | 与模型无关的 SHAP 实现(慢) |
| 蜂巢图 beeswarm | SHAP 值全局分布可视化 |
| 依赖图 dependence plot | 单特征 SHAP 值 vs 特征值散点图 |
| 瀑布图 waterfall | 单样本 SHAP 分解可视化 |
| LIME 邻域 | 在局部扰动样本构成的"邻域"拟合 |