Coverage for hopwise/model/knowledge_graph_embedding_recommender/conve.py: 80%

116 statements  

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

1# @Time : 2024/11/22 

2# @Author : Alessandro Soccol 

3# @Email : alessandro.soccol@unica.it 

4 

5"""ConvE 

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

7Reference: 

8 Dettmers et al. "Convolutional 2D Knowledge Graph Embeddings." in AAAI 2018. 

9 

10Reference code: 

11 https://github.com/TimDettmers/ConvE 

12""" 

13 

14import torch 

15import torch.nn.functional as F 

16from torch import nn 

17 

18from hopwise.model.abstract_recommender import KnowledgeRecommender 

19from hopwise.model.init import xavier_normal_initialization 

20from hopwise.utils import InputType 

21 

22 

23class ConvE(KnowledgeRecommender): 

24 r"""ConvE represent h,r,t in a subset of real number in d dimension. When scoring them, 

25 it concatenates and reshape h and r into a unique input [h;r]. This input is passed through 

26 a convolutional layers with a set of k filters and then through a dense layer with d neurons 

27 and a set of weight W. The output is finally combined with the tail embedding t 

28 using the dot product to produce the final score. 

29 

30 Note: 

31 In this version, we sample recommender data and knowledge data separately, and put them together for training. 

32 """ 

33 

34 input_type = InputType.PAIRWISE 

35 

36 def __init__(self, config, dataset): 

37 super().__init__(config, dataset) 

38 

39 # Load parameters info 

40 self.embedding_size = config["embedding_size"] 

41 self.device = config["device"] 

42 self.label_smoothing = config["label_smoothing"] 

43 self.input_dropout = config["input_dropout"] 

44 self.hidden_dropout = config["hidden_dropout"] 

45 self.feature_dropout = config["feature_dropout"] 

46 self.embedding_dim1 = config["embedding_shape"] 

47 self.embedding_dim2 = self.embedding_size // self.embedding_dim1 

48 self.hidden_size = config["hidden_size"] 

49 self.use_bias = config["use_bias"] 

50 self.ui_relation = dataset.field2token_id["relation_id"][dataset.ui_relation] 

51 

52 # Embeddings 

53 self.user_embedding = nn.Embedding(self.n_users + self.n_items, self.embedding_size, padding_idx=0) 

54 self.entity_embedding = nn.Embedding(self.n_entities, self.embedding_size, padding_idx=0) 

55 

56 self.relations_embeddings = nn.Embedding(self.n_relations, self.embedding_size, padding_idx=0) 

57 

58 # Layers 

59 self.inp_drop = torch.nn.Dropout(self.input_dropout) 

60 self.hidden_drop = torch.nn.Dropout(self.hidden_dropout) 

61 self.feature_map_drop = torch.nn.Dropout2d(self.feature_dropout) 

62 self.conv1 = torch.nn.Conv2d(1, 32, (3, 3), 1, 0, bias=self.use_bias) 

63 self.bn0 = torch.nn.BatchNorm2d(1) 

64 self.bn1 = torch.nn.BatchNorm2d(32) 

65 self.bn2 = torch.nn.BatchNorm1d(self.embedding_size) 

66 self.register_parameter("b_users", nn.Parameter(torch.zeros(self.n_users + self.n_items))) 

67 self.register_parameter("b_entities", nn.Parameter(torch.zeros(self.n_entities))) 

68 self.fc = torch.nn.Linear(self.hidden_size, self.embedding_size) 

69 

70 # Loss 

71 self.loss = nn.BCELoss() 

72 

73 # Parameters initialization 

74 self.apply(xavier_normal_initialization) 

75 

76 def forward(self, head, relation, embeddings, bias): 

77 stacked_inputs = torch.cat([head, relation], 2) 

78 stacked_inputs = self.bn0(stacked_inputs) 

79 x = self.inp_drop(stacked_inputs) 

80 x = self.conv1(x) 

81 x = self.bn1(x) 

82 x = F.relu(x) 

83 x = self.feature_map_drop(x) 

84 x = x.view(x.shape[0], -1) 

85 x = self.fc(x) 

86 x = self.hidden_drop(x) 

87 x = self.bn2(x) 

88 x = F.relu(x) 

89 x = torch.mm(x, embeddings.weight.transpose(1, 0)) 

90 x += bias.expand_as(x) 

91 pred = torch.sigmoid(x) 

92 return pred 

93 

94 def _get_rec_embeddings(self, user): 

95 relation_users = torch.tensor([self.ui_relation] * user.shape[0], device=self.device) 

96 

97 head_embeddings = self.user_embedding(user).view(-1, 1, self.embedding_dim1, self.embedding_dim2) 

98 relation_embeddings = self.relations_embeddings(relation_users).view( 

99 -1, 1, self.embedding_dim1, self.embedding_dim2 

100 ) 

101 

102 return head_embeddings, relation_embeddings 

103 

104 def _get_kg_embeddings(self, head, relation): 

105 head_embeddings = self.entity_embedding(head).view(-1, 1, self.embedding_dim1, self.embedding_dim2) 

106 relation_embeddings = self.relations_embeddings(relation).view(-1, 1, self.embedding_dim1, self.embedding_dim2) 

107 

108 return head_embeddings, relation_embeddings 

109 

110 def calculate_loss(self, interaction): 

111 user = interaction[self.USER_ID] 

112 

113 item = interaction[self.ITEM_ID] 

114 

115 head = interaction[self.HEAD_ENTITY_ID] 

116 

117 relation = interaction[self.RELATION_ID] 

118 

119 tail = interaction[self.TAIL_ENTITY_ID] 

120 

121 user_e, rec_r_e = self._get_rec_embeddings(user) 

122 head_e, kg_r_e = self._get_kg_embeddings(head, relation) 

123 

124 score_users = self.forward(user_e, rec_r_e, self.user_embedding, self.b_users) 

125 score_kg = self.forward(head_e, kg_r_e, self.entity_embedding, self.b_entities) 

126 

127 items = torch.zeros((item.size(0), self.n_users + self.n_items), device=self.device) 

128 items[:, item + self.n_users] = 1.0 

129 

130 tails = torch.zeros((tail.size(0), self.n_entities), device=self.device) 

131 tails[:, tail] = 1.0 

132 

133 if self.label_smoothing: 

134 items = ((1.0 - self.label_smoothing) * items) + (1.0 / self.n_items) 

135 tails = ((1.0 - self.label_smoothing) * tails) + (1.0 / self.n_entities) 

136 

137 rec_loss = self.loss(score_users, items) 

138 kg_loss = self.loss(score_kg, tails) 

139 

140 return rec_loss + kg_loss 

141 

142 def predict(self, interaction): 

143 user = interaction[self.USER_ID] 

144 item = interaction[self.ITEM_ID] 

145 relation = torch.tensor([self.ui_relation] * user.shape[0], device=self.device) 

146 

147 users_embedding = self.user_embedding(user).view(-1, 1, self.embedding_dim1, self.embedding_dim2) 

148 relation_embeddings = self.relations_embeddings(relation).view(-1, 1, self.embedding_dim1, self.embedding_dim2) 

149 

150 score = self.forward(users_embedding, relation_embeddings, self.user_embedding, self.b_users) 

151 

152 score = score[:, self.n_users :] 

153 score = score[torch.arange(user.size(0)), item] 

154 return score 

155 

156 def predict_kg(self, interaction): 

157 head = interaction[self.HEAD_ENTITY_ID] 

158 relation = interaction[self.RELATION_ID] 

159 tail = interaction[self.TAIL_ENTITY_ID] 

160 

161 head_embeddings = self.entity_embedding(head).view(-1, 1, self.embedding_dim1, self.embedding_dim2) 

162 relation_embeddings = self.relations_embeddings(relation).view(-1, 1, self.embedding_dim1, self.embedding_dim2) 

163 

164 score = self.forward(head_embeddings, relation_embeddings, self.entity_embedding, self.b_entities) 

165 

166 score = score[:, tail] 

167 return score 

168 

169 def full_sort_predict(self, interaction): 

170 user = interaction[self.USER_ID] 

171 relation = torch.tensor([self.ui_relation] * user.shape[0], device=self.device) 

172 

173 users_embedding = self.user_embedding(user).view(-1, 1, self.embedding_dim1, self.embedding_dim2) 

174 

175 relation_embeddings = self.relations_embeddings(relation).view(-1, 1, self.embedding_dim1, self.embedding_dim2) 

176 

177 score = self.forward(users_embedding, relation_embeddings, self.user_embedding, self.b_users) 

178 score = score[:, self.n_users :] 

179 return score 

180 

181 def full_sort_predict_kg(self, interaction): 

182 head = interaction[self.HEAD_ENTITY_ID] 

183 relation = interaction[self.RELATION_ID] 

184 

185 head_embeddings = self.entities_embeddings(head).view(-1, 1, self.embedding_dim1, self.embedding_dim2) 

186 relation_embeddings = self.relations_embeddings(relation).view(-1, 1, self.embedding_dim1, self.embedding_dim2) 

187 

188 score = self.forward(head_embeddings, relation_embeddings, self.entities_embeddings, self.b_entities) 

189 

190 return score