• DGL学习笔记——第一章 图


    提示:DGL用户指南学习中


    一、关于图的基本概念?

    图是用以表示实体及其关系的结构,记为 𝐺=(𝑉,𝐸) 。图由两个集合组成,一是节点的集合 𝑉 ,一个是边的集合 𝐸 。 在边集 𝐸 中,一条边 (𝑢,𝑣) 连接一对节点 𝑢 和 𝑣 ,表明两节点间存在关系。关系可以是无向的, 如描述节点之间的对称关系;也可以是有向的,如描述非对称关系。例如,若用图对社交网络中人们的友谊关系进行建模,因为友谊是相互的,则边是无向的; 若用图对Twitter用户的关注行为进行建模,则边是有向的。图可以是有向的无向的,这取决于图中边的方向性。

    图可以是加权的未加权的 。在加权图中,每条边都与一个标量权重值相关联。例如,该权重可以表示长度或连接的强度。

    图可以是同构的 或是异构的 。在同构图中,所有节点表示同一类型的实体,所有边表示同一类型的关系。 例如,社交网络的图由表示同一实体类型的人及其相互之间的社交关系组成。

    相对地,在异构图中,节点和边的类型可以是不同的。例如,编码市场的图可以有表示”顾客”、”商家”和”商品”的节点, 它们通过“想购买”、“已经购买”、“是顾客”和“正在销售”的边互相连接。二分图是一类特殊的、常用的异构图, 其中的边连接两类不同类型的节点。例如,在推荐系统中,可以使用二分图表示”用户”和”物品”之间的关系。

    在多重图中,同一对节点之间可以有多条(有向)边,包括自循环的边。例如,两名作者可以在不同年份共同署名文章, 这就带来了具有不同特征的多条边。

    二、图、节点和边

    DGL使用一个唯一的整数来表示一个节点,称为点ID;并用对应的两个端点ID表示一条边。同时,DGL也会根据边被添加的顺序, 给每条边分配一个唯一的整数编号,称为边ID。节点和边的ID都是从0开始构建的。在DGL的图里,所有的边都是有方向的, 即边 (𝑢,𝑣) 表示它是从节点 𝑢 指向节点 𝑣 的。

    创建一个 DGLGraph 对象的一种方法是使用 dgl.graph() 函数。它接受一个边的集合作为输入。DGL也支持从其他的数据源来创建图对象。

    import dgl
    import torch
    import networkx as nx
    import matplotlib.pyplot as plt
    
    
    u, v = torch.tensor([0, 0, 0, 1]), torch.tensor([1, 2, 3, 4])
    g = dgl.graph((u, v))
    print(g)
    
    
    nx_G = g.to_networkx().to_undirected()
    pos = nx.kamada_kawai_layout(nx_G)
    nx.draw(nx_G, pos, with_labels=True, node_color=[[.7, .7, .7]])
    plt.savefig('casual.png')
    plt.show()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    在这里插入图片描述

    # 图中节点的数量是DGL通过给定的图的边列表中最大的点ID推断所得出的
    print(g)
    '''
    Graph(num_nodes=5, num_edges=4,
          ndata_schemes={}
          edata_schemes={})
    '''
    
    #获取节点的ID
    print(g.nodes())
    #tensor([0, 1, 2, 3, 4])
    
    #获取边的对应端点
    print(g.edges())
    #(tensor([0, 0, 0, 1]), tensor([1, 2, 3, 4]))
    #获取边的对应端点和边ID
    print(g.edges(form='all'))
    #(tensor([0, 0, 0, 1]), tensor([1, 2, 3, 4]), tensor([0, 1, 2, 3]))
    #如果有最大ID的节点没有边,在创建图的时候,用户需要明确地指明节点的数量
    g1 = dgl.graph((u, v), num_nodes=8)
    
    nx_G = g1.to_networkx().to_undirected()
    pos = nx.kamada_kawai_layout(nx_G)
    nx.draw(nx_G, pos, with_labels=True, node_color=[[.7, .7, .7]])
    plt.savefig('max_id_no_edge.png')
    plt.show()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    在这里插入图片描述
    从图中我们可以看出,如果设置了5个节点之间有边,但是一共有8个节点,会有三个节点是孤立的。

    对于无向的图,用户需要为每条边都创建两个方向的边。可以使用 dgl.to_bidirected() 函数来实现这个目的。 如下面的代码段所示,这个函数可以把原图转换成一个包含反向边的图。

    bg = dgl.to_bidirected(g)
    print(bg.edges())
    #(tensor([0, 0, 0, 1, 1, 2, 3, 4]), tensor([1, 2, 3, 0, 4, 0, 0, 1]))
    
    • 1
    • 2
    • 3

    DGL支持使用 32 位或 64 位的整数作为节点ID和边ID。节点和边ID的数据类型必须一致。如果使用 64 位整数, DGL可以处理最多 263−1 个节点或边。不过,如果图里的节点或者边的数量小于 263−1 ,用户最好使用 32 位整数。 这样不仅能提升速度,还能减少内存的使用。DGL提供了进行数据类型转换的方法,如下例所示。

    edges = torch.tensor([2, 5, 3]), torch.tensor([3, 5, 0])
    #边2->3  5->5  3—>0
    g64 = dgl.graph(edges) #DGL默认使用int64
    print(g64.idtype)
    #torch.int64
    
    g32 = dgl.graph(edges, idtype=torch.int32) #使用int32构建图
    print(g32.idtype)
    #torch.int32
    
    g64_2 = g32.long() #转换成int64
    print(g64_2.idtype)
    #torch.int64
    
    g32_2 = g64_2.int() #转换成int32
    print(g32_2.idtype)
    #torch.int32
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    三、节点和边的特征

    DGLGraph 对象的节点和边可具有多个用户定义的、可命名的特征,以储存图的节点和边的属性。 通过 ndata 和 edata 接口可访问这些特征。 例如,以下代码创建了2个节点特征和1个边特征。

    import dgl
    import torch
    
    g = dgl.graph(([0, 0, 1, 5], [1, 2, 2, 0])) #6个节点,4条边
    print(g)
    '''
    Graph(num_nodes=6, num_edges=4,
          ndata_schemes={}
          edata_schemes={})
    '''
    
    g.ndata['x'] = torch.ones(g.num_nodes(), 3)
    g.edata['x'] = torch.ones(g.num_edges(), dtype=torch.int32)
    print(g)
    
    '''
    Graph(num_nodes=6, num_edges=4,
          ndata_schemes={'x': Scheme(shape=(3,), dtype=torch.float32)} #长度为3的节点特征
          edata_schemes={'x': Scheme(shape=(), dtype=torch.int32)}) #标量模型特征
    '''
    
    #不同名称的特征可以具有不同的形状
    g.ndata['y'] = torch.randn(g.num_nodes(), 5)
    '''
    Graph(num_nodes=6, num_edges=4,
          ndata_schemes={'x': Scheme(shape=(3,), dtype=torch.float32)}
          edata_schemes={'x': Scheme(shape=(), dtype=torch.int32)})
    '''
    
    print(g.ndata['x'][1]) #获取节点1的特征
    #tensor([1., 1., 1.])
    print(g.edata['x'][torch.tensor([0, 3])]) #获取边0和3的特征
    #tensor([1, 1], dtype=torch.int32)
    
    #1、定义边
    edges = torch.tensor([0, 0, 0, 1]), torch.tensor([1, 2, 3, 3])
    #2、定义边的权重
    weights = torch.tensor([0.1, 0.6, 0.9, 0.7]) #每条边的权重
    #3、画图
    g = dgl.graph(edges)
    #4、将边的权重赋值到图上
    g.edata['w'] = weights
    print(g)
    '''
    Graph(num_nodes=4, num_edges=4,
          ndata_schemes={}
          edata_schemes={'w': Scheme(shape=(), dtype=torch.float32)})
    '''
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    关于 ndata 和 edata 接口的重要说明:

    1、仅允许使用数值类型(如单精度浮点型、双精度浮点型和整型)的特征。这些特征可以是标量、向量或多维张量;
    2、每个节点特征具有唯一名称,每个边特征也具有唯一名称。节点和边的特征可以具有相同的名称(如上述示例代码中的 ‘x’ );
    3、通过张量分配创建特征时,DGL会将特征赋给图中的每个节点和每条边。该张量的第一维必须与图中节点或边的数量一致。 不能将特征赋给图中节点或边的子集;
    4、相同名称的特征必须具有相同的维度和数据类型;
    5、特征张量使用”行优先”的原则,即每个行切片储存1个节点或1条边的特征(参考上述示例代码的第16和18行)。

    四、从外部源创建图

    1、从Scipy稀疏矩阵中构建图示例

    import dgl
    import torch
    import scipy.sparse as sp
    import networkx as nx
    
    
    spmat = sp.rand(100, 100, density=0.05) #5%非零项
    g = dgl.from_scipy(spmat)
    print(g)
    '''
    Graph(num_nodes=100, num_edges=500,
          ndata_schemes={}
          edata_schemes={})
    '''
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    2、从networkx中构建图示例

    nx_g = nx.path_graph(5)  #一条链路0-1-2-3-4
    g_ = dgl.from_networkx(nx_g)
    print(g_)
    '''
    Graph(num_nodes=5, num_edges=8,
          ndata_schemes={}
          edata_schemes={})
    '''
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    注意,当使用 nx.path_graph(5) 进行创建时, DGLGraph 对象有8条边,而非4条。 这是由于 nx.path_graph(5) 构建了一个无向的NetworkX图 networkx.Graph ,而 DGLGraph 的边总是有向的。 所以当将无向的NetworkX图转换为 DGLGraph 对象时,DGL会在内部将1条无向边转换为2条有向边。 使用有向的NetworkX图 networkx.DiGraph 可避免该行为。

    nxg = nx.DiGraph([(2, 1), (1, 2), (2, 3), (0, 0)])
    g__ = dgl.from_networkx(nxg)
    print(g__)
    '''
    Graph(num_nodes=4, num_edges=4,
          ndata_schemes={}
          edata_schemes={})
    '''
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    3、从csv中构建图结构

    The nodes.csv stores every club members and their attributes.
    The edges.csv stores the pair-wise interactions between two club members
    请添加图片描述

    五、创建异构图

    请添加图片描述

    在DGL中,一个异构图由一系列子图构成,一个子图对应一种关系。每个关系由一个字符串三元组 定义 (源节点类型, 边类型, 目标节点类型) 。由于这里的关系定义消除了边类型的歧义,DGL称它们为规范边类型。

    1、创建异构图

    import dgl
    import torch
    
    #创建一个具有3种节点类型和3种边类型的异构图
    graph_data = {
       ('drug', 'interacts', 'drug'): (torch.tensor([0, 1]), torch.tensor([1, 2])),
       ('drug', 'interacts', 'gene'): (torch.tensor([0, 1]), torch.tensor([2, 3])),
       ('drug', 'treats', 'disease'): (torch.tensor([1]), torch.tensor([2]))
    }
    
    g = dgl.heterograph(graph_data)
    print(g.ntypes)
    #['disease', 'drug', 'gene']
    print(g.etypes)
    #['interacts', 'interacts', 'treats']
    print(g.canonical_etypes)
    #[('drug', 'interacts', 'drug'), ('drug', 'interacts', 'gene'), ('drug', 'treats', 'disease')]
    print(g)
    '''
    Graph(num_nodes={'disease': 3, 'drug': 3, 'gene': 4},
          num_edges={('drug', 'interacts', 'drug'): 2, ('drug', 'interacts', 'gene'): 2, ('drug', 'treats', 'disease'): 1},
          metagraph=[('drug', 'drug', 'interacts'), ('drug', 'gene', 'interacts'), ('drug', 'disease', 'treats')])
    '''
    print(g.metagraph().edges())
    #[('drug', 'drug'), ('drug', 'gene'), ('drug', 'disease')]
    
    #获取图种所有节点的数量
    print(g.num_nodes())
    #10
    
    #获取drug种节点的数量
    print(g.num_nodes('drug'))
    #3
    
    #不同类型的节点都有单独的ID,因此,没有指定节点类型就没有明确的返回值
    print(g.nodes())
    DGLError: Node type name must be specified if there are more than one node types.
    
    print(g.nodes('drug'))
    #tensor([0, 1, 2])
    
    #设置/获取'drug'类型的节点'hv'tezh
    g.nodes['drug']. data['hv'] = torch.ones(3, 1)
    print(g.nodes['drug']. data['hv'])
    '''
    tensor([[1.],
            [1.],
            [1.]])
    '''
    
    
    #设置/获取'treats'类型的边的'he'特征
    g.edges['treats'].data['he'] = torch.zeros(1, 1)
    print(g.edges['treats'].data['he'] )
    #tensor([[0.]])
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55

    如果图里只有一种节点或边类型,则不需要指定节点或边的类型。

    g_ = dgl.heterograph({
       ('drug', 'interacts', 'drug'): (torch.tensor([0, 1]), torch.tensor([1, 2])),
       ('drug', 'is similar', 'drug'): (torch.tensor([0, 1]), torch.tensor([2, 3]))
    })
    
    print(g_.nodes())
    #tensor([0, 1, 2, 3])
    
    #设置/获取但一类型的节点或边特征,不必使用新的语法
    g_.ndata['hv'] = torch.ones(4, 1)
    print(g_.ndata['hv'])
    '''
    tensor([[1.],
            [1.],
            [1.],
            [1.]])
    '''
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    注意,同构图和二分图只是一种特殊的异构图,它们只包括一种关系。

    #同构图和二分图只是一种特殊的异构图,它们只包括一种关系。
    #一个同构图
    g1 = dgl.heterograph({('node_type', 'edge_type', 'node_type'):(u, v)})
    
    #一个二分图
    g2 = dgl.heterograph({('source_type', 'edge_type', 'destination_type'): (u,v)})
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    2、从磁盘加载异构图

    请添加图片描述
    DGL提供了 dgl.save_graphs() 和 dgl.load_graphs() 函数,分别用于以二进制格式保存异构图和加载它们。

    3、边类型子图

    
    graph_data = {
       ('drug', 'interacts', 'drug'): (torch.tensor([0, 1]), torch.tensor([1, 2])),
       ('drug', 'interacts', 'gene'): (torch.tensor([0, 1]), torch.tensor([2, 3])),
       ('drug', 'treats', 'disease'): (torch.tensor([1]), torch.tensor([2]))
    }
    
    g = dgl.heterograph(graph_data)
    
    
    eg = dgl.edge_type_subgraph(g, [('drug', 'interacts', 'drug'),
                                    ('drug', 'treats', 'disease')])
    print(eg)
    '''
    Graph(num_nodes={'disease': 3, 'drug': 3},
          num_edges={('drug', 'interacts', 'drug'): 2, ('drug', 'treats', 'disease'): 1},
          metagraph=[('drug', 'drug', 'interacts'), ('drug', 'disease', 'treats')])
    '''
    
    
    #相关特征也会被拷贝
    print(eg.nodes['drug'].data['hv'])
    '''
    tensor([[1.],
            [1.],
            [1.]])
    '''
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    4、将异构图转化为同构图

    异构图为管理不同类型的节点和边及其相关特征提供了一个清晰的接口。这在以下情况下尤其有用:
    (1)不同类型的节点和边的特征具有不同的数据类型或大小。
    (2)用户希望对不同类型的节点和边应用不同的操作。

    如果上述情况不适用,并且用户不希望在建模中区分节点和边的类型,则DGL允许使用 dgl.DGLGraph.to_homogeneous() API将异构图转换为同构图。 具体行为如下:
    (1)用从0开始的连续整数重新标记所有类型的节点和边。
    (2)对所有的节点和边合并用户指定的特征。

    g = dgl.heterograph({
       ('drug', 'interacts', 'drug'): (torch.tensor([0, 1]), torch.tensor([1, 2])),
       ('drug', 'treats', 'disease'): (torch.tensor([1]), torch.tensor([2]))})
    
    g.nodes['drug'].data['hv'] = torch.zeros(3, 1)
    g.nodes['disease'].data['hv'] = torch.ones(3, 1)
    g.edges['interacts'].data['he'] = torch.zeros(2, 1)
    g.edges['treats'].data['he'] = torch.zeros(1, 2)
    
    #默认情况下不进行特征合并
    hg = dgl.to_homogeneous(g)
    print('hv' in hg.ndata)
    #False
    
    #拷贝边的特征
    #对于要拷贝的特征,DGL假定不同类型的节点或边的需要合并的特征具有相同的大小和数据类型
    eg_ = dgl.to_homogeneous(g, edata=['he'])
    print(eg_)
    DGLError: Cannot concatenate column ‘he’ with shape Scheme(shape=(2,), dtype=torch.float32) and shape Scheme(shape=(1,), dtype=torch.float32)
    #因为两条边的特征shape不同,一个是(2,1),另一个是(1,1)
    
    
    hg = dgl.to_homogeneous(g, ndata=['hv'])
    print(hg.ndata['hv'])
    '''
    tensor([[1.],
            [1.],
            [1.],
            [0.],
            [0.],
            [0.]])
    '''
    
    #异构图中的节点类型的顺序
    print(g.ntypes)
    #['disease', 'drug']
    #原始节点类型
    print(hg.ndata[dgl.NTYPE])
    #tensor([0, 0, 0, 1, 1, 1])
    #从这个结果我们可以原来的异构图有两种节点,前三个节点在异构图中属于第一种类型的节点,后三个节点在异构图中属于第二种类型的节点
    
    #原始的特定类型节点ID
    print(hg.ndata[dgl.NID])
    #tensor([0, 1, 2, 0, 1, 2])
    #我们可以得到每个节点在原来异构图中所属节点类型范围内的索引
    
    #异构图中边类的顺序
    print(g.etypes)
    #['interacts', 'treats']
    #原始边类型
    print(hg.edata[dgl.ETYPE])
    #tensor([0, 0, 1])
    #前两条边属于异构图中的一种边类型,第三条边属于异构图中的另一种边类型
    
    #原始的特定类型边ID
    print(hg.edata[dgl.EID])
    #tensor([0, 1, 0])
    #这三条边在原来的异构同所属边类型中的索引位置
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59

    出于建模的目的,用户可能需要将一些关系合并,并对它们应用相同的操作。为了实现这一目的,可以先抽取异构图的边类型子图,然后将该子图转换为同构图。

    g = dgl.heterograph({
       ('drug', 'interacts', 'drug'): (torch.tensor([0, 1]), torch.tensor([1, 2])),
       ('drug', 'interacts', 'gene'): (torch.tensor([0, 1]), torch.tensor([2, 3])),
       ('drug', 'treats', 'disease'): (torch.tensor([1]), torch.tensor([2]))
    })
    
    #1、抽取边类子图
    sub_g = dgl.edge_type_subgraph(g,[('drug', 'interacts', 'drug'),
                                       ('drug', 'interacts', 'gene')])
    
    
    #2、将子图转换为同构图
    h_sub_g = dgl.to_homogeneous(sub_g)
    print(h_sub_g)
    '''
    Graph(num_nodes=7, num_edges=4,
          ndata_schemes={'_ID': Scheme(shape=(), dtype=torch.int64), '_TYPE': Scheme(shape=(), dtype=torch.int64)}
          edata_schemes={'_ID': Scheme(shape=(), dtype=torch.int64), '_TYPE': Scheme(shape=(), dtype=torch.int64)})
    '''
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    总结

    提示:这里对文章进行总结:
    例如:以上就是今天要讲的内容,本文仅仅简单介绍了pandas的使用,而pandas提供了大量能使我们快速便捷地处理数据的函数和方法。

  • 相关阅读:
    halcon-scaled_shape_model注释
    C++学习 --map
    【LeetCode】 贴纸拼词(动态规划)
    图像采集卡在应用程序的重要性概述
    华为OD:跳房子I
    【Day_14 0510】▲幸运的袋子
    PgSql使用技巧总结 PgSql和MySql的对比
    Android assets
    .NET Emit 入门教程:第六部分:IL 指令:9:详解 ILGenerator 指令方法:运算操作指令(指令篇结束)
    MyBatis核心对象
  • 原文地址:https://blog.csdn.net/weixin_51589123/article/details/127967767