pytorch loss function
值得注意的是,很多的 loss 函式都有 size_average
和 reduce
兩個布林型別的引數,需要解釋一下。因為一般損失函式都是直接計算 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)=|xi−yi|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(xi−yi)2|xi−yi|−12,if |xi−yi|<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)=−∑ipilogqi 表示該項的權重大小。可以看出,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 的推導。
這裡的 x∈ℝNx∈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)
在前面接上一個 nn.LogSoftMax 層就等價於交叉熵損失了。事實上,nn.CrossEntropyLoss 也是呼叫這個函式。注意這裡的 xlabelxlabel 運算後的數值,
nn.NLLLoss2d
和上面類似,但是多了幾個維度,一般用在圖片上。現在的 pytorch 版本已經和上面的函式合併了。
- input, (N, C, H, W)
- target, (N, H, W)
比如用全卷積網路做 Semantic Segmentation 時,最後圖片的每個點都會預測一個類別標籤。
nn.KLDivLoss
KL 散度,又叫做相對熵,算的是兩個分佈之間的距離,越相似則越接近零。
注意這裡的 xixi 概率,剛開始還以為 API 弄錯了。
nn.MarginRankingLoss
評價相似度的損失
loss(x1,x2,y)=max(0,−y∗(x1−x2)+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)=1N∑i=1,i≠yNmax(0,(margin−xy+xi)p)loss(x,y)=1N∑i=1,i≠yNmax(0,(margin−xy+xi)p)
其中 1≤y≤N1≤y≤N 預設取 1,也可以取別的值。參考 cs231n 作業裡對 SVM Loss 的推導。
nn.MultiLabelMarginLoss
多類別(multi-class)多分類(multi-classification)的 Hinge 損失,是上面 MultiMarginLoss 在多類別上的拓展。同時限定 p = 1,margin = 1.
loss(x,y)=1N∑i=1,i≠yjn∑j=1yj≠0[max(0,1−(xyj−xi))]loss(x,y)=1N∑i=1,i≠yjn∑j=1yj≠0[max(0,1−(xyj−xi))]
這個介面有點坑,是直接從 Torch 那裡抄過來的,見 MultiLabelMarginCriterion 的描述。而 Lua 的下標和 Python 不一樣,前者的陣列下標是從 1 開始的,所以用 0 表示佔位符。有幾個坑需要注意,
- 這裡的 x,yx,y 那麼就會被認為是屬於類別 5 和 3,而 4 因為在零後面,因此會被忽略。
- 上面的公式和說明只是為了和文件保持一致,其實在呼叫介面的時候,用的是 -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=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.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+e−yixi)loss(x,y)=∑i=1Nlog(1+e−yixi)
nn.MultiLabelSoftMarginLoss
上面的多分類版本,根據最大熵的多標籤 one-versue-all 損失,其中 yy
nn.CosineEmbeddingLoss
餘弦相似度的損失,目的是讓兩個向量儘量相近。注意這兩個向量都是有梯度的。