Voir sur TensorFlow.org | Exécuter dans Google Colab | Afficher sur GitHub | Télécharger le cahier | Voir le modèle TF Hub |
Ce didacticiel montre comment implémenter les gradients intégrés (IG) , une technique d' IA explicable introduite dans l'article Attribution axiomatique pour les réseaux profonds . IG vise à expliquer la relation entre les prédictions d'un modèle en fonction de ses caractéristiques. Il a de nombreux cas d'utilisation, notamment la compréhension de l'importance des fonctionnalités, l'identification de l'asymétrie des données et le débogage des performances du modèle.
L'IG est devenue une technique d'interprétabilité populaire en raison de sa large applicabilité à tout modèle différentiable (par exemple, images, texte, données structurées), de sa facilité de mise en œuvre, de ses justifications théoriques et de son efficacité de calcul par rapport aux approches alternatives qui lui permettent de s'adapter à de grands réseaux et fonctionnalités. espaces tels que les images.
Dans ce didacticiel, vous allez parcourir une implémentation d'IG étape par étape pour comprendre l'importance des caractéristiques de pixel d'un classificateur d'image. À titre d'exemple, considérons cette image d'un bateau-pompe pulvérisant des jets d'eau. Vous classeriez cette image comme un bateau-pompe et pourriez mettre en évidence les pixels qui composent le bateau et les canons à eau comme étant importants pour votre décision. Votre modèle classera également cette image en tant que bateau-pompe plus tard dans ce didacticiel ; Cependant, met-il en évidence les mêmes pixels comme étant importants pour expliquer sa décision ?
Dans les images ci-dessous intitulées "IG Attribution Mask" et "Original + IG Mask Overlay", vous pouvez voir que votre modèle met en évidence (en violet) les pixels comprenant les canons à eau et les jets d'eau du bateau comme étant plus importants que le bateau lui-même pour sa décision. Comment votre modèle va-t-il se généraliser aux nouveaux bateaux-pompes ? Qu'en est-il des bateaux-pompes sans jets d'eau ? Poursuivez votre lecture pour en savoir plus sur le fonctionnement de l'IG et sur la manière de l'appliquer à vos modèles afin de mieux comprendre la relation entre leurs prédictions et les fonctionnalités sous-jacentes.
Installer
import matplotlib.pylab as plt
import numpy as np
import tensorflow as tf
import tensorflow_hub as hub
Télécharger un classificateur d'images pré-entraîné depuis TF-Hub
IG peut être appliqué à n'importe quel modèle différentiable. Dans l'esprit de l'article original, vous utiliserez une version pré-entraînée du même modèle, Inception V1, que vous téléchargerez depuis 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 _________________________________________________________________
À partir de la page du module, vous devez garder à l'esprit ce qui suit concernant Inception V1 :
Entrées : la forme d'entrée attendue pour le modèle est (None, 224, 224, 3)
. Il s'agit d'un tenseur 4D dense de type float32 et de forme (batch_size, height, width, RGB channels)
dont les éléments sont des valeurs de couleur RVB de pixels normalisées à la plage [0, 1]. Le premier élément est None
pour indiquer que le modèle peut accepter n'importe quelle taille de lot d'entiers.
Sorties : Un tf.Tensor
de logits sous la forme de (batch_size, 1001)
. Chaque ligne représente le score prévu du modèle pour chacune des 1 001 classes d'ImageNet. Pour l'index de classe prédit supérieur du modèle, vous pouvez utiliser tf.argmax(predictions, axis=-1)
. En outre, vous pouvez également convertir la sortie logit du modèle en probabilités prédites dans toutes les classes à l'aide tf.nn.softmax(predictions, axis=-1)
pour quantifier l'incertitude du modèle et explorer des classes prédites similaires pour le débogage.
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')
Charger et prétraiter les images avec tf.image
Vous illustrerez IG en utilisant deux images de Wikimedia Commons : un bateau- pompe et un panda géant .
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()
Classer les images
Commençons par classer ces images et afficher les 3 prédictions les plus fiables. Voici une fonction utilitaire pour récupérer les top k étiquettes et probabilités prédites.
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%
Calculer les dégradés intégrés
Votre modèle, Inception V1, est une fonction apprise qui décrit un mappage entre votre espace de caractéristiques d'entrée, les valeurs de pixels d'image et un espace de sortie défini par les valeurs de probabilité de classe ImageNet entre 0 et 1. Les premières méthodes d'interprétabilité pour les réseaux de neurones ont attribué des scores d'importance des caractéristiques à l'aide les gradients, qui vous indiquent quels pixels ont le local le plus raide par rapport à la prédiction de votre modèle à un point donné le long de la fonction de prédiction de votre modèle. Cependant, les gradients ne décrivent que les modifications locales de la fonction de prédiction de votre modèle en ce qui concerne les valeurs de pixel et ne décrivent pas entièrement la fonction de prédiction de votre modèle dans son intégralité. Au fur et à mesure que votre modèle "apprend" complètement la relation entre la plage d'un pixel individuel et la classe ImageNet correcte, le dégradé de ce pixel saturera , ce qui signifie qu'il deviendra de plus en plus petit et qu'il ira même jusqu'à zéro. Considérez la fonction de modèle simple ci-dessous :
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();
gauche : Les gradients de votre modèle pour le pixel
x
sont positifs entre 0.0 et 0.8 mais passent à 0.0 entre 0.8 et 1.0. Le pixelx
a clairement un impact significatif sur la poussée de votre modèle vers une probabilité prédite de 80 % sur la vraie classe. Est-il logique que l'importance du pixelx
soit faible ou discontinue ?à droite : l'intuition derrière IG est d'accumuler les gradients locaux du pixel
x
et d'attribuer son importance en tant que score pour combien il ajoute ou soustrait à la probabilité globale de classe de sortie de votre modèle. Vous pouvez décomposer et calculer IG en 3 parties :- interpoler de petits pas le long d'une ligne droite dans l'espace des caractéristiques entre 0 (une ligne de base ou un point de départ) et 1 (la valeur du pixel d'entrée)
- calculer les gradients à chaque étape entre les prédictions de votre modèle par rapport à chaque étape
- approximez l'intégrale entre votre ligne de base et votre entrée en accumulant (moyenne cumulée) ces gradients locaux.
Pour renforcer cette intuition, vous parcourrez ces 3 parties en appliquant IG à l'exemple d'image "Fireboat" ci-dessous.
Établir une ligne de base
Une ligne de base est une image d'entrée utilisée comme point de départ pour calculer l'importance des caractéristiques. Intuitivement, vous pouvez considérer le rôle explicatif de la ligne de base comme représentant l'impact de l'absence de chaque pixel sur la prédiction "Fireboat" pour contraster avec son impact de chaque pixel sur la prédiction "Fireboat" lorsqu'il est présent dans l'image d'entrée. Par conséquent, le choix de la ligne de base joue un rôle central dans l'interprétation et la visualisation de l'importance des caractéristiques des pixels. Pour une discussion supplémentaire sur la sélection de la ligne de base, consultez les ressources de la section « Étapes suivantes » au bas de ce didacticiel. Ici, vous allez utiliser une image noire dont les valeurs de pixel sont toutes nulles.
D'autres choix que vous pouvez expérimenter incluent une image entièrement blanche ou une image aléatoire, que vous pouvez créer avec 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()
Déballer les formules dans le code
La formule des dégradés intégrés est la suivante :
\(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}\)
où:
\(_{i}\) = fonctionnalité
\(x\) = entrée
\(x'\) = ligne de base
\(\alpha\) = constante d'interpolation pour perturber les caractéristiques par
En pratique, le calcul d'une intégrale définie n'est pas toujours numériquement possible et peut être coûteux en calcul, vous calculez donc l'approximation numérique suivante :
\(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}\)
où:
\(_{i}\) = caractéristique (pixel individuel)
\(x\) = entrée (tenseur d'image)
\(x'\) = ligne de base (tenseur d'image)
\(k\) = constante de perturbation de caractéristique mise à l'échelle
\(m\) = nombre d'étapes dans l'approximation de la somme de Riemann de l'intégrale
\((x_{i}-x'_{i})\) = un terme pour la différence par rapport à la ligne de base. Cela est nécessaire pour mettre à l'échelle les dégradés intégrés et les conserver en fonction de l'image d'origine. Le chemin de l'image de base à l'entrée est dans l'espace pixel. Puisqu'avec IG vous intégrez en ligne droite (transformation linéaire), cela finit par être à peu près équivalent au terme intégral de la dérivée de la fonction d'image interpolée par rapport à \(\alpha\) avec suffisamment de pas. L'intégrale additionne le gradient de chaque pixel multiplié par la variation du pixel le long du chemin. Il est plus simple d'implémenter cette intégration sous forme d'étapes uniformes d'une image à l'autre, en remplaçant \(x := (x' + \alpha(x-x'))\). Ainsi, le changement de variables donne \(dx = (x-x')d\alpha\). Le terme \((x-x')\) est constant et est factorisé hors de l'intégrale.
Interpoler des images
\(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}\)
Tout d'abord, vous allez générer une interpolation linéaire entre la ligne de base et l'image d'origine. Vous pouvez considérer les images interpolées comme de petites étapes dans l'espace des caractéristiques entre votre ligne de base et l'entrée, représentées par \(\alpha\) dans l'équation d'origine.
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
Utilisons la fonction ci-dessus pour générer des images interpolées le long d'un chemin linéaire à des intervalles alpha entre une image de base noire et l'exemple d'image "Fireboat".
interpolated_images = interpolate_images(
baseline=baseline,
image=img_name_tensors['Fireboat'],
alphas=alphas)
Visualisons les images interpolées. Remarque : une autre façon de penser à la constante \(\alpha\) est qu'elle augmente constamment l'intensité de chaque image interpolée.
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();
Calculer les gradients
Voyons maintenant comment calculer les gradients afin de mesurer la relation entre les modifications d'une caractéristique et les modifications des prédictions du modèle. Dans le cas des images, le gradient nous indique quels pixels ont le plus fort effet sur les probabilités de classe prédites par les modèles.
\(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}\)
où:
\(F()\) = la fonction de prédiction de votre modèle
\(\frac{\partial{F} }{\partial{x_i} }\) = gradient (vecteur de dérivées partielles \(\partial\)) de la fonction de prédiction de votre modèle F par rapport à chaque caractéristique \(x_i\)
TensorFlow vous facilite le calcul des gradients avec un 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)
Calculons les gradients pour chaque image le long du chemin d'interpolation par rapport à la sortie correcte. Rappelez-vous que votre modèle renvoie un tenseur de forme (1, 1001)
avec des Tensor
que vous convertissez en probabilités prédites pour chaque classe. Vous devez transmettre l'index de classe cible ImageNet correct à la fonction compute_gradients
pour votre image.
path_gradients = compute_gradients(
images=interpolated_images,
target_class_idx=555)
Notez la forme de sortie de (n_interpolated_images, img_height, img_width, RGB)
, qui nous donne le gradient pour chaque pixel de chaque image le long du chemin d'interpolation. Vous pouvez considérer ces gradients comme mesurant le changement dans les prédictions de votre modèle pour chaque petite étape dans l'espace des caractéristiques.
print(path_gradients.shape)
(51, 224, 224, 3)
Visualisation de la saturation des dégradés
Rappelez-vous que les gradients que vous venez de calculer ci-dessus décrivent les changements locaux de la probabilité prédite de votre modèle de "Fireboat" et peuvent saturer .
Ces concepts sont visualisés à l'aide des gradients que vous avez calculés ci-dessus dans les 2 tracés ci-dessous.
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]);
gauche : ce graphique montre comment la confiance de votre modèle dans la classe "Fireboat" varie selon les alphas. Remarquez comment les gradients, ou la pente de la ligne, s'aplatissent ou saturent en grande partie entre 0,6 et 1,0 avant de se stabiliser à la probabilité prédite finale "Fireboat" d'environ 40 %.
droite : Le graphique de droite montre plus directement les magnitudes moyennes des gradients sur alpha. Notez comment les valeurs s'approchent brusquement et plongent même brièvement en dessous de zéro. En fait, votre modèle "apprend" le plus des gradients à des valeurs inférieures d'alpha avant de saturer. Intuitivement, vous pouvez penser à cela car votre modèle a appris les pixels, par exemple les canons à eau, pour faire la prédiction correcte, en envoyant ces gradients de pixels à zéro, mais il est encore assez incertain et concentré sur de faux pixels de pont ou de jet d'eau lorsque les valeurs alpha se rapprochent de la image d'entrée d'origine.
Pour vous assurer que ces pixels de canon à eau importants sont reflétés comme importants pour la prédiction "Fireboat", vous continuerez ci-dessous pour apprendre comment accumuler ces gradients pour approximer avec précision l'impact de chaque pixel sur votre probabilité prédite "Fireboat".
Accumuler des gradients (approximation intégrale)
Il existe de nombreuses façons différentes de calculer l'approximation numérique d'une intégrale pour IG avec différents compromis en termes de précision et de convergence entre différentes fonctions. Une classe populaire de méthodes s'appelle les sommes de Riemann . Ici, vous utiliserez la règle trapézoïdale (vous pouvez trouver du code supplémentaire pour explorer différentes méthodes d'approximation à la fin de ce didacticiel).
$IntegratedGrads^{approx} {i}(x)::=(x {i}-x' {i})\times \overbrace{\sum {k=1}^{m} }^\text{Sum m gradients locaux} \text{gradients(images interpolées)} \times \overbrace{\frac{1}{m} }^\text{Divide by m steps}$
À partir de l'équation, vous pouvez voir que vous additionnez sur m
gradients et que vous divisez par m
étapes. Vous pouvez implémenter les deux opérations ensemble pour la partie 3 en tant que moyenne des gradients locaux de m
prédictions interpolées et images d'entrée .
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
La fonction integral_approximation
prend les gradients de la probabilité prédite de la classe cible par rapport aux images interpolées entre la ligne de base et l'image d'origine.
ig = integral_approximation(
gradients=path_gradients)
Vous pouvez confirmer que la moyenne sur les gradients de m
images interpolées renvoie un tenseur de gradients intégré avec la même forme que l'image "Giant Panda" originale.
print(ig.shape)
(224, 224, 3)
Mettre tous ensemble
Vous allez maintenant combiner les 3 parties générales précédentes dans une fonction IntegratedGradients
et utiliser un décorateur @tf.function pour le compiler dans un graphique TensorFlow appelable haute performance. Ceci est mis en œuvre en 5 étapes plus petites ci-dessous :
\(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.}\)
Générer des alphas \(\alpha\)
Générer des images interpolées = \((x' + \frac{k}{m}\times(x - x'))\)
Calculer les gradients entre les prédictions de sortie du modèle \(F\) par rapport aux caractéristiques d'entrée = \(\frac{\partial F(\text{interpolated path inputs})}{\partial x_{i} }\)
Approximation intégrale par gradients de moyenne = \(\sum_{k=1}^m \text{gradients} \times \frac{1}{m}\)
Mettre à l'échelle les dégradés intégrés par rapport à l'image d'origine = \((x_{i}-x'_{i}) \times \text{integrated gradients}\). La raison pour laquelle cette étape est nécessaire est de s'assurer que les valeurs d'attribution accumulées sur plusieurs images interpolées sont dans les mêmes unités et représentent fidèlement l'importance des pixels sur l'image d'origine.
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)
Encore une fois, vous pouvez vérifier que les attributions d'entités IG ont la même forme que l'image d'entrée "Fireboat".
print(ig_attributions.shape)
(224, 224, 3)
Le document suggère que le nombre d'étapes se situe entre 20 et 300 selon l'exemple (bien qu'en pratique, cela puisse être plus élevé dans les milliers pour se rapprocher avec précision de l'intégrale). Vous pouvez trouver du code supplémentaire pour vérifier le nombre approprié d'étapes dans les ressources "Étapes suivantes" à la fin de ce didacticiel.
Visualisez les attributions
Vous êtes prêt à visualiser les attributions et à les superposer sur l'image d'origine. Le code ci-dessous additionne les valeurs absolues des dégradés intégrés sur les canaux de couleur pour produire un masque d'attribution. Cette méthode de traçage capture l'impact relatif des pixels sur les prédictions du modèle.
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
En regardant les attributions sur l'image "Fireboat", vous pouvez voir que le modèle identifie les canons à eau et les becs comme contribuant à sa prédiction correcte.
_ = 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)
Sur l'image "Giant Panda", les attributions mettent en évidence la texture, le nez et la fourrure du visage du 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)
Utilisations et limites
Cas d'utilisation
- L'utilisation de techniques telles que les dégradés intégrés avant de déployer votre modèle peut vous aider à développer une intuition sur comment et pourquoi cela fonctionne. Les traits mis en évidence par cette technique correspondent-ils à votre intuition ? Si ce n'est pas le cas, cela peut indiquer un bogue dans votre modèle ou votre jeu de données, ou un surajustement.
Limites
Les dégradés intégrés fournissent des importances de fonctionnalités sur des exemples individuels, cependant, ils ne fournissent pas d'importances de fonctionnalités globales sur l'ensemble d'un ensemble de données.
Les dégradés intégrés fournissent des importances de fonctionnalités individuelles, mais n'expliquent pas les interactions et les combinaisons de fonctionnalités.
Prochaines étapes
Ce didacticiel a présenté une implémentation de base des dégradés intégrés. Dans une prochaine étape, vous pouvez utiliser ce cahier pour essayer vous-même cette technique avec différents modèles et images.
Pour les lecteurs intéressés, il existe une version plus longue de ce didacticiel (qui inclut du code pour différentes lignes de base, pour calculer des approximations intégrales et pour déterminer un nombre suffisant d'étapes) que vous pouvez trouver ici .
Pour approfondir votre compréhension, consultez l'article Axiomatic Attribution for Deep Networks and Github repository , qui contient une implémentation dans une version précédente de TensorFlow. Vous pouvez également explorer l'attribution des fonctionnalités et l'impact de différentes lignes de base sur distill.pub .
Intéressé par l'intégration de l'IG dans vos workflows d'apprentissage automatique de production pour l'importance des fonctionnalités, l'analyse des erreurs de modèle et la surveillance de l'asymétrie des données ? Découvrez le produit d' IA Explicable de Google Cloud qui prend en charge les attributions IG. Le groupe de recherche Google AI PAIR a également ouvert l' outil What-if qui peut être utilisé pour le débogage de modèles, y compris la visualisation des attributions de fonctionnalités IG.