Zobacz na TensorFlow.org | Uruchom w Google Colab | Zobacz na GitHub | Pobierz notatnik | Zobacz model piasty TF |
Ten samouczek pokazuje, jak zaimplementować Integrated Gradients (IG) , technikę Explainable AI przedstawioną w artykule Axiomatic Attribution for Deep Networks . IG ma na celu wyjaśnienie związku między przewidywaniami modelu w zakresie jego cech. Ma wiele przypadków użycia, w tym zrozumienie ważności funkcji, identyfikowanie skosu danych i debugowanie wydajności modelu.
IG stał się popularną techniką interpretacji ze względu na jej szerokie zastosowanie do dowolnego modelu różnicującego (np. obrazów, tekstu, danych strukturalnych), łatwość implementacji, uzasadnienia teoretyczne i wydajność obliczeniową w porównaniu z alternatywnymi podejściami, które pozwalają na skalowanie do dużych sieci i funkcji przestrzenie, takie jak obrazy.
W tym samouczku przejdziesz krok po kroku przez implementację IG, aby zrozumieć znaczenie funkcji pikseli w klasyfikatorze obrazu. Jako przykład rozważmy ten obraz łodzi strażackiej rozpylającej strumienie wody. Sklasyfikowałbyś ten obraz jako łódź strażacką i mógłbyś wyróżnić piksele tworzące łódź i armatki wodne jako ważne dla Twojej decyzji. Twój model również zaklasyfikuje ten obraz jako łódź strażacką w dalszej części tego samouczka; jednak czy podkreśla te same piksele jako ważne przy wyjaśnianiu swojej decyzji?
Na poniższych obrazach zatytułowanych „Ig Attribution Mask” i „Oryginalna + IG Mask Overlay” widać, że model zamiast tego podświetla (na fioletowo) piksele składające się na armatki wodne i strumienie wody jako ważniejsze niż sama łódź swoją decyzję. W jaki sposób Twój model uogólni się na nowe łodzie strażackie? A co z łodziami strażackimi bez dysz wodnych? Czytaj dalej, aby dowiedzieć się więcej o tym, jak działa IG i jak zastosować IG w swoich modelach, aby lepiej zrozumieć związek między ich przewidywaniami a podstawowymi funkcjami.
Ustawiać
import matplotlib.pylab as plt
import numpy as np
import tensorflow as tf
import tensorflow_hub as hub
Pobierz przeszkolony klasyfikator obrazów z TF-Hub
IG można zastosować do dowolnego modelu różniczkowego. Zgodnie z duchem oryginalnego artykułu, użyjesz wstępnie wytrenowanej wersji tego samego modelu, Inception V1, którą pobierzesz z 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 _________________________________________________________________
Ze strony modułu należy pamiętać o następujących kwestiach dotyczących Incepcji V1:
Dane wejściowe : Oczekiwany kształt wejściowy dla modelu to (None, 224, 224, 3)
. Jest to gęsty tensor 4D typu dtype float32 i shape (batch_size, height, width, RGB channels)
którego elementami są wartości kolorów RGB pikseli znormalizowanych do zakresu [0, 1]. Pierwszym elementem jest None
, aby wskazać, że model może przyjmować dowolną całkowitą wielkość partii.
Dane wyjściowe : tf.Tensor
logit w kształcie (batch_size, 1001)
. Każdy wiersz reprezentuje przewidywany wynik modelu dla każdej z 1001 klas z ImageNet. Dla najwyższego przewidywanego indeksu klasy modelu można użyć tf.argmax(predictions, axis=-1)
. Co więcej, można również przekonwertować wynik logitowy modelu na przewidywane prawdopodobieństwa we wszystkich klasach, używając tf.nn.softmax(predictions, axis=-1)
w celu ilościowego określenia niepewności modelu, a także zbadania podobnych przewidywanych klas w celu debugowania.
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')
Załaduj i wstępnie przetwórz obrazy za pomocą tf.image
Zilustrujesz IG za pomocą dwóch obrazów z Wikimedia Commons : Fireboat i 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()
Klasyfikuj obrazy
Zacznijmy od sklasyfikowania tych obrazów i wyświetlenia trzech najbardziej wiarygodnych prognoz. Poniżej znajduje się funkcja narzędziowa do pobierania k najpopularniejszych przewidywanych etykiet i prawdopodobieństw.
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%
Oblicz zintegrowane gradienty
Twój model, Incepcja V1, to wyuczona funkcja, która opisuje mapowanie między wejściową przestrzenią cech, wartościami pikseli obrazu i przestrzenią wyjściową zdefiniowaną przez wartości prawdopodobieństwa klasy ImageNet z zakresu od 0 do 1. gradienty, które informują, które piksele mają najbardziej strome lokalne względem przewidywania modelu w danym punkcie wzdłuż funkcji przewidywania modelu. Jednak gradienty opisują tylko lokalne zmiany w funkcji przewidywania modelu w odniesieniu do wartości pikseli i nie opisują w pełni funkcji przewidywania całego modelu. Gdy twój model w pełni „nauczy się” relacji między zakresem pojedynczego piksela a właściwą klasą ImageNet, gradient dla tego piksela będzie się nasycał , co oznacza, że będzie coraz mniejszy, a nawet spadnie do zera. Rozważ prostą funkcję modelu poniżej:
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();
po lewej : Gradienty Twojego modelu dla piksela
x
są dodatnie w zakresie od 0,0 do 0,8, ale osiągają wartość 0,0 w zakresie od 0,8 do 1,0. Pixelx
wyraźnie ma znaczący wpływ na przesuwanie modelu w kierunku 80% przewidywanego prawdopodobieństwa w prawdziwej klasie. Czy to ma sens, że znaczenie pikselax
jest małe lub nieciągłe?po prawej : intuicja stojąca za IG polega na akumulowaniu lokalnych gradientów piksela
x
i przypisywaniu ich znaczenia jako punktacji za to, ile dodaje lub odejmuje do ogólnego prawdopodobieństwa klasy wyjściowej modelu. Możesz podzielić i obliczyć IG na 3 części:- interpolować małe kroki wzdłuż linii prostej w przestrzeni funkcji między 0 (linia bazowa lub punkt początkowy) a 1 (wartość wejściowa piksela)
- oblicz gradienty na każdym kroku między przewidywaniami modelu w odniesieniu do każdego kroku
- przybliżyć całkę między linią bazową a danymi wejściowymi, gromadząc (skumulowaną średnią) te lokalne gradienty.
Aby wzmocnić tę intuicję, przejdziesz przez te 3 części, stosując IG do przykładowego obrazu „Fireboat” poniżej.
Ustal punkt odniesienia
Linia bazowa to obraz wejściowy używany jako punkt wyjścia do obliczania ważności funkcji. Intuicyjnie można myśleć o wyjaśniającej roli linii bazowej jako reprezentującej wpływ braku każdego piksela na prognozę „Fireboat” w przeciwieństwie do wpływu każdego piksela na prognozę „Fireboat”, gdy jest ona obecna na obrazie wejściowym. W rezultacie wybór linii bazowej odgrywa kluczową rolę w interpretacji i wizualizacji ważności funkcji pikseli. Aby uzyskać dodatkowe omówienie wyboru linii bazowej, zobacz zasoby w sekcji „Następne kroki” u dołu tego samouczka. Tutaj użyjesz czarnego obrazu, którego wartości pikseli wynoszą zero.
Inne opcje, z którymi możesz poeksperymentować, obejmują cały biały obraz lub losowy obraz, który możesz utworzyć za pomocą 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()
Rozpakuj formuły do kodu
Wzór na zintegrowane gradienty jest następujący:
\(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}\)
gdzie:
\(_{i}\) = element
\(x\) = wejście
\(x'\) = linia bazowa
\(\alpha\) = stała interpolacji do obiektów perturb przez
W praktyce obliczenie całki oznaczonej nie zawsze jest możliwe numerycznie i może być kosztowne obliczeniowo, dlatego obliczamy następujące przybliżenie liczbowe:
\(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}\)
gdzie:
\(_{i}\) = funkcja (pojedynczy piksel)
\(x\) = wejście (tensor obrazu)
\(x'\) = linia bazowa (tensor obrazu)
\(k\) = skalowana stała perturbacji elementu
\(m\) = liczba kroków w aproksymacji sumy Riemanna całki
\((x_{i}-x'_{i})\) = termin określający różnicę w stosunku do linii bazowej. Jest to konieczne do skalowania zintegrowanych gradientów i zachowania ich w zakresie oryginalnego obrazu. Ścieżka od obrazu bazowego do wejścia znajduje się w przestrzeni pikseli. Ponieważ z IG integrujesz w linii prostej (transformacja liniowa), kończy się to w przybliżeniu równaniem całki pochodnej interpolowanej funkcji obrazu w odniesieniu do \(\alpha\) z wystarczającą liczbą kroków. Całka sumuje gradient każdego piksela pomnożony przez zmianę piksela na ścieżce. Łatwiej jest zaimplementować tę integrację jako jednolite kroki od jednego obrazu do drugiego, zastępując \(x := (x' + \alpha(x-x'))\). Tak więc zmiana zmiennych daje \(dx = (x-x')d\alpha\). \((x-x')\) jest stały i jest uwzględniany w całce.
Interpoluj obrazy
\(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}\)
Najpierw wygenerujesz liniową interpolację między linią bazową a oryginalnym obrazem. Możesz myśleć o obrazach interpolowanych jako o małych krokach w przestrzeni funkcji między linią bazową a danymi wejściowymi, reprezentowanymi przez \(\alpha\) w oryginalnym równaniu.
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
Użyjmy powyższej funkcji, aby wygenerować obrazy interpolowane wzdłuż ścieżki liniowej w odstępach alfa między czarnym obrazem bazowym a przykładowym obrazem „Fireboat”.
interpolated_images = interpolate_images(
baseline=baseline,
image=img_name_tensors['Fireboat'],
alphas=alphas)
Zwizualizujmy obrazy interpolowane. Uwaga: innym sposobem myślenia o stałej \(\alpha\) jest to, że konsekwentnie zwiększa ona intensywność każdego interpolowanego obrazu.
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();
Oblicz gradienty
Przyjrzyjmy się teraz, jak obliczyć gradienty, aby zmierzyć związek między zmianami w funkcji a zmianami w przewidywaniach modelu. W przypadku obrazów gradient mówi nam, które piksele mają najsilniejszy wpływ na przewidywane przez modele prawdopodobieństwa klas.
\(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}\)
gdzie:
\(F()\) = funkcja przewidywania modelu
\(\frac{\partial{F} }{\partial{x_i} }\) = gradient (wektor pochodnych cząstkowych \(\partial\)) funkcji predykcji modelu F względem każdej cechy \(x_i\)
TensorFlow ułatwia obliczanie gradientów dzięki 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)
Obliczmy gradienty dla każdego obrazu wzdłuż ścieżki interpolacji w odniesieniu do prawidłowego wyniku. Przypomnij sobie, że Twój model zwraca tensor w kształcie (1, 1001)
z Tensor
, które konwertujesz na przewidywane prawdopodobieństwa dla każdej klasy. Musisz przekazać poprawny indeks klasy docelowej ImageNet do funkcji compute_gradients
dla twojego obrazu.
path_gradients = compute_gradients(
images=interpolated_images,
target_class_idx=555)
Zwróć uwagę na kształt wyjściowy (n_interpolated_images, img_height, img_width, RGB)
, który daje nam gradient dla każdego piksela każdego obrazu wzdłuż ścieżki interpolacji. Możesz myśleć o tych gradientach jako o mierzeniu zmian w przewidywaniach modelu dla każdego małego kroku w przestrzeni cech.
print(path_gradients.shape)
(51, 224, 224, 3)
Wizualizacja nasycenia gradientem
Przypomnij sobie, że gradienty, które właśnie obliczone powyżej opisują lokalne zmiany przewidywanego prawdopodobieństwa modelu „Fireboat” i mogą być nasycone .
Te koncepcje są wizualizowane za pomocą gradientów obliczonych powyżej na 2 wykresach poniżej.
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 : Ten wykres pokazuje, jak zaufanie Twojego modelu do klasy „Fireboat” różni się w poszczególnych wersjach alfa. Zwróć uwagę, jak nachylenie lub nachylenie linii w dużej mierze spłaszcza się lub nasyca w zakresie od 0,6 do 1,0, zanim ustali się na końcowym przewidywanym prawdopodobieństwie „łodzi strażackiej” wynoszącym około 40%.
right : Prawy wykres pokazuje bardziej bezpośrednio średnie wartości gradientów względem wartości alfa. Zwróć uwagę, jak wartości gwałtownie się zbliżają, a nawet na chwilę spadają poniżej zera. W rzeczywistości twój model „uczy się” najwięcej z gradientów przy niższych wartościach alfa przed nasyceniem. Intuicyjnie możesz myśleć o tym, jak twój model nauczył się pikseli, np. armatek wodnych, aby dokonać prawidłowej prognozy, wysyłając gradienty tych pikseli do zera, ale nadal jest dość niepewny i koncentruje się na fałszywych pikselach mostu lub strumienia wody, gdy wartości alfa zbliżają się do oryginalny obraz wejściowy.
Aby upewnić się, że te ważne piksele armatki wodnej są odzwierciedlane jako ważne dla prognozy „Róża strażacka”, będziesz kontynuować poniżej, aby dowiedzieć się, jak akumulować te gradienty, aby dokładnie przybliżyć, w jaki sposób każdy piksel wpływa na przewidywane prawdopodobieństwo „Ratnicy ognistej”.
Akumuluj gradienty (przybliżenie całkowe)
Istnieje wiele różnych sposobów obliczania liczbowego przybliżenia całki dla IG z różnymi kompromisami w dokładności i zbieżności w różnych funkcjach. Popularna klasa metod nazywa się sumami Riemanna . Tutaj użyjesz reguły trapezowej (na końcu tego samouczka znajdziesz dodatkowy kod do odkrywania różnych metod aproksymacji).
$IntegratedGrads^{około} {i}(x)::=(x {i}-x' {i})\times \overbrace{\sum {k=1}^{m} }^\text{Sum m gradienty lokalne} \text{gradienty(obrazy interpolowane)} \times \overbrace{\frac{1}{m} }^\text{Podziel przez m kroków}$
Z równania widać, że sumujesz m
gradientów i dzielisz je przez m
kroków. Możesz zaimplementować te dwie operacje razem dla części 3 jako średnią lokalnych gradientów m
interpolowanych predykcji i obrazów wejściowych .
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
Funkcja integral_approximation
pobiera gradienty przewidywanego prawdopodobieństwa klasy docelowej w odniesieniu do interpolowanych obrazów między linią bazową a oryginalnym obrazem.
ig = integral_approximation(
gradients=path_gradients)
Możesz potwierdzić, że uśrednianie gradientów m
interpolowanych obrazów zwraca zintegrowany tensor gradientów o tym samym kształcie, co oryginalny obraz „Wielkiej Pandy”.
print(ig.shape)
(224, 224, 3)
Kładąc wszystko razem
Teraz połączysz 3 poprzednie ogólne części razem w funkcję IntegratedGradients
i użyjesz dekoratora @tf.function , aby skompilować go w wysoce wydajny, wywoływany wykres TensorFlow. Jest to realizowane jako 5 mniejszych kroków poniżej:
\(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.}\)
Wygeneruj alfa \(\alpha\)
Generuj obrazy interpolowane = \((x' + \frac{k}{m}\times(x - x'))\)
Oblicz gradienty między prognozami wyjściowymi modelu \(F\) w odniesieniu do cech wejściowych = \(\frac{\partial F(\text{interpolated path inputs})}{\partial x_{i} }\)
Aproksymacja całkowa poprzez uśrednianie gradientów = \(\sum_{k=1}^m \text{gradients} \times \frac{1}{m}\)
Skaluj zintegrowane gradienty względem oryginalnego obrazu = \((x_{i}-x'_{i}) \times \text{integrated gradients}\). Powodem, dla którego ten krok jest konieczny, jest upewnienie się, że wartości atrybucji skumulowane w wielu interpolowanych obrazach są w tych samych jednostkach i wiernie odzwierciedlają znaczenie pikseli na oryginalnym obrazie.
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)
Ponownie możesz sprawdzić, czy atrybuty funkcji IG mają taki sam kształt, jak obraz wejściowy „Fireboat”.
print(ig_attributions.shape)
(224, 224, 3)
Artykuł sugeruje liczbę kroków w zakresie od 20 do 300 w zależności od przykładu (chociaż w praktyce może to być wyższa w tysiącach, aby dokładnie przybliżyć całkę). Dodatkowy kod do sprawdzenia odpowiedniej liczby kroków można znaleźć w zasobach „Następne kroki” na końcu tego samouczka.
Wizualizuj atrybucje
Jesteś gotowy, aby zwizualizować atrybucje i nałożyć je na oryginalny obraz. Poniższy kod sumuje wartości bezwzględne zintegrowanych gradientów w kanałach kolorów, tworząc maskę atrybucji. Ta metoda kreślenia przechwytuje względny wpływ pikseli na prognozy modelu.
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
Patrząc na atrybucje na obrazie „Fireboat”, można zauważyć, że model identyfikuje armatki wodne i dziobki jako przyczyniające się do jego prawidłowego przewidywania.
_ = 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 obrazie „Giant Panda” atrybucje podkreślają fakturę, nos i futro twarzy Pandy.
_ = 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)
Zastosowania i ograniczenia
Przypadków użycia
- Zastosowanie technik, takich jak zintegrowane gradienty, przed wdrożeniem modelu może pomóc w opracowaniu intuicji dotyczącej tego, jak i dlaczego działa. Czy cechy wyróżnione tą techniką pasują do Twojej intuicji? Jeśli nie, może to wskazywać na błąd w Twoim modelu lub zbiorze danych albo na nadmierne dopasowanie.
Ograniczenia
Gradienty zintegrowane zapewniają ważność funkcji na poszczególnych przykładach, jednak nie zapewniają globalnego znaczenia funkcji w całym zestawie danych.
Gradienty zintegrowane zapewniają indywidualne znaczenie funkcji, ale nie wyjaśniają interakcji i kombinacji funkcji.
Następne kroki
W tym samouczku przedstawiono podstawową implementację zintegrowanych gradientów. W następnym kroku możesz użyć tego notatnika, aby samodzielnie wypróbować tę technikę z różnymi modelami i obrazami.
Dla zainteresowanych czytelników dostępna jest dłuższa wersja tego samouczka (zawierająca kod dla różnych linii bazowych, służący do obliczania przybliżeń całkowych i określania wystarczającej liczby kroków), którą można znaleźć tutaj .
Aby pogłębić swoją wiedzę, zapoznaj się z artykułem Axiomatic Attribution for Deep Networks i repozytorium Github , który zawiera implementację w poprzedniej wersji TensorFlow. Możesz także zapoznać się z przypisywaniem funkcji i wpływem różnych poziomów bazowych w witrynie distill.pub .
Interesuje Cię włączenie IG do swoich produkcyjnych przepływów pracy uczenia maszynowego w celu uzyskania ważności funkcji, analizy błędów modelu i monitorowania pochylenia danych? Sprawdź produkt Google Cloud Explainable AI , który obsługuje atrybucje IG. Grupa badawcza Google AI PAIR udostępniła również narzędzie typu „What-if”, które może być wykorzystywane do debugowania modeli, w tym wizualizacji atrybucji funkcji IG.