GANs et modèles génératifs : Créer du contenu avec PyTorch

tl;dr: Guide complet des modèles génératifs : GANs avec DCGAN pour générer des images, VAEs pour l'apprentissage de représentations, Diffusion Models (Stable Diffusion) pour la génération haute qualité. Inclut implémentations complètes et techniques de stabilisation.

Les modèles génératifs sont des réseaux de neurones capables de créer du nouveau contenu : images, musique, texte, vidéos. Contrairement aux modèles discriminatifs (qui classifient), les modèles génératifs apprennent la distribution des données pour générer des échantillons réalistes.

Code et visualisation PyTorch pour les Generative Adversarial Networks (GANs) dans le développement de modèles d’IA

Dans cet article, vous allez :

  • 🎨 Comprendre les GANs (Generative Adversarial Networks)
  • 🖼️ Implémenter DCGAN pour générer des images réalistes
  • 🧬 Maîtriser les VAEs (Variational Autoencoders)
  • ✨ Découvrir les Diffusion Models (Stable Diffusion, DALL-E)
  • 🎭 Créer des applications : génération, style transfer, super-resolution
  • 🛠️ Techniques pour stabiliser l’entraînement des GANs

Niveau : Avancé

💡 Prérequis : Maîtrise de PyTorch (articles 1-6), connaissance des CNNs, expérience en entraînement de modèles. Les GANs sont notoirement difficiles à entraîner !

Modèles génératifs : Vue d’ensemble

Discriminatif vs Génératif

Modèle Discriminatif :

P(y|x) : "Quelle est la classe y sachant l'entrée x ?"
Exemple : "Cette image représente-t-elle un chat ou un chien ?"

Modèle Génératif :

P(x) ou P(x|z) : "Quelle est la probabilité de cette donnée x ?"
Exemple : "Génère une nouvelle image de chat réaliste"

Les 3 grandes familles

  1. GANs : Deux réseaux en compétition (générateur vs discriminateur)

    • ✅ Images ultra-réalistes, haute résolution
    • ❌ Difficiles à entraîner, mode collapse
  2. VAEs : Encodeur-décodeur avec espace latent probabiliste

    • ✅ Entraînement stable, interpolation lisse
    • ❌ Images parfois floues
  3. Diffusion Models : Apprentissage du processus de débruitage

    • ✅ Qualité exceptionnelle (DALL-E, Midjourney, Stable Diffusion)
    • ❌ Génération lente (50-100 étapes d’inférence)

GANs : Generative Adversarial Networks

Introduits par Ian Goodfellow en 2014, les GANs reposent sur un jeu adversarial entre deux réseaux :

Architecture GAN

Générateur (G) :

Bruit aléatoire z → [Générateur] → Image fake x̃

Discriminateur (D) :

Image (vraie ou fake) → [Discriminateur] → Probabilité [0,1]

Le jeu adversarial

Objectif du Générateur : Tromper le discriminateur en générant des images réalistes

max_G E[log(D(G(z)))]
"Maximiser la probabilité que D pense que mes fakes sont vrais"

Objectif du Discriminateur : Distinguer les vraies images des fakes

max_D E[log(D(x))] + E[log(1 - D(G(z)))]
"Identifier correctement les vraies (D(x)=1) et les fakes (D(G(z))=0)"

Fonction objectif min-max :

min_G max_D V(D,G) = E[log(D(x))] + E[log(1 - D(G(z)))]

Analogie : Le faussaire et le détective

  • Générateur = Faussaire qui crée de faux billets
  • Discriminateur = Détective qui détecte les faux
  • Au fil du temps, le faussaire s’améliore, le détective aussi
  • À l’équilibre (équilibre de Nash) : les faux sont indiscernables des vrais !

DCGAN : Deep Convolutional GAN

DCGAN (Radford et al., 2015) a révolutionné les GANs en introduisant des convolutions et des bonnes pratiques architecturales.

Principes de Design DCGAN

  1. Remplacer les couches fully-connected par des convolutions
  2. Batch Normalization dans G et D (sauf première/dernière couche)
  3. Pas de pooling : utiliser strided convolutions (D) et transposed convolutions (G)
  4. Activations : ReLU dans G (Tanh en sortie), LeakyReLU dans D
  5. Architecture symétrique : D fait le miroir de G

Implémentation DCGAN

import torch
import torch.nn as nn

class Generator(nn.Module):
    def __init__(self, latent_dim=100, channels=3, features_g=64):
        """
        Générateur DCGAN
        Input: vecteur latent z de dimension latent_dim
        Output: image (channels, 64, 64)
        """
        super().__init__()

        # Architecture : latent_dim → 4x4 → 8x8 → 16x16 → 32x32 → 64x64
        self.model = nn.Sequential(
            # Input: (batch, latent_dim, 1, 1)
            self._block(latent_dim, features_g * 16, 4, 1, 0),  # 4x4

            self._block(features_g * 16, features_g * 8, 4, 2, 1),  # 8x8
            self._block(features_g * 8, features_g * 4, 4, 2, 1),   # 16x16
            self._block(features_g * 4, features_g * 2, 4, 2, 1),   # 32x32

            # Dernière couche : pas de BatchNorm, activation Tanh
            nn.ConvTranspose2d(features_g * 2, channels, 4, 2, 1),  # 64x64
            nn.Tanh()  # Output dans [-1, 1]
        )

    def _block(self, in_channels, out_channels, kernel_size, stride, padding):
        """Bloc : ConvTranspose → BatchNorm → ReLU"""
        return nn.Sequential(
            nn.ConvTranspose2d(
                in_channels, out_channels, kernel_size, stride, padding, bias=False
            ),
            nn.BatchNorm2d(out_channels),
            nn.ReLU(True)
        )

    def forward(self, z):
        """
        z: (batch, latent_dim, 1, 1)
        """
        return self.model(z)

class Discriminator(nn.Module):
    def __init__(self, channels=3, features_d=64):
        """
        Discriminateur DCGAN
        Input: image (channels, 64, 64)
        Output: probabilité [0,1]
        """
        super().__init__()

        # Architecture : 64x64 → 32x32 → 16x16 → 8x8 → 4x4 → 1
        self.model = nn.Sequential(
            # Input: (batch, channels, 64, 64)
            # Première couche : pas de BatchNorm
            nn.Conv2d(channels, features_d, 4, 2, 1),  # 32x32
            nn.LeakyReLU(0.2, inplace=True),

            self._block(features_d, features_d * 2, 4, 2, 1),      # 16x16
            self._block(features_d * 2, features_d * 4, 4, 2, 1),  # 8x8
            self._block(features_d * 4, features_d * 8, 4, 2, 1),  # 4x4

            # Dernière couche : réduction à 1x1
            nn.Conv2d(features_d * 8, 1, 4, 1, 0),  # 1x1
            nn.Sigmoid()  # Probabilité
        )

    def _block(self, in_channels, out_channels, kernel_size, stride, padding):
        """Bloc : Conv → BatchNorm → LeakyReLU"""
        return nn.Sequential(
            nn.Conv2d(in_channels, out_channels, kernel_size, stride, padding, bias=False),
            nn.BatchNorm2d(out_channels),
            nn.LeakyReLU(0.2, inplace=True)
        )

    def forward(self, x):
        """
        x: (batch, channels, 64, 64)
        """
        return self.model(x).view(-1, 1).squeeze(1)  # (batch,)

# Instancier les modèles
latent_dim = 100
generator = Generator(latent_dim=latent_dim, channels=3)
discriminator = Discriminator(channels=3)

# Test
z = torch.randn(16, latent_dim, 1, 1)
fake_images = generator(z)
print(f"Generated images shape: {fake_images.shape}")  # (16, 3, 64, 64)

predictions = discriminator(fake_images)
print(f"Discriminator predictions: {predictions.shape}")  # (16,)

Initialisation des poids (important !)

Les GANs sont sensibles à l’initialisation. DCGAN recommande :

def weights_init(m):
    """Initialisation des poids selon DCGAN paper"""
    classname = m.__class__.__name__
    if classname.find('Conv') != -1:
        nn.init.normal_(m.weight.data, 0.0, 0.02)
    elif classname.find('BatchNorm') != -1:
        nn.init.normal_(m.weight.data, 1.0, 0.02)
        nn.init.constant_(m.bias.data, 0)

# Appliquer l'initialisation
generator.apply(weights_init)
discriminator.apply(weights_init)

Boucle d’entraînement DCGAN

import torch.optim as optim
from torchvision import datasets, transforms
from torch.utils.data import DataLoader

# Hyperparamètres
latent_dim = 100
lr = 0.0002
beta1 = 0.5  # Pour Adam (DCGAN paper)
num_epochs = 50
batch_size = 128

# Dataset (exemple : CelebA, CIFAR-10, ou vos données)
transform = transforms.Compose([
    transforms.Resize(64),
    transforms.CenterCrop(64),
    transforms.ToTensor(),
    transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))  # [-1, 1]
])

dataset = datasets.CIFAR10(root="./data", train=True, download=True, transform=transform)
dataloader = DataLoader(dataset, batch_size=batch_size, shuffle=True, num_workers=2)

# Modèles
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
generator = Generator(latent_dim).to(device)
discriminator = Discriminator().to(device)

generator.apply(weights_init)
discriminator.apply(weights_init)

# Optimizers
optimizer_g = optim.Adam(generator.parameters(), lr=lr, betas=(beta1, 0.999))
optimizer_d = optim.Adam(discriminator.parameters(), lr=lr, betas=(beta1, 0.999))

# Loss
criterion = nn.BCELoss()

# Labels
real_label = 1.0
fake_label = 0.0

# Entraînement
for epoch in range(num_epochs):
    for i, (real_images, _) in enumerate(dataloader):
        batch_size = real_images.size(0)
        real_images = real_images.to(device)

        # Labels
        labels_real = torch.full((batch_size,), real_label, device=device)
        labels_fake = torch.full((batch_size,), fake_label, device=device)

        ############################
        # (1) Update Discriminator: maximize log(D(x)) + log(1 - D(G(z)))
        ############################
        optimizer_d.zero_grad()

        # Train sur vraies images
        output_real = discriminator(real_images)
        loss_d_real = criterion(output_real, labels_real)
        loss_d_real.backward()

        # Train sur fausses images
        z = torch.randn(batch_size, latent_dim, 1, 1, device=device)
        fake_images = generator(z)
        output_fake = discriminator(fake_images.detach())  # detach !
        loss_d_fake = criterion(output_fake, labels_fake)
        loss_d_fake.backward()

        # Update D
        loss_d = loss_d_real + loss_d_fake
        optimizer_d.step()

        ############################
        # (2) Update Generator: maximize log(D(G(z)))
        ############################
        optimizer_g.zero_grad()

        # G veut que D classifie ses fakes comme vrais
        output_fake = discriminator(fake_images)  # Pas de detach ici !
        loss_g = criterion(output_fake, labels_real)  # On veut 1 !
        loss_g.backward()

        optimizer_g.step()

        # Logging
        if i % 100 == 0:
            print(f"Epoch [{epoch}/{num_epochs}] Batch [{i}/{len(dataloader)}] "
                  f"Loss D: {loss_d.item():.4f}, Loss G: {loss_g.item():.4f}")

    # Sauvegarder des images générées
    if (epoch + 1) % 5 == 0:
        with torch.no_grad():
            z_fixed = torch.randn(64, latent_dim, 1, 1, device=device)
            fake_images = generator(z_fixed).cpu()

            # Visualiser avec torchvision.utils.make_grid
            from torchvision.utils import save_image
            save_image(fake_images, f"generated_epoch_{epoch+1}.png",
                      normalize=True, nrow=8)
⚠️ Warning
Detach Important : Lors de l’entraînement du discriminateur sur les fausses images, utilisez fake_images.detach() pour empêcher les gradients de remonter vers le générateur. Sinon, vous mettrez à jour G pendant l’optimisation de D !

Challenges et techniques de stabilisation

Problèmes courants des GANs

1. Mode Collapse

Le générateur produit toujours les mêmes images (diversité nulle)
Cause : G trouve un "truc" pour tromper D et s'y tient

2. Non-Convergence

Loss D → 0, Loss G → ∞ (ou l'inverse)
G et D ne trouvent pas d'équilibre

3. Vanishing Gradients

Si D est trop bon, il renvoie toujours 0 pour les fakes
→ log(D(G(z))) = -∞, pas de gradient pour G

Solutions et best practices

1. Utiliser Wasserstein GAN (WGAN)

Remplacer BCELoss par Wasserstein distance :

# WGAN Loss (pas de sigmoid dans D !)
class Discriminator_WGAN(nn.Module):
    # ... même architecture mais SANS Sigmoid en sortie

def train_wgan():
    # Loss pour D : maximiser D(x) - D(G(z))
    loss_d = -torch.mean(discriminator(real_images)) + torch.mean(discriminator(fake_images))

    # Loss pour G : maximiser D(G(z))
    loss_g = -torch.mean(discriminator(fake_images))

    # Weight clipping pour D (WGAN original)
    for p in discriminator.parameters():
        p.data.clamp_(-0.01, 0.01)

WGAN-GP (Gradient Penalty) est encore meilleur :

def gradient_penalty(discriminator, real_images, fake_images, device):
    """Calcul du gradient penalty pour WGAN-GP"""
    batch_size = real_images.size(0)

    # Interpolation aléatoire entre real et fake
    alpha = torch.rand(batch_size, 1, 1, 1, device=device)
    interpolates = (alpha * real_images + (1 - alpha) * fake_images).requires_grad_(True)

    # Passer par D
    d_interpolates = discriminator(interpolates)

    # Calculer les gradients
    gradients = torch.autograd.grad(
        outputs=d_interpolates,
        inputs=interpolates,
        grad_outputs=torch.ones_like(d_interpolates),
        create_graph=True,
        retain_graph=True
    )[0]

    # Flatten
    gradients = gradients.view(batch_size, -1)

    # Pénalité : ||gradient|| doit être proche de 1
    gradient_penalty = ((gradients.norm(2, dim=1) - 1) ** 2).mean()
    return gradient_penalty

# Dans la boucle d'entraînement
gp = gradient_penalty(discriminator, real_images, fake_images.detach(), device)
loss_d = -torch.mean(discriminator(real_images)) + torch.mean(discriminator(fake_images)) + 10 * gp

2. Label Smoothing

# Au lieu de labels 0 et 1 parfaits
real_label = 0.9  # Légèrement < 1
fake_label = 0.1  # Légèrement > 0

3. Feature Matching

Entraîner G à matcher les statistiques des features de D plutôt que de tromper D directement :

# Dans D, retourner aussi les features intermédiaires
features_real = discriminator.get_features(real_images)
features_fake = discriminator.get_features(fake_images)

loss_g = nn.MSELoss()(features_fake, features_real.detach())

4. Progressive Growing

Commencer avec de petites images (4×4), puis progressivement augmenter la résolution (8×8, 16×16, …, 1024×1024). Utilisé par StyleGAN.

5. Spectral Normalization

Normaliser les poids de D pour contrôler la fonction lipschitz :

from torch.nn.utils import spectral_norm

class Discriminator_SN(nn.Module):
    def __init__(self):
        super().__init__()
        # Appliquer spectral norm à chaque Conv
        self.conv1 = spectral_norm(nn.Conv2d(3, 64, 4, 2, 1))
        # ...
🔎 Tip
Hyperparamètres recommandés : Learning rate faible (1e-4 à 2e-4), Adam avec beta1=0.5, entraîner D plus souvent que G (ratio 1:1 à 5:1), utiliser BatchNorm, monitorer à la fois loss_d et loss_g.

VAE : Variational Autoencoder

Les VAEs (Kingma & Welling, 2013) sont une alternative aux GANs basée sur un encodeur-décodeur probabiliste.

Architecture VAE

Image  [Encoder]  μ, σ  z ~ N(μ, σ²)  [Decoder]  Image reconstruite

Différence avec autoencoder classique :

  • Autoencoder : encoder → z (déterministe) → decoder
  • VAE : encoder → (μ, σ) → z ~ N(μ, σ²) → decoder (probabiliste !)

Fonction de Loss VAE

Deux composantes :

  1. Reconstruction Loss : L’image reconstruite doit ressembler à l’originale

    L_recon = ||x - x̂||²  (MSE) ou BCE
    
  2. KL Divergence : L’espace latent doit suivre N(0, 1)

    L_KL = KL(N(μ, σ²) || N(0, 1))
         = -0.5 * Σ(1 + log(σ²) - μ² - σ²)
    

Loss totale :

L = L_recon + β * L_KL  (β = 1 en théorie, souvent ajusté)

Implémentation VAE

class VAE(nn.Module):
    def __init__(self, input_dim=784, hidden_dim=400, latent_dim=20):
        super().__init__()

        # Encoder
        self.fc1 = nn.Linear(input_dim, hidden_dim)
        self.fc_mu = nn.Linear(hidden_dim, latent_dim)     # Moyenne
        self.fc_logvar = nn.Linear(hidden_dim, latent_dim) # Log-variance

        # Decoder
        self.fc3 = nn.Linear(latent_dim, hidden_dim)
        self.fc4 = nn.Linear(hidden_dim, input_dim)

    def encode(self, x):
        """Encoder : x → (μ, log(σ²))"""
        h = torch.relu(self.fc1(x))
        mu = self.fc_mu(h)
        logvar = self.fc_logvar(h)
        return mu, logvar

    def reparameterize(self, mu, logvar):
        """Reparameterization trick : z = μ + σ * ε, ε ~ N(0,1)"""
        std = torch.exp(0.5 * logvar)
        eps = torch.randn_like(std)
        z = mu + eps * std
        return z

    def decode(self, z):
        """Decoder : z → x̂"""
        h = torch.relu(self.fc3(z))
        x_recon = torch.sigmoid(self.fc4(h))  # [0,1] pour images
        return x_recon

    def forward(self, x):
        mu, logvar = self.encode(x)
        z = self.reparameterize(mu, logvar)
        x_recon = self.decode(z)
        return x_recon, mu, logvar

def vae_loss(x_recon, x, mu, logvar):
    """Loss VAE : reconstruction + KL divergence"""
    # Reconstruction loss (BCE pour images binaires)
    BCE = nn.functional.binary_cross_entropy(x_recon, x, reduction='sum')

    # KL divergence
    KLD = -0.5 * torch.sum(1 + logvar - mu.pow(2) - logvar.exp())

    return BCE + KLD

# Entraînement
vae = VAE(input_dim=28*28, hidden_dim=400, latent_dim=20)
optimizer = optim.Adam(vae.parameters(), lr=1e-3)

# MNIST
from torchvision import datasets, transforms
dataset = datasets.MNIST('./data', train=True, download=True,
                        transform=transforms.ToTensor())
dataloader = DataLoader(dataset, batch_size=128, shuffle=True)

vae.train()
for epoch in range(10):
    total_loss = 0
    for batch_idx, (x, _) in enumerate(dataloader):
        x = x.view(-1, 28*28)  # Flatten

        optimizer.zero_grad()
        x_recon, mu, logvar = vae(x)
        loss = vae_loss(x_recon, x, mu, logvar)
        loss.backward()
        optimizer.step()

        total_loss += loss.item()

    avg_loss = total_loss / len(dataloader.dataset)
    print(f'Epoch {epoch+1}, Loss: {avg_loss:.4f}')

# Génération de nouvelles images
with torch.no_grad():
    z = torch.randn(64, 20)  # Sample depuis N(0,1)
    generated = vae.decode(z).view(-1, 1, 28, 28)
    # Visualiser generated

VAE vs GAN

CaractéristiqueVAEGAN
EntraînementStable, fonction de loss claireDifficile, équilibre fragile
Qualité imagesParfois flouesTrès nettes et réalistes
DiversitéBonne (explore tout l’espace latent)Risque de mode collapse
InterpolationLisse et significativeParfois instable
Cas d’usageCompression, interpolation, apprentissage de représentationsGénération haute qualité, super-resolution

Diffusion Models : L’état de l’art

Les Diffusion Models (DDPM, Stable Diffusion, DALL-E 2/3) sont devenus la référence pour la génération d’images haute qualité.

Principe

Forward Process (Diffusion) : Ajouter progressivement du bruit à une image

x₀ → x₁ → x₂ → ... → x_T (bruit pur)

Reverse Process (Denoising) : Apprendre à enlever le bruit étape par étape

x_T → x_{T-1} → ... → x₁ → x₀ (image propre)

Architecture simplifiée

Le modèle apprend à prédire le bruit ajouté :

class SimpleDiffusion(nn.Module):
    def __init__(self, image_size=28, channels=1, time_emb_dim=128):
        super().__init__()
        # UNet-like architecture
        # Input: image bruitée + timestep
        # Output: bruit prédit
        pass  # Implémentation complexe (voir Stable Diffusion)

    def forward(self, x_noisy, t):
        """Prédire le bruit à l'étape t"""
        # x_noisy: image bruitée
        # t: timestep (0 à T)
        noise_pred = self.unet(x_noisy, t)
        return noise_pred

Utiliser Stable Diffusion avec PyTorch

from diffusers import StableDiffusionPipeline
import torch

# Charger le modèle (nécessite ~4-7 GB VRAM)
model_id = "stabilityai/stable-diffusion-2-1"
pipe = StableDiffusionPipeline.from_pretrained(model_id, torch_dtype=torch.float16)
pipe = pipe.to("cuda")

# Génération
prompt = "A beautiful landscape with mountains and a lake, photorealistic, 4k"
negative_prompt = "blurry, low quality, distorted"

image = pipe(
    prompt,
    negative_prompt=negative_prompt,
    num_inference_steps=50,  # Plus = meilleure qualité mais plus lent
    guidance_scale=7.5,       # Contrôle l'adhérence au prompt
    height=512,
    width=512
).images[0]

image.save("generated_landscape.png")

# Image-to-Image
from diffusers import StableDiffusionImg2ImgPipeline
from PIL import Image

pipe_img2img = StableDiffusionImg2ImgPipeline.from_pretrained(
    model_id, torch_dtype=torch.float16
).to("cuda")

init_image = Image.open("photo.jpg").resize((512, 512))

image = pipe_img2img(
    prompt="Transform into oil painting style",
    image=init_image,
    strength=0.75,  # 0 = identique, 1 = complètement nouveau
    guidance_scale=7.5
).images[0]

image.save("oil_painting.png")

Applications Pratiques

Génération d’images conditionnelles (cGAN)

class ConditionalGenerator(nn.Module):
    def __init__(self, latent_dim, num_classes, embed_dim=50):
        super().__init__()
        # Embedding pour la classe
        self.label_embedding = nn.Embedding(num_classes, embed_dim)

        # Générateur avec condition
        self.model = nn.Sequential(
            nn.Linear(latent_dim + embed_dim, 256),
            nn.ReLU(),
            nn.Linear(256, 512),
            nn.ReLU(),
            nn.Linear(512, 784),
            nn.Tanh()
        )

    def forward(self, z, labels):
        """
        z: (batch, latent_dim)
        labels: (batch,) - classe souhaitée
        """
        # Combiner bruit et classe
        label_emb = self.label_embedding(labels)
        gen_input = torch.cat([z, label_emb], dim=1)

        # Générer
        img = self.model(gen_input)
        return img.view(-1, 1, 28, 28)

# Utilisation : générer un "3" MNIST
z = torch.randn(1, 100)
labels = torch.tensor([3])  # Classe 3
img = conditional_gen(z, labels)

Style Transfer (CycleGAN)

CycleGAN permet de transformer des images d’un domaine A vers B sans paires d’entraînement :

Photo → Peinture
Cheval → Zèbre
Été → Hiver

Principe : Deux générateurs (G: A→B, F: B→A) et deux discriminateurs.

Cycle Consistency Loss :

F(G(x))  x  (aller-retour doit reconstruire l'original)
G(F(y))  y

Super-Resolution (SRGAN)

Améliorer la résolution d’images basse qualité :

class SRResNet(nn.Module):
    """Générateur pour super-resolution (×4)"""
    def __init__(self, num_residual_blocks=16):
        super().__init__()
        # Extraction de features
        self.conv1 = nn.Conv2d(3, 64, 9, padding=4)

        # Residual blocks
        self.res_blocks = nn.Sequential(
            *[ResidualBlock(64) for _ in range(num_residual_blocks)]
        )

        # Upsampling (×2 puis ×2 = ×4)
        self.upsample = nn.Sequential(
            UpsampleBlock(64, 2),  # ×2
            UpsampleBlock(64, 2),  # ×2
        )

        # Reconstruction
        self.conv_out = nn.Conv2d(64, 3, 9, padding=4)

    def forward(self, x):
        # x: low-res (batch, 3, 64, 64)
        out = self.conv1(x)
        out = self.res_blocks(out)
        out = self.upsample(out)
        out = self.conv_out(out)
        return out  # high-res (batch, 3, 256, 256)

Exercices pratiques

DCGAN sur CIFAR-10

Entraînez un DCGAN pour générer des images CIFAR-10 réalistes.

Objectifs :

  • Implémenter Generator et Discriminator DCGAN
  • Entraîner pendant 50-100 epochs
  • Monitorer et visualiser les images générées tous les 5 epochs
  • Analyser la qualité avec FID (Fréchet Inception Distance)
  • Identifier et résoudre le mode collapse s’il apparaît

Bonus :

  • Essayer WGAN-GP pour comparer
  • Interpolation dans l’espace latent (morphing progressif)

VAE avec interpolation

Créez un VAE pour MNIST et explorez l’espace latent.

Objectifs :

  • Implémenter un VAE complet (encoder, decoder, reparameterization)
  • Visualiser la reconstruction des chiffres
  • Interpolation : prendre deux chiffres (ex: “3” et “8”), interpoler dans l’espace latent, visualiser la transition progressive
  • Visualiser l’espace latent 2D (avec t-SNE ou PCA si latent_dim > 2)
  • Générer des chiffres en “dialing” les dimensions latentes

Conditional GAN pour génération contrôlée

Implémentez un cGAN pour générer des chiffres MNIST spécifiques.

Objectifs :

  • Modifier Generator et Discriminator pour accepter des labels de classe
  • Entraîner le cGAN sur MNIST
  • Démontrer le contrôle : générer spécifiquement “0”, “1”, …, “9”
  • Tester avec des labels incorrects ou interpolés (ex: 0.5 entre classe 3 et 5)

Conclusion

Vous maîtrisez maintenant les modèles génératifs avec PyTorch :

GANs : Architecture adversarial, DCGAN, techniques de stabilisation

VAEs : Espace latent probabiliste, interpolation, reconstruction

Diffusion Models : Principes du denoising, utilisation de Stable Diffusion

Applications : Génération, style transfer, super-resolution

Best Practices : WGAN-GP, spectral norm, label smoothing

Prochaines Étapes

➡️ Prochain article : Déploiement en Production - TorchScript, ONNX, FastAPI, Docker pour mettre vos modèles en production.

Pour aller plus loin :

Les modèles génératifs repoussent les limites de la créativité IA. Vous êtes équipé pour créer du contenu original et réaliste !