Aller au contenu principal

Module 2 — Autoencodeurs et espace latent

Un autoencodeur est le modèle génératif le plus simple qu'on puisse écrire, et c'est justement pour cela qu'on commence par lui : il rend visibles les deux pièces qu'on retrouvera dans un VAE et dans le décodeur d'une diffusion latente. Mais son incapacité à générer proprement révèle le problème central que les modules suivants s'attachent à résoudre.

Deux réseaux qui s'affrontent au milieu

Un autoencodeur enchaîne un encodeur qϕ(zx)q_\phi(z \mid x) qui compresse une donnée xx en un code zz de faible dimension, et un décodeur pθ(xz)p_\theta(x \mid z) qui reconstruit xx à partir de zz. La perte à minimiser est l'erreur de reconstruction :

L(ϕ,θ)=Ex[xpθ(qϕ(x))2]\mathcal{L}(\phi, \theta) = \mathbb{E}_x \left[ \| x - p_\theta(q_\phi(x)) \|^2 \right]

En PyTorch, sur MNIST aplati en vecteurs de 784 valeurs :

import torch
from torch import nn

class Autoencodeur(nn.Module):
def __init__(self, dim_latente=16):
super().__init__()
self.encodeur = nn.Sequential(
nn.Linear(784, 256), nn.ReLU(),
nn.Linear(256, 64), nn.ReLU(),
nn.Linear(64, dim_latente),
)
self.decodeur = nn.Sequential(
nn.Linear(dim_latente, 64), nn.ReLU(),
nn.Linear(64, 256), nn.ReLU(),
nn.Linear(256, 784), nn.Sigmoid(),
)

def forward(self, x):
z = self.encodeur(x)
return self.decodeur(z), z

Le nom goulot d'étranglement vient du fait que dim_latente est petite devant 784. C'est ce rétrécissement qui force le réseau à retenir la structure des chiffres plutôt que leur bruit. Sans lui, l'autoencodeur apprendrait la fonction identité et n'aurait rien à dire.

L'espace latent capture-t-il la structure ?

Une fois l'autoencodeur entraîné pendant une dizaine d'époques sur les 60 000 chiffres d'entraînement, on peut projeter tous les codes latents en deux dimensions (par exemple avec t-SNE ou UMAP) et colorier chaque point selon sa classe. Le résultat est frappant : les dix chiffres forment des amas visibles, alors que le réseau n'a jamais vu les étiquettes. C'est la propriété centrale d'un bon espace latent : la géométrie des codes reflète la sémantique des données.

Cette propriété se vérifie par une expérience simple, l'interpolation latente. Prenons deux images xax_a (un 3) et xbx_b (un 8), encodons-les, tirons une ligne droite entre zaz_a et zbz_b, et décodons chaque point :

za = modele.encodeur(x_a)
zb = modele.encodeur(x_b)
for alpha in torch.linspace(0, 1, steps=10):
z = (1 - alpha) * za + alpha * zb
image = modele.decodeur(z).view(28, 28)

Les images intermédiaires forment une transition continue du 3 vers le 8. Sur les bons codes, la transition est plausible visuellement ; sur d'autres, elle traverse des zones qui ne ressemblent à rien.

Pourquoi un autoencodeur simple ne génère pas

C'est ici que le modèle atteint sa limite. Pour générer un nouveau chiffre, on aimerait tirer un zz au hasard et décoder. Le problème : on ne sait pas d'où tirer zz.

L'encodeur a placé les codes des images d'entraînement dans une région complexe de R16\mathbb{R}^{16}, dont la forme est inconnue. Rien n'a été fait pour la régulariser. Concrètement :

  1. Tirer zz selon une gaussienne standard produit presque toujours un code hors de la région couverte par les images vues à l'entraînement. Le décodeur, appelé sur ces codes, renvoie des images floues, tachées ou reconnaissables mais bizarres.
  2. Même en tirant zz dans l'enveloppe convexe des codes vus, la couverture est inégale : certaines zones sont denses, d'autres vides. Rien ne dit qu'un point choisi uniformément corresponde à un chiffre.
  3. Le décodeur est déterministe : à un zz correspond une image unique. Il n'y a pas de moyen de dire « ce zz n'appartient pas à ma distribution ».
L'autoencodeur n'est pas un modèle génératif

Un autoencodeur entraîné à la reconstruction est un outil de compression et de représentation, pas un générateur. Il donne des reconstructions correctes pour les images qu'on lui présente, mais échantillonner sans information supplémentaire sur p(z)p(z) donne des résultats médiocres. Toute la suite du cours répond à cette limite : les VAE régularisent la distribution latente, les GAN se passent d'encodeur, la diffusion apprend à partir de bruit gaussien pur.

Deux variantes utiles qui restent des autoencodeurs

Deux modifications simples élargissent l'usage sans changer la structure du modèle. L'autoencodeur débruiteur reçoit x~=x+ε\tilde x = x + \varepsilon en entrée et doit prédire xx propre en sortie. La perte de reconstruction est calculée par rapport à xx, pas à x~\tilde x. Le réseau apprend à ignorer le bruit et devient plus robuste. Un cas particulier : masquer aléatoirement 30 % des pixels et demander de les reconstruire. Cette variante est la brique de base des modèles masqués modernes.

L'autoencodeur contractif ajoute à la perte un terme qui pénalise la sensibilité de l'encodeur aux variations locales de xx. Il pousse le réseau à ne retenir que les directions qui comptent, en supprimant les variations liées au bruit d'acquisition.

Aucune de ces deux variantes ne résout le problème de génération : elles produisent de meilleures représentations, mais l'espace latent reste tout aussi mal contraint.

Comment évaluer un autoencodeur

Trois indicateurs suffisent pour juger utile un autoencodeur avant de passer à la suite :

  • L'erreur de reconstruction sur un jeu de validation. Elle décroît avec la dimension latente, mais un plateau apparaît, souvent autour de 16 à 32 dimensions pour MNIST. Au-delà, on paie de la capacité sans gagner en fidélité.
  • La visualisation par projection 2D des codes de validation, coloriée par classe. Des amas nets signalent que la représentation apprise est sémantiquement organisée.
  • Une petite tâche discriminative en aval : entraîner une régression logistique sur les zz pour prédire la classe. Un bon autoencodeur donne 90 % ou plus avec un classifieur linéaire, alors que les pixels bruts stagnent nettement plus bas.

C'est cette dernière évaluation qui a fait le succès des autoencodeurs comme pré-entraînement entre 2006 et 2012, avant que la disponibilité de grands jeux étiquetés ne rende cette étape moins critique.

En résumé

  • Un autoencodeur enchaîne encodeur et décodeur autour d'un goulot latent de faible dimension, et minimise l'erreur de reconstruction.
  • L'espace latent bien entraîné capture la sémantique : les classes forment des amas et l'interpolation entre deux codes reste plausible visuellement.
  • Il ne génère pas correctement : rien n'a été fait pour régulariser la distribution des codes, donc tirer zz au hasard produit des images médiocres.
  • Les variantes débruiteur et contractif améliorent la représentation mais n'y changent rien ; la solution est structurelle et vient au module suivant avec les VAE.

Module suivant : les autoencodeurs variationnels, qui forcent la distribution latente à ressembler à une gaussienne et rendent l'échantillonnage possible.