PyTorch損失函數

引言

在機器學習模型訓練中,損失函數是根本的,在大多數機器學習项目中,如果没有損失函數,就無法驱使模型做出正確的預測。簡單來說,損失函數是一種用來衡量模型在某些數據集中的表現的数学函數或表達式。知道模型在特定數據集中的表現情況,可以使開發者者在訓練過程中做出許多決策,比如使用一个新的、更強大的模型,或者甚至將損失函數本身更改為不同類型的。谈到損失函數的類型,這些年来已經開發出幾種損失函數,各有適合用於 particular training task。

先修課程

這篇文章需要了解神經網絡。在高层次上,神經網絡由互相連接的節點(”神經元”)組成的層次結構。他們通過一種稱為 “訓練” 的過程進行學習和預測,該過程調整神經元之間的連接權重和偏置。了解神經網絡包括對它們不同層次(輸入層、隱藏層、輸出層)、激活函數、優化算法(梯度下降的變體)、損失函數等的知識。

此外,熟悉Python語法以及PyTorch庫對於理解本文中展示的代碼片段是根本的。

在本文中,我們將探索PyTorch nn模塊中不同的損失函數。我們將進一步深入研究PyTorch如何通過其nn模塊API向用戶暴露這些損失函數,通過建立自定義的一个來深入了解。

現在我們已經有了損失函數的高层次理解,讓我們來探索一些關於損失函數如何工作的重要技術詳細信息。

損失函數是什麼?

我們早先提到过,損失函數告訴我們一個模型在特定數據集上的表現。從技術上講,它是通過計算預測值與實際值之間的差距來實現這一點。當我們的模型在訓練和測試數據集上做出的預測非常接近實際值時,這意味著我們有一個相当健壯的模型。

雖然損失函數給我們有關模型性能的關鍵信息,但損失函數的主要功能並不是評估模型,因為還有更多健壯的方法可以評估我們的模型,如準確度和F-分數。損失函數的重要性主要在訓練過程中實現,我們通過減少損失函數的值來調整模型的權重。通過這樣做,我們增加了模型做出正確預測的概率,這件事如果没有損失函數的帮助可能無法實現。

不同的損失函數適合不同的問題,每個都是由研究者精心設計的,以確保在訓練過程中具有良好的梯度流。

有時,損失函數的数学表達可能會讓人感到 intimating,這也導致一些開發者將其當作黑盒子來處理。我們稍後將揭幕 PyTorch 中最常用的損失函數,但在那之前,讓我們看看在 PyTorch 的世界中如何使用損失函數。

PyTorch 中的損失函數

PyTorch 箱子裡有很多標準化的損失函數,它們都有簡單的設計模式,讓開發者在訓練過程中可以快速地遍歷這些不同的損失函數。PyTorch 所有的損失函數都打包在 nn 模塊中,這是 PyTorch 中所有神經網絡的基礎類。這意味著向你的项目中添加一個損失函數只需要添加一行代码那麼簡單。讓我們看看如何在 PyTorch 中添加一個均方誤差損失函數。

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

上方代碼返回的函數可以用以下格式來計算預測值與實際值之間的差異。

#預測值是我們神經網絡做出的預測
#目標值是數據集中的實際值
#損失值是預測值與目標值之間的損失
Loss_value = MSE_loss_fn(predicted_value, target)

既然我們已經了解如何在PyTorch中使用損失函數,讓我們深入探究PyTorch提供的几个損失函數的幕后机理。

PyTorch提供了哪些損失函數?

PyTorch自带的這些損失函數主要被 broadly categorised 成3個群組 – 回歸損失、分類損失和排名損失。

回歸損失主要處理連續值,這些值可以在兩個限制值之間的任何值。一個例子就是對一個社區的房子價格的預測。

分類損失函數則處理離散值,例如將一個物件分類為盒子、鉛筆或水瓶。

排名損失則預測值之間的相對距離。一個例子就是面部識別,我們希望知道哪些面部图像屬於某個特定的面部,並且可以通過排名來確定哪些面部與原面部持有者更接近目標面部掃描。

L1損失函數/均值絕對錯誤

L1 損失函數計算預測張量中每個值與目標值的平均絕對誤差。它首先計算預測張量中每個值與目標值的絕對差,然後計算所有絕對差計算返回的值的總和。最後,它計算這個總和值的平均值,以獲得平均絕對誤差(MAE)。L1 損失函數對於處理噪音非常健壯。

import torch.nn as nn

#size_average 和 reduce 已經不被推薦使用

#reduction 指定要應用於輸出的減少方法。可能的值有 'mean'(默認值,我們計算輸出的平均值)、'sum'(輸出總和)和 'none'(對輸出不做減少)

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

返回的單個值是兩個維度為 3×5 的張量之間的計算損失。

平方的平均誤差

均方誤差與MAE有顯著類似之处。不同的是,它不是像均绝对误差那样计算预测张量和目标之间的绝对差异,而是计算预测张量和目标张量之间值的平方差。这样做的话,相对较大的差异会被更多地惩罚,而相对较小的差异则被较少地惩罚。MSE在处理异常值和噪声方面被认为是比MAE更不稳健的。

import torch.nn as nn

loss = nn.MSELoss(size_average=None, reduce=None, reduction='mean')
#L1损失函数参数解释适用于此处。

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

交叉熵损失

交叉熵损失用于涉及多个离散类别的分类问题。它度量了一组随机变量的两个概率分布之间的差异。通常,在使用交叉熵损失时,我们网络的输出是一个softmax层,这确保了神经网络的输出是一个概率值(0-1之间的值)。

softmax层由两部分组成——特定类的预测的指数。

yi 是神经网络对特定类的输出。如果 yi 很大且为负,这个函数的输出接近于零,但不会为零,如果 yi 为正且非常大,则输出的数值更接近于1。

import numpy as np

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

第二部分是標準化值,用來確保softmax層的輸出始終是一個機率值。

这是通過將每個類值的指數相加得到的。softmax的最終方程式如下所示:

]

在PyTorch的nn模組中,交叉熵損失函數將對数softmax和負對数似然(NLL)損失合併成一個損失函數。

注意打印輸出中的梯度函數是一個NLL損失。這實際上揭示交叉熵損失在背后結合了NLL損失和對數softmax層。

負對數似然(NLL)損失

NLL損失函數的運作方式與交叉熵損失函數非常相似。交叉熵損失通過結合對數softmax層和NLL損失來獲得交叉熵損失的值。這意味著NLL損失可以通過將神经網絡的最後一层替換為對數softmax層而非一般softmax層來獲得交叉熵損失值。

m = nn.LogSoftmax(dim=1)
loss = nn.NLLLoss()
輸入大小為 N x C = 3 x 5
input = torch.randn(3, 5, requires_grad=True)
# 目標中的每個元素必須有 0 <= 值 < C
target = torch.tensor([1, 0, 4])
output = loss(m(input), target)
output.backward()
# 2D 損失示例(例如,用於圖像輸入)
N, C = 5, 4
loss = nn.NLLLoss()
# 輸入大小為 N x C x 高度 x 寬度
data = torch.randn(N, 16, 10, 10)
conv = nn.Conv2d(16, C, (3, 3))
m = nn.LogSoftmax(dim=1)
# 目標中的每個元素必須有 0 <= 值 < C
target = torch.empty(N, 8, 8, dtype=torch.long).random_(0, C)
output = loss(m(conv(data)), target)
print(output) #張量(1.4892, 梯度函數=<NllLoss2DBackward>)

# NLLLoss — PyTorch 1.9.0 文档

二元交叉熵損失

二元交叉熵損失是用於將數據點分類為僅兩個類別的特殊類型的交叉熵損失。這種問題的標籤通常是二進制的,因此我們的目標是使模型猜測值接近零對於零標籤和值接近一對於一標籤。通常在用於二元分類的 BCE 損失時,神经網絡的輸出是一個 sigmoid 層,以確保輸出值接近零或值接近一。

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) #張量(0.4198, 梯度函數=<BinaryCrossEntropyBackward>)

二元交叉熵损失與 LOGITS

我們在上一節提到,二元交叉熵損失通常會作為一個 sigmoid 層的輸出,以確保輸出值介於 0 和 1 之間。具有 LOGITS 的二元交叉熵損失將這兩層組合為一個單一的層。根據 PyTorch 文檔,這是一個數值上更穩健的版本,因為它利用了 log-sum exp 技巧。

import torch
import torch.nn as nn

target = torch.ones([10, 64], dtype=torch.float32)  # 64 類別,批次大小 = 10
output = torch.full([10, 64], 1.5)  # 一個預測(LOGIT)
pos_weight = torch.ones([64])  # 所有權重均等於 1
criterion = torch.nn.BCEWithLogitsLoss(pos_weight=pos_weight)
loss = criterion(output, target)  # -log(sigmoid(1.5))
print(loss) #tensor(0.2014)

平滑 L1 損失

平滑L1損失函數通過一個启发式的值beta,結合了MSE損失和MAE損失的優點。這個標準在Fast R-CNN論文中首次介紹。當真實值和預測值之間的絕對差異低於beta時,標準使用平方差異,類似於MSE損失。MSE損失的圖形是一個連續曲線,這意味著每個損失值之間的梯度變化並且可以在任何地方导出。而且,隨著損失值的減少,梯度減小,这在梯度下降過程中非常方便。然而,對於非常大的損失值,梯度會爆炸,因此當絕對差異超過beta時,標準會切換到MAE,對於這種情況,每個損失值的梯度几乎是常數,潛在的梯度爆炸被消除。

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

隙合嵌入損失

隙合嵌入損失主要用於半監督學習任務來衡量兩個輸入之間的相似性。當有一個輸入張量以及一個標籤張量,其中包含1或-1的值時使用。它主要用於涉及非線性嵌入和半監督學習的問題。

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: 張量([[ 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]],
#       requires_grad=True)
#target: 張量([[-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: 張量(1.2103, grad_fn=<MeanBackward0>)

邊距排名損失

邊距排名損失是排名損失的一種,其主要目標與其他損失函數不同,是衡量數據集中的 Input 集合之間的相對距離。邊距排名損失函數取兩個 Input 和一個只包含 1 或 -1 的標籤。如果標籤是 1,那麼假設第一個 Input 應該比第二個 Input 有更高的排名,如果標籤是 -1,那麼假設第二個 Input 應該比第一個 Input 有更高的排名。這種關係如下方的方程式和代碼所示。

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:  張量([-1.1109,  0.1187,  0.9441], 需要梯度=真)
#input2:  張量([ 0.9284, -0.3707, -0.7504], 需要梯度=真)
#output:  張量(0.5648, 梯度函數=)

三元組邊距損失

此標準通過使用訓練數據樣本的三元組來度量數據點之間的相似性。涉及其中的三元組是一個锚點樣本、一個正樣本和一個負樣本。目標是1)使正樣本與锚點之間的距離最小化,以及2)使锚點與負樣本之間的距離大於邊距值加上正樣本與锚點之間的距離。通常,正樣本屬於與锚點相同的類,但負樣本不屬於。因此,通過使用此損失函數,我們 aims to 使用三元組邊距損失來預測锚點與正樣本之間的高相似性值以及锚點與負樣本之間的低相似性值。

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)  #張量(1.1151, 梯度函數=)

余弦嵌入損失

余弦嵌入损失是在給定輸入 x1, x2 和一個標記張量 y,其中包含值 1 或 -1 時所用來衡量損失的。它用來衡量兩個輸入之間的相似度或不同程度。

該標準是通过計算空間中兩點之間的余弦距離來衡量相似度。余弦距離與兩點之間的角度相關,意即角度越小,輸入越接近,因此它們越相似。

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

Kullback-Leibler 分散損失

給定兩個分佈P和Q,Kullback-Leibler (KL) 分散損失衡量當P(假設為真實分佈)被Q取代時失去了多少信息。通過衡量當我們用Q來近似P時失去了多少信息,我們能夠獲得P和Q之間的相似度,從而使我们 algorithms 生成的分佈非常接近真實分佈P。當用Q來近似P時所損失的信息不一樣當用P來近似Q,因此KL 分散不是對稱的。

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

建立自訂損失函數

PyTorch 为我们提供了两种流行的方法来构建适合我们问题的自定义损失函数;這兩種分别是使用类实现和使用函數实现。讓我們看看我們如何可以實現兩種方法,從函數實現開始。

這絕對是寫出自定義損失函數的最簡單方法。這就像創建一個函數一樣簡單,將必要的輸入和其他參數傳遞給它,使用PyTorch的核API或Functional API進行一些操作,並返回一個值。讓我們用自定義的平均平方誤差來看一下一個示例。

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

在上面的工作中,我們定義了一個自定義損失函數,用於計算給定預測張量和目標張量之間的平均平方誤差

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

我們可以使用自定義損失函數和PyTorch的MSE損失函數來計算損失,以觀察我們獲得了同樣的結果。

使用Python類的自定義損失

這恐怕是PyTorch中定義自定義損失的標準和推薦方法。損失函數通過擴展nn模塊作為神經網絡圖中的節點創建。這意味著我們的自定義損失函數就像卷積層一樣,是完全相同的PyTorch層。讓我們看看使用自定義MSE損失的這個工作方式。

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

最終想法

我們已經 discuss 了很多關於在 PyTorch 中可用的損失函數,並且也对這些損失函數的內部工作机制進行了深入的探讨。為特定的問題選擇正確的損失函數可能是一個令人不知所措的任務。希望,本教程和官方 PyTorch 文档可以作為一個指導,當您嘗試理解哪個損失函數適合您的问题時。

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