figure.png

    1. import math
    2. import torch
    3. from torch import nn
    4. class GraphConvolution(nn.Module):
    5. """
    6. Simple GCN layer, similar to https://arxiv.org/abs/1609.02907
    7. """
    8. def __init__(self, in_features, out_features, bias=True):
    9. super(GraphConvolution, self).__init__()
    10. self.in_features = in_features
    11. self.out_features = out_features
    12. self.weight = nn.Parameter(torch.FloatTensor(in_features, out_features))
    13. if bias:
    14. self.bias = nn.Parameter(torch.FloatTensor(out_features))
    15. else:
    16. self.register_parameter('bias', None)
    17. self.reset_parameters()
    18. def reset_parameters(self):
    19. stdv = 1. / math.sqrt(self.weight.size(1))
    20. self.weight.data.uniform_(-stdv, stdv)
    21. if self.bias is not None:
    22. self.bias.data.uniform_(-stdv, stdv)
    23. def forward(self, input, adj):
    24. """
    25. :param input:
    26. :param adj:
    27. :return:
    28. """
    29. support = torch.mm(input, self.weight)
    30. # sparse matrix multiplication
    31. output = torch.spmm(adj, support)
    32. if self.bias is not None:
    33. return output + self.bias
    34. else:
    35. return output
    36. def __repr__(self):
    37. return self.__class__.__name__ + ' (' \
    38. + str(self.in_features) + ' -> ' \
    39. + str(self.out_features) + ')'
    1. import torch.nn as nn
    2. import torch.nn.functional as F
    3. from layers import GraphConvolution
    4. class GCN(nn.Module):
    5. def __init__(self, nfeat, nhid, nclass, dropout):
    6. super(GCN, self).__init__()
    7. self.gc1 = GraphConvolution(nfeat, nhid)
    8. self.gc2 = GraphConvolution(nhid, nclass)
    9. self.dropout = dropout
    10. def forward(self, x, adj):
    11. """
    12. :param x: [2708, 1433]
    13. :param adj: [2708, 2708]
    14. :return:
    15. """
    16. # print('x:', x.shape, 'adj:', adj.shape)
    17. # => [2708, 16]
    18. x = F.relu(self.gc1(x, adj))
    19. # print('gcn1:', x.shape)
    20. x = F.dropout(x, self.dropout, training=self.training)
    21. # => [2708, 7]
    22. x = self.gc2(x, adj)
    23. # print('gcn2:', x.shape)
    24. return F.log_softmax(x, dim=1)
    1. from __future__ import division
    2. from __future__ import print_function
    3. import time
    4. import argparse
    5. import numpy as np
    6. import torch
    7. import torch.nn.functional as F
    8. import torch.optim as optim
    9. from utils import load_data, accuracy
    10. from models import GCN
    11. # Training settings
    12. parser = argparse.ArgumentParser()
    13. parser.add_argument('--no-cuda', action='store_true', default=False,
    14. help='Disables CUDA training.')
    15. parser.add_argument('--fastmode', action='store_true', default=False,
    16. help='Validate during training pass.')
    17. parser.add_argument('--seed', type=int, default=42, help='Random seed.')
    18. parser.add_argument('--epochs', type=int, default=200,
    19. help='Number of epochs to train.')
    20. parser.add_argument('--lr', type=float, default=0.01,
    21. help='Initial learning rate.')
    22. parser.add_argument('--weight_decay', type=float, default=5e-4,
    23. help='Weight decay (L2 loss on parameters).')
    24. parser.add_argument('--hidden', type=int, default=16,
    25. help='Number of hidden units.')
    26. parser.add_argument('--dropout', type=float, default=0.5,
    27. help='Dropout rate (1 - keep probability).')
    28. args = parser.parse_args()
    29. args.cuda = not args.no_cuda and torch.cuda.is_available()
    30. np.random.seed(args.seed)
    31. torch.manual_seed(args.seed)
    32. if args.cuda:
    33. torch.cuda.manual_seed(args.seed)
    34. # Load data
    35. adj, features, labels, idx_train, idx_val, idx_test = load_data()
    36. # Model and optimizer
    37. model = GCN(nfeat=features.shape[1],
    38. nhid=args.hidden,
    39. nclass=labels.max().item() + 1,
    40. dropout=args.dropout)
    41. optimizer = optim.Adam(model.parameters(),
    42. lr=args.lr, weight_decay=args.weight_decay)
    43. print(model)
    44. if args.cuda:
    45. model.cuda()
    46. features = features.cuda()
    47. adj = adj.cuda()
    48. labels = labels.cuda()
    49. idx_train = idx_train.cuda()
    50. idx_val = idx_val.cuda()
    51. idx_test = idx_test.cuda()
    52. def train(epoch):
    53. t = time.time()
    54. model.train()
    55. # [N, 7]
    56. output = model(features, adj)
    57. loss_train = F.nll_loss(output[idx_train], labels[idx_train])
    58. acc_train = accuracy(output[idx_train], labels[idx_train])
    59. optimizer.zero_grad()
    60. loss_train.backward()
    61. optimizer.step()
    62. if not args.fastmode:
    63. # Evaluate validation set performance separately,
    64. # deactivates dropout during validation run.
    65. model.eval()
    66. output = model(features, adj)
    67. loss_val = F.nll_loss(output[idx_val], labels[idx_val])
    68. acc_val = accuracy(output[idx_val], labels[idx_val])
    69. print('Epoch: {:04d}'.format(epoch+1),
    70. 'loss_train: {:.4f}'.format(loss_train.item()),
    71. 'acc_train: {:.4f}'.format(acc_train.item()),
    72. 'loss_val: {:.4f}'.format(loss_val.item()),
    73. 'acc_val: {:.4f}'.format(acc_val.item()),
    74. 'time: {:.4f}s'.format(time.time() - t))
    75. def test():
    76. model.eval()
    77. output = model(features, adj)
    78. loss_test = F.nll_loss(output[idx_test], labels[idx_test])
    79. acc_test = accuracy(output[idx_test], labels[idx_test])
    80. print("Test set results:",
    81. "loss= {:.4f}".format(loss_test.item()),
    82. "accuracy= {:.4f}".format(acc_test.item()))
    83. # Train model
    84. t_total = time.time()
    85. for epoch in range(args.epochs):
    86. train(epoch)
    87. print("Optimization Finished!")
    88. print("Total time elapsed: {:.4f}s".format(time.time() - t_total))
    89. # Testing
    90. test()
    1. import numpy as np
    2. import scipy.sparse as sp
    3. import torch
    4. def encode_onehot(labels):
    5. classes = set(labels)
    6. classes_dict = {c: np.identity(len(classes))[i, :] for i, c in
    7. enumerate(classes)}
    8. labels_onehot = np.array(list(map(classes_dict.get, labels)),
    9. dtype=np.int32)
    10. return labels_onehot
    11. def load_data(path="./data/cora/", dataset="cora"):
    12. """Load citation network dataset (cora only for now)"""
    13. print('Loading {} dataset...'.format(dataset))
    14. idx_features_labels = np.genfromtxt("{}{}.content".format(path, dataset),
    15. dtype=np.dtype(str))
    16. features = sp.csr_matrix(idx_features_labels[:, 1:-1], dtype=np.float32)
    17. labels = encode_onehot(idx_features_labels[:, -1])
    18. # build graph
    19. idx = np.array(idx_features_labels[:, 0], dtype=np.int32)
    20. idx_map = {j: i for i, j in enumerate(idx)}
    21. edges_unordered = np.genfromtxt("{}{}.cites".format(path, dataset),
    22. dtype=np.int32)
    23. edges = np.array(list(map(idx_map.get, edges_unordered.flatten())),
    24. dtype=np.int32).reshape(edges_unordered.shape)
    25. adj = sp.coo_matrix((np.ones(edges.shape[0]), (edges[:, 0], edges[:, 1])),
    26. shape=(labels.shape[0], labels.shape[0]),
    27. dtype=np.float32)
    28. # build symmetric adjacency matrix
    29. adj = adj + adj.T.multiply(adj.T > adj) - adj.multiply(adj.T > adj)
    30. features = normalize(features)
    31. adj = normalize(adj + sp.eye(adj.shape[0]))
    32. idx_train = range(140)
    33. idx_val = range(200, 500)
    34. idx_test = range(500, 1500)
    35. features = torch.FloatTensor(np.array(features.todense()))
    36. labels = torch.LongTensor(np.where(labels)[1])
    37. adj = sparse_mx_to_torch_sparse_tensor(adj)
    38. idx_train = torch.LongTensor(idx_train)
    39. idx_val = torch.LongTensor(idx_val)
    40. idx_test = torch.LongTensor(idx_test)
    41. return adj, features, labels, idx_train, idx_val, idx_test
    42. def normalize(mx):
    43. """Row-normalize sparse matrix"""
    44. rowsum = np.array(mx.sum(1))
    45. r_inv = np.power(rowsum, -1).flatten()
    46. r_inv[np.isinf(r_inv)] = 0.
    47. r_mat_inv = sp.diags(r_inv)
    48. mx = r_mat_inv.dot(mx)
    49. return mx
    50. def accuracy(output, labels):
    51. preds = output.max(1)[1].type_as(labels)
    52. correct = preds.eq(labels).double()
    53. correct = correct.sum()
    54. return correct / len(labels)
    55. def sparse_mx_to_torch_sparse_tensor(sparse_mx):
    56. """Convert a scipy sparse matrix to a torch sparse tensor."""
    57. sparse_mx = sparse_mx.tocoo().astype(np.float32)
    58. indices = torch.from_numpy(
    59. np.vstack((sparse_mx.row, sparse_mx.col)).astype(np.int64))
    60. values = torch.from_numpy(sparse_mx.data)
    61. shape = torch.Size(sparse_mx.shape)
    62. return torch.sparse.FloatTensor(indices, values, shape)