Kurs
In diesem Tutorial lernst du, was Generative Adversarial Networks (GANs) sind – ganz ohne Mathe-Details. Anschließend programmierst du ein einfaches GAN, das Ziffern erzeugen kann!
Analogie
Am einfachsten verstehst du GANs über eine kleine Analogie:
Stell dir einen Laden vor, der bestimmte Weinsorten von Kundinnen und Kunden ankauft, um sie später weiterzuverkaufen.

Allerdings gibt es windige Gestalten, die Fälschungen verkaufen, um an Geld zu kommen. Die Ladenbesitzerin muss also echte von gefälschten Weinen unterscheiden können.

Anfangs macht der Fälscher viele Fehler, und es fällt der Besitzerin leicht zu erkennen, dass der Wein nicht echt ist. Aus diesen Fehlschlägen lernt der Fälscher, probiert neue Techniken aus, um echte Weine nachzuahmen, und einige davon funktionieren. Sobald er weiß, welche Tricks die Kontrollen der Besitzerin passieren, verfeinert er seine Fälschungen weiter.
Gleichzeitig bekommt die Ladenbesitzerin Feedback von anderen Läden oder Weinexpertinnen, dass einige ihrer Weine nicht original sind. Also verbessert sie ihre Prüfmethoden, um echte und falsche Weine sicher zu unterscheiden. Ziel des Fälschers ist es, Weine herzustellen, die von echten nicht zu unterscheiden sind. Ziel der Besitzerin ist es, echte und falsche zuverlässig zu erkennen.
Dieses Hin und Her ist die Grundidee hinter GANs.
Bestandteile eines Generative Adversarial Network
Anhand des Beispiels oben lässt sich die Architektur eines GANs ableiten.

GANs bestehen aus zwei Hauptkomponenten: Generator und Diskriminator. Die Ladenbesitzerin entspricht dem Diskriminatornetz und ist in der Regel ein Convolutional Neural Network (da GANs vor allem für Bildaufgaben genutzt werden), das die Wahrscheinlichkeit bewertet, ob ein Bild echt ist.
Der Fälscher ist das Generatornetz und ebenfalls meist ein Convolutional Neural Network (mit Deconvolution-Layern). Dieses Netz nimmt einen Rauschvektor als Eingabe und gibt ein Bild aus. Beim Training lernt der Generator, welche Bildbereiche er verbessern oder verändern muss, damit es dem Diskriminator schwerer fällt, seine generierten Bilder von echten zu unterscheiden.
Der Generator produziert nach und nach Bilder, die echten immer ähnlicher sehen, während der Diskriminator versucht, die Unterschiede zwischen echten und falschen Bildern herauszuarbeiten. Am Ende soll der Generator Bilder erzeugen, die von echten nicht zu unterscheiden sind.
Ein einfaches Generative Adversarial Network mit Keras
Jetzt, da du GANs und ihre Hauptkomponenten kennst, kannst du ein sehr einfaches Netz programmieren. Du verwendest Keras. Falls du diese Python-Bibliothek noch nicht kennst, lies vorab dieses Tutorial. Die Vorlage für dieses Tutorial basiert auf dem hier entwickelten GAN: hier.
Installiere zunächst die folgenden Pakete via pip:
kerasmatplotlibtensorflowtqdm
matplotlib nutzt du zum Plotten, tensorflow als Keras-Backend und tqdm für eine komfortable Fortschrittsanzeige pro Epoche (Iteration).
Als Nächstes erstellst du ein Python-Skript. Darin importierst du zuerst alle Module und Funktionen, die du nutzt. Die Erklärungen folgen jeweils beim Einsatz.
import os
import numpy as np
import matplotlib.pyplot as plt
from tqdm import tqdm
from keras.layers import Input
from keras.models import Model, Sequential
from keras.layers.core import Dense, Dropout
from keras.layers.advanced_activations import LeakyReLU
from keras.datasets import mnist
from keras.optimizers import Adam
from keras import initializers
Jetzt setzt du ein paar Variablen:
# Let Keras know that we are using tensorflow as our backend engine
os.environ["KERAS_BACKEND"] = "tensorflow"
# To make sure that we can reproduce the experiment and get the same results
np.random.seed(10)
# The dimension of our random noise vector.
random_dim = 100
Bevor du den Diskriminator und den Generator baust, sammelst und bereitest du die Daten vor. Du verwendest den populären MNIST-Datensatz mit Bildern einzelner Ziffern von 0 bis 9.

def load_minst_data():
# load the data
(x_train, y_train), (x_test, y_test) = mnist.load_data()
# normalize our inputs to be in the range[-1, 1]
x_train = (x_train.astype(np.float32) - 127.5)/127.5
# convert x_train with a shape of (60000, 28, 28) to (60000, 784) so we have
# 784 columns per row
x_train = x_train.reshape(60000, 784)
return (x_train, y_train, x_test, y_test)
Hinweis: mnist.load_data() ist Teil von Keras und ermöglicht dir, den MNIST-Datensatz bequem in deinen Workspace zu laden.
Jetzt erstellst du die Generator- und Diskriminator-Netze. Du verwendest für beide den Adam-Optimizer. Sowohl Generator als auch Diskriminator bestehen aus drei Hidden-Layern mit der Aktivierungsfunktion Leaky ReLU. Für den Diskriminator fügst du außerdem Dropout-Layer hinzu, um ihn gegenüber unbekannten Bildern robuster zu machen.
# You will use the Adam optimizer
def get_optimizer():
return Adam(lr=0.0002, beta_1=0.5)
def get_generator(optimizer):
generator = Sequential()
generator.add(Dense(256, input_dim=random_dim, kernel_initializer=initializers.RandomNormal(stddev=0.02)))
generator.add(LeakyReLU(0.2))
generator.add(Dense(512))
generator.add(LeakyReLU(0.2))
generator.add(Dense(1024))
generator.add(LeakyReLU(0.2))
generator.add(Dense(784, activation='tanh'))
generator.compile(loss='binary_crossentropy', optimizer=optimizer)
return generator
def get_discriminator(optimizer):
discriminator = Sequential()
discriminator.add(Dense(1024, input_dim=784, kernel_initializer=initializers.RandomNormal(stddev=0.02)))
discriminator.add(LeakyReLU(0.2))
discriminator.add(Dropout(0.3))
discriminator.add(Dense(512))
discriminator.add(LeakyReLU(0.2))
discriminator.add(Dropout(0.3))
discriminator.add(Dense(256))
discriminator.add(LeakyReLU(0.2))
discriminator.add(Dropout(0.3))
discriminator.add(Dense(1, activation='sigmoid'))
discriminator.compile(loss='binary_crossentropy', optimizer=optimizer)
return discriminator
Jetzt führst du Generator und Diskriminator zusammen!
def get_gan_network(discriminator, random_dim, generator, optimizer):
# We initially set trainable to False since we only want to train either the
# generator or discriminator at a time
discriminator.trainable = False
# gan input (noise) will be 100-dimensional vectors
gan_input = Input(shape=(random_dim,))
# the output of the generator (an image)
x = generator(gan_input)
# get the output of the discriminator (probability if the image is real or not)
gan_output = discriminator(x)
gan = Model(inputs=gan_input, outputs=gan_output)
gan.compile(loss='binary_crossentropy', optimizer=optimizer)
return gan
Der Vollständigkeit halber kannst du noch eine Funktion erstellen, die alle 20 Epochen die generierten Bilder speichert. Da dies nicht der Kern der Lektion ist, musst du sie nicht im Detail verstehen.
# Create a wall of generated MNIST images
def plot_generated_images(epoch, generator, examples=100, dim=(10, 10), figsize=(10, 10)):
noise = np.random.normal(0, 1, size=[examples, random_dim])
generated_images = generator.predict(noise)
generated_images = generated_images.reshape(examples, 28, 28)
plt.figure(figsize=figsize)
for i in range(generated_images.shape[0]):
plt.subplot(dim[0], dim[1], i+1)
plt.imshow(generated_images[i], interpolation='nearest', cmap='gray_r')
plt.axis('off')
plt.tight_layout()
plt.savefig('gan_generated_image_epoch_%d.png' % epoch)
Damit ist der Großteil deines Netzes programmiert. Jetzt musst du es nur noch trainieren und dir die erzeugten Bilder ansehen.
def train(epochs=1, batch_size=128):
# Get the training and testing data
x_train, y_train, x_test, y_test = load_minst_data()
# Split the training data into batches of size 128
batch_count = x_train.shape[0] / batch_size
# Build our GAN netowrk
adam = get_optimizer()
generator = get_generator(adam)
discriminator = get_discriminator(adam)
gan = get_gan_network(discriminator, random_dim, generator, adam)
for e in xrange(1, epochs+1):
print '-'*15, 'Epoch %d' % e, '-'*15
for _ in tqdm(xrange(batch_count)):
# Get a random set of input noise and images
noise = np.random.normal(0, 1, size=[batch_size, random_dim])
image_batch = x_train[np.random.randint(0, x_train.shape[0], size=batch_size)]
# Generate fake MNIST images
generated_images = generator.predict(noise)
X = np.concatenate([image_batch, generated_images])
# Labels for generated and real data
y_dis = np.zeros(2*batch_size)
# One-sided label smoothing
y_dis[:batch_size] = 0.9
# Train discriminator
discriminator.trainable = True
discriminator.train_on_batch(X, y_dis)
# Train generator
noise = np.random.normal(0, 1, size=[batch_size, random_dim])
y_gen = np.ones(batch_size)
discriminator.trainable = False
gan.train_on_batch(noise, y_gen)
if e == 1 or e % 20 == 0:
plot_generated_images(e, generator)
if __name__ == '__main__':
train(400, 128)
Nach 400 Epochen Training kannst du die generierten Bilder betrachten. Nach der ersten Epoche ist noch keine echte Struktur zu erkennen. Nach 40 Epochen nehmen die Ziffern Form an. Und nach 400 Epochen sind die Ziffern deutlich zu erkennen – auch wenn ein paar noch unleserlich sind.
| Ergebnisse nach 1 Epoche | Ergebnisse nach 40 Epochen | Ergebnisse nach 400 Epochen |
|---|---|---|
!![]() |
![]() |
![]() |
Dieser Code benötigt auf einer CPU etwa 2 Minuten pro Epoche – das ist der Hauptgrund, warum wir ihn ausgewählt haben. Du kannst mit mehr Epochen experimentieren und dem Generator wie auch dem Diskriminator weitere (oder andere) Layer hinzufügen. Bedenke aber: Mit komplexeren, tieferen Architekturen steigt die Laufzeit auf einer reinen CPU. Lass dich davon trotzdem nicht vom Ausprobieren abhalten!
Fazit
Glückwunsch, du hast es bis zum Ende geschafft und die Grundlagen von Generative Adversarial Networks (GANs) auf intuitive Weise kennengelernt! Außerdem hast du dein erstes Modell mit der Keras-Bibliothek implementiert. Wenn du mehr über Deep Learning mit Python erfahren möchtest, schau dir den DataCamp-Kurs Deep Learning in Python an.


