Ta strona została przetłumaczona przez Cloud Translation API.
Switch to English

Zintegrowane gradienty

Zobacz na TensorFlow.org Uruchom w Google Colab Wyświetl źródło na GitHub Pobierz notatnik

W tym samouczku pokazano, jak zaimplementować zintegrowane gradienty (IG) , technikę możliwej do wyjaśnienia sztucznej inteligencji wprowadzoną w artykule Axiomatic Attribution for Deep Networks . IG ma na celu wyjaśnienie związku między przewidywaniami modelu pod względem jego cech. Ma wiele przypadków użycia, w tym zrozumienie znaczenia funkcji, identyfikowanie pochylenia danych i debugowanie wydajności modelu.

IG stała się popularną techniką interpretacji ze względu na jej szerokie zastosowanie do dowolnego zróżnicowanego modelu (np. Obrazy, tekst, dane strukturalne), łatwość implementacji, uzasadnienia teoretyczne i wydajność obliczeniową w stosunku do podejść alternatywnych, które pozwalają na skalowanie do dużych sieci i funkcji przestrzenie, takie jak obrazy.

W tym samouczku przeprowadzisz krok po kroku implementację IG, aby zrozumieć znaczenie funkcji pikseli w klasyfikatorze obrazu. Jako przykład rozważmy ten obraz łodzi strażackiej rozpylającej strumienie wody. Możesz zaklasyfikować ten obraz jako łódź strażacką i możesz wyróżnić piksele tworzące łódź i armatki wodne jako ważne dla Twojej decyzji. Twój model również sklasyfikuje ten obraz jako łódź strażacką w dalszej części tego samouczka; Czy jednak wskazuje te same piksele jako ważne, wyjaśniając swoją decyzję?

Na poniższych obrazach zatytułowanych „Maska atrybucji IG” i „Oryginalna + nakładka maski IG” widać, że model zamiast tego wyróżnia (na fioletowo) piksele armatek wodnych i dysz wodnych łodzi jako ważniejsze niż sama łódź swoją decyzję. Jak 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 do modeli, aby lepiej zrozumieć związek między ich przewidywaniami a podstawowymi funkcjami.

Obraz wyjściowy 1

Ustawiać

import matplotlib.pylab as plt
import numpy as np
import tensorflow as tf
import tensorflow_hub as hub

Pobierz wstępnie wyszkolony klasyfikator obrazu z TF-Hub

IG można zastosować do dowolnego zróżnicowanego modelu. Zgodnie z duchem oryginalnego artykułu, użyjesz wstępnie wyszkolonej 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 Inception V1:

Dane wejściowe : Oczekiwany kształt danych wejściowych dla modelu to (None, 224, 224, 3) . Jest to gęsty tensor 4D o dtype float32 i kształcie (batch_size, height, width, RGB channels) którego elementami są wartości kolorów RGB pikseli znormalizowane do zakresu [0, 1]. Pierwszym elementem jest None aby wskazać, że model może przyjąć dowolny całkowity rozmiar partii.

Wyjścia : tf.Tensor logitów w kształcie (batch_size, 1001) . Każdy wiersz przedstawia przewidywany wynik modelu dla każdej z 1001 klas z ImageNet. Jako indeks najwyższej przewidywanej klasy modelu możesz użyć tf.argmax(predictions, axis=-1) . Ponadto można również przekonwertować wynik logit modelu na przewidywane prawdopodobieństwa we wszystkich klasach przy użyciu tf.nn.softmax(predictions, axis=-1) aby określić ilościowo niepewność modelu, a także zbadać podobne przewidywane klasy do 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')

Ładuj i wstępnie przetwarzaj 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.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

Klasyfikuj obrazy

Zacznijmy od sklasyfikowania tych obrazów i wyświetlenia 3 najbardziej wiarygodnych prognoz. Następujące jest funkcją narzędziową do pobierania najwyższych k 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%}')

png

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

png

giant panda: 89.4%
teddy: 0.3%
gibbon: 0.3%

Oblicz zintegrowane gradienty

Twój model, Inception V1, jest wyuczoną funkcją, która opisuje mapowanie między przestrzenią cech wejściowych, wartościami pikseli obrazu i przestrzenią wyjściową zdefiniowaną przez wartości prawdopodobieństwa klasy ImageNet z zakresu od 0 do 1. Wczesne metody interpretacji dla sieci neuronowych przypisane oceny ważności funkcji przy użyciu gradienty, które informują, które piksele są najbardziej strome w stosunku do przewidywań modelu w danym punkcie na podstawie funkcji przewidywania modelu. Jednak gradienty opisują tylko lokalne zmiany funkcji przewidywania modelu w odniesieniu do wartości pikseli i nie opisują w pełni całej funkcji przewidywania modelu. Gdy model w pełni „uczy się” związku 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 zejdzie 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();

png

  • po lewej : Gradienty Twojego modelu dla piksela x są dodatnie między 0,0 a 0,8, ale idą do 0,0 między 0,8 a 1,0. Piksel x wyraźnie ma znaczący wpływ na popychanie modelu w kierunku 80% przewidywanego prawdopodobieństwa w prawdziwej klasie. Czy ma sens, że znaczenie piksela x jest małe lub nieciągłe?

  • po prawej : Intuicja stojąca za IG polega na gromadzeniu 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 twojego modelu. Możesz rozbić i obliczyć IG w 3 częściach:

    1. interpoluj małe kroki wzdłuż linii prostej w przestrzeni cech od 0 (linia bazowa lub punkt początkowy) do 1 (wartość piksela wejściowego)
    2. oblicz gradienty na każdym kroku między przewidywaniami modelu w odniesieniu do każdego kroku
    3. przybliż całkę między swoją linią bazową a danymi wejściowymi, sumując (skumulowaną średnią) te lokalne gradienty.

Aby wzmocnić tę intuicję, przejdziesz przez te 3 części, stosując IG na przykładowym obrazku „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 o wpływie braku każdego piksela na prognozę „Fireboat”, w przeciwieństwie do wpływu każdego piksela na prognozę „Fireboat”, jeśli jest ona obecna w obrazie wejściowym. W rezultacie wybór linii bazowej odgrywa kluczową rolę w interpretacji i wizualizacji znaczenia cech pikseli. Dodatkowe omówienie wyboru planu bazowego można znaleźć w zasobach w sekcji „Następne kroki” u dołu tego samouczka. Tutaj użyjesz czarnego obrazu, którego wszystkie piksele 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()

png

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 '))} {\ częściowe x_i} {d \ alpha} $

gdzie:

$ _ {i} $ = funkcja
$ x $ = wejście
$ x '$ = linia bazowa
$ \ alpha $ = stała interpolacji do perturbe funkcji według

W praktyce obliczenie całki oznaczonej nie zawsze jest możliwe numerycznie i może być kosztowne obliczeniowo, dlatego oblicza się następujące przybliżenie liczbowe:

$ IntegratedGrads ^ {około} _ {i} (x) :: = (x_ {i} -x '_ {i}) \ times \ sum_ {k = 1} ^ {m} \ frac {\ częściowe F (x '+ \ frac {k} {m} \ times (x - x'))} {\ częściowe x_ {i}} \ times \ frac {1} {m} $

gdzie:

$ _ {i} $ = funkcja (pojedynczy piksel)
$ x $ = input (tensor obrazu)
$ x '$ = linia bazowa (tensor obrazu)
$ k $ = skalowana stała zaburzenia funkcji
$ m $ = liczba kroków w przybliżeniu sumy Riemanna całki
$ (x_ {i} -x '_ {i}) $ = termin określający różnicę w stosunku do linii bazowej. Jest to konieczne, aby przeskalować zintegrowane gradienty i zachować je zgodnie z oryginalnym obrazem. Ścieżka od obrazu bazowego do wejścia jest podana w pikselach. Ponieważ w przypadku IG całkujesz w linii prostej (transformacja liniowa), kończy się to w przybliżeniu jako równoważnik członu całkowego pochodnej funkcji obrazu interpolowanego w odniesieniu do $ \ alpha $ z wystarczającą liczbą kroków. Całka sumuje gradient każdego piksela razy zmianę piksela na ścieżce. Łatwiej jest zaimplementować tę integrację jako jednolite kroki od jednego obrazu do drugiego, zastępując $ x: = (x '+ \ alpha (xx')) $. Zatem zmiana zmiennych daje $ dx = (xx ') d \ alpha $. Wyrażenie $ (xx ') $ jest stałe i jest wyodrębniane z całki.

Interpoluj obrazy

$ IntegratedGrads ^ {około} _ {i} (x) :: = (x_ {i} -x '_ {i}) \ times \ sum_ {k = 1} ^ {m} \ frac {\ częściowe F (\ overbrace {x '+ \ frac {k} {m} \ times (x - x')} ^ \ text {interpoluj m obrazków w k odstępach})} {\ częściowe x_ {i}} \ times \ frac {1} {m.} $

Najpierw wygenerujesz liniową interpolację między linią bazową a oryginalnym obrazem. Możesz myśleć o interpolowanych obrazach jako małych krokach w przestrzeni między linią bazową a danymi wejściowymi, reprezentowanymi przez $ \ alpha $ w pierwotnym 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ć interpolowane obrazy 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)

Wizualizujmy interpolowane obrazy. Uwaga: inny sposób myślenia o stałej $ \ alpha $ polega na tym, ż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();

png

Oblicz gradienty

Przyjrzyjmy się teraz, jak obliczyć gradienty, aby zmierzyć związek między zmianami w elemencie a zmianami przewidywań modelu. W przypadku obrazów gradient mówi nam, które piksele mają najsilniejszy wpływ na modele przewidywane prawdopodobieństwa klas.

$ IntegratedGrads ^ {około} _ {i} (x) :: = (x_ {i} -x '_ {i}) \ times \ sum_ {k = 1} ^ {m} \ frac {\ overbrace {\ częściowe F (\ text {interpolowane obrazy})} ^ \ text {oblicz gradienty}} {\ częściowe x_ {i}} \ times \ frac {1} {m} $

gdzie:
$ F () $ = funkcja przewidywania modelu
$ \ frac {\ częściowy {F}} {\ częściowy {x_i}} $ = gradient (wektor pochodnych cząstkowych $ \ częściowy $) 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 wyjścia. Przypomnij sobie, że model zwraca Tensor kształcie (1, 1001) z logitami, 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 pomiarze zmiany przewidywań modelu dla każdego małego kroku w przestrzeni cech.

4046CA759a
(51, 224, 224, 3)

Wizualizacja nasycenia gradientu

Przypomnij sobie, że gradienty, które właśnie obliczyłeś powyżej, opisują lokalne zmiany przewidywanego przez model prawdopodobieństwa „łodzi strażackiej” i mogą się nasycić .

Pojęcia te są wizualizowane przy użyciu 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]);

png

  • Po lewej : Ten wykres pokazuje, jak zaufanie twojego modelu do klasy „Fireboat” różni się w zależności od alf. Zwróć uwagę, jak gradienty lub nachylenie linii w znacznym stopniu spłaszczają się lub nasycają między 0,6 a 1,0, zanim osiądą na końcowym przewidywanym prawdopodobieństwie „Fireboat” wynoszącym około 40%.

  • po prawej : Wykres po prawej pokazuje bardziej bezpośrednio średnie gradienty wielkości w obszarze alfa. Zwróć uwagę, jak wartości gwałtownie zbliżają się, a nawet na krótko 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 o tym pomyśleć, ponieważ twój model nauczył się pikseli, np. Armatek wodnych, aby dokonać prawidłowej prognozy, wysyłając gradienty pikseli do zera, ale nadal jest dość niepewny i koncentruje się na fałszywych pikselach mostka lub strumienia wody, gdy wartości alfa zbliżają się do oryginalny obraz wejściowy.

Aby upewnić się, że te ważne piksele armatek wodnych są odzwierciedlone jako ważne dla prognozy „Łodzi strażackiej”, poniżej dowiesz się, jak gromadzić te gradienty, aby dokładnie oszacować, jak każdy piksel wpływa na przewidywane prawdopodobieństwo „Łodzi strażackiej”.

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 trapezu (możesz znaleźć dodatkowy kod do eksploracji różnych metod aproksymacji na końcu tego samouczka).

$ IntegratedGrads ^ {około} _ {i} (x) :: = (x_ {i} -x '_ {i}) \ times \ overbrace {\ sum_ {k = 1} ^ {m}} ^ \ text { Suma m lokalnych gradientów} \ text {gradienty (obrazy interpolowane)} \ times \ overbrace {\ frac {1} {m}} ^ \ text {Podziel przez m kroków} $

Z równania widać, że sumujesz ponad m gradientów i dzielisz przez m kroków. Możesz zaimplementować te dwie operacje razem dla części 3 jako średnią lokalnych gradientów m interpolowanych prognoz 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 przybliżenie przyjmuje 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ć uśrednienie w zakresie gradientów interpolowanych obrazów m zwracając zintegrowany tensor gradientów o takim samym kształcie, jak oryginalny obraz „Giant Panda”.

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

Kładąc wszystko razem

Teraz połączysz razem 3 poprzednie części ogólne w funkcję IntegratedGradients i wykorzystasz dekorator @ tf.function do skompilowania go w wysokowydajny wywoływalny wykres TensorFlow. Jest to realizowane w 5 mniejszych krokach poniżej:

$ IntegratedGrads ^ {około} _ {i} (x) :: = \ overbrace {(x_ {i} -x '_ {i})} ^ \ text {5.} \ Times \ overbrace {\ sum_ {k = 1} ^ {m}} ^ \ text {4.} \ Frac {\ części \ overbrace {F (\ overbrace {x '+ \ overbrace {\ frac {k} {m}} ^ \ text {1.} \ times (x - x '))} ^ \ text {2.}} ^ \ text {3.}} {\ częściowy x_ {i}} \ times \ overbrace {\ frac {1} {m}} ^ \ text {4.} $

  1. Wygeneruj alfy $ \ alpha $

  2. Generuj obrazy interpolowane = $ (x '+ \ frac {k} {m} \ times (x - x')) $

  3. Oblicz gradienty między prognozami wyjściowymi modelu $ F $ w odniesieniu do cech wejściowych = $ \ frac {\ częściowe F (\ text {interpolowane dane wejściowe ścieżki})} {\ częściowe x_ {i}} $

  4. Całkowe przybliżenie poprzez uśrednione gradienty = $ \ sum_ {k = 1} ^ m \ text {gradients} \ times \ frac {1} {m} $

  5. Skaluj zintegrowane gradienty w odniesieniu do oryginalnego obrazu = $ (x_ {i} -x '_ {i}) \ times \ text {zintegrowane gradienty} $. Powodem, dla którego ten krok jest konieczny, jest upewnienie się, że wartości atrybucji zgromadzone w wielu interpolowanych obrazach są w tych samych jednostkach i wiernie odzwierciedlają znaczenie pikseli na oryginalnym obrazie.

@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)

Ponownie możesz sprawdzić, czy atrybucje funkcji IG mają taki sam kształt jak wejściowy obraz „Fireboat”.

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

Artykuł sugeruje liczbę kroków mieszczącą się w zakresie od 20 do 300 w zależności od przykładu (chociaż w praktyce może to być większa o 1000 sekund, aby dokładnie przybliżyć całkę). Dodatkowy kod umożliwiający sprawdzenie odpowiedniej liczby kroków można znaleźć w zasobach „Następne kroki” na końcu tego samouczka.

Wizualizuj atrybucje

Jesteś gotowy, aby wizualizować atrybucje i nakładać je na oryginalny obraz. Poniższy kod sumuje wartości bezwzględne zintegrowanych gradientów w kanałach kolorów, aby utworzyć maskę atrybucji. Ta metoda kreślenia pozwala uchwycić względny wpływ pikseli na przewidywania 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 zobaczyć, że model identyfikuje armatki wodne i dziobki jako przyczyniające się do jego prawidłowej prognozy.

_ = 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 zdjęciu „Giant Panda” atrybuty podkreślają teksturę, nos i futro 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)

png

Zastosowania i ograniczenia

Przypadków użycia

  • Zastosowanie technik takich jak zintegrowane gradienty przed wdrożeniem modelu może pomóc w rozwinięciu intuicji dotyczącej tego, jak i dlaczego to działa. Czy cechy wyróżnione tą techniką pasują do Twojej intuicji? Jeśli nie, może to wskazywać na błąd w modelu lub zestawie danych lub nadmierne dopasowanie.

Ograniczenia

  • Zintegrowane gradienty zapewniają znaczenie funkcji w poszczególnych przykładach, jednak nie zapewniają globalnych importów funkcji w całym zestawie danych.

  • Zintegrowane gradienty zapewniają znaczenie poszczególnych funkcji, ale nie wyjaśnia 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 (która zawiera kod dla różnych linii bazowych, do obliczania całkowitych przybliżeń i do określenia wystarczającej liczby kroków), którą można znaleźć tutaj .

Aby pogłębić zrozumienie, zapoznaj się z artykułem Axiomatic Attribution for Deep Networks and Github repozytorium , które zawiera implementację w poprzedniej wersji TensorFlow. Możesz także zbadać przypisywanie cech i wpływ różnych linii bazowych na distill.pub .

Interesuje Cię włączenie IG do przepływów pracy uczenia maszynowego produkcji w celu importowania funkcji, analizy błędów modelu i monitorowania odchylenia danych? Zapoznaj się z możliwym do wyjaśnienia produktem sztucznej inteligencji Google Cloud, który obsługuje atrybucje IG. Grupa badawcza Google AI PAIR również udostępniła narzędzie typu „ co, jeśli”, które może być używane do debugowania modeli, w tym do wizualizacji atrybucji funkcji IG.