Partie 3 · Voir
Éviter le par-cœur
Un vrai problème de dépistage : PneumoniaMNIST, des radiographies du thorax d'enfants de 1 à 5 ans (hôpital de Canton). Le jeu d'entraînement compte 4 708 radiographies, plus de 7 fois moins que les coupes d'organes ; environ 3 sur 4 montrent une pneumonie (3 494 sur 4 708). Répondre toujours « pneumonie » donne déjà 390 bonnes réponses sur 624 au test, soit 62,5 % : la référence à battre. Une seule question : pneumonie, oui ou non.
Le même CNN sert, avec une seule sortie : nn.Linear(32 * 7 * 7, 1) et la perte nn.BCEWithLogitsLoss (sigmoïde, puis −log(p)). Pour un enfant malade, p = a ; pour un enfant sain, p = 1 − a, où a est la sortie de la sigmoïde. Le CNN a maintenant 6 369 paramètres. Vérifie-le, et recode la perte.
À toi de coder
Entraîné sur peu d'images, ce CNN apprend par cœur : la perte d'entraînement baisse sans cesse, mais celle de test baisse, atteint un minimum, puis remonte. Le remède d'hier (garder les poids de la meilleure époque) a un défaut : pour choisir l'époque, on a regardé le test, qui n'est plus « jamais vu ». C'est comme laisser un élève passer le même examen 30 fois et ne retenir que sa meilleure note.
D'où un troisième groupe : l'entraînement (4 708 images) apprend les poids ; la validation (524) sert à choisir (époque, réglages, seuil) ; le test (624) note, une seule fois. La validation, c'est l'examen blanc ; le test, c'est l'examen. L'early stopping : à chaque époque on mesure la perte de validation ; si c'est la meilleure, on garde une copie des poids ; si elle ne baisse plus depuis plusieurs époques (la patience, par exemple 5), on arrête et on recharge la meilleure copie (copy.deepcopy(model.state_dict()) : sans deepcopy, on garde un lien vers les poids vivants).
À toi de coder
L'augmentation : plus d'images, sans en prendre de nouvelles. À chaque époque, chaque radiographie est légèrement transformée au hasard : T.RandomAffine(degrees=10, translate=(0.1, 0.1)) (jusqu'à 10° et 10 % de décalage ; valeurs courantes, à régler sur la validation). Le réseau ne revoit jamais exactement la même image, et il apprend qu'un petit décalage ne change pas la réponse. Seulement sur l'entraînement : la validation et le test restent intacts.
Et retourner l'image comme un miroir ? Pour un chat, oui ; pour une radiographie, non : le cœur changerait de côté, le réseau apprendrait sur des thorax qui n'existent presque pas. Une augmentation doit produire des images plausibles : le choix est médical autant qu'informatique.
Le dropout (nn.Dropout(0.5), avant la dernière couche) : pendant l'apprentissage, à chaque pas, chaque neurone est éteint au hasard, souvent avec une chance sur deux. Le réseau ne peut plus compter sur une petite équipe de neurones qui auraient retenu les détails d'une image ; à l'évaluation, tous travaillent.
Pourquoi doubler les valeurs gardées ? Un neurone reçoit 10 valeurs égales à 1. À l'évaluation : somme 10. À l'apprentissage, avec une valeur éteinte sur deux : 4 dans le tirage du cours (5 en moyenne). Sans correction, il apprendrait avec des entrées deux fois plus petites que celles de l'évaluation. On multiplie donc les valeurs gardées par 1 / (1 − p) = 1 / (1 − 0,5) = 2 : somme 8 pour ce tirage, 10 en moyenne. Avec p = 0,2, le facteur serait 1,25.
Le dropout ne sait pas dans quelle phase on est : on le lui dit. model.train() avant chaque époque d'apprentissage (le dropout tire au sort et double) ; model.eval() avant de mesurer validation ou test (il ne fait rien). Oublier model.eval() est l'erreur classique : la note change d'une mesure à l'autre.
À toi de coder
Le weight decay ramène à chaque pas chaque poids un peu vers 0 : w ← w × (1 − η λ), puis le pas habituel de la descente. Avec η = 0,001 et λ = 0,01 : × 0,99999 à chaque pas, presque rien, mais un poids dont la perte n'a pas besoin finit près de 0. En PyTorch : AdamW(model.parameters(), lr=0.001, weight_decay=0.01).
Pourquoi tous les poids ne baissent pas pareil ? Le rappel est le même pour tous, mais le pas de descente qui suit dépend de la pente de chaque poids. Exemple jouet du cours (facteur exagéré 0,99, η = 0,1, deux poids valant 2) : le poids utile (perte (w − 2)²) fait 2 × 0,99 = 1,98, pente 2 × (1,98 − 2) = −0,04, puis 1,98 − 0,1 × (−0,04) = 1,984 et se stabilise à 1,923 ; le poids inutile (perte indépendante de lui) tombe à 1,81 après 10 pas et 0,01 après 500.
À toi de coder