有关于DGL中图的构建
DGL 将有向图表示为一个 DGL 图对象。图中的节点编号连续,从0开始。我们一般通过指定图中的节点数,以及源节点和目标节点的列表,来构建这么一个图。
下面的代码构造了一个图,这个图有五个叶子节点。中心节点的 ID 为 0,边从中心节点出发,指向众多的叶子节点。
g = dgl.graph(([0, 0, 0, 0, 0], [1, 2, 3, 4, 5]), num_nodes=6)
# 同样地,PyTorch LongTensors 也可以使用
g = dgl.graph((torch.LongTensor([0, 0, 0, 0, 0]), torch.LongTensor([1, 2, 3, 4, 5])), num_nodes=6)
# 如果你可以从 edge list 中看出有多少个节点,也可以不制定 nodes 的数量
g = dgl.graph(([0, 0, 0, 0, 0], [1, 2, 3, 4, 5]))
在这个图中,边具有从0开始且连续的ID。并且在创建的过程中,边的顺序和源节点到目标节点列表的顺序相同。换句话说,我们在创建 g 的时候,并不需要特地指定边,而是直接通过起始点列表,也就是 [0, 0, 0, 0, 0] 和 目标点列表 [1, 2, 3, 4, 5] 来自动生成边。
# 打印每条边的源节点和目标节点
print(g.edges())
为图指定节点和边的特征
我们建立的图,往往其边和节点都是有特定的属性的。在现实世界中,图中节点代表的实体可能有多种多样的属性,比如“人”实体可能有性别、年龄、姓名等等属性。
不过在 DGLGraph 中,我们的属性都是张量化的存储的,因此所有的节点或者边的属性都具有相同的维度(shape)。
当然我们现在是为了学习图神经网络,所以这里我们就把这些属性称为“特征”。我们可以采用 ndate
和 edata
来给节点(node)
和边(edge)
赋予特征。
# 为每个节点赋予一个 3维 的特征向量,总共6个节点。
g.ndata['x'] = torch.randn(6, 3)
# 为每条边赋予一个 4维 的特征向量,总共5条节点。
g.edata['a'] = torch.randn(5, 4)
# 为每个节点赋予一个 5x4 的特征矩阵,总共6个节点。
# 注意在 DGL 中,点和边的特征可以是多维的。
g.ndata['y'] = torch.randn(6, 5, 4)
print(g.edata['a'])
下图中心形代表是边的特征。
请注意,这里我们为 ndata 赋予了 x 和 y 两种特征,这里的 x 和 y 就是节点的特征名称,应该是作为一个key去查询所有节点对应的 tensor 列表,然后返回相应的值。同理我们可以赋予更多的特征,然后给特征起名。
其他建议:
·对于分类属性(例如性别、职业),请考虑将它们转换为整数或 one-hot 编码。
·对于可变长度的字符串内容(例如新闻文章),请考虑应用语言模型。
·对于图像,请考虑应用 CNN 等 CV 模型。
图结构的查询
DGLGraph 对象提供了不同的方法,以方便我们查询图的结构。
查询节点数量
print(g.num_nodes())
查询边数量
print(g.num_edges())
中心节点 0 的出度
print(g.out_degrees(0))
中心节点 0 的入度
print(g.in_degrees(0))
代码整合
# 图结构的查询
"""DGLGraph 对象提供了不同的方法,以方便我们查询图的结构。"""
# 查询节点数量
print(g.num_nodes())
# 查询边数量
print(g.num_edges())
# 中心节点 0 的出度
print(g.out_degrees(0))
# 中心节点 0 的入度,这里是有向图所以入度应该为0
print(g.in_degrees(0))
图变换
图变换主要包含两种:子图和反向边。子图指的是从一张图中抽离相关的顶点或者相关的边组成新的图;反向边是指将图中每个边添加相反方向的边,一般用于将有向图转变成无向图。
子图
DGL 提供了许多API,让我们可以将图转换为其他结构,比如提取一个子图。这方便我们查询子图和原图的关系。子图的抽取可以分为两种方式:按照边和按照顶点。
# 从原图的节点0、节点1和节点3产生一个子图。
sg1 = g.subgraph([0, 1, 3])
# 从原图的边0、边1和边3产生一个子图。
sg2 = g.edge_subgraph([0, 1, 3])# 对应的节点为:0 1 2 4
"""通过 dgl.NID 或 dgl.EID 我们可以获得从子图到原图的节点/边映射,如下:"""
# The original IDs of each node in sg1
print(sg1.ndata[dgl.NID])
# The original IDs of each edge in sg1--->边的编号:(0,1):编号0 (0,3):编号2
print(sg1.edata[dgl.EID])
# The original IDs of each node in sg2
print(sg2.ndata[dgl.NID])
# The original IDs of each edge in sg2
print(sg2.edata[dgl.EID])
此外, subgraph 和 edge_subgraph 也复制了原图的点和边特征到子图:
# The original node feature of each node in sg1
print(sg1.ndata['x'])
# The original edge feature of each node in sg1
print(sg1.edata['a'])
# The original node feature of each node in sg2
print(sg2.ndata['x'])
# The original edge feature of each node in sg2
print(sg2.edata['a'])
增加反向边
还有一种常用的变换就是,使用 dgl.add_reverse_edges ,向原图的每一条边都增加一个反向边。比如你希望建立一个双向图的时候,这就很有用了。再强调一下,如果你想建立一个无向图,那最好当做双向图来建立。
newg = dgl.add_reverse_edges(g)
newg.edges()
图的保存和读取
您可以通过dgl保存一个图形或一个图形列表。保存并使用dgl.load_graphs加载它们如下所示:(当然保存图可以保存一张或者多张,读取亦是如此)
# 保存图
dgl.save_graphs('graph.dgl', g) # 保存一张图
dgl.save_graphs('graphs.dgl', [g, sg1, sg2]) # 保存多张图
# 读取图
(g,), _ = dgl.load_graphs('graph.dgl')
print(g)
(g, sg1, sg2), _ = dgl.load_graphs('graphs.dgl')
print(g)
print(sg1)
print(sg2)
当使用了 save_graphs 命令后,我们可以看到在当前目录下多了两个 .dgl 文件,这就是我们保存的两个图。其中 graphs.dgl 文件包含了之前创建的原图和两个子图。