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,193 @@
|
|
|
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 TransH(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
|
+
regularizer = None,
|
|
27
|
+
regularizer_kwargs = None,
|
|
28
|
+
relation_initializer = None,
|
|
29
|
+
relation_regularizer = None,
|
|
30
|
+
relation_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.regularizer = regularizer
|
|
42
|
+
self.regularizer_kwargs = regularizer_kwargs
|
|
43
|
+
self.relation_initializer = relation_initializer
|
|
44
|
+
self.relation_regularizer = relation_regularizer
|
|
45
|
+
self.relation_regularizer_kwargs = relation_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 = "TransH based model + cosine similarity"
|
|
56
|
+
text += f";embedding_dim={self.embedding_dim};scoring_fct_norm={self.scoring_fct_norm};entity_initializer={self.entity_initializer};regularizer={self.regularizer};relation_initializer={self.relation_initializer};relation_regularizer={self.relation_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
|
+
users = list(self.G_train.get_user_nodes())
|
|
74
|
+
items = list(self.G_train.get_item_nodes())
|
|
75
|
+
|
|
76
|
+
users_embeddings = np.array([self._embedding[user] for user in users])
|
|
77
|
+
items_embeddings = np.array([self._embedding[item] for item in items])
|
|
78
|
+
|
|
79
|
+
n_neighbors = self._get_n_neighbors(users, items, k)
|
|
80
|
+
knn = NearestNeighbors(n_neighbors=n_neighbors, metric="cosine")
|
|
81
|
+
knn.fit(items_embeddings)
|
|
82
|
+
rec_indices = knn.kneighbors(users_embeddings, return_distance=False)
|
|
83
|
+
|
|
84
|
+
recommendations = {}
|
|
85
|
+
for user_idx, user in enumerate(users):
|
|
86
|
+
rated_items = self.G_train.get_user_rated_items(user)
|
|
87
|
+
recs = []
|
|
88
|
+
for item_idx in rec_indices[user_idx]:
|
|
89
|
+
item = items[item_idx]
|
|
90
|
+
if item not in rated_items:
|
|
91
|
+
recs.append(item)
|
|
92
|
+
if not self.all_recs and len(recs) == k:
|
|
93
|
+
break
|
|
94
|
+
|
|
95
|
+
recommendations[user] = recs
|
|
96
|
+
|
|
97
|
+
return recommendations
|
|
98
|
+
|
|
99
|
+
def get_user_recommendation(self, user: UserNode, k: int = 5):
|
|
100
|
+
raise NotImplementedError(
|
|
101
|
+
"Override get_user_recommendation() for your model subclass."
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
def fit(self):
|
|
105
|
+
triples = TriplesFactory.from_labeled_triples(
|
|
106
|
+
self._triples[["head", "relation", "tail"]].values,
|
|
107
|
+
create_inverse_triples=False,
|
|
108
|
+
entity_to_id=None,
|
|
109
|
+
relation_to_id=None,
|
|
110
|
+
compact_id=False,
|
|
111
|
+
filter_out_candidate_inverse_relations=True,
|
|
112
|
+
metadata=None,
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
dataset = Dataset.from_tf(triples, ratios=[0.95, 0.05, 0.0])
|
|
116
|
+
entity_to_id = dataset.entity_to_id
|
|
117
|
+
|
|
118
|
+
result = pipeline(
|
|
119
|
+
dataset=dataset,
|
|
120
|
+
model="TransH",
|
|
121
|
+
model_kwargs=dict(
|
|
122
|
+
embedding_dim=self.embedding_dim,
|
|
123
|
+
scoring_fct_norm=self.scoring_fct_norm,
|
|
124
|
+
entity_initializer=self.entity_initializer,
|
|
125
|
+
regularizer=self.regularizer,
|
|
126
|
+
regularizer_kwargs=self.regularizer_kwargs,
|
|
127
|
+
relation_initializer=self.relation_initializer,
|
|
128
|
+
relation_regularizer=self.relation_regularizer,
|
|
129
|
+
relation_regularizer_kwargs=self.relation_regularizer_kwargs,
|
|
130
|
+
),
|
|
131
|
+
epochs = self.epochs,
|
|
132
|
+
random_seed = self.seed,
|
|
133
|
+
evaluation_fallback = self.evaluation_fallback,
|
|
134
|
+
)
|
|
135
|
+
model = result.model
|
|
136
|
+
|
|
137
|
+
users_iter = self.G_train.get_user_nodes()
|
|
138
|
+
items_iter = self.G_train.get_item_nodes()
|
|
139
|
+
|
|
140
|
+
users = list(users_iter)
|
|
141
|
+
items = list(items_iter)
|
|
142
|
+
|
|
143
|
+
# deal with entities that might have not been embedded
|
|
144
|
+
users_indices, users_no_embedding = [], []
|
|
145
|
+
for user in users_iter:
|
|
146
|
+
try:
|
|
147
|
+
users_indices.append(entity_to_id[user.__str__()])
|
|
148
|
+
except:
|
|
149
|
+
users_no_embedding.append(user)
|
|
150
|
+
users.remove(user)
|
|
151
|
+
continue
|
|
152
|
+
users_indices = torch.LongTensor(users_indices)
|
|
153
|
+
|
|
154
|
+
items_indices, items_no_embedding = [], []
|
|
155
|
+
for item in items_iter:
|
|
156
|
+
try:
|
|
157
|
+
items_indices.append(entity_to_id[item.__str__()])
|
|
158
|
+
except:
|
|
159
|
+
items_no_embedding.append(item)
|
|
160
|
+
items.remove(item)
|
|
161
|
+
continue
|
|
162
|
+
items_indices = torch.LongTensor(items_indices)
|
|
163
|
+
|
|
164
|
+
users_embeddings = (model.entity_representations[0](indices=users_indices).detach().cpu().numpy())
|
|
165
|
+
items_embeddings = (model.entity_representations[0](indices=items_indices).detach().cpu().numpy())
|
|
166
|
+
|
|
167
|
+
if len(users_no_embedding) > 0:
|
|
168
|
+
users_embeddings = np.concatenate((users_embeddings, np.zeros((len(users_no_embedding), self.embedding_dim), dtype=users_embeddings.dtype)), axis=0)
|
|
169
|
+
users = users + users_no_embedding
|
|
170
|
+
|
|
171
|
+
if len(items_no_embedding) > 0:
|
|
172
|
+
items_embeddings = np.concatenate((items_embeddings, np.zeros((len(items_no_embedding), self.embedding_dim), dtype=items_embeddings.dtype)), axis=0)
|
|
173
|
+
items = items + items_no_embedding
|
|
174
|
+
|
|
175
|
+
for index, user in enumerate(users):
|
|
176
|
+
self._embedding[user] = users_embeddings[index]
|
|
177
|
+
|
|
178
|
+
for index, item in enumerate(items):
|
|
179
|
+
self._embedding[item] = items_embeddings[index]
|
|
180
|
+
|
|
181
|
+
def _get_n_neighbors(self, users, items, top_k):
|
|
182
|
+
n_neighbors = 0
|
|
183
|
+
if self.all_recs:
|
|
184
|
+
n_neighbors = len(items)
|
|
185
|
+
else:
|
|
186
|
+
max_recs = 0
|
|
187
|
+
for user in users:
|
|
188
|
+
rated_items = self.G_train.get_user_rated_items(user)
|
|
189
|
+
if len(rated_items) > max_recs:
|
|
190
|
+
max_recs = len(rated_items)
|
|
191
|
+
n_neighbors = min(max_recs + top_k, len(items))
|
|
192
|
+
|
|
193
|
+
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 TransR(Recommender):
|
|
20
|
+
def __init__(
|
|
21
|
+
self,
|
|
22
|
+
config: dict,
|
|
23
|
+
embedding_dim: int = 50,
|
|
24
|
+
relation_dim: int = 30,
|
|
25
|
+
scoring_fct_norm: int = 1,
|
|
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.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.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 = "TransR based model + cosine similarity"
|
|
54
|
+
text += f";embedding_dim={self.embedding_dim};relation_dim={self.relation_dim};scoring_fct_norm={self.scoring_fct_norm};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="TransR",
|
|
119
|
+
model_kwargs=dict(
|
|
120
|
+
embedding_dim=self.embedding_dim,
|
|
121
|
+
relation_dim=self.relation_dim,
|
|
122
|
+
scoring_fct_norm=self.scoring_fct_norm,
|
|
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,199 @@
|
|
|
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 TuckER(Recommender):
|
|
20
|
+
def __init__(
|
|
21
|
+
self,
|
|
22
|
+
config: dict,
|
|
23
|
+
embedding_dim: int = 200,
|
|
24
|
+
relation_dim = None,
|
|
25
|
+
dropout_0: float = 0.3,
|
|
26
|
+
dropout_1: float = 0.4,
|
|
27
|
+
dropout_2: float = 0.5,
|
|
28
|
+
apply_batch_normalization: bool = True,
|
|
29
|
+
entity_initializer= None,
|
|
30
|
+
relation_initializer = None,
|
|
31
|
+
core_tensor_initializer = None,
|
|
32
|
+
core_tensor_initializer_kwargs=None,
|
|
33
|
+
epochs: int = 5,
|
|
34
|
+
seed: int = 42,
|
|
35
|
+
evaluation_fallback: bool = True,
|
|
36
|
+
all_recs: bool = False,
|
|
37
|
+
triples: str = "all",
|
|
38
|
+
):
|
|
39
|
+
super().__init__(config)
|
|
40
|
+
self.embedding_dim = embedding_dim
|
|
41
|
+
self.entity_initializer = entity_initializer
|
|
42
|
+
self.relation_dim = relation_dim
|
|
43
|
+
self.dropout_0 = dropout_0
|
|
44
|
+
self.dropout_1 = dropout_1
|
|
45
|
+
self.dropout_2 = dropout_2
|
|
46
|
+
self.apply_batch_normalization = apply_batch_normalization
|
|
47
|
+
self.relation_initializer = relation_initializer
|
|
48
|
+
self.core_tensor_initializer = core_tensor_initializer
|
|
49
|
+
self.core_tensor_initializer_kwargs = core_tensor_initializer_kwargs
|
|
50
|
+
self.epochs = epochs
|
|
51
|
+
self.seed = seed
|
|
52
|
+
self.evaluation_fallback = evaluation_fallback
|
|
53
|
+
self.all_recs = all_recs
|
|
54
|
+
self.triples = triples
|
|
55
|
+
self._triples = None
|
|
56
|
+
self._embedding = {}
|
|
57
|
+
|
|
58
|
+
def name(self):
|
|
59
|
+
text = "TuckER based model + cosine similarity"
|
|
60
|
+
text += f";embedding_dim={self.embedding_dim};relation_dim={self.relation_dim};dropout_0={self.dropout_0};dropout_1={self.dropout_1};dropout_2={self.dropout_2};apply_batch_normalization={self.apply_batch_normalization};relation_initializer={self.relation_initializer};core_tensor_initializer={self.core_tensor_initializer};epochs={self.epochs};seed={self.seed};triples={self.triples}"
|
|
61
|
+
return text
|
|
62
|
+
|
|
63
|
+
def train(self, G_train, ratings_train):
|
|
64
|
+
self.G_train = G_train
|
|
65
|
+
if self.triples == "all":
|
|
66
|
+
self._triples = G_train.get_all_triples()
|
|
67
|
+
elif self.triples == "ratings":
|
|
68
|
+
self._triples = G_train.get_ratings_triples()
|
|
69
|
+
self.fit()
|
|
70
|
+
|
|
71
|
+
def get_recommendations(self, k: int = 5) -> t.Dict[UserNode, t.List[ItemNode]]:
|
|
72
|
+
"""
|
|
73
|
+
:param k: cutoff recommendation (int)
|
|
74
|
+
:return dict of recommendations for each user
|
|
75
|
+
of type {user1: [item1, item2]}
|
|
76
|
+
"""
|
|
77
|
+
users = list(self.G_train.get_user_nodes())
|
|
78
|
+
items = list(self.G_train.get_item_nodes())
|
|
79
|
+
|
|
80
|
+
users_embeddings = np.array([self._embedding[user] for user in users])
|
|
81
|
+
items_embeddings = np.array([self._embedding[item] for item in items])
|
|
82
|
+
|
|
83
|
+
n_neighbors = self._get_n_neighbors(users, items, k)
|
|
84
|
+
knn = NearestNeighbors(n_neighbors=n_neighbors, metric="cosine")
|
|
85
|
+
knn.fit(items_embeddings)
|
|
86
|
+
rec_indices = knn.kneighbors(users_embeddings, return_distance=False)
|
|
87
|
+
|
|
88
|
+
recommendations = {}
|
|
89
|
+
for user_idx, user in enumerate(users):
|
|
90
|
+
rated_items = self.G_train.get_user_rated_items(user)
|
|
91
|
+
recs = []
|
|
92
|
+
for item_idx in rec_indices[user_idx]:
|
|
93
|
+
item = items[item_idx]
|
|
94
|
+
if item not in rated_items:
|
|
95
|
+
recs.append(item)
|
|
96
|
+
if not self.all_recs and len(recs) == k:
|
|
97
|
+
break
|
|
98
|
+
|
|
99
|
+
recommendations[user] = recs
|
|
100
|
+
|
|
101
|
+
return recommendations
|
|
102
|
+
|
|
103
|
+
def get_user_recommendation(self, user: UserNode, k: int = 5):
|
|
104
|
+
raise NotImplementedError(
|
|
105
|
+
"Override get_user_recommendation() for your model subclass."
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
def fit(self):
|
|
109
|
+
triples = TriplesFactory.from_labeled_triples(
|
|
110
|
+
self._triples[["head", "relation", "tail"]].values,
|
|
111
|
+
create_inverse_triples=False,
|
|
112
|
+
entity_to_id=None,
|
|
113
|
+
relation_to_id=None,
|
|
114
|
+
compact_id=False,
|
|
115
|
+
filter_out_candidate_inverse_relations=True,
|
|
116
|
+
metadata=None,
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
dataset = Dataset.from_tf(triples, ratios=[0.95, 0.05, 0.0])
|
|
120
|
+
entity_to_id = dataset.entity_to_id
|
|
121
|
+
|
|
122
|
+
result = pipeline(
|
|
123
|
+
dataset=dataset,
|
|
124
|
+
model="TuckER",
|
|
125
|
+
model_kwargs=dict(
|
|
126
|
+
embedding_dim=self.embedding_dim,
|
|
127
|
+
relation_dim=self.relation_dim,
|
|
128
|
+
dropout_0=self.dropout_0,
|
|
129
|
+
dropout_1=self.dropout_1,
|
|
130
|
+
dropout_2=self.dropout_2,
|
|
131
|
+
apply_batch_normalization=self.apply_batch_normalization,
|
|
132
|
+
entity_initializer=self.entity_initializer,
|
|
133
|
+
relation_initializer=self.relation_initializer,
|
|
134
|
+
core_tensor_initializer=self.core_tensor_initializer,
|
|
135
|
+
core_tensor_initializer_kwargs=self.core_tensor_initializer_kwargs,
|
|
136
|
+
),
|
|
137
|
+
epochs = self.epochs,
|
|
138
|
+
random_seed = self.seed,
|
|
139
|
+
evaluation_fallback = self.evaluation_fallback,
|
|
140
|
+
)
|
|
141
|
+
model = result.model
|
|
142
|
+
|
|
143
|
+
users_iter = self.G_train.get_user_nodes()
|
|
144
|
+
items_iter = self.G_train.get_item_nodes()
|
|
145
|
+
|
|
146
|
+
users = list(users_iter)
|
|
147
|
+
items = list(items_iter)
|
|
148
|
+
|
|
149
|
+
# deal with entities that might have not been embedded
|
|
150
|
+
users_indices, users_no_embedding = [], []
|
|
151
|
+
for user in users_iter:
|
|
152
|
+
try:
|
|
153
|
+
users_indices.append(entity_to_id[user.__str__()])
|
|
154
|
+
except:
|
|
155
|
+
users_no_embedding.append(user)
|
|
156
|
+
users.remove(user)
|
|
157
|
+
continue
|
|
158
|
+
users_indices = torch.LongTensor(users_indices)
|
|
159
|
+
|
|
160
|
+
items_indices, items_no_embedding = [], []
|
|
161
|
+
for item in items_iter:
|
|
162
|
+
try:
|
|
163
|
+
items_indices.append(entity_to_id[item.__str__()])
|
|
164
|
+
except:
|
|
165
|
+
items_no_embedding.append(item)
|
|
166
|
+
items.remove(item)
|
|
167
|
+
continue
|
|
168
|
+
items_indices = torch.LongTensor(items_indices)
|
|
169
|
+
|
|
170
|
+
users_embeddings = (model.entity_representations[0](indices=users_indices).detach().cpu().numpy())
|
|
171
|
+
items_embeddings = (model.entity_representations[0](indices=items_indices).detach().cpu().numpy())
|
|
172
|
+
|
|
173
|
+
if len(users_no_embedding) > 0:
|
|
174
|
+
users_embeddings = np.concatenate((users_embeddings, np.zeros((len(users_no_embedding), self.embedding_dim), dtype=users_embeddings.dtype)), axis=0)
|
|
175
|
+
users = users + users_no_embedding
|
|
176
|
+
|
|
177
|
+
if len(items_no_embedding) > 0:
|
|
178
|
+
items_embeddings = np.concatenate((items_embeddings, np.zeros((len(items_no_embedding), self.embedding_dim), dtype=items_embeddings.dtype)), axis=0)
|
|
179
|
+
items = items + items_no_embedding
|
|
180
|
+
|
|
181
|
+
for index, user in enumerate(users):
|
|
182
|
+
self._embedding[user] = users_embeddings[index]
|
|
183
|
+
|
|
184
|
+
for index, item in enumerate(items):
|
|
185
|
+
self._embedding[item] = items_embeddings[index]
|
|
186
|
+
|
|
187
|
+
def _get_n_neighbors(self, users, items, top_k):
|
|
188
|
+
n_neighbors = 0
|
|
189
|
+
if self.all_recs:
|
|
190
|
+
n_neighbors = len(items)
|
|
191
|
+
else:
|
|
192
|
+
max_recs = 0
|
|
193
|
+
for user in users:
|
|
194
|
+
rated_items = self.G_train.get_user_rated_items(user)
|
|
195
|
+
if len(rated_items) > max_recs:
|
|
196
|
+
max_recs = len(rated_items)
|
|
197
|
+
n_neighbors = min(max_recs + top_k, len(items))
|
|
198
|
+
|
|
199
|
+
return n_neighbors
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
from ..dataloader.graph.graph import Graph
|
|
2
|
+
from ..dataloader.graph.node import UserNode, ItemNode
|
|
3
|
+
|
|
4
|
+
from ..utils import get_optional_argument
|
|
5
|
+
|
|
6
|
+
import typing as t
|
|
7
|
+
|
|
8
|
+
"""
|
|
9
|
+
Recommender base class
|
|
10
|
+
"""
|
|
11
|
+
class Recommender:
|
|
12
|
+
def __init__(self, config : dict = {}):
|
|
13
|
+
if config is not None:
|
|
14
|
+
self.save_weights = get_optional_argument(config, 'save_weights', False)
|
|
15
|
+
|
|
16
|
+
def train(self, G_train : Graph, ratings_train : t.Dict[UserNode, t.List[t.Tuple[ItemNode, float]]]):
|
|
17
|
+
raise NotImplementedError('Override train() method for your model subclass.')
|
|
18
|
+
|
|
19
|
+
def name(self):
|
|
20
|
+
raise NotImplementedError('Override name() method for your model subclass.')
|
|
21
|
+
|
|
22
|
+
def get_recommendations(self, k : int = 5) -> t.Dict[UserNode, t.List[ItemNode]]:
|
|
23
|
+
"""
|
|
24
|
+
:param k: cutoff recommendation (int)
|
|
25
|
+
:return dict of recommendations for each user
|
|
26
|
+
of type {user1: [item1, item2]}
|
|
27
|
+
"""
|
|
28
|
+
raise NotImplementedError('Override get_recommendations() for your model subclass.')
|
|
29
|
+
|
|
30
|
+
def get_user_recommendation(self, user : UserNode, k : int = 5):
|
|
31
|
+
raise NotImplementedError('Override get_user_recommendation() for your model subclass.')
|
|
32
|
+
|
|
File without changes
|