Python图神经网络:PyTorch Geometric与DGL深度解析
大家好,今天我们将深入探讨如何使用Python中的两个主流图神经网络库:PyTorch Geometric (PyG) 和 Deep Graph Library (DGL) 对图数据进行建模。GNN在处理社交网络、知识图谱、分子结构等复杂关系型数据方面展现出强大的能力,而PyG和DGL则提供了高效、灵活的工具来构建和训练这些模型。本次讲座将涵盖图数据的表示、库的选择依据、PyG和DGL的基本用法、常见GNN层的实现,以及一些高级技巧。
1. 图数据的表示
在深入了解PyG和DGL之前,我们首先需要理解如何在计算机中表示图数据。一个图通常由节点(Nodes,也称为顶点)和边(Edges,也称为弧)组成。
- 节点 (Nodes/Vertices): 图中的基本单元,可以代表任何实体。
- 边 (Edges/Arcs): 连接两个节点,表示它们之间的关系。边可以是无向的(表示双向关系)或有向的(表示单向关系)。
除了节点和边,图还可以包含以下信息:
- 节点特征 (Node Features): 描述节点的属性,例如用户的年龄、文章的主题等。
- 边特征 (Edge Features): 描述边的属性,例如关系的强度、交互的时间等。
- 全局特征 (Global Features): 描述整个图的属性,例如图的类别、图的统计信息等。
常见的图数据表示方法有以下几种:
-
邻接矩阵 (Adjacency Matrix): 一个二维矩阵,其中
A[i][j] = 1
表示节点i
和节点j
之间存在一条边,A[i][j] = 0
表示不存在边。 对于加权图,可以使用权重值代替 1。- 优点:简单直观。
- 缺点:对于大型稀疏图,存储效率低(大量的0)。
-
邻接列表 (Adjacency List): 对于每个节点,存储一个列表,其中包含所有与该节点相邻的节点。
- 优点:节省存储空间,特别适合稀疏图。
- 缺点:查找特定边的效率较低。
-
边列表 (Edge List): 存储所有边的列表,每个边由两个节点索引表示。
- 优点:简单易懂,易于构建。
- 缺点:难以进行复杂的图操作。
PyG和DGL都提供了高效的数据结构来存储和操作图数据。通常,它们会使用稀疏矩阵或邻接列表的变体来提高效率。
2. PyTorch Geometric (PyG)
PyTorch Geometric (PyG) 是一个基于 PyTorch 的图神经网络库,它提供了许多常用的图神经网络层、数据集和评估指标。PyG 的主要特点包括:
- 易用性: 提供了一套简洁的 API,方便用户构建和训练 GNN 模型。
- 高性能: 利用 PyTorch 的底层优化,实现了高效的图操作和计算。
- 灵活性: 支持自定义 GNN 层和图数据结构。
- 丰富的数据集: 内置了大量常用的图数据集,方便用户进行实验和评估。
2.1 PyG 的数据结构:torch_geometric.data.Data
在 PyG 中,图数据主要由 torch_geometric.data.Data
对象表示。一个 Data
对象通常包含以下属性:
x
: 节点特征矩阵,形状为[num_nodes, num_node_features]
。edge_index
: 边的连接信息,形状为[2, num_edges]
。edge_index[0]
存储源节点索引,edge_index[1]
存储目标节点索引。edge_attr
: 边特征矩阵,形状为[num_edges, num_edge_features]
。y
: 目标值 (例如节点标签或图标签),形状可以是任意的,取决于任务。pos
: 节点的位置信息(例如坐标),用于几何图神经网络。
下面是一个创建 Data
对象的例子:
import torch
from torch_geometric.data import Data
# 节点特征
x = torch.tensor([[1.0, 2.0], [3.0, 4.0], [5.0, 6.0]], dtype=torch.float)
# 边的连接信息
edge_index = torch.tensor([[0, 1, 1, 2],
[1, 0, 2, 1]], dtype=torch.long)
# 创建 Data 对象
data = Data(x=x, edge_index=edge_index)
print(data)
输出:
Data(x=[3, 2], edge_index=[2, 4])
2.2 PyG 的基本用法
-
数据集加载: PyG 提供了许多内置的数据集,例如
Planetoid
(Cora, CiteSeer, PubMed)、TUDatasets
、QM9
等。from torch_geometric.datasets import Planetoid dataset = Planetoid(root='/tmp/Cora', name='Cora') print(dataset) print(len(dataset)) print(dataset.num_classes) print(dataset.num_node_features)
-
数据访问: 数据集中的每个图都以
Data
对象的形式存储。data = dataset[0] # 获取第一个图 print(data) print(data.is_undirected()) #检查图是否是无向图 print(data.train_mask.sum().item()) #训练节点的数量
-
数据转换 (Transforms): PyG 提供了许多数据转换操作,例如添加自环边、归一化节点特征等。
from torch_geometric.transforms import NormalizeFeatures transform = NormalizeFeatures() dataset = Planetoid(root='/tmp/Cora', name='Cora', transform=transform) data = dataset[0] print(data.x.mean(dim=0)) #查看节点特征的均值 print(data.x.std(dim=0)) #查看节点特征的标准差
-
图神经网络层: PyG 提供了许多常用的 GNN 层,例如
GCNConv
、GATConv
、SAGEConv
等。import torch.nn.functional as F from torch_geometric.nn import GCNConv class GCN(torch.nn.Module): def __init__(self, num_node_features, num_classes): super(GCN, self).__init__() self.conv1 = GCNConv(num_node_features, 16) self.conv2 = GCNConv(16, num_classes) def forward(self, data): x, edge_index = data.x, data.edge_index x = self.conv1(x, edge_index) x = F.relu(x) x = F.dropout(x, training=self.training) x = self.conv2(x, edge_index) return F.log_softmax(x, dim=1) device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') model = GCN(dataset.num_node_features, dataset.num_classes).to(device) data = dataset[0].to(device) optimizer = torch.optim.Adam(model.parameters(), lr=0.01, weight_decay=5e-4) model.train() for epoch in range(200): optimizer.zero_grad() out = model(data) loss = F.nll_loss(out[data.train_mask], data.y[data.train_mask]) loss.backward() optimizer.step() model.eval() _, pred = model(data).max(dim=1) correct = float (pred[data.test_mask].eq(data.y[data.test_mask]).sum().item()) acc = correct / data.test_mask.sum().item() print('Accuracy: {:.4f}'.format(acc))
3. Deep Graph Library (DGL)
Deep Graph Library (DGL) 是另一个流行的图神经网络库,它支持多种深度学习框架,包括 PyTorch、TensorFlow 和 MXNet。DGL 的主要特点包括:
- 多框架支持: 可以在不同的深度学习框架中使用。
- 灵活性: 提供了灵活的 API,可以自定义图神经网络层和图操作。
- 高效性: 通过底层优化,实现了高效的图计算。
- 异构图支持: 可以处理包含不同类型节点和边的异构图。
3.1 DGL 的数据结构:dgl.DGLGraph
在 DGL 中,图数据由 dgl.DGLGraph
对象表示。DGLGraph
对象可以存储节点特征、边特征和图结构信息。
import dgl
import torch
# 创建一个 DGLGraph 对象
g = dgl.DGLGraph()
# 添加节点
g.add_nodes(3)
# 添加边
g.add_edges([0, 1], [1, 2]) # 从节点0到节点1,从节点1到节点2
# 设置节点特征
g.ndata['feat'] = torch.randn(3, 5) # 每个节点有5维特征
# 设置边特征
g.edata['weight'] = torch.randn(2, 1) # 每条边有1维权重
print(g)
print(g.ndata['feat'])
print(g.edata['weight'])
输出:
DGLGraph(num_nodes=3, num_edges=2,
ndata_schemes={'feat': Scheme(shape=(5,), dtype=torch.float32)}
edata_schemes={'weight': Scheme(shape=(1,), dtype=torch.float32)}
gdata_schemes={})
tensor([[ 0.5734, 0.1490, -0.6678, 0.2753, 0.2184],
[-0.2087, -0.1330, 0.7288, -1.4494, -0.6294],
[-0.6424, -1.5249, 0.0871, -0.1504, 0.1800]])
tensor([[ 0.2343],
[ 0.7557]])
3.2 DGL 的基本用法
-
图的创建: DGL 提供了多种创建图的方法,例如从邻接列表、边列表等创建。
# 从边列表创建图 u = torch.tensor([0, 1, 2]) v = torch.tensor([1, 2, 0]) g = dgl.graph((u, v)) # 从邻接矩阵创建图 (需要将邻接矩阵转换为 COO 格式) adj = torch.tensor([[0, 1, 0], [0, 0, 1], [1, 0, 0]]) u, v = adj.nonzero(as_tuple=True) g = dgl.graph((u, v))
-
消息传递 (Message Passing): DGL 的核心是消息传递机制。 消息传递是指节点之间通过边交换信息的过程。DGL 提供了灵活的 API 来定义消息函数、归约函数和更新函数。
import dgl.function as fn # 定义消息函数:将源节点的特征作为消息传递到目标节点 message_func = fn.copy_src(src='h', out='m') # 定义归约函数:对接收到的消息求和 reduce_func = fn.sum(msg='m', out='h') # 定义更新函数:将归约后的结果更新到目标节点的特征 def apply_node_func(nodes): return {'h': nodes.data['h'] + 1} # 简单地将每个节点的特征加1 # 在图上进行消息传递 g.ndata['h'] = torch.ones(g.num_nodes(), 1) # 初始化节点特征 g.update_all(message_func, reduce_func, apply_node_func) print(g.ndata['h'])
-
图神经网络层: DGL 提供了许多常用的 GNN 层,例如
GraphConv
、GATConv
、SAGEConv
等。import torch.nn as nn import dgl.nn as dglnn class GCN(nn.Module): def __init__(self, in_feats, h_feats, num_classes): super(GCN, self).__init__() self.conv1 = dglnn.GraphConv(in_feats, h_feats) self.conv2 = dglnn.GraphConv(h_feats, num_classes) def forward(self, g, in_feat): h = self.conv1(g, in_feat) h = torch.relu(h) h = self.conv2(g, h) return h # 训练 GCN 模型 def train(g, model, features, labels, mask): optimizer = torch.optim.Adam(model.parameters(), lr=0.01) loss_fcn = nn.CrossEntropyLoss() for epoch in range(200): logits = model(g, features) loss = loss_fcn(logits[mask], labels[mask]) optimizer.zero_grad() loss.backward() optimizer.step() print(f"Epoch {epoch+1}/{200} | Loss: {loss.item():.4f}") # 创建一个图 g = dgl.graph(([0,0,1,2,3,3], [1,2,2,3,0,1])) features = torch.randn(4, 5) labels = torch.tensor([0, 1, 2, 0]) train_mask = torch.tensor([True, True, True, False]) # 创建 GCN 模型 model = GCN(5, 16, 3) # 训练模型 train(g, model, features, labels, train_mask)
4. PyG 与 DGL 的选择
PyG 和 DGL 都是优秀的图神经网络库,选择哪个取决于你的具体需求和偏好。以下是一些选择的参考:
特性 | PyTorch Geometric (PyG) | Deep Graph Library (DGL) |
---|---|---|
框架支持 | PyTorch | PyTorch, TensorFlow, MXNet |
易用性 | 较为简洁易用 | 较为灵活,但可能略复杂 |
数据集 | 内置数据集丰富 | 需要手动构建或使用第三方数据集 |
异构图支持 | 有限 | 良好 |
消息传递机制 | 基于层的实现 | 灵活的消息传递 API |
社区支持 | 活跃 | 活跃 |
- 如果你主要使用 PyTorch,并且需要快速上手,处理同构图数据,那么 PyG 是一个不错的选择。
- 如果你需要支持多种深度学习框架,或者需要处理复杂的异构图数据,那么 DGL 可能更适合你。
- 如果你需要高度定制化的消息传递机制,DGL 的灵活性会更有优势。
5. 常见的 GNN 层
无论是 PyG 还是 DGL,都提供了许多常用的 GNN 层。以下是一些常见的 GNN 层及其原理:
-
图卷积网络 (GCN): GCN 是一种基于谱图理论的图卷积方法,它通过聚合邻居节点的特征来更新目标节点的特征。
公式:
H^(l+1) = σ(D^(-1/2) A D^(-1/2) H^(l) W^(l))
其中:
H^(l)
是第l
层的节点特征矩阵。A
是邻接矩阵。D
是度矩阵(对角矩阵,对角线上的元素是节点的度)。W^(l)
是第l
层的权重矩阵。σ
是激活函数。
-
GraphSAGE: GraphSAGE 是一种归纳式的图嵌入方法,它通过采样邻居节点并聚合它们的特征来生成目标节点的嵌入。
公式:
h_v^(l+1) = σ(W^(l) · MEAN({h_u^l, ∀u ∈ N(v)}))
其中:
h_v^(l+1)
是节点v
在第l+1
层的嵌入。N(v)
是节点v
的邻居节点集合。MEAN
是聚合函数(例如平均值、最大值等)。W^(l)
是第l
层的权重矩阵。σ
是激活函数。
-
图注意力网络 (GAT): GAT 是一种基于注意力机制的图神经网络,它通过学习节点之间的注意力权重来聚合邻居节点的特征。
公式:
e_{ij} = a(W h_i, W h_j)
计算节点 i 和节点 j 之间的注意力系数α_{ij} = softmax_j(e_{ij})
对注意力系数进行归一化h_i' = σ(∑_{j∈N(i)} α_{ij} W h_j)
根据注意力权重聚合邻居节点特征
其中:
h_i
是节点i
的特征向量。W
是权重矩阵。a
是注意力机制函数。α_{ij}
是节点j
对节点i
的注意力权重。N(i)
是节点i
的邻居节点集合。σ
是激活函数。
6. 高级技巧
-
批处理 (Batching): 为了提高训练效率,可以将多个图组合成一个批次进行处理。PyG 和 DGL 都提供了批处理的机制。
- PyG:
torch_geometric.data.Batch
对象可以将多个Data
对象组合成一个批次。 - DGL:
dgl.batch
函数可以将多个DGLGraph
对象组合成一个批次。
- PyG:
-
异构图 (Heterogeneous Graph): 异构图包含不同类型的节点和边。DGL 提供了良好的异构图支持,可以定义不同类型的节点和边,并使用不同的消息传递函数。
-
图采样 (Graph Sampling): 对于大型图,进行全图训练可能非常耗时。图采样技术可以采样一部分节点和边进行训练,从而提高效率。PyG 和 DGL 都提供了图采样的 API。
-
自定义 GNN 层: PyG 和 DGL 都允许用户自定义 GNN 层。你可以根据自己的需求,设计新的消息传递机制和聚合函数。
7. 代码示例:使用 DGL 实现 GAT
import torch
import torch.nn as nn
import dgl
import dgl.function as fn
from dgl.nn.pytorch import GATConv
class GAT(nn.Module):
def __init__(self, in_feats, hidden_size, num_classes, num_heads):
super(GAT, self).__init__()
self.gat_layers = nn.ModuleList()
# two-layer GAT
self.gat_layers.append(GATConv(
in_feats, hidden_size, num_heads,
feat_drop=0.6, attn_drop=0.6, activation=torch.relu))
self.gat_layers.append(GATConv(
hidden_size * num_heads, num_classes, num_heads,
feat_drop=0.6, attn_drop=0.6, activation=None))
def forward(self, graph, inputs):
h = inputs
for i, layer in enumerate(self.gat_layers):
h = layer(graph, h)
if i == 0:
h = h.flatten(1)
else:
h = h.mean(1)
return h
def evaluate(model, graph, features, labels, mask):
model.eval()
with torch.no_grad():
logits = model(graph, features)
logits = logits[mask]
labels = labels[mask]
_, indices = torch.max(logits, dim=1)
correct = torch.sum(indices == labels)
return correct.item() * 1.0 / len(labels)
def train(model, graph, features, labels, train_mask, val_mask, epochs=200, lr=0.005, weight_decay=5e-4):
optimizer = torch.optim.Adam(model.parameters(), lr=lr, weight_decay=weight_decay)
loss_fcn = nn.CrossEntropyLoss()
for epoch in range(epochs):
model.train()
logits = model(graph, features)
loss = loss_fcn(logits[train_mask], labels[train_mask])
optimizer.zero_grad()
loss.backward()
optimizer.step()
val_acc = evaluate(model, graph, features, labels, val_mask)
print(f"Epoch {epoch+1}/{epochs} | Loss: {loss.item():.4f} | Val Acc: {val_acc:.4f}")
if __name__ == '__main__':
# Load data (using a synthetic dataset for demonstration purposes)
g = dgl.graph(([0,0,1,1,2,2,3,3], [1,2,0,2,0,1,0,1]))
features = torch.randn(4, 16) # 4 nodes, 16 input features
labels = torch.tensor([0, 1, 0, 1])
train_mask = torch.tensor([True, True, False, False])
val_mask = torch.tensor([False, False, True, True])
# Define model
model = GAT(in_feats=16, hidden_size=8, num_classes=2, num_heads=3)
# Train model
train(model, g, features, labels, train_mask, val_mask)
总结这次分享的内容
今天我们深入探讨了如何使用 PyTorch Geometric 和 DGL 这两个强大的图神经网络库进行图数据建模。我们讨论了图数据的表示方法,PyG 和 DGL 的基本用法和选择依据,以及常见的 GNN 层和高级技巧。希望这次讲座能够帮助你更好地理解和应用图神经网络。