SoftNNloss ( Soft Nearest Neighbor loss )

2023. 4. 1. 14:06💗 AI/💡 Theory

instance들 간의 유사도를 기반으로 loss를 측정하는 softNN loss에 대해서 알아보자 

 

출처 : https://arxiv.org/abs/1902.01889

 

Analyzing and Improving Representations with the Soft Nearest Neighbor Loss

We explore and expand the $\textit{Soft Nearest Neighbor Loss}$ to measure the $\textit{entanglement}$ of class manifolds in representation space: i.e., how close pairs of points from the same class are relative to pairs of points from different classes. W

arxiv.org

 

Contrastive learning, Metric learning등을 학습 할 때 instance들 간의 유사도를 기반으로 loss를 측정할 일이 생긴다 

 

이때 

contrastive loss, triplet loss, lifted structed loss, NCE loss, infoNCE loss 와 같이 다양한 로스들을 사용 할 수 있다.

 

이 페이지에서는 softNN loss에 대해서 알아보고자 한다. 

 

softNN loss의 경우 기존에 one postivie - multiple negative sample로 계산하는 loss를 보완하기 위해 제안된 loss이다. 

 

논문의 abstract에 따르면

" hidden layers에서 각각 다른 클래스들 간의 entanglement of representations를 최대화하는 것이 마지막 레이어의 변별력에 이득이 된다"라고 한다. 

 

거두절미하고 식부터 바로 보자 

식은 사실 별게 없다 (x는 batch(b)의 샘플, y는 클래스)

instance i 에 대해서 i를 제외한 i와 같은 클래스인 모든 instance들에 대해 거리를 구한다.

그리고 그 거리에 exp를 취해주고 평균을 구하면 된다 

 

 

이 softNNloss를 적용할 수 있는 참고할 만 한 코드도 함께 첨부한다.

 

https://github.com/minggli/deep-metric-learning/blob/aeb738ac11477f726e2fee3871e0d0d6d8808827/app/models.py#L44

 

GitHub - minggli/deep-metric-learning: InfoNCE, Soft Nearest Neighbour on MNIST and Fashion-MNIST

InfoNCE, Soft Nearest Neighbour on MNIST and Fashion-MNIST - GitHub - minggli/deep-metric-learning: InfoNCE, Soft Nearest Neighbour on MNIST and Fashion-MNIST

github.com

위의 코드를 응용(?) 해 보았다

 

import torch

batch_label = torch.Tensor([1,1,2,3,4,5,2,1])
positive_mask = torch.stack([torch.eq(batch_label,i) for i in batch_label]).float()

diagonal_inf = torch.diag(torch.stack([torch.tensor(-float("inf"))] * positive_mask.shape[0]))
at_least_two_positives_mask = (positive_mask.sum(dim=1) > 1.0).unsqueeze(1).float()
positive_mask *= at_least_two_positives_mask

# as of Frosst et al 2019
score = torch.rand(8,8)
#score = -1 * torch.cdist(input_1, input_1, p=2).square() / 1.0
score += diagonal_inf
import torch.nn.functional as F
loss = torch.log((F.softmax(score, dim=1) * positive_mask).sum(dim=1) + torch.finfo().eps)

한 batch에 대해서 gt label이 [1,1,2,3,4,5,2,1] 라고 해보자 (batch size 8)

배치에 있는 라벨에 대해서 자신과 같은 라벨인 아이들을 모은다.

즉 positive_mask은 다음과 같다

tensor([[1., 1., 0., 0., 0., 0., 0., 1.],
        [1., 1., 0., 0., 0., 0., 0., 1.],
        [0., 0., 1., 0., 0., 0., 1., 0.],
        [0., 0., 0., 0., 0., 0., 0., 0.],
        [0., 0., 0., 0., 0., 0., 0., 0.],
        [0., 0., 0., 0., 0., 0., 0., 0.],
        [0., 0., 1., 0., 0., 0., 1., 0.],
        [1., 1., 0., 0., 0., 0., 0., 1.]])

그리고 각 계산된 변수들은 다음과 같다

# diagnonal_inf
tensor([[-inf, 0., 0., 0., 0., 0., 0., 0.],
        [0., -inf, 0., 0., 0., 0., 0., 0.],
        [0., 0., -inf, 0., 0., 0., 0., 0.],
        [0., 0., 0., -inf, 0., 0., 0., 0.],
        [0., 0., 0., 0., -inf, 0., 0., 0.],
        [0., 0., 0., 0., 0., -inf, 0., 0.],
        [0., 0., 0., 0., 0., 0., -inf, 0.],
        [0., 0., 0., 0., 0., 0., 0., -inf]])

# at_least_two_positives_mask
tensor([[1.],
        [1.],
        [1.],
        [0.],
        [0.],
        [0.],
        [1.],
        [1.]])
        
# positive_mask *= at_least_two_positive_mask
tensor([[1., 1., 0., 0., 0., 0., 0., 1.],
        [1., 1., 0., 0., 0., 0., 0., 1.],
        [0., 0., 1., 0., 0., 0., 1., 0.],
        [0., 0., 0., 0., 0., 0., 0., 0.],
        [0., 0., 0., 0., 0., 0., 0., 0.],
        [0., 0., 0., 0., 0., 0., 0., 0.],
        [0., 0., 1., 0., 0., 0., 1., 0.],
        [1., 1., 0., 0., 0., 0., 0., 1.]])
        
# score += diagonal_inf
tensor([[  -inf, 0.0690, 0.1157, 0.5534, 0.8878, 0.1508, 0.0292, 0.6182],
        [0.6962,   -inf, 0.1318, 0.4295, 0.3354, 0.2861, 0.0207, 0.7232],
        [0.3852, 0.5831,   -inf, 0.2593, 0.3734, 0.9604, 0.3205, 0.4382],
        [0.8570, 0.4590, 0.4989,   -inf, 0.6128, 0.6770, 0.1192, 0.3382],
        [0.9990, 0.8546, 0.5761, 0.0645,   -inf, 0.9183, 0.7525, 0.2062],
        [0.5688, 0.8475, 0.9995, 0.6783, 0.3639,   -inf, 0.8031, 0.9515],
        [0.6795, 0.8438, 0.0714, 0.5123, 0.2669, 0.5641,   -inf, 0.0841],
        [0.4445, 0.1030, 0.7346, 0.0428, 0.1578, 0.2113, 0.6154,   -inf]])

at_least_two_positive_mask를 구하는 이유는 어차피 1개인 경우는 자기 자신이기 때문

 

score에  i=j인 부분에 -inf를 더해주는 이유는 

자기 자신과의 similarity가 계산된 부분을 제외하고 또 softmax를 취하면 0이 되기 때문

score는 편의상 임의의 rand 값들을 취하였는데 원하는 distance 거리 계산을 하면 된다

 

 

 

tensorflow soft nn code )

https://github.com/tensorflow/similarity/blob/master/tensorflow_similarity/losses/softnn_loss.py

 

GitHub - tensorflow/similarity: TensorFlow Similarity is a python package focused on making similarity learning quick and easy.

TensorFlow Similarity is a python package focused on making similarity learning quick and easy. - GitHub - tensorflow/similarity: TensorFlow Similarity is a python package focused on making similar...

github.com