Modelo de Interpretabilidade e Compreensão para PyTorch usando Captum

Introdução

Métodos de interpretabilidade de modelos têm ganhado cada vez mais importância nos últimos anos, como consequência direta do aumento na complexidade dos modelos e a falta de transparência associada. A compreensão do modelo é um tópico de estudo quente e um setor de aplicações práticas que emprega aprendizagem automática em diversos setores.

Captum fornece a acadêmicos e desenvolvedores técnicas de ponta, como o Gradiente Integrado, que tornam fácil identificar os elementos que contribuem para a saída do modelo. Captum torna mais fácil para pesquisadores de ML usarem modelos PyTorch para construir métodos de interpretabilidade.

Ao tornar mais fácil identificar os muitos elementos que contribuem para a saída do modelo, o Captum pode ajudar os desenvolvedores de modelos a criar melhores modelos e corrigir modelos que fornecem resultados inesperados.

Descrições de Algoritmos

Captum é uma biblioteca que permite a implementação de várias abordagens de interpretabilidade. É possível classificar os algoritmos de atribuição de Captum em três categorias amplas:

  • atribuição primária: Determina a contribuição de cada feature de entrada para a saída do modelo.
  • Anotação de Camada: Cada neurônio em uma camada particular é avaliado quanto a seu contributo para a saída do modelo.
  • Anotação de Neurônio: A ativação de um neurônio oculto é determinada avaliando o contributo de cada features de entrada.

A seguir, há um breve resumo das várias abordagens que estão atualmente implementadas no Captum para anotação primária, de camada e de neurônio. Também está incluída uma descrição do túnel de ruído, que pode ser usado para suavizar os resultados de qualquer método de anotação.
O Captum fornece métricas para estimar a confiabilidade das explicações do modelo, além de suas algoritmos de anotação. Atualmente, eles fornecem métricas de infidelidade e sensibilidade que auxiliam a avaliar a precisão das explicações.

Técnicas de Anotação Primária

Gradientes Integrados

Vamos dizer que temos uma representação formal de uma rede profunda, F : Rn → [0, 1].
Vamos dizer que x ∈ Rn é a entrada atual e x′ ∈ Rn é a entrada de base.
A base na rede de imagens pode ser a imagem preta, enquanto que pode ser o vetor de implantação zero em modelos de texto.
Da base x′ para a entrada x, calculamos as gradientes em todos os pontos ao longo do caminho reto (em Rn). Cumulando essas gradientes, é possível gerar gradientes integrados. Gradientes integrados são definidos como o integral da gradiente ao longo de uma caminho direto da base x′ para a entrada x.

As duas hipóteses básicas, sensibilidade e invariança de implementação, formam o基础 deste método. Consulte o artigo original para obter mais informações sobre esses axiomas.

Gradiente SHAP

Valores de Shapley em teoria de jogos cooperativos são usados para calcular os Valores de SHAP de Gradiente, que são calculados usando uma abordagem de gradiente. Gradiente SHAP adiciona ruído gaussiano a cada amostra de entrada várias vezes, depois escolhe um ponto aleatório no caminho entre o baseline e a entrada para determinarmos o gradiente das saídas. Como resultado, os valores finais de SHAP representam a média esperada dos gradientes. * (entradas – baselines). Os valores de SHAP são aproximados com a premissa de que as features de entrada são independentes e que o modelo explicativo é linear entre as entradas e os baselines fornecidos.

DeepLIFT

É possível usar DeepLIFT (uma técnica de propagação reversa) para atribuir mudanças de entrada com base nas diferenças entre entradas e suas referências correspondentes (ou baseline). O DeepLIFT tenta explicar a disparidade entre a saída da referência usando a disparidade entre as entradas da referência. O DeepLIFT emprega a ideia de multiplicadores para “culpar” neurônios individuais pela diferença nas saídas. Para um dado neurônio de entrada x com diferença da referência ∆x, e um neurônio alvo t com diferença da referência ∆t que queremos computar a contribuição para, definimos o multiplicador m∆x∆t como:

DeepLIFT SHAP

DeepLIFT SHAP é uma extensão do DeepLIFT baseada em Shapley values estabelecidas na teoria de jogos cooperativos. O DeepLIFT SHAP calcula a atribuição de DeepLIFT para cada par de entrada-baseamento e divide a média das atribuições resultantes por exemplo de entrada usando uma distribuição de baseamentos. As regras não-lineares do DeepLIFT ajudam a linearizar as funções não-lineares da rede, e a aproximação dos valores de SHAP também se aplica à rede linearizada. As features de entrada são presumidas igualmente ser independentes neste método.

Saliência

A computação de atribuição de entrada via saliência é um processo simples que dá o gradiente da saída em relação à entrada. Uma expansão de rede de primeiro grau de Taylor é usada na entrada, e os gradientes são os coeficientes de cada característica na representação linear do modelo. O valor absoluto destes coeficientes pode ser usado para indicar a relevância de uma característica. Você pode encontrar informações adicionais sobre o método de saliência no artigo original.

Entrada X Gradiente

Gradiente de Entrada X é uma extensão do método de saliência, que calcula os gradientes da saída em relação à entrada e os multiplica pelos valores de features de entrada. Uma intuição para este método considera um modelo linear; os gradientes são simplesmente os coeficientes de cada entrada, e a multiplicação da entrada por um coeficiente corresponde à contribuição total da feature para a saída do modelo linear.

Backpropagation Guiada e Deconvolução

A computação de gradientes é realizada através de backpropagation guiada e deconvolução, embora a backpropagation de funções ReLU seja substituída para que somente gradientes não negativos sejam propagados de volta. Enquanto a função ReLU é aplicada aos gradientes de entrada na backpropagation guiada, ela é aplicada diretamente aos gradientes de saída na deconvolução. É prática comum usar esses métodos em conjunto com redes convolucionais, mas eles também podem ser usados em outras arquiteturas de rede neural.

Guided GradCAM

A computação de atribuições de backpropagation guiada calcula a multiplicação elemento a elemento das atribuições guiadas do GradCAM (Guided GradCAM) com as atribuições (layer) GradCAM upsampladas. A computação de atribuições é feita para uma camada dada e upsamplada para caber à dimensão de entrada. As redes neurais convolucionais são o foco desta técnica. No entanto, qualquer camada que possa ser alinhada espacialmente com a entrada pode ser fornecida. Normalmente, a última camada convolucional é fornecida.

Feature Ablation

Para computar atribuições, uma técnica conhecida como “feature ablation” usa um método baseado em perturbação que substitui um “padrão” ou “valor de referência” conhecido (como 0) por cada característica de entrada antes de computar a diferença no saída. Agrupar e ablatar características de entrada é uma alternativa melhor que fazendo individualmente, e muitas aplicações diferentes podem beneficiar disto. Agrupando e ablatando segmentos de uma imagem, podemos determinar a importância relativa do segmento.

Permutação de Recurso

Permutação de recurso é um método baseado em perturbação no qual cada recurso é permutado aleatoriamente dentro de um lote, e a mudança no resultado (ou perda) é calculada como resultado desta modificação. Os recursos também podem ser agrupados juntos, em vez de individualmente, do mesmo modo que a ablação de recursos. Observe que, em contraste com os outros algoritmos disponíveis em Captum, este algoritmo é o único que pode fornecer atribuições apropriadas quando é fornecido com um lote de vários exemplos de entrada. Outros algoritmos precisam apenas de um exemplo de entrada.

Occlusion

Occlusão é uma abordagem baseada em perturbação para calcular atribuição, substituindo cada região retangular contínua com um valor de referência/base e calculando a diferença no resultado. Para recursos localizados em áreas múltiplas (hiperretangulos), as diferenças de saída correspondentes são agregadas para calcular a atribuição para esse recurso. A oclusão é mais útil em casos como imagens, onde os pixels the região retangular contínua são provavelmente altamente correlacionados.

Aquisição de Valores de Shapley

A técnica de atribuição Valor de Shapley é baseada na teoria de jogos cooperativos. Esta técnica considera cada permutação dos atributos de entrada e os adiciona um a um a uma linha de base específica. A diferença na saída após a adição de cada atributo corresponde à sua contribuição, e essas diferenças são somadas em todas as permutações para determiná-la.

Lime

Uma das ferramentas mais amplamente usadas para interpretabilidade é o Lime, que treina um modelo substituto interpretável ao samplear pontos de dados em volta de um exemplo de entrada e usando avaliações de modelo nestes pontos para treinar um modelo “substituto” mais simples, como um modelo linear.

KernelSHAP

Kernel SHAP é uma técnica para calcular os Valores de Shapley que usa o framework LIME. Os Valores de Shapley podem ser obtidos de forma mais eficiente no framework LIME, definindo a função de perda, pesando o kernel e regularizando corretamente os termos.

Técnicas de Atribuição de Camadas

Condução de Camadas

A Condução de Camadas é uma metodologia que constrói uma imagem mais abrangente da importância de uma neurona, combinando a ativação da neurona com as derivadas parciais tanto da neurona em relação à entrada quanto da saída em relação à neurona. Através da neurona oculta, a condução constrói sobre o fluxo de atribuição de Integrated Gradients (IG). A condução total de uma neurona oculta y é definida da seguinte forma no artigo original:

Influência Interna

Usando o Influência Interna, é possível estimar a integral dos gradientes ao longo do caminho a partir de uma entrada de base até a entrada fornecida. Esta técnica é semelhante a aplicar gradientes integrados, que envolve integrar o gradiente em relação à camada (em vez da entrada).

Gradiente de Camada X Ativação

Gradiente de Camada X Ativação é a versão da rede do método Input X Gradiente para camadas ocultas na rede…
Ele multiplica a ativação de cada elemento da camada pelos gradientes da saída alvo em relação à camada especificada.

GradCAM

GradCAM é uma técnica de atribuição de camada de rede convolucional que geralmente é aplicada à última camada convolucional. O GradCAM calcula os gradientes da saída alvo em relação à camada especificada, divide cada canal de saída (dimensão de saída 2) em média e multiplica a média de gradiente de cada canal pela ativação da camada. Uma função ReLU é aplicada à saída para garantir que apenas atribuições não-negativas forem retornadas a partir da soma dos resultados em todos os canais.

Técnicas de Atribuição de Neurônios

Condutividade de Neurônio

Condutividade combina ativação de neurônios com as derivadas parciais tanto do neurônio em relação à entrada quanto da saída em relação ao neurônio, para fornecer uma visão mais abrangente da relevância do neurônio. Para determinar a condutividade de um neurônio específico, se examina o fluxo de atribuição de IG (Impacto Global) de cada entrada que passa através desse neurônio. A seguir está a definição formal de condutividade do neurônio y dada a atribuição de entrada i do artigo original:

De acordo com esta definição, deve-se notar que a soma da condutividade de um neurônio (por todoas as características de entrada) é sempre igual à condutividade da camada na qual o neurônio específico está localizado.

Gradiente de Neurônio

A abordagem gradiente de neurônio é o equivalente do método de saliência para um neurônio único na rede. Ela simplesmente calcula o gradiente do saída de neurônio em relação à entrada do modelo. Este método, como a Saliência, pode ser pensado como fazendo uma expansão de Taylor de primeira ordem da saída de neurônio no ponto de entrada dado, com os gradientes correspondentes aos coeficientes de cada feature na representação linear do modelo.

Gradientes Integrados de Neurônio

É possível estimar a integral dos gradientes de entrada em relação a um determinado neurônio ao longo do caminho de uma entrada de referência (baseline) até a entrada de interesse, usando uma técnica chamada de “Gradientes Integrados de Neurônio.” Gradientes integrados são equivalentes a este método, assumindo que a saída é apenas a de neurônio identificado. Você pode encontrar informações adicionais sobre a abordagem de gradientes integrados no artigo original aqui.

GradienteSHAP de Neurônio

O Neuron GradientSHAP é o equivalente do GradientSHAP para um neurônio específico. O Neuron GradientSHAP adiciona ruído gaussiano a cada amostra de entrada várias vezes, escolhe um ponto aleatório ao longo do caminho entre o baseline e a entrada e calcula o gradiente do neurônio alvo em relação a cada ponto escolhido aleatoriamente.
Os valores SHAP resultantes são semelhantes aos valores de gradiente preditivos *. (entradas – baseline).

Neuron DeepLIFT SHAP

O Neuron DeepLIFT SHAP é o equivalente do DeepLIFT para um neurônio específico. Usando a distribuição de baselines, o algoritmo DeepLIFT SHAP calcula a atribuição de Neuron DeepLIFT para cada par entrada-baseline e média os resultados de atribuição por exemplo de entrada.

Túnel de Ruído

Túnel de Ruído é uma técnica de atribuição que pode ser usada em conjunto com outros métodos. O túnel de ruído calcula atribuição várias vezes, adicionando ruído gaussiano à entrada cada vez, e então junta as atribuições resultantes dependendo do tipo escolhido. Os seguintes tipos de túnel de ruído são suportados:

  • Smoothgrad: Retorna a média das atribuições amostradas. A suavização da técnica de atribuição especificada usando um kernel gaussiano é uma aproximação deste processo.
  • Smoothgrad Quadrado: Retorna a média das atribuições amostradas quadradas.
  • Vargrad: Retorna a variância das atribuições de amostra.

Medidas

Infidelidade

Infidelidade mede a média do erro quadrático médio entre as explicações de modelos nas magnitude de perturbações de entrada e mudanças da função preditora nestas perturbações de entrada. A infidelidade é definida da seguinte forma:

A partir de técnicas de atribuição bem conhecidas, como o gradiente integrado, isso é um conceito mais eficiente computacionalmente e estendido de Sensitivy-n. O último analisa as correlações entre a soma das atribuições e as diferenças da função preditora em seu input e um baseline predefinido.

Sensibilidade

Sensibilidade, que é definida como o grau de mudança da explicação causada por pequenas perturbações de entrada usando a aproximação baseada em amostragem de Monte Carlo, é medida da seguinte forma:

Por padrão, nós amostramos the subespaço de uma esfera de L-Infinity com um raio padrão para aproximar a sensibilidade. Os usuários podem mudar o raio da esfera e a função de amostragem.

Interpretação de Modelos para Modelo ResNet Pré-treinado

Este tutorial mostra como usar métodos de interpretabilidade de modelos the um modelo ResNet pré-treinado com uma imagem escolhida, e ele visualiza as atribuições para cada pixel pela sobreposição nelas na imagem. Neste tutorial, vamos usar os algoritmos de interpretação de modelos Integrated Gradients, GradientShape, Attribution com Layer GradCAM e Occlusion.

Antes de começar, você deve ter um ambiente Python que inclua:

  • Versão de Python 3.6 ou superior
  • Versão de PyTorch 1.2 ou superior (a versão mais recente é recomendada)
  • Versão de TorchVision 0
  • .6 ou superior (a versão mais recente é recomendada)
  • Captum (a versão mais recente é recomendada)

Dependendo de se você está usando o ambiente virtual Anaconda ou pip, as seguintes instruções ajudarão você a configurar o Captum:

Com conda:

conda install pytorch torchvision captum -c pytorch

Com pip:

pip install torch torchvision captum

Vamos importar as bibliotecas.

import torch
import torch.nn.functional as F

from PIL import Image

import os
import json
import numpy as np
from matplotlib.colors import LinearSegmentedColormap
import os, sys
import json

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

import torchvision
from torchvision import models
from torchvision import transforms

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

Carrega o modelo ResNet treinado e o coloca no modo de avaliação

model = models.resnet18(pretrained=True)
model = model.eval()

O ResNet está treinado no conjunto de dados ImageNet. Baixa e le a lista de classes/etiquetas do conjunto de dados ImageNet em memória.

wget -P $HOME/.torch/models https://s3.amazonaws.com/deep-learning-models/image-models/imagenet_class_index.json
labels_path = os.getenv("HOME") + '/.torch/models/imagenet_class_index.json'
with open(labels_path) as json_data:
    idx_to_labels = json.load(json_data)

Agora que terminamos o modelo, podemos baixar a imagem para análise.
No meu caso, eu escolhi uma imagem de gato.

source

A pasta de imagens deve conter o arquivo cat.jpg. Como podemos ver abaixo, Image.open() abre e identifica o arquivo de imagem fornecido e np.asarry() o converte em um array.

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

No código abaixo, definiremos transformadores e funções de normalização para a imagem. Para treinar nosso modelo ResNet, usamos o conjunto de dados ImageNet, que exige que as imagens sejam de um tamanho particular, com os dados de canal normalizados a um intervalo de valores especificado. transforms.Compose() compõe vários transformadores juntos e transforms.Normalize() normaliza uma imagem tensor com média e desvio padrão.

# Esperança de modelo é uma imagem de 224x224 cores três
transform = transforms.Compose([
 transforms.Resize(256),
 transforms.CenterCrop(224), #crop the given tensor image at the center
 transforms.ToTensor()
])
# Normalização ImageNet
transform_normalize = transforms.Normalize(
     mean=[0.485, 0.456, 0.406],
     std=[0.229, 0.224, 0.225]
 )

img = Image.open('path/cat.jpg')

transformed_img = transform(img)

input = transform_normalize(transformed_img)
#unsqueeze retorna um novo tensor com uma dimensão de tamanho um inserida na posição especificada.
input = input.unsqueeze(0)

Agora, vamos prever a classe da imagem de entrada. A questão que pode ser feita é: “O que o nosso modelo acha que essa imagem representa?”

#chamar o nosso modelo
output = model(input)
## função aplicada softmax()
output = F.softmax(output, dim=1)
#torch.topk retorna os k elementos maiores de um tensor de entrada dado along a dimensão dada. K aqui é 1
prediction_score, pred_label_idx = torch.topk(output, 1)
pred_label_idx.squeeze_()
#converter em um dicionário de chave-valor do rótulo de previsão, convertê-lo em uma string para obter o rótulo previsão
predicted_label = idx_to_labels[str(pred_label_idx.item())][1]
print('Predicted:', predicted_label, '(', prediction_score.squeeze().item(), ')')

saída:

Predicted: tabby ( 0.5530276298522949 )

O fato de que o ResNet acha que nossa imagem de gato representa um gato real é confirmado. Mas o que dá ao modelo a impressão de que isso é uma imagem de gato? Para obter a solução para essa questão, vamos consultar o Captum.

Atribuição de Funcionalidade com Gradientes Integrados

Uma das várias técnicas de atribuição de funcionalidade em Captum é o Gradientes Integrados. Gradientes Integrados atribui uma pontuação de relevância a cada feature de entrada estimando o integral dos gradientes do output do modelo em relação às entradas.

Para nosso caso, nós iremos pegar um componente particular do vetor de saída – o que indica a confiança do modelo em sua categoria selecionada – e usar o gradiente integrado para descobrir quais aspectos da imagem de entrada contribuíram para essa saída. Isso nos permitirá determinar quais partes da imagem foram as mais importantes na produção desse resultado.
Depois de obter o mapa de importância do Gradiente Integrado, nós iremos usar as ferramentas de visualização capturadas por Captum para fornecer uma representação clara e compreensível do mapa de importância.

O Gradiente Integrado calculará o integral dos gradientes da saída do modelo para a classe predita pred_label_idx com relação aos pixels da imagem de entrada ao longo do caminho da imagem preta até nossa imagem de entrada.

print('Predicted:', predicted_label, '(', prediction_score.squeeze().item(), ')')
#Criar objeto Gradiente Integrado e obter atributos
integrated_gradients = IntegratedGradients(model)
#Solicitar ao algoritmo que atribua nossa saída alvo para
attributions_ig = integrated_gradients.attribute(input, target=pred_label_idx, n_steps=200)

Saída:

Predicted: tabby ( 0.5530276298522949 )

Vamos ver a imagem e as atribuições que vêm com ela, sobrepondo as últimas sobre a imagem. O método visualize_image_attr() que Captum oferece fornece um conjunto de possibilidades para personalizar a apresentação dos dados de atribuição às suas preferências. Aqui, nós passamos uma cor padrão personalizada do Matplotlib (veja LinearSegmentedColormap()).

Visualização de resultado com mapa de cores personalizado
default_cmap = LinearSegmentedColormap.from_list('custom blue',
                                                 [(0, '#ffffff'),
                                                  (0.25, '#000000'),
                                                  (1, '#000000')], N=256)
# Use o método de ajuda visualize_image_attr para visualização para mostrar a # imagem original para comparação
_ = 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',
                             outlier_perc=1)

saída:

Você deve conseguir notar na imagem que mostramos acima que a área à volta do gato na imagem é onde o algoritmo de Integrações Gradientes dá-nos o sinal mais forte.

Vamos calcular atribuições usando Integrações Gradientes e então suavizá-las em várias imagens que foram produzidas por um túnel de ruído:.
O último modifica a entrada adicionando ruído Gaussiano com uma desvio padrão de uma, 10 vezes (nt_samples=10). A abordagem smoothgrad_sq é usada pelo túnel de ruído para tornar as atribuições consistentes em todos os smoothgrad_sq de amostras ruído.
O valor de smoothgrad_sq é a média das atribuições quadradas em nt_samples amostras.

noise_tunnel = NoiseTunnel(integrated_gradients)

attributions_ig_nt = noise_tunnel.attribute(input, nt_samples=10, nt_type='smoothgrad_sq', target=pred_label_idx)
_ = viz.visualize_image_attr_multiple(np.transpose(attributions_ig_nt.squeeze().cpu().detach().numpy(), (1,2,0)),
                                      np.transpose(transformed_img.squeeze().cpu().detach().numpy(), (1,2,0)),
                                      ["original_image", "heat_map"],
                                      ["all", "positive"],
                                      cmap=default_cmap,
                                      show_colorbar=True)

saída:

Posso ver nas imagens acima que o modelo se concentra na cabeça do gato.

Vamos finalizar usando GradientShap. GradientShap é uma abordagem de gradiente que pode ser usada para calcular valores SHAP e também é uma ferramenta fantástica para obter insights sobre o comportamento global. É um modelo de explicação linear que explica as previsões do modelo usando uma distribuição de amostras de referência. Determina as gradientes esperadas para uma entrada escolhida aleatoriamente entre a entrada e um baseline.
O baseline é escolhido aleatoriamente da distribuição de baselines fornecida.

torch.manual_seed(0)
np.random.seed(0)

gradient_shap = GradientShap(model)

# Definição da distribuição de baseline de imagens
rand_img_dist = torch.cat([input * 0, input * 1])

attributions_gs = gradient_shap.attribute(input,
                                          n_samples=50,
                                          stdevs=0.0001,
                                          baselines=rand_img_dist,
                                          target=pred_label_idx)
_ = viz.visualize_image_attr_multiple(np.transpose(attributions_gs.squeeze().cpu().detach().numpy(), (1,2,0)),
                                      np.transpose(transformed_img.squeeze().cpu().detach().numpy(), (1,2,0)),
                                      ["original_image", "heat_map"],
                                      ["all", "absolute_value"],
                                      cmap=default_cmap,
                                      show_colorbar=True)

Saída:

Atribuição de Camada com GradCAM

Você pode relacionar a atividade das camadas ocultas dentro de seu modelo aos recursos de sua entrada usando a Atribuição de Camada.
Nós aplicaremos um algoritmo de atribuição de camada para investigar a atividade de uma das camadas convolucionais incluídas em nosso modelo.
O GradCAM é responsável por calcular os gradientes da saída alvo em relação à camada especificada. Estes gradientes são então calculados para cada canal de saída (dimensão 2 da saída), e as ativações da camada são multiplicadas pelo gradiente médio para cada canal.
Os resultados são somados em todos os canais. Como a atividade das camadas convolucionais frequentemente se mapeia espacialmente para a entrada, as atribuições GradCAM são frequentemente upsampled e usadas para mascarar a entrada. Note que o GradCAM é explicitamente desenvolvido para redes neurais convolucionais (convnets). A atribuição de camada é configurada da mesma forma que a atribuição de entrada, com a diferença de que, além do modelo, você deve fornecer uma camada oculta dentro do modelo que você deseja analisar. Similar a aquilo que foi discutido anteriormente, quando chamamos attribute(), indicamos a classe de interesse do alvo.

layer_gradcam = LayerGradCam(model, model.layer3[1].conv2)
attributions_lgc = layer_gradcam.attribute(input, 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")

Para fazer uma comparação mais precisa entre a imagem de entrada e este dado de atribuição, nós vamos upsampling com a ajuda da função interpolate(), localizada na classe base LayerAttribution.

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

print(attributions_lgc.shape)
print(upsamp_attr_lgc.shape)
print(input.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))

Output:

Visualizações como esta têm o potencial para fornecer insights únicos sobre como suas camadas ocultas respondem à entrada que você fornece.

Atribuição de Funcionalidade com Ocultação

Métodos baseados em gradientes ajudam a entender o modelo em termos de computar diretamente as mudanças no saída com relação à entrada.
A técnica conhecida como atribuição baseada em perturbações tem uma abordagem mais direta para esse problema, fazendo modificações na entrada para quantificar o impacto dessas mudanças na saída. Uma dessas estratégias é chamada de ocultação.
Elabora-se trocando peças da imagem de entrada e analisando como essa mudança afeta o sinal produzido na saída.

A seguir, configuraremos a atribuição de ocultação. Assim como a configuração de uma rede neural convolucional, é possível escolher o tamanho da região alvo e a espessura da grade, que determina a separação entre as medições individuais.
Utilizaremos a função visualize_image_attr_multiple() para visualizar os resultados da nossa atribuição de ocultação. Esta função exibirá mapas de calor de ambas as atribuições positivas e negativas por região e mascarará a imagem original com as regiões de atribuição positiva.
A mascara fornece uma olhada muito iluminativa nas regiões da nossa foto de gato que o modelo identificou como sendo as mais “gatunas”.

occlusion = Occlusion(model)

attributions_occ = occlusion.attribute(input,
                                       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)
                                     )

Saída:

A parte da imagem que contém o gato parece ser dada um nível de importância maior.

Conclusão

Captum é uma biblioteca de interpretabilidade de modelos para PyTorch que é versátil e simples. Ela oferece técnicas de ponta para entender como neurônios e camadas específicas afetam as previsões.
Existem três tipos de técnicas de atribuição principais: Técnicas de Atribuição Primária, Técnicas de Atribuição de Camada e Técnicas de Atribuição de Neurônio.

Referências

https://pytorch.org/tutorials/beginner/introyt/captumyt.html
https://gilberttanner.com/blog/interpreting-pytorch-models-with-captum/
https://arxiv.org/pdf/1805.12233.pdf
https://arxiv.org/pdf/1704.02685.pdf

Source:
https://www.digitalocean.com/community/tutorials/model-interpretability-and-understanding-for-pytorch-using-captum