關於pytorch中的CrossEntropyLoss()的理解

分類問題中,交叉熵函數是比較常用也是比較基礎的損失函數。

基本推導過程

提到交叉熵,腦子裏就會出現這個公式:
L=[ylogy^+(1y)log(1y^)]L=-[y*log\hat{y}+(1-y)*log(1-\hat{y})]
然後,腦子裏還會浮現出sigmoid這個函數:
g(s)=11+esg(s)=\frac{1}{1+e^{-s}}

pytorch中的CrossEntropyLoss()函數其實就是把輸出結果進行sigmoid(將數據設置到0-1之間),隨後再放到傳統的交叉熵函數中,就會得到結果。

我們知道sigmoidsigmoid的作用其實就是把前一層的輸入映射到0-1這個區間上,可以認爲上一層某個輸入樣本的數據越大,就代表這個樣本標籤屬於1的概率就越大,反之, 上一層樣本的輸入數據越小,這個樣本標籤屬於0的概率就越大, 而且通過sigmoidsigmoid函數的圖像(見下)我們可以看出來,隨着輸入值的增大, 其對概率增大的作用效果是逐漸減弱的, 反之同理, 這就是非線性映射的一個好處,讓模型處於對中間範圍的輸入數據更敏感,下面是sigmoidsigmoid圖:

在這裏插入圖片描述
既然經過sigmoidsigmoid之後的數據能表示樣本所屬某個標籤的概率, 那麼舉個例子,我們將模型預測某個樣本標籤爲1的概率是:

y^=P(y=1x)\hat{y}=P(y=1|x)
那麼, 這個樣本標籤不爲1的概率是:
1y^=P(y=0x)1-\hat{y}=P(y=0|x)

從極大似然的角度來講就是:
P(yx)=y^y(1y^)1yP(y|x)=\hat{y}^{y}*(1-\hat{y})^{1-y}

上式可以理解爲,某一個樣本x,我們通過模型預測出其屬於樣本標籤爲y的概率, 因爲y是我們給的正確結果, 所以我們當然希望上式越大越好。
接下來我們在P(yx)P(y|x)的外面套上一層log函數,相當於進行了一次非線性映射。衆所周知,loglog函數是不會改變單調性的,所以我們也希望log(P(yx))log(P(y|x))越大越好。
log(P(yx))=log(y^y(1y^)1y)=ylogy^+(1y)log(1y^)log(P(y|x))=log(\hat{y}^{y}*(1-\hat{y})^{1-y})=y*log\hat{y}+(1-y)*log(1-\hat{y})

這樣我們就得到了一開始說的交叉熵的形式了,因爲一般來說如果使用上述公式做loss函數來用,我們希望loss越小越好,這樣符合我們的直觀理解,所以變形成爲log(P(yx))-log(P(y|x))就達到了我們的目的。
L=[ylogy^+(1y)log(1y^)]L=-[y*log\hat{y}+(1-y)*log(1-\hat{y})]

上面 是二分類問題的交叉熵,如果是有多分類,就對每個變遷類別下的可能概率分別求對應的log-log然後求和就好了:
L=i=1Ny(i)logy^(i) L=-\sum_{i=1}^{N}y^{(i)}*log\,\hat{y}^{(i)}

驗證

我們結合pytorchpytorch中的小例子來說明一下CrossEntropyLoss()CrossEntropyLoss()的用法。

舉個小例子,假設我們有個一樣本,他經過我們的神經網絡後會輸出一個5維的向量,分別代表這個樣本分別屬於這5種標籤的數值(注意此時我們的5個數求和還並不等於1,需要先經過softmax處理,下面會說),我們還會從數據集中得到該樣本的正確分類結果,下面我們要把經過神經網絡的5維向量和正確的分類結果放到CrossEntropyLoss()CrossEntropyLoss()中,看看會發生什麼:

import torch
import torch.nn as nn
import math

loss = nn.CrossEntropyLoss()
input = torch.randn(1, 5, requires_grad= True)
target = torch.empty(1, dtype = torch.long).random_(5)
output = loss(input, target)

print('輸入爲5類:', input)
print('要計算loss的真實類別', target)
print('loss=', output)
#自己計算的結果
first = 0
for i in range(1):
    first -= input[i][target[i]]
second = 0
for i in range(1):
    for j in range(5):
        second += math.exp(input[i][j])
res = 0
res += first + math.log(second)
print('自己的計算結果:', res)

輸出結果:

輸入爲5類: tensor([[ 1.0249, -0.4067,  0.3037,  0.8252,  0.6239]], requires_grad=True)
要計算loss的真實類別 tensor([1])
loss= tensor(2.5990, grad_fn=<NllLossBackward>)
自己的計算結果: tensor(2.5990, grad_fn=<AddBackward0>)

源代碼中的解釋:

程序中運行後產生的target=1target=1,可以知道我們的targettarget就是隻有一個數的張量形式, (不是向量,不是矩陣,只是一個簡單的張量形式,而且裏面只有一個數), inputinput是一個5維張量,我們在計算交叉熵之前,需要先獲得下面交叉熵公式的y^(i)\hat{y}^{(i)}.
L=i=1Ny(i)logy^(i) L=-\sum_{i=1}^{N}y^{(i)}*log\,\hat{y}^{(i)}

這個地方的y^(i)\hat{y}^{(i)}需要將輸入的inputinput向量進行softmaxsoftmax處理(這也是一種映射,可以將inputinput變成屬於每個標籤的概率值, 對每個input[i]input[i]進行如下處理)
y^=P(y^=ix)=einput[i]j=0Neinput[j]\hat{y}=P(\hat{y}=i|x)=\frac{e^{input[i]}}{\sum_{j=0}^{N}e^{input[j]}}
這樣我們就得到了交叉熵公式中的y^(i)\hat{y}^{(i)}
隨後我們就可以將y^(i)\hat{y}^{(i)}代入公式了,下面還缺少y(i)y^{(i)},但奇怪的是我們輸入的targettarget是一個只有一個數的張量,但是y^(i)\hat{y}^{(i)}是一個5維的張量,這時候應該怎麼處理呢?
原來CrossEntropyLoss()CrossEntropyLoss()會把targettarget自動變成onehotone-hot形式,我們現在例子的樣本標籤是1(從0開始計算)。那麼轉換成的onehotone-hot編碼就是[01000][0 \,1\, 0\, 0\, 0 ],所以我們的y(i)y^{(i)}最後也變成了一個5維的張量,這樣我們在計算交叉熵的時候只計算y(i)y^{(i)}給定的那一項scorescore就好了,所以我們的公式最後變成了:
L(input,target)=logeinput[target]j=0Neinput[j]=input[target]+log(j=0Neinput[j]) L(input, target)=-log\frac{e^{input[target]}}{\sum_{j=0}^{N}e^{input[j]}}=-input[target]+log(\sum_{j=0}^{N}e^{input[j]})

這樣,根據上面的公式就很簡單的計算出了交叉熵損失函數。

我們再看一個具有三個待預測值,而且需要五分類的例子:

import torch
import torch.nn as nn
import numpy as np
import math
#我們這次計算有三個待預測像素的點的loss值
loss = nn.CrossEntropyLoss()
input = torch.randn(3, 5, requires_grad= True)
target = torch.empty(3, dtype = torch.long).random_(5)
output = loss(input, target)
print('input is ', input)
print('target is ', target)
print('loss=', output)
#自己跟據公式計算loss
first = []
for i in range(3):
    val = input[i][target[i]]
    first.append(val)
# print(first)
second = []
sum = 0
for i in range(3):
    for j in range(5):
        sum += math.exp(input[i][j])
    second.append(sum)
    sum = 0
# print(second)
L = []
for i in range(3):
    l = -first[i] + math.log(second[i])
    L.append(l)
# print(L)

Loss = 0
for i in range(3):
    Loss += L[i]
Loss = Loss / 3 #需要除以3
print('自己計算的loss', Loss)

結果:

input is  tensor([[-0.9011, -0.0138, -0.4324, -0.3815,  0.0678],
        [ 0.3449, -1.4938, -0.3058, -1.3013,  0.5152],
        [-0.7477,  0.1374,  0.0129,  0.2553, -0.7631]], requires_grad=True)
target is  tensor([3, 0, 4])
loss= tensor(1.6919, grad_fn=<NllLossBackward>)
自己計算的loss tensor(1.6919, grad_fn=<DivBackward0>)

這個小實驗可以看出,pytorchpytorch中計算的loss是平均值.
全文完

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章