Fonctions de perte PyTorch

La fonction de perte mesure l'écart entre les prédictions du modèle et les valeurs réelles, et sert de guide central pour l'entraînement des réseaux de neurones—l'optimiseur met à jour les paramètres du modèle en minimisant la fonction de perte.

PyTorch, dans sontorch.nnmodule, dispose de plus de dix fonctions de perte courantes intégrées, couvrant les principaux types de tâches tels que la classification, la régression et le classement.


1. Notions de base des fonctions de perte

Utilisation de base

Toutes les fonctions de perte PyTorch sontnn.Moduledes sous-classes de , avec une utilisation unifiée :

Exemple

import torch
import torch.nn as nn

# 1. Instancier la fonction de perte
criterion = nn.CrossEntropyLoss()

# 2. Calculer la perte (prédictions d'abord, vérité terrain ensuite)
loss = criterion(predictions, targets)

# 3. Rétropropagation
loss.backward()

Conventions de forme pour les valeurs de prédiction

Différentes fonctions de perte ont des exigences de forme différentes pour les entrées ; c'est la source d'erreurs la plus courante pour les débutants :

Fonction de perteForme de la prédiction (entrée)Forme de l’étiquette (cible)
CrossEntropyLoss(N, C)Logits bruts(N,)Indices de classe entiers
BCELoss(N,)Probabilités après Sigmoid(N,)Nombres à virgule flottante 0/1
BCEWithLogitsLoss(N,)Logits bruts(N,)Nombres à virgule flottante 0/1
MSELoss(N,)Tout nombre réel(N,)Tout nombre réel
NLLLoss(N, C)Probabilités après log_softmax(N,)Indices de classe entiers

N = taille du lot,C= nombre de classes


2. Fonctions de perte pour les tâches de classification

2.1 CrossEntropyLoss (perte d’entropie croisée)

La fonction de perte multiclasse la plus couramment utilisée,effectue automatiquement en interne Softmax + log + signe négatif; nul besoin d’appliquer manuellement Softmax à la sortie du modèle.

Formule mathématique :

Loss = -sum(y_c * log(p_c))

où p_c = exp(x_c) / sum_j exp(x_j) est la sortie Softmax.

Exemple

import torch
import torch.nn as nn

criterion = nn.CrossEntropyLoss()

# Sortie du modèle : logits bruts, forme (batch_size, num_classes)
# Pas besoin d'appliquer Softmax à l'avance !
predictions = torch.tensor([
    [2.0, 0.5, 0.3],   # Échantillon 1, classe la plus probable 0
    [0.1, 3.0, 0.2],   # Échantillon 2, classe la plus probable 1
    [0.2, 0.1, 4.0],   # Échantillon 3, classe la plus probable 2
])

# Étiquettes : indices de classe entiers, forme (batch_size,)
targets = torch.tensor([0, 1, 2])

loss = criterion(predictions, targets)
print(f"Perte: {loss.item():.4f}")  # Perte: 0.1763

Prend en charge les étiquettes souples (Label Smoothing) :

Exemple

# Lissage d'étiquettes, atténue le surapprentissage, souvent utilisé dans les compétitions de classification d'images
criterion = nn.CrossEntropyLoss(label_smoothing=0.1)

# Prend également en charge le passage direct d'étiquettes souples (distributions de probabilité)
soft_targets = torch.tensor([
    [0.9, 0.05, 0.05],
    [0.05, 0.9, 0.05],
])
predictions = torch.randn(2, 3)
loss = criterion(predictions, soft_targets)

Scénarios applicables :Toutes les tâches multi-classes telles que la classification multi-classes (chat/chien/oiseau), la classification d'images, la classification de texte, etc.


2.2 BCELoss (perte d'entropie croisée binaire)

Spécifiquement utilisé pourla classification binaireouet la classification multi-étiquettestâches. L'entrée doit êtreSigmoidvaleurs de probabilité après traitement (0-1).

Formule mathématique :

Loss = -[y * log(p) + (1-y) * log(1-p)]

Exemple

criterion = nn.BCELoss()

# La sortie du modèle doit d'abord passer par Sigmoid, intervalle (0, 1)
raw_output = torch.tensor([2.0, -1.0, 0.5, -3.0])
predictions = torch.sigmoid(raw_output)   # [0.88, 0.27, 0.62, 0.05]

# Étiquettes : type float 0.0 ou 1.0
targets = torch.tensor([1.0, 0.0, 1.0, 0.0])

loss = criterion(predictions, targets)
print(f"Perte : {loss.item():.4f}")  # Perte : 0.2824

# Classification multi-étiquettes (chaque échantillon peut appartenir à plusieurs classes)
# forme des prédictions : (batch_size, num_labels)
predictions_ml = torch.sigmoid(torch.randn(4, 5))
targets_ml     = torch.randint(0, 2, (4, 5)).float()
loss_ml = criterion(predictions_ml, targets_ml)

BCELossL'entrée doit être dans l'intervalle (0, 1). Passer des logits bruts provoquera une instabilité numérique, voire NaN. Il est recommandé d'utiliser la version améliorée ci-dessous,BCEWithLogitsLoss。


2.3 BCEWithLogitsLoss

BCELossqui est une version améliorée de ,et effectue automatiquement Sigmoid en interne, offrant une meilleure stabilité numérique et recommandée comme premier choix.

Exemple

criterion = nn.BCEWithLogitsLoss()

# Passer les logits bruts directement, pas besoin de Sigmoid manuel
predictions = torch.tensor([2.0, -1.0, 0.5, -3.0])
targets     = torch.tensor([1.0,  0.0, 1.0,  0.0])

loss = criterion(predictions, targets)
print(f"Perte : {loss.item():.4f}")

# Équivalent à (mais avec une meilleure stabilité numérique) :
# loss = BCELoss(Sigmoid(predictions), targets)

Avec des poids d'échantillons positifs (gestion du déséquilibre des classes) :

Exemple

# pos_weight : poids pour les échantillons positifs ; plus la valeur est grande, plus l'attention est portée aux échantillons positifs
# Par exemple, si les échantillons négatifs sont 10 fois plus nombreux que les échantillons positifs, définissez pos_weight=10
pos_weight = torch.tensor([10.0])
criterion = nn.BCEWithLogitsLoss(pos_weight=pos_weight)

Scénarios d'application :Classification binaire (détection de spam), classification multi-étiquettes (étiquetage multi-étiquettes d'articles), détection d'objets (discrimination premier plan/arrière-plan).


2.4 NLLLoss (perte de log-vraisemblance négative)

Nécessite une application manuelle à la sortie du modèlelog_softmax, ce qui offre une plus grande flexibilité.CrossEntropyLoss = LogSoftmax + NLLLoss。

Exemple

criterion = nn.NLLLoss()

# Il faut d'abord appliquer manuellement log_softmax
raw_output   = torch.randn(4, 3)   # (batch, num_classes)
log_probs    = torch.log_softmax(raw_output, dim=1)

targets = torch.tensor([0, 2, 1, 0])
loss = criterion(log_probs, targets)

Cas d'utilisation :Lorsque les log-probabilités sont nécessaires dans les étapes intermédiaires (par exemple, CTC, Beam Search) ; dans les autres cas, préférez utiliserCrossEntropyLoss。


3. Fonctions de perte pour la régression

3.1 MSELoss (erreur quadratique moyenne)

La perte de régression la plus classique, qui esttrès sensible aux erreurs importantes(car la mise au carré amplifie l'impact des erreurs importantes).

Formule mathématique :

MSELoss = (1/N) * sum((y_i - y_hat_i)^2)

Exemple

criterion = nn.MSELoss()

predictions = torch.tensor([2.5, 0.5, 2.0, 8.0])
targets     = torch.tensor([3.0, -0.5, 2.0, 7.0])

loss = criterion(predictions, targets)
print(f"MSE Loss: {loss.item():.4f}")  # MSE Loss: 0.3750

# Vérification manuelle
manual = ((predictions - targets) ** 2).mean()
print(f"Calcul manuel : {manual.item():.4f}")  # 0.3750

Scénarios applicables :Régression sur valeurs continues telle que la prédiction de prix immobiliers et la prédiction de température ; fonctionne bien lorsqu'il n'y a pas de valeurs aberrantes évidentes dans les données.


3.2 L1Loss (Erreur absolue moyenne)

PairePlus robuste aux valeurs aberrantescar il utilise la valeur absolue au lieu du carré, donc les grandes erreurs ne sont pas sur-amplifiées.

Formule mathématique :

L1Loss = (1/N) * sum(|y_i - y_hat_i|)

Exemple

criterion = nn.L1Loss()

predictions = torch.tensor([2.5, 0.5, 2.0, 8.0])
targets     = torch.tensor([3.0, -0.5, 2.0, 7.0])

loss = criterion(predictions, targets)
print(f"Perte L1 : {loss.item():.4f}")  # Perte L1 : 0.5000

3.3 SmoothL1Loss (Huber Loss)

Combine les avantages de MSE et de L1: utilise MSE pour les petites erreurs (gradients lisses et stables), et L1 pour les grandes erreurs (résistant aux valeurs aberrantes). C'est la perte standard en détection d'objets (Faster R-CNN).

Formule mathématique :

SmoothL1(x) = 0.5*x^2 if |x| < 1, else |x| - 0.5

Exemple

criterion = nn.SmoothL1Loss()

predictions = torch.tensor([2.5, 0.5, 2.0, 8.0])
targets     = torch.tensor([3.0, -0.5, 2.0, 7.0])

loss = criterion(predictions, targets)
print(f"Perte SmoothL1 : {loss.item():.4f}")

Comparaison des trois pertes de régression :

Exemple

import torch
import torch.nn as nn

predictions = torch.tensor([0.0, 1.0, 5.0, 10.0])  # Simuler des erreurs de différentes amplitudes
targets     = torch.zeros(4)

for name, fn in [("MSELoss", nn.MSELoss(reduction='none')),
                 ("L1Loss",  nn.L1Loss(reduction='none')),
                 ("SmoothL1",nn.SmoothL1Loss(reduction='none'))]:
    losses = fn(predictions, targets)
    print(f"{name:12s}: {[f'{l:.2f}' for l in losses.tolist()]}")
输出示例:
MSELoss     : ['0.00', '1.00', '25.00', '100.00']   # 大误差被平方放大
L1Loss      : ['0.00', '1.00',  '5.00',  '10.00']   # 线性增长
SmoothL1    : ['0.00', '0.50',  '4.50',   '9.50']   # 中间值

Scénarios d'application :Tâches de régression comportant à la fois de petites erreurs et des valeurs aberrantes, comme la régression de boîtes englobantes en détection d'objets et l'estimation de profondeur.


4. Fonctions de perte avancées

4.1 HuberLoss

SmoothL1LossUne version généralisée de , permettant un basculement de seuil personnalisédelta(par défaut 1.0).

Exemple

# delta contrôle le point de basculement entre MSE et L1
criterion = nn.HuberLoss(delta=1.5)

predictions = torch.randn(10)
targets     = torch.randn(10)
loss = criterion(predictions, targets)

4.2 KLDivLoss (Divergence KL)

Mesure la différence entre deux distributions de probabilité, couramment utilisée dansla distillation de connaissancesetl'autoencodeur variationnel (VAE)。

Formule mathématique :

KL(P || Q) = sum(P(i) * log(P(i) / Q(i)))

Exemple

criterion = nn.KLDivLoss(reduction='batchmean')

# l'entrée doit être des log-probabilités, la cible est des probabilités ordinaires
log_predictions = torch.log_softmax(torch.randn(4, 5), dim=1)
targets         = torch.softmax(torch.randn(4, 5), dim=1)

loss = criterion(log_predictions, targets)
print(f"Perte de divergence KL : {loss.item():.4f}")

Utilisation typique dans la distillation de connaissances :

Exemple

temperature = 4.0  # Coefficient de température, plus il est grand, plus c'est doux

# Sortie du modèle enseignant
teacher_logits = torch.randn(32, 10)
# Sortie du modèle étudiant
student_logits = torch.randn(32, 10)

soft_labels = torch.softmax(teacher_logits / temperature, dim=1)
soft_preds  = torch.log_softmax(student_logits / temperature, dim=1)

distill_loss = nn.KLDivLoss(reduction='batchmean')(soft_preds, soft_labels)
distill_loss *= temperature ** 2  # Restaurer la magnitude du gradient

4.3 MarginRankingLoss (perte de classement)

Détermine l'ordre relatif de deux entrées, couramment utilisé dansl'apprentissage du classementetl'apprentissage de similarité。

Exemple

criterion = nn.MarginRankingLoss(margin=0.5)

# x1 doit être plus proche de la cible que x2 (y=1 signifie x1 > x2)
x1 = torch.tensor([0.8, 0.3, 0.6])
x2 = torch.tensor([0.2, 0.7, 0.5])
y  = torch.tensor([1.0, -1.0, 1.0])  # 1: x1>x2, -1: x1<x2

loss = criterion(x1, x2, y)

4.4 TripletMarginLoss (perte de triplet)

Utilisé pour l'apprentissage de métriques, nécessitantune ancreetun positif (même classe)que la distance soit inférieure à celle avecun négatif (classe différente)distance.

Exemple

criterion = nn.TripletMarginLoss(margin=1.0)

# Chaque vecteur a une dimension embedding_dim
anchor   = torch.randn(32, 128)   # Échantillon d'ancre
positive = torch.randn(32, 128)   # Échantillon positif (même classe)
negative = torch.randn(32, 128)   # Échantillon négatif (classe différente)

loss = criterion(anchor, positive, negative)
# Objectif : dist(anchor, positive) + margin < dist(anchor, negative)

Scénarios d'application :Reconnaissance faciale, recherche d'images, apprentissage few-shot.


4.5 CTCLoss (Perte d'étiquetage de séquence)

Utilisé pourles tâches séquentielles où les longueurs d'entrée et de sortie ne sont pas alignées,telles que la reconnaissance vocale (séquence acoustique -> séquence de texte), la reconnaissance d'écriture manuscrite.

Exemple

criterion = nn.CTCLoss(blank=0)  # indice de l'étiquette blank

# log_probs : (T, N, C) T=pas de temps, N=batch, C=nombre de classes
T, N, C = 50, 4, 20
log_probs    = torch.log_softmax(torch.randn(T, N, C), dim=2)
targets      = torch.randint(1, C, (N * 10,))   # Séquence cible concaténée
input_lengths  = torch.full((N,), T, dtype=torch.long)
target_lengths = torch.full((N,), 10, dtype=torch.long)

loss = criterion(log_probs, targets, input_lengths, target_lengths)

5. Explication détaillée du paramètre reduction

Toutes les fonctions de perte prennent en charge lereductionparamètre, qui contrôle la façon dont les pertes par échantillon sont agrégées :

Exemple

predictions = torch.tensor([1.0, 2.0, 3.0, 4.0])
targets     = torch.tensor([1.5, 2.5, 2.0, 5.0])

# Erreurs par échantillon : [0.25, 0.25, 1.00, 1.00]

# mean (par défaut) : moyenne sur tous les échantillons
loss_mean = nn.MSELoss(reduction='mean')(predictions, targets)
print(f"moyenne : {loss_mean.item():.4f}")   # 0.6250

# somme : somme sur tous les échantillons
loss_sum  = nn.MSELoss(reduction='sum')(predictions, targets)
print(f"somme : {loss_sum.item():.4f}")    # 2.5000

# none : renvoie la perte individuelle de chaque échantillon (souvent utilisé pour la pondération)
loss_none = nn.MSELoss(reduction='none')(predictions, targets)
print(f"none : {loss_none.tolist()}")     # [0.25, 0.25, 1.0, 1.0]

reduction='none'Application pratique — pondération de différents échantillons :

Exemple

# Donner un poids plus élevé aux échantillons avec des erreurs plus grandes (idée de focal loss)
per_sample_loss = nn.MSELoss(reduction='none')(predictions, targets)
weights = torch.tensor([1.0, 1.0, 2.0, 2.0])   # Définir manuellement les poids
weighted_loss = (per_sample_loss * weights).mean()

6. Poids de classe et poids d'échantillon

Poids de classe (gestion du déséquilibre de classe)

Lorsque certaines classes ont très peu d'échantillons dans le jeu de données, attribuez des poids plus élevés aux classes minoritaires :

Exemple

# Supposons 3 classes : la classe 0 a 1000, la classe 1 a 100, la classe 2 a 50
# Poids inversement proportionnel à la fréquence
class_counts = torch.tensor([1000.0, 100.0, 50.0])
weights = 1.0 / class_counts
weights = weights / weights.sum() * len(weights)  # Normaliser

criterion = nn.CrossEntropyLoss(weight=weights)

Ignorer des étiquettes spécifiques

Dans des tâches telles que la segmentation sémantique, il est souvent nécessaire d'ignorer les pixels de bordure (étiquetés 255) :

Exemple

# ignore_index : ignorer cette étiquette lors du calcul de la perte
criterion = nn.CrossEntropyLoss(ignore_index=255)

# Scénario de segmentation sémantique
predictions = torch.randn(2, 21, 256, 256)   # (N, C, H, W)
targets     = torch.randint(0, 22, (2, 256, 256))
targets[targets == 21] = 255                  # Limites étiquetées comme 255

loss = criterion(predictions, targets)

7. Fonctions de perte personnalisées

Lorsque les fonctions de perte intégrées ne peuvent pas répondre aux exigences, vous pouvez les personnaliser de deux manières :

Méthode 1 : Fonctionnelle (simple)

Exemple

import torch
import torch.nn.functional as F

def focal_loss(predictions, targets, gamma=2.0, alpha=0.25):
    """
Focal Loss : résout le grave problème de déséquilibre entre échantillons positifs et négatifs en détection d'objets
Réduit le poids des échantillons facilement classifiés, permettant au modèle de se concentrer sur les échantillons difficiles
    """

    ce_loss = F.cross_entropy(predictions, targets, reduction='none')
    pt = torch.exp(-ce_loss)                          # Probabilité de prédiction correcte
    focal_weight = alpha * (1 - pt) ** gamma          # Les échantillons difficiles obtiennent un poids plus élevé
    return (focal_weight * ce_loss).mean()


# Utiliser
predictions = torch.randn(8, 10)
targets     = torch.randint(0, 10, (8,))
loss = focal_loss(predictions, targets)

Méthode 2 : Sous-classement de nn.Module (recommandé)

Exemple

import torch
import torch.nn as nn

class DiceLoss(nn.Module):
    """
Perte Dice : couramment utilisée en segmentation d'images, optimise directement le coefficient Dice
Plus robuste que CrossEntropy face au déséquilibre des classes (par ex., segmentation de petits objets)
    """

    def __init__(self, smooth=1.0):
        super().__init__()
        self.smooth = smooth

    def forward(self, predictions, targets):
        # predictions: (N, C, H, W) -> probabilités après sigmoid
        # targets: (N, C, H, W) -> étiquettes encodées en one-hot
        predictions = torch.sigmoid(predictions)

        # Aplatir en (N, -1)
        pred_flat   = predictions.view(predictions.size(0), -1)
        target_flat = targets.view(targets.size(0), -1).float()

        intersection = (pred_flat * target_flat).sum(dim=1)
        dice = (2.0 * intersection + self.smooth) / (
            pred_flat.sum(dim=1) + target_flat.sum(dim=1) + self.smooth
        )
        return 1 - dice.mean()


class CombinedLoss(nn.Module):
    """
Perte combinée : CrossEntropy + Dice, équilibrant la classification au niveau des pixels et le chevauchement des régions
Combinaison courante pour la segmentation d'images
    """

    def __init__(self, ce_weight=0.5, dice_weight=0.5):
        super().__init__()
        self.ce_weight   = ce_weight
        self.dice_weight = dice_weight
        self.ce   = nn.CrossEntropyLoss()
        self.dice = DiceLoss()

    def forward(self, predictions, targets):
        return (self.ce_weight   * self.ce(predictions, targets) +
                self.dice_weight * self.dice(predictions, targets))


# Utiliser
criterion = CombinedLoss(ce_weight=0.4, dice_weight=0.6)

8. Guide de sélection de la fonction de perte

Sélectionner par type de tâche

Type de tâcheFonction de perte recommandéeNotes
Classification multiclasseCrossEntropyLossLe plus général, choix privilégié
Classification multiclasse (déséquilibre de classes)CrossEntropyLoss(weight=...)Pondérer les classes minoritaires
Classification multiclasse (étiquettes bruitées)CrossEntropyLoss(label_smoothing=0.1)Empêche le surapprentissage
Classification binaireBCEWithLogitsLossPlus stable que BCELoss
Classification multi-étiquettesBCEWithLogitsLossChaque étiquette est jugée indépendamment
Détection d'objets (tête de classification)CrossEntropyLoss / Focal LossUtilisez Focal lorsque les échantillons positifs et négatifs sont déséquilibrés
Détection d'objets (tête de régression)SmoothL1Loss / GIoULossPratique standard
Régression ordinaireMSELossPremier choix lorsqu'il n'y a pas de valeurs aberrantes
Régression avec valeurs aberrantesHuberLoss / SmoothL1LossRégression robuste
Segmentation d'imagesCrossEntropyLoss + DiceLossL'utilisation combinée fonctionne mieux
Reconnaissance de la paroleCTCLossAlignement de séquences
Apprentissage de métrique / reconnaissance facialeTripletMarginLossApprendre la distance dans l'espace des caractéristiques
Distillation de connaissancesKLDivLossApprendre la distribution des étiquettes souples

Erreurs d'utilisation courantes et notes

Exemple

# Erreur : application manuelle de Softmax avant CrossEntropyLoss
output = torch.softmax(model(x), dim=1)   # Softmax redondant
loss   = nn.CrossEntropyLoss()(output, targets)  # Il sera appliqué à nouveau en interne

# Correct : passer les logits directement
output = model(x)  # Logits bruts
loss   = nn.CrossEntropyLoss()(output, targets)


# Incorrect : passer des valeurs brutes sans sigmoïde à BCELoss
loss = nn.BCELoss()(model(x), targets)    # Peut dépasser [0,1], numériquement instable

# Correct : utiliser BCEWithLogitsLoss
loss = nn.BCEWithLogitsLoss()(model(x), targets)


# Incorrect : incompatibilité de type d'étiquette (entier vs flottant)
targets = torch.tensor([1, 0, 1])                     # Type entier
loss    = nn.BCEWithLogitsLoss()(preds, targets)       # Erreur !

# Correct : BCEWithLogitsLoss nécessite des étiquettes de type float
targets = torch.tensor([1.0, 0.0, 1.0])               # Type float
loss    = nn.BCEWithLogitsLoss()(preds, targets)       # Correct


# Incorrect : la perte n'est pas extraite avec .item(), ce qui fait que le graphe de calcul continue de s'accumuler et que la mémoire GPU déborde
total_loss += loss      # la perte est un tenseur et conserve le graphe de calcul

# Correct : utilisez .item() pour extraire un scalaire
total_loss += loss.item()

Exemple complet d'entraînement

Exemple

import torch
import torch.nn as nn
import torch.optim as optim

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

# Modèle & fonction de perte & optimiseur
model     = MyModel().to(device)
criterion = nn.CrossEntropyLoss(label_smoothing=0.1)
optimizer = optim.Adam(model.parameters(), lr=1e-3)

for epoch in range(num_epochs):
    model.train()
    total_loss, correct = 0.0, 0

    for inputs, labels in train_loader:
        inputs = inputs.to(device)
        labels = labels.to(device)

        optimizer.zero_grad()
        outputs = model(inputs)          # Logits bruts

        loss = criterion(outputs, labels)
        loss.backward()
        optimizer.step()

        total_loss += loss.item() * inputs.size(0)    # .item() extrait un scalaire
        correct += (outputs.argmax(1) == labels).sum().item()

    avg_loss = total_loss / len(train_loader.dataset)
    accuracy = correct / len(train_loader.dataset)
    print(f"Époque {epoch+1} | Perte: {avg_loss:.4f} | Préc: {accuracy:.4f}")
Autres extensions