Deep Learning 2 — Classification
:::tip Notebook Kaggle Le code complet et exécutable de ce chapitre est sur Kaggle : Ouvrir →
Versions anglaise et chinoise disponibles depuis la page d'accueil. :::
Au chapitre précédent, nous avons fait de la régression — prédire un nombre. Ici nous attaquons la classification — prédire une catégorie. La grande surprise du chapitre, et c'est sa beauté : passer de l'un à l'autre ne demande quasiment rien. Le gradient garde la même forme.
Pourquoi ce chapitre ?
Vous y voyez :
- le neurone logistique (régression linéaire + sigmoïde) ;
- l'explication intuitive de la cross-entropy et sa dérivation depuis la loi de Bernoulli ;
- le fait que le gradient garde la même forme ;
- les pièges PyTorch :
BCELossvsBCEWithLogitsLossvsCrossEntropyLoss; - la classification multiclasse : softmax +
CrossEntropyLoss.
Du linéaire au logistique
Pour la classification binaire, la cible est . On veut une probabilité , donc une sortie dans .
Solution : on applique la fonction sigmoïde à la sortie linéaire.
écrase n'importe quel réel dans . C'est le neurone logistique.
La cross-entropy comme « surprise »
La fonction de coût pour la classification est la cross-entropy :
L'intuition : la cross-entropy mesure la « surprise » du modèle face à la vérité.
- Si et : aucune surprise, .
- Si et : énorme surprise, .
- Symétrique si .
Plus le modèle est confiant dans la bonne réponse, plus la loss est petite. Plus il est confiant dans la mauvaise réponse, plus la loss explose. C'est cette asymétrie qui pousse le modèle à apprendre des probabilités calibrées.
Dérivation depuis Bernoulli
Pourquoi cette formule précise ? Elle vient du maximum de vraisemblance. Si on modélise comme une variable de Bernoulli de paramètre :
La vraisemblance des observations est le produit. On prend le log négatif (pour avoir une fonction à minimiser) et on retombe exactement sur la cross-entropy.
Le gradient garde la même forme
C'est le moment magique du chapitre. Pour le neurone linéaire :
Pour le neurone logistique, après calcul :
Strictement la même formule. La seule différence est dans la définition de :
- linéaire :
- logistique :
Conséquence pratique : pour transformer un LinearNeuron en LogisticNeuron, il suffit de changer forward() pour appliquer la sigmoïde. Tout le reste reste identique.
Version PyTorch
model = nn.Linear(m, 1) # juste les logits, pas de Sigmoid
criterion = nn.BCEWithLogitsLoss() # applique la sigmoïde + cross-entropy
optimizer = torch.optim.SGD(model.parameters(), lr=0.1)
Pièges classiques
PyTorch propose plusieurs combinaisons modèle + loss qui se ressemblent mais ne sont pas équivalentes :
| Si la loss est... | Le modèle doit sortir... |
|---|---|
nn.BCELoss | une probabilité (avec Sigmoid à la fin) |
nn.BCEWithLogitsLoss | un logit (sans Sigmoid) |
nn.CrossEntropyLoss (multiclasse) | un vecteur de logits (sans Softmax) |
:::warning Le piège n°1
Ne jamais mettre Sigmoid dans le modèle ET utiliser BCEWithLogitsLoss. La sigmoïde s'appliquerait deux fois, le modèle n'apprend rien.
:::
BCEWithLogitsLoss est recommandé : numériquement plus stable que la combinaison Sigmoid + BCELoss.
Prédire la classe
Après l'entraînement, pour passer du logit à la classe :
model.eval()
with torch.no_grad():
logits = model(X_test_t)
proba = torch.sigmoid(logits) # logit → probabilité
y_hat = (proba >= 0.5).float() # probabilité → classe
Multiclasse : softmax + CrossEntropyLoss
Pour classes, la dernière couche a neurones et produit un vecteur de logits :
La généralisation de la sigmoïde est la softmax :
Toutes les probabilités sont positives et somment à 1.
La cross-entropy multiclasse est simplement :
Moins le log de la probabilité prédite pour la vraie classe.
model = nn.Sequential(
nn.Linear(m, 64),
nn.ReLU(),
nn.Linear(64, C), # logits multiclasse
)
criterion = nn.CrossEntropyLoss() # applique LogSoftmax + cross-entropy
Forme attendue de y
| Tâche | Forme du modèle | Forme de y | Type | Loss |
|---|---|---|---|---|
| Régression | (n, 1) | (n, 1) | float32 | MSELoss |
| Classification binaire | (n, 1) | (n, 1) | float32 | BCEWithLogitsLoss |
| Classification multiclasse | (n, C) | (n,) | long | CrossEntropyLoss |
:::warning y multiclasse
Pour CrossEntropyLoss, est un vecteur 1D d'entiers (pas one-hot). Type long, pas float.
:::
Prédire la classe
with torch.no_grad():
logits = model(X_test_t)
y_hat = torch.argmax(logits, dim=1) # classe la plus probable
argmax sur la dimension des classes — pas besoin d'appliquer la softmax explicitement, l'ordre est préservé.