text: from typing import Tuple
  
  import torch
  from torch import nn, Tensor
  import torch.nn.functional as F
  
  
  def convert_label_to_similarity(normed_feature: Tensor, label: Tensor) -> Tuple[Tensor, Tensor]:
      normed_feature = F.normalize(normed_feature)
      similarity_matrix = normed_feature @ normed_feature.transpose(1, 0)
      label_matrix = label.unsqueeze(1) == label.unsqueeze(0)
  
      positive_matrix = label_matrix.triu(diagonal=1)
      negative_matrix = label_matrix.logical_not().triu(diagonal=1)
  
      similarity_matrix = similarity_matrix.view(-1)
      similarity_matrix = 1 - (1 - similarity_matrix.pow(2)).clamp(1e-6, 1).sqrt()        # 使用1-sin替代余弦距离
      positive_matrix = positive_matrix.view(-1)
      negative_matrix = negative_matrix.view(-1)
      return similarity_matrix[positive_matrix], similarity_matrix[negative_matrix]
  
  
  class CircleLoss(nn.Module):
      def __init__(self, m=0.25, gamma=256.0) -> None:
          super(CircleLoss, self).__init__()
          self.m = m
          self.gamma = gamma
          self.soft_plus = nn.Softplus()
  
      def forward(self, feature, labels) -> Tensor:
          sp, sn = convert_label_to_similarity(feature, labels)
          ap = torch.clamp_min(- sp.detach() + 1 + self.m, min=0.)
          an = torch.clamp_min(sn.detach() + self.m, min=0.)
  
  
          delta_p = 1 - self.m
          delta_n = self.m
  
          logit_p = - ap * (sp - delta_p) * self.gamma
          logit_n = an * (sn - delta_n) * self.gamma
  
          loss = self.soft_plus(torch.logsumexp(logit_n, dim=0) + torch.logsumexp(logit_p, dim=0)) / self.gamma
  
          return loss
      # def forward(self, sp: Tensor, sn: Tensor) -> Tensor:
      #     ap = torch.clamp_min(- sp.detach() + 1 + self.m, min=0.)
      #     an = torch.clamp_min(sn.detach() + self.m, min=0.)
      #
      #
      #     delta_p = 1 - self.m
      #     delta_n = self.m
      #
      #     logit_p = - ap * (sp - delta_p) * self.gamma
      #     logit_n = an * (sn - delta_n) * self.gamma
      #
      #     loss = self.soft_plus(torch.logsumexp(logit_n, dim=0) + torch.logsumexp(logit_p, dim=0))
      #
      #     return loss
  
  if __name__ == "__main__":
      feat = nn.functional.normalize(torch.rand(256, 64, requires_grad=True))
      lbl = torch.randint(high=10, size=(256,))
  
      inp_sp, inp_sn = convert_label_to_similarity(feat, lbl)
  
      criterion = CircleLoss(m=0.25, gamma=256)
      circle_loss = criterion(inp_sp, inp_sn)
  
      print(circle_loss)
  
  # import torch
  # from torch import nn
  # import torch.nn.functional as F
  #
  #
  # class CircleLoss(nn.Module):
  #     def __init__(self, scale=32, margin=0.25, similarity='cos', **kwargs):
  #         super(CircleLoss, self).__init__()
  #         self.scale = scale
  #         self.margin = margin
  #         self.similarity = similarity
  #
  #     def forward(self, feats, labels):
  #         assert feats.size(0) == labels.size(0), \
  #             f"feats.size(0): {feats.size(0)} is not equal to labels.size(0): {labels.size(0)}"
  #
  #         m = labels.size(0)
  #         mask = labels.expand(m, m).t().eq(labels.expand(m, m)).float()
  #         pos_mask = mask.triu(diagonal=1)
  #         neg_mask = (mask - 1).abs_().triu(diagonal=1)
  #         if self.similarity == 'dot':
  #             sim_mat = torch.matmul(feats, torch.t(feats))
  #         elif self.similarity == 'cos':
  #             feats = F.normalize(feats)
  #             sim_mat = feats.mm(feats.t())
  #         else:
  #             raise ValueError('This similarity is not implemented.')
  #
  #         pos_pair_ = sim_mat[pos_mask == 1]
  #         neg_pair_ = sim_mat[neg_mask == 1]
  #
  #         alpha_p = torch.relu(-pos_pair_ + 1 + self.margin)
  #         alpha_n = torch.relu(neg_pair_ + self.margin)
  #         margin_p = 1 - self.margin
  #         margin_n = self.margin
  #         loss_p = torch.sum(torch.exp(-self.scale * alpha_p * (pos_pair_ - margin_p)))
  #         loss_n = torch.sum(torch.exp(self.scale * alpha_n * (neg_pair_ - margin_n)))
  #         loss = torch.log(1 + loss_p * loss_n)
  #         return loss
  #
  #
  # if __name__ == '__main__':
  #     batch_size = 10
  #     feats = torch.rand(batch_size, 1028)
  #     labels = torch.randint(high=10, dtype=torch.long, size=(batch_size,))
  #     circleloss = CircleLoss(similarity='cos')
  #     print(circleloss(feats, labels))