本主題主要梳理損失函數(shù),并同時使用損失函數(shù)實(shí)現(xiàn)邏輯回歸。本主題內(nèi)容結(jié)構(gòu):
??1. 邏輯回歸模型;
??2. 邏輯回歸Torch實(shí)現(xiàn);
??3. 損失函數(shù)介紹;
邏輯回歸模型
- 邏輯回歸與線性回歸是有區(qū)別的,只要用來解決分類的問題,邏輯回歸把從二分類基本模型分析,通過概率的方式來區(qū)分兩個類:
- 屬于A的概率,屬于B的概率,哪個可能概率大就屬于哪一類。
決策模型
-
邏輯回歸的決策模型,使用的還是是線性模型:
-
為了從形式上接近概率,采用了一個概率密度函數(shù)(邏輯分布密度函數(shù))
-
從而決策模型為:
損失模型
- 邏輯回歸中,損失函數(shù)是:
-
- 上述公式被稱為交叉熵(Cross Entropy)
- 其中
-
邏輯回歸PyTorch實(shí)現(xiàn)
采用PyTorch實(shí)現(xiàn),我們還是使用梯度下降方法實(shí)現(xiàn)。
-
首先認(rèn)識下?lián)p失函數(shù)的表示。
- 損失函數(shù)的表達(dá)與決策函數(shù)有關(guān)。
-
數(shù)據(jù)集:
- 采用鳶尾花數(shù)據(jù)集
決策函數(shù)的表示
- 線性函數(shù)在torch中已經(jīng)定義:linear函數(shù);
- sigmoid函數(shù)在torch中已經(jīng)定義:sigmoid函數(shù);
import sklearn
import sklearn.datasets
import torch
data, target = sklearn.datasets.load_iris(return_X_y=True)
x = torch.Tensor(data[0:100]) # 取前面100個數(shù)據(jù)樣本
y = torch.Tensor(target[0:100]).view(100,1) # 形狀與x線性運(yùn)算后的形狀一樣
w = torch.randn(1, 4) # 注意形狀(linear會自動轉(zhuǎn)置)
b = torch.randn(1) # w,b是可訓(xùn)練的,就是需要求導(dǎo)或者梯度
y_ = torch.nn.functional.linear(input=x, weight=w, bias=b)
print(y_.shape)
sy_ = torch.sigmoid(y_)
print(sy_.shape, y.shape)
torch.Size([100, 1])
torch.Size([100, 1]) torch.Size([100, 1])
損失函數(shù)的表示
- 邏輯回歸使用的損失函數(shù)是交叉熵,在Torch中也封裝了一個函數(shù):
- 其他損失函數(shù)后面專門用一節(jié)作為主題介紹。
binary_cross_entropy函數(shù)
- 對數(shù)損失函數(shù),沒有做邏輯分布函數(shù)(sigmoid)運(yùn)算
torch.nn.functional.binary_cross_entropy(input, target, weight=None, size_average=None, reduce=None, reduction='mean')
- 參數(shù)說明:
- input:就是計算出來的y_
- target:就是原來數(shù)據(jù)集中標(biāo)簽y
- reduction: 損失的計算方式:
- 均值
- 求和
binary_cross_entropy_with_logits函數(shù)
- 自動做邏輯分布函數(shù)(sigmoid函數(shù))運(yùn)算。
torch.nn.functional.binary_cross_entropy_with_logits(input, target, weight=None, size_average=None, reduce=None, reduction='mean', pos_weight=None)
損失函數(shù)的例子
import sklearn
import sklearn.datasets
import torch
data, target = sklearn.datasets.load_iris(return_X_y=True)
x = torch.Tensor(data[0:100]) # 取前面100個數(shù)據(jù)樣本
y = torch.Tensor(target[0:100]).view(100, 1) # 形狀與x線性運(yùn)算后的形狀一樣
w = torch.randn(1, 4) # 注意形狀(linear會自動轉(zhuǎn)置)
b = torch.randn(1) # w,b是可訓(xùn)練的,就是需要求導(dǎo)或者梯度
y_ = torch.nn.functional.linear(input=x, weight=w, bias=b)
sy_ = torch.sigmoid(y_)
loss_mean = torch.nn.functional.binary_cross_entropy_with_logits(y_, y, reduction="mean") # 多一個運(yùn)算,除以樣本個數(shù)
print(loss_mean)
loss_sum = torch.nn.functional.binary_cross_entropy_with_logits(y_, y, reduction="sum")
print(loss_sum)
# 自己手工做sigmoid運(yùn)算
loss_mean = torch.nn.functional.binary_cross_entropy(sy_, y, reduction="mean") # 多一個運(yùn)算,除以樣本個數(shù)
print(loss_mean)
loss_sum = torch.nn.functional.binary_cross_entropy(sy_, y, reduction="sum")
print(loss_sum)
tensor(0.3298)
tensor(32.9785)
tensor(0.3298)
tensor(32.9785)
邏輯回歸分類實(shí)現(xiàn)
- 下面的實(shí)現(xiàn)方法,沒有使用隨機(jī)梯度,實(shí)現(xiàn)的核心關(guān)鍵是:
- 迭代梯度計算
- 數(shù)據(jù)預(yù)測分類
import sklearn
import sklearn.datasets
import torch
data, target = sklearn.datasets.load_iris(return_X_y=True)
x = torch.Tensor(data[50:150]) # 取前面100個數(shù)據(jù)樣本 (前面50與后面的兩個50是可分的,后面兩個50線性可分性差點(diǎn))
y = torch.Tensor(target[0:100]).float().view(100, 1) # 形狀與x線性運(yùn)算后的形狀一樣
w = torch.randn(1, 4)
b = torch.randn(1)
w.requires_grad = True # 可訓(xùn)練(x,y是不需要訓(xùn)練的)
b.requires_grad = True
epoch = 10000
learn_rate = 0.01
for n in range(epoch):
# forward:決策模型表示
y_ = torch.nn.functional.linear(input=x, weight=w, bias=b) # 計算線性輸出
sy_ = torch.sigmoid(y_) # 計算邏輯分布運(yùn)算(輸出的值可以作為概率使用)
# loss:損失函數(shù)表示
loss_mean = torch.nn.functional.binary_cross_entropy(sy_, y, reduction="mean")
# backward:計算梯度
loss_mean.backward()
# 更新梯度
with torch.autograd.no_grad(): # 關(guān)閉梯度計算跟蹤
w -= learn_rate * w.grad # 更新權(quán)重梯度
w.grad.zero_() # 清空本次計算的梯度(因?yàn)樘荻仁抢奂佑嬎悖磺蹇站屠奂樱? b -= learn_rate * b.grad # 更新偏置項梯度
b.grad.zero_()
# 觀察訓(xùn)練過程中的損失下降,與訓(xùn)練集預(yù)測的準(zhǔn)確性
if n % 1000 == 0:
print(F"誤差損失值:{loss_mean:10.6f},", end="")
sy_[sy_ > 0.5] = 1
sy_[sy_ <= 0.5] = 0
correct_rate = (sy_ == y).float().mean() # 邏輯值在Torch不給計算平均值,所以需要轉(zhuǎn)換為float類型
print(F"\t準(zhǔn)確率為:{correct_rate*100: 8.2f}%")
print("訓(xùn)練完畢!") # 下面輸出的結(jié)果與sklearn與tensorflow訓(xùn)練的結(jié)果一致
誤差損失值: 8.051266, 準(zhǔn)確率為: 50.00%
誤差損失值: 0.370522, 準(zhǔn)確率為: 95.00%
誤差損失值: 0.295042, 準(zhǔn)確率為: 95.00%
誤差損失值: 0.252245, 準(zhǔn)確率為: 95.00%
誤差損失值: 0.224739, 準(zhǔn)確率為: 96.00%
誤差損失值: 0.205526, 準(zhǔn)確率為: 96.00%
誤差損失值: 0.191302, 準(zhǔn)確率為: 96.00%
誤差損失值: 0.180313, 準(zhǔn)確率為: 97.00%
誤差損失值: 0.171543, 準(zhǔn)確率為: 97.00%
誤差損失值: 0.164363, 準(zhǔn)確率為: 97.00%
訓(xùn)練完畢!
損失函數(shù)
-
torch提供的損失函數(shù)有:
- binary_cross_entropy
- binary_cross_entropy_with_logits
- poisson_nll_loss
- cosine_embedding_loss
- cross_entropy
- ctc_loss
- hinge_embedding_loss
- kl_div
- l1_loss
- mse_loss
- margin_ranking_loss
- multilabel_margin_loss
- multilabel_soft_margin_loss
- multi_margin_loss
- nll_loss
- smooth_l1_loss
- soft_margin_loss
- triplet_margin_loss
這些函數(shù)都有標(biāo)準(zhǔn)的數(shù)學(xué)公式,每個函數(shù)的使用方式都一樣,下面直接列出公式:
binary_cross_entropy與binary_cross_entropy_with_logits函數(shù)
- 這兩個函數(shù)的本質(zhì)是一樣的,區(qū)別在于帶后綴的with_logits函數(shù)對input數(shù)據(jù)多一個sigmoid操作。
- sigmoid函數(shù)也稱logits函數(shù)。
- 主要用于二分類,比如典型的邏輯回歸,例子見上面;
poisson_nll_loss函數(shù)
- 泊松負(fù)對數(shù)似然損失(Poisson negative log likelihood loss)
-
泊松分布的函數(shù)為:
-
-
表示單位時間內(nèi)隨機(jī)事件發(fā)生的次數(shù);
- 泊松分布的期望與方差都為
;
-
是
的階乘;
-
-
-
泊松分布與二項分布的關(guān)系:
- 泊松分布是由二項分布推導(dǎo)而來,當(dāng)二項分布的n很大,p很小的時候,泊松分布可以作為二項分布的近似,這時
。
- 泊松分布是由二項分布推導(dǎo)而來,當(dāng)二項分布的n很大,p很小的時候,泊松分布可以作為二項分布的近似,這時
函數(shù)定義:
torch.nn.functional.poisson_nll_loss(input, target, log_input=True, full=False, size_average=None, eps=1e-08, reduce=None, reduction='mean')
-
參數(shù):
- log_input:邏輯值,用來設(shè)置是否對輸入做exp指數(shù)運(yùn)算:
- False:
- True:
:其中eps是無窮小量,用來防止input為0的情況。
- False:
- full:是否添加Stirling近似項
- log_input:邏輯值,用來設(shè)置是否對輸入做exp指數(shù)運(yùn)算:
-
損失函數(shù)計算公式:
- target數(shù)據(jù)服從泊松分布;
例子代碼
import sklearn
import sklearn.datasets
import torch
data, target = sklearn.datasets.load_iris(return_X_y=True)
x = torch.Tensor(data[0:100]) # 取前面100個數(shù)據(jù)樣本
y = torch.Tensor(target[0:100]).view(100, 1) # 形狀與x線性運(yùn)算后的形狀一樣
w = torch.randn(1, 4) # 注意形狀(linear會自動轉(zhuǎn)置)
b = torch.randn(1) # w,b是可訓(xùn)練的,就是需要求導(dǎo)或者梯度
y_ = torch.nn.functional.linear(input=x, weight=w, bias=b)
sy_ = torch.sigmoid(y_)
loss = torch.nn.functional.poisson_nll_loss(sy_, y) # 默認(rèn)均值:比較常采用
print(loss)
# 手工計算的效果(log_input = True)
loss_manual = sy_.exp() - y * sy_
loss_manual = loss_manual.mean()
print(loss_manual)
tensor(1.0067)
tensor(1.0067)
cosine_embedding_loss函數(shù)
用來度量兩個向量是否相似。主要用于半監(jiān)督學(xué)習(xí)與學(xué)習(xí)非線性嵌入。
函數(shù)定義:
torch.nn.functional.cosine_embedding_loss(
input1, # 向量1
input2, # 向量2
target, # 標(biāo)簽
margin=0, size_average=None, reduce=None, reduction='mean') → Tensor
- 計算公式:
-
- 其中:
是向量
夾角的余弦。
- margin的取值范圍
[-1, 1]
- 其中:
-
nll_loss與cross_entropy函數(shù)
負(fù)對數(shù)似然損失函數(shù)。用來做C個類別的分類損失函數(shù)。
-
實(shí)際上這兩個函數(shù)本質(zhì)是一樣的。
- cross_entropy多做了log_softmax運(yùn)算
函數(shù)定義:
torch.nn.functional.nll_loss(input, target, weight=None, size_average=None, ignore_index=-100, reduce=None, reduction='mean')
-
參數(shù)說明:
- weight是一個1-D的張量(Tensor),張量的長度與類別數(shù)相同,用來加權(quán)每個分類的類別。
- input:是2-D數(shù)據(jù)(N,C):N表示數(shù)量,C表示分類類別;
- target:是1-D數(shù)據(jù),長度為N即可。
-
提示:
- 因?yàn)槭嵌喾诸悊栴},所以對于輸出的結(jié)果應(yīng)該是one-hot,比如2的one-hot就是
[0,0,1,0,0,0],假設(shè)最大標(biāo)簽是5。 - 所以target必須是LongTensor類型的張量。
- 因?yàn)槭嵌喾诸悊栴},所以對于輸出的結(jié)果應(yīng)該是one-hot,比如2的one-hot就是
例子代碼
import sklearn
import sklearn.datasets
import torch
data, target = sklearn.datasets.load_iris(return_X_y=True)
x = torch.Tensor(data) # (150,4)
y = torch.Tensor(target).long() # (150)
w = torch.randn(3, 4) # 注意形狀(linear會自動轉(zhuǎn)置) :3表示類別數(shù)據(jù)(輸出的長度)
b = torch.randn(3) # w,b是可訓(xùn)練的,就是需要求導(dǎo)或者梯度
y_ = torch.nn.functional.linear(input=x, weight=w, bias=b)
sy_ = y_.log_softmax(dim=1)
# print(sy_.shape)
loss = torch.nn.functional.nll_loss(y_, y) # 默認(rèn)均值:比較常采用
print(loss)
loss = torch.nn.functional.nll_loss(sy_, y) #
print(loss)
# cross_entropy交叉熵函數(shù)(多運(yùn)算了一個sigmoid運(yùn)算)
loss_mamual = torch.nn.functional.cross_entropy(y_, y) # nll_loss就是cross_entrop編碼,自動采用softmax的one-hotb
print(loss_mamual)
tensor(-9.1227)
tensor(7.1684)
tensor(7.1684)
cross_entropy的補(bǔ)充
-
cross_entropy函數(shù)的計算公式是:
-
下面就是cross_entropy的手工實(shí)現(xiàn)函數(shù):
- 只使用了一個樣本測試,類別是5個類別。
import torch
import math
data_input = torch.FloatTensor([[1.0, 2.0, 3.0, 4.0, 5.0]]) # 計算的y,
data_target = torch.LongTensor([2]) # 改下標(biāo)不能超過上面的維數(shù)-1,這是損失函數(shù)的計算過程決定的
loss_out = torch.nn.functional.cross_entropy(data_input, data_target)
print("輸入的數(shù)據(jù)集:", data_input)
print("輸出的數(shù)據(jù)集:", data_target)
print("cross_entropy函數(shù)輸出的結(jié)果:", loss_out)
# 下面是交叉熵函數(shù)的手工計算過程
result_1 = 0.0
# 計算第一部分:???[????????????]
for row in range(data_input.size()[0]): # 行循環(huán)(表示樣本與對應(yīng)的標(biāo)簽),這里其實(shí)是1
result_1 -= data_input[row][data_target[row]]
result_2 = 0.0
# 計算第二部分:∑????[??]
for row in range(data_input.size()[0]): # 行循環(huán)(表示樣本與對應(yīng)的標(biāo)簽),這里其實(shí)是1
for col in range(data_input.size()[1]):
result_2 += math.exp(data_input[row][col])
# 最終的結(jié)果
print("手工計算結(jié)果:", result_1 + math.log(result_2)) #
輸入的數(shù)據(jù)集: tensor([[1., 2., 3., 4., 5.]])
輸出的數(shù)據(jù)集: tensor([2])
cross_entropy函數(shù)輸出的結(jié)果: tensor(2.4519)
手工計算結(jié)果: tensor(2.4519)
nll_loss函數(shù)的補(bǔ)充
-
nll_loss函數(shù)名字叫負(fù)對數(shù)似然函數(shù),實(shí)際上根本沒有做任何對數(shù)運(yùn)算,其計算公式如下:
直接把x當(dāng)成對數(shù)概率,并最終取
x[target]作為這個類別的損失,最后的損失就是所有樣本的損失。
-
注意:
- 一般nll_loss會與log_softmax一起使用,本質(zhì)也就等于cross_entropy損失函數(shù)。
import sklearn
import sklearn.datasets
import torch
data, target = sklearn.datasets.load_iris(return_X_y=True)
x = torch.Tensor(data) # (150,4)
y = torch.Tensor(target).long() # (150)
w = torch.randn(3, 4) # 注意形狀(linear會自動轉(zhuǎn)置) :3表示類別數(shù)據(jù)(輸出的長度)
b = torch.randn(3) # w,b是可訓(xùn)練的,就是需要求導(dǎo)或者梯度
y_ = torch.nn.functional.linear(input=x, weight=w, bias=b)
# sy _ = y_.log_softmax(dim=1)
sy_ = y_
loss = torch.nn.functional.nll_loss(sy_, y)
print(loss)
# 手工計算
y_one = torch.nn.functional.one_hot(y).float() # 做了個單熱編碼,方便矩陣運(yùn)算,否則就要取下標(biāo)。
re = sy_ * y_one
# re = re.log()
re = -re
re =re.sum(dim=1)
print(re.mean())
tensor(0.0533)
tensor(0.0533)
mse_loss損失函數(shù)
-
最直觀的損失函數(shù):均方差損失,計算公式如下:
函數(shù)說明:
torch.nn.functional.mse_loss(input, target, size_average=None, reduce=None, reduction='mean') → Tensor
- 例子代碼
import sklearn
import sklearn.datasets
import torch
data, target = sklearn.datasets.load_iris(return_X_y=True)
x = torch.Tensor(data) # (150,4)
y = torch.Tensor(target).view(150,1) # (150)
w = torch.randn(1, 4) # 注意形狀(linear會自動轉(zhuǎn)置) :3表示類別數(shù)據(jù)(輸出的長度)
b = torch.randn(1) # w,b是可訓(xùn)練的,就是需要求導(dǎo)或者梯度
y_ = torch.nn.functional.linear(input=x, weight=w, bias=b)
loss = torch.nn.functional.mse_loss(y_, y)
print(loss)
# 手工計算
loss_manual = ((y -y_) ** 2).mean()
print(loss_manual)
tensor(14.2841)
tensor(14.2841)
l1_loss函數(shù)
-
這個函數(shù)從字面上理解,應(yīng)該是L1范數(shù)度量的距離誤差,與均方差損失函數(shù)屬于同一性質(zhì)的損失函數(shù)。函數(shù)公式為:
函數(shù)的定義
torch.nn.functional.l1_loss(input, target, size_average=None, reduce=None, reduction='mean') → Tensor
- 使用例子
import sklearn
import sklearn.datasets
import torch
data, target = sklearn.datasets.load_iris(return_X_y=True)
x = torch.Tensor(data) # (150,4)
y = torch.Tensor(target).view(150, 1) # (150, 1)
w = torch.randn(1, 4) # 注意形狀(linear會自動轉(zhuǎn)置) :3表示類別數(shù)據(jù)(輸出的長度)
b = torch.randn(1) # w,b是可訓(xùn)練的,就是需要求導(dǎo)或者梯度
y_ = torch.nn.functional.linear(input=x, weight=w, bias=b)
sy_ = y_.sigmoid()
# sy_ = y_
loss = torch.nn.functional.l1_loss(sy_, y) #
print(loss)
# 手工計算
loss_manual = (y - sy_).abs().mean()
print(loss_manual)
tensor(0.6954)
tensor(0.6954)
kl_div函數(shù)
-
The
Kullback-Leibler divergence_ Loss.也稱KL距離,一種不同于幾何距離的度量方式,用來度量兩個概率的差異的距離。
??相對熵(relative entropy),又被稱為Kullback-Leibler散度(Kullback-Leibler divergence)或信息散度(information divergence),是兩個概率分布(probability distribution)間差異的非對稱性度量 。
- > ??在在信息理論中,相對熵等價于兩個概率分布的信息熵(Shannon entropy)的差值。
- >??相對熵是一些優(yōu)化算法,例如最大期望算法(Expectation-Maximization algorithm, EM)的損失函數(shù) 。此時參與計算的一個概率分布為真實(shí)分布,另一個為理論(擬合)分布,相對熵表示使用理論分布擬合真實(shí)分布時產(chǎn)生的信息損耗 。
-
計算公式:
信息熵:
散度:
-
Torch中封裝的公式:
-
:target=0的情況總體看成0,只考慮target為1的情況
-
函數(shù)定義
torch.nn.functional.kl_div(input, target, size_average=None, reduce=None, reduction='mean')
-
參數(shù)說明:
- reduction參數(shù):batchmean最后的均值使用batch_size,mean使用輸出的個數(shù);
- 注意batch_size與輸出總數(shù)是有差別的。如果是(N,1)維度,則沒有差別。
- reduction參數(shù):batchmean最后的均值使用batch_size,mean使用輸出的個數(shù);
例子代碼
import sklearn
import sklearn.datasets
import torch
data, target = sklearn.datasets.load_iris(return_X_y=True)
x = torch.Tensor(data[:100]) # (150,4)
y = torch.Tensor(target[:100]).view(100, 1) # (150, 1)
w = torch.randn(1, 4) # 注意形狀(linear會自動轉(zhuǎn)置) :3表示類別數(shù)據(jù)(輸出的長度)
b = torch.randn(1) # w,b是可訓(xùn)練的,就是需要求導(dǎo)或者梯度
y_ = torch.nn.functional.linear(input=x, weight=w, bias=b)
sy_ = y_
loss = torch.nn.functional.kl_div(sy_, y, reduction="batchmean")
print(loss)
# 手工計算(本質(zhì)與nll_loss函數(shù)一樣:nll_loss支持多類)
loss_manual = - y * (sy_)
print(loss_manual.mean())
tensor(-0.9451)
tensor(-0.9451)
hinge_embedding_loss函數(shù)
-
用來測試兩個輸入的數(shù)據(jù)是否相似。
- y的取值為-1或者1
函數(shù)定義
torch.nn.functional.hinge_embedding_loss(input, target, margin=1.0, size_average=None, reduce=None, reduction='mean') → Tensor
-
函數(shù)公式:
例子代碼:
import sklearn
import sklearn.datasets
import torch
data, target = sklearn.datasets.load_iris(return_X_y=True)
target[target == 0] = -1
x = torch.Tensor(data[:100]) # (150,4)
y = torch.Tensor(target[:100]).view(100, 1) # (150, 1)
w = torch.randn(1, 4) # 注意形狀(linear會自動轉(zhuǎn)置) :3表示類別數(shù)據(jù)(輸出的長度)
b = torch.randn(1) # w,b是可訓(xùn)練的,就是需要求導(dǎo)或者梯度
y_ = torch.nn.functional.linear(input=x, weight=w, bias=b)
sy_ = y_
# print(y_)
# sy_ = y_.sigmoid()
loss = torch.nn.functional.hinge_embedding_loss(sy_, y, reduction="mean") # 默認(rèn)均值:比較常采用(多一個sigmoid運(yùn)算)
print(loss)
# 手工計算(本質(zhì)與nll_loss函數(shù)一樣:nll_loss支持多類)
loss_manual[0:50] = 1 - sy_[0:50]
loss_manual[loss_manual< 0] = 0
loss_manual[50:100] = sy_[50:100]
# loss_manual[loss_manual <0] = 0
print(loss_manual.mean())
tensor(0.3959)
tensor(0.3959)
其他損失函數(shù)
- 其他損失函數(shù)是基于多分類與其他目的的變種函數(shù),這些損失函數(shù)在特定的需求理解會更加容易。
- 比如:soft_margin_loss是基于SVM的軟距離提出的一種損失優(yōu)化方法。