Aller au contenu principal

Module 8 — Normalisation par lots et par couche

Le cours sur l'ingénierie des variables insistait sur la mise à l'échelle des entrées. La normalisation par lots étend cette idée à l'intérieur du réseau, et son introduction en 2015 a probablement fait plus pour la stabilité de l'entraînement que n'importe quelle autre technique de cette période.

Le problème : chaque couche voit sa distribution d'entrée bouger

Une couche cachée reçoit les activations de la couche précédente. Or les poids de cette couche précédente changent à chaque mise à jour, donc la distribution de ce que reçoit notre couche se déplace continuellement pendant l'entraînement.

C'est inconfortable : la couche doit s'adapter à une cible mouvante. Les auteurs de la méthode ont nommé ce phénomène le internal covariate shift. Il faut savoir que cette explication a été contestée depuis : des travaux ultérieurs attribuent plutôt l'efficacité de la méthode au lissage du paysage de la perte, qui autorise des taux d'apprentissage plus élevés. Le mécanisme reste débattu ; l'efficacité pratique, elle, ne l'est pas.

Le calcul, et surtout les deux paramètres appris

Pour chaque variable d'activation, on normalise sur les observations du lot :

x^=xμlotσlot2+ϵ,y=γx^+β\hat{x} = \frac{x - \mu_{\text{lot}}}{\sqrt{\sigma^2_{\text{lot}} + \epsilon}}, \qquad y = \gamma \hat{x} + \beta

La première étape centre et réduit. La seconde est celle qu'on oublie souvent, alors qu'elle est essentielle : γ\gamma et β\beta sont des paramètres appris, qui permettent au réseau de remettre à l'échelle et de décaler le résultat.

Pourquoi normaliser puis autoriser la dénormalisation ? Parce qu'imposer une moyenne nulle et une variance unitaire est une contrainte forte qui peut nuire. Avec une sigmoïde, forcer les entrées dans une plage étroite autour de zéro cantonne l'activation à sa portion linéaire et lui fait perdre son intérêt. Les paramètres γ\gamma et β\beta rendent la transformation facultative : le réseau peut, s'il en a besoin, réapprendre l'identité. On lui offre une possibilité, on ne lui impose pas un régime.

Les bénéfices observés sont substantiels : des taux d'apprentissage plus élevés deviennent utilisables, la sensibilité à l'initialisation diminue nettement, et un effet régularisant apparaît, dû au bruit des statistiques de lot — au point qu'on peut souvent réduire l'abandon.

Les deux régimes, et le piège qui en découle

Comme l'abandon, la normalisation par lots se comporte différemment selon la phase, mais l'enjeu est ici plus grave.

À l'entraînement, elle utilise la moyenne et la variance du lot courant, et met à jour au passage des moyennes courantes. À l'inférence, ces statistiques de lot ne sont pas utilisables : la prédiction dépendrait des autres observations présentes dans le lot, ce qui est absurde — et pour un seul exemple, la variance serait nulle. Elle utilise donc les moyennes courantes accumulées pendant l'entraînement.

D'où la conséquence pratique : oublier modele.eval() avec une normalisation par lots ne dégrade pas seulement les résultats, cela rend les prédictions dépendantes de la composition du lot. Le même exemple obtient des prédictions différentes selon ceux qui l'accompagnent. C'est un bogue redoutable parce qu'il ne lève aucune erreur et disparaît quand on teste exemple par exemple.

Les limites, et la normalisation par couche

Trois situations mettent la normalisation par lots en difficulté. Avec de petits lots, les statistiques deviennent peu fiables — sous 8 ou 16 observations, la méthode perd son intérêt. Avec des séquences de longueurs variables, comme en traitement du langage, la notion de statistique par position perd son sens. Et son comportement dissymétrique entre entraînement et inférence est une source d'incidents en production.

La normalisation par couche répond à tout cela par un changement d'axe : au lieu de normaliser une variable à travers les observations du lot, elle normalise toutes les activations d'une observation donnée.

Normalisation par lotsNormalisation par couche
Axe de calculà travers le lotà travers les variables d'une observation
Dépend du lotouinon
Régimes distinctsouinon
Domaine de prédilectionvision, réseaux convolutifstransformeurs, séquences

L'avantage décisif est là : la normalisation par couche ne dépend pas du lot. Elle se comporte donc identiquement à l'entraînement et à l'inférence, fonctionne avec un lot d'une seule observation, et s'accommode de séquences de longueurs quelconques. C'est pour ces raisons qu'elle équipe tous les transformeurs, et le cours 12 la retrouvera dans chaque bloc.

import torch.nn as nn

# Vision : normalisation par lots, apres la couche et avant l'activation.
bloc_vision = nn.Sequential(
nn.Linear(256, 128),
nn.BatchNorm1d(128),
nn.ReLU(),
)

# Sequences : normalisation par couche, insensible a la taille du lot.
bloc_sequence = nn.Sequential(
nn.Linear(256, 128),
nn.LayerNorm(128),
nn.GELU(),
)
Où placer la normalisation

La convention historique place la normalisation après la transformation linéaire et avant l'activation, comme ci-dessus. Les architectures récentes de transformeurs adoptent souvent la disposition dite pre-norm, qui normalise avant le bloc : elle stabilise nettement l'entraînement des réseaux très profonds. Notez enfin que la couche linéaire qui précède une normalisation par lots n'a pas besoin de biais, puisque le paramètre β\beta joue déjà ce rôle — d'où le bias=False qu'on rencontre dans le code des grands modèles.

En résumé

  • La normalisation traite le déplacement de distribution subi par chaque couche ; l'explication d'origine est débattue, l'efficacité ne l'est pas.
  • Les paramètres appris γ\gamma et β\beta rendent la transformation facultative : le réseau peut réapprendre l'identité s'il en a besoin.
  • La normalisation par lots a deux régimes ; oublier eval() rend les prédictions dépendantes de la composition du lot.
  • La normalisation par couche ne dépend pas du lot, se comporte identiquement dans les deux phases, et équipe tous les transformeurs.

Module suivant : la lecture des courbes d'apprentissage, l'outil de diagnostic qui relie tous les modules précédents.