Skip to main content

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).
Loading Cora
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.

model.py
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.

train.py
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.

MetricResult (Typical)
Train Loss~0.15
Validation Acc~79%
Test Acc~81%
Visualizing Clusters

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.