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
« 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
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"""
14import numpy as np
15import scipy.sparse as sp
16import torch
17from sklearn.utils.extmath import randomized_svd
19from hopwise.model.abstract_recommender import GeneralRecommender
20from hopwise.utils import InputType
23class NCEPLRec(GeneralRecommender):
24 input_type = InputType.POINTWISE
26 def __init__(self, config, dataset):
27 super().__init__(config, dataset)
29 # need at least one param
30 self.dummy_param = torch.nn.Parameter(torch.zeros(1))
32 R = dataset.inter_matrix(form="csr").astype(np.float32)
34 beta = config["beta"]
35 rank = int(config["rank"])
36 reg_weight = config["reg_weight"]
37 seed = config["seed"]
39 # just directly calculate the entire score matrix in init
40 # (can't be done incrementally)
41 num_users, num_items = R.shape
43 item_popularities = R.sum(axis=0)
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)))
57 D = sp.vstack(D_rows)
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 )
67 sqrt_Sigma = np.diag(np.power(sigma, 1 / 2))
69 V_star = Vt.T @ sqrt_Sigma
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
75 # instead of computing and storing the entire score matrix, just store Q and W and compute the scores on demand
77 self.user_embeddings = torch.from_numpy(Q).to(self.device)
78 self.item_embeddings = torch.from_numpy(W).to(self.device)
80 def forward(self):
81 pass
83 def calculate_loss(self, interaction):
84 return torch.nn.Parameter(torch.zeros(1))
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()
92 def full_sort_predict(self, interaction):
93 user = interaction[self.USER_ID]
95 result = self.user_embeddings[user, :] @ self.item_embeddings
96 return result.flatten()