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,187 @@
|
|
|
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 DistMult(Recommender):
|
|
20
|
+
def __init__(
|
|
21
|
+
self,
|
|
22
|
+
config: dict,
|
|
23
|
+
embedding_dim: int = 50,
|
|
24
|
+
entity_initializer=None,
|
|
25
|
+
entity_constrainer=None,
|
|
26
|
+
relation_initializer=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.entity_constrainer = entity_constrainer
|
|
39
|
+
self.relation_initializer = relation_initializer
|
|
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 = "DistMult based model + cosine similarity"
|
|
52
|
+
text += f";embedding_dim={self.embedding_dim};entity_initializer={self.entity_initializer};entity_constrainer={self.entity_constrainer};relation_initializer={self.relation_initializer};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="DistMult",
|
|
117
|
+
model_kwargs=dict(
|
|
118
|
+
embedding_dim=self.embedding_dim,
|
|
119
|
+
entity_initializer=self.entity_initializer,
|
|
120
|
+
entity_constrainer=self.entity_constrainer,
|
|
121
|
+
relation_initializer=self.relation_initializer,
|
|
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
|
+
if len(users_no_embedding) > 0:
|
|
162
|
+
users_embeddings = np.concatenate((users_embeddings, np.zeros((len(users_no_embedding), self.embedding_dim), dtype=users_embeddings.dtype)), axis=0)
|
|
163
|
+
users = users + users_no_embedding
|
|
164
|
+
|
|
165
|
+
if len(items_no_embedding) > 0:
|
|
166
|
+
items_embeddings = np.concatenate((items_embeddings, np.zeros((len(items_no_embedding), self.embedding_dim), dtype=items_embeddings.dtype)), axis=0)
|
|
167
|
+
items = items + items_no_embedding
|
|
168
|
+
|
|
169
|
+
for index, user in enumerate(users):
|
|
170
|
+
self._embedding[user] = users_embeddings[index]
|
|
171
|
+
|
|
172
|
+
for index, item in enumerate(items):
|
|
173
|
+
self._embedding[item] = items_embeddings[index]
|
|
174
|
+
|
|
175
|
+
def _get_n_neighbors(self, users, items, top_k):
|
|
176
|
+
n_neighbors = 0
|
|
177
|
+
if self.all_recs:
|
|
178
|
+
n_neighbors = len(items)
|
|
179
|
+
else:
|
|
180
|
+
max_recs = 0
|
|
181
|
+
for user in users:
|
|
182
|
+
rated_items = self.G_train.get_user_rated_items(user)
|
|
183
|
+
if len(rated_items) > max_recs:
|
|
184
|
+
max_recs = len(rated_items)
|
|
185
|
+
n_neighbors = min(max_recs + top_k, len(items))
|
|
186
|
+
|
|
187
|
+
return n_neighbors
|
|
File without changes
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
from framework.dataloader.graph.node import UserNode, ItemNode
|
|
2
|
+
from ...recommender import Recommender
|
|
3
|
+
|
|
4
|
+
from ...model2class import model2class
|
|
5
|
+
|
|
6
|
+
from sklearn.neighbors import NearestNeighbors
|
|
7
|
+
from sentence_transformers import SentenceTransformer
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
import pandas as pd
|
|
11
|
+
import random
|
|
12
|
+
import importlib
|
|
13
|
+
|
|
14
|
+
from tqdm import tqdm
|
|
15
|
+
from copy import deepcopy
|
|
16
|
+
|
|
17
|
+
import os
|
|
18
|
+
os.environ["TOKENIZERS_PARALLELISM"] = "false"
|
|
19
|
+
|
|
20
|
+
"""
|
|
21
|
+
Recommender base class
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
class EPHEN(Recommender):
|
|
25
|
+
def __init__(
|
|
26
|
+
self,
|
|
27
|
+
config: dict,
|
|
28
|
+
embedding_model: str = "sentence-transformers/distiluse-base-multilingual-cased-v2",
|
|
29
|
+
embedding_model_kwargs: dict = None,
|
|
30
|
+
embed_with: str = "movie_title", # 'graph' for graph embedding model or column for text embedding model
|
|
31
|
+
iterations: int = 30,
|
|
32
|
+
mi: float = 0.85,
|
|
33
|
+
all_recs: bool = False,
|
|
34
|
+
):
|
|
35
|
+
super().__init__(config)
|
|
36
|
+
self.embedding_model = embedding_model
|
|
37
|
+
self.embedding_model_kwargs = embedding_model_kwargs
|
|
38
|
+
self.embed_with = embed_with
|
|
39
|
+
self.iterations = iterations
|
|
40
|
+
self.mi = mi
|
|
41
|
+
self.all_recs = all_recs
|
|
42
|
+
self._embedding = {}
|
|
43
|
+
|
|
44
|
+
def name(self):
|
|
45
|
+
text = "EPHEN based model + cosine similarity"
|
|
46
|
+
text += f";embedding_model={self.embedding_model};embedding_model_kwargs={self.embedding_model_kwargs};embed_with={self.embed_with};iterations={self.iterations};mi={self.mi}."
|
|
47
|
+
return text
|
|
48
|
+
|
|
49
|
+
def train(self, G_train, ratings_train):
|
|
50
|
+
self.G_train = G_train
|
|
51
|
+
self.ratings_train = ratings_train
|
|
52
|
+
self.fit()
|
|
53
|
+
|
|
54
|
+
def get_recommendations(self, k: int = 5):
|
|
55
|
+
# Set doesnt guarantee the order of set elements
|
|
56
|
+
users = list(self.G_train.get_user_nodes())
|
|
57
|
+
items = list(self.G_train.get_item_nodes())
|
|
58
|
+
|
|
59
|
+
users_embeddings = np.array([self._embedding[user] for user in users])
|
|
60
|
+
items_embeddings = np.array([self._embedding[item] for item in items])
|
|
61
|
+
|
|
62
|
+
n_neighbors = self._get_n_neighbors(users, items, k)
|
|
63
|
+
knn = NearestNeighbors(n_neighbors=n_neighbors, metric='cosine')
|
|
64
|
+
knn.fit(items_embeddings)
|
|
65
|
+
rec_indices = knn.kneighbors(users_embeddings, return_distance=False)
|
|
66
|
+
|
|
67
|
+
recommendations = {}
|
|
68
|
+
for user_idx, user in enumerate(users):
|
|
69
|
+
rated_items = self.G_train.get_user_rated_items(user)
|
|
70
|
+
recs = []
|
|
71
|
+
for item_idx in rec_indices[user_idx]:
|
|
72
|
+
item = items[item_idx]
|
|
73
|
+
if item not in rated_items:
|
|
74
|
+
recs.append(item)
|
|
75
|
+
if not self.all_recs and len(recs) == k:
|
|
76
|
+
break
|
|
77
|
+
|
|
78
|
+
recommendations[user] = recs
|
|
79
|
+
|
|
80
|
+
return recommendations
|
|
81
|
+
|
|
82
|
+
def get_user_recommendation(self, user: UserNode, k: int = 5):
|
|
83
|
+
raise NotImplementedError(
|
|
84
|
+
"Override get_user_recommendation() for your model subclass."
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
def fit(self):
|
|
88
|
+
if self.embed_with == "graph":
|
|
89
|
+
self._generate_graph_init_embeddings()
|
|
90
|
+
else:
|
|
91
|
+
self._generate_text_init_embeddings()
|
|
92
|
+
self._regularization()
|
|
93
|
+
|
|
94
|
+
def _get_n_neighbors(self, users, items, top_k):
|
|
95
|
+
n_neighbors = 0
|
|
96
|
+
if self.all_recs:
|
|
97
|
+
n_neighbors = len(items)
|
|
98
|
+
else:
|
|
99
|
+
max_recs = 0
|
|
100
|
+
for user in users:
|
|
101
|
+
rated_items = self.G_train.get_user_rated_items(user)
|
|
102
|
+
if len(rated_items) > max_recs:
|
|
103
|
+
max_recs = len(rated_items)
|
|
104
|
+
n_neighbors = min(max_recs + top_k, len(items))
|
|
105
|
+
|
|
106
|
+
return n_neighbors
|
|
107
|
+
|
|
108
|
+
def _generate_text_init_embeddings(self):
|
|
109
|
+
model = SentenceTransformer(self.embedding_model)
|
|
110
|
+
item_properties = pd.DataFrame(self.G_train.get_item_property_edges())
|
|
111
|
+
filtered_properties = item_properties[1][item_properties[1].apply(lambda x: x.get_property_type() == self.embed_with)].reset_index(drop=True)
|
|
112
|
+
self.init_embeddings = {}
|
|
113
|
+
for property in tqdm(filtered_properties):
|
|
114
|
+
self.init_embeddings[property] = model.encode(property.id)
|
|
115
|
+
|
|
116
|
+
def _generate_graph_init_embeddings(self):
|
|
117
|
+
module_name = f'framework.recommender.models.{model2class[self.embedding_model]["submodule"]}'
|
|
118
|
+
class_name = model2class[self.embedding_model]['class']
|
|
119
|
+
|
|
120
|
+
model = getattr(importlib.import_module(module_name), class_name)
|
|
121
|
+
model = model(self.embedding_model_kwargs['config'], **self.embedding_model_kwargs['parameters'])
|
|
122
|
+
|
|
123
|
+
model.train(self.G_train, self.ratings_train)
|
|
124
|
+
|
|
125
|
+
self.init_embeddings = model._embedding
|
|
126
|
+
|
|
127
|
+
def _regularization(self):
|
|
128
|
+
dim = len(self.init_embeddings[list(self.init_embeddings.keys())[0]])
|
|
129
|
+
self._embedding = deepcopy(self.init_embeddings)
|
|
130
|
+
for node in self.G_train.nodes():
|
|
131
|
+
if node not in self._embedding:
|
|
132
|
+
self._embedding[node] = np.array([0.0] * dim)
|
|
133
|
+
nodes = list(self.G_train.nodes())
|
|
134
|
+
pbar = tqdm(range(0, self.iterations))
|
|
135
|
+
for iteration in pbar:
|
|
136
|
+
random.shuffle(nodes)
|
|
137
|
+
energy = 0.0
|
|
138
|
+
for node in nodes:
|
|
139
|
+
f_new = np.array([0.0] * dim)
|
|
140
|
+
f_old = np.array(self._embedding[node])
|
|
141
|
+
sum_w = 0.0
|
|
142
|
+
for neighbor in self.G_train.neighbors(node):
|
|
143
|
+
w = 1.0
|
|
144
|
+
if 'weight' in self.G_train[node][neighbor]:
|
|
145
|
+
w = self.G_train[node][neighbor]['weight']
|
|
146
|
+
w /= np.sqrt(self.G_train.degree[neighbor])
|
|
147
|
+
f_new = f_new + w * f_old
|
|
148
|
+
sum_w = sum_w + w
|
|
149
|
+
if sum_w == 0.0:
|
|
150
|
+
sum_w = 1.0
|
|
151
|
+
f_new /= sum_w
|
|
152
|
+
self._embedding[node] = f_new
|
|
153
|
+
if node in self.init_embeddings:
|
|
154
|
+
self._embedding[node] = self.init_embeddings[node] * \
|
|
155
|
+
self.mi + self._embedding[node] * (1.0 - self.mi)
|
|
156
|
+
energy = energy + np.linalg.norm(f_new-f_old)
|
|
157
|
+
iteration = iteration + 1
|
|
158
|
+
message = f"Iteration {iteration} | Energy = {energy}"
|
|
159
|
+
pbar.set_description(message)
|
|
File without changes
|
|
@@ -0,0 +1,184 @@
|
|
|
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 RESCAL(Recommender):
|
|
20
|
+
def __init__(
|
|
21
|
+
self,
|
|
22
|
+
config: dict,
|
|
23
|
+
embedding_dim: int = 50,
|
|
24
|
+
entity_initializer=None,
|
|
25
|
+
relation_initializer=None,
|
|
26
|
+
regularizer=None,
|
|
27
|
+
regularizer_kwargs=None,
|
|
28
|
+
epochs: int = 5,
|
|
29
|
+
seed: int = 42,
|
|
30
|
+
evaluation_fallback: bool = True,
|
|
31
|
+
all_recs: bool = False,
|
|
32
|
+
triples: str = "all",
|
|
33
|
+
):
|
|
34
|
+
super().__init__(config)
|
|
35
|
+
self.embedding_dim = embedding_dim
|
|
36
|
+
self.entity_initializer = entity_initializer
|
|
37
|
+
self.relation_initializer = relation_initializer
|
|
38
|
+
self.regularizer = regularizer
|
|
39
|
+
self.regularizer_kwargs = regularizer_kwargs
|
|
40
|
+
self.epochs = epochs
|
|
41
|
+
self.seed = seed
|
|
42
|
+
self.evaluation_fallback = evaluation_fallback
|
|
43
|
+
self.all_recs = all_recs
|
|
44
|
+
self.triples = triples
|
|
45
|
+
self._triples = None
|
|
46
|
+
self._embedding = {}
|
|
47
|
+
|
|
48
|
+
def name(self):
|
|
49
|
+
text = "RESCAL based model + cosine similarity"
|
|
50
|
+
text += f";embedding_dim={self.embedding_dim};entity_initializer={self.entity_initializer};relation_initializer={self.relation_initializer};regularizer={self.regularizer};epochs={self.epochs};seed={self.seed};triples={self.triples}"
|
|
51
|
+
return text
|
|
52
|
+
|
|
53
|
+
def train(self, G_train, ratings_train):
|
|
54
|
+
self.G_train = G_train
|
|
55
|
+
if self.triples == "all":
|
|
56
|
+
self._triples = G_train.get_all_triples()
|
|
57
|
+
elif self.triples == "ratings":
|
|
58
|
+
self._triples = G_train.get_ratings_triples()
|
|
59
|
+
self.fit()
|
|
60
|
+
|
|
61
|
+
def get_recommendations(self, k: int = 5) -> t.Dict[UserNode, t.List[ItemNode]]:
|
|
62
|
+
"""
|
|
63
|
+
:param k: cutoff recommendation (int)
|
|
64
|
+
:return dict of recommendations for each user
|
|
65
|
+
of type {user1: [item1, item2]}
|
|
66
|
+
"""
|
|
67
|
+
users = list(self.G_train.get_user_nodes())
|
|
68
|
+
items = list(self.G_train.get_item_nodes())
|
|
69
|
+
|
|
70
|
+
users_embeddings = np.array([self._embedding[user] for user in users])
|
|
71
|
+
items_embeddings = np.array([self._embedding[item] for item in items])
|
|
72
|
+
|
|
73
|
+
n_neighbors = self._get_n_neighbors(users, items, k)
|
|
74
|
+
knn = NearestNeighbors(n_neighbors=n_neighbors, metric="cosine")
|
|
75
|
+
knn.fit(items_embeddings)
|
|
76
|
+
rec_indices = knn.kneighbors(users_embeddings, return_distance=False)
|
|
77
|
+
|
|
78
|
+
recommendations = {}
|
|
79
|
+
for user_idx, user in enumerate(users):
|
|
80
|
+
rated_items = self.G_train.get_user_rated_items(user)
|
|
81
|
+
recs = []
|
|
82
|
+
for item_idx in rec_indices[user_idx]:
|
|
83
|
+
item = items[item_idx]
|
|
84
|
+
if item not in rated_items:
|
|
85
|
+
recs.append(item)
|
|
86
|
+
if not self.all_recs and len(recs) == k:
|
|
87
|
+
break
|
|
88
|
+
|
|
89
|
+
recommendations[user] = recs
|
|
90
|
+
|
|
91
|
+
return recommendations
|
|
92
|
+
|
|
93
|
+
def get_user_recommendation(self, user: UserNode, k: int = 5):
|
|
94
|
+
raise NotImplementedError(
|
|
95
|
+
"Override get_user_recommendation() for your model subclass."
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
def fit(self):
|
|
99
|
+
triples = TriplesFactory.from_labeled_triples(
|
|
100
|
+
self._triples[["head", "relation", "tail"]].values,
|
|
101
|
+
create_inverse_triples=False,
|
|
102
|
+
entity_to_id=None,
|
|
103
|
+
relation_to_id=None,
|
|
104
|
+
compact_id=False,
|
|
105
|
+
filter_out_candidate_inverse_relations=True,
|
|
106
|
+
metadata=None,
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
dataset = Dataset.from_tf(triples, ratios=[0.95, 0.05, 0.0])
|
|
110
|
+
entity_to_id = dataset.entity_to_id
|
|
111
|
+
|
|
112
|
+
result = pipeline(
|
|
113
|
+
dataset=dataset,
|
|
114
|
+
model="RESCAL",
|
|
115
|
+
model_kwargs=dict(
|
|
116
|
+
embedding_dim=self.embedding_dim,
|
|
117
|
+
entity_initializer=self.entity_initializer,
|
|
118
|
+
relation_initializer=self.relation_initializer,
|
|
119
|
+
regularizer=self.regularizer,
|
|
120
|
+
regularizer_kwargs=self.regularizer_kwargs,
|
|
121
|
+
),
|
|
122
|
+
epochs = self.epochs,
|
|
123
|
+
random_seed = self.seed,
|
|
124
|
+
evaluation_fallback = self.evaluation_fallback,
|
|
125
|
+
)
|
|
126
|
+
model = result.model
|
|
127
|
+
|
|
128
|
+
users_iter = self.G_train.get_user_nodes()
|
|
129
|
+
items_iter = self.G_train.get_item_nodes()
|
|
130
|
+
|
|
131
|
+
users = list(users_iter)
|
|
132
|
+
items = list(items_iter)
|
|
133
|
+
|
|
134
|
+
# deal with entities that might have not been embedded
|
|
135
|
+
users_indices, users_no_embedding = [], []
|
|
136
|
+
for user in users_iter:
|
|
137
|
+
try:
|
|
138
|
+
users_indices.append(entity_to_id[user.__str__()])
|
|
139
|
+
except:
|
|
140
|
+
users_no_embedding.append(user)
|
|
141
|
+
users.remove(user)
|
|
142
|
+
continue
|
|
143
|
+
users_indices = torch.LongTensor(users_indices)
|
|
144
|
+
|
|
145
|
+
items_indices, items_no_embedding = [], []
|
|
146
|
+
for item in items_iter:
|
|
147
|
+
try:
|
|
148
|
+
items_indices.append(entity_to_id[item.__str__()])
|
|
149
|
+
except:
|
|
150
|
+
items_no_embedding.append(item)
|
|
151
|
+
items.remove(item)
|
|
152
|
+
continue
|
|
153
|
+
items_indices = torch.LongTensor(items_indices)
|
|
154
|
+
|
|
155
|
+
users_embeddings = (model.entity_representations[0](indices=users_indices).detach().cpu().numpy())
|
|
156
|
+
items_embeddings = (model.entity_representations[0](indices=items_indices).detach().cpu().numpy())
|
|
157
|
+
|
|
158
|
+
if len(users_no_embedding) > 0:
|
|
159
|
+
users_embeddings = np.concatenate((users_embeddings, np.zeros((len(users_no_embedding), self.embedding_dim), dtype=users_embeddings.dtype)), axis=0)
|
|
160
|
+
users = users + users_no_embedding
|
|
161
|
+
|
|
162
|
+
if len(items_no_embedding) > 0:
|
|
163
|
+
items_embeddings = np.concatenate((items_embeddings, np.zeros((len(items_no_embedding), self.embedding_dim), dtype=items_embeddings.dtype)), axis=0)
|
|
164
|
+
items = items + items_no_embedding
|
|
165
|
+
|
|
166
|
+
for index, user in enumerate(users):
|
|
167
|
+
self._embedding[user] = users_embeddings[index]
|
|
168
|
+
|
|
169
|
+
for index, item in enumerate(items):
|
|
170
|
+
self._embedding[item] = items_embeddings[index]
|
|
171
|
+
|
|
172
|
+
def _get_n_neighbors(self, users, items, top_k):
|
|
173
|
+
n_neighbors = 0
|
|
174
|
+
if self.all_recs:
|
|
175
|
+
n_neighbors = len(items)
|
|
176
|
+
else:
|
|
177
|
+
max_recs = 0
|
|
178
|
+
for user in users:
|
|
179
|
+
rated_items = self.G_train.get_user_rated_items(user)
|
|
180
|
+
if len(rated_items) > max_recs:
|
|
181
|
+
max_recs = len(rated_items)
|
|
182
|
+
n_neighbors = min(max_recs + top_k, len(items))
|
|
183
|
+
|
|
184
|
+
return n_neighbors
|
|
File without changes
|