Vai al contenuto principale

Demistificare le Generative Adversarial Nets (GAN)

Scopri cosa sono le Generative Adversarial Networks senza entrare nei dettagli matematici e scrivi il codice di una semplice GAN che può creare cifre!
· 9 min leggi

Scopri con l'IA

In questo tutorial scoprirai cosa sono le Generative Adversarial Networks (GAN) senza entrare nei dettagli matematici. Poi imparerai a scrivere il codice di una semplice GAN in grado di creare cifre!

Analogia

Il modo più semplice per capire cosa sono le GAN è attraverso una semplice analogia:

Supponiamo che ci sia un negozio che compra dai clienti alcuni tipi di vino che poi rivenderà.

analogy

Tuttavia, ci sono clienti malintenzionati che vendono vino contraffatto per ottenere denaro. In questo caso, il proprietario del negozio deve essere in grado di distinguere tra vini falsi e autentici.

analogy

Puoi immaginare che all’inizio il falsario possa commettere molti errori quando cerca di vendere il vino contraffatto, e per il proprietario del negozio sarà facile identificare che il vino non è autentico. A causa di questi fallimenti, il falsario continuerà a provare tecniche diverse per simulare i vini autentici e alcune alla fine avranno successo. Ora che il falsario sa che certe tecniche hanno superato i controlli del negoziante, può iniziare a migliorare ulteriormente i vini falsi basandosi su quelle tecniche.

Allo stesso tempo, probabilmente la proprietaria del negozio riceverà feedback da altri negozianti o esperti di vino sul fatto che alcuni dei vini che possiede non sono originali. Questo significa che la proprietaria dovrà migliorare il modo in cui determina se un vino è falso o autentico. L’obiettivo del falsario è creare vini indistinguibili da quelli autentici, e l’obiettivo della proprietaria è riconoscere con precisione se un vino è vero o no.

Questa competizione a botta e risposta è l’idea principale alla base delle GAN.

Componenti di una Generative Adversarial Network

Usando l’esempio sopra, possiamo ricavare l’architettura di una GAN.

gan

Nelle GAN ci sono due componenti principali: il generatore e il discriminatore. La proprietaria del negozio nell’esempio è nota come rete discriminatrice ed è di solito una rete neurale convoluzionale (dato che le GAN sono usate soprattutto per compiti di visione) che assegna una probabilità che l’immagine sia reale.

Il falsario è noto come rete generativa ed è anch’essa tipicamente una rete neurale convoluzionale (con strati di deconvoluzione). Questa rete prende un vettore di rumore e produce un’immagine. Durante l’addestramento della rete generativa, questa impara quali parti dell’immagine migliorare/modificare in modo che al discriminatore risulti più difficile distinguere le immagini generate da quelle reali.

La rete generativa continua a produrre immagini sempre più simili a quelle reali, mentre la rete discriminatrice cerca di determinare le differenze tra immagini reali e false. L’obiettivo finale è ottenere una rete generativa capace di produrre immagini indistinguibili da quelle reali.

Una semplice Generative Adversarial Network con Keras

Ora che hai capito cosa sono le GAN e i loro componenti principali, possiamo iniziare a scriverne una molto semplice. Userai Keras e, se non conosci questa libreria Python, dovresti leggere questo tutorial prima di continuare. Questo tutorial si basa sulla GAN sviluppata qui.

La prima cosa da fare è installare i seguenti pacchetti tramite pip

  • keras
  • matplotlib
  • tensorflow
  • tqdm

Userai matplotlib per il plotting, tensorflow come backend di Keras e tqdm per mostrare una comoda barra di avanzamento per ogni epoca (iterazione).

Il passo successivo è creare uno script Python. In questo script, per prima cosa importerai tutti i moduli e le funzioni che utilizzerai. Man mano che verranno usati, ne verrà data una spiegazione.

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

Ora vuoi impostare alcune variabili:

# 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

Prima di iniziare a costruire il discriminatore e il generatore, dovresti prima raccogliere e pre-processare i dati. Userai il popolare dataset MNIST, che contiene un insieme di immagini di singole cifre da 0 a 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)

Nota che mnist.load_data() fa parte di Keras e ti permette di importare facilmente il dataset MNIST nel tuo workspace.

Ora puoi creare le reti del generatore e del discriminatore. Userai l’ottimizzatore Adam per entrambe le reti. Per generatore e discriminatore, creerai una rete neurale con tre livelli nascosti con funzione di attivazione Leaky ReLU. Dovresti anche aggiungere strati di dropout al discriminatore per migliorarne la robustezza su immagini mai viste.

# 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

È finalmente arrivato il momento di mettere insieme generatore e discriminatore!

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

Per completezza, puoi creare una funzione che salvi le immagini generate ogni 20 epoche. Dato che non è al centro di questa lezione, non è necessario capirla a fondo.

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

Hai ora programmato la maggior parte della tua rete. Non resta che addestrarla e dare un’occhiata alle immagini che hai creato.

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)

Dopo l’addestramento per 400 epoche, puoi visualizzare le immagini generate. Guardando le immagini prodotte dopo la prima epoca, si vede che non hanno alcuna struttura reale; osservando l’immagine dopo 40 epoche, le cifre iniziano a prendere forma e, infine, le immagini prodotte dopo 400 epoche mostrano cifre chiare anche se un paio sono ancora irriconoscibili.

Risultati dopo 1 epocaRisultati dopo 40 epocheRisultati dopo 400 epoche
!images1images2images3

Questo codice impiega circa 2 minuti per epoca su una CPU ed è il motivo principale per cui è stato scelto. Puoi fare esperimenti usando più epoche e aggiungendo più (e diversi) strati sia al generatore che al discriminatore. Tieni però presente che, usando architetture più complesse e profonde, il tempo di esecuzione aumenterà se utilizzi solo una CPU. Tuttavia, non lasciare che questo ti fermi dallo sperimentare!

Conclusione

Complimenti, sei arrivato alla fine di questo tutorial, in cui hai imparato in modo intuitivo le basi delle Generative Adversarial Networks (GAN)! Inoltre, hai implementato il tuo primo modello con l’aiuto della libreria Keras. Se vuoi saperne di più sul deep learning con Python, prendi in considerazione il corso di DataCamp Deep Learning in Python.

Argomenti

Correlato