📊

## GNNを学ぶ_vol.5:ガウスカーネルでグラフの「重み」を計算する

に公開

こんにちは、七花ファームの古閑です。

前回のポストでは、グラフニューラルネットワーク(GNN)のモデル学習に備え、データセットの「顔」を見るための可視化に取り組みました。今回は、そのステップをさらに進め、いよいよGNNモデルの中核をなす数式に踏み込んでいきたいと思います。

データの可視化から一転、抽象的な数式を前にすると身構えてしまいますが、これをコードに落とし込んでいく作業は、理論と実践を結びつける重要なステップです。今回は、モデルの「重み」を定義する数式を読み解き、Pythonで実装するプロセスを紹介します。


グラフの「重み」を計算する数式

今回取り組むのは、GNNの隣接行列を計算する以下の数式です。この行列は、各センサー局(ノード)間の関係性の強さを数値で表す、いわば「グラフの設計図」のようなものです。

W_{ij} =\{ exp(-\frac{d^2_{ij}}{\sigma^2}), i\neq j \ and \exp(-\frac{d^2_{ij}}{\sigma^2})\geq\epsilon \ 0, otherwise

一見すると複雑に見えますが、この数式は「センサー局間の物理的な距離が近いほど、関係性が強い」という直感を、数学的に表現しています。

  • W_{ij}: ノード i とノード j の間の重み(関係性の強さ)です。
  • d_{ij}: ノード i とノード j の間の物理的な距離です。
  • \sigma^2 (シグマ2乗): データのばらつきを表すハイパーパラメータです。
  • \epsilon (イプシロン): 関係性の強さの閾値です。

数式の前半exp(...)で距離が近いほど大きな値が計算され、後半の条件式で、その値が\epsilon以上の場合にのみ重みとして採用されます。これによって、無関係なノード間の弱い繋がりを排除し、重要な関係性だけを抽出できます。


Pythonでの実装

この数式をPythonで実装するコードは、以下のようになります。

def compute_adj(distances, sigma2=0.1, epsilon=0.5):
    d = distances.to_numpy() / 10000.
    d2 = d * d
    # 数式をPythonの演算で表現
    adj = np.exp(-d2 / sigma2)
    adj[adj < epsilon] = 0.
    return adj

この関数は、距離行列と2つのハイパーパラメータを受け取り、最終的な隣接行列を返します。数式をそのままコードに落とし込んでいることがわかりますね。

コードの出力結果

隣接行列を計算し、結果を1行で出力してみましょう。

adj[0]

上記コードを実行した出力は以下のとおりです。

array([0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.61266012, 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.58926163, 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.79274914,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        ])

学びと新たな発見 ✨

今回、数式を読み解き、実際に手を動かしてコードに落とし込む作業を通じて、いくつかの発見がありました。

  1. LaTeXで数式を書くことの価値: 数式を単に読むだけでなく、自分でLaTeXを使ってタイプしてみることで、各記号や構造の意味がより深く理解できるようになった気がします。
  • 備忘録
LaTexについて説明
W_{ij} =
\begin{cases}
    \exp\left(-\frac{d^2_{ij}}{\sigma^2}\right), & i \neq j \text{ and } \exp\left(-\frac{d^2_{ij}}{\sigma^2}\right) \geq \epsilon \\
    0, & \text{otherwise}
\end{cases}

数式の構成要素
W_{ij}: これは、行列のi行j列目の要素を意味します。{}で囲むことで、添字(インデックス)として表示されます。

=: 等号です。

\begin{cases} ... \end{cases}: これは、場合分けされた数式を表現するための環境です。複数の条件に応じて数式の値が変わる場合に使用します。

\exp(...): これは、自然対数の底eのべき乗、つまりe^{(\dots)}を意味します。

\left(...): 括弧の大きさを自動的に調整します。\left(-\frac{d^2_{ij}}{\sigma^2}\right)とすることで、分数全体を囲む適切なサイズの括弧が生成されます。

\frac{...}{...}: 分数を表現します。\frac{d^2_{ij}}{\sigma^2}は\frac{d^2_{ij}}{\sigma^2}と表示されます。

\neq: 「等しくない」ことを示す記号(

=)です。

\text{...}: LaTeXの数式モード内で、通常のテキスト(この場合は"and"や"otherwise")を表示するために使用します。

\geq: 「大なりイコール」の記号(≥)です。

\: 改行を表します。cases環境では、各条件式を区切るために使います。

  1. 数式とコードの繋がり: このポストを書く前に線形代数の勉強を始めることを考えていましたが、図らずも数式の計算をPythonで行うことができ、理論と実践が直結していることを実感しました。これは非常に嬉しい経験でした。
  2. 対話型AIの活用: 数式でつまずいた際、GeminiにLaTeXで質問すると、とても分かりやすく解説してくれたことも大きな助けになりました。

最近は仕事でなかなか時間が取れませんが、焦らず、一つ一つのステップを丁寧に踏んでいくことが重要だと感じています。


参考文献

Hands-On Graph Neural Networks Using Python
https://www.oreilly.com/library/view/hands-on-graph-neural/9781804617526/

Discussion