Partie 4 · Voir
Emprunter un réseau
Ton CNN de la pneumonie part de filtres tirés au hasard. Avec 4 708 radiographies, il doit tout apprendre, jusqu'aux filtres de bords. Or d'autres équipes ont déjà entraîné de grands CNN sur ImageNet : 1,28 million de photos annotées (1 281 167 pour l'entraînement), en 1 000 catégories (chiens, voitures, champignons…).
Les premières couches de ces réseaux ont appris des filtres très généraux : bords dans toutes les orientations, rayures, taches de couleur. Ils servent pour n'importe quelle image, radiographie comprise ; ce sont les dernières couches qui deviennent propres aux catégories d'ImageNet (Yosinski et al., 2014). D'où l'idée : on garde les filtres, on remplace la dernière couche par la nôtre. C'est l'apprentissage par transfert (transfer learning).
Objection classique : « des photos de chiens, quel rapport avec une radiographie ? » Aucun pour les catégories ; mais un bord reste un bord. Et le gain n'est pas garanti : il est net quand on a peu d'images ; avec beaucoup d'images médicales, un réseau entraîné de zéro fait souvent presque aussi bien (Raghu et al., 2019).
D'où vient ResNet-18 ?
| Année | Réseau | Taille | Repère |
|---|---|---|---|
| 1998 | LeNet-5 | 7 couches · environ 60 000 paramètres | lit les chiffres manuscrits des chèques |
| 2012 | AlexNet | 8 couches · 60 millions de paramètres | ImageNet : 15,3 % d'erreur top-5, contre 26,2 % pour le deuxième |
| 2015 | ResNet | jusqu'à 152 couches | ImageNet : 3,57 % d'erreur top-5, en combinant 6 réseaux |
L'erreur top-5 compte une erreur quand la bonne catégorie n'est pas parmi les 5 que le réseau juge les plus probables. Les briques sont celles de la partie 2 (convolutions, poolings, couches denses) ; ce qui a changé : bien plus de données, des cartes graphiques, la profondeur. Chaque réseau est entraîné de zéro : les flèches du temps marquent des idées reprises, pas des poids copiés.
ResNet-18, le réseau qu'on emprunte
| Étape | Forme de la sortie |
|---|---|
| Entrée : une image couleur | 3 × 224 × 224 |
| Convolution : 64 filtres 7 × 7, stride 2, padding 3, puis ReLU | 64 × 112 × 112 |
| Max pooling 3 × 3, stride 2 (padding 1 dans torchvision, non précisé sur la diapositive) | 64 × 56 × 56 |
| 8 blocs de 2 convolutions 3 × 3 : 64, puis 128, 256 et 512 filtres | 512 × 7 × 7 |
| La moyenne de chaque carte | 512 |
| Couche dense : 512 → 1 000 | 1 000 scores |
Comme dans ton CNN : quand la taille est divisée par 2 (56 → 28 → 14 → 7), le nombre de filtres double. 11 689 512 paramètres (11,7 millions), contre 6 369 pour ton CNN de la pneumonie. Le « 18 » : 17 convolutions et la couche dense. Les poids viennent de la bibliothèque torchvision.
Nouveauté : résumer une carte par sa moyenne
Reprends la carte 4 × 4 du coin de la partie 2 : l'organe y est un seul 1. S'il est un pixel plus bas (ça arrive d'un patient à l'autre), le max pooling 2 × 2 donne un autre résultat, car le 1 a changé de zone. Mais la moyenne des 16 cases vaut 1 / 16 ≈ 0,06 dans les deux cas : elle dit si le motif est présent, pas où.
C'est le global average pooling : à la fin de ResNet-18, chacune des 512 cartes de 7 × 7 est remplacée par la moyenne de ses 49 cases, il reste 512 nombres. À plat, on aurait 512 × 7 × 7 = 25 088 entrées pour la couche dense ; avec la moyenne, 512. Limite : la position est perdue. Ici c'est voulu (« pneumonie, oui ou non ? », pas « où ? »). En PyTorch : nn.AdaptiveAvgPool2d(1).
À toi de coder
Présenter nos radiographies comme les photos d'ImageNet
ResNet-18 a appris sur des photos préparées d'une certaine façon : on prépare les nôtres de la même façon.
| Réglage | Pourquoi |
|---|---|
| 224 × 224 | La taille des photos sur lesquelles il a appris. Il divise la taille par 32 : en 28 × 28, il ne resterait qu'une case par carte. MedMNIST fournit PneumoniaMNIST avec size=224 (images floues si on agrandissait nous-mêmes). |
| 3 canaux | Il attend du rouge, du vert et du bleu : as_rgb=True recopie le gris dans les trois canaux (ajout hors diapositive : plus simple que de changer la première couche, et on garde ses filtres). |
| Même normalisation | Chaque canal : (x − moyenne) / écart-type, avec les valeurs mesurées sur ImageNet : moyennes 0,485 · 0,456 · 0,406, écarts-types 0,229 · 0,224 · 0,225 (rouge, vert, bleu). |
Un pixel gris à 0,5 donne (0,5 − 0,485) / 0,229 ≈ 0,07 en rouge, 0,20 en vert et 0,42 en bleu : les trois canaux ne sont plus tout à fait égaux, et c'est normal. Erreur fréquente : oublier la normalisation. Le réseau reçoit des nombres d'une autre échelle que ceux qu'il connaît et marche nettement moins bien, sans message d'erreur.
À toi de coder
Deux façons d'emprunter, et le code
Extraction de caractéristiques : on gèle tout le réseau, il ne fait plus que lire l'image et produire les 512 moyennes ; seule la nouvelle dernière couche apprend : 512 poids + 1 biais = 513 paramètres. Rapide, et avec si peu de paramètres le risque de par-cœur est faible.
Fine-tuning (réglage fin) : tout le réseau continue d'apprendre, avec un pas 10 fois plus petit (0,0001 au lieu de 0,001) pour que les filtres bougent peu. Plus long, plus de risque de par-cœur, mais meilleure adaptation à des images différentes des photos. Ni la diapositive ni le notebook ne désignent d'avance un gagnant : on essaie les deux et on compare sur la validation.
model = resnet18(weights=ResNet18_Weights.IMAGENET1K_V1) # poids appris sur ImageNet for param in model.parameters(): param.requires_grad = False # on gèle tout model.fc = nn.Linear(512, 1) # créée APRÈS le gel : elle apprend optimizer = torch.optim.AdamW(model.fc.parameters(), lr=0.001) device = "cuda" if torch.cuda.is_available() else "cpu" model = model.to(device) # et X.to(device), y.to(device) dans la boucle
Fine-tuning : on saute les deux lignes du gel, et AdamW(model.parameters(), lr=0.0001). La perte reste BCEWithLogitsLoss. L'ordre compte : une couche créée avant le gel serait gelée aussi. ResNet-18 calcule environ 1 800 fois plus que ton CNN : une carte graphique (GPU) fait ces calculs des milliers à la fois. Erreur fréquente : oublier d'envoyer le paquet sur le device (« les données et le modèle ne sont pas sur le même appareil »).
À toi de coder