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,185 @@
|
|
|
1
|
+
# Code extracted from: https://raw.githubusercontent.com/benedekrozemberczki/karateclub/master/karateclub/utils/walker.py
|
|
2
|
+
|
|
3
|
+
import random
|
|
4
|
+
from functools import partial
|
|
5
|
+
from typing import List, Callable
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
import networkx as nx
|
|
9
|
+
from tqdm import tqdm
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class RandomWalker:
|
|
13
|
+
"""
|
|
14
|
+
Class to do fast first-order random walks.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
walk_length (int): Number of random walks.
|
|
18
|
+
walk_number (int): Number of nodes in truncated walk.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def __init__(self, walk_length: int, walk_number: int):
|
|
22
|
+
self.walk_length = walk_length
|
|
23
|
+
self.walk_number = walk_number
|
|
24
|
+
|
|
25
|
+
def do_walk(self, node: int) -> List[str]:
|
|
26
|
+
"""
|
|
27
|
+
Doing a single truncated random walk from a source node.
|
|
28
|
+
|
|
29
|
+
Arg types:
|
|
30
|
+
* **node** *(int)* - The source node of the random walk.
|
|
31
|
+
|
|
32
|
+
Return types:
|
|
33
|
+
* **walk** *(list of strings)* - A single truncated random walk.
|
|
34
|
+
"""
|
|
35
|
+
walk: List[int] = [node]
|
|
36
|
+
for _ in range(self.walk_length - 1):
|
|
37
|
+
nebs = [node for node in self.graph.neighbors(walk[-1])]
|
|
38
|
+
if len(nebs) > 0:
|
|
39
|
+
walk = walk + random.sample(nebs, 1)
|
|
40
|
+
walk = [str(w) for w in walk]
|
|
41
|
+
return walk
|
|
42
|
+
|
|
43
|
+
def do_walks(self, graph):
|
|
44
|
+
"""
|
|
45
|
+
Doing a fixed number of truncated random walk from every node in the graph.
|
|
46
|
+
|
|
47
|
+
Arg types:
|
|
48
|
+
* **graph** *(NetworkX graph)* - The graph to run the random walks on.
|
|
49
|
+
"""
|
|
50
|
+
self.walks: List[List[str]] = []
|
|
51
|
+
self.graph = graph
|
|
52
|
+
for node in tqdm(self.graph.nodes()):
|
|
53
|
+
for _ in range(self.walk_number):
|
|
54
|
+
walk_from_node = self.do_walk(node)
|
|
55
|
+
self.walks.append(walk_from_node)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _check_value(value, name):
|
|
59
|
+
try:
|
|
60
|
+
_ = 1 / value
|
|
61
|
+
|
|
62
|
+
except ZeroDivisionError:
|
|
63
|
+
raise ValueError(
|
|
64
|
+
f"The value of {name} is too small " f"or zero to be used in 1/{name}."
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _undirected(node, graph) -> List[tuple]:
|
|
69
|
+
edges = graph.edges(node)
|
|
70
|
+
|
|
71
|
+
return edges
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _directed(node, graph) -> List[tuple]:
|
|
75
|
+
edges = graph.out_edges(node, data=True)
|
|
76
|
+
|
|
77
|
+
return edges
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _get_edge_fn(graph) -> Callable:
|
|
81
|
+
fn = _directed if nx.classes.function.is_directed(graph) else _undirected
|
|
82
|
+
|
|
83
|
+
fn = partial(fn, graph=graph)
|
|
84
|
+
return fn
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _unweighted(edges: List[tuple]) -> np.ndarray:
|
|
88
|
+
return np.ones(len(edges))
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _weighted(edges: List[tuple]) -> np.ndarray:
|
|
92
|
+
weights = map(lambda edge: edge[-1]["weight"], edges)
|
|
93
|
+
|
|
94
|
+
return np.array([*weights])
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _get_weight_fn(graph) -> Callable:
|
|
98
|
+
fn = _weighted if nx.classes.function.is_weighted(graph) else _unweighted
|
|
99
|
+
|
|
100
|
+
return fn
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class BiasedRandomWalker:
|
|
104
|
+
"""
|
|
105
|
+
Class to do biased second order random walks.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
walk_length (int): Number of random walks.
|
|
109
|
+
walk_number (int): Number of nodes in truncated walk.
|
|
110
|
+
p (float): Return parameter (1/p transition probability) to move towards previous node.
|
|
111
|
+
q (float): In-out parameter (1/q transition probability) to move away from previous node.
|
|
112
|
+
"""
|
|
113
|
+
|
|
114
|
+
walks: list
|
|
115
|
+
graph: nx.classes.graph.Graph
|
|
116
|
+
edge_fn: Callable
|
|
117
|
+
weight_fn: Callable
|
|
118
|
+
|
|
119
|
+
def __init__(self, walk_length: int, walk_number: int, p: float, q: float):
|
|
120
|
+
self.walk_length = walk_length
|
|
121
|
+
self.walk_number = walk_number
|
|
122
|
+
|
|
123
|
+
_check_value(p, "p")
|
|
124
|
+
self.p = p
|
|
125
|
+
|
|
126
|
+
_check_value(q, "q")
|
|
127
|
+
self.q = q
|
|
128
|
+
|
|
129
|
+
def do_walk(self, node: int) -> List[str]:
|
|
130
|
+
"""
|
|
131
|
+
Doing a single truncated second order random walk from a source node.
|
|
132
|
+
|
|
133
|
+
Arg types:
|
|
134
|
+
* **node** *(int)* - The source node of the random walk.
|
|
135
|
+
|
|
136
|
+
Return types:
|
|
137
|
+
* **walk** *(list of strings)* - A single truncated random walk.
|
|
138
|
+
"""
|
|
139
|
+
walk = [node]
|
|
140
|
+
previous_node = None
|
|
141
|
+
previous_node_neighbors = []
|
|
142
|
+
for _ in range(self.walk_length - 1):
|
|
143
|
+
current_node = walk[-1]
|
|
144
|
+
edges = self.edge_fn(current_node)
|
|
145
|
+
current_node_neighbors = np.array([edge[1] for edge in edges])
|
|
146
|
+
|
|
147
|
+
weights = self.weight_fn(edges)
|
|
148
|
+
probability = np.piecewise(
|
|
149
|
+
weights,
|
|
150
|
+
[
|
|
151
|
+
current_node_neighbors == previous_node,
|
|
152
|
+
np.isin(current_node_neighbors, previous_node_neighbors),
|
|
153
|
+
],
|
|
154
|
+
[lambda w: w / self.p, lambda w: w / 1, lambda w: w / self.q],
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
norm_probability = probability / sum(probability)
|
|
158
|
+
selected = np.random.choice(current_node_neighbors, 1, p=norm_probability)[
|
|
159
|
+
0
|
|
160
|
+
]
|
|
161
|
+
walk.append(selected)
|
|
162
|
+
|
|
163
|
+
previous_node_neighbors = current_node_neighbors
|
|
164
|
+
previous_node = current_node
|
|
165
|
+
|
|
166
|
+
walk = [str(w) for w in walk]
|
|
167
|
+
return walk
|
|
168
|
+
|
|
169
|
+
def do_walks(self, graph) -> None:
|
|
170
|
+
"""
|
|
171
|
+
Doing a fixed number of truncated random walk from every node in the graph.
|
|
172
|
+
|
|
173
|
+
Arg types:
|
|
174
|
+
* **graph** *(NetworkX graph)* - The graph to run the random walks on.
|
|
175
|
+
"""
|
|
176
|
+
self.walks = []
|
|
177
|
+
self.graph = graph
|
|
178
|
+
|
|
179
|
+
self.edge_fn = _get_edge_fn(graph)
|
|
180
|
+
self.weight_fn = _get_weight_fn(graph)
|
|
181
|
+
|
|
182
|
+
for node in tqdm(self.graph.nodes()):
|
|
183
|
+
for _ in range(self.walk_number):
|
|
184
|
+
walk_from_node = self.do_walk(node)
|
|
185
|
+
self.walks.append(walk_from_node)
|
|
File without changes
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
import numpy as np
|
|
3
|
+
import typing as t
|
|
4
|
+
import os
|
|
5
|
+
import cpuinfo
|
|
6
|
+
import psutil
|
|
7
|
+
import GPUtil
|
|
8
|
+
|
|
9
|
+
class Reporter:
|
|
10
|
+
def __init__(self, output_file, eval_metrics):
|
|
11
|
+
self.filename = output_file
|
|
12
|
+
self.eval_metrics = eval_metrics
|
|
13
|
+
|
|
14
|
+
def report(self, results) -> None:
|
|
15
|
+
"""
|
|
16
|
+
Creates the .csv file with experiment results
|
|
17
|
+
"""
|
|
18
|
+
results = self.__process(results)
|
|
19
|
+
df = pd.DataFrame.from_dict(results, orient='index')
|
|
20
|
+
df['model'] = df.index
|
|
21
|
+
df = df.set_index('model')
|
|
22
|
+
|
|
23
|
+
self.__to_csv(df)
|
|
24
|
+
|
|
25
|
+
def __to_csv(self, df):
|
|
26
|
+
folder = os.path.dirname(self.filename)
|
|
27
|
+
if not os.path.isdir(folder):
|
|
28
|
+
os.makedirs(folder)
|
|
29
|
+
df.to_csv(self.filename)
|
|
30
|
+
|
|
31
|
+
def __process(self, results) -> t.Dict[str, t.Dict[str, float]]:
|
|
32
|
+
"""
|
|
33
|
+
returns
|
|
34
|
+
For k-fold:
|
|
35
|
+
{model_name: {fold-1_metric: value, fold-1_metric2: value, ..., metric_mean:value, metric_std}}
|
|
36
|
+
For hold-out:
|
|
37
|
+
{model_name: {metric1: value, metric2: value}}
|
|
38
|
+
"""
|
|
39
|
+
results_processed = {}
|
|
40
|
+
for model, metrics in results.items():
|
|
41
|
+
report = {}
|
|
42
|
+
if len(metrics) > 1:
|
|
43
|
+
for fold, fold_metrics in enumerate(metrics):
|
|
44
|
+
for i, metric in enumerate(fold_metrics):
|
|
45
|
+
col = f'fold-{fold+1}_{self.eval_metrics[i].name()}'
|
|
46
|
+
report[col] = metric
|
|
47
|
+
metrics_mean = np.array(metrics).mean(axis=0)
|
|
48
|
+
metrics_std = np.array(metrics).std(axis=0)
|
|
49
|
+
|
|
50
|
+
for idx, (metric_mean, metric_std) in enumerate(zip(metrics_mean, metrics_std)):
|
|
51
|
+
col_mean = f'{self.eval_metrics[idx].name()}_mean'
|
|
52
|
+
col_std = f'{self.eval_metrics[idx].name()}_std'
|
|
53
|
+
report[col_mean] = metric_mean
|
|
54
|
+
report[col_std] = metric_std
|
|
55
|
+
else:
|
|
56
|
+
metrics = metrics[0]
|
|
57
|
+
for i, metric in enumerate(metrics):
|
|
58
|
+
report[self.eval_metrics[i].name()] = metric
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
results_processed[model] = report
|
|
62
|
+
|
|
63
|
+
return results_processed
|
|
64
|
+
|
|
65
|
+
class ExecutionTimesReporter:
|
|
66
|
+
def __init__(self, output_file):
|
|
67
|
+
self.filename = output_file
|
|
68
|
+
|
|
69
|
+
def report(self, results):
|
|
70
|
+
"""
|
|
71
|
+
Creates the .csv file with experiment results
|
|
72
|
+
"""
|
|
73
|
+
results = self.__process(results)
|
|
74
|
+
df = pd.DataFrame.from_dict(results, orient='index')
|
|
75
|
+
df['model'] = df.index
|
|
76
|
+
df = df.set_index('model')
|
|
77
|
+
|
|
78
|
+
self.__to_csv(df)
|
|
79
|
+
|
|
80
|
+
def __to_csv(self, df):
|
|
81
|
+
folder = os.path.dirname(self.filename)
|
|
82
|
+
if not os.path.isdir(folder):
|
|
83
|
+
os.makedirs(folder)
|
|
84
|
+
df.to_csv(self.filename)
|
|
85
|
+
|
|
86
|
+
def __process(self, results) -> t.Dict[str, float]:
|
|
87
|
+
"""
|
|
88
|
+
returns
|
|
89
|
+
{model_name (machine_specs): {fold-1_execution_time: value, fold-2_execution_time: value, ..., execution_time_mean:value,execution_time_std}}
|
|
90
|
+
"""
|
|
91
|
+
results_processed = {}
|
|
92
|
+
for model, execution_times in results.items():
|
|
93
|
+
report = {}
|
|
94
|
+
for fold, execution_time in enumerate(execution_times):
|
|
95
|
+
report[f'fold-{fold+1}_execution_time'] = execution_time
|
|
96
|
+
report['execution_time_mean'] = np.array(execution_times).mean(axis=0)
|
|
97
|
+
report['execution_time_std'] = np.array(execution_times).std(axis=0)
|
|
98
|
+
|
|
99
|
+
results_processed[f'{model} (CPU: {cpuinfo.get_cpu_info()["brand_raw"]}; RAM: {round(psutil.virtual_memory().total/(1024**3))}GB; GPUs: {[gpu.name for gpu in GPUtil.getGPUs()]})'] = report
|
|
100
|
+
|
|
101
|
+
return results_processed
|