FranKGraphBench 0.0.1a0__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- FranKGraphBench-0.0.1a0.dist-info/LICENSE +201 -0
- FranKGraphBench-0.0.1a0.dist-info/METADATA +189 -0
- FranKGraphBench-0.0.1a0.dist-info/RECORD +70 -0
- FranKGraphBench-0.0.1a0.dist-info/WHEEL +6 -0
- FranKGraphBench-0.0.1a0.dist-info/entry_points.txt +3 -0
- FranKGraphBench-0.0.1a0.dist-info/top_level.txt +2 -0
- data_integration/__init__.py +47 -0
- data_integration/__main__.py +4 -0
- data_integration/dataset.py +270 -0
- data_integration/dataset2class.py +34 -0
- data_integration/datasets/__init__.py +0 -0
- data_integration/datasets/amazon.py +140 -0
- data_integration/datasets/book-crossing.py +98 -0
- data_integration/datasets/douban-movie.py +75 -0
- data_integration/datasets/lastfm.py +215 -0
- data_integration/datasets/movielens.py +375 -0
- data_integration/datasets/steam.py +98 -0
- data_integration/datasets/worker.py +31 -0
- data_integration/datasets/yelp.py +156 -0
- framework/__init__.py +17 -0
- framework/__main__.py +4 -0
- framework/dataloader/__init__.py +0 -0
- framework/dataloader/dataloader.py +52 -0
- framework/dataloader/dataset/__init__.py +0 -0
- framework/dataloader/dataset/dataset.py +38 -0
- framework/dataloader/edge_splitter/__init__.py +0 -0
- framework/dataloader/edge_splitter/edge_splitter.py +163 -0
- framework/dataloader/graph/__init__.py +0 -0
- framework/dataloader/graph/graph.py +314 -0
- framework/dataloader/graph/node.py +80 -0
- framework/dataloader/preprocess/__init__.py +0 -0
- framework/dataloader/preprocess/methods.py +47 -0
- framework/evaluator/__init__.py +0 -0
- framework/evaluator/metric.py +39 -0
- framework/evaluator/metric2class.py +10 -0
- framework/evaluator/metrics/__init__.py +0 -0
- framework/evaluator/metrics/map.py +33 -0
- framework/evaluator/metrics/ndcg.py +49 -0
- framework/experiment.py +120 -0
- framework/recommender/__init__.py +0 -0
- framework/recommender/model2class.py +46 -0
- framework/recommender/models/__init__.py +0 -0
- framework/recommender/models/complEx/__init__.py +0 -0
- framework/recommender/models/complEx/model.py +193 -0
- framework/recommender/models/deep_walk_based/__init__.py +0 -0
- framework/recommender/models/deep_walk_based/model.py +149 -0
- framework/recommender/models/distMult/__init__.py +0 -0
- framework/recommender/models/distMult/model.py +187 -0
- framework/recommender/models/ePHEN/__init__.py +0 -0
- framework/recommender/models/ePHEN/model.py +159 -0
- framework/recommender/models/rESCAL/__init__.py +0 -0
- framework/recommender/models/rESCAL/model.py +184 -0
- framework/recommender/models/rotatE/__init__.py +0 -0
- framework/recommender/models/rotatE/model.py +195 -0
- framework/recommender/models/transD/__init__.py +0 -0
- framework/recommender/models/transD/model.py +190 -0
- framework/recommender/models/transE/__init__.py +0 -0
- framework/recommender/models/transE/model.py +194 -0
- framework/recommender/models/transH/__init__.py +0 -0
- framework/recommender/models/transH/model.py +193 -0
- framework/recommender/models/transR/__init__.py +0 -0
- framework/recommender/models/transR/model.py +190 -0
- framework/recommender/models/tuckER/__init__.py +0 -0
- framework/recommender/models/tuckER/model.py +199 -0
- framework/recommender/recommender.py +32 -0
- framework/recommender/utils/__init__.py +0 -0
- framework/recommender/utils/walker.py +185 -0
- framework/reporter/__init__.py +0 -0
- framework/reporter/report.py +101 -0
- framework/utils.py +10 -0
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
from framework.dataloader.graph.node import UserNode, ItemNode
|
|
2
|
+
from ...recommender import Recommender
|
|
3
|
+
|
|
4
|
+
from pykeen.pipeline import pipeline
|
|
5
|
+
from pykeen.triples import TriplesFactory
|
|
6
|
+
from pykeen.datasets import Dataset
|
|
7
|
+
from sklearn.neighbors import NearestNeighbors
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
import numpy as np
|
|
11
|
+
import torch
|
|
12
|
+
import typing as t
|
|
13
|
+
|
|
14
|
+
"""
|
|
15
|
+
Recommender base class
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class RotatE(Recommender):
|
|
20
|
+
def __init__(
|
|
21
|
+
self,
|
|
22
|
+
config: dict,
|
|
23
|
+
embedding_dim: int = 200,
|
|
24
|
+
entity_initializer=None,
|
|
25
|
+
relation_initializer=None,
|
|
26
|
+
relation_constrainer=None,
|
|
27
|
+
regularizer=None,
|
|
28
|
+
regularizer_kwargs=None,
|
|
29
|
+
epochs: int = 5,
|
|
30
|
+
seed: int = 42,
|
|
31
|
+
evaluation_fallback: bool = True,
|
|
32
|
+
all_recs: bool = False,
|
|
33
|
+
triples: str = "all",
|
|
34
|
+
):
|
|
35
|
+
super().__init__(config)
|
|
36
|
+
self.embedding_dim = embedding_dim
|
|
37
|
+
self.entity_initializer = entity_initializer
|
|
38
|
+
self.relation_initializer = relation_initializer
|
|
39
|
+
self.relation_constrainer = relation_constrainer
|
|
40
|
+
self.regularizer = regularizer
|
|
41
|
+
self.regularizer_kwargs = regularizer_kwargs
|
|
42
|
+
self.epochs = epochs
|
|
43
|
+
self.seed = seed
|
|
44
|
+
self.evaluation_fallback = evaluation_fallback
|
|
45
|
+
self.all_recs = all_recs
|
|
46
|
+
self.triples = triples
|
|
47
|
+
self._triples = None
|
|
48
|
+
self._embedding = {}
|
|
49
|
+
|
|
50
|
+
def name(self):
|
|
51
|
+
text = "RotatE based model + cosine similarity"
|
|
52
|
+
text += f";embedding_dim={self.embedding_dim};entity_initializer={self.entity_initializer};relation_initializer={self.relation_initializer};relation_constrainer={self.relation_constrainer};regularizer={self.regularizer};epochs={self.epochs};seed={self.seed};triples={self.triples}"
|
|
53
|
+
return text
|
|
54
|
+
|
|
55
|
+
def train(self, G_train, ratings_train):
|
|
56
|
+
self.G_train = G_train
|
|
57
|
+
if self.triples == "all":
|
|
58
|
+
self._triples = G_train.get_all_triples()
|
|
59
|
+
elif self.triples == "ratings":
|
|
60
|
+
self._triples = G_train.get_ratings_triples()
|
|
61
|
+
self.fit()
|
|
62
|
+
|
|
63
|
+
def get_recommendations(self, k: int = 5) -> t.Dict[UserNode, t.List[ItemNode]]:
|
|
64
|
+
"""
|
|
65
|
+
:param k: cutoff recommendation (int)
|
|
66
|
+
:return dict of recommendations for each user
|
|
67
|
+
of type {user1: [item1, item2]}
|
|
68
|
+
"""
|
|
69
|
+
users = list(self.G_train.get_user_nodes())
|
|
70
|
+
items = list(self.G_train.get_item_nodes())
|
|
71
|
+
|
|
72
|
+
users_embeddings = np.array([self._embedding[user] for user in users])
|
|
73
|
+
items_embeddings = np.array([self._embedding[item] for item in items])
|
|
74
|
+
|
|
75
|
+
n_neighbors = self._get_n_neighbors(users, items, k)
|
|
76
|
+
knn = NearestNeighbors(n_neighbors=n_neighbors, metric="cosine")
|
|
77
|
+
knn.fit(items_embeddings)
|
|
78
|
+
rec_indices = knn.kneighbors(users_embeddings, return_distance=False)
|
|
79
|
+
|
|
80
|
+
recommendations = {}
|
|
81
|
+
for user_idx, user in enumerate(users):
|
|
82
|
+
rated_items = self.G_train.get_user_rated_items(user)
|
|
83
|
+
recs = []
|
|
84
|
+
for item_idx in rec_indices[user_idx]:
|
|
85
|
+
item = items[item_idx]
|
|
86
|
+
if item not in rated_items:
|
|
87
|
+
recs.append(item)
|
|
88
|
+
if not self.all_recs and len(recs) == k:
|
|
89
|
+
break
|
|
90
|
+
|
|
91
|
+
recommendations[user] = recs
|
|
92
|
+
|
|
93
|
+
return recommendations
|
|
94
|
+
|
|
95
|
+
def get_user_recommendation(self, user: UserNode, k: int = 5):
|
|
96
|
+
raise NotImplementedError(
|
|
97
|
+
"Override get_user_recommendation() for your model subclass."
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
def fit(self):
|
|
101
|
+
triples = TriplesFactory.from_labeled_triples(
|
|
102
|
+
self._triples[["head", "relation", "tail"]].values,
|
|
103
|
+
create_inverse_triples=False,
|
|
104
|
+
entity_to_id=None,
|
|
105
|
+
relation_to_id=None,
|
|
106
|
+
compact_id=False,
|
|
107
|
+
filter_out_candidate_inverse_relations=True,
|
|
108
|
+
metadata=None,
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
dataset = Dataset.from_tf(triples, ratios=[0.95, 0.05, 0.0])
|
|
112
|
+
entity_to_id = dataset.entity_to_id
|
|
113
|
+
|
|
114
|
+
result = pipeline(
|
|
115
|
+
dataset=dataset,
|
|
116
|
+
model="RotatE",
|
|
117
|
+
model_kwargs=dict(
|
|
118
|
+
embedding_dim=self.embedding_dim,
|
|
119
|
+
entity_initializer=self.entity_initializer,
|
|
120
|
+
relation_initializer=self.relation_initializer,
|
|
121
|
+
relation_constrainer=self.relation_constrainer,
|
|
122
|
+
regularizer=self.regularizer,
|
|
123
|
+
regularizer_kwargs=self.regularizer_kwargs,
|
|
124
|
+
),
|
|
125
|
+
epochs = self.epochs,
|
|
126
|
+
random_seed = self.seed,
|
|
127
|
+
evaluation_fallback = self.evaluation_fallback,
|
|
128
|
+
)
|
|
129
|
+
model = result.model
|
|
130
|
+
|
|
131
|
+
users_iter = self.G_train.get_user_nodes()
|
|
132
|
+
items_iter = self.G_train.get_item_nodes()
|
|
133
|
+
|
|
134
|
+
users = list(users_iter)
|
|
135
|
+
items = list(items_iter)
|
|
136
|
+
|
|
137
|
+
# deal with entities that might have not been embedded
|
|
138
|
+
users_indices, users_no_embedding = [], []
|
|
139
|
+
for user in users_iter:
|
|
140
|
+
try:
|
|
141
|
+
users_indices.append(entity_to_id[user.__str__()])
|
|
142
|
+
except:
|
|
143
|
+
users_no_embedding.append(user)
|
|
144
|
+
users.remove(user)
|
|
145
|
+
continue
|
|
146
|
+
users_indices = torch.LongTensor(users_indices)
|
|
147
|
+
|
|
148
|
+
items_indices, items_no_embedding = [], []
|
|
149
|
+
for item in items_iter:
|
|
150
|
+
try:
|
|
151
|
+
items_indices.append(entity_to_id[item.__str__()])
|
|
152
|
+
except:
|
|
153
|
+
items_no_embedding.append(item)
|
|
154
|
+
items.remove(item)
|
|
155
|
+
continue
|
|
156
|
+
items_indices = torch.LongTensor(items_indices)
|
|
157
|
+
|
|
158
|
+
users_embeddings = (model.entity_representations[0](indices=users_indices).detach().cpu().numpy())
|
|
159
|
+
items_embeddings = (model.entity_representations[0](indices=items_indices).detach().cpu().numpy())
|
|
160
|
+
|
|
161
|
+
concat_users_embeddings, concat_items_embeddings = np.zeros((len(users_embeddings), self.embedding_dim*2)), np.zeros((len(items_embeddings), self.embedding_dim*2))
|
|
162
|
+
|
|
163
|
+
# complex data not supported by sklearn
|
|
164
|
+
for index, ue in enumerate(users_embeddings):
|
|
165
|
+
concat_users_embeddings[index] = np.concatenate((np.real(ue), np.imag(ue)))
|
|
166
|
+
for index, ie in enumerate(items_embeddings):
|
|
167
|
+
concat_items_embeddings[index] = np.concatenate((np.real(ie), np.imag(ie)))
|
|
168
|
+
|
|
169
|
+
if len(users_no_embedding) > 0:
|
|
170
|
+
concat_users_embeddings = np.concatenate((concat_users_embeddings, np.zeros((len(users_no_embedding), self.embedding_dim*2), dtype=concat_users_embeddings.dtype)), axis=0)
|
|
171
|
+
users = users + users_no_embedding
|
|
172
|
+
|
|
173
|
+
if len(items_no_embedding) > 0:
|
|
174
|
+
concat_items_embeddings = np.concatenate((concat_items_embeddings, np.zeros((len(items_no_embedding), self.embedding_dim*2), dtype=concat_items_embeddings.dtype)), axis=0)
|
|
175
|
+
items = items + items_no_embedding
|
|
176
|
+
|
|
177
|
+
for index, user in enumerate(users):
|
|
178
|
+
self._embedding[user] = concat_users_embeddings[index]
|
|
179
|
+
|
|
180
|
+
for index, item in enumerate(items):
|
|
181
|
+
self._embedding[item] = concat_items_embeddings[index]
|
|
182
|
+
|
|
183
|
+
def _get_n_neighbors(self, users, items, top_k):
|
|
184
|
+
n_neighbors = 0
|
|
185
|
+
if self.all_recs:
|
|
186
|
+
n_neighbors = len(items)
|
|
187
|
+
else:
|
|
188
|
+
max_recs = 0
|
|
189
|
+
for user in users:
|
|
190
|
+
rated_items = self.G_train.get_user_rated_items(user)
|
|
191
|
+
if len(rated_items) > max_recs:
|
|
192
|
+
max_recs = len(rated_items)
|
|
193
|
+
n_neighbors = min(max_recs + top_k, len(items))
|
|
194
|
+
|
|
195
|
+
return n_neighbors
|
|
File without changes
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
from framework.dataloader.graph.node import UserNode, ItemNode
|
|
2
|
+
from ...recommender import Recommender
|
|
3
|
+
|
|
4
|
+
from pykeen.pipeline import pipeline
|
|
5
|
+
from pykeen.triples import TriplesFactory
|
|
6
|
+
from pykeen.datasets import Dataset
|
|
7
|
+
from sklearn.neighbors import NearestNeighbors
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
import numpy as np
|
|
11
|
+
import torch
|
|
12
|
+
import typing as t
|
|
13
|
+
|
|
14
|
+
"""
|
|
15
|
+
Recommender base class
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class TransD(Recommender):
|
|
20
|
+
def __init__(
|
|
21
|
+
self,
|
|
22
|
+
config: dict,
|
|
23
|
+
embedding_dim: int = 50,
|
|
24
|
+
relation_dim = None,
|
|
25
|
+
interaction_kwargs = None,
|
|
26
|
+
entity_initializer=None,
|
|
27
|
+
entity_constrainer=None,
|
|
28
|
+
relation_initializer=None,
|
|
29
|
+
relation_constrainer=None,
|
|
30
|
+
epochs: int = 5,
|
|
31
|
+
seed: int = 42,
|
|
32
|
+
evaluation_fallback: bool = True,
|
|
33
|
+
all_recs: bool = False,
|
|
34
|
+
triples: str = "all",
|
|
35
|
+
):
|
|
36
|
+
super().__init__(config)
|
|
37
|
+
self.embedding_dim = embedding_dim
|
|
38
|
+
self.relation_dim = relation_dim
|
|
39
|
+
self.interaction_kwargs = interaction_kwargs
|
|
40
|
+
self.entity_initializer = entity_initializer
|
|
41
|
+
self.entity_constrainer = entity_constrainer
|
|
42
|
+
self.relation_initializer = relation_initializer
|
|
43
|
+
self.relation_constrainer = relation_constrainer
|
|
44
|
+
self.epochs = epochs
|
|
45
|
+
self.seed = seed
|
|
46
|
+
self.evaluation_fallback = evaluation_fallback
|
|
47
|
+
self.all_recs = all_recs
|
|
48
|
+
self.triples = triples
|
|
49
|
+
self._triples = None
|
|
50
|
+
self._embedding = {}
|
|
51
|
+
|
|
52
|
+
def name(self):
|
|
53
|
+
text = "TransD based model + cosine similarity"
|
|
54
|
+
text += f";embedding_dim={self.embedding_dim};relation_dim={self.relation_dim};entity_initializer={self.entity_initializer};entity_constrainer={self.entity_constrainer};relation_initializer={self.relation_initializer};relation_constrainer={self.relation_constrainer};epochs={self.epochs};seed={self.seed};triples={self.triples}"
|
|
55
|
+
return text
|
|
56
|
+
|
|
57
|
+
def train(self, G_train, ratings_train):
|
|
58
|
+
self.G_train = G_train
|
|
59
|
+
if self.triples == "all":
|
|
60
|
+
self._triples = G_train.get_all_triples()
|
|
61
|
+
elif self.triples == "ratings":
|
|
62
|
+
self._triples = G_train.get_ratings_triples()
|
|
63
|
+
self.fit()
|
|
64
|
+
|
|
65
|
+
def get_recommendations(self, k: int = 5) -> t.Dict[UserNode, t.List[ItemNode]]:
|
|
66
|
+
"""
|
|
67
|
+
:param k: cutoff recommendation (int)
|
|
68
|
+
:return dict of recommendations for each user
|
|
69
|
+
of type {user1: [item1, item2]}
|
|
70
|
+
"""
|
|
71
|
+
users = list(self.G_train.get_user_nodes())
|
|
72
|
+
items = list(self.G_train.get_item_nodes())
|
|
73
|
+
|
|
74
|
+
users_embeddings = np.array([self._embedding[user] for user in users])
|
|
75
|
+
items_embeddings = np.array([self._embedding[item] for item in items])
|
|
76
|
+
|
|
77
|
+
n_neighbors = self._get_n_neighbors(users, items, k)
|
|
78
|
+
knn = NearestNeighbors(n_neighbors=n_neighbors, metric="cosine")
|
|
79
|
+
knn.fit(items_embeddings)
|
|
80
|
+
rec_indices = knn.kneighbors(users_embeddings, return_distance=False)
|
|
81
|
+
|
|
82
|
+
recommendations = {}
|
|
83
|
+
for user_idx, user in enumerate(users):
|
|
84
|
+
rated_items = self.G_train.get_user_rated_items(user)
|
|
85
|
+
recs = []
|
|
86
|
+
for item_idx in rec_indices[user_idx]:
|
|
87
|
+
item = items[item_idx]
|
|
88
|
+
if item not in rated_items:
|
|
89
|
+
recs.append(item)
|
|
90
|
+
if not self.all_recs and len(recs) == k:
|
|
91
|
+
break
|
|
92
|
+
|
|
93
|
+
recommendations[user] = recs
|
|
94
|
+
|
|
95
|
+
return recommendations
|
|
96
|
+
|
|
97
|
+
def get_user_recommendation(self, user: UserNode, k: int = 5):
|
|
98
|
+
raise NotImplementedError(
|
|
99
|
+
"Override get_user_recommendation() for your model subclass."
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
def fit(self):
|
|
103
|
+
triples = TriplesFactory.from_labeled_triples(
|
|
104
|
+
self._triples[["head", "relation", "tail"]].values,
|
|
105
|
+
create_inverse_triples=False,
|
|
106
|
+
entity_to_id=None,
|
|
107
|
+
relation_to_id=None,
|
|
108
|
+
compact_id=False,
|
|
109
|
+
filter_out_candidate_inverse_relations=True,
|
|
110
|
+
metadata=None,
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
dataset = Dataset.from_tf(triples, ratios=[0.95, 0.05, 0.0])
|
|
114
|
+
entity_to_id = dataset.entity_to_id
|
|
115
|
+
|
|
116
|
+
result = pipeline(
|
|
117
|
+
dataset=dataset,
|
|
118
|
+
model="TransD",
|
|
119
|
+
model_kwargs=dict(
|
|
120
|
+
embedding_dim=self.embedding_dim,
|
|
121
|
+
relation_dim=self.relation_dim,
|
|
122
|
+
interaction_kwargs=self.interaction_kwargs,
|
|
123
|
+
entity_initializer=self.entity_initializer,
|
|
124
|
+
entity_constrainer=self.entity_constrainer,
|
|
125
|
+
relation_initializer=self.relation_initializer,
|
|
126
|
+
relation_constrainer=self.relation_constrainer,
|
|
127
|
+
),
|
|
128
|
+
epochs = self.epochs,
|
|
129
|
+
random_seed = self.seed,
|
|
130
|
+
evaluation_fallback = self.evaluation_fallback,
|
|
131
|
+
)
|
|
132
|
+
model = result.model
|
|
133
|
+
|
|
134
|
+
users_iter = self.G_train.get_user_nodes()
|
|
135
|
+
items_iter = self.G_train.get_item_nodes()
|
|
136
|
+
|
|
137
|
+
users = list(users_iter)
|
|
138
|
+
items = list(items_iter)
|
|
139
|
+
|
|
140
|
+
# deal with entities that might have not been embedded
|
|
141
|
+
users_indices, users_no_embedding = [], []
|
|
142
|
+
for user in users_iter:
|
|
143
|
+
try:
|
|
144
|
+
users_indices.append(entity_to_id[user.__str__()])
|
|
145
|
+
except:
|
|
146
|
+
users_no_embedding.append(user)
|
|
147
|
+
users.remove(user)
|
|
148
|
+
continue
|
|
149
|
+
users_indices = torch.LongTensor(users_indices)
|
|
150
|
+
|
|
151
|
+
items_indices, items_no_embedding = [], []
|
|
152
|
+
for item in items_iter:
|
|
153
|
+
try:
|
|
154
|
+
items_indices.append(entity_to_id[item.__str__()])
|
|
155
|
+
except:
|
|
156
|
+
items_no_embedding.append(item)
|
|
157
|
+
items.remove(item)
|
|
158
|
+
continue
|
|
159
|
+
items_indices = torch.LongTensor(items_indices)
|
|
160
|
+
|
|
161
|
+
users_embeddings = (model.entity_representations[0](indices=users_indices).detach().cpu().numpy())
|
|
162
|
+
items_embeddings = (model.entity_representations[0](indices=items_indices).detach().cpu().numpy())
|
|
163
|
+
|
|
164
|
+
if len(users_no_embedding) > 0:
|
|
165
|
+
users_embeddings = np.concatenate((users_embeddings, np.zeros((len(users_no_embedding), self.embedding_dim), dtype=users_embeddings.dtype)), axis=0)
|
|
166
|
+
users = users + users_no_embedding
|
|
167
|
+
|
|
168
|
+
if len(items_no_embedding) > 0:
|
|
169
|
+
items_embeddings = np.concatenate((items_embeddings, np.zeros((len(items_no_embedding), self.embedding_dim), dtype=items_embeddings.dtype)), axis=0)
|
|
170
|
+
items = items + items_no_embedding
|
|
171
|
+
|
|
172
|
+
for index, user in enumerate(users):
|
|
173
|
+
self._embedding[user] = users_embeddings[index]
|
|
174
|
+
|
|
175
|
+
for index, item in enumerate(items):
|
|
176
|
+
self._embedding[item] = items_embeddings[index]
|
|
177
|
+
|
|
178
|
+
def _get_n_neighbors(self, users, items, top_k):
|
|
179
|
+
n_neighbors = 0
|
|
180
|
+
if self.all_recs:
|
|
181
|
+
n_neighbors = len(items)
|
|
182
|
+
else:
|
|
183
|
+
max_recs = 0
|
|
184
|
+
for user in users:
|
|
185
|
+
rated_items = self.G_train.get_user_rated_items(user)
|
|
186
|
+
if len(rated_items) > max_recs:
|
|
187
|
+
max_recs = len(rated_items)
|
|
188
|
+
n_neighbors = min(max_recs + top_k, len(items))
|
|
189
|
+
|
|
190
|
+
return n_neighbors
|
|
File without changes
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
from framework.dataloader.graph.node import UserNode, ItemNode
|
|
2
|
+
from ...recommender import Recommender
|
|
3
|
+
|
|
4
|
+
from pykeen.pipeline import pipeline
|
|
5
|
+
from pykeen.triples import TriplesFactory
|
|
6
|
+
from pykeen.datasets import Dataset
|
|
7
|
+
from sklearn.neighbors import NearestNeighbors
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
import numpy as np
|
|
11
|
+
import torch
|
|
12
|
+
import typing as t
|
|
13
|
+
|
|
14
|
+
"""
|
|
15
|
+
Recommender base class
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class TransE(Recommender):
|
|
20
|
+
def __init__(
|
|
21
|
+
self,
|
|
22
|
+
config: dict,
|
|
23
|
+
embedding_dim: int = 50,
|
|
24
|
+
scoring_fct_norm: int = 1,
|
|
25
|
+
entity_initializer=None,
|
|
26
|
+
entity_constrainer=None,
|
|
27
|
+
relation_initializer=None,
|
|
28
|
+
relation_constrainer=None,
|
|
29
|
+
regularizer=None,
|
|
30
|
+
regularizer_kwargs=None,
|
|
31
|
+
epochs: int = 5,
|
|
32
|
+
seed: int = 42,
|
|
33
|
+
evaluation_fallback: bool = True,
|
|
34
|
+
all_recs: bool = False,
|
|
35
|
+
triples: str = "all",
|
|
36
|
+
):
|
|
37
|
+
super().__init__(config)
|
|
38
|
+
self.embedding_dim = embedding_dim
|
|
39
|
+
self.scoring_fct_norm = scoring_fct_norm
|
|
40
|
+
self.entity_initializer = entity_initializer
|
|
41
|
+
self.entity_constrainer = entity_constrainer
|
|
42
|
+
self.relation_initializer = relation_initializer
|
|
43
|
+
self.relation_constrainer = relation_constrainer
|
|
44
|
+
self.regularizer = regularizer
|
|
45
|
+
self.regularizer_kwargs = regularizer_kwargs
|
|
46
|
+
self.epochs = epochs
|
|
47
|
+
self.seed = seed
|
|
48
|
+
self.evaluation_fallback = evaluation_fallback
|
|
49
|
+
self.all_recs = all_recs
|
|
50
|
+
self.triples = triples
|
|
51
|
+
self._triples = None
|
|
52
|
+
self._embedding = {}
|
|
53
|
+
|
|
54
|
+
def name(self):
|
|
55
|
+
text = "TransE based model + cosine similarity"
|
|
56
|
+
text += f";embedding_dim={self.embedding_dim};scoring_fct_norm={self.scoring_fct_norm};entity_initializer={self.entity_initializer};relation_initializer={self.relation_initializer};relation_constrainer={self.relation_constrainer};regularizer={self.regularizer};epochs={self.epochs};seed={self.seed};triples={self.triples}"
|
|
57
|
+
return text
|
|
58
|
+
|
|
59
|
+
def train(self, G_train, ratings_train):
|
|
60
|
+
self.G_train = G_train
|
|
61
|
+
if self.triples == "all":
|
|
62
|
+
self._triples = G_train.get_all_triples()
|
|
63
|
+
elif self.triples == "ratings":
|
|
64
|
+
self._triples = G_train.get_ratings_triples()
|
|
65
|
+
self.fit()
|
|
66
|
+
|
|
67
|
+
def get_recommendations(self, k: int = 5) -> t.Dict[UserNode, t.List[ItemNode]]:
|
|
68
|
+
"""
|
|
69
|
+
:param k: cutoff recommendation (int)
|
|
70
|
+
:return dict of recommendations for each user
|
|
71
|
+
of type {user1: [item1, item2]}
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
users = list(self.G_train.get_user_nodes())
|
|
75
|
+
items = list(self.G_train.get_item_nodes())
|
|
76
|
+
|
|
77
|
+
users_embeddings = np.array([self._embedding[user] for user in users])
|
|
78
|
+
items_embeddings = np.array([self._embedding[item] for item in items])
|
|
79
|
+
|
|
80
|
+
n_neighbors = self._get_n_neighbors(users, items, k)
|
|
81
|
+
knn = NearestNeighbors(n_neighbors=n_neighbors, metric="cosine")
|
|
82
|
+
knn.fit(items_embeddings)
|
|
83
|
+
rec_indices = knn.kneighbors(users_embeddings, return_distance=False)
|
|
84
|
+
|
|
85
|
+
recommendations = {}
|
|
86
|
+
for user_idx, user in enumerate(users):
|
|
87
|
+
rated_items = self.G_train.get_user_rated_items(user)
|
|
88
|
+
recs = []
|
|
89
|
+
for item_idx in rec_indices[user_idx]:
|
|
90
|
+
item = items[item_idx]
|
|
91
|
+
if item not in rated_items:
|
|
92
|
+
recs.append(item)
|
|
93
|
+
if not self.all_recs and len(recs) == k:
|
|
94
|
+
break
|
|
95
|
+
|
|
96
|
+
recommendations[user] = recs
|
|
97
|
+
|
|
98
|
+
return recommendations
|
|
99
|
+
|
|
100
|
+
def get_user_recommendation(self, user: UserNode, k: int = 5):
|
|
101
|
+
raise NotImplementedError(
|
|
102
|
+
"Override get_user_recommendation() for your model subclass."
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
def fit(self):
|
|
106
|
+
triples = TriplesFactory.from_labeled_triples(
|
|
107
|
+
self._triples[["head", "relation", "tail"]].values,
|
|
108
|
+
create_inverse_triples=False,
|
|
109
|
+
entity_to_id=None,
|
|
110
|
+
relation_to_id=None,
|
|
111
|
+
compact_id=False,
|
|
112
|
+
filter_out_candidate_inverse_relations=True,
|
|
113
|
+
metadata=None,
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
dataset = Dataset.from_tf(triples, ratios=[0.95, 0.05, 0.0])
|
|
117
|
+
entity_to_id = dataset.entity_to_id
|
|
118
|
+
|
|
119
|
+
result = pipeline(
|
|
120
|
+
dataset=dataset,
|
|
121
|
+
model="TransE",
|
|
122
|
+
model_kwargs=dict(
|
|
123
|
+
embedding_dim=self.embedding_dim,
|
|
124
|
+
scoring_fct_norm=self.scoring_fct_norm,
|
|
125
|
+
entity_initializer=self.entity_initializer,
|
|
126
|
+
entity_constrainer=self.entity_constrainer,
|
|
127
|
+
relation_initializer=self.relation_initializer,
|
|
128
|
+
relation_constrainer=self.relation_constrainer,
|
|
129
|
+
regularizer=self.regularizer,
|
|
130
|
+
regularizer_kwargs=self.regularizer_kwargs,
|
|
131
|
+
),
|
|
132
|
+
epochs=self.epochs,
|
|
133
|
+
random_seed=self.seed,
|
|
134
|
+
evaluation_fallback=self.evaluation_fallback,
|
|
135
|
+
)
|
|
136
|
+
model = result.model
|
|
137
|
+
|
|
138
|
+
users_iter = self.G_train.get_user_nodes()
|
|
139
|
+
items_iter = self.G_train.get_item_nodes()
|
|
140
|
+
|
|
141
|
+
users = list(users_iter)
|
|
142
|
+
items = list(items_iter)
|
|
143
|
+
|
|
144
|
+
# deal with entities that might have not been embedded
|
|
145
|
+
users_indices, users_no_embedding = [], []
|
|
146
|
+
for user in users_iter:
|
|
147
|
+
try:
|
|
148
|
+
users_indices.append(entity_to_id[user.__str__()])
|
|
149
|
+
except:
|
|
150
|
+
users_no_embedding.append(user)
|
|
151
|
+
users.remove(user)
|
|
152
|
+
continue
|
|
153
|
+
users_indices = torch.LongTensor(users_indices)
|
|
154
|
+
|
|
155
|
+
items_indices, items_no_embedding = [], []
|
|
156
|
+
for item in items_iter:
|
|
157
|
+
try:
|
|
158
|
+
items_indices.append(entity_to_id[item.__str__()])
|
|
159
|
+
except:
|
|
160
|
+
items_no_embedding.append(item)
|
|
161
|
+
items.remove(item)
|
|
162
|
+
continue
|
|
163
|
+
items_indices = torch.LongTensor(items_indices)
|
|
164
|
+
|
|
165
|
+
users_embeddings = (model.entity_representations[0](indices=users_indices).detach().cpu().numpy())
|
|
166
|
+
items_embeddings = (model.entity_representations[0](indices=items_indices).detach().cpu().numpy())
|
|
167
|
+
|
|
168
|
+
if len(users_no_embedding) > 0:
|
|
169
|
+
users_embeddings = np.concatenate((users_embeddings, np.zeros((len(users_no_embedding), self.embedding_dim), dtype=users_embeddings.dtype)), axis=0)
|
|
170
|
+
users = users + users_no_embedding
|
|
171
|
+
|
|
172
|
+
if len(items_no_embedding) > 0:
|
|
173
|
+
items_embeddings = np.concatenate((items_embeddings, np.zeros((len(items_no_embedding), self.embedding_dim), dtype=items_embeddings.dtype)), axis=0)
|
|
174
|
+
items = items + items_no_embedding
|
|
175
|
+
|
|
176
|
+
for index, user in enumerate(users):
|
|
177
|
+
self._embedding[user] = users_embeddings[index]
|
|
178
|
+
|
|
179
|
+
for index, item in enumerate(items):
|
|
180
|
+
self._embedding[item] = items_embeddings[index]
|
|
181
|
+
|
|
182
|
+
def _get_n_neighbors(self, users, items, top_k):
|
|
183
|
+
n_neighbors = 0
|
|
184
|
+
if self.all_recs:
|
|
185
|
+
n_neighbors = len(items)
|
|
186
|
+
else:
|
|
187
|
+
max_recs = 0
|
|
188
|
+
for user in users:
|
|
189
|
+
rated_items = self.G_train.get_user_rated_items(user)
|
|
190
|
+
if len(rated_items) > max_recs:
|
|
191
|
+
max_recs = len(rated_items)
|
|
192
|
+
n_neighbors = min(max_recs + top_k, len(items))
|
|
193
|
+
|
|
194
|
+
return n_neighbors
|
|
File without changes
|