DIGITAL
In den letzten Lehreinheiten haben wir Neuronalen Netzen das Klassifizieren von Eingabedaten beigebracht. In dieser Lehreinheit möchten wir Bilder einer gegebenen Klasse generieren. In der Vorlesung wurden dazu verschiedene Mechanismen vorgestellt, die diese Aufgabe lösen. Als Beispiel ist hier der Variational Autoencoder zu nennen, der die latenten Variablen an eine normalverteilte Zufallsvariable „fittet“ und beim ziehen aus dieser ungesehene Beispiele generiert. Ein anderes Beispiel sind Generative Adversarial Networks, bei denen zwei Netzwerkteile spieltheoretisch Motiviert gegen- und miteinander optimiert werden (siehe Vorlesung).
Beide Mechanismen sind leider mit aufwändigerem Training verbunden. Aus diesem Grund schauen wir uns Energiebasierte Modelle an. Überraschenderweise kann man nämlich mit einigen wenigen Tricks auch einen fertig trainierten Klassifizierer nutzen, um selbst wieder neue Bilder zu generieren. Die Einsicht ist eng damit verknüpft, dass Klassifizierer automatisch auch interne Repräsentationen bauen, um eben eine gute Vorhersage machen zu können.
Ich empfehle Ihnen dazu wärmstens den oben verlinkten interaktiven Blogartikel „The Building Blocks of Interpretability“ durchzusehen.
Abstrakte Idee:
Bei einem Klassizierungsproblem haben wir typischerweise zufällige Daten \(x\) (etwa Bilder), zugehöriges Ground Truth Wissen \(y\) (etwa Label) und eine differentierbare Zielfunktion \(E(f(x), y)\) gegeben, die die Qualität des Klassifizerers \(f\) (etwa ein Neuronales Netz) bewertet.
Stochastic Gradient Descent: Für alle Parameter \(\theta\) eines Modells \(f_\theta\) haben wir die Gradienten \(\nabla_\theta E(f_\theta(x), y)\) bestimmt, mit der Learning Rate skaliert und additiv angepasst.
Wir gehen nun davon aus, dass das Netzwerk bereits trainiert wurde. Die Idee ist nun, die Netzwerkparameter nicht mehr zu verändern, sondern den Gradienten \(\nabla_x E(f(x), y)\) der Eingabe zu bestimmen und diese Anzupassen in der Hoffnung den Loss zu minimieren. Wir schauen uns dazu drei verschiedene Methoden an, die dieser Idee folgend Bilder generiert.
Im speziellen schauen wir uns wieder den Bilddatensatz CIFAR-10 an, da dieser leicht zu rechnen ist.
Beispielcode: Um Ihnen die Arbeit zu erleichtern, habe ich im folgenden ein Codeschnipsel angefügt, mit dem Sie arbeiten können. Das Skript lädt dazu vortrainierte Gewichte eines VGG-11 herunter und testet zuerst die Accuracy. Danach habe ich noch ein paar Zeilen angefügt, die Ihnen beim Bearbeiten der folgenden Aufgaben helfen sollte.
# "you know the drill"
import torch
import torch.nn as nn
import torchvision
import matplotlib.pyplot as plt
# liste aller modelle:
# https://github.com/chenyaofo/pytorch-cifar-models
# num images to sample
batch_size = 16
# cifar-10 datensatz & normalisierung
mean = torch.tensor([0.4914, 0.4822, 0.4465])
std = torch.tensor([0.2023, 0.1994, 0.2010])
transform = torchvision.transforms.Compose([
torchvision.transforms.ToTensor(),
torchvision.transforms.Normalize(mean, std)
])
trainset = torchvision.datasets.CIFAR10(root='./data', train=True, download=True, transform=transform)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=batch_size, shuffle=True, num_workers=2)
classes = ('plane', 'car', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck')
# load pretrained model
model = model = torch.hub.load("chenyaofo/pytorch-cifar-models", "cifar10_vgg11_bn", pretrained=True)
model.eval() # make sure we are using eval-mode (see batch normalization section of lecture)
# check if accuracy converges to about 99.9% (for the test set it would be around 92.6%, just change train=True to train=False above)
# >>> remove this section if the accuracy is correct
# CE = nn.CrossEntropyLoss()
# num, correct = 0, 0
# for batch in trainloader:
# imgs, classes = batch
# pred = model(imgs)
# correct += (pred.argmax(-1) == classes).sum()
# num += len(imgs)
# print("Accuracy=%f, CELoss=%f" % (correct/num, CE(pred, classes)))
# << remove until this point
# define energy-function
# create random data
x = torch.randn((batch_size, 3, 32, 32))
# alternative: get first batch from data set
x, y_real = next(iter(trainloader))
# save initial picture to show difference in plot later
x0 = x.clone()
# define optimizer w.r.t. the input *only*
x.requires_grad = True
optim = torch.optim.Adam([x], lr=1e-3) # adapt hyperparameters if needed
for i in range(1000):
pred = model(x)
E = x.sum() # just for testing optimization loop
print("Step %i: E=%f, diff to init=%f" % (i,E, ((x0[0] - x[0])**2).sum()))
optim.zero_grad()
E.backward()
optim.step()
# plot images
def plotimg(x, ax, label):
x = x.detach()*std.reshape((3,1,1)) + mean.reshape((3,1,1))
x = x.permute(1,2,0) # matplotlib requires channel dimension to be at the end
ax.imshow(x)
ax.set_title(label)
y_pred = model(x).argmax(-1)
fig, axs = plt.subplots(2,1)
plotimg(x0[0], ax=axs[0], label="Startbild "+classes[y_real[0].item()])
plotimg(x[0], ax=axs[1], label="Endbild "+classes[y_pred[0].item()])
plt.show()
Wir machen uns als erstes mit dem oben angegebenen Beispielcode vertraut und lösen das Problem der Generierung auf folgende Weise: Als erstes Testen wir, ob der Klassizierungsloss (Cross-Entropy) bereits hübsche Bilder erzeugen würde. Dazu definieren wir \(E := \operatorname{SoftmaxCrossEntropy}\).
Wie oben beschrieben bilden wir nun den Gradienten \(\nabla_x E\) der Eingabe und passen die Eingabe mithilfe eines vorgefertigten Optimizers (etwa Adam, SGD, etc.) oder alternativ durch die Updateregel \(x \leftarrow x + \eta \cdot \nabla_x E\) für eine feste Learning Rate \(\eta\) an.
Statt nun den Klassifzierungsloss zu optimieren können wir mit genau der gleichen Technik visualisieren welche Features einzelne Layer oder Neuronen besonders stark aktiviert.
Wir wählen dazu ein oder mehrere Layer oder Neuronen aus und definieren das zu maximierende Energiefunktional als die Summe der Ausgaben.
Siehe auch / Beispiele: https://ai.googleblog.com/2015/06/inceptionism-going-deeper-into-neural.html
Hinweise:
layer_outputs = {}
def hook_fn(module, input, output):
layer_outputs["#layer%i-%s" % (module.id, module)] = output
for id, m in enumerate(model.modules()):
if not isinstance(m, nn.Conv2d):
continue
m.id = id
m.register_forward_hook(hook_fn)
Wir möchten die vorgestellte Methode aus dem Paper Your Classifier is Secretly an Energy Based Model and You Should Treat it Like One nachimplementieren.
Sehr ähnliches Paper, nur unsupervised ist: Implicit Generation and Generalization in Energy-Based Models
Die Idee ist die folgende:
Aufgaben: