Veja no TensorFlow.org | Executar no Google Colab | Ver no GitHub | Baixar caderno | Veja o modelo do TF Hub |
Este tutorial demonstra como implementar Gradientes Integrados (IG) , uma técnica de IA explicável apresentada no artigo Axiomatic Attribution for Deep Networks . O IG visa explicar a relação entre as previsões de um modelo em termos de suas características. Ele tem muitos casos de uso, incluindo entender a importância dos recursos, identificar a distorção de dados e depurar o desempenho do modelo.
O IG tornou-se uma técnica de interpretabilidade popular devido à sua ampla aplicabilidade a qualquer modelo diferenciável (por exemplo, imagens, texto, dados estruturados), facilidade de implementação, justificativas teóricas e eficiência computacional em relação a abordagens alternativas que permitem escalar para grandes redes e recursos espaços como imagens.
Neste tutorial, você percorrerá uma implementação do IG passo a passo para entender a importância dos recursos de pixel de um classificador de imagens. Como exemplo, considere esta imagem de um barco de bombeiros pulverizando jatos de água. Você classificaria esta imagem como um barco de bombeiros e poderia destacar os pixels que compõem o barco e os canhões de água como sendo importantes para sua decisão. Seu modelo também classificará esta imagem como um fireboat posteriormente neste tutorial; no entanto, ele destaca os mesmos pixels como importantes ao explicar sua decisão?
Nas imagens abaixo intituladas "IG Attribution Mask" e "Original + IG Mask Overlay", você pode ver que seu modelo destaca (em roxo) os pixels que compõem os canhões de água e jatos de água do barco como sendo mais importantes do que o próprio barco para sua decisão. Como seu modelo será generalizado para novos barcos de bombeiros? E os barcos de bombeiros sem jatos de água? Continue lendo para saber mais sobre como o IG funciona e como aplicar o IG aos seus modelos para entender melhor a relação entre as previsões e os recursos subjacentes.
Configurar
import matplotlib.pylab as plt
import numpy as np
import tensorflow as tf
import tensorflow_hub as hub
Baixe um classificador de imagem pré-treinado do TF-Hub
O IG pode ser aplicado a qualquer modelo diferenciável. No espírito do artigo original, você usará uma versão pré-treinada do mesmo modelo, Inception V1, que será baixada do TensorFlow Hub .
model = tf.keras.Sequential([
hub.KerasLayer(
name='inception_v1',
handle='https://tfhub.dev/google/imagenet/inception_v1/classification/4',
trainable=False),
])
model.build([None, 224, 224, 3])
model.summary()
Model: "sequential" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= inception_v1 (KerasLayer) (None, 1001) 6633209 ================================================================= Total params: 6,633,209 Trainable params: 0 Non-trainable params: 6,633,209 _________________________________________________________________
Na página do módulo, você precisa ter em mente o seguinte sobre o Inception V1:
Entradas : A forma de entrada esperada para o modelo é (None, 224, 224, 3)
. Este é um tensor 4D denso de dtype float32 e forma (batch_size, height, width, RGB channels)
cujos elementos são valores de cores RGB de pixels normalizados para o intervalo [0, 1]. O primeiro elemento é None
para indicar que o modelo pode ter qualquer tamanho de lote inteiro.
Saídas : Um tf.Tensor
de logits na forma de (batch_size, 1001)
. Cada linha representa a pontuação prevista do modelo para cada uma das 1.001 classes do ImageNet. Para o índice de classe previsto superior do modelo, você pode usar tf.argmax(predictions, axis=-1)
. Além disso, você também pode converter a saída logit do modelo em probabilidades previstas em todas as classes usando tf.nn.softmax(predictions, axis=-1)
para quantificar a incerteza do modelo, bem como explorar classes previstas semelhantes para depuração.
def load_imagenet_labels(file_path):
labels_file = tf.keras.utils.get_file('ImageNetLabels.txt', file_path)
with open(labels_file) as reader:
f = reader.read()
labels = f.splitlines()
return np.array(labels)
imagenet_labels = load_imagenet_labels('https://storage.googleapis.com/download.tensorflow.org/data/ImageNetLabels.txt')
Carregar e pré-processar imagens com tf.image
Você irá ilustrar o IG usando duas imagens do Wikimedia Commons : um Fireboat e um Giant Panda .
def read_image(file_name):
image = tf.io.read_file(file_name)
image = tf.io.decode_jpeg(image, channels=3)
image = tf.image.convert_image_dtype(image, tf.float32)
image = tf.image.resize_with_pad(image, target_height=224, target_width=224)
return image
img_url = {
'Fireboat': 'http://storage.googleapis.com/download.tensorflow.org/example_images/San_Francisco_fireboat_showing_off.jpg',
'Giant Panda': 'http://storage.googleapis.com/download.tensorflow.org/example_images/Giant_Panda_2.jpeg',
}
img_paths = {name: tf.keras.utils.get_file(name, url) for (name, url) in img_url.items()}
img_name_tensors = {name: read_image(img_path) for (name, img_path) in img_paths.items()}
Downloading data from http://storage.googleapis.com/download.tensorflow.org/example_images/San_Francisco_fireboat_showing_off.jpg 3956736/3954129 [==============================] - 0s 0us/step 3964928/3954129 [==============================] - 0s 0us/step Downloading data from http://storage.googleapis.com/download.tensorflow.org/example_images/Giant_Panda_2.jpeg 811008/802859 [==============================] - 0s 0us/step 819200/802859 [==============================] - 0s 0us/step
plt.figure(figsize=(8, 8))
for n, (name, img_tensors) in enumerate(img_name_tensors.items()):
ax = plt.subplot(1, 2, n+1)
ax.imshow(img_tensors)
ax.set_title(name)
ax.axis('off')
plt.tight_layout()
Classificar imagens
Vamos começar classificando essas imagens e exibindo as 3 principais previsões mais confiáveis. A seguir está uma função de utilidade para recuperar os k principais rótulos e probabilidades previstos.
def top_k_predictions(img, k=3):
image_batch = tf.expand_dims(img, 0)
predictions = model(image_batch)
probs = tf.nn.softmax(predictions, axis=-1)
top_probs, top_idxs = tf.math.top_k(input=probs, k=k)
top_labels = imagenet_labels[tuple(top_idxs)]
return top_labels, top_probs[0]
for (name, img_tensor) in img_name_tensors.items():
plt.imshow(img_tensor)
plt.title(name, fontweight='bold')
plt.axis('off')
plt.show()
pred_label, pred_prob = top_k_predictions(img_tensor)
for label, prob in zip(pred_label, pred_prob):
print(f'{label}: {prob:0.1%}')
fireboat: 32.6% pier: 12.7% suspension bridge: 5.7%
giant panda: 89.4% teddy: 0.3% gibbon: 0.3%
Calcular gradientes integrados
Seu modelo, Inception V1, é uma função aprendida que descreve um mapeamento entre seu espaço de recursos de entrada, valores de pixel de imagem e um espaço de saída definido por valores de probabilidade de classe ImageNet entre 0 e 1. Os primeiros métodos de interpretabilidade para redes neurais atribuíam pontuações de importância de recursos usando gradientes, que informam quais pixels têm o local mais inclinado em relação à previsão do seu modelo em um determinado ponto ao longo da função de previsão do seu modelo. No entanto, os gradientes descrevem apenas as alterações locais na função de previsão do seu modelo em relação aos valores de pixel e não descrevem totalmente a função de previsão do modelo inteiro. À medida que seu modelo "aprende" totalmente a relação entre o intervalo de um pixel individual e a classe ImageNet correta, o gradiente desse pixel saturará , o que significa que ficará cada vez menor e até chegará a zero. Considere a função de modelo simples abaixo:
def f(x):
"""A simplified model function."""
return tf.where(x < 0.8, x, 0.8)
def interpolated_path(x):
"""A straight line path."""
return tf.zeros_like(x)
x = tf.linspace(start=0.0, stop=1.0, num=6)
y = f(x)
fig = plt.figure(figsize=(12, 5))
ax0 = fig.add_subplot(121)
ax0.plot(x, f(x), marker='o')
ax0.set_title('Gradients saturate over F(x)', fontweight='bold')
ax0.text(0.2, 0.5, 'Gradients > 0 = \n x is important')
ax0.text(0.7, 0.85, 'Gradients = 0 \n x not important')
ax0.set_yticks(tf.range(0, 1.5, 0.5))
ax0.set_xticks(tf.range(0, 1.5, 0.5))
ax0.set_ylabel('F(x) - model true class predicted probability')
ax0.set_xlabel('x - (pixel value)')
ax1 = fig.add_subplot(122)
ax1.plot(x, f(x), marker='o')
ax1.plot(x, interpolated_path(x), marker='>')
ax1.set_title('IG intuition', fontweight='bold')
ax1.text(0.25, 0.1, 'Accumulate gradients along path')
ax1.set_ylabel('F(x) - model true class predicted probability')
ax1.set_xlabel('x - (pixel value)')
ax1.set_yticks(tf.range(0, 1.5, 0.5))
ax1.set_xticks(tf.range(0, 1.5, 0.5))
ax1.annotate('Baseline', xy=(0.0, 0.0), xytext=(0.0, 0.2),
arrowprops=dict(facecolor='black', shrink=0.1))
ax1.annotate('Input', xy=(1.0, 0.0), xytext=(0.95, 0.2),
arrowprops=dict(facecolor='black', shrink=0.1))
plt.show();
left : os gradientes do seu modelo para o pixel
x
são positivos entre 0,0 e 0,8, mas vão para 0,0 entre 0,8 e 1,0. O Pixelx
claramente tem um impacto significativo em empurrar seu modelo para 80% de probabilidade prevista na classe real. Faz sentido que a importância do pixelx
seja pequena ou descontínua?à direita : A intuição por trás do IG é acumular os gradientes locais do pixel
x
e atribuir sua importância como uma pontuação para o quanto ele adiciona ou subtrai à probabilidade geral da classe de saída do seu modelo. Você pode dividir e calcular o IG em 3 partes:- interpolar pequenos passos ao longo de uma linha reta no espaço de feição entre 0 (uma linha de base ou ponto inicial) e 1 (valor do pixel de entrada)
- calcular gradientes em cada etapa entre as previsões do seu modelo em relação a cada etapa
- aproxime a integral entre sua linha de base e a entrada acumulando (média cumulativa) esses gradientes locais.
Para reforçar essa intuição, você percorrerá essas 3 partes aplicando IG à imagem de exemplo "Fireboat" abaixo.
Estabeleça uma linha de base
Uma linha de base é uma imagem de entrada usada como ponto de partida para calcular a importância do recurso. Intuitivamente, você pode pensar no papel explicativo da linha de base como representando o impacto da ausência de cada pixel na previsão "Fireboat" para contrastar com o impacto de cada pixel na previsão "Fireboat" quando presente na imagem de entrada. Como resultado, a escolha da linha de base desempenha um papel central na interpretação e visualização da importância dos recursos do pixel. Para obter uma discussão adicional sobre a seleção de linha de base, consulte os recursos na seção "Próximas etapas" na parte inferior deste tutorial. Aqui, você usará uma imagem preta cujos valores de pixel são todos zero.
Outras opções que você pode experimentar incluem uma imagem toda branca ou uma imagem aleatória, que você pode criar com tf.random.uniform(shape=(224,224,3), minval=0.0, maxval=1.0)
.
baseline = tf.zeros(shape=(224,224,3))
plt.imshow(baseline)
plt.title("Baseline")
plt.axis('off')
plt.show()
Descompacte fórmulas em código
A fórmula para Gradientes Integrados é a seguinte:
\(IntegratedGradients_{i}(x) ::= (x_{i} - x'_{i})\times\int_{\alpha=0}^1\frac{\partial F(x'+\alpha \times (x - x'))}{\partial x_i}{d\alpha}\)
Onde:
\(_{i}\) = recurso
\(x\) = entrada
\(x'\) = linha de base
\(\alpha\) = constante de interpolação para perturbar recursos por
Na prática, calcular uma integral definida nem sempre é numericamente possível e pode ser computacionalmente caro, então você calcula a seguinte aproximação numérica:
\(IntegratedGrads^{approx}_{i}(x)::=(x_{i}-x'_{i})\times\sum_{k=1}^{m}\frac{\partial F(x' + \frac{k}{m}\times(x - x'))}{\partial x_{i} } \times \frac{1}{m}\)
Onde:
\(_{i}\) = recurso (pixel individual)
\(x\) = entrada (tensor de imagem)
\(x'\) = linha de base (tensor de imagem)
\(k\) = constante de perturbação de recurso dimensionado
\(m\) = número de passos na aproximação da soma de Riemann da integral
\((x_{i}-x'_{i})\) = um termo para a diferença da linha de base. Isso é necessário para dimensionar os gradientes integrados e mantê-los em termos da imagem original. O caminho da imagem de linha de base para a entrada está no espaço de pixel. Como com IG você está integrando em uma linha reta (transformação linear), isso acaba sendo aproximadamente equivalente ao termo integral da derivada da função de imagem interpolada em relação a \(\alpha\) com passos suficientes. A integral soma o gradiente de cada pixel vezes a mudança no pixel ao longo do caminho. É mais simples implementar essa integração como etapas uniformes de uma imagem para outra, substituindo \(x := (x' + \alpha(x-x'))\). Então a mudança de variáveis dá \(dx = (x-x')d\alpha\). O termo \((x-x')\) é constante e é fatorado da integral.
Interpolar imagens
\(IntegratedGrads^{approx}_{i}(x)::=(x_{i}-x'_{i})\times\sum_{k=1}^{m}\frac{\partial F(\overbrace{x' + \frac{k}{m}\times(x - x')}^\text{interpolate m images at k intervals})}{\partial x_{i} } \times \frac{1}{m}\)
Primeiro, você gerará uma interpolação linear entre a linha de base e a imagem original. Você pode pensar em imagens interpoladas como pequenos passos no espaço de recursos entre sua linha de base e a entrada, representada por \(\alpha\) na equação original.
m_steps=50
alphas = tf.linspace(start=0.0, stop=1.0, num=m_steps+1) # Generate m_steps intervals for integral_approximation() below.
def interpolate_images(baseline,
image,
alphas):
alphas_x = alphas[:, tf.newaxis, tf.newaxis, tf.newaxis]
baseline_x = tf.expand_dims(baseline, axis=0)
input_x = tf.expand_dims(image, axis=0)
delta = input_x - baseline_x
images = baseline_x + alphas_x * delta
return images
Vamos usar a função acima para gerar imagens interpoladas ao longo de um caminho linear em intervalos alfa entre uma imagem de linha de base preta e o exemplo de imagem "Fireboat".
interpolated_images = interpolate_images(
baseline=baseline,
image=img_name_tensors['Fireboat'],
alphas=alphas)
Vamos visualizar as imagens interpoladas. Nota: outra maneira de pensar sobre a constante \(\alpha\) é que ela está aumentando consistentemente a intensidade de cada imagem interpolada.
fig = plt.figure(figsize=(20, 20))
i = 0
for alpha, image in zip(alphas[0::10], interpolated_images[0::10]):
i += 1
plt.subplot(1, len(alphas[0::10]), i)
plt.title(f'alpha: {alpha:.1f}')
plt.imshow(image)
plt.axis('off')
plt.tight_layout();
Gradientes de computação
Agora vamos dar uma olhada em como calcular gradientes para medir a relação entre as alterações em um recurso e as alterações nas previsões do modelo. No caso de imagens, o gradiente nos diz quais pixels têm o efeito mais forte nas probabilidades de classe previstas pelos modelos.
\(IntegratedGrads^{approx}_{i}(x)::=(x_{i}-x'_{i})\times\sum_{k=1}^{m}\frac{\overbrace{\partial F(\text{interpolated images})}^\text{compute gradients} }{\partial x_{i} } \times \frac{1}{m}\)
Onde:
\(F()\) = função de previsão do seu modelo
\(\frac{\partial{F} }{\partial{x_i} }\) = gradiente (vetor de derivadas parciais \(\partial\)) da função de previsão do seu modelo F em relação a cada recurso \(x_i\)
O TensorFlow facilita a computação de gradientes com uma tf.GradientTape
.
def compute_gradients(images, target_class_idx):
with tf.GradientTape() as tape:
tape.watch(images)
logits = model(images)
probs = tf.nn.softmax(logits, axis=-1)[:, target_class_idx]
return tape.gradient(probs, images)
Vamos calcular os gradientes para cada imagem ao longo do caminho de interpolação em relação à saída correta. Lembre-se de que seu modelo retorna um Tensor
em forma (1, 1001)
com logits que você converte em probabilidades previstas para cada classe. Você precisa passar o índice de classe de destino ImageNet correto para a função compute_gradients
para sua imagem.
path_gradients = compute_gradients(
images=interpolated_images,
target_class_idx=555)
Observe a forma de saída de (n_interpolated_images, img_height, img_width, RGB)
, que nos dá o gradiente para cada pixel de cada imagem ao longo do caminho de interpolação. Você pode pensar nesses gradientes como uma medida da mudança nas previsões do seu modelo para cada pequena etapa no espaço de recursos.
print(path_gradients.shape)
(51, 224, 224, 3)
Visualizando a saturação do gradiente
Lembre-se de que os gradientes que você calculou acima descrevem mudanças locais na probabilidade prevista de "Fireboat" do seu modelo e podem saturar .
Esses conceitos são visualizados usando os gradientes calculados acima nos 2 gráficos abaixo.
pred = model(interpolated_images)
pred_proba = tf.nn.softmax(pred, axis=-1)[:, 555]
plt.figure(figsize=(10, 4))
ax1 = plt.subplot(1, 2, 1)
ax1.plot(alphas, pred_proba)
ax1.set_title('Target class predicted probability over alpha')
ax1.set_ylabel('model p(target class)')
ax1.set_xlabel('alpha')
ax1.set_ylim([0, 1])
ax2 = plt.subplot(1, 2, 2)
# Average across interpolation steps
average_grads = tf.reduce_mean(path_gradients, axis=[1, 2, 3])
# Normalize gradients to 0 to 1 scale. E.g. (x - min(x))/(max(x)-min(x))
average_grads_norm = (average_grads-tf.math.reduce_min(average_grads))/(tf.math.reduce_max(average_grads)-tf.reduce_min(average_grads))
ax2.plot(alphas, average_grads_norm)
ax2.set_title('Average pixel gradients (normalized) over alpha')
ax2.set_ylabel('Average pixel gradients')
ax2.set_xlabel('alpha')
ax2.set_ylim([0, 1]);
left : Este gráfico mostra como a confiança do seu modelo na classe "Fireboat" varia entre os alfas. Observe como os gradientes, ou inclinação da linha, em grande parte achatam ou saturam entre 0,6 e 1,0 antes de se estabelecerem na probabilidade final prevista do "Fireboat" de cerca de 40%.
right : O gráfico da direita mostra as magnitudes médias dos gradientes sobre alfa mais diretamente. Observe como os valores se aproximam acentuadamente e até mergulham brevemente abaixo de zero. Na verdade, seu modelo "aprende" mais com gradientes em valores mais baixos de alfa antes de saturar. Intuitivamente, você pode pensar nisso como seu modelo aprendeu os pixels, por exemplo, canhões de água para fazer a previsão correta, enviando esses gradientes de pixels para zero, mas ainda é bastante incerto e focado em pixels espúrios de ponte ou jato de água à medida que os valores alfa se aproximam do imagem de entrada original.
Para garantir que esses pixels importantes de canhão de água sejam refletidos como importantes para a previsão "Fireboat", você continuará abaixo para aprender como acumular esses gradientes para aproximar com precisão como cada pixel afeta sua probabilidade prevista de "Fireboat".
Acumular gradientes (aproximação integral)
Há muitas maneiras diferentes de calcular a aproximação numérica de uma integral para IG com diferentes compensações em precisão e convergência em várias funções. Uma classe popular de métodos é chamada de somas de Riemann . Aqui, você usará a regra trapezoidal (você pode encontrar código adicional para explorar diferentes métodos de aproximação no final deste tutorial).
$IntegratedGrads^{approx} {i}(x)::=(x {i}-x' {i})\times \overbrace{\sum {k=1}^{m} }^\text{Sum m gradientes locais} \text{gradients(imagens interpoladas)} \times \overbrace{\frac{1}{m} }^\text{Dividir por m passos}$
A partir da equação, você pode ver que está somando em m
gradientes e dividindo por m
passos. Você pode implementar as duas operações juntas para a parte 3 como uma média dos gradientes locais de m
previsões interpoladas e imagens de entrada .
def integral_approximation(gradients):
# riemann_trapezoidal
grads = (gradients[:-1] + gradients[1:]) / tf.constant(2.0)
integrated_gradients = tf.math.reduce_mean(grads, axis=0)
return integrated_gradients
A função integral_approximation
pega os gradientes da probabilidade prevista da classe alvo em relação às imagens interpoladas entre a linha de base e a imagem original.
ig = integral_approximation(
gradients=path_gradients)
Você pode confirmar que a média entre os gradientes de m
imagens interpoladas retorna um tensor de gradientes integrado com a mesma forma que a imagem original do "Panda Gigante".
print(ig.shape)
(224, 224, 3)
Juntando tudo
Agora você combinará as 3 partes gerais anteriores em uma função IntegratedGradients
e utilizará um decorador @tf.function para compilá-lo em um gráfico TensorFlow de alto desempenho que pode ser chamado. Isso é implementado como 5 etapas menores abaixo:
\(IntegratedGrads^{approx}_{i}(x)::=\overbrace{(x_{i}-x'_{i})}^\text{5.}\times \overbrace{\sum_{k=1}^{m} }^\text{4.} \frac{\partial \overbrace{F(\overbrace{x' + \overbrace{\frac{k}{m} }^\text{1.}\times(x - x'))}^\text{2.} }^\text{3.} }{\partial x_{i} } \times \overbrace{\frac{1}{m} }^\text{4.}\)
Gerar alfas \(\alpha\)
Gerar imagens interpoladas = \((x' + \frac{k}{m}\times(x - x'))\)
Computar gradientes entre as previsões de saída do modelo \(F\) em relação aos recursos de entrada = \(\frac{\partial F(\text{interpolated path inputs})}{\partial x_{i} }\)
Aproximação integral através de gradientes médios = \(\sum_{k=1}^m \text{gradients} \times \frac{1}{m}\)
Escalar gradientes integrados em relação à imagem original = \((x_{i}-x'_{i}) \times \text{integrated gradients}\). A razão pela qual essa etapa é necessária é garantir que os valores de atribuição acumulados em várias imagens interpoladas estejam nas mesmas unidades e representem fielmente as importâncias dos pixels na imagem original.
def integrated_gradients(baseline,
image,
target_class_idx,
m_steps=50,
batch_size=32):
# Generate alphas.
alphas = tf.linspace(start=0.0, stop=1.0, num=m_steps+1)
# Collect gradients.
gradient_batches = []
# Iterate alphas range and batch computation for speed, memory efficiency, and scaling to larger m_steps.
for alpha in tf.range(0, len(alphas), batch_size):
from_ = alpha
to = tf.minimum(from_ + batch_size, len(alphas))
alpha_batch = alphas[from_:to]
gradient_batch = one_batch(baseline, image, alpha_batch, target_class_idx)
gradient_batches.append(gradient_batch)
# Stack path gradients together row-wise into single tensor.
total_gradients = tf.stack(gradient_batch)
# Integral approximation through averaging gradients.
avg_gradients = integral_approximation(gradients=total_gradients)
# Scale integrated gradients with respect to input.
integrated_gradients = (image - baseline) * avg_gradients
return integrated_gradients
@tf.function
def one_batch(baseline, image, alpha_batch, target_class_idx):
# Generate interpolated inputs between baseline and input.
interpolated_path_input_batch = interpolate_images(baseline=baseline,
image=image,
alphas=alpha_batch)
# Compute gradients between model outputs and interpolated inputs.
gradient_batch = compute_gradients(images=interpolated_path_input_batch,
target_class_idx=target_class_idx)
return gradient_batch
ig_attributions = integrated_gradients(baseline=baseline,
image=img_name_tensors['Fireboat'],
target_class_idx=555,
m_steps=240)
Novamente, você pode verificar se as atribuições do recurso IG têm a mesma forma que a imagem "Fireboat" de entrada.
print(ig_attributions.shape)
(224, 224, 3)
O artigo sugere que o número de etapas varia de 20 a 300, dependendo do exemplo (embora na prática isso possa ser maior em 1.000 para aproximar com precisão a integral). Você pode encontrar código adicional para verificar o número apropriado de etapas nos recursos "Próximas etapas" no final deste tutorial.
Visualizar atribuições
Você está pronto para visualizar as atribuições e sobrepô-las na imagem original. O código abaixo soma os valores absolutos dos gradientes integrados nos canais de cores para produzir uma máscara de atribuição. Esse método de plotagem captura o impacto relativo dos pixels nas previsões do modelo.
def plot_img_attributions(baseline,
image,
target_class_idx,
m_steps=50,
cmap=None,
overlay_alpha=0.4):
attributions = integrated_gradients(baseline=baseline,
image=image,
target_class_idx=target_class_idx,
m_steps=m_steps)
# Sum of the attributions across color channels for visualization.
# The attribution mask shape is a grayscale image with height and width
# equal to the original image.
attribution_mask = tf.reduce_sum(tf.math.abs(attributions), axis=-1)
fig, axs = plt.subplots(nrows=2, ncols=2, squeeze=False, figsize=(8, 8))
axs[0, 0].set_title('Baseline image')
axs[0, 0].imshow(baseline)
axs[0, 0].axis('off')
axs[0, 1].set_title('Original image')
axs[0, 1].imshow(image)
axs[0, 1].axis('off')
axs[1, 0].set_title('Attribution mask')
axs[1, 0].imshow(attribution_mask, cmap=cmap)
axs[1, 0].axis('off')
axs[1, 1].set_title('Overlay')
axs[1, 1].imshow(attribution_mask, cmap=cmap)
axs[1, 1].imshow(image, alpha=overlay_alpha)
axs[1, 1].axis('off')
plt.tight_layout()
return fig
Observando as atribuições na imagem "Fireboat", você pode ver que o modelo identifica os canhões e bicas de água como contribuindo para sua previsão correta.
_ = plot_img_attributions(image=img_name_tensors['Fireboat'],
baseline=baseline,
target_class_idx=555,
m_steps=240,
cmap=plt.cm.inferno,
overlay_alpha=0.4)
Na imagem do "Panda Gigante", as atribuições destacam a textura, o nariz e a pelagem do rosto do Panda.
_ = plot_img_attributions(image=img_name_tensors['Giant Panda'],
baseline=baseline,
target_class_idx=389,
m_steps=55,
cmap=plt.cm.viridis,
overlay_alpha=0.5)
Usos e limitações
Casos de uso
- O emprego de técnicas como gradientes integrados antes de implantar seu modelo pode ajudá-lo a desenvolver a intuição de como e por que ele funciona. As características destacadas por esta técnica correspondem à sua intuição? Caso contrário, isso pode ser indicativo de um bug em seu modelo ou conjunto de dados, ou overfitting.
Limitações
Os gradientes integrados fornecem importâncias de recursos em exemplos individuais, no entanto, não fornecem importâncias de recursos globais em um conjunto de dados inteiro.
Os gradientes integrados fornecem importâncias de recursos individuais, mas não explicam interações e combinações de recursos.
Próximos passos
Este tutorial apresentou uma implementação básica de Gradientes Integrados. Como próximo passo, você pode usar este caderno para experimentar essa técnica com diferentes modelos e imagens.
Para leitores interessados, há uma versão mais longa deste tutorial (que inclui código para diferentes linhas de base, para calcular aproximações integrais e determinar um número suficiente de etapas) que você pode encontrar aqui .
Para aprofundar sua compreensão, confira o artigo Axiomatic Attribution for Deep Networks and Github repository , que contém uma implementação em uma versão anterior do TensorFlow. Você também pode explorar a atribuição de recursos e o impacto de diferentes linhas de base em disstill.pub .
Interessado em incorporar IG em seus fluxos de trabalho de aprendizado de máquina de produção para importância de recursos, análise de erros de modelo e monitoramento de distorção de dados? Confira o produto Explainable AI do Google Cloud que oferece suporte a atribuições de IG. O grupo de pesquisa do Google AI PAIR também abriu o código-fonte da ferramenta What-if, que pode ser usada para depuração de modelos, incluindo a visualização de atribuições de recursos do IG.