最近开始学习图神经网络,发现pyg框架不仅好用,官方文档中的教程也很详细。
今天就分享一下pyg的安装和环境配置,记录自己的踩坑过程,顺便展示一些基本的使用。
一、创建虚拟环境
打开Anaconda Prompt,使用如下命令创建虚拟环境:
conda create -n pyg
conda activate pyg
默认使用的python的就是最新版,我这里是python3.12.4,如果你想用稳定一点的配置,可以选择更低版本(例如3.8)
二、安装pytorch
进入我们创建的虚拟环境后,首先要安装pytorch。
通过pytorch官网(PyTorch),查找我们对应要下载的pytorch版本:
如图,复制对应版本的下载命令,粘贴到Anaconda Prompt命令行。
conda install pytorch torchvision torchaudio pytorch-cuda=11.8 -c pytorch -c nvidia
这里我们选用conda命令进行安装, pip安装其实也行,不过据说conda安装会更好一点。
三、安装pyg和扩展包:
这是pyg的官方文档地址:
(Installation — pytorch_geometric documentation (pytorch-geometric.readthedocs.io)
在其中找到自己环境对应的pyg版本,根据你的pytorch和cuda进行选择,比如我安装的pytorch版本是2.4.0,cuda版本是11.8,那么我就选择如下命令:
pip install torch_geometric
为什么这里没选conda呢?因为我发现conda下载的时候会出现一些问题,比如明明下载好了文件却找不到依赖,官方文档也写了问题的原因:
当然,你要是在linux系统上进行配置,那么直接用conda命令就好了,conda安装命令如下:
conda install pyg -c pyg
如果你和我一样,是用pip安装的pyg,那么可能你还需要下载安装额外的一些拓展库。
拓展库包括了:
pyg-lib:异构 GNN 算子和图采样例程
torch-scatter:加速且高效的稀疏减少
torch-sparse: 支持,详见 hereSparseTensor
torch-cluster:图形聚类例程
torch-spline-conv: SplineConv 支持
官方文档中也给出了一次性全部下载的安装命令:
# Optional dependencies:
pip install pyg_lib torch_scatter torch_sparse torch_cluster torch_spline_conv -f https://data.pyg.org/whl/torch-2.4.0+cu118.html
如果你不想安装全部拓展库,而是想针对自己的任务去安装其中特定的几个,那么也可以对每个拓展进行单独的安装。
具体方式是,进入pyg提供的whl链接:data.pyg.org/whl/
其中内容如下:
在其中找到自己torch对应的版本和cuda版本。
以我自己为例,我要下载torch2.4.0+cuda11.8的对应版本,于是找到torch-2.4.0+cu118,点进去跳转到另一个链接:data.pyg.org/whl/torch-2.4.0%2Bcu118.html
接下来,我们再去看看自己要下载的哪个拓展包,比如我想下载pyg-lib,那么命令就是:
pip install pyg-lib -f https://data.pyg.org/whl/torch-2.4.0%2Bcu118.html
同理,只需要对其中的包的名字,以及后面的链接做替换,就能在对应版本下安装自己想要的包了 。
四、pyg入门
现在,我们基本配置好了环境,来尝试一下使用pyg吧。
首先我们要知道,pyg是一个用于图类型数据处理和图机器学习的框架,其中内置了很多常见的模型、算子、算法,能让我们像使用pytorch一样创建图神经网络。
参考官方教程,我们先在环境下创建一个pyg_test.py文件,然后声明一下需要的包:
import torch
from torch_geometric.data import Data
先来学习如何创建一个图类型的数据,以这张图为例:
很明显,这是一张无向图,它有三个节点和两条边,其中每个节点的特征向量长度都只有1,在pyg中是这样创建这张图的:
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])
其中edge_index是边索引信息,用的是coo格式的矩阵,注意这里的数据类型是tensor.long;
x是每个节点的特征向量信息;
data是我们最终得到的图数据,在pyg中,它的数据类型是:
<class 'torch_geometric.data.data.Data'>
现在,我们可以对这张图的形状进行一些查看之类的操作。
print(data.keys()) #查看data的参数
>>> ['x', 'edge_index']
print(data['x']) #查看data的特征矩阵
>>> tensor([[-1.0],
[0.0],
[1.0]])
for key, item in data: #查看data各个参数的信息
print(f'{key} found in data')
>>> x found in data
>>> edge_index found in data
'edge_attr' in data #查看参数在data中是否存在
>>> False
data.num_nodes #查看图节点数量
>>> 3
data.num_edges
#查看data边数量,注意这里之所以是4,是因为这张图是无向图,一条无向边是一条入边和一条出边的组合
>>> 4
data.num_node_features #查看图节点特征的维度
>>> 1
data.has_isolated_nodes() #查看图中是否存在孤立节点
>>> False
data.has_self_loops() #查看图中是否存在自环
>>> False
data.is_directed() #查看图是否是有向图
>>> False
#将数据对象传输到 GPU。
device = torch.device('cuda')
data = data.to(device)
我们可能注意到,上文是用coo格式的边索引矩阵去进行的图创建,实际问题中,我们可能更常用的是图的邻接矩阵格式,那么这二者该如何进行转化呢?
下面是一个将邻接矩阵转化为coo格式的例子:
import torch
import numpy as np
import scipy.sparse as sp
from torch_geometric.data import Data
# 边的连接信息
# 注意,无向图的边要定义两次
A = np.array([[0, 0, 0, 1, 0, 0],
[0, 0, 1, 1, 0, 1],
[0, 1, 0, 1, 1, 0],
[1, 1, 1, 0, 0, 0],
[0, 0, 1, 0, 0, 1],
[0, 1, 0, 0, 1, 0],])
edge_index_temp = sp.coo_matrix(A)
print(edge_index_temp)
#将邻接矩阵转化为coo形式
values = edge_index_temp.data # 边上对应权重值weight
indices = np.vstack((edge_index_temp.row, edge_index_temp.col))#这个结果是一个数组
edge_index_A = torch.LongTensor(indices) # 将数组转化为tensor.long的形式,这就是我们真正需要的coo形式
print(edge_index_A)
<COOrdinate sparse matrix of dtype 'int32'
with 14 stored elements and shape (6, 6)>
Coords Values
(0, 3) 1
(1, 2) 1
(1, 3) 1
(1, 5) 1
(2, 1) 1
(2, 3) 1
(2, 4) 1
(3, 0) 1
(3, 1) 1
(3, 2) 1
(4, 2) 1
(4, 5) 1
(5, 1) 1
(5, 4) 1
tensor([[0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 5, 5],
[3, 2, 3, 5, 1, 3, 4, 0, 1, 2, 2, 5, 1, 4]])
将邻接矩阵转化为coo矩阵,并以tensor.long的格式进行保存,这样我们就可以将其用于创建data对象了。
再来学习一下如何导入内置的数据集:
pyg中内置了很多图数据集,可以用一行命令直接下载、清洗、导入。
这里,我们选用 ENZYMES 数据集(包含600个图,以及6种分类)作为演示:
from torch_geometric.datasets import TUDataset
dataset = TUDataset(root='/tmp/ENZYMES', name='ENZYMES')
其中,root是数据保存的目录,name是数据集的名字,更详细的信息请移步官方文档,在此只做粗略的介绍。
想要查看数据集相关的信息,做法和前文是一样的。
len(dataset)
>>> 600
dataset.num_classes
>>> 6
dataset.num_node_features
>>> 3
如果我们需要划分数据集,可以直接用分割的方法:
train_dataset = dataset[:540]
>>> ENZYMES(540)
test_dataset = dataset[540:]
>>> ENZYMES(60)
和我们处理别的序列类型数据是一样的。
当然,机器学习过程中肯定是需要随机划分数据集的,一种办法是直接用torch里的方法,torch.randperm()来进行对数据集进行打乱,例如:
perm = torch.randperm(len(dataset))
dataset = dataset[perm]
此外,pyg还内置了一个方法,用于打乱数据集:
dataset = dataset.shuffle()
图数据可视化:
懒得长篇大论了,直接上代码。
import networkx as nx
import matplotlib.pyplot as plt
from torch_geometric.utils import to_networkx
import matplotlib
matplotlib.use('TkAgg')
#用于可视化图数据(二维)
def visualize_graph(data, color):
G = to_networkx(data, to_undirected=True)
plt.figure(figsize=(7,7))
plt.xticks([])
plt.yticks([])
nx.draw_networkx(G, pos=nx.spring_layout(G, seed=42), with_labels=False,
node_color=color, cmap="Set2")
plt.show()
把这个函数保存到另一个文件vis.py里,以后要用导入一下就行了。
用这个函数可视化一下我们前面创建的图:
最后是我自己学习过程中的全部代码:
import torch
import numpy as np
import scipy.sparse as sp
from torch_geometric.data import Data
from vis import visualize_graph
from torch_geometric.datasets import KarateClub
# 边的连接信息
# 注意,无向图的边要定义两次
#print("----------------------")
A = np.array([[0, 0, 0, 1, 0, 0],
[0, 0, 1, 1, 0, 1],
[0, 1, 0, 1, 1, 0],
[1, 1, 1, 0, 0, 0],
[0, 0, 1, 0, 0, 1],
[0, 1, 0, 0, 1, 0],])
edge_index_temp = sp.coo_matrix(A)
print(edge_index_temp)
#将邻接矩阵转化为coo形式
values = edge_index_temp.data # 边上对应权重值weight
indices = np.vstack((edge_index_temp.row, edge_index_temp.col))#这个结果是一个数组
edge_index_A = torch.LongTensor(indices) # 将数组转化为tensor.long的形式,这就是我们真正需要的coo形式
print(edge_index_A)
#以下的结果不能输入Data,纯粹用于观察图的性质
i = torch.LongTensor(indices) # 转tensor
v = torch.FloatTensor(values) # 转tensor
edge_index = torch.sparse_coo_tensor(i, v, edge_index_temp.shape)
print(edge_index)
print("----------------------")
# 节点的属性信息
x = torch.tensor(
[
# 每个节点的属性向量维度为1
[0],
[1],
[2],
[3],
[4],
[5],
]
)
# 节点的标签信息
y = torch.tensor(
[
# 每个节点的标签向量维度为1
[0],
[1],
[1],
[0],
[1],
[0],
]
)
# 实例化为一个图结构的数据
data1 = Data(x=x, y=y,edge_index=edge_index_A)
print("type:",type(data1))
#从内置数据集导入
dataset = KarateClub()
data2 = dataset[0]
def show(data):
# 查看图数据
print(data)
# 图数据中包含什么信息
print(data.keys)
# 查看节点的属性信息
print(data['x'])
# 节点数
print(data.num_nodes)
# 边数
print(data.num_edges)
# 节点属性向量的维度
print(data.num_node_features)
# 图中是否有孤立节点
print(data.has_isolated_nodes())
# 图中是否有环
print(data.has_self_loops())
# 是否是有向图
print(data.is_directed())
print("data1:",show(data1))
visualize_graph(data1,data1.y)
#print("data2:",show(data2))
#visualize_graph(data2,data2.y)