GNN实战:用PyTorch Geometric搞定社交网络节点分类(附Cora数据集完整代码)
GNN实战:用PyTorch Geometric搞定社交网络节点分类(附Cora数据集完整代码)
当你在社交平台上看到"可能认识的人"推荐时,背后很可能就运行着图神经网络(GNN)。这种能够捕捉复杂关系的数据结构,正在推荐系统、欺诈检测等领域大放异彩。今天我们就用PyTorch Geometric这个利器,带你从零实现一个真实的学术论文引用网络分类任务。
1. 环境配置与数据准备
工欲善其事,必先利其器。我们先来搭建实验环境:
conda create -n gnn python=3.8 conda activate gnn pip install torch torch-geometric torch-scatter torch-sparse -f https://data.pyg.org/whl/torch-1.10.0+cu113.htmlCora数据集包含2708篇学术论文,分为7个类别(如神经网络、概率方法等)。每篇论文用1433维的词向量表示,引用关系构成5429条边。来看看如何加载数据:
from torch_geometric.datasets import Planetoid import torch_geometric.transforms as T dataset = Planetoid(root='./data', name='Cora', transform=T.NormalizeFeatures()) data = dataset[0] print(f'节点数: {data.num_nodes}') # 2708 print(f'边数: {data.num_edges}') # 5429 print(f'特征维度: {dataset.num_features}') # 1433 print(f'类别数: {dataset.num_classes}') # 7数据对象包含以下关键属性:
x: 节点特征矩阵(2708×1433)edge_index: 边索引(2×5429)y: 节点标签(2708)train_mask/test_mask/val_mask: 划分训练/测试/验证集
提示:NormalizeFeatures变换会自动对节点特征做L1归一化,这对GNN训练稳定性很重要
2. 构建图卷积网络模型
现在搭建一个双层GCN模型,结构如下:
输入层(1433) → GCN(16) → ReLU → Dropout → GCN(7) → Softmax具体实现代码:
import torch import torch.nn.functional as F from torch_geometric.nn import GCNConv class GCN(torch.nn.Module): def __init__(self, hidden_channels=16): super().__init__() self.conv1 = GCNConv(dataset.num_features, hidden_channels) self.conv2 = GCNConv(hidden_channels, dataset.num_classes) self.dropout = 0.5 def forward(self, x, edge_index): x = self.conv1(x, edge_index) x = F.relu(x) x = F.dropout(x, p=self.dropout, training=self.training) x = self.conv2(x, edge_index) return F.log_softmax(x, dim=1)关键组件解析:
GCNConv: 实现图卷积操作,公式为:
其中A是邻接矩阵,D是度矩阵,W是可学习权重Z = D^(-1/2) A D^(-1/2) X W- 第一层将1433维特征压缩到16维隐藏空间
- 第二层映射到7维输出空间(对应7个类别)
3. 模型训练与评估
训练流程采用标准监督学习范式:
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') model = GCN().to(device) data = data.to(device) optimizer = torch.optim.Adam(model.parameters(), lr=0.01, weight_decay=5e-4) def train(): model.train() optimizer.zero_grad() out = model(data.x, data.edge_index) loss = F.nll_loss(out[data.train_mask], data.y[data.train_mask]) loss.backward() optimizer.step() return loss.item() 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, 201): loss = train() if epoch % 20 == 0: acc = test() print(f'Epoch: {epoch:03d}, Loss: {loss:.4f}, Acc: {acc:.4f}')典型训练输出:
Epoch: 020, Loss: 1.5321, Acc: 0.7820 Epoch: 040, Loss: 0.9234, Acc: 0.8140 Epoch: 060, Loss: 0.7158, Acc: 0.8250 Epoch: 080, Loss: 0.6023, Acc: 0.8310 Epoch: 100, Loss: 0.5281, Acc: 0.8350注意:验证集准确率通常在83%左右波动,这与论文报告的基准结果一致
4. 高级技巧与优化方案
4.1 模型深度与过平滑问题
增加GCN层数可能导致性能下降,这就是著名的过平滑现象。我们测试不同层数的表现:
| 层数 | 验证集准确率 | 训练时间(秒) |
|---|---|---|
| 2 | 83.5% | 12.4 |
| 3 | 82.1% | 15.7 |
| 4 | 80.3% | 18.9 |
| 5 | 78.6% | 22.3 |
解决方法:
- 添加残差连接
- 使用Jumping Knowledge网络
- 尝试GAT等注意力机制
4.2 邻居采样与大规模图处理
对于超大规模图(如百万节点),可以使用采样技术:
from torch_geometric.loader import NeighborLoader loader = NeighborLoader( data, num_neighbors=[10, 5], # 两层采样,每层采样10和5个邻居 batch_size=32, input_nodes=data.train_mask ) for batch in loader: # 小批量训练逻辑 ...4.3 可视化节点嵌入
用UMAP可视化学习到的节点表示:
import umap import matplotlib.pyplot as plt model.eval() out = model.conv1(data.x, data.edge_index) out = out.detach().cpu().numpy() reducer = umap.UMAP() embedding = reducer.fit_transform(out) plt.scatter(embedding[:,0], embedding[:,1], c=data.y.cpu(), s=10, cmap='Set1') plt.colorbar() plt.show()你会看到同类论文在嵌入空间中形成明显聚类,这正是GNN的强大之处——同时利用节点特征和图结构信息。
