Coverage for hopwise/model/loss.py: 76%
99 statements
« prev ^ index » next coverage.py v7.16.2, created at 2026-09-30 13:25 +0000
« prev ^ index » next coverage.py v7.16.2, created at 2026-09-30 13:25 +0000
1# @Time : 2020/6/26
2# @Author : Shanlei Mu
3# @Email : slmu@ruc.edu.cn
5# UPDATE:
6# @Time : 2020/8/7, 2021/12/22
7# @Author : Shanlei Mu, Gaowei Zhang
8# @Email : slmu@ruc.edu.cn, 1462034631@qq.com
10# UPDATE
11# @Time : 2025
12# @Author : Alessandro Soccol
13# @Email : alessandro.soccol@unica.it
15"""hopwise.model.loss
16#######################
17Common Loss in recommender system
18"""
20import torch
21import torch.nn.functional as F
22from torch import nn
25class SSMLoss(nn.Module):
26 """Samples Softmax Loss (SSM) according to the implementation in"""
28 def __init__(self, cosine_sim=True, temperature=1.0, eps=1e-7):
29 super().__init__()
30 self.cosine_sim = cosine_sim
31 self.temperature = temperature
32 self.eps = eps
34 def forward(self, user_emb, pos_item_emb, neg_item_emb):
35 if self.cosine_sim:
36 user_emb = nn.functional.normalize(user_emb, p=2, dim=-1)
37 pos_item_emb = nn.functional.normalize(pos_item_emb, p=2, dim=-1)
38 neg_item_emb = nn.functional.normalize(neg_item_emb, p=2, dim=-1)
40 pos_score = torch.mul(user_emb, pos_item_emb).sum(dim=1, keepdim=True)
41 neg_score = torch.einsum("ijk, ik->ij", neg_item_emb, user_emb)
43 # Temperatue-aware
44 pos_score = torch.exp(pos_score / self.temperature)
45 neg_score = torch.exp(neg_score / self.temperature).sum(dim=1, keepdim=True)
47 total_score = pos_score + neg_score
48 nce_loss = -(pos_score / total_score + self.eps).log().sum()
50 return nce_loss
53class SimCELoss(nn.Module):
54 """Simplified Sampled Softmax Cross- Entropy Loss (SimCE),
55 based on the implementation in https://arxiv.org/pdf/2406.16170"""
57 def __init__(self, margin=5.0):
58 super().__init__()
59 self.margin = margin
61 def forward(self, user_emb, pos_item_emb, neg_item_emb):
62 # user_emb: [batch, dim]
63 # pos_item_emb: [batch, dim]
64 # neg_item_emb: [batch, num_neg, dim]
65 num_neg, dim = neg_item_emb.shape[1], neg_item_emb.shape[2]
66 neg_item_emb = neg_item_emb.reshape(-1, num_neg, dim)
67 pos_score = torch.mul(user_emb, pos_item_emb).sum(dim=1)
68 neg_score = torch.mul(user_emb.unsqueeze(dim=1), neg_item_emb).sum(dim=-1)
69 neg_score = torch.max(neg_score, dim=-1).values
70 loss = torch.relu(self.margin - pos_score + neg_score)
72 return torch.mean(loss)
75class BPRLoss(nn.Module):
76 """BPRLoss, based on Bayesian Personalized Ranking
78 Args:
79 - gamma(float): Small value to avoid division by zero
81 Shape:
82 - Pos_score: (N)
83 - Neg_score: (N), same shape as the Pos_score
84 - Output: scalar.
86 Examples::
88 >>> loss = BPRLoss()
89 >>> pos_score = torch.randn(3, requires_grad=True)
90 >>> neg_score = torch.randn(3, requires_grad=True)
91 >>> output = loss(pos_score, neg_score)
92 >>> output.backward()
93 """
95 def __init__(self, gamma=1e-10):
96 super().__init__()
97 self.gamma = gamma
99 def forward(self, pos_score, neg_score):
100 loss = -torch.log(self.gamma + torch.sigmoid(pos_score - neg_score)).mean()
101 return loss
104class RegLoss(nn.Module):
105 """RegLoss, L2 regularization on model parameters"""
107 def __init__(self):
108 super().__init__()
110 def forward(self, parameters, reg_loss=None):
111 for W in parameters:
112 if reg_loss is None:
113 reg_loss = W.norm(2)
114 else:
115 reg_loss = reg_loss + W.norm(2)
116 return reg_loss
119class EmbLoss(nn.Module):
120 """EmbLoss, regularization on embeddings"""
122 def __init__(self, norm=2):
123 super().__init__()
124 self.norm = norm
126 def forward(self, *embeddings, require_pow=False):
127 if require_pow:
128 emb_loss = torch.zeros(1).to(embeddings[-1].device)
129 for embedding in embeddings:
130 emb_loss += torch.pow(input=torch.norm(embedding, p=self.norm), exponent=self.norm)
131 emb_loss /= embeddings[-1].shape[0]
132 emb_loss /= self.norm
133 return emb_loss
134 else:
135 emb_loss = torch.zeros(1).to(embeddings[-1].device)
136 for embedding in embeddings:
137 emb_loss += torch.norm(embedding, p=self.norm)
138 emb_loss /= embeddings[-1].shape[0]
139 return emb_loss
142class EmbMarginLoss(nn.Module):
143 """EmbMarginLoss, regularization on embeddings"""
145 def __init__(self, power=2):
146 super().__init__()
147 self.power = power
149 def forward(self, *embeddings):
150 dev = embeddings[-1].device
151 cache_one = torch.tensor(1.0).to(dev)
152 cache_zero = torch.tensor(0.0).to(dev)
153 emb_loss = torch.tensor(0.0).to(dev)
154 for embedding in embeddings:
155 norm_e = torch.sum(embedding**self.power, dim=1, keepdim=True)
156 emb_loss += torch.sum(torch.max(norm_e - cache_one, cache_zero))
157 return emb_loss
160class InnerProductLoss(nn.Module):
161 r"""This is the inner-product loss used in CFKG for optimization."""
163 def __init__(self):
164 super().__init__()
166 def forward(self, anchor, positive, negative):
167 pos_score = torch.mul(anchor, positive).sum(dim=1)
168 neg_score = torch.mul(anchor, negative).sum(dim=1)
169 return (F.softplus(-pos_score) + F.softplus(neg_score)).mean()
172class LogisticLoss(nn.Module):
173 """This is the logistic loss"""
175 def __init__(self):
176 super().__init__()
177 self.softplus = nn.Softplus()
179 def forward(self, positive_score, negative_score, pos_regularization=None, neg_regularization=None):
180 positive_labels = torch.ones_like(positive_score)
181 negative_labels = -torch.ones_like(negative_score)
183 positive_score = torch.mean(self.softplus(positive_score * positive_labels))
184 negative_score = torch.mean(self.softplus(negative_score * negative_labels))
186 if pos_regularization and neg_regularization:
187 positive_score = positive_score + pos_regularization
188 negative_score = negative_score + neg_regularization
190 return torch.mean(positive_score + negative_score)