Fonctions de perte PyTorch

Introduction

Les fonctions de perte sont fondamentales dans l’entraînement de modèles ML, et dans la plupart des projets de apprentissage automatique, il n’y a pas de moyen de faire en sorte que votre modèle fasse des prédictions correctes sans une fonction de perte. En termes simples, une fonction de perte est une fonction mathématique ou une expression utilisée pour mesurer combien bien un modèle se porte sur un jeu de données donné. Connaître comment un modèle se porte bien sur un jeu de données donné donne au développeur des informations utiles pour prendre de nombreuses décisions pendant l’entraînement, telles que l’utilisation d’un nouveau modèle plus puissant ou même le changement de la fonction de perte elle-même en une autre type. En parlant des types de fonctions de perte, plusieurs de ces fonctions de perte ont été développées au fil des ans, chacune adaptée pour être utilisée pour une tâche d’entraînement particulière.

Prérequis

Cet article nécessite une compréhension des réseaux de neurones. Au niveau supérieur, les réseaux de neurones sont composés de nœuds interconnectés («neurones») organisés en couches. Ils apprennent et font des prédictions par un processus appelé «entraînement» qui ajuste les poids et les biais des connexions entre les neurones. Comprendre les réseaux de neurones inclut connaître leurs différentes couches (couche d’entrée, couches cachées, couche de sortie), les fonctions d’activation, les algorithmes d’optimisation (variantes de descente des gradients), les fonctions de perte, etc.

De plus, la familiarité avec la syntaxe de Python et la bibliothèque PyTorch est essentielle pour comprendre les extraits de code présentés dans cet article.

Dans cet article, nous allons explorer différentes fonctions de perte qui font partie du module PyTorch nn. Nous plongerons ensuite plus profondément dans la manière dont PyTorch expose ces fonctions de perte aux utilisateurs comme partie de son API de module nn en construisant une personnalisée.

Maintenant que nous avons une compréhension de haut niveau de ce que sont les fonctions de perte, explorerons certains détails techniques plus avancés sur leur fonctionnement.

Quelles sont les fonctions de perte ?

Nous avons mentionné plus tôt que les fonctions de perte nous disent comment bien un modèle se comporte sur un jeu de données particulier. Techniquement, ce qu’elles font est de mesurer à quel point une valeur prédite est proche de la valeur réelle. Lorsque notre modèle fait des prédictions très proches des valeurs réelles sur notre jeu de données d’entraînement et de test, cela signifie que nous avons un modèle assez robuste.

Bien que les fonctions de perte nous fournissent des informations critiques sur la performance de notre modèle, celles-ci n’ont pas pour fonction principale de le déterminer, car il existe des méthodes plus robustes pour évaluer nos modèles, telles que l’accuracy et les F-scores. L’importance des fonctions de perte est surtout réalisée pendant l’entraînement, lorsque nous nudgem les poids de notre modèle dans la direction qui minimise la perte. En faisant ainsi, nous augmentons la probabilité que notre modèle fasse des prédictions correctes, ce qui sans les fonctions de perte serait probablement impossible.

Les fonctions de perte diffèrentes conviennent à différents problèmes, chacune conçue avec soin par les chercheurs pour assurer un débit de gradient stable pendant l’entraînement.

Parfois, les expressions mathématiques des fonctions de perte peuvent être un peu intimidantes, ce qui a conduit certains développeurs à les traiter comme des boîtes noires. Nous allons découvrir certaines des fonctions de perte les plus utilisées de PyTorch plus tard, mais avant cela, jetons un coup d’œil à la manière dont nous utilisons les fonctions de perte dans le monde de PyTorch.

Fonctions de perte dans PyTorch

PyTorch est fourni avec de nombreuses fonctions de perte canoniques dotées de schémas de conception simples qui permettent aux développeurs de passer très rapidement entre ces différentes fonctions de perte pendant l’entraînement. toutes les fonctions de perte de PyTorch sont emballées dans le module nn, la classe de base de PyTorch pour toutes les neural networks. Cela permet d’ajouter une fonction de perte à votre projet en tant que simple ligne de code. Voyons comment ajouter une fonction de perte de erreur carré moyen dans PyTorch.

import torch.nn as nn
MSE_loss_fn = nn.MSELoss()

La fonction retournée par le code ci-dessus peut être utilisée pour calculer à quelle distance une prédiction est de la valeur réelle en utilisant le format ci-dessous.

#value_prédite est la prédiction de notre réseau neural
#valeur_cible est la valeur réelle dans notre jeu de données
#valeur_perte est la perte entre la valeur prédite et la valeur réelle
Loss_value = MSE_loss_fn(predicted_value, target)

Maintenant que nous avons une idée de comment utiliser les fonctions de perte dans PyTorch, plongeons-y plus profondément dans les détails de quelques des fonctions de perte que PyTorch offre.

Quelles fonctions de perte sont disponibles dans PyTorch ?

PyTorch propose de nombreuses fonctions de perte, qui sont généralement réparties en trois grands types : les pertes de régression, les pertes de classification et les pertes de classement.

Les pertes de régression se concentrent généralement sur des valeurs continues qui peuvent prendre n’importe quelle valeur entre deux limites. Un exemple de cela serait les prédictions du prix des maisons dans une communauté.

Les fonctions de perte de classification traitent des valeurs discrètes, comme le devoir de classer un objet comme une boîte, un stylo ou une bouteille.

Les pertes de classement prédisposent les valeurs relatives. Un exemple de cela serait la vérification faciale, où nous voulons savoir quelles images de visages appartiennent à un visage particulier, et pouvons le faire en classant les visages qui et ceux qui ne sont pas associés au visage d’origine grâce à leur degré d’approximation relative à l’empreinte faciale cible.

Fonction de perte L1 / Erreur absolue moyenne

La fonction de perte L1 calcule l’erreur absolue moyenne entre chaque valeur de tenseur prédit et celle de la cible. Elle calcule d’abord la différence absolue entre chaque valeur du tenseur prédit et celle de la cible, et compute la somme de toutes les valeurs retournées à partir de chaque calcul de différence absolue. Enfin, elle calcule la moyenne de cette valeur de somme pour obtenir l’erreur absolue moyenne (MAE). La fonction de perte L1 est très robuste pour traiter le bruit.

import torch.nn as nn

#size_average and reduce are deprecated

#reduction specifies the method of reduction to apply to output. Possible values are 'mean' (default) where we compute the average of the output, 'sum' where the output is summed and 'none' which applies no reduction to output

Loss_fn = nn.L1Loss(size_average=None, reduce=None, reduction='mean')

input = torch.randn(3, 5, requires_grad=True)
target = torch.randn(3, 5)
output = loss_fn(input, target)
print(output) #tensor(0.7772, grad_fn=<L1LossBackward>)

La seule valeur retournée est l’erreur calculée entre deux tenseurs de dimension 3 par 5.

Erreur carrée moyenne

L’erreur carrée moyenne présente des ressemblances frappantes avec l’erreur absolue moyenne. Au lieu de calculer la différence absolue entre les valeurs dans le tenseur de prediction et la cible, comme c’est le cas avec l’erreur absolue moyenne, elle calcule la différence carrée entre les valeurs dans le tenseur de prediction et celui de la cible. En le faisant, les différences relativement importantes sont plus sévèrement pénalisées, tandis que les différences relativement petites sont moins sévèrement pénalisées. L’erreur carrée moyenne est considérée comme moins robuste pour traiter les valeurs extrêmes et le bruit que l’erreur absolue moyenne, toutefois.

import torch.nn as nn

loss = nn.MSELoss(size_average=None, reduce=None, reduction='mean')
#Les paramètres de la fonction de perte L1 s'appliquent ici.

input = torch.randn(3, 5, requires_grad=True)
target = torch.randn(3, 5)
output = loss(input, target)
print(output) #tensor(0.9823, grad_fn=<MseLossBackward>)

Perte de cross-entropie

La perte de cross-entropie est utilisée dans les problèmes de classification impliquant un nombre de classes discrètes. Elle mesure la différence entre deux distributions de probabilité pour un ensemble donné de variables aléatoires. généralement, lors de l’utilisation de la perte de cross-entropie, la sortie de notre réseau est une couche softmax, qui garantit que la sortie de la couche de neurones est une valeur de probabilité (valeur entre 0 et 1).

La couche softmax se compose de deux parties – l’exponent de la prédiction pour une classe particulière.

yi est la sortie de la couche de neurones pour une classe particulière. La sortie de cette fonction est un nombre proche de zéro, mais jamais zéro, si yi est grand et négatif, et plus proche de 1 si yi est positif et très grand.

import numpy as np

np.exp(34) #583461742527454.9
np.exp(-34) #1.713908431542013e-15

La deuxième partie est une valeur de normalisation et est utilisée pour s’assurer que la sortie de la couche softmax est toujours une valeur de probabilité.

Cela est obtenu en additionnant tous les exposants de chaque valeur de classe. L’équation finale de softmax ressemble à cela :

]

Dans le module nn de PyTorch, la perte de cross-entropie combine la log-softmax et la perte de -log-likelihood (NLL) en une seule fonction de perte.

Notez comment la fonction de gradient dans l’affichage imprimé est une perte NLL. Cela révèle en fait que la perte de cross-entropie combine la perte NLL sous le capot avec une couche log-softmax.

Perte de -Log-Likelihood (NLL)

La fonction de perte NLL fonctionne de manière assez similaire à la fonction de perte de cross-entropie. La perte de cross-entropie combine une couche log-softmax et la perte NLL pour obtenir la valeur de la perte de cross-entropie. Cela signifie que la perte NLL peut être utilisée pour obtenir la valeur de la perte de cross-entropie en ayant la dernière couche du réseau neuronal être une couche log-softmax au lieu d’une couche softmax normale.

m = nn.LogSoftmax(dim=1)
loss = nn.NLLLoss()
# l'entrée est de taille N x C = 3 x 5
input = torch.randn(3, 5, requires_grad=True)
# chaque élément de la cible doit avoir 0 <= valeur < C
target = torch.tensor([1, 0, 4])
output = loss(m(input), target)
output.backward()
# exemple de perte 2D (utilisé, par exemple, avec des entrées d'image)
N, C = 5, 4
loss = nn.NLLLoss()
# l'entrée est de taille N x C x hauteur x largeur
data = torch.randn(N, 16, 10, 10)
conv = nn.Conv2d(16, C, (3, 3))
m = nn.LogSoftmax(dim=1)
# chaque élément de la cible doit avoir 0 <= valeur < C
target = torch.empty(N, 8, 8, dtype=torch.long).random_(0, C)
output = loss(m(conv(data)), target)
print(output) #tensor(1.4892, grad_fn=)

#crédit NLLLoss — Documentation PyTorch 1.9.0

Perte par Entropie Croisée Binaire

La perte par entropie croisée binaire est une classe spéciale de pertes par entropie croisée utilisée pour le problème particulier de la classification des points de données en seulement deux classes. Les étiquettes pour ce type de problème sont généralement binaires, et notre objectif est donc de pousser le modèle à prédire un nombre proche de zéro pour une étiquette zéro et un nombre proche de un pour une étiquette un. Habituellement, lors de l’utilisation de la perte BCE pour la classification binaire, la sortie du réseau de neurones est une couche sigmoïde pour garantir que la sortie est soit une valeur proche de zéro soit une valeur proche de un.

import torch.nn as nn

m = nn.Sigmoid()
loss = nn.BCELoss()
input = torch.randn(3, requires_grad=True)
target = torch.empty(3).random_(2)
output = loss(m(input), target)
print(output) #tensor(0.4198, grad_fn=)

Perte de cross-entropie binaire avec logits

Nous avons mentionné dans la section précédente que une perte de cross-entropie binaire est généralement sortie avec une couche sigmoïde pour s’assurer que les sorties se situent entre 0 et 1. Une perte de cross-entropie binaire avec logits combine ces deux couches en une seule. Selon la documentation de PyTorch, c’est une version numériquement plus stable car elle utilise la technique du log-sum exp.

import torch
import torch.nn as nn

target = torch.ones([10, 64], dtype=torch.float32)  # 64 classes, taille de lot = 10
output = torch.full([10, 64], 1.5)  # Une prédiction (logit)
pos_weight = torch.ones([64])  # Toutes les poids sont égaux à 1
criterion = torch.nn.BCEWithLogitsLoss(pos_weight=pos_weight)
loss = criterion(output, target)  # -log(sigmoid(1.5))
print(loss) #tensor(0.2014)

Perte L1 Floue

La fonction de perte L1 smooth combine les avantages de la perte MSE et de la perte MAE à travers une valeur heuristique beta. Ce critère a été présenté dans le papier Fast R-CNN. Lorsque la différence absolue entre la valeur de référence et la valeur prédite est inférieure à beta, le critère utilise une différence carrée, similaire à la perte MSE. La courbe de la perte MSE est une courbe continue, ce qui signifie que le gradient pour chaque valeur de perte varie et peut être dérivé en tout endroit. De plus, comme la valeur de perte diminue, le gradient diminue, ce qui est pratique pendant le descente des gradients. Cependant, pour de très grandes valeurs de perte, le gradient explose, donc le critère de basculement vers la MAE est utilisé, pour laquelle le gradient est presque constant pour chaque valeur de perte lorsque la différence absolue devient plus grande que beta et que l’explosion potentielle du gradient est éliminée.

import torch.nn as nn

loss = nn.SmoothL1Loss()
input = torch.randn(3, 5, requires_grad=True)
target = torch.randn(3, 5)
output = loss(input, target)

print(output) #tensor(0.7838, grad_fn=<SmoothL1LossBackward>)

Perte d’empreinte hingée

La perte d’empreinte hingée est principalement utilisée dans les tâches de apprentissage semi-supervisé pour mesurer la similarité entre deux entrées. Elle est utilisée lorsqu’il y a un tenseur d’entrée et un tenseur de labels contenant des valeurs de 1 ou -1. Elle est principalement utilisée dans les problèmes impliquant des embeddings non-linéaires et l’apprentissage semi-supervisé.

import torch
import torch.nn as nn

input = torch.randn(3, 5, requires_grad=True)
target = torch.randn(3, 5)

hinge_loss = nn.HingeEmbeddingLoss()
output = hinge_loss(input, target)
output.backward()

print('input: ', input)
print('target: ', target)
print('output: ', output)

#input:  vecteur([[ 1.4668e+00,  2.9302e-01, -3.5806e-01,  1.8045e-01,  #1.1793e+00],
#       [-6.9471e-05,  9.4336e-01,  8.8339e-01, -1.1010e+00,  #1.5904e+00],
#       [-4.7971e-02, -2.7016e-01,  1.5292e+00, -6.0295e-01,  #2.3883e+00]],
#       necessite_grad=True)
#target:  vecteur([[-0.2386, -1.2860, -0.7707,  1.2827, -0.8612],
#        [ 0.6747,  0.1610,  0.5223, -0.8986,  0.8069],
#        [ 1.0354,  0.0253,  1.0896, -1.0791, -0.0834]])
#output:  nombre(1.2103, grad_fn=<MeanBackward0>)

Perte de classement par marge

La perte de classement par marge fait partie des pertes de classement dont l’objectif principal, contrairement aux autres fonctions de perte, est de mesurer la distance relative entre un ensemble d’entrées dans un jeu de données. La fonction de perte de classement par marge prend deux entrées et une étiquette ne contenant que 1 ou -1. Si l’étiquette est 1, il est supposé que la première entrée doit avoir un meilleur classement que la deuxième entrée, et si l’étiquette est -1, il est supposé que la deuxième entrée doit avoir un meilleur classement que la première entrée. Cette relation est illustrée par l’équation et le code ci-dessous.

import torch.nn as nn

loss = nn.MarginRankingLoss()
input1 = torch.randn(3, requires_grad=True)
input2 = torch.randn(3, requires_grad=True)
target = torch.randn(3).sign()
output = loss(input1, input2, target)
print('input1: ', input1)
print('input2: ', input2)
print('output: ', output)

#input1:  tenseur([-1.1109,  0.1187,  0.9441], nécessite_grad=True)
#input2:  tenseur([ 0.9284, -0.3707, -0.7504], nécessite_grad=True)
#output:  tenseur(0.5648, grad_fn=<MeanBackward0>)

Perte de Marges Triplet

Ce critère mesure la similarité entre les points de données en utilisant des triplets de l’échantillon de données de formation. Les triplets impliqués sont un échantillon d’ancre, un échantillon positif et un échantillon négatif. L’objectif est 1) de minimiser la distance entre l’échantillon positif et l’ancre, et 2) de faire en sorte que la distance entre l’ancre et l’échantillon négatif soit supérieure à la valeur de la marge plus la distance entre l’échantillon positif et l’ancre. Habituellement, l’échantillon positif appartient à la même classe que l’ancre, mais l’échantillon négatif ne le fait pas. Par conséquent, en utilisant cette fonction de perte, nous visons à utiliser la perte de marges triplet pour prédire une valeur de similarité élevée entre l’ancre et l’échantillon positif et une valeur de similarité basse entre l’ancre et l’échantillon négatif.

import torch.nn as nn

triplet_loss = nn.TripletMarginLoss(margin=1.0, p=2)
anchor = torch.randn(100, 128, requires_grad=True)
positive = torch.randn(100, 128, requires_grad=True)
negative = torch.randn(100, 128, requires_grad=True)
output = triplet_loss(anchor, positive, negative)
print(output)  #tensor(1.1151, grad_fn=<MeanBackward0>)

Perte d’Embedding Cosinus

La perte par embarquement de cosinus mesure la perte donnée aux entrées x1, x2 et à un tenseur de label y contenant les valeurs 1 ou -1. Elle est utilisée pour mesurer le degré de similarité ou de dissimilarité entre deux entrées.

Le critère mesure la similarité en calculant la distance cosinus entre les deux points de données dans l’espace. La distance cosinus est corrélée à l’angle entre les deux points, ce qui signifie que plus l’angle est petit, plus les entrées sont proches et donc plus elles sont similaires.

import torch.nn as nn

loss = nn.CosineEmbeddingLoss()
input1 = torch.randn(3, 6, requires_grad=True)
input2 = torch.randn(3, 6, requires_grad=True)
target = torch.randn(3).sign()
output = loss(input1, input2, target)
print('input1: ', input1)
print('input2: ', input2)
print('output: ', output)

#input1:  tensor([[ 1.2969e-01,  1.9397e+00, -1.7762e+00, -1.2793e-01, #-4.7004e-01,
#         -1.1736e+00],
#        [-3.7807e-02,  4.6385e-03, -9.5373e-01,  8.4614e-01, -1.1113e+00,
#          4.0305e-01],
#        [-1.7561e-01,  8.8705e-01, -5.9533e-02,  1.3153e-03, -6.0306e-01,
#          7.9162e-01]], requires_grad=True)
#input2:  tensor([[-0.6177, -0.0625, -0.7188,  0.0824,  0.3192,  1.0410],
#        [-0.5767,  0.0298, -0.0826,  0.5866,  1.1008,  1.6463],
#        [-0.9608, -0.6449,  1.4022,  1.2211,  0.8248, -1.9933]],
#       requires_grad=True)
#output:  tensor(0.0033, grad_fn=<MeanBackward0>)

Perte de divergence de Kullback-Leibler

Données deux distributions P et Q, la perte de divergence de Kullback-Leibler (KL) mesure combien d’information est perdue lorsque P (pré supposée être la distribution véritable) est remplacée par Q. En mesurant combien d’information est perdue lorsque nous utilisons Q pour approcher P, nous sommes en mesure d’obtenir la similarité entre P et Q et ainsi conduire notre algorithme à produire une distribution très proche de la distribution véritable, P. La perte d’information lorsque Q est utilisée pour approcher P n’est pas la même lorsque P est utilisée pour approcher Q, et donc la divergence KL n’est pas symétrique.

import torch.nn as nn

loss = nn.KLDivLoss(size_average=None, reduce=None, reduction='mean', log_target=False)
input1 = torch.randn(3, 6, requires_grad=True)
input2 = torch.randn(3, 6, requires_grad=True)
output = loss(input1, input2)

print('output: ', output) #tensor(-0.0284, grad_fn=<KlDivBackward>)

Créer une Fonction de Perte Personnalisée

PyTorch nous offre deux façons populaires de construire notre propre fonction de perte pour notre problème ; il s’agit soit de l’utilisation d’une classe implémentée, soit de l’utilisation d’une fonction implémentée. Voyons comment nous pouvons implémenter les deux méthodes en commençant par l’implémentation de fonction.

C’est sans doute la manière la plus simple de créer votre propre fonction de perte personnalisée. Il est tout aussi simple de créer une fonction, de lui passer les entrees requises et d’autres paramètres, d’effectuer une opération en utilisant l’API de base de PyTorch ou l’API fonctionnelle, et de retourner une valeur. Voyons une démonstration avec une erreur carré moyenne personnalisée.

def custom_mean_square_error(y_predictions, target):
  square_difference = torch.square(y_predictions - target)
  loss_value = torch.mean(square_difference)
  return loss_value

Dans le code ci-dessus, nous définissons une fonction de perte personnalisée pour calculer l’erreur carré moyenne donnée un tenseur de prediction et un tenseur d’objectif

y_predictions = torch.randn(3, 5, requires_grad=True);
target = torch.randn(3, 5)
pytorch_loss = nn.MSELoss();
p_loss = pytorch_loss(y_predictions, target)
loss = custom_mean_square_error(y_predictions, target)
print('custom loss: ', loss)
print('pytorch loss: ', p_loss)

#custom loss:  tensor(2.3134, grad_fn=<MeanBackward0>)
#pytorch loss:  tensor(2.3134, grad_fn=<MseLossBackward>)

Nous pouvons calculer la perte en utilisant notre fonction de perte personnalisée et la fonction d’erreur carré moyen de PyTorch pour observer que nous avons obtenu les mêmes résultats.

Perte personnalisée avec des classes Python

Cette méthode est probablement la méthode standard et recommandée pour définir les pertes personnalisées dans PyTorch. La fonction de perte est créée en tant que nœud dans le graphe de réseau neuronal en sous-clasant le module nn. Cela signifie que notre fonction de perte personnalisée est un layer de PyTorch exactement de la même manière qu’une couche de convolution. Voyons maintenant une démonstration de comment cela fonctionne avec une erreur carré moyenne personnalisée.

class Custom_MSE(nn.Module):
  def __init__(self):
    super(Custom_MSE, self).__init__();

  def forward(self, predictions, target):
    square_difference = torch.square(predictions - target)
    loss_value = torch.mean(square_difference)
    return loss_value
  
  def __call__(self, predictions, target):
     square_difference = torch.square(y_predictions - target)
     loss_value = torch.mean(square_difference)
     return loss_value

Pensées Finales

Nous avons discuté de nombreuses pertes fonctions disponibles dans PyTorch et effectué un plongeon dans les fonctionnements internes de la plupart de ces fonctions de perte. Choisir la bonne fonction de perte pour un problème particulier peut être une tâche éprouvante. J’espère que ce didacticiel, accompagné de la documentation officielle de PyTorch, puisse servir de guide lors de la recherche de la fonction de perte qui convient le mieux à votre problème.

Source:
https://www.digitalocean.com/community/tutorials/pytorch-loss-functions