图神经网络实战(19)——异构图神经网络

图神经网络实战(19)------异构图神经网络

    • [0. 前言](#0. 前言)
    • [1. 异构图](#1. 异构图)
      • [1.1 异构图基本概念](#1.1 异构图基本概念)
      • [1.2 构建异构图数据集](#1.2 构建异构图数据集)
    • [2. 将同构图神经网络转换为异构图神经网络](#2. 将同构图神经网络转换为异构图神经网络)
      • [2.1 数据集介绍](#2.1 数据集介绍)
      • [2.2 同构图注意力网络](#2.2 同构图注意力网络)
      • [2.3 异构图神经网络](#2.3 异构图神经网络)
    • 小结
    • 系列链接

0. 前言

我们已经学习了如何生成包含不同类型节点(原子)和边(键)的分子结构,这种技术在其它应用中也具有广泛用途,例如推荐系统(用户和商品)、社交网络(关注者和被关注者)或网络安全(路由器和服务器)。我们将这类图称为异构图 (heterogeneous graph),与同构图 (homogeneous graph) 相对,后者只涉及一种类型的节点和一种类型的边。在本节中,我们将回顾关于同构图神经网络 (Graph Neural Networks, GNN)消息传递神经网络框架的相关概念,以扩展 GNN 架构适用于异构图。首先,我们将创建自定义异构数据集。然后,将同构架构转化为异构架构。

1. 异构图

1.1 异构图基本概念

异构图 (heterogeneous graph)是表示不同实体间关系的强大工具,拥有不同类型的节点和边会创建更复杂但也更难学习的图结构。同时,异构图的一个主要问题是,来自不同类型节点或边的特征不一定具有相同的意义或维度。

因此,合并不同的特征会破坏大量信息。而同构图 (homogeneous graph) 则不同,在同构图中,每个节点或边的每个维度都具有完全相同的含义。

异构图是一种更通用的网络,可以表示不同类型的节点和边。从形式上看,异构图定义为由节点集 V V V 和边集 E E E 组成的图 G = ( V , E ) G = (V, E) G=(V,E),在异构图中,包括节点类型映射函数 ϕ : V → A ϕ :V→A ϕ:V→A (其中 A A A 表示节点类型集),以及边类型映射函数 ψ : E → R ψ:E→R ψ:E→R (其中 R R R 表示边类型集)。下图是一个具有三种节点类型和三种边类型的异构图。

在上图中,我们可以看到三种类型的节点(用户、游戏和开发者)和三种类型的边(关注、游戏和开发)。它代表了一个涉及人员(用户和开发者)和游戏的网络,可用于游戏推荐等各种应用。如果这个图包含数百万个元素,它就可以用作图结构的知识数据库或知识图谱。知识图谱能够用来回答查询,比如"谁玩 Dev 1 开发的游戏?"。

类似的查询可以提取有用的同质图。例如,我们可能只想考虑玩 Game 1 的用户,输出结果为 User 1User 2。我们也可以创建更复杂的查询,例如"谁是玩 Dev 1 开发的游戏的用户?"结果是相同的,但遍历了两个关系来获得用户,这种查询称为元路径 (meta-path)。

在第一个例子中,元路径是 User → Game → User (通常表示为 UGU),而在第二个例子中,我们的元路径是 User → Game → Dev → Game → User (或表示为 UGDGU)。需要注意的是,起点节点类型和终点节点类型是相同的。元路径是异构图中的一个基本概念,通常用于衡量不同节点的相似性。

1.2 构建异构图数据集

接下来,我们使用 PyTorch Geometric (PyG) 实现异构图,使用数据对象 HeteroData 创建一个数据对象来存储上示异构图。

(1)torch_geometric.data 中导入 HeteroData 类,并创建变量 data

python 复制代码
from torch_geometric.data import HeteroData

data = HeteroData()

(2) 首先,存储节点特征。例如,可以使用 data['user'].x 访问用户特征。我们使用一个维度为 [num_users, num_features_users] 的张量作为输入,其中 num_users 表示用户数量,num_features_users 表示用户特征数量。在本例中,内容并不重要,因此我们将创建一个用 1 表示 user 1、用 2 表示 user 2、用 3 表示 user 3 的特征向量:

python 复制代码
data['user'].x = torch.Tensor([[1, 1, 1, 1], [2, 2, 2, 2], [3, 3, 3, 3]]) # [num_users, num_features_users]

(3) 我们对游戏特征和开发者特征重复以上过程。需要注意的是,特征向量的维度并不相同;这是异构图在处理不同表示时的一个重要优势:

python 复制代码
data['game'].x = torch.Tensor([[1, 1], [2, 2]])
data['dev'].x = torch.Tensor([[1], [2]])

(4) 接下来,在节点之间建立联系。连接具有不同的含义,因此我们将创建三组边索引。我们可以使用三元组(源节点类型、边缘类型、目标节点类型)来声明每组边索引,例如 data['user','follows','user'].edge_index。然后,将连接存储在一个维数为 [2, num_edge] 的张量中,其中 num_edge 表示边的数量:

python 复制代码
data['user', 'follows', 'user'].edge_index = torch.Tensor([[0, 1], [1, 2]]) # [2, num_edges_follows]
data['user', 'plays', 'game'].edge_index = torch.Tensor([[0, 1, 1, 2], [0, 0, 1, 1]])
data['dev', 'develops', 'game'].edge_index = torch.Tensor([[0, 1], [0, 1]])

(5) 边也可以具有特征,例如,边 plays 可以包括用户玩相应游戏的小时数。我们假设 user 1 玩了 2 小时 game 1user 2 玩了半小时 game 110 小时 game 2user 3 玩了 12 小时 game 2

python 复制代码
data['user', 'plays', 'game'].edge_attr = torch.Tensor([[2], [0.5], [10], [12]])

(6) 最后,打印 data 对象来验证结果:

python 复制代码
print(data)
'''
HeteroData(
  user={ x=[3, 4] },
  game={ x=[2, 2] },
  dev={ x=[2, 1] },
  (user, follows, user)={ edge_index=[2, 2] },
  (user, plays, game)={
    edge_index=[2, 4],
    edge_attr=[4, 1]
  },
  (dev, develops, game)={ edge_index=[2, 2] }
)
'''

从以上实现中可以看出,不同类型的节点和边并不共享相同的张量,甚至它们的维度也并不相同。因此,我们需要思考如何使用图神经网络 (Graph Neural Networks, GNN)聚合来自多个张量的信息。

在同构图中,我们只关注单一类型的节点,权重矩阵的大小适合与预定义的维度相乘。然而,当具有不同维度的输入时,该如何实现 GNN

2. 将同构图神经网络转换为异构图神经网络

2.1 数据集介绍

为了更好地理解如何将同构图神经网络 (Graph Neural Networks, GNN) 转换为异构 GNN,我们以一个真实的数据集为例。DBLP 计算机科学文献提供了一个包含四种节点类型的数据集,分别是论文(papers14328 篇)、术语(terms7723 个)、作者(authors4057 个)和会议(conferences20 个)。该数据集的目标是将作者正确地分为四类研究领域------数据库 (database)、数据挖掘 (data mining)、人工智能 (artificial intelligence) 和信息检索 (information retrieval)。作者的节点特征是他们在论文中可能使用的 334 个关键词组成的词袋( "0" 或 "1"),不同节点类型之间的关系如下所示。

这些节点类型的维度和语义关系并不相同。在异构图中,节点之间的关系至关重要,这也是需要考虑节点对的原因。例如,不需要向 GNN 层输入作者节点,而是考虑 (作者、论文) 这种节点对。这意味着我们现在需要为每个关系建立一个 GNN 层;在这种情况下,"to" 关系是双向的,因此我们需要建立六个层。

这些新层具有独立的权重矩阵,适用于每种节点类型的正确维度。现在我们有了六个不共享任何信息的不同层,可以通过引入跳跃连接 (skip-connections)、共享层 (shared layers)、跳转知识 (jumping knowledge) 等方法来解决信息共享问题。

在将同构模型转化为异构模型之前,我们先在 DBLP 数据集上实现经典的图注意力网络 (Graph Attention Networks,GAT)模型。GAT 无法考虑不同的关系;我们必须给它一个唯一的邻接矩阵,将作者相互连接起来。可以通过使用元路径技术生成这种邻接矩阵,如作者-论文-作者,将同一篇论文的作者连接起来。

也可以通过随机游走构建一个良好的邻接矩阵。即使图是异构的,也可以进行探索,并连接经常出现在相同序列中的节点。

2.2 同构图注意力网络

接下来,使用 PyTorch Geometric (PyG) 在 DBLP 数据集上实现经典图注意力网络 (Graph Attention Networks,GAT)架构。

(1) 导入所需的库:

python 复制代码
from torch import nn
import torch.nn.functional as F

import torch_geometric.transforms as T
from torch_geometric.datasets import DBLP
from torch_geometric.nn import GAT

(2) 使用特定语法定义要使用的元路径:

python 复制代码
metapaths = [[('author', 'paper'), ('paper', 'author')]]

(3) 使用 AddMetaPaths 转换函数自动计算元路径。使用 drop_orig_edge_types=True 从数据集中移除其他关系( GAT 只能考虑一种关系):

python 复制代码
transform = T.AddMetaPaths(metapaths=metapaths, drop_orig_edge_types=True)

(4) 加载 DBLP 数据集并打印相关信息:

python 复制代码
dataset = DBLP('.', transform=transform)
data = dataset[0]
print(data)

输出结果如下所示,可以看到转换函数创建的 (author, metapath_0, author) 关系:

(5) 创建一个单层 GAT 模型,其中 in_channels=-1 用于执行懒初始化(模型将自动计算值),out_channels=4 用于将作者节点分为四种类别:

python 复制代码
model = GAT(in_channels=-1, hidden_channels=64, out_channels=4, num_layers=1)

(6) 实例化 Adam 优化器并尝试将模型和数据转移到GPU中:

python 复制代码
optimizer = torch.optim.Adam(model.parameters(), lr=0.001, weight_decay=0.001)
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
data, model = data.to(device), model.to(device)

(7) 定义 test() 函数用于评估模型预测的准确性:

python 复制代码
@torch.no_grad()
def test(mask):
    model.eval()
    pred = model(data.x_dict['author'], data.edge_index_dict[('author', 'metapath_0', 'author')]).argmax(dim=-1)
    acc = (pred[mask] == data['author'].y[mask]).sum() / mask.sum()
    return float(acc)

(8) 创建训练循环:

python 复制代码
for epoch in range(101):
    model.train()
    optimizer.zero_grad()
    out = model(data.x_dict['author'], data.edge_index_dict[('author', 'metapath_0', 'author')])
    mask = data['author'].train_mask
    loss = F.cross_entropy(out[mask], data['author'].y[mask])
    loss.backward()
    optimizer.step()

    if epoch % 20 == 0:
        train_acc = test(data['author'].train_mask)
        val_acc = test(data['author'].val_mask)
        print(f'Epoch: {epoch:>3} | Train Loss: {loss:.4f} | Train Acc: {train_acc*100:.2f}% | Val Acc: {val_acc*100:.2f}%')

(9) 在测试集上对训练后的模型进行了测试:

python 复制代码
test_acc = test(data['author'].test_mask)
print(f'Test accuracy: {test_acc*100:.2f}%')

# Test accuracy: 73.29%

使用元路径将异构数据集缩减为同构数据集,并应用了经典 GAT 架构。模型的测试准确率为 73.29%,这可以作为其他技术进行比较的基准。

2.3 异构图神经网络

接下来,构建图注意力网络 (Graph Attention NetworksGAT) 模型的异构版本。如前所示,我们需要六个(而不再是一个) GAT 层。在 PyTorch Geometric 可以使用 to_hetero()to_hetero_bases() 函数自动完成。to_hetero() 函数需要三个重要参数:

  • module: 要转换的同构模型
  • metadata: 有关图的异构性质的信息,用元组 (node_types, edge_types) 表示,其中 node_typesedge_types 分别表示节点类型和边类型
  • aggr:聚合算子,用于聚合由不同关系(例如,求和、最大值或均值)生成的节点嵌入

同构 GAT (左图)和使用 to_hetero() 得到的异构版本(右图)如下所示。

异构 GAT 的实现过程于同构 GAT 模型相似。

(1) 首先,从 PyTorch Geometric 中导入 GNN 层:

python 复制代码
from torch_geometric.nn import GATConv, Linear, to_hetero

(2) 加载 DBLP 数据集:

python 复制代码
dataset = DBLP(root='.')
data = dataset[0]

(3) 当我们打印这个数据集的信息时,注意到会议节点没有任何特征。这于我们的架构假设(每个节点类型都有自己的特征)相违背,可以通过生成零值作为特征来解决此问题:

python 复制代码
data['conference'].x = torch.zeros(20, 1)

(4) 创建 GAT 类,其中包含 GAT 层和线性层,使用 (-1, -1) 元组再次进行懒初始化:

python 复制代码
class GAT(torch.nn.Module):
    def __init__(self, dim_h, dim_out):
        super().__init__()
        self.conv = GATConv((-1, -1), dim_h, add_self_loops=False)
        self.linear = nn.Linear(dim_h, dim_out)

    def forward(self, x, edge_index):
        h = self.conv(x, edge_index).relu()
        h = self.linear(h)
        return h

(5) 实例化 GAT 模型,并使用 to_hetero() 进行转换:

python 复制代码
model = GAT(dim_h=64, dim_out=4)
model = to_hetero(model, data.metadata(), aggr='sum')
print(model)

(5) 实例化 Adam 优化器,并尝试将模型和数据转移到 GPU 上:

python 复制代码
optimizer = torch.optim.Adam(model.parameters(), lr=0.001, weight_decay=0.001)
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
data, model = data.to(device), model.to(device)

(6) 编写 test() 函数,不需要指定任何关系,因为模型会考虑所有关系:

python 复制代码
@torch.no_grad()
def test(mask):
    model.eval()
    pred = model(data.x_dict, data.edge_index_dict)['author'].argmax(dim=-1)
    acc = (pred[mask] == data['author'].y[mask]).sum() / mask.sum()
    return float(acc)

(7) 实现训练循环:

python 复制代码
for epoch in range(101):
    model.train()
    optimizer.zero_grad()
    out = model(data.x_dict, data.edge_index_dict)['author']
    mask = data['author'].train_mask
    loss = F.cross_entropy(out[mask], data['author'].y[mask])
    loss.backward()
    optimizer.step()

    if epoch % 20 == 0:
        train_acc = test(data['author'].train_mask)
        val_acc = test(data['author'].val_mask)
        print(f'Epoch: {epoch:>3} | Train Loss: {loss:.4f} | Train Acc: {train_acc*100:.2f}% | Val Acc: {val_acc*100:.2f}%')
'''
...
Epoch:  60 | Train Loss: 0.5049 | Train Acc: 98.00% | Val Acc: 73.25%
Epoch:  80 | Train Loss: 0.2687 | Train Acc: 99.25% | Val Acc: 76.75%
Epoch: 100 | Train Loss: 0.1574 | Train Acc: 100.00% | Val Acc: 76.50%
'''

(8) 训练后模型在测试数据集上的测试准确率如下:

python 复制代码
test_acc = test(data['author'].test_mask)
print(f'Test accuracy: {test_acc*100:.2f}%')

# Test accuracy: 78.39%

异构 GAT 的测试准确率为 78.39%,比之同构版本有了较大提高 (+5.10%)。

小结

在本节中,我们扩展了消息传递神经网络 (Message Passing Neural Network, MPNN)框架,以考虑由不同类型的节点和边组成的异构图。这种特殊的图可以表示实体之间的各种关系,这比单一类型的连接具有更高的表达能力。此外,我们还介绍了如何利用 PyTorch Geometric 将同构图神经网络 (Graph Neural Networks, GNN)转换为异构 GNN,描述了异构图注意力网络 (Graph Attention Networks,GAT)中的不同层,将节点对作为输入来模拟它们之间的关系。

系列链接

图神经网络实战(1)------图神经网络(Graph Neural Networks, GNN)基础
图神经网络实战(2)------图论基础
图神经网络实战(3)------基于DeepWalk创建节点表示
图神经网络实战(4)------基于Node2Vec改进嵌入质量
图神经网络实战(5)------常用图数据集
图神经网络实战(6)------使用PyTorch构建图神经网络
图神经网络实战(7)------图卷积网络(Graph Convolutional Network, GCN)详解与实现
图神经网络实战(8)------图注意力网络(Graph Attention Networks, GAT)
图神经网络实战(9)------GraphSAGE详解与实现
图神经网络实战(10)------归纳学习
图神经网络实战(11)------Weisfeiler-Leman测试
图神经网络实战(12)------图同构网络(Graph Isomorphism Network, GIN)
图神经网络实战(13)------经典链接预测算法
图神经网络实战(14)------基于节点嵌入预测链接
图神经网络实战(15)------SEAL链接预测算法
图神经网络实战(16)------经典图生成算法
图神经网络实战(17)------深度图生成模型
图神经网络实战(18)------消息传播神经网络

相关推荐
靴子学长1 小时前
基于字节大模型的论文翻译(含免费源码)
人工智能·深度学习·nlp
海棠AI实验室2 小时前
AI的进阶之路:从机器学习到深度学习的演变(一)
人工智能·深度学习·机器学习
四口鲸鱼爱吃盐3 小时前
Pytorch | 从零构建GoogleNet对CIFAR10进行分类
人工智能·pytorch·分类
leaf_leaves_leaf4 小时前
win11用一条命令给anaconda环境安装GPU版本pytorch,并检查是否为GPU版本
人工智能·pytorch·python
夜雨飘零14 小时前
基于Pytorch实现的说话人日志(说话人分离)
人工智能·pytorch·python·声纹识别·说话人分离·说话人日志
四口鲸鱼爱吃盐4 小时前
Pytorch | 从零构建MobileNet对CIFAR10进行分类
人工智能·pytorch·分类
苏言の狗4 小时前
Pytorch中关于Tensor的操作
人工智能·pytorch·python·深度学习·机器学习
paixiaoxin7 小时前
CV-OCR经典论文解读|An Empirical Study of Scaling Law for OCR/OCR 缩放定律的实证研究
人工智能·深度学习·机器学习·生成对抗网络·计算机视觉·ocr·.net
weixin_515202497 小时前
第R3周:RNN-心脏病预测
人工智能·rnn·深度学习
吕小明么9 小时前
OpenAI o3 “震撼” 发布后回归技术本身的审视与进一步思考
人工智能·深度学习·算法·aigc·agi