Partie 4 · Apprendre
Un neurone pour la pneumonie
Jusqu'ici tu as calculé à la main (et avec PyTorch) un neurone sur six patients. Place au premier vrai modèle entraîné sur de vraies images : un neurone qui lit les 784 pixels d'une radiographie (PneumoniaMNIST, 28 × 28) et donne une probabilité de pneumonie. L'objectif de l'exercice 4 : faire mieux qu'un programme qui répond toujours « pneumonie ».
Les données : 4 708 images pour apprendre, 524 pour la validation. Chaque image devient un tenseur de forme (1, 28, 28) : 1 canal (niveaux de gris), 28 lignes, 28 colonnes. Les pixels sont ramenés entre −1 et 1 (Normalize(mean=0.5, std=0.5)).
D'abord, la note à battre
Un programme qui répond toujours « pneumonie » a pour note la part de radiographies avec pneumonie. Sur les images de validation : 74,2 % de bonnes réponses. C'est la note à battre : un modèle à 70 % serait pire que ce programme sans intelligence. Les étiquettes valent 1 pour une pneumonie, 0 sinon, donc la part se calcule par une moyenne (en .float() avant .mean()).
Les images sont distribuées par paquets (DataLoader) : train_loader par paquets de 64, mélangés à chaque époque ; val_loader par paquets de 256, sans mélange.
À toi de coder
Le neurone : 785 paramètres
Avec nn.Sequential : nn.Flatten() met l'image à plat en 784 nombres, puis nn.Linear(784, 1) : un neurone avec 784 poids et 1 biais, soit 785 paramètres. Il renvoie un score, pas de sigmoïde à la fin : la perte s'en charge. nn.BCEWithLogitsLoss() applique la sigmoïde puis l'entropie croisée de BCELoss (exercice 2), en une seule étape plus stable ; elle attend les étiquettes en décimaux (etiquettes.float()). L'optimiseur : Adam, pas par défaut 0,001.
Perte de départ. Un neurone qui n'a rien appris donne environ 0,5 à chaque image, donc une perte proche de −log(0,5) ≈ 0,69 (le notebook accepte un écart de 0,25 autour de 0,69).
À toi de coder
Mesurer la note : evaluer
La fonction evaluer calcule la perte moyenne et l'accuracy (la part de bonnes réponses) sur un jeu d'images, sans calculer de pentes (torch.no_grad()). Piège classique : le modèle renvoie des scores, pas des probabilités. Le modèle répond « pneumonie » quand sa probabilité dépasse 0,5, c'est-à-dire quand le score dépasse 0 (la sigmoïde vaut 0,5 en 0) : choix = (scores > 0).long(). Comparer le score à 0,5 est une erreur.
À toi de coder
L'entraînement : cinq étapes, cinq époques
La boucle d'une époque, pour chaque paquet : (1) prédire scores = modele(images), (2) mesurer l'erreur loss_fn(scores, etiquettes.float()), (3) effacer les anciennes pentes optimizer.zero_grad(), (4) calculer les pentes perte.backward(), (5) faire un pas optimizer.step(). À chaque époque on note la perte d'entraînement (moyenne sur les paquets) et la perte de validation. Les 74 paquets défilent 5 fois.
Résultats du notebook :
| Époque | Perte entraînement | Perte validation | Accuracy validation |
|---|---|---|---|
| 1 | 0,376 | 0,266 | 91,8 % |
| 2 | 0,230 | 0,203 | 94,1 % |
| 3 | 0,192 | 0,179 | 94,8 % |
| 4 | 0,175 | 0,163 | 95,6 % |
| 5 | 0,164 | 0,156 | 95,0 % |
Les deux pertes baissent ensemble : le neurone apprend sans sur-apprendre.
La limite du neurone
Le neurone fait mieux que la note à battre (95,0 % contre 74,2 %, question 5.2 du notebook), mais il lit les pixels un par un, sans savoir lesquels sont voisins : il donne un poids à chaque case, comme s'il n'y avait pas de formes. Qu'est-ce qui pourrait l'aider ? Des couches cachées, qui repèrent des motifs (partie suivante), puis, en J2, des réseaux faits pour les images.
Pour aller plus loin (notebook) : entraîne 10 époques au lieu de 5. La perte de validation continue-t-elle de baisser ? Ce résultat n'est pas dans le notebook : à toi de l'observer.