PyG构建图对象并转换成networkx图对象

一、写在前面

PyG 是一款基于PyTorch 的图神经网络库,它提供了很多经典的图神经网络模型和图数据集。
在使用 PyG 框架来构建和训练图网络模型时,需要事先将图数据换成PyG定义的“图对象”
PyG 提供多种类型的图对象(在torch_geometric.data下),常用的包括:Data(同构图)和HeteroData(异构图)

无标题.png

二、基本用法(以Data对象为例)

2.1) 构建图对象

构建一张图的Data对象时,通常需要提供以下基本数据:
from torch_geometric.data import Data
Data ( x: Optional[torch.Tensor] = None,
   edge_index: Optional[torch.Tensor] = None,
   edge_attr: Optional[torch.Tensor] = None,
   y: Optional[torch.Tensor] = None,
   pos: Optional[torch.Tensor] = None,
   **kwargs)

  • 节点节点名称用数字序号表示:0,1,2,... ,num _ node-1(共num_nodes个节点),这是默认且固定的,不需要指定

  • x:节点特征矩阵:shape为[num_nodes, num_node_features]
    一张图的所有节点的特征存储于该二维矩阵中,即一行表示一个节点,一列表示一个特征(一个节点可以有多个特征),行序号对应节点序号(0,1,2,... ,num _ node-1)

  • edge_index:边矩阵:shape为[2, num_edges]
    一张图的所有边存储于该二维矩阵中,其中第一行表示所有边的起始节点编号,第二行表示所有边的目标节点编号,类型为 torch.long。
    (注意:edge _ index 中的元素必须在{0,1,2,... ,num _ node-1}

  • edge_attr:边特征矩阵:shape为[num_edges, num_edge_features]
    一张图的所有边的特征存储于该二维矩阵中,即一行表示一条边,一列表示一个特征(一条边可以有多个特征)

  • y:训练标签:(可能具有任意形状)。例如,如果是节点级别的标签,其形状为 [num_nodes, *];如果是图级别的标签, 其形状为为 [1,*]

  • 节点位置(pos):记录每个节点的具体位置,存储于shape为[num_nodes, num_dimensions]的二维矩阵中

上述信息通常需要用户提前准备好,才能构建一个Data对象,但都不是必须要提供的。一般对于一张图而言,最重要的是节点特征矩阵、边矩阵、边特征矩阵

Data 对象有点类似 Python 中的字典,属性和数据用键值对表示,因此可以用点“.”或方括号“[]”来访问、修改、增加其内部的数据,就跟字典的操作方式一样。

2.2) 图对象的方法

见‘举例1’一节的3.3)

三、举例1(简单例子)

目标:为下图创建Data对象:


原图.png
import torch
from torch_geometric.data import Data
import networkx as nx
from torch_geometric.utils import to_networkx
import matplotlib.pyplot as plt

3.1)图的原始图数据准备:

首先将上图中的图(Graph)转化成对应的tensor(非常重要,决定了后面的图对象是否能正确构建)。

# 节点特征矩阵(一行对应一个节点的特征,每个节点有3个特征)
>>my_node_features = torch.tensor([[-1, -1, -1], 
                                 [-2, -2, -2],
                                 [-3, -3, -3],
                                 [-4, -4, -4]],dtype=torch.float)

# 边的节点对,共有7条边(四个节点:0、1、2、3),必须用7组节点对来表示
>>my_edge_index = torch.tensor([[0, 1, 2, 1, 3, 2, 3],
                              [1, 2, 1, 3, 1, 3, 2]], dtype=torch.long)

# 边特征矩阵(一行对应一条边的特征,每条边有4个特征)
>>my_edge_attr = torch.tensor([[11, 11, 11, 11],
                             [22, 22, 22, 22],
                             [33, 33, 33, 33],
                             [44, 44, 44, 44],
                             [55, 55, 55, 55],
                             [66, 66, 66, 66],
                             [77, 77, 77, 77]], dtype=torch.float)

# 边权重,共有7个边权重,一条边一个
>>my_edge_weight = torch.tensor([1, 2, 3, 4, 5, 6, 7], dtype=torch.float)

3.2)根据图的原始数据构建PyG图对象(Data对象):

>>pyg_G = Data(x=my_node_features, 
             edge_index=my_edge_index, 
             edge_attr=my_edge_attr, 
             edge_weight=my_edge_weight)
>>print(pyg_G)
输出:
Data(x=[4, 3], edge_index=[2, 7], edge_attr=[7, 4], edge_weight=[7])

PyG对象输出解读.jpg

对PyG对象输出信息的解读很重要(特别是对于无法图像化的大图)!

3.3)图对象(Data对象)提供的几种常用方法(其他方法使用‘dir(图对象)’获取):

  • .num_nodes:返回节点个数(int)
  • .num_node_types:返回节点种类数(int)
  • .num_node_features:返回节点特征数(int)
  • .node_attrs():返回与节点相关的属性名列表(str list)
>>pyg_G.node_attrs()
['x']
  • .x:★返回节点特征矩阵(tensor array)
>>pyg_G.x
tensor([[-1., -1., -1.],
        [-2., -2., -2.],
        [-3., -3., -3.],
        [-4., -4., -4.]])
  • .num_edges:返回边条数(int)

  • .num_edge_types:返回边种类数(int)

  • .num_edge_features:返回边特征数(int)

  • .edge_index:★返回边的节点对(tensor array)

>>pyg_G.edge_index
tensor([[0, 1, 2, 1, 3, 2, 3],
        [1, 2, 1, 3, 1, 3, 2]])
  • edge_attrs():返回与边相关的属性名列表(str list)
pyg_G.edge_attrs()
['edge_weight', 'edge_attr', 'edge_index']
  • pyg_G.edge_weight:返回边权重(tensor array)
>>pyg_G.edge_weight
tensor([1., 2., 3., 4., 5., 6., 7.])
  • .edge_attr:返回边的特征矩阵(tensor array)
>>pyg_G.edge_attr
tensor([[11., 11., 11., 11.],
        [22., 22., 22., 22.],
        [33., 33., 33., 33.],
        [44., 44., 44., 44.],
        [55., 55., 55., 55.],
        [66., 66., 66., 66.],
        [77., 77., 77., 77.]])
  • .edge_stores 和.node_stores:返回存储了整个图的信息(dict list)
>>pyg_G.node_stores
[{'x': tensor([[-1., -1., -1.],
         [-2., -2., -2.],
         [-3., -3., -3.],
         [-4., -4., -4.]]), 'edge_index': tensor([[0, 1, 2, 1, 3, 2, 3],
         [1, 2, 1, 3, 1, 3, 2]]), 'edge_attr': tensor([[11., 11., 11., 11.],
         [22., 22., 22., 22.],
         [33., 33., 33., 33.],
         [44., 44., 44., 44.],
         [55., 55., 55., 55.],
         [66., 66., 66., 66.],
         [77., 77., 77., 77.]]), 'edge_weight': tensor([1., 2., 3., 4., 5., 6., 7.])}]

3.4)PyG图对象与networkx图对象的转换(检查我们创建的PyG对象是否与原图一致)

https://blog.csdn.net/zzy_NIC/article/details/127996911
https://zhuanlan.zhihu.com/p/92482339
PyG主要用于图网络计算,本身没有可视化功能。可利用PyG的to_networkx()方法将PyG同构图对象转化成networkx对象,然后可视化。
to_networkx(
   data: PyG的Data或HeteroData对象,
   node_attrs: 节点属性名(可迭代str对象,默认None),
   edge_attrs: 边属性名(可迭代str对象,默认None),
   graph_attrs: 图属性名(可迭代str对象,默认None),
   to_undirected: 转换成无向图还是有向图(True/False,默认False),
   remove_self_loops: 是否将图中的loop移除(True/False,默认False),
)

■■Case1:转换时,不指定 node_attrs、edge_attrs、graph_attrs参数。
从输出结果来看,这种情况to_networkx()只会把PyG对象的节点(nodes)和边(edges)转换到networkx对象中,其他属性信息不会包含(下图中全是空{ })。其次,从输出的节点名、边的节点对以及图像来看,与最前面的‘原图’是相同的,说明我们构建的PyG是对的。

# Case1
>>nx_G = to_networkx(data=pyg_G, to_undirected=False)  # 将PyG的Data对象转化成networkx的数据对象

>>print(f'节点名:{nx_G.nodes}')
>>print(f'边的节点对:{nx_G.edges}')
>>print('每个节点的属性:')
# print(nx_G.nodes(data=True))
>>for node in nx_G.nodes(data=True):
    print(node)
>>print('每条边的属性:')
# print(nx_G.edges(data=True))
>>for edge in nx_G.edges(data=True):
    print(edge)

# 画图
>>pos = nx.spring_layout(nx_G)  # 迭代计算‘可视化图片’上每个节点的坐标
>>nx.draw(nx_G, pos, node_size=800, with_labels=True, font_size=20)  # 绘图
>>plt.show()

输出:如下图所示
图片1.png

■■Case2:转换时,指定 node_attrs、edge_attrs、graph_attrs参数。
这种情况,首先得查看原PyG对象有哪些属性:

>>print(pyg_G.node_attrs())
>>print(pyg_G.edge_attrs())
输出:
['x']
['edge_weight', 'edge_attr', 'edge_index']

可见,该PyG对象有节点属性有['x'],边属性有['edge_weight', 'edge_attr', 'edge_index'],
于是可以在to_networkx()转换时进行指定(特别注意:'edge_index'这个属性不能写在to_networkx()的edge_attrs变量中,否则出错),见下面代码:

# Case2
>>nx_G = to_networkx(data=pyg_G, 
                   node_attrs=['x'],
                   edge_attrs=['edge_weight', 'edge_attr'],
                   to_undirected=True)  # 将PyG的Data对象转化成networkx的数据对象

>>print(f'节点名:{nx_G.nodes}')
>>print(f'边的节点对:{nx_G.edges}')
>>print('每个节点的属性:')
# print(nx_G.nodes(data=True))
>>for node in nx_G.nodes(data=True):
    print(node)
>>print('每条边的属性:')
# print(nx_G.edges(data=True))
>>for edge in nx_G.edges(data=True):
    print(edge)

# 画图
>>pos = nx.spring_layout(nx_G)  # 迭代计算‘可视化图片’上每个节点的坐标
>>nx.draw(nx_G, pos, node_size=400, with_labels=True)  # 绘图
>>plt.show()
图片2.png

从上图的输出结果看,已经把PyG对象的节点和边的各种属性同时转化成networkx对象的属性了。

四、举例2(PyG对象节点、边、节点特征、边特征之间的对应关系剖析)

import torch
from torch_geometric.data import Data
import networkx as nx
from torch_geometric.utils import to_networkx
import matplotlib.pyplot as plt

4.1)原始图数据准备

与前面不同的是,此例中事先并不知道图的结构,只有数据。
而且注意:
my_node_features的shape=[5,3],即节点序号为:0、1、2、3、4;
但边的节点对my_edge_index 指定的节点为:10、11、12、13。

# 节点特征矩阵(一行对应一个节点的特征,每个节点有3个特征)
>>my_node_features = torch.tensor([[-1, -1, -1], 
                                 [-2, -2, -2],
                                 [-3, -3, -3],
                                 [-4, -4, -4],
                                 [-5, -5, -5]],
                                dtype=torch.float)

# 边矩阵(这里共有7条边,必须用7组节点对来表示,节点对的前后位置可以任意调换,对结果没有影响)
>>my_edge_index = torch.tensor([[10, 11, 12, 11, 13, 13, 12],
                              [11, 12, 11, 13, 11, 12, 13]], dtype=torch.long)

# 边特征矩阵(一行对应一条边的特征,每条边有4个特征)
>>my_edge_attr = torch.tensor([[11, 11, 11, 11],
                             [22, 22, 22, 22],
                             [33, 33, 33, 33],
                             [44, 44, 44, 44],
                             [55, 55, 55, 55],
                             [66, 66, 66, 66],
                             [77, 77, 77, 77]], dtype=torch.float)

# 边权重,共设置了7个边权重
>>my_edge_weight = torch.tensor([1, 2, 3, 4, 5, 6, 7], dtype=torch.float)

4.2)根据原始数据构建PyG图对象

>>pyg_G = Data(x=my_node_features, 
             edge_index=my_edge_index, 
             edge_attr=my_edge_attr, 
             edge_weight=my_edge_weight)
>>print(pyg_G)
输出:
Data(x=[5, 3], edge_index=[2, 7], edge_attr=[7, 4], edge_weight=[7])

从PyG对象的输出结果看,该图有5个节点,每个节点3个特征;共有7条边,每条边4个特征,1个权重。

输出节点和边的属性名列表:
>>print(pyg_G.node_attrs())
>>print(pyg_G.edge_attrs())
输出:
['x']
['edge_index', 'edge_weight', 'edge_attr']

4.3)将PyG对象转换成networkx对象,并成图

>>nx_G = to_networkx(data=pyg_G, 
                   node_attrs=['x'],
                   edge_attrs=['edge_weight', 'edge_attr'],
                   to_undirected=False)  # 将PyG的Data对象转化成networkx的数据对象

>>print(f'节点名:{nx_G.nodes}')
>>print(f'边的节点对:{nx_G.edges}')
>>print('每个节点的属性:')
# print(nx_G.nodes(data=True))
>>for node in nx_G.nodes(data=True):
    print(node)
>>print('每条边的属性:')
# print(nx_G.edges(data=True))
>>for edge in nx_G.edges(data=True):
    print(edge)

# 画图
>>pos = nx.circular_layout(nx_G)  # 迭代计算‘可视化图片’上每个节点的坐标
>>nx.draw(nx_G, pos, node_size=800, with_labels=True, font_size=20)  # 绘图
>>plt.show()

Case1:参数to_undirected=False,即有向图
从输出结果的节点名来看,该图共有9个节点,前面的[0,1,2,3,4]五个节点(注意,代码中我们并没有指定这些节点名)是to_networkx()根据节点特征矩阵my_node_features的行数按0,1,2……顺序自动分配的(这是PyG固定的);后面四个节点[10,11,12,13]是to_networkx()根据用户给的边的节点对矩阵my_edge_index中自动抽取并生成的
★★可见,在利用to_networkx()将PyG对象转换成networkx对象时,to_networkx会自动补充一些节点,比如这里的[0,1,2,3,4],我们将其称为冗余节点!可以写额外的代码来将这些冗余节点删除,见子图抽取的‘2.2.5 将冗余节点从子图的networkx图对象中删除’

关于边的特征和权重,PyG会自动将边特征矩阵my_edge_attr的
第1行作为第1条边【这里是(10,11)】的特征;
第2行作为第2条边【这里是(11,12)】的特征;
第3行作为第3条边【这里是(12,11)】的特征;
……
同理,PyG会自动将边权重向量my_edge_weight的
第1个值作为第1条边【这里是(10,11)】的权重;
第2个值作为第2条边【这里是(11,12)】的权重;
第3个值作为第3条边【这里是(12,11)】的权重;
……

特别注意:边特征矩阵(my_edge_attr)的行数、边权重向量(my_edge_weight)的元素个数都必须和边节点对矩阵(my_edge_index )的列数相同,否则结果会出错

14029140-5305108e9eb8121b.jpg

Case2:参数to_undirected=True,即无向图
Case2除了边有所变化以外,其他都与Cas1一样。
Case2主要为了说明to_networkx()这个函数的参数to_undirected=False/True(有向图和无向图)的区别。
Cas1是有向图,根据给定的节点对矩阵my_edge_index从起点到终点画图即可,这个没啥疑问。
Cas2是无向图:

  • 如果两个节点之间只有1条边,则有向图和无向图都用这条边,比如这里的(10,11);
  • 如果两个节点之间有2条边,则使用小节点序号到大节点序号的边作为无向边,比如这里的(11,12)和(12,11),选择(11,12)作为无向边,(11,13)和(13,11),选择(11,13)作为无向边,(13,12)和(12,13),选择(12,13)作为无向边。
新建 Microsoft Visio 绘图.jpg

参考:
https://zhuanlan.zhihu.com/p/599104296
https://blog.csdn.net/ARPOSPF/article/details/128398393

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,324评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,356评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,328评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,147评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,160评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,115评论 1 296
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,025评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,867评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,307评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,528评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,688评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,409评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,001评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,657评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,811评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,685评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,573评论 2 353

推荐阅读更多精彩内容