hinge loss是一種常用損失[1],常用于度量學(xué)習和表征學(xué)習。對于一個模型,如果給定了樣本x的標簽y(假設(shè)標簽是0/1標簽,分別表示負樣本和正樣本),那么可以有兩種選擇進行模型的表征學(xué)習。第一是pointwise形式的監(jiān)督學(xué)習,通過交叉熵損失進行模型訓(xùn)練,也即是如式子(1-1)所示。
其中的是softmax函數(shù)。第二種方式是將樣本之間組成如
的pair,通過hinge loss進行pair的偏序關(guān)系學(xué)習,其hinge loss可以描述為式子(1-2):
其中的和
分別表示負樣本和正樣本的打分,而m mm這是正樣本與負樣本之間打分的最小間隔。如Fig 1.所示,我們發(fā)現(xiàn)
,而
,從式子(1-2)中可以發(fā)現(xiàn),只有
會產(chǎn)生loss,而
? 則不會產(chǎn)生loss,這一點能防止模型過擬合一些簡單的負樣本,而盡量去學(xué)習難負例。
從實現(xiàn)的角度出發(fā),我們通常可以采用下面的方式實現(xiàn),我們簡單介紹下其實現(xiàn)邏輯。
import torch
import torch.nn.functional as F
margin = 0.3
for data in dataloader():
inputs, labels = data
score_orig = model(inputs) # score_orig shape (N, 1)
N = score_orig.shape[0]
score_1 = score_orig.expand(1, N) # score_1 shape (N, N)
score_2 = torch.transpose(score_1, 1, 0)
label_1 = label.expand(1, N) # label_1 shape (N, N)
label_2 = label_1.transpose(label_1, 1, 0)
label_diff = F.relu(label_1 - label_2)
score_diff = F.relu(score_2 - score_1 + margin)
hinge_loss = score_diff * label_diff
...
為了實現(xiàn)充分利用一個batch內(nèi)的樣本,我們希望對batch內(nèi)的所有樣本都進行組pair,也就是說當batch size為N的時候,將會產(chǎn)出個pair(樣本自身不產(chǎn)生pair),為了實現(xiàn)這個目的,就需要代碼中expand和transpose這兩個操作,如Fig 2.所示,通過這兩個操作產(chǎn)出的score_1和score_2之差就是batch內(nèi)所有樣本之間的打分差,也就可以認為是batch內(nèi)兩兩均組了pair。
Fig 2. 對score的處理流程圖
與此相似的,如Fig 3.所示,我們也對label進行類似的處理,但是考慮到偏序已經(jīng)預(yù)測對了的pair不需要產(chǎn)生loss,而只有偏序錯誤的pair需要產(chǎn)出loss,因此是label_1-label_2產(chǎn)出label_diff。通過F.relu()我們替代max()的操作,將不產(chǎn)出loss的pair進行屏蔽,將score_diff和label_diff相乘就產(chǎn)出了hinge loss。
Fig 3. 對label處理的流程圖。
即便我們的label不是0/1標簽,而是分檔標簽,比如相關(guān)性中的0/1/2/3四個分檔,只要具有高檔位大于低檔位的這種物理含義(而不是分類標簽),同樣也可以采用相同的方法進行組pair,不過此時label_1-label_2產(chǎn)出的label_diff中會出現(xiàn)大于1的item,可視為是對某組pair的loss加權(quán),此時需要進行標準化,代碼將會改成如下:
import torch
import torch.nn.functional as F
margin = 0.3
epsilon = 1e-6
for data in dataloader():
inputs, labels = data
score_orig = model(inputs) # score_orig shape (N, 1)
N = score_orig.shape[0]
score_1 = score_orig.expand(1, N) # score_1 shape (N, N)
score_2 = torch.transpose(score_1, 1, 0)
label_1 = label.expand(1, N) # label_1 shape (N, N)
label_2 = label_1.transpose(label_1, 1, 0)
label_diff = F.relu(label_1 - label_2)
score_diff = F.relu(score_2 - score_1 + margin)
hinge_loss = torch.sum(score_diff * label_diff) / (torch.sum(label_diff) + epsilon) # 標準化處理,加上epsilon防止溢出
...
Reference
[1]. https://blog.csdn.net/LoseInVain/article/details/103995962, 《一文理解Ranking Loss/Contrastive Loss/Margin Loss/Triplet Loss/Hinge Loss》