Coverage for hopwise/model/general_recommender/slimelastic.py: 90%

50 statements  

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

1r"""SLIMElastic 

2################################################ 

3Reference: 

4 Xia Ning et al. "SLIM: Sparse Linear Methods for Top-N Recommender Systems." in ICDM 2011. 

5Reference code: 

6 https://github.com/KarypisLab/SLIM 

7 https://github.com/MaurizioFD/RecSys2019_DeepLearning_Evaluation/blob/master/SLIM_ElasticNet/SLIMElasticNetRecommender.py 

8""" 

9 

10import warnings 

11 

12import numpy as np 

13import scipy.sparse as sp 

14import torch 

15from sklearn.exceptions import ConvergenceWarning 

16from sklearn.linear_model import ElasticNet 

17 

18from hopwise.model.abstract_recommender import GeneralRecommender 

19from hopwise.utils import InputType, ModelType 

20 

21 

22class SLIMElastic(GeneralRecommender): 

23 r"""SLIMElastic is a sparse linear method for top-K recommendation, which learns 

24 a sparse aggregation coefficient matrix by solving an L1-norm and L2-norm 

25 regularized optimization problem. 

26 

27 """ 

28 

29 input_type = InputType.POINTWISE 

30 type = ModelType.TRADITIONAL 

31 

32 def __init__(self, config, dataset): 

33 super().__init__(config, dataset) 

34 

35 # load parameters info 

36 self.hide_item = config["hide_item"] 

37 self.alpha = config["alpha"] 

38 self.l1_ratio = config["l1_ratio"] 

39 self.positive_only = config["positive_only"] 

40 

41 # need at least one param 

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

43 

44 X = dataset.inter_matrix(form="csr").astype(np.float32) 

45 X = X.tolil() 

46 self.interaction_matrix = X 

47 

48 model = ElasticNet( 

49 alpha=self.alpha, 

50 l1_ratio=self.l1_ratio, 

51 positive=self.positive_only, 

52 fit_intercept=False, 

53 copy_X=False, 

54 precompute=True, 

55 selection="random", 

56 max_iter=100, 

57 tol=1e-4, 

58 ) 

59 item_coeffs = [] 

60 

61 # ignore ConvergenceWarnings 

62 with warnings.catch_warnings(): 

63 warnings.simplefilter("ignore", category=ConvergenceWarning) 

64 

65 for j in range(X.shape[1]): 

66 # target column 

67 r = X[:, j] 

68 

69 if self.hide_item: 

70 # set item column to 0 

71 X[:, j] = 0 

72 

73 # fit the model 

74 model.fit(X, r.todense().getA1()) 

75 

76 # store the coefficients 

77 coeffs = model.sparse_coef_ 

78 

79 item_coeffs.append(coeffs) 

80 

81 if self.hide_item: 

82 # reattach column if removed 

83 X[:, j] = r 

84 

85 self.item_similarity = sp.vstack(item_coeffs).T 

86 self.other_parameter_name = ["interaction_matrix", "item_similarity"] 

87 

88 def forward(self): 

89 pass 

90 

91 def calculate_loss(self, interaction): 

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

93 

94 def predict(self, interaction): 

95 user = interaction[self.USER_ID].cpu().numpy() 

96 item = interaction[self.ITEM_ID].cpu().numpy() 

97 

98 r = torch.from_numpy( 

99 (self.interaction_matrix[user, :].multiply(self.item_similarity[:, item].T)).sum(axis=1).getA1() 

100 ).to(self.device) 

101 

102 return r 

103 

104 def full_sort_predict(self, interaction): 

105 user = interaction[self.USER_ID].cpu().numpy() 

106 

107 r = self.interaction_matrix[user, :] @ self.item_similarity 

108 r = torch.from_numpy(r.todense().getA1()) 

109 

110 return r