# CornerNet Embedding Loss閱讀理解
本文並未解釋其他部分的程式碼,請搭配網路上相關文章服用。
此文章為[CornerNet官方版本](https://github.com/princeton-vl/CornerNet)的Embedding Loss程式碼解讀。
程式碼段落來自:[princeton-vl/CornerNet/models/py_utils/kp_utils.py](https://github.com/princeton-vl/CornerNet/blob/e5c39a31a8abef5841976c8eab18da86d6ee5f9a/models/py_utils/kp_utils.py#L180)
```language-python
def _ae_loss(tag0, tag1, mask):
num = mask.sum(dim=1, keepdim=True).float() #num==正樣本個數
tag0 = tag0.squeeze() #左上角的embedding值
tag1 = tag1.squeeze() #右下角的embedding值
tag_mean = (tag0 + tag1) / 2
tag0 = torch.pow(tag0 - tag_mean, 2) / (num + 1e-4)
tag0 = tag0[mask].sum()
tag1 = torch.pow(tag1 - tag_mean, 2) / (num + 1e-4)
tag1 = tag1[mask].sum()
pull = tag0 + tag1
mask = mask.unsqueeze(1) + mask.unsqueeze(2)
mask = mask.eq(2)
num = num.unsqueeze(2)
num2 = (num - 1) * num
dist = tag_mean.unsqueeze(1) - tag_mean.unsqueeze(2)
dist = 1 - torch.abs(dist)
dist = nn.functional.relu(dist, inplace=True)
dist = dist - 1 / (num + 1e-4)
dist = dist / (num2 + 1e-4)
dist = dist[mask]
push = dist.sum()
return pull, push
```
下面仔細解釋每行的意義:
```language-python
dist = tag_mean.unsqueeze(1) - tag_mean.unsqueeze(2)
```
利用pytorch的廣播機制製造每個元素對每個元素相減的效果。具體操作展示:
```language-python
import torch
a = torch.arange(5).view(1, -1) #shape:(object num, embedding value)
#a == tensor([[0, 1, 2, 3, 4]])
b = torch.ones(5).view(1, -1) #同上
#b == tensor([[1., 1., 1., 1., 1.]])
a.unsqueeze(1) - b.unsqueeze(2)
'''
tensor([[[-1., 0., 1., 2., 3.],
[-1., 0., 1., 2., 3.],
[-1., 0., 1., 2., 3.],
[-1., 0., 1., 2., 3.],
[-1., 0., 1., 2., 3.]]])
'''
a.unsqueeze(1) - a.unsqueeze(2)
'''
tensor([[[ 0, 1, 2, 3, 4],
[-1, 0, 1, 2, 3],
[-2, -1, 0, 1, 2],
[-3, -2, -1, 0, 1],
[-4, -3, -2, -1, 0]]])
'''
```
若是以batch為單位下去做的話:
```language-python
a = torch.arange(10).view(1, 5, -1) #shape:(batch size, object num, embedding channels)
'''
tensor([[[0, 1],
[2, 3],
[4, 5],
[6, 7],
[8, 9]]])
'''
b = torch.ones(10).view(1, 5, -1) #同上
'''
tensor([[[1., 1.],
[1., 1.],
[1., 1.],
[1., 1.],
[1., 1.]]])
'''
c = a.T.unsqueeze(1) - b.T.unsqueeze(2)
c.shape # torch.Size([2, 5, 5, 1])
c.squeeze(-1)
'''
tensor([[[-1., -1., -1., -1., -1.],
[ 1., 1., 1., 1., 1.],
[ 3., 3., 3., 3., 3.],
[ 5., 5., 5., 5., 5.],
[ 7., 7., 7., 7., 7.]],
[[ 0., 0., 0., 0., 0.],
[ 2., 2., 2., 2., 2.],
[ 4., 4., 4., 4., 4.],
[ 6., 6., 6., 6., 6.],
[ 8., 8., 8., 8., 8.]]])
'''
c = a.T.unsqueeze(1) - a.T.unsqueeze(2)
c.squeeze(-1)
'''
tensor([[[ 0, -2, -4, -6, -8],
[ 2, 0, -2, -4, -6],
[ 4, 2, 0, -2, -4],
[ 6, 4, 2, 0, -2],
[ 8, 6, 4, 2, 0]],
[[ 0, -2, -4, -6, -8],
[ 2, 0, -2, -4, -6],
[ 4, 2, 0, -2, -4],
[ 6, 4, 2, 0, -2],
[ 8, 6, 4, 2, 0]]])
'''
```
上面取得對其他embedding的dist方陣後,訓練目的是讓距離值小於1的(embedding值小於delta(1))變大,且距離越接近0者應該越大。所以讓delta(1)被減去距離值,並用relu過濾掉負數。
```language-python
dist = 1 - torch.abs(dist) #delta 論文中預設為 1
dist = nn.functional.relu(dist, inplace=True) #使距離大於delta者不參與梯度下降。
```
這時會發現對角線全為delta(1),但這個1是我們不需要的,為了除掉這個1,出現了我眼中最詭異的一步:
```language-python
dist = dist - 1 / (num + 1e-4) #對全部元素減去 (delta / num)
dist = dist / (num2 + 1e-4)
dist = dist[mask] #只有前景需要學習embeding
push = dist.sum()
```
num是正樣本個數。原本push會比預期值多出num\*delta,mask的True共num\*num個。
為了減去這個值,先對dist每個元素都減去delta / num。
但我很困惑,為什麼不使用mask連對角線也mask住就好?像是改成這樣:
```language-python
dist = 1 - torch.abs(dist) #delta 論文中預設為 1
dist = nn.functional.relu(dist, inplace=True) #使距離大於delta者不參與梯度下降。
size = mask.shape[-1]
mask[..., range(size), range(size)] = False 對角線設為False
dist = dist[mask] #只有前景需要學習embeding
push = dist.sum()/ (num2 + 1e-4)
```