Coverage for hopwise/model/knowledge_aware_recommender/usermkr.py: 0%

122 statements  

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

1# @Time : 2020/10/08 

2# @Author : Xinyan Fan 

3# @Email : xinyan.fan@ruc.edu.cn 

4 

5r""" 

6MKR 

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

8Reference: 

9 Hongwei Wang et al. "Multi-Task Feature Learning for Knowledge Graph Enhanced Recommendation." in WWW 2019. 

10 

11Reference code: 

12 https://github.com/hsientzucheng/MKR.PyTorch 

13""" 

14 

15import torch 

16from torch import nn 

17 

18from hopwise.model.abstract_recommender import KnowledgeRecommender 

19from hopwise.model.init import xavier_normal_initialization 

20from hopwise.model.layers import MLPLayers 

21from hopwise.utils import InputType 

22 

23 

24class UserMKR(KnowledgeRecommender): 

25 r"""MKR is a Multi-task feature learning approach for Knowledge graph enhanced Recommendation. It is a deep 

26 end-to-end framework that utilizes knowledge graph embedding task to assist recommendation task. The two 

27 tasks are associated by cross&compress units, which automatically share latent features and learn high-order 

28 interactions between items in recommender systems and entities in the knowledge graph. 

29 """ 

30 

31 input_type = InputType.POINTWISE 

32 

33 def __init__(self, config, dataset): 

34 super().__init__(config, dataset) 

35 

36 # load parameters info 

37 self.LABEL = config["LABEL_FIELD"] 

38 self.embedding_size = config["embedding_size"] 

39 self.kg_embedding_size = config["kg_embedding_size"] 

40 self.L = config["low_layers_num"] # the number of low layers 

41 self.H = config["high_layers_num"] # the number of high layers 

42 self.reg_weight = config["reg_weight"] 

43 self.use_inner_product = config["use_inner_product"] 

44 self.dropout_prob = config["dropout_prob"] 

45 

46 # init embeddings 

47 self.user_embeddings_lookup = nn.Embedding(self.n_entities, self.embedding_size) 

48 self.item_embeddings_lookup = nn.Embedding(self.n_entities, self.embedding_size) 

49 self.entity_embeddings_lookup = nn.Embedding(self.n_entities, self.embedding_size) 

50 self.relation_embeddings_lookup = nn.Embedding(self.n_relations, self.embedding_size) 

51 

52 # define layers 

53 lower_mlp_layers = [] 

54 high_mlp_layers = [] 

55 for i in range(self.L + 1): 

56 lower_mlp_layers.append(self.embedding_size) 

57 for i in range(self.H): 

58 high_mlp_layers.append(self.embedding_size * 3) 

59 

60 self.user_mlp = MLPLayers(lower_mlp_layers, self.dropout_prob, "sigmoid") 

61 self.tail_mlp = MLPLayers(lower_mlp_layers, self.dropout_prob, "sigmoid") 

62 self.user_cc_unit = nn.Sequential() 

63 for i_cnt in range(self.L): 

64 self.user_cc_unit.add_module(f"user_cc_unit{i_cnt}", CrossCompressUnit(self.embedding_size)) 

65 self.item_cc_unit = nn.Sequential() 

66 for i_cnt in range(self.L): 

67 self.item_cc_unit.add_module(f"item_cc_unit{i_cnt}", CrossCompressUnit(self.embedding_size)) 

68 self.kge_mlp = MLPLayers(high_mlp_layers, self.dropout_prob, "sigmoid") 

69 self.kge_pred_mlp = MLPLayers([self.embedding_size * 3, self.embedding_size], self.dropout_prob, "sigmoid") 

70 if not self.use_inner_product: 

71 self.rs_pred_mlp = MLPLayers([self.embedding_size * 2, 1], self.dropout_prob, "sigmoid") 

72 self.rs_mlp = MLPLayers(high_mlp_layers, self.dropout_prob, "sigmoid") 

73 

74 # loss 

75 self.sigmoid_BCE = nn.BCEWithLogitsLoss() 

76 

77 # parameters initialization 

78 self.apply(xavier_normal_initialization) 

79 

80 def forward( 

81 self, 

82 user_indices=None, 

83 item_indices=None, 

84 head_indices=None, 

85 relation_indices=None, 

86 tail_indices=None, 

87 task="rs", 

88 ): 

89 self.user_embeddings = self.user_embeddings_lookup(user_indices) 

90 self.item_embeddings = self.item_embeddings_lookup(item_indices) 

91 head_embeddings = self.entity_embeddings_lookup(head_indices) 

92 self.item_embeddings, item_head_embeddings = self.item_cc_unit( 

93 [self.item_embeddings, head_embeddings] 

94 ) # calculate feature interactions between items and entities 

95 self.user_embeddings, user_head_embeddings = self.user_cc_unit( 

96 [self.user_embeddings, head_embeddings] 

97 ) # calculate feature interactions between items and entities 

98 

99 self.head_embeddings = torch.cat([item_head_embeddings, user_head_embeddings], 1) 

100 

101 if task == "rs": 

102 # RS 

103 self.user_embeddings = self.user_mlp(self.user_embeddings) 

104 

105 if self.use_inner_product: # get scores by inner product. 

106 self.scores = torch.sum(self.user_embeddings * self.item_embeddings, 1) # [batch_size] 

107 else: # get scores by mlp layers 

108 self.user_item_concat = torch.cat( 

109 [self.user_embeddings, self.item_embeddings], 1 

110 ) # [batch_size, emb_dim*2] 

111 self.user_item_concat = self.rs_mlp(self.user_item_concat) 

112 

113 self.scores = torch.squeeze(self.rs_pred_mlp(self.user_item_concat)) # [batch_size] 

114 self.scores_normalized = torch.sigmoid(self.scores) 

115 outputs = [ 

116 self.user_embeddings, 

117 self.item_embeddings, 

118 self.scores, 

119 self.scores_normalized, 

120 ] 

121 

122 if relation_indices is not None and task == "kge": 

123 # KGE 

124 self.tail_embeddings = self.entity_embeddings_lookup(tail_indices) 

125 self.relation_embeddings = self.relation_embeddings_lookup(relation_indices) 

126 self.tail_embeddings = self.tail_mlp(self.tail_embeddings) 

127 

128 self.head_relation_concat = torch.cat( 

129 [self.head_embeddings, self.relation_embeddings], 1 

130 ) # [batch_size, emb_dim*2] 

131 self.head_relation_concat = self.kge_mlp(self.head_relation_concat) 

132 

133 self.tail_pred = self.kge_pred_mlp(self.head_relation_concat) # [batch_size, 1] 

134 self.tail_pred = torch.sigmoid(self.tail_pred) 

135 self.scores_kge = torch.sigmoid(torch.sum(self.tail_embeddings * self.tail_pred, 1)) 

136 self.rmse = torch.mean( 

137 torch.sqrt(torch.sum(torch.pow(self.tail_embeddings - self.tail_pred, 2), 1) / self.embedding_size) 

138 ) 

139 outputs = [ 

140 self.head_embeddings, 

141 self.tail_embeddings, 

142 self.scores_kge, 

143 self.rmse, 

144 ] 

145 

146 return outputs 

147 

148 def _l2_loss(self, inputs): 

149 return torch.sum(inputs**2) / 2 

150 

151 def calculate_rs_loss(self, interaction): 

152 r"""Calculate the training loss for a batch data of RS.""" 

153 # inputs 

154 self.user_indices = interaction[self.USER_ID] 

155 self.item_indices = interaction[self.ITEM_ID] 

156 self.head_indices = interaction[self.ITEM_ID] 

157 self.labels = interaction[self.LABEL] 

158 # RS model 

159 user_embeddings, item_embeddings, scores, scores_normalized = self.forward( 

160 user_indices=self.user_indices, 

161 item_indices=self.item_indices + self.n_users, 

162 head_indices=self.head_indices + self.n_users, 

163 relation_indices=None, 

164 tail_indices=None, 

165 task="rs", 

166 ) 

167 # loss 

168 base_loss_rs = torch.mean(self.sigmoid_BCE(scores, self.labels)) 

169 l2_loss_rs = self._l2_loss(user_embeddings) + self._l2_loss(item_embeddings) 

170 loss_rs = base_loss_rs + l2_loss_rs * self.reg_weight 

171 

172 return loss_rs 

173 

174 def calculate_kg_loss(self, interaction): 

175 r"""Calculate the training loss for a batch data of KG.""" 

176 # inputs 

177 self.user_indices = interaction[self.HEAD_ENTITY_ID] 

178 self.item_indices = interaction[self.HEAD_ENTITY_ID] 

179 self.head_indices = interaction[self.HEAD_ENTITY_ID] 

180 self.relation_indices = interaction[self.RELATION_ID] 

181 self.tail_indices = interaction[self.TAIL_ENTITY_ID] 

182 # KGE model 

183 head_embeddings, tail_embeddings, scores_kge, rmse = self.forward( 

184 user_indices=self.user_indices, 

185 item_indices=self.item_indices, 

186 head_indices=self.head_indices, 

187 relation_indices=self.relation_indices, 

188 tail_indices=self.tail_indices, 

189 task="kge", 

190 ) 

191 # loss 

192 base_loss_kge = -scores_kge 

193 l2_loss_kge = self._l2_loss(head_embeddings) + self._l2_loss(tail_embeddings) 

194 loss_kge = base_loss_kge + l2_loss_kge * self.reg_weight 

195 

196 return loss_kge.sum() 

197 

198 def predict(self, interaction): 

199 user = interaction[self.USER_ID] 

200 item = interaction[self.ITEM_ID] 

201 head = interaction[self.ITEM_ID] 

202 

203 outputs = self.forward(user, item + self.n_users, head + self.n_users, task="rs") 

204 _, _, scores, _ = outputs 

205 

206 return scores 

207 

208 

209class CrossCompressUnit(nn.Module): 

210 r"""This is Cross&Compress Unit for MKR model to model feature interactions between items and entities.""" 

211 

212 def __init__(self, dim): 

213 super().__init__() 

214 self.dim = dim 

215 self.fc_vv = nn.Linear(dim, 1, bias=True) 

216 self.fc_ev = nn.Linear(dim, 1, bias=True) 

217 self.fc_ve = nn.Linear(dim, 1, bias=True) 

218 self.fc_ee = nn.Linear(dim, 1, bias=True) 

219 

220 def forward(self, inputs): 

221 v, e = inputs 

222 # [batch_size, dim, 1], [batch_size, 1, dim] 

223 v = torch.unsqueeze(v, 2) 

224 e = torch.unsqueeze(e, 1) 

225 # [batch_size, dim, dim] 

226 c_matrix = torch.matmul(v, e) 

227 c_matrix_transpose = c_matrix.permute(0, 2, 1) 

228 # [batch_size * dim, dim] 

229 c_matrix = c_matrix.view(-1, self.dim) 

230 c_matrix_transpose = c_matrix_transpose.contiguous().view(-1, self.dim) 

231 # [batch_size, dim] 

232 v_intermediate = self.fc_vv(c_matrix) + self.fc_ev(c_matrix_transpose) 

233 e_intermediate = self.fc_ve(c_matrix) + self.fc_ee(c_matrix_transpose) 

234 v_output = v_intermediate.view(-1, self.dim) 

235 e_output = e_intermediate.view(-1, self.dim) 

236 

237 return v_output, e_output