chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
"""
|
||||
[Predict then Propagate: Graph Neural Networks meet Personalized PageRank]
|
||||
(https://arxiv.org/abs/1810.05997)
|
||||
"""
|
||||
|
||||
import dgl.sparse as dglsp
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
from dgl.data import CoraGraphDataset
|
||||
from torch.optim import Adam
|
||||
|
||||
|
||||
class APPNP(nn.Module):
|
||||
def __init__(
|
||||
self,
|
||||
in_size,
|
||||
out_size,
|
||||
hidden_size=64,
|
||||
dropout=0.1,
|
||||
num_hops=10,
|
||||
alpha=0.1,
|
||||
):
|
||||
super().__init__()
|
||||
|
||||
self.f_theta = nn.Sequential(
|
||||
nn.Dropout(dropout),
|
||||
nn.Linear(in_size, hidden_size),
|
||||
nn.ReLU(),
|
||||
nn.Dropout(dropout),
|
||||
nn.Linear(hidden_size, out_size),
|
||||
)
|
||||
self.num_hops = num_hops
|
||||
self.A_dropout = nn.Dropout(dropout)
|
||||
self.alpha = alpha
|
||||
|
||||
def forward(self, A_hat, X):
|
||||
Z_0 = Z = self.f_theta(X)
|
||||
for _ in range(self.num_hops):
|
||||
A_drop = dglsp.val_like(A_hat, self.A_dropout(A_hat.val))
|
||||
Z = (1 - self.alpha) * A_drop @ Z + self.alpha * Z_0
|
||||
return 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
|
||||
|
||||
|
||||
def train(model, g, A_hat, X):
|
||||
label = g.ndata["label"]
|
||||
train_mask = g.ndata["train_mask"]
|
||||
optimizer = Adam(model.parameters(), lr=1e-2, weight_decay=5e-4)
|
||||
|
||||
for epoch in range(50):
|
||||
# Forward.
|
||||
model.train()
|
||||
logits = model(A_hat, X)
|
||||
|
||||
# Compute loss with nodes in training set.
|
||||
loss = F.cross_entropy(logits[train_mask], label[train_mask])
|
||||
|
||||
# Backward.
|
||||
optimizer.zero_grad()
|
||||
loss.backward()
|
||||
optimizer.step()
|
||||
|
||||
# Compute prediction.
|
||||
model.eval()
|
||||
logits = model(A_hat, X)
|
||||
pred = logits.argmax(dim=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.
|
||||
indices = torch.stack(g.edges())
|
||||
N = g.num_nodes()
|
||||
A = dglsp.spmatrix(indices, shape=(N, N))
|
||||
|
||||
# Calculate the symmetrically normalized adjacency matrix.
|
||||
I = dglsp.identity(A.shape, device=dev)
|
||||
A_hat = A + I
|
||||
D_hat = dglsp.diag(A_hat.sum(dim=1)) ** -0.5
|
||||
A_hat = D_hat @ A_hat @ D_hat
|
||||
|
||||
# Create APPNP model.
|
||||
X = g.ndata["feat"]
|
||||
in_size = X.shape[1]
|
||||
out_size = dataset.num_classes
|
||||
model = APPNP(in_size, out_size).to(dev)
|
||||
|
||||
# Kick off training.
|
||||
train(model, g, A_hat, X)
|
||||
Reference in New Issue
Block a user