可解释 AI:SHAP、LIME 与注意力可视化

FreeGuideOnline 12阅读 2026-07-03

什么是可解释人工智能 (XAI)?

可解释人工智能 (Explainable AI,简称 XAI) 是一套方法与技术,旨在让机器学习模型的预测过程与结果能够被人类理解。随着模型日趋复杂,从线性模型、决策树演进到深度神经网络,我们虽然获得了更高的准确性,却牺牲了透明度。XAI 的目标不是牺牲性能,而是在复杂模型之上建立一层“解释层”,回答诸如“模型为什么做出这个决策?”、“哪些特征对预测最重要?”、“如何信任模型?”等问题。

本教程将重点介绍三种最具代表性的解释方法:SHAPLIME,以及面向 Transformer 模型的注意力可视化。无论你是数据科学家、工程师,还是对 AI 伦理感兴趣的从业者,掌握这些工具都能帮助你构建更透明、更可信的机器学习系统。


SHAP:基于博弈论的一致性解释

什么是 SHAP?

SHAP (SHapley Additive exPlanations) 由 Lundberg 和 Lee 于 2017 年提出,其核心思想源于博弈论中的 Shapley 值。Shapley 值原本用于公平分配合作博弈中每个参与者的贡献,在机器学习场景中,每个特征就是一名“玩家”,模型的预测输出就是“总收益”。SHAP 将每个特征对预测结果的边际贡献进行加权平均,从而得到该特征对最终预测的贡献值。

SHAP 的核心优势

  • 一致性:如果模型变更使得某个特征变得更加重要,其 SHAP 值不会减小。
  • 加和性:所有特征的 SHAP 值之和等于模型预测值与基线值(全体样本预测均值)之间的差异。这确保了全局解释与局部解释的数学统一。
  • 模型无关与模型特定:提供 TreeExplainer(针对树模型)、DeepExplainer(针对深度学习)、KernelExplainer(模型无关)等实现。

SHAP 值是如何计算的?

给定一个实例 ( x ),特征集合 ( F ),模型 ( f )。对于某个特征 ( i ),其 SHAP 值 ( \phi_i ) 定义为:

[ \phi_i = \sum_{S \subseteq F \setminus {i}} \frac{|S|! (|F|-|S|-1)!}{|F|!} \left[ f_{S \cup {i}}(x_{S \cup {i}}) - f_S(x_S) \right] ]

其中,( S ) 是特征子集,( f_S(x_S) ) 是仅使用子集 ( S ) 中特征时模型的预测值(通常通过背景数据集的期望来近似)。每个差值表示加入特征 ( i ) 后的边际贡献,权重则是所有可能特征顺序中形成该子集的概率。

在实际计算中,Python 库 shap 提供了高效的近似算法,例如 KernelExplainer 使用加权线性回归来估计 SHAP 值。

如何使用 SHAP 进行解释?

  1. 安装与导入
pip install shap
import shap
import xgboost
  1. 训练一个模型并创建解释器
model = xgboost.train({"learning_rate":0.1}, xgboost.DMatrix(X_train, label=y_train))
explainer = shap.TreeExplainer(model)
  1. 计算并可视化 SHAP 值
shap_values = explainer.shap_values(X_test)
# 单个预测的瀑布图
shap.waterfall_plot(explainer.expected_value, shap_values[0], X_test.iloc[0])
# 特征重要性概要图
shap.summary_plot(shap_values, X_test)

常见可视化解读

  • 概要图 (Summary Plot):每个点代表一个样本的某个特征 SHAP 值,颜色表示特征值高低,横轴为 SHAP 值。可以直观看出哪些特征影响大,以及高特征值是否推高预测。
  • 依赖图 (Dependence Plot):展示一个特征的 SHAP 值与特征取值的关系,可附加交互特征,揭示非线性效应。
  • 瀑布图 (Waterfall Plot):针对单个样本,展示从基线值到最终预测的逐步推演过程。

LIME:局部忠实解释

什么是 LIME?

LIME (Local Interpretable Model-agnostic Explanations) 由 Ribeiro 等人在 2016 年提出。与 SHAP 不同,LIME 并不试图全局统一地解释模型,而是在某个待解释的预测点附近,构建一个简单的、可解释的代理模型(如线性模型或决策树)。该代理模型仅需在局部范围内忠实于原始模型,而非全局正确。

LIME 的工作原理

  1. 选取一个待解释的样本 ( x )。
  2. 在 ( x ) 周围生成扰动样本(例如,对文本随机删除单词,对表格数据添加噪声等)。
  3. 使用黑箱模型为这些扰动样本生成预测。
  4. 根据扰动样本与 ( x ) 的距离赋予权重(近的样本权重高)。
  5. 在加权样本上训练一个稀疏线性模型,其特征为原始特征(或可解释表示)。
  6. 线性模型的权重即作为特征重要性的解释。

对于文本和图像数据,LIME 使用“可解释表示”(如词的“存在/缺失”或图像的超像素),使得解释对人类来说有意义。

LIME 的优缺点

  • 优点:模型无关、简单直观、可解释表示灵活。
  • 缺点:结果可能不稳定(对扰动采样方式敏感),局部忠实度受邻居范围影响大,不能很好地捕捉全局特征交互。

动手实践:LIME 解释文本分类器

# 安装
pip install lime
from lime.lime_text import LimeTextExplainer
from sklearn.pipeline import make_pipeline
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.linear_model import LogisticRegression

# 构建一个简单的文本分类模型
vectorizer = TfidfVectorizer()
classifier = LogisticRegression()
pipeline = make_pipeline(vectorizer, classifier)
pipeline.fit(train_texts, train_labels)

# 创建 LIME 解释器
class_names = ['negative', 'positive']
explainer = LimeTextExplainer(class_names=class_names)

# 解释一个实例
idx = 0
exp = explainer.explain_instance(test_texts[idx], pipeline.predict_proba, num_features=6)
print('文档真实标签: ', class_names[test_labels[idx]])
print('解释:')
for feat, weight in exp.as_list():
    print(f'{feat}: {weight:.4f}')

# 可视化
exp.show_in_notebook(text=test_texts[idx])

对于表格数据,使用 LimeTabularExplainer;对于图像,使用 LimeImageExplainer

何时选择 LIME 而非 SHAP?

  • 当你需要快速、直观的局部解释,且模型调用成本较高(SHAP 需要进行大量子集采样)时,LIME 采样效率可能更高。
  • 如果解释需要基于可理解的抽象表示(如超像素),LIME 更适合。
  • 如果要求理论上的一致性保证,SHAP 是更好的选择。

注意力可视化:洞察 Transformer 的焦点

注意力机制与可解释性

基于 Transformer 的模型(如 BERT、GPT)通过自注意力机制来捕获词元之间的依赖关系。注意力权重可以揭示模型在处理某个单词时,重点关注了输入序列中的哪些部分。然而,需要谨慎:注意力权重并不直接等同于特征重要性,但经过合理的可视化设计,它们能提供关于模型行为的宝贵线索。

常见可视化方法

  1. 注意力热力图 (Attention Heatmap):最直接的方式,绘制一个矩阵,其中行表示查询位置,列表示键位置,颜色深浅代表注意力权重大小。这能展示单词间的“对齐”强度。
  2. 输入-输出注意力流:将一层或多层注意力聚合,以看最终预测是如何汇聚信息的。
  3. 头顶注意力可视化:针对多头注意力,不同的头可能学会关注不同的语言学关系(如句法依赖、共指关系等),可以分别绘制每个头的热力图。

提取并可视化 BERT 的注意力权重

import torch
from transformers import BertTokenizer, BertModel
import matplotlib.pyplot as plt
import seaborn as sns

# 加载模型和分词器
tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')
model = BertModel.from_pretrained('bert-base-uncased', output_attentions=True)

# 输入文本
text = "The cat sat on the mat because it was comfortable."
inputs = tokenizer(text, return_tensors='pt')
outputs = model(**inputs)

# attention 形状: (层数, 批大小, 头数, 序列长度, 序列长度)
attentions = outputs.attentions  # tuple of length num_layers
# 选择第一层第一个头的注意力矩阵
attn_matrix = attentions[0][0, 0, :, :].detach().cpu().numpy()
tokens = tokenizer.convert_ids_to_tokens(inputs['input_ids'][0])

plt.figure(figsize=(10,8))
sns.heatmap(attn_matrix, xticklabels=tokens, yticklabels=tokens, cmap='viridis')
plt.title('Layer 1, Head 0 Attention')
plt.show()