GANs et modèles génératifs : Créer du contenu avec PyTorch
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.

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é
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
GANs : Deux réseaux en compétition (générateur vs discriminateur)
- ✅ Images ultra-réalistes, haute résolution
- ❌ Difficiles à entraîner, mode collapse
VAEs : Encodeur-décodeur avec espace latent probabiliste
- ✅ Entraînement stable, interpolation lisse
- ❌ Images parfois floues
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
- Remplacer les couches fully-connected par des convolutions
- Batch Normalization dans G et D (sauf première/dernière couche)
- Pas de pooling : utiliser strided convolutions (D) et transposed convolutions (G)
- Activations : ReLU dans G (Tanh en sortie), LeakyReLU dans D
- 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)
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))
# ...
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 :
Reconstruction Loss : L’image reconstruite doit ressembler à l’originale
L_recon = ||x - x̂||² (MSE) ou BCEKL 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éristique | VAE | GAN |
|---|---|---|
| Entraînement | Stable, fonction de loss claire | Difficile, équilibre fragile |
| Qualité images | Parfois floues | Très nettes et réalistes |
| Diversité | Bonne (explore tout l’espace latent) | Risque de mode collapse |
| Interpolation | Lisse et significative | Parfois instable |
| Cas d’usage | Compression, interpolation, apprentissage de représentations | Gé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 :
- GAN Lab : Visualisation interactive des GANs
- StyleGAN2 Paper : Architecture state-of-the-art pour génération haute résolution
- Stable Diffusion : Code source et modèles
- Papers With Code - Generative Models : Dernières avancées
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 !