chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,185 @@
|
||||
import easygraph as eg
|
||||
import numpy as np
|
||||
|
||||
from easygraph.utils import *
|
||||
|
||||
|
||||
__all__ = ["NOBE", "NOBE_GA"]
|
||||
|
||||
|
||||
@not_implemented_for("multigraph")
|
||||
def NOBE(G, K):
|
||||
"""Graph embedding via NOBE[1].
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : easygraph.Graph
|
||||
An unweighted and undirected graph.
|
||||
|
||||
K : int
|
||||
Embedding dimension k
|
||||
|
||||
Returns
|
||||
-------
|
||||
Y : list
|
||||
list of embedding vectors (y1, y2, · · · , yn)
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> NOBE(G,K=15)
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] https://www.researchgate.net/publication/325004496_On_Spectral_Graph_Embedding_A_Non-Backtracking_Perspective_and_Graph_Approximation
|
||||
|
||||
"""
|
||||
dict = {}
|
||||
a = 0
|
||||
for i in G.nodes:
|
||||
dict[i] = a
|
||||
a += 1
|
||||
LG = graph_to_d_atleast2(G)
|
||||
N = len(G)
|
||||
P, pair = Transition(LG)
|
||||
V = eigs_nodes(P, K)
|
||||
Y = embedding(V, pair, K, N, dict, G)
|
||||
return Y
|
||||
|
||||
|
||||
@not_implemented_for("multigraph")
|
||||
@only_implemented_for_UnDirected_graph
|
||||
def NOBE_GA(G, K):
|
||||
"""Graph embedding via NOBE-GA[1].
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : easygraph.Graph
|
||||
An unweighted and undirected graph.
|
||||
|
||||
K : int
|
||||
Embedding dimension k
|
||||
|
||||
Returns
|
||||
-------
|
||||
Y : list
|
||||
list of embedding vectors (y1, y2, · · · , yn)
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> NOBE_GA(G,K=15)
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] https://www.researchgate.net/publication/325004496_On_Spectral_Graph_Embedding_A_Non-Backtracking_Perspective_and_Graph_Approximation
|
||||
|
||||
"""
|
||||
from scipy.sparse.linalg import eigs
|
||||
|
||||
N = len(G)
|
||||
A = np.eye(N, N)
|
||||
for i in G.edges:
|
||||
(u, v, t) = i
|
||||
u = int(u) - 1
|
||||
v = int(v) - 1
|
||||
A[u, v] = 1
|
||||
degree = G.degree()
|
||||
D_inv = np.zeros([N, N])
|
||||
a = 0
|
||||
for i in degree:
|
||||
D_inv[a, a] = 1 / degree[i]
|
||||
a += 1
|
||||
D_I_inv = np.zeros([N, N])
|
||||
b = 0
|
||||
for i in degree:
|
||||
if degree[i] > 1:
|
||||
D_I_inv[b, b] = 1 / (degree[i] - 1)
|
||||
b += 1
|
||||
I = np.identity(N)
|
||||
M_D = 0.5 * A * D_I_inv * (I - D_inv)
|
||||
D_D = 0.5 * I
|
||||
T_ua = np.zeros([2 * N, 2 * N])
|
||||
T_ua[0:N, 0:N] = M_D
|
||||
T_ua[N : 2 * N, N : 2 * N] = M_D
|
||||
T_ua[N : 2 * N, 0:N] = D_D
|
||||
T_ua[0:N, N : 2 * N] = D_D
|
||||
Y1, Y = eigs(T_ua, K + 1, which="LR")
|
||||
Y = Y[0:N, :-1]
|
||||
return Y
|
||||
|
||||
|
||||
def graph_to_d_atleast2(G):
|
||||
n = len(G)
|
||||
LG = eg.Graph()
|
||||
LG = G.copy()
|
||||
new_node = n
|
||||
degree = LG.degree()
|
||||
node = LG.nodes.copy()
|
||||
for i in node:
|
||||
if degree[i] == 1:
|
||||
for neighbors in LG.neighbors(node=i):
|
||||
LG.add_edge(i, new_node)
|
||||
LG.add_edge(new_node, neighbors)
|
||||
break
|
||||
new_node = new_node + 1
|
||||
return LG
|
||||
|
||||
|
||||
def Transition(LG):
|
||||
N = len(LG)
|
||||
M = LG.size()
|
||||
LLG = eg.DiGraph()
|
||||
for i in LG.edges:
|
||||
(u, v, t) = i
|
||||
LLG.add_edge(u, v)
|
||||
LLG.add_edge(v, u)
|
||||
degree = LLG.degree()
|
||||
P = np.zeros([2 * M, 2 * M])
|
||||
pair = []
|
||||
k = 0
|
||||
l = 0
|
||||
for i in LLG.edges:
|
||||
l = 0
|
||||
for j in LLG.edges:
|
||||
(u, v, t) = i
|
||||
(x, y, z) = j
|
||||
if v == x and u != y:
|
||||
P[k][l] = 1 / (degree[v] - 1)
|
||||
l += 1
|
||||
k += 1
|
||||
a = 0
|
||||
for i in LLG.edges:
|
||||
(u, v, t) = i
|
||||
pair.append([u, v])
|
||||
a += 1
|
||||
return P, pair
|
||||
|
||||
|
||||
def eigs_nodes(P, K):
|
||||
from scipy.sparse.linalg import eigs
|
||||
|
||||
M = np.size(P, 0)
|
||||
L = np.zeros([M, M])
|
||||
I = np.identity(M)
|
||||
P_T = P.T
|
||||
L = I - (P + P_T) / 2
|
||||
U, D = eigs(L, K + 1, which="LR")
|
||||
D = D[:, :-1]
|
||||
V = np.zeros([M, K], dtype=complex)
|
||||
a = 0
|
||||
for i in D:
|
||||
V[a] = i
|
||||
a += 1
|
||||
return V
|
||||
|
||||
|
||||
def embedding(V, pair, K, N, dict, G):
|
||||
Y = np.zeros([N, K], dtype=complex)
|
||||
idx = 0
|
||||
for i in pair:
|
||||
[v, u] = i
|
||||
if u in G.nodes:
|
||||
t = dict[u]
|
||||
for j in range(0, len(V[idx])):
|
||||
Y[t, j] += V[idx, j]
|
||||
idx += 1
|
||||
return Y
|
||||
@@ -0,0 +1,13 @@
|
||||
from .deepwalk import *
|
||||
from .NOBE import *
|
||||
from .node2vec import *
|
||||
|
||||
|
||||
try:
|
||||
from .line import *
|
||||
from .sdne import *
|
||||
except:
|
||||
print(
|
||||
"Warning raise in module:graph_embedding. Please install packages Pytorch"
|
||||
" before you use functions related to graph_embedding"
|
||||
)
|
||||
@@ -0,0 +1,103 @@
|
||||
import random
|
||||
|
||||
from easygraph.functions.graph_embedding.node2vec import (
|
||||
_get_embedding_result_from_gensim_skipgram_model,
|
||||
)
|
||||
from easygraph.functions.graph_embedding.node2vec import learn_embeddings
|
||||
from easygraph.utils import *
|
||||
from tqdm import tqdm
|
||||
|
||||
|
||||
__all__ = ["deepwalk"]
|
||||
|
||||
|
||||
@not_implemented_for("multigraph")
|
||||
def deepwalk(G, dimensions=128, walk_length=80, num_walks=10, **skip_gram_params):
|
||||
"""Graph embedding via DeepWalk.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : easygraph.Graph or easygraph.DiGraph
|
||||
|
||||
dimensions : int
|
||||
Embedding dimensions, optional(default: 128)
|
||||
|
||||
walk_length : int
|
||||
Number of nodes in each walk, optional(default: 80)
|
||||
|
||||
num_walks : int
|
||||
Number of walks per node, optional(default: 10)
|
||||
|
||||
skip_gram_params : dict
|
||||
Parameters for gensim.models.Word2Vec - do not supply `size`, it is taken from the `dimensions` parameter
|
||||
|
||||
Returns
|
||||
-------
|
||||
embedding_vector : dict
|
||||
The embedding vector of each node
|
||||
|
||||
most_similar_nodes_of_node : dict
|
||||
The most similar nodes of each node and its similarity
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
>>> deepwalk(G,
|
||||
... dimensions=128, # The graph embedding dimensions.
|
||||
... walk_length=80, # Walk length of each random walks.
|
||||
... num_walks=10, # Number of random walks.
|
||||
... skip_gram_params = dict( # The skip_gram parameters in Python package gensim.
|
||||
... window=10,
|
||||
... min_count=1,
|
||||
... batch_words=4,
|
||||
... iter=15
|
||||
... ))
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] https://arxiv.org/abs/1403.6652
|
||||
|
||||
"""
|
||||
G_index, index_of_node, node_of_index = G.to_index_node_graph()
|
||||
|
||||
walks = simulate_walks(G_index, walk_length=walk_length, num_walks=num_walks)
|
||||
model = learn_embeddings(walks=walks, dimensions=dimensions, **skip_gram_params)
|
||||
|
||||
(
|
||||
embedding_vector,
|
||||
most_similar_nodes_of_node,
|
||||
) = _get_embedding_result_from_gensim_skipgram_model(
|
||||
G=G, index_of_node=index_of_node, node_of_index=node_of_index, model=model
|
||||
)
|
||||
|
||||
del G_index
|
||||
return embedding_vector, most_similar_nodes_of_node
|
||||
|
||||
|
||||
def simulate_walks(G, walk_length, num_walks):
|
||||
walks = []
|
||||
nodes = list(G.nodes)
|
||||
print("Walk iteration:")
|
||||
for walk_iter in tqdm(range(num_walks)):
|
||||
random.shuffle(nodes)
|
||||
for node in nodes:
|
||||
walks.append(_deepwalk_walk(G, walk_length=walk_length, start_node=node))
|
||||
|
||||
return walks
|
||||
|
||||
|
||||
def _deepwalk_walk(G, walk_length, start_node):
|
||||
"""
|
||||
Simulate a random walk starting from start node.
|
||||
"""
|
||||
walk = [start_node]
|
||||
|
||||
while len(walk) < walk_length:
|
||||
cur = walk[-1]
|
||||
cur_nbrs = sorted(G.neighbors(cur))
|
||||
if len(cur_nbrs) > 0:
|
||||
pick_node = random.choice(cur_nbrs)
|
||||
walk.append(pick_node)
|
||||
else:
|
||||
break
|
||||
return walk
|
||||
@@ -0,0 +1,303 @@
|
||||
import time
|
||||
import warnings
|
||||
|
||||
import easygraph as eg
|
||||
import numpy as np
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
|
||||
from easygraph.utils import alias_draw
|
||||
from easygraph.utils import alias_setup
|
||||
from sklearn import preprocessing
|
||||
|
||||
# from easygraph.functions.graph_embedding import *
|
||||
from tqdm import tqdm
|
||||
|
||||
|
||||
warnings.filterwarnings("ignore")
|
||||
|
||||
|
||||
class LINE(nn.Module):
|
||||
"""Graph embedding via LINE.
|
||||
Parameters
|
||||
----------
|
||||
G : easygraph.Graph or easygraph.DiGraph
|
||||
dimension: int
|
||||
walk_length: int
|
||||
|
||||
walk_num: int
|
||||
|
||||
negative: int
|
||||
batch_size: int
|
||||
|
||||
init_alpha: float
|
||||
order: int
|
||||
Returns
|
||||
-------
|
||||
embedding_vector : dict
|
||||
The embedding vector of each node
|
||||
Examples
|
||||
--------
|
||||
>>> model = LINE(
|
||||
... dimension=128,
|
||||
... walk_length=80,
|
||||
... walk_num=20,
|
||||
... negative=5,
|
||||
... batch_size=128,
|
||||
... init_alpha=0.025,
|
||||
... order=3 )
|
||||
>>> model.train()
|
||||
>>> emb = model(g, return_dict=True) # g: easygraph.Graph or easygraph.DiGraph
|
||||
|
||||
References
|
||||
----------
|
||||
|
||||
.. [1] Tang, J., Qu, M., Wang, M., Zhang, M., Yan, J., & Mei, Q. (2015, May). Line: Large-scale information network embedding. In Proceedings of the 24th international conference on world wide web (pp. 1067-1077).
|
||||
|
||||
https://www.microsoft.com/en-us/research/wp-content/uploads/2016/02/frp0228-Tang.pdf
|
||||
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def add_args(parser):
|
||||
"""Add model-specific arguments to the parser."""
|
||||
parser.add_argument(
|
||||
"--walk-length",
|
||||
type=int,
|
||||
default=80,
|
||||
help="Length of walk per source. Default is 80.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--walk-num",
|
||||
type=int,
|
||||
default=20,
|
||||
help="Number of walks per source. Default is 20.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--negative",
|
||||
type=int,
|
||||
default=5,
|
||||
help="Number of negative node in sampling. Default is 5.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--batch-size",
|
||||
type=int,
|
||||
default=1000,
|
||||
help="Batch size in SGD training process. Default is 1000.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--alpha",
|
||||
type=float,
|
||||
default=0.025,
|
||||
help="Initial learning rate of SGD. Default is 0.025.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--order",
|
||||
type=int,
|
||||
default=3,
|
||||
help="Order of proximity in LINE. Default is 3 for 1+2.",
|
||||
)
|
||||
parser.add_argument("--hidden-size", type=int, default=128)
|
||||
|
||||
@classmethod
|
||||
def build_model_from_args(cls, args):
|
||||
return cls(
|
||||
args.hidden_size,
|
||||
args.walk_length,
|
||||
args.walk_num,
|
||||
args.negative,
|
||||
args.batch_size,
|
||||
args.alpha,
|
||||
args.order,
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
dimension=128,
|
||||
walk_length=80,
|
||||
walk_num=20,
|
||||
negative=5,
|
||||
batch_size=128,
|
||||
init_alpha=0.025,
|
||||
order=3,
|
||||
):
|
||||
super(LINE, self).__init__()
|
||||
self.dimension = dimension
|
||||
self.walk_length = walk_length
|
||||
self.walk_num = walk_num
|
||||
self.negative = negative
|
||||
self.batch_size = batch_size
|
||||
self.init_alpha = init_alpha
|
||||
self.order = order
|
||||
|
||||
def forward(self, g, return_dict=True):
|
||||
# run LINE algorithm, 1-order, 2-order or 3(1-order + 2-order)
|
||||
|
||||
self.G = g
|
||||
self.is_directed = g.is_directed()
|
||||
self.num_node = len(g.nodes)
|
||||
self.num_edge = g.number_of_edges()
|
||||
self.num_sampling_edge = self.walk_length * self.walk_num * self.num_node
|
||||
|
||||
node2id = dict([(node, vid) for vid, node in enumerate(g.nodes)])
|
||||
self.edges = [[node2id[e[0]], node2id[e[1]]] for e in self.G.edges]
|
||||
|
||||
self.edges_prob = np.asarray([1.0 for e in g.edges])
|
||||
self.edges_prob /= np.sum(self.edges_prob)
|
||||
self.edges_table, self.edges_prob = alias_setup(self.edges_prob)
|
||||
|
||||
degree_weight = np.asarray([0] * self.num_node)
|
||||
degree_weight = np.array(list(g.degree(node2id[u] for u in g.nodes).values()))
|
||||
# for u,v in g.edges:
|
||||
|
||||
# degree_weight[node2id[u]] += 1.0
|
||||
# if not self.is_directed:
|
||||
# degree_weight[node2id[v]] += 1.0
|
||||
self.node_prob = np.power(degree_weight, 0.75)
|
||||
self.node_prob /= np.sum(self.node_prob)
|
||||
self.node_table, self.node_prob = alias_setup(self.node_prob)
|
||||
|
||||
if self.order == 3:
|
||||
self.dimension = int(self.dimension / 2)
|
||||
if self.order == 1 or self.order == 3:
|
||||
print("train line with 1-order")
|
||||
print(type(self.dimension))
|
||||
self.emb_vertex = (
|
||||
np.random.random((self.num_node, self.dimension)) - 0.5
|
||||
) / self.dimension
|
||||
self._train_line(order=1)
|
||||
embedding1 = preprocessing.normalize(self.emb_vertex, "l2")
|
||||
|
||||
if self.order == 2 or self.order == 3:
|
||||
print("train line with 2-order")
|
||||
self.emb_vertex = (
|
||||
np.random.random((self.num_node, self.dimension)) - 0.5
|
||||
) / self.dimension
|
||||
self.emb_context = self.emb_vertex
|
||||
self._train_line(order=2)
|
||||
embedding2 = preprocessing.normalize(self.emb_vertex, "l2")
|
||||
|
||||
if self.order == 1:
|
||||
embeddings = embedding1
|
||||
elif self.order == 2:
|
||||
embeddings = embedding2
|
||||
else:
|
||||
print("concatenate two embedding...")
|
||||
embeddings = np.hstack((embedding1, embedding2))
|
||||
|
||||
if return_dict:
|
||||
features_matrix = dict()
|
||||
for vid, node in enumerate(g.nodes):
|
||||
features_matrix[node] = embeddings[vid]
|
||||
else:
|
||||
features_matrix = np.zeros((len(g.nodes), embeddings.shape[1]))
|
||||
nx_nodes = list(g.nodes)
|
||||
features_matrix[nx_nodes] = embeddings[np.arange(len(g.nodes))]
|
||||
return features_matrix
|
||||
|
||||
def _update(self, vec_u, vec_v, vec_error, label):
|
||||
# update vetex embedding and vec_error
|
||||
f = 1 / (1 + np.exp(-np.sum(vec_u * vec_v, axis=1)))
|
||||
g = (self.alpha * (label - f)).reshape((len(label), 1))
|
||||
vec_error += g * vec_v
|
||||
vec_v += g * vec_u
|
||||
|
||||
def _train_line(self, order):
|
||||
# train Line model with order
|
||||
self.alpha = self.init_alpha
|
||||
batch_size = self.batch_size
|
||||
t0 = time.time()
|
||||
num_batch = int(self.num_sampling_edge / batch_size)
|
||||
epoch_iter = tqdm(range(num_batch))
|
||||
for b in epoch_iter:
|
||||
if b % 100 == 0:
|
||||
epoch_iter.set_description(
|
||||
# f"Progress: {b * 1.0 / num_batch * 100:.4f}, alpha: {self.alpha:.6f}, time: {time.time() - t0:.4f}"
|
||||
)
|
||||
self.alpha = self.init_alpha * max((1 - b * 1.0 / num_batch), 0.0001)
|
||||
u, v = [0] * batch_size, [0] * batch_size
|
||||
for i in range(batch_size):
|
||||
edge_id = alias_draw(self.edges_table, self.edges_prob)
|
||||
u[i], v[i] = self.edges[edge_id]
|
||||
if not self.is_directed and np.random.rand() > 0.5:
|
||||
v[i], u[i] = self.edges[edge_id]
|
||||
|
||||
vec_error = np.zeros((batch_size, self.dimension))
|
||||
label, target = np.asarray([1 for i in range(batch_size)]), np.asarray(v)
|
||||
for j in range(1 + self.negative):
|
||||
if j != 0:
|
||||
label = np.asarray([0 for i in range(batch_size)])
|
||||
for i in range(batch_size):
|
||||
target[i] = alias_draw(self.node_table, self.node_prob)
|
||||
if order == 1:
|
||||
self._update(
|
||||
self.emb_vertex[u], self.emb_vertex[target], vec_error, label
|
||||
)
|
||||
else:
|
||||
self._update(
|
||||
self.emb_vertex[u], self.emb_context[target], vec_error, label
|
||||
)
|
||||
self.emb_vertex[u] += vec_error
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
dataset = eg.CiteseerGraphDataset(
|
||||
force_reload=True
|
||||
) # Download CiteseerGraphDataset contained in EasyGraph
|
||||
num_classes = dataset.num_classes
|
||||
g = dataset[0]
|
||||
labels = g.ndata["label"]
|
||||
edge_list = []
|
||||
for i in g.edges:
|
||||
edge_list.append((i[0], i[1]))
|
||||
g1 = eg.Graph()
|
||||
g1.add_edges_from(edge_list)
|
||||
# print(g.edges)
|
||||
# print(g.__dir__())
|
||||
|
||||
model = LINE(
|
||||
dimension=128,
|
||||
walk_length=80,
|
||||
walk_num=20,
|
||||
negative=5,
|
||||
batch_size=128,
|
||||
init_alpha=0.025,
|
||||
order=3,
|
||||
)
|
||||
print(model)
|
||||
|
||||
model.train()
|
||||
out = model(g1, return_dict=True)
|
||||
|
||||
keylist = sorted(out)
|
||||
tmp = torch.cat(
|
||||
(
|
||||
torch.unsqueeze(torch.tensor(out[keylist[0]]), -2),
|
||||
torch.unsqueeze(torch.tensor(out[keylist[1]]), -2),
|
||||
),
|
||||
0,
|
||||
)
|
||||
|
||||
for i in range(2, len(keylist)):
|
||||
tmp = torch.cat((tmp, torch.unsqueeze(torch.tensor(out[keylist[i]]), -2)), 0)
|
||||
torch.save(tmp, "line.emb")
|
||||
print(tmp, tmp.shape)
|
||||
|
||||
line_emb = []
|
||||
for i in range(0, len(tmp)):
|
||||
line_emb.append(list(tmp[i]))
|
||||
line_emb = np.array(line_emb)
|
||||
|
||||
# tsne = TSNE(n_components=2)
|
||||
# z = tsne.fit_transform(line_emb)
|
||||
# z_data = np.vstack((z.T, labels)).T
|
||||
# df_tsne = pd.DataFrame(z_data, columns=['Dim1', 'Dim2', 'class'])
|
||||
# df_tsne['class'] = df_tsne['class'].astype(int)
|
||||
# df_tsne.head()
|
||||
#
|
||||
# plt.figure(figsize=(8, 8))
|
||||
# sns.scatterplot(data=df_tsne, hue='class', x='Dim1', y='Dim2', palette=['green','orange','brown','red', 'blue','black'])
|
||||
# plt.savefig('torch_line_citeseer.pdf', bbox_inches='tight')
|
||||
# plt.show()
|
||||
#
|
||||
#
|
||||
Binary file not shown.
@@ -0,0 +1,175 @@
|
||||
from __future__ import print_function
|
||||
|
||||
import argparse
|
||||
import csv
|
||||
import time
|
||||
import warnings
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
import easygraph as eg
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import seaborn as sns
|
||||
import torch
|
||||
|
||||
from easygraph.datasets.citation_graph import CiteseerGraphDataset
|
||||
from easygraph.functions.community import greedy_modularity_communities
|
||||
from easygraph.functions.community import modularity
|
||||
from easygraph.functions.graph_embedding import *
|
||||
from mpl_toolkits.mplot3d import Axes3D
|
||||
from sklearn.decomposition import PCA
|
||||
from sklearn.manifold import TSNE
|
||||
|
||||
|
||||
warnings.filterwarnings("ignore")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
|
||||
dataset = CiteseerGraphDataset(
|
||||
force_reload=True
|
||||
) # Download CiteseerGraphDataset contained in EasyGraph
|
||||
num_classes = dataset.num_classes
|
||||
g = dataset[0]
|
||||
labels = g.ndata["label"]
|
||||
print(labels, labels.shape, len(g.nodes))
|
||||
|
||||
print("Graph embedding via DeepWalk...........")
|
||||
deepwalk_emb, _ = deepwalk(g, dimensions=128, walk_length=80, num_walks=10)
|
||||
# print(deepwalk_emb, len(deepwalk_emb))
|
||||
|
||||
dw_emb = []
|
||||
for i in range(0, len(deepwalk_emb)):
|
||||
dw_emb.append(list(deepwalk_emb[i]))
|
||||
# print(len(dw_emb))
|
||||
dw_emb = np.array(dw_emb)
|
||||
print(dw_emb)
|
||||
|
||||
tsne = TSNE(n_components=2, verbose=1, random_state=0)
|
||||
z = tsne.fit_transform(dw_emb)
|
||||
z_data = np.vstack((z.T, labels)).T
|
||||
df_tsne = pd.DataFrame(z_data, columns=["Dim1", "Dim2", "class"])
|
||||
df_tsne["class"] = df_tsne["class"].astype(int)
|
||||
plt.figure(figsize=(8, 8))
|
||||
sns.scatterplot(
|
||||
data=df_tsne,
|
||||
hue="class",
|
||||
x="Dim1",
|
||||
y="Dim2",
|
||||
palette=["green", "orange", "brown", "red", "blue", "black"],
|
||||
)
|
||||
plt.savefig(
|
||||
"figs/dw_citeseer.pdf", bbox_inches="tight"
|
||||
) # save embeddings if needed
|
||||
plt.savefig("figs/dw_citeseer.png", bbox_inches="tight")
|
||||
plt.show()
|
||||
|
||||
print("Graph embedding via Node2Vec..............")
|
||||
node2vec_emb, _ = node2vec(
|
||||
g, dimensions=128, walk_length=80, num_walks=10, p=4, q=0.25
|
||||
)
|
||||
# print(node2vec_emb, len(node2vec_emb))
|
||||
|
||||
n2v_emb = []
|
||||
for i in range(0, len(node2vec_emb)):
|
||||
n2v_emb.append(list(node2vec_emb[i]))
|
||||
# print(len(n2v_emb))
|
||||
n2v_emb = np.array(n2v_emb)
|
||||
print(n2v_emb)
|
||||
|
||||
tsne = TSNE(n_components=2, verbose=1, random_state=0)
|
||||
z = tsne.fit_transform(n2v_emb)
|
||||
z_data = np.vstack((z.T, labels)).T
|
||||
df_tsne = pd.DataFrame(z_data, columns=["Dim1", "Dim2", "class"])
|
||||
df_tsne["class"] = df_tsne["class"].astype(int)
|
||||
plt.figure(figsize=(8, 8))
|
||||
sns.scatterplot(
|
||||
data=df_tsne,
|
||||
hue="class",
|
||||
x="Dim1",
|
||||
y="Dim2",
|
||||
palette=["green", "orange", "brown", "red", "blue", "black"],
|
||||
)
|
||||
|
||||
plt.savefig("figs/n2v_citeseer.pdf", bbox_inches="tight")
|
||||
plt.savefig("figs/n2v_citeseer.png", bbox_inches="tight")
|
||||
plt.show()
|
||||
|
||||
print("Graph embedding via LINE........")
|
||||
|
||||
model = LINE(
|
||||
dimension=128,
|
||||
walk_length=80,
|
||||
walk_num=10,
|
||||
negative=5,
|
||||
batch_size=128,
|
||||
init_alpha=0.025,
|
||||
order=2,
|
||||
)
|
||||
|
||||
model.train()
|
||||
line_emb = model(g, return_dict=True)
|
||||
|
||||
l_emb = []
|
||||
for i in range(0, len(line_emb)):
|
||||
l_emb.append(list(line_emb[i]))
|
||||
# print(len(l_emb))
|
||||
l_emb = np.array(l_emb)
|
||||
print(l_emb)
|
||||
|
||||
tsne = TSNE(n_components=2, verbose=1, random_state=0)
|
||||
z = tsne.fit_transform(l_emb)
|
||||
z_data = np.vstack((z.T, labels)).T
|
||||
df_tsne = pd.DataFrame(z_data, columns=["Dim1", "Dim2", "class"])
|
||||
df_tsne["class"] = df_tsne["class"].astype(int)
|
||||
plt.figure(figsize=(8, 8))
|
||||
sns.scatterplot(
|
||||
data=df_tsne,
|
||||
hue="class",
|
||||
x="Dim1",
|
||||
y="Dim2",
|
||||
palette=["green", "orange", "brown", "red", "blue", "black"],
|
||||
)
|
||||
|
||||
plt.savefig("figs/line_citeseer.pdf", bbox_inches="tight")
|
||||
plt.savefig("figs/line_citeseer.png", bbox_inches="tight")
|
||||
plt.show()
|
||||
|
||||
print("Graph embedding via SDNE...........")
|
||||
model = eg.SDNE(
|
||||
g,
|
||||
node_size=len(g.nodes),
|
||||
nhid0=256,
|
||||
nhid1=32,
|
||||
dropout=0.025,
|
||||
alpha=5e-4,
|
||||
beta=10,
|
||||
)
|
||||
sdne_emb = model.train(model)
|
||||
|
||||
sd_emb = []
|
||||
for i in range(0, len(sdne_emb)):
|
||||
sd_emb.append(list(sdne_emb[i]))
|
||||
# print(len(sd_emb))
|
||||
sd_emb = np.array(sd_emb)
|
||||
print(sd_emb)
|
||||
|
||||
tsne = TSNE(n_components=2, verbose=1, random_state=0)
|
||||
z = tsne.fit_transform(sd_emb)
|
||||
z_data = np.vstack((z.T, labels)).T
|
||||
df_tsne = pd.DataFrame(z_data, columns=["Dim1", "Dim2", "class"])
|
||||
df_tsne["class"] = df_tsne["class"].astype(int)
|
||||
plt.figure(figsize=(8, 8))
|
||||
sns.scatterplot(
|
||||
data=df_tsne,
|
||||
hue="class",
|
||||
x="Dim1",
|
||||
y="Dim2",
|
||||
palette=["green", "orange", "brown", "red", "blue", "black"],
|
||||
)
|
||||
|
||||
plt.savefig("figs/sdne_citeseer2.pdf", bbox_inches="tight")
|
||||
plt.savefig("figs/sdne_citeseer2.png", bbox_inches="tight")
|
||||
plt.show()
|
||||
@@ -0,0 +1,308 @@
|
||||
import random
|
||||
|
||||
import numpy as np
|
||||
|
||||
from easygraph.utils import *
|
||||
from tqdm import tqdm
|
||||
|
||||
|
||||
__all__ = ["node2vec"]
|
||||
|
||||
|
||||
@not_implemented_for("multigraph")
|
||||
def node2vec(
|
||||
G,
|
||||
dimensions=128,
|
||||
walk_length=80,
|
||||
num_walks=10,
|
||||
p=1.0,
|
||||
q=1.0,
|
||||
weight_key=None,
|
||||
workers=None,
|
||||
**skip_gram_params,
|
||||
):
|
||||
"""Graph embedding via Node2Vec.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : easygraph.Graph or easygraph.DiGraph
|
||||
|
||||
dimensions : int
|
||||
Embedding dimensions, optional(default: 128)
|
||||
|
||||
walk_length : int
|
||||
Number of nodes in each walk, optional(default: 80)
|
||||
|
||||
num_walks : int
|
||||
Number of walks per node, optional(default: 10)
|
||||
|
||||
p : float
|
||||
The return hyper parameter, optional(default: 1.0)
|
||||
|
||||
q : float
|
||||
The input parameter, optional(default: 1.0)
|
||||
|
||||
weight_key : string or None (default: None)
|
||||
On weighted graphs, this is the key for the weight attribute
|
||||
|
||||
workers : int or None, optional(default : None)
|
||||
The number of workers generating random walks (default: None). None if not using only one worker.
|
||||
|
||||
skip_gram_params : dict
|
||||
Parameters for gensim.models.Word2Vec - do not supply 'size', it is taken from the 'dimensions' parameter
|
||||
|
||||
Returns
|
||||
-------
|
||||
embedding_vector : dict
|
||||
The embedding vector of each node
|
||||
|
||||
most_similar_nodes_of_node : dict
|
||||
The most similar nodes of each node and its similarity
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
>>> node2vec(G,
|
||||
... dimensions=128, # The graph embedding dimensions.
|
||||
... walk_length=80, # Walk length of each random walks.
|
||||
... num_walks=10, # Number of random walks.
|
||||
... p=1.0, # The `p` possibility in random walk in [1]_
|
||||
... q=1.0, # The `q` possibility in random walk in [1]_
|
||||
... weight_key='weight',
|
||||
... skip_gram_params=dict( # The skip_gram parameters in Python package gensim.
|
||||
... window=10,
|
||||
... min_count=1,
|
||||
... batch_words=4
|
||||
... ))
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] https://arxiv.org/abs/1607.00653
|
||||
|
||||
"""
|
||||
G_index, index_of_node, node_of_index = G.to_index_node_graph()
|
||||
|
||||
if workers is None:
|
||||
walks = simulate_walks(
|
||||
G_index,
|
||||
walk_length=walk_length,
|
||||
num_walks=num_walks,
|
||||
p=p,
|
||||
q=q,
|
||||
weight_key=weight_key,
|
||||
)
|
||||
else:
|
||||
from joblib import Parallel
|
||||
from joblib import delayed
|
||||
|
||||
num_walks_lists = np.array_split(range(num_walks), workers)
|
||||
walks = Parallel(n_jobs=workers)(
|
||||
delayed(simulate_walks)(
|
||||
G_index, walk_length, len(num_walks), p, q, weight_key
|
||||
)
|
||||
for num_walks in num_walks_lists
|
||||
)
|
||||
# Change multidimensional array to one dimensional array
|
||||
walks = [walk for walk_group in walks for walk in walk_group]
|
||||
|
||||
model = learn_embeddings(walks=walks, dimensions=dimensions, **skip_gram_params)
|
||||
|
||||
(
|
||||
embedding_vector,
|
||||
most_similar_nodes_of_node,
|
||||
) = _get_embedding_result_from_gensim_skipgram_model(
|
||||
G=G, index_of_node=index_of_node, node_of_index=node_of_index, model=model
|
||||
)
|
||||
|
||||
del G_index
|
||||
return embedding_vector, most_similar_nodes_of_node
|
||||
|
||||
|
||||
def _get_embedding_result_from_gensim_skipgram_model(
|
||||
G, index_of_node, node_of_index, model
|
||||
):
|
||||
embedding_vector = dict()
|
||||
most_similar_nodes_of_node = dict()
|
||||
|
||||
def change_string_to_node_from_gensim_return_value(value_including_str):
|
||||
# As the return value of gensim model.wv.most_similar includes string index in G_index,
|
||||
# the string index should be changed to the original node element in G.
|
||||
result = []
|
||||
for node_index, value in value_including_str:
|
||||
node_index = int(node_index)
|
||||
node = node_of_index[node_index]
|
||||
result.append((node, value))
|
||||
return result
|
||||
|
||||
for node in G.nodes:
|
||||
# Output node names are always strings in gensim
|
||||
embedding_vector[node] = model.wv[str(index_of_node[node])]
|
||||
|
||||
most_similar_nodes = model.wv.most_similar(str(index_of_node[node]))
|
||||
most_similar_nodes_of_node[
|
||||
node
|
||||
] = change_string_to_node_from_gensim_return_value(most_similar_nodes)
|
||||
|
||||
return embedding_vector, most_similar_nodes_of_node
|
||||
|
||||
|
||||
def simulate_walks(G, walk_length, num_walks, p, q, weight_key=None):
|
||||
alias_nodes, alias_edges = _preprocess_transition_probs(G, p, q, weight_key)
|
||||
walks = []
|
||||
nodes = list(G.nodes)
|
||||
for walk_iter in tqdm(range(num_walks)):
|
||||
random.shuffle(nodes)
|
||||
for node in nodes:
|
||||
walks.append(
|
||||
_node2vec_walk(
|
||||
G,
|
||||
walk_length=walk_length,
|
||||
start_node=node,
|
||||
alias_nodes=alias_nodes,
|
||||
alias_edges=alias_edges,
|
||||
)
|
||||
)
|
||||
|
||||
return walks
|
||||
|
||||
|
||||
def _preprocess_transition_probs(G, p, q, weight_key=None):
|
||||
is_directed = G.is_directed()
|
||||
alias_nodes = {}
|
||||
|
||||
for node in G.nodes:
|
||||
if weight_key is None:
|
||||
unnormalized_probs = [1.0 for nbr in sorted(G.neighbors(node))]
|
||||
else:
|
||||
unnormalized_probs = [
|
||||
G[node][nbr][weight_key] for nbr in sorted(G.neighbors(node))
|
||||
]
|
||||
norm_const = sum(unnormalized_probs)
|
||||
normalized_probs = [float(u_prob) / norm_const for u_prob in unnormalized_probs]
|
||||
alias_nodes[node] = _alias_setup(normalized_probs)
|
||||
|
||||
alias_edges = {}
|
||||
triads = {}
|
||||
|
||||
if is_directed:
|
||||
for edge in G.edges:
|
||||
alias_edges[(edge[0], edge[1])] = _get_alias_edge(
|
||||
G, edge[0], edge[1], p, q, weight_key
|
||||
)
|
||||
else:
|
||||
for edge in G.edges:
|
||||
alias_edges[(edge[0], edge[1])] = _get_alias_edge(
|
||||
G, edge[0], edge[1], p, q, weight_key
|
||||
)
|
||||
alias_edges[(edge[1], edge[0])] = _get_alias_edge(
|
||||
G, edge[1], edge[0], p, q, weight_key
|
||||
)
|
||||
|
||||
return alias_nodes, alias_edges
|
||||
|
||||
|
||||
def _get_alias_edge(G, src, dst, p, q, weight_key=None):
|
||||
unnormalized_probs = []
|
||||
|
||||
if weight_key is None:
|
||||
for dst_nbr in sorted(G.neighbors(dst)):
|
||||
if dst_nbr == src:
|
||||
unnormalized_probs.append(1.0 / p)
|
||||
elif G.has_edge(dst_nbr, src):
|
||||
unnormalized_probs.append(1.0)
|
||||
else:
|
||||
unnormalized_probs.append(1.0 / q)
|
||||
else:
|
||||
for dst_nbr in sorted(G.neighbors(dst)):
|
||||
if dst_nbr == src:
|
||||
unnormalized_probs.append(G[dst][dst_nbr][weight_key] / p)
|
||||
elif G.has_edge(dst_nbr, src):
|
||||
unnormalized_probs.append(G[dst][dst_nbr][weight_key])
|
||||
else:
|
||||
unnormalized_probs.append(G[dst][dst_nbr][weight_key] / q)
|
||||
|
||||
norm_const = sum(unnormalized_probs)
|
||||
normalized_probs = [float(u_prob) / norm_const for u_prob in unnormalized_probs]
|
||||
|
||||
return _alias_setup(normalized_probs)
|
||||
|
||||
|
||||
def _alias_setup(probs):
|
||||
K = len(probs)
|
||||
q = np.zeros(K)
|
||||
J = np.zeros(K, dtype=int)
|
||||
|
||||
smaller = []
|
||||
larger = []
|
||||
for kk, prob in enumerate(probs):
|
||||
q[kk] = K * prob
|
||||
if q[kk] < 1.0:
|
||||
smaller.append(kk)
|
||||
else:
|
||||
larger.append(kk)
|
||||
|
||||
while len(smaller) > 0 and len(larger) > 0:
|
||||
small = smaller.pop()
|
||||
large = larger.pop()
|
||||
|
||||
J[small] = large
|
||||
q[large] = q[large] + q[small] - 1.0
|
||||
if q[large] < 1.0:
|
||||
smaller.append(large)
|
||||
else:
|
||||
larger.append(large)
|
||||
|
||||
return J, q
|
||||
|
||||
|
||||
def _node2vec_walk(G, walk_length, start_node, alias_nodes, alias_edges):
|
||||
"""
|
||||
Simulate a random walk starting from start node.
|
||||
"""
|
||||
walk = [start_node]
|
||||
|
||||
while len(walk) < walk_length:
|
||||
cur = walk[-1]
|
||||
cur_nbrs = sorted(G.neighbors(cur))
|
||||
if len(cur_nbrs) > 0:
|
||||
if len(walk) == 1:
|
||||
walk.append(
|
||||
cur_nbrs[_alias_draw(alias_nodes[cur][0], alias_nodes[cur][1])]
|
||||
)
|
||||
else:
|
||||
prev = walk[-2]
|
||||
next_node = cur_nbrs[
|
||||
_alias_draw(
|
||||
alias_edges[(prev, cur)][0], alias_edges[(prev, cur)][1]
|
||||
)
|
||||
]
|
||||
walk.append(next_node)
|
||||
else:
|
||||
break
|
||||
|
||||
return walk
|
||||
|
||||
|
||||
def _alias_draw(J, q):
|
||||
K = len(J)
|
||||
kk = int(np.floor(np.random.rand() * K))
|
||||
if np.random.rand() < q[kk]:
|
||||
return kk
|
||||
else:
|
||||
return J[kk]
|
||||
|
||||
|
||||
def learn_embeddings(walks, dimensions, **skip_gram_params):
|
||||
"""
|
||||
Learn embeddings with Word2Vec.
|
||||
"""
|
||||
from gensim.models import Word2Vec
|
||||
|
||||
walks = [list(map(str, walk)) for walk in walks]
|
||||
|
||||
if "vector_size" not in skip_gram_params:
|
||||
skip_gram_params["vector_size"] = dimensions
|
||||
|
||||
model = Word2Vec(walks, **skip_gram_params)
|
||||
|
||||
return model
|
||||
@@ -0,0 +1,280 @@
|
||||
from argparse import ArgumentDefaultsHelpFormatter
|
||||
from argparse import ArgumentParser
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
import torch.optim as optim
|
||||
|
||||
from torch.utils import data
|
||||
from torch.utils.data.dataloader import DataLoader
|
||||
|
||||
|
||||
def parse_args():
|
||||
parser = ArgumentParser(
|
||||
formatter_class=ArgumentDefaultsHelpFormatter, conflict_handler="resolve"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--output", default="node.emb", help="Output representation file"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--workers", default=8, type=int, help="Number of parallel processes."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--weighted", action="store_true", default=False, help="Treat graph as weighted"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--epochs", default=400, type=int, help="The training epochs of SDNE"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--dropout",
|
||||
default=0.05,
|
||||
type=float,
|
||||
help="Dropout rate (1 - keep probability)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--weight-decay",
|
||||
type=float,
|
||||
default=5e-4,
|
||||
help="Weight for L2 loss on embedding matrix",
|
||||
)
|
||||
parser.add_argument("--lr", default=0.006, type=float, help="learning rate")
|
||||
parser.add_argument(
|
||||
"--alpha", default=1e-2, type=float, help="alhpa is a hyperparameter in SDNE"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--beta", default=5.0, type=float, help="beta is a hyperparameter in SDNE"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--nu1", default=1e-5, type=float, help="nu1 is a hyperparameter in SDNE"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--nu2", default=1e-4, type=float, help="nu2 is a hyperparameter in SDNE"
|
||||
)
|
||||
parser.add_argument("--bs", default=100, type=int, help="batch size of SDNE")
|
||||
parser.add_argument("--nhid0", default=1000, type=int, help="The first dim")
|
||||
parser.add_argument("--nhid1", default=128, type=int, help="The second dim")
|
||||
parser.add_argument(
|
||||
"--step_size", default=10, type=int, help="The step size for lr"
|
||||
)
|
||||
parser.add_argument("--gamma", default=0.9, type=int, help="The gamma for lr")
|
||||
args = parser.parse_args()
|
||||
|
||||
return args
|
||||
|
||||
|
||||
class Dataload(data.Dataset):
|
||||
def __init__(self, Adj, Node):
|
||||
self.Adj = Adj
|
||||
self.Node = Node
|
||||
|
||||
def __getitem__(self, index):
|
||||
return index
|
||||
# adj_batch = self.Adj[index]
|
||||
# adj_mat = adj_batch[index]
|
||||
# b_mat = torch.ones_like(adj_batch)
|
||||
# b_mat[adj_batch != 0] = self.Beta
|
||||
# return adj_batch, adj_mat, b_mat
|
||||
|
||||
def __len__(self):
|
||||
return self.Node
|
||||
|
||||
|
||||
def get_adj(g):
|
||||
edges = list(g.edges)
|
||||
edges = [(edges[i][0], edges[i][1]) for i in range(len(edges))]
|
||||
# print(edges)
|
||||
edges = np.array([np.array(i) for i in edges])
|
||||
min_node, max_node = edges.min(), edges.max()
|
||||
if min_node == 0:
|
||||
Node = max_node + 1
|
||||
else:
|
||||
Node = max_node
|
||||
|
||||
Adj = np.zeros([Node, Node], dtype=int)
|
||||
for i in range(edges.shape[0]):
|
||||
g.add_edge(edges[i][0], edges[i][1])
|
||||
if min_node == 0:
|
||||
Adj[edges[i][0], edges[i][1]] = 1
|
||||
Adj[edges[i][1], edges[i][0]] = 1
|
||||
|
||||
else:
|
||||
Adj[edges[i][0] - 1, edges[i][1] - 1] = 1
|
||||
Adj[edges[i][1] - 1, edges[i][0] - 1] = 1
|
||||
Adj = torch.FloatTensor(Adj)
|
||||
return Adj, Node
|
||||
|
||||
|
||||
class SDNE(nn.Module):
|
||||
"""
|
||||
Graph embedding via SDNE.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
graph : easygraph.Graph or easygraph.DiGraph
|
||||
|
||||
node: Size of nodes
|
||||
|
||||
nhid0, nhid1: Two dimensions of two hiddenlayers, default: 128, 64
|
||||
|
||||
dropout: One parameter for regularization, default: 0.025
|
||||
|
||||
alpha, beta: Twe parameters
|
||||
graph=g: : easygraph.Graph or easygraph.DiGraph
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import easygraph as eg
|
||||
>>> model = eg.SDNE(graph=g, node_size= len(g.nodes), nhid0=128, nhid1=64, dropout=0.025, alpha=2e-2, beta=10)
|
||||
>>> emb = model.train(model, epochs, lr, bs, step_size, gamma, nu1, nu2, device, output)
|
||||
|
||||
|
||||
epochs, "--epochs", default=400, type=int, help="The training epochs of SDNE"
|
||||
|
||||
alpha, "--alpha", default=2e-2, type=float, help="alhpa is a hyperparameter in SDNE"
|
||||
|
||||
beta, "--beta", default=10.0, type=float, help="beta is a hyperparameter in SDNE"
|
||||
|
||||
lr, "--lr", default=0.006, type=float, help="learning rate"
|
||||
|
||||
bs, "--bs", default=100, type=int, help="batch size of SDNE"
|
||||
|
||||
step_size, "--step_size", default=10, type=int, help="The step size for lr"
|
||||
|
||||
gamma, # "--gamma", default=0.9, type=int, help="The gamma for lr"
|
||||
|
||||
step_size, "--step_size", default=10, type=int, help="The step size for lr"
|
||||
|
||||
nu1, # "--nu1", default=1e-5, type=float, help="nu1 is a hyperparameter in SDNE"
|
||||
|
||||
nu2, "--nu2", default=1e-4, type=float, help="nu2 is a hyperparameter in SDNE"
|
||||
|
||||
device, "-- device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") "
|
||||
|
||||
output "--output", default="node.emb", help="Output representation file"
|
||||
|
||||
|
||||
Reference
|
||||
----------
|
||||
.. [1] Wang, D., Cui, P., & Zhu, W. (2016, August). Structural deep network embedding. In Proceedings of the 22nd ACM SIGKDD international conference on Knowledge discovery and data mining (pp. 1225-1234).
|
||||
|
||||
https://www.kdd.org/kdd2016/papers/files/rfp0191-wangAemb.pdf
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, graph, node_size, nhid0, nhid1, dropout=0.06, alpha=2e-2, beta=10.0
|
||||
):
|
||||
super(SDNE, self).__init__()
|
||||
self.encode0 = nn.Linear(node_size, nhid0)
|
||||
self.encode1 = nn.Linear(nhid0, nhid1)
|
||||
self.decode0 = nn.Linear(nhid1, nhid0)
|
||||
self.decode1 = nn.Linear(nhid0, node_size)
|
||||
self.droput = dropout
|
||||
self.alpha = alpha
|
||||
self.beta = beta
|
||||
self.graph = graph
|
||||
|
||||
def forward(self, adj_batch, adj_mat, b_mat):
|
||||
t0 = F.leaky_relu(self.encode0(adj_batch))
|
||||
t0 = F.leaky_relu(self.encode1(t0))
|
||||
embedding = t0
|
||||
t0 = F.leaky_relu(self.decode0(t0))
|
||||
t0 = F.leaky_relu(self.decode1(t0))
|
||||
embedding_norm = torch.sum(embedding * embedding, dim=1, keepdim=True)
|
||||
L_1st = torch.sum(
|
||||
adj_mat
|
||||
* (
|
||||
embedding_norm
|
||||
- 2 * torch.mm(embedding, torch.transpose(embedding, dim0=0, dim1=1))
|
||||
+ torch.transpose(embedding_norm, dim0=0, dim1=1)
|
||||
)
|
||||
)
|
||||
L_2nd = torch.sum(((adj_batch - t0) * b_mat) * ((adj_batch - t0) * b_mat))
|
||||
return L_1st, self.alpha * L_2nd, L_1st + self.alpha * L_2nd
|
||||
|
||||
def train(
|
||||
self,
|
||||
model,
|
||||
epochs=100,
|
||||
lr=0.006,
|
||||
bs=100,
|
||||
step_size=10,
|
||||
gamma=0.9,
|
||||
nu1=1e-5,
|
||||
nu2=1e-4,
|
||||
device="cpu",
|
||||
output="out.emb",
|
||||
):
|
||||
Adj, Node = get_adj(self.graph)
|
||||
model = model.to(device)
|
||||
|
||||
opt = optim.Adam(model.parameters(), lr=lr)
|
||||
scheduler = torch.optim.lr_scheduler.StepLR(
|
||||
opt, step_size=step_size, gamma=gamma
|
||||
)
|
||||
Data = Dataload(Adj, Node)
|
||||
Data = DataLoader(
|
||||
Data,
|
||||
batch_size=bs,
|
||||
shuffle=True,
|
||||
)
|
||||
|
||||
for epoch in range(1, epochs + 1):
|
||||
loss_sum, loss_L1, loss_L2, loss_reg = 0, 0, 0, 0
|
||||
for index in Data:
|
||||
adj_batch = Adj[index]
|
||||
adj_mat = adj_batch[:, index]
|
||||
b_mat = torch.ones_like(adj_batch)
|
||||
b_mat[adj_batch != 0] = self.beta
|
||||
|
||||
opt.zero_grad()
|
||||
L_1st, L_2nd, L_all = model(adj_batch, adj_mat, b_mat)
|
||||
L_reg = 0
|
||||
for param in model.parameters():
|
||||
L_reg += nu1 * torch.sum(torch.abs(param)) + nu2 * torch.sum(
|
||||
param * param
|
||||
)
|
||||
Loss = L_all + L_reg
|
||||
Loss.backward()
|
||||
opt.step()
|
||||
loss_sum += Loss
|
||||
loss_L1 += L_1st
|
||||
loss_L2 += L_2nd
|
||||
loss_reg += L_reg
|
||||
scheduler.step(epoch)
|
||||
# print("The lr for epoch %d is %f" %(epoch, scheduler.get_lr()[0]))
|
||||
print("loss for epoch %d is:" % epoch)
|
||||
print("loss_sum is %f" % loss_sum)
|
||||
print("loss_L1 is %f" % loss_L1)
|
||||
print("loss_L2 is %f" % loss_L2)
|
||||
print("loss_reg is %f" % loss_reg)
|
||||
|
||||
# model.eval()
|
||||
embedding = model.savector(Adj)
|
||||
outVec = embedding.detach().numpy()
|
||||
np.savetxt(output, outVec)
|
||||
|
||||
return outVec
|
||||
|
||||
def savector(self, adj):
|
||||
t0 = self.encode0(adj)
|
||||
t0 = self.encode1(t0)
|
||||
return t0
|
||||
|
||||
|
||||
# if __name__ == '__main__':
|
||||
# args = parse_args()
|
||||
# print(args)
|
||||
# dataset = eg.CiteseerGraphDataset(force_reload=True) # Download CiteseerGraphDataset contained in EasyGraph
|
||||
# num_classes = dataset.num_classes
|
||||
# g = dataset[0]
|
||||
# print(g)
|
||||
# device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
|
||||
# adj, node = get_adj(g)
|
||||
# # labels = g.ndata['label']
|
||||
# nhid0, nhid1, dropout, alpha = args.nhid0, args.nhid1, args.dropout, args.alpha
|
||||
# model = SDNE(node, nhid0, nhid1, dropout, alpha, graph=g)
|
||||
# print(model)
|
||||
#
|
||||
# emb = model.train(args, device)
|
||||
@@ -0,0 +1,4 @@
|
||||
-1.765814423561096191e-01 2.083084881305694580e-01 -1.271556913852691650e-01 -1.702362895011901855e-01 8.119292855262756348e-01 -3.134809732437133789e-01 -9.992567449808120728e-02 -1.093881502747535706e-01
|
||||
-2.064122706651687622e-01 -1.475724577903747559e-01 -1.439859867095947266e-01 -7.331190109252929688e-01 6.787545084953308105e-01 -3.651908636093139648e-01 -9.232180565595626831e-02 -8.407155275344848633e-01
|
||||
-1.765814423561096191e-01 2.083084881305694580e-01 -1.271556913852691650e-01 -1.702362895011901855e-01 8.119292855262756348e-01 -3.134809732437133789e-01 -9.992567449808120728e-02 -1.093881502747535706e-01
|
||||
-2.064122706651687622e-01 -1.475724577903747559e-01 -1.439859867095947266e-01 -7.331190109252929688e-01 6.787545084953308105e-01 -3.651908636093139648e-01 -9.232180565595626831e-02 -8.407155275344848633e-01
|
||||
@@ -0,0 +1,101 @@
|
||||
import unittest
|
||||
|
||||
import easygraph as eg
|
||||
import numpy as np
|
||||
|
||||
|
||||
class Test_Deepwalk(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.ds = eg.datasets.get_graph_karateclub()
|
||||
self.edges = [(1, 4), (2, 4)]
|
||||
self.test_graphs = []
|
||||
self.test_graphs.append(eg.classes.DiGraph(self.edges))
|
||||
self.shs = eg.common_greedy(self.ds, int(len(self.ds.nodes) / 3))
|
||||
|
||||
self.graph = eg.Graph()
|
||||
self.graph.add_edges_from([(0, 1), (1, 2), (2, 3), (3, 4), (4, 0)])
|
||||
|
||||
self.empty_graph = eg.Graph()
|
||||
|
||||
self.single_node_graph = eg.Graph()
|
||||
self.single_node_graph.add_node(0)
|
||||
|
||||
def test_deepwalk(self):
|
||||
for i in self.test_graphs:
|
||||
print(eg.deepwalk(i))
|
||||
|
||||
def test_deepwalk_output_structure(self):
|
||||
emb, sim = eg.deepwalk(
|
||||
self.graph,
|
||||
dimensions=16,
|
||||
walk_length=5,
|
||||
num_walks=3,
|
||||
window=2,
|
||||
min_count=1,
|
||||
batch_words=4,
|
||||
epochs=5,
|
||||
)
|
||||
self.assertIsInstance(emb, dict)
|
||||
self.assertIsInstance(sim, dict)
|
||||
for k, v in emb.items():
|
||||
self.assertEqual(len(v), 16)
|
||||
self.assertTrue(isinstance(v, np.ndarray))
|
||||
|
||||
def test_deepwalk_similarity_keys_match_nodes(self):
|
||||
emb, sim = eg.deepwalk(
|
||||
self.graph,
|
||||
dimensions=8,
|
||||
walk_length=3,
|
||||
num_walks=2,
|
||||
window=2,
|
||||
min_count=1,
|
||||
batch_words=2,
|
||||
epochs=3,
|
||||
)
|
||||
self.assertEqual(set(emb.keys()), set(sim.keys()))
|
||||
self.assertEqual(set(emb.keys()), set(self.graph.nodes))
|
||||
|
||||
def test_deepwalk_on_single_node(self):
|
||||
emb, sim = eg.deepwalk(
|
||||
self.single_node_graph,
|
||||
dimensions=4,
|
||||
walk_length=2,
|
||||
num_walks=1,
|
||||
window=1,
|
||||
min_count=1,
|
||||
batch_words=2,
|
||||
epochs=2,
|
||||
)
|
||||
self.assertEqual(len(emb), 1)
|
||||
self.assertEqual(list(emb.keys()), [0])
|
||||
self.assertEqual(len(emb[0]), 4)
|
||||
|
||||
def test_deepwalk_on_empty_graph(self):
|
||||
with self.assertRaises(RuntimeError):
|
||||
eg.deepwalk(
|
||||
self.empty_graph,
|
||||
dimensions=4,
|
||||
walk_length=2,
|
||||
num_walks=1,
|
||||
window=1,
|
||||
min_count=1,
|
||||
batch_words=2,
|
||||
epochs=2,
|
||||
)
|
||||
|
||||
def test_deepwalk_walk_length_zero(self):
|
||||
emb, sim = eg.deepwalk(
|
||||
self.graph,
|
||||
dimensions=4,
|
||||
walk_length=0,
|
||||
num_walks=2,
|
||||
window=1,
|
||||
min_count=1,
|
||||
batch_words=2,
|
||||
epochs=2,
|
||||
)
|
||||
self.assertEqual(len(emb), len(self.graph.nodes))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,77 @@
|
||||
import unittest
|
||||
|
||||
import easygraph as eg
|
||||
import numpy as np
|
||||
|
||||
|
||||
class Test_LINE(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.edges = [(0, 1), (1, 2), (2, 3), (3, 4)]
|
||||
self.graph = eg.Graph()
|
||||
self.graph.add_edges_from(self.edges)
|
||||
|
||||
def test_output_is_dict_with_correct_dim(self):
|
||||
model = eg.functions.graph_embedding.LINE(
|
||||
dimension=16, walk_length=10, walk_num=5, order=1
|
||||
)
|
||||
emb = model(self.graph, return_dict=True)
|
||||
self.assertIsInstance(emb, dict)
|
||||
for v in emb.values():
|
||||
self.assertEqual(len(v), 16)
|
||||
|
||||
def test_output_as_matrix(self):
|
||||
model = eg.functions.graph_embedding.LINE(
|
||||
dimension=8, walk_length=5, walk_num=3, order=1
|
||||
)
|
||||
emb = model(self.graph, return_dict=False)
|
||||
self.assertEqual(emb.shape, (len(self.graph.nodes), 8))
|
||||
|
||||
def test_output_with_order_2(self):
|
||||
model = eg.functions.graph_embedding.LINE(
|
||||
dimension=16, walk_length=10, walk_num=5, order=2
|
||||
)
|
||||
emb = model(self.graph)
|
||||
for vec in emb.values():
|
||||
self.assertEqual(len(vec), 16)
|
||||
|
||||
def test_output_with_order_3_combination(self):
|
||||
model = eg.functions.graph_embedding.LINE(
|
||||
dimension=16, walk_length=10, walk_num=5, order=3
|
||||
)
|
||||
emb = model(self.graph)
|
||||
for vec in emb.values():
|
||||
self.assertEqual(len(vec), 16)
|
||||
|
||||
def test_directed_graph(self):
|
||||
g = eg.DiGraph()
|
||||
g.add_edges_from(self.edges)
|
||||
model = eg.functions.graph_embedding.LINE(
|
||||
dimension=8, walk_length=5, walk_num=3, order=1
|
||||
)
|
||||
emb = model(g)
|
||||
self.assertEqual(len(emb), len(g.nodes))
|
||||
|
||||
def test_empty_graph_raises(self):
|
||||
g = eg.Graph()
|
||||
model = eg.functions.graph_embedding.LINE(
|
||||
dimension=8, walk_length=5, walk_num=3, order=1
|
||||
)
|
||||
with self.assertRaises(Exception):
|
||||
_ = model(g)
|
||||
|
||||
def test_embeddings_are_normalized(self):
|
||||
model = eg.functions.graph_embedding.LINE(
|
||||
dimension=16, walk_length=10, walk_num=5, order=1
|
||||
)
|
||||
emb = model(self.graph)
|
||||
for vec in emb.values():
|
||||
norm = np.linalg.norm(vec)
|
||||
self.assertTrue(np.isclose(norm, 1.0, atol=1e-5))
|
||||
|
||||
def test_embedding_value_finiteness(self):
|
||||
model = eg.functions.graph_embedding.LINE(
|
||||
dimension=16, walk_length=10, walk_num=5, order=1
|
||||
)
|
||||
emb = model(self.graph)
|
||||
for vec in emb.values():
|
||||
self.assertTrue(np.all(np.isfinite(vec)))
|
||||
@@ -0,0 +1,57 @@
|
||||
import unittest
|
||||
|
||||
import easygraph as eg
|
||||
import easygraph.functions.graph_embedding as fn
|
||||
import numpy as np
|
||||
|
||||
|
||||
class Test_Nobe(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.ds = eg.datasets.get_graph_karateclub()
|
||||
self.edges = [(1, 4), (2, 4), (4, 1), (0, 4), (4, 3)]
|
||||
self.test_directed_graphs = [eg.DiGraph()]
|
||||
self.test_undirected_graphs = [eg.Graph(self.edges)]
|
||||
self.test_directed_graphs.append(eg.classes.DiGraph(self.edges))
|
||||
self.shs = eg.common_greedy(self.ds, int(len(self.ds.nodes) / 3))
|
||||
|
||||
self.valid_graph = eg.Graph([(0, 1), (1, 2), (2, 0), (2, 3), (3, 4)])
|
||||
self.directed_graph = eg.DiGraph([(0, 1), (1, 2)])
|
||||
self.graph_with_isolated = eg.Graph()
|
||||
self.graph_with_isolated.add_edges_from([(0, 1), (1, 2)])
|
||||
self.graph_with_isolated.add_node(3)
|
||||
self.graph_with_isolated.add_node(4)
|
||||
|
||||
def test_NOBE(self):
|
||||
fn.NOBE(self.test_undirected_graphs[0], 1)
|
||||
|
||||
def test_NOBE_GA(self):
|
||||
"""
|
||||
for i in self.test_graphs:
|
||||
eg.functions.NOBE_GA(i, K=1)
|
||||
print(i)
|
||||
"""
|
||||
fn.NOBE_GA(self.test_directed_graphs[1], 1)
|
||||
|
||||
def test_nobe_output_shape(self):
|
||||
emb = fn.NOBE(self.valid_graph, K=2)
|
||||
self.assertIsInstance(emb, np.ndarray)
|
||||
self.assertEqual(emb.shape[1], 2)
|
||||
|
||||
def test_nobe_ga_output_shape(self):
|
||||
undirected_graph = eg.Graph([(0, 1), (1, 2), (2, 3)])
|
||||
emb = fn.NOBE_GA(undirected_graph, K=2)
|
||||
self.assertIsInstance(emb, np.ndarray)
|
||||
self.assertEqual(emb.shape[1], 2)
|
||||
|
||||
def test_nobe_on_graph_with_isolated_nodes(self):
|
||||
emb = fn.NOBE(self.graph_with_isolated, K=2)
|
||||
self.assertEqual(emb.shape[0], len(self.graph_with_isolated))
|
||||
|
||||
def test_nobe_invalid_K_zero(self):
|
||||
emb = fn.NOBE(self.valid_graph, 0)
|
||||
self.assertIsInstance(emb, np.ndarray)
|
||||
self.assertEqual(emb.shape, (len(self.valid_graph), 0))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,58 @@
|
||||
import unittest
|
||||
|
||||
import easygraph as eg
|
||||
import numpy as np
|
||||
|
||||
from easygraph.functions.graph_embedding.NOBE import NOBE
|
||||
from easygraph.functions.graph_embedding.NOBE import NOBE_GA
|
||||
|
||||
|
||||
class Test_Nobe(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.ds = eg.datasets.get_graph_karateclub()
|
||||
self.edges = [(1, 4), (2, 4), (4, 1), (0, 4)]
|
||||
self.test_graphs = [eg.classes.DiGraph(self.edges)]
|
||||
self.test_undirected_graphs = [eg.classes.Graph(self.edges)]
|
||||
self.shs = eg.common_greedy(self.ds, int(len(self.ds.nodes) / 3))
|
||||
|
||||
self.valid_graph = eg.Graph([(0, 1), (1, 2), (2, 3)])
|
||||
self.directed_graph = eg.DiGraph([(0, 1), (1, 2)])
|
||||
self.graph_with_isolated = eg.Graph([(0, 1), (1, 2)])
|
||||
self.graph_with_isolated.add_node(5) # isolated node
|
||||
|
||||
#
|
||||
def test_NOBE(self):
|
||||
for i in self.test_graphs:
|
||||
NOBE(i, K=1)
|
||||
|
||||
def test_NOBE_GA(self):
|
||||
for i in self.test_undirected_graphs:
|
||||
NOBE_GA(i, K=1)
|
||||
|
||||
def test_nobe_embedding_shape(self):
|
||||
emb = NOBE(self.valid_graph, K=2)
|
||||
self.assertIsInstance(emb, np.ndarray)
|
||||
self.assertEqual(emb.shape, (len(self.valid_graph.nodes), 2))
|
||||
|
||||
def test_nobe_ga_embedding_shape(self):
|
||||
emb = NOBE_GA(self.valid_graph, K=2)
|
||||
self.assertIsInstance(emb, np.ndarray)
|
||||
self.assertEqual(emb.shape, (len(self.valid_graph.nodes), 2))
|
||||
|
||||
def test_nobe_invalid_k_zero(self):
|
||||
emb = NOBE(self.valid_graph, 0)
|
||||
self.assertIsInstance(emb, np.ndarray)
|
||||
self.assertEqual(emb.shape, (len(self.valid_graph), 0))
|
||||
|
||||
def test_nobe_ga_invalid_k_zero(self):
|
||||
emb = NOBE_GA(self.valid_graph, 0)
|
||||
self.assertIsInstance(emb, np.ndarray)
|
||||
self.assertEqual(emb.shape, (len(self.valid_graph), 0))
|
||||
|
||||
def test_nobe_with_isolated_node(self):
|
||||
emb = NOBE(self.graph_with_isolated, K=2)
|
||||
self.assertEqual(emb.shape[0], len(self.graph_with_isolated))
|
||||
|
||||
|
||||
# if __name__ == "__main__":
|
||||
# unittest.main()
|
||||
@@ -0,0 +1,107 @@
|
||||
import unittest
|
||||
|
||||
import easygraph as eg
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
|
||||
class Test_Sdne(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.ds = eg.datasets.get_graph_karateclub()
|
||||
self.edges = [
|
||||
(1, 4),
|
||||
(2, 4),
|
||||
(4, 1),
|
||||
(0, 4),
|
||||
(4, 3),
|
||||
]
|
||||
self.test_graphs = []
|
||||
self.test_graphs.append(eg.classes.DiGraph(self.edges))
|
||||
self.shs = eg.common_greedy(self.ds, int(len(self.ds.nodes) / 3))
|
||||
self.graph = eg.DiGraph()
|
||||
self.graph.add_edges_from([(0, 1), (1, 2), (2, 3), (3, 0)])
|
||||
self.device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
|
||||
|
||||
def test_sdne(self):
|
||||
sdne = eg.SDNE(
|
||||
graph=self.test_graphs[0],
|
||||
node_size=len(self.test_graphs[0].nodes),
|
||||
nhid0=128,
|
||||
nhid1=64,
|
||||
dropout=0.025,
|
||||
alpha=2e-2,
|
||||
beta=10,
|
||||
)
|
||||
# todo add test
|
||||
# emb = sdne.train(sdne)
|
||||
|
||||
def test_sdne_model_instantiation(self):
|
||||
model = eg.SDNE(
|
||||
graph=self.graph,
|
||||
node_size=len(self.graph.nodes),
|
||||
nhid0=32,
|
||||
nhid1=16,
|
||||
dropout=0.05,
|
||||
alpha=0.01,
|
||||
beta=5.0,
|
||||
)
|
||||
self.assertIsInstance(model, eg.SDNE)
|
||||
|
||||
def test_sdne_training_embedding_output(self):
|
||||
model = eg.SDNE(
|
||||
graph=self.graph,
|
||||
node_size=len(self.graph.nodes),
|
||||
nhid0=16,
|
||||
nhid1=8,
|
||||
dropout=0.05,
|
||||
alpha=0.01,
|
||||
beta=5.0,
|
||||
)
|
||||
embedding = model.train(
|
||||
model=model,
|
||||
epochs=5,
|
||||
lr=0.01,
|
||||
bs=2,
|
||||
step_size=2,
|
||||
gamma=0.9,
|
||||
nu1=1e-5,
|
||||
nu2=1e-4,
|
||||
device=self.device,
|
||||
output="test.emb",
|
||||
)
|
||||
self.assertIsInstance(embedding, np.ndarray)
|
||||
self.assertEqual(embedding.shape, (len(self.graph.nodes), 8))
|
||||
|
||||
def test_savector_output_shape(self):
|
||||
adj, _ = eg.get_adj(self.graph)
|
||||
model = eg.SDNE(
|
||||
graph=self.graph,
|
||||
node_size=len(self.graph.nodes),
|
||||
nhid0=16,
|
||||
nhid1=8,
|
||||
dropout=0.05,
|
||||
alpha=0.01,
|
||||
beta=5.0,
|
||||
)
|
||||
with torch.no_grad():
|
||||
emb = model.savector(adj)
|
||||
self.assertEqual(emb.shape, (len(self.graph.nodes), 8))
|
||||
|
||||
def test_get_adj_shape_and_symmetry(self):
|
||||
adj, node_count = eg.get_adj(self.graph)
|
||||
self.assertEqual(adj.shape[0], node_count)
|
||||
self.assertTrue(torch.equal(adj, adj.T)) # check symmetry for undirected
|
||||
|
||||
def test_training_on_empty_graph(self):
|
||||
empty_graph = eg.Graph()
|
||||
model = eg.SDNE(
|
||||
graph=empty_graph,
|
||||
node_size=0,
|
||||
nhid0=8,
|
||||
nhid1=4,
|
||||
dropout=0.05,
|
||||
alpha=0.01,
|
||||
beta=5.0,
|
||||
)
|
||||
with self.assertRaises(ValueError):
|
||||
model.train(model=model, epochs=5, device=self.device)
|
||||
Reference in New Issue
Block a user