"tools/vscode:/vscode.git/clone" did not exist on "cf19254a190d5933b9dca78d7cdbc6d6488ea64e"
Unverified Commit a9f2acf3 authored by Hongzhi (Steve), Chen's avatar Hongzhi (Steve), Chen Committed by GitHub
Browse files

[Misc] Black auto fix. (#4641)



* [Misc] Black auto fix.

* sort
Co-authored-by: default avatarSteve <ubuntu@ip-172-31-34-29.ap-northeast-1.compute.internal>
parent 08c50eb7
import time import time
import dgl
from dgl.nn.pytorch import SAGEConv
import torch import torch
import torch.nn as nn import torch.nn as nn
import torch.nn.functional as F import torch.nn.functional as F
import dgl
from dgl.nn.pytorch import SAGEConv
from .. import utils from .. import utils
class GraphSAGE(nn.Module): class GraphSAGE(nn.Module):
def __init__(self, def __init__(
self,
in_feats, in_feats,
n_hidden, n_hidden,
n_classes, n_classes,
n_layers, n_layers,
activation, activation,
dropout, dropout,
aggregator_type): aggregator_type,
):
super(GraphSAGE, self).__init__() super(GraphSAGE, self).__init__()
self.layers = nn.ModuleList() self.layers = nn.ModuleList()
self.dropout = nn.Dropout(dropout) self.dropout = nn.Dropout(dropout)
...@@ -27,7 +32,9 @@ class GraphSAGE(nn.Module): ...@@ -27,7 +32,9 @@ class GraphSAGE(nn.Module):
for i in range(n_layers - 1): for i in range(n_layers - 1):
self.layers.append(SAGEConv(n_hidden, n_hidden, aggregator_type)) self.layers.append(SAGEConv(n_hidden, n_hidden, aggregator_type))
# output layer # output layer
self.layers.append(SAGEConv(n_hidden, n_classes, aggregator_type)) # activation None self.layers.append(
SAGEConv(n_hidden, n_classes, aggregator_type)
) # activation None
def forward(self, graph, inputs): def forward(self, graph, inputs):
h = self.dropout(inputs) h = self.dropout(inputs)
...@@ -38,8 +45,9 @@ class GraphSAGE(nn.Module): ...@@ -38,8 +45,9 @@ class GraphSAGE(nn.Module):
h = self.dropout(h) h = self.dropout(h)
return h return h
@utils.benchmark('time')
@utils.parametrize('data', ['cora', 'pubmed']) @utils.benchmark("time")
@utils.parametrize("data", ["cora", "pubmed"])
def track_time(data): def track_time(data):
data = utils.process_data(data) data = utils.process_data(data)
device = utils.get_bench_device() device = utils.get_bench_device()
...@@ -47,11 +55,11 @@ def track_time(data): ...@@ -47,11 +55,11 @@ def track_time(data):
g = data[0].to(device) g = data[0].to(device)
features = g.ndata['feat'] features = g.ndata["feat"]
labels = g.ndata['label'] labels = g.ndata["label"]
train_mask = g.ndata['train_mask'] train_mask = g.ndata["train_mask"]
val_mask = g.ndata['val_mask'] val_mask = g.ndata["val_mask"]
test_mask = g.ndata['test_mask'] test_mask = g.ndata["test_mask"]
in_feats = features.shape[1] in_feats = features.shape[1]
n_classes = data.num_classes n_classes = data.num_classes
...@@ -60,16 +68,14 @@ def track_time(data): ...@@ -60,16 +68,14 @@ def track_time(data):
g = dgl.add_self_loop(g) g = dgl.add_self_loop(g)
# create model # create model
model = GraphSAGE(in_feats, 16, n_classes, 1, F.relu, 0.5, 'gcn') model = GraphSAGE(in_feats, 16, n_classes, 1, F.relu, 0.5, "gcn")
loss_fcn = torch.nn.CrossEntropyLoss() loss_fcn = torch.nn.CrossEntropyLoss()
model = model.to(device) model = model.to(device)
model.train() model.train()
# optimizer # optimizer
optimizer = torch.optim.Adam(model.parameters(), optimizer = torch.optim.Adam(model.parameters(), lr=1e-2, weight_decay=5e-4)
lr=1e-2,
weight_decay=5e-4)
# dry run # dry run
for i in range(10): for i in range(10):
......
...@@ -3,10 +3,20 @@ import torch.nn as nn ...@@ -3,10 +3,20 @@ import torch.nn as nn
import dgl import dgl
class BaseRGCN(nn.Module): class BaseRGCN(nn.Module):
def __init__(self, num_nodes, h_dim, out_dim, num_rels, num_bases, def __init__(
num_hidden_layers=1, dropout=0, self,
use_self_loop=False, use_cuda=False): num_nodes,
h_dim,
out_dim,
num_rels,
num_bases,
num_hidden_layers=1,
dropout=0,
use_self_loop=False,
use_cuda=False,
):
super(BaseRGCN, self).__init__() super(BaseRGCN, self).__init__()
self.num_nodes = num_nodes self.num_nodes = num_nodes
self.h_dim = h_dim self.h_dim = h_dim
...@@ -50,10 +60,12 @@ class BaseRGCN(nn.Module): ...@@ -50,10 +60,12 @@ class BaseRGCN(nn.Module):
h = layer(g, h, r, norm) h = layer(g, h, r, norm)
return h return h
def initializer(emb): def initializer(emb):
emb.uniform_(-1.0, 1.0) emb.uniform_(-1.0, 1.0)
return emb return emb
class RelGraphEmbedLayer(nn.Module): class RelGraphEmbedLayer(nn.Module):
r"""Embedding layer for featureless heterograph. r"""Embedding layer for featureless heterograph.
Parameters Parameters
...@@ -74,16 +86,19 @@ class RelGraphEmbedLayer(nn.Module): ...@@ -74,16 +86,19 @@ class RelGraphEmbedLayer(nn.Module):
dgl_sparse : bool, optional dgl_sparse : bool, optional
If true, use dgl.nn.NodeEmbedding otherwise use torch.nn.Embedding If true, use dgl.nn.NodeEmbedding otherwise use torch.nn.Embedding
""" """
def __init__(self,
def __init__(
self,
dev_id, dev_id,
num_nodes, num_nodes,
node_tids, node_tids,
num_of_ntype, num_of_ntype,
input_size, input_size,
embed_size, embed_size,
dgl_sparse=False): dgl_sparse=False,
):
super(RelGraphEmbedLayer, self).__init__() super(RelGraphEmbedLayer, self).__init__()
self.dev_id = th.device(dev_id if dev_id >= 0 else 'cpu') self.dev_id = th.device(dev_id if dev_id >= 0 else "cpu")
self.embed_size = embed_size self.embed_size = embed_size
self.num_nodes = num_nodes self.num_nodes = num_nodes
self.dgl_sparse = dgl_sparse self.dgl_sparse = dgl_sparse
...@@ -96,10 +111,16 @@ class RelGraphEmbedLayer(nn.Module): ...@@ -96,10 +111,16 @@ class RelGraphEmbedLayer(nn.Module):
for ntype in range(num_of_ntype): for ntype in range(num_of_ntype):
if isinstance(input_size[ntype], int): if isinstance(input_size[ntype], int):
if dgl_sparse: if dgl_sparse:
self.node_embeds[str(ntype)] = dgl.nn.NodeEmbedding(input_size[ntype], embed_size, name=str(ntype), self.node_embeds[str(ntype)] = dgl.nn.NodeEmbedding(
init_func=initializer) input_size[ntype],
embed_size,
name=str(ntype),
init_func=initializer,
)
else: else:
sparse_emb = th.nn.Embedding(input_size[ntype], embed_size, sparse=True) sparse_emb = th.nn.Embedding(
input_size[ntype], embed_size, sparse=True
)
nn.init.uniform_(sparse_emb.weight, -1.0, 1.0) nn.init.uniform_(sparse_emb.weight, -1.0, 1.0)
self.node_embeds[str(ntype)] = sparse_emb self.node_embeds[str(ntype)] = sparse_emb
else: else:
...@@ -110,8 +131,7 @@ class RelGraphEmbedLayer(nn.Module): ...@@ -110,8 +131,7 @@ class RelGraphEmbedLayer(nn.Module):
@property @property
def dgl_emb(self): def dgl_emb(self):
""" """ """
"""
if self.dgl_sparse: if self.dgl_sparse:
embs = [emb for emb in self.node_embeds.values()] embs = [emb for emb in self.node_embeds.values()]
return embs return embs
...@@ -137,15 +157,23 @@ class RelGraphEmbedLayer(nn.Module): ...@@ -137,15 +157,23 @@ class RelGraphEmbedLayer(nn.Module):
embeddings as the input of the next layer embeddings as the input of the next layer
""" """
tsd_ids = node_ids.to(self.dev_id) tsd_ids = node_ids.to(self.dev_id)
embeds = th.empty(node_ids.shape[0], self.embed_size, device=self.dev_id) embeds = th.empty(
node_ids.shape[0], self.embed_size, device=self.dev_id
)
for ntype in range(self.num_of_ntype): for ntype in range(self.num_of_ntype):
loc = node_tids == ntype loc = node_tids == ntype
if isinstance(features[ntype], int): if isinstance(features[ntype], int):
if self.dgl_sparse: if self.dgl_sparse:
embeds[loc] = self.node_embeds[str(ntype)](type_ids[loc], self.dev_id) embeds[loc] = self.node_embeds[str(ntype)](
type_ids[loc], self.dev_id
)
else: else:
embeds[loc] = self.node_embeds[str(ntype)](type_ids[loc]).to(self.dev_id) embeds[loc] = self.node_embeds[str(ntype)](
type_ids[loc]
).to(self.dev_id)
else: else:
embeds[loc] = features[ntype][type_ids[loc]].to(self.dev_id) @ self.embeds[str(ntype)].to(self.dev_id) embeds[loc] = features[ntype][type_ids[loc]].to(
self.dev_id
) @ self.embeds[str(ntype)].to(self.dev_id)
return embeds return embeds
import dgl
from dgl.nn.pytorch import RelGraphConv
import torch import torch
import torch.nn as nn import torch.nn as nn
import torch.nn.functional as F import torch.nn.functional as F
import dgl
from dgl.nn.pytorch import RelGraphConv
from . import utils from . import utils
class RGCN(nn.Module): class RGCN(nn.Module):
def __init__(self, num_nodes, h_dim, out_dim, num_rels, def __init__(
regularizer="basis", num_bases=-1, dropout=0., self,
num_nodes,
h_dim,
out_dim,
num_rels,
regularizer="basis",
num_bases=-1,
dropout=0.0,
self_loop=False, self_loop=False,
ns_mode=False): ns_mode=False,
):
super(RGCN, self).__init__() super(RGCN, self).__init__()
if num_bases == -1: if num_bases == -1:
num_bases = num_rels num_bases = num_rels
self.emb = nn.Embedding(num_nodes, h_dim) self.emb = nn.Embedding(num_nodes, h_dim)
self.conv1 = RelGraphConv(h_dim, h_dim, num_rels, regularizer, self.conv1 = RelGraphConv(
num_bases, self_loop=self_loop) h_dim, h_dim, num_rels, regularizer, num_bases, self_loop=self_loop
)
self.conv2 = RelGraphConv( self.conv2 = RelGraphConv(
h_dim, out_dim, num_rels, regularizer, num_bases, self_loop=self_loop) h_dim,
out_dim,
num_rels,
regularizer,
num_bases,
self_loop=self_loop,
)
self.dropout = nn.Dropout(dropout) self.dropout = nn.Dropout(dropout)
self.ns_mode = ns_mode self.ns_mode = ns_mode
...@@ -26,15 +44,15 @@ class RGCN(nn.Module): ...@@ -26,15 +44,15 @@ class RGCN(nn.Module):
if self.ns_mode: if self.ns_mode:
# forward for neighbor sampling # forward for neighbor sampling
x = self.emb(g[0].srcdata[dgl.NID]) x = self.emb(g[0].srcdata[dgl.NID])
h = self.conv1(g[0], x, g[0].edata[dgl.ETYPE], g[0].edata['norm']) h = self.conv1(g[0], x, g[0].edata[dgl.ETYPE], g[0].edata["norm"])
h = self.dropout(F.relu(h)) h = self.dropout(F.relu(h))
h = self.conv2(g[1], h, g[1].edata[dgl.ETYPE], g[1].edata['norm']) h = self.conv2(g[1], h, g[1].edata[dgl.ETYPE], g[1].edata["norm"])
return h return h
else: else:
x = self.emb.weight if nids is None else self.emb(nids) x = self.emb.weight if nids is None else self.emb(nids)
h = self.conv1(g, x, g.edata[dgl.ETYPE], g.edata['norm']) h = self.conv1(g, x, g.edata[dgl.ETYPE], g.edata["norm"])
h = self.dropout(F.relu(h)) h = self.dropout(F.relu(h))
h = self.conv2(g, h, g.edata[dgl.ETYPE], g.edata['norm']) h = self.conv2(g, h, g.edata[dgl.ETYPE], g.edata["norm"])
return h return h
...@@ -47,9 +65,9 @@ def load_data(data_name, get_norm=False, inv_target=False): ...@@ -47,9 +65,9 @@ def load_data(data_name, get_norm=False, inv_target=False):
num_rels = len(hg.canonical_etypes) num_rels = len(hg.canonical_etypes)
category = dataset.predict_category category = dataset.predict_category
num_classes = dataset.num_classes num_classes = dataset.num_classes
labels = hg.nodes[category].data.pop('labels') labels = hg.nodes[category].data.pop("labels")
train_mask = hg.nodes[category].data.pop('train_mask') train_mask = hg.nodes[category].data.pop("train_mask")
test_mask = hg.nodes[category].data.pop('test_mask') test_mask = hg.nodes[category].data.pop("test_mask")
train_idx = torch.nonzero(train_mask, as_tuple=False).squeeze() train_idx = torch.nonzero(train_mask, as_tuple=False).squeeze()
test_idx = torch.nonzero(test_mask, as_tuple=False).squeeze() test_idx = torch.nonzero(test_mask, as_tuple=False).squeeze()
...@@ -57,9 +75,10 @@ def load_data(data_name, get_norm=False, inv_target=False): ...@@ -57,9 +75,10 @@ def load_data(data_name, get_norm=False, inv_target=False):
# Calculate normalization weight for each edge, # Calculate normalization weight for each edge,
# 1. / d, d is the degree of the destination node # 1. / d, d is the degree of the destination node
for cetype in hg.canonical_etypes: for cetype in hg.canonical_etypes:
hg.edges[cetype].data['norm'] = dgl.norm_by_dst( hg.edges[cetype].data["norm"] = dgl.norm_by_dst(
hg, cetype).unsqueeze(1) hg, cetype
edata = ['norm'] ).unsqueeze(1)
edata = ["norm"]
else: else:
edata = None edata = None
...@@ -68,20 +87,30 @@ def load_data(data_name, get_norm=False, inv_target=False): ...@@ -68,20 +87,30 @@ def load_data(data_name, get_norm=False, inv_target=False):
g = dgl.to_homogeneous(hg, edata=edata) g = dgl.to_homogeneous(hg, edata=edata)
# Rename the fields as they can be changed by for example DataLoader # Rename the fields as they can be changed by for example DataLoader
g.ndata['ntype'] = g.ndata.pop(dgl.NTYPE) g.ndata["ntype"] = g.ndata.pop(dgl.NTYPE)
g.ndata['type_id'] = g.ndata.pop(dgl.NID) g.ndata["type_id"] = g.ndata.pop(dgl.NID)
node_ids = torch.arange(g.num_nodes()) node_ids = torch.arange(g.num_nodes())
# find out the target node ids in g # find out the target node ids in g
loc = (g.ndata['ntype'] == category_id) loc = g.ndata["ntype"] == category_id
target_idx = node_ids[loc] target_idx = node_ids[loc]
if inv_target: if inv_target:
# Map global node IDs to type-specific node IDs. This is required for # Map global node IDs to type-specific node IDs. This is required for
# looking up type-specific labels in a minibatch # looking up type-specific labels in a minibatch
inv_target = torch.empty((g.num_nodes(),), dtype=torch.int64) inv_target = torch.empty((g.num_nodes(),), dtype=torch.int64)
inv_target[target_idx] = torch.arange(0, target_idx.shape[0], inv_target[target_idx] = torch.arange(
dtype=inv_target.dtype) 0, target_idx.shape[0], dtype=inv_target.dtype
return g, num_rels, num_classes, labels, train_idx, test_idx, target_idx, inv_target )
return (
g,
num_rels,
num_classes,
labels,
train_idx,
test_idx,
target_idx,
inv_target,
)
else: else:
return g, num_rels, num_classes, labels, train_idx, test_idx, target_idx return g, num_rels, num_classes, labels, train_idx, test_idx, target_idx
from timeit import default_timer import inspect
import json import json
import os import os
import pickle import pickle
import shutil import shutil
import time
import zipfile import zipfile
import requests from functools import partial, reduce, wraps
import inspect from timeit import default_timer
import numpy as np import numpy as np
import pandas import pandas
import dgl import requests
import torch import torch
import time
from ogb.nodeproppred import DglNodePropPredDataset from ogb.nodeproppred import DglNodePropPredDataset
from functools import partial, reduce, wraps import dgl
def _download(url, path, filename): def _download(url, path, filename):
...@@ -23,18 +24,20 @@ def _download(url, path, filename): ...@@ -23,18 +24,20 @@ def _download(url, path, filename):
os.makedirs(path, exist_ok=True) os.makedirs(path, exist_ok=True)
f_remote = requests.get(url, stream=True) f_remote = requests.get(url, stream=True)
sz = f_remote.headers.get('content-length') sz = f_remote.headers.get("content-length")
assert f_remote.status_code == 200, 'fail to open {}'.format(url) assert f_remote.status_code == 200, "fail to open {}".format(url)
with open(fn, 'wb') as writer: with open(fn, "wb") as writer:
for chunk in f_remote.iter_content(chunk_size=1024*1024): for chunk in f_remote.iter_content(chunk_size=1024 * 1024):
writer.write(chunk) writer.write(chunk)
print('Download finished.') print("Download finished.")
import traceback
from _thread import start_new_thread
# GRAPH_CACHE = {} # GRAPH_CACHE = {}
import torch.multiprocessing as mp import torch.multiprocessing as mp
from _thread import start_new_thread
import traceback
def thread_wrapped_func(func): def thread_wrapped_func(func):
""" """
...@@ -64,20 +67,21 @@ def thread_wrapped_func(func): ...@@ -64,20 +67,21 @@ def thread_wrapped_func(func):
return decorated_function return decorated_function
def get_graph(name, format = None):
def get_graph(name, format=None):
# global GRAPH_CACHE # global GRAPH_CACHE
# if name in GRAPH_CACHE: # if name in GRAPH_CACHE:
# return GRAPH_CACHE[name].to(format) # return GRAPH_CACHE[name].to(format)
if isinstance(format, str): if isinstance(format, str):
format = [format] # didn't specify format format = [format] # didn't specify format
if format is None: if format is None:
format = ['csc', 'csr', 'coo'] format = ["csc", "csr", "coo"]
g = None g = None
if name == 'cora': if name == "cora":
g = dgl.data.CoraGraphDataset(verbose=False)[0] g = dgl.data.CoraGraphDataset(verbose=False)[0]
elif name == 'pubmed': elif name == "pubmed":
g = dgl.data.PubmedGraphDataset(verbose=False)[0] g = dgl.data.PubmedGraphDataset(verbose=False)[0]
elif name == 'livejournal': elif name == "livejournal":
bin_path = "/tmp/dataset/livejournal/livejournal_{}.bin".format(format) bin_path = "/tmp/dataset/livejournal/livejournal_{}.bin".format(format)
if os.path.exists(bin_path): if os.path.exists(bin_path):
g_list, _ = dgl.load_graphs(bin_path) g_list, _ = dgl.load_graphs(bin_path)
...@@ -112,32 +116,50 @@ def get_graph(name, format = None): ...@@ -112,32 +116,50 @@ def get_graph(name, format = None):
def get_ogb_graph(name): def get_ogb_graph(name):
os.symlink('/tmp/dataset/', os.path.join(os.getcwd(), 'dataset')) os.symlink("/tmp/dataset/", os.path.join(os.getcwd(), "dataset"))
data = DglNodePropPredDataset(name=name) data = DglNodePropPredDataset(name=name)
return data[0][0] return data[0][0]
def get_livejournal(): def get_livejournal():
# Same as https://snap.stanford.edu/data/soc-LiveJournal1.txt.gz # Same as https://snap.stanford.edu/data/soc-LiveJournal1.txt.gz
_download('https://dgl-asv-data.s3-us-west-2.amazonaws.com/dataset/livejournal/soc-LiveJournal1.txt.gz', _download(
'/tmp/dataset/livejournal', 'soc-LiveJournal1.txt.gz') "https://dgl-asv-data.s3-us-west-2.amazonaws.com/dataset/livejournal/soc-LiveJournal1.txt.gz",
df = pandas.read_csv('/tmp/dataset/livejournal/soc-LiveJournal1.txt.gz', sep='\t', skiprows=4, header=None, "/tmp/dataset/livejournal",
names=['src', 'dst'], compression='gzip') "soc-LiveJournal1.txt.gz",
src = df['src'].values )
dst = df['dst'].values df = pandas.read_csv(
print('construct the graph') "/tmp/dataset/livejournal/soc-LiveJournal1.txt.gz",
sep="\t",
skiprows=4,
header=None,
names=["src", "dst"],
compression="gzip",
)
src = df["src"].values
dst = df["dst"].values
print("construct the graph")
return dgl.graph((src, dst)) return dgl.graph((src, dst))
def get_friendster(): def get_friendster():
# Same as https://snap.stanford.edu/data/bigdata/communities/com-friendster.ungraph.txt.gz # Same as https://snap.stanford.edu/data/bigdata/communities/com-friendster.ungraph.txt.gz
_download('https://dgl-asv-data.s3-us-west-2.amazonaws.com/dataset/friendster/com-friendster.ungraph.txt.gz', _download(
'/tmp/dataset/friendster', 'com-friendster.ungraph.txt.gz') "https://dgl-asv-data.s3-us-west-2.amazonaws.com/dataset/friendster/com-friendster.ungraph.txt.gz",
df = pandas.read_csv('/tmp/dataset/friendster/com-friendster.ungraph.txt.gz', sep='\t', skiprows=4, header=None, "/tmp/dataset/friendster",
names=['src', 'dst'], compression='gzip') "com-friendster.ungraph.txt.gz",
src = df['src'].values )
dst = df['dst'].values df = pandas.read_csv(
print('construct the graph') "/tmp/dataset/friendster/com-friendster.ungraph.txt.gz",
sep="\t",
skiprows=4,
header=None,
names=["src", "dst"],
compression="gzip",
)
src = df["src"].values
dst = df["dst"].values
print("construct the graph")
return dgl.graph((src, dst)) return dgl.graph((src, dst))
...@@ -164,68 +186,73 @@ class OGBDataset(object): ...@@ -164,68 +186,73 @@ class OGBDataset(object):
def load_ogb_product(): def load_ogb_product():
name = 'ogbn-products' name = "ogbn-products"
os.symlink('/tmp/dataset/', os.path.join(os.getcwd(), 'dataset')) os.symlink("/tmp/dataset/", os.path.join(os.getcwd(), "dataset"))
print('load', name) print("load", name)
data = DglNodePropPredDataset(name=name) data = DglNodePropPredDataset(name=name)
print('finish loading', name) print("finish loading", name)
splitted_idx = data.get_idx_split() splitted_idx = data.get_idx_split()
graph, labels = data[0] graph, labels = data[0]
labels = labels[:, 0] labels = labels[:, 0]
graph.ndata['label'] = labels graph.ndata["label"] = labels
in_feats = graph.ndata['feat'].shape[1] in_feats = graph.ndata["feat"].shape[1]
num_labels = len(torch.unique( num_labels = len(
labels[torch.logical_not(torch.isnan(labels))])) torch.unique(labels[torch.logical_not(torch.isnan(labels))])
)
# Find the node IDs in the training, validation, and test set. # Find the node IDs in the training, validation, and test set.
train_nid, val_nid, test_nid = splitted_idx['train'], splitted_idx['valid'], splitted_idx['test'] train_nid, val_nid, test_nid = (
splitted_idx["train"],
splitted_idx["valid"],
splitted_idx["test"],
)
train_mask = torch.zeros((graph.number_of_nodes(),), dtype=torch.bool) train_mask = torch.zeros((graph.number_of_nodes(),), dtype=torch.bool)
train_mask[train_nid] = True train_mask[train_nid] = True
val_mask = torch.zeros((graph.number_of_nodes(),), dtype=torch.bool) val_mask = torch.zeros((graph.number_of_nodes(),), dtype=torch.bool)
val_mask[val_nid] = True val_mask[val_nid] = True
test_mask = torch.zeros((graph.number_of_nodes(),), dtype=torch.bool) test_mask = torch.zeros((graph.number_of_nodes(),), dtype=torch.bool)
test_mask[test_nid] = True test_mask[test_nid] = True
graph.ndata['train_mask'] = train_mask graph.ndata["train_mask"] = train_mask
graph.ndata['val_mask'] = val_mask graph.ndata["val_mask"] = val_mask
graph.ndata['test_mask'] = test_mask graph.ndata["test_mask"] = test_mask
return OGBDataset(graph, num_labels) return OGBDataset(graph, num_labels)
def load_ogb_mag(): def load_ogb_mag():
name = 'ogbn-mag' name = "ogbn-mag"
os.symlink('/tmp/dataset/', os.path.join(os.getcwd(), 'dataset')) os.symlink("/tmp/dataset/", os.path.join(os.getcwd(), "dataset"))
print('load', name) print("load", name)
dataset = DglNodePropPredDataset(name=name) dataset = DglNodePropPredDataset(name=name)
print('finish loading', name) print("finish loading", name)
split_idx = dataset.get_idx_split() split_idx = dataset.get_idx_split()
train_idx = split_idx["train"]['paper'] train_idx = split_idx["train"]["paper"]
val_idx = split_idx["valid"]['paper'] val_idx = split_idx["valid"]["paper"]
test_idx = split_idx["test"]['paper'] test_idx = split_idx["test"]["paper"]
hg_orig, labels = dataset[0] hg_orig, labels = dataset[0]
subgs = {} subgs = {}
for etype in hg_orig.canonical_etypes: for etype in hg_orig.canonical_etypes:
u, v = hg_orig.all_edges(etype=etype) u, v = hg_orig.all_edges(etype=etype)
subgs[etype] = (u, v) subgs[etype] = (u, v)
subgs[(etype[2], 'rev-'+etype[1], etype[0])] = (v, u) subgs[(etype[2], "rev-" + etype[1], etype[0])] = (v, u)
hg = dgl.heterograph(subgs) hg = dgl.heterograph(subgs)
hg.nodes['paper'].data['feat'] = hg_orig.nodes['paper'].data['feat'] hg.nodes["paper"].data["feat"] = hg_orig.nodes["paper"].data["feat"]
hg.nodes['paper'].data['labels'] = labels['paper'].squeeze() hg.nodes["paper"].data["labels"] = labels["paper"].squeeze()
train_mask = torch.zeros((hg.number_of_nodes('paper'),), dtype=torch.bool) train_mask = torch.zeros((hg.number_of_nodes("paper"),), dtype=torch.bool)
train_mask[train_idx] = True train_mask[train_idx] = True
val_mask = torch.zeros((hg.number_of_nodes('paper'),), dtype=torch.bool) val_mask = torch.zeros((hg.number_of_nodes("paper"),), dtype=torch.bool)
val_mask[val_idx] = True val_mask[val_idx] = True
test_mask = torch.zeros((hg.number_of_nodes('paper'),), dtype=torch.bool) test_mask = torch.zeros((hg.number_of_nodes("paper"),), dtype=torch.bool)
test_mask[test_idx] = True test_mask[test_idx] = True
hg.nodes['paper'].data['train_mask'] = train_mask hg.nodes["paper"].data["train_mask"] = train_mask
hg.nodes['paper'].data['val_mask'] = val_mask hg.nodes["paper"].data["val_mask"] = val_mask
hg.nodes['paper'].data['test_mask'] = test_mask hg.nodes["paper"].data["test_mask"] = test_mask
num_classes = dataset.num_classes num_classes = dataset.num_classes
return OGBDataset(hg, num_classes, 'paper') return OGBDataset(hg, num_classes, "paper")
class PinsageDataset: class PinsageDataset:
...@@ -253,30 +280,28 @@ class PinsageDataset: ...@@ -253,30 +280,28 @@ class PinsageDataset:
def load_nowplaying_rs(): def load_nowplaying_rs():
import torchtext.legacy as torchtext import torchtext.legacy as torchtext
# follow examples/pytorch/pinsage/README to create train_g.bin # follow examples/pytorch/pinsage/README to create train_g.bin
name = 'train_g.bin' name = "train_g.bin"
dataset_dir = os.path.join(os.getcwd(), 'dataset') dataset_dir = os.path.join(os.getcwd(), "dataset")
os.symlink('/tmp/dataset/', dataset_dir) os.symlink("/tmp/dataset/", dataset_dir)
dataset_path = os.path.join(dataset_dir, "nowplaying_rs", name) dataset_path = os.path.join(dataset_dir, "nowplaying_rs", name)
g_list, _ = dgl.load_graphs(dataset_path) g_list, _ = dgl.load_graphs(dataset_path)
g = g_list[0] g = g_list[0]
user_ntype = 'user' user_ntype = "user"
item_ntype = 'track' item_ntype = "track"
# Assign user and movie IDs and use them as features (to learn an individual trainable # Assign user and movie IDs and use them as features (to learn an individual trainable
# embedding for each entity) # embedding for each entity)
g.nodes[user_ntype].data['id'] = torch.arange( g.nodes[user_ntype].data["id"] = torch.arange(g.number_of_nodes(user_ntype))
g.number_of_nodes(user_ntype)) g.nodes[item_ntype].data["id"] = torch.arange(g.number_of_nodes(item_ntype))
g.nodes[item_ntype].data['id'] = torch.arange(
g.number_of_nodes(item_ntype))
# Prepare torchtext dataset and vocabulary # Prepare torchtext dataset and vocabulary
fields = {} fields = {}
examples = [] examples = []
for i in range(g.number_of_nodes(item_ntype)): for i in range(g.number_of_nodes(item_ntype)):
example = torchtext.data.Example.fromlist( example = torchtext.data.Example.fromlist([], [])
[], [])
examples.append(example) examples.append(example)
textset = torchtext.data.Dataset(examples, fields) textset = torchtext.data.Dataset(examples, fields)
...@@ -284,32 +309,32 @@ def load_nowplaying_rs(): ...@@ -284,32 +309,32 @@ def load_nowplaying_rs():
def process_data(name): def process_data(name):
if name == 'cora': if name == "cora":
return dgl.data.CoraGraphDataset() return dgl.data.CoraGraphDataset()
elif name == 'pubmed': elif name == "pubmed":
return dgl.data.PubmedGraphDataset() return dgl.data.PubmedGraphDataset()
elif name == 'aifb': elif name == "aifb":
return dgl.data.AIFBDataset() return dgl.data.AIFBDataset()
elif name == 'mutag': elif name == "mutag":
return dgl.data.MUTAGDataset() return dgl.data.MUTAGDataset()
elif name == 'bgs': elif name == "bgs":
return dgl.data.BGSDataset() return dgl.data.BGSDataset()
elif name == 'am': elif name == "am":
return dgl.data.AMDataset() return dgl.data.AMDataset()
elif name == 'reddit': elif name == "reddit":
return dgl.data.RedditDataset(self_loop=True) return dgl.data.RedditDataset(self_loop=True)
elif name == 'ogbn-products': elif name == "ogbn-products":
return load_ogb_product() return load_ogb_product()
elif name == 'ogbn-mag': elif name == "ogbn-mag":
return load_ogb_mag() return load_ogb_mag()
elif name == 'nowplaying_rs': elif name == "nowplaying_rs":
return load_nowplaying_rs() return load_nowplaying_rs()
else: else:
raise ValueError('Invalid dataset name:', name) raise ValueError("Invalid dataset name:", name)
def get_bench_device(): def get_bench_device():
device = os.environ.get('DGL_BENCH_DEVICE', 'cpu') device = os.environ.get("DGL_BENCH_DEVICE", "cpu")
if device.lower() == "gpu": if device.lower() == "gpu":
return "cuda:0" return "cuda:0"
else: else:
...@@ -335,15 +360,15 @@ def setup_track_flops(*args, **kwargs): ...@@ -335,15 +360,15 @@ def setup_track_flops(*args, **kwargs):
TRACK_UNITS = { TRACK_UNITS = {
'time': 's', "time": "s",
'acc': '%', "acc": "%",
'flops': 'GFLOPS', "flops": "GFLOPS",
} }
TRACK_SETUP = { TRACK_SETUP = {
'time': setup_track_time, "time": setup_track_time,
'acc': setup_track_acc, "acc": setup_track_acc,
'flops': setup_track_flops, "flops": setup_track_flops,
} }
...@@ -390,12 +415,13 @@ def parametrize(param_name, params): ...@@ -390,12 +415,13 @@ def parametrize(param_name, params):
def foo(x, y): def foo(x, y):
pass pass
""" """
def _wrapper(func): def _wrapper(func):
sig_params = inspect.signature(func).parameters.keys() sig_params = inspect.signature(func).parameters.keys()
num_params = len(sig_params) num_params = len(sig_params)
if getattr(func, 'params', None) is None: if getattr(func, "params", None) is None:
func.params = [None] * num_params func.params = [None] * num_params
if getattr(func, 'param_names', None) is None: if getattr(func, "param_names", None) is None:
func.param_names = [None] * num_params func.param_names = [None] * num_params
found_param = False found_param = False
for i, sig_param in enumerate(sig_params): for i, sig_param in enumerate(sig_params):
...@@ -405,16 +431,18 @@ def parametrize(param_name, params): ...@@ -405,16 +431,18 @@ def parametrize(param_name, params):
found_param = True found_param = True
break break
if not found_param: if not found_param:
raise ValueError('Invalid parameter name:', param_name) raise ValueError("Invalid parameter name:", param_name)
return func return func
return _wrapper return _wrapper
def noop_decorator(param_name, params): def noop_decorator(param_name, params):
"""noop decorator """noop decorator"""
"""
def _wrapper(func): def _wrapper(func):
return func return func
return _wrapper return _wrapper
...@@ -423,18 +451,21 @@ class TestFilter: ...@@ -423,18 +451,21 @@ class TestFilter:
self.conf = None self.conf = None
if "DGL_REG_CONF" in os.environ: if "DGL_REG_CONF" in os.environ:
current_dir = os.path.dirname(os.path.abspath(__file__)) current_dir = os.path.dirname(os.path.abspath(__file__))
path = os.path.join(current_dir, "../../", path = os.path.join(
os.environ["DGL_REG_CONF"]) current_dir, "../../", os.environ["DGL_REG_CONF"]
)
with open(path, "r") as f: with open(path, "r") as f:
self.conf = json.load(f) self.conf = json.load(f)
if "INSTANCE_TYPE" in os.environ: if "INSTANCE_TYPE" in os.environ:
instance_type = os.environ["INSTANCE_TYPE"] instance_type = os.environ["INSTANCE_TYPE"]
else: else:
raise Exception( raise Exception(
"Must set both DGL_REG_CONF and INSTANCE_TYPE as env") "Must set both DGL_REG_CONF and INSTANCE_TYPE as env"
)
self.enabled_tests = self.conf[instance_type]["tests"] self.enabled_tests = self.conf[instance_type]["tests"]
else: else:
import logging import logging
logging.warning("No regression test conf file specified") logging.warning("No regression test conf file specified")
def check(self, func): def check(self, func):
...@@ -451,7 +482,7 @@ class TestFilter: ...@@ -451,7 +482,7 @@ class TestFilter:
filter = TestFilter() filter = TestFilter()
device = os.environ.get('DGL_BENCH_DEVICE', 'cpu') device = os.environ.get("DGL_BENCH_DEVICE", "cpu")
if device == "cpu": if device == "cpu":
parametrize_cpu = parametrize parametrize_cpu = parametrize
...@@ -461,38 +492,46 @@ elif device == "gpu": ...@@ -461,38 +492,46 @@ elif device == "gpu":
parametrize_gpu = parametrize parametrize_gpu = parametrize
else: else:
raise Exception( raise Exception(
"Unknown device. Must be one of ['cpu', 'gpu'], but got {}".format(device)) "Unknown device. Must be one of ['cpu', 'gpu'], but got {}".format(
device
)
)
def skip_if_gpu(): def skip_if_gpu():
"""skip if DGL_BENCH_DEVICE is gpu """skip if DGL_BENCH_DEVICE is gpu"""
""" device = os.environ.get("DGL_BENCH_DEVICE", "cpu")
device = os.environ.get('DGL_BENCH_DEVICE', 'cpu')
def _wrapper(func): def _wrapper(func):
if device == "gpu": if device == "gpu":
# skip if not enabled # skip if not enabled
func.benchmark_name = "skip_" + func.__name__ func.benchmark_name = "skip_" + func.__name__
return func return func
return _wrapper return _wrapper
def _cuda_device_count(q): def _cuda_device_count(q):
import torch import torch
q.put(torch.cuda.device_count()) q.put(torch.cuda.device_count())
def get_num_gpu(): def get_num_gpu():
import multiprocessing as mp import multiprocessing as mp
q = mp.Queue() q = mp.Queue()
p = mp.Process(target=_cuda_device_count, args=(q, )) p = mp.Process(target=_cuda_device_count, args=(q,))
p.start() p.start()
p.join() p.join()
return q.get(block=False) return q.get(block=False)
GPU_COUNT = get_num_gpu() GPU_COUNT = get_num_gpu()
def skip_if_not_4gpu(): def skip_if_not_4gpu():
"""skip if DGL_BENCH_DEVICE is gpu """skip if DGL_BENCH_DEVICE is gpu"""
"""
def _wrapper(func): def _wrapper(func):
if GPU_COUNT != 4: if GPU_COUNT != 4:
...@@ -500,6 +539,7 @@ def skip_if_not_4gpu(): ...@@ -500,6 +539,7 @@ def skip_if_not_4gpu():
print("Skip {}".format(func.__name__)) print("Skip {}".format(func.__name__))
func.benchmark_name = "skip_" + func.__name__ func.benchmark_name = "skip_" + func.__name__
return func return func
return _wrapper return _wrapper
...@@ -525,7 +565,7 @@ def benchmark(track_type, timeout=60): ...@@ -525,7 +565,7 @@ def benchmark(track_type, timeout=60):
def foo(): def foo():
pass pass
""" """
assert track_type in ['time', 'acc', 'flops'] assert track_type in ["time", "acc", "flops"]
def _wrapper(func): def _wrapper(func):
func.unit = TRACK_UNITS[track_type] func.unit = TRACK_UNITS[track_type]
...@@ -535,8 +575,10 @@ def benchmark(track_type, timeout=60): ...@@ -535,8 +575,10 @@ def benchmark(track_type, timeout=60):
# skip if not enabled # skip if not enabled
func.benchmark_name = "skip_" + func.__name__ func.benchmark_name = "skip_" + func.__name__
return func return func
return _wrapper return _wrapper
##################################### #####################################
# Timer # Timer
##################################### #####################################
...@@ -551,7 +593,7 @@ class Timer: ...@@ -551,7 +593,7 @@ class Timer:
self.device = device self.device = device
def __enter__(self): def __enter__(self):
if self.device == 'cuda:0': if self.device == "cuda:0":
self.start_event = torch.cuda.Event(enable_timing=True) self.start_event = torch.cuda.Event(enable_timing=True)
self.end_event = torch.cuda.Event(enable_timing=True) self.end_event = torch.cuda.Event(enable_timing=True)
self.start_event.record() self.start_event.record()
...@@ -560,10 +602,11 @@ class Timer: ...@@ -560,10 +602,11 @@ class Timer:
return self return self
def __exit__(self, type, value, traceback): def __exit__(self, type, value, traceback):
if self.device == 'cuda:0': if self.device == "cuda:0":
self.end_event.record() self.end_event.record()
torch.cuda.synchronize() # Wait for the events to be recorded! torch.cuda.synchronize() # Wait for the events to be recorded!
self.elapsed_secs = self.start_event.elapsed_time( self.elapsed_secs = (
self.end_event) / 1e3 self.start_event.elapsed_time(self.end_event) / 1e3
)
else: else:
self.elapsed_secs = self.timer() - self.tic self.elapsed_secs = self.timer() - self.tic
from pathlib import Path
import json import json
from pathlib import Path
def main(): def main():
result_dir = Path(__file__).parent/ ".." / Path("results/") result_dir = Path(__file__).parent / ".." / Path("results/")
for per_machine_dir in result_dir.iterdir(): for per_machine_dir in result_dir.iterdir():
if per_machine_dir.is_dir(): if per_machine_dir.is_dir():
try: try:
machine_json = json.loads((per_machine_dir/"machine.json").read_text()) machine_json = json.loads(
(per_machine_dir / "machine.json").read_text()
)
ram = machine_json["ram"] ram = machine_json["ram"]
for f in per_machine_dir.glob("*.json"): for f in per_machine_dir.glob("*.json"):
if f.stem != "machine": if f.stem != "machine":
...@@ -21,4 +24,5 @@ def main(): ...@@ -21,4 +24,5 @@ def main():
except Exception as e: except Exception as e:
print(e) print(e)
main() main()
import pandas as pd
import json import json
from pathlib import Path
from itertools import product from itertools import product
from pathlib import Path
import pandas as pd
def get_branch_name_from_hash(hash): def get_branch_name_from_hash(hash):
import subprocess import subprocess
process = subprocess.Popen(['git', 'name-rev', '--name-only', hash],
process = subprocess.Popen(
["git", "name-rev", "--name-only", hash],
stdout=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=subprocess.PIPE) stderr=subprocess.PIPE,
)
stdout, stderr = process.communicate() stdout, stderr = process.communicate()
if len(stderr) > 0: if len(stderr) > 0:
return hash[:10] return hash[:10]
else: else:
return stdout.decode("utf-8") .strip("\n") return stdout.decode("utf-8").strip("\n")
def main(): def main():
...@@ -28,22 +32,30 @@ def main(): ...@@ -28,22 +32,30 @@ def main():
# commit_results_dict = {} # commit_results_dict = {}
per_machine_result = {} per_machine_result = {}
commit_results_json_paths = [ commit_results_json_paths = [
f for f in machine.glob("*") if f.name != "machine.json"] f for f in machine.glob("*") if f.name != "machine.json"
]
for commit in commit_results_json_paths: for commit in commit_results_json_paths:
with commit.open() as f: with commit.open() as f:
commit_result = json.load(f) commit_result = json.load(f)
commit_hash = commit_result['commit_hash'] commit_hash = commit_result["commit_hash"]
per_commit_result = {} per_commit_result = {}
for test_name, result in commit_result['results'].items(): for test_name, result in commit_result["results"].items():
per_commit_result[test_name] = [] per_commit_result[test_name] = []
if result['result'] is None: if result["result"] is None:
for test_args in product(*result['params']): for test_args in product(*result["params"]):
per_commit_result[test_name].append( per_commit_result[test_name].append(
{"params": ", ".join(test_args), "result": None}) {"params": ", ".join(test_args), "result": None}
)
else: else:
for test_args, performance_number in zip(product(*result['params']), result['result']): for test_args, performance_number in zip(
product(*result["params"]), result["result"]
):
per_commit_result[test_name].append( per_commit_result[test_name].append(
{"params": ", ".join(test_args), "result": performance_number}) {
"params": ", ".join(test_args),
"result": performance_number,
}
)
per_machine_result[commit_hash] = per_commit_result per_machine_result[commit_hash] = per_commit_result
output_results_dict[machine.name] = per_machine_result output_results_dict[machine.name] = per_machine_result
return output_results_dict return output_results_dict
...@@ -54,8 +66,8 @@ def dict_to_csv(output_results_dict): ...@@ -54,8 +66,8 @@ def dict_to_csv(output_results_dict):
benchmark_conf = json.load(f) benchmark_conf = json.load(f)
unit_dict = {} unit_dict = {}
for k, v in benchmark_conf.items(): for k, v in benchmark_conf.items():
if k != 'version': if k != "version":
unit_dict[k] = v['unit'] unit_dict[k] = v["unit"]
result_list = [] result_list = []
for machine, per_machine_result in output_results_dict.items(): for machine, per_machine_result in output_results_dict.items():
for commit, test_cases in per_machine_result.items(): for commit, test_cases in per_machine_result.items():
...@@ -65,19 +77,34 @@ def dict_to_csv(output_results_dict): ...@@ -65,19 +77,34 @@ def dict_to_csv(output_results_dict):
for test_case_name, results in test_cases.items(): for test_case_name, results in test_cases.items():
for result in results: for result in results:
result_list.append( result_list.append(
{"test_name": test_case_name, 'params': result['params'], 'unit': unit_dict[test_case_name], "number": result['result'], 'commit': branch_name, 'machine': machine}) {
"test_name": test_case_name,
"params": result["params"],
"unit": unit_dict[test_case_name],
"number": result["result"],
"commit": branch_name,
"machine": machine,
}
)
df = pd.DataFrame(result_list) df = pd.DataFrame(result_list)
return df return df
def side_by_side_view(df): def side_by_side_view(df):
commits = df['commit'].unique().tolist() commits = df["commit"].unique().tolist()
full_df = df.loc[df['commit'] == commits[0]] full_df = df.loc[df["commit"] == commits[0]]
for commit in commits[1:]: for commit in commits[1:]:
per_commit_df = df.loc[df['commit'] == commit] per_commit_df = df.loc[df["commit"] == commit]
full_df: pd.DataFrame = full_df.merge( full_df: pd.DataFrame = full_df.merge(
per_commit_df, on=['test_name', 'params', 'machine', 'unit'], how='outer', suffixes=("_{}".format(full_df.iloc[0]["commit"]), "_{}".format(per_commit_df.iloc[0]["commit"]))) per_commit_df,
full_df = full_df.loc[:, ~full_df.columns.str.startswith('commit')] on=["test_name", "params", "machine", "unit"],
how="outer",
suffixes=(
"_{}".format(full_df.iloc[0]["commit"]),
"_{}".format(per_commit_df.iloc[0]["commit"]),
),
)
full_df = full_df.loc[:, ~full_df.columns.str.startswith("commit")]
return full_df return full_df
......
import argparse import argparse
import json import json
import os import os
import re import re
def json_minify(string, strip_space=True): def json_minify(string, strip_space=True):
''' """
Based on JSON.minify.js: Based on JSON.minify.js:
https://github.com/getify/JSON.minify https://github.com/getify/JSON.minify
Contributers: Contributers:
- Pradyun S. Gedam (conditions and variable names changed) - Pradyun S. Gedam (conditions and variable names changed)
''' """
tokenizer = re.compile(r'"|(/\*)|(\*/)|(//)|\n|\r') tokenizer = re.compile(r'"|(/\*)|(\*/)|(//)|\n|\r')
in_string = False in_string = False
in_multi = False in_multi = False
...@@ -24,44 +22,48 @@ def json_minify(string, strip_space=True): ...@@ -24,44 +22,48 @@ def json_minify(string, strip_space=True):
for match in re.finditer(tokenizer, string): for match in re.finditer(tokenizer, string):
if not (in_multi or in_single): if not (in_multi or in_single):
tmp = string[index:match.start()] tmp = string[index : match.start()]
if not in_string and strip_space: if not in_string and strip_space:
# replace white space as defined in standard # replace white space as defined in standard
tmp = re.sub('[ \t\n\r]+', '', tmp) tmp = re.sub("[ \t\n\r]+", "", tmp)
new_str.append(tmp) new_str.append(tmp)
index = match.end() index = match.end()
val = match.group() val = match.group()
if val == '"' and not (in_multi or in_single): if val == '"' and not (in_multi or in_single):
escaped = re.search(r'(\\)*$', string[:match.start()]) escaped = re.search(r"(\\)*$", string[: match.start()])
# start of string or unescaped quote character to end string # start of string or unescaped quote character to end string
if not in_string or (escaped is None or len(escaped.group()) % 2 == 0): if not in_string or (
escaped is None or len(escaped.group()) % 2 == 0
):
in_string = not in_string in_string = not in_string
index -= 1 # include " character in next catch index -= 1 # include " character in next catch
elif not (in_string or in_multi or in_single): elif not (in_string or in_multi or in_single):
if val == '/*': if val == "/*":
in_multi = True in_multi = True
elif val == '//': elif val == "//":
in_single = True in_single = True
elif val == '*/' and in_multi and not (in_string or in_single): elif val == "*/" and in_multi and not (in_string or in_single):
in_multi = False in_multi = False
elif val in '\r\n' and not (in_multi or in_string) and in_single: elif val in "\r\n" and not (in_multi or in_string) and in_single:
in_single = False in_single = False
elif not ((in_multi or in_single) or (val in ' \r\n\t' and strip_space)): elif not (
(in_multi or in_single) or (val in " \r\n\t" and strip_space)
):
new_str.append(val) new_str.append(val)
new_str.append(string[index:]) new_str.append(string[index:])
content = ''.join(new_str) content = "".join(new_str)
content = content.replace(",]", "]") content = content.replace(",]", "]")
content = content.replace(",}", "}") content = content.replace(",}", "}")
return content return content
def add_prefix(branch_name): def add_prefix(branch_name):
if '/' not in branch_name: if "/" not in branch_name:
return "origin/"+branch_name return "origin/" + branch_name
else: else:
return branch_name return branch_name
......
import argparse, time import argparse
import numpy as np import time
import dgl
import mxnet as mx import mxnet as mx
from mxnet import nd, gluon import numpy as np
from mxnet import gluon, nd
from mxnet.gluon import nn from mxnet.gluon import nn
import dgl import dgl
from dgl.data import register_data_args from dgl.data import (CiteseerGraphDataset, CoraGraphDataset,
from dgl.data import CoraGraphDataset, CiteseerGraphDataset, PubmedGraphDataset PubmedGraphDataset, register_data_args)
from dgl.nn.mxnet.conv import APPNPConv from dgl.nn.mxnet.conv import APPNPConv
class APPNP(nn.Block): class APPNP(nn.Block):
def __init__(self, def __init__(
self,
g, g,
in_feats, in_feats,
hiddens, hiddens,
...@@ -19,7 +23,8 @@ class APPNP(nn.Block): ...@@ -19,7 +23,8 @@ class APPNP(nn.Block):
feat_drop, feat_drop,
edge_drop, edge_drop,
alpha, alpha,
k): k,
):
super(APPNP, self).__init__() super(APPNP, self).__init__()
self.g = g self.g = g
...@@ -51,21 +56,23 @@ class APPNP(nn.Block): ...@@ -51,21 +56,23 @@ class APPNP(nn.Block):
h = self.propagate(self.g, h) h = self.propagate(self.g, h)
return h return h
def evaluate(model, features, labels, mask): def evaluate(model, features, labels, mask):
pred = model(features).argmax(axis=1) pred = model(features).argmax(axis=1)
accuracy = ((pred == labels) * mask).sum() / mask.sum().asscalar() accuracy = ((pred == labels) * mask).sum() / mask.sum().asscalar()
return accuracy.asscalar() return accuracy.asscalar()
def main(args): def main(args):
# load and preprocess dataset # load and preprocess dataset
if args.dataset == 'cora': if args.dataset == "cora":
data = CoraGraphDataset() data = CoraGraphDataset()
elif args.dataset == 'citeseer': elif args.dataset == "citeseer":
data = CiteseerGraphDataset() data = CiteseerGraphDataset()
elif args.dataset == 'pubmed': elif args.dataset == "pubmed":
data = PubmedGraphDataset() data = PubmedGraphDataset()
else: else:
raise ValueError('Unknown dataset: {}'.format(args.dataset)) raise ValueError("Unknown dataset: {}".format(args.dataset))
g = data[0] g = data[0]
if args.gpu < 0: if args.gpu < 0:
...@@ -76,31 +83,37 @@ def main(args): ...@@ -76,31 +83,37 @@ def main(args):
ctx = mx.gpu(args.gpu) ctx = mx.gpu(args.gpu)
g = g.to(ctx) g = g.to(ctx)
features = g.ndata['feat'] features = g.ndata["feat"]
labels = mx.nd.array(g.ndata['label'], dtype="float32", ctx=ctx) labels = mx.nd.array(g.ndata["label"], dtype="float32", ctx=ctx)
train_mask = g.ndata['train_mask'] train_mask = g.ndata["train_mask"]
val_mask = g.ndata['val_mask'] val_mask = g.ndata["val_mask"]
test_mask = g.ndata['test_mask'] test_mask = g.ndata["test_mask"]
in_feats = features.shape[1] in_feats = features.shape[1]
n_classes = data.num_labels n_classes = data.num_labels
n_edges = data.graph.number_of_edges() n_edges = data.graph.number_of_edges()
print("""----Data statistics------' print(
"""----Data statistics------'
#Edges %d #Edges %d
#Classes %d #Classes %d
#Train samples %d #Train samples %d
#Val samples %d #Val samples %d
#Test samples %d""" % #Test samples %d"""
(n_edges, n_classes, % (
n_edges,
n_classes,
train_mask.sum().asscalar(), train_mask.sum().asscalar(),
val_mask.sum().asscalar(), val_mask.sum().asscalar(),
test_mask.sum().asscalar())) test_mask.sum().asscalar(),
)
)
# add self loop # add self loop
g = dgl.remove_self_loop(g) g = dgl.remove_self_loop(g)
g = dgl.add_self_loop(g) g = dgl.add_self_loop(g)
# create APPNP model # create APPNP model
model = APPNP(g, model = APPNP(
g,
in_feats, in_feats,
args.hidden_sizes, args.hidden_sizes,
n_classes, n_classes,
...@@ -108,7 +121,8 @@ def main(args): ...@@ -108,7 +121,8 @@ def main(args):
args.in_drop, args.in_drop,
args.edge_drop, args.edge_drop,
args.alpha, args.alpha,
args.k) args.k,
)
model.initialize(ctx=ctx) model.initialize(ctx=ctx)
n_train_samples = train_mask.sum().asscalar() n_train_samples = train_mask.sum().asscalar()
...@@ -116,8 +130,11 @@ def main(args): ...@@ -116,8 +130,11 @@ def main(args):
# use optimizer # use optimizer
print(model.collect_params()) print(model.collect_params())
trainer = gluon.Trainer(model.collect_params(), 'adam', trainer = gluon.Trainer(
{'learning_rate': args.lr, 'wd': args.weight_decay}) model.collect_params(),
"adam",
{"learning_rate": args.lr, "wd": args.weight_decay},
)
# initialize graph # initialize graph
dur = [] dur = []
...@@ -137,35 +154,52 @@ def main(args): ...@@ -137,35 +154,52 @@ def main(args):
loss.asscalar() loss.asscalar()
dur.append(time.time() - t0) dur.append(time.time() - t0)
acc = evaluate(model, features, labels, val_mask) acc = evaluate(model, features, labels, val_mask)
print("Epoch {:05d} | Time(s) {:.4f} | Loss {:.4f} | Accuracy {:.4f} | " print(
"ETputs(KTEPS) {:.2f}". format( "Epoch {:05d} | Time(s) {:.4f} | Loss {:.4f} | Accuracy {:.4f} | "
epoch, np.mean(dur), loss.asscalar(), acc, n_edges / np.mean(dur) / 1000)) "ETputs(KTEPS) {:.2f}".format(
epoch,
np.mean(dur),
loss.asscalar(),
acc,
n_edges / np.mean(dur) / 1000,
)
)
# test set accuracy # test set accuracy
acc = evaluate(model, features, labels, test_mask) acc = evaluate(model, features, labels, test_mask)
print("Test accuracy {:.2%}".format(acc)) print("Test accuracy {:.2%}".format(acc))
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='APPNP') if __name__ == "__main__":
parser = argparse.ArgumentParser(description="APPNP")
register_data_args(parser) register_data_args(parser)
parser.add_argument("--in-drop", type=float, default=0.5, parser.add_argument(
help="input feature dropout") "--in-drop", type=float, default=0.5, help="input feature dropout"
parser.add_argument("--edge-drop", type=float, default=0.5, )
help="edge propagation dropout") parser.add_argument(
parser.add_argument("--gpu", type=int, default=-1, "--edge-drop", type=float, default=0.5, help="edge propagation dropout"
help="gpu") )
parser.add_argument("--lr", type=float, default=1e-2, parser.add_argument("--gpu", type=int, default=-1, help="gpu")
help="learning rate") parser.add_argument("--lr", type=float, default=1e-2, help="learning rate")
parser.add_argument("--n-epochs", type=int, default=200, parser.add_argument(
help="number of training epochs") "--n-epochs", type=int, default=200, help="number of training epochs"
parser.add_argument("--hidden_sizes", type=int, nargs='+', default=[64], )
help="hidden unit sizes for appnp") parser.add_argument(
parser.add_argument("--k", type=int, default=10, "--hidden_sizes",
help="Number of propagation steps") type=int,
parser.add_argument("--alpha", type=float, default=0.1, nargs="+",
help="Teleport Probability") default=[64],
parser.add_argument("--weight-decay", type=float, default=5e-4, help="hidden unit sizes for appnp",
help="Weight for L2 loss") )
parser.add_argument(
"--k", type=int, default=10, help="Number of propagation steps"
)
parser.add_argument(
"--alpha", type=float, default=0.1, help="Teleport Probability"
)
parser.add_argument(
"--weight-decay", type=float, default=5e-4, help="Weight for L2 loss"
)
args = parser.parse_args() args = parser.parse_args()
print(args) print(args)
......
...@@ -8,11 +8,13 @@ Pytorch implementation: https://github.com/Diego999/pyGAT ...@@ -8,11 +8,13 @@ Pytorch implementation: https://github.com/Diego999/pyGAT
""" """
import mxnet.gluon.nn as nn import mxnet.gluon.nn as nn
from dgl.nn.mxnet.conv import GATConv from dgl.nn.mxnet.conv import GATConv
class GAT(nn.Block): class GAT(nn.Block):
def __init__(self, def __init__(
self,
g, g,
num_layers, num_layers,
in_dim, in_dim,
...@@ -23,26 +25,45 @@ class GAT(nn.Block): ...@@ -23,26 +25,45 @@ class GAT(nn.Block):
feat_drop, feat_drop,
attn_drop, attn_drop,
alpha, alpha,
residual): residual,
):
super(GAT, self).__init__() super(GAT, self).__init__()
self.g = g self.g = g
self.num_layers = num_layers self.num_layers = num_layers
self.gat_layers = [] self.gat_layers = []
self.activation = activation self.activation = activation
# input projection (no residual) # input projection (no residual)
self.gat_layers.append(GATConv( self.gat_layers.append(
in_dim, num_hidden, heads[0], GATConv(
feat_drop, attn_drop, alpha, False)) in_dim, num_hidden, heads[0], feat_drop, attn_drop, alpha, False
)
)
# hidden layers # hidden layers
for l in range(1, num_layers): for l in range(1, num_layers):
# due to multi-head, the in_dim = num_hidden * num_heads # due to multi-head, the in_dim = num_hidden * num_heads
self.gat_layers.append(GATConv( self.gat_layers.append(
num_hidden * heads[l-1], num_hidden, heads[l], GATConv(
feat_drop, attn_drop, alpha, residual)) num_hidden * heads[l - 1],
num_hidden,
heads[l],
feat_drop,
attn_drop,
alpha,
residual,
)
)
# output projection # output projection
self.gat_layers.append(GATConv( self.gat_layers.append(
num_hidden * heads[-2], num_classes, heads[-1], GATConv(
feat_drop, attn_drop, alpha, residual)) num_hidden * heads[-2],
num_classes,
heads[-1],
feat_drop,
attn_drop,
alpha,
residual,
)
)
for i, layer in enumerate(self.gat_layers): for i, layer in enumerate(self.gat_layers):
self.register_child(layer, "gat_layer_{}".format(i)) self.register_child(layer, "gat_layer_{}".format(i))
......
...@@ -9,20 +9,22 @@ Pytorch implementation: https://github.com/Diego999/pyGAT ...@@ -9,20 +9,22 @@ Pytorch implementation: https://github.com/Diego999/pyGAT
""" """
import argparse import argparse
import networkx as nx
import time import time
import mxnet as mx import mxnet as mx
from mxnet import gluon import networkx as nx
import numpy as np import numpy as np
import dgl
from dgl.data import register_data_args
from dgl.data import CoraGraphDataset, CiteseerGraphDataset, PubmedGraphDataset
from gat import GAT from gat import GAT
from mxnet import gluon
from utils import EarlyStopping from utils import EarlyStopping
import dgl
from dgl.data import (CiteseerGraphDataset, CoraGraphDataset,
PubmedGraphDataset, register_data_args)
def elu(data): def elu(data):
return mx.nd.LeakyReLU(data, act_type='elu') return mx.nd.LeakyReLU(data, act_type="elu")
def evaluate(model, features, labels, mask): def evaluate(model, features, labels, mask):
...@@ -36,14 +38,14 @@ def evaluate(model, features, labels, mask): ...@@ -36,14 +38,14 @@ def evaluate(model, features, labels, mask):
def main(args): def main(args):
# load and preprocess dataset # load and preprocess dataset
if args.dataset == 'cora': if args.dataset == "cora":
data = CoraGraphDataset() data = CoraGraphDataset()
elif args.dataset == 'citeseer': elif args.dataset == "citeseer":
data = CiteseerGraphDataset() data = CiteseerGraphDataset()
elif args.dataset == 'pubmed': elif args.dataset == "pubmed":
data = PubmedGraphDataset() data = PubmedGraphDataset()
else: else:
raise ValueError('Unknown dataset: {}'.format(args.dataset)) raise ValueError("Unknown dataset: {}".format(args.dataset))
g = data[0] g = data[0]
if args.gpu < 0: if args.gpu < 0:
...@@ -54,13 +56,13 @@ def main(args): ...@@ -54,13 +56,13 @@ def main(args):
ctx = mx.gpu(args.gpu) ctx = mx.gpu(args.gpu)
g = g.to(ctx) g = g.to(ctx)
features = g.ndata['feat'] features = g.ndata["feat"]
labels = mx.nd.array(g.ndata['label'], dtype="float32", ctx=ctx) labels = mx.nd.array(g.ndata["label"], dtype="float32", ctx=ctx)
mask = g.ndata['train_mask'] mask = g.ndata["train_mask"]
mask = mx.nd.array(np.nonzero(mask.asnumpy())[0], ctx=ctx) mask = mx.nd.array(np.nonzero(mask.asnumpy())[0], ctx=ctx)
val_mask = g.ndata['val_mask'] val_mask = g.ndata["val_mask"]
val_mask = mx.nd.array(np.nonzero(val_mask.asnumpy())[0], ctx=ctx) val_mask = mx.nd.array(np.nonzero(val_mask.asnumpy())[0], ctx=ctx)
test_mask = g.ndata['test_mask'] test_mask = g.ndata["test_mask"]
test_mask = mx.nd.array(np.nonzero(test_mask.asnumpy())[0], ctx=ctx) test_mask = mx.nd.array(np.nonzero(test_mask.asnumpy())[0], ctx=ctx)
in_feats = features.shape[1] in_feats = features.shape[1]
n_classes = data.num_labels n_classes = data.num_labels
...@@ -70,7 +72,8 @@ def main(args): ...@@ -70,7 +72,8 @@ def main(args):
g = dgl.add_self_loop(g) g = dgl.add_self_loop(g)
# create model # create model
heads = ([args.num_heads] * args.num_layers) + [args.num_out_heads] heads = ([args.num_heads] * args.num_layers) + [args.num_out_heads]
model = GAT(g, model = GAT(
g,
args.num_layers, args.num_layers,
in_feats, in_feats,
args.num_hidden, args.num_hidden,
...@@ -80,14 +83,17 @@ def main(args): ...@@ -80,14 +83,17 @@ def main(args):
args.in_drop, args.in_drop,
args.attn_drop, args.attn_drop,
args.alpha, args.alpha,
args.residual) args.residual,
)
if args.early_stop: if args.early_stop:
stopper = EarlyStopping(patience=100) stopper = EarlyStopping(patience=100)
model.initialize(ctx=ctx) model.initialize(ctx=ctx)
# use optimizer # use optimizer
trainer = gluon.Trainer(model.collect_params(), 'adam', {'learning_rate': args.lr}) trainer = gluon.Trainer(
model.collect_params(), "adam", {"learning_rate": args.lr}
)
dur = [] dur = []
for epoch in range(args.epochs): for epoch in range(args.epochs):
...@@ -96,14 +102,22 @@ def main(args): ...@@ -96,14 +102,22 @@ def main(args):
# forward # forward
with mx.autograd.record(): with mx.autograd.record():
logits = model(features) logits = model(features)
loss = mx.nd.softmax_cross_entropy(logits[mask].squeeze(), labels[mask].squeeze()) loss = mx.nd.softmax_cross_entropy(
logits[mask].squeeze(), labels[mask].squeeze()
)
loss.backward() loss.backward()
trainer.step(mask.shape[0]) trainer.step(mask.shape[0])
if epoch >= 3: if epoch >= 3:
dur.append(time.time() - t0) dur.append(time.time() - t0)
print("Epoch {:05d} | Loss {:.4f} | Time(s) {:.4f} | ETputs(KTEPS) {:.2f}".format( print(
epoch, loss.asnumpy()[0], np.mean(dur), n_edges / np.mean(dur) / 1000)) "Epoch {:05d} | Loss {:.4f} | Time(s) {:.4f} | ETputs(KTEPS) {:.2f}".format(
epoch,
loss.asnumpy()[0],
np.mean(dur),
n_edges / np.mean(dur) / 1000,
)
)
val_accuracy = evaluate(model, features, labels, val_mask) val_accuracy = evaluate(model, features, labels, val_mask)
print("Validation Accuracy {:.4f}".format(val_accuracy)) print("Validation Accuracy {:.4f}".format(val_accuracy))
if args.early_stop: if args.early_stop:
...@@ -112,41 +126,70 @@ def main(args): ...@@ -112,41 +126,70 @@ def main(args):
print() print()
if args.early_stop: if args.early_stop:
model.load_parameters('model.param') model.load_parameters("model.param")
test_accuracy = evaluate(model, features, labels, test_mask) test_accuracy = evaluate(model, features, labels, test_mask)
print("Test Accuracy {:.4f}".format(test_accuracy)) print("Test Accuracy {:.4f}".format(test_accuracy))
if __name__ == '__main__': if __name__ == "__main__":
parser = argparse.ArgumentParser(description='GAT') parser = argparse.ArgumentParser(description="GAT")
register_data_args(parser) register_data_args(parser)
parser.add_argument("--gpu", type=int, default=-1, parser.add_argument(
help="which GPU to use. Set -1 to use CPU.") "--gpu",
parser.add_argument("--epochs", type=int, default=200, type=int,
help="number of training epochs") default=-1,
parser.add_argument("--num-heads", type=int, default=8, help="which GPU to use. Set -1 to use CPU.",
help="number of hidden attention heads") )
parser.add_argument("--num-out-heads", type=int, default=1, parser.add_argument(
help="number of output attention heads") "--epochs", type=int, default=200, help="number of training epochs"
parser.add_argument("--num-layers", type=int, default=1, )
help="number of hidden layers") parser.add_argument(
parser.add_argument("--num-hidden", type=int, default=8, "--num-heads",
help="number of hidden units") type=int,
parser.add_argument("--residual", action="store_true", default=False, default=8,
help="use residual connection") help="number of hidden attention heads",
parser.add_argument("--in-drop", type=float, default=.6, )
help="input feature dropout") parser.add_argument(
parser.add_argument("--attn-drop", type=float, default=.6, "--num-out-heads",
help="attention dropout") type=int,
parser.add_argument("--lr", type=float, default=0.005, default=1,
help="learning rate") help="number of output attention heads",
parser.add_argument('--weight-decay', type=float, default=5e-4, )
help="weight decay") parser.add_argument(
parser.add_argument('--alpha', type=float, default=0.2, "--num-layers", type=int, default=1, help="number of hidden layers"
help="the negative slop of leaky relu") )
parser.add_argument('--early-stop', action='store_true', default=False, parser.add_argument(
help="indicates whether to use early stop or not") "--num-hidden", type=int, default=8, help="number of hidden units"
)
parser.add_argument(
"--residual",
action="store_true",
default=False,
help="use residual connection",
)
parser.add_argument(
"--in-drop", type=float, default=0.6, help="input feature dropout"
)
parser.add_argument(
"--attn-drop", type=float, default=0.6, help="attention dropout"
)
parser.add_argument("--lr", type=float, default=0.005, help="learning rate")
parser.add_argument(
"--weight-decay", type=float, default=5e-4, help="weight decay"
)
parser.add_argument(
"--alpha",
type=float,
default=0.2,
help="the negative slop of leaky relu",
)
parser.add_argument(
"--early-stop",
action="store_true",
default=False,
help="indicates whether to use early stop or not",
)
args = parser.parse_args() args = parser.parse_args()
print(args) print(args)
......
import numpy as np import numpy as np
class EarlyStopping: class EarlyStopping:
def __init__(self, patience=10): def __init__(self, patience=10):
self.patience = patience self.patience = patience
...@@ -14,7 +15,9 @@ class EarlyStopping: ...@@ -14,7 +15,9 @@ class EarlyStopping:
self.save_checkpoint(model) self.save_checkpoint(model)
elif score < self.best_score: elif score < self.best_score:
self.counter += 1 self.counter += 1
print(f'EarlyStopping counter: {self.counter} out of {self.patience}') print(
f"EarlyStopping counter: {self.counter} out of {self.patience}"
)
if self.counter >= self.patience: if self.counter >= self.patience:
self.early_stop = True self.early_stop = True
else: else:
...@@ -24,5 +27,5 @@ class EarlyStopping: ...@@ -24,5 +27,5 @@ class EarlyStopping:
return self.early_stop return self.early_stop
def save_checkpoint(self, model): def save_checkpoint(self, model):
'''Saves model when validation loss decrease.''' """Saves model when validation loss decrease."""
model.save_parameters('model.param') model.save_parameters("model.param")
...@@ -7,18 +7,15 @@ References: ...@@ -7,18 +7,15 @@ References:
""" """
import mxnet as mx import mxnet as mx
from mxnet import gluon from mxnet import gluon
import dgl import dgl
from dgl.nn.mxnet import GraphConv from dgl.nn.mxnet import GraphConv
class GCN(gluon.Block): class GCN(gluon.Block):
def __init__(self, def __init__(
g, self, g, in_feats, n_hidden, n_classes, n_layers, activation, dropout
in_feats, ):
n_hidden,
n_classes,
n_layers,
activation,
dropout):
super(GCN, self).__init__() super(GCN, self).__init__()
self.g = g self.g = g
self.layers = gluon.nn.Sequential() self.layers = gluon.nn.Sequential()
...@@ -26,7 +23,9 @@ class GCN(gluon.Block): ...@@ -26,7 +23,9 @@ class GCN(gluon.Block):
self.layers.add(GraphConv(in_feats, n_hidden, activation=activation)) self.layers.add(GraphConv(in_feats, n_hidden, activation=activation))
# hidden layers # hidden layers
for i in range(n_layers - 1): for i in range(n_layers - 1):
self.layers.add(GraphConv(n_hidden, n_hidden, activation=activation)) self.layers.add(
GraphConv(n_hidden, n_hidden, activation=activation)
)
# output layer # output layer
self.layers.add(GraphConv(n_hidden, n_classes)) self.layers.add(GraphConv(n_hidden, n_classes))
self.dropout = gluon.nn.Dropout(rate=dropout) self.dropout = gluon.nn.Dropout(rate=dropout)
......
...@@ -8,14 +8,15 @@ References: ...@@ -8,14 +8,15 @@ References:
import mxnet as mx import mxnet as mx
from mxnet import gluon from mxnet import gluon
def gcn_msg(edge): def gcn_msg(edge):
msg = edge.src['h'] * edge.src['norm'] msg = edge.src["h"] * edge.src["norm"]
return {'m': msg} return {"m": msg}
def gcn_reduce(node): def gcn_reduce(node):
accum = mx.nd.sum(node.mailbox['m'], 1) * node.data['norm'] accum = mx.nd.sum(node.mailbox["m"], 1) * node.data["norm"]
return {'h': accum} return {"h": accum}
class NodeUpdate(gluon.Block): class NodeUpdate(gluon.Block):
...@@ -23,66 +24,59 @@ class NodeUpdate(gluon.Block): ...@@ -23,66 +24,59 @@ class NodeUpdate(gluon.Block):
super(NodeUpdate, self).__init__() super(NodeUpdate, self).__init__()
with self.name_scope(): with self.name_scope():
if bias: if bias:
self.bias = self.params.get('bias', shape=(out_feats,), self.bias = self.params.get(
init=mx.init.Zero()) "bias", shape=(out_feats,), init=mx.init.Zero()
)
else: else:
self.bias = None self.bias = None
self.activation = activation self.activation = activation
def forward(self, node): def forward(self, node):
h = node.data['h'] h = node.data["h"]
if self.bias is not None: if self.bias is not None:
h = h + self.bias.data(h.context) h = h + self.bias.data(h.context)
if self.activation: if self.activation:
h = self.activation(h) h = self.activation(h)
return {'h': h} return {"h": h}
class GCNLayer(gluon.Block): class GCNLayer(gluon.Block):
def __init__(self, def __init__(self, g, in_feats, out_feats, activation, dropout, bias=True):
g,
in_feats,
out_feats,
activation,
dropout,
bias=True):
super(GCNLayer, self).__init__() super(GCNLayer, self).__init__()
self.g = g self.g = g
self.dropout = dropout self.dropout = dropout
with self.name_scope(): with self.name_scope():
self.weight = self.params.get('weight', shape=(in_feats, out_feats), self.weight = self.params.get(
init=mx.init.Xavier()) "weight", shape=(in_feats, out_feats), init=mx.init.Xavier()
)
self.node_update = NodeUpdate(out_feats, activation, bias) self.node_update = NodeUpdate(out_feats, activation, bias)
def forward(self, h): def forward(self, h):
if self.dropout: if self.dropout:
h = mx.nd.Dropout(h, p=self.dropout) h = mx.nd.Dropout(h, p=self.dropout)
h = mx.nd.dot(h, self.weight.data(h.context)) h = mx.nd.dot(h, self.weight.data(h.context))
self.g.ndata['h'] = h self.g.ndata["h"] = h
self.g.update_all(gcn_msg, gcn_reduce, self.node_update) self.g.update_all(gcn_msg, gcn_reduce, self.node_update)
h = self.g.ndata.pop('h') h = self.g.ndata.pop("h")
return h return h
class GCN(gluon.Block): class GCN(gluon.Block):
def __init__(self, def __init__(
g, self, g, in_feats, n_hidden, n_classes, n_layers, activation, dropout
in_feats, ):
n_hidden,
n_classes,
n_layers,
activation,
dropout):
super(GCN, self).__init__() super(GCN, self).__init__()
self.layers = gluon.nn.Sequential() self.layers = gluon.nn.Sequential()
# input layer # input layer
self.layers.add(GCNLayer(g, in_feats, n_hidden, activation, 0)) self.layers.add(GCNLayer(g, in_feats, n_hidden, activation, 0))
# hidden layers # hidden layers
for i in range(n_layers - 1): for i in range(n_layers - 1):
self.layers.add(GCNLayer(g, n_hidden, n_hidden, activation, dropout)) self.layers.add(
GCNLayer(g, n_hidden, n_hidden, activation, dropout)
)
# output layer # output layer
self.layers.add(GCNLayer(g, n_hidden, n_classes, None, dropout)) self.layers.add(GCNLayer(g, n_hidden, n_classes, None, dropout))
def forward(self, features): def forward(self, features):
h = features h = features
for layer in self.layers: for layer in self.layers:
......
"""Training GCN model on citation graphs.""" """Training GCN model on citation graphs."""
import argparse, time import argparse
import numpy as np import time
import mxnet as mx import mxnet as mx
import numpy as np
from gcn import GCN
from mxnet import gluon from mxnet import gluon
import dgl import dgl
from dgl.data import CoraGraphDataset, CiteseerGraphDataset, PubmedGraphDataset from dgl.data import CiteseerGraphDataset, CoraGraphDataset, PubmedGraphDataset
# from gcn_mp import GCN
# from gcn_spmv import GCN
from gcn import GCN
#from gcn_mp import GCN
#from gcn_spmv import GCN
def evaluate(model, features, labels, mask): def evaluate(model, features, labels, mask):
pred = model(features).argmax(axis=1) pred = model(features).argmax(axis=1)
accuracy = ((pred == labels) * mask).sum() / mask.sum().asscalar() accuracy = ((pred == labels) * mask).sum() / mask.sum().asscalar()
return accuracy.asscalar() return accuracy.asscalar()
def main(args): def main(args):
# load and preprocess dataset # load and preprocess dataset
if args.dataset == 'cora': if args.dataset == "cora":
data = CoraGraphDataset() data = CoraGraphDataset()
elif args.dataset == 'citeseer': elif args.dataset == "citeseer":
data = CiteseerGraphDataset() data = CiteseerGraphDataset()
elif args.dataset == 'pubmed': elif args.dataset == "pubmed":
data = PubmedGraphDataset() data = PubmedGraphDataset()
else: else:
raise ValueError('Unknown dataset: {}'.format(args.dataset)) raise ValueError("Unknown dataset: {}".format(args.dataset))
g = data[0] g = data[0]
if args.gpu < 0: if args.gpu < 0:
...@@ -36,51 +40,61 @@ def main(args): ...@@ -36,51 +40,61 @@ def main(args):
ctx = mx.gpu(args.gpu) ctx = mx.gpu(args.gpu)
g = g.int().to(ctx) g = g.int().to(ctx)
features = g.ndata['feat'] features = g.ndata["feat"]
labels = mx.nd.array(g.ndata['label'], dtype="float32", ctx=ctx) labels = mx.nd.array(g.ndata["label"], dtype="float32", ctx=ctx)
train_mask = g.ndata['train_mask'] train_mask = g.ndata["train_mask"]
val_mask = g.ndata['val_mask'] val_mask = g.ndata["val_mask"]
test_mask = g.ndata['test_mask'] test_mask = g.ndata["test_mask"]
in_feats = features.shape[1] in_feats = features.shape[1]
n_classes = data.num_labels n_classes = data.num_labels
n_edges = data.graph.number_of_edges() n_edges = data.graph.number_of_edges()
print("""----Data statistics------' print(
"""----Data statistics------'
#Edges %d #Edges %d
#Classes %d #Classes %d
#Train samples %d #Train samples %d
#Val samples %d #Val samples %d
#Test samples %d""" % #Test samples %d"""
(n_edges, n_classes, % (
n_edges,
n_classes,
train_mask.sum().asscalar(), train_mask.sum().asscalar(),
val_mask.sum().asscalar(), val_mask.sum().asscalar(),
test_mask.sum().asscalar())) test_mask.sum().asscalar(),
)
)
# add self loop # add self loop
if args.self_loop: if args.self_loop:
g = dgl.remove_self_loop(g) g = dgl.remove_self_loop(g)
g = dgl.add_self_loop(g) g = dgl.add_self_loop(g)
# normalization # normalization
degs = g.in_degrees().astype('float32') degs = g.in_degrees().astype("float32")
norm = mx.nd.power(degs, -0.5) norm = mx.nd.power(degs, -0.5)
if cuda: if cuda:
norm = norm.as_in_context(ctx) norm = norm.as_in_context(ctx)
g.ndata['norm'] = mx.nd.expand_dims(norm, 1) g.ndata["norm"] = mx.nd.expand_dims(norm, 1)
model = GCN(g, model = GCN(
g,
in_feats, in_feats,
args.n_hidden, args.n_hidden,
n_classes, n_classes,
args.n_layers, args.n_layers,
mx.nd.relu, mx.nd.relu,
args.dropout) args.dropout,
)
model.initialize(ctx=ctx) model.initialize(ctx=ctx)
n_train_samples = train_mask.sum().asscalar() n_train_samples = train_mask.sum().asscalar()
loss_fcn = gluon.loss.SoftmaxCELoss() loss_fcn = gluon.loss.SoftmaxCELoss()
# use optimizer # use optimizer
print(model.collect_params()) print(model.collect_params())
trainer = gluon.Trainer(model.collect_params(), 'adam', trainer = gluon.Trainer(
{'learning_rate': args.lr, 'wd': args.weight_decay}) model.collect_params(),
"adam",
{"learning_rate": args.lr, "wd": args.weight_decay},
)
# initialize graph # initialize graph
dur = [] dur = []
...@@ -100,34 +114,52 @@ def main(args): ...@@ -100,34 +114,52 @@ def main(args):
loss.asscalar() loss.asscalar()
dur.append(time.time() - t0) dur.append(time.time() - t0)
acc = evaluate(model, features, labels, val_mask) acc = evaluate(model, features, labels, val_mask)
print("Epoch {:05d} | Time(s) {:.4f} | Loss {:.4f} | Accuracy {:.4f} | " print(
"ETputs(KTEPS) {:.2f}". format( "Epoch {:05d} | Time(s) {:.4f} | Loss {:.4f} | Accuracy {:.4f} | "
epoch, np.mean(dur), loss.asscalar(), acc, n_edges / np.mean(dur) / 1000)) "ETputs(KTEPS) {:.2f}".format(
epoch,
np.mean(dur),
loss.asscalar(),
acc,
n_edges / np.mean(dur) / 1000,
)
)
# test set accuracy # test set accuracy
acc = evaluate(model, features, labels, test_mask) acc = evaluate(model, features, labels, test_mask)
print("Test accuracy {:.2%}".format(acc)) print("Test accuracy {:.2%}".format(acc))
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='GCN') if __name__ == "__main__":
parser.add_argument("--dataset", type=str, default="cora", parser = argparse.ArgumentParser(description="GCN")
help="Dataset name ('cora', 'citeseer', 'pubmed').") parser.add_argument(
parser.add_argument("--dropout", type=float, default=0.5, "--dataset",
help="dropout probability") type=str,
parser.add_argument("--gpu", type=int, default=-1, default="cora",
help="gpu") help="Dataset name ('cora', 'citeseer', 'pubmed').",
parser.add_argument("--lr", type=float, default=3e-2, )
help="learning rate") parser.add_argument(
parser.add_argument("--n-epochs", type=int, default=200, "--dropout", type=float, default=0.5, help="dropout probability"
help="number of training epochs") )
parser.add_argument("--n-hidden", type=int, default=16, parser.add_argument("--gpu", type=int, default=-1, help="gpu")
help="number of hidden gcn units") parser.add_argument("--lr", type=float, default=3e-2, help="learning rate")
parser.add_argument("--n-layers", type=int, default=1, parser.add_argument(
help="number of hidden gcn layers") "--n-epochs", type=int, default=200, help="number of training epochs"
parser.add_argument("--weight-decay", type=float, default=5e-4, )
help="Weight for L2 loss") parser.add_argument(
parser.add_argument("--self-loop", action='store_true', "--n-hidden", type=int, default=16, help="number of hidden gcn units"
help="graph self-loop (default=False)") )
parser.add_argument(
"--n-layers", type=int, default=1, help="number of hidden gcn layers"
)
parser.add_argument(
"--weight-decay", type=float, default=5e-4, help="Weight for L2 loss"
)
parser.add_argument(
"--self-loop",
action="store_true",
help="graph self-loop (default=False)",
)
parser.set_defaults(self_loop=False) parser.set_defaults(self_loop=False)
args = parser.parse_args() args = parser.parse_args()
......
...@@ -2,24 +2,29 @@ ...@@ -2,24 +2,29 @@
MxNet compatible dataloader MxNet compatible dataloader
""" """
from mxnet.gluon.data import DataLoader, Sampler
import math import math
import numpy as np import numpy as np
from mxnet import nd from mxnet import nd
from mxnet.gluon.data import DataLoader, Sampler
from sklearn.model_selection import StratifiedKFold from sklearn.model_selection import StratifiedKFold
import dgl import dgl
class SubsetRandomSampler(Sampler): class SubsetRandomSampler(Sampler):
def __init__(self, indices): def __init__(self, indices):
self.indices = indices self.indices = indices
def __iter__(self): def __iter__(self):
return iter([self.indices[i] for i in np.random.permutation(len(self.indices))]) return iter(
[self.indices[i] for i in np.random.permutation(len(self.indices))]
)
def __len__(self): def __len__(self):
return len(self.indices) return len(self.indices)
# default collate function # default collate function
def collate(samples): def collate(samples):
# The input `samples` is a list of pairs (graph, label). # The input `samples` is a list of pairs (graph, label).
...@@ -34,28 +39,33 @@ def collate(samples): ...@@ -34,28 +39,33 @@ def collate(samples):
labels = nd.concat(*labels, dim=0) labels = nd.concat(*labels, dim=0)
return batched_graph, labels return batched_graph, labels
class GraphDataLoader():
def __init__(self, class GraphDataLoader:
def __init__(
self,
dataset, dataset,
batch_size, batch_size,
collate_fn=collate, collate_fn=collate,
seed=0, seed=0,
shuffle=True, shuffle=True,
split_name='fold10', split_name="fold10",
fold_idx=0, fold_idx=0,
split_ratio=0.7): split_ratio=0.7,
):
self.shuffle = shuffle self.shuffle = shuffle
self.seed = seed self.seed = seed
labels = [l for _, l in dataset] labels = [l for _, l in dataset]
if split_name == 'fold10': if split_name == "fold10":
train_idx, valid_idx = self._split_fold10( train_idx, valid_idx = self._split_fold10(
labels, fold_idx, seed, shuffle) labels, fold_idx, seed, shuffle
elif split_name == 'rand': )
elif split_name == "rand":
train_idx, valid_idx = self._split_rand( train_idx, valid_idx = self._split_rand(
labels, split_ratio, seed, shuffle) labels, split_ratio, seed, shuffle
)
else: else:
raise NotImplementedError() raise NotImplementedError()
...@@ -63,29 +73,36 @@ class GraphDataLoader(): ...@@ -63,29 +73,36 @@ class GraphDataLoader():
valid_sampler = SubsetRandomSampler(valid_idx) valid_sampler = SubsetRandomSampler(valid_idx)
self.train_loader = DataLoader( self.train_loader = DataLoader(
dataset, sampler=train_sampler, dataset,
batch_size=batch_size, batchify_fn=collate_fn) sampler=train_sampler,
batch_size=batch_size,
batchify_fn=collate_fn,
)
self.valid_loader = DataLoader( self.valid_loader = DataLoader(
dataset, sampler=valid_sampler, dataset,
batch_size=batch_size, batchify_fn=collate_fn) sampler=valid_sampler,
batch_size=batch_size,
batchify_fn=collate_fn,
)
def train_valid_loader(self): def train_valid_loader(self):
return self.train_loader, self.valid_loader return self.train_loader, self.valid_loader
def _split_fold10(self, labels, fold_idx=0, seed=0, shuffle=True): def _split_fold10(self, labels, fold_idx=0, seed=0, shuffle=True):
''' 10 flod ''' """10 flod"""
assert 0 <= fold_idx and fold_idx < 10, print( assert 0 <= fold_idx and fold_idx < 10, print(
"fold_idx must be from 0 to 9.") "fold_idx must be from 0 to 9."
)
skf = StratifiedKFold(n_splits=10, shuffle=shuffle, random_state=seed) skf = StratifiedKFold(n_splits=10, shuffle=shuffle, random_state=seed)
idx_list = [] idx_list = []
for idx in skf.split(np.zeros(len(labels)), [label.asnumpy() for label in labels]): # split(x, y) for idx in skf.split(
np.zeros(len(labels)), [label.asnumpy() for label in labels]
): # split(x, y)
idx_list.append(idx) idx_list.append(idx)
train_idx, valid_idx = idx_list[fold_idx] train_idx, valid_idx = idx_list[fold_idx]
print( print("train_set : test_set = %d : %d", len(train_idx), len(valid_idx))
"train_set : test_set = %d : %d",
len(train_idx), len(valid_idx))
return train_idx, valid_idx return train_idx, valid_idx
...@@ -97,8 +114,6 @@ class GraphDataLoader(): ...@@ -97,8 +114,6 @@ class GraphDataLoader():
split = int(math.floor(split_ratio * num_entries)) split = int(math.floor(split_ratio * num_entries))
train_idx, valid_idx = indices[:split], indices[split:] train_idx, valid_idx = indices[:split], indices[split:]
print( print("train_set : test_set = %d : %d", len(train_idx), len(valid_idx))
"train_set : test_set = %d : %d",
len(train_idx), len(valid_idx))
return train_idx, valid_idx return train_idx, valid_idx
...@@ -6,14 +6,16 @@ Author's implementation: https://github.com/weihua916/powerful-gnns ...@@ -6,14 +6,16 @@ Author's implementation: https://github.com/weihua916/powerful-gnns
""" """
import mxnet as mx import mxnet as mx
from mxnet import nd, gluon from mxnet import gluon, nd
from mxnet.gluon import nn from mxnet.gluon import nn
from dgl.nn.mxnet.conv import GINConv from dgl.nn.mxnet.conv import GINConv
from dgl.nn.mxnet.glob import SumPooling, AvgPooling, MaxPooling from dgl.nn.mxnet.glob import AvgPooling, MaxPooling, SumPooling
class ApplyNodeFunc(nn.Block): class ApplyNodeFunc(nn.Block):
"""Update the node feature hv with MLP, BN and ReLU.""" """Update the node feature hv with MLP, BN and ReLU."""
def __init__(self, mlp): def __init__(self, mlp):
super(ApplyNodeFunc, self).__init__() super(ApplyNodeFunc, self).__init__()
with self.name_scope(): with self.name_scope():
...@@ -29,6 +31,7 @@ class ApplyNodeFunc(nn.Block): ...@@ -29,6 +31,7 @@ class ApplyNodeFunc(nn.Block):
class MLP(nn.Block): class MLP(nn.Block):
"""MLP with linear output""" """MLP with linear output"""
def __init__(self, num_layers, input_dim, hidden_dim, output_dim): def __init__(self, num_layers, input_dim, hidden_dim, output_dim):
"""MLP layers construction """MLP layers construction
...@@ -79,9 +82,19 @@ class MLP(nn.Block): ...@@ -79,9 +82,19 @@ class MLP(nn.Block):
class GIN(nn.Block): class GIN(nn.Block):
"""GIN model""" """GIN model"""
def __init__(self, num_layers, num_mlp_layers, input_dim, hidden_dim,
output_dim, final_dropout, learn_eps, graph_pooling_type, def __init__(
neighbor_pooling_type): self,
num_layers,
num_mlp_layers,
input_dim,
hidden_dim,
output_dim,
final_dropout,
learn_eps,
graph_pooling_type,
neighbor_pooling_type,
):
"""model parameters setting """model parameters setting
Paramters Paramters
...@@ -120,27 +133,39 @@ class GIN(nn.Block): ...@@ -120,27 +133,39 @@ class GIN(nn.Block):
if i == 0: if i == 0:
mlp = MLP(num_mlp_layers, input_dim, hidden_dim, hidden_dim) mlp = MLP(num_mlp_layers, input_dim, hidden_dim, hidden_dim)
else: else:
mlp = MLP(num_mlp_layers, hidden_dim, hidden_dim, hidden_dim) mlp = MLP(
num_mlp_layers, hidden_dim, hidden_dim, hidden_dim
)
self.ginlayers.add( self.ginlayers.add(
GINConv(ApplyNodeFunc(mlp), neighbor_pooling_type, 0, self.learn_eps)) GINConv(
ApplyNodeFunc(mlp),
neighbor_pooling_type,
0,
self.learn_eps,
)
)
self.batch_norms.add(nn.BatchNorm(in_channels=hidden_dim)) self.batch_norms.add(nn.BatchNorm(in_channels=hidden_dim))
self.linears_prediction = nn.Sequential() self.linears_prediction = nn.Sequential()
for i in range(num_layers): for i in range(num_layers):
if i == 0: if i == 0:
self.linears_prediction.add(nn.Dense(output_dim, in_units=input_dim)) self.linears_prediction.add(
nn.Dense(output_dim, in_units=input_dim)
)
else: else:
self.linears_prediction.add(nn.Dense(output_dim, in_units=hidden_dim)) self.linears_prediction.add(
nn.Dense(output_dim, in_units=hidden_dim)
)
self.drop = nn.Dropout(final_dropout) self.drop = nn.Dropout(final_dropout)
if graph_pooling_type == 'sum': if graph_pooling_type == "sum":
self.pool = SumPooling() self.pool = SumPooling()
elif graph_pooling_type == 'mean': elif graph_pooling_type == "mean":
self.pool = AvgPooling() self.pool = AvgPooling()
elif graph_pooling_type == 'max': elif graph_pooling_type == "max":
self.pool = MaxPooling() self.pool = MaxPooling()
else: else:
raise NotImplementedError raise NotImplementedError
...@@ -158,6 +183,8 @@ class GIN(nn.Block): ...@@ -158,6 +183,8 @@ class GIN(nn.Block):
# perform pooling over all nodes in each graph in every layer # perform pooling over all nodes in each graph in every layer
for i, h in enumerate(hidden_rep): for i, h in enumerate(hidden_rep):
pooled_h = self.pool(g, h) pooled_h = self.pool(g, h)
score_over_layer = score_over_layer + self.drop(self.linears_prediction[i](pooled_h)) score_over_layer = score_over_layer + self.drop(
self.linears_prediction[i](pooled_h)
)
return score_over_layer return score_over_layer
import sys import sys
import numpy as np from parser import Parser
from tqdm import tqdm
import mxnet as mx import mxnet as mx
import numpy as np
from dataloader import GraphDataLoader, collate
from gin import GIN
from mxnet import gluon, nd from mxnet import gluon, nd
from mxnet.gluon import nn from mxnet.gluon import nn
from tqdm import tqdm
from dgl.data.gindt import GINDataset from dgl.data.gindt import GINDataset
from dataloader import GraphDataLoader, collate
from parser import Parser
from gin import GIN
def train(args, net, trainloader, trainer, criterion, epoch): def train(args, net, trainloader, trainer, criterion, epoch):
running_loss = 0 running_loss = 0
total_iters = len(trainloader) total_iters = len(trainloader)
# setup the offset to avoid the overlap with mouse cursor # setup the offset to avoid the overlap with mouse cursor
bar = tqdm(range(total_iters), unit='batch', position=2, file=sys.stdout) bar = tqdm(range(total_iters), unit="batch", position=2, file=sys.stdout)
for pos, (graphs, labels) in zip(bar, trainloader): for pos, (graphs, labels) in zip(bar, trainloader):
# batch graphs will be shipped to device in forward part of model # batch graphs will be shipped to device in forward part of model
labels = labels.as_in_context(args.device) labels = labels.as_in_context(args.device)
feat = graphs.ndata['attr'].as_in_context(args.device) feat = graphs.ndata["attr"].as_in_context(args.device)
with mx.autograd.record(): with mx.autograd.record():
graphs = graphs.to(args.device) graphs = graphs.to(args.device)
...@@ -36,7 +36,7 @@ def train(args, net, trainloader, trainer, criterion, epoch): ...@@ -36,7 +36,7 @@ def train(args, net, trainloader, trainer, criterion, epoch):
trainer.step(batch_size=1) trainer.step(batch_size=1)
# report # report
bar.set_description('epoch-{}'.format(epoch)) bar.set_description("epoch-{}".format(epoch))
bar.close() bar.close()
# the final batch will be aligned # the final batch will be aligned
running_loss = running_loss / total_iters running_loss = running_loss / total_iters
...@@ -52,20 +52,20 @@ def eval_net(args, net, dataloader, criterion): ...@@ -52,20 +52,20 @@ def eval_net(args, net, dataloader, criterion):
for data in dataloader: for data in dataloader:
graphs, labels = data graphs, labels = data
labels = labels.as_in_context(args.device) labels = labels.as_in_context(args.device)
feat = graphs.ndata['attr'].as_in_context(args.device) feat = graphs.ndata["attr"].as_in_context(args.device)
total += len(labels) total += len(labels)
graphs = graphs.to(args.device) graphs = graphs.to(args.device)
outputs = net(graphs, feat) outputs = net(graphs, feat)
predicted = nd.argmax(outputs, axis=1) predicted = nd.argmax(outputs, axis=1)
predicted = predicted.astype('int64') predicted = predicted.astype("int64")
total_correct += (predicted == labels).sum().asscalar() total_correct += (predicted == labels).sum().asscalar()
loss = criterion(outputs, labels) loss = criterion(outputs, labels)
# crossentropy(reduce=True) for default # crossentropy(reduce=True) for default
total_loss += loss.sum().asscalar() total_loss += loss.sum().asscalar()
loss, acc = 1.0 * total_loss / total, 1.0*total_correct / total loss, acc = 1.0 * total_loss / total, 1.0 * total_correct / total
return loss, acc return loss, acc
...@@ -76,8 +76,6 @@ def main(args): ...@@ -76,8 +76,6 @@ def main(args):
mx.random.seed(0) mx.random.seed(0)
np.random.seed(seed=0) np.random.seed(seed=0)
if args.device >= 0: if args.device >= 0:
args.device = mx.gpu(args.device) args.device = mx.gpu(args.device)
else: else:
...@@ -86,75 +84,102 @@ def main(args): ...@@ -86,75 +84,102 @@ def main(args):
dataset = GINDataset(args.dataset, not args.learn_eps) dataset = GINDataset(args.dataset, not args.learn_eps)
trainloader, validloader = GraphDataLoader( trainloader, validloader = GraphDataLoader(
dataset, batch_size=args.batch_size, dataset,
collate_fn=collate, seed=args.seed, shuffle=True, batch_size=args.batch_size,
split_name='fold10', fold_idx=args.fold_idx).train_valid_loader() collate_fn=collate,
seed=args.seed,
shuffle=True,
split_name="fold10",
fold_idx=args.fold_idx,
).train_valid_loader()
# or split_name='rand', split_ratio=0.7 # or split_name='rand', split_ratio=0.7
model = GIN( model = GIN(
args.num_layers, args.num_mlp_layers, args.num_layers,
dataset.dim_nfeats, args.hidden_dim, dataset.gclasses, args.num_mlp_layers,
args.final_dropout, args.learn_eps, dataset.dim_nfeats,
args.graph_pooling_type, args.neighbor_pooling_type) args.hidden_dim,
dataset.gclasses,
args.final_dropout,
args.learn_eps,
args.graph_pooling_type,
args.neighbor_pooling_type,
)
model.initialize(ctx=args.device) model.initialize(ctx=args.device)
criterion = gluon.loss.SoftmaxCELoss() criterion = gluon.loss.SoftmaxCELoss()
print(model.collect_params()) print(model.collect_params())
lr_scheduler = mx.lr_scheduler.FactorScheduler(50, 0.5) lr_scheduler = mx.lr_scheduler.FactorScheduler(50, 0.5)
trainer = gluon.Trainer(model.collect_params(), 'adam', trainer = gluon.Trainer(
{'lr_scheduler': lr_scheduler}) model.collect_params(), "adam", {"lr_scheduler": lr_scheduler}
)
# it's not cost-effective to hanle the cursor and init 0 # it's not cost-effective to hanle the cursor and init 0
# https://stackoverflow.com/a/23121189 # https://stackoverflow.com/a/23121189
tbar = tqdm(range(args.epochs), unit="epoch", position=3, ncols=0, file=sys.stdout) tbar = tqdm(
vbar = tqdm(range(args.epochs), unit="epoch", position=4, ncols=0, file=sys.stdout) range(args.epochs), unit="epoch", position=3, ncols=0, file=sys.stdout
lrbar = tqdm(range(args.epochs), unit="epoch", position=5, ncols=0, file=sys.stdout) )
vbar = tqdm(
range(args.epochs), unit="epoch", position=4, ncols=0, file=sys.stdout
)
lrbar = tqdm(
range(args.epochs), unit="epoch", position=5, ncols=0, file=sys.stdout
)
for epoch, _, _ in zip(tbar, vbar, lrbar): for epoch, _, _ in zip(tbar, vbar, lrbar):
train(args, model, trainloader, trainer, criterion, epoch) train(args, model, trainloader, trainer, criterion, epoch)
train_loss, train_acc = eval_net( train_loss, train_acc = eval_net(args, model, trainloader, criterion)
args, model, trainloader, criterion)
tbar.set_description( tbar.set_description(
'train set - average loss: {:.4f}, accuracy: {:.0f}%' "train set - average loss: {:.4f}, accuracy: {:.0f}%".format(
.format(train_loss, 100. * train_acc)) train_loss, 100.0 * train_acc
)
)
valid_loss, valid_acc = eval_net( valid_loss, valid_acc = eval_net(args, model, validloader, criterion)
args, model, validloader, criterion)
vbar.set_description( vbar.set_description(
'valid set - average loss: {:.4f}, accuracy: {:.0f}%' "valid set - average loss: {:.4f}, accuracy: {:.0f}%".format(
.format(valid_loss, 100. * valid_acc)) valid_loss, 100.0 * valid_acc
)
)
if not args.filename == "": if not args.filename == "":
with open(args.filename, 'a') as f: with open(args.filename, "a") as f:
f.write('%s %s %s %s' % ( f.write(
"%s %s %s %s"
% (
args.dataset, args.dataset,
args.learn_eps, args.learn_eps,
args.neighbor_pooling_type, args.neighbor_pooling_type,
args.graph_pooling_type args.graph_pooling_type,
)) )
)
f.write("\n") f.write("\n")
f.write("%f %f %f %f" % ( f.write(
train_loss, "%f %f %f %f"
train_acc, % (train_loss, train_acc, valid_loss, valid_acc)
valid_loss, )
valid_acc
))
f.write("\n") f.write("\n")
lrbar.set_description( lrbar.set_description(
"Learning eps with learn_eps={}: {}".format( "Learning eps with learn_eps={}: {}".format(
args.learn_eps, [layer.eps.data(args.device).asscalar() for layer in model.ginlayers])) args.learn_eps,
[
layer.eps.data(args.device).asscalar()
for layer in model.ginlayers
],
)
)
tbar.close() tbar.close()
vbar.close() vbar.close()
lrbar.close() lrbar.close()
if __name__ == '__main__': if __name__ == "__main__":
args = Parser(description='GIN').args args = Parser(description="GIN").args
print('show all arguments configuration...') print("show all arguments configuration...")
print(args) print(args)
main(args) main(args)
...@@ -5,12 +5,11 @@ Put all arguments in one file and group similar arguments ...@@ -5,12 +5,11 @@ Put all arguments in one file and group similar arguments
import argparse import argparse
class Parser(): class Parser:
def __init__(self, description): def __init__(self, description):
''' """
arguments parser arguments parser
''' """
self.parser = argparse.ArgumentParser(description=description) self.parser = argparse.ArgumentParser(description=description)
self.args = None self.args = None
self._parse() self._parse()
...@@ -18,69 +17,109 @@ class Parser(): ...@@ -18,69 +17,109 @@ class Parser():
def _parse(self): def _parse(self):
# dataset # dataset
self.parser.add_argument( self.parser.add_argument(
'--dataset', type=str, default="MUTAG", "--dataset",
help='name of dataset (default: MUTAG)') type=str,
self.parser.add_argument( default="MUTAG",
'--batch_size', type=int, default=32, help="name of dataset (default: MUTAG)",
help='batch size for training and validation (default: 32)') )
self.parser.add_argument( self.parser.add_argument(
'--fold_idx', type=int, default=0, "--batch_size",
help='the index(<10) of fold in 10-fold validation.') type=int,
self.parser.add_argument( default=32,
'--filename', type=str, default="", help="batch size for training and validation (default: 32)",
help='output file') )
self.parser.add_argument(
"--fold_idx",
type=int,
default=0,
help="the index(<10) of fold in 10-fold validation.",
)
self.parser.add_argument(
"--filename", type=str, default="", help="output file"
)
# device # device
self.parser.add_argument( self.parser.add_argument(
'--disable-cuda', action='store_true', "--disable-cuda", action="store_true", help="Disable CUDA"
help='Disable CUDA') )
self.parser.add_argument( self.parser.add_argument(
'--device', type=int, default=0, "--device",
help='which gpu device to use (default: 0)') type=int,
default=0,
help="which gpu device to use (default: 0)",
)
# net # net
self.parser.add_argument( self.parser.add_argument(
'--net', type=str, default="gin", "--net", type=str, default="gin", help="gnn net (default: gin)"
help='gnn net (default: gin)') )
self.parser.add_argument( self.parser.add_argument(
'--num_layers', type=int, default=5, "--num_layers",
help='number of layers (default: 5)') type=int,
self.parser.add_argument( default=5,
'--num_mlp_layers', type=int, default=2, help="number of layers (default: 5)",
help='number of MLP layers(default: 2). 1 means linear model.') )
self.parser.add_argument( self.parser.add_argument(
'--hidden_dim', type=int, default=64, "--num_mlp_layers",
help='number of hidden units (default: 64)') type=int,
default=2,
help="number of MLP layers(default: 2). 1 means linear model.",
)
self.parser.add_argument(
"--hidden_dim",
type=int,
default=64,
help="number of hidden units (default: 64)",
)
# graph # graph
self.parser.add_argument( self.parser.add_argument(
'--graph_pooling_type', type=str, "--graph_pooling_type",
default="sum", choices=["sum", "mean", "max"], type=str,
help='type of graph pooling: sum, mean or max') default="sum",
self.parser.add_argument( choices=["sum", "mean", "max"],
'--neighbor_pooling_type', type=str, help="type of graph pooling: sum, mean or max",
default="sum", choices=["sum", "mean", "max"], )
help='type of neighboring pooling: sum, mean or max') self.parser.add_argument(
self.parser.add_argument( "--neighbor_pooling_type",
'--learn_eps', action="store_true", type=str,
help='learn the epsilon weighting') default="sum",
self.parser.add_argument( choices=["sum", "mean", "max"],
'--degree_as_tag', action="store_true", help="type of neighboring pooling: sum, mean or max",
help='take the degree of nodes as input feature') )
self.parser.add_argument(
"--learn_eps",
action="store_true",
help="learn the epsilon weighting",
)
self.parser.add_argument(
"--degree_as_tag",
action="store_true",
help="take the degree of nodes as input feature",
)
# learning # learning
self.parser.add_argument( self.parser.add_argument(
'--seed', type=int, default=0, "--seed", type=int, default=0, help="random seed (default: 0)"
help='random seed (default: 0)') )
self.parser.add_argument( self.parser.add_argument(
'--epochs', type=int, default=350, "--epochs",
help='number of epochs to train (default: 350)') type=int,
self.parser.add_argument( default=350,
'--lr', type=float, default=0.01, help="number of epochs to train (default: 350)",
help='learning rate (default: 0.01)') )
self.parser.add_argument( self.parser.add_argument(
'--final_dropout', type=float, default=0.5, "--lr",
help='final layer dropout (default: 0.5)') type=float,
default=0.01,
help="learning rate (default: 0.01)",
)
self.parser.add_argument(
"--final_dropout",
type=float,
default=0.5,
help="final layer dropout (default: 0.5)",
)
# done # done
self.args = self.parser.parse_args() self.args = self.parser.parse_args()
...@@ -6,19 +6,22 @@ Simple reference implementation of GraphSAGE. ...@@ -6,19 +6,22 @@ Simple reference implementation of GraphSAGE.
""" """
import argparse import argparse
import time import time
import numpy as np
import networkx as nx
import mxnet as mx import mxnet as mx
from mxnet import nd, gluon import networkx as nx
import numpy as np
from mxnet import gluon, nd
from mxnet.gluon import nn from mxnet.gluon import nn
import dgl import dgl
from dgl.data import register_data_args from dgl.data import (CiteseerGraphDataset, CoraGraphDataset,
from dgl.data import CoraGraphDataset, CiteseerGraphDataset, PubmedGraphDataset PubmedGraphDataset, register_data_args)
from dgl.nn.mxnet.conv import SAGEConv from dgl.nn.mxnet.conv import SAGEConv
class GraphSAGE(nn.Block): class GraphSAGE(nn.Block):
def __init__(self, def __init__(
self,
g, g,
in_feats, in_feats,
n_hidden, n_hidden,
...@@ -26,19 +29,44 @@ class GraphSAGE(nn.Block): ...@@ -26,19 +29,44 @@ class GraphSAGE(nn.Block):
n_layers, n_layers,
activation, activation,
dropout, dropout,
aggregator_type): aggregator_type,
):
super(GraphSAGE, self).__init__() super(GraphSAGE, self).__init__()
self.g = g self.g = g
with self.name_scope(): with self.name_scope():
self.layers = nn.Sequential() self.layers = nn.Sequential()
# input layer # input layer
self.layers.add(SAGEConv(in_feats, n_hidden, aggregator_type, feat_drop=dropout, activation=activation)) self.layers.add(
SAGEConv(
in_feats,
n_hidden,
aggregator_type,
feat_drop=dropout,
activation=activation,
)
)
# hidden layers # hidden layers
for i in range(n_layers - 1): for i in range(n_layers - 1):
self.layers.add(SAGEConv(n_hidden, n_hidden, aggregator_type, feat_drop=dropout, activation=activation)) self.layers.add(
SAGEConv(
n_hidden,
n_hidden,
aggregator_type,
feat_drop=dropout,
activation=activation,
)
)
# output layer # output layer
self.layers.add(SAGEConv(n_hidden, n_classes, aggregator_type, feat_drop=dropout, activation=None)) # activation None self.layers.add(
SAGEConv(
n_hidden,
n_classes,
aggregator_type,
feat_drop=dropout,
activation=None,
)
) # activation None
def forward(self, features): def forward(self, features):
h = features h = features
...@@ -46,21 +74,23 @@ class GraphSAGE(nn.Block): ...@@ -46,21 +74,23 @@ class GraphSAGE(nn.Block):
h = layer(self.g, h) h = layer(self.g, h)
return h return h
def evaluate(model, features, labels, mask): def evaluate(model, features, labels, mask):
pred = model(features).argmax(axis=1) pred = model(features).argmax(axis=1)
accuracy = ((pred == labels) * mask).sum() / mask.sum().asscalar() accuracy = ((pred == labels) * mask).sum() / mask.sum().asscalar()
return accuracy.asscalar() return accuracy.asscalar()
def main(args): def main(args):
# load and preprocess dataset # load and preprocess dataset
if args.dataset == 'cora': if args.dataset == "cora":
data = CoraGraphDataset() data = CoraGraphDataset()
elif args.dataset == 'citeseer': elif args.dataset == "citeseer":
data = CiteseerGraphDataset() data = CiteseerGraphDataset()
elif args.dataset == 'pubmed': elif args.dataset == "pubmed":
data = PubmedGraphDataset() data = PubmedGraphDataset()
else: else:
raise ValueError('Unknown dataset: {}'.format(args.dataset)) raise ValueError("Unknown dataset: {}".format(args.dataset))
g = data[0] g = data[0]
if args.gpu < 0: if args.gpu < 0:
...@@ -71,24 +101,29 @@ def main(args): ...@@ -71,24 +101,29 @@ def main(args):
ctx = mx.gpu(args.gpu) ctx = mx.gpu(args.gpu)
g = g.int().to(ctx) g = g.int().to(ctx)
features = g.ndata['feat'] features = g.ndata["feat"]
labels = mx.nd.array(g.ndata['label'], dtype="float32", ctx=ctx) labels = mx.nd.array(g.ndata["label"], dtype="float32", ctx=ctx)
train_mask = g.ndata['train_mask'] train_mask = g.ndata["train_mask"]
val_mask = g.ndata['val_mask'] val_mask = g.ndata["val_mask"]
test_mask = g.ndata['test_mask'] test_mask = g.ndata["test_mask"]
in_feats = features.shape[1] in_feats = features.shape[1]
n_classes = data.num_labels n_classes = data.num_labels
n_edges = data.graph.number_of_edges() n_edges = data.graph.number_of_edges()
print("""----Data statistics------' print(
"""----Data statistics------'
#Edges %d #Edges %d
#Classes %d #Classes %d
#Train samples %d #Train samples %d
#Val samples %d #Val samples %d
#Test samples %d""" % #Test samples %d"""
(n_edges, n_classes, % (
n_edges,
n_classes,
train_mask.sum().asscalar(), train_mask.sum().asscalar(),
val_mask.sum().asscalar(), val_mask.sum().asscalar(),
test_mask.sum().asscalar())) test_mask.sum().asscalar(),
)
)
# add self loop # add self loop
g = dgl.remove_self_loop(g) g = dgl.remove_self_loop(g)
...@@ -96,14 +131,15 @@ def main(args): ...@@ -96,14 +131,15 @@ def main(args):
n_edges = g.number_of_edges() n_edges = g.number_of_edges()
# create GraphSAGE model # create GraphSAGE model
model = GraphSAGE(g, model = GraphSAGE(
g,
in_feats, in_feats,
args.n_hidden, args.n_hidden,
n_classes, n_classes,
args.n_layers, args.n_layers,
nd.relu, nd.relu,
args.dropout, args.dropout,
args.aggregator_type args.aggregator_type,
) )
model.initialize(ctx=ctx) model.initialize(ctx=ctx)
...@@ -111,9 +147,11 @@ def main(args): ...@@ -111,9 +147,11 @@ def main(args):
loss_fcn = gluon.loss.SoftmaxCELoss() loss_fcn = gluon.loss.SoftmaxCELoss()
print(model.collect_params()) print(model.collect_params())
trainer = gluon.Trainer(model.collect_params(), 'adam', trainer = gluon.Trainer(
{'learning_rate': args.lr, 'wd': args.weight_decay}) model.collect_params(),
"adam",
{"learning_rate": args.lr, "wd": args.weight_decay},
)
# initialize graph # initialize graph
dur = [] dur = []
...@@ -133,34 +171,48 @@ def main(args): ...@@ -133,34 +171,48 @@ def main(args):
loss.asscalar() loss.asscalar()
dur.append(time.time() - t0) dur.append(time.time() - t0)
acc = evaluate(model, features, labels, val_mask) acc = evaluate(model, features, labels, val_mask)
print("Epoch {:05d} | Time(s) {:.4f} | Loss {:.4f} | Accuracy {:.4f} | " print(
"ETputs(KTEPS) {:.2f}". format( "Epoch {:05d} | Time(s) {:.4f} | Loss {:.4f} | Accuracy {:.4f} | "
epoch, np.mean(dur), loss.asscalar(), acc, n_edges / np.mean(dur) / 1000)) "ETputs(KTEPS) {:.2f}".format(
epoch,
np.mean(dur),
loss.asscalar(),
acc,
n_edges / np.mean(dur) / 1000,
)
)
# test set accuracy # test set accuracy
acc = evaluate(model, features, labels, test_mask) acc = evaluate(model, features, labels, test_mask)
print("Test accuracy {:.2%}".format(acc)) print("Test accuracy {:.2%}".format(acc))
if __name__ == '__main__': if __name__ == "__main__":
parser = argparse.ArgumentParser(description='GraphSAGE') parser = argparse.ArgumentParser(description="GraphSAGE")
register_data_args(parser) register_data_args(parser)
parser.add_argument("--dropout", type=float, default=0.5, parser.add_argument(
help="dropout probability") "--dropout", type=float, default=0.5, help="dropout probability"
parser.add_argument("--gpu", type=int, default=-1, )
help="gpu") parser.add_argument("--gpu", type=int, default=-1, help="gpu")
parser.add_argument("--lr", type=float, default=1e-2, parser.add_argument("--lr", type=float, default=1e-2, help="learning rate")
help="learning rate") parser.add_argument(
parser.add_argument("--n-epochs", type=int, default=200, "--n-epochs", type=int, default=200, help="number of training epochs"
help="number of training epochs") )
parser.add_argument("--n-hidden", type=int, default=16, parser.add_argument(
help="number of hidden gcn units") "--n-hidden", type=int, default=16, help="number of hidden gcn units"
parser.add_argument("--n-layers", type=int, default=1, )
help="number of hidden gcn layers") parser.add_argument(
parser.add_argument("--weight-decay", type=float, default=5e-4, "--n-layers", type=int, default=1, help="number of hidden gcn layers"
help="Weight for L2 loss") )
parser.add_argument("--aggregator-type", type=str, default="gcn", parser.add_argument(
help="Aggregator type: mean/gcn/pool/lstm") "--weight-decay", type=float, default=5e-4, help="Weight for L2 loss"
)
parser.add_argument(
"--aggregator-type",
type=str,
default="gcn",
help="Aggregator type: mean/gcn/pool/lstm",
)
args = parser.parse_args() args = parser.parse_args()
print(args) print(args)
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment