Aller au contenu principal

Module 4 — Rétropropagation expliquée pas à pas

Voici l'algorithme qui rend le deep learning possible. Il a une réputation d'obscurité largement imméritée : la rétropropagation n'est que la règle de dérivation en chaîne, appliquée avec méthode et en réutilisant les calculs intermédiaires.

Le problème à résoudre

Après la propagation avant, nous avons une perte L\mathcal{L}, un seul nombre. Pour l'améliorer, il faut savoir dans quel sens pousser chaque paramètre, c'est-à-dire calculer L/w\partial \mathcal{L} / \partial w pour chacun des poids — parfois des millions.

L'approche naïve consisterait à modifier légèrement chaque poids et à observer l'effet sur la perte. Cela demanderait une propagation avant complète par paramètre : pour un million de poids, un million de passages. Inapplicable.

La rétropropagation obtient tous les gradients en un seul passage arrière, dont le coût est du même ordre que la propagation avant. C'est ce facteur d'accélération qui change tout.

La règle de la chaîne, seul prérequis

Si yy dépend de uu qui dépend de xx, alors :

yx=yuux\frac{\partial y}{\partial x} = \frac{\partial y}{\partial u} \cdot \frac{\partial u}{\partial x}

Un réseau est précisément une composition de fonctions : l'entrée traverse la couche 1, puis la couche 2, jusqu'à la perte. La dérivée de la perte par rapport à un poids profond est donc le produit des dérivées locales le long du chemin qui les relie.

Cette lecture donne immédiatement l'intuition du module 6 : si chaque facteur de ce produit est inférieur à 1, le produit de nombreux facteurs tend vers zéro. C'est l'évanescence du gradient, et elle est inscrite dans la structure même de l'algorithme.

Le passage arrière, couche par couche

La quantité centrale est l'erreur locale d'une couche, notée δ()\delta^{(\ell)} : la sensibilité de la perte à la pré-activation z()z^{(\ell)}.

On commence par la dernière couche. Avec une softmax suivie d'une entropie croisée, le résultat se simplifie remarquablement :

δ(L)=y^y\delta^{(L)} = \hat{y} - y

L'erreur de la couche de sortie est simplement l'écart entre la prédiction et la vérité. Cette élégance n'est pas un hasard : c'est l'appariement entre softmax et entropie croisée qui la produit, et c'est une raison de plus de respecter les couples du module 2.

On remonte ensuite d'une couche à l'autre :

δ()=(W(+1)δ(+1))f(z())\delta^{(\ell)} = \left(W^{(\ell+1)\top} \delta^{(\ell+1)}\right) \odot f'\left(z^{(\ell)}\right)

Deux opérations, chacune avec un sens clair. Le produit par W(+1)W^{(\ell+1)\top} redistribue l'erreur de la couche suivante vers la couche courante, au prorata des poids : un neurone qui a beaucoup contribué reçoit une part d'erreur proportionnelle. La multiplication terme à terme par f(z())f'(z^{(\ell)}) filtre cette erreur selon la sensibilité locale de l'activation. Si la sortie est saturée, ff' est proche de zéro et l'erreur ne remonte plus.

Les gradients des paramètres s'en déduisent directement :

LW()=δ()a(1),Lb()=δ()\frac{\partial \mathcal{L}}{\partial W^{(\ell)}} = \delta^{(\ell)} a^{(\ell-1)\top}, \qquad \frac{\partial \mathcal{L}}{\partial b^{(\ell)}} = \delta^{(\ell)}

La première formule mérite d'être lue attentivement : le gradient d'un poids est le produit de l'erreur en aval par l'activation en amont. Un poids n'est corrigé que si les deux sont non nuls. Si le neurone d'entrée était inactif, ce poids ne bouge pas — ce qui explique concrètement le neurone mort du module 2.

Une implémentation minimale

def passe_arriere(activations, pre_activations, poids, y_vrai):
"""Renvoie les gradients d'un reseau ReLU a sortie softmax."""
gradients_W, gradients_b = [], []
# Couche de sortie : l'appariement softmax + entropie croisee se simplifie.
delta = activations[-1] - y_vrai

for i in reversed(range(len(poids))):
gradients_W.insert(0, delta.T @ activations[i])
gradients_b.insert(0, delta.sum(axis=0))
if i > 0:
# Redistribution par les poids, puis filtrage par la derivee de ReLU.
delta = (delta @ poids[i]) * (pre_activations[i - 1] > 0)

return gradients_W, gradients_b

La dérivée de la ReLU se réduit au test > 0, ce qui vaut 1 pour les entrées positives et 0 ailleurs. C'est l'un des avantages pratiques de cette activation.

Ce que font réellement les bibliothèques

Vous n'écrirez jamais ce code en production, et c'est heureux. PyTorch et TensorFlow pratiquent la différentiation automatique : chaque opération de la propagation avant est enregistrée dans un graphe de calcul, et un appel à loss.backward() le parcourt à l'envers en appliquant les dérivées locales connues de chaque opération.

Deux conséquences valent d'être retenues. D'abord, la différentiation automatique n'est ni du calcul symbolique ni une approximation par différences finies : les gradients obtenus sont exacts, à la précision machine près. Ensuite, le graphe doit conserver les activations intermédiaires pour le passage arrière, ce qui explique pourquoi l'entraînement consomme beaucoup plus de mémoire que l'inférence — et pourquoi réduire la taille du lot est le premier réflexe face à une saturation de mémoire du processeur graphique.

Pourquoi comprendre un algorithme qu'on n'écrira pas

Parce que les symptômes d'un entraînement défaillant se lisent dans ces formules. Une perte bloquée peut venir de gradients filtrés par une activation saturée. Des gradients nuls dans les premières couches signalent le produit de facteurs trop petits. Une perte qui devient NaN vient souvent d'un gradient qui explose. Sans le modèle mental de la rétropropagation, ces diagnostics relèvent de la superstition ; avec lui, ils deviennent une lecture.

En résumé

  • La rétropropagation obtient tous les gradients en un seul passage arrière, là où l'approche naïve en exigerait un par paramètre.
  • Ce n'est que la règle de la chaîne : le gradient d'un poids profond est le produit des dérivées locales le long du chemin.
  • Le passage arrière redistribue l'erreur par WW^{\top} puis la filtre par f(z)f'(z) ; une activation saturée bloque donc la remontée.
  • Le gradient d'un poids est le produit de l'erreur en aval par l'activation en amont ; les bibliothèques automatisent tout cela, au prix d'une forte consommation mémoire pendant l'entraînement.

Module suivant : les optimiseurs, qui décident quoi faire de ces gradients une fois calculés.