Ga naar hoofdinhoud

GAN’s ontrafeld

Leer wat Generative Adversarial Networks zijn zonder in de wiskundige details te duiken, en codeer een eenvoudige GAN die cijfers kan genereren!
· 9 min lezen

Verken met AI

In deze tutorial leer je wat Generative Adversarial Networks (GAN’s) zijn, zonder in de wiskundige details te duiken. Daarna leer je hoe je een simpele GAN codeert die cijfers kan maken!

Analogie

De makkelijkste manier om te begrijpen wat GAN’s zijn, is via een eenvoudige analogie:

Stel dat er een winkel is die bepaalde soorten wijn opkoopt van klanten om die later weer te verkopen.

analogy

Er zijn echter kwaadwillende klanten die nepwijn verkopen om geld te verdienen. In dit geval moet de winkelier het verschil kunnen zien tussen neppe en authentieke wijnen.

analogy

Je kunt je voorstellen dat de vervalser in het begin veel fouten maakt bij het verkopen van nepwijn en dat het voor de winkelier makkelijk is om te zien dat de wijn niet echt is. Door deze mislukkingen blijft de vervalser verschillende technieken proberen om echte wijn te imiteren, en sommige zullen uiteindelijk slagen. Nu de vervalser weet dat bepaalde technieken door de controles van de winkelier heen kwamen, kan hij de nepwijnen verder verbeteren op basis van die technieken.

Tegelijkertijd krijgt de winkelier waarschijnlijk feedback van andere winkeliers of wijnexperts dat sommige wijnen die zij heeft niet origineel zijn. Dat betekent dat de winkelier moet verbeteren hoe ze bepaalt of een wijn nep of authentiek is. Het doel van de vervalser is wijnen te maken die niet te onderscheiden zijn van de echte, en het doel van de winkelier is nauwkeurig vast te stellen of een wijn echt is of niet.

Deze competitie heen en weer is het kernidee achter GAN’s.

Componenten van een Generative Adversarial Network

Met het voorbeeld hierboven kunnen we de architectuur van een GAN opstellen.

gan

Er zijn twee hoofdcomponenten binnen GAN’s: de generator en de discriminator. De winkelier in het voorbeeld staat bekend als een discriminatornetwerk en is meestal een convolutioneel neuraal netwerk (aangezien GAN’s vooral voor beeldtaken worden gebruikt) dat een waarschijnlijkheid toekent dat de afbeelding echt is.

De vervalser staat bekend als het generatieve netwerk, en is doorgaans ook een convolutioneel neuraal netwerk (met deconvolutielagen). Dit netwerk neemt een ruisvector en geeft een afbeelding als output. Tijdens het trainen leert het generatieve netwerk welke delen van de afbeelding het moet verbeteren of aanpassen zodat de discriminator meer moeite heeft om zijn gegenereerde beelden van echte te onderscheiden.

Het generatieve netwerk blijft afbeeldingen produceren die qua uiterlijk dichter bij de echte beelden komen, terwijl het discriminatieve netwerk probeert de verschillen tussen echte en nepafbeeldingen te bepalen. Het uiteindelijke doel is een generatief netwerk dat afbeeldingen kan produceren die niet te onderscheiden zijn van de echte.

Een eenvoudige Generative Adversarial Network met Keras

Nu je begrijpt wat GAN’s zijn en wat hun belangrijkste componenten zijn, kunnen we een heel eenvoudige versie gaan coderen. Je gebruikt Keras en als je niet bekend bent met deze Python-bibliotheek, lees dan eerst deze tutorial voordat je doorgaat. Deze tutorial is gebaseerd op de GAN die hier is ontwikkeld.

Het eerste wat je moet doen, is de volgende pakketten installeren via pip

  • keras
  • matplotlib
  • tensorflow
  • tqdm

Je gebruikt matplotlib om te plotten, tensorflow als de Keras-backendbibliotheek en tqdm om een mooie voortgangsbalk te tonen voor elke epoch (iteratie).

De volgende stap is het maken van een Python-script. In dit script importeer je eerst alle modules en functies die je gaat gebruiken. Een uitleg van elk volgt wanneer ze worden gebruikt.

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

Nu wil je enkele variabelen instellen:

# 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

Voordat je begint met het bouwen van de discriminator en generator, moet je eerst de data verzamelen en pre-processen. Je gebruikt de populaire MNIST-dataset met afbeeldingen van enkelvoudige cijfers van 0 tot en met 9.

mnist dataset

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)

Let op dat mnist.load_data() onderdeel is van Keras en het je makkelijk maakt om de MNIST-dataset in je werkomgeving te importeren.

Nu kun je je generator- en discriminatornetwerken maken. Je gebruikt de Adam-optimizer voor beide netwerken. Voor zowel de generator als de discriminator maak je een neuraal netwerk met drie verborgen lagen, met als activatiefunctie de Leaky ReLU. Voeg voor de discriminator ook dropoutlagen toe om de robuustheid op ongeziene beelden te verbeteren.

# 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

Het is eindelijk tijd om de generator en discriminator samen te brengen!

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

Voor de volledigheid kun je een functie maken die elke 20 epochs je gegenereerde afbeeldingen opslaat. Omdat dit niet de kern van deze les is, hoef je de functie niet volledig te begrijpen.

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

Je hebt nu het grootste deel van je netwerk gecodeerd. Het enige wat nog rest, is dit netwerk trainen en de gemaakte afbeeldingen bekijken.

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)

Na trainen gedurende 400 epochs kun je de gegenereerde afbeeldingen bekijken. Als je naar de beelden na de eerste epoch kijkt, zie je dat er nog geen echte structuur is; na 40 epochs beginnen de cijfers vorm te krijgen en tot slot tonen de afbeeldingen na 400 epochs duidelijke cijfers, al zijn er nog een paar onherkenbaar.

Resultaten na 1 epochResultaten na 40 epochsResultaten na 400 epochs
!images1images2images3

Deze code kost op een CPU ongeveer 2 minuten per epoch, en dat is de belangrijkste reden dat deze code is gekozen. Je kunt experimenteren met meer epochs en met het toevoegen van meer (en andere) lagen aan zowel de generator als de discriminator. Houd er wel rekening mee dat bij complexere en diepere architecturen de looptijd ook toeneemt als je alleen een CPU gebruikt. Laat dat je echter niet weerhouden om te experimenteren!

Conclusie

Gefeliciteerd, je bent aan het einde van deze tutorial gekomen, waarin je op een intuïtieve manier de basis van Generative Adversarial Networks (GAN’s) hebt geleerd! Ook heb je je eerste model geïmplementeerd met behulp van de Keras-bibliotheek. Wil je meer weten over deep learning met Python, overweeg dan DataCamp’s cursus Deep Learning in Python.

Onderwerpen

Gerelateerd