From 1bdcd8d1685d5e0eb232a6592298443c97cf20d3 Mon Sep 17 00:00:00 2001 From: aurora-xin <2398486514@qq.com> Date: Sat, 21 May 2022 16:21:57 +0800 Subject: [PATCH 1/2] diffpool files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit diffpool模型 --- examples/diffpool/README.md | 26 + examples/diffpool/diffpool_trainer.py | 709 ++++++++++++++++++++++++++ gammagl/data/graph_sampler.py | 143 ++++++ gammagl/datasets/dd.py | 190 +++++++ gammagl/models/diffpool.py | 460 +++++++++++++++++ 5 files changed, 1528 insertions(+) create mode 100644 examples/diffpool/README.md create mode 100644 examples/diffpool/diffpool_trainer.py create mode 100644 gammagl/data/graph_sampler.py create mode 100644 gammagl/datasets/dd.py create mode 100644 gammagl/models/diffpool.py diff --git a/examples/diffpool/README.md b/examples/diffpool/README.md new file mode 100644 index 000000000..1a8903bb6 --- /dev/null +++ b/examples/diffpool/README.md @@ -0,0 +1,26 @@ +# Differentiable Pooling + +- Paper link: [ https://arxiv.org/abs/1806.08804]( https://arxiv.org/abs/1806.08804) +- Author's code repo: [https://github.com/RexYing/diffpool](https://github.com/RexYing/diffpool). Note that the original code is + implemented with Pytorch for the paper. + +# Dataset Statics + +| Dataset | # Graphs | # Edges | # Avg of graph size | +|---------|----------|----------|---------------------| +| syn1v2 | 1000 | 181067 | 49.23 | +| D&D | | | | +| ENZYMES | | | | + +Results +------- + +```bash +python diffpool_trainer.py --dataset syn1v2 --num_pool 1 --epochs 50 +``` + +| Dataset | Paper | Our(pytorch) | Our(tf) | +|---------|-------|--------------|---------| +| syn1v2 | | 0.800002 | | +| D&D | 81.15 | | | +| ENZYMES | 64.23 | | | \ No newline at end of file diff --git a/examples/diffpool/diffpool_trainer.py b/examples/diffpool/diffpool_trainer.py new file mode 100644 index 000000000..50fdf5da5 --- /dev/null +++ b/examples/diffpool/diffpool_trainer.py @@ -0,0 +1,709 @@ +# !/usr/bin/env python +# -*- encoding: utf-8 -*- +""" +@File : diffpool_trainer.py +@Time : 2022/04/05 18:00:00 +@Author : yang yuxin +""" + +import os +os.environ['TL_BACKEND'] = 'torch' # set your backend here, default `tensorflow` +# os.environ["CUDA_VISIBLE_DEVICES"] = "8" +# os.environ['CUDA_LAUNCH_BLOCKING'] = "1" +import sys +sys.path.insert(0, os.path.abspath('../../')) + +import pickle +import shutil +import random +import argparse +import tensorlayerx as tlx +import numpy as np +import networkx as nx +import abc +from tensorboardX import SummaryWriter +from tensorlayerx import Variable #from torch.autograd import Variable +import sklearn.metrics as metrics + +from gammagl.datasets.dd import prepare_val_data,read_graphfile +from gammagl.models.diffpool import GcnEncoderGraph#,GcnSet2SetEncoder,SoftPoolingGcnEncoder +from gammagl.data.graph_sampler import GraphSampler + + +def evaluate(dataset, model, args, name='Validation', max_num_examples=None): + model.eval() + labels = [] + preds = [] + for batch_idx, data in enumerate(dataset): + adj = Variable(data['adj'].float())#.cuda() #requires_grad=False + h0 = Variable(data['feats'].float())#.cuda() + labels.append(data['label'].long().numpy()) + batch_num_nodes = data['num_nodes'].int().numpy() + assign_input = Variable(data['assign_feats'].float())#.cuda() + #Variable(data['assign_feats'].float(), requires_grad=False).cuda() + ypred = model(h0, adj, batch_num_nodes, assign_x=assign_input) #20,2 + indices = tlx.argmax(ypred, axis=1) #_, indices = torch.max(ypred, 1) #20 + preds.append(indices.cpu().data.numpy()) + + if max_num_examples is not None: + if (batch_idx + 1) * args.batch_size > max_num_examples: + break + + #6*20 --> 120 + labels = np.hstack(labels) + preds = np.hstack(preds) + # labels=tlx.convert_to_tensor(labels) #6*20=120 + # preds=tlx.convert_to_tensor(preds) #6*20=120 + + + # metrics = tlx.metrics.Accuracy() + # metrics.update(preds, labels) + # result = metrics.result() + #print(name, " accuracy:", result) + # metrics.reset() + + result = { + # 'prec': metrics.precision_score(labels, preds, average='macro'), + # 'recall': metrics.recall_score(labels, preds, average='macro'), + 'acc': metrics.accuracy_score(labels, preds) + #,'F1': metrics.f1_score(labels, preds, average="micro") + } + print(name, " accuracy:", result['acc']) + + return result + +def gen_prefix(args): + if args.bmname is not None: + name = args.bmname + else: + name = args.dataset + name += '_' + args.method + if args.method == 'soft-assign': + name += '_l' + str(args.num_gc_layers) + 'x' + str(args.num_pool) + name += '_ar' + str(int(args.assign_ratio*100)) + if args.linkpred: + name += '_lp' + else: + name += '_l' + str(args.num_gc_layers) + name += '_h' + str(args.hidden_dim) + '_o' + str(args.output_dim) + if not args.bias: + name += '_nobias' + if len(args.name_suffix) > 0: + name += '_' + args.name_suffix + return name + + +def train(dataset, model, args, same_feat=True, val_dataset=None, test_dataset=None, writer=None, + mask_nodes=True): + writer_batch_idx = [0, 3, 6, 9] + + optimizer = tlx.optimizers.Adam(lr=0.001) + #参数:(filter(lambda p: p.requires_grad, model.parameters()), learning_rate=0.001) + iter = 0 + # best_val_result = { + # 'epoch': 0, + # 'loss': 0, + # 'acc': 0} + # test_result = { + # 'epoch': 0, + # 'loss': 0, + # 'acc': 0} + train_accs = [] + train_epochs = [] + # best_val_accs = [] + # best_val_epochs = [] + test_accs = [] + # test_epochs = [] + val_accs = [] + + for epoch in range(args.num_epochs): + total_time = 0 + avg_loss = 0.0 + #model.train() + model.set_train() + print('Epoch: ', epoch) + for batch_idx, data in enumerate(dataset): + # adj:20,1000,1000; feat 20,1000,89; assign_feats:20,1000,89; label:20 + # begin_time = time.time() + model.zero_grad() + adj = Variable(data['adj'].float(),"adj",False)#.cuda() #,requires_grad=False + h0 = Variable(data['feats'].float(),"h0",False)#.cuda()#,requires_grad=False + label = Variable(data['label'].long(),"label",False)#.cuda() + batch_num_nodes = data['num_nodes'].int().numpy() if mask_nodes else None + assign_input = Variable(data['assign_feats'].float(),"assign_input",False)#.cuda()#,requires_grad=False + + + ypred = model(h0, adj, batch_num_nodes, assign_x=assign_input) # 20,2 + if not args.method == 'soft-assign' or not args.linkpred: + loss = model.loss(ypred, label) + else: + loss = model.loss(ypred, label, adj, batch_num_nodes) + #loss.backward() + + # torch.nn.utils.clip_grad_norm_(model.parameters(), args.clip) + #optimizer.step() + g=optimizer.gradient(loss,weights=model.trainable_weights) + optimizer.apply_gradients(zip(g,model.trainable_weights)) + + iter += 1 + avg_loss += loss + # if iter % 20 == 0: + # print('Iter: ', iter, ', loss: ', loss.data[0]) + + avg_loss /= batch_idx + 1 + if writer is not None: + writer.add_scalar('loss/avg_loss', avg_loss, epoch) + if args.linkpred: + writer.add_scalar('loss/linkpred_loss', model.link_loss, epoch) + print('Avg loss: ', avg_loss) + result = evaluate(dataset, model, args, name='Train', max_num_examples=100) + train_accs.append(result['acc']) + train_epochs.append(epoch) + if val_dataset is not None: + val_result = evaluate(val_dataset, model, args, name='Validation') + val_accs.append(val_result['acc']) + # if val_result['acc'] > best_val_result['acc'] - 1e-7: + # best_val_result['acc'] = val_result['acc'] + # best_val_result['epoch'] = epoch + # best_val_result['loss'] = avg_loss + if test_dataset is not None: + test_result = evaluate(test_dataset, model, args, name='Test') + test_result['epoch'] = epoch + if writer is not None: + writer.add_scalar('acc/train_acc', result['acc'], epoch) + writer.add_scalar('acc/val_acc', val_result['acc'], epoch) + # writer.add_scalar('loss/best_val_loss', best_val_result['loss'], epoch) + if test_dataset is not None: + writer.add_scalar('acc/test_acc', test_result['acc'], epoch) + # print('Best val result: ', best_val_result) + # best_val_epochs.append(best_val_result['epoch']) + # best_val_accs.append(best_val_result['acc']) + # if test_dataset is not None: + # print('Test result: ', test_result) + # test_epochs.append(test_result['epoch']) + # test_accs.append(test_result['acc']) + return model, val_accs + + +def prepare_data(graphs, args, test_graphs=None, max_nodes=0): + random.shuffle(graphs) + if test_graphs is None: + train_idx = int(len(graphs) * args.train_ratio) + test_idx = int(len(graphs) * (1 - args.test_ratio))#args + train_graphs = graphs[:train_idx] + val_graphs = graphs[train_idx: test_idx] + test_graphs = graphs[test_idx:] + else: + train_idx = int(len(graphs) * args.train_ratio) + train_graphs = graphs[:train_idx] + val_graphs = graphs[train_idx:] + # print('Num training graphs: ', len(train_graphs), + # '; Num validation graphs: ', len(val_graphs), + # '; Num testing graphs: ', len(test_graphs)) + # + # print('Number of graphs: ', len(graphs)) + # print('Number of edges: ', sum([G.number_of_edges() for G in graphs])) + # print('Max, avg, std of graph size: ', + # max([G.number_of_nodes() for G in graphs]), ', ' + # "{0:.2f}".format(np.mean([G.number_of_nodes() for G in graphs])), + # ', ' + # "{0:.2f}".format(np.std([G.number_of_nodes() for G in graphs]))) + + # minibatch + dataset_sampler = GraphSampler(train_graphs, normalize=False, max_num_nodes=max_nodes, + features=args.feature_type) + train_dataset_loader = tlx.dataflow.DataLoader( + dataset_sampler, + batch_size=args.batch_size, + shuffle=True, + num_workers=args.num_workers) + + dataset_sampler = GraphSampler(val_graphs, normalize=False, max_num_nodes=max_nodes, + features=args.feature_type) + val_dataset_loader = tlx.dataflow.DataLoader( # torch.utils.data.DataLoader + dataset_sampler, + batch_size=args.batch_size, + shuffle=False, + num_workers=args.num_workers) + + dataset_sampler = GraphSampler(test_graphs, normalize=False, max_num_nodes=max_nodes, + features=args.feature_type) + test_dataset_loader = tlx.dataflow.DataLoader( + dataset_sampler, + batch_size=args.batch_size, + shuffle=False, + num_workers=args.num_workers) + + return train_dataset_loader, val_dataset_loader, test_dataset_loader, \ + dataset_sampler.max_num_nodes, dataset_sampler.feat_dim, dataset_sampler.assign_feat_dim + + +#gen #feat +class FeatureGen(metaclass=abc.ABCMeta): + @abc.abstractmethod + def gen_node_features(self, G): + pass + +class ConstFeatureGen(FeatureGen): + def __init__(self, val): + self.val = val + + def gen_node_features(self, G): + feat_dict = {i:{'feat': self.val} for i in G.nodes()} + nx.set_node_attributes(G, feat_dict) + +class GaussianFeatureGen(FeatureGen): + def __init__(self, mu, sigma): + self.mu = mu + self.sigma = sigma + + def gen_node_features(self, G): + feat = np.random.multivariate_normal(self.mu, self.sigma, G.number_of_nodes()) + feat_dict = {i:{'feat': feat[i]} for i in range(feat.shape[0])} + nx.set_node_attributes(G, feat_dict) + +#data +def gen_ba(n_range, m_range, num_graphs, feature_generator=None): + graphs = [] + for i in np.random.choice(n_range, num_graphs): + for j in np.random.choice(m_range, 1): + graphs.append(nx.barabasi_albert_graph(i,j)) + + if feature_generator is None: + feature_generator = ConstFeatureGen(0) #featgen. + for G in graphs: + feature_generator.gen_node_features(G) + return graphs + +def gen_er(n_range, p, num_graphs, feature_generator=None): + graphs = [] + for i in np.random.choice(n_range, num_graphs): + graphs.append(nx.erdos_renyi_graph(i,p)) + + if feature_generator is None: + feature_generator = ConstFeatureGen(0) #featgen. + for G in graphs: + feature_generator.gen_node_features(G) + return graphs + +def gen_2community_ba(n_range, m_range, num_graphs, inter_prob, feature_generators): + ''' Each community is a BA graph. + Args: + inter_prob: probability of one node connecting to any node in the other community. + ''' + + if feature_generators is None: + mu0 = np.zeros(10) + mu1 = np.ones(10) + sigma0 = np.ones(10, 10) * 0.1 + sigma1 = np.ones(10, 10) * 0.1 + fg0 = GaussianFeatureGen(mu0, sigma0) #featgen. + fg1 = GaussianFeatureGen(mu1, sigma1) #featgen. + else: + fg0 = feature_generators[0] + fg1 = feature_generators[1] if len(feature_generators) > 1 else feature_generators[0] + + graphs1 = [] + graphs2 = [] + #for (i1, i2) in zip(np.random.choice(n_range, num_graphs), + # np.random.choice(n_range, num_graphs)): + # for (j1, j2) in zip(np.random.choice(m_range, num_graphs), + # np.random.choice(m_range, num_graphs)): + graphs0 = gen_ba(n_range, m_range, num_graphs, fg0) + graphs1 = gen_ba(n_range, m_range, num_graphs, fg1) + graphs = [] + for i in range(num_graphs): + G = nx.disjoint_union(graphs0[i], graphs1[i]) + n0 = graphs0[i].number_of_nodes() + for j in range(n0): + if np.random.rand() < inter_prob: + target = np.random.choice(G.number_of_nodes() - n0) + n0 + G.add_edge(j, target) + graphs.append(G) + return graphs + +def gen_2hier(num_graphs, num_clusters, n, m_range, inter_prob1, inter_prob2, feat_gen): + ''' Each community is a BA graph. + Args: + inter_prob1: probability of one node connecting to any node in the other community within + the large cluster. + inter_prob2: probability of one node connecting to any node in the other community between + the large cluster. + ''' + graphs = [] + + for i in range(num_graphs): + clusters2 = [] + for j in range(len(num_clusters)): + clusters = gen_er(range(n, n+1), 0.5, num_clusters[j], feat_gen[0]) + G = nx.disjoint_union_all(clusters) + for u1 in range(G.number_of_nodes()): + if np.random.rand() < inter_prob1: + target = np.random.choice(G.number_of_nodes() - n) + # move one cluster after to make sure it's not an intra-cluster edge + if target // n >= u1 // n: + target += n + G.add_edge(u1, target) + clusters2.append(G) + G = nx.disjoint_union_all(clusters2) + cluster_sizes_cum = np.cumsum([cluster2.number_of_nodes() for cluster2 in clusters2]) + curr_cluster = 0 + for u1 in range(G.number_of_nodes()): + if u1 >= cluster_sizes_cum[curr_cluster]: + curr_cluster += 1 + if np.random.rand() < inter_prob2: + target = np.random.choice(G.number_of_nodes() - + clusters2[curr_cluster].number_of_nodes()) + # move one cluster after to make sure it's not an intra-cluster edge + if curr_cluster == 0 or target >= cluster_sizes_cum[curr_cluster - 1]: + target += cluster_sizes_cum[curr_cluster] + G.add_edge(u1, target) + graphs.append(G) + + return graphs + +def syn_community1v2(args, writer=None, export_graphs=False): + graphs1 = gen_ba(range(40, 60), range(4, 5), 500,#datagen. + ConstFeatureGen(np.ones(args.input_dim, dtype=float)))#featgen. + for G in graphs1: + G.graph['label'] = 0 + + graphs2 = gen_2community_ba(range(20, 30), range(4, 5), 500, 0.3, #datagen. + [ConstFeatureGen(np.ones(args.input_dim, dtype=float))])#featgen. + for G in graphs2: + G.graph['label'] = 1 + + graphs = graphs1 + graphs2 + + train_dataset, val_dataset, test_dataset, max_num_nodes, input_dim, assign_input_dim = prepare_data(graphs, args) + if args.method == 'soft-assign': + print('Method: soft-assign') + # model = SoftPoolingGcnEncoder( + # max_num_nodes, + # input_dim, args.hidden_dim, args.output_dim, args.num_classes, args.num_gc_layers, + # args.hidden_dim, assign_ratio=args.assign_ratio, num_pooling=args.num_pool, + # bn=args.bn, linkpred=args.linkpred, assign_input_dim=assign_input_dim).cuda() + elif args.method == 'base-set2set': + print('Method: base-set2set') + # model = GcnSet2SetEncoder(input_dim, args.hidden_dim, args.output_dim, 2, + # args.num_gc_layers, bn=args.bn).cuda() + else: + #print('Method: base') + model = GcnEncoderGraph(input_dim, args.hidden_dim, args.output_dim, 2, + args.num_gc_layers, bn=args.bn)#.cuda() + + train(train_dataset, model, args, val_dataset=val_dataset, test_dataset=test_dataset, + writer=writer) + + +def syn_community2hier(args, writer=None): + # data + feat_gen = [ConstFeatureGen(np.ones(args.input_dim, dtype=float))]#featgen + graphs1 = gen_2hier(1000, [2, 4], 10, range(4, 5), 0.1, 0.03, feat_gen) #datagen. + graphs2 = gen_2hier(1000, [3, 3], 10, range(4, 5), 0.1, 0.03, feat_gen) #datagen. + graphs3 = gen_2community_ba(range(28, 33), range(4, 7), 1000, 0.25, feat_gen) #datagen. + + for G in graphs1: + G.graph['label'] = 0 + for G in graphs2: + G.graph['label'] = 1 + for G in graphs3: + G.graph['label'] = 2 + + graphs = graphs1 + graphs2 + graphs3 + + train_dataset, val_dataset, test_dataset, max_num_nodes, input_dim, assign_input_dim = prepare_data(graphs, args) + + if args.method == 'soft-assign': + print('Method: soft-assign') + # model = SoftPoolingGcnEncoder( + # max_num_nodes, + # input_dim, args.hidden_dim, args.output_dim, args.num_classes, args.num_gc_layers, + # args.hidden_dim, assign_ratio=args.assign_ratio, num_pooling=args.num_pool, + # bn=args.bn, linkpred=args.linkpred, args=args, assign_input_dim=assign_input_dim).cuda() + elif args.method == 'base-set2set': + print('Method: base-set2set') + # model = GcnSet2SetEncoder(input_dim, args.hidden_dim, args.output_dim, 2, + # args.num_gc_layers, bn=args.bn, args=args, + # assign_input_dim=assign_input_dim).cuda() + else: + print('Method: base') + model = GcnEncoderGraph(input_dim, args.hidden_dim, args.output_dim, 2, + args.num_gc_layers, bn=args.bn, args=args)#.cuda() + train(train_dataset, model, args, val_dataset=val_dataset, test_dataset=test_dataset, + writer=writer) + + +def pkl_task(args, feat=None): + with open(os.path.join(args.datadir, args.pkl_fname), 'rb') as pkl_file: + data = pickle.load(pkl_file) + graphs = data[0] + labels = data[1] + test_graphs = data[2] + test_labels = data[3] + + for i in range(len(graphs)): + graphs[i].graph['label'] = labels[i] + for i in range(len(test_graphs)): + test_graphs[i].graph['label'] = test_labels[i] + + if feat is None: + featgen_const = ConstFeatureGen(np.ones(args.input_dim, dtype=float)) #featgen + for G in graphs: + featgen_const.gen_node_features(G) + for G in test_graphs: + featgen_const.gen_node_features(G) + + train_dataset, test_dataset, max_num_nodes = prepare_data(graphs, args, test_graphs=test_graphs) + model = GcnEncoderGraph( + args.input_dim, args.hidden_dim, args.output_dim, args.num_classes, + args.num_gc_layers, bn=args.bn).cuda() + train(train_dataset, model, args, test_dataset=test_dataset) + evaluate(test_dataset, model, args, 'Validation') + + +def benchmark_task(args, writer=None, feat='node-label'): + graphs = read_graphfile(args.datadir, args.bmname, max_nodes=args.max_nodes) + + if feat == 'node-feat' and 'feat_dim' in graphs[0].graph: + print('Using node features') + input_dim = graphs[0].graph['feat_dim'] + elif feat == 'node-label' and 'label' in graphs[0].node[0]: + print('Using node labels') + for G in graphs: + for u in G.nodes(): + G.node[u]['feat'] = np.array(G.node[u]['label']) + else: + print('Using constant labels') + featgen_const = ConstFeatureGen(np.ones(args.input_dim, dtype=float)) #featgen. + for G in graphs: + featgen_const.gen_node_features(G) + + train_dataset, val_dataset, test_dataset, max_num_nodes, input_dim, assign_input_dim = \ + prepare_data(graphs, args, max_nodes=args.max_nodes) + if args.method == 'soft-assign': + print('Method: soft-assign') + # model = SoftPoolingGcnEncoder( + # max_num_nodes, + # input_dim, args.hidden_dim, args.output_dim, args.num_classes, args.num_gc_layers, + # args.hidden_dim, assign_ratio=args.assign_ratio, num_pooling=args.num_pool, + # bn=args.bn, dropout=args.dropout, linkpred=args.linkpred, args=args, + # assign_input_dim=assign_input_dim).cuda() + elif args.method == 'base-set2set': + print('Method: base-set2set') + # model = GcnSet2SetEncoder( + # input_dim, args.hidden_dim, args.output_dim, args.num_classes, + # args.num_gc_layers, bn=args.bn, dropout=args.dropout, args=args).cuda() + else: + # print('Method: base') + model = GcnEncoderGraph( + input_dim, args.hidden_dim, args.output_dim, args.num_classes, + args.num_gc_layers, bn=args.bn, dropout=args.dropout, args=args).cuda() + + train(train_dataset, model, args, val_dataset=val_dataset, test_dataset=test_dataset, + writer=writer) + evaluate(test_dataset, model, args, 'Validation') + +#import util +# ---- NetworkX compatibility +def node_iter(G): + # if float(nx.__version__)<2.0: + # return G.nodes() + # else: + return G.nodes + +def node_dict(G): + #if float(nx.__version__)>2.1: + node_dict = G.nodes #159 + # else: + # node_dict = G.node + return node_dict +# --------------------------- + +def exp_moving_avg(x, decay=0.9): + shadow = x[0] + a = [shadow] + for v in x[1:]: + shadow -= (1-decay) * (shadow-v) + a.append(shadow) + return a + +def benchmark_task_val(args, writer=None, feat='node-label'): + all_vals = [] + graphs = read_graphfile(args.datadir, args.bmname, max_nodes=args.max_nodes) + + example_node = node_dict(graphs[0])[0] + + if feat == 'node-feat' and 'feat_dim' in graphs[0].graph: + print('Using node features') + input_dim = graphs[0].graph['feat_dim'] + elif feat == 'node-label' and 'label' in example_node: + print('Using node labels') + for G in graphs: + for u in G.nodes(): + node_dict(G)[u]['feat'] = np.array(node_dict(G)[u]['label']) # ? + else: + print('Using constant labels') + featgen_const = ConstFeatureGen(np.ones(args.input_dim, dtype=float)) #featgen. + for G in graphs: + featgen_const.gen_node_features(G) + + for i in range(10): + train_dataset, val_dataset, max_num_nodes, input_dim, assign_input_dim = \ + prepare_val_data(graphs, args, i, max_nodes=args.max_nodes) + if args.method == 'base': + print('Method: base') + model = GcnEncoderGraph( + input_dim, args.hidden_dim, args.output_dim, args.num_classes, + args.num_gc_layers, bn=args.bn, dropout=args.dropout, args=args)#.cuda() + # if args.method == 'soft-assign': + # print('Method: soft-assign') + # model = SoftPoolingGcnEncoder( + # max_num_nodes, + # input_dim, args.hidden_dim, args.output_dim, args.num_classes, args.num_gc_layers, + # args.hidden_dim, assign_ratio=args.assign_ratio, num_pooling=args.num_pool, + # bn=args.bn, dropout=args.dropout, linkpred=args.linkpred, args=args, + # assign_input_dim=assign_input_dim).cuda() + # elif args.method == 'base-set2set': + # print('Method: base-set2set') + # model = GcnSet2SetEncoder( + # input_dim, args.hidden_dim, args.output_dim, args.num_classes, + # args.num_gc_layers, bn=args.bn, dropout=args.dropout, args=args).cuda() + # else: + # print('Method: base') + # model = GcnEncoderGraph( + # input_dim, args.hidden_dim, args.output_dim, args.num_classes, + # args.num_gc_layers, bn=args.bn, dropout=args.dropout, args=args).cuda() + + + _, val_accs = train(train_dataset, model, args, val_dataset=val_dataset, test_dataset=None, + writer=writer) + all_vals.append(np.array(val_accs)) + all_vals = np.vstack(all_vals) + all_vals = np.mean(all_vals, axis=0) + print(all_vals) + print(np.max(all_vals)) + print(np.argmax(all_vals)) + + +def main(prog_args): + import multiprocessing as mp + mp.set_start_method('spawn') + # export scalar data to JSON for external processing + path = os.path.join(prog_args.logdir, gen_prefix(prog_args)) + if os.path.isdir(path): + # print('Remove existing log dir: ', path) + shutil.rmtree(path) + writer = SummaryWriter(path) + writer = None + + # print('CUDA', prog_args.cuda) + + if prog_args.bmname is not None: + benchmark_task_val(prog_args, writer=writer) + elif prog_args.pkl_fname is not None: + pkl_task(prog_args) + elif prog_args.dataset is not None: + if prog_args.dataset == 'syn1v2': + syn_community1v2(prog_args, writer=writer) + if prog_args.dataset == 'syn2hier': + syn_community2hier(prog_args, writer=writer) + + writer.close() + + +if __name__ == '__main__': + # parameters setting + parser = argparse.ArgumentParser(description='GraphPool arguments.') + io_parser = parser.add_mutually_exclusive_group(required=False) + io_parser.add_argument('--dataset', default='syn1v2',dest='dataset', + help='Input dataset.') + benchmark_parser = io_parser.add_argument_group() + benchmark_parser.add_argument('--bmname', dest='bmname', + help='Name of the benchmark dataset') + io_parser.add_argument('--pkl', dest='pkl_fname', + help='Name of the pkl data file') + + softpool_parser = parser.add_argument_group() + softpool_parser.add_argument('--assign-ratio',default=0.1, dest='assign_ratio', type=float, + help='ratio of number of nodes in consecutive layers') + softpool_parser.add_argument('--num-pool', default=1,dest='num_pool', type=int, + help='number of pooling layers') + parser.add_argument('--linkpred', dest='linkpred', action='store_const', + const=True, default=False, + help='Whether link prediction side objective is used') + + parser.add_argument('--datadir', dest='datadir', default='data', + help='Directory where benchmark is located') + parser.add_argument('--logdir', dest='logdir', default='log', + help='Tensorboard log directory') + parser.add_argument('--cuda', dest='cuda', default='8', + help='CUDA.') + parser.add_argument('--max-nodes', dest='max_nodes', type=int,default=1000, + help='Maximum number of nodes (ignore graghs with nodes exceeding the number.') + parser.add_argument('--lr', dest='lr', type=float, default=0.001, + help='Learning rate.') + parser.add_argument('--clip', dest='clip', type=float, default=2.0, + help='Gradient clipping.') + parser.add_argument('--batch-size', dest='batch_size', type=int, default=20, + help='Batch size.') + parser.add_argument('--epochs', dest='num_epochs', type=int, default=1000, + help='Number of epochs to train.') + parser.add_argument('--train-ratio', dest='train_ratio', type=float,default=0.8, + help='Ratio of number of graphs training set to all graphs.') + parser.add_argument('--num_workers', dest='num_workers', type=int,default=1, + help='Number of workers to load data.') + parser.add_argument('--feature', dest='feature_type',default='default', + help='Feature used for encoder. Can be: id, deg') + parser.add_argument('--input-dim', dest='input_dim', type=int, default=10, + help='Input feature dimension') + parser.add_argument('--hidden-dim', dest='hidden_dim', type=int, default=20, + help='Hidden dimension') + parser.add_argument('--output-dim', dest='output_dim', type=int,default=20, + help='Output dimension') + parser.add_argument('--num-classes', dest='num_classes', type=int,default=2, + help='Number of label classes') + parser.add_argument('--num-gc-layers', dest='num_gc_layers', type=int,default=3, + help='Number of graph convolution layers before each pooling') + parser.add_argument('--nobn', dest='bn', action='store_const', + const=False, default=True, + help='Whether batch normalization is used') + parser.add_argument('--dropout', dest='dropout', type=float, default=0.0, + help='Dropout rate.') + parser.add_argument('--nobias', dest='bias', action='store_const', + const=False, default=True, + help='Whether to add bias. Default to True.') + parser.add_argument('--no-log-graph', dest='log_graph', action='store_const', + const=False, default=True, + help='Whether disable log graph') + + parser.add_argument('--method', dest='method',default='base', + help='Method. Possible values: base, base-set2set, soft-assign') + parser.add_argument('--name-suffix', dest='name_suffix',default='', + help='suffix added to the output filename') + parser.set_defaults(datadir='data', + logdir='log', + dataset='syn1v2', + max_nodes=1000, + cuda='8', + feature_type='default', + lr=0.001, + clip=2.0, + batch_size=20, + num_epochs=50, + train_ratio=0.8, + test_ratio=0.1, + num_workers=1, + input_dim=10, + hidden_dim=20, + output_dim=20, + num_classes=2, + num_gc_layers=3, + dropout=0.0, + method='base', + name_suffix='', + assign_ratio=0.1, + num_pool=1 + ) + prog_args = parser.parse_args() + main(prog_args) diff --git a/gammagl/data/graph_sampler.py b/gammagl/data/graph_sampler.py new file mode 100644 index 000000000..5ae294f11 --- /dev/null +++ b/gammagl/data/graph_sampler.py @@ -0,0 +1,143 @@ +# !/usr/bin/env python +# -*- encoding: utf-8 -*- +import networkx as nx +import numpy as np +import tensorlayerx as tlx +# import torch +# import torch.utils.data + +import community + + +#import util +# ---- NetworkX compatibility +def node_iter(G): + # if float(nx.__version__)<2.0: + # return G.nodes() + # else: + return G.nodes + +def node_dict(G): + # if float(nx.__version__)>2.1: + # node_dict = G.nodes #159 + node_dict = G.nodes + # else: + # node_dict = G.node + return node_dict +# --------------------------- + +def exp_moving_avg(x, decay=0.9): + shadow = x[0] + a = [shadow] + for v in x[1:]: + shadow -= (1-decay) * (shadow-v) + a.append(shadow) + return a + +class GraphSampler(tlx.dataflow.Dataset): #torch.utils.data.Dataset + ''' Sample graphs and nodes in graph + ''' + def __init__(self, G_list, features='default', normalize=True, assign_feat='default', max_num_nodes=0): + self.adj_all = [] + self.len_all = [] + self.feature_all = [] + self.label_all = [] + + self.assign_feat_all = [] + + if max_num_nodes == 0: + self.max_num_nodes = max([G.number_of_nodes() for G in G_list]) + else: + self.max_num_nodes = max_num_nodes #1000 + + + if features == 'default': + self.feat_dim = node_dict(G_list[0])[0]['feat'].shape[0] #89 + +#G_list 1052 + for G in G_list: + adj = np.array(nx.to_numpy_matrix(G)) #159,159 + if normalize: + sqrt_deg = np.diag(1.0 / np.sqrt(np.sum(adj, axis=0, dtype=float).squeeze())) + adj = np.matmul(np.matmul(sqrt_deg, adj), sqrt_deg) + self.adj_all.append(adj) + self.len_all.append(G.number_of_nodes()) + self.label_all.append(G.graph['label']) + # feat matrix: max_num_nodes x feat_dim + if features == 'default': + f = np.zeros((self.max_num_nodes, self.feat_dim), dtype=float) #1000,89 + for i,u in enumerate(G.nodes()): #314 + f[i,:] = node_dict(G)[u]['feat'] + self.feature_all.append(f) #1000,89 + elif features == 'id': + self.feature_all.append(np.identity(self.max_num_nodes)) + elif features == 'deg-num': + degs = np.sum(np.array(adj), 1) + degs = np.expand_dims(np.pad(degs, [0, self.max_num_nodes - G.number_of_nodes()], 0), + axis=1) + self.feature_all.append(degs) + elif features == 'deg': + self.max_deg = 10 + degs = np.sum(np.array(adj), 1).astype(int) + degs[degs > self.max_deg] = self.max_deg + feat = np.zeros((len(degs), self.max_deg + 1)) + feat[np.arange(len(degs)), degs] = 1 + feat = np.pad(feat, ((0, self.max_num_nodes - G.number_of_nodes()), (0, 0)), + 'constant', constant_values=0) + + f = np.zeros((self.max_num_nodes, self.feat_dim), dtype=float) + for i,u in enumerate(node_iter(G)): + f[i,:] = node_dict(G)[u]['feat'] + + feat = np.concatenate((feat, f), axis=1) + + self.feature_all.append(feat) + elif features == 'struct': + self.max_deg = 10 + degs = np.sum(np.array(adj), 1).astype(int) + degs[degs>10] = 10 + feat = np.zeros((len(degs), self.max_deg + 1)) + feat[np.arange(len(degs)), degs] = 1 + degs = np.pad(feat, ((0, self.max_num_nodes - G.number_of_nodes()), (0, 0)), + 'constant', constant_values=0) + + clusterings = np.array(list(nx.clustering(G).values())) + clusterings = np.expand_dims(np.pad(clusterings, + [0, self.max_num_nodes - G.number_of_nodes()], + 'constant'), + axis=1) + g_feat = np.hstack([degs, clusterings]) + if 'feat' in node_dict(G)[0]: + node_feats = np.array([node_dict(G)[i]['feat'] for i in range(G.number_of_nodes())]) + node_feats = np.pad(node_feats, ((0, self.max_num_nodes - G.number_of_nodes()), (0, 0)), + 'constant') + g_feat = np.hstack([g_feat, node_feats]) + + self.feature_all.append(g_feat) + + if assign_feat == 'id': + self.assign_feat_all.append( + np.hstack((np.identity(self.max_num_nodes), self.feature_all[-1])) ) + else: + self.assign_feat_all.append(self.feature_all[-1]) + + self.feat_dim = self.feature_all[0].shape[1] + self.assign_feat_dim = self.assign_feat_all[0].shape[1] + + def __len__(self): + return len(self.adj_all) + + def __getitem__(self, idx): + adj = self.adj_all[idx] + num_nodes = adj.shape[0] + adj_padded = np.zeros((self.max_num_nodes, self.max_num_nodes)) #1000,1000 + adj_padded[:num_nodes, :num_nodes] = adj + + # use all nodes for aggregation (baseline) + + return {'adj':adj_padded, + 'feats':self.feature_all[idx].copy(), + 'label':self.label_all[idx], + 'num_nodes': num_nodes, + 'assign_feats':self.assign_feat_all[idx].copy()} + diff --git a/gammagl/datasets/dd.py b/gammagl/datasets/dd.py new file mode 100644 index 000000000..dc91cff86 --- /dev/null +++ b/gammagl/datasets/dd.py @@ -0,0 +1,190 @@ +import networkx as nx +import numpy as np +import scipy as sc +import os +import re +import tensorlayerx as tlx +# import torch + +import pickle +import random + +from gammagl.data.graph_sampler import GraphSampler + +#import util +# ---- NetworkX compatibility +def node_iter(G): + # if float(nx.__version__)<2.0: + # return G.nodes() + # else: + return G.nodes + +def node_dict(G): + #if float(nx.__version__)>2.1: + node_dict = G.nodes #159 + # else: + # node_dict = G.node + return node_dict +# --------------------------- + +def exp_moving_avg(x, decay=0.9): + shadow = x[0] + a = [shadow] + for v in x[1:]: + shadow -= (1-decay) * (shadow-v) + a.append(shadow) + return a + +def read_graphfile(datadir, dataname, max_nodes=None): + ''' Read data from https://ls11-www.cs.tu-dortmund.de/staff/morris/graphkerneldatasets + graph index starts with 1 in file + + Returns: + List of networkx objects with graph and node labels + ''' + prefix = os.path.join(datadir, dataname, dataname) + filename_graph_indic = prefix + '_graph_indicator.txt' + # index of graphs that a given node belongs to + graph_indic = {} + with open(filename_graph_indic) as f: + i = 1 + for line in f: + line = line.strip("\n") + graph_indic[i] = int(line) + i += 1 + + filename_nodes = prefix + '_node_labels.txt' + node_labels = [] + try: + with open(filename_nodes) as f: + for line in f: + line = line.strip("\n") + node_labels += [int(line) - 1] + num_unique_node_labels = max(node_labels) + 1 + except IOError: + print('No node labels') + + filename_node_attrs = prefix + '_node_attributes.txt' + node_attrs = [] + try: + with open(filename_node_attrs) as f: + for line in f: + line = line.strip("\s\n") + attrs = [float(attr) for attr in re.split("[,\s]+", line) if not attr == ''] + node_attrs.append(np.array(attrs)) + except IOError: + print('No node attributes') + + label_has_zero = False + filename_graphs = prefix + '_graph_labels.txt' + graph_labels = [] + + # assume that all graph labels appear in the dataset + # (set of labels don't have to be consecutive) + label_vals = [] + with open(filename_graphs) as f: + for line in f: + line = line.strip("\n") + val = int(line) + # if val == 0: + # label_has_zero = True + if val not in label_vals: + label_vals.append(val) + graph_labels.append(val) + # graph_labels = np.array(graph_labels) + label_map_to_int = {val: i for i, val in enumerate(label_vals)} + graph_labels = np.array([label_map_to_int[l] for l in graph_labels]) + # if label_has_zero: + # graph_labels += 1 + + filename_adj = prefix + '_A.txt' + adj_list = {i: [] for i in range(1, len(graph_labels) + 1)} + index_graph = {i: [] for i in range(1, len(graph_labels) + 1)} + num_edges = 0 + with open(filename_adj) as f: + for line in f: + line = line.strip("\n").split(",") + e0, e1 = (int(line[0].strip(" ")), int(line[1].strip(" "))) + adj_list[graph_indic[e0]].append((e0, e1)) + index_graph[graph_indic[e0]] += [e0, e1] + num_edges += 1 + for k in index_graph.keys(): + index_graph[k] = [u - 1 for u in set(index_graph[k])] + + graphs = [] + for i in range(1, 1 + len(adj_list)): + # indexed from 1 here + G = nx.from_edgelist(adj_list[i]) + if max_nodes is not None and G.number_of_nodes() > max_nodes: + continue + + # add features and labels + G.graph['label'] = graph_labels[i - 1] + for u in node_iter(G): + if len(node_labels) > 0: + node_label_one_hot = [0] * num_unique_node_labels + node_label = node_labels[u - 1] + node_label_one_hot[node_label] = 1 + node_dict(G)[u]['label'] = node_label_one_hot + if len(node_attrs) > 0: + node_dict(G)[u]['feat'] = node_attrs[u - 1] + if len(node_attrs) > 0: + G.graph['feat_dim'] = node_attrs[0].shape[0] + + # relabeling + mapping = {} + it = 0 + for n in node_iter(G): + mapping[n] = it + it += 1 + + # indexed from 0 + graphs.append(nx.relabel_nodes(G, mapping)) + return graphs + + +def prepare_val_data(graphs, args, val_idx, max_nodes=0): +# DD 1168 + random.shuffle(graphs) + val_size = len(graphs) // 10 #116 + train_graphs = graphs[:val_idx * val_size] #1052 + if val_idx < 9: + train_graphs = train_graphs + graphs[(val_idx+1) * val_size :] + val_graphs = graphs[val_idx*val_size: (val_idx+1)*val_size] #116 + print('Num training graphs: ', len(train_graphs), + '; Num validation graphs: ', len(val_graphs)) + + print('Number of graphs: ', len(graphs)) + print('Number of edges: ', sum([G.number_of_edges() for G in graphs])) + print('Max, avg, std of graph size: ', + max([G.number_of_nodes() for G in graphs]), ', ' + "{0:.2f}".format(np.mean([G.number_of_nodes() for G in graphs])), ', ' + "{0:.2f}".format(np.std([G.number_of_nodes() for G in graphs]))) + +# #Using node labels +# Num training graphs: 1052 ; Num validation graphs: 116 +# Number of graphs: 1168 +# Number of edges: 789819 +#Max, avg, std of graph size: 903 , 268.70 , 161.33 + + # minibatch + dataset_sampler = GraphSampler(train_graphs, normalize=False, max_num_nodes=max_nodes, + features=args.feature_type) #adj_all1052; feat_all 1052; assign_feat_dim 1052; feat_dim 89 + + train_dataset_loader = tlx.dataflow.DataLoader( + dataset_sampler, + batch_size=args.batch_size, #20 #batch_sampler53 + shuffle=True, + num_workers=args.num_workers) + + dataset_sampler = GraphSampler(val_graphs, normalize=False, max_num_nodes=max_nodes, + features=args.feature_type) #116,89 + val_dataset_loader = tlx.dataflow.DataLoader( + dataset_sampler, + batch_size=args.batch_size, #20 #6 + shuffle=False, + num_workers=args.num_workers) +#53,6 + return train_dataset_loader, val_dataset_loader, \ + dataset_sampler.max_num_nodes, dataset_sampler.feat_dim, dataset_sampler.assign_feat_dim #89.89 + diff --git a/gammagl/models/diffpool.py b/gammagl/models/diffpool.py new file mode 100644 index 000000000..d17eaf1fc --- /dev/null +++ b/gammagl/models/diffpool.py @@ -0,0 +1,460 @@ +import tensorlayerx as tlx +# from gammagl.layers.conv import xConv +import numpy as np +from gammagl.models.set2set import Set2Set + +# GCN basic operation +class GraphConv(tlx.nn.Module): + @property + def all_weights(self): + return self._all_weights + + def __init__(self, input_dim, output_dim, add_self=False, normalize_embedding=False, + dropout=0.0, bias=True): + super(GraphConv, self).__init__() + self.add_self = add_self + self.dropout = dropout + if dropout > 0.001: + self.dropout_layer = tlx.nn.Dropout(p=dropout) + self.normalize_embedding = normalize_embedding + self.input_dim = input_dim # 89 #64 + self.output_dim = output_dim # 64 #100 + initor = tlx.initializers.TruncatedNormal() + self.weight = self._get_weights("weight", shape=(input_dim, output_dim),init=initor)#.cuda() # 89,64 #64,100 + #nn.Parameter(torch.FloatTensor(input_dim, output_dim).cuda()) + if bias: + self.bias = self._get_weights("bias", shape=(output_dim,),init=initor)#.cuda() #nn.Parameter(torch.FloatTensor(output_dim).cuda()) + else: + self.bias = None + + def forward(self, x, adj): + if self.dropout > 0.001: + x = self.dropout_layer(x) + y = tlx.ops.matmul(adj, x) + if self.add_self: + y += x + #print(y.is_cuda, self.weight.is_cuda) #true false + #device = 'cuda'#torch.device('cuda' if torch.cuda.is_available() else 'cpu')'' + #self.weight.to(device) + #print(y.is_cuda, self.weight.is_cuda) + y = tlx.ops.matmul(y, self.weight) + if self.bias is not None: + y = y + self.bias + if self.normalize_embedding: + y = tlx.ops.l2_normalize(y, axis=2) #l2_nor [y = F.normalize(y, p=2, dim=2)] + # print(y[0][0]) + return y + + @all_weights.setter + def all_weights(self, value): + self._all_weights = value + + +class GcnEncoderGraph(tlx.nn.Module): + r""" + Graph Convolutional Network proposed in `Semi-Supervised Classification with Graph Convolutional Networks`_. + .. _Semi-Supervised Classification with Graph Convolutional Networks: + https://arxiv.org/pdf/1609.02907.pdf + + Parameters + ---------- + input_dim (int): input dimension + hidden_dim (int): hidden dimension + embedding_dim (int): embedding dimension + label_dim (int): label dimension + num_layers (int): number of layers + pred_hidden_dims (int[]): predict hidden dimension + concat (boolean): concat to get predict input dimension + bn (boolean): batch normalization + drop_rate (float): dropout rate + args (parser): parser + """ + + @property + def all_weights(self): + return self._all_weights + + def __init__(self, input_dim, hidden_dim, embedding_dim, label_dim, num_layers, + pred_hidden_dims=[], concat=True, bn=True, dropout=0.0, args=None): + super(GcnEncoderGraph, self).__init__() + self.concat = concat + add_self = not concat + self.bn = bn + self.num_layers = num_layers + self.num_aggs = 1 + + self.bias = True + if args is not None: + self.bias = args.bias + + self.conv_first, self.conv_block, self.conv_last = self.build_conv_layers( + input_dim, hidden_dim, embedding_dim, num_layers, + add_self, normalize=True, dropout=dropout) + self.act = tlx.ReLU() + self.label_dim = label_dim + + if concat: + self.pred_input_dim = hidden_dim * (num_layers - 1) + embedding_dim + else: + self.pred_input_dim = embedding_dim + self.pred_model = self.build_pred_layers(self.pred_input_dim, pred_hidden_dims, + label_dim, num_aggs=self.num_aggs) + + if isinstance(self, GraphConv): + self.all_weights = tlx.initializers.XavierUniform(self.all_weights) + if self.bias is not None: + self.bias = tlx.initializers.constant(0.0) + # for m in self.modules():#modules(): + # if isinstance(m, GraphConv): + # m.weight.data = tlx.initializers.XavierUniform(m.weight.data) + # #(m.weight.data, gain=nn.init.calculate_gain('relu')) + # if m.bias is not None: + # m.bias.data = tlx.initializers.constant(0.0) + + def build_conv_layers(self, input_dim, hidden_dim, embedding_dim, num_layers, add_self, + normalize=False, dropout=0.0): + conv_first = GraphConv(input_dim=input_dim, output_dim=hidden_dim, add_self=add_self, + normalize_embedding=normalize, bias=self.bias) # weight89,64; bias64 + conv_block = tlx.nn.ModuleList( + [GraphConv(input_dim=hidden_dim, output_dim=hidden_dim, add_self=add_self, + normalize_embedding=normalize, dropout=dropout, bias=self.bias) + for i in range(num_layers - 2)]) + conv_last = GraphConv(input_dim=hidden_dim, output_dim=embedding_dim, add_self=add_self, + normalize_embedding=normalize, + bias=self.bias) # output_dim=assign_dim=embedding_dim=100 #weight 64,100 + return conv_first, conv_block, conv_last + + def build_pred_layers(self, pred_input_dim, pred_hidden_dims, label_dim, num_aggs=1): + pred_input_dim = pred_input_dim * num_aggs + if len(pred_hidden_dims) == 0: + pred_model = tlx.nn.Linear(in_features=pred_input_dim, out_features=label_dim) #in&out + else: + pred_layers = [] + for pred_dim in pred_hidden_dims: + pred_layers.append(tlx.nn.Linear(in_features=pred_input_dim, out_features=pred_dim)) + pred_layers.append(self.act) + pred_input_dim = pred_dim + pred_layers.append(tlx.nn.Linear(pred_dim, label_dim)) + pred_model = tlx.nn.SequentialLayer(*pred_layers) + return pred_model + + def construct_mask(self, max_nodes, batch_num_nodes): + ''' For each num_nodes in batch_num_nodes, the first num_nodes entries of the + corresponding column are 1's, and the rest are 0's (to be masked out). + Dimension of mask: [batch_size x max_nodes x 1] + ''' + # masks + + tup= [] + for num in batch_num_nodes: + tup.append(int(num)) + packed_masks = tlx.ones(shape=(1,tup[0]))#!@@! + # packed_masks = [tlx.ops.ones(int(num) for num in batch_num_nodes)] + #packed_masks = [torch.ones(int(num)) for num in batch_num_nodes] + batch_size = len(batch_num_nodes) + out_tensor = tlx.ops.zeros(shape=(batch_size, max_nodes)) + for i, mask in enumerate(packed_masks): + out_tensor[i, :batch_num_nodes[i]] = mask + return out_tensor.unsqueeze(2).cuda() + + def apply_bn(self, x): + ''' Batch normalization of 3D tensor x + ''' + #bn_module = tlx.nn.BatchNorm1d(x.size()[1]).cuda() #20,59,20 --》对59归一 + device = 'cpu' + bn_module=tlx.nn.BatchNorm1d()(x.to(device)) #.size()[1] + #decay momentum 0.1 + return bn_module #(x) + + def gcn_forward(self, x, adj, conv_first, conv_block, conv_last, embedding_mask=None): + + ''' Perform forward prop with graph convolution. + Returns: + Embedding matrix with dimension [batch_size x num_nodes x embedding] + ''' + + x = conv_first(x, adj) + x = self.act(x) + if self.bn: + x = self.apply_bn(x) + x_all = [x] + # out_all = [] + # out, _ = torch.max(x, dim=1) + # out_all.append(out) + for i in range(len(conv_block)): + x = conv_block[i](x, adj) + x = self.act(x) + if self.bn: + x = self.apply_bn(x) + x_all.append(x) + x = conv_last(x, adj) + x_all.append(x) + # x_tensor: [batch_size x num_nodes x embedding] + x_tensor = tlx.ops.concat(x_all, 2)#x_tensor = torch.cat(x_all, dim=2) + if embedding_mask is not None: + x_tensor = x_tensor * embedding_mask + return x_tensor + + def forward(self, x, adj, batch_num_nodes=None, **kwargs): + #mask + #max_num_nodes = np.size(adj) + max_num_nodes = adj.size()[1] + if batch_num_nodes is not None: + self.embedding_mask = self.construct_mask(max_num_nodes, batch_num_nodes) + else: + self.embedding_mask = None + + # conv + x = self.conv_first(x, adj) #索引矩阵,索引矩阵的行,索引矩阵的列。 + x = self.act(x) + # print(x.is_cuda) + if self.bn: + x = self.apply_bn(x) #59nawei + out_all = [] + out = tlx.ops.reduce_max(x, axis=1) #out, _ = torch.max(x, dim=1) 每行的最大值 + out_all.append(out) + for i in range(self.num_layers - 2): + x = self.conv_block[i](x, adj) + x = self.act(x) + if self.bn: + x = self.apply_bn(x) + out = tlx.ops.reduce_max(x, 1) #out, _每行的最大值 + out_all.append(out) + if self.num_aggs == 2: + out = tlx.ops.cumsum(x, 1) #torch.sum(x,dim=1) 每行的和 + out_all.append(out) + x = self.conv_last(x, adj) + # x = self.act(x) + out= tlx.ops.reduce_max(x, 1) #out, _ + out_all.append(out) + if self.num_aggs == 2: + out = tlx.ops.cumsum(x, 1) + out_all.append(out) + if self.concat: + output = tlx.ops.concat(out_all, 1) #沿行拼接 dim=1 + else: + output = out #20,60 + ypred = self.pred_model(output) #20,2 + # print(output.size()) + return ypred + + def loss(self, pred, label, type='softmax'): + # softmax + CE + if type == 'softmax': + return tlx.losses.softmax_cross_entropy_with_logits(pred, label, reduction='mean') + elif type == 'margin': + batch_size = pred.size()[0] + label_onehot = tlx.ops.zeros(batch_size, self.label_dim).long().cuda() + label_onehot.scatter_(1, label.view(-1, 1), 1) + return tlx.losses.binary_cross_entropy(pred, label_onehot) #!!!!!! + #return torch.nn.MultiLabelMarginLoss()(pred, label_onehot) #multi-label one-versus-all 多分类 + # return F.binary_cross_entropy(F.sigmoid(pred[:,0]), label.float()) + + @all_weights.setter + def all_weights(self, value): + self._all_weights = value + + +class GcnSet2SetEncoder(GcnEncoderGraph): + def __init__(self, input_dim, hidden_dim, embedding_dim, label_dim, num_layers, + pred_hidden_dims=[], concat=True, bn=True, dropout=0.0, args=None): + super(GcnSet2SetEncoder, self).__init__(input_dim, hidden_dim, embedding_dim, label_dim, + num_layers, pred_hidden_dims, concat, bn, dropout, args=args) + self.s2s = Set2Set(self.pred_input_dim, self.pred_input_dim * 2) + + def forward(self, x, adj, batch_num_nodes=None, **kwargs): + # mask + max_num_nodes = adj.size()[1] + if batch_num_nodes is not None: + embedding_mask = self.construct_mask(max_num_nodes, batch_num_nodes) + else: + embedding_mask = None + + embedding_tensor = self.gcn_forward(x, adj, + self.conv_first, self.conv_block, self.conv_last, embedding_mask) + out = self.s2s(embedding_tensor) + # out, _ = torch.max(embedding_tensor, dim=1) + ypred = self.pred_model(out) + return ypred + + +class SoftPoolingGcnEncoder(GcnEncoderGraph): + def __init__(self, max_num_nodes, input_dim, hidden_dim, embedding_dim, label_dim, num_layers, + assign_hidden_dim, assign_ratio=0.25, assign_num_layers=-1, num_pooling=1, + pred_hidden_dims=[50], concat=True, bn=True, dropout=0.0, linkpred=True, + assign_input_dim=-1, args=None): + ''' + Args: + num_layers: number of gc layers before each pooling + num_nodes: number of nodes for each graph in batch + linkpred: flag to turn on link prediction side objective + ''' + + super(SoftPoolingGcnEncoder, self).__init__(input_dim, hidden_dim, embedding_dim, label_dim, + num_layers, pred_hidden_dims=pred_hidden_dims, concat=concat, + args=args) + add_self = not concat + self.num_pooling = num_pooling + self.linkpred = linkpred + self.assign_ent = True + + # GC + self.conv_first_after_pool = tlx.nn.ModuleList() + self.conv_block_after_pool = tlx.nn.ModuleList() + self.conv_last_after_pool = tlx.nn.ModuleList() + for i in range(num_pooling): + # use self to register the modules in self.modules() + conv_first2, conv_block2, conv_last2 = self.build_conv_layers( + self.pred_input_dim, hidden_dim, embedding_dim, num_layers, + add_self, normalize=True, dropout=dropout) + self.conv_first_after_pool.append(conv_first2) + self.conv_block_after_pool.append(conv_block2) + self.conv_last_after_pool.append(conv_last2) + + # assignment + assign_dims = [] + if assign_num_layers == -1: + assign_num_layers = num_layers + if assign_input_dim == -1: + assign_input_dim = input_dim + + self.assign_conv_first_modules = tlx.nn.ModuleList() + self.assign_conv_block_modules = tlx.nn.ModuleList() + self.assign_conv_last_modules = tlx.nn.ModuleList() + self.assign_pred_modules = tlx.nn.ModuleList() + assign_dim = int(max_num_nodes * assign_ratio) # 100 + for i in range(num_pooling): + assign_dims.append(assign_dim) + assign_conv_first, assign_conv_block, assign_conv_last = self.build_conv_layers( + assign_input_dim, assign_hidden_dim, assign_dim, assign_num_layers, add_self, + normalize=True) + assign_pred_input_dim = assign_hidden_dim * (num_layers - 1) + assign_dim if concat else assign_dim # 228 + assign_pred = self.build_pred_layers(assign_pred_input_dim, [], assign_dim, num_aggs=1) + + # next pooling layer + assign_input_dim = self.pred_input_dim + assign_dim = int(assign_dim * assign_ratio) + + self.assign_conv_first_modules.append(assign_conv_first) + self.assign_conv_block_modules.append(assign_conv_block) + self.assign_conv_last_modules.append(assign_conv_last) + self.assign_pred_modules.append(assign_pred) + + self.pred_model = self.build_pred_layers(self.pred_input_dim * (num_pooling + 1), pred_hidden_dims, + label_dim, num_aggs=self.num_aggs) + + for m in self.named_children(): #modules() + if isinstance(m, GraphConv): + m.weight.data = tlx.initializers.XavierUniform(m.weight.data)# gain=nn.init.calculate_gain('relu')) + if m.bias is not None: + m.bias.data = tlx.initializers.constant(0.0) + + def forward(self, x, adj, batch_num_nodes, **kwargs): + if 'assign_x' in kwargs: + x_a = kwargs['assign_x'] + else: + x_a = x + + # mask + max_num_nodes = adj.size()[1] + if batch_num_nodes is not None: + embedding_mask = self.construct_mask(max_num_nodes, batch_num_nodes) + else: + embedding_mask = None + + out_all = [] + + # self.assign_tensor = self.gcn_forward(x_a, adj, + # self.assign_conv_first_modules[0], self.assign_conv_block_modules[0], self.assign_conv_last_modules[0], + # embedding_mask) + ## [batch_size x num_nodes x next_lvl_num_nodes] + # self.assign_tensor = nn.Softmax(dim=-1)(self.assign_pred(self.assign_tensor)) + # if embedding_mask is not None: + # self.assign_tensor = self.assign_tensor * embedding_mask + # [batch_size x num_nodes x embedding_dim] + embedding_tensor = self.gcn_forward(x, adj, + self.conv_first, self.conv_block, self.conv_last, embedding_mask) + + out, _ = tlx.ops.reduce_max(embedding_tensor, 1) + out_all.append(out) + if self.num_aggs == 2: + out = tlx.ops.cumsum(embedding_tensor, 1) + out_all.append(out) + + for i in range(self.num_pooling): + if batch_num_nodes is not None and i == 0: + embedding_mask = self.construct_mask(max_num_nodes, batch_num_nodes) + else: + embedding_mask = None + + self.assign_tensor = self.gcn_forward(x_a, adj, + self.assign_conv_first_modules[i], self.assign_conv_block_modules[i], + self.assign_conv_last_modules[i], + embedding_mask) + # [batch_size x num_nodes x next_lvl_num_nodes] + self.assign_tensor = tlx.nn.Softmax()(self.assign_pred_modules[i](self.assign_tensor)) + #nn.Softmax(dim=-1)(self.assign_pred_modules[i](self.assign_tensor)) + if embedding_mask is not None: + self.assign_tensor = self.assign_tensor * embedding_mask + + # update pooled features and adj matrix + x = tlx.ops.matmul(tlx.ops.transpose(self.assign_tensor), embedding_tensor) + #torch.transpose(self.assign_tensor, 1, 2), embedding_tensor + adj = tlx.ops.transpose(self.assign_tensor) @ adj @ self.assign_tensor + #torch.transpose(self.assign_tensor, 1, 2) @ adj @ self.assign_tensor + x_a = x + + embedding_tensor = self.gcn_forward(x, adj, + self.conv_first_after_pool[i], self.conv_block_after_pool[i], + self.conv_last_after_pool[i]) + + out, _ = tlx.ops.reduce_max(embedding_tensor, 1) + out_all.append(out) + if self.num_aggs == 2: + # out = torch.mean(embedding_tensor, dim=1) + out = tlx.ops.cumsum(embedding_tensor, 1) + out_all.append(out) + + if self.concat: + output = tlx.ops.concat(out_all, 1) #(out_all) ? + else: + output = out + ypred = self.pred_model(output) + return ypred + + def loss(self, pred, label, adj=None, batch_num_nodes=None, adj_hop=1): + ''' + Args: + batch_num_nodes: numpy array of number of nodes in each graph in the minibatch. + ''' + eps = 1e-7 + loss = super(SoftPoolingGcnEncoder, self).loss(pred, label) + if self.linkpred: + max_num_nodes = adj.size()[1] + pred_adj0 = self.assign_tensor @ tlx.ops.transpose(self.assign_tensor) + tmp = pred_adj0 + pred_adj = pred_adj0 + for adj_pow in range(adj_hop - 1): + tmp = tmp @ pred_adj0 + pred_adj = pred_adj + tmp + pred_adj = tlx.ops.reduce_min(pred_adj, tlx.ops.ones(1, dtype=pred_adj.dtype).cuda()) + # print('adj1', torch.sum(pred_adj0) / torch.numel(pred_adj0)) + # print('adj2', torch.sum(pred_adj) / torch.numel(pred_adj)) + # self.link_loss = F.nll_loss(torch.log(pred_adj), adj) + self.link_loss = -adj * tlx.ops.log(pred_adj + eps) - (1 - adj) * tlx.ops.log(1 - pred_adj + eps) + if batch_num_nodes is None: + num_entries = max_num_nodes * max_num_nodes * adj.size()[0] + print('Warning: calculating link pred loss without masking') + else: + num_entries = np.sum(batch_num_nodes * batch_num_nodes) + embedding_mask = self.construct_mask(max_num_nodes, batch_num_nodes) + adj_mask = embedding_mask @ tlx.ops.transpose(embedding_mask) + self.link_loss[(1 - adj_mask).bool()] = 0.0 + + self.link_loss = tlx.ops.cumsum(self.link_loss) / float(num_entries) + # print('linkloss: ', self.link_loss) + return loss + self.link_loss + return loss + + + From 53aa769ee74c6f0632ab64008eaf1e4861f4f237 Mon Sep 17 00:00:00 2001 From: aurora-xin <2398486514@qq.com> Date: Tue, 24 May 2022 15:36:32 +0800 Subject: [PATCH 2/2] update delete redundant code, merge graph sampler into dd.py --- examples/diffpool/README.md | 17 +- examples/diffpool/diffpool_trainer.py | 365 +++----------------------- gammagl/data/graph_sampler.py | 143 ---------- gammagl/datasets/dd.py | 156 ++++++++--- gammagl/models/diffpool.py | 273 ++----------------- 5 files changed, 182 insertions(+), 772 deletions(-) delete mode 100644 gammagl/data/graph_sampler.py diff --git a/examples/diffpool/README.md b/examples/diffpool/README.md index 1a8903bb6..e3f41ebdc 100644 --- a/examples/diffpool/README.md +++ b/examples/diffpool/README.md @@ -1,26 +1,27 @@ # Differentiable Pooling -- Paper link: [ https://arxiv.org/abs/1806.08804]( https://arxiv.org/abs/1806.08804) +- Paper link: [https://arxiv.org/abs/1806.08804]( https://arxiv.org/abs/1806.08804) - Author's code repo: [https://github.com/RexYing/diffpool](https://github.com/RexYing/diffpool). Note that the original code is implemented with Pytorch for the paper. # Dataset Statics -| Dataset | # Graphs | # Edges | # Avg of graph size | -|---------|----------|----------|---------------------| -| syn1v2 | 1000 | 181067 | 49.23 | -| D&D | | | | -| ENZYMES | | | | +| Dataset | # Graphs | # Edges | # Avg of graph size | +|---------|----------|---------|---------------------| +| syn1v2 | 1000 | 181067 | 49.23 | +| D&D | 1168 | 789819 | 268.70 | +| ENZYMES | 600 | 37282 | 32.46 | Results ------- ```bash python diffpool_trainer.py --dataset syn1v2 --num_pool 1 --epochs 50 +python diffpool_trainer.py --bmname DD ``` | Dataset | Paper | Our(pytorch) | Our(tf) | |---------|-------|--------------|---------| -| syn1v2 | | 0.800002 | | -| D&D | 81.15 | | | +| syn1v2 | - | 0.80 | | +| D&D | 81.15 | 79.31 | | | ENZYMES | 64.23 | | | \ No newline at end of file diff --git a/examples/diffpool/diffpool_trainer.py b/examples/diffpool/diffpool_trainer.py index 50fdf5da5..0e1d8fd0d 100644 --- a/examples/diffpool/diffpool_trainer.py +++ b/examples/diffpool/diffpool_trainer.py @@ -12,7 +12,6 @@ # os.environ['CUDA_LAUNCH_BLOCKING'] = "1" import sys sys.path.insert(0, os.path.abspath('../../')) - import pickle import shutil import random @@ -22,54 +21,32 @@ import networkx as nx import abc from tensorboardX import SummaryWriter -from tensorlayerx import Variable #from torch.autograd import Variable +from tensorlayerx import Variable import sklearn.metrics as metrics - -from gammagl.datasets.dd import prepare_val_data,read_graphfile -from gammagl.models.diffpool import GcnEncoderGraph#,GcnSet2SetEncoder,SoftPoolingGcnEncoder -from gammagl.data.graph_sampler import GraphSampler - +from gammagl.datasets.dd import prepare_val_data,read_graphfile,GraphSampler +from gammagl.models.diffpool import GcnEncoderGraph def evaluate(dataset, model, args, name='Validation', max_num_examples=None): model.eval() labels = [] preds = [] for batch_idx, data in enumerate(dataset): - adj = Variable(data['adj'].float())#.cuda() #requires_grad=False - h0 = Variable(data['feats'].float())#.cuda() + adj = Variable(data['adj'].float()) + h0 = Variable(data['feats'].float()) labels.append(data['label'].long().numpy()) batch_num_nodes = data['num_nodes'].int().numpy() - assign_input = Variable(data['assign_feats'].float())#.cuda() - #Variable(data['assign_feats'].float(), requires_grad=False).cuda() - ypred = model(h0, adj, batch_num_nodes, assign_x=assign_input) #20,2 - indices = tlx.argmax(ypred, axis=1) #_, indices = torch.max(ypred, 1) #20 + assign_input = Variable(data['assign_feats'].float()) + ypred = model(h0, adj, batch_num_nodes, assign_x=assign_input) + indices = tlx.argmax(ypred, axis=1) preds.append(indices.cpu().data.numpy()) - if max_num_examples is not None: if (batch_idx + 1) * args.batch_size > max_num_examples: break - #6*20 --> 120 labels = np.hstack(labels) preds = np.hstack(preds) - # labels=tlx.convert_to_tensor(labels) #6*20=120 - # preds=tlx.convert_to_tensor(preds) #6*20=120 - - - # metrics = tlx.metrics.Accuracy() - # metrics.update(preds, labels) - # result = metrics.result() - #print(name, " accuracy:", result) - # metrics.reset() - - result = { - # 'prec': metrics.precision_score(labels, preds, average='macro'), - # 'recall': metrics.recall_score(labels, preds, average='macro'), - 'acc': metrics.accuracy_score(labels, preds) - #,'F1': metrics.f1_score(labels, preds, average="micro") - } + result = { 'acc': metrics.accuracy_score(labels, preds)} print(name, " accuracy:", result['acc']) - return result def gen_prefix(args): @@ -78,13 +55,8 @@ def gen_prefix(args): else: name = args.dataset name += '_' + args.method - if args.method == 'soft-assign': - name += '_l' + str(args.num_gc_layers) + 'x' + str(args.num_pool) - name += '_ar' + str(int(args.assign_ratio*100)) - if args.linkpred: - name += '_lp' - else: - name += '_l' + str(args.num_gc_layers) + + name += '_l' + str(args.num_gc_layers) name += '_h' + str(args.hidden_dim) + '_o' + str(args.output_dim) if not args.bias: name += '_nobias' @@ -98,57 +70,36 @@ def train(dataset, model, args, same_feat=True, val_dataset=None, test_dataset=N writer_batch_idx = [0, 3, 6, 9] optimizer = tlx.optimizers.Adam(lr=0.001) - #参数:(filter(lambda p: p.requires_grad, model.parameters()), learning_rate=0.001) iter = 0 - # best_val_result = { - # 'epoch': 0, - # 'loss': 0, - # 'acc': 0} - # test_result = { - # 'epoch': 0, - # 'loss': 0, - # 'acc': 0} train_accs = [] train_epochs = [] - # best_val_accs = [] - # best_val_epochs = [] test_accs = [] - # test_epochs = [] val_accs = [] for epoch in range(args.num_epochs): total_time = 0 avg_loss = 0.0 - #model.train() model.set_train() - print('Epoch: ', epoch) for batch_idx, data in enumerate(dataset): - # adj:20,1000,1000; feat 20,1000,89; assign_feats:20,1000,89; label:20 - # begin_time = time.time() model.zero_grad() - adj = Variable(data['adj'].float(),"adj",False)#.cuda() #,requires_grad=False - h0 = Variable(data['feats'].float(),"h0",False)#.cuda()#,requires_grad=False - label = Variable(data['label'].long(),"label",False)#.cuda() + adj = Variable(data['adj'].float(),"adj",False) + h0 = Variable(data['feats'].float(),"h0",False) + label = Variable(data['label'].long(),"label",False) batch_num_nodes = data['num_nodes'].int().numpy() if mask_nodes else None - assign_input = Variable(data['assign_feats'].float(),"assign_input",False)#.cuda()#,requires_grad=False + assign_input = Variable(data['assign_feats'].float(),"assign_input",False) - - ypred = model(h0, adj, batch_num_nodes, assign_x=assign_input) # 20,2 + ypred = model(h0, adj, batch_num_nodes, assign_x=assign_input) if not args.method == 'soft-assign' or not args.linkpred: loss = model.loss(ypred, label) else: loss = model.loss(ypred, label, adj, batch_num_nodes) #loss.backward() - # torch.nn.utils.clip_grad_norm_(model.parameters(), args.clip) - #optimizer.step() - g=optimizer.gradient(loss,weights=model.trainable_weights) - optimizer.apply_gradients(zip(g,model.trainable_weights)) + g=optimizer.gradient(loss, weights=model.trainable_weights) + optimizer.apply_gradients(zip(g, model.trainable_weights)) iter += 1 avg_loss += loss - # if iter % 20 == 0: - # print('Iter: ', iter, ', loss: ', loss.data[0]) avg_loss /= batch_idx + 1 if writer is not None: @@ -162,26 +113,14 @@ def train(dataset, model, args, same_feat=True, val_dataset=None, test_dataset=N if val_dataset is not None: val_result = evaluate(val_dataset, model, args, name='Validation') val_accs.append(val_result['acc']) - # if val_result['acc'] > best_val_result['acc'] - 1e-7: - # best_val_result['acc'] = val_result['acc'] - # best_val_result['epoch'] = epoch - # best_val_result['loss'] = avg_loss if test_dataset is not None: test_result = evaluate(test_dataset, model, args, name='Test') test_result['epoch'] = epoch if writer is not None: writer.add_scalar('acc/train_acc', result['acc'], epoch) writer.add_scalar('acc/val_acc', val_result['acc'], epoch) - # writer.add_scalar('loss/best_val_loss', best_val_result['loss'], epoch) if test_dataset is not None: writer.add_scalar('acc/test_acc', test_result['acc'], epoch) - # print('Best val result: ', best_val_result) - # best_val_epochs.append(best_val_result['epoch']) - # best_val_accs.append(best_val_result['acc']) - # if test_dataset is not None: - # print('Test result: ', test_result) - # test_epochs.append(test_result['epoch']) - # test_accs.append(test_result['acc']) return model, val_accs @@ -189,7 +128,7 @@ def prepare_data(graphs, args, test_graphs=None, max_nodes=0): random.shuffle(graphs) if test_graphs is None: train_idx = int(len(graphs) * args.train_ratio) - test_idx = int(len(graphs) * (1 - args.test_ratio))#args + test_idx = int(len(graphs) * (1 - args.test_ratio)) train_graphs = graphs[:train_idx] val_graphs = graphs[train_idx: test_idx] test_graphs = graphs[test_idx:] @@ -197,17 +136,6 @@ def prepare_data(graphs, args, test_graphs=None, max_nodes=0): train_idx = int(len(graphs) * args.train_ratio) train_graphs = graphs[:train_idx] val_graphs = graphs[train_idx:] - # print('Num training graphs: ', len(train_graphs), - # '; Num validation graphs: ', len(val_graphs), - # '; Num testing graphs: ', len(test_graphs)) - # - # print('Number of graphs: ', len(graphs)) - # print('Number of edges: ', sum([G.number_of_edges() for G in graphs])) - # print('Max, avg, std of graph size: ', - # max([G.number_of_nodes() for G in graphs]), ', ' - # "{0:.2f}".format(np.mean([G.number_of_nodes() for G in graphs])), - # ', ' - # "{0:.2f}".format(np.std([G.number_of_nodes() for G in graphs]))) # minibatch dataset_sampler = GraphSampler(train_graphs, normalize=False, max_num_nodes=max_nodes, @@ -220,7 +148,7 @@ def prepare_data(graphs, args, test_graphs=None, max_nodes=0): dataset_sampler = GraphSampler(val_graphs, normalize=False, max_num_nodes=max_nodes, features=args.feature_type) - val_dataset_loader = tlx.dataflow.DataLoader( # torch.utils.data.DataLoader + val_dataset_loader = tlx.dataflow.DataLoader( dataset_sampler, batch_size=args.batch_size, shuffle=False, @@ -238,7 +166,6 @@ def prepare_data(graphs, args, test_graphs=None, max_nodes=0): dataset_sampler.max_num_nodes, dataset_sampler.feat_dim, dataset_sampler.assign_feat_dim -#gen #feat class FeatureGen(metaclass=abc.ABCMeta): @abc.abstractmethod def gen_node_features(self, G): @@ -262,7 +189,6 @@ def gen_node_features(self, G): feat_dict = {i:{'feat': feat[i]} for i in range(feat.shape[0])} nx.set_node_attributes(G, feat_dict) -#data def gen_ba(n_range, m_range, num_graphs, feature_generator=None): graphs = [] for i in np.random.choice(n_range, num_graphs): @@ -270,7 +196,7 @@ def gen_ba(n_range, m_range, num_graphs, feature_generator=None): graphs.append(nx.barabasi_albert_graph(i,j)) if feature_generator is None: - feature_generator = ConstFeatureGen(0) #featgen. + feature_generator = ConstFeatureGen(0) for G in graphs: feature_generator.gen_node_features(G) return graphs @@ -281,7 +207,7 @@ def gen_er(n_range, p, num_graphs, feature_generator=None): graphs.append(nx.erdos_renyi_graph(i,p)) if feature_generator is None: - feature_generator = ConstFeatureGen(0) #featgen. + feature_generator = ConstFeatureGen(0) for G in graphs: feature_generator.gen_node_features(G) return graphs @@ -297,18 +223,14 @@ def gen_2community_ba(n_range, m_range, num_graphs, inter_prob, feature_generato mu1 = np.ones(10) sigma0 = np.ones(10, 10) * 0.1 sigma1 = np.ones(10, 10) * 0.1 - fg0 = GaussianFeatureGen(mu0, sigma0) #featgen. - fg1 = GaussianFeatureGen(mu1, sigma1) #featgen. + fg0 = GaussianFeatureGen(mu0, sigma0) + fg1 = GaussianFeatureGen(mu1, sigma1) else: fg0 = feature_generators[0] fg1 = feature_generators[1] if len(feature_generators) > 1 else feature_generators[0] graphs1 = [] graphs2 = [] - #for (i1, i2) in zip(np.random.choice(n_range, num_graphs), - # np.random.choice(n_range, num_graphs)): - # for (j1, j2) in zip(np.random.choice(m_range, num_graphs), - # np.random.choice(m_range, num_graphs)): graphs0 = gen_ba(n_range, m_range, num_graphs, fg0) graphs1 = gen_ba(n_range, m_range, num_graphs, fg1) graphs = [] @@ -322,180 +244,44 @@ def gen_2community_ba(n_range, m_range, num_graphs, inter_prob, feature_generato graphs.append(G) return graphs -def gen_2hier(num_graphs, num_clusters, n, m_range, inter_prob1, inter_prob2, feat_gen): - ''' Each community is a BA graph. - Args: - inter_prob1: probability of one node connecting to any node in the other community within - the large cluster. - inter_prob2: probability of one node connecting to any node in the other community between - the large cluster. - ''' - graphs = [] - - for i in range(num_graphs): - clusters2 = [] - for j in range(len(num_clusters)): - clusters = gen_er(range(n, n+1), 0.5, num_clusters[j], feat_gen[0]) - G = nx.disjoint_union_all(clusters) - for u1 in range(G.number_of_nodes()): - if np.random.rand() < inter_prob1: - target = np.random.choice(G.number_of_nodes() - n) - # move one cluster after to make sure it's not an intra-cluster edge - if target // n >= u1 // n: - target += n - G.add_edge(u1, target) - clusters2.append(G) - G = nx.disjoint_union_all(clusters2) - cluster_sizes_cum = np.cumsum([cluster2.number_of_nodes() for cluster2 in clusters2]) - curr_cluster = 0 - for u1 in range(G.number_of_nodes()): - if u1 >= cluster_sizes_cum[curr_cluster]: - curr_cluster += 1 - if np.random.rand() < inter_prob2: - target = np.random.choice(G.number_of_nodes() - - clusters2[curr_cluster].number_of_nodes()) - # move one cluster after to make sure it's not an intra-cluster edge - if curr_cluster == 0 or target >= cluster_sizes_cum[curr_cluster - 1]: - target += cluster_sizes_cum[curr_cluster] - G.add_edge(u1, target) - graphs.append(G) - - return graphs - def syn_community1v2(args, writer=None, export_graphs=False): - graphs1 = gen_ba(range(40, 60), range(4, 5), 500,#datagen. - ConstFeatureGen(np.ones(args.input_dim, dtype=float)))#featgen. + graphs1 = gen_ba(range(40, 60), range(4, 5), 500, + ConstFeatureGen(np.ones(args.input_dim, dtype=float))) for G in graphs1: G.graph['label'] = 0 - graphs2 = gen_2community_ba(range(20, 30), range(4, 5), 500, 0.3, #datagen. - [ConstFeatureGen(np.ones(args.input_dim, dtype=float))])#featgen. + graphs2 = gen_2community_ba(range(20, 30), range(4, 5), 500, 0.3, + [ConstFeatureGen(np.ones(args.input_dim, dtype=float))]) for G in graphs2: G.graph['label'] = 1 graphs = graphs1 + graphs2 train_dataset, val_dataset, test_dataset, max_num_nodes, input_dim, assign_input_dim = prepare_data(graphs, args) - if args.method == 'soft-assign': - print('Method: soft-assign') - # model = SoftPoolingGcnEncoder( - # max_num_nodes, - # input_dim, args.hidden_dim, args.output_dim, args.num_classes, args.num_gc_layers, - # args.hidden_dim, assign_ratio=args.assign_ratio, num_pooling=args.num_pool, - # bn=args.bn, linkpred=args.linkpred, assign_input_dim=assign_input_dim).cuda() - elif args.method == 'base-set2set': - print('Method: base-set2set') - # model = GcnSet2SetEncoder(input_dim, args.hidden_dim, args.output_dim, 2, - # args.num_gc_layers, bn=args.bn).cuda() - else: - #print('Method: base') + if args.method == 'base': model = GcnEncoderGraph(input_dim, args.hidden_dim, args.output_dim, 2, - args.num_gc_layers, bn=args.bn)#.cuda() + args.num_gc_layers, bn=args.bn) train(train_dataset, model, args, val_dataset=val_dataset, test_dataset=test_dataset, writer=writer) - -def syn_community2hier(args, writer=None): - # data - feat_gen = [ConstFeatureGen(np.ones(args.input_dim, dtype=float))]#featgen - graphs1 = gen_2hier(1000, [2, 4], 10, range(4, 5), 0.1, 0.03, feat_gen) #datagen. - graphs2 = gen_2hier(1000, [3, 3], 10, range(4, 5), 0.1, 0.03, feat_gen) #datagen. - graphs3 = gen_2community_ba(range(28, 33), range(4, 7), 1000, 0.25, feat_gen) #datagen. - - for G in graphs1: - G.graph['label'] = 0 - for G in graphs2: - G.graph['label'] = 1 - for G in graphs3: - G.graph['label'] = 2 - - graphs = graphs1 + graphs2 + graphs3 - - train_dataset, val_dataset, test_dataset, max_num_nodes, input_dim, assign_input_dim = prepare_data(graphs, args) - - if args.method == 'soft-assign': - print('Method: soft-assign') - # model = SoftPoolingGcnEncoder( - # max_num_nodes, - # input_dim, args.hidden_dim, args.output_dim, args.num_classes, args.num_gc_layers, - # args.hidden_dim, assign_ratio=args.assign_ratio, num_pooling=args.num_pool, - # bn=args.bn, linkpred=args.linkpred, args=args, assign_input_dim=assign_input_dim).cuda() - elif args.method == 'base-set2set': - print('Method: base-set2set') - # model = GcnSet2SetEncoder(input_dim, args.hidden_dim, args.output_dim, 2, - # args.num_gc_layers, bn=args.bn, args=args, - # assign_input_dim=assign_input_dim).cuda() - else: - print('Method: base') - model = GcnEncoderGraph(input_dim, args.hidden_dim, args.output_dim, 2, - args.num_gc_layers, bn=args.bn, args=args)#.cuda() - train(train_dataset, model, args, val_dataset=val_dataset, test_dataset=test_dataset, - writer=writer) - - -def pkl_task(args, feat=None): - with open(os.path.join(args.datadir, args.pkl_fname), 'rb') as pkl_file: - data = pickle.load(pkl_file) - graphs = data[0] - labels = data[1] - test_graphs = data[2] - test_labels = data[3] - - for i in range(len(graphs)): - graphs[i].graph['label'] = labels[i] - for i in range(len(test_graphs)): - test_graphs[i].graph['label'] = test_labels[i] - - if feat is None: - featgen_const = ConstFeatureGen(np.ones(args.input_dim, dtype=float)) #featgen - for G in graphs: - featgen_const.gen_node_features(G) - for G in test_graphs: - featgen_const.gen_node_features(G) - - train_dataset, test_dataset, max_num_nodes = prepare_data(graphs, args, test_graphs=test_graphs) - model = GcnEncoderGraph( - args.input_dim, args.hidden_dim, args.output_dim, args.num_classes, - args.num_gc_layers, bn=args.bn).cuda() - train(train_dataset, model, args, test_dataset=test_dataset) - evaluate(test_dataset, model, args, 'Validation') - - def benchmark_task(args, writer=None, feat='node-label'): graphs = read_graphfile(args.datadir, args.bmname, max_nodes=args.max_nodes) if feat == 'node-feat' and 'feat_dim' in graphs[0].graph: - print('Using node features') input_dim = graphs[0].graph['feat_dim'] elif feat == 'node-label' and 'label' in graphs[0].node[0]: - print('Using node labels') for G in graphs: for u in G.nodes(): G.node[u]['feat'] = np.array(G.node[u]['label']) else: - print('Using constant labels') - featgen_const = ConstFeatureGen(np.ones(args.input_dim, dtype=float)) #featgen. + featgen_const = ConstFeatureGen(np.ones(args.input_dim, dtype=float)) for G in graphs: featgen_const.gen_node_features(G) train_dataset, val_dataset, test_dataset, max_num_nodes, input_dim, assign_input_dim = \ prepare_data(graphs, args, max_nodes=args.max_nodes) - if args.method == 'soft-assign': - print('Method: soft-assign') - # model = SoftPoolingGcnEncoder( - # max_num_nodes, - # input_dim, args.hidden_dim, args.output_dim, args.num_classes, args.num_gc_layers, - # args.hidden_dim, assign_ratio=args.assign_ratio, num_pooling=args.num_pool, - # bn=args.bn, dropout=args.dropout, linkpred=args.linkpred, args=args, - # assign_input_dim=assign_input_dim).cuda() - elif args.method == 'base-set2set': - print('Method: base-set2set') - # model = GcnSet2SetEncoder( - # input_dim, args.hidden_dim, args.output_dim, args.num_classes, - # args.num_gc_layers, bn=args.bn, dropout=args.dropout, args=args).cuda() - else: - # print('Method: base') + if args.method == 'base': model = GcnEncoderGraph( input_dim, args.hidden_dim, args.output_dim, args.num_classes, args.num_gc_layers, bn=args.bn, dropout=args.dropout, args=args).cuda() @@ -504,21 +290,12 @@ def benchmark_task(args, writer=None, feat='node-label'): writer=writer) evaluate(test_dataset, model, args, 'Validation') -#import util -# ---- NetworkX compatibility def node_iter(G): - # if float(nx.__version__)<2.0: - # return G.nodes() - # else: return G.nodes def node_dict(G): - #if float(nx.__version__)>2.1: - node_dict = G.nodes #159 - # else: - # node_dict = G.node + node_dict = G.nodes return node_dict -# --------------------------- def exp_moving_avg(x, decay=0.9): shadow = x[0] @@ -528,92 +305,23 @@ def exp_moving_avg(x, decay=0.9): a.append(shadow) return a -def benchmark_task_val(args, writer=None, feat='node-label'): - all_vals = [] - graphs = read_graphfile(args.datadir, args.bmname, max_nodes=args.max_nodes) - - example_node = node_dict(graphs[0])[0] - - if feat == 'node-feat' and 'feat_dim' in graphs[0].graph: - print('Using node features') - input_dim = graphs[0].graph['feat_dim'] - elif feat == 'node-label' and 'label' in example_node: - print('Using node labels') - for G in graphs: - for u in G.nodes(): - node_dict(G)[u]['feat'] = np.array(node_dict(G)[u]['label']) # ? - else: - print('Using constant labels') - featgen_const = ConstFeatureGen(np.ones(args.input_dim, dtype=float)) #featgen. - for G in graphs: - featgen_const.gen_node_features(G) - - for i in range(10): - train_dataset, val_dataset, max_num_nodes, input_dim, assign_input_dim = \ - prepare_val_data(graphs, args, i, max_nodes=args.max_nodes) - if args.method == 'base': - print('Method: base') - model = GcnEncoderGraph( - input_dim, args.hidden_dim, args.output_dim, args.num_classes, - args.num_gc_layers, bn=args.bn, dropout=args.dropout, args=args)#.cuda() - # if args.method == 'soft-assign': - # print('Method: soft-assign') - # model = SoftPoolingGcnEncoder( - # max_num_nodes, - # input_dim, args.hidden_dim, args.output_dim, args.num_classes, args.num_gc_layers, - # args.hidden_dim, assign_ratio=args.assign_ratio, num_pooling=args.num_pool, - # bn=args.bn, dropout=args.dropout, linkpred=args.linkpred, args=args, - # assign_input_dim=assign_input_dim).cuda() - # elif args.method == 'base-set2set': - # print('Method: base-set2set') - # model = GcnSet2SetEncoder( - # input_dim, args.hidden_dim, args.output_dim, args.num_classes, - # args.num_gc_layers, bn=args.bn, dropout=args.dropout, args=args).cuda() - # else: - # print('Method: base') - # model = GcnEncoderGraph( - # input_dim, args.hidden_dim, args.output_dim, args.num_classes, - # args.num_gc_layers, bn=args.bn, dropout=args.dropout, args=args).cuda() - - - _, val_accs = train(train_dataset, model, args, val_dataset=val_dataset, test_dataset=None, - writer=writer) - all_vals.append(np.array(val_accs)) - all_vals = np.vstack(all_vals) - all_vals = np.mean(all_vals, axis=0) - print(all_vals) - print(np.max(all_vals)) - print(np.argmax(all_vals)) - - def main(prog_args): import multiprocessing as mp mp.set_start_method('spawn') # export scalar data to JSON for external processing path = os.path.join(prog_args.logdir, gen_prefix(prog_args)) if os.path.isdir(path): - # print('Remove existing log dir: ', path) shutil.rmtree(path) writer = SummaryWriter(path) writer = None - - # print('CUDA', prog_args.cuda) - if prog_args.bmname is not None: benchmark_task_val(prog_args, writer=writer) - elif prog_args.pkl_fname is not None: - pkl_task(prog_args) elif prog_args.dataset is not None: if prog_args.dataset == 'syn1v2': syn_community1v2(prog_args, writer=writer) - if prog_args.dataset == 'syn2hier': - syn_community2hier(prog_args, writer=writer) - writer.close() - if __name__ == '__main__': - # parameters setting parser = argparse.ArgumentParser(description='GraphPool arguments.') io_parser = parser.add_mutually_exclusive_group(required=False) io_parser.add_argument('--dataset', default='syn1v2',dest='dataset', @@ -621,9 +329,6 @@ def main(prog_args): benchmark_parser = io_parser.add_argument_group() benchmark_parser.add_argument('--bmname', dest='bmname', help='Name of the benchmark dataset') - io_parser.add_argument('--pkl', dest='pkl_fname', - help='Name of the pkl data file') - softpool_parser = parser.add_argument_group() softpool_parser.add_argument('--assign-ratio',default=0.1, dest='assign_ratio', type=float, help='ratio of number of nodes in consecutive layers') @@ -632,7 +337,6 @@ def main(prog_args): parser.add_argument('--linkpred', dest='linkpred', action='store_const', const=True, default=False, help='Whether link prediction side objective is used') - parser.add_argument('--datadir', dest='datadir', default='data', help='Directory where benchmark is located') parser.add_argument('--logdir', dest='logdir', default='log', @@ -676,9 +380,8 @@ def main(prog_args): parser.add_argument('--no-log-graph', dest='log_graph', action='store_const', const=False, default=True, help='Whether disable log graph') - parser.add_argument('--method', dest='method',default='base', - help='Method. Possible values: base, base-set2set, soft-assign') + help='Method. Possible values: base') parser.add_argument('--name-suffix', dest='name_suffix',default='', help='suffix added to the output filename') parser.set_defaults(datadir='data', diff --git a/gammagl/data/graph_sampler.py b/gammagl/data/graph_sampler.py deleted file mode 100644 index 5ae294f11..000000000 --- a/gammagl/data/graph_sampler.py +++ /dev/null @@ -1,143 +0,0 @@ -# !/usr/bin/env python -# -*- encoding: utf-8 -*- -import networkx as nx -import numpy as np -import tensorlayerx as tlx -# import torch -# import torch.utils.data - -import community - - -#import util -# ---- NetworkX compatibility -def node_iter(G): - # if float(nx.__version__)<2.0: - # return G.nodes() - # else: - return G.nodes - -def node_dict(G): - # if float(nx.__version__)>2.1: - # node_dict = G.nodes #159 - node_dict = G.nodes - # else: - # node_dict = G.node - return node_dict -# --------------------------- - -def exp_moving_avg(x, decay=0.9): - shadow = x[0] - a = [shadow] - for v in x[1:]: - shadow -= (1-decay) * (shadow-v) - a.append(shadow) - return a - -class GraphSampler(tlx.dataflow.Dataset): #torch.utils.data.Dataset - ''' Sample graphs and nodes in graph - ''' - def __init__(self, G_list, features='default', normalize=True, assign_feat='default', max_num_nodes=0): - self.adj_all = [] - self.len_all = [] - self.feature_all = [] - self.label_all = [] - - self.assign_feat_all = [] - - if max_num_nodes == 0: - self.max_num_nodes = max([G.number_of_nodes() for G in G_list]) - else: - self.max_num_nodes = max_num_nodes #1000 - - - if features == 'default': - self.feat_dim = node_dict(G_list[0])[0]['feat'].shape[0] #89 - -#G_list 1052 - for G in G_list: - adj = np.array(nx.to_numpy_matrix(G)) #159,159 - if normalize: - sqrt_deg = np.diag(1.0 / np.sqrt(np.sum(adj, axis=0, dtype=float).squeeze())) - adj = np.matmul(np.matmul(sqrt_deg, adj), sqrt_deg) - self.adj_all.append(adj) - self.len_all.append(G.number_of_nodes()) - self.label_all.append(G.graph['label']) - # feat matrix: max_num_nodes x feat_dim - if features == 'default': - f = np.zeros((self.max_num_nodes, self.feat_dim), dtype=float) #1000,89 - for i,u in enumerate(G.nodes()): #314 - f[i,:] = node_dict(G)[u]['feat'] - self.feature_all.append(f) #1000,89 - elif features == 'id': - self.feature_all.append(np.identity(self.max_num_nodes)) - elif features == 'deg-num': - degs = np.sum(np.array(adj), 1) - degs = np.expand_dims(np.pad(degs, [0, self.max_num_nodes - G.number_of_nodes()], 0), - axis=1) - self.feature_all.append(degs) - elif features == 'deg': - self.max_deg = 10 - degs = np.sum(np.array(adj), 1).astype(int) - degs[degs > self.max_deg] = self.max_deg - feat = np.zeros((len(degs), self.max_deg + 1)) - feat[np.arange(len(degs)), degs] = 1 - feat = np.pad(feat, ((0, self.max_num_nodes - G.number_of_nodes()), (0, 0)), - 'constant', constant_values=0) - - f = np.zeros((self.max_num_nodes, self.feat_dim), dtype=float) - for i,u in enumerate(node_iter(G)): - f[i,:] = node_dict(G)[u]['feat'] - - feat = np.concatenate((feat, f), axis=1) - - self.feature_all.append(feat) - elif features == 'struct': - self.max_deg = 10 - degs = np.sum(np.array(adj), 1).astype(int) - degs[degs>10] = 10 - feat = np.zeros((len(degs), self.max_deg + 1)) - feat[np.arange(len(degs)), degs] = 1 - degs = np.pad(feat, ((0, self.max_num_nodes - G.number_of_nodes()), (0, 0)), - 'constant', constant_values=0) - - clusterings = np.array(list(nx.clustering(G).values())) - clusterings = np.expand_dims(np.pad(clusterings, - [0, self.max_num_nodes - G.number_of_nodes()], - 'constant'), - axis=1) - g_feat = np.hstack([degs, clusterings]) - if 'feat' in node_dict(G)[0]: - node_feats = np.array([node_dict(G)[i]['feat'] for i in range(G.number_of_nodes())]) - node_feats = np.pad(node_feats, ((0, self.max_num_nodes - G.number_of_nodes()), (0, 0)), - 'constant') - g_feat = np.hstack([g_feat, node_feats]) - - self.feature_all.append(g_feat) - - if assign_feat == 'id': - self.assign_feat_all.append( - np.hstack((np.identity(self.max_num_nodes), self.feature_all[-1])) ) - else: - self.assign_feat_all.append(self.feature_all[-1]) - - self.feat_dim = self.feature_all[0].shape[1] - self.assign_feat_dim = self.assign_feat_all[0].shape[1] - - def __len__(self): - return len(self.adj_all) - - def __getitem__(self, idx): - adj = self.adj_all[idx] - num_nodes = adj.shape[0] - adj_padded = np.zeros((self.max_num_nodes, self.max_num_nodes)) #1000,1000 - adj_padded[:num_nodes, :num_nodes] = adj - - # use all nodes for aggregation (baseline) - - return {'adj':adj_padded, - 'feats':self.feature_all[idx].copy(), - 'label':self.label_all[idx], - 'num_nodes': num_nodes, - 'assign_feats':self.assign_feat_all[idx].copy()} - diff --git a/gammagl/datasets/dd.py b/gammagl/datasets/dd.py index dc91cff86..94b3e06e7 100644 --- a/gammagl/datasets/dd.py +++ b/gammagl/datasets/dd.py @@ -1,31 +1,17 @@ import networkx as nx import numpy as np -import scipy as sc import os import re import tensorlayerx as tlx -# import torch - -import pickle import random -from gammagl.data.graph_sampler import GraphSampler -#import util -# ---- NetworkX compatibility def node_iter(G): - # if float(nx.__version__)<2.0: - # return G.nodes() - # else: return G.nodes def node_dict(G): - #if float(nx.__version__)>2.1: - node_dict = G.nodes #159 - # else: - # node_dict = G.node + node_dict = G.nodes return node_dict -# --------------------------- def exp_moving_avg(x, decay=0.9): shadow = x[0] @@ -35,6 +21,114 @@ def exp_moving_avg(x, decay=0.9): a.append(shadow) return a +class GraphSampler(tlx.dataflow.Dataset): + ''' Sample graphs and nodes in graph + ''' + + def __init__(self, G_list, features='default', normalize=True, assign_feat='default', max_num_nodes=0): + self.adj_all = [] + self.len_all = [] + self.feature_all = [] + self.label_all = [] + + self.assign_feat_all = [] + + if max_num_nodes == 0: + self.max_num_nodes = max([G.number_of_nodes() for G in G_list]) + else: + self.max_num_nodes = max_num_nodes # 1000 + + if features == 'default': + self.feat_dim = node_dict(G_list[0])[0]['feat'].shape[0] # 89 + + # G_list 1052 + for G in G_list: + adj = np.array(nx.to_numpy_matrix(G)) # 159,159 + if normalize: + sqrt_deg = np.diag(1.0 / np.sqrt(np.sum(adj, axis=0, dtype=float).squeeze())) + adj = np.matmul(np.matmul(sqrt_deg, adj), sqrt_deg) + self.adj_all.append(adj) + self.len_all.append(G.number_of_nodes()) + self.label_all.append(G.graph['label']) + # feat matrix: max_num_nodes x feat_dim + if features == 'default': + f = np.zeros((self.max_num_nodes, self.feat_dim), dtype=float) # 1000,89 + for i, u in enumerate(G.nodes()): # 314 + f[i, :] = node_dict(G)[u]['feat'] + self.feature_all.append(f) # 1000,89 + elif features == 'id': + self.feature_all.append(np.identity(self.max_num_nodes)) + elif features == 'deg-num': + degs = np.sum(np.array(adj), 1) + degs = np.expand_dims(np.pad(degs, [0, self.max_num_nodes - G.number_of_nodes()], 0), + axis=1) + self.feature_all.append(degs) + elif features == 'deg': + self.max_deg = 10 + degs = np.sum(np.array(adj), 1).astype(int) + degs[degs > self.max_deg] = self.max_deg + feat = np.zeros((len(degs), self.max_deg + 1)) + feat[np.arange(len(degs)), degs] = 1 + feat = np.pad(feat, ((0, self.max_num_nodes - G.number_of_nodes()), (0, 0)), + 'constant', constant_values=0) + + f = np.zeros((self.max_num_nodes, self.feat_dim), dtype=float) + for i, u in enumerate(node_iter(G)): + f[i, :] = node_dict(G)[u]['feat'] + + feat = np.concatenate((feat, f), axis=1) + + self.feature_all.append(feat) + elif features == 'struct': + self.max_deg = 10 + degs = np.sum(np.array(adj), 1).astype(int) + degs[degs > 10] = 10 + feat = np.zeros((len(degs), self.max_deg + 1)) + feat[np.arange(len(degs)), degs] = 1 + degs = np.pad(feat, ((0, self.max_num_nodes - G.number_of_nodes()), (0, 0)), + 'constant', constant_values=0) + + clusterings = np.array(list(nx.clustering(G).values())) + clusterings = np.expand_dims(np.pad(clusterings, + [0, self.max_num_nodes - G.number_of_nodes()], + 'constant'), + axis=1) + g_feat = np.hstack([degs, clusterings]) + if 'feat' in node_dict(G)[0]: + node_feats = np.array([node_dict(G)[i]['feat'] for i in range(G.number_of_nodes())]) + node_feats = np.pad(node_feats, ((0, self.max_num_nodes - G.number_of_nodes()), (0, 0)), + 'constant') + g_feat = np.hstack([g_feat, node_feats]) + + self.feature_all.append(g_feat) + + if assign_feat == 'id': + self.assign_feat_all.append( + np.hstack((np.identity(self.max_num_nodes), self.feature_all[-1]))) + else: + self.assign_feat_all.append(self.feature_all[-1]) + + self.feat_dim = self.feature_all[0].shape[1] + self.assign_feat_dim = self.assign_feat_all[0].shape[1] + + def __len__(self): + return len(self.adj_all) + + def __getitem__(self, idx): + adj = self.adj_all[idx] + num_nodes = adj.shape[0] + adj_padded = np.zeros((self.max_num_nodes, self.max_num_nodes)) # 1000,1000 + adj_padded[:num_nodes, :num_nodes] = adj + + # use all nodes for aggregation (baseline) + + return {'adj': adj_padded, + 'feats': self.feature_all[idx].copy(), + 'label': self.label_all[idx], + 'num_nodes': num_nodes, + 'assign_feats': self.assign_feat_all[idx].copy()} + + def read_graphfile(datadir, dataname, max_nodes=None): ''' Read data from https://ls11-www.cs.tu-dortmund.de/staff/morris/graphkerneldatasets graph index starts with 1 in file @@ -137,54 +231,36 @@ def read_graphfile(datadir, dataname, max_nodes=None): for n in node_iter(G): mapping[n] = it it += 1 - # indexed from 0 graphs.append(nx.relabel_nodes(G, mapping)) return graphs def prepare_val_data(graphs, args, val_idx, max_nodes=0): -# DD 1168 random.shuffle(graphs) - val_size = len(graphs) // 10 #116 - train_graphs = graphs[:val_idx * val_size] #1052 + val_size = len(graphs) // 10 + train_graphs = graphs[:val_idx * val_size] if val_idx < 9: train_graphs = train_graphs + graphs[(val_idx+1) * val_size :] - val_graphs = graphs[val_idx*val_size: (val_idx+1)*val_size] #116 - print('Num training graphs: ', len(train_graphs), - '; Num validation graphs: ', len(val_graphs)) - - print('Number of graphs: ', len(graphs)) - print('Number of edges: ', sum([G.number_of_edges() for G in graphs])) - print('Max, avg, std of graph size: ', - max([G.number_of_nodes() for G in graphs]), ', ' - "{0:.2f}".format(np.mean([G.number_of_nodes() for G in graphs])), ', ' - "{0:.2f}".format(np.std([G.number_of_nodes() for G in graphs]))) - -# #Using node labels -# Num training graphs: 1052 ; Num validation graphs: 116 -# Number of graphs: 1168 -# Number of edges: 789819 -#Max, avg, std of graph size: 903 , 268.70 , 161.33 + val_graphs = graphs[val_idx*val_size: (val_idx+1)*val_size] # minibatch dataset_sampler = GraphSampler(train_graphs, normalize=False, max_num_nodes=max_nodes, - features=args.feature_type) #adj_all1052; feat_all 1052; assign_feat_dim 1052; feat_dim 89 + features=args.feature_type) train_dataset_loader = tlx.dataflow.DataLoader( dataset_sampler, - batch_size=args.batch_size, #20 #batch_sampler53 + batch_size=args.batch_size, shuffle=True, num_workers=args.num_workers) dataset_sampler = GraphSampler(val_graphs, normalize=False, max_num_nodes=max_nodes, - features=args.feature_type) #116,89 + features=args.feature_type) val_dataset_loader = tlx.dataflow.DataLoader( dataset_sampler, - batch_size=args.batch_size, #20 #6 + batch_size=args.batch_size, shuffle=False, num_workers=args.num_workers) -#53,6 return train_dataset_loader, val_dataset_loader, \ dataset_sampler.max_num_nodes, dataset_sampler.feat_dim, dataset_sampler.assign_feat_dim #89.89 diff --git a/gammagl/models/diffpool.py b/gammagl/models/diffpool.py index d17eaf1fc..4dc715e86 100644 --- a/gammagl/models/diffpool.py +++ b/gammagl/models/diffpool.py @@ -1,7 +1,5 @@ import tensorlayerx as tlx -# from gammagl.layers.conv import xConv import numpy as np -from gammagl.models.set2set import Set2Set # GCN basic operation class GraphConv(tlx.nn.Module): @@ -17,13 +15,12 @@ def __init__(self, input_dim, output_dim, add_self=False, normalize_embedding=Fa if dropout > 0.001: self.dropout_layer = tlx.nn.Dropout(p=dropout) self.normalize_embedding = normalize_embedding - self.input_dim = input_dim # 89 #64 - self.output_dim = output_dim # 64 #100 + self.input_dim = input_dim + self.output_dim = output_dim initor = tlx.initializers.TruncatedNormal() - self.weight = self._get_weights("weight", shape=(input_dim, output_dim),init=initor)#.cuda() # 89,64 #64,100 - #nn.Parameter(torch.FloatTensor(input_dim, output_dim).cuda()) + self.weight = self._get_weights("weight", shape=(input_dim, output_dim),init=initor) if bias: - self.bias = self._get_weights("bias", shape=(output_dim,),init=initor)#.cuda() #nn.Parameter(torch.FloatTensor(output_dim).cuda()) + self.bias = self._get_weights("bias", shape=(output_dim,),init=initor) else: self.bias = None @@ -33,16 +30,11 @@ def forward(self, x, adj): y = tlx.ops.matmul(adj, x) if self.add_self: y += x - #print(y.is_cuda, self.weight.is_cuda) #true false - #device = 'cuda'#torch.device('cuda' if torch.cuda.is_available() else 'cpu')'' - #self.weight.to(device) - #print(y.is_cuda, self.weight.is_cuda) y = tlx.ops.matmul(y, self.weight) if self.bias is not None: y = y + self.bias if self.normalize_embedding: - y = tlx.ops.l2_normalize(y, axis=2) #l2_nor [y = F.normalize(y, p=2, dim=2)] - # print(y[0][0]) + y = tlx.ops.l2_normalize(y, axis=2) return y @all_weights.setter @@ -104,24 +96,18 @@ def __init__(self, input_dim, hidden_dim, embedding_dim, label_dim, num_layers, self.all_weights = tlx.initializers.XavierUniform(self.all_weights) if self.bias is not None: self.bias = tlx.initializers.constant(0.0) - # for m in self.modules():#modules(): - # if isinstance(m, GraphConv): - # m.weight.data = tlx.initializers.XavierUniform(m.weight.data) - # #(m.weight.data, gain=nn.init.calculate_gain('relu')) - # if m.bias is not None: - # m.bias.data = tlx.initializers.constant(0.0) def build_conv_layers(self, input_dim, hidden_dim, embedding_dim, num_layers, add_self, normalize=False, dropout=0.0): conv_first = GraphConv(input_dim=input_dim, output_dim=hidden_dim, add_self=add_self, - normalize_embedding=normalize, bias=self.bias) # weight89,64; bias64 + normalize_embedding=normalize, bias=self.bias) conv_block = tlx.nn.ModuleList( [GraphConv(input_dim=hidden_dim, output_dim=hidden_dim, add_self=add_self, normalize_embedding=normalize, dropout=dropout, bias=self.bias) for i in range(num_layers - 2)]) conv_last = GraphConv(input_dim=hidden_dim, output_dim=embedding_dim, add_self=add_self, normalize_embedding=normalize, - bias=self.bias) # output_dim=assign_dim=embedding_dim=100 #weight 64,100 + bias=self.bias) return conv_first, conv_block, conv_last def build_pred_layers(self, pred_input_dim, pred_hidden_dims, label_dim, num_aggs=1): @@ -148,9 +134,7 @@ def construct_mask(self, max_nodes, batch_num_nodes): tup= [] for num in batch_num_nodes: tup.append(int(num)) - packed_masks = tlx.ones(shape=(1,tup[0]))#!@@! - # packed_masks = [tlx.ops.ones(int(num) for num in batch_num_nodes)] - #packed_masks = [torch.ones(int(num)) for num in batch_num_nodes] + packed_masks = tlx.ones(shape=(1,tup[0])) batch_size = len(batch_num_nodes) out_tensor = tlx.ops.zeros(shape=(batch_size, max_nodes)) for i, mask in enumerate(packed_masks): @@ -160,11 +144,9 @@ def construct_mask(self, max_nodes, batch_num_nodes): def apply_bn(self, x): ''' Batch normalization of 3D tensor x ''' - #bn_module = tlx.nn.BatchNorm1d(x.size()[1]).cuda() #20,59,20 --》对59归一 device = 'cpu' - bn_module=tlx.nn.BatchNorm1d()(x.to(device)) #.size()[1] - #decay momentum 0.1 - return bn_module #(x) + bn_module=tlx.nn.BatchNorm1d()(x.to(device)) + return bn_module def gcn_forward(self, x, adj, conv_first, conv_block, conv_last, embedding_mask=None): @@ -178,9 +160,6 @@ def gcn_forward(self, x, adj, conv_first, conv_block, conv_last, embedding_mask= if self.bn: x = self.apply_bn(x) x_all = [x] - # out_all = [] - # out, _ = torch.max(x, dim=1) - # out_all.append(out) for i in range(len(conv_block)): x = conv_block[i](x, adj) x = self.act(x) @@ -190,14 +169,12 @@ def gcn_forward(self, x, adj, conv_first, conv_block, conv_last, embedding_mask= x = conv_last(x, adj) x_all.append(x) # x_tensor: [batch_size x num_nodes x embedding] - x_tensor = tlx.ops.concat(x_all, 2)#x_tensor = torch.cat(x_all, dim=2) + x_tensor = tlx.ops.concat(x_all, 2) if embedding_mask is not None: x_tensor = x_tensor * embedding_mask return x_tensor def forward(self, x, adj, batch_num_nodes=None, **kwargs): - #mask - #max_num_nodes = np.size(adj) max_num_nodes = adj.size()[1] if batch_num_nodes is not None: self.embedding_mask = self.construct_mask(max_num_nodes, batch_num_nodes) @@ -205,37 +182,35 @@ def forward(self, x, adj, batch_num_nodes=None, **kwargs): self.embedding_mask = None # conv - x = self.conv_first(x, adj) #索引矩阵,索引矩阵的行,索引矩阵的列。 + x = self.conv_first(x, adj) x = self.act(x) # print(x.is_cuda) if self.bn: - x = self.apply_bn(x) #59nawei + x = self.apply_bn(x) out_all = [] - out = tlx.ops.reduce_max(x, axis=1) #out, _ = torch.max(x, dim=1) 每行的最大值 + out = tlx.ops.reduce_max(x, axis=1) out_all.append(out) for i in range(self.num_layers - 2): x = self.conv_block[i](x, adj) x = self.act(x) if self.bn: x = self.apply_bn(x) - out = tlx.ops.reduce_max(x, 1) #out, _每行的最大值 + out = tlx.ops.reduce_max(x, 1) out_all.append(out) if self.num_aggs == 2: - out = tlx.ops.cumsum(x, 1) #torch.sum(x,dim=1) 每行的和 + out = tlx.ops.cumsum(x, 1) out_all.append(out) x = self.conv_last(x, adj) - # x = self.act(x) - out= tlx.ops.reduce_max(x, 1) #out, _ + out= tlx.ops.reduce_max(x, 1) out_all.append(out) if self.num_aggs == 2: out = tlx.ops.cumsum(x, 1) out_all.append(out) if self.concat: - output = tlx.ops.concat(out_all, 1) #沿行拼接 dim=1 + output = tlx.ops.concat(out_all, 1) else: - output = out #20,60 - ypred = self.pred_model(output) #20,2 - # print(output.size()) + output = out + ypred = self.pred_model(output) return ypred def loss(self, pred, label, type='softmax'): @@ -244,217 +219,15 @@ def loss(self, pred, label, type='softmax'): return tlx.losses.softmax_cross_entropy_with_logits(pred, label, reduction='mean') elif type == 'margin': batch_size = pred.size()[0] - label_onehot = tlx.ops.zeros(batch_size, self.label_dim).long().cuda() - label_onehot.scatter_(1, label.view(-1, 1), 1) - return tlx.losses.binary_cross_entropy(pred, label_onehot) #!!!!!! - #return torch.nn.MultiLabelMarginLoss()(pred, label_onehot) #multi-label one-versus-all 多分类 - # return F.binary_cross_entropy(F.sigmoid(pred[:,0]), label.float()) + diag = tlx.convert_to_tensor(np.eye(tlx.reduce_max(label) + 1), dtype=tlx.int64) + label_onehot=tlx.gather(diag, label) + return tlx.losses.binary_cross_entropy(pred, label_onehot) #MultiLabelMarginLoss()(pred, label_onehot) @all_weights.setter def all_weights(self, value): self._all_weights = value -class GcnSet2SetEncoder(GcnEncoderGraph): - def __init__(self, input_dim, hidden_dim, embedding_dim, label_dim, num_layers, - pred_hidden_dims=[], concat=True, bn=True, dropout=0.0, args=None): - super(GcnSet2SetEncoder, self).__init__(input_dim, hidden_dim, embedding_dim, label_dim, - num_layers, pred_hidden_dims, concat, bn, dropout, args=args) - self.s2s = Set2Set(self.pred_input_dim, self.pred_input_dim * 2) - - def forward(self, x, adj, batch_num_nodes=None, **kwargs): - # mask - max_num_nodes = adj.size()[1] - if batch_num_nodes is not None: - embedding_mask = self.construct_mask(max_num_nodes, batch_num_nodes) - else: - embedding_mask = None - - embedding_tensor = self.gcn_forward(x, adj, - self.conv_first, self.conv_block, self.conv_last, embedding_mask) - out = self.s2s(embedding_tensor) - # out, _ = torch.max(embedding_tensor, dim=1) - ypred = self.pred_model(out) - return ypred - - -class SoftPoolingGcnEncoder(GcnEncoderGraph): - def __init__(self, max_num_nodes, input_dim, hidden_dim, embedding_dim, label_dim, num_layers, - assign_hidden_dim, assign_ratio=0.25, assign_num_layers=-1, num_pooling=1, - pred_hidden_dims=[50], concat=True, bn=True, dropout=0.0, linkpred=True, - assign_input_dim=-1, args=None): - ''' - Args: - num_layers: number of gc layers before each pooling - num_nodes: number of nodes for each graph in batch - linkpred: flag to turn on link prediction side objective - ''' - - super(SoftPoolingGcnEncoder, self).__init__(input_dim, hidden_dim, embedding_dim, label_dim, - num_layers, pred_hidden_dims=pred_hidden_dims, concat=concat, - args=args) - add_self = not concat - self.num_pooling = num_pooling - self.linkpred = linkpred - self.assign_ent = True - - # GC - self.conv_first_after_pool = tlx.nn.ModuleList() - self.conv_block_after_pool = tlx.nn.ModuleList() - self.conv_last_after_pool = tlx.nn.ModuleList() - for i in range(num_pooling): - # use self to register the modules in self.modules() - conv_first2, conv_block2, conv_last2 = self.build_conv_layers( - self.pred_input_dim, hidden_dim, embedding_dim, num_layers, - add_self, normalize=True, dropout=dropout) - self.conv_first_after_pool.append(conv_first2) - self.conv_block_after_pool.append(conv_block2) - self.conv_last_after_pool.append(conv_last2) - - # assignment - assign_dims = [] - if assign_num_layers == -1: - assign_num_layers = num_layers - if assign_input_dim == -1: - assign_input_dim = input_dim - - self.assign_conv_first_modules = tlx.nn.ModuleList() - self.assign_conv_block_modules = tlx.nn.ModuleList() - self.assign_conv_last_modules = tlx.nn.ModuleList() - self.assign_pred_modules = tlx.nn.ModuleList() - assign_dim = int(max_num_nodes * assign_ratio) # 100 - for i in range(num_pooling): - assign_dims.append(assign_dim) - assign_conv_first, assign_conv_block, assign_conv_last = self.build_conv_layers( - assign_input_dim, assign_hidden_dim, assign_dim, assign_num_layers, add_self, - normalize=True) - assign_pred_input_dim = assign_hidden_dim * (num_layers - 1) + assign_dim if concat else assign_dim # 228 - assign_pred = self.build_pred_layers(assign_pred_input_dim, [], assign_dim, num_aggs=1) - - # next pooling layer - assign_input_dim = self.pred_input_dim - assign_dim = int(assign_dim * assign_ratio) - - self.assign_conv_first_modules.append(assign_conv_first) - self.assign_conv_block_modules.append(assign_conv_block) - self.assign_conv_last_modules.append(assign_conv_last) - self.assign_pred_modules.append(assign_pred) - - self.pred_model = self.build_pred_layers(self.pred_input_dim * (num_pooling + 1), pred_hidden_dims, - label_dim, num_aggs=self.num_aggs) - - for m in self.named_children(): #modules() - if isinstance(m, GraphConv): - m.weight.data = tlx.initializers.XavierUniform(m.weight.data)# gain=nn.init.calculate_gain('relu')) - if m.bias is not None: - m.bias.data = tlx.initializers.constant(0.0) - - def forward(self, x, adj, batch_num_nodes, **kwargs): - if 'assign_x' in kwargs: - x_a = kwargs['assign_x'] - else: - x_a = x - - # mask - max_num_nodes = adj.size()[1] - if batch_num_nodes is not None: - embedding_mask = self.construct_mask(max_num_nodes, batch_num_nodes) - else: - embedding_mask = None - - out_all = [] - - # self.assign_tensor = self.gcn_forward(x_a, adj, - # self.assign_conv_first_modules[0], self.assign_conv_block_modules[0], self.assign_conv_last_modules[0], - # embedding_mask) - ## [batch_size x num_nodes x next_lvl_num_nodes] - # self.assign_tensor = nn.Softmax(dim=-1)(self.assign_pred(self.assign_tensor)) - # if embedding_mask is not None: - # self.assign_tensor = self.assign_tensor * embedding_mask - # [batch_size x num_nodes x embedding_dim] - embedding_tensor = self.gcn_forward(x, adj, - self.conv_first, self.conv_block, self.conv_last, embedding_mask) - - out, _ = tlx.ops.reduce_max(embedding_tensor, 1) - out_all.append(out) - if self.num_aggs == 2: - out = tlx.ops.cumsum(embedding_tensor, 1) - out_all.append(out) - - for i in range(self.num_pooling): - if batch_num_nodes is not None and i == 0: - embedding_mask = self.construct_mask(max_num_nodes, batch_num_nodes) - else: - embedding_mask = None - - self.assign_tensor = self.gcn_forward(x_a, adj, - self.assign_conv_first_modules[i], self.assign_conv_block_modules[i], - self.assign_conv_last_modules[i], - embedding_mask) - # [batch_size x num_nodes x next_lvl_num_nodes] - self.assign_tensor = tlx.nn.Softmax()(self.assign_pred_modules[i](self.assign_tensor)) - #nn.Softmax(dim=-1)(self.assign_pred_modules[i](self.assign_tensor)) - if embedding_mask is not None: - self.assign_tensor = self.assign_tensor * embedding_mask - - # update pooled features and adj matrix - x = tlx.ops.matmul(tlx.ops.transpose(self.assign_tensor), embedding_tensor) - #torch.transpose(self.assign_tensor, 1, 2), embedding_tensor - adj = tlx.ops.transpose(self.assign_tensor) @ adj @ self.assign_tensor - #torch.transpose(self.assign_tensor, 1, 2) @ adj @ self.assign_tensor - x_a = x - - embedding_tensor = self.gcn_forward(x, adj, - self.conv_first_after_pool[i], self.conv_block_after_pool[i], - self.conv_last_after_pool[i]) - - out, _ = tlx.ops.reduce_max(embedding_tensor, 1) - out_all.append(out) - if self.num_aggs == 2: - # out = torch.mean(embedding_tensor, dim=1) - out = tlx.ops.cumsum(embedding_tensor, 1) - out_all.append(out) - - if self.concat: - output = tlx.ops.concat(out_all, 1) #(out_all) ? - else: - output = out - ypred = self.pred_model(output) - return ypred - - def loss(self, pred, label, adj=None, batch_num_nodes=None, adj_hop=1): - ''' - Args: - batch_num_nodes: numpy array of number of nodes in each graph in the minibatch. - ''' - eps = 1e-7 - loss = super(SoftPoolingGcnEncoder, self).loss(pred, label) - if self.linkpred: - max_num_nodes = adj.size()[1] - pred_adj0 = self.assign_tensor @ tlx.ops.transpose(self.assign_tensor) - tmp = pred_adj0 - pred_adj = pred_adj0 - for adj_pow in range(adj_hop - 1): - tmp = tmp @ pred_adj0 - pred_adj = pred_adj + tmp - pred_adj = tlx.ops.reduce_min(pred_adj, tlx.ops.ones(1, dtype=pred_adj.dtype).cuda()) - # print('adj1', torch.sum(pred_adj0) / torch.numel(pred_adj0)) - # print('adj2', torch.sum(pred_adj) / torch.numel(pred_adj)) - # self.link_loss = F.nll_loss(torch.log(pred_adj), adj) - self.link_loss = -adj * tlx.ops.log(pred_adj + eps) - (1 - adj) * tlx.ops.log(1 - pred_adj + eps) - if batch_num_nodes is None: - num_entries = max_num_nodes * max_num_nodes * adj.size()[0] - print('Warning: calculating link pred loss without masking') - else: - num_entries = np.sum(batch_num_nodes * batch_num_nodes) - embedding_mask = self.construct_mask(max_num_nodes, batch_num_nodes) - adj_mask = embedding_mask @ tlx.ops.transpose(embedding_mask) - self.link_loss[(1 - adj_mask).bool()] = 0.0 - - self.link_loss = tlx.ops.cumsum(self.link_loss) / float(num_entries) - # print('linkloss: ', self.link_loss) - return loss + self.link_loss - return loss