Aller au contenu principal

Module 5 — VGG et l'empilement de petits filtres

AlexNet, qu'on a démonté au module 4, ouvrait avec un filtre 11x11 à pas 4 et continuait avec des 5x5 et des 3x3 mélangés. En 2014, l'équipe VGG de l'université d'Oxford a posé la question inverse : que se passe-t-il si on n'utilise que des filtres 3x3, empilés indéfiniment ? Le résultat — VGG16, VGG19 — a remporté la deuxième place à ImageNet cette année-là, mais c'est surtout l'analyse qui l'a rendu célèbre. C'est la démonstration formelle qu'empiler bat élargir, et c'est le raisonnement qui guide toutes les architectures modernes.

Deux 3x3 valent un 5x5, calcul à l'appui

On a esquissé cette équivalence au module 2. Voici le calcul complet, avec les deux gains.

Champ récepteur : deux couches 3x3 à pas 1 empilées voient une région 5x5 de l'entrée, exactement comme une seule couche 5x5.

Paramètres, à nombre de canaux CC égal en entrée et en sortie :

2(33CC)=18C2contre55CC=25C2.2 \cdot (3 \cdot 3 \cdot C \cdot C) = 18 C^2 \quad \text{contre} \quad 5 \cdot 5 \cdot C \cdot C = 25 C^2.

Deux 3x3 utilisent 28 % de paramètres en moins qu'un 5x5 équivalent.

Non-linéarité : entre les deux 3x3, on insère une ReLU. Le réseau applique donc deux fois une transformation non linéaire là où un seul 5x5 n'en applique qu'une. La famille de fonctions représentables par la pile est strictement plus large que celle d'une convolution unique.

Le même argument monte en puissance : trois 3x3 empilés couvrent un champ récepteur de 7x7, avec 27C227 C^2 paramètres contre 49C249 C^2 pour un 7x7 direct, soit 45 % d'économie et deux ReLU supplémentaires. À partir de cinq couches empilées, l'économie dépasse 60 %.

L'architecture VGG en une figure de style

Toute la subtilité tient dans une règle : après chaque bloc de convolutions, on double le nombre de canaux et on divise la résolution par deux. Le budget mémoire par carte reste à peu près constant.

from tensorflow import keras
from tensorflow.keras import layers

def bloc_vgg(entrees, canaux, n):
"""Empile n couches Conv3x3 puis un MaxPool 2x2."""
x = entrees
for _ in range(n):
x = layers.Conv2D(canaux, 3, activation="relu", padding="same")(x)
return layers.MaxPooling2D()(x)

entrees = keras.Input(shape=(224, 224, 3))
x = bloc_vgg(entrees, 64, 2) # 224 -> 112, 64 canaux
x = bloc_vgg(x, 128, 2) # 112 -> 56, 128 canaux
x = bloc_vgg(x, 256, 3) # 56 -> 28, 256 canaux
x = bloc_vgg(x, 512, 3) # 28 -> 14, 512 canaux
x = bloc_vgg(x, 512, 3) # 14 -> 7, 512 canaux
x = layers.Flatten()(x)
x = layers.Dense(4096, activation="relu")(x)
x = layers.Dense(4096, activation="relu")(x)
sortie = layers.Dense(1000, activation="softmax")(x)
vgg16 = keras.Model(entrees, sortie)

On compte les couches : 2+2+3+3+3=132 + 2 + 3 + 3 + 3 = 13 convolutives, plus 3 denses — d'où le nom VGG16. VGG19 remplace les blocs 3 et 4 par 4 couches chacun.

Le coût mémoire, l'ennemi caché

VGG16 pèse 138 millions de paramètres, dont plus de 100 millions dans les deux couches denses à 4096 unités. Mais ce n'est pas le stockage des poids qui pose problème à l'entraînement, c'est la mémoire des activations. Chaque carte de caractéristiques doit être conservée entre la passe avant et la passe arrière pour calculer les gradients.

Estimons pour un lot de 32 images en 224x224 :

BlocSortie par imageActivations, lot 32, float32
1224x224x64411 Mo
2112x112x128206 Mo
356x56x256103 Mo
428x28x51251 Mo
514x14x51213 Mo

Le premier bloc consomme à lui seul plus de mémoire que tous les autres réunis, parce que la résolution est encore pleine et que le nombre de canaux est déjà de 64. Un GPU 16 Go tient le lot 32 en float32 à condition que le reste (poids, gradients, moments d'Adam) reste dans le budget résiduel — c'est serré. En pratique, on entraîne VGG en précision mixte, avec des lots réduits, ou en accumulant les gradients.

La mémoire n'est pas linéaire dans la taille de lot

Doubler la taille du lot double la mémoire des activations, qui domine, mais laisse les poids inchangés. C'est pourquoi une architecture qui tient en lot 16 tient rarement en lot 64 — on cogne beaucoup plus vite qu'un facteur 4.

Pourquoi VGG reste une base de référence

Malgré son âge et son inefficacité paramétrique, VGG est encore présent dans de nombreux pipelines en 2026. Trois raisons :

  • Cartes de caractéristiques faciles à extraire : sa structure séquentielle, sans branchement ni connexion résiduelle, en fait un fournisseur idéal de représentations intermédiaires. La perte perceptuelle, utilisée en transfert de style et en super-résolution, se calcule presque toujours sur les activations d'un VGG préentraîné.
  • Comportement prévisible : chaque couche fait exactement ce qu'on attend d'elle. Aucune magie architecturale (pas de bloc résiduel, pas de branche parallèle) ne complique le diagnostic. C'est un excellent modèle jouet pour comprendre ce qui se passe couche par couche.
  • Références académiques : quinze années d'articles utilisent VGG comme point de comparaison. Rejouer une méthode d'un article passe souvent par la reproduction de sa configuration VGG.

En revanche, VGG ne doit pas être utilisé comme extracteur pour une nouvelle application. Un ResNet50 ou un EfficientNet a moins de paramètres, moins de FLOPs et de meilleures représentations. Le module 9 le confirmera au moment du transfert vers le jeu de déchets à trier.

VGG sur CIFAR-10, la version réduite

Pour CIFAR-10, on adapte : les cinq sous-échantillonnages de VGG16 rétréciraient une entrée 32x32 en 1x1 avant la fin, ce qui n'a pas de sens. On en garde trois :

def vgg_cifar():
entrees = keras.Input(shape=(32, 32, 3))
x = bloc_vgg(entrees, 64, 2) # 32 -> 16
x = bloc_vgg(x, 128, 2) # 16 -> 8
x = bloc_vgg(x, 256, 3) # 8 -> 4
x = layers.GlobalAveragePooling2D()(x)
sortie = layers.Dense(10, activation="softmax")(x)
return keras.Model(entrees, sortie)

Ce modèle atteint 87 à 89 % de précision de validation sur CIFAR-10, avec environ 1,5 million de paramètres. On monte de 5 à 7 points par rapport à l'AlexNet réduit du module précédent, à budget mémoire comparable. Le gain vient d'une profondeur mieux exploitée — sept couches convolutives contre cinq — et de l'homogénéité des blocs, qui rendent l'optimisation plus stable.

Toujours vérifier qu'il reste des pixels

Un MaxPooling2D répété trop souvent réduit la carte à 1x1 avant la fin. Sur une entrée de côté HH, on peut appliquer au plus log2H\lfloor \log_2 H \rfloor sous-échantillonnages 2x2. Sur CIFAR-10 (32), c'est cinq, mais la dernière étape ne laisse qu'un pixel — inutile de compter dessus.

Là où VGG s'essouffle

VGG à seize couches est près du plafond de ce qui s'entraîne encore proprement sans connexion résiduelle. VGG19 gagne à peine sur VGG16 malgré la profondeur supplémentaire, et l'équipe originale a admis qu'aller au-delà dégradait les performances. C'est cette dégradation, contre-intuitive puisqu'un réseau plus profond est au moins aussi expressif qu'un réseau plus court, qui a motivé les connexions résiduelles de ResNet — le sujet du module 6.

En résumé

  • Deux 3x3 valent un 5x5 : même champ récepteur, 28 % de paramètres en moins, une non-linéarité de plus. À trois 3x3 contre un 7x7, l'économie passe à 45 %.
  • La règle VGG — doubler les canaux, diviser la résolution — garde le budget mémoire par carte à peu près constant à chaque bloc.
  • La mémoire des activations, dominée par les premiers blocs à haute résolution, borne davantage l'entraînement que la mémoire des poids.
  • VGG reste utile comme fournisseur de représentations intermédiaires et comme référence académique, mais n'est plus le bon choix pour extraire des caractéristiques dans un projet neuf.

Module suivant : ResNet et les connexions résiduelles, qui rendent enfin l'empilement de dizaines de couches profitable.