import torch from ..distances import CosineSimilarity from ..reducers import AvgNonZeroReducer from ..utils import common_functions as c_f from ..utils import loss_and_miner_utils as lmu from .generic_pair_loss import GenericPairLoss class CircleLoss(GenericPairLoss): """ Circle loss for pairwise labels only. Args: m: The relaxation factor that controls the radious of the decision boundary. gamma: The scale factor that determines the largest scale of each similarity score. According to the paper, the suggested default values of m and gamma are: Face Recognition: m = 0.25, gamma = 256 Person Reidentification: m = 0.25, gamma = 128 Fine-grained Image Retrieval: m = 0.4, gamma = 80 By default, we set m = 0.4 and gamma = 80 """ def __init__(self, m=0.4, gamma=80, **kwargs): super().__init__(mat_based_loss=True, **kwargs) c_f.assert_distance_type(self, CosineSimilarity) self.m = m self.gamma = gamma self.soft_plus = torch.nn.Softplus(beta=1) self.op = 1 + self.m self.on = -self.m self.delta_p = 1 - self.m self.delta_n = self.m self.add_to_recordable_attributes( list_of_names=["m", "gamma", "op", "on", "delta_p", "delta_n"], is_stat=False, ) def _compute_loss(self, mat, pos_mask, neg_mask): pos_mask_bool = pos_mask.bool() neg_mask_bool = neg_mask.bool() anchor_positive = mat[pos_mask_bool] anchor_negative = mat[neg_mask_bool] new_mat = torch.zeros_like(mat) new_mat[pos_mask_bool] = ( -self.gamma * torch.relu(self.op - anchor_positive.detach()) * (anchor_positive - self.delta_p) ) new_mat[neg_mask_bool] = ( self.gamma * torch.relu(anchor_negative.detach() - self.on) * (anchor_negative - self.delta_n) ) logsumexp_pos = lmu.logsumexp( new_mat, keep_mask=pos_mask_bool, add_one=False, dim=1 ) logsumexp_neg = lmu.logsumexp( new_mat, keep_mask=neg_mask_bool, add_one=False, dim=1 ) losses = self.soft_plus(logsumexp_pos + logsumexp_neg) zero_rows = torch.where( (torch.sum(pos_mask, dim=1) == 0) | (torch.sum(neg_mask, dim=1) == 0) )[0] final_mask = torch.ones_like(losses) final_mask[zero_rows] = 0 losses = losses * final_mask return { "loss": { "losses": losses, "indices": c_f.torch_arange_from_size(new_mat), "reduction_type": "element", } } def get_default_reducer(self): return AvgNonZeroReducer() def get_default_distance(self): return CosineSimilarity()
Memory