← Blog
Une sphère corail dans le creux central de trois cavités vertes d’un bloc en céramique crème sur fond lavande pâle.
Publié le
2026-11-09

CrossEntropyLoss PyTorch : logits, cibles et réduction

CrossEntropyLoss de PyTorch compare les scores de classe d’un modèle aux cibles attendues. Pour une classification multiclasse classique, fournissez des logits bruts de forme [N, C] et des indices de classe entiers de forme [N]. N’appliquez pas softmax aux logits avant le calcul de la perte.

Ce guide utilise de petits exemples déterministes sur CPU pour montrer le calcul de la perte, l’effet de la forme des cibles et le dénominateur d’une moyenne pondérée. Les exemples et 18 contrôles ont été exécutés sous Windows avec Python 3.12.14 et PyTorch 2.15.0+cpu. Aucun modèle ni jeu de données à télécharger n’est nécessaire.

Commencer avec un indice de classe par exemple

Supposons qu’un modèle choisisse parmi trois classes mutuellement exclusives. Chaque ligne de sortie contient trois scores. La cible indique la colonne correcte, en commençant à zéro. Un lot de deux exemples utilise donc des logits de forme [2, 3] et des cibles de forme [2].

torch.nn.CrossEntropyLoss() est l’interface sous forme de module ; torch.nn.functional.cross_entropy est l’interface fonctionnelle utilisée ici. La référence de l’API fonctionnelle décrit ses arguments. Exécutez les blocs dans un environnement disposant de PyTorch et conservez les imports du premier bloc.

import torch
import torch.nn.functional as F

logits = torch.tensor([[2.0, 0.0, -1.0],
                       [0.0, 1.0, 3.0]], requires_grad=True)
targets = torch.tensor([0, 1], dtype=torch.long)

per_item = F.cross_entropy(logits, targets, reduction="none")
loss = per_item.mean()
loss.backward()

print("Per item:", [round(x, 6) for x in per_item.tolist()])
print("Mean:", round(loss.item(), 6))
print("Gradient shape:", tuple(logits.grad.shape))

Voici la sortie observée :

Per item: [0.169846, 2.169846]
Mean: 1.169846
Gradient shape: (2, 3)

La première ligne attribue le score le plus élevé à la classe 0, qui est aussi sa cible : sa perte est faible. La seconde favorise la classe 2, mais sa cible est la classe 1, ce qui produit une perte supérieure. La moyenne vaut 1.169846. Il s’agit d’une valeur de perte, pas d’un pourcentage de prédictions correctes.

L’appel à backward() remplit le tenseur des gradients des logits. Dans un modèle, le calcul se poursuit à travers les opérations qui les ont produits. Cet exemple ne réalise aucune mise à jour d’optimiseur et ne démontre pas la qualité d’un apprentissage sur des données réelles.

Fournir des logits, pas des probabilités issues de softmax

Avec des cibles sous forme d’indices et les réglages par défaut, le calcul par exemple sélectionne la classe cible dans la sortie de log-softmax, puis en prend l’opposé. Nos contrôles ont reproduit le résultat avec F.nll_loss(F.log_softmax(logits, dim=1), targets). La référence LogSoftmax décrit cette transformation.

Appliquer softmax avant CrossEntropyLoss change les scores reçus par la fonction. Dans ce test, F.cross_entropy(logits.softmax(dim=1), targets) a renvoyé 1.022646 au lieu de 1.169846. L’appel aboutit, ce qui rend l’erreur discrète. Utilisez les probabilités pour examiner les prédictions, tout en conservant les logits d’origine pour cette perte.

Ajouter la même constante à tous les scores d’une ligne ne change pas les probabilités relatives des classes. L’ajout de 10 000 à ces logits a conservé la perte dans la tolérance du test. Utilisez le calcul stable de la bibliothèque plutôt qu’une implémentation séparant exponentielles et logarithmes.

Vérifier la forme et le type des cibles

Pour les indices de classe de cet exemple, utilisez le type torch.long. Chaque valeur doit désigner une classe comprise entre 0 et C - 1, sauf la valeur configurée pour les cibles ignorées. La référence CrossEntropyLoss décrit les deux formes de cibles : indices et probabilités.

  • Étiquettes flottantes de forme [N] : l’environnement de test les a rejetées. Convertissez les indices catégoriels en long après avoir vérifié qu’ils représentent bien des identifiants de classe entiers.
  • Étiquettes de forme [N, 1] : cette dimension supplémentaire a été rejetée pour des logits [N, C]. Corrigez la construction des cibles au lieu de remodeler les tenseurs sans vérifier leur sens.
  • Étiquette égale à C : elle dépasse la plage des classes. Avec trois colonnes de sortie, la cible 3 a échoué, car les indices valides sont 0, 1 et 2.

Examinez un lot avant de lancer un long entraînement. Vérifiez les formes, la correspondance des étiquettes et la présence de valeurs finies au moment de l’appel à la perte. Notre guide DataLoader, en anglais explique l’assemblage des lots : une erreur de préparation peut n’apparaître qu’au calcul de la perte.

Choisir la réduction en connaissance de cause

reduction="none" conserve ici une perte par exemple. "sum" additionne ces pertes ; la valeur par défaut, "mean", en calcule la moyenne dans le cas sans pondération ni cible ignorée. Nous avons vérifié les trois options avec le premier exemple.

Une moyenne ne divise pas toujours la somme par la taille du lot. Avec des indices de classe, des poids par classe, aucun lissage des étiquettes et un poids effectif positif, la moyenne divise la somme des pertes pondérées par les poids des classes cibles valides. Un exemple ignoré ne contribue ni à la perte ni au poids total.

logits = torch.tensor([[2.0, 0.0, -1.0],
                       [0.0, 1.0, 3.0],
                       [1.0, 2.0, 0.0]])
targets = torch.tensor([0, 1, -100], dtype=torch.long)
weights = torch.tensor([1.0, 3.0, 2.0])
valid = targets != -100

if not valid.any().item():
    raise ValueError("This batch has no supervised targets")
denominator = weights[targets[valid]].sum()
if denominator.item() <= 0:
    raise ValueError("The valid targets have zero total weight")

parts = F.cross_entropy(logits, targets, weight=weights,
                        ignore_index=-100, reduction="none")
manual_mean = parts.sum() / denominator
builtin_mean = F.cross_entropy(logits, targets, weight=weights,
                               ignore_index=-100)
torch.testing.assert_close(manual_mean, builtin_mean)
print("Weighted parts:", [round(x, 6) for x in parts.tolist()])
print("Denominator:", denominator.item())
print("Weighted mean:", round(builtin_mean.item(), 6))

Résultat :

Weighted parts: [0.169846, 6.509538, 0.0]
Denominator: 4.0
Weighted mean: 1.669846

Les cibles valides sont les classes 0 et 1, de poids respectifs 1 et 3. Le dénominateur vaut donc 4, et non deux exemples ou trois lignes. Diviser la même somme pondérée par deux donnerait 3.339692 et changerait l’échelle du gradient. Les poids modifient la contribution des exemples ; choisissez-les pour une raison de modélisation explicite et évaluez leur effet.

Traiter les cibles ignorées et l’absence de supervision

ignore_index=-100 ignore les positions portant cette valeur cible. Il ne retire pas une classe de sortie du calcul softmax du modèle. Il s’applique aux indices ; les cibles probabilistes nécessitent leur propre logique de masque et de réduction.

Dans l’environnement testé, la moyenne par défaut d’un lot dont toutes les cibles étaient ignorées a renvoyé NaN. Un lot dont toutes les cibles valides avaient un poids de classe nul a produit le même résultat. L’exemple pondéré vérifie les deux situations avant la division. Dans une boucle d’entraînement, décidez explicitement de rejeter ce lot ou d’omettre sa mise à jour ; remplacer une perte non finie par un zéro arbitraire peut masquer un problème d’étiquetage.

Le dénominateur compte aussi lors de la combinaison de micro-lots. Deux micro-lots dont les poids cibles valides diffèrent ne doivent pas automatiquement contribuer à parts égales à la moyenne rapportée. Notre guide d’accumulation des gradients, en anglais traite le moment des mises à jour ; préservez la normalisation voulue lorsque vous l’adaptez à des cibles pondérées ou masquées.

Utiliser des cibles probabilistes lorsque le problème le justifie

Les cibles probabilistes décrivent une distribution sur les mêmes classes mutuellement exclusives, par exemple lorsqu’une étiquette attribue volontairement une masse partielle à plusieurs classes. Ce sont des tenseurs flottants de même forme que les logits. Vérifiez les valeurs et leur somme le long de la dimension des classes.

logits = torch.tensor([[2.0, 0.0, -1.0], [0.0, 1.0, 3.0]])
soft_targets = torch.tensor([[0.7, 0.2, 0.1], [0.1, 0.3, 0.6]])
if not torch.isfinite(soft_targets).all().item():
    raise ValueError("Targets must be finite")
if ((soft_targets < 0) | (soft_targets > 1)).any().item():
    raise ValueError("Target probabilities must be between 0 and 1")
torch.testing.assert_close(soft_targets.sum(dim=1), torch.ones(2))

loss = F.cross_entropy(logits, soft_targets)
manual = -(soft_targets * F.log_softmax(logits, dim=1)).sum(dim=1).mean()
torch.testing.assert_close(loss, manual)
print("Soft-target mean:", round(loss.item(), 6))

La moyenne obtenue vaut 0.969846. Le contrôle la reconstruit en multipliant les probabilités cibles par les log-probabilités, en sommant sur les classes, puis en moyennant sur les exemples. Cette forme de cible diffère de celle de l’exemple pondéré à indices : n’en réutilisez pas le dénominateur sans vérifier la formule applicable.

Notre test a aussi fourni une cible invalide remplie de 2.0. PyTorch a renvoyé une perte finie au lieu de rejeter la distribution. Un appel réussi ne prouve donc pas la validité des cibles probabilistes. Les contrôles explicites ci-dessus détectent ce type d’erreur avant le calcul.

Placer correctement la dimension des classes

La prédiction dense ajoute des dimensions spatiales. Pour des logits de segmentation de forme [N, C, H, W], les indices cibles ont la forme [N, H, W]. Sans réduction, on obtient une perte à chaque position cible.

logits = torch.arange(24, dtype=torch.float32).reshape(2, 3, 2, 2) / 10
targets = torch.tensor([[[0, 1], [2, 0]], [[2, 1], [0, 2]]])
pixel_losses = F.cross_entropy(logits, targets, reduction="none")
print("Pixel loss shape:", tuple(pixel_losses.shape))

La sortie a la forme (2, 2, 2). Nous avons comparé ces huit pertes par pixel à un calcul de classification équivalent de forme [8, 3], après avoir déplacé la dimension des classes à la fin puis aplati les tenseurs. Aplatir directement les logits d’origine mélangerait les scores des classes.

Distinguer les classes exclusives des étiquettes indépendantes

Lorsqu’un exemple peut porter plusieurs étiquettes indépendantes, les contraindre à former une seule distribution de classes exprime généralement le mauvais problème. BCEWithLogitsLoss associe une perte binaire fondée sur sigmoid aux logits et prend en charge des cibles binaires indépendantes. Une classification binaire peut également utiliser deux logits avec CrossEntropyLoss : gardez une convention cohérente entre sorties et cibles.

Avant de passer à l’échelle, confirmez l’axe des classes, la représentation des cibles, les pertes individuelles et le dénominateur sur un lot. Ces contrôles CPU vérifient les calculs illustrés, la rétropropagation et les cas d’erreur. Ils n’établissent ni débit GPU, ni convergence, ni calibration, ni précision sur des données réelles. L’absence de NumPy a déclenché un avertissement d’initialisation ; les exemples, qui utilisent uniquement des tenseurs, ont abouti sans cette bibliothèque.

Votre prochaine charge de travail sur Hivenet.

Choisissez une charge de travail d’IA, de calcul ou de stockage à tester sur Hivenet, ou échangez avec notre équipe sur sa mise en production.