GAN (Generative Adversarial Network) : Guide Complet — Réseaux Antagonistes Génératifs

GAN (Generative Adversarial Network) : Guide Complet — Réseaux Antagonistes Génératifs

Generative Adversarial Network — Guide Complet

Résumé

Les Generative Adversarial Networks (GANs), ou réseaux antagonistes génératifs, constituent l’une des architectures d’apprentissage profond les plus puissantes et les plus créatives jamais inventées. Proposés par Ian Goodfellow et ses collaborateurs en 2014, les GANs reposent sur un principe élégant : mettre en compétition deux réseaux de neurones — un générateur et un discriminateur — pour produire des données artificielles d’une qualité remarquable. Depuis leur introduction, les GANs ont révolutionné la génération d’images, la synthèse de données, le transfert de style et de nombreux autres domaines de l’intelligence artificielle.

Contrairement aux approches génératives classiques comme les VAE (Variational Autoencoders) qui maximisent une borne inférieure de la vraisemblance, les GANs adoptent une approche compétitive inspirée de la théorie des jeux. Cette approche leur permet de produire des échantillons d’une netteté exceptionnelle, sans le flou caractéristique des autres méthodes génératives. Ce guide complet vous expliquera le fonctionnement mathématique, l’intuition fondamentale et l’implémentation pratique des GANs en Python avec Keras.

Principe Mathématique du Generative Adversarial Network

Les Deux Acteurs

Un GAN repose sur deux réseaux de neurones aux rôles complémentaires mais antagonistes :

Le Générateur G : Ce réseau prend en entrée un vecteur de bruit aléatoire z tiré d’une distribution simple (généralement une gaussienne ou une uniforme) et produit une donnée de synthèse x_fake = G(z). Son objectif est de générer des échantillons si réalistes que le discriminateur ne puisse pas les distinguer de vraies données.

Le Discriminateur D : Ce réseau reçoit en entrée une donnée x (soit une vraie donnée issue du jeu de données, soit une fausse donnée produite par le générateur) et retourne une probabilité D(x) ∈ [0, 1]. Une valeur proche de 1 signifie que le discriminateur pense que la donnée est réelle ; une valeur proche de 0 signifie qu’il pense qu’elle est fausse.

La Fonction de Coût Minimax

L’entraînement d’un Generative Adversarial Network se formule comme un jeu à somme nulle entre G et D. La fonction de valeur s’écrit :

V(D, G) = E_{x~p_réelle}[log(D(x))] + E_{z~p_bruit}[log(1 - D(G(z)))]

L’objectif global est un problème minimax :

min_G max_D V(D, G) = min_G max_D { E_{x~p_réelle}[log(D(x))] + E_{z~p_bruit}[log(1 - D(G(z)))] }

Le discriminateur D cherche à maximiser cette fonction : il veut attribuer une probabilité élevée aux vraies données (D(x) proche de 1) et une probabilité faible aux fausses données (D(G(z)) proche de 0). Inversement, le générateur G cherche à minimiser cette fonction : il veut que D(G(z)) soit proche de 1, c’est-à-dire que le discriminateur soit trompé.

Entraînement Alterné

En pratique, on ne résout pas ce problème de manière simultanée mais par entraînement alterné :

  1. Phase 1 — Entraîner le discriminateur (G figé) : On présente au discriminateur un mélange de vraies images (étiquetées 1) et de fausses images produites par G (étiquetées 0). On calcule la perte binaire croisée et on met à jour uniquement les poids de D. Le générateur est gelé pendant cette phase.
  2. Phase 2 — Entraîner le générateur (D figé) : On génère de fausses données avec G, on les soumet au discriminateur, et on calcule la perte du générateur. L’astuce consiste à étiqueter ces fausses données comme 1 : on demande au générateur de produire des données dont le discriminateur dira « c’est réel ». On met à jour uniquement les poids de G. Le discriminateur est gelé pendant cette phase.

On répète ces deux phases de manière itérative sur des milliers d’époques. À l’équilibre théorique (équilibre de Nash), le discriminateur retourne D(x) = 0.5 pour toute donnée (il est incapable de distinguer le vrai du faux), et le générateur reproduit parfaitement la distribution réelle.

Problèmes d’Entraînement

L’entraînement des GANs est notoirement instable. Plusieurs phénomènes problématiques apparaissent fréquemment :

  • Mode collapse : Le générateur apprend à produire un seul type de sortie qui trompe systématiquement le discriminateur, au lieu de capturer la diversité de la distribution réelle. Toutes les images générées se ressemblent.
  • Vanishing gradients : Si le discriminateur devient trop performant trop rapidement, les gradients qui remontent au générateur deviennent minuscules, empêchant toute amélioration.
  • Oscillations : Les deux réseaux peuvent osciller sans jamais converger vers un équilibre stable.

Ces défis ont motivé de nombreuses variantes architecturales : DCGAN, WGAN, WGAN-GP, PGAN, StyleGAN, etc.

Intuition : Le Faussaire et l’Expert

Pour comprendre l’essence d’un Generative Adversarial Network sans se perdre dans les mathématiques, imaginez cette scène :

Un faussaire tente de copier des tableaux de maîtres. Au début, ses copies sont grossières et facilement détectables. Un expert en art examine ses productions, pointe les défauts : les couleurs ne sont pas justes, le trait est maladroit, la composition ne respecte pas les canons de l’époque.

Le faussaire apprend de ces critiques et améliore sa technique. Il étudie les coups de pinceau, mélange les pigments avec plus de précision, copie les textures du support. L’expert, face à ces nouvelles copies améliorées, doit affiner sa propre expertise pour continuer à détecter les falsifications. Il apprend à repérer des indices plus subtils : la signature microscopique, la composition chimique des pigments, les micro-fissures du vernis.

Ce cycle se répète indéfiniment. Le faussaire devient de plus en plus habile. L’expert de plus en plus perspicace. Jusqu’au moment où — théoriquement — le faussaire atteint un niveau tel que même l’expert ne peut plus distinguer l’original de la copie. À ce stade, le faussaire a capturé l’essence même du style des maîtres.

Dans cette analogie, le faussaire est le générateur, l’expert est le discriminateur, et les tableaux sont les données. La compétition mutuelle pousse les deux acteurs à se surpasser constamment, produisant finalement un générateur capable de créer des données d’un réalisme remarquable. C’est cette dynamique antagoniste qui donne leur puissance aux GANs.

Implémentation Python — DCGAN sur MNIST avec Keras

Nous allons implémenter un DCGAN (Deep Convolutional GAN), la variante de Radford et al. (2015) qui utilise des couches convolutionnelles transposées dans le générateur et des convolutions standards dans le discriminateur. Notre jeu de données sera MNIST, le célèbre corpus de chiffres manuscrits.

Installation des Dépendances

pip install tensorflow keras matplotlib numpy

Code Complet du DCGAN

import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
import matplotlib.pyplot as plt
import numpy as np
import os

# ============================================================
# Configuration
# ============================================================
latent_dim = 128          # Dimension de l'espace latent
learning_rate = 0.0002    # Taux d'apprentissage (Adam)
beta_1 = 0.5              # Premier moment d'Adam (standard pour les GANs)
batch_size = 64           # Taille du lot
n_epochs = 50             # Nombre d'époques
img_shape = (28, 28, 1)   # Format des images MNIST

# Charger les données MNIST
(x_train, _), (_, _) = tf.keras.datasets.mnist.load_data()
x_train = x_train.astype('float32') / 255.0
x_train = np.expand_dims(x_train, axis=-1)

# ============================================================
# Générateur
# ============================================================
def build_generator():
    model = keras.Sequential([
        layers.Dense(7 * 7 * 128, use_bias=False, input_shape=(latent_dim,)),
        layers.BatchNormalization(),
        layers.LeakyReLU(alpha=0.2),

        layers.Reshape((7, 7, 128)),

        layers.Conv2DTranspose(64, (5, 5), strides=(2, 2),
                               padding='same', use_bias=False),
        layers.BatchNormalization(),
        layers.LeakyReLU(alpha=0.2),

        layers.Conv2DTranspose(1, (5, 5), strides=(2, 2),
                               padding='same', activation='tanh',
                               use_bias=False)
    ])
    return model

# ============================================================
# Discriminateur
# ============================================================
def build_discriminator():
    model = keras.Sequential([
        layers.Conv2D(64, (5, 5), strides=(2, 2), padding='same',
                      input_shape=img_shape),
        layers.LeakyReLU(alpha=0.2),
        layers.Dropout(0.3),

        layers.Conv2D(128, (5, 5), strides=(2, 2), padding='same'),
        layers.LeakyReLU(alpha=0.2),
        layers.Dropout(0.3),

        layers.Flatten(),
        layers.Dense(1, activation='sigmoid')
    ])
    return model

# ============================================================
# Construction du modèle
# ============================================================
generator = build_generator()
discriminator = build_discriminator()

# Optimiseurs (beta_1 = 0.5 est crucial pour la stabilité des GANs)
gen_optimizer = keras.optimizers.Adam(learning_rate=learning_rate, beta_1=beta_1)
disc_optimizer = keras.optimizers.Adam(learning_rate=learning_rate, beta_1=beta_1)

# Fonction de perte binaire croisée
cross_entropy = keras.losses.BinaryCrossentropy()

# ============================================================
# Fonctions de perte
# ============================================================
def discriminator_loss(real_output, fake_output):
    real_loss = cross_entropy(tf.ones_like(real_output), real_output)
    fake_loss = cross_entropy(tf.zeros_like(fake_output), fake_output)
    return real_loss + fake_loss

def generator_loss(fake_output):
    # Le générateur veut que le discriminateur classe ses images comme réelles (1)
    return cross_entropy(tf.ones_like(fake_output), fake_output)

# ============================================================
# Boucle d'entraînement
# ============================================================
@tf.function
def train_step(images):
    noise = tf.random.normal([batch_size, latent_dim])

    # --- Phase 1 : Entraîner le discriminateur ---
    with tf.GradientTape() as disc_tape:
        generated_images = generator(noise, training=True)
        real_output = discriminator(images, training=True)
        fake_output = discriminator(generated_images, training=True)
        disc_loss = discriminator_loss(real_output, fake_output)

    gradients_of_discriminator = disc_tape.gradient(
        disc_loss, discriminator.trainable_variables)
    disc_optimizer.apply_gradients(
        zip(gradients_of_discriminator, discriminator.trainable_variables))

    # --- Phase 2 : Entraîner le générateur ---
    with tf.GradientTape() as gen_tape:
        generated_images = generator(noise, training=True)
        fake_output = discriminator(generated_images, training=True)
        gen_loss = generator_loss(fake_output)

    gradients_of_generator = gen_tape.gradient(
        gen_loss, generator.trainable_variables)
    gen_optimizer.apply_gradients(
        zip(gradients_of_generator, generator.trainable_variables))

    return gen_loss, disc_loss

# ============================================================
# Visualisation de la progression
# ============================================================
def generate_and_save_images(model, epoch, test_input):
    predictions = model(test_input, training=False)
    fig = plt.figure(figsize=(4, 4))
    for i in range(predictions.shape[0]):
        plt.subplot(4, 4, i+1)
        plt.imshow((predictions[i, :, :, 0] + 1) / 2.0, cmap='gray')
        plt.axis('off')
    plt.suptitle(f'Époque {epoch}')
    plt.savefig(f'generated_images/epoch_{epoch:04d}.png')
    plt.close()

# Graine fixe pour suivre la progression
seed = tf.random.normal([16, latent_dim])

# Répertoire de sauvegarde
os.makedirs('generated_images', exist_ok=True)

# ============================================================
# Entraînement principal
# ============================================================
dataset = tf.data.Dataset.from_tensor_slices(x_train).shuffle(60000).batch(batch_size)

for epoch in range(n_epochs):
    gen_loss_total = 0.0
    disc_loss_total = 0.0
    num_batches = 0

    for images in dataset:
        # Normaliser les images dans [-1, 1] pour la sortie tanh du générateur
        images = images * 2.0 - 1.0

        gen_loss, disc_loss = train_step(images)
        gen_loss_total += gen_loss
        disc_loss_total += disc_loss
        num_batches += 1

    if (epoch + 1) % 5 == 0:
        avg_gen = gen_loss_total / num_batches
        avg_disc = disc_loss_total / num_batches
        print(f'Époque {epoch+1}/{n_epochs} | '
              f'Perte G: {avg_gen:.4f} | Perte D: {avg_disc:.4f}')
        generate_and_save_images(generator, epoch + 1, seed)

# Sauvegarde finale des poids
generator.save('generator_final.keras')
discriminator.save('discriminator_final.keras')
print('Entraînement terminé ! Modèles sauvegardés.')

Points Clés de l’Implémentation

BatchNormalization dans le générateur : essentielle pour stabiliser l’entraînement des GANs convolutionnels. Elle normalise les activations de chaque couche, ce qui réduit la sensibilité à l’initialisation des poids.

LeakyReLU au lieu de ReLU classique : la pente négative non nulle (α = 0.2) évite le problème des « neurones morts » où certains neurones ne s’activent plus jamais. Ceci est particulièrement important dans le discriminateur.

tf.GradientTape séparés : on utilise deux contextes de gradient distincts pour calculer indépendamment les gradients du générateur et du discriminateur, reflétant parfaitement la nature alternée de l’entraînement.

@tf.function : ce décorateur compile la fonction en un graphe statique, accélérant considérablement l’entraînement (jusqu’à 3x sur GPU).

Détecter et Gérer le Mode Collapse

Le mode collapse se détecte visuellement : si toutes les images générées se ressemblent ou convergent vers un même chiffre, le générateur a « trouvé une faille » dans le discriminateur. Voici quelques stratégies de mitigation :

# 1. Ajouter du lissage d'étiquettes au discriminateur (label smoothing)
# Au lieu d'étiqueter les vraies images à 1.0, utiliser 0.9
real_labels = tf.ones_like(real_output) * 0.9

# 2. Augmenter le dropout dans le discriminateur
layers.Dropout(0.5)  # au lieu de 0.3

# 3. Réduire le taux d'apprentissage
learning_rate = 0.0001

# 4. Utiliser WGAN-GP au lieu de la perte binaire standard
# La distance de Wasserstein fournit des gradients plus stables

Hyperparamètres du Generative Adversarial Network

Le réglage des hyperparamètres est critique pour obtenir un GAN fonctionnel. Voici les valeurs typiques et leur rôle :

Hyperparamètre Valeur typique Rôle
latent_dim 100–256 Dimension de l’espace latent. Un vecteur plus grand permet plus de diversité mais augmente le temps d’entraînement et le risque de produire du bruit. 128 est un bon compromis pour MNIST.
learning_rate 0.0001–0.0002 Les GANs nécessitent des taux d’apprentissage faibles. Trop élevé → instabilité et divergence. Trop faible → convergence lente ou stagnation.
beta_1 (Adam) 0.5 Le moment de premier ordre d’Adam. La valeur par défaut de 0.9 est trop élevée pour les GANs ; 0.5 améliore la stabilité en réduisant l’inertie de l’optimiseur.
batch_size 32–128 Influence la stabilité des gradients. Un lot plus grand donne des mises à jour plus stables mais nécessite plus de mémoire GPU. 64 est un choix équilibré.
n_epochs 50–500 Dépend de la complexité des données. MNIST converge en ~50 époques, tandis que CIFAR-10 peut nécessiter plusieurs centaines.

Recommandations de Réglage

  • Commencez petit : entraînez d’abord sur un sous-ensemble des données pour valider l’architecture.
  • Surveillez les pertes : si la perte du discriminateur tend vers 0 tandis que celle du générateur explose, le discriminateur est trop fort. Si l’inverse se produit, c’est le générateur qui domine. L’idéal est un équilibre relatif.
  • Visualisez régulièrement : la perte ne dit pas tout. Regardez les images générées toutes les 5 ou 10 époques pour détecter le mode collapse ou la dégradation de la qualité.
  • Utilisez un seed fixe : pour évaluer l’amélioration au fil des époques, générez toujours les mêmes images de test avec la même graine aléatoire.

Avantages et Limites des GANs

Avantages

Qualité de génération exceptionnelle : Les GANs produisent les images générées les plus nettes et les plus réalistes parmi toutes les approches génératives. Contrairement aux VAEs qui tendent à produire des images floues, les GANs capturent les détails fins et les textures complexes.

Pas de fonction de vraisemblance explicite : Les GANs n’ont pas besoin de modéliser explicitement la distribution de probabilité des données. Cela les rend applicables à des distributions complexes et multimodales où le calcul de vraisemblance serait impossible.

Apprentissage non supervisé : Les GANs n’ont besoin que de données brutes, sans étiquettes. Ils apprennent la structure sous-jacente des données de manière entièrement autonome.

Flexibilité architecturale : Le cadre général des GANs permet de nombreuses variantes adaptées à des tâches spécifiques : cGANs (conditionnels), CycleGAN (transfert de style), StyleGAN (contrôle fin de la génération), etc.

Limites

Instabilité d’entraînement : C’est le principal défaut des GANs. L’entraînement est sensible à l’initialisation, aux hyperparamètres et à l’architecture. Obtenir un modèle qui converge nécessite souvent de nombreux essais-erreurs.

Mode collapse : Le générateur peut apprendre à produire un nombre limité de sorties au lieu de capturer toute la diversité de la distribution. Le discriminateur est alors « piégé » et ne fournit plus de signal d’apprentissage utile.

Évaluation difficile : Contrairement aux modèles de classification où la précision suffit, évaluer la qualité et la diversité des données générées est complexe. Les métriques comme l’IS (Inception Score) et le FID (Fréchet Inception Distance) sont approximatives et coûteuses à calculer.

Biais et éthique : Les GANs amplifient les biais présents dans les données d’entraînement. De plus, leur capacité à produire des deepfakes soulève des questions éthiques majeures sur la désinformation et le consentement.

4 Cas d’Usage Concrets des GANs

1. Génération d’Images Réalistes et Synthèse de Données

Les GANs peuvent générer des visages humains photoréalistes qui n’existent pas. Des modèles comme StyleGAN de NVIDIA produisent des portraits d’une qualité telle qu’il est impossible de les distinguer de photographies réelles. Cette capacité est également utilisée pour augmenter les jeux de données en médecine : on génère des images IRM artificielles pour enrichir l’entraînement des modèles de diagnostic sans compromettre la vie privée des patients.

2. Transfert de Style avec CycleGAN

CycleGAN permet de transférer le style entre deux domaines d’images sans paires appariées. Exemples célèbres : transformer des photos de chevaux en zèbres, des paysages d’été en paysages d’hiver, ou des peintures en photographies. Cette technique est utilisée dans l’industrie du divertissement pour la retouche photographique automatique, dans la mode pour visualiser des vêtements dans différents contextes, et dans l’urbanisme pour simuler l’impact visuel de projets architecturaux.

3. Super-Résolution d’Images

Les GANs de super-résolution (comme SRGAN) prennent une image basse résolution et la reconstruisent en haute résolution en hallucinant les détails manquants de manière cohérente. Contrairement aux méthodes d’interpolation traditionnelles qui produisent des images floues, les GANs ajoutent des textures et des contours plausibles. Applications : amélioration de vieilles photographies, upscaling de vidéos, imagerie médicale et astronomique.

4. Anonymisation et Protection de la Vie Privée

Une application émergente et socialement responsable des GANs consiste à générer des données synthétiques qui préservent les propriétés statistiques des données originales tout en supprimant toute information identifiable. Des entreprises génèrent des données financières, médicales ou comportementales synthétiques pour partager des jeux de données de recherche sans violer le RGPD. Les GANs apprennent les corrélations complexes entre les variables et les reproduisent dans des données entièrement artificielles.

Voir Aussi


Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur la façon dont les données de vos commentaires sont traitées.