Salta al contenuto principale

05 - GAN (Generative Adversarial Networks): Creare Dati Sintetici

Introduzione: Due Reti in Competizione Le Generative Adversarial Networks (GAN) , introdotte da Ian Goodfellow nel 2014, rappresentano uno dei paradigmi...

Cosa ne pensi di questo articolo?

05 - GAN (Generative Adversarial Networks): Creare Dati Sintetici
Deep LearningFoto:panumas nikhomkhaiPexels

In Breve

Introduzione: Due Reti in Competizione Le Generative Adversarial Networks (GAN) , introdotte da Ian Goodfellow nel 2014, rappresentano uno dei paradigmi...

Introduzione: Due Reti in Competizione

Le Generative Adversarial Networks (GAN), introdotte da Ian Goodfellow nel 2014, rappresentano uno dei paradigmi più innovativi del deep learning. L'idea e elegante nella sua semplicità: due reti neurali competono tra loro in un gioco a somma zero. Il Generatore crea dati sintetici cercando di ingannare il Discriminatore, che a sua volta impara a distinguere dati reali da quelli generati. Questa competizione spinge entrambe le reti a migliorare continuamente.

Le GAN hanno rivoluzionato la generazione di immagini: volti fotorealistici che non esistono, trasferimento di stile artistico, super-risoluzione, data augmentation e molto altro. In questo articolo esploreremo la teoria, le sfide del training e le varianti più importanti, con implementazione pratica in PyTorch.

Cosa Imparerai

  • Architettura GAN: Generatore e Discriminatore
  • La loss function adversariale: il gioco min-max
  • Training loop: alternanza tra le due reti
  • Mode collapse e training instability: le sfide principali
  • Varianti: Conditional GAN, DCGAN, Wasserstein GAN
  • Applicazioni: generazione volti, style transfer, data augmentation
  • Implementazione DCGAN completa in PyTorch

Architettura: Generatore e Discriminatore

Il Generatore

Il Generatore G prende in input un vettore di rumore casuale z (tipicamente campionato da una distribuzione gaussiana o uniforme) e lo trasforma in un dato sintetico (es. un'immagine). L'obiettivo del generatore e produrre output cosi realistici da ingannare il discriminatore.

Il Discriminatore

Il Discriminatore D e un classificatore binario: riceve un dato (reale o generato) e produce una probabilità che sia reale. L'obiettivo del discriminatore e distinguere correttamente i dati reali da quelli generati.

La loss adversariale formalizza questo gioco: il discriminatore massimizza la probabilità di classificare correttamente, mentre il generatore minimizza la probabilità che il discriminatore rilevi i suoi output come falsi.


import torch
import torch.nn as nn

class Generator(nn.Module):
    """Generatore: rumore casuale -> immagine 64x64"""
    def __init__(self, latent_dim=100, channels=3):
        super().__init__()
        self.net = nn.Sequential(
            # latent_dim -> 512 x 4 x 4
            nn.ConvTranspose2d(latent_dim, 512, 4, 1, 0, bias=False),
            nn.BatchNorm2d(512),
            nn.ReLU(True),
            # 512 x 4 x 4 -> 256 x 8 x 8
            nn.ConvTranspose2d(512, 256, 4, 2, 1, bias=False),
            nn.BatchNorm2d(256),
            nn.ReLU(True),
            # 256 x 8 x 8 -> 128 x 16 x 16
            nn.ConvTranspose2d(256, 128, 4, 2, 1, bias=False),
            nn.BatchNorm2d(128),
            nn.ReLU(True),
            # 128 x 16 x 16 -> 64 x 32 x 32
            nn.ConvTranspose2d(128, 64, 4, 2, 1, bias=False),
            nn.BatchNorm2d(64),
            nn.ReLU(True),
            # 64 x 32 x 32 -> channels x 64 x 64
            nn.ConvTranspose2d(64, channels, 4, 2, 1, bias=False),
            nn.Tanh()  # Output in [-1, 1]
        )

    def forward(self, z):
        return self.net(z.view(z.size(0), -1, 1, 1))

class Discriminator(nn.Module):
    """Discriminatore: immagine 64x64 -> reale/falso"""
    def __init__(self, channels=3):
        super().__init__()
        self.net = nn.Sequential(
            nn.Conv2d(channels, 64, 4, 2, 1, bias=False),
            nn.LeakyReLU(0.2, inplace=True),
            nn.Conv2d(64, 128, 4, 2, 1, bias=False),
            nn.BatchNorm2d(128),
            nn.LeakyReLU(0.2, inplace=True),
            nn.Conv2d(128, 256, 4, 2, 1, bias=False),
            nn.BatchNorm2d(256),
            nn.LeakyReLU(0.2, inplace=True),
            nn.Conv2d(256, 512, 4, 2, 1, bias=False),
            nn.BatchNorm2d(512),
            nn.LeakyReLU(0.2, inplace=True),
            nn.Conv2d(512, 1, 4, 1, 0, bias=False),
            nn.Sigmoid()
        )

    def forward(self, img):
        return self.net(img).view(-1, 1)
      

Training Loop: Il Gioco Adversariale

Il training delle GAN alterna l'aggiornamento del discriminatore e del generatore. Ad ogni iterazione: (1) il discriminatore viene addestrato su un batch di dati reali e un batch di dati generati; (2) il generatore viene addestrato cercando di ingannare il discriminatore aggiornato.


import torch.optim as optim

# Setup
latent_dim = 100
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
G = Generator(latent_dim).to(device)
D = Discriminator().to(device)
criterion = nn.BCELoss()
optimizer_G = optim.Adam(G.parameters(), lr=0.0002, betas=(0.5, 0.999))
optimizer_D = optim.Adam(D.parameters(), lr=0.0002, betas=(0.5, 0.999))

def train_step(real_images):
    batch_size = real_images.size(0)
    real_labels = torch.ones(batch_size, 1).to(device)
    fake_labels = torch.zeros(batch_size, 1).to(device)

    # --- Addestra Discriminatore ---
    optimizer_D.zero_grad()
    # Su dati reali
    real_output = D(real_images)
    d_loss_real = criterion(real_output, real_labels)
    # Su dati generati
    z = torch.randn(batch_size, latent_dim).to(device)
    fake_images = G(z).detach()  # detach: non calcolare gradiente per G
    fake_output = D(fake_images)
    d_loss_fake = criterion(fake_output, fake_labels)
    d_loss = d_loss_real + d_loss_fake
    d_loss.backward()
    optimizer_D.step()

    # --- Addestra Generatore ---
    optimizer_G.zero_grad()
    z = torch.randn(batch_size, latent_dim).to(device)
    fake_images = G(z)
    fake_output = D(fake_images)
    g_loss = criterion(fake_output, real_labels)  # Vuole ingannare D
    g_loss.backward()
    optimizer_G.step()

    return d_loss.item(), g_loss.item()
      

Sfide del Training: Mode Collapse e Instabilita

Mode Collapse

Il mode collapse e il problema più comune delle GAN: il generatore trova pochi output che ingannano il discriminatore e continua a produrre solo quelli, ignorando la diversità dei dati reali. Invece di generare volti diversi, potrebbe produrre sempre lo stesso volto con piccole variazioni.

Training Instability

Le GAN sono notoriamente difficili da addestrare. Se il discriminatore diventa troppo forte, il gradiente per il generatore diventa quasi zero (vanishing gradient). Se il generatore domina, il discriminatore non fornisce feedback utile. Trovare il bilanciamento e una sfida continua.

Wasserstein GAN: Stabilizzare il Training

La Wasserstein GAN (WGAN) risolve molti problemi di instabilita sostituendo la Binary Cross-Entropy con la distanza di Wasserstein (Earth Mover's Distance). Questo fornisce un gradiente significativo anche quando le distribuzioni reale e generata sono molto diverse, eliminando il vanishing gradient. WGAN usa weight clipping o gradient penalty (WGAN-GP) per garantire la condizione di Lipschitz.

Varianti Importanti

Conditional GAN (cGAN)

Le Conditional GAN aggiungono informazione condizionale (es. classe, testo) sia al generatore che al discriminatore. Questo permette di controllare la generazione: "genera un'immagine di un gatto" oppure "genera il numero 7".

CycleGAN

CycleGAN permette la traduzione tra domini senza dati accoppiati. Può trasformare foto in dipinti in stile Monet, convertire cavalli in zebre o trasformare paesaggi estivi in invernali, senza mai aver visto le stesse scene in entrambi i domini.

StyleGAN

StyleGAN (NVIDIA) ha raggiunto la generazione di volti fotorealistici ad alta risoluzione. Introduce il concetto di "stile" a diversi livelli della rete: gli stili grossolani controllano la struttura del volto, quelli fini i dettagli come texture e colore dei capelli.

Applicazioni nel Mondo Reale

  • Generazione di volti: volti realistici per gaming, cinema, avatar (ThisPersonDoesNotExist.com)
  • Data augmentation: generare dati di training sintetici per classi sotto-rappresentate in medicina, manifattura
  • Super-risoluzione: aumentare la risoluzione di immagini (SRGAN) per fotografia, satelliti, medicina
  • Image-to-image translation: sketch-to-photo, day-to-night, segmentation-to-image (Pix2Pix)
  • Drug discovery: generare strutture molecolari con proprietà desiderate

Prossimi Passi nella Serie

  • Nel prossimo articolo esploreremo i Diffusion Models, che hanno superato le GAN in qualità di generazione
  • Vedremo il processo di diffusione: aggiungere e rimuovere rumore per generare immagini
  • Analizzeremo DALL-E, Stable Diffusion e ControlNet

Prova Questi Strumenti

Strumenti gratuiti correlati a questo argomento.

Commenti

Caricamento commenti...

Accedi per commentare

Discuti questo articolo nella community

Unisciti al gruppo Telegram per discutere con altri sviluppatori, fare domande e condividere le tue esperienze.

Ti è piaciuto questo articolo?

Esplora altri contenuti sul blog o scopri i miei progetti