目录
一. 图的表示
对于一个具有三个节点和四个边的无权无向图的示例:
由于边的定义有紧凑表示和元组表示两种,所以上图定义也有两种:
方式一:
import torch from torch_geometric.data import Data edge_index = torch.tensor([[0, 1, 1, 2], [1, 0, 2, 1]], dtype=torch.long) x = torch.tensor([[-1], [0], [1]], dtype=torch.float) data = Data(x=x, edge_index=edge_index) >>> Data(edge_index=[2, 4], x=[3, 1])
方式二:
import torch from torch_geometric.data import Data edge_index = torch.tensor([[0, 1], [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(edge_index=[2, 4], x=[3, 1])
说明:
x
是一个形状为[3, 1]
的张量,表示图中有3个节点,每个节点有一个特征。这里,每个节点的特征是一个单一的浮点数。节点0的特征是-1,节点1的特征是0,节点2的特征是1。edge_index
是一个形状为[2, 4]
的张量,表示图中有4条边。方式一的edge_index
的第一行包含起点节点的索引,第二行包含终点节点的索引。方式二则是将各边分开表示。
- 具体来说,这4条边分别是:
- 从节点0到节点1
- 从节点1到节点0
- 从节点1到节点2
- 从节点2到节点1
二. 常见基准数据集
PyG包含大量常见的基准数据集,例如所有 Planetoid 数据集(Cora、Citeseer、Pubmed)、 TUDatasets的所有图分类数据集及其清理版本、QM7 和 QM9 数据集,以及少量 3D 网格/点云数据集,如 FAUST、ModelNet10/40 和 ShapeNet。
- 初始化化数据集Demo1——ENZYMES 数据集(由 6 个类别中的 600 个图表组成):
from torch_geometric.datasets import TUDataset # 下载指定数据集到本机D:/PyCharmProjects/tmp dataset = TUDataset(root='D:/PyCharmProjects/tmp/ENZYMES', name='ENZYMES') print(len(dataset)) # 600 数据集大小 print(dataset.num_classes) # 6 类别数量 print(dataset.num_node_features) # 3 节点特征数量 print(dataset[0]) # Data(edge_index=[2, 168], x=[37, 3], y=[1]) 第一个图包含 37 个节点,每个节点有 3 个特征。有 168/2 = 84 条无向边,并且该图被分配到恰好一个类 print(dataset[0].is_undirected()) # True 数据对象恰好包含一个图级目标 # 创建 90/10 的训练/测试分割 train_dataset = dataset[:540] print(train_dataset) # ENZYMES(540) test_dataset = dataset[540:] print(test_dataset) # ENZYMES(60) dataset = dataset.shuffle() # 不确定在拆分之前数据集是否已被打乱,您可以通过运行以下命令随机排列它
- 初始化化数据集Demo2——Cora 数据集:
from torch_geometric.datasets import Planetoid # 下载指定数据集到本机D:/PyCharmProjects/tmp dataset = Planetoid(root='D:/PyCharmProjects/tmp/Cora', name='Cora') print(len(dataset)) # 600 数据集大小 print(dataset.num_classes) # 6 类别数量 print(dataset.num_node_features) # 3 节点特征数量 data = dataset[0] print(data) # Data(edge_index=[2, 10556], test_mask=[2708],train_mask=[2708], val_mask=[2708], x=[2708, 1433], y=[2708]) 10556/2条无向边,长度为2078的测试集、训练集、测试集 print(data.is_undirected()) # True 数据对象恰好包含一个图级目标 print(data.train_mask.sum().item()) # 140 表示针对哪些节点进行训练(140 个节点) print(data.val_mask.sum().item()) # 500 表示使用哪些节点进行验证,例如执行早期停止(500 个节点) print(data.test_mask.sum().item()) # 1000 表示针对哪些节点进行测试(1000 个节点)
PS:上面两个数据集由于网络原因,可能会下载失败,网上也有很多解决方法(如配置host文件等)。
三. 小批量
神经网络通常以批量方式进行训练。 PyG
edge_index
通过创建稀疏块对角邻接矩阵(由 定义)并在节点维度上连接特征和目标矩阵,实现小批量的并行化。此组合允许在一个批次中的示例上使用不同数量的节点和边:![]()
PyG包含其自己的torch_geometric.loader.DataLoader,它已经处理了这个连接过程。让我们通过一个例子来了解它:
from torch_geometric.datasets import TUDataset from torch_geometric.loader import DataLoader dataset = TUDataset(root='D:/PyCharmProjects/tmp/ENZYMES', name='ENZYMES', use_node_attr=True) loader = DataLoader(dataset, batch_size=32, shuffle=True) for batch in loader: print(batch) # 返回当前批次中的图的总数 eg.DataBatch(edge_index=[2, 4388], x=[1221, 21], y=[32], batch=[1221], ptr=[33]) 这里的数据也是所有图的总和 print(batch.num_graphs) # 当前批次中有多少个图 32
torch_geometric数据。批处理继承自torch_geometric.data。并包含一个名为batch的附加属性。
batch
是一个列向量,将每个节点映射到批次中的相应图:![]()
例如,使用它来分别计算每个图的节点维度中节点特征的平均数:
from torch_geometric.utils import scatter from torch_geometric.datasets import TUDataset from torch_geometric.loader import DataLoader dataset = TUDataset(root='D:/PyCharmProjects/tmp/ENZYMES', name='ENZYMES', use_node_attr=True) loader = DataLoader(dataset, batch_size=32, shuffle=True) for data in loader: print(data) # DataBatch(batch=[1082], edge_index=[2, 4066], x=[1082, 21], y=[32]) 一个包含图数据的批处理对象,批次中节点的总数是1082,有4066/2=2033条无向边,y=[32] 表示32个图的标签 print(data.num_graphs) # 32 当前批处理数据中图的数量 x = scatter(data.x, data.batch, dim=0, reduce='mean') # 用 scatter 函数,将每个图的节点特征通过求均值的方式聚合成图的特征 print(x.size()) # torch.Size([32, 21]) # 32个图,每个图有21个特征
您可以详细了解内部批处理流程PyG例如,如何修改其行为,请参见此处。有关散射操作的文档,可以参阅
torch_scatter
文档。
四. 数据转换
torchvision变换是转换图像和执行增强的常用方法。
PyG带有自己的转换,它接受一个Data对象作为输入并返回一个新的转换Data对象。可以使用转换链接在一起torch_geometric.transforms.Compose,并在将处理后的数据集保存到磁盘(pre_transform)之前或在访问数据集中的图表(transform)之前应用转换。
- Demo1——在 ShapeNet 数据集上应用变换(包含 17,000 个 3D 形状点云和来自 16 个形状类别的每个点标签):
from torch_geometric.datasets import ShapeNet dataset = ShapeNet(root='D:/PyCharmProjects/tmp/ShapeNet', categories=['Airplane']) print(dataset[0]) # Data(x=[2518, 3], y=[2518], pos=[2518, 3], category=[1])
- Demo2——通过变换从点云生成最近邻图,将点云数据集转换为图形数据集:
import torch_geometric.transforms as T from torch_geometric.datasets import ShapeNet # 使用 force_reload 强制重新加载数据 dataset = ShapeNet(root='D:/PyCharmProjects/tmp/ShapeNet', categories=['Airplane'], pre_transform=T.KNNGraph(k=6), force_reload=True) # 获取第一个数据 data = dataset[0] print(data) # Data(x=[2518, 3], y=[2518], pos=[2518, 3], category=[1], edge_index=[2, 15108])
这里需要安装torch-cluster依赖。而且使用
pre_transform
来转换数据,然后再将其保存到磁盘(从而缩短加载时间)。下次初始化数据集时,即使您没有传递任何转换,它也已经包含图形边缘。如果pre_transform
与已处理数据集中的不匹配,将会显示warn。
- Demo3——使用transform参数随机地增强一个Data对象,例如,将每个节点位置平移一个小数:
import torch_geometric.transforms as T from torch_geometric.datasets import ShapeNet dataset = ShapeNet(root='D:/PyCharmProjects/tmp/ShapeNet', categories=['Airplane'], pre_transform=T.KNNGraph(k=6), transform=T.RandomJitter(0.01)) print(dataset[0]) # Data(x=[2518, 3], y=[2518], pos=[2518, 3], category=[1], edge_index=[2, 15108])
五.实现第一个图神经网络
使用一个简单的 GCN 层并在 Cora 引文数据集上复制实验。有关 GCN 的高级解释,请查看其博客文章。
- 创建GCN_load.py:写如下代码,用于加载数据集
from torch_geometric.datasets import Planetoid dataset = Planetoid(root='D:/PyCharmProjects/tmp/Cora', name='Cora')
- 创建GCN.PY
- 代码:实现一个两层 GCN
import torch import torch.nn.functional as F from torch_geometric.nn import GCNConv from GCN_load import dataset 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
调用中,因此需要在之后应用(这在所有运算符中都是一致的)。在这里,我们选择使用 ReLU 作为我们的中间非线性,并最终输出类数上的 softmax 分布- 创建GCN_train.py:在训练节点上训练这个模型 200 个 epoch
import torch from GCN import GCN from GCN_load import dataset import torch.nn.functional as F 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()
- 创建GCN_evaluate.py:评估模型
from GCN_train import model, data 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.8080
以上第一个图神经网络所需的全部内容。