Aller au contenu principal

Module 3 — Sous-échantillonnage et réduction spatiale

Les deux premiers modules ont montré qu'un CNN peut détecter un motif à n'importe quelle position, mais toujours à la même résolution. Or une image comporte des motifs à plusieurs échelles : une roue de vélo dans CIFAR-10 mesure 12 pixels, un cadre en mesure 25. Pour couvrir ces échelles sans exploser en profondeur, il faut réduire la résolution spatiale à mesure qu'on progresse dans le réseau. Ce module compare les trois moyens de le faire — max, moyenne, convolution à pas 2 — et introduit GlobalAveragePooling, la brique qui remplace la couche dense finale dans les architectures modernes.

Max, ou ne garder que le signal le plus fort

Le sous-échantillonnage max divise la carte en fenêtres non chevauchantes de 2x2 et ne garde que la valeur maximale de chacune. Sur une entrée de 32x32, la sortie fait 16x16, et le nombre de canaux ne change pas.

from tensorflow.keras import layers

# entree 32 x 32 x 64 -> sortie 16 x 16 x 64
layers.MaxPooling2D(pool_size=2, strides=2)

Le maximum a un sens précis : sur une carte de caractéristiques qui répond fort là où un motif est présent, garder le max, c'est conserver la présence du motif sans en préserver la position exacte au pixel près. Cela confère au réseau une invariance locale à la translation — un décalage d'un pixel dans l'image ne change pas la sortie tant que la valeur maximale reste dans la même fenêtre 2x2.

Le sous-échantillonnage max n'a aucun paramètre appris. C'est une opération pure, très économique en calcul, et sa dérivée est simple : le gradient remonte uniquement vers le pixel qui portait le maximum, les autres reçoivent zéro. Cette rareté du signal en amont peut ralentir l'apprentissage des premières couches — c'est l'une des raisons pour lesquelles les architectures récentes lui préfèrent la convolution à pas 2.

Moyenne, quand le contexte compte plus que le pic

Le sous-échantillonnage moyen remplace le max par la moyenne des 4 valeurs de la fenêtre. Le résultat est plus lisse, moins piqué, et rend explicite l'énergie moyenne du motif plutôt que sa présence maximale.

layers.AveragePooling2D(pool_size=2, strides=2)

En pratique, la moyenne était utilisée par LeNet (module 4) parce que le calcul en 1998 le permettait mieux, puis a été remplacée par le max avec AlexNet (module 4 aussi). Elle est revenue par une autre porte, en fin de réseau, sous la forme du sous-échantillonnage moyen global.

GlobalAveragePooling, la brique qui remplace le dense final

Une architecture classique se termine par un aplatissement puis une ou deux couches denses. Sur CIFAR-10 avec un CNN qui sort une carte 8x8x256, l'aplatissement produit un vecteur de 16 384 composantes, et une couche dense finale en 10 classes coûte 16384×1016400016\,384 \times 10 \approx 164\,000 paramètres. Sur ImageNet en 224x224, les architectures anciennes payaient plusieurs dizaines de millions de paramètres uniquement dans les couches denses finales.

Le GlobalAveragePooling2D prend la moyenne de chaque carte de caractéristiques sur toute sa surface. Une entrée 8x8x256 sort en un vecteur de 256 composantes, sans aucun paramètre appris. On y branche ensuite une seule couche dense en 10 classes, à 2 560 paramètres.

from tensorflow import keras

modele = keras.Sequential([
layers.Conv2D(32, 3, activation="relu", padding="same", input_shape=(32, 32, 3)),
layers.MaxPooling2D(),
layers.Conv2D(64, 3, activation="relu", padding="same"),
layers.MaxPooling2D(),
layers.Conv2D(128, 3, activation="relu", padding="same"),
layers.GlobalAveragePooling2D(), # 8 x 8 x 128 -> 128
layers.Dense(10, activation="softmax"),
])

Trois avantages, dans l'ordre d'importance :

  1. Compression massive : le classifieur passe de centaines de milliers à quelques milliers de paramètres, ce qui réduit le surajustement.
  2. Interprétation directe : chaque composante du vecteur final correspond à l'activation moyenne d'un filtre, ce qui rend Grad-CAM (module 10) applicable presque tel quel.
  3. Taille d'entrée flexible : le modèle accepte n'importe quelle taille d'image plus grande que le minimum imposé par les sous-échantillonnages, alors qu'un aplatissement fige la taille.

Convolution à pas 2, l'alternative apprenable

La troisième voie consiste à ne pas mettre de couche de sous-échantillonnage du tout, mais à donner un pas 2 à certaines convolutions. On divise la résolution par deux, mais la manière de le faire est apprise au lieu d'être imposée.

# a la place de MaxPooling
layers.Conv2D(64, 3, strides=2, padding="same")

Cette approche coûte des paramètres que le max n'a pas — les 3×3×C23 \times 3 \times C^2 du filtre — mais donne au réseau la liberté de choisir ce qu'il conserve. Les architectures récentes (ResNet, EfficientNet, ConvNeXt) l'utilisent presque systématiquement en remplacement du max, sauf tout au début du réseau où le max reste courant.

ApprocheParamètresComportement
MaxPooling2D0garde la valeur maximale, gradient concentré
AveragePooling2D0garde la moyenne, gradient réparti
Conv2D(strides=2)k2C2k^2 C^2appris, plus expressif, plus coûteux
GlobalAveragePooling2D0réduit chaque carte à un scalaire, taille flexible

Invariance approximative, pas garantie

Un point souvent mal compris : le sous-échantillonnage max donne une invariance approximative à la translation, pas une invariance parfaite. Un décalage d'un pixel n'affecte pas la sortie tant que la position du maximum reste dans la même fenêtre 2x2 ; un décalage de trois pixels, ou un décalage qui fait sortir le pic de sa fenêtre, change la sortie.

C'est ce qui explique un phénomène troublant : sur un CIFAR-10 avec MaxPooling2D répété, une même image translatée d'un pixel peut basculer d'une classe à une autre au voisinage de la frontière de décision. Le problème est réel et a été mesuré finement dans « Making Convolutional Networks Shift-Invariant Again » (Zhang, 2019), qui propose un anti-alias placé avant chaque sous-échantillonnage pour restaurer une invariance plus lissée.

Le sous-échantillonnage n'invariante pas à la rotation ni au changement d'échelle

L'équivariance à la translation vient du partage des poids (module 1) ; le sous-échantillonnage ajoute une invariance locale à la translation. Aucune de ces deux propriétés ne s'étend à la rotation ni à l'agrandissement. Un modèle qui doit tolérer ces transformations les apprend par augmentation de données (module 8), pas par l'architecture.

Petit CNN complet sur CIFAR-10

Assemblons les trois premiers modules dans un CNN minimal, entraînable en quelques minutes sur un portable :

from tensorflow import keras
from tensorflow.keras import layers

(x_ent, y_ent), (x_val, y_val) = keras.datasets.cifar10.load_data()
x_ent, x_val = x_ent / 255.0, x_val / 255.0

modele = keras.Sequential([
layers.Conv2D(32, 3, activation="relu", padding="same", input_shape=(32, 32, 3)),
layers.Conv2D(32, 3, activation="relu", padding="same"),
layers.MaxPooling2D(), # 32 -> 16
layers.Conv2D(64, 3, activation="relu", padding="same"),
layers.Conv2D(64, 3, activation="relu", padding="same"),
layers.MaxPooling2D(), # 16 -> 8
layers.Conv2D(128, 3, activation="relu", padding="same"),
layers.GlobalAveragePooling2D(), # 8x8x128 -> 128
layers.Dense(10, activation="softmax"),
])

modele.compile(
optimizer="adam",
loss="sparse_categorical_crossentropy",
metrics=["accuracy"],
)
modele.fit(x_ent, y_ent, validation_data=(x_val, y_val), epochs=15, batch_size=128)

Ce réseau atteint 75 à 78 % de précision de validation en une quinzaine d'époques, avec environ 250 000 paramètres. C'est la ligne de base que les modules 4 à 7 vont chercher à améliorer, chacun avec une idée précise.

En résumé

  • Le sous-échantillonnage max conserve la présence d'un motif sans en préserver la position exacte, et n'a aucun paramètre.
  • La moyenne lisse la carte ; sa forme utile aujourd'hui est le sous-échantillonnage moyen global en fin de réseau, qui remplace un dense énorme par une couche à 10 fois moins de paramètres.
  • La convolution à pas 2 est l'alternative apprenable au max, préférée par la plupart des architectures modernes.
  • L'invariance à la translation qu'apporte le sous-échantillonnage reste locale et approximative ; la rotation et le changement d'échelle s'obtiennent par augmentation, pas par l'architecture.

Module suivant : LeNet et AlexNet, les premières architectures qui ont assemblé ces briques dans un ordre qui fonctionne.