Funzioni di perdita di PyTorch

Introduzione

Le perdite sono fondamentali nell’addestramento di modelli ML, e, nella maggior parte dei progetti di apprendimento automatico, non esiste nessun modo per indurre il modello a fare predizioni corrette senza una funzione di perdita. In termini semplici, una funzione di perdita è una funzione matematica o un’espressione utilizzata per misurare quanto bene un modello stia facendo su qualche insieme di dati. Sapere come un modello sta facendo su un particolare insieme di dati dà allo sviluppatore informazioni utili per prendere molte decisioni durante l’addestramento, come ad esempio utilizzare un nuovo modello più potente o persino cambiare la funzione di perdita stessa in un tipo diverso. Parlando dei tipi di funzione di perdita, ne sono state create diverse nel corso degli anni, ognuna adatta all’uso per una particolare task di addestramento.

Prerequisiti

Questo articolo richiede una comprensione delle reti neurali. A un alto livello, le reti neurali sono composte da nodi (“neuroni”) interconnessi organizzati in layer. Imparano e fanno predizioni attraverso un processo chiamato “addestramento” che regola il peso e il bias delle connessioni tra i neuroni. Comprendere le reti neurali include conoscere le loro diverse layer (layer input, layer nascoste, layer output), funzioni di attivazione, algoritmi di ottimizzazione (varianti del discendente del gradiente), funzioni di perdita, ecc.

Inoltre, la familiarità con la sintassi di Python e la libreria PyTorch è essenziale per capire i frammenti di codice presentati in questo articolo.

In questo articolo, esploriamo differenti funzioni di perdita che fanno parte del modulo PyTorch nn. Scavando ancora più a fondo, vedremo come PyTorch espone queste funzioni di perdita agli utenti come parte della sua API del modulo nn, creando una personalizzata.

Ora che abbiamo un’idea di alto livello di cosa siano le funzioni di perdita, esploriamo alcuni dettagli tecnici sulla loro funzione.

Cosa sono le funzioni di perdita?

Hanno detto prima che le funzioni di perdita ci dicono quanto bene fa un modello su un determinato dataset. Tecnicamente, lo fanno misurando quanto una stima predetta sia vicina alla stima reale. Quando il nostro modello fa predizioni molto vicine alle stime reali sia sul nostro dataset di training che su quello di test, significa che abbiamo un modello piuttosto robusto.

Anche se le funzioni di perdita ci forniscono informazioni cruciali sulla performance del nostro modello, non è la loro funzione primaria, poiché ci sono tecniche più robuste per valutare i nostri modelli, come l’accuratezza e gli F-score. L’importanza delle funzioni di perdita viene realizzata principalmente durante il training, dove spingiamo lepesi del nostro modello nella direzione che minimizza la perdita. Fatto così, aumentiamo la probabilità che il nostro modello faccia predizioni corrette, cosa che probabilmente non sarebbe stata possibile senza una funzione di perdita.

Le diverse perdite funzionano meglio per problemi diversi, ognuna creata con cura da ricercatori per assicurare un flusso di gradienti stabili durante l’addestramento.

A volte, le espressioni matematiche delle perdite possono essere un po ‘imbarazzanti, e questo ha portato alcuni sviluppatori a trattare queste funzioni come scatole nere. Più tardi, scopriremo alcune delle perdite più usate in PyTorch, ma prima di tutto, diamo un’occhiata a come usiamo le perdite nell’ambiente di PyTorch.

Perdite in PyTorch

PyTorch viene fornito con molte perdite classiche con schemi di design semplificati che consentono agli sviluppatori di iterare facilmente su queste diverse perdite durante l’addestramento. Tutte le perdite di PyTorch sono incluse nel modulo nn, la classe base di PyTorch per tutte le reti neurali. Ciò rende l’aggiunta di una perdita al tuo progetto facile come l’aggiunta di una singola riga di codice. Vediamo come aggiungere una perdita di errore quadratico medio in PyTorch.

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

La funzione restituita dal codice sopra può essere usata per calcolare quanto una previsione sia lontana dalla realtà utilizzando il formato seguente.

#predicted_value è la previsione dalla nostra rete neurale
#target è il valore reale nel nostro dataset
#loss_value è la perdita tra il valore predetto e il valore reale
Loss_value = MSE_loss_fn(predicted_value, target)

Ora che abbiamo una idea di come usare le funzioni di perdita in PyTorch, spingiamo il nostro sguardo dietro le quinte di diverse funzioni di perdita che PyTorch offre.

Le quali funzioni di perdita sono disponibili in PyTorch?

Molte di queste funzioni di perdita che PyTorch include vengono ampiamente classificate in tre gruppi – perdite di regressione, perdite di classificazione e perdite di classificazione.

Le perdite di regressione sono principalmente relative a valori continui che possono assumere qualsiasi valore tra due limiti. Un esempio sarebbe la previsione del prezzo delle case in una comunità.

Le funzioni di perdita di classificazione si occupano di valori discreti, come il compito di classificare un oggetto come scatola, penne o bottiglia.

Le perdite di ranking predicono le distanze relative tra valori. Un esempio sarebbe la verifica facciale, dove vogliamo sapere quali immagini di volti appartengono ad un determinato volto, e ciò si può fare classificando quali volti appartengono o meno al volto originale tramite la loro approssimazione relativa al volto di riferimento.

Funzione di perdita L1 / Errore Medio Assoluto

La funzione di perdita L1 calcola la media dell’errore assoluto tra ciascun valore nel tensore predetto e quello dell’obiettivo. Prima di tutto, calcola l’errore assoluto tra ciascun valore nel tensore predetto e quello dell’obiettivo, e computa la somma di tutti i valori restituiti da ciascuna computazione dell’errore assoluto. Infine, calcola la media di questo valore di somma per ottenere l’errore assoluto medio (MAE). La funzione di perdita L1 è molto robusta per la gestione del rumore.

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>)

Il singolo valore restituito è l’errore calcolato tra due tensori con dimensione 3×5.

Mean Squared Error

L’errore quadratico medio condivide alcune somiglianze con l’errore massimo absoluto. Invece di calcolare la differenza assoluta tra i valori nel tensore di predizione e nel target, come avviene nell’errore massimo assoluto, calcola la differenza quadratica tra i valori nel tensore di predizione e in quello del target. Facendo così, le differenze relativamente grandi sono penalizzate di più, mentre le differenze relativamente piccole sono penalizzate di meno. L’MSE è considerato meno robusto nel trattare outliers e rumore rispetto all’MAE, tuttavia.

import torch.nn as nn

loss = nn.MSELoss(size_average=None, reduce=None, reduction='mean')
#Le spiegazioni sul parametro della funzione di perdita L1 si applicano qui.

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>)

Perda di Cross-Entropy

La perda di cross-entropy viene utilizzata in problemi di classificazione che coinvolgono un numero di classi discreti. Misura la differenza tra due distribuzioni di probabilità per un insieme di variabili casuali data. Di solito, quando si utilizza la perda di cross-entropy, l’output della nostra rete è una camicia softmax, che assicura che l’output della rete neurale sia un valore di probabilità (valore tra 0-1).

La camicia softmax consiste in due parti – l’esponenziale dell’output della predizione per una classe particolare.

yi è l’output della rete neurale per una classe particolare. L’output di questa funzione è un numero vicino a zero, ma mai zero, se yi è grande e negativo, e più vicino a 1 se yi è positivo e molto grande.

import numpy as np

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

La seconda parte è un valore di normalizzazione ed è utilizzato per assicurarsi che l’output della layer softmax sia sempre un valore probabilistico.

Questo viene ottenuto sommando tutti gli esponenti di ciascun valore di classe. L’equazione finale della softmax è simile a questo:

]

Nel modulo nn di PyTorch, la perdita di cross-entropy combina la log-softmax e la perdita di negative log-likelihood (NLL) in una singola funzione di perdita.

Osservate come la funzione di gradiente nell’output stampato sia una perdita NLL. Ciò rivela di fatto che la perdita di cross-entropy combina la perdita NLL sotto il coinvolgimento di una layer log-softmax.

Perdita di Negative Log-Likelihood (NLL)

La funzione di perdita NLL funziona piuttosto similmente alla funzione di perdita di cross-entropy. La perdita di cross-entropy combina una layer log-softmax e la perdita NLL per ottenere il valore della perdita di cross-entropy. Questo significa che la perdita NLL può essere utilizzata per ottenere il valore della perdita di cross-entropy impostando l’ultima layer della rete neurale come una layer log-softmax invece di una layer softmax normale.

m = nn.LogSoftmax(dim=1)
loss = nn.NLLLoss()
L'input ha dimensione N x C = 3 x 5
input = torch.randn(3, 5, requires_grad=True)
Ogni elemento nel target deve avere un valore tra 0 e C
target = torch.tensor([1, 0, 4])
output = loss(m(input), target)
output.backward()
Esempio di perdita 2D (utilizzato, per esempio, con input immagine)
N, C = 5, 4
loss = nn.NLLLoss()
L'input ha dimensione N x C x altezza x larghezza
data = torch.randn(N, 16, 10, 10)
conv = nn.Conv2d(16, C, (3, 3))
m = nn.LogSoftmax(dim=1)
Ogni elemento nel target deve avere un valore tra 0 e C
target = torch.empty(N, 8, 8, dtype=torch.long).random_(0, C)
output = loss(m(conv(data)), target)
print(output) tensore(1.4892, grad_fn=)

credito NLLLoss — Documentazione di PyTorch 1.9.0

Perdita di Cross-Entropy Binaria

La perdita di cross-entropy binaria è una classe speciale di perdite di cross-entropia utilizzate per il problema speciale della classificazione di dati in soli due classi. I标签 per questo tipo di problema sono di solito binarie, quindi il nostro obiettivo è spingere il modello a predire un numero vicino a zero per un taglio zero e un numero vicino a uno per un taglio uno. Di solito, quando si utilizza la perdita BCE per la classificazione binaria, l’output della rete neurale è una层 sigmoida per assicurarsi che l’output sia un valore vicino a zero o un valore vicino a uno.

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) tensore(0.4198, grad_fn=)

Perdita di Cross-Entropia Binaria con Logit

Come menzionammo nella sezione precedente, una perdita di cross-entropia binaria è di solito emessa come una layer sigmoidica per assicurarsi che l’output si trovi tra 0 e 1. Una perdita di cross-entropia binaria con logit combina queste due layer in una sola layer. Secondo la documentazione di PyTorch, questa è una versione numericamente più stabile poiché fa uso della trick log-sum exp.

import torch
import torch.nn as nn

target = torch.ones([10, 64], dtype=torch.float32)  # 64 classi, dimensione del batch = 10
output = torch.full([10, 64], 1.5)  # Una previsione (logit)
pos_weight = torch.ones([64])  # Tutte lepesi sono uguali a 1
criterion = torch.nn.BCEWithLogitsLoss(pos_weight=pos_weight)
loss = criterion(output, target)  # -log(sigmoid(1.5))
print(loss) #tensor(0.2014)

Perdita di L1 Smooth

La funzione di perdita L1 smussata combina i benefici della perdita MSE e della perdita MAE attraverso un valoreuristico beta. Questo criterio è stato introdotto nel paper di Fast R-CNN. Quando la differenza assoluta tra il valore vero e il valore predetto è inferiore a beta, il criterio utilizza una differenza quadratica, simile alla perdita MSE. Il grafico della perdita MSE è una curva continua, il che significa che la gradientedi ogni valore di perdita varia e può essere derivata in ogni punto. Inoltre, man mano che la perdita si riduce, il gradiente diminuisce, il che è conveniente durante il discendimento del gradiente. Tuttavia, per valori di perdita molto grandi la gradientedi esplode, quindi il criterio per il cambio alla MAE, per la quale la gradientedi è quasi costante per ogni valore di perdita, quando la differenza assoluta diventa maggiore di beta e l’esplosione del potenziale gradientedi è eliminata.

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>)

Perdita di嵌入 Hinge

La perdita di嵌入 Hinge è principalmente usata in task di apprendimento supervisionato parziale per misurare la similitudine tra due input. È usata quando c’è un tensore di input e un tensore di etichetta che contiene valori di 1 o -1. È principalmente usata nei problemi che coinvolgono嵌入 non lineari e apprendimento supervisionato parziale.

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:  vettore di tensori([[ 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]],
#       richiede_grad=True)
#target:  vettore di tensori([[-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:  vettore di tensori(1.2103, grad_fn=<MeanBackward0>)

Perda di Classificazione a Margine

La perda di classificazione a margine è una classe di perdite di classificazione in cui l’obiettivo principale, differentemente da altre funzioni di perdita, è misurare la distanza relativa tra un insieme di input nel dataset. La funzione di perdita a margine riceve due input e un etichetta che può contenere solo 1 o -1. Se l’etichetta è 1, si assume che il primo input debba avere un ranking superiore all’secondo input, mentre se l’etichetta è -1, si assume che l’secondo input debba avere un ranking superiore al primo input. Questo rapporto è illustrato dall’equazione e dal codice sottostante.

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:  vettore tensore([-1.1109,  0.1187,  0.9441], require_grad=True)
#input2:  vettore tensore([ 0.9284, -0.3707, -0.7504], require_grad=True)
#output:  vettore tensore(0.5648, grad_fn=)

Perda Margine Triplet

Questo criterio misura la similitudine tra i punti dati utilizzando tripletti di campioni di training. I tripletti coinvolti sono un campione ancora, un campione positivo e un campione negativo. L’obiettivo è 1) ottenere una distanza tra il campione positivo e l’ancora il più piccola possibile e 2) ottenere una distanza tra l’ancora e il campione negativo maggiore del valore di margine più la distanza tra il campione positivo e l’ancora. Di solito, il campione positivo appartiene alla stessa classe dell’ancora, ma il campione negativo non lo fa. Quindi, utilizzando questa perdita, si mira ad usare la perda di margine triplet per predire un alto valore di similitudine tra l’ancora e il campione positivo e un basso valore di similitudine tra l’ancora e il campione negativo.

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)  #vettore tensore(1.1151, grad_fn=)

Perda di Embedding Cosseno

La perdita di embedding di cosine misura la perdita data dagli input x1, x2 e da una label tensor y contenente valori 1 o -1. È utilizzato per misurare la similitudine o la dissimilitudine tra due input.

Il criterio misura la similitudine calcolando la distanza di cosine tra i due punti dati in spazio. La distanza di cosine corrisponde all’angolo tra i due punti, il che significa che più piccolo è l’angolo, più vicini sono gli input e quindi più simili sono.

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>)

Perdita di Divergenza di Kullback-Leibler

Dato due insiemi di probabilità, P e Q, la perdita di divergenza di Kullback-Leibler (KL) misura quanto informazione viene perse quando P (preso come distribuzione vera) è sostituito da Q. Misurando quanto informazione viene perse quando usiamo Q per approssimare P, siamo in grado di ottenere la similitudine tra P e Q e, di conseguenza, indirizzare il nostro algoritmo a generare una distribuzione molto simile alla distribuzione vera, P. La perdita di informazione quando Q è usata per approssimare P non è la stessa quando P è usata per approssimare Q, dunque la divergenza KL non è simmetrica.

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>)

Creazione di una Funzione di Perdita Personalizzata

PyTorch ci fornisce due modi popolari per costruire una funzione di perdita personalizzata per i nostri problemi; questi sono, in ordine, l’implementazione di una classe e l’implementazione di una funzione. Vediamo come implementare entrambi i metodi iniziando con l’implementazione di una funzione.

Questa è probabilmente la semplice via per scrivere una perdita personalizzata. È altrettanto semplice come creare una funzione, passando i dati richiesti e altri parametri, eseguendo un’operazione utilizzando l’API di base di PyTorch o l’API Funzionale e restituendo un valore. Vediamo un demo con una perdita di errore quadratico personizzata.

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

Nel codice sopra, definiamo una perdita personalizzata per calcolare l’errore quadratico medio date una matrice di predizione e una matrice di target

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>)

Possiamo calcolare la perdita usando la nostra perdita personalizzata e la funzione di perdita MSE di PyTorch per osservare che abbiamo ottenuto gli stessi risultati.

Perdita Personalizzata con Classi Python

Questo approcio è probabilmente la via standard e raccomandata per la definizione di perdite personalizzate in PyTorch. La funzione di perdita viene creata come un nodo nel grafo di rete neurale tramite l’eredità del modulo nn. Questo significa che la nostra funzione di perdita personalizzata è un layer di PyTorch esattamente allo stesso modo in cui una layer convoluzionale lo è. Vediamo un demo di come questo funziona con una perdita MSE personalizzata.

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

Pensieri finali

Abbiamo discusso molto riguardo alle perdite disponibili in PyTorch e abbiamo anche fatto un’analisi approfondita delle modalità di funzionamento di molte di queste perdite. Scegliere la perdita giusta per un determinato problema può essere un compito impegnativo. Si spera, questo tutorial insieme alle documentazioni ufficiali di PyTorch faccia da guida quando si cerca di capire quale perdita è adatta al proprio problema.

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