Partie 1 · Voir
Repérer un motif partout
Hier, ton réseau lisait une coupe comme une liste de 784 nombres. Trois défauts, qu'on corrige aujourd'hui :
- Les voisins : l'image est aplatie, deux pixels voisins comptent comme deux pixels quelconques.
- Les décalages : d'un patient à l'autre, l'organe n'est pas au même endroit ; décalé de quelques pixels, c'est pour le MLP une tout autre liste de nombres.
- La taille : une photo couleur de 224 × 224 donne plus de 19 millions de poids pour une seule couche de 128 neurones.
Pour tout calculer à la main, on zoome sur un carré de 6 × 6 pixels au coin d'un organe : 0 = sombre, 1 = clair. L'organe a un bord à gauche et un bord en haut. Un bord, c'est deux pixels voisins de valeurs nettement différentes. Comment le trouver par un calcul ?
Prends une seule ligne : les pixels n° 1 à 6 valent 0, 0, 0, 1, 1, 1. Le pixel 3 touche-t-il un bord ? On compare ses deux voisins : droite − gauche = 1 − 0 = 1 : oui. Deux voisins égaux donneraient 0 : pas de bord.
C'est une somme pondérée, comme dans un neurone de J1, avec les poids −1, 0, +1 : −1 retire le voisin de gauche, +1 ajoute celui de droite, 0 ignore le pixel du milieu. Les trois pixels lus forment la fenêtre. Calcul : (−1) × 0 + 0 × 0 + (+1) × 1 = 1.
À toi de le faire glisser sur toute la ligne : la fenêtre avance d'un pixel à la fois, et on range chaque réponse sous le pixel du milieu. Les pixels 1 et 6 n'ont qu'un voisin : pas de réponse pour eux.
À toi de coder
Sur l'image entière, on fait le même calcul avec une fenêtre de 3 × 3 pixels et 9 poids : chaque ligne des poids est notre −1, 0, +1. On multiplie chaque pixel par le poids placé au même endroit (case par case, ce n'est pas un produit de matrices), puis on additionne les 9 produits.
Exemple de la fenêtre qui chevauche le bord sur ses trois lignes : trois fois (1 − 0), donc 3. Un pixel isolé, plus clair par hasard, ne fait répondre qu'une ligne : 1. Trois lignes rendent donc la réponse plus forte pour un vrai bord que pour une tache. À toi : la fenêtre tout en haut, colonnes 2 à 4, donne 1 (le bord ne traverse qu'une ligne sur trois).
En faisant glisser la fenêtre partout, on obtient une réponse par position : un tableau de 4 × 4 réponses, rangées à la place du pixel du milieu. Essaie toi-même.
À toi de coder
On vient de construire une convolution. Trois mots à bien poser :
- le filtre (ou noyau, kernel) : les 9 poids, qui fixent le motif cherché ;
- la convolution : refaire le même calcul à chaque position de l'image ;
- la carte (feature map) : le tableau des réponses, qui montre où est le motif.
Chaque case de la carte est la sortie d'un neurone de J1 qui ne lirait que 9 pixels voisins, et tous ces neurones ont les mêmes 9 poids : les poids sont partagés. Il manque deux ingrédients du neurone : le biais et l'activation. z = w₁x₁ + … + w₉x₉ + b, puis ReLU(z) = max(0, z). Avec b = −1 (choisi pour l'exemple, dans un vrai CNN il est appris), une case ne reste positive que si le bord traverse au moins deux lignes de la fenêtre. Un filtre 3 × 3 a 9 poids + 1 biais = 10 paramètres, quelle que soit l'image :
| Image | Un neurone du MLP | Un filtre 3 × 3 |
|---|---|---|
| 6 × 6 | 36 + 1 = 37 | 9 + 1 = 10 |
| 28 × 28 | 784 + 1 = 785 | 9 + 1 = 10 |
| 224 × 224 (gris) | 50 176 + 1 = 50 177 | 9 + 1 = 10 |
Attention : peu de poids ne veut pas dire peu de calculs, le filtre refait sa somme à chaque position. Et le calcul ne sait pas ce qu'est un organe : un vaisseau ou du bruit répondent aussi.
Les pixels du bord de l'image. Un pixel du bord n'a pas de voisin de l'autre côté : la fenêtre ne peut pas se centrer sur lui, d'où 4 × 4 réponses pour 6 × 6 pixels, et une carte qui rétrécirait encore à chaque couche. Le padding entoure l'image d'un cadre de zéros : avec padding=1, les 36 pixels ont une réponse et la carte fait 6 × 6. Limite : ces voisins inventés valent 0, les réponses du bord sont un peu faussées.
Le stride. Avec un stride de 2, la fenêtre avance de deux pixels, en largeur et en hauteur : la carte 4 × 4 devient 2 × 2 (0 1 / 0 3), 4 fois moins de réponses. Utile pour réduire vite une grande image (ResNet le fait dès sa première couche).
Tout tient dans une formule, avec n la taille de l'image, k celle du filtre, p le padding et s le stride :
taille = ⌊(n + 2p − k) / s⌋ + 1
Notre carré : (6 + 0 − 3)/1 + 1 = 4. Avec padding 1 : (6 + 2 − 3)/1 + 1 = 6. Avec stride 2 : ⌊(6 + 0 − 3)/2⌋ + 1 = 2. Pour une coupe de 28 × 28 et un filtre 5 × 5 : 24 sans padding, et il faut p = 2 pour garder 28.
À toi de coder