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.html

Cora数据集包含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: 实现图卷积操作,公式为:
    Z = D^(-1/2) A D^(-1/2) X W
    
    其中A是邻接矩阵,D是度矩阵,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的强大之处——同时利用节点特征和图结构信息。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐