Deep Learning/Graph Neural Network

DGL을 통한 Graph 생성하기.

언킴 2022. 9. 1. 11:30
반응형

Contents

     

     

    본 글에서는 DGL 패키지를 이용해서 그래프를 생성하는 방법에 대해서 다룬다. DGL 패키지는 그래프를 다루기 위한 패키지로 그래프를 생성하거나 기존 연구에서 제안된 함수(GCN, GraphSAGE 등)를 호출을 통해 편리하게 사용하도록 도와주는 패키지다. 

     

    패키지 불러오기

    import dgl 
    import torch

    기본적으로 dgl은 PyTorch와 함께 사용한다. tensorflow도 사용하는 것이 가능하지만 본 글에서는 PyTorch를 이용한 dgl을 다루어 볼 것이다. 필요한 함수는 dgl이며 pip install을 통해 설치하면 된다. 

     

    그래프 생성하기

    dgl에서 그래프를 생성하는 것은 매우 간단하다. dgl 내에 내장된 graph 함수를 사용하면 바로 만들 수 있다. 

    g = dgl.graph(
        ([0, 0, 0, 0, 0], 
         [1, 2, 3, 4, 5]), num_nodes = 6)
    
    g = dgl.graph(
        (torch.LongTensor([0, 0, 0, 0, 0]), 
         torch.LongTensor([1, 2, 3, 4, 5])), num_nodes = 6
    )
    
    g = dgl.graph(
        ([0, 0, 0, 0, 0], 
         [1, 2, 3, 4, 5])
    )
    
    print(g)
    print(g.edges())
    
    # Graph(num_nodes=6, num_edges=5,
    #      ndata_schemes={}
    #      edata_schemes={})
    # (tensor([0, 0, 0, 0, 0]), tensor([1, 2, 3, 4, 5]))

    위에서 생성한 방법이 전부 가능하다. num_nodes를 통해 node의 수를 지정해주어도 되고, 지정하지 않는다면 dgl.graph 내에 존재하는 node를 계산해서 지정해준다. 또한 LongTensor를 사용해서 그래프를 만들어도 상관없다. 

     

    Feature 할당하기

    그래프를 다룰 때 단순히 node와 edge의 연결성만 보는 연구도 존재하지만 node 내의 feature 즉 속성까지 고려한 연구도 많이 진행되고 있다. node features는 나이, 연령, 직업 등 다양한 형태로 존재할 수 있으며 ndata를 통해 직접 할당할 수 있다. 

    g.ndata['x'] = torch.randn(6, 3)
    g.edata['a'] = torch.randn(5, 4)
    g.ndata['y'] = torch.randn(6, 5, 4)
    
    print(g.edata['a'])
    
    # tensor([[ 1.3593, -0.4375,  0.5276,  0.1857],
    #        [-1.0860, -0.6479,  0.2746,  0.1281],
    #        [-0.1216,  0.5021,  0.6441,  0.8764],
    #        [-0.2157, -0.8268,  0.1002,  0.7997],
    #        [ 2.2535,  1.3702, -0.0113, -0.8284]])

    이때 주의해야 되는 부분은 node혹은 edge의 shape와 feature의 shape가 동일해야 입력이 가능하다.

     

    그래프 구조 조회하기

    그래프 내에는 node의 수, edge의 수, feature, degree 등의 다양한 정보들이 존재한다. dgl에서는 이와 같은 정보를 조회할 수 있는 다양한 함수들을 제공한다. 

    print(g.num_nodes())
    print(g.num_edges())
    
    print(g.out_degrees())
    print(g.in_degrees())
    
    # 6
    # 5
    # tensor([5, 0, 0, 0, 0, 0])
    # tensor([0, 1, 1, 1, 1, 1])

     

    SubGraph 도출하기

    가장 초창기 모델인 Recurrent GNN을 사용하는 경우에는 Subgraph가 요구되지 않지만, Spatial GCN을 사용하는 경우에는 Subgraph를 도출하는 일이 발생할 수 있다. 

    # node0, 1, 3으로 구성된 subgraph를 의미합니다.
    subgraph1 = g.subgraph([0, 1, 3])
    
    # edge0, 1, 3으로 구성된 subgraph를 의미합니다. 
    subgraph2 = g.edge_subgraph([0, 1, 3])
    
    
    # 새로 생성된 그래프에서 `dgl.NID`, `dgl.EID`를 사용함으로써 
    # subgraph에서 original Graph의 node, edge를 찾을 수 있습니다.
    
    print('original ID of each node in subgraph1', subgraph1.ndata[dgl.NID])
    print('original ID of each edge in subgraph1', subgraph1.edata[dgl.EID], '\n')
    
    print('original ID of each node in subgraph2', subgraph2.ndata[dgl.NID])
    print('original ID of each edge in subgraph2', subgraph2.edata[dgl.EID])

    NID, EID는 node와 edge에 대한 고유 ID를 의미한다. 이를 통해 subgraph로 도출된 node와 edge의 원래 original Graph에서 위치를 찾을 수 있다. 

     

    new_graph = dgl.add_reverse_edges(g)
    new_graph.edges()
    
    # (tensor([0, 0, 0, 0, 0, 1, 2, 3, 4, 5]),
    # tensor([1, 2, 3, 4, 5, 0, 0, 0, 0, 0]))

    추가적으로 add_reverse_edge를 사용하게 되면 원래 있던 구조를 뒤집어서 다시 한 번 생성하는 것이다. 단순히 undirected graph로 표현되어 있는 구조를 양방향 graph로 표현함으로써 보다 좋은 결과를 도출할 수 있다.

     

     

    그래프 저장 및 호출하기

    그래프를 생성했으면 이제 저장을 해야 된다. 저장을 하지 않으면 그래프를 사용할 때마다 매번 위 코드를 반복해서 실행시켜야 하기 때문에 매우 비효율적이다. 따라서 마지막으로 그래프를 저장하는 방법과 저장된 그래프를 호출하는 방법에 대해서 알아보자. 

    # glist, label_dict = load_graphs('path')
    
    # Save Graph 
    dgl.save_graphs('graph.dgl', g)
    dgl.save_graphs('graphs.dgl', [g, subgraph1, subgraph2])
    
    # Load Graph 
    (g,), _ = dgl.load_graphs('graph.dgl')
    print(g)
    
    (g, subgraph1, subgraph2), _ = dgl.load_graphs('graphs.dgl')
    print(g)
    print(subgraph1)
    print(subgraph2)

    load_graph를 수행할 때 graph node의 label도 함께 지정할 수도 있다.