# 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) ```