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.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 perte | Forme 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.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
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
# 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
# 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
# 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
# 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 utiliser
CrossEntropyLoss。
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
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
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
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.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
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
# 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
# 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
# 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
# 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
# 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
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
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
# 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
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.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.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âche | Fonction de perte recommandée | Notes |
|---|---|---|
| Classification multiclasse | CrossEntropyLoss | Le 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 binaire | BCEWithLogitsLoss | Plus stable que BCELoss |
| Classification multi-étiquettes | BCEWithLogitsLoss | Chaque étiquette est jugée indépendamment |
| Détection d'objets (tête de classification) | CrossEntropyLoss / Focal Loss | Utilisez Focal lorsque les échantillons positifs et négatifs sont déséquilibrés |
| Détection d'objets (tête de régression) | SmoothL1Loss / GIoULoss | Pratique standard |
| Régression ordinaire | MSELoss | Premier choix lorsqu'il n'y a pas de valeurs aberrantes |
| Régression avec valeurs aberrantes | HuberLoss / SmoothL1Loss | Régression robuste |
| Segmentation d'images | CrossEntropyLoss + DiceLoss | L'utilisation combinée fonctionne mieux |
| Reconnaissance de la parole | CTCLoss | Alignement de séquences |
| Apprentissage de métrique / reconnaissance faciale | TripletMarginLoss | Apprendre la distance dans l'espace des caractéristiques |
| Distillation de connaissances | KLDivLoss | Apprendre la distribution des étiquettes souples |
Erreurs d'utilisation courantes et notes
Exemple
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.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}")