Lab: Node Classification on Cora
In this lab, we will solve the classic Node Classification problem using the Cora citation network. We will build a GCN from scratch using PyTorch and PyTorch Geometric to categorize scientific papers based on their citations.
1. The Dataset: Cora Citation Network
The Cora dataset is the "MNIST" of graph machine learning.
- Nodes: 2,708 scientific papers.
- Edges: 5,429 citation links.
- Features: 1,433-dimensional word vectors (binary indicators of word presence).
- Classes: 7 categories (e.g., Theory, Neural Networks, Reinforcement Learning).
from torch_geometric.datasets import Planetoid
from torch_geometric.transforms import NormalizeFeatures
dataset = Planetoid(root='/tmp/Cora', name='Cora', transform=NormalizeFeatures())
data = dataset[0] # Cora is a single graph
print(f'Nodes: {data.num_nodes}')
print(f'Edges: {data.num_edges}')
print(f'Features: {dataset.num_features}')
2. Model Architecture: 2-Layer GCN
We will implement a standard 2-layer GCN. The first layer expands the receptive field (1-hop neighbors), and the second layer integrates this information to produce the final class probabilities.
import torch
import torch.nn.functional as F
from torch_geometric.nn import GCNConv
class GCN(torch.nn.Module):
def __init__(self, in_channels, hidden_channels, out_channels):
super().__init__()
self.conv1 = GCNConv(in_channels, hidden_channels)
self.conv2 = GCNConv(hidden_channels, out_channels)
def forward(self, x, edge_index):
# Layer 1: Aggregate neighbors + ReLU
x = self.conv1(x, edge_index).relu()
x = F.dropout(x, p=0.5, training=self.training)
# Layer 2: Final classification
x = self.conv2(x, edge_index)
return x
3. The Training Loop
We use the standard Cross-Entropy loss but apply it only to the training mask—this is a key characteristic of semi-supervised learning on graphs.
model = GCN(dataset.num_features, 16, dataset.num_classes)
optimizer = torch.optim.Adam(model.parameters(), lr=0.01, weight_decay=5e-4)
criterion = torch.nn.CrossEntropyLoss()
def train():
model.train()
optimizer.zero_grad()
out = model(data.x, data.edge_index)
# Only compute loss on the nodes designated for training
loss = criterion(out[data.train_mask], data.y[data.train_mask])
loss.backward()
optimizer.step()
return loss
for epoch in range(1, 201):
loss = train()
if epoch % 20 == 0:
print(f'Epoch: {epoch:03d}, Loss: {loss:.4f}')
4. Evaluating Results
After 200 epochs, a well-tuned GCN should achieve around 80-82% accuracy on the Cora test set.
| Metric | Result (Typical) |
|---|---|
| Train Loss | ~0.15 |
| Validation Acc | ~79% |
| Test Acc | ~81% |
If you project the final 16-dimensional hidden embeddings into 2D using t-SNE, you will see that the GCN has perfectly clustered the papers by their scientific category, even if it only saw labels for a few of them!
Hands-on Resources
- Google Colab: Full Cora GCN Lab
- Source Code: Available in the
labs/directory of this portfolio.