Coverage for hopwise/model/general_recommender/convncf.py: 92%

75 statements  

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

1# @Time : 2020/10/6 

2# @Author : Yingqian Min 

3# @Email : eliver_min@foxmail.com 

4 

5r"""ConvNCF 

6################################################ 

7Reference: 

8 Xiangnan He et al. "Outer Product-based Neural Collaborative Filtering." in IJCAI 2018. 

9 

10Reference code: 

11 https://github.com/duxy-me/ConvNCF 

12""" 

13 

14import copy 

15 

16import torch 

17from torch import nn 

18 

19from hopwise.model.abstract_recommender import GeneralRecommender 

20from hopwise.model.general_recommender.bpr import BPR 

21from hopwise.model.layers import CNNLayers, MLPLayers 

22from hopwise.utils import InputType 

23 

24 

25class ConvNCFBPRLoss(nn.Module): 

26 """ConvNCFBPRLoss, based on Bayesian Personalized Ranking, 

27 

28 Shape: 

29 - Pos_score: (N) 

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

31 - Output: scalar. 

32 

33 Examples:: 

34 

35 >>> loss = ConvNCFBPRLoss() 

36 >>> pos_score = torch.randn(3, requires_grad=True) 

37 >>> neg_score = torch.randn(3, requires_grad=True) 

38 >>> output = loss(pos_score, neg_score) 

39 >>> output.backward() 

40 """ 

41 

42 def __init__(self): 

43 super().__init__() 

44 

45 def forward(self, pos_score, neg_score): 

46 distance = pos_score - neg_score 

47 loss = torch.sum(torch.log(1 + torch.exp(-distance))) 

48 return loss 

49 

50 

51class ConvNCF(GeneralRecommender): 

52 r"""ConvNCF is a a new neural network framework for collaborative filtering based on NCF. 

53 It uses an outer product operation above the embedding layer, 

54 which results in a semantic-rich interaction map that encodes pairwise correlations between embedding dimensions. 

55 We carefully design the data interface and use sparse tensor to train and test efficiently. 

56 We implement the model following the original author with a pairwise training mode. 

57 """ 

58 

59 input_type = InputType.PAIRWISE 

60 

61 def __init__(self, config, dataset): 

62 super().__init__(config, dataset) 

63 

64 # load dataset info 

65 self.LABEL = config["LABEL_FIELD"] 

66 

67 # load parameters info 

68 self.embedding_size = config["embedding_size"] 

69 self.cnn_channels = config["cnn_channels"] 

70 self.cnn_kernels = config["cnn_kernels"] 

71 self.cnn_strides = config["cnn_strides"] 

72 self.dropout_prob = config["dropout_prob"] 

73 self.regs = config["reg_weights"] 

74 self.train_method = config["train_method"] 

75 self.pre_model_path = config["pre_model_path"] 

76 

77 # define layers and loss 

78 assert self.train_method in ["after_pretrain", "no_pretrain"] 

79 if self.train_method == "after_pretrain": 

80 assert self.pre_model_path != "" 

81 pretrain_state = torch.load(self.pre_model_path)["state_dict"] 

82 bpr = BPR(config=config, dataset=dataset) 

83 bpr.load_state_dict(pretrain_state) 

84 self.user_embedding = copy.deepcopy(bpr.user_embedding) 

85 self.item_embedding = copy.deepcopy(bpr.item_embedding) 

86 else: 

87 self.user_embedding = nn.Embedding(self.n_users, self.embedding_size) 

88 self.item_embedding = nn.Embedding(self.n_items, self.embedding_size) 

89 

90 self.cnn_layers = CNNLayers(self.cnn_channels, self.cnn_kernels, self.cnn_strides, activation="relu") 

91 self.predict_layers = MLPLayers([self.cnn_channels[-1], 1], self.dropout_prob, activation="none") 

92 self.loss = ConvNCFBPRLoss() 

93 

94 def forward(self, user, item): 

95 user_e = self.user_embedding(user) 

96 item_e = self.item_embedding(item) 

97 

98 interaction_map = torch.bmm(user_e.unsqueeze(2), item_e.unsqueeze(1)) 

99 interaction_map = interaction_map.unsqueeze(1) 

100 

101 cnn_output = self.cnn_layers(interaction_map) 

102 cnn_output = cnn_output.sum(axis=(2, 3)) 

103 

104 prediction = self.predict_layers(cnn_output) 

105 prediction = prediction.squeeze(-1) 

106 

107 return prediction 

108 

109 def reg_loss(self): 

110 r"""Calculate the L2 normalization loss of model parameters. 

111 Including embedding matrices and weight matrices of model. 

112 

113 Returns: 

114 loss(torch.FloatTensor): The L2 Loss tensor. shape of [1,] 

115 """ 

116 reg_1, reg_2 = self.regs[:2] 

117 loss_1 = reg_1 * self.user_embedding.weight.norm(2) 

118 loss_2 = reg_1 * self.item_embedding.weight.norm(2) 

119 loss_3 = 0 

120 for name, parm in self.cnn_layers.named_parameters(): 

121 if name.endswith("weight"): 

122 loss_3 = loss_3 + reg_2 * parm.norm(2) 

123 for name, parm in self.predict_layers.named_parameters(): 

124 if name.endswith("weight"): 

125 loss_3 = loss_3 + reg_2 * parm.norm(2) 

126 return loss_1 + loss_2 + loss_3 

127 

128 def calculate_loss(self, interaction): 

129 user = interaction[self.USER_ID] 

130 pos_item = interaction[self.ITEM_ID] 

131 neg_item = interaction[self.NEG_ITEM_ID] 

132 

133 pos_item_score = self.forward(user, pos_item) 

134 neg_item_score = self.forward(user, neg_item) 

135 

136 loss = self.loss(pos_item_score, neg_item_score) 

137 opt_loss = loss + self.reg_loss() 

138 

139 return opt_loss 

140 

141 def predict(self, interaction): 

142 user = interaction[self.USER_ID] 

143 item = interaction[self.ITEM_ID] 

144 return self.forward(user, item)