Python的图神经网络(GNN):使用PyTorch Geometric和DGL库对图数据进行建模。

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)、TUDatasetsQM9 等。

    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 层,例如 GCNConvGATConvSAGEConv 等。

    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 层,例如 GraphConvGATConvSAGEConv 等。

    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 对象组合成一个批次。
  • 异构图 (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 层和高级技巧。希望这次讲座能够帮助你更好地理解和应用图神经网络。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注