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$ に対して、次のステップに要約されます :
- 中間的な表現 $\hat{h}_u$ を生成するために近傍の表現 $h_{v}$ を収集する
- 収集された表現 $\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 実装は実際に組み込み関数の使用により既にこのトリックを使用しています。内部を理解するためには、ページランク のチュートリアルを読んでください。
以上