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

1# @Time : 2020/6/26 

2# @Author : Shanlei Mu 

3# @Email : slmu@ruc.edu.cn 

4 

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 

9 

10# UPDATE 

11# @Time : 2025 

12# @Author : Alessandro Soccol 

13# @Email : alessandro.soccol@unica.it 

14 

15"""hopwise.model.loss 

16####################### 

17Common Loss in recommender system 

18""" 

19 

20import torch 

21import torch.nn.functional as F 

22from torch import nn 

23 

24 

25class SSMLoss(nn.Module): 

26 """Samples Softmax Loss (SSM) according to the implementation in""" 

27 

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 

33 

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) 

39 

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) 

42 

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) 

46 

47 total_score = pos_score + neg_score 

48 nce_loss = -(pos_score / total_score + self.eps).log().sum() 

49 

50 return nce_loss 

51 

52 

53class SimCELoss(nn.Module): 

54 """Simplified Sampled Softmax Cross- Entropy Loss (SimCE), 

55 based on the implementation in https://arxiv.org/pdf/2406.16170""" 

56 

57 def __init__(self, margin=5.0): 

58 super().__init__() 

59 self.margin = margin 

60 

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) 

71 

72 return torch.mean(loss) 

73 

74 

75class BPRLoss(nn.Module): 

76 """BPRLoss, based on Bayesian Personalized Ranking 

77 

78 Args: 

79 - gamma(float): Small value to avoid division by zero 

80 

81 Shape: 

82 - Pos_score: (N) 

83 - Neg_score: (N), same shape as the Pos_score 

84 - Output: scalar. 

85 

86 Examples:: 

87 

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 """ 

94 

95 def __init__(self, gamma=1e-10): 

96 super().__init__() 

97 self.gamma = gamma 

98 

99 def forward(self, pos_score, neg_score): 

100 loss = -torch.log(self.gamma + torch.sigmoid(pos_score - neg_score)).mean() 

101 return loss 

102 

103 

104class RegLoss(nn.Module): 

105 """RegLoss, L2 regularization on model parameters""" 

106 

107 def __init__(self): 

108 super().__init__() 

109 

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 

117 

118 

119class EmbLoss(nn.Module): 

120 """EmbLoss, regularization on embeddings""" 

121 

122 def __init__(self, norm=2): 

123 super().__init__() 

124 self.norm = norm 

125 

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 

140 

141 

142class EmbMarginLoss(nn.Module): 

143 """EmbMarginLoss, regularization on embeddings""" 

144 

145 def __init__(self, power=2): 

146 super().__init__() 

147 self.power = power 

148 

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 

158 

159 

160class InnerProductLoss(nn.Module): 

161 r"""This is the inner-product loss used in CFKG for optimization.""" 

162 

163 def __init__(self): 

164 super().__init__() 

165 

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() 

170 

171 

172class LogisticLoss(nn.Module): 

173 """This is the logistic loss""" 

174 

175 def __init__(self): 

176 super().__init__() 

177 self.softplus = nn.Softplus() 

178 

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) 

182 

183 positive_score = torch.mean(self.softplus(positive_score * positive_labels)) 

184 negative_score = torch.mean(self.softplus(negative_score * negative_labels)) 

185 

186 if pos_regularization and neg_regularization: 

187 positive_score = positive_score + pos_regularization 

188 negative_score = negative_score + neg_regularization 

189 

190 return torch.mean(positive_score + negative_score)