简介 || 张量 || 自动求导 || 构建模型 || TensorBoard 支持 || 训练模型 || 模型理解
使用 Captum 进行模型理解
请跟随下方视频或前往 youtube 进行操作。点击 这里 下载笔记本及相关文件。
Captum(拉丁语中意为“理解”)是一个基于 PyTorch 构建的开源、可扩展的模型可解释性库。
随着模型复杂性的增加以及由此带来的透明度缺失,模型可解释性方法变得越来越重要。模型理解不仅是研究中的一个活跃领域,也是各行各业使用机器学习的实际应用中的重点领域。Captum 提供了最先进的算法,包括 Integrated Gradients,旨在为研究人员和开发者提供一种简单的方式,以理解哪些特征对模型的输出做出了贡献。
完整文档、API 参考以及一系列特定主题的教程可在 captum.ai 网站上获取。
简介
Captum 的模型可解释性方法基于归因。Captum 提供了三种类型的归因:
-
特征归因 试图通过生成该输出的输入特征来解释特定输出。例如,通过评论中的某些词来解释电影评论是正面还是负面,这就是特征归因的一个例子。
-
层归因 检查模型在特定输入后的隐藏层活动。例如,检查卷积层在输入图像后的空间映射输出,就是层归因的一个例子。
-
神经元归因 与层归因类似,但专注于单个神经元的活动。
在这份交互式笔记中,我们将探讨特征归因(Feature Attribution)和层归因(Layer Attribution)。
这三种归因类型中的每一种都有多种归因算法与之相关。许多归因算法可以分为两大类:
-
基于梯度的算法 计算模型输出、层输出或神经元激活相对于输入的梯度。积分梯度(用于特征)、层梯度 * 激活 和 神经元传导 都是基于梯度的算法。
-
基于扰动的算法 检查模型、层或神经元的输出在输入变化时的响应。输入扰动可以是有方向的或随机的。遮挡、特征消融 和 特征排列 都是基于扰动的算法。
我们将在下面探讨这两种类型的算法。
特别是在涉及大型模型的情况下,将归因数据以易于与所检查的输入特征相关联的方式进行可视化可能非常有价值。虽然使用 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
:
condainstallpytorchtorchvisioncaptumflask-compressmatplotlib=3.3.4-cpytorch
使用 pip
:
pipinstalltorchtorchvisioncaptummatplotlib==3.3.4Flask-Compress
在您设置的环境中重新启动此笔记本,然后您就可以开始了!
第一个示例
首先,我们来看一个简单的可视化示例。我们将从一个在 ImageNet 数据集上预训练的 ResNet 模型开始。获取一个测试输入,并使用不同的 特征归因 算法来检查输入图像如何影响输出,并查看一些测试图像的输入归因图的可视化结果。
首先,导入一些必要的库:
importtorch
importtorch.nn.functionalasF
importtorchvision.transformsastransforms
importtorchvision.modelsasmodels
importcaptum
fromcaptum.attrimport IntegratedGradients, Occlusion, LayerGradCam, LayerAttribution
fromcaptum.attrimport visualization as viz
importos,sys
importjson
importnumpyasnp
fromPILimport Image
importmatplotlib.pyplotasplt
frommatplotlib.colorsimport 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 通过近似模型输出相对于输入的梯度积分,为每个输入特征分配一个重要性分数。
在我们的案例中,我们将选取输出向量的一个特定元素——即表示模型对其所选类别置信度的那个元素——并使用 Integrated Gradients 来理解输入图像的哪些部分对此输出有贡献。
一旦我们获得了 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')
在上图中,您应该可以看到,Integrated Gradients 方法在图像中猫的位置周围给出了最强的信号。
基于遮挡的特征归因
基于梯度的归因方法通过直接计算输出相对于输入的变化来帮助理解模型。基于扰动的归因方法则更直接地通过引入输入的变化来测量输出效果。Occlusion 就是其中一种方法。它涉及替换输入图像的部分区域,并检查对输出信号的影响。
接下来,我们设置 Occlusion 归因。与配置卷积神经网络类似,您可以指定目标区域的大小和步长,以确定单个测量之间的间距。我们将使用 visualize_image_attr_multiple()
来可视化 Occlusion 归因的输出,显示每个区域的正负归因热图,并通过正归因区域对原始图像进行遮罩处理。遮罩处理为我们提供了非常直观的视图,展示了模型认为我们猫照片中哪些区域最具有“猫的特征”。
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 基类中的便捷方法 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 从模型中提取 top-k 预测结果
-
一个有序且易于理解的类别列表,表示我们的模型所训练的类别
-
一组要查找的特征列表 —— 在我们的案例中,是一个
ImageFeature
-
一个数据集,它是一个可迭代对象,返回输入和标签的批次 —— 就像您用于训练时那样
fromcaptum.insightsimport AttributionVisualizer, Batch
fromcaptum.insights.attr_vis.featuresimport ImageFeature
# Baseline is all-zeros input - this may differ depending on your data
defbaseline_func(input):
return input * 0
# merging our image transforms from above
deffull_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()