注意
轉到底部 下載完整的示例程式碼。
簡介 || 張量 || 自動微分 || 構建模型 || TensorBoard 支援 || 訓練模型 || 模型理解
使用 Captum 進行模型理解#
創建於: 2021年11月30日 | 最後更新: 2024年1月19日 | 最後驗證: 2024年11月05日
請跟隨下面的影片或在 youtube 上觀看。在此處 下載 notebook 和相應檔案。
Captum(拉丁語意為“理解”)是一個基於 PyTorch 構建的開源、可擴充套件的模型可解釋性庫。
隨著模型複雜性的增加以及由此帶來的不透明性,模型可解釋性方法變得越來越重要。模型理解既是一個活躍的研究領域,也是在各行業使用機器學習的實際應用關注的重點。Captum 提供了最先進的演算法,包括整合梯度,為研究人員和開發人員提供了一種簡單的方式來理解哪些特徵對模型的輸出做出了貢獻。
完整的文件、API 參考以及一系列特定主題的教程可在 captum.ai 網站上找到。
簡介#
Captum 對模型可解釋性的方法是基於 *歸因* 的。Captum 中有三種歸因型別:
特徵歸因 旨在根據生成特定輸出的輸入特徵來解釋該輸出。解釋一個電影評論是正面還是負面,取決於評論中的某些詞語,這是特徵歸因的一個例子。
層歸因 檢查給定輸入後模型隱藏層的活動。檢查卷積層響應輸入影像的 the spatially-mapped output 是層歸因的一個例子。
神經元歸因 類似於層歸因,但側重於單個神經元的活動。
在這個互動式 notebook 中,我們將研究特徵歸因和層歸因。
每種歸因型別都有多個相關的 **歸因演算法**。許多歸因演算法屬於兩大類:
基於梯度的演算法 計算模型輸出、層輸出或神經元啟用相對於輸入的後向梯度。**整合梯度**(針對特徵)、**層梯度 * 啟用** 和 **神經元電導** 都是基於梯度的演算法。
基於擾動的演算法 透過改變輸入來衡量輸出的變化,來檢查模型、層或神經元輸出的變化。輸入擾動可以是定向的或隨機的。**遮擋**、**特徵消融** 和 **特徵置換** 都是基於擾動的演算法。
我們將在下面考察這兩種型別的演算法。
尤其是在涉及大型模型時,以易於關聯到被檢查的輸入特徵的方式視覺化歸因資料可能非常有價值。雖然使用 Matplotlib、Plotly 或類似工具建立自己的視覺化是可行的,但 Captum 提供了其歸因的專用增強工具。
captum.attr.visualization模組(在下面匯入為viz)提供了用於視覺化與影像相關的歸因的有用函式。Captum Insights 是一個易於使用的 Captum 之上的 API,提供了一個視覺化 widget,其中包含適用於影像、文字和任意模型的現成視覺化。
在本 notebook 的最後,我們將演示這兩種視覺化工具集。前幾個示例將側重於計算機視覺用例,但最後的 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
在此 notebook 中,在您設定的環境中重新啟動,即可開始!
第一個示例#
首先,我們來看一個簡單的視覺示例。我們將從一個在 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()
您獲取此互動式 notebook 的位置也應該有一個包含 cat.jpg 檔案的 img 資料夾。
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。
使用整合梯度進行特徵歸因#
特徵歸因 將特定輸出歸因於輸入的特徵。它使用一個特定的輸入——這裡是我們的測試影像——來生成一個輸入特徵對特定輸出特徵的相對重要性的圖。
整合梯度 是 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 計算目標輸出相對於給定層的梯度,為每個輸出通道(輸出的第二個維度)求平均,並將每個通道的平均梯度乘以層啟用。然後將結果對所有通道求和。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 構建的可解釋性視覺化 widget,旨在促進模型理解。Captum Insights 可處理影像、文字和其他特徵,以幫助使用者理解特徵歸因。它允許您視覺化多個輸入/輸出對的歸因,並提供適用於影像、文字和任意資料的視覺化工具。
在本 notebook 的這一部分,我們將使用 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 widget,我們透過一個 AttributionVisualizer 物件進行配置,該物件將在下面匯入。AttributionVisualizer 需要資料批次,因此我們將引入 Captum 的 Batch 輔助類。並且我們將專門檢視影像,因此我們還將匯入 ImageFeature。
我們使用以下引數配置 AttributionVisualizer:
要檢查的模型陣列(在本例中只有一個)
一個評分函式,它允許 Captum Insights 從模型中提取 top-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 允許您在一個視覺化 widget 中配置不同的歸因演算法,之後它將計算並顯示歸因。那個過程需要幾分鐘。
執行下面的單元格將渲染 Captum Insights widget。然後,您可以選擇歸因方法及其引數,根據預測類別或預測的正確性過濾模型響應,檢視帶有相關機率的模型預測,並檢視與原始影像相比的歸因熱圖。
visualizer.render()