1. 程式人生 > >pytorch loss function

pytorch loss function

值得注意的是,很多的 loss 函式都有 size_averagereduce 兩個布林型別的引數,需要解釋一下。因為一般損失函式都是直接計算 batch 的資料,因此返回的 loss 結果都是維度為 (batch_size, ) 的向量。

  • 如果 reduce = False,那麼 size_average 引數失效,直接返回向量形式的 loss;
  • 如果 reduce = True,那麼 loss 返回的是標量
    • 如果 size_average = True,返回 loss.mean();
    • 如果 size_average = True,返回 loss.sum();

所以下面講解的時候,一般都把這兩個引數設定成 False,這樣子比較好理解原始的損失函式定義。

下面是常見的損失函式。

nn.L1Loss

loss(xi,yi)=|xiyi|loss(xi,yi)=|xi−yi|

這裡表述的還是不太清楚,其實要求 xx 個元素。

loss_fn = torch.nn.L1Loss(reduce=False, size_average=False)
input = torch.autograd.Variable(torch.randn(3,4))
target = torch.autograd.Variable(torch.randn(3
,4)) loss = loss_fn(input, target) print(input); print(target); print(loss) print(input.size(), target.size(), loss.size())
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

nn.SmoothL1Loss

也叫作 Huber Loss,誤差在 (-1,1) 上是平方損失,其他情況是 L1 損失。

loss(xi,yi)={12(xiyi)2|xiyi|12,if |xiyi|<1otherwiseloss(xi,yi)={12(xi−yi)2if |xi−yi|<1|xi−yi|−12,otherwise

這裡很上面的 L1Loss 類似,都是 element-wise 的操作,下標 ii 個元素。

loss_fn = torch.nn.SmoothL1Loss(reduce=False, size_average=False)
input = torch.autograd.Variable(torch.randn(3,4))
target = torch.autograd.Variable(torch.randn(3,4))
loss = loss_fn(input, target)
print(input); print(target); print(loss)
print(input.size(), target.size(), loss.size())
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

nn.MSELoss

均方損失函式,用法和上面類似,這裡 loss, x, y 的維度是一樣的,可以是向量或者矩陣,ii

loss_fn = torch.nn.MSELoss(reduce=False, size_average=False)
input = torch.autograd.Variable(torch.randn(3,4))
target = torch.autograd.Variable(torch.randn(3,4))
loss = loss_fn(input, target)
print(input); print(target); print(loss)
print(input.size(), target.size(), loss.size())
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

nn.BCELoss

二分類用的交叉熵,用的時候需要在該層前面加上 Sigmoid 函式。交叉熵的定義參考 wikipedia 頁面: Cross Entropy

因為離散版的交叉熵定義是 H(p,q)=ipilogqiH(p,q)=−∑ipilog⁡qi 表示該項的權重大小。可以看出,loss, x, y, w 的維度都是一樣的。

import torch.nn.functional as F
loss_fn = torch.nn.BCELoss(reduce=False, size_average=False)
input = Variable(torch.randn(3, 4))
target = Variable(torch.FloatTensor(3, 4).random_(2))
loss = loss_fn(F.sigmoid(input), target)
print(input); print(target); print(loss)
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

這裡比較奇怪的是,權重的維度不是 2,而是和 x, y 一樣,有時候遇到正負例樣本不均衡的時候,可能要多寫一句話

class_weight = Variable(torch.FloatTensor([1, 10])) # 這裡正例比較少,因此權重要大一些
target = Variable(torch.FloatTensor(3, 4).random_(2))
weight = class_weight[target.long()] # (3, 4)
loss_fn = torch.nn.BCELoss(weight=weight, reduce=False, size_average=False)
# balabala...
  
  • 1
  • 2
  • 3
  • 4
  • 5

其實這樣子做的話,如果每次 batch_size 長度不一樣,只能每次都定義 loss_fn 了,不知道有沒有更好的解決方案。

nn.BCEWithLogitsLoss

上面的 nn.BCELoss 需要手動加上一個 Sigmoid 層,這裡是結合了兩者,這樣做能夠利用 log_sum_exp trick,使得數值結果更加穩定(numerical stability)。建議使用這個損失函式。

值得注意的是,文件裡的引數只有 weight, size_average 兩個,但是實際測試 reduce 引數也是可以用的。此外兩個損失函式的 target 要求是 FloatTensor,而且不一樣是隻能取 0, 1 兩種值,任意值應該都是可以的。

nn.CrossEntropyLoss

多分類用的交叉熵損失函式,用這個 loss 前面不需要加 Softmax 層。

這裡損害函式的計算,按理說應該也是原始交叉熵公式的形式,但是這裡限制了 target 型別為 torch.LongTensr,而且不是多標籤意味著標籤是 one-hot 編碼的形式,即只有一個位置是 1,其他位置都是 0,那麼帶入交叉熵公式中化簡後就成了下面的簡化形式。參考 cs231n 作業裡對 Softmax Loss 的推導。

loss(x,label)=wlabellogexlabelNj=1exj=wlabel[xlabel+logj=1Nexj]loss(x,label)=−wlabellog⁡exlabel∑j=1Nexj=wlabel[−xlabel+log⁡∑j=1Nexj]

這裡的 xNx∈RN 的向量,表示標籤的權重,樣本少的類別,可以考慮把權重設定大一點。

weight = torch.Tensor([1,2,1,1,10])
loss_fn = torch.nn.CrossEntropyLoss(reduce=False, size_average=False, weight=weight)
input = Variable(torch.randn(3, 5)) # (batch_size, C)
target = Variable(torch.FloatTensor(3).random_(5))
loss = loss_fn(input, target)
print(input); print(target); print(loss)
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

nn.NLLLoss

用於多分類的負對數似然損失函式(Negative Log Likelihood)

loss(x,label)=xlabelloss(x,label)=−xlabel

在前面接上一個 nn.LogSoftMax 層就等價於交叉熵損失了。事實上,nn.CrossEntropyLoss 也是呼叫這個函式。注意這裡的 xlabelxlabel 運算後的數值,

nn.NLLLoss2d

和上面類似,但是多了幾個維度,一般用在圖片上。現在的 pytorch 版本已經和上面的函式合併了。

  • input, (N, C, H, W)
  • target, (N, H, W)

比如用全卷積網路做 Semantic Segmentation 時,最後圖片的每個點都會預測一個類別標籤。

nn.KLDivLoss

KL 散度,又叫做相對熵,算的是兩個分佈之間的距離,越相似則越接近零。

loss(x,y)=1Ni=1N[yi(logyixi)]loss(x,y)=1N∑i=1N[yi∗(log⁡yi−xi)]

注意這裡的 xixi 概率,剛開始還以為 API 弄錯了。

nn.MarginRankingLoss

評價相似度的損失

loss(x1,x2,y)=max(0,y(x1x2)+margin)loss(x1,x2,y)=max(0,−y∗(x1−x2)+margin)

這裡的三個都是標量,y 只能取 1 或者 -1,取 1 時表示 x1 比 x2 要大;反之 x2 要大。引數 margin 表示兩個向量至少要相聚 margin 的大小,否則 loss 非負。預設 margin 取零。

nn.MultiMarginLoss

多分類(multi-class)的 Hinge 損失,

loss(x,y)=1Ni=1,iyNmax(0,(marginxy+xi)p)loss(x,y)=1N∑i=1,i≠yNmax(0,(margin−xy+xi)p)

其中 1yN1≤y≤N 預設取 1,也可以取別的值。參考 cs231n 作業裡對 SVM Loss 的推導。

nn.MultiLabelMarginLoss

多類別(multi-class)多分類(multi-classification)的 Hinge 損失,是上面 MultiMarginLoss 在多類別上的拓展。同時限定 p = 1,margin = 1.

loss(x,y)=1Ni=1,iyjnj=1yj0[max(0,1(xyjxi))]loss(x,y)=1N∑i=1,i≠yjn∑j=1yj≠0[max(0,1−(xyj−xi))]

這個介面有點坑,是直接從 Torch 那裡抄過來的,見 MultiLabelMarginCriterion 的描述。而 Lua 的下標和 Python 不一樣,前者的陣列下標是從 1 開始的,所以用 0 表示佔位符。有幾個坑需要注意,

  1. 這裡的 x,yx,y 那麼就會被認為是屬於類別 5 和 3,而 4 因為在零後面,因此會被忽略。
  2. 上面的公式和說明只是為了和文件保持一致,其實在呼叫介面的時候,用的是 -1 做佔位符,而 0 是第一個類別。

舉個梨子,

import torch
loss = torch.nn.MultiLabelMarginLoss()
x = torch.autograd.Variable(torch.FloatTensor([[0.1, 0.2, 0.4, 0.8]]))
y = torch.autograd.Variable(torch.LongTensor([[3, 0, -1, 1]]))
print loss(x, y) # will give 0.8500
  
  • 1
  • 2
  • 3
  • 4
  • 5

按照上面的理解,第 3, 0 個是正確的類,1, 2 不是,那麼,

loss=14i=1,2j=3,0[max(0,1(xjxi))]=14[(1(0.80.2))+(1(0.10.2))+(1(0.80.4))+(1(0.10.4))]=14[0.4+1.1+0.6+1.3]=0.85loss=14∑i=1,2∑j=3,0[max(0,1−(xj−xi))]=14[(1−(0.8−0.2))+(1−(0.1−0.2))+(1−(0.8−0.4))+(1−(0.1−0.4))]=14[0.4+1.1+0.6+1.3]=0.85

*注意這裡推導的第二行,我為了簡短,都省略了 max(0, x) 符號。

nn.SoftMarginLoss

多標籤二分類問題,這 NN 的形式不同。

loss(x,y)=i=1Nlog(1+eyixi)loss(x,y)=∑i=1Nlog⁡(1+e−yixi)

nn.MultiLabelSoftMarginLoss

上面的多分類版本,根據最大熵的多標籤 one-versue-all 損失,其中 yy

nn.CosineEmbeddingLoss

餘弦相似度的損失,目的是讓兩個向量儘量相近。注意這兩個向量都是有梯度的。

loss(x,y)={