Module 1 — Convolution, filtres et cartes de caractéristiques
Un réseau dense qui prend en entrée une image de 32 pixels de côté à trois canaux voit d'abord 3 072 entrées à plat. Cent neurones cachés derrière signifie 307 200 poids, et deux images du même chat décalées d'un pixel deviennent, pour le réseau, deux vecteurs qui n'ont rien à voir l'un avec l'autre. La convolution résout ces deux problèmes d'un coup : elle réutilise les mêmes poids partout dans l'image et transforme le décalage en simple translation de la sortie. C'est ce mécanisme que ce module démonte pièce par pièce, sur le jeu CIFAR-10 qui sert de fil rouge des sept premiers modules.
Une convolution 2D calculée à la main
Prenons une petite image en niveaux de gris de 5 lignes sur 5 colonnes, et un filtre — on dit aussi noyau — de 3 sur 3 :
import numpy as np
image = np.array([
[0, 0, 0, 0, 0],
[0, 1, 1, 1, 0],
[0, 1, 2, 1, 0],
[0, 1, 1, 1, 0],
[0, 0, 0, 0, 0],
], dtype=float)
filtre = np.array([
[ 0, -1, 0],
[-1, 4, -1],
[ 0, -1, 0],
], dtype=float) # detecteur de discontinuite locale
Le filtre parcourt l'image, une position à la fois. À chaque position, on multiplie terme à terme les 9 valeurs du filtre avec les 9 valeurs de la fenêtre correspondante, et on somme. En centrant le filtre sur la case (2, 2) — le pixel à la valeur 2 — on obtient :
En répétant l'opération pour chaque position centrale possible, on obtient une carte de caractéristiques de 3 sur 3. La formule générale, sans remplissage, produit une sortie de par à partir d'une entrée de par et d'un filtre de par : c'est le module 2 qui la démonte en détail. Ici, .
Une convention utile : mathématiquement, la convolution retourne le filtre avant de sommer. Ce que les cadriciels appellent « convolution » est en fait une corrélation croisée, sans retournement. C'est sans conséquence quand le filtre est appris — il apprendra la version retournée si besoin — mais cela évite bien des maux de tête quand on veut recopier un filtre à la main.
Sobel, ou pourquoi les mêmes filtres reviennent partout
Avant que les CNN n'apprennent leurs filtres à partir de données, les praticiens de la vision les écrivaient à la main. Les filtres de Sobel détectent les contours verticaux et horizontaux :
sobel_x = np.array([
[-1, 0, 1],
[-2, 0, 2],
[-1, 0, 1],
], dtype=float)
sobel_y = sobel_x.T # transposition : contours horizontaux
Appliquer sobel_x à une image met en évidence les frontières où l'intensité passe de sombre à clair de gauche à droite. La magnitude du gradient , calculée pixel à pixel, donne une carte des contours utilisable telle quelle.
La leçon est ailleurs. Quand on entraîne un CNN moderne sur des millions d'images, les filtres appris par la première couche ressemblent, à peu de choses près, aux filtres de Sobel, de Gabor et de leurs variantes. Le réseau redécouvre seul les détecteurs de contours : c'est un signe fort que ces filtres ne sont pas une convention humaine mais des primitives quasi universelles de la vision.
Trois canaux d'entrée, plusieurs filtres en sortie
Une image couleur a trois canaux — rouge, vert, bleu. Un filtre convolutionnel a alors une profondeur égale au nombre de canaux d'entrée : sur RGB, un filtre 3x3 devient un tenseur de 3x3x3, soit 27 poids appris, plus un biais. La convolution somme sur les trois canaux en même temps et produit une seule carte de sortie.
Pour extraire plusieurs types de caractéristiques, on empile plusieurs filtres. Une couche convolutionnelle configurée avec 32 filtres 3x3 sur une entrée RGB apprend poids et sort un tenseur à 32 canaux — parfois appelés cartes de caractéristiques, chacune détectant un motif différent.
from tensorflow import keras
from tensorflow.keras import layers
modele = keras.Sequential([
layers.Conv2D(32, kernel_size=3, activation="relu", input_shape=(32, 32, 3)),
layers.Conv2D(64, kernel_size=3, activation="relu"),
layers.GlobalAveragePooling2D(),
layers.Dense(10, activation="softmax"),
])
modele.summary()
Sur CIFAR-10, ces deux couches convolutives produisent 32 puis 64 cartes de caractéristiques. Le sous-échantillonnage global du module 3 réduit ensuite chaque carte à une seule valeur, avant la classification finale en 10 classes.
Partage des poids et invariance par translation
Le point crucial : les 27 poids d'un filtre 3x3 sur RGB sont les mêmes à toutes les positions de l'image. C'est le partage des poids. Un même détecteur de coin repère un coin dans le ciel comme dans un coin du sol, sans avoir eu à apprendre les deux séparément.
Concrètement, cela produit deux propriétés :
- L'équivariance par translation : décaler l'entrée d'un pixel décale la sortie d'un pixel, sans changer le reste. Un chat en haut à gauche active les mêmes filtres qu'un chat en bas à droite, aux mêmes valeurs.
- La compression paramétrique : là où une couche dense reliant entrées à 32 sorties utiliserait poids, la couche convolutive n'en utilise que 896 pour la même sortie spatiale.
Le partage des poids donne l'équivariance, pas l'invariance. Le classifieur final voit une position différente de l'activation selon l'endroit du chat. C'est le sous-échantillonnage (module 3) et surtout le sous-échantillonnage moyen global qui donnent l'invariance approximative par translation. Confondre les deux mène à sur-augmenter par des rotations quand il aurait suffi de mettre GlobalAveragePooling2D.
Compter les paramètres, à faire mentalement
Une compétence sous-estimée : savoir compter les paramètres d'une couche sans lancer summary. La formule est
où est la taille du noyau, le nombre de canaux d'entrée, le nombre de filtres, et le est le biais par filtre.
| Couche | Entrée | Filtres | Noyau | Paramètres |
|---|---|---|---|---|
| Conv2D | 32x32x3 | 32 | 3x3 | 896 |
| Conv2D | 30x30x32 | 64 | 3x3 | 18 496 |
| Dense équivalent | 32x32x3 aplatie | 32 sorties | — | 98 336 |
Le rapport parle de lui-même : à sortie spatiale équivalente, la convolution utilise plus de cent fois moins de poids que la couche dense correspondante. C'est ce budget économisé qui permet d'empiler davantage de couches, et c'est cette profondeur qui distingue les architectures modernes.
Empiler dix couches Conv2D sans fonction d'activation revient à faire une seule convolution équivalente. Toute la capacité de représentation vient de la non-linéarité — habituellement ReLU — insérée entre les couches. Oublier activation="relu" sur les couches intermédiaires est une erreur qui n'apparaît qu'à la stagnation de la perte, jamais dans un message d'erreur.
En résumé
- La convolution 2D est une somme pondérée locale répétée sur toute l'image ; sortie de par sans remplissage.
- Les filtres écrits à la main de Sobel et Gabor sont redécouverts par la première couche d'un CNN entraîné, ce qui confirme leur universalité.
- Le partage des poids donne l'équivariance par translation et divise le nombre de paramètres par plus de cent face à une couche dense équivalente.
- Compter les paramètres via est un réflexe qui évite les architectures inutilement lourdes et rend les tableaux
summarylisibles d'un coup d'œil.
Module suivant : la formule exacte de la taille de sortie, le rôle du pas et du remplissage, et la croissance du champ récepteur avec la profondeur.