Coverage for hopwise/model/general_recommender/nceplrec.py: 89%

47 statements  

« prev     ^ index     » next       coverage.py v7.16.2, created at 2026-09-30 13:25 +0000

1# @Time : 2022/02/19 

2# @Author : Gaowei Zhang 

3# @email : 1462034631@qq.com 

4 

5 

6"""NCE-PLRec 

7###################################### 

8Reference: 

9 Ga Wu, et al. "Noise Contrastive Estimation for One-Class Collaborative Filtering" in SIGIR 2019. 

10Reference code: 

11 https://github.com/wuga214/NCE_Projected_LRec 

12""" 

13 

14import numpy as np 

15import scipy.sparse as sp 

16import torch 

17from sklearn.utils.extmath import randomized_svd 

18 

19from hopwise.model.abstract_recommender import GeneralRecommender 

20from hopwise.utils import InputType 

21 

22 

23class NCEPLRec(GeneralRecommender): 

24 input_type = InputType.POINTWISE 

25 

26 def __init__(self, config, dataset): 

27 super().__init__(config, dataset) 

28 

29 # need at least one param 

30 self.dummy_param = torch.nn.Parameter(torch.zeros(1)) 

31 

32 R = dataset.inter_matrix(form="csr").astype(np.float32) 

33 

34 beta = config["beta"] 

35 rank = int(config["rank"]) 

36 reg_weight = config["reg_weight"] 

37 seed = config["seed"] 

38 

39 # just directly calculate the entire score matrix in init 

40 # (can't be done incrementally) 

41 num_users, num_items = R.shape 

42 

43 item_popularities = R.sum(axis=0) 

44 

45 D_rows = [] 

46 for i in range(num_users): 

47 row_index, col_index = R[i].nonzero() 

48 if len(row_index) > 0: 

49 values = item_popularities[:, col_index].getA1() 

50 # note this is a slight variation of what's in the paper, for convenience 

51 # see https://github.com/wuga214/NCE_Projected_LRec/issues/38 

52 values = np.maximum(np.log(num_users / np.power(values, beta)), 0) 

53 D_rows.append(sp.coo_matrix((values, (row_index, col_index)), shape=(1, num_items))) 

54 else: 

55 D_rows.append(sp.coo_matrix((1, num_items))) 

56 

57 D = sp.vstack(D_rows) 

58 

59 _, sigma, Vt = randomized_svd( 

60 D, 

61 n_components=rank, 

62 n_iter="auto", 

63 power_iteration_normalizer="QR", 

64 random_state=seed, 

65 ) 

66 

67 sqrt_Sigma = np.diag(np.power(sigma, 1 / 2)) 

68 

69 V_star = Vt.T @ sqrt_Sigma 

70 

71 Q = R @ V_star 

72 # Vt.shape[0] instead of rank for cases when the interaction matrix is smaller than given rank 

73 W = np.linalg.inv(Q.T @ Q + reg_weight * np.identity(Vt.shape[0])) @ Q.T @ R 

74 

75 # instead of computing and storing the entire score matrix, just store Q and W and compute the scores on demand 

76 

77 self.user_embeddings = torch.from_numpy(Q).to(self.device) 

78 self.item_embeddings = torch.from_numpy(W).to(self.device) 

79 

80 def forward(self): 

81 pass 

82 

83 def calculate_loss(self, interaction): 

84 return torch.nn.Parameter(torch.zeros(1)) 

85 

86 def predict(self, interaction): 

87 user = interaction[self.USER_ID] 

88 item = interaction[self.ITEM_ID] 

89 result = (self.user_embeddings[user, :] * self.item_embeddings[:, item].T).sum(axis=1) 

90 return result.float() 

91 

92 def full_sort_predict(self, interaction): 

93 user = interaction[self.USER_ID] 

94 

95 result = self.user_embeddings[user, :] @ self.item_embeddings 

96 return result.flatten()