LangueFR
Français
English
Retour au sommaire du cours

Deep learning

Le deep learning n'est pas un meilleur machine learning : c'est une famille de modèles qui apprennent leurs propres représentations, ce qui les rend imbattables là où les données brutes sont structurées et abondantes — images, son, texte, signaux — et souvent décevants ailleurs. Ce module couvre les architectures, les recettes d'entraînement qui font réellement la différence, et les PINN, qui relient réseaux de neurones et équations aux dérivées partielles.

1. Quand le deep learning est pertinent

arbitrage
Le deep learning gagne quand...          Le ML classique gagne quand...
──────────────────────────────────────────────────────────────────────────────
données brutes structurées               données tabulaires hétérogènes
(pixels, audio, texte, séquences)        (le boosting reste devant, module 14)
beaucoup de données (10⁴ – 10⁹)          peu de données (10² – 10⁴)
la REPRÉSENTATION est le problème        les variables métier existent déjà
une architecture pré-entraînée existe    l'interprétabilité est exigée
un GPU est disponible                    le coût d'inférence doit rester minimal

La question utile n'est donc pas « quel modèle est le meilleur » mais « existe-t-il un biais inductif adapté à mes données ? » (module 15). Une convolution encode l'invariance par translation ; une attention encode des dépendances à longue portée ; un arbre encode des effets de seuil. Choisir un modèle, c'est choisir l'hypothèse structurelle qui correspond au problème.

2. Du neurone au MLP

Un neurone calcule une somme pondérée de ses entrées, ajoute un biais, et passe le tout dans une fonction non linéaire : \(a = \sigma(w^\top x + b)\). Une couche est un ensemble de neurones qui voient les mêmes entrées, ce qui s'écrit matriciellement \(h = \sigma(Wx + b)\). Un perceptron multicouche (MLP) empile ces couches.

le flux, sur un réseau à deux couches cachées
  entrée x        couche 1          couche 2         sortie
  (10 var.)      (64 neurones)     (32 neurones)     (1 valeur)

    x₁ ──┐        ┌─ σ(·) ─┐        ┌─ σ(·) ─┐
    x₂ ──┼─ W₁ ──►│  ...   │─ W₂ ──►│  ...   │─ W₃ ──► ŷ
    ...  │        │        │        │        │
    x₁₀ ─┘        └─ σ(·) ─┘        └─ σ(·) ─┘
                  (10×64 + 64      (64×32 + 32      (32×1 + 1
                   = 704 param.)    = 2 080 param.)  = 33 param.)

Sans la non-linéarité \(\sigma\), la composition de couches affines resterait affine : trois couches vaudraient une seule, et toute la profondeur serait inutile. C'est \(\sigma\) qui donne sa puissance à l'empilement. Le théorème d'approximation universelle garantit d'ailleurs qu'un réseau à une seule couche cachée suffisamment large approche n'importe quelle fonction continue sur un compact — résultat rassurant mais peu opérationnel, car il ne dit rien du nombre de neurones nécessaires ni de la possibilité d'apprendre ces poids. La profondeur, elle, permet d'exprimer certaines fonctions avec exponentiellement moins de paramètres que la largeur.

fonctions d'activation
ReLU        max(0, x)          le défaut. Gradient constant à droite, nul à gauche —
                               d'où les neurones "morts" si le gradient les pousse
                               durablement dans la zone négative.
LeakyReLU   max(αx, x)         corrige les neurones morts avec une pente résiduelle.
GELU        x·Φ(x)             lisse ; le standard dans les transformers.
SiLU/Swish  x·sigmoid(x)       proche de GELU, très utilisé en vision moderne.
tanh        (-1, 1)            centrée ; encore utile dans les RNN et les PINN,
                               car ses dérivées d'ordre 2 sont lisses.
sigmoid     (0, 1)             réservée à la SORTIE binaire. En couche cachée, elle
                               sature et tue le gradient (dérivée ≤ 0,25).
softmax                        sortie multiclasse : produit une distribution.

L'initialisation n'est pas un détail. Trop petite, le signal s'éteint en traversant les couches ; trop grande, il explose. Les schémas de Xavier/Glorot (pour tanh) et He/Kaiming (pour ReLU) choisissent la variance des poids de façon à conserver l'échelle du signal d'une couche à l'autre — \(\operatorname{Var}(w) = 2/n_{\text{entrées}}\) pour He. C'est ce qui a rendu possible l'entraînement des réseaux profonds bien avant les architectures modernes.

3. Rétropropagation

La rétropropagation n'est rien d'autre que la règle de dérivation des fonctions composées, appliquée efficacement. Pour une couche \(z^{(l)} = W^{(l)}a^{(l-1)}+b^{(l)}\), on définit l'erreur locale \(\delta^{(l)} = \partial L/\partial z^{(l)}\) et on la propage vers l'arrière :

\[ \delta^{(l)} = \big(W^{(l+1)\top}\delta^{(l+1)}\big)\odot \sigma'\!\big(z^{(l)}\big), \qquad \frac{\partial L}{\partial W^{(l)}} = \delta^{(l)}\,a^{(l-1)\top} \]

L'astuce algorithmique tient en une phrase : en réutilisant les \(\delta\) déjà calculés au lieu de recalculer chaque dérivée depuis le début, le coût du gradient complet est du même ordre que celui d'une passe avant — et non proportionnel au nombre de paramètres, comme le serait une différentiation numérique paramètre par paramètre. C'est ce qui rend l'entraînement de réseaux à des milliards de paramètres possible.

Le produit de matrices sur toute la profondeur explique aussi les pathologies classiques. Si les termes \(W^\top \sigma'\) sont systématiquement inférieurs à 1 en module, leur produit tend vers zéro : le gradient s'évanouit et les premières couches n'apprennent plus rien. S'ils dépassent 1, il explose, et les poids partent à l'infini en une itération. Les trois remèdes structurels sont les connexions résiduelles (section 5), la normalisation (section 4) et l'écrêtage du gradient (module 8).

4. Les recettes d'entraînement

En deep learning, l'écart entre un modèle qui converge et un modèle qui stagne tient rarement à l'architecture. Les leviers, par ordre d'impact :

  1. Taux d'apprentissage — de loin le plus important (module 8). Le trouver par une montée exponentielle sur quelques centaines de pas (LR range test), puis prendre un ordre de grandeur en dessous du point de divergence.
  2. Normalisation — la batch norm normalise chaque variable sur le lot (efficace en vision, mais dépendante de la taille du lot et délicate en inférence, où l'on utilise des statistiques mémorisées) ; la layer norm normalise sur les variables d'un exemple (indépendante du lot, standard dans les transformers). L'effet principal est un meilleur conditionnement, donc une convergence plus rapide et plus stable.
  3. Régularisation — le dropout désactive aléatoirement une fraction des neurones à chaque passage, ce qui force le réseau à ne pas dépendre d'un chemin unique ; le weight decay via AdamW ; et surtout l'early stopping, la plus simple et la plus efficace : on garde les poids du meilleur point de validation.
  4. Augmentation de données — en vision, c'est souvent le gain le plus important : recadrages, symétries, variations colorimétriques, mixup, cutmix. On encode ainsi des invariances connues sans annoter davantage (module 13).
  5. Taille de lot — grand lot = gradient moins bruité et meilleure utilisation du GPU, mais généralisation parfois moins bonne. Règle empirique : en multipliant le lot par \(k\), multiplier le taux d'apprentissage par \(k\) (ou \(\sqrt{k}\) avec Adam). Si la mémoire manque, l'accumulation de gradient simule un grand lot (module 25).
  6. Précision mixte — calculs en 16 bits avec accumulation en 32 bits : mémoire divisée par deux et accélération d'un facteur 2 à 3 sur GPU récent, sans perte mesurable de qualité.
Python — une boucle d'entraînement complète et correcte
import torch, torch.nn as nn
from torch.amp import autocast, GradScaler

modele = modele.to("cuda")
opt = torch.optim.AdamW(modele.parameters(), lr=3e-4, weight_decay=0.01)
sched = torch.optim.lr_scheduler.OneCycleLR(opt, max_lr=3e-4,
                                            total_steps=n_epoques * len(train_loader))
scaler = GradScaler()
meilleure_val, patience, attente = float("inf"), 10, 0

for epoque in range(n_epoques):
    modele.train()                                   # active dropout et batch norm
    for xb, yb in train_loader:
        xb, yb = xb.to("cuda", non_blocking=True), yb.to("cuda", non_blocking=True)
        opt.zero_grad(set_to_none=True)
        with autocast("cuda", dtype=torch.bfloat16): # précision mixte
            perte = critere(modele(xb), yb)
        scaler.scale(perte).backward()
        scaler.unscale_(opt)                          # avant l'écrêtage
        nn.utils.clip_grad_norm_(modele.parameters(), 1.0)
        scaler.step(opt); scaler.update(); sched.step()

    modele.eval()                                     # désactive dropout
    with torch.no_grad():                             # pas de graphe = plus rapide
        val = sum(critere(modele(x.to("cuda")), y.to("cuda")).item()
                  for x, y in val_loader) / len(val_loader)

    if val < meilleure_val:                           # EARLY STOPPING
        meilleure_val, attente = val, 0
        torch.save(modele.state_dict(), "meilleur.pt")
    else:
        attente += 1
        if attente >= patience:
            break
modele.load_state_dict(torch.load("meilleur.pt"))     # on garde le MEILLEUR, pas le dernier

La première chose à faire avant tout entraînement long

Surapprendre volontairement un tout petit échantillon — seize exemples, sans augmentation ni régularisation. Si le modèle n'atteint pas une perte quasi nulle dessus, il y a un bug : étiquettes mal alignées, normalisation erronée, gradients non propagés, perte mal branchée, ou couche gelée par erreur. Aucun réglage d'hyperparamètre ne sauvera cela. Ce test de trente secondes évite des heures de calcul inutile, et c'est le premier réflexe à acquérir.

5. CNN et vision

Une convolution applique un même petit noyau à toutes les positions de l'image. Concrètement, sur une fenêtre 3×3 :

une convolution, en clair
  image (extrait)        noyau 3×3          on multiplie terme à terme
  ┌───┬───┬───┐        ┌───┬───┬───┐       et on somme :
  │ 10│ 10│ 10│        │ -1│  0│  1│
  ├───┼───┼───┤        ├───┼───┼───┤       (10×-1)+(10×0)+(10×1) +
  │ 10│ 10│ 80│   ×    │ -1│  0│  1│   =   (10×-1)+(10×0)+(80×1) +
  ├───┼───┼───┤        ├───┼───┼───┤       (10×-1)+(10×0)+(80×1)
  │ 10│ 10│ 80│        │ -1│  0│  1│       = 140  → un CONTOUR VERTICAL détecté
  └───┴───┴───┘        └───┴───┴───┘
  Ce noyau est un détecteur de contours verticaux. Un CNN n'utilise pas des noyaux
  écrits à la main : il les APPREND, et les premières couches convergent
  spontanément vers des détecteurs de contours et de textures.

Deux propriétés en découlent. Le partage de poids : un noyau 3×3 a neuf paramètres, quelle que soit la taille de l'image — là où une couche dense sur une image 224×224 en aurait des dizaines de millions. Et l'équivariance par translation : un motif déplacé produit une réponse déplacée. C'est le biais inductif exact des images, et la raison pour laquelle un CNN bat massivement un MLP à nombre de paramètres égal.

En empilant des couches, le champ réceptif — la zone de l'image influençant un neurone — s'élargit : deux couches 3×3 voient une zone 5×5, trois couches en voient 7×7. Les premières couches détectent des contours, les suivantes des textures, puis des parties d'objets. Le pooling (ou une convolution à pas 2) réduit la résolution spatiale tout en augmentant le nombre de canaux, ce qui concentre l'information sémantique.

Le tournant architectural est la connexion résiduelle de ResNet : \(y = \mathcal{F}(x) + x\). Le bloc n'apprend qu'une correction à l'identité, ce qui est plus facile que d'apprendre la transformation entière, et le terme \(+x\) ouvre au gradient un chemin direct vers les couches profondes — le gradient passe par l'addition sans être multiplié. C'est ce qui a rendu possible l'entraînement de réseaux de plus de cent couches, et l'idée est si générale qu'on la retrouve dans tous les transformers.

Python — transfer learning, le cas d'usage réaliste
import torch, torchvision as tv

modele = tv.models.resnet50(weights="IMAGENET1K_V2")

for p in modele.parameters():          # 1. on gèle le tronc pré-entraîné
    p.requires_grad = False
modele.fc = torch.nn.Linear(2048, n_classes)   # nouvelle tête, entraînable

# 2. entraîner la tête seule quelques époques, avec un taux élevé (1e-3)
# 3. puis dégeler les derniers blocs et affiner avec un taux 10 à 100× plus faible
for p in modele.layer4.parameters():
    p.requires_grad = True
opt = torch.optim.AdamW([
    {"params": modele.layer4.parameters(), "lr": 1e-5},   # tronc : taux minuscule
    {"params": modele.fc.parameters(),     "lr": 1e-3},   # tête : taux normal
])

6. RNN, LSTM, et leurs limites

Un réseau récurrent maintient un état caché \(h_t = \sigma(W_h h_{t-1} + W_x x_t + b)\), mis à jour pas à pas : à chaque instant, il combine ce qu'il vient de voir et ce qu'il a retenu. Élégant pour les séquences, mais handicapé par deux défauts. Le gradient, propagé à travers le temps, s'évanouit sur les longues dépendances — le même produit de matrices que plus haut, mais sur la longueur de la séquence. Et le calcul est intrinsèquement séquentiel, donc impossible à paralléliser sur la longueur : on ne peut pas calculer \(h_{100}\) sans avoir calculé les 99 précédents.

LSTM et GRU corrigent partiellement le premier point avec des portes — des vannes apprises qui régulent ce qui entre dans la mémoire, ce qui en sort et ce qui est oublié — mais pas le second. Les transformers ont remplacé les RNN presque partout en traitement du langage, précisément parce qu'ils traitent toute la séquence en parallèle. Les RNN gardent un intérêt en inférence à très faible latence sur séquences courtes, et sur les très petits jeux de données temporelles — où, cela dit, un gradient boosting sur variables de décalage reste souvent supérieur aux deux (module 17).

7. Transformers et attention

Le mécanisme d'attention répond à une question simple : pour produire la représentation du mot \(i\), quels autres mots faut-il regarder, et avec quel poids ? Dans la phrase « le chat que le chien poursuivait était noir », comprendre « était » demande de remonter à « chat », six mots plus tôt — et un RNN doit faire transiter cette information par tous les mots intermédiaires.

Chaque position projette son vecteur en trois rôles, par trois matrices apprises : une requête \(Q\) (« ce que je cherche »), une clé \(K\) (« ce que je propose ») et une valeur \(V\) (« ce que je transmets »). L'analogie est celle d'un annuaire : on compare sa requête à toutes les clés, et l'on récupère un mélange des valeurs correspondantes, pondéré par la ressemblance.

\[ \operatorname{Attention}(Q,K,V) = \operatorname{softmax}\!\left(\frac{QK^\top}{\sqrt{d_k}}\right)V \]
l'opération, étape par étape
1. QKᵀ          produit scalaire entre chaque requête et chaque clé
                → matrice n×n de scores de compatibilité
2. / √d_k       mise à l'échelle. INDISPENSABLE : en dimension d_k, les produits
                scalaires ont une variance ∝ d_k ; sans division, le softmax
                sature (un poids à 1, tous les autres à 0) et le gradient disparaît.
3. softmax      chaque ligne devient une distribution de poids sommant à 1
4. × V          moyenne pondérée des valeurs : voilà la nouvelle représentation

L'attention multi-têtes réalise plusieurs fois cette opération en parallèle dans des sous-espaces différents, chaque tête pouvant se spécialiser (accord syntaxique, coréférence, position relative), puis concatène les résultats. Comme l'opération est invariante à l'ordre — permuter les mots permute simplement les lignes —, il faut injecter la position explicitement : encodages sinusoïdaux à l'origine, positions relatives ou rotatives (RoPE) aujourd'hui.

anatomie d'un bloc transformer
x = x + MultiHeadAttention(LayerNorm(x))      # connexion résiduelle + pré-normalisation
x = x + FeedForward(LayerNorm(x))             # MLP 2 couches, expansion ×4, GELU

Encodeur seul      BERT — compréhension, classification, embeddings (module 22)
Décodeur seul      GPT — génération, attention CAUSALE (on ne regarde que le passé)
Encodeur-décodeur  T5, traduction — l'architecture d'origine

Le coût : l'attention est quadratique en longueur de séquence, \(O(n^2 d)\), puisqu'on compare chaque position à toutes les autres. C'est ce qui borne la taille du contexte, et ce qui a engendré une industrie entière de variantes — attention creuse, FlashAttention (exacte, mais optimisée pour la hiérarchie mémoire du GPU, module 25), et modèles à espace d'états comme Mamba, linéaires en \(n\). Le bénéfice : chaque position accède directement à toutes les autres, sans le chemin de longueur \(n\) d'un RNN, et l'ensemble se parallélise — c'est ce qui a rendu possible l'entraînement à l'échelle actuelle.

Le Vision Transformer applique la même recette aux images en les découpant en imagettes traitées comme des jetons. Il dépasse les CNN au-delà d'un certain volume de données, mais reste en retrait sur les petits jeux : n'ayant pas le biais inductif de la convolution, il doit apprendre la localité à partir des exemples (module 15).

8. Autoencodeurs et représentations

Un autoencodeur compresse l'entrée dans un espace latent de faible dimension, puis la reconstruit. Avec des couches linéaires et une perte quadratique, il retrouve exactement le sous-espace de la PCA (module 8) ; avec des non-linéarités, il capture des variétés courbes. Usages : réduction de dimension non linéaire, débruitage (on entraîne à reconstruire une entrée propre depuis une entrée bruitée), et détection d'anomalies par erreur de reconstruction — ce qui est mal reconstruit est ce que le modèle n'a jamais vu.

Plus important encore : l'apprentissage auto-supervisé, qui fabrique une tâche à partir des données non annotées — prédire un mot masqué, rapprocher deux vues augmentées d'une même image (module 13). C'est le principe qui a permis de pré-entraîner sur des corpus gigantesques sans annotation humaine, et qui produit les embeddings utilisés en recherche sémantique (module 22).

9. PINN : apprendre sous contrainte physique

Les Physics-Informed Neural Networks répondent à une situation fréquente en ingénierie : on connaît les équations qui régissent le système, mais on a peu de données — ou l'inverse, on a des mesures et un paramètre physique inconnu à identifier. L'idée est d'ajouter le résidu de l'équation dans la fonction de perte : le réseau doit à la fois coller aux données et satisfaire la physique.

Pour une EDP \(\mathcal{N}[u](x,t) = 0\), on approche la solution par un réseau \(u_\theta(x,t)\) et on minimise :

\[ \mathcal{L} = \underbrace{\frac{1}{N_d}\sum_i \big|u_\theta(x_i,t_i) - u_i\big|^2}_{\text{données}} + \lambda_b \underbrace{\mathcal{L}_{\text{bord/initial}}}_{\text{conditions}} + \lambda_f \underbrace{\frac{1}{N_f}\sum_j \big|\mathcal{N}[u_\theta](x_j,t_j)\big|^2}_{\text{résidu physique}} \]

Le point élégant : les dérivées de l'EDP sont obtenues par différentiation automatique du réseau lui-même (module 3), exactement et en tout point — pas de maillage, pas de schéma aux différences finies, pas d'erreur de discrétisation. Les points de collocation où l'on évalue le résidu peuvent être tirés au hasard dans le domaine, et ils ne coûtent aucune donnée expérimentale.

PyTorch — oscillateur amorti : m·u'' + c·u' + k·u = 0
import torch, torch.nn as nn

m, c, k = 1.0, 0.4, 20.0            # paramètres physiques connus

reseau = nn.Sequential(             # tanh : dérivées d'ordre 2 lisses et non nulles
    nn.Linear(1, 64), nn.Tanh(),    # (avec ReLU, la dérivée seconde serait nulle
    nn.Linear(64, 64), nn.Tanh(),   #  partout — inutilisable pour une EDP d'ordre 2)
    nn.Linear(64, 64), nn.Tanh(),
    nn.Linear(64, 1),
)
opt = torch.optim.Adam(reseau.parameters(), lr=1e-3)

def residu(t):
    t = t.requires_grad_(True)
    u    = reseau(t)
    # create_graph=True : on dérivera ENCORE cette dérivée (pour u'' et pour backward)
    u_t  = torch.autograd.grad(u,   t, torch.ones_like(u),   create_graph=True)[0]
    u_tt = torch.autograd.grad(u_t, t, torch.ones_like(u_t), create_graph=True)[0]
    return m * u_tt + c * u_t + k * u          # doit valoir 0 partout

t0 = torch.zeros(1, 1, requires_grad=True)     # conditions initiales : u(0)=1, u'(0)=0
for etape in range(20_000):
    tf = torch.rand(256, 1) * 2.0              # collocation ALÉATOIRE sur [0, 2]

    u0   = reseau(t0)
    u0_t = torch.autograd.grad(u0, t0, torch.ones_like(u0), create_graph=True)[0]

    perte = (residu(tf) ** 2).mean() + (u0 - 1.0) ** 2 + u0_t ** 2
    opt.zero_grad(); perte.backward(); opt.step()

Ce que les PINN apportent : une solution continue et différentiable, donc évaluable et dérivable partout ; l'absence de maillage, avantage décisif en géométrie complexe ou en grande dimension ; l'assimilation naturelle de données expérimentales bruitées ; et surtout la résolution de problèmes inverses — en déclarant un paramètre physique comme entraînable (c = nn.Parameter(torch.tensor(0.1))), le réseau l'identifie à partir de quelques mesures. C'est le cas d'usage où ils sont réellement compétitifs (module 21).

Ce que les PINN ne sont pas. Ils ne remplacent pas un solveur d'EDP classique : sur un problème direct bien posé où un schéma aux éléments finis existe, celui-ci est plus rapide, plus précis et vient avec des garanties de convergence. Les PINN sont difficiles à entraîner — la pondération \(\lambda_f\) entre termes de perte est délicate, et les gradients des différents termes entrent en compétition — et ils échouent sur les solutions raides ou à forts gradients (chocs, hautes fréquences) à cause du biais spectral des réseaux, qui apprennent les basses fréquences en premier. Les remèdes existent (pondération adaptative, décomposition de domaine, Fourier features) mais restent des sujets de recherche.

10. Limites

  • Appétit en données et en calcul — sans pré-entraînement, un réseau profond sur 2 000 exemples surapprend systématiquement.
  • Opacité — l'attribution par gradients ou par SHAP donne des indications, pas une explication vérifiable.
  • Incertitude mal calibrée — un réseau est typiquement trop confiant, y compris sur des entrées hors distribution. Remèdes : température de calibration, ensembles profonds, dropout à l'inférence, ou modèles probabilistes (module 10).
  • Fragilité — les exemples adverses montrent qu'une perturbation imperceptible peut renverser une prédiction (module 27).
  • Reproductibilité — non-déterminisme des noyaux GPU, sensibilité aux graines : deux entraînements identiques ne donnent pas exactement le même modèle (module 26).
  • Empreinte — coût énergétique et financier de l'entraînement comme de l'inférence, qui doit entrer dans l'arbitrage (module 25).

Ce qu'il faut retenir du module

  • Choisir une architecture, c'est choisir un biais inductif : convolution pour la localité, attention pour les dépendances longues.
  • Sans non-linéarité, la profondeur ne sert à rien ; l'initialisation conditionne la propagation du signal.
  • La rétropropagation coûte le prix d'une passe avant, ce qui rend l'échelle actuelle possible.
  • Le taux d'apprentissage domine tous les autres hyperparamètres ; commencer par surapprendre 16 exemples pour valider la chaîne.
  • Connexions résiduelles, normalisation et écrêtage résolvent les pathologies du gradient.
  • Transfer learning par défaut : entraîner de zéro est rarement le bon choix.
  • L'attention est quadratique en longueur de séquence — c'est la contrainte structurante des LLM.
  • Les PINN brillent sur les problèmes inverses et l'assimilation de données, pas en remplacement d'un solveur.

MLPCNNTransformersAttentionPINNPyTorch