Aller au contenu principal

Module 3 — Propagation avant et calcul de la perte

La propagation avant est le trajet d'une donnée depuis l'entrée jusqu'à la prédiction. C'est la partie facile du deep learning, et il faut la maîtriser complètement avant d'aborder la rétropropagation, qui n'en est que la lecture à l'envers.

Une couche est un produit matriciel

Le module 1 décrivait un neurone. Une couche calcule tous ses neurones d'un seul coup, et l'écriture matricielle rend cela naturel. Pour une couche \ell :

z()=W()a(1)+b(),a()=f(z())z^{(\ell)} = W^{(\ell)} a^{(\ell-1)} + b^{(\ell)}, \qquad a^{(\ell)} = f\left(z^{(\ell)}\right)

a(1)a^{(\ell-1)} est la sortie de la couche précédente, W()W^{(\ell)} la matrice des poids, b()b^{(\ell)} le vecteur des biais, et ff l'activation appliquée terme à terme.

La forme des matrices est la première source d'erreurs en pratique, et deux minutes de vérification en épargnent beaucoup. Si la couche précédente compte nn neurones et la couche courante mm, alors W()W^{(\ell)} est de taille m×nm \times n, b()b^{(\ell)} de taille mm, et la sortie a()a^{(\ell)} de taille mm. Le nombre de paramètres de la couche vaut donc m×n+mm \times n + m.

Cette dernière formule explique la taille des modèles. Une couche de 1 000 neurones suivie d'une autre de 1 000 neurones contient à elle seule un million de poids. C'est aussi pourquoi les couches entièrement connectées deviennent vite intenables sur des images, et pourquoi les réseaux convolutifs du cours 10 partagent leurs poids.

Le traitement par lots

En pratique on ne fait jamais passer une observation seule. On empile BB observations dans une matrice XX de taille B×nB \times n, et le calcul devient :

Z=XW+bZ = X W^{\top} + b

Le résultat est de taille B×mB \times m : une ligne par observation. Deux raisons rendent cela indispensable. La première est matérielle : les processeurs graphiques sont conçus pour les opérations matricielles massivement parallèles, et traiter 256 observations d'un coup est presque aussi rapide qu'en traiter une. La seconde est statistique, et le module 5 y reviendra : le gradient moyenné sur un lot est bien moins bruité que celui d'une observation isolée.

import numpy as np

def propagation_avant(X, poids, biais):
"""Traverse un reseau entierement connecte avec ReLU en couches cachees."""
a = X
for i, (W, b) in enumerate(zip(poids, biais)):
z = a @ W.T + b
derniere = i == len(poids) - 1
a = z if derniere else np.maximum(0, z) # sortie lineaire au bout
return a

Ce code, en dix lignes, est la totalité de la propagation avant. Toute la difficulté du deep learning est ailleurs.

La perte transforme une erreur en nombre à minimiser

Le réseau produit une prédiction ; la fonction de perte mesure son écart à la vérité, en un seul nombre que l'optimisation cherchera à réduire. Son choix découle de la tâche, exactement comme l'activation de sortie du module précédent — et les deux doivent s'accorder.

Pour la régression, l'erreur quadratique moyenne :

L=1Bi=1B(yiy^i)2\mathcal{L} = \frac{1}{B}\sum_{i=1}^{B}\left(y_i - \hat{y}_i\right)^2

Elle pénalise le carré de l'écart, donc lourdement les grandes erreurs — utile si elles sont graves, nuisible si les données contiennent des valeurs aberrantes. L'erreur absolue moyenne ou la perte de Huber, qui est quadratique près de zéro et linéaire au-delà, sont alors plus robustes.

Pour la classification, l'entropie croisée. Dans le cas binaire :

L=1Bi=1B[yilogy^i+(1yi)log(1y^i)]\mathcal{L} = -\frac{1}{B}\sum_{i=1}^{B}\left[y_i \log \hat{y}_i + (1 - y_i)\log(1 - \hat{y}_i)\right]

Il vaut la peine de comprendre son comportement plutôt que de la retenir par cœur. Si la vraie classe est 1 et que le modèle prédit 0,99, le terme log(0,99)\log(0{,}99) est presque nul : la perte est faible. S'il prédit 0,01, alors log(0,01)4,6\log(0{,}01) \approx -4{,}6 : la perte est forte. Et si le modèle affirme 0 avec certitude alors que la réponse est 1, la perte tend vers l'infini.

C'est la propriété décisive : l'entropie croisée punit sévèrement la confiance erronée. Un modèle qui se trompe en hésitant est bien moins pénalisé qu'un modèle qui se trompe en étant catégorique. C'est ce qui produit des probabilités mieux calibrées, et c'est pourquoi on ne classe pas avec une erreur quadratique.

Un piège numérique classique

Calculer une softmax puis son logarithme séparément provoque des débordements : l'exponentielle d'un grand score explose, et le logarithme de zéro n'existe pas. Toutes les bibliothèques offrent donc une version fusionnée et stabilisée — CrossEntropyLoss en PyTorch, from_logits=True en Keras. Elles attendent les scores bruts de la dernière couche, sans softmax. Appliquer une softmax puis cette perte est une erreur fréquente : la softmax est alors appliquée deux fois, le modèle apprend mal et rien ne le signale.

En résumé

  • Une couche calcule z=Wa+bz = Wa + b puis applique l'activation ; la couche compte m×n+mm \times n + m paramètres, ce qui explique la taille des modèles.
  • Le traitement par lots empile les observations : indispensable pour le parallélisme du processeur graphique et pour réduire le bruit du gradient.
  • La perte convertit l'erreur en un scalaire à minimiser ; erreur quadratique pour la régression, entropie croisée pour la classification.
  • L'entropie croisée punit la confiance erronée, ce qui produit des probabilités mieux calibrées ; et la perte doit recevoir les scores bruts, non une softmax déjà appliquée.

Module suivant : la rétropropagation, où l'on remonte ce même chemin pour savoir de combien corriger chaque poids.