保姆级教程:用PyTorch Geometric搞定Cora数据集节点分类(附完整代码)
从零构建图神经网络:PyTorch Geometric实战Cora节点分类
第一次接触图神经网络时,我被它的独特魅力所吸引——不同于传统神经网络处理网格化数据(如图像)或序列数据(如文本),图神经网络直接处理非结构化的图数据。这种能力让我在处理社交网络、推荐系统等实际问题时有了全新武器。本文将带你用PyTorch Geometric(简称PyG)这个强大的图神经网络库,从零开始实现Cora学术论文引文网络的节点分类任务。
1. 环境准备与数据理解
在开始之前,我们需要确保环境配置正确。推荐使用Python 3.8+和PyTorch 1.10+版本,这是PyG稳定运行的基础。安装PyTorch Geometric只需一行命令:
pip install torch torch-geometric
Cora数据集是图神经网络领域的"MNIST"——一个经典的基准测试集。它包含2708篇机器学习领域的学术论文,每篇论文被表示为图中的一个节点。这些论文被分为7个类别:
- 基于案例的推理
- 遗传算法
- 神经网络
- 概率方法
- 强化学习
- 规则学习
- 理论
节点之间的边代表引用关系——如果论文A引用了论文B,图中就有一条从A指向B的边。每个节点还有1433维的特征向量,表示论文中是否包含特定词汇。
提示:Cora数据集会自动下载到指定路径。如果遇到下载问题,可以手动下载并放到
root参数指定的目录中。
2. 数据加载与预处理
让我们首先加载数据并查看其基本结构:
from torch_geometric.datasets import Planetoid
from torch_geometric.transforms import NormalizeFeatures
dataset = Planetoid(root='./data/Cora', name='Cora', transform=NormalizeFeatures())
data = dataset[0]
print(f'Number of nodes: {data.num_nodes}') # 2708
print(f'Number of edges: {data.num_edges}') # 10556
print(f'Number of features: {dataset.num_features}') # 1433
print(f'Number of classes: {dataset.num_classes}') # 7
print(f'Training nodes: {data.train_mask.sum().item()}') # 140
关键数据结构说明:
x: 节点特征矩阵,形状为[节点数, 特征维度]edge_index: 图的边信息,形状为[2, 边数]y: 节点标签train_mask/val_mask/test_mask: 划分训练、验证和测试集的布尔掩码
PyG的一个便利之处是它已经帮我们处理好了数据划分。Cora数据集按照如下比例划分:
| 数据集 | 节点数量 | 比例 |
|---|---|---|
| 训练集 | 140 | 5% |
| 验证集 | 500 | 18% |
| 测试集 | 1000 | 37% |
3. 构建图神经网络模型
我们将实现一个经典的两层图卷积网络(GCN)。与普通神经网络不同,GCN在传播信息时会考虑节点之间的连接关系。
import torch
import torch.nn.functional as F
from torch_geometric.nn import GCNConv
class GCN(torch.nn.Module):
def __init__(self, hidden_channels):
super().__init__()
torch.manual_seed(1234567)
self.conv1 = GCNConv(dataset.num_features, hidden_channels)
self.conv2 = GCNConv(hidden_channels, dataset.num_classes)
def forward(self, x, edge_index):
# 第一层图卷积
x = self.conv1(x, edge_index)
x = x.relu()
x = F.dropout(x, p=0.5, training=self.training)
# 第二层图卷积
x = self.conv2(x, edge_index)
return x
model = GCN(hidden_channels=16)
print(model)
这个简单的架构已经包含了GCN的核心思想:
- 图卷积层(GCNConv) : 聚合邻居信息来更新节点表示
- 非线性激活(ReLU) : 引入非线性变换能力
- Dropout : 防止过拟合,增强泛化能力
注意:GCNConv会自动处理自环边(节点到自身的边),这是GCN能够保留节点自身信息的关键。
4. 模型训练与评估
训练图神经网络与训练传统神经网络类似,但有几点需要特别注意:
model = GCN(hidden_channels=16)
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)
loss = criterion(out[data.train_mask], data.y[data.train_mask])
loss.backward()
optimizer.step()
return loss
def test():
model.eval()
out = model(data.x, data.edge_index)
pred = out.argmax(dim=1)
correct = pred[data.test_mask] == data.y[data.test_mask]
acc = int(correct.sum()) / int(data.test_mask.sum())
return acc
for epoch in range(1, 101):
loss = train()
if epoch % 10 == 0:
test_acc = test()
print(f'Epoch: {epoch:03d}, Loss: {loss:.4f}, Test Acc: {test_acc:.4f}')
训练过程中有几个关键点:
- 只使用训练节点计算损失 :通过
data.train_mask筛选 - 测试时关闭Dropout :通过
model.eval()设置 - 权重衰减(weight_decay) :相当于L2正则化,防止过拟合
典型的训练输出可能如下:
Epoch: 010, Loss: 1.6862, Test Acc: 0.5730
Epoch: 020, Loss: 1.3971, Test Acc: 0.7040
Epoch: 030, Loss: 1.0893, Test Acc: 0.7610
...
Epoch: 100, Loss: 0.3124, Test Acc: 0.8150
5. 结果可视化与分析
理解模型学到了什么至关重要。我们可以使用t-SNE将高维节点嵌入降维到2D空间进行可视化:
import matplotlib.pyplot as plt
from sklearn.manifold import TSNE
def visualize(h, color):
z = TSNE(n_components=2).fit_transform(h.detach().cpu().numpy())
plt.figure(figsize=(10,10))
plt.xticks([])
plt.yticks([])
plt.scatter(z[:,0], z[:,1], s=70, c=color, cmap="Set2")
plt.show()
model.eval()
out = model(data.x, data.edge_index)
visualize(out, color=data.y)
好的可视化结果应该显示:
- 同类节点在嵌入空间中聚集在一起
- 不同类节点之间有相对清晰的边界
- 边界区域可能存在引文关系密切的不同类论文
如果发现某些类别难以区分,可以考虑:
- 增加网络深度(更多GCN层)
- 调整隐藏层维度
- 尝试更复杂的图神经网络架构(如GAT、GraphSAGE)
6. 进阶技巧与优化
当基本模型运行起来后,我们可以考虑以下优化策略:
学习率调度 :动态调整学习率可以提升模型性能
scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(
optimizer, mode='max', factor=0.5, patience=5, verbose=True)
早停(Early Stopping) :防止过拟合
best_val_acc = 0
patience = 20
counter = 0
for epoch in range(1, 1001):
train()
val_acc = evaluate(val_mask)
if val_acc > best_val_acc:
best_val_acc = val_acc
counter = 0
else:
counter += 1
if counter == patience:
print("Early stopping!")
break
模型集成 :结合多个模型的预测
models = [GCN(hidden_channels=16) for _ in range(5)]
for m in models:
train_model(m) # 分别训练每个模型
# 预测时取平均
preds = [model(data.x, data.edge_index).argmax(dim=1) for model in models]
final_pred = torch.mode(torch.stack(preds), dim=0).values
7. 常见问题排查
在实际应用中,你可能会遇到以下问题:
问题1:验证集准确率高但测试集准确率低
可能原因:
- 数据泄露(验证集和测试集划分不合理)
- 模型在验证集上过拟合
解决方案:
- 检查数据划分
- 增加正则化(更高的dropout率、更强的权重衰减)
问题2:训练损失不下降
可能原因:
- 学习率设置不当
- 梯度消失/爆炸
- 模型容量不足
解决方案:
- 尝试不同的学习率(0.1, 0.01, 0.001等)
- 添加残差连接
- 增加隐藏层维度
问题3:GPU内存不足
解决方案:
- 减小批处理大小
- 使用更小的模型
- 尝试梯度累积技术
accumulation_steps = 4 # 累积4个batch的梯度再更新
for epoch in range(epochs):
model.train()
for i, batch in enumerate(train_loader):
out = model(batch.x, batch.edge_index)
loss = criterion(out, batch.y) / accumulation_steps
loss.backward()
if (i + 1) % accumulation_steps == 0:
optimizer.step()
optimizer.zero_grad()
在Kaggle等平台的GPU环境中,合理利用梯度累积可以在有限内存下训练更大模型。
更多推荐

所有评论(0)