Shortcuts

介绍 || 张量 || 自动求导 || 构建模型 || TensorBoard 支持 || 训练模型 || 模型理解

使用Captum进行模型理解

创建于:2021年11月30日 | 最后更新:2024年1月19日 | 最后验证:2024年11月5日

跟随下面的视频或在youtube上观看。下载笔记本和相应的文件这里

Captum(拉丁语中的“理解”)是一个基于PyTorch构建的开源、可扩展的模型可解释性库。

随着模型复杂性的增加和由此带来的透明度不足,模型可解释性方法变得越来越重要。模型理解既是研究的活跃领域,也是使用机器学习的各行业实际应用的重点领域。Captum 提供了最先进的算法,包括 Integrated Gradients,为研究人员和开发人员提供了一种简单的方法来理解哪些特征对模型的输出有贡献。

完整的文档、API参考以及一系列关于特定主题的教程可在captum.ai网站上找到。

介绍

Captum 的模型可解释性方法是通过归因来实现的。在 Captum 中,有三种类型的归因可用:

  • 特征归因旨在通过生成输入的某些特征来解释特定的输出。例如,通过评论中的某些词语来解释电影评论是正面还是负面,就是特征归因的一个例子。

  • 层归因检查模型隐藏层在特定输入后的活动。检查卷积层在响应输入图像时的空间映射输出是层归因的一个例子。

  • 神经元归因类似于层归因,但侧重于单个神经元的活动。

在这个交互式笔记本中,我们将探讨特征归因和层归因。

每种归因类型都有多个归因算法与之关联。许多归因算法可以分为两大类:

  • 基于梯度的算法计算模型输出、层输出或神经元激活相对于输入的向后梯度。集成梯度(用于特征)、层梯度 * 激活神经元传导都是基于梯度的算法。

  • 基于扰动的算法 检查模型、层或神经元在输入变化时的输出变化。输入扰动可以是有方向的或随机的。遮挡, 特征消融,特征置换 都是基于扰动的算法。

我们将在下面检查这两种类型的算法。

特别是在涉及大型模型的情况下,以易于与所检查的输入特征相关联的方式可视化归因数据可能非常有价值。虽然使用Matplotlib、Plotly或类似工具创建自己的可视化当然是可能的,但Captum提供了专门针对其归因的增强工具:

  • captum.attr.visualization 模块(下面导入为 viz)提供了用于可视化与图像相关的归因的有用函数。

  • Captum Insights 是一个基于 Captum 的易于使用的 API,它为图像、文本和任意模型类型提供了带有现成可视化的可视化小部件。

本笔记本将展示这两种可视化工具集。前几个示例将重点介绍计算机视觉用例,但最后的Captum Insights部分将展示多模型视觉问答模型中的归因可视化。

安装

在开始之前,你需要一个包含以下内容的Python环境:

  • Python 版本 3.6 或更高

  • 对于Captum Insights示例,需要Flask 1.1或更高版本以及Flask-Compress(建议使用最新版本)

  • PyTorch 版本 1.2 或更高(推荐使用最新版本)

  • TorchVision 版本 0.6 或更高(推荐使用最新版本)

  • Captum(建议使用最新版本)

  • Matplotlib 版本 3.3.4,因为 Captum 目前使用了一个 Matplotlib 函数,该函数的参数在后续版本中已被重命名

要在Anaconda或pip虚拟环境中安装Captum,请使用以下适用于您环境的命令:

使用 conda

conda install pytorch torchvision captum flask-compress matplotlib=3.3.4 -c pytorch

使用 pip

pip install torch torchvision captum matplotlib==3.3.4 Flask-Compress

在你设置的环境中重新启动这个笔记本,你就可以开始了!

第一个示例

首先,让我们来看一个简单的视觉示例。我们将从一个在ImageNet数据集上预训练的ResNet模型开始。我们将获取一个测试输入,并使用不同的特征归因算法来检查输入图像如何影响输出,并查看一些测试图像的输入归因图的有用可视化。

首先,导入一些内容:

import torch
import torch.nn.functional as F
import torchvision.transforms as transforms
import torchvision.models as models

import captum
from captum.attr import IntegratedGradients, Occlusion, LayerGradCam, LayerAttribution
from captum.attr import visualization as viz

import os, sys
import json

import numpy as np
from PIL import Image
import matplotlib.pyplot as plt
from matplotlib.colors import LinearSegmentedColormap

现在我们将使用TorchVision模型库下载一个预训练的ResNet。由于我们不进行训练,我们暂时将其置于评估模式。

model = models.resnet18(weights='IMAGENET1K_V1')
model = model.eval()

你获取这个交互式笔记本的地方应该也有一个 img 文件夹,里面有一个文件 cat.jpg

test_img = Image.open('img/cat.jpg')
test_img_data = np.asarray(test_img)
plt.imshow(test_img_data)
plt.show()

我们的ResNet模型是在ImageNet数据集上训练的,期望图像具有特定的大小,并且通道数据被归一化到特定的值范围。我们还将提取模型识别的类别的人类可读标签列表 - 这些标签也应该在img文件夹中。

# model expects 224x224 3-color image
transform = transforms.Compose([
 transforms.Resize(224),
 transforms.CenterCrop(224),
 transforms.ToTensor()
])

# standard ImageNet normalization
transform_normalize = transforms.Normalize(
     mean=[0.485, 0.456, 0.406],
     std=[0.229, 0.224, 0.225]
 )

transformed_img = transform(test_img)
input_img = transform_normalize(transformed_img)
input_img = input_img.unsqueeze(0) # the model requires a dummy batch dimension

labels_path = 'img/imagenet_class_index.json'
with open(labels_path) as json_data:
    idx_to_labels = json.load(json_data)

现在,我们可以提出一个问题:我们的模型认为这张图片代表什么?

output = model(input_img)
output = F.softmax(output, dim=1)
prediction_score, pred_label_idx = torch.topk(output, 1)
pred_label_idx.squeeze_()
predicted_label = idx_to_labels[str(pred_label_idx.item())][1]
print('Predicted:', predicted_label, '(', prediction_score.squeeze().item(), ')')

我们已经确认ResNet认为我们的猫的图片确实是一只猫。但是为什么模型认为这是一张猫的图片呢?

为了找到答案,我们转向Captum。

使用积分梯度进行特征归因

特征归因将特定输出归因于输入的特征。它使用特定的输入——在这里是我们的测试图像——来生成每个输入特征对特定输出特征的相对重要性图。

Integrated Gradients 是 Captum 中可用的特征归因算法之一。Integrated Gradients 通过近似模型输出相对于输入的梯度的积分,为每个输入特征分配一个重要性分数。

在我们的案例中,我们将提取输出向量的一个特定元素——即表示模型对其所选类别置信度的那个元素——并使用集成梯度来理解输入图像的哪些部分对这个输出有贡献。

一旦我们从集成梯度中获得了重要性图,我们将使用Captum中的可视化工具来提供重要性图的有用表示。Captum的visualize_image_attr()函数提供了多种选项来定制您的归因数据的显示。在这里,我们传入了一个自定义的Matplotlib颜色图。

运行包含integrated_gradients.attribute()调用的单元格通常需要一两分钟。

# Initialize the attribution algorithm with the model
integrated_gradients = IntegratedGradients(model)

# Ask the algorithm to attribute our output target to
attributions_ig = integrated_gradients.attribute(input_img, target=pred_label_idx, n_steps=200)

# Show the original image for comparison
_ = viz.visualize_image_attr(None, np.transpose(transformed_img.squeeze().cpu().detach().numpy(), (1,2,0)),
                      method="original_image", title="Original Image")

default_cmap = LinearSegmentedColormap.from_list('custom blue',
                                                 [(0, '#ffffff'),
                                                  (0.25, '#0000ff'),
                                                  (1, '#0000ff')], N=256)

_ = viz.visualize_image_attr(np.transpose(attributions_ig.squeeze().cpu().detach().numpy(), (1,2,0)),
                             np.transpose(transformed_img.squeeze().cpu().detach().numpy(), (1,2,0)),
                             method='heat_map',
                             cmap=default_cmap,
                             show_colorbar=True,
                             sign='positive',
                             title='Integrated Gradients')

在上图中,您应该看到集成梯度在图像中猫的位置周围给出了最强的信号。

使用遮挡进行特征归因

基于梯度的归因方法有助于通过直接计算输出相对于输入的变化来理解模型。基于扰动的归因方法更直接地处理这个问题,通过引入输入的变化来测量对输出的影响。Occlusion就是这样一种方法。它涉及替换输入图像的部分,并检查对输出信号的影响。

下面,我们设置了遮挡归因。与配置卷积神经网络类似,您可以指定目标区域的大小,以及步长以确定单个测量的间距。我们将使用visualize_image_attr_multiple()来可视化我们的遮挡归因输出,显示每个区域的正负归因热图,并通过用正归因区域遮罩原始图像。遮罩提供了一个非常有启发性的视图,展示了模型认为我们猫照片中最“像猫”的区域。

occlusion = Occlusion(model)

attributions_occ = occlusion.attribute(input_img,
                                       target=pred_label_idx,
                                       strides=(3, 8, 8),
                                       sliding_window_shapes=(3,15, 15),
                                       baselines=0)


_ = viz.visualize_image_attr_multiple(np.transpose(attributions_occ.squeeze().cpu().detach().numpy(), (1,2,0)),
                                      np.transpose(transformed_img.squeeze().cpu().detach().numpy(), (1,2,0)),
                                      ["original_image", "heat_map", "heat_map", "masked_image"],
                                      ["all", "positive", "negative", "positive"],
                                      show_colorbar=True,
                                      titles=["Original", "Positive Attribution", "Negative Attribution", "Masked"],
                                      fig_size=(18, 6)
                                     )

再次,我们看到图像中包含猫的区域被赋予了更大的重要性。

使用Layer GradCAM进行层归因

层归因允许你将模型中隐藏层的活动归因于输入的特征。下面,我们将使用一种层归因算法来检查模型中一个卷积层的活动。

GradCAM 计算目标输出相对于给定层的梯度,对每个输出通道(输出的第二维度)进行平均,并将每个通道的平均梯度乘以层的激活值。结果在所有通道上求和。GradCAM 是为卷积网络设计的;由于卷积层的活动通常在空间上映射到输入,GradCAM 归因通常会被上采样并用于掩码输入。

层归因的设置与输入归因类似,除了需要指定模型外,还必须指定模型中您希望检查的隐藏层。如上所述,当我们调用attribute()时,我们指定了感兴趣的目标类别。

layer_gradcam = LayerGradCam(model, model.layer3[1].conv2)
attributions_lgc = layer_gradcam.attribute(input_img, target=pred_label_idx)

_ = viz.visualize_image_attr(attributions_lgc[0].cpu().permute(1,2,0).detach().numpy(),
                             sign="all",
                             title="Layer 3 Block 1 Conv 2")

我们将使用基类LayerAttribution中的便捷方法interpolate()来上采样这些归因数据,以便与输入图像进行比较。

upsamp_attr_lgc = LayerAttribution.interpolate(attributions_lgc, input_img.shape[2:])

print(attributions_lgc.shape)
print(upsamp_attr_lgc.shape)
print(input_img.shape)

_ = viz.visualize_image_attr_multiple(upsamp_attr_lgc[0].cpu().permute(1,2,0).detach().numpy(),
                                      transformed_img.permute(1,2,0).numpy(),
                                      ["original_image","blended_heat_map","masked_image"],
                                      ["all","positive","positive"],
                                      show_colorbar=True,
                                      titles=["Original", "Positive Attribution", "Masked"],
                                      fig_size=(18, 6))

这样的可视化可以让你对隐藏层如何响应输入有新的见解。

使用Captum Insights进行可视化

Captum Insights 是一个基于 Captum 构建的可解释性可视化小部件,旨在促进模型理解。Captum Insights 适用于图像、文本和其他特征,帮助用户理解特征归因。它允许您可视化多个输入/输出对的归因,并提供图像、文本和任意数据的可视化工具。

在本节笔记本中,我们将使用Captum Insights可视化多个图像分类推断。

首先,让我们收集一些图片,看看模型对它们的看法。 为了多样化,我们将拍摄我们的猫、一个茶壶和一个三叶虫化石:

imgs = ['img/cat.jpg', 'img/teapot.jpg', 'img/trilobite.jpg']

for img in imgs:
    img = Image.open(img)
    transformed_img = transform(img)
    input_img = transform_normalize(transformed_img)
    input_img = input_img.unsqueeze(0) # the model requires a dummy batch dimension

    output = model(input_img)
    output = F.softmax(output, dim=1)
    prediction_score, pred_label_idx = torch.topk(output, 1)
    pred_label_idx.squeeze_()
    predicted_label = idx_to_labels[str(pred_label_idx.item())][1]
    print('Predicted:', predicted_label, '/', pred_label_idx.item(), ' (', prediction_score.squeeze().item(), ')')

…看起来我们的模型正在正确识别它们——但当然,我们想更深入地挖掘。为此,我们将使用Captum Insights小部件,我们使用下面导入的AttributionVisualizer对象进行配置。AttributionVisualizer期望批量数据,所以我们将引入Captum的Batch辅助类。我们将特别关注图像,所以还会导入ImageFeature

我们使用以下参数配置AttributionVisualizer

  • 要检查的模型数组(在我们的例子中,只有一个)

  • 一个评分函数,允许Captum Insights从模型中提取前k个预测

  • 我们模型训练的有序、人类可读的类别列表

  • 要查找的特征列表 - 在我们的例子中,是一个ImageFeature

  • 一个数据集,它是一个可迭代对象,返回输入和标签的批次 - 就像你用于训练的那样

from captum.insights import AttributionVisualizer, Batch
from captum.insights.attr_vis.features import ImageFeature

# Baseline is all-zeros input - this may differ depending on your data
def baseline_func(input):
    return input * 0

# merging our image transforms from above
def full_img_transform(input):
    i = Image.open(input)
    i = transform(i)
    i = transform_normalize(i)
    i = i.unsqueeze(0)
    return i


input_imgs = torch.cat(list(map(lambda i: full_img_transform(i), imgs)), 0)

visualizer = AttributionVisualizer(
    models=[model],
    score_func=lambda o: torch.nn.functional.softmax(o, 1),
    classes=list(map(lambda k: idx_to_labels[k][1], idx_to_labels.keys())),
    features=[
        ImageFeature(
            "Photo",
            baseline_transforms=[baseline_func],
            input_transforms=[],
        )
    ],
    dataset=[Batch(input_imgs, labels=[282,849,69])]
)

请注意,与上面的归因不同,运行上面的单元格并没有花费太多时间。这是因为Captum Insights允许您在可视化小部件中配置不同的归因算法,之后它将计算并显示归因。那个过程将需要几分钟。

运行下面的单元格将渲染Captum Insights小部件。然后,您可以选择归因方法及其参数,根据预测类别或预测正确性过滤模型响应,查看模型的预测及其相关概率,并查看归因的热图与原始图像的对比。

visualizer.render()

脚本总运行时间: ( 0 分钟 0.000 秒)

Gallery generated by Sphinx-Gallery

优云智算