Esta página foi traduzida pela API Cloud Translation.
Switch to English

Gradientes integrados

Ver no TensorFlow.org Executar no Google Colab Ver fonte no GitHub Baixar caderno

Este tutorial demonstra como implementar Gradientes Integrados (IG) , uma técnica de IA explicável apresentada no artigo Atribuição axiomática para redes profundas . 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 a compreensão da importância dos recursos, identificação de distorção de dados e depuração do desempenho do modelo.

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 de IG passo a passo para entender as importâncias dos recursos de pixel de um classificador de imagem. Como exemplo, considere esta imagem de um fireboat pulverizando jatos de água. Você classificaria esta imagem como um fireboat e pode 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 "Máscara de atribuição IG" e "Sobreposição de máscara original + IG", você pode ver que o seu modelo destaca (em roxo) os pixels que compreendem 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 o seu modelo vai generalizar para novos fireboats? E os fireboats sem jatos d'água? Continue lendo para aprender mais sobre como o IG funciona e como aplicar o IG aos seus modelos para entender melhor a relação entre suas previsões e recursos subjacentes.

Imagem de saída 1

Configuração

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

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 você baixará 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 cor RGB de pixels normalizados para o intervalo [0, 1]. O primeiro elemento é None para indicar que o modelo pode aceitar qualquer tamanho de lote inteiro.

Saídas : Um tf.Tensor de tf.Tensor na forma de (batch_size, 1001) . Cada linha representa a pontuação prevista do modelo para cada uma das 1.001 classes da ImageNet. Para o índice de classe predito 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')
Downloading data from https://storage.googleapis.com/download.tensorflow.org/data/ImageNetLabels.txt
16384/10484 [==============================================] - 0s 0us/step

Carregue e pré- tf.image imagens com tf.image

Você ilustrará o IG usando duas imagens do Wikimedia Commons : um Fireboat e um Panda Gigante .

def read_image(file_name):
  image = tf.io.read_file(file_name)
  image = tf.image.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
Downloading data from http://storage.googleapis.com/download.tensorflow.org/example_images/Giant_Panda_2.jpeg
811008/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()

png

Classificar imagens

Vamos começar classificando essas imagens e exibindo as 3 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%}')

png

fireboat: 32.6%
pier: 12.7%
suspension bridge: 5.7%

png

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 recurso 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. Métodos iniciais de interpretabilidade para redes neurais atribuídas pontuações de importância de recurso 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 apenas descrevem mudanças 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 seu modelo inteiro. À medida que seu modelo "aprende" totalmente a relação entre o intervalo de um pixel individual e a classe correta do ImageNet, o gradiente para esse pixel vai saturar , o que significa que se tornará cada vez menor e até mesmo 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();

png

  • esquerda : 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. Pixel x claramente tem um impacto significativo em empurrar seu modelo para 80% de probabilidade prevista na classe verdadeira. Faz sentido que a importância do pixel x seja pequena ou descontínua?

  • direita : a intuição por trás do IG é acumular 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 IG em 3 partes:

    1. interpolar pequenos passos ao longo de uma linha reta no espaço do recurso entre 0 (uma linha de base ou ponto inicial) e 1 (valor do pixel de entrada)
    2. computar gradientes em cada etapa entre as previsões do seu modelo com relação a cada etapa
    3. 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 três partes aplicando IG ao exemplo da imagem "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 na função explicativa da linha de base como representando o impacto da ausência de cada pixel na previsão "Fireboat" para contrastar com seu 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 de pixel. Para uma discussão adicional sobre a seleção da 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 totalmente 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()

png

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 '))} {\ parcial x_i} {d \ alpha} $

Onde:

$ _ {i} $ = recurso
$ x $ = entrada
$ x '$ = linha de base
$ \ alpha $ = constante de interpolação para características perturbe por

Na prática, computar uma integral definida nem sempre é numericamente possível e pode ser caro computacionalmente, 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'))} {\ parcial x_ {i}} \ times \ frac {1} {m} $

Onde:

$ _ {i} $ = característica (pixel individual)
$ x $ = entrada (tensor de imagem)
$ x '$ = linha de base (tensor de imagem)
$ k $ = constante de perturbação de recurso em escala
$ 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 até a entrada está no espaço de pixel. Visto que 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 (xx')) $. Portanto, a mudança de variáveis ​​dá $ dx = (xx ') d \ alpha $. O termo $ (xx ') $ é constante e é fatorado fora 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 {interpolar m imagens em k intervalos})} {\ partial x_ {i}} \ times \ frac {1} {m} $

Primeiro, você irá 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 do recurso entre sua linha de base e a entrada, representados 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 a imagem "Fireboat" de exemplo.

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();

png

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 dos modelos.

$ IntegratedGrads ^ {approx} _ {i} (x) :: = (x_ {i} -x '_ {i}) \ times \ sum_ {k = 1} ^ {m} \ frac {\ overbrace {\ partial F (\ text {imagens interpoladas})} ^ \ text {gradientes de computação}} {\ parcial 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 característica $ x_i $

O TensorFlow facilita a computação de gradientes com um 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 formato (1, 1001) com logits que você converte em probabilidades previstas para cada classe. Você precisa passar o índice correto da classe de destino do ImageNet 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 medindo a 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 saturação de gradiente

Lembre-se de que os gradientes que você acabou de calcular descrevem mudanças locais na probabilidade prevista de "Fireboat" do seu modelo e podem saturar .

Esses conceitos são visualizados usando os gradientes que você calculou 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]);

png

  • esquerda : 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, achatam ou saturam amplamente entre 0,6 e 1,0 antes de se estabelecerem na probabilidade final prevista de "Fireboat" de cerca de 40%.

  • direita : O gráfico da direita mostra as magnitudes médias dos gradientes sobre alfa mais diretamente. Observe como os valores se aproximam bruscamente e até mesmo caem brevemente abaixo de zero. Na verdade, seu modelo "aprende" o máximo com gradientes em valores mais baixos de alfa antes de saturar. Intuitivamente, você pode pensar nisso porque 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 conforme os valores alfa se aproximam de imagem de entrada original.

Para garantir que esses pixels de canhão de água importantes sejam refletidos como importantes para a previsão do "Fireboat", você continuará a seguir para aprender como acumular esses gradientes para aproximar com precisão como cada pixel afeta sua probabilidade prevista do "Fireboat".

Gradientes acumulados (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 funções variáveis. 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 { Soma gradientes locais} \ text {gradientes (imagens interpoladas)} \ times \ overbrace {\ frac {1} {m}} ^ \ text {Divide por m etapas} $

Pela equação, você pode ver que está somando m gradientes e dividindo por m etapas. 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 leva os gradientes da probabilidade prevista da classe de destino 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 da 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 do 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.} $

  1. Gerar alfas $ \ alpha $

  2. Gerar imagens interpoladas = $ (x '+ \ frac {k} {m} \ times (x - x')) $

  3. Calcule gradientes entre as previsões de saída do modelo $ F $ em relação aos recursos de entrada = $ \ frac {\ partial F (\ text {entradas de caminho interpolado})} {\ partial x_ {i}} $

  4. Aproximação integral por meio de gradientes de média = $ \ sum_ {k = 1} ^ m \ text {gradientes} \ times \ frac {1} {m} $

  5. Dimensione gradientes integrados em relação à imagem original = $ (x_ {i} -x '_ {i}) \ times \ text {gradientes integrados} $. A razão pela qual esta etapa é necessária é certificar-se de que os valores de atribuição acumulados em várias imagens interpoladas estão nas mesmas unidades e representam fielmente as importâncias dos pixels na imagem original.

@tf.function
def integrated_gradients(baseline,
                         image,
                         target_class_idx,
                         m_steps=50,
                         batch_size=32):
  # 1. Generate alphas.
  alphas = tf.linspace(start=0.0, stop=1.0, num=m_steps+1)

  # Initialize TensorArray outside loop to collect gradients.    
  gradient_batches = tf.TensorArray(tf.float32, size=m_steps+1)
    
  # 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]

    # 2. Generate interpolated inputs between baseline and input.
    interpolated_path_input_batch = interpolate_images(baseline=baseline,
                                                       image=image,
                                                       alphas=alpha_batch)

    # 3. Compute gradients between model outputs and interpolated inputs.
    gradient_batch = compute_gradients(images=interpolated_path_input_batch,
                                       target_class_idx=target_class_idx)
    
    # Write batch indices and gradients to extend TensorArray.
    gradient_batches = gradient_batches.scatter(tf.range(from_, to), gradient_batch)    
  
  # Stack path gradients together row-wise into single tensor.
  total_gradients = gradient_batches.stack()

  # 4. Integral approximation through averaging gradients.
  avg_gradients = integral_approximation(gradients=total_gradients)

  # 5. Scale integrated gradients with respect to input.
  integrated_gradients = (image - baseline) * avg_gradients

  return integrated_gradients
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 o mesmo formato da imagem de entrada "Fireboat".

print(ig_attributions.shape)
(224, 224, 3)

O artigo sugere que o número de etapas varia entre 20 e 300, dependendo do exemplo (embora na prática isso possa ser maior na casa dos 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.

Visualize 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. Este 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

Olhando as atribuições na imagem do "Fireboat", você pode ver que o modelo identifica os canhões de água e bicas 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)

png

Na imagem do "Panda Gigante", as atribuições destacam a textura, o nariz e o pelo 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)

png

Usos e limitações

Casos de uso

  • Empregar técnicas como gradientes integrados antes de implantar seu modelo pode ajudá-lo a desenvolver 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 um indicativo de um bug em seu modelo ou conjunto de dados, ou overfitting.

Limitações

  • Gradientes integrados fornece importâncias de recursos em exemplos individuais, no entanto, não fornece importâncias de recursos globais em um conjunto de dados inteiro.

  • Gradientes integrados fornece importâncias de recursos individuais, mas não explica as interações e combinações de recursos.

Próximos passos

Este tutorial apresentou uma implementação básica de Gradientes Integrados. Como uma próxima etapa, você pode usar este notebook para experimentar essa técnica com diferentes modelos e imagens você mesmo.

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 para determinar um número suficiente de etapas) que você pode encontrar aqui .

Para aprofundar sua compreensão, confira o artigo Atribuição axiomática para redes profundas e repositório Github , 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 distill.pub .

Interessado em incorporar IG em seus fluxos de trabalho de aprendizado de máquina de produção para importâncias de recursos, análise de erro de modelo e monitoramento de distorção de dados? Confira o produto Explainable AI do Google Cloud que oferece suporte às atribuições de IG. O grupo de pesquisa Google AI PAIR também abriu o código-fonte da ferramenta What-if, que pode ser usada para depuração de modelo, incluindo a visualização de atribuições de recursos IG.