sign.py 3.72 KB
Newer Older
1
2
3
4
5
6
7
8
"""
[SIGN: Scalable Inception Graph Neural Networks]
(https://arxiv.org/abs/2004.11198)

This example shows a simplified version of SIGN: a precomputed 2-hops diffusion
operator on top of symmetrically normalized adjacency matrix A_hat.
"""

9
import dgl.mock_sparse2 as dglsp
10
11
12
13
import torch
import torch.nn as nn
import torch.nn.functional as F
from dgl.data import CoraGraphDataset
Mufei Li's avatar
Mufei Li committed
14
15
from torch.optim import Adam

16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
################################################################################
# (HIGHLIGHT) Take the advantage of DGL sparse APIs to implement the feature
# diffusion in SIGN laconically.
################################################################################
def sign_diffusion(A, X, r):
    # Perform the r-hop diffusion operation.
    X_sign = [X]
    for _ in range(r):
        X = A @ X
        X_sign.append(X)
    return X_sign


class SIGN(nn.Module):
    def __init__(self, in_size, out_size, r, hidden_size=256):
        super().__init__()
        # Note that theta and omega refer to the learnable matrices in the
        # original paper correspondingly. The variable r refers to subscript to
        # theta.
        self.theta = nn.ModuleList(
            [nn.Linear(in_size, hidden_size) for _ in range(r + 1)]
        )
        self.omega = nn.Linear(hidden_size * (r + 1), out_size)

    def forward(self, X_sign):
        results = []
        for i in range(len(X_sign)):
            results.append(self.theta[i](X_sign[i]))
        Z = F.relu(torch.cat(results, dim=1))
        return self.omega(Z)


def evaluate(g, pred):
    label = g.ndata["label"]
    val_mask = g.ndata["val_mask"]
    test_mask = g.ndata["test_mask"]

    # Compute accuracy on validation/test set.
    val_acc = (pred[val_mask] == label[val_mask]).float().mean()
    test_acc = (pred[test_mask] == label[test_mask]).float().mean()
    return val_acc, test_acc


Hongzhi (Steve), Chen's avatar
Hongzhi (Steve), Chen committed
59
def train(model, g, X_sign):
60
    label = g.ndata["label"]
61
62
63
64
    train_mask = g.ndata["train_mask"]
    optimizer = Adam(model.parameters(), lr=3e-3)

    for epoch in range(10):
65
66
67
        # Switch the model to training mode.
        model.train()

68
69
70
71
        # Forward.
        logits = model(X_sign)

        # Compute loss with nodes in training set.
72
        loss = F.cross_entropy(logits[train_mask], label[train_mask])
73
74
75
76
77
78

        # Backward.
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

79
80
81
        # Switch the model to evaluating mode.
        model.eval()

82
        # Compute prediction.
83
        logits = model(X_sign)
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
        pred = logits.argmax(1)

        # Evaluate the prediction.
        val_acc, test_acc = evaluate(g, pred)
        print(
            f"In epoch {epoch}, loss: {loss:.3f}, val acc: {val_acc:.3f}, test"
            f" acc: {test_acc:.3f}"
        )


if __name__ == "__main__":
    # If CUDA is available, use GPU to accelerate the training, use CPU
    # otherwise.
    dev = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")

    # Load graph from the existing dataset.
    dataset = CoraGraphDataset()
    g = dataset[0].to(dev)

    # Create the sparse adjacency matrix A (note that W was used as the notation
    # for adjacency matrix in the original paper).
    src, dst = g.edges()
    N = g.num_nodes()
107
    A = dglsp.create_from_coo(dst, src, shape=(N, N))
108
109

    # Calculate the symmetrically normalized adjacency matrix.
110
    I = dglsp.identity(A.shape, device=dev)
111
    A_hat = A + I
112
    D_hat = dglsp.diag(A_hat.sum(dim=1)) ** -0.5
113
114
115
116
117
118
119
120
121
122
123
124
125
    A_hat = D_hat @ A_hat @ D_hat

    # 2-hop diffusion.
    r = 2
    X = g.ndata["feat"]
    X_sign = sign_diffusion(A_hat, X, r)

    # Create SIGN model.
    in_size = X.shape[1]
    out_size = dataset.num_classes
    model = SIGN(in_size, out_size, r).to(dev)

    # Kick off training.
Hongzhi (Steve), Chen's avatar
Hongzhi (Steve), Chen committed
126
    train(model, g, X_sign)