こんにちは。
現役エンジニアの”はやぶさ”@Cpp_Learningです。仕事でもプライベートでも機械学習で色々やってます。
今回はGraph Neural Network(以下 GNN)について勉強したので、備忘録も兼ねて本記事を書きます。
Contents
データ構造とグラフ
ここでいうグラフとは折れ線グラフや棒グラフではなく、下図のようなデータ構造のことをグラフと呼びます。
例えば、以下に示すネットワークや分子構造、3Dデータなどもグラフといえます。
- インターネット
- Web
- 交通網
- SNS(FacebookやTwitterなど)
- 分子構造(化合物など)
- 神経回路
- 3Dデータのポリゴン
データとデータが繋がったものという意味で、画像(隣り合うピクセル)やテキスト(文字列)もグラフとして表現できます。
グラフとは -ノードとエッジなどの用語について-
SNSや分子構造などの様々なデータが下図のようなグラフで表現できます。

グラフの用語について簡単に整理したものが以下です。
- ノード(node):データ
- エッジ(edge):ノード間の関係、相互作用
- 有向エッジ:向きがあるエッジ
- 無向エッジ:向きがないエッジ
- 有向グラフ:有向エッジがあるグラフ
- 無向グラフ:有向エッジがないグラフ
- ループ(loop):エッジが1つのノードにしか繋がっていないもの
- 孤立ノード(isolation):ノードが孤立しているもの
- 属性:各ノードや各エッジがもつパラメータ(≒特徴量)
- ノードの位置情報:あり/なし
- ラベル:あり/なし ※
- 重み:あり/なし
※上図だと色でラベルを表現
例えば、SNSの場合は以下の通りです。
- ノード(node):人(アカウント名)
- エッジ(edge):フォロー関係(相互フォローなど)
- ノード属性:アカウント情報(生年月日など)
問題設定 -グラフと深層学習(GNN)-
グラフと深層学習(GNN)の活用により、解決できる問題(タスク)の例を紹介します。
グラフ分類
化合物の場合、グラフから毒性の有無などを分類することができます。

深層学習によるグラフ分類ができれば、未知の化合物に対し、毒性の有無などを予測できるようになります。
ノード分類
赤チーム・青チームで対戦するゲームの各キャラクタ相関図(グラフ)が下図だとします(所属が不明なノードはグレーで表現)。

同じチーム内でも交友の有無があり、敵同士でもライバル関係などのエッジが存在します。
各ノードとエッジの情報から、グレーの所属(ラベル)を分類できれば、分類結果を考慮した戦略を検討できます(情報戦)。
このような各ノードのラベルを予測するノード分類にも深層学習が活用できます。
リンク予測
Amazonなどの購入履歴をグラフで表現したものが下図だとします。

- カバン
を購入する人は、革靴
も一緒に購入する
- 革靴
のページをn日間でm回以上閲覧している人は購入する
など
もし上記の関係性を予測できるなら、潜在的な顧客の発見に繋がります。このような関係性の予測(リンク予測)にも深層学習を活用できます。
本記事では分類タスクのみを紹介しましたが、回帰タスクもあります。
Graph Neural Network(GNN)とは
グラフの概要や深層学習(GNN)で解決できる問題(タスク)について説明したので、今度はGNN(主にGCN)のアルゴリズムについて説明します。
GNN・GCNについて
よくわかる Gated Graph Neural Network (GGNN)の記事で紹介している以下の動画がとても分かりやすいです。
この動画でイメージを掴んだら、次はGraph Convolutional Network(GCN)について学びます。以下の2つスライドがオススメです。
最後に数式の意味を深く学びたい人のために、以下の記事をオススメしておきます。
Graph Neural Network(GNN)実践
各資料でGNNのアルゴリズムを勉強したので、次は実践します!
Graph Neural Network(GNN)ライブラリ
どのGNNライブラリを使うか迷ったので、Twitterのアンケート機能を使ってみました。
GNNライブラリってどれが主流なの?
使ってるライブラリ教えて下さい。— はやぶさ (@Cpp_Learning) April 3, 2020
アンケートにご回答いただき、ありがとうございました。ユーザーの多い PyTorch Geometric を使ってGNNを実践します。
インストール
メインで使用するライブラリは以下の通りです。
- PyTorch Geometric 1.4.3
- PyTorch 1.4.0
- CUDA 10.1
※Google Colaboratoryで動作確認しました(2020/04/18)
まずはGoogle Colaboratoryにプリインストールしてあるライブラリのバージョンを確認します。
PyTorch == 1.4.0
CUDA available True
CUDA == 10.1
CUDAについては以下のコマンドでも確認できます。
nvcc: NVIDIA (R) Cuda compiler driver Copyright (c) 2005-2018 NVIDIA Corporation Built on Sat_Aug_25_21:08:01_CDT_2018 Cuda compilation tools, release 10.0, V10.0.130
※CUDAのバージョンに注意!
PyTorch Geometric|公式のインストール手順だと失敗するので、解決策を参考に以下のコマンドでインストールします。
以上でPyTorch Geometricを使う環境が準備できました。以降からソースコードを書いてきます。
PyTorch Geometricの基本的な使い方 -グラフ作成-
まずは下図の有向グラフ(ノードに属性とラベルあり)を作成してみます。

PyTorch Geometricを使えば、以下のコードで簡単にグラフを作成できます。
edge_indexでエッジを定義します。今回の場合「0⇒1, 1⇒2, 2⇒1」とメッセージを伝播させたいので、コードの4~9行目で設定しています。
各ノードの属性(特徴量)とラベルはtorch.tensorで定義します(コード 15行目、21行目)。
最後にDataモジュールでグラフを作成します(コード 23行目)。
慣れてくれば、以下のような短いコードでグラフを作成できます。
今回は各ノードの特徴量とラベルを定義してますが、edge_indexだけでもグラフを生成できます
グラフ情報の確認
作成したグラフを以下の自作関数で確認します。
グラフ構造: Data(edge_index=[2, 3], x=[3, 2], y=[3, 1])
グラフのキー: [‘x’, ‘edge_index’, ‘y’]
ノード数: 3
エッジ数: 3
ノードの特徴量数: 2
孤立したノードの有無: False
自己ループの有無: False
====== ノードの特徴量:x ======
tensor([[1., 2.], [3., 4.], [5., 6.]])
====== ノードのクラス:y ======
tensor([[1.], [0.], [1.]])
======== エッジ形状 ========
tensor([[0, 1, 2], [1, 2, 1]])
ちゃんと下図のグラフを作成できてますね。

Graph Convolutional Network(GCN)実践
PyTorch Geometricによるグラフの扱い方が分かったので、今度はGCNによるノード分類を実践します。
Import
最初はimportから
データセットのダウンロード
自作グラフのデータセットを作成しても良いのですが、今回はGNN界隈のMNISTに相当する空手クラブのデータセットを使います。
グラフ数: 1
クラス数: 2
グラフ構造: Data(edge_index=[2, 156], x=[34, 34], y=[34])
グラフのキー: [‘x’, ‘edge_index’, ‘y’]
ノード数: 34
エッジ数: 156
ノードの特徴量数: 34
孤立したノードの有無: False
自己ループの有無: False
====== ノードの特徴量:x ======
tensor([[1., 0., 0., …, 0., 0., 0.],
[0., 1., 0., …, 0., 0., 0.],
[0., 0., 1., …, 0., 0., 0.],
…,
[0., 0., 0., …, 1., 0., 0.],
[0., 0., 0., …, 0., 1., 0.],
[0., 0., 0., …, 0., 0., 1.]])
==== ノードのクラス:y ====
tensor([0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1])
========= エッジ形状 =========
tensor([[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8, 8, 9, 9, 10, 10, 10, 11, 12, 12, 13, 13, 13, 13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 18, 18, 19, 19, 19, 20, 20, 21, 21, 22, 22, 23, 23, 23, 23, 23, 24, 24, 24, 25, 25, 25, 26, 26, 27, 27, 27, 27, 28, 28, 28, 29, 29, 29, 29, 30, 30, 30, 30, 31, 31, 31, 31, 31, 31, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33],
[ 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 17, 19, 21, 31, 0, 2, 3, 7, 13, 17, 19, 21, 30, 0, 1, 3, 7, 8, 9, 13, 27, 28, 32, 0, 1, 2, 7, 12, 13, 0, 6, 10, 0, 6, 10, 16, 0, 4, 5, 16, 0, 1, 2, 3, 0, 2, 30, 32, 33, 2, 33, 0, 4, 5, 0, 0, 3, 0, 1, 2, 3, 33, 32, 33, 32, 33, 5, 6, 0, 1, 32, 33, 0, 1, 33, 32, 33, 0, 1, 32, 33, 25, 27, 29, 32, 33, 25, 27, 31, 23, 24, 31, 29, 33, 2, 23, 24, 33, 2, 31, 33, 23, 26, 32, 33, 1, 8, 32, 33, 0, 24, 25, 28, 32, 33, 2, 8, 14, 15, 18, 20, 22, 23, 29, 30, 31, 33, 8, 9, 13, 14, 15, 18, 19, 20, 22, 23, 26, 27, 28, 29, 30, 31, 32]])
空手クラブの情報を簡単に整理すると以下の通りです。
- 会員数(ノード数):34
- 特徴量x:ノード番号(会員ナンバー)をone-hotベクトルで定義
- ラベルy:”0”または”1″の2クラス(2グループ)
- 交友関係(エッジ数):156
- グラフの種類:無向グラフ※
※各ノード間を有効エッジで相互に接続(0⇒1, 1⇒0など)することで無向グラフとして表現することがあります
グラフ構造の可視化
PyTorch Geometricにはグラフ構造を可視化する関数がないため、NetworkXを使って可視化します(ここのコードやここのコードを参考にしました)。
可視化によりリーダー格のノード”0”とノード”33”を中心とした2グループの存在が明確になりました。
またノード”8”やノード”9”は両グループと交友関係があることも分かります(伏線)。
この空手クラブのデータセットを使ってノード分類を実践します。
モデル設計
PyTorch Geometricは GCNConv などの メジャーなLayers をサポートしているので、ピュアPyTorchと同じ感覚でモデルを設計できます。
ピュアPyTorchと同じコードが使えます
各種設定
以下のコードで各種の設定を行います。
学習
入力データ(入力するグラフ)、オプティマイザ設定、学習ループのコードは以下の通りです。
Epoch 0 | Loss: 0.6980
Epoch 1 | Loss: 0.6863
Epoch 2 | Loss: 0.6748
…
Epoch 97 | Loss: 0.0563
Epoch 98 | Loss: 0.0559
Epoch 99 | Loss: 0.0555
推論
以下のコードで推論します。
結果: tensor([0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1])
真値: tensor([0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1])
ノード”8”を誤分類していることが分かります。
ノード”8”は両グループのリーダーと交流があるため、ラベル(所属)の判断が難しかったと考えられます
テストデータ作成
学習に未使用のテストデータでも推論してみます。空手クラブのデータセットを活用し、交友関係(リンク)を一部変更したグラフを作成します。
変更前後のグラフは以下の通りです。


- ノード”2”とノード”33”のリンクを接続
- ノード”8”とノード”0”のリンクを切る
- ノード”9”とノード”33”のリンクを切る
- ノード”9”とノード”0”のリンクを接続
など
要するにリーダとの交友関係の有無(リンク変更)により、モデルのラベル予測(ノード分類結果)がどう変わるかを検証するために、グラフに色々と手を加えました。
- モデルがリーダとの交友関係を考慮して、ノード分類をしているか検証
- GNNとモデルの説明性に興味津々
リンク変更によるノード分類結果について
リンク変更後のグラフに対し、学習済みモデルによるノード分類(ラベル予測)を行います。
======== リンク変更前のラベル ========
tensor([0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1])
==== リンク変更後のラベル予測結果 ====
tensor([0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1])
ノード分類結果を反映したグラフが下図です。


つまり今回のモデルは、以下の予測傾向があると考えられます。
- 交流のあるリーダと同じグループだと予測
- 両リーダーと交流がある場合、ノード”0”と同じグループと予測
以上 簡単な検証ですが、リンク変更による検証からGNNのモデル説明性を深堀りできそうです。
- GNN(GCN)により、ノード分類(ラベル予測)ができる
- リンク変更によりモデルのノード分類結果が変化する
- 結果の変化からモデルの予測傾向を把握できそう(モデル説明性)
- 分類結果に大きく影響を与える重要なノードを把握できそう(潜在的なリーダの予測に活用できそう)
まとめ
Graph Neural Network(GNN)の概要からPyTorch Geometricを使ったノード分類の実践などについて説明しました。
と考えている人の参考になれば嬉しいです。
また私自身もGNN入門したばかりなので、本記事の内容について間違っている箇所があれば、教えて頂けると嬉しいです。