捷徑

簡介 || 張量 || 自動微分 || 建構模型 || TensorBoard 支援 || 訓練模型 || 模型理解

使用 Captum 理解模型

建立於:2021 年 11 月 30 日 | 最後更新:2024 年 1 月 19 日 | 最後驗證:2024 年 11 月 05 日

請觀看以下影片或在 youtube 上觀看。下載 notebook 和相關檔案這裡

Captum (拉丁文的「理解」) 是一個開源、可擴展的程式庫,用於建立在 PyTorch 上的模型可解釋性。

隨著模型複雜性的增加和由此產生的透明度不足,模型可解釋性方法變得越來越重要。模型理解既是一個活躍的研究領域,也是各行業使用機器學習的實際應用重點領域。Captum 提供了最先進的演算法,包括 Integrated Gradients,為研究人員和開發人員提供了一種簡單的方法來了解哪些特徵正在影響模型的輸出。

完整的說明文件、API 參考和特定主題的教學可在 captum.ai 網站上取得。

簡介

Captum 的模型可解釋性方法基於歸因 (attributions)。 Captum 中有三種歸因可用

  • 特徵歸因 (Feature Attribution) 旨在根據產生特定輸出的輸入特徵來解釋特定輸出。根據評論中的某些詞語來解釋電影評論是正面還是負面就是特徵歸因的一個例子。

  • 層歸因 (Layer Attribution) 檢查模型隱藏層在特定輸入之後的活動。檢查卷積層響應輸入影像的空間映射輸出是層歸因的一個例子。

  • 神經元歸因 (Neuron Attribution) 類似於層歸因,但側重於單個神經元的活動。

在這個互動式 notebook 中,我們將研究特徵歸因和層歸因。

三種類型的歸因中的每一種都有多個相關聯的 歸因演算法 (attribution algorithms)。 許多歸因演算法可分為兩大類

  • 基於梯度的演算法 (Gradient-based algorithms) 計算模型輸出、層輸出或神經元激活相對於輸入的反向梯度。 Integrated Gradients (適用於特徵)、Layer Gradient * ActivationNeuron Conductance 都是基於梯度的演算法。

  • 基於擾動的演算法 (Perturbation-based algorithms) 檢查模型、層或神經元的輸出響應輸入變化的變化。 輸入擾動可能是定向的或隨機的。 Occlusion、 Feature AblationFeature Permutation 都是基於擾動的演算法。

我們將在下面檢查這兩種演算法。

特別是在涉及大型模型的情況下,以易於將其與正在檢查的輸入特徵相關聯的方式可視化歸因資料可能很有價值。 雖然肯定可以使用 Matplotlib、Plotly 或類似工具建立自己的視覺化效果,但 Captum 提供了針對其歸因的增強工具

  • captum.attr.visualization 模組 (如下方匯入為 viz) 提供了有助於可視化與影像相關的歸因的函式。

  • Captum Insights 是 Captum 之上的一個易於使用的 API,它提供了一個視覺化小工具,其中包含用於影像、文字和任意模型類型的現成視覺化效果。

這兩種視覺化工具集將在本 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 模型開始。我們會取得一個測試輸入,並使用不同的特徵歸因 (Feature Attribution) 演算法來檢驗輸入圖像如何影響輸出,並查看一些測試圖像的輸入歸因圖的實用視覺化呈現。

首先,匯入一些套件

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) 進行特徵歸因

特徵歸因 (Feature attribution) 將特定的輸出歸因於輸入的特徵。它使用特定的輸入 - 在這裡,是我們的測試圖像 - 來生成一個地圖,顯示每個輸入特徵對於特定輸出特徵的相對重要性。

積分梯度 (Integrated Gradients) 是 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')

在上面的圖像中,您應該看到積分梯度在圖像中貓的位置附近給了我們最強烈的信號。

使用遮擋 (Occlusion) 進行特徵歸因

基於梯度的歸因方法有助於通過直接計算輸出相對於輸入的變化來理解模型。基於擾動的歸因方法更直接地處理這個問題,通過引入對輸入的更改來測量對輸出的影響。遮擋 (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 進行層歸因

層歸因 (Layer Attribution) 允許您將模型中隱藏層的活動歸因於輸入的特徵。下面,我們將使用層歸因演算法來檢驗模型中卷積層之一的活動。

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 從模型中提取前 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 的完整開發者文件

檢視文件

教學

取得初學者和高級開發人員的深入教學

檢視教學

資源

尋找開發資源並獲得問題解答

檢視資源