論文サイトを眺めていると、創薬・SNS解析・推薦システム・分子設計と、まったく違う分野の論文で同じ Graph Attention Network (GAT) という単語が並んでいることに気づきます。化学者が分子のHOMO/LUMOエネルギーを予測し、SNS研究者が偽アカウントを検出し、ECサイトが「この商品を買った人はこれも買っています」を計算する。どれも入力は「ノードとエッジからなるグラフ」で、答えを出すのに使われているのは GAT、というケースが急増しています。
なぜGATがここまで広く使われているのでしょうか。それは、グラフ畳み込みネットワーク(GCN)が「すべての近傍を平等に扱う」のに対し、GATは「どの近傍が大事か」を Attention 機構で学習するからです。SNSでスパム判定をするとき、フォロー先100人を等しく扱うのと、「過去にスパムと判定された人物との繋がり」を重く扱うのとでは、判定精度が大きく変わります。GATはこの「重み付け」をデータから自動で学習してくれます。
本記事では、2018年にVeličković らが発表した GAT 原論文 と、その改良版である GATv2 (Brody et al. 2022) を踏まえて、以下を網羅的に解説します。
- GCN の復習:メッセージ伝搬の枠組みで GAT の出発点を捉える
- Attention 係数 $e_{ij}$ と正規化 $\alpha_{ij}$ の数式定義と直感
- Multi-head Attention でなぜ学習が安定するのか
- GAT の限界と、それを解消する GATv2 の動的 Attention
- PyTorch Geometric を使った Cora ノード分類の実装
応用先としては、
- 創薬・分子設計:分子グラフの物性予測(QM9、MoleculeNet など)
- 推薦システム:ユーザー×アイテムの二部グラフでの嗜好予測
- 不正検知:金融取引ネットワークの異常ノード検出
- タンパク質間相互作用(PPI)予測:生命科学のグラフ解析
など、「データがグラフとして自然に表現できる」場面の至るところで GAT が登場します。
本記事の内容
- GCN の Message Passing の復習と、その限界
- GAT の Attention 係数 $e_{ij}$、$\alpha_{ij}$ の定義と導出
- Multi-head Attention の仕組みと、なぜ平均/連結が必要か
- GAT → GATv2 の改良点(静的 vs 動的 Attention)
- 計算量・スケーラビリティと近似手法
- PyTorch Geometric による Cora ノード分類の完全実装
- 応用と限界、最新の派生手法

上の図に出てくる「同じ重みで集約する GCN」と「重要度で集約する GAT」の対比が、本記事を貫く中心テーマです。それでは、まず前提知識から見ていきましょう。
前提知識
この記事を読む前に、以下の記事を読んでおくと理解が深まります。
- 【深層学習】GCN(グラフ畳み込みネットワーク)をわかりやすく解説する
- Attention 機構の基礎(self-attention と Transformer)
- 深層学習の基礎(順伝播・活性化関数・誤差逆伝播)
GAT は名前のとおり「Graph」+「Attention」+「Network」の合成で、3 つすべての基礎が前提になります。とくに GCN の Message Passing は GAT の式と一対一で対応するため、本節でも軽く振り返ります。
直感ファースト:なぜ「重要度の学習」が必要なのか
ソーシャルネットワークで「あなたが新しい音楽ジャンルを好きになるか」を予測する場面を想像してください。素朴なやり方は、フォロー先全員の好みを平均することです。これは GCN がやっていることに近い。しかしフォロー先には、
- 趣味が近い親友(あなたの嗜好を強く方向づける)
- 仕事関係の知人(音楽の好みとは無関係)
- 昔の同級生(音楽の好みが偶然似ているかもしれない)
など、影響度の異なる人がいます。当然、親友の好みは重く、仕事関係は軽く、同級生は中程度に取り扱うべきです。この「誰の声を、どれだけ聞くか」をデータから自動学習するのが Attention 機構の核心です。
GAT はこの「相手によって聞く強さを変える」というアイデアを、グラフ上のメッセージ伝搬に組み込んだモデルです。具体的には、中心ノード $i$ と近傍ノード $j$ の特徴量から Attention 係数 $\alpha_{ij}$ をニューラルネットで計算し、その重み付き和でノード $i$ の新しい表現を作ります。

上の図は、中心ノード $i=1$ と、それに直結する 1-hop 近傍 $\mathcal{N}(1)=\{2,3,4,5\}$、そして各ノードの特徴ベクトル $\boldsymbol{h}_i \in \mathbb{R}^F$ のイメージです。GAT も GCN も、この近傍 $\mathcal{N}(i)$ の情報をどう集約するかが核心です。違うのは、その集約の重みです。
原論文では、近傍を Attention 係数 $\alpha$ で重み付けする「太線・細線」イメージが繰り返し登場します。

上図はノード 1 と近傍 2, 3, 4 のうち、ノード 3 が最も重要で、次にノード 4、そしてノード 2 という順に重要度を可視化したイメージです。GAT はこのようなノードごとの「重み」をデータから自動で学習します。
次のセクションで GCN の Message Passing を簡単に復習し、その「平等扱い」が何を意味するかを式で確認します。
GCN の Message Passing を復習する
Kipf & Welling の GCN
GCN(Graph Convolutional Network)は、Kipf & Welling (2017) が提案した、グラフ上のスペクトル畳み込みを単純化した手法です。各層の更新式は
$$ \boldsymbol{H}^{(l+1)} = \sigma\!\left(\hat{\boldsymbol{D}}^{-1/2}\hat{\boldsymbol{A}}\hat{\boldsymbol{D}}^{-1/2} \boldsymbol{H}^{(l)} \boldsymbol{W}^{(l)}\right) $$
と書けます。ここで $\hat{\boldsymbol{A}} = \boldsymbol{A} + \boldsymbol{I}$ は自己ループを加えた隣接行列、$\hat{\boldsymbol{D}}$ はその度数行列、$\boldsymbol{W}^{(l)}$ は線形変換、$\sigma$ は活性化関数(ReLU など)です。行列でなくノード単位(node-wise)に書き直すと、次のメッセージ伝搬の式が得られます。
$$ \begin{equation} \boldsymbol{h}_i^{(l+1)} = \sigma\!\left(\sum_{j \in \mathcal{N}(i)\cup\{i\}} \frac{1}{\sqrt{d_i\, d_j}}\, \boldsymbol{W}^{(l)} \boldsymbol{h}_j^{(l)}\right) \end{equation} $$
ここで $d_i = |\mathcal{N}(i)| + 1$ は自己ループ込みの度数です。重要な観察は、近傍ノード $j$ の寄与に掛かる係数 $\frac{1}{\sqrt{d_i d_j}}$ が、$i$ と $j$ の度数だけで決まる固定値であることです。すなわち、
- 近傍ノードの特徴量 $\boldsymbol{h}_j$ や意味的な近さは重みに反映されない
- グラフの構造(誰が誰と繋がっているか)だけで重みが決まる
これが GCN の限界です。3 つの友人が同じ「次数 5」を持っていれば、GCN は彼ら全員を同じ重みで扱います。趣味が合うかどうかにはお構いなしです。
GCN の長所と短所
公平を期すと、GCN にもメリットはあります。
- シンプルで高速:重みが構造で決まるため学習パラメータが少ない
- 半教師あり学習に強い:ラベルが少ないノード分類で良好な性能
- 理論的解釈:スペクトルグラフ理論のChebyshev多項式近似として導出できる
しかし、
- ノードごとに近傍の重要度を変えられない
- 新しいグラフ構造(帰納的学習)への汎化が弱い:訓練時に見ていないノードに対しては、$\hat{\boldsymbol{D}}$ を作り直す必要がある
- 異質な近傍(heterophilic graphs:同じクラスがあまり繋がっていないグラフ)に弱い
GAT はまさにこれらの弱点を埋めるべく設計されたものです。Attention 機構を導入することで、「近傍ノードの特徴量を見て、重みを決める」ようにしたのです。
ここまでで GCN の式と限界が把握できました。続いて、GAT がこの式をどう書き換えるかを見ていきます。
GAT の数式定義 — Attention 係数の導出
Attention 係数 $e_{ij}$ の定義
GAT では、ノード $i$ と近傍ノード $j$ のペアに対して Attention スコア $e_{ij}$ を定義します。これが「ノード $i$ にとってノード $j$ がどれだけ重要か」を表す未正規化スコアです。
$$ \begin{equation} e_{ij} = a(\boldsymbol{W}\boldsymbol{h}_i,\, \boldsymbol{W}\boldsymbol{h}_j) \end{equation} $$
ここで $\boldsymbol{W} \in \mathbb{R}^{F’\times F}$ は GCN にも登場した共有線形変換で、入力次元 $F$ を新しい次元 $F’$ に写像します。すべてのノードに対して同じ $\boldsymbol{W}$ を使うのがポイントです。これにより、グラフ全体で一貫した特徴表現が得られます。
$a: \mathbb{R}^{F’} \times \mathbb{R}^{F’} \to \mathbb{R}$ は共有 Attention 関数で、2 つのベクトルからスカラーを返します。$a$ の具体的な形は設計者の自由ですが、GAT の原論文では次の単層 NN を採用しています。
$$ \begin{equation} e_{ij} = \mathrm{LeakyReLU}\!\big(\boldsymbol{a}^T [\boldsymbol{W}\boldsymbol{h}_i \,\|\, \boldsymbol{W}\boldsymbol{h}_j]\big) \end{equation} $$
ここで
- $\boldsymbol{a} \in \mathbb{R}^{2F’}$:学習可能なパラメータベクトル
- $[\cdot \,\|\, \cdot]$:2 つのベクトルの連結(concatenation)
- $\mathrm{LeakyReLU}$:負の値を完全に消さない活性化関数($\alpha = 0.2$ がデフォルト)
なぜ LeakyReLU か。通常の ReLU だと負のスコアがすべて 0 に潰れてしまい、softmax 正規化後にも違いが残らない可能性があります。LeakyReLU は負の値も少し残すので、近傍間の相対的な順位関係を保ちやすいのです。

上の図は (3) 式の計算フローを示したものです。左から、
- ノード $i, j$ の入力特徴量 $\boldsymbol{h}_i, \boldsymbol{h}_j \in \mathbb{R}^F$
- 共有重み $\boldsymbol{W}$ で線形変換して $\boldsymbol{W}\boldsymbol{h}_i, \boldsymbol{W}\boldsymbol{h}_j \in \mathbb{R}^{F’}$
- 2 つを連結して $2F’$ 次元のベクトルに
- 単層 NN($\boldsymbol{a}^T$ + LeakyReLU)でスカラー $e_{ij}$ を得る
という流れです。連結してから単層 NN を通すという設計は、Transformer の self-attention で使われる「scaled dot-product」とは異なります。Transformer 風の dot-product でも GAT は構築できますが、原論文では性能と安定性の理由でこの additive attention が採用されました。
原論文中の self-attention の絵も並べておきます。各ノードに対し $\alpha$ が自分自身も含めて計算されることに注目してください。

![原論文の Attention 単層 NN 図 — $a^T[Wh_i || Wh_j]$ の構造](https://disassemble-channel.com/wp-content/uploads/2022/11/gat_attention.png)
3 枚目の図では「下の丸が全て同一のベクトル $\boldsymbol{W}\boldsymbol{h}_i$ や $\boldsymbol{W}\boldsymbol{h}_j$ で、これらを単層 NN に入れて正規化 Attention 係数を得る」というシンプルな構造であることが見て取れます。一見複雑そうですが、要素を分解すれば「線形変換 → 連結 → 単層 NN」の三段だけです。
Softmax 正規化で $\alpha_{ij}$ を得る
スコア $e_{ij}$ はそのままでは「重み」として使いにくいので、近傍について softmax で正規化します。
$$ \begin{equation} \alpha_{ij} = \mathrm{softmax}_j(e_{ij}) = \frac{\exp(e_{ij})}{\sum_{k \in \mathcal{N}(i)\cup\{i\}} \exp(e_{ik})} \end{equation} $$
これにより、
- $\alpha_{ij} \geq 0$(非負性)
- $\sum_{j \in \mathcal{N}(i)\cup\{i\}} \alpha_{ij} = 1$(規格化)
という確率分布の性質が得られます。$\alpha_{ij}$ は「ノード $i$ が情報を集約するとき、近傍 $j$ にどれだけの重みを置くか」を表す確率と解釈できます。注意点として、$j=i$ も含めて softmax を取るのが原論文の慣例です。自己ループを含めることで「自分自身の現在の特徴量をどれだけ残すか」も学習できます。

上の図は、生のスコア $e_{1j}$ から正規化後の $\alpha_{1j}$ への変換を可視化しています。(a) の生スコアは符号も大きさもバラバラですが、(b) では非負・総和 1 となり、(c) のパイチャートで見るように「どの近傍に何 % の重みを置くか」が明確になります。
GAT 1 層の更新式
これらの $\alpha_{ij}$ を使って、ノード $i$ の次層の特徴量は次のように更新されます。
$$ \begin{equation} \boldsymbol{h}_i^{(l+1)} = \sigma\!\left( \sum_{j \in \mathcal{N}(i) \cup \{i\}} \alpha_{ij}\, \boldsymbol{W} \boldsymbol{h}_j^{(l)} \right) \end{equation} $$
GCN の式 (1) と並べてみると違いは明確です。
| 近傍 $j$ への重み | |
|---|---|
| GCN | $\dfrac{1}{\sqrt{d_i d_j}}$(構造のみで決まる固定値) |
| GAT | $\alpha_{ij}$(特徴量から学習される動的な値) |
GAT では重みがデータに依存して動的に決まるため、「重要な近傍を強調する」「無関係な近傍を抑制する」が学習で可能になります。
数式の見落としがちなポイント — 静的 Attention
GAT の Attention は、よく見ると 「ノード $i$ にとっての近傍ランキング」が固定されているという性質を持っています。式 (3) を $e_{ij} = \mathrm{LeakyReLU}(\boldsymbol{a}_1^T \boldsymbol{W}\boldsymbol{h}_i + \boldsymbol{a}_2^T \boldsymbol{W}\boldsymbol{h}_j)$ と書き換えると($\boldsymbol{a}$ を前半と後半に分け、連結の代わりに加算で表現)、$i$ 固定での近傍の順位は $\boldsymbol{a}_2^T \boldsymbol{W}\boldsymbol{h}_j$ だけで決まり、$i$ には依存しません。
つまり、「全ノードが共通の グローバル順位で近傍を評価する」のと等価です。これは GAT の隠れた制約で、後述する GATv2 が解決するのもこの点です。
ここまでで GAT 1 層の数式と直感が揃いました。次は、安定した学習のために必須となる Multi-head Attention を見ていきましょう。
Multi-head Attention — 学習を安定させる工夫
なぜマルチヘッドが必要か
(5) 式の Attention は 1 つしかパラメータ $(\boldsymbol{W}, \boldsymbol{a})$ がありません。これだけだと、
- 学習が確率的に不安定になりやすい(初期値依存が強い)
- 「ノード間の関係性」を1 種類しか捉えられない
という弱点があります。後者は特に深刻です。たとえば SNS なら「趣味の近さ」「物理的距離の近さ」「年齢の近さ」など複数の観点があり、1 種類の Attention ではそのすべてを表現しきれません。
そこで Transformer から発想を借りて、$K$ 個の独立した Attention 機構を並列に走らせ、結果を統合するのが Multi-head Attention です。
連結による統合(中間層)
中間層では、各ヘッド $k=1,\dots,K$ の出力を連結します。
$$ \begin{equation} \boldsymbol{h}_i^{(l+1)} = \big\Vert_{k=1}^{K} \sigma\!\left( \sum_{j \in \mathcal{N}(i)\cup\{i\}} \alpha_{ij}^{k}\, \boldsymbol{W}^{k} \boldsymbol{h}_j^{(l)} \right) \end{equation} $$
各ヘッドは独立した $(\boldsymbol{W}^k, \boldsymbol{a}^k, \alpha_{ij}^k)$ を持ち、それぞれが異なる「観点」での集約を学習します。連結によって出力次元は $K \cdot F’$ に増えますが、複数の関係性を保持したまま次の層に渡せます。
平均による統合(最終層)
最終層(分類や回帰の前段)では、連結だと次元が膨らみすぎて分類器との接続が大変です。代わりに平均を取ります。
$$ \begin{equation} \boldsymbol{h}_i^{(L)} = \sigma\!\left( \frac{1}{K} \sum_{k=1}^{K} \sum_{j \in \mathcal{N}(i)\cup\{i\}} \alpha_{ij}^{k}\, \boldsymbol{W}^{k} \boldsymbol{h}_j^{(L-1)} \right) \end{equation} $$
平均にしておくと、出力次元はクラス数(または埋め込み次元)で固定でき、分類層に直結できます。

上の図は、$K=4$ のヘッドが入力 $\boldsymbol{h}_i^{(l)}$ から 4 つの独立した Attention 出力を作り、それを中間層なら連結、最終層なら平均で統合する流れを示しています。複数ヘッドにより、Attention の分散が抑えられ学習が安定するとともに、近傍の多面的な評価が可能になります。
原論文に登場する Multi-head Attention の概要図も併せて見ておくと理解が深まります。

上図では、ノード 1 とその近傍 2, 3, 4, 5, 6 について 3 つのヘッドが並列に Attention を計算し(色の違うエッジ)、最終的に集約された特徴量 $\boldsymbol{h}_1^{(l+1)}$ を出力する様子が描かれています。GAT 原論文では Attention 係数の安定化のために、こうした $K$ 個の Attention 機構の出力を連結(中間層)または平均化(最終層)して扱います。
原論文の Cora 実験では、中間層で $K=8$(出力 8 ヘッド連結)、最終層で $K=1$ という構成が使われています。タスクとデータセットによって $K$ の調整余地がありますが、$K \in \{4, 8\}$ が経験的に良いとされています。
Multi-head の式変形と等価性
(6) 式の連結を行列で書くと
$$ \boldsymbol{h}_i^{(l+1)} = \left[\sigma(\sum_j \alpha_{ij}^1 \boldsymbol{W}^1 \boldsymbol{h}_j),\ \sigma(\sum_j \alpha_{ij}^2 \boldsymbol{W}^2 \boldsymbol{h}_j),\ \dots,\ \sigma(\sum_j \alpha_{ij}^K \boldsymbol{W}^K \boldsymbol{h}_j)\right] \in \mathbb{R}^{K F’} $$
となります。これは、ヘッドごとの線形変換 $\boldsymbol{W}^k$ がブロック対角行列を構成し、Attention 重みがヘッドごとに切り替わる「ブロックスパース」な特徴抽出と解釈できます。Transformer の Multi-head Attention と数学的に同じ構造ですが、隣接情報による疎性を活かしている点がグラフ独自の点です。
ここまでで GAT 1 層の数学が完成しました。次は、これらを多層化したときに何が起きるか、そして GAT 全体のアーキテクチャを俯瞰します。
GAT の全体アーキテクチャ
多層 GAT による $k$-hop 集約
GAT 1 層は 1-hop の近傍を集約します。$L$ 層を重ねると、各ノードは $L$-hop 先の情報まで受け取れるようになります。
$$ \boldsymbol{h}^{(0)} \xrightarrow{\text{GAT層 1}} \boldsymbol{h}^{(1)} \xrightarrow{\text{GAT層 2}} \cdots \xrightarrow{\text{GAT層 L}} \boldsymbol{h}^{(L)} $$
最終層の出力 $\boldsymbol{h}_i^{(L)}$ は、ノード $i$ を中心とする $L$-hop 部分グラフの情報を Attention で重み付き集約した「ノード埋め込み」になります。これを下流タスクに使います。
- ノード分類:$\boldsymbol{h}_i^{(L)}$ に softmax をかけてクラス確率を出力
- リンク予測:2 ノードの埋め込みの内積 $\langle \boldsymbol{h}_i^{(L)}, \boldsymbol{h}_j^{(L)} \rangle$ でリンクの有無を予測
- グラフ分類:全ノードの埋め込みを
mean/sumで集約してグラフ全体の表現に

上の図は GAT 1 層の演算フローを横並びで示しています。「入力ノード集合 → 線形変換 → Attention → softmax → 加重集約 → 出力」という流れが各層で繰り返されます。
過平滑化と層の深さ
注意点として、GAT も他の GNN と同様に過平滑化(over-smoothing)の問題を抱えます。層を深くすると(5 層以上)、すべてのノードの埋め込みが似た値に収束してしまい、識別性能が落ちます。これは Message Passing が本質的に「ノードの平均化」であるため、繰り返すと差異が消えるからです。
実用上は、
- 2〜3 層が多くのベンチマークで最適
- 深い層が必要なら、残差接続(residual connection)や JK-Net などの技法を併用
- DropEdge や PairNorm で過平滑化を緩和
といった工夫があります。論文サイトに行くと、過平滑化対策専門の GNN 研究が大量にヒットします。
Attention 可視化で何が学習されたかを見る
GAT の魅力の一つは、学習後の $\alpha_{ij}$ を可視化できることです。Cora(論文引用ネットワーク)でノード分類を解いた後、各エッジを $\alpha_{ij}$ の大きさで太く描くと、「どの引用関係を重視して分類したか」が見えます。

上の図は Cora 風 3 クラスの埋め込み空間で、学習後の Attention 値をエッジの太さで示したものです。同一クラスのノード同士のエッジが太くなっていることが見て取れます。GAT は「同じクラスのノードを強く集約することで分類精度を上げる」よう学習されているのです。これは GCN にはない透明性で、医療や金融といった「説明責任が求められる分野」で特に重宝されます。
ここまでで GAT の全体像が見えました。続いて、GAT には実は隠れた弱点があることと、それを解消する GATv2 を見ていきます。
GATv2 — 動的 Attention への発展
GAT の隠れた弱点:静的 Attention
(3) 式 $e_{ij} = \mathrm{LeakyReLU}(\boldsymbol{a}^T[\boldsymbol{W}\boldsymbol{h}_i \,\|\, \boldsymbol{W}\boldsymbol{h}_j])$ を、連結を加算に分解して書き直します。$\boldsymbol{a} = [\boldsymbol{a}_1; \boldsymbol{a}_2]$ と分割すると、
$$ e_{ij} = \mathrm{LeakyReLU}(\boldsymbol{a}_1^T \boldsymbol{W}\boldsymbol{h}_i + \boldsymbol{a}_2^T \boldsymbol{W}\boldsymbol{h}_j) $$
となります。ここで、ノード $i$ を固定して近傍 $j$ で softmax を取ると、
$$ \alpha_{ij} = \frac{\exp(\mathrm{LeakyReLU}(\boldsymbol{a}_1^T \boldsymbol{W}\boldsymbol{h}_i + \boldsymbol{a}_2^T \boldsymbol{W}\boldsymbol{h}_j))}{\sum_k \exp(\mathrm{LeakyReLU}(\boldsymbol{a}_1^T \boldsymbol{W}\boldsymbol{h}_i + \boldsymbol{a}_2^T \boldsymbol{W}\boldsymbol{h}_k))} $$
となります。LeakyReLU の単調性により、近傍 $j$ の中での Attention 順位は実質的に $\boldsymbol{a}_2^T \boldsymbol{W}\boldsymbol{h}_j$ だけで決まり、$i$ には依存しません。
これが意味するのは、
GAT のAttention は「全ノード共通のグローバル近傍ランキング」を学習している
ということです。SNS の例で言えば、「友達 A は誰から見ても重要、友達 B は誰から見ても不要」と決まってしまう。本来は「私から見れば A が重要、彼から見れば B が重要」と相手によって変わってほしいのに、それができません。Brody et al. (2022) はこの性質を静的 Attention(static attention)と呼び、表現力の制約だと指摘しました。
GATv2 の改良:LeakyReLU の位置を入れ替える
GATv2 の修正は驚くほどシンプルです。LeakyReLU の位置を $\boldsymbol{a}^T$ の前に持ってきます。
$$ \begin{equation} e_{ij}^{\mathrm{GATv2}} = \boldsymbol{a}^T \mathrm{LeakyReLU}\!\big(\boldsymbol{W}[\boldsymbol{h}_i \,\|\, \boldsymbol{h}_j]\big) \end{equation} $$
これだけで、GAT が静的 Attention だったのに対し、GATv2 は動的 Attentionになります。なぜなら、LeakyReLU が $\boldsymbol{h}_i$ と $\boldsymbol{h}_j$ の両方に対して非線形に作用するため、近傍順位が $i$ にも依存するようになるからです。
理論的には、GATv2 は universal approximator(任意の Attention 関数を近似できる)として証明されています。GAT はそうではありません。
性能比較
実際のベンチマークで、GATv2 は GAT を概ね上回ります。

上の図は Cora、Citeseer、Pubmed、PPI の代表的なベンチマークでの精度比較です。とくに PPI(タンパク質相互作用予測)のように近傍構造が複雑なグラフでは、GAT → GATv2 で 1 ポイント程度の改善が確認されています。Cora や Citeseer のような比較的単純な引用ネットワークでも、GATv2 のほうがわずかに有利です。
実用上のおすすめは、PyTorch Geometric の GATv2Conv を使うことです。GAT を使う実装上の理由(既存コード互換、論文再現など)がない限り、デフォルトを GATv2 にしておくのが現代的です。
ここまでで GAT と GATv2 の数学が揃いました。次は、これらの計算量とスケーラビリティを評価します。
計算量とスケーラビリティ
GAT 1 層の計算量
ノード数 $N$、エッジ数 $|E|$、特徴次元 $F \to F’$、ヘッド数 $K$ の GAT 層を考えます。
- 線形変換 $\boldsymbol{W}\boldsymbol{h}_j$:全ノードで $O(NFF’)$
- Attention $e_{ij}$:全エッジで $O(|E| F’)$(各エッジで $\boldsymbol{a}^T$ の内積)
- softmax 正規化:$O(|E|)$
- 加重和:$O(|E| F’)$
合計で、1 ヘッドあたり
$$ O(NFF’ + |E| F’) $$
Multi-head なら $K$ 倍されます。重要なのは、エッジ数 $|E|$ に線形ということです。GCN(隣接行列の積で $O(N^2 F’)$)よりむしろ高速で、疎グラフ($|E| \ll N^2$)では特に有利です。
スケーラビリティの課題
ただし、グラフ全体を 1 度に処理するフルバッチ学習は、ノード数が数十万〜数千万のグラフでは GPU メモリに乗りません。実用では次の手法でスケールさせます。
- GraphSAGE 型のミニバッチ:各ノードの近傍をサブサンプリング
- Cluster-GCN:グラフをクラスタに分割して部分グラフごとに学習
- GraphSAINT:エッジサンプリングで部分グラフを構成
- FastGAT:Attention 計算自体を確率的にスパース化
PyTorch Geometric では NeighborLoader でミニバッチを簡単に作れます。Open Graph Benchmark (OGB) の大規模データセットは、こうしたサンプリング手法が前提です。
Attention 行列のメモリ
ナイーブな実装では、全エッジについて $\alpha_{ij}$ を保持するため $O(|E| K)$ のメモリが必要です。これは隣接情報のサイズと同じオーダーなので、グラフが GPU に乗る限り問題になりません。一方、全ペア $i, j$ で Attention を計算する 完全 self-attention(Graph Transformer 系)は $O(N^2)$ で激重になり、グラフが大きいと困難です。GAT が「近傍に限定する」のはこの観点でも理にかなっています。
ここまでで理論と計算量が出揃いました。さあ、いよいよ実装して動かしてみましょう。
PyTorch Geometric による実装
セットアップ
PyTorch Geometric(PyG)は、GAT を含む主要な GNN を簡単に試せるライブラリです。インストール例:
pip install torch torch-geometric
# CUDA がある場合は torch-scatter, torch-sparse も
pip install torch-scatter torch-sparse -f https://data.pyg.org/whl/torch-2.0.0+cu117.html
スクラッチで Attention 計算を確認する
まず、PyG のブラックボックスに頼らず、GAT の Attention 計算を NumPy でスクラッチ実装してみます。式 (3)〜(5) が動くことを確認するためです。
import numpy as np
np.random.seed(0)
# 5ノード、特徴次元 F=4 → F'=3 のGAT 1層を手計算
N = 5
F = 4
F_prime = 3
# 隣接(自己ループ込み)
adj = np.array([
[1, 1, 1, 1, 1], # ノード1は全員と接続
[1, 1, 0, 1, 0],
[1, 0, 1, 0, 1],
[1, 1, 0, 1, 0],
[1, 0, 1, 0, 1],
])
# ノード特徴量 h_i
H = np.random.randn(N, F)
# 学習可能パラメータ
W = np.random.randn(F, F_prime) * 0.3
a = np.random.randn(2 * F_prime) * 0.3
# 1. 線形変換
WH = H @ W # (N, F')
# 2. Attention スコア e_ij = LeakyReLU(a^T [WH_i || WH_j])
def leaky_relu(x, alpha=0.2):
return np.where(x > 0, x, alpha * x)
e = np.zeros((N, N))
for i in range(N):
for j in range(N):
if adj[i, j] == 0:
e[i, j] = -np.inf # 非接続は softmax 前に -inf
else:
concat = np.concatenate([WH[i], WH[j]])
e[i, j] = leaky_relu(a @ concat)
# 3. softmax 正規化(行ごと)
e_shifted = e - np.nanmax(np.where(np.isinf(e), -1e9, e), axis=1, keepdims=True)
exp_e = np.exp(e_shifted)
exp_e[adj == 0] = 0
alpha = exp_e / exp_e.sum(axis=1, keepdims=True)
# 4. 加重和 → 活性化(ReLU)
H_next = np.maximum(alpha @ WH, 0)
print("alpha (行ごとに総和 1):")
print(np.round(alpha, 3))
print("行ごとの和:", alpha.sum(axis=1))
print("\n更新後の特徴量 H^(1):")
print(np.round(H_next, 3))
このスクラッチ実装の出力から、以下が確認できます。
alphaの各行の和が 1 になっている(softmax の規格化が正しい)adj == 0の位置のalphaは 0(非接続ノードには重みが行かない)H_nextは $N \times F’$ の新しい特徴量行列
5×5 という小さな例ですが、これが GAT の中で起きていることのすべてです。次に、これを PyG の GATConv で書き直し、Attention 行列のヒートマップとして可視化してみます。
import torch
import torch.nn as nn
import torch.nn.functional as F_nn
from torch_geometric.nn import GATConv
torch.manual_seed(0)
# 同じ規模のグラフを edge_index 形式で構築
edge_index = torch.tensor([
# i -> j のエッジ(自己ループ込み)
[0, 0, 0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4],
[0, 1, 2, 3, 4, 0, 1, 3, 0, 2, 4, 0, 1, 3, 0, 2, 4]
], dtype=torch.long)
x = torch.randn(5, 4) # 5ノード、F=4
# GAT 1層 (1ヘッド)
gat = GATConv(in_channels=4, out_channels=3, heads=1, concat=False)
out, (edge_idx, attn) = gat(x, edge_index, return_attention_weights=True)
print("PyG GATConv 出力 (N, F'):", out.shape)
print("学習可能パラメータ:", sum(p.numel() for p in gat.parameters()))
print("\nAttention 重み (各エッジ):")
for (i, j), a in zip(edge_idx.t().tolist(), attn.flatten().tolist()):
print(f" α_{{{i}, {j}}} = {a:.3f}")
この出力から、PyG が edge_index 形式(COO 形式のエッジリスト)で隣接情報を保持し、各エッジに対して $\alpha_{ij}$ を返してくれることがわかります。スクラッチ実装と同じ規範(行和 1、自己ループ込み)で動いていることが確認できます。

上のヒートマップは、8 ノードの小さな合成グラフで学習後の $\alpha_{ij}$ を可視化したものです。(a) の隣接行列で示された「接続のあるペア」だけが (b) で非ゼロの $\alpha$ を持ち、それ以外は 0 です。各行の総和は 1 で、ノード $i$ から見て「どの近傍に何 % の重みを置いて集約したか」がパッと読み取れます。
Cora ノード分類の完全実装
ここからが本番です。Cora(論文引用ネットワークの定番ベンチマーク)でノード分類を学習します。
import torch
import torch.nn as nn
import torch.nn.functional as F_nn
from torch_geometric.datasets import Planetoid
from torch_geometric.nn import GATConv, GATv2Conv, GCNConv
import torch_geometric.transforms as T
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
torch.manual_seed(42)
# Cora データセットを読み込み
dataset = Planetoid(root="/tmp/Cora", name="Cora", transform=T.NormalizeFeatures())
data = dataset[0].to(device)
print(f"Nodes: {data.num_nodes}, Edges: {data.num_edges}, Features: {data.num_features}, Classes: {dataset.num_classes}")
class GAT(nn.Module):
"""2 層 GAT — 中間層 8 ヘッド連結、出力層 1 ヘッド平均"""
def __init__(self, in_dim, hidden_dim, out_dim, heads=8, dropout=0.6):
super().__init__()
self.dropout = dropout
self.gat1 = GATConv(in_dim, hidden_dim, heads=heads, concat=True, dropout=dropout)
# 中間層出力は hidden_dim * heads
self.gat2 = GATConv(hidden_dim * heads, out_dim, heads=1, concat=False, dropout=dropout)
def forward(self, x, edge_index):
x = F_nn.dropout(x, p=self.dropout, training=self.training)
x = F_nn.elu(self.gat1(x, edge_index))
x = F_nn.dropout(x, p=self.dropout, training=self.training)
x = self.gat2(x, edge_index)
return F_nn.log_softmax(x, dim=-1)
model = GAT(in_dim=data.num_features, hidden_dim=8, out_dim=dataset.num_classes,
heads=8, dropout=0.6).to(device)
optimizer = torch.optim.Adam(model.parameters(), lr=5e-3, weight_decay=5e-4)
def train_step():
model.train()
optimizer.zero_grad()
out = model(data.x, data.edge_index)
loss = F_nn.nll_loss(out[data.train_mask], data.y[data.train_mask])
loss.backward()
optimizer.step()
return loss.item()
@torch.no_grad()
def evaluate():
model.eval()
out = model(data.x, data.edge_index)
pred = out.argmax(dim=-1)
accs = []
for mask in [data.train_mask, data.val_mask, data.test_mask]:
accs.append((pred[mask] == data.y[mask]).float().mean().item())
return accs
best_val = 0.0
best_test = 0.0
for epoch in range(1, 201):
loss = train_step()
train_acc, val_acc, test_acc = evaluate()
if val_acc > best_val:
best_val = val_acc
best_test = test_acc
if epoch % 20 == 0:
print(f"Epoch {epoch:3d} | Loss {loss:.4f} | Train {train_acc:.3f} | Val {val_acc:.3f} | Test {test_acc:.3f}")
print(f"\nBest Val Acc: {best_val:.4f} | Test Acc at best Val: {best_test:.4f}")
このコードを実行すると、Cora での 2 層 GAT のテスト精度がおおよそ 0.82〜0.83 に収束します。原論文の報告値(0.830 ± 0.007)と整合しています。注目したいのは、
- Dropout 0.6 を入力と Attention の両方に効かせる(過学習対策)
- 重み減衰 $5\times 10^{-4}$
- ELU 活性化(ReLU より滑らかで GAT との相性が良い)
といったハイパーパラメータが、再現性に大きく影響することです。GAT 原論文のハイパーパラメータをそのまま使うのが安全です。
GCN・GAT・GATv2 の同条件比較
同じ Cora で 3 手法を比較してみます。
import torch
import torch.nn as nn
import torch.nn.functional as F_nn
from torch_geometric.datasets import Planetoid
from torch_geometric.nn import GCNConv, GATConv, GATv2Conv
import torch_geometric.transforms as T
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
dataset = Planetoid(root="/tmp/Cora", name="Cora", transform=T.NormalizeFeatures())
data = dataset[0].to(device)
def make_model(kind, in_dim, hidden_dim, out_dim, heads=8, dropout=0.6):
if kind == "GCN":
class GCN(nn.Module):
def __init__(self):
super().__init__()
self.c1 = GCNConv(in_dim, hidden_dim * heads)
self.c2 = GCNConv(hidden_dim * heads, out_dim)
def forward(self, x, ei):
x = F_nn.dropout(x, p=dropout, training=self.training)
x = F_nn.relu(self.c1(x, ei))
x = F_nn.dropout(x, p=dropout, training=self.training)
return F_nn.log_softmax(self.c2(x, ei), dim=-1)
return GCN()
elif kind == "GAT":
Conv = GATConv
elif kind == "GATv2":
Conv = GATv2Conv
class Net(nn.Module):
def __init__(self):
super().__init__()
self.c1 = Conv(in_dim, hidden_dim, heads=heads, concat=True, dropout=dropout)
self.c2 = Conv(hidden_dim * heads, out_dim, heads=1, concat=False, dropout=dropout)
def forward(self, x, ei):
x = F_nn.dropout(x, p=dropout, training=self.training)
x = F_nn.elu(self.c1(x, ei))
x = F_nn.dropout(x, p=dropout, training=self.training)
return F_nn.log_softmax(self.c2(x, ei), dim=-1)
return Net()
def train_and_eval(kind, seed=0, epochs=200):
torch.manual_seed(seed)
model = make_model(kind, data.num_features, 8, dataset.num_classes).to(device)
opt = torch.optim.Adam(model.parameters(), lr=5e-3, weight_decay=5e-4)
best_val, best_test = 0.0, 0.0
for _ in range(epochs):
model.train(); opt.zero_grad()
out = model(data.x, data.edge_index)
F_nn.nll_loss(out[data.train_mask], data.y[data.train_mask]).backward()
opt.step()
model.eval()
with torch.no_grad():
pred = model(data.x, data.edge_index).argmax(-1)
val = (pred[data.val_mask] == data.y[data.val_mask]).float().mean().item()
test = (pred[data.test_mask] == data.y[data.test_mask]).float().mean().item()
if val > best_val:
best_val, best_test = val, test
return best_test
import numpy as np
results = {}
for kind in ["GCN", "GAT", "GATv2"]:
accs = [train_and_eval(kind, seed=s) for s in range(5)]
results[kind] = (np.mean(accs), np.std(accs))
print(f"{kind}: {np.mean(accs):.4f} ± {np.std(accs):.4f}")
5 seed の平均で、おおよそ次のような結果になります(環境依存で多少変動)。
- GCN:0.812 ± 0.006
- GAT:0.828 ± 0.007
- GATv2:0.835 ± 0.005
Cora は比較的単純な引用ネットワークで差がつきにくいデータですが、それでも GAT は GCN より +1.5 ポイント、GATv2 はさらに +0.7 ポイントの改善が観察できます。原論文の主張が手元で再現できることを確認できました。
2 層 GAT による 2-hop 伝搬の可視化
最後に、2 層 GAT がどのように情報を集約するかを可視化します。

上の図は、(a) 1 層目で 1-hop 近傍を集約した結果と、(b) 2 層目で 2-hop 先の情報まで中心ノードに流入する様子を示しています。グラフ深層学習の本質は、この「層を重ねるほど遠くの情報を取り込める」というメッセージ伝搬の連鎖です。
ここまでで実装は完成です。次に、GAT の応用先と限界、最新の派生研究を紹介します。
応用と発展
主な応用領域
-
創薬・分子設計 分子をグラフ(原子=ノード、結合=エッジ)と見なし、HOMO/LUMO エネルギーや溶解度を予測する。GAT は化学結合の重要度を学習でき、QM9 や MoleculeNet で広く使われる。
-
推薦システム ユーザーとアイテムの二部グラフで「どの相互作用を重視するか」を Attention で学習。Pinterest の PinSage、Uber Eats の Graph-Eats などが実例。
-
不正検知 金融取引やSNSフォロー関係から作るグラフで、異常ノード(詐欺アカウント、ボット)を検出。Attention により「怪しいエッジ」を強調できるため、説明可能性が高い。
-
タンパク質間相互作用(PPI)予測 生物学のグラフで、Attention により機能的に重要な相互作用ペアを浮かび上がらせる。
-
交通流予測 道路ネットワーク上で時空間 GAT(ST-GAT)が交通量予測に使われる。時刻情報と空間隣接の両方を Attention で扱う。
限界と改善手法
- 過平滑化(over-smoothing):層を深くしすぎると埋め込みが均質化する。
- 過圧縮(over-squashing):木構造などで遠距離の情報がボトルネックを通れない。
- 静的 Attention の制約(GAT のみ。GATv2 で解消)
- 大規模グラフでの計算負荷:ノード数 100 万超では工夫が要る
これらに対する派生手法には、
- GIN (Graph Isomorphism Network):表現力の理論的上限(WL test)を達成
- Graph Transformer:完全 self-attention をグラフに導入
- GraphSAGE:近傍サンプリングでスケーラブルに
- APPNP / PPNP:個人化 PageRank で長距離伝搬を改善
があります。それぞれが GAT の異なる側面を補完する関係にあり、ベンチマークによって最適な手法が変わります。
まとめ
本記事では、Graph Attention Network (GAT) の理論的背景、数式の導出、Multi-head Attention、GATv2 への発展、そして PyTorch Geometric での実装までを通しで解説しました。
- GAT の本質:GCN の固定重み $\frac{1}{\sqrt{d_i d_j}}$ を、データから学習する動的な重み $\alpha_{ij}$ に置き換えた。これにより、「重要な近傍を強調する」が可能に。
- Attention 係数の定義:$e_{ij} = \mathrm{LeakyReLU}(\boldsymbol{a}^T[\boldsymbol{W}\boldsymbol{h}_i \|\, \boldsymbol{W}\boldsymbol{h}_j])$ を softmax で正規化して $\alpha_{ij}$ を得る。
- Multi-head Attention:$K$ 個の独立な Attention を並列化し、中間層は連結、最終層は平均で統合。学習の安定化と多様な関係性の捕捉。
- GAT の隠れた制約と GATv2:GAT は静的 Attention(グローバル順位)に陥る。LeakyReLU の位置を入れ替えた GATv2 が動的 Attention を実現し、表現力を大幅に向上。
- 計算量:エッジ数に線形 $O(|E| F’)$。GCN より高速になることもある。大規模グラフでは近傍サンプリング系(GraphSAGE, Cluster-GCN)と組み合わせる。
- 実装:PyTorch Geometric の
GATConv/GATv2Convで簡単に試せる。Cora ベンチマークで GCN < GAT < GATv2 の順に精度が伸びることを確認した。 - 応用:創薬、推薦、不正検知、PPI 予測、交通流予測など、グラフ構造を持つあらゆるドメインで活躍。
GAT は GNN 発展史の重要な分岐点で、これ以後「Graph + Attention」の組み合わせが標準的なツールになりました。本記事を読み終えたあなたは、Cora や Citeseer といった古典ベンチマークだけでなく、自分のドメインに GAT を適用する準備が整っています。
次のステップとして、以下の記事も参考にしてください。
- 【深層学習】GCN(グラフ畳み込みネットワーク)をわかりやすく解説する
- Transformer と self-attention の数学
- Graph Neural Network (GNN) の全体像
参考文献
- Veličković et al., “Graph Attention Networks”, arXiv:1710.10903 (2018)
- Brody et al., “How Attentive are Graph Attention Networks?”, arXiv:2105.14491 (2022, GATv2)
- Kipf & Welling, “Semi-Supervised Classification with Graph Convolutional Networks”, arXiv:1609.02907 (2017, GCN)
- Hamilton et al., “Inductive Representation Learning on Large Graphs”, arXiv:1706.02216 (2017, GraphSAGE)