Aller au contenu
Apprendre.
  • M1 · toutes les matières15 matières : Préparation de données, API & sécurité, CI/CD, Docker, Merise, Dataviz, KNIME, ML appliqué : projet LoL, SQL & UML, SQL avancé, Machine Learning, Git, IA frugale & responsable, Qualité des données, MathématiquesAnnée
  • M2 · toutes les matières4 matières : Deep Learning, AI Act, Comex & Fundraising, Gouvernance & DataMeshAnnée
  • Deep LearningM2 · 4 modules · Réseaux de neurones, CNN, texte, modèles génératifs.Matière
  • Préparation de donnéesM1 · 3 modules · Nettoyer, transformer et fusionner des données avec Python.Matière
  • API & sécuritéM1 · 2 modules · Consommer, concevoir et sécuriser des API.Matière
  • CI/CDM1 · 1 module · Intégration et livraison continues.Matière
  • DockerM1 · 3 modules · Conteneurs, images, volumes, réseaux, Compose.Matière
  • MeriseM1 · 2 modules · Modéliser une base : MCD, MLD, MPD.Matière
  • DatavizM1 · 2 modules · Visualiser pour comprendre et décider.Matière
  • KNIMEM1 · 1 module · Explorer et nettoyer des données en workflow.Matière
  • ML appliqué : projet LoLM1 · 2 modules · Du besoin métier au POC de vision par ordinateur.Matière
  • SQL & UMLM1 · 3 modules · Modèles UML et requêtes SQL.Matière
  • SQL avancéM1 · 3 modules · Jointures, fenêtres, CTE, optimisation.Matière
  • Machine LearningM1 · 3 modules · Régression, classification, segmentation, projet.Matière

↑↓ naviguer↵ ouvre le résultat en surbrillanceesc fermer

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éeRéseauTailleRepère
1998LeNet-57 couches · environ 60 000 paramètreslit les chiffres manuscrits des chèques
2012AlexNet8 couches · 60 millions de paramètresImageNet : 15,3 % d'erreur top-5, contre 26,2 % pour le deuxième
2015ResNetjusqu'à 152 couchesImageNet : 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

ÉtapeForme de la sortie
Entrée : une image couleur3 × 224 × 224
Convolution : 64 filtres 7 × 7, stride 2, padding 3, puis ReLU64 × 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 filtres512 × 7 × 7
La moyenne de chaque carte512
Couche dense : 512 → 1 0001 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).

Déplace le pixel de l'organe : le résumé du max pooling 2 × 2 change quand le 1 change de zone, la moyenne reste 1 / 16 ≈ 0,06. (Carte 4 × 4 de la partie 2 ; position de départ : deuxième ligne, troisième colonne.)

À toi de coder

Max pooling contre moyenne globale

Pourquoi : voir de tes yeux que le max pooling dépend de la position du motif et que la moyenne globale, non. Deux cartes 4 × 4 sont données : carte_a (le 1 en deuxième ligne, troisième colonne) et carte_b (le même 1, une ligne plus bas). Complète maxpool2 (zones 2 × 2 sans chevauchement, résultat 2 × 2) et moyenne (la moyenne de toutes les cases, quelle que soit la taille de la carte), puis calcule pool_a, pool_b, moy_a, moy_b.

Ctrl/⌘+Entrée

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églagePourquoi
224 × 224La 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 canauxIl 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 normalisationChaque 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.

Fais varier le pixel gris : les trois canaux reçoivent des valeurs différentes. À 0,5 tu retrouves 0,07 / 0,20 / 0,42 de la diapositive.

À toi de coder

Normaliser un pixel et suivre les tailles de ResNet-18

Pourquoi : deux vérifications qui évitent des heures de débogage. (1) Un réseau emprunté ne marche que si ses entrées ont l'échelle d'ImageNet : écris normalise(x, canal) = (x − moyenne) / écart-type pour le canal 0 (rouge), 1 (vert) ou 2 (bleu). (2) Retrouve les tailles de la diapositive avec la formule de la partie 1 : sortie(n, k, p, s) = ⌊(n + 2p − k) / s⌋ + 1. Appelle-la pour la première convolution (224, noyau 7, padding 3, stride 2) puis pour le max pooling (noyau 3, padding 1, stride 2 ; le padding 1 est celui de torchvision, non précisé sur la diapositive).

Ctrl/⌘+Entrée

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 »).

Bascule entre extraction et fine-tuning : 513 poids entraînés contre tout le réseau. Le total du fine-tuning (11 177 025) est calculé ici : 11 689 512 − 513 000 (ancienne couche 512 → 1 000) + 513 (nouvelle couche) ; il n'est pas écrit tel quel dans le cours.

À toi de coder

Compter les poids gelés et entraînés

Pourquoi : c'est le chiffre qui justifie l'extraction (513 poids qui apprennent) et qui explique le risque de par-cœur du fine-tuning. Une couche dense entrees → sorties a entrees × sorties poids plus sorties biais. ResNet-18 d'origine compte 11 689 512 paramètres, dont sa couche dense 512 → 1 000. On la remplace par 512 → 1. Calcule ancienne_fc, nouvelle_fc, puis le nombre de paramètres entraînés en extraction (tout gelé sauf la nouvelle couche) et en finetuning (rien de gelé, après remplacement), et enfin rapport = finetuning / extraction.

Ctrl/⌘+Entrée