Shortcuts

简介 || 张量 || 自动微分 || 构建模型 || TensorBoard支持 || 训练模型 || 模型理解

利用 Captum 理解模型

Created On: Nov 30, 2021 | Last Updated: Jan 19, 2024 | Last Verified: Nov 05, 2024

可以观看下面的视频或在 YouTube 上观看。点击 这里 下载笔记本和对应文件。

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

随着模型复杂性的增加及由此导致的透明性缺失,模型可解释性方法变得日益重要。模型理解既是一个活跃的研究领域,也是机器学习在行业中的实践应用的重点领域。Captum 提供包括集成梯度算法在内的最先进算法,为研究人员和开发人员提供一种易于理解哪些特征对模型输出有贡献的方法。

完整文档、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。

使用集成梯度进行特征归因

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

`集成梯度 <https://captum.ai/api/integrated_gradients.html>`__是Captum提供的特征归因算法之一。集成梯度通过与输入有关的模型输出梯度的积分来赋予每个输入特征一个重要性分数。

在我们的案例中,我们会获取输出向量的一个特定元素——即模型对所选类别的置信度——并使用集成梯度来理解输入图像的哪些部分促成了该输出。

一旦我们拥有了集成梯度的重要性地图,我们会使用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')

在上面的图像中,您应该可以看到集成梯度在猫所在的图像区域给出了最强的信号。

使用遮挡进行特征归因

基于梯度的归因方法帮助理解模型,直观地计算输出如何随输入变化。*基于扰动的归因*方法更直接,通过改变输入来测量对输出的影响。遮挡 是一种这样的方法。它包含替换输入图像的某些部分,并检查对输出信号的影响。

下面,我们设置遮挡归因。类似于配置卷积神经网络,您可以指定目标区域的大小以及步长以确定单个测量值的间距。我们会使用``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计算目标输出对指定层的梯度,对于每个输出通道(输出的维度2)进行平均,并将每个通道的平均梯度与层激活相乘。结果会对所有通道求和。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 <https://captum.ai/api/base_classes.html?highlight=layerattribution#captum.attr.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 秒)

画廊由 Sphinx-Gallery 生成

文档

访问 PyTorch 的详细开发者文档

查看文档

教程

获取针对初学者和高级开发人员的深入教程

查看教程

资源

查找开发资源并获得问题的解答

查看资源