Dans ce didacticiel, je vais montrer comment utiliser l'ascension de gradient pour résoudre la mauvaise classification des entrées.
Découvrez comment utiliser l'ascension de gradient pour modifier une classification d'entrée
Le réseau neuronal est une boîte noire. Comprendre leurs décisions demande de la créativité, mais elles ne sont pas si opaques.
Dans ce tutoriel, je vais vous montrer comment utiliser la rétropropagation pour modifier l'entrée afin qu'elle soit classée comme vous le souhaitez.
Boîte noire humaine
Prenons d'abord les humains comme exemple. Si je vous montre l'entrée suivante:
Il y a de fortes chances que vous ne sachiez pas si c'est 5 ou 6. En fait, je crois pouvoir vous convaincre que cela peut aussi être 8.
Maintenant, si vous demandez à quelqu'un ce qu'il doit faire pour transformer quelque chose en 5, vous pouvez faire quelque chose comme ceci visuellement:
Si je veux que vous changiez cela en 8, vous pouvez le faire:
Or, il n'est pas facile d'expliquer la réponse à cette question avec quelques affirmations ou en regardant quelques coefficients. Et pour certains types d'entrées (image, son, vidéo, etc.), l'interprétabilité deviendra sans doute plus difficile, mais pas impossible.
Comment gérer le réseau neuronal
Comment un réseau de neurones répond-il à la même question que j'ai posée ci-dessus? Pour répondre à cette question, nous pouvons utiliser l'ascension de gradient.
C'est ainsi que le réseau de neurones pense que nous devons modifier l'entrée pour la rapprocher des autres classifications.
Cela a produit deux résultats intéressants. Tout d'abord, la zone noire est l'objet réseau dont nous avons besoin pour supprimer la densité de pixels. Deuxièmement, la zone jaune est l'endroit où il pense que nous devons augmenter la densité de pixels.
Nous pouvons faire un pas dans cette direction du dégradé et ajouter le dégradé à l'image d'origine. Bien sûr, nous pouvons répéter ce processus encore et encore, en changeant finalement l'entrée dans la prédiction que nous voulons.
Vous pouvez voir que la tache noire dans le coin inférieur gauche de l'image est très similaire aux pensées humaines.
Que diriez-vous de faire en sorte que l'entrée ressemble plus à 8? C'est ainsi que le réseau pense que vous devez changer l'entrée.
Il est à noter qu'il y a une masse noire dans le coin inférieur gauche et une masse brillante au milieu. Si nous ajoutons ceci à l'entrée, nous obtenons le résultat suivant:
Dans ce cas, je ne crois pas particulièrement que nous ayons changé ce 5 en 8. Cependant, nous avons réduit la probabilité de 5. Il sera certainement plus facile d'utiliser l'image de droite au lieu de l'image de gauche pour vous convaincre que l'argument est 8.
pente
Dans l'analyse de régression, nous utilisons des coefficients pour comprendre ce que nous avons appris. Dans la forêt aléatoire, nous pouvons observer les nœuds de décision.
Dans les réseaux de neurones, cela se résume à la façon dont nous utilisons les gradients de manière créative. Pour classer ce nombre, nous avons généré une distribution basée sur des prédictions possibles.
C'est ce que nous appelons la propagation vers l'avant
Au fur et à mesure que nous avançons, nous calculons la distribution de probabilité de la sortie
Le code ressemble à ceci:
Supposons maintenant que nous voulions tromper le réseau en lui faisant prédire que la valeur de l'entrée x est "5". Le moyen d'y parvenir est de lui donner une image (x), de calculer la prédiction pour l'image, puis de maximiser la probabilité de prédire l'étiquette "5" .
Pour cela, nous pouvons utiliser la montée de gradient pour calculer le gradient prédit au 6ème indice (ie label = 5) § par rapport à l'entrée x.
Pour ce faire dans le code, nous entrons x comme paramètre du réseau de neurones, sélectionnons la 6ème prédiction (car nous avons des étiquettes: 0,1,2,3,4,5, ...), le 6ème index Signifie l'étiquette «5».
Visuellement, cela ressemble à:
code montrer comme ci-dessous:
Lorsque nous appelons .backward (), ce qui s'est passé peut être visualisé par l'animation précédente.
Maintenant que nous avons calculé les dégradés, nous pouvons les visualiser et les tracer:
Puisque le réseau n'a pas été formé, le gradient ci-dessus ressemble à du bruit aléatoire ... Mais une fois que nous formons le réseau, les informations de gradient seront plus riches:
Automatisation grâce aux rappels
C'est un outil très utile pour clarifier ce qui se passe pendant la formation de votre réseau. Dans ce cas, nous voulons automatiser ce processus afin qu'il se produise automatiquement pendant la formation.
Pour cela, nous utiliserons PyTorch Lightning pour implémenter notre réseau de neurones:
import torch
import torch.nn.functional as F
import pytorch_lightning as pl
class LitClassifier(pl.LightningModule):
def __init__(self):
super().__init__()
self.l1 = torch.nn.Linear(28 * 28, 10)
def forward(self, x):
return torch.relu(self.l1(x.view(x.size(0), -1)))
def training_step(self, batch, batch_idx):
x, y = batch
y_hat = self(x)
loss = F.cross_entropy(y_hat, y)
result = pl.TrainResult(loss)
# enable the auto confused logit callback
self.last_batch = batch
self.last_logits = y_hat.detach()
result.log('train_loss', loss, on_epoch=True)
return result
def validation_step(self, batch, batch_idx):
x, y = batch
y_hat = self(x)
loss = F.cross_entropy(y_hat, y)
result = pl.EvalResult(checkpoint_on=loss)
result.log('val_loss', loss)
return result
def configure_optimizers(self):
return torch.optim.Adam(self.parameters(), lr=0.005)
Le code complexe qui dessine automatiquement le contenu décrit ici peut être résumé en tant que rappel dans Lightning. Le rappel est un petit programme, vous pouvez l'appeler dans différentes parties de la formation.
Dans cet exemple, lors du traitement du lot d'apprentissage, nous voulons générer ces images au cas où certaines entrées seraient confondues. .
import torch
from pytorch_lightning import Callback
from torch import nn
class ConfusedLogitCallback(Callback):
def __init__(
self,
top_k,
projection_factor=3,
min_logit_value=5.0,
logging_batch_interval=20,
max_logit_difference=0.1
):
super().__init__()
self.top_k = top_k
self.projection_factor = projection_factor
self.max_logit_difference = max_logit_difference
self.logging_batch_interval = logging_batch_interval
self.min_logit_value = min_logit_value
def on_train_batch_end(self, trainer, pl_module, batch, batch_idx, dataloader_idx):
# show images only every 20 batches
if (trainer.batch_idx + 1) % self.logging_batch_interval != 0:
return
# pick the last batch and logits
x, y = batch
try:
logits = pl_module.last_logits
except AttributeError as e:
m = """please track the last_logits in the training_step like so:
def training_step(...):
self.last_logits = your_logits
"""
raise AttributeError(m)
# only check when it has opinions (ie: the logit > 5)
if logits.max() > self.min_logit_value:
# pick the top two confused probs
(values, idxs) = torch.topk(logits, k=2, dim=1)
# care about only the ones that are at most eps close to each other
eps = self.max_logit_difference
mask = (values[:, 0] - values[:, 1]).abs() < eps
if mask.sum() > 0:
# pull out the ones we care about
confusing_x = x[mask, ...]
confusing_y = y[mask]
mask_idxs = idxs[mask]
pl_module.eval()
self._plot(confusing_x, confusing_y, trainer, pl_module, mask_idxs)
pl_module.train()
def _plot(self, confusing_x, confusing_y, trainer, model, mask_idxs):
from matplotlib import pyplot as plt
confusing_x = confusing_x[:self.top_k]
confusing_y = confusing_y[:self.top_k]
x_param_a = nn.Parameter(confusing_x)
x_param_b = nn.Parameter(confusing_x)
batch_size, c, w, h = confusing_x.size()
for logit_i, x_param in enumerate((x_param_a, x_param_b)):
x_param = x_param.to(model.device)
logits = model(x_param.view(batch_size, -1))
logits[:, mask_idxs[:, logit_i]].sum().backward()
# reshape grads
grad_a = x_param_a.grad.view(batch_size, w, h)
grad_b = x_param_b.grad.view(batch_size, w, h)
for img_i in range(len(confusing_x)):
x = confusing_x[img_i].squeeze(0).cpu()
y = confusing_y[img_i].cpu()
ga = grad_a[img_i].cpu()
gb = grad_b[img_i].cpu()
mask_idx = mask_idxs[img_i].cpu()
fig, axarr = plt.subplots(nrows=2, ncols=3, figsize=(15, 10))
self.__draw_sample(fig, axarr, 0, 0, x, f'True: {y}')
self.__draw_sample(fig, axarr, 0, 1, ga, f'd{mask_idx[0]}-logit/dx')
self.__draw_sample(fig, axarr, 0, 2, gb, f'd{mask_idx[1]}-logit/dx')
self.__draw_sample(fig, axarr, 1, 1, ga * 2 + x, f'd{mask_idx[0]}-logit/dx')
self.__draw_sample(fig, axarr, 1, 2, gb * 2 + x, f'd{mask_idx[1]}-logit/dx')
trainer.logger.experiment.add_figure('confusing_imgs', fig, global_step=trainer.global_step)
@staticmethod
def __draw_sample(fig, axarr, row_idx, col_idx, img, title):
im = axarr[row_idx, col_idx].imshow(img)
fig.colorbar(im, ax=axarr[row_idx, col_idx])
axarr[row_idx, col_idx].set_title(title, fontsize=20)
Cependant, en installant des éclairs en pytorch, nous avons facilité les choses
!pip install pytorch-lightning-bolts
from pl_bolts.callbacks.vision import ConfusedLogitCallback
trainer = Trainer(callbacks=[ConfusedLogitCallback(1)])
Mets les ensemble
Enfin, nous pouvons entraîner notre modèle et générer automatiquement des images lorsque la logique de jugement est confuse.
# data
dataset = MNIST(os.getcwd(), download=True, transform=transforms.ToTensor())
train, val = random_split(dataset, [55000, 5000])
# model
model = LitClassifier()
# attach callback
trainer = Trainer(callbacks=[ConfusedLogitCallback(1)])
# train!
trainer.fit(model, DataLoader(train, batch_size=64), DataLoader(val, batch_size=64))
tensorboard générera automatiquement les images suivantes:
Voyez si c'est différent
Auteur: William Falcon
Code complet: https://colab.research.google.com/drive/16HVAJHdCkyj7W43Q3ZChnxZ7DOwx6K5i?usp=sharing
équipe de traduction deephub