深度學習(十三)——損失函數與反向傳播

2023-08-25 06:00:55

一、損失函數:Loss Function

官網檔案:torch.nn — PyTorch 2.0 documentation

1. Loss Function的作用

  • 每次訓練神經網路的時候都會有一個目標,也會有一個輸出。目標和輸出之間的誤差,就是用\(Loss\) \(Function\)來衡量的。所以,誤差\(Loss\)越小越好的。

  • 此外,我們可以根據誤差\(Loss\),指導輸出\(output\)接近目標\(target\)。即我們可以以\(Loss\)為依據,不斷訓練神經網路,優化神經網路中各個模組,從而優化\(output\)

\(Loss\) \(Function\)的作用:

(1)計算實際輸出和目標之間的差距

(2)為我們更新輸出提供一定的依據,這個提供依據的過程也叫反向傳播

2. Loss Function中的函數介紹

(1)nn.L1Loss

計算\(MAE\) (mean absolute error),即假設輸入為\(x_i\),目標為\(y_i\),特徵數量為\(n\)。在預設情況下,\(nn.L1Loss\)通過下面公式計算誤差:

\[\frac{\sum^{n}_{i=1}{|x_i-y_i|}}{n} \]

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

引數說明:

  • reduction:預設為 ‘mean’ ,可選meansum

    • reduction='mean'時,計算誤差採用公式:

      \[\frac{\sum^{n}_{i=1}{|x_i-y_i|}}{n} \]

    • reduction='sum'時,計算誤差採用公式:

      \[\sum^{n}_{i=1}{|x_i-y_i|} \]

  • 需要注意的是,計算的資料必須為浮點數

程式碼栗子:

import torch
from torch.nn import L1Loss

input=torch.tensor([1,2,3],dtype=torch.float32)
target=torch.tensor([1,2,5],dtype=torch.float32)

input=torch.reshape(input,(1,1,1,3))
target=torch.reshape(target,(1,1,1,3))

loss1=L1Loss()  #reduction='mean'
loss2=L1Loss(reduction='sum')  #reduction='mean'
result1=loss1(input,target)
result2=loss2(input,target)

print(result1,result2)

(2)nn.MSELoss

計算\(MSE\) (mean squared error),即假設輸入為\(x_i\),目標為\(y_i\),特徵數量為\(n\)。在預設情況下,\(nn.MSELoss\)通過下面公式計算誤差:

\[\frac{\sum^{n}_{i=1}{(x_i-y_i)^2}}{n} \]

class torch.nn.MSELoss(size_average=None, reduce=None, reduction='mean')

引數說明:

  • reduction:預設為 ‘mean’ ,可選meansum

    • reduction='mean'時,計算誤差採用公式:

      \[\frac{\sum^{n}_{i=1}{(x_i-y_i)^2}}{n} \]

    • reduction='sum'時,計算誤差採用公式:

      \[\sum^{n}_{i=1}{(x_i-y_i)^2} \]

程式碼栗子:

import torch
from torch.nn import L1Loss,MSELoss

input=torch.tensor([1,2,3],dtype=torch.float32)
target=torch.tensor([1,2,5],dtype=torch.float32)

input=torch.reshape(input,(1,1,1,3))
target=torch.reshape(target,(1,1,1,3))

loss_mse1=MSELoss()  #reduction='mean'
loss_mse2=MSELoss(reduction='sum')  #reduction='mean'
result_mse1=loss_mse1(input,target)
result_mse2=loss_mse2(input,target)

print(result_mse1,result_mse2)

(3)nn.CrossEntropyLoss(交叉熵)

當訓練一個分類問題的時候,假設這個分類問題有\(C\)個類別,那麼有:

\[loss(x,class)=-log(\frac{exp(x[class])}{\sum_{j}exp(x[j])})=-x[class]+log(\sum_{j}exp(x[j]) \]

*注意:其中的\(log\)在數學中表示的是\(ln\),即以10為底的對數函數

舉個栗子:

  • 我們對包含了人、狗、貓的圖片進行分類,其標籤的索引分別為0、1、2。這時候將一張的圖片輸入神經網路,即目標\(target\))為\(1\)(對應標籤索引)。輸出結果為\([0.1,0.2,0.3]\),該列表中的數位分別代表分類標籤對應的概率。

  • 根據上述分類結果,圖片為的概率更大,即\(0.3\)。對於該分類的\(Loss\) \(Function\),我們可以通過交叉熵去計算,即:

    \[x=[0.1,0.2,0.3];x[class]=x[1]=0.2 \]

    \[loss(x,class)=-0.2+log[exp(0.1)+exp(0.2)+exp(0.3)] \]

那麼如何驗證這個公式的合理性呢?根據上面的栗子,分類結果越準確,\(Loss\)應該越小。這條公式由兩個部分組成:

  • \(log(\sum_{j}exp(x[j])\):主要作用是控制或限制預測結果的概率分佈。比如說,預測出來的人、狗、貓的概率均為0.9,每個結果概率都很高,這顯然是不合理的。此時\(log(\sum_{j}exp(x[j])\)的值會變大,誤差\(loss(x,class)\)也會隨之變大。同時該指標也可以作為分類器效能評判標準。

  • \(-x[class]\):在已知圖片類別的情況下,預測出來對應該類別的概率\(x[class]\)越高,其預測結果誤差越小。

引數說明:

  • Input: \((N,C)\),其中\(N\)代表batch_size\(C\)代表分類的數量(或者叫標籤數量),即資料要分成幾類(或有幾個標籤)。

  • Target: \((N)\),對於每個資料:\(0\leq{target[i]}\leq{C-1}\)

程式碼栗子:

  • 仍然以上面圖片分類栗子的結果為例,編寫程式
import torch
from torch.nn import L1Loss,MSELoss,CrossEntropyLoss

x=torch.tensor([0.1,0.2,0.3])
y=torch.tensor([1])

x=torch.reshape(x,(1,3))

loss_cross=CrossEntropyLoss()
result_cross=loss_cross(x,y)
print(result_cross)
  • 直接用CIFAR 10資料進行實戰分類:
import torchvision
from torch import nn
from torch.nn import Conv2d, MaxPool2d, Flatten, Linear, Sequential
from torch.utils.data import DataLoader

dataset=torchvision.datasets.CIFAR10("./dataset",train=False,download=True,transform=torchvision.transforms.ToTensor())
dataloder=DataLoader(dataset,batch_size=1)

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

        self.model1=Sequential(
            Conv2d(3,32,5,padding=2),
            MaxPool2d(2),
            Conv2d(32, 32, 5, padding=2),
            MaxPool2d(2),
            Conv2d(32, 64, 5, padding=2),
            MaxPool2d(2),
            Flatten(),
            Linear(1024, 64),
            Linear(64, 10)
        )

    def forward(self,x):
        x=self.model1(x)
        return x

demo=Demo()
loss=nn.CrossEntropyLoss()
for data in dataloder:
    imgs,targets=data
    output=demo(imgs)

    # print(output)
    #[Run] 一共輸出10個資料,分別代表該影象為各個標籤的概率.具體如下:
    # tensor([[-0.0151, -0.0990, 0.0908, 0.0354, 0.0731, -0.0313, -0.0329, 0.1006,
    #          -0.0953, 0.0449]], grad_fn= < AddmmBackward0 >)

    # print(targets)
    #[Run] 輸出該影象真實的標籤,具體如下:
    # tensor([7])

    result_loss=loss(output,targets)
    print(result_loss)

二、反向傳播

如何根據\(Loss\) \(Function\)為更新神經網路資料提供依據?

  • 對於每個折積核當中的引數,設定一個\(grad\)(梯度)。

  • 當我們進行反向傳播的時候,對每一個節點的引數都會求出一個對應的梯度。之後我們根據梯度對每一個引數進行優化,最終達到降低\(Loss\)的一個目的。比較典型的一個方法——梯度下降法

程式碼舉例:

  • 在上面的程式碼for迴圈的最後,加上:
result_loss.backward()
  • 上面就是反向傳播的使用方法,它的主要作用是計算一個\(grad\)。使用debug功能並刪掉上面這行程式碼,會發現單純由result_loss=loss(output,targets)計算出來的結果,是沒有\(grad\)這個引數的。