PyG入门
PyG(PyTorch Geometric)是一个建立在 PyTorch 之上的库,可以轻松编写和训练图形神经网络(GNN),用于与结构化数据相关的广泛应用。它包含各种用于图形和其他不规则结构的深度学习方法,也称为几何深度学习,来自各种已发表的论文。 此外,它包含易于使用的小批量加载器,用于在许多小型和单个巨型图上运行,多 GPU 支持,torch.compile 支持,DataPipe 支持,大量通用基准数据集(基于简单的接口 创建您自己的)、GraphGym 实验管理器和有用的转换,既可用于学习任意图形,也可用于 3D 网格或点云。
PyG的核心提供以下主要功能:
- 图形的数据处理
- 通用基准数据集
- 小批量
- 数据转换
- 在图上的学习方法
主要包含的几个模块:
- torch_geometric:主模块
- torch_geometric.nn:搭建图神经网络层
- torch_geometric.data:图结构数据的表示
- torch_geometric.loader:加载数据集
- torch_geometric.datasets:常用的图神经网络数据集
- torch_geometric.transforms:数据变换
- torch_geometric.utils:常用工具
- torch_geometric.graphgym:常用的图神经网络模型
- torch_geometric.profile:监督模型的训练
1. 图数据处理
图用于对对象(节点)之间的成对关系(边)建模,PyG中的单个图由torch_geometric.data.Data
的实例描述,默认情况下它具有以下属性:
- data.x:形状为 [num_nodes, num_node_features] 的节点特征矩阵
- data.edge_index:形状为 [2, num_edges] 且类型为 torch.long 的 COO 格式的图形连接
- data.edge_attr:形状为 [num_edges, num_edge_features] 的边特征矩阵
- data.y:要训练的目标(可能具有任意形状),例如,形状为 [num_nodes, *] 的节点级目标或形状为 [1, *] 的图级目标
- data.pos:形状为 [num_nodes, num_dimensions] 的节点位置矩阵
上述属性都不是必需的,事实上,Data对象甚至不局限于这些属性,例如,我们可以通过 data.face 扩展它,以将来自 3D 网格的三角形的连通性保存在形状为 [3, num_faces] 并输入 torch.long 的张量中。
示例:以一个具有三个节点和四个边的未加权和无向图为示例,每个节点只包含一个特征:
import torch
from torch_geometric.data import Data
#边的连接信息
edge_index = torch.tensor([ #注意,无向图要定义两次
[0, 1, 1, 2], # 上下对应着看,节点0和节点1有连接,那么节点1和节点0也有连接
[1, 0, 2, 1]
], dtype=torch.long)
# 指定节点属性信息,这里有三个节点,每个节点的属性向量维度为1
x = torch.tensor([[-1], [0], [1]], dtype=torch.float)
# 实例化一个图结构的数据对象
data = Data(x=x, edge_index=edge_index)
输出:
Data(x=[3, 1], edge_index=[2, 4])
请注意,edge_index,即定义所有边的源节点和目标节点的张量,不是索引元组的列表。如果你想以这种方式编写你的索引,你应该在将它们传递给数据构造函数之前对其进行转置和调用连续:
import torch
from torch_geometric.data import Data
# 另一种定义边索引的写法
edge_index = torch.tensor([[0, 1], #节点0连接节点1
[1, 0], #节点1连接节点0
[1, 2],
[2, 1]], dtype=torch.long)
x = torch.tensor([[-1], [0], [1]], dtype=torch.float)
data = Data(x=x, edge_index=edge_index.t().contiguous())
输出:
Data(x=[3, 1], edge_index=[2, 4])
虽然图只有两条边,但我们需要定义四个索引元组来说明一条边的两个方向,因为是无向图
请注意,edge_index 中的元素必须仅包含 { 0, …, num_nodes - 1} 范围内的索引。这是必需的,因为我们希望我们的最终数据表示尽可能紧凑,例如,我们希望分别通过 x[0] 和 x[1] 索引第一条边 (0, 1) 的源节点和目标节点特征
Data还提供了其他关于图属性的相关函数:
#返回图属性名的list
data.keys
#图的节点数
data.num_nodes
#图的边数
data.num_edges
#图节点的特征
data.num_node_features
#是否含有孤立点
data.has_isolated_nodes()
#是否含有自环
data.has_self_loops()
#图是否是无向的
data.is_directed()
2.常用的图神经网络数据集
PyG 包含大量常见的基准数据集:
- Planetoid数据集(Cora、Citeseer、Pubmed)
- QM7、QM9
- 3D点云数据集,如FAUST、ModelNet10等
初始化数据集很简单。数据集的初始化将自动下载其原始文件并将它们处理为前面描述的数据格式。
以ENZYMES为例
from torch_geometric.datasets import TUDataset
dataset = TUDataset(root='E:/GNN/data/ENZYMES', name='ENZYMES')
len(dataset) #600
dataset.num_classes #6
dataset.num_node_features #3
在这个数据集中有600个图
data = dataset[0]
>>> Data(edge_index=[2, 168], x=[37, 3], y=[1])
data.is_undirected()
>>> True
我们可以看到数据集中的第一个图包含 37 个节点,每个节点具有 3 个特征。 168/2 = 84 条无向边,并且该图仅分配给一个类。此外,数据对象恰好包含一个图级目标。
我们甚至可以使用切片、长或布尔张量来分割数据集。例如,要创建 90/10 训练/测试拆分:
train_dataset = dataset[:540]
>>> ENZYMES(540)
test_dataset = dataset[540:]
>>> ENZYMES(60)
如果您不确定数据集在拆分之前是否已经洗牌,您可以通过运行以下命令随机排列它:
dataset = dataset.shuffle()
>>> ENZYMES(600)
相当于:
perm = torch.randperm(len(dataset))
dataset = dataset[perm]
>> ENZYMES(600)
以Cora数据集为例
from torch_geometric.datasets import Planetoid
dataset = Planetoid(root='/tmp/Cora', name='Cora')
>>> Cora()
len(dataset)
>>> 1
dataset.num_classes
>>> 7
dataset.num_node_features
>>> 1433
上述方式可能无法直接下载数据集,会报错
Planetoid无法直接下载Cora数据集解决方式
从Github或者Gitee直接下载数据:GitHub项目地址,gitee项目地址,下载到指定文件夹,我下载到以下文件夹中
将其放在Cora/raw文件夹下,然后运行以下代码:
from torch_geometric.datasets import Planetoid
root = 'E:/GNN/data'
dataset = Planetoid(root, name='Cora')
无报错并显示以下输出则成功:
Processing...
Done!
这里,数据集只包含一个单一的、无向的引用图:
data = dataset[0]
>>> Data(edge_index=[2, 10556], test_mask=[2708],
train_mask=[2708], val_mask=[2708], x=[2708, 1433], y=[2708])
data.is_undirected()
>>> True
data.train_mask.sum().item()
>>> 140
data.val_mask.sum().item()
>>> 500
data.test_mask.sum().item()
>>> 1000
这一次,Data 对象为每个节点保存一个标签,以及额外的节点级属性:train_mask、val_mask 和 test_mask,其中
- train_mask 表示针对哪些节点进行训练(140 个节点)
- val_mask 表示使用哪些节点进行验证,例如,执行提前停止(500 个节点)
- test_mask 表示要测试的节点(1000 个节点)。
3.Mini-batches
神经网络通常以分批方式进行训练。PyG 通过创建稀疏块对角邻接矩阵(由 edge_index 定义)并在节点维度中连接特征和目标矩阵来实现小批量的并行化。这种组合允许在一批示例中使用不同数量的节点和边缘:
PyG 包含它自己的 torch_geometric.loader.DataLoader,它已经处理了这个连接过程。让我们通过一个例子来了解它:
from torch_geometric.datasets import TUDataset
from torch_geometric.loader import DataLoader
dataset = TUDataset(root='/tmp/ENZYMES', name='ENZYMES', use_node_attr=True)
loader = DataLoader(dataset, batch_size=32, shuffle=True)
for batch in loader:
batch
>>> DataBatch(batch=[1082], edge_index=[2, 4066], x=[1082, 21], y=[32])
batch.num_graphs
>>> 32
torch_geometric.data.Batch 继承自 torch_geometric.data.Data 并包含一个名为 batch 的附加属性。
batch 是一个列向量,它将每个节点映射到批处理中的相应图形:
可以使用它来分别为每个图形的节点维度中的平均节点特征:
from torch_geometric.utils import scatter
from torch_geometric.datasets import TUDataset
from torch_geometric.loader import DataLoader
dataset = TUDataset(root='/tmp/ENZYMES', name='ENZYMES', use_node_attr=True)
loader = DataLoader(dataset, batch_size=32, shuffle=True)
for data in loader:
data
>>> DataBatch(batch=[1082], edge_index=[2, 4066], x=[1082, 21], y=[32])
data.num_graphs
>>> 32
x = scatter(data.x, data.batch, dim=0, reduce='mean')
x.size()
>>> torch.Size([32, 21])
4.数据转换
变换是 torchvision 中变换图像和执行增强的常用方法。PyG 带有自己的转换,它期望一个 Data 对象作为输入并返回一个新的转换后的 Data 对象。可以使用torch_geometric.transforms.Compose 将转换链接在一起,并在将已处理的数据集保存在磁盘上(pre_transform) 之前或在访问数据集中的图形 (transform) 之前应用。
具体例子:在 ShapeNet 数据集上应用变换(包含 17,000 个 3D 形状点云和来自 16 个形状类别的每个点标签)
from torch_geometric.datasets import ShapeNet
dataset = ShapeNet(root='/tmp/ShapeNet', categories=['Airplane'])
dataset[0]
>>> Data(pos=[2518, 3], y=[2518])
我们可以通过变换从点云生成最近邻图,将点云数据集转换为图数据集:
import torch_geometric.transforms as T
from torch_geometric.datasets import ShapeNet
dataset = ShapeNet(root='/tmp/ShapeNet', categories=['Airplane'],
pre_transform=T.KNNGraph(k=6))
dataset[0]
>>> Data(edge_index=[2, 15108], pos=[2518, 3], y=[2518])
此外,我们可以使用 transform 参数随机扩充 Data 对象,例如,将每个节点位置平移一个小数:
import torch_geometric.transforms as T
from torch_geometric.datasets import ShapeNet
dataset = ShapeNet(root='/tmp/ShapeNet', categories=['Airplane'],
pre_transform=T.KNNGraph(k=6),
transform=T.RandomJitter(0.01))
dataset[0]
>>> Data(edge_index=[2, 15108], pos=[2518, 3], y=[2518])
注意,在将数据保存到磁盘之前,使用pre_transform
来转换数据(从而加快加载时间)。请注意,下次初始化数据集时,它将已经包含图形边,即使没有传递任何变换。如果pre_transform
与已经处理的数据集中的不匹配,则会发出警告
5.在图上的学习方法
在了解PyG中的数据处理、数据集、加载器和转换之后,可以实现我们第一个图神经网络
使用一个简单的GCN层并在Cora引文数据集上复制实验
加载Cora数据集:
from torch_geometric.datasets import Planetoid
dataset = Planetoid(root='/tmp/Cora', name='Cora')
请注意,我们不需要使用转换或数据加载器。现在让我们实现一个两层的 GCN:
import torch
import torch.nn.functional as F
from torch_geometric.nn import GCNConv
class GCN(torch.nn.Module):
def __init__(self):
super().__init__()
self.conv1 = GCNConv(dataset.num_node_features,16)
self.conv2 = GCNConv(16,dataset.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)
构造函数定义了两个 GCNConv 层,它们在我们网络的前向传递中被调用。请注意,非线性未集成到 conv 调用中,因此需要在之后应用(这在 PyG 中的所有运算符中都是一致的)。在这里,我们选择使用 ReLU 作为我们的中间非线性,并最终输出类数的 softmax 分布。让我们在训练节点上训练这个模型 200 个时期:
device = torch.device('cuda'if torch.cuda.is_available() else 'cpu')
model = GCN().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).argmax(dim=1)
correct = (pred[data.test_mask] == data.y[data.test_mask]).sum()
acc = int(correct) / int(data.test_mask.sum())
print(f'Accuracy: {acc:.4f}')
输出:
Accuracy: 0.8110
上述就是从0到1实现一个图神经网络
参考资料
https://xinzhe.blog.csdn.net/article/details/129628335
https://blog.csdn.net/PolarisRisingWar/article/details/117374824