Coverage for hopwise/model/knowledge_graph_embedding_recommender/tucker.py: 83%
96 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 : 2024/11/21
2# @Author : Alessandro Soccol
3# @Email : alessandro.soccol@unica.it
5"""TuckER
6##################################################
7Reference:
8 Balažević et al. "TuckER: Tensor Factorization for Knowledge Graph Completion." in EMNLP/IJCNLP 2019.
10Reference code:
11 https://github.com/ibalazevic/TuckER
12"""
14import torch
15from torch import nn
17from hopwise.model.abstract_recommender import KnowledgeRecommender
18from hopwise.model.init import xavier_normal_initialization
19from hopwise.utils import InputType
22class TuckER(KnowledgeRecommender):
23 r"""TuckER relies on Tucker Decomposition. It handles entity and relation embeddings of independent dimension
24 and jointly learns a share core W.
26 Note:
27 In this version, we sample recommender data and knowledge data separately, and put them together for training.
28 """
30 input_type = InputType.PAIRWISE
32 def __init__(self, config, dataset):
33 super().__init__(config, dataset)
35 # load parameters info
36 self.embedding_size = config["embedding_size"]
37 self.device = config["device"]
38 self.ui_relation = dataset.field2token_id["relation_id"][dataset.ui_relation]
39 self.label_smoothing = config["label_smoothing"]
40 self.input_dropout = config["input_dropout"]
41 self.input_dropout1 = config["input_dropout1"]
42 self.input_dropout2 = config["input_dropout2"]
44 # define layers and loss
45 self.user_embedding = nn.Embedding(self.n_users + self.n_items, self.embedding_size)
46 self.entity_embedding = nn.Embedding(self.n_entities, self.embedding_size)
48 self.relation_embedding = nn.Embedding(self.n_relations, self.embedding_size)
50 self.weights = torch.nn.Parameter(
51 torch.tensor(
52 torch.empty(self.embedding_size, self.embedding_size, self.embedding_size).uniform_(-1, 1),
53 requires_grad=True,
54 )
55 )
57 self.input_dropout = torch.nn.Dropout(self.input_dropout)
58 self.hidden_dropout1 = torch.nn.Dropout(self.input_dropout1)
59 self.hidden_dropout2 = torch.nn.Dropout(self.input_dropout2)
60 self.bn0 = torch.nn.BatchNorm1d(self.embedding_size)
61 self.bn1 = torch.nn.BatchNorm1d(self.embedding_size)
63 # Loss
64 self.loss = nn.BCELoss()
66 # Parameters initialization
67 self.apply(xavier_normal_initialization)
69 def forward(self, h, r, embeddings):
70 x = self.bn0(h)
71 x = self.input_dropout(x)
72 x = x.view(-1, 1, h.size(1))
74 w_mat = torch.mm(r, self.weights.view(r.size(1), -1))
75 w_mat = w_mat.view(-1, h.size(1), h.size(1))
76 w_mat = self.hidden_dropout1(w_mat)
78 x = torch.bmm(x, w_mat)
79 x = x.view(-1, h.size(1))
80 x = self.bn1(x)
81 x = self.hidden_dropout2(x)
82 x = torch.mm(x, embeddings.weight.transpose(1, 0))
83 pred = torch.sigmoid(x)
84 return pred
86 def _get_rec_embeddings(self, user):
87 relation_users = torch.tensor([self.ui_relation] * user.shape[0], device=self.device)
88 user_e = self.user_embedding(user)
89 r_e = self.relation_embedding(relation_users)
90 return user_e, r_e
92 def _get_kg_embeddings(self, h, r):
93 h = self.entity_embedding(h)
94 r = self.relation_embedding(r)
95 return h, r
97 def calculate_loss(self, interaction):
98 user = interaction[self.USER_ID]
100 item = interaction[self.ITEM_ID]
102 head = interaction[self.HEAD_ENTITY_ID]
104 relation = interaction[self.RELATION_ID]
106 tail = interaction[self.TAIL_ENTITY_ID]
108 head_e, relation_e = self._get_kg_embeddings(head, relation)
109 user_e, rec_r_e = self._get_rec_embeddings(user)
111 item_new = torch.zeros((item.size(0), self.n_users + self.n_items), device=self.device)
112 item_new[:, item + self.n_users] = 1.0
114 tail_new = torch.zeros((tail.size(0), self.n_entities), device=self.device)
115 tail_new[:, tail] = 1.0
117 if self.label_smoothing:
118 item_new = ((1.0 - self.label_smoothing) * item_new) + (1.0 / self.n_items)
119 tail_new = ((1.0 - self.label_smoothing) * tail_new) + (1.0 / self.n_entities)
121 score_users = self.forward(user_e, rec_r_e, self.user_embedding)
122 score_kg = self.forward(head_e, relation_e, self.entity_embedding)
124 loss_rec = self.loss(score_users, item_new)
125 loss_kg = self.loss(score_kg, tail_new)
127 return loss_rec + loss_kg
129 def predict(self, interaction):
130 user = interaction[self.USER_ID]
131 item = interaction[self.ITEM_ID]
133 relation_users = torch.tensor([self.ui_relation] * user.shape[0], device=self.device)
134 user_e = self.user_embedding(user)
135 r_e = self.relation_embedding(relation_users)
137 score = self.forward(user_e, r_e, self.user_embedding)
139 score = score[torch.arange(user.size(0)), item]
140 return score
142 def predict_kg(self, interaction):
143 head = interaction[self.HEAD_ENTITY_ID]
144 relation = interaction[self.RELATION_ID]
145 tail = interaction[self.TAIL_ENTITY_ID]
147 head_e = self.entity_embedding(head)
148 r_e = self.relation_embedding(relation)
150 score = self.forward(head_e, r_e, self.entity_embedding)
151 score = score[torch.arange(head.size(0)), tail]
152 return score
154 def full_sort_predict(self, interaction):
155 user = interaction[self.USER_ID]
156 relation_users = torch.tensor([self.ui_relation] * user.shape[0], device=self.device)
157 user_e = self.user_embedding(user)
158 r_e = self.relation_embedding(relation_users)
160 score = self.forward(user_e, r_e, self.user_embedding)
161 score = score[:, self.n_users :]
162 return score
164 # def full_sort_predict_kg(self, interaction):
165 # head = interaction[self.HEAD_ENTITY_ID]
166 # relation = interaction[self.RELATION_ID]
168 # head_e = self.entity_embedding(head)
169 # r_e = self.relation_embedding(relation)
171 # score = self.forward(head_e, r_e, self.entity_embedding)
172 # return score