跳转至

05-可解释人工智能(XAI)

学习时间: 约5-7小时 难度级别: ⭐⭐⭐⭐ 中高级 前置知识: 深度学习基础、CNN/Transformer架构、PyTorch 学习目标: 理解模型可解释性的主要方法(梯度方法、CAM、SHAP、LIME等),能对深度模型进行解释与可视化


目录


1. 为什么需要可解释AI

1.1 现实需求

领域 需求 示例
医疗 法规要求模型决策可解释 辅助诊断需说明判断依据
金融 信贷审批需透明 拒绝原因须告知用户
自动驾驶 安全攸关决策 事故责任需归因
司法 公平性审查 检测算法偏见

1.2 可解释性 vs 准确性

Text Only
准确性高 ←→ 可解释性高

线性回归   决策树   随机森林   SVM   CNN   Transformer   LLM
|←————————可解释性强————————→|←————————难以解释————————→|
|←————————表达能力弱————————→|←————————表达能力强————————→|

深度模型是"黑盒",需要事后解释(Post-hoc Explanation)方法。


2. 可解释性分类体系

2.1 按时机分

类型 说明 代表
内在可解释 模型自身可解释 线性模型、决策树、注意力
事后解释 对已训练模型进行解释 Grad-CAM、SHAP、LIME

2.2 按范围分

类型 说明 方法
全局解释 理解模型整体行为 特征重要性、概念提取
局部解释 解释单个预测 显著性图、LIME

2.3 按方法分

XAI方法分类

Text Only
可解释方法
├── 梯度类: Saliency Map, Integrated Gradients, SmoothGrad
├── CAM类: CAM, Grad-CAM, Grad-CAM++, Score-CAM
├── 扰动类: LIME, Occlusion
├── 博弈论: SHAP (KernelSHAP, DeepSHAP)
├── 注意力: Attention Rollout, Attention Flow
└── 概念类: TCAV, Network Dissection

3. 梯度类方法

3.1 Saliency Map(显著性图)

计算输出对输入的梯度:

\[S = \left|\frac{\partial y_c}{\partial x}\right|\]

直觉:梯度大的像素对预测结果影响大。

3.2 Integrated Gradients(积分梯度)

从基线 \(x'\)(通常为全黑图像)到输入 \(x\) 沿路径积分梯度:

\[IG_i(x) = (x_i - x_i') \cdot \int_0^1 \frac{\partial F(x' + t(x - x'))}{\partial x_i} dt\]

优点:满足完备性公理(各特征归因之和等于模型输出差值)。

3.3 SmoothGrad

对输入加多次高斯噪声并平均梯度,减少噪声:

\[\hat{S}(x) = \frac{1}{N} \sum_{n=1}^{N} \frac{\partial y_c}{\partial x}\bigg|_{x + \epsilon_n}, \quad \epsilon_n \sim \mathcal{N}(0, \sigma^2)\]

3.4 PyTorch实现

Python
import torch
import torch.nn.functional as F
from torchvision import models, transforms
from PIL import Image
import numpy as np

def compute_saliency_map(model, image_tensor, target_class):
    """计算显著性图"""
    model.eval()  # eval()开启评估模式(关闭Dropout等)
    image_tensor.requires_grad_(True)

    output = model(image_tensor.unsqueeze(0))  # unsqueeze增加一个维度
    score = output[0, target_class]
    score.backward()  # 反向传播计算梯度

    saliency = image_tensor.grad.abs().max(dim=0)[0]  # 取RGB通道最大值
    return saliency.detach().cpu().numpy()  # detach()分离计算图,cpu()确保在CPU上

def compute_integrated_gradients(model, image_tensor, target_class,
                                  baseline=None, steps=50):
    """计算积分梯度"""
    if baseline is None:
        baseline = torch.zeros_like(image_tensor)

    # 生成插值路径
    scaled_inputs = [baseline + (float(i) / steps) * (image_tensor - baseline)
                     for i in range(steps + 1)]

    grads = []
    model.eval()
    for scaled_input in scaled_inputs:
        scaled_input = scaled_input.unsqueeze(0).requires_grad_(True)
        output = model(scaled_input)
        score = output[0, target_class]
        score.backward()
        grads.append(scaled_input.grad.squeeze(0).clone())  # squeeze去除大小为1的维度

    # 梯形积分
    avg_grads = torch.stack(grads).mean(dim=0)
    ig = (image_tensor - baseline) * avg_grads

    return ig.abs().max(dim=0)[0].detach().cpu().numpy()

# 使用示例
model = models.resnet50(weights=models.ResNet50_Weights.DEFAULT)
transform = transforms.Compose([
    transforms.Resize(256),
    transforms.CenterCrop(224),
    transforms.ToTensor(),
    transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
])

# img = Image.open("cat.jpg")
# image_tensor = transform(img)
# saliency = compute_saliency_map(model, image_tensor, target_class=281)

4. CAM系列方法

4.1 CAM(Class Activation Mapping)

利用GAP层前的特征图与分类权重生成类别激活图:

\[M_c(x, y) = \sum_k w_k^c \cdot A_k(x, y)\]

限制:要求网络结构必须有GAP层 + 全连接分类器。

4.2 Grad-CAM

通用化的CAM,不限制网络结构:

\[\alpha_k^c = \frac{1}{Z} \sum_i \sum_j \frac{\partial y^c}{\partial A_k(i,j)}\]
\[L_{Grad-CAM}^c = \text{ReLU}\left(\sum_k \alpha_k^c A_k\right)\]

4.3 PyTorch实现 Grad-CAM

Python
class GradCAM:
    def __init__(self, model, target_layer):  # __init__构造方法,创建对象时自动调用
        self.model = model
        self.model.eval()
        self.gradients = None
        self.activations = None

        # 注册钩子
        target_layer.register_forward_hook(self._save_activation)
        target_layer.register_full_backward_hook(self._save_gradient)

    def _save_activation(self, module, input, output):
        self.activations = output.detach()

    def _save_gradient(self, module, grad_input, grad_output):
        self.gradients = grad_output[0].detach()

    def generate(self, input_tensor, target_class=None):
        output = self.model(input_tensor)

        if target_class is None:
            target_class = output.argmax(dim=1).item()  # .item()将单元素张量转为Python数值

        self.model.zero_grad()  # 清零梯度,防止梯度累积
        one_hot = torch.zeros_like(output)
        one_hot[0, target_class] = 1
        output.backward(gradient=one_hot)

        # 计算权重:全局平均池化梯度
        weights = self.gradients.mean(dim=[2, 3], keepdim=True)  # [1, C, 1, 1]

        # 加权求和
        cam = (weights * self.activations).sum(dim=1, keepdim=True)
        cam = F.relu(cam)

        # 上采样到输入尺寸
        cam = F.interpolate(cam, size=input_tensor.shape[2:],
                           mode='bilinear', align_corners=False)
        cam = cam.squeeze()
        cam = (cam - cam.min()) / (cam.max() - cam.min() + 1e-8)
        return cam.numpy()

# 使用
# model = models.resnet50(weights=models.ResNet50_Weights.DEFAULT)
# grad_cam = GradCAM(model, model.layer4[-1])
# cam = grad_cam.generate(image_tensor.unsqueeze(0))

5. 扰动类方法(LIME)

LIME局部解释

5.1 LIME核心思想

对单个样本:在其邻域采样扰动样本 → 用原模型预测 → 拟合一个局部线性模型作为解释。

\[\xi(x) = \arg\min_{g \in G} \mathcal{L}(f, g, \pi_x) + \Omega(g)\]

其中 \(\pi_x\) 为局部邻域权重(离 \(x\) 越近权重越大),\(\Omega(g)\) 为复杂度惩罚。

5.2 图像LIME的流程

Text Only
1. 将图像分割为超像素
2. 随机关闭/保留不同超像素组合
3. 用原模型预测每个扰动样本
4. 以超像素是否保留为特征,拟合加权线性回归
5. 线性模型系数即为各超像素的重要性

5.3 PyTorch + LIME示例

Python
# pip install lime
from lime import lime_image
from skimage.segmentation import quickshift

def predict_fn(images):
    """LIME需要的批量预测函数"""
    batch = torch.stack([transform(Image.fromarray(img)) for img in images])  # 列表推导式,简洁创建列表
    with torch.no_grad():  # 禁用梯度计算,节省内存
        outputs = model(batch)
    return F.softmax(outputs, dim=1).numpy()

# explainer = lime_image.LimeImageExplainer()
# explanation = explainer.explain_instance(
#     np.array(img),
#     predict_fn,
#     top_labels=3,
#     num_samples=1000,
#     segmentation_fn=quickshift
# )
# temp, mask = explanation.get_image_and_mask(
#     explanation.top_labels[0], positive_only=True, num_features=5
# )

6. Shapley值与SHAP

SHAP值可视化

6.1 Shapley值

源于合作博弈论,衡量每个"玩家"对总收益的边际贡献:

\[\phi_i = \sum_{S \subseteq N \setminus \{i\}} \frac{|S|!(|N|-|S|-1)!}{|N|!} [v(S \cup \{i\}) - v(S)]\]

6.2 SHAP(SHapley Additive exPlanations)

将Shapley值应用于机器学习模型解释:

变体 适用模型 方法
KernelSHAP 任意模型 用加权线性回归近似
DeepSHAP 深度网络 DeepLIFT + Shapley
TreeSHAP 树模型 精确多项式时间计算

6.3 使用示例

Python
# pip install shap
import shap

# 对图像分类模型
# masker = shap.maskers.Image("inpaint_telea", image_tensor.shape[1:])
# explainer = shap.Explainer(predict_fn, masker)
# shap_values = explainer(images[:5], max_evals=500)
# shap.image_plot(shap_values)

6.4 SHAP的优势

  • 理论保证:满足Shapley值的四条公理(效率性、对称性、虚拟性、可加性)
  • 统一框架:LIME、DeepLIFT等方法都是SHAP的特例
  • 全局+局部:既能解释单个预测,也能汇总为全局特征重要性

7. 注意力可视化

注意力热力图

7.1 Transformer注意力图

直接可视化注意力权重矩阵 \(A = \text{softmax}(QK^T / \sqrt{d_k})\)

7.2 Attention Rollout

考虑多层注意力的传递效应,递归相乘:

\[\hat{A}^{(l)} = A^{(l)} \cdot \hat{A}^{(l-1)}\]

加残差连接的修正:\(A^{(l)} = 0.5 \cdot A_{attn}^{(l)} + 0.5 \cdot I\)

7.3 注意力可视化示例

Python
import torch
from torchvision import models

def visualize_vit_attention(model, image_tensor):
    """ViT的注意力可视化"""
    model.eval()
    # 获取所有层的注意力(需要模型支持输出attention)
    with torch.no_grad():
        # 假设模型返回注意力权重
        output = model(image_tensor.unsqueeze(0))

    # Attention Rollout
    # attn_weights: list of [1, heads, tokens, tokens]
    # rollout = compute_rollout(attn_weights)
    # 可视化CLS token对所有patch的注意力
    pass

def compute_rollout(attn_list):
    """计算Attention Rollout"""
    result = torch.eye(attn_list[0].size(-1))
    for attn in attn_list:
        attn_mean = attn.mean(dim=1).squeeze(0)  # 多头平均
        attn_with_residual = 0.5 * attn_mean + 0.5 * torch.eye(attn_mean.size(-1))
        # 行归一化
        attn_with_residual = attn_with_residual / attn_with_residual.sum(dim=-1, keepdim=True)
        result = attn_with_residual @ result
    return result

7.4 注意力 ≠ 解释

⚠️ 研究表明注意力权重不一定是可靠的解释: - 不同注意力分布可能产生相同输出 - 注意力反映的是"信息流"而非"因果归因"


8. 概念级解释

8.1 TCAV(Testing with Concept Activation Vectors)

不解释单个像素,而是测试模型是否依赖某个人类可理解的概念

Text Only
1. 收集概念样本(如"条纹"图片)和随机样本
2. 训练线性分类器区分概念 vs 随机 → 得到概念激活向量(CAV)
3. 计算模型预测对CAV方向的敏感度
4. 得到: "模型在分类斑马时,89%依赖了条纹概念"

8.2 Network Dissection

自动发现网络中每个神经元学到了什么语义概念:

典型概念
低层 边缘、颜色、纹理
中层 纹理组合、部件
高层 物体、场景

9. 实战:综合解释流程

Python
"""完整的模型解释流程"""
import torch
from torchvision import models

def explain_prediction(model, image_tensor, class_idx):
    """对一个预测进行多角度解释"""
    results = {}

    # 1. 显著性图
    image_tensor.requires_grad_(True)
    output = model(image_tensor.unsqueeze(0))
    output[0, class_idx].backward()
    results['saliency'] = image_tensor.grad.abs().max(dim=0)[0]

    # 2. Grad-CAM
    grad_cam = GradCAM(model, model.layer4[-1])  # [-1]负索引取最后一个元素
    results['grad_cam'] = grad_cam.generate(
        image_tensor.unsqueeze(0), class_idx
    )

    # 3. 预测置信度
    with torch.no_grad():
        probs = torch.softmax(model(image_tensor.unsqueeze(0)), dim=1)
        results['confidence'] = probs[0, class_idx].item()

    return results

# 最佳实践:结合多种方法交叉验证解释的一致性

10. 面试高频题

Q1: Grad-CAM的原理是什么?

:Grad-CAM利用目标类别分数对最后一个卷积层特征图的梯度进行全局平均池化,得到每个通道的重要性权重,然后对特征图加权求和并通过ReLU,得到类别激活热力图。它不要求特定网络结构,适用于CNN、多任务网络等。

Q2: LIME和SHAP的区别?

:LIME通过在样本邻域采样并拟合局部可解释模型(线性模型),解释单个预测。SHAP基于博弈论的Shapley值,为每个特征分配具有理论保证(公理化)的贡献值。SHAP可被视为LIME的一种理论化、统一化表述。SHAP提供全局一致性保证,而LIME的结果可能因超参数而不稳定。

Q3: 注意力权重能作为可靠的解释吗?

:不能完全依赖。研究(Jain & Wallace, 2019)表明:(1) 不同注意力分布可能产生近似相同的输出;(2) 注意力反映信息路由而非因果归因。但在某些场景下注意力仍是有价值的参考,建议与梯度方法交叉验证。

Q4: 什么是Shapley值?为什么适合模型解释?

:Shapley值源于合作博弈论,衡量每个玩家对联盟总收益的边际贡献。它满足四条公理:效率性(贡献之和等于总收益)、对称性、虚拟性、可加性。应用于模型解释时,它能公平地将模型预测归因到每个输入特征上,且具有唯一性和理论保证。

Q5: 可解释AI面临哪些挑战?

:(1) 解释的忠实性——解释是否真正反映了模型的决策过程;(2) 评估困难——缺乏统一的量化标准;(3) 效率问题——SHAP的精确计算是NP-hard的;(4) 人类理解与模型机制之间的语义鸿沟;(5) 解释可能被操纵(对抗性解释)。


11. 练习与自我检查

编程练习

  1. 基础:实现Grad-CAM并对ResNet-50的预测生成热力图
  2. 进阶:对同一图像分别用显著性图、积分梯度、Grad-CAM生成解释并对比
  3. 挑战:使用SHAP库对一个文本分类模型进行解释

检查清单

  • 能说明可解释AI的主要分类(内在vs事后、全局vs局部)
  • 理解显著性图和积分梯度的原理与区别
  • 能手写Grad-CAM的核心代码
  • 理解LIME的工作流程
  • 能解释Shapley值的概念及SHAP的优势
  • 知道注意力可视化的方法与局限
  • 了解TCAV等概念级解释方法
  • 能综合运用多种方法对模型进行解释

XAI应用场景

扩展阅读: - Selvaraju et al., 2017: Grad-CAM: Visual Explanations from Deep Networks - Lundberg & Lee, 2017: A Unified Approach to Interpreting Model Predictions (SHAP) - Ribeiro et al., 2016: "Why Should I Trust You?": Explaining the Predictions of Any Classifier (LIME) - Kim et al., 2018: Interpretability Beyond Feature Attribution: TCAV