PyTorch : DGL モデル : グラフ畳み込みネットワーク

PyTorch : DGL Tutorials : モデル : グラフ畳み込みネットワーク (翻訳/解説)

翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 06/06/2019

* 本ページは、DGL のドキュメント “Graph Convolutional Network” を翻訳した上で適宜、補足説明したものです:

* サンプルコードの動作確認はしておりますが、必要な場合には適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。

 

DGL Tutorials : モデル : グラフ畳み込みネットワーク

これはグラフ畳み込みネットワークを実装するために DGL を使用する易しいイントロダクションです (Kipf & Welling et al., Semi-Supervised Classification with Graph Convolutional Networks)。DGL グラフ上の 先のチュートリアル 上に構築して DGL がグラフを深層ニューラルネットワークとどのように結合して構造的表現を学習するかを示します。

 

モデル概要

メッセージパッシングの視点から GCN

メッセージパッシングの視点からグラフ畳み込みニューラルネットワークの層を説明します ; その数学については (このチュートリアルの) 最後で見つかります。それは各ノード $u$ に対して、次のステップに要約されます :

  1. 中間的な表現 $\hat{h}_u$ を生成するために近傍の表現 $h_{v}$ を収集する
  2. 収集された表現 $\hat{h}_{u}$ を線形射影とそれに続く非線形で変換します : $h_{u} = f(W_{u} \hat{h}_u)$

ステップ 1 を DGL メッセージパッシングで、ステップ 2 を apply_nodes メソッドで実装します、そのノード UDF は PyTorch nn.Module です。

 

DGL による GCN 実装

最初に通常のように message と reduce 関数を定義します。ノード $u$ 上の収集は近傍の表現 $h_v$ に渡る総計を伴うだけですので、単に組み込み関数を使用できます :

import dgl
import dgl.function as fn
import torch as th
import torch.nn as nn
import torch.nn.functional as F
from dgl import DGLGraph

gcn_msg = fn.copy_src(src='h', out='m')
gcn_reduce = fn.sum(msg='m', out='h')

それから apply_nodes のためのノード UDF を定義します、これは完全結合層です :

class NodeApplyModule(nn.Module):
    def __init__(self, in_feats, out_feats, activation):
        super(NodeApplyModule, self).__init__()
        self.linear = nn.Linear(in_feats, out_feats)
        self.activation = activation

    def forward(self, node):
        h = self.linear(node.data['h'])
        h = self.activation(h)
        return {'h' : h}

それから GCN モジュールを定義することに進みます。GCN 層は本質的には総てのノード上でメッセージパッシングを遂行してそして NodeApplyModule を適用します。単純化のためにペーパーでは dropout を省略したことに注意してください。

class GCN(nn.Module):
    def __init__(self, in_feats, out_feats, activation):
        super(GCN, self).__init__()
        self.apply_mod = NodeApplyModule(in_feats, out_feats, activation)

    def forward(self, g, feature):
        g.ndata['h'] = feature
        g.update_all(gcn_msg, gcn_reduce)
        g.apply_nodes(func=self.apply_mod)
        return g.ndata.pop('h')

forward 関数は本質的には PyTorch の任意の他の一般に見られる NN モデルと同じです。任意の nn.Module のように GCN を初期化できます。例えば、2 つの GCN 層から成る単純なニューラルネットワークを定義しましょう。cora データセットのための分類器を訓練していると仮定します (入力特徴サイズは 1433 でクラス数は 7 です)。

class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.gcn1 = GCN(1433, 16, F.relu)
        self.gcn2 = GCN(16, 7, F.relu)

    def forward(self, g, features):
        x = self.gcn1(g, features)
        x = self.gcn2(g, x)
        return x
net = Net()
print(net)
Net(
  (gcn1): GCN(
    (apply_mod): NodeApplyModule(
      (linear): Linear(in_features=1433, out_features=16, bias=True)
    )
  )
  (gcn2): GCN(
    (apply_mod): NodeApplyModule(
      (linear): Linear(in_features=16, out_features=7, bias=True)
    )
  )
)

 
DGL 組み込みデータモジュールを使用して cora データセットをロードします。

from dgl.data import citation_graph as citegrh
def load_cora_data():
    data = citegrh.load_cora()
    features = th.FloatTensor(data.features)
    labels = th.LongTensor(data.labels)
    mask = th.ByteTensor(data.train_mask)
    g = data.graph
    # add self loop
    g.remove_edges_from(g.selfloop_edges())
    g = DGLGraph(g)
    g.add_edges(g.nodes(), g.nodes())

それから次のようにネットワークを訓練します :

import time
import numpy as np
g, features, labels, mask = load_cora_data()
optimizer = th.optim.Adam(net.parameters(), lr=1e-3)
dur = []
for epoch in range(30):
    if epoch >=3:
        t0 = time.time()

    logits = net(g, features)
    logp = F.log_softmax(logits, 1)
    loss = F.nll_loss(logp[mask], labels[mask])

    optimizer.zero_grad()
    loss.backward()
    optimizer.step()

    if epoch >=3:
        dur.append(time.time() - t0)

    print("Epoch {:05d} | Loss {:.4f} | Time(s) {:.4f}".format(
            epoch, loss.item(), np.mean(dur)))
Epoch 00000 | Loss 1.9204 | Time(s) nan
Epoch 00001 | Loss 1.8998 | Time(s) nan
Epoch 00002 | Loss 1.8820 | Time(s) nan
Epoch 00003 | Loss 1.8655 | Time(s) 0.3558
Epoch 00004 | Loss 1.8498 | Time(s) 0.3564
Epoch 00005 | Loss 1.8339 | Time(s) 0.3563
Epoch 00006 | Loss 1.8175 | Time(s) 0.3563
Epoch 00007 | Loss 1.8002 | Time(s) 0.3569
Epoch 00008 | Loss 1.7826 | Time(s) 0.3567
Epoch 00009 | Loss 1.7650 | Time(s) 0.3568
Epoch 00010 | Loss 1.7470 | Time(s) 0.3567
Epoch 00011 | Loss 1.7289 | Time(s) 0.3567
Epoch 00012 | Loss 1.7109 | Time(s) 0.3565
Epoch 00013 | Loss 1.6934 | Time(s) 0.3567
Epoch 00014 | Loss 1.6763 | Time(s) 0.3565
Epoch 00015 | Loss 1.6594 | Time(s) 0.3571
Epoch 00016 | Loss 1.6428 | Time(s) 0.3572
Epoch 00017 | Loss 1.6265 | Time(s) 0.3572
Epoch 00018 | Loss 1.6108 | Time(s) 0.3572
Epoch 00019 | Loss 1.5957 | Time(s) 0.3572
Epoch 00020 | Loss 1.5809 | Time(s) 0.3571
Epoch 00021 | Loss 1.5666 | Time(s) 0.3570
Epoch 00022 | Loss 1.5526 | Time(s) 0.3570
Epoch 00023 | Loss 1.5389 | Time(s) 0.3570
Epoch 00024 | Loss 1.5257 | Time(s) 0.3570
Epoch 00025 | Loss 1.5130 | Time(s) 0.3570
Epoch 00026 | Loss 1.5007 | Time(s) 0.3569
Epoch 00027 | Loss 1.4887 | Time(s) 0.3571
Epoch 00028 | Loss 1.4771 | Time(s) 0.3571
Epoch 00029 | Loss 1.4659 | Time(s) 0.3570

 

一つの数式の GCN

数学的には、GCN モデルはこの数式に従います :

\[
H^{(l+1)} = \sigma(\tilde{D}^{-\frac{1}{2}}\tilde{A}\tilde{D}^{-\frac{1}{2}}H^{(l)}W^{(l)})
\]

ここで、$H^{(l)}$ はネットワークの $l^{th}$ 層を表し、$\sigma$ は非線形で、そして $W$ はこの層のための重み行列です。$D$ と $A$ は一般に良く見られるように、それぞれ次数行列と隣接行列を表します。The ~ は繰り込み (= renormalization) トリックで、そこではグラフの各ノードに自己接続を追加して、対応する次数と隣接行列を構築します。入力 $H^{(0)}$ の shape は $N \times D$ で、そこでは $N$ はノード数で $D$ は入力特徴数です。shape $N \times F$ を持つノードレベル表現出力を生成するためにそのような複数層を連鎖できます、ここで $F$ は出力ノード特徴ベクトルの次元です。

等式は (Kipf の pygcn コードのような) スパース行列乗算カーネルを使用して効率的に実装できます。上の DGL 実装は実際に組み込み関数の使用により既にこのトリックを使用しています。内部を理解するためには、ページランク のチュートリアルを読んでください。

 

以上