Attentionの解釈性と限界 — 注意重みはモデルの説明になるか?

ある感情分析モデルが「この映画は退屈だったが、最後のどんでん返しは見事だった」という文を「ポジティブ」と分類したとします。このとき、モデルの判断根拠を知りたいと思うのは自然なことです。Transformerベースのモデルでは、内部のAttention重みが「各トークンが他のどのトークンにどれだけ注目しているか」を数値化しています。直感的には、この重みを見れば「モデルが何を重視したか」がわかりそうに思えます。

しかし、本当にAttention重みは「説明」として信頼できるのでしょうか? もし注意重みを完全に別のパターンに入れ替えても予測が変わらないとしたら、元の注意重みは本当にモデルの判断根拠を示していたのでしょうか? この疑問は、2019年に発表された2つの重要な論文をきっかけに、NLPコミュニティで激しい議論を巻き起こしました。

注意重みは計算の途中経過であり説明になるのかという問題構図

問題の構図はこの1枚に集約されます。注意重み $\alpha$ は入力から予測に至る計算の「途中経過」の一つにすぎず、その先にはValue・FFN・残差接続・LayerNormという多くの変換が挟まります。それでも私たちは $\alpha$ を見て「モデルはここを見た」と言いたくなる——この直感がどこまで正当かを、本記事では実データで検証していきます。

Attentionの解釈性を正しく理解することは、以下のような場面で不可欠です。

  • 医療・法律など高リスク領域: モデルの判断根拠を説明する義務がある場面で、Attention重みを「説明」として提示してよいかの判断が必要です
  • モデルのデバッグと改善: Attention可視化でバグを発見したと思っても、それが本当のモデルの挙動を反映しているか確信を持つ必要があります
  • 研究の正しい解釈: 多くの論文がAttention重みを分析して言語構造の学習を主張していますが、その結論の信頼性を評価する力が求められます

本記事の内容

  • Attention重みを「説明」として使うことの問題点
  • Jain & Wallace (2019) “Attention is not Explanation” の主張と実験
  • Wiegreffe & Pinter (2019) の反論と「Attention is not not Explanation」
  • Attention Rolloutによる情報フロー追跡の理論と実装
  • Gradient-based手法(入力勾配、Integrated Gradients)との比較
  • BERTの注意パターン分析と解釈の実践
  • Pythonでの注意重み可視化、Attention Rollout、勾配法の実装

前提知識

この記事を読む前に、以下の記事を読んでおくと理解が深まります。

画像なし
Self-Attentionの理論と実装
Query・Key・Valueの線形射影からScaled Dot-Product Attentionの導出、Pythonでのスクラッチ実装まで解説します。
画像なし
Multi-Head Attentionの理論と実装
複数のAttentionヘッドを並列に計算し結合する仕組みを理論と実装の両面から解説します。
画像なし
Attention重みの可視化と解釈
Attention重みのヒートマップ可視化、BERTViz、典型的な注意パターンの解説と実装です。
画像なし
BERTのアーキテクチャ
BERTの事前学習・ファインチューニングの仕組みとTransformerエンコーダの構造を解説します。

なぜAttention重みを「説明」として使いたくなるのか

解釈性が求められる背景

ディープラーニングモデルは「ブラックボックス」と呼ばれることがあります。数百万から数十億のパラメータが複雑に絡み合い、入力から出力への変換過程を人間が追うことは事実上不可能です。しかし、モデルの判断に責任を持つ必要がある場面は増え続けています。EUのGDPR(一般データ保護規則)では、自動化された意思決定に対して「説明を受ける権利」が定められています。医療診断、与信審査、刑事司法など、人間の人生に直接影響する領域では、「モデルがそう言ったから」では済まされません。

こうした要求に応えるために発展してきたのが説明可能AI(Explainable AI, XAI)の分野です。XAIでは、モデルの判断根拠を人間が理解できる形で提示することを目指します。代表的な手法としては、LIME(局所的な線形近似)、SHAP(Shapley値に基づく特徴量寄与)、勾配ベースの手法などがあります。

Attentionは「無料の説明」に見える

Transformerモデルの場合、Attention重みという便利な副産物が存在します。Self-Attentionの計算過程で、各トークンペア間の注意重み $\alpha_{ij}$ が自動的に算出されます。

$$ \alpha_{ij} = \frac{\exp(e_{ij})}{\sum_{k=1}^{n} \exp(e_{ik})}, \quad e_{ij} = \frac{\bm{q}_i^\top \bm{k}_j}{\sqrt{d_k}} $$

この重みは $\alpha_{ij} \geq 0$ かつ $\sum_j \alpha_{ij} = 1$ を満たす確率分布であり、「トークン $i$ がトークン $j$ にどれだけ注目しているか」と直感的に解釈できます。追加の計算コストなしに得られるため、いわば「無料の説明」です。

多くの研究者や実務家が、Attention重みを可視化して「モデルは”not”という否定語に注目して否定的な感情を検出した」「モデルは主語と動詞の対応関係を捉えている」といった分析を行ってきました。しかし、この「無料の説明」には根本的な問題が潜んでいます。

問題の核心:Attention重みは出力の一部ではない

ここで重要なのは、Attention重みが最終的な予測に至るプロセスの途中段階の一つに過ぎないという点です。Self-Attentionの出力は次のように計算されます。

$$ \bm{z}_i = \sum_{j=1}^{n} \alpha_{ij} \bm{v}_j $$

ここで $\bm{v}_j$ はValue(値)ベクトルです。つまり、Attention重みだけでなくValueベクトルの方向と大きさも出力に強く影響します。あるトークンへの注意重みが高くても、そのトークンのValueベクトルが最終予測に無関係な方向を向いていれば、高い注意重みは実質的に何の影響も及ぼしません。

注意重みが高くてもValueが小さければ寄与は小さい概念例

この関係を概念例で示しました。左のパネルでは [SEP] への注意重みが最大(0.50)ですが、中央のパネルのとおり [SEP] のValueノルムはほぼ空です。実際の寄与 $\alpha \times \|\bm{v}\|$(右)を計算すると、順位は逆転して “boring” が最大になります。注意重みのランキングと寄与のランキングは、このように簡単に食い違うのです。

さらに、Multi-Head Attentionでは複数のヘッドの出力が結合され、その後にフィードフォワード層、残差接続、層正規化が適用されます。最終予測に至るまでに、Attentionの出力は多くの非線形変換を経ます。一つの層の注意重みだけを見てモデル全体の判断を説明しようとするのは、巨大な工場の一つの工程だけ見て最終製品の品質を説明するようなものです。

この問題を実験的に明らかにしたのが、次に紹介する2つの重要な研究です。

Jain & Wallace (2019): “Attention is not Explanation”

研究の動機と概要

Jain & Wallace (2019) は、Attention重みを「説明」として使うことに対して体系的な疑問を投げかけた画期的な論文です。彼らの主張は明快です。もしAttention重みがモデルの判断根拠を忠実に反映しているなら、Attention重みのパターンは予測結果と強く結びついているはずだ — しかし実験はそうなっていないことを示しました。

実験1: 代替的Attention分布の存在

Jain & Wallaceの最初の実験は、学習済みモデルのAttention重みを異なる分布に置き換えても、予測が変わらないケースが多数存在することを示しました。

具体的には、ある入力に対するモデルの予測 $\hat{y}$ と、元のAttention重み $\bm{\alpha}$ が与えられたとき、以下の最適化問題を解きます。

$$ \tilde{\bm{\alpha}} = \arg\max_{\bm{\alpha}’ \in \Delta} \; \text{KL}(\bm{\alpha} \| \bm{\alpha}’) $$

$$ \text{subject to} \quad |\hat{y}(\bm{\alpha}’) – \hat{y}(\bm{\alpha})| < \epsilon $$

ここで $\Delta$ は確率単体(全要素が非負で和が1の集合)、$\text{KL}$ はKullback-Leibler情報量です。この最適化の目的を日本語で言い換えると、「予測結果をほぼ変えずに、元の注意分布からできるだけ離れた別の注意分布を見つける」ということです。

彼らはこの実験を感情分析、自然言語推論、質問応答など複数のタスクで実施し、元の注意分布とは大きく異なるにもかかわらず同じ予測を生む「代替的Attention分布」が容易に見つかることを示しました。

まったく違う注意分布でも予測が変わらない代替分布の概念図

実験のイメージを図にするとこうなります。左は “boring” と “brilliant” に集中した元の注意分布、右はそれらをほぼ無視して機能語に分散させた代替分布です。それでも予測確率はほぼ同じ——このような組が系統的に構成できてしまう、というのが彼らの発見でした。

この結果が意味するのは深刻です。もし全く異なるAttentionパターンが同じ予測をもたらすなら、元のAttentionパターンが「モデルがこう判断した理由」であるとは言えません。

実験2: Attention重みと勾配ベースの重要度の不一致

2つ目の実験では、Attention重みと入力勾配(gradient-based feature importance) の相関を調べました。入力勾配は、各入力特徴量が出力にどれだけ影響するかを微分によって測るもので、モデルの計算グラフ全体を通じた因果的な影響を反映します。

入力トークン $x_j$ の重要度を勾配で測る方法として、最も単純なのは出力 $\hat{y}$ の入力埋め込み $\bm{e}_j$ に対する勾配のノルムを使うことです。

$$ g_j = \left\| \frac{\partial \hat{y}}{\partial \bm{e}_j} \right\| $$

Jain & Wallaceは、この勾配ベースの重要度とAttention重みの間のKendallの順位相関 $\tau$ を計算しました。もしAttention重みがモデルの判断根拠を正しく反映しているなら、勾配ベースの重要度と高い相関を示すはずです。

しかし結果は、多くのタスクでAttention重みと勾配ベースの重要度の相関は弱いというものでした。特に一部のタスクでは相関がほぼゼロに近く、Attention重みが勾配の示す特徴量重要度とは全く異なる情報を持っていることが明らかになりました。

Jain & Wallaceの結論

これらの実験結果を踏まえ、Jain & Wallaceは以下のように結論づけました。

  1. Attention重みは予測と一意に対応しない: 同じ予測を維持しながら、大きく異なるAttention分布を構成できる
  2. Attention重みは特徴量の重要度を反映しない: 勾配ベースの重要度とAttention重みの間に一貫した強い相関がない
  3. したがって、Attention重みは「説明」として扱うべきではない

この結論はNLPコミュニティに大きな衝撃を与えました。それまで多くの研究がAttention可視化に基づいてモデルの振る舞いを分析していたためです。しかし、この主張に対してすぐに反論が提出されました。

Wiegreffe & Pinter (2019): “Attention is not not Explanation”

反論の骨子

Jain & Wallaceの論文の発表からわずか数ヶ月後、Wiegreffe & Pinter (2019) は「Attention is not not Explanation(Attentionは説明でないとは言えない)」という挑発的なタイトルの論文で反論しました。彼らの主張は、「代替的Attention分布が存在する」ことは「Attentionが説明にならない」ことの十分な根拠にはならないというものです。

論点1: 代替分布の存在は反証にならない

Wiegreffe & Pinterの核心的な批判は、「代替的な説明が存在すること」は「元の説明が無意味であること」を意味しないという点です。

これは日常的な推論でも同じです。ある事件の原因として2つの仮説が考えられるからといって、どちらの仮説も無意味であるとは言えません。むしろ、複数の説明が成り立つことは複雑な現象では自然なことです。

より技術的には、Jain & Wallaceの最適化問題は推論時にAttention重みを強制的に書き換えている点が問題です。これは、モデルが通常の計算で生成するAttention重みとは異なる、人工的な介入です。モデルが正常に動作しているときのAttention重みが特定のパターンを示すなら、それはモデルの内部表現について何かを語っている可能性があります。

論点2: Adversarial Attention — 「学習された代替分布」の検証

Wiegreffe & Pinterは、Jain & Wallaceの実験を一歩進めた巧妙な実験を設計しました。単に推論時にAttention重みを入れ替えるのではなく、代替的なAttention分布を生成するモデルを一から学習させることを試みました。

具体的には、元のモデルと同じ構造を持つがAttention機構だけが異なる挙動を学習するように訓練されたモデル(adversarial attention model)を構築します。このモデルは以下の目的関数で学習されます。

$$ \mathcal{L}_{\text{adv}} = \mathcal{L}_{\text{task}}(\hat{y}, y) – \lambda \cdot \text{KL}(\bm{\alpha}_{\text{orig}} \| \bm{\alpha}_{\text{adv}}) $$

第一項 $\mathcal{L}_{\text{task}}$ はタスクの損失関数(元のモデルと同程度の精度を達成させる)、第二項は元のAttention分布 $\bm{\alpha}_{\text{orig}}$ と新しいAttention分布 $\bm{\alpha}_{\text{adv}}$ のKL情報量にマイナスをつけたもの(できるだけ異なる分布にする)です。$\lambda$ はトレードオフを制御するハイパーパラメータです。

この実験の結果は興味深いものでした。adversarialモデルは確かに異なるAttention分布を持ちますが、タスクの精度を維持しながら元の分布から大きく乖離したAttention分布を学習することは容易ではないことがわかりました。特に、adversarialモデルの精度は元のモデルよりも低下する傾向がありました。

この結果は、モデルが学習した特定のAttentionパターンには意味がある — 少なくとも、任意のパターンで代替できるほど冗長なものではない — ことを示唆しています。

論点3: 「説明」の定義の曖昧さ

Wiegreffe & Pinterは、そもそも「Attentionが説明である」とは何を意味するのか、という根本的な問題も提起しました。XAIの文献では「説明」にはさまざまな定義があります。

概念 定義 Attention重みに当てはまるか
忠実性(Faithfulness) 説明がモデルの実際の計算過程を反映しているか 部分的 — Attentionは計算の一部だが全体ではない
もっともらしさ(Plausibility) 説明が人間にとって納得できるか しばしば高い — 人間の注意と一致することが多い
因果性(Causality) 説明が予測の因果的要因を示しているか 不明確 — Valueベクトルや後続の層の影響を無視している

Jain & Wallaceの批判は主に「忠実性」と「因果性」の観点からのものですが、Wiegreffe & Pinterは「もっともらしさ」にも価値があると主張しました。人間にとって理解しやすい説明は、たとえ厳密に因果的でなくとも、モデルの振る舞いについての洞察を与えてくれます。

議論の現在地

この2つの論文をきっかけに、コミュニティでは以下のような合意が形成されつつあります。

  1. Attention重みだけでは完全な説明にならない: Valueベクトル、残差接続、フィードフォワード層を無視しているため、忠実な説明とは言えない
  2. しかし、Attention重みは完全に無意味でもない: モデルの情報処理パターンについての部分的な手がかりを与える
  3. Attentionを「説明」として使うなら、その限界を明示すべき: 「Attentionが高い = モデルが重視した」と無条件に結論づけるのは危険
  4. より信頼性の高い解釈手法と組み合わせるべき: 勾配法、Attention Rollout、probing classifierなどとの併用が推奨される

この議論を踏まえ、次にAttention重みの限界を補う手法として「Attention Rollout」を詳しく見ていきましょう。

Attention Rolloutによる情報フロー追跡

単一層のAttentionの限界

前節で見たように、Attention重みの最大の問題の一つは「一つの層の情報しか見ていない」ことです。BERTのような深いモデルでは12層(BERT-base)もしくは24層(BERT-large)のTransformerブロックが積み重なっています。層1でトークンAがトークンBに注目し、層2でトークンBがトークンCに注目しているなら、トークンAの最終表現にはトークンCの情報も間接的に含まれています。しかし、どの一つの層のAttention重みを見ても、この間接的な情報伝搬は見えません。

Attention Rolloutの定式化

Abnar & Zuidema (2020) が提案したAttention Rolloutは、この問題に対処する手法です。基本的なアイデアは、全ての層のAttention行列を順番に「畳み込む」ことで、入力トークンから最終表現への情報の流れを追跡するというものです。

まず、各層 $l$ のAttention行列 $\bm{A}^{(l)} \in \mathbb{R}^{n \times n}$ を考えます。Multi-Head Attentionの場合、ヘッド $h$ のAttention行列 $\bm{A}_h^{(l)}$ をヘッド間で平均して使います。

$$ \bar{\bm{A}}^{(l)} = \frac{1}{H} \sum_{h=1}^{H} \bm{A}_h^{(l)} $$

ここで $H$ はヘッド数です。

次に、Transformerの残差接続を考慮します。各層の出力は、Self-Attentionの出力と入力の(残差接続)として計算されます。これは、各トークンの表現が「Attentionを通じた新しい情報」と「元の情報(恒等写像)」の混合であることを意味します。この混合を反映するために、Attention行列と単位行列の平均を取ります。

$$ \hat{\bm{A}}^{(l)} = \frac{1}{2}\left(\bar{\bm{A}}^{(l)} + \bm{I}\right) $$

$\bm{I}$ は $n \times n$ の単位行列です。この操作により、「自分自身の情報も半分は保持される」ことを表現しています。

ここで各行の和が1になるように再正規化を施します。

$$ \hat{\bm{A}}^{(l)}_{\text{norm}} = \text{diag}\left(\hat{\bm{A}}^{(l)} \bm{1}\right)^{-1} \hat{\bm{A}}^{(l)} $$

$\bm{1}$ は全要素が1のベクトル、$\text{diag}(\cdot)$ はベクトルを対角行列にする操作です。ここでは、各行の要素の合計を計算し、その逆数を各行にかけることで行和を1に正規化しています。

最後に、層1から層 $L$ までのAttention行列を順番に掛け合わせます。

$$ \bm{R}^{(L)} = \hat{\bm{A}}^{(L)}_{\text{norm}} \cdot \hat{\bm{A}}^{(L-1)}_{\text{norm}} \cdots \hat{\bm{A}}^{(1)}_{\text{norm}} $$

得られた行列 $\bm{R}^{(L)}$ の $(i, j)$ 成分は、「全層を通じて、トークン $i$ の最終表現にトークン $j$ の情報がどれだけ寄与しているか」の近似値を表します。

Attention Rolloutの直感的理解

Attention Rolloutの計算を直感的に理解するために、2層のモデルを例に考えましょう。

層1でトークンAがトークンBに重み $0.6$、トークンCに重み $0.4$ で注目しているとします。層2でトークンBがトークンDに重み $0.7$、トークンEに重み $0.3$ で注目しているとします。このとき、トークンAの最終表現には、トークンB経由でトークンDの情報が $0.6 \times 0.7 = 0.42$ の寄与で到達していることになります。残差接続を加味すると計算はもう少し複雑になりますが、基本的なアイデアは「各層の注意の経路を行列積で追跡する」というものです。

Attention Rolloutの限界

Attention Rolloutは単一層のAttention重みよりも情報フローを正確に追跡しますが、いくつかの限界があります。

  1. ヘッドの平均化: 各ヘッドが異なる言語的機能を学習しているにもかかわらず、全ヘッドを一様に平均してしまいます
  2. Valueベクトルの無視: 依然としてAttention重みのみに基づいており、Valueベクトルの影響を考慮していません
  3. フィードフォワード層の無視: 各層のフィードフォワード層(Attention出力の後に適用される2層の全結合ネットワーク)が情報を大きく変換する可能性があります
  4. 線形近似: 行列の積は線形操作であり、非線形変換(LayerNorm、活性化関数など)の効果を捉えきれません

これらの限界があるとはいえ、Attention Rolloutは単一層のAttentionよりも信頼性の高い近似を提供し、計算コストも低いため、実用的な解釈手法として広く使われています。

次に、Attentionとは異なるアプローチでモデルの判断根拠を推定する勾配ベースの手法を見ていきましょう。

勾配ベースの手法 — Attentionとの比較

なぜ勾配に注目するのか

Attention重みの解釈性が疑問視される中で、代替的な説明手法として注目されるのが勾配ベースの手法です。勾配の最大の利点は、モデルの計算グラフ全体を通じた因果的な影響を反映する点にあります。

Attentionが「モデルの内部状態の一スナップショット」であるのに対し、勾配は「入力を微小に変化させたときに出力がどれだけ変わるか」という感度分析です。これはモデルの全ての層(Attention、フィードフォワード、正規化、残差接続)を通じた影響を自動的に反映します。

Vanilla Gradient(入力勾配)

最も単純な勾配ベースの手法は、出力の入力に対する偏微分を計算する方法です。分類タスクで正解クラスの出力ロジット $y_c$ を考えると、トークン $j$ の重要度は次のように計算されます。

$$ g_j = \left\| \frac{\partial y_c}{\partial \bm{e}_j} \right\|_2 $$

ここで $\bm{e}_j \in \mathbb{R}^{d}$ はトークン $j$ の入力埋め込みベクトルです。勾配のノルムが大きいトークンほど、出力に対する感度が高い — つまり、そのトークンの変化が予測に大きく影響することを意味します。

Gradient × Input

Vanilla Gradientの問題点の一つは、勾配の大きさだけを見ているため、実際の入力値の大きさを考慮していないことです。勾配が大きくても入力値がゼロに近ければ、そのトークンの実際の寄与は小さいはずです。この問題に対処するのがGradient × Inputです。

$$ \text{GI}_j = \bm{e}_j \odot \frac{\partial y_c}{\partial \bm{e}_j} $$

ここで $\odot$ は要素ごとの積(アダマール積)です。最終的なトークン $j$ の重要度スコアは、このベクトルのL2ノルムまたは要素の総和として計算されます。

$$ s_j = \left\| \text{GI}_j \right\|_2 \quad \text{または} \quad s_j = \sum_k (\text{GI}_j)_k $$

Integrated Gradients

Gradient × Inputにはさらなる問題があります。ニューラルネットワークの活性化関数(ReLUなど)には勾配が飽和する領域があり、ある入力点での瞬間的な勾配がトークンの本来の重要度を過小評価する可能性があります。

Integrated Gradients(Sundararajan et al., 2017)はこの問題に対処する手法で、入力の「ベースライン」(通常はゼロベクトルやパディングトークン)から実際の入力までの経路に沿って勾配を積分します。

$$ \text{IG}_j = (\bm{e}_j – \bm{e}_j’) \odot \int_0^1 \frac{\partial y_c}{\partial \bm{e}_j}\bigg|_{\bm{e}=\bm{e}’ + t(\bm{e} – \bm{e}’)} dt $$

ここで $\bm{e}’$ はベースライン入力(例えばゼロベクトル)、$\bm{e}$ は実際の入力です。直感的には、「入力が何もない状態から現在の入力まで徐々に変化させたとき、各ステップでの勾配を足し合わせたもの」です。

実装上はこの積分をリーマン和で近似します。ステップ数 $M$ の離散近似は次のようになります。

$$ \text{IG}_j \approx (\bm{e}_j – \bm{e}_j’) \odot \frac{1}{M} \sum_{m=1}^{M} \frac{\partial y_c}{\partial \bm{e}_j}\bigg|_{\bm{e}=\bm{e}’ + \frac{m}{M}(\bm{e} – \bm{e}’)} $$

各手法の比較

手法 メリット デメリット
Attention重み 追加計算不要、直感的 因果性不明、Valueを無視
Attention Rollout 多層の情報フローを追跡 ヘッド平均化、FFN無視
Vanilla Gradient 計算グラフ全体を反映 ノイズが多い、飽和問題
Gradient × Input 入力値を考慮 飽和問題が残る
Integrated Gradients 飽和問題を解決、理論的裏付け 計算コスト高($M$ 回の前方伝播)

重要なのは、どの手法も完璧ではなく、複数の手法を組み合わせて結果の一貫性を確認することが推奨されるという点です。

それでは、ここまでの理論をPythonで実装し、実際にBERTモデルの注意パターンを分析してみましょう。

BERTの注意パターン分析

BERTの層とヘッドが学習するパターン

BERT-baseは12層×12ヘッド = 144個のAttentionヘッドを持っています。各ヘッドが何を学習しているかを分析する研究が多数行われており、興味深いパターンが報告されています。

構文的パターン

  • 特定のヘッドが主語と動詞の一致関係を捉えている
  • 名詞とその修飾語(形容詞、関係代名詞)の間に高い注意が向けられる
  • 構文木の親子関係に対応するAttentionパターンを持つヘッドが存在する

位置的パターン

  • 直前のトークンや直後のトークンに常に高い注意を向けるヘッド(局所的注意)
  • 文の先頭トークンや[SEP]トークンに集中するヘッド
  • 対角的な注意パターン(自分自身に高い重みを持つ)

意味的パターン

  • 共参照関係(「彼」→「太郎」など代名詞の指示先)を捉えるヘッド
  • 意味的に類似したトークン同士が互いに注目し合うパターン

[CLS]トークンと[SEP]トークンへの集中

BERTの注意パターンで最も顕著な特徴の一つは、多くの層・ヘッドで[SEP]トークンに注意が集中する現象です。これは一見すると奇妙に見えます — 区切りトークンに言語的な意味はないはずです。

Clark et al. (2019) の分析によると、[SEP]への注意集中は一種の「no-op(何もしない操作)」として機能している可能性があります。あるヘッドが現在の入力に対して有用な注意パターンを持たないとき、情報量の少ない[SEP]トークンに注意を分散させることで、表現を大きく変えずに「パスする」ことができるのです。

同様に[CLS]トークンへの注意も特徴的です。BERTでは[CLS]トークンの最終表現が文全体の表現として使われるため、[CLS]は全トークンの情報を集約する役割を果たします。特に上位層では、[CLS]から他の全トークンへの注意が比較的均等になる傾向があります。

層による注意パターンの変化

BERTの注意パターンは層が深くなるにつれて変化します。

  • 下位層(層1-4): 局所的な注意が支配的。隣接トークンや自分自身への注意が高い
  • 中間層(層5-8): 構文的パターンが最も明確に現れる。主語-動詞、修飾関係など
  • 上位層(層9-12): 注意がより分散する傾向。[SEP]トークンへの集中も顕著になる

この階層的な構造は、BERTが下位層で局所的な特徴を、上位層でより抽象的な特徴を学習しているという仮説と整合的です。

それでは、ここまでの理論的な議論をPythonで実装し、実際のデータで確認していきましょう。まずBERTからAttention重みを抽出し、可視化と解析を行います。

Pythonでの実装: Attention重みの抽出と可視化

BERTからのAttention重み抽出

まず、Hugging Face Transformersを使ってBERTからAttention重みを抽出します。ここでは多言語BERTを使用し、日本語のテキストで分析します。

import torch
import numpy as np
import matplotlib, matplotlib.pyplot as plt
from transformers import BertTokenizer, BertModel

# 図の日本語ラベル用フォント設定
for cand in ["Hiragino Sans", "Yu Gothic", "Noto Sans CJK JP", "IPAexGothic", "Meiryo"]:
    if any(cand == f.name for f in matplotlib.font_manager.fontManager.ttflist):
        plt.rcParams["font.family"] = cand
        break
plt.rcParams["axes.unicode_minus"] = False

# モデルとトークナイザの準備
# attn_implementation="eager" でAttention重みを確実に取得できるようにする
model_name = "bert-base-uncased"
tokenizer = BertTokenizer.from_pretrained(model_name)
model = BertModel.from_pretrained(model_name, output_attentions=True,
                                  attn_implementation="eager")
model.eval()

# テスト文
text = "The cat sat on the mat because it was tired."
inputs = tokenizer(text, return_tensors="pt")
tokens = tokenizer.convert_ids_to_tokens(inputs["input_ids"][0])

# Attention重みの抽出
with torch.no_grad():
    outputs = model(**inputs)

# outputs.attentions: (層数, batch, ヘッド数, seq_len, seq_len) のタプル
attentions = torch.stack(outputs.attentions)  # (12, 1, 12, seq_len, seq_len)
attentions = attentions.squeeze(1)  # (12, 12, seq_len, seq_len)

print(f"トークン: {tokens}")
print(f"Attention形状: {attentions.shape}")
print(f"層数: {attentions.shape[0]}, ヘッド数: {attentions.shape[1]}")
print(f"系列長: {attentions.shape[2]}")

このコードでは、output_attentions=True を指定することで、全層の全ヘッドのAttention重みが返されます。形状は (12層, 12ヘッド, 系列長, 系列長) で、各層の各ヘッドについて、トークンペア間の注意重み行列が得られます。

単一ヘッドのAttentionヒートマップ

まず、特定の層とヘッドのAttention重みをヒートマップで可視化します。

def plot_attention_heatmap(attention_matrix, tokens, layer, head, ax=None):
    """単一ヘッドのAttention重みをヒートマップで表示"""
    if ax is None:
        fig, ax = plt.subplots(figsize=(10, 8))

    im = ax.imshow(attention_matrix, cmap="Blues", vmin=0, vmax=1)
    ax.set_xticks(range(len(tokens)))
    ax.set_yticks(range(len(tokens)))
    ax.set_xticklabels(tokens, rotation=45, ha="right", fontsize=8)
    ax.set_yticklabels(tokens, fontsize=8)
    ax.set_xlabel("注意される側 (Key)")
    ax.set_ylabel("注意する側 (Query)")
    ax.set_title(f"{layer+1}層目 ヘッド{head+1}")
    plt.colorbar(im, ax=ax, fraction=0.046)
    return ax

# 層0ヘッド0と層11ヘッド0を比較
fig, axes = plt.subplots(1, 2, figsize=(18, 7))

# 下位層(層1)のAttention
attn_l0_h0 = attentions[0, 0].numpy()
plot_attention_heatmap(attn_l0_h0, tokens, layer=0, head=0, ax=axes[0])

# 上位層(層12)のAttention
attn_l11_h0 = attentions[11, 0].numpy()
plot_attention_heatmap(attn_l11_h0, tokens, layer=11, head=0, ax=axes[1])

plt.tight_layout()
plt.savefig("attention_heatmaps.png", dpi=150, bbox_inches="tight")
plt.show()

BERT下位層と上位層の注意ヒートマップ実測

実測したヒートマップを比較すると、下位層(層1)と上位層(層12)の注意パターンの違いが明確に見えます。下位層のこのヘッドは文全体に広く浅く注意を配っているのに対し、上位層では句読点と[SEP]トークンの列に注意が強く集中しています。内容語の列はほとんど白いまま——最終層の注意だけを見て「モデルが何を重視したか」を語ることの危うさが、この1枚からも読み取れます。これは前節で述べた「上位層での特殊トークンへの注意集中(no-op仮説)」と整合的です。

全ヘッドのAttentionパターン概観

次に、全12層×12ヘッドのAttentionパターンを一度に概観します。各ヘッドの注意のエントロピー(分散度)を計算し、注意がどの程度集中しているかを可視化します。

def attention_entropy(attn_matrix):
    """Attention分布のエントロピーを計算(集中度の指標)"""
    # 各行(Queryトークン)のエントロピーの平均
    # attn_matrix: (seq_len, seq_len)
    eps = 1e-10
    entropy = -np.sum(attn_matrix * np.log(attn_matrix + eps), axis=-1)
    return np.mean(entropy)

# 全層×全ヘッドのエントロピーマップ
n_layers, n_heads = attentions.shape[0], attentions.shape[1]
entropy_map = np.zeros((n_layers, n_heads))

for layer in range(n_layers):
    for head in range(n_heads):
        attn = attentions[layer, head].numpy()
        entropy_map[layer, head] = attention_entropy(attn)

# エントロピー(ビット)の最大値は log(seq_len)
max_entropy = np.log(attentions.shape[2])

fig, ax = plt.subplots(figsize=(12, 6))
im = ax.imshow(entropy_map, cmap="YlOrRd", aspect="auto",
               vmin=0, vmax=max_entropy)
ax.set_xlabel("ヘッド", fontsize=12)
ax.set_ylabel("層", fontsize=12)
ax.set_xticks(range(n_heads))
ax.set_yticks(range(n_layers))
ax.set_xticklabels([f"H{i+1}" for i in range(n_heads)])
ax.set_yticklabels([f"L{i+1}" for i in range(n_layers)])
ax.set_title("各層・各ヘッドの注意エントロピー\n(低=集中 / 高=分散)", fontsize=13)
plt.colorbar(im, ax=ax, label="エントロピー (nats)")

# エントロピーが最も低い(最も集中した)ヘッドを表示
min_idx = np.unravel_index(np.argmin(entropy_map), entropy_map.shape)
ax.annotate(f"最も集中\nL{min_idx[0]+1}H{min_idx[1]+1}",
            xy=(min_idx[1], min_idx[0]),
            xytext=(min_idx[1]+2, min_idx[0]+2),
            arrowprops=dict(arrowstyle="->", color="black"),
            fontsize=9, color="black",
            bbox=dict(boxstyle="round,pad=0.3", facecolor="white", alpha=0.8))

plt.tight_layout()
plt.savefig("attention_entropy_map.png", dpi=150, bbox_inches="tight")
plt.show()

BERT各層各ヘッドの注意エントロピーマップ

このエントロピーマップから、いくつかの興味深い特徴が読み取れます。実測ではエントロピーは最小0.02(3層目ヘッド1、ほぼ1点集中)から最大2.48まで大きくばらつき、144個のヘッドがまったく均質でないことがわかります。エントロピーが低い(注意が集中している)ヘッドは特定のトークンペアに強く注目しており、構文的または意味的な関係を捉えている可能性が高いです。一方、エントロピーが高い(注意が分散している)ヘッドは、広範な文脈から情報を集約する役割を果たしていると考えられます。この多様性こそが、次に見る「ヘッドを平均してよいのか」という問題の伏線になります。

それでは次に、Attention Rolloutを実装して単一層のAttentionとの違いを確認しましょう。

Pythonでの実装: Attention Rollout

Attention Rolloutの実装

理論で解説した手順をPythonで実装します。

def compute_attention_rollout(attentions, head_reduction="mean",
                              add_residual=True):
    """
    Attention Rolloutを計算する

    Parameters
    ----------
    attentions : np.ndarray, shape (n_layers, n_heads, seq_len, seq_len)
        全層の全ヘッドのAttention重み
    head_reduction : str
        ヘッド間の集約方法("mean" or "max")
    add_residual : bool
        残差接続を考慮するかどうか

    Returns
    -------
    rollout : np.ndarray, shape (seq_len, seq_len)
        Attention Rollout行列
    """
    n_layers = attentions.shape[0]
    seq_len = attentions.shape[2]

    # ステップ1: ヘッド間の集約
    if head_reduction == "mean":
        # 全ヘッドの平均
        attn_per_layer = np.mean(attentions, axis=1)  # (n_layers, seq_len, seq_len)
    elif head_reduction == "max":
        # 各位置で最大のヘッドを選択
        attn_per_layer = np.max(attentions, axis=1)
    else:
        raise ValueError(f"Unknown head_reduction: {head_reduction}")

    # ステップ2: 残差接続の考慮
    if add_residual:
        identity = np.eye(seq_len)
        attn_per_layer = 0.5 * (attn_per_layer + identity)

    # ステップ3: 行の正規化
    row_sums = attn_per_layer.sum(axis=-1, keepdims=True)
    attn_per_layer = attn_per_layer / row_sums

    # ステップ4: 層を順番に掛け合わせる
    rollout = attn_per_layer[0]
    for layer in range(1, n_layers):
        rollout = attn_per_layer[layer] @ rollout

    return rollout

単一層Attentionとの比較可視化

Attention Rolloutの結果を単一層のAttention重みと比較して可視化します。

# Attention Rolloutの計算
attn_np = attentions.numpy()  # (12, 12, seq_len, seq_len)
rollout = compute_attention_rollout(attn_np, head_reduction="mean",
                                     add_residual=True)

# 比較可視化: 最終層の平均Attention vs Attention Rollout
fig, axes = plt.subplots(1, 2, figsize=(18, 7))

# 最終層の平均Attention
last_layer_mean = np.mean(attn_np[11], axis=0)  # (seq_len, seq_len)
im1 = axes[0].imshow(last_layer_mean, cmap="Blues", vmin=0)
axes[0].set_xticks(range(len(tokens)))
axes[0].set_yticks(range(len(tokens)))
axes[0].set_xticklabels(tokens, rotation=45, ha="right", fontsize=8)
axes[0].set_yticklabels(tokens, fontsize=8)
axes[0].set_title("最終層(12層目)の平均注意", fontsize=12)
axes[0].set_xlabel("Key")
axes[0].set_ylabel("Query")
plt.colorbar(im1, ax=axes[0], fraction=0.046)

# Attention Rollout
im2 = axes[1].imshow(rollout, cmap="Blues", vmin=0)
axes[1].set_xticks(range(len(tokens)))
axes[1].set_yticks(range(len(tokens)))
axes[1].set_xticklabels(tokens, rotation=45, ha="right", fontsize=8)
axes[1].set_yticklabels(tokens, fontsize=8)
axes[1].set_title("Attention Rollout (全12層の畳み込み)", fontsize=12)
axes[1].set_xlabel("Key")
axes[1].set_ylabel("Query")
plt.colorbar(im2, ax=axes[1], fraction=0.046)

plt.tight_layout()
plt.savefig("attention_vs_rollout.png", dpi=150, bbox_inches="tight")
plt.show()

# [CLS]トークンから各トークンへの注意の比較
print("=== [CLS]トークンから各トークンへの寄与 ===")
print(f"{'Token':<12} {'Last Layer':>12} {'Rollout':>12}")
print("-" * 38)
for i, token in enumerate(tokens):
    print(f"{token:<12} {last_layer_mean[0, i]:>12.4f} {rollout[0, i]:>12.4f}")

最終層平均注意とAttention Rolloutの比較

この比較から、2つの手法がまったく異なる絵を見せることがわかります。最終層の平均Attention(左)では句読点と[SEP]の列への注意集中が目立ちます。一方Attention Rollout(右)では、残差接続の項(各層で単位行列と平均する操作)が12層分累積するため、対角成分——特に[CLS]の自己保持——が支配的になります。つまりRolloutは万能の改善ではなく、単一層とは別種のバイアスを持つのです。どちらか一方を鵜呑みにせず、両方の癖を知った上で使うことが大切です。

「it」の参照先の分析

テスト文 “The cat sat on the mat because it was tired.” では、”it” が “cat” を指しています。モデルがこの共参照関係を捉えているかを分析しましょう。

# "it"トークンから他トークンへの注意を詳細に分析
it_idx = tokens.index("it")
print(f"'it' のインデックス: {it_idx}")

# 各層の全ヘッドで "it" から "cat" への注意重みを抽出
cat_idx = tokens.index("cat")

fig, axes = plt.subplots(2, 1, figsize=(12, 10))

# 全層×全ヘッドの "it" → "cat" 注意重み
it_to_cat = attn_np[:, :, it_idx, cat_idx]  # (12, 12)
im = axes[0].imshow(it_to_cat, cmap="Reds", aspect="auto", vmin=0, vmax=0.5)
axes[0].set_xlabel("ヘッド", fontsize=11)
axes[0].set_ylabel("層", fontsize=11)
axes[0].set_xticks(range(12))
axes[0].set_yticks(range(12))
axes[0].set_xticklabels([f"H{i+1}" for i in range(12)])
axes[0].set_yticklabels([f"L{i+1}" for i in range(12)])
axes[0].set_title('"it" から "cat" への注意重み(層×ヘッド)', fontsize=12)
plt.colorbar(im, ax=axes[0], label="注意重み")

# Rollout vs 最終層: "it"からの注意分布
rollout_it = rollout[it_idx]
last_layer_it = last_layer_mean[it_idx]

x = np.arange(len(tokens))
width = 0.35

axes[1].bar(x - width/2, last_layer_it, width, label="最終層の平均注意",
            color="#4A90D9", alpha=0.8)
axes[1].bar(x + width/2, rollout_it, width, label="Attention Rollout",
            color="#E74C3C", alpha=0.8)
axes[1].set_xticks(x)
axes[1].set_xticklabels(tokens, rotation=45, ha="right", fontsize=9)
axes[1].set_ylabel("重み", fontsize=11)
axes[1].set_title('"it" からの注意分布(手法の比較)', fontsize=12)
axes[1].legend(fontsize=10)

# "cat" の位置を強調
axes[1].axvline(x=cat_idx, color="green", linestyle="--", alpha=0.5,
                label='"cat" position')

plt.tight_layout()
plt.savefig("it_coreference_analysis.png", dpi=150, bbox_inches="tight")
plt.show()

itからcatへの共参照注意の層ヘッド分析

実測結果は示唆に富んでいます。上のヒートマップを見ると、”it” から “cat” への注意重みは上位寄りの層の特定のヘッド——9層目ヘッド11で0.84、10層目ヘッド1で約0.8、11層目ヘッド7で約0.83——に強烈に現れています。BERTは確かに共参照を捉えるヘッドを持っているのです。ところが下段の棒グラフを見ると、最終層の平均でもRolloutでも “cat” への重みはごく小さく、代わりに句読点・[SEP](平均)や[CLS](Rollout)が支配しています。ヘッド間で平均を取った瞬間に、せっかくの共参照ヘッドの信号がかき消されてしまう——これがAttention Rolloutの限界として挙げた「ヘッドの平均化」の実例です。144個のヘッドは分業しており、平均は分業を見えなくします。

次に、勾配ベースの手法を実装し、Attention系の手法との比較を行いましょう。

Pythonでの実装: 勾配ベースの手法との比較

Gradient × Inputの実装

分類タスクのモデル(BERT + 線形分類ヘッド)を想定し、入力勾配を計算します。ここでは簡略化のため、[CLS]表現の特定の次元に対する勾配を計算しますが、実際のタスクでは損失関数に対する勾配を使います。

from transformers import BertForSequenceClassification

# 感情分析用の学習済みモデルを使用
# attn_implementation="eager" は後でAttention重みを取り出すために必要
model_cls = BertForSequenceClassification.from_pretrained(
    "textattack/bert-base-uncased-SST-2",
    output_attentions=True, attn_implementation="eager"
)
model_cls.eval()

text = "The movie was boring but the final twist was brilliant."
inputs = tokenizer(text, return_tensors="pt")
tokens_cls = tokenizer.convert_ids_to_tokens(inputs["input_ids"][0])

# 単語埋め込みだけをリーフテンソル化して勾配を流す
# 注意: embeddings全体の出力を inputs_embeds に渡すと位置埋め込みが
# 二重適用されるため、word_embeddings のみを使う
inputs_embeds = model_cls.bert.embeddings.word_embeddings(
    inputs["input_ids"]).detach()
inputs_embeds.requires_grad_(True)

# 順伝播
outputs = model_cls(inputs_embeds=inputs_embeds,
                    attention_mask=inputs["attention_mask"])
logits = outputs.logits  # (1, 2) — positive/negative

# 予測クラスの確認
pred_class = torch.argmax(logits, dim=-1).item()
labels = ["Negative", "Positive"]
print(f"予測: {labels[pred_class]} (logits: {logits.detach().numpy()})")

# 予測クラスのロジットに対する勾配
pred_logit = logits[0, pred_class]
pred_logit.backward()

# Gradient × Input の計算
grad = inputs_embeds.grad[0]  # (seq_len, hidden_dim)
embeds = inputs_embeds.detach()[0]  # (seq_len, hidden_dim)

# Gradient × Input: 各トークンの重要度スコア
grad_x_input = (grad * embeds).sum(dim=-1).numpy()  # (seq_len,)

# Vanilla Gradientのノルム
vanilla_grad = grad.norm(dim=-1).numpy()  # (seq_len,)

print(f"\n=== トークンごとの重要度 ===")
print(f"{'Token':<14} {'Grad×Input':>12} {'Vanilla Grad':>14}")
print("-" * 42)
for i, token in enumerate(tokens_cls):
    print(f"{token:<14} {grad_x_input[i]:>12.4f} {vanilla_grad[i]:>14.4f}")

このコードでは、inputs_embeds を使って入力埋め込みに勾配を流し、各トークンの埋め込みベクトルに対する勾配を計算しています。Gradient × Input は勾配と入力の要素積の次元方向の和で、各トークンが予測にどれだけ寄与したかを示します。正の値はそのクラスの予測を強める方向、負の値は弱める方向の寄与を意味します。実行するとモデルの予測は「Positive」となり、”boring”(-0.080、ポジティブ予測を弱める方向)が絶対値で最大の寄与を持ちます。感情の反転を担う接続詞 “but”(+0.067)にも大きな正の寄与が付いており、勾配がモデルの計算全体を通した影響を捉えていることがうかがえます。

Attention重みと勾配の相関分析

Jain & Wallaceの実験を再現し、Attention重みと勾配ベースの重要度の相関を計算します。

from scipy.stats import kendalltau

# Attention重みの抽出(分類モデルから)
with torch.no_grad():
    outputs_attn = model_cls(
        **tokenizer(text, return_tensors="pt"),
        output_attentions=True
    )

attn_cls = torch.stack(outputs_attn.attentions).squeeze(1).numpy()

# [CLS]トークン(インデックス0)から各トークンへのAttention重みを取得
# 各層×各ヘッドについて、勾配との相関を計算
n_layers, n_heads = attn_cls.shape[0], attn_cls.shape[1]
seq_len = attn_cls.shape[2]

# 勾配の絶対値を正規化して重要度分布に変換
grad_importance = np.abs(grad_x_input)
grad_importance = grad_importance / grad_importance.sum()

# 各層×各ヘッドのKendall τ相関
tau_map = np.zeros((n_layers, n_heads))
for layer in range(n_layers):
    for head in range(n_heads):
        # [CLS]からの注意分布
        attn_dist = attn_cls[layer, head, 0, :]
        tau, _ = kendalltau(attn_dist, grad_importance)
        tau_map[layer, head] = tau

fig, ax = plt.subplots(figsize=(12, 6))
im = ax.imshow(tau_map, cmap="RdBu", aspect="auto", vmin=-1, vmax=1)
ax.set_xlabel("ヘッド", fontsize=12)
ax.set_ylabel("層", fontsize=12)
ax.set_xticks(range(n_heads))
ax.set_yticks(range(n_layers))
ax.set_xticklabels([f"H{i+1}" for i in range(n_heads)])
ax.set_yticklabels([f"L{i+1}" for i in range(n_layers)])
ax.set_title("Kendall τ: 注意重み vs Gradient×Input重要度\n"
             "(赤=正の相関, 青=負の相関)", fontsize=12)
plt.colorbar(im, ax=ax, label="Kendall τ")

# 統計情報
mean_tau = np.mean(tau_map)
max_tau = np.max(tau_map)
min_tau = np.min(tau_map)
ax.text(0.02, 0.02, f"平均 τ={mean_tau:.3f}, 最大={max_tau:.3f}, 最小={min_tau:.3f}",
        transform=ax.transAxes, fontsize=10,
        bbox=dict(boxstyle="round,pad=0.3", facecolor="white", alpha=0.8))

plt.tight_layout()
plt.savefig("attention_gradient_correlation.png", dpi=150, bbox_inches="tight")
plt.show()

注意重みと勾配重要度のKendallタウ相関マップ

このKendall τ相関マップから、Jain & Wallaceの指摘が実データでも確認できます。実測では144ヘッドの相関は平均 $\tau = -0.094$、最大でも $0.308$、最小は $-0.590$ でした。つまり多くの層・ヘッドでAttention重みと勾配ベースの重要度の相関は弱く、負の相関(勾配が重要と言うトークンほど注意が低い)を示すヘッドも多数あります。全体的な傾向として「Attention重みが勾配の示す特徴量重要度と一貫して一致する」とはまったく言えない結果です。これは、Attention重みを無条件に「モデルの判断根拠」とみなすことの危険性を裏付けています。

3手法の統合比較

最後に、Attention重み、Attention Rollout、勾配ベースの手法の3つを統合して比較する可視化を作成します。

# Attention Rolloutの計算(分類モデル)
rollout_cls = compute_attention_rollout(attn_cls, head_reduction="mean",
                                          add_residual=True)

# [CLS]からの各手法による重要度分布
# 1. 最終層平均Attention([CLS]行)
last_attn = np.mean(attn_cls[11], axis=0)[0]  # [CLS]から各トークンへ

# 2. Attention Rollout([CLS]行)
rollout_scores = rollout_cls[0]

# 3. Gradient × Input(絶対値を正規化)
grad_scores = np.abs(grad_x_input)

# 各手法を正規化(比較のため)
def normalize(x):
    x_min, x_max = x.min(), x.max()
    if x_max - x_min > 0:
        return (x - x_min) / (x_max - x_min)
    return x

last_attn_norm = normalize(last_attn)
rollout_norm = normalize(rollout_scores)
grad_norm = normalize(grad_scores)

# 可視化
fig, axes = plt.subplots(3, 1, figsize=(14, 12), sharex=True)

colors = ["#3498DB", "#E74C3C", "#2ECC71"]
methods = ["最終層の平均注意 ([CLS]行)", "Attention Rollout ([CLS]行)",
           "Gradient × Input (正規化)"]
scores_list = [last_attn_norm, rollout_norm, grad_norm]

for ax, scores, color, method in zip(axes, scores_list, colors, methods):
    bars = ax.bar(range(len(tokens_cls)), scores, color=color, alpha=0.8,
                  edgecolor="white")
    ax.set_ylabel("重要度(正規化)", fontsize=10)
    ax.set_title(method, fontsize=12, fontweight="bold")
    ax.set_ylim(0, 1.15)

    # 上位3トークンを強調
    top3 = np.argsort(scores)[-3:]
    for idx in top3:
        bars[idx].set_edgecolor("black")
        bars[idx].set_linewidth(2)
        ax.text(idx, scores[idx] + 0.03, f"{scores[idx]:.2f}",
                ha="center", fontsize=8, fontweight="bold")

axes[-1].set_xticks(range(len(tokens_cls)))
axes[-1].set_xticklabels(tokens_cls, rotation=45, ha="right", fontsize=10)
axes[-1].set_xlabel("トークン", fontsize=12)

fig.suptitle("3つの解釈手法の比較\n"
             '"The movie was boring but the final twist was brilliant."',
             fontsize=14, fontweight="bold", y=1.02)

plt.tight_layout()
plt.savefig("three_methods_comparison.png", dpi=150, bbox_inches="tight")
plt.show()

3つの解釈手法の重要度比較

実測結果は、3つの手法がまったく違う絵を描くという、本記事の主題を鮮やかに実証するものになりました。

  1. 最終層Attention([CLS]行): 上位3トークンは “brilliant”(1.00)、”but”(0.65)、”twist”(0.58)。[CLS]から見た最終層の注意は、この文では意外にも内容語をよく拾っています
  2. Attention Rollout: 上位は [CLS](1.00)、[SEP](0.35)、句点(0.13)で、内容語はほぼ消えています。残差項の累積により[CLS]の自己保持が支配する、先ほど確認したRollout特有のバイアスです
  3. Gradient × Input: 上位は “boring”(1.00)、句点(0.93)、”but”(0.83)。ポジティブ予測を弱める “boring” と、感情を反転させる接続詞 “but” が高い重要度を持ち、因果的影響という観点で最も納得感のある結果です

注目してほしいのは、3手法すべてで上位に入るトークンが1つもないことです。かろうじて “but” が最終層Attentionと勾配の両方で上位に入っており、この2手法の「三角測量」なら “but” は判断根拠の候補と言えそうです。逆に、どれか1つの手法だけを見ていたら——たとえばRolloutだけなら「[CLS]が重要」という無意味な結論を出していたでしょう。複数の手法で一貫して高い重要度を示すトークンだけを判断根拠の候補として扱う。この「三角測量」的なアプローチが、信頼性の高い解釈のための実践的なガイドラインです。

実践ガイド: Attentionの解釈で気をつけるべきこと

やってよいこと

ここまでの議論を踏まえ、Attention重みを解釈に使う際の実践的なガイドラインをまとめます。

パターンの発見ツールとして使う: Attention重みは、モデルの内部で何が起きているかの「仮説生成」には有用です。特定のヘッドが構文的パターンを捉えているように見えたら、それを仮説として立て、別の手法(probing classifier、ablation study)で検証するという使い方です。

相対的な比較に使う: 同じモデル・同じ手法で、異なる入力間のAttentionパターンの違いを比較するのは有意義です。あるクラスの入力では特定のトークンパターンに注意が向き、別のクラスでは異なるパターンが現れるなら、それはモデルの振る舞いについて有用な情報です。

Attention Rolloutと組み合わせる: 単一層のAttention重みではなく、Attention Rolloutを使うことで、多層にわたる情報フローのより正確な近似が得られます。

複数手法の三角測量: Attention、勾配、LIME、SHAPなど複数の手法の結果を突き合わせ、一貫した結論が得られるかを確認します。

複数手法による三角測量の実践ガイド

この三角測量の考え方を図にまとめました。注意重みは「仮説生成」、Rolloutは「多層フローの近似」、勾配は「因果的影響の推定」と、それぞれ役割が違います。前節の実験で見たとおり単独では手法ごとに違う絵になるため、交点だけを信頼するのが実務的な落とし所です。

やってはいけないこと

Attention重みだけで因果的説明を主張する: 「モデルはこのトークンに注目したから、このように判断した」という因果的な主張は、Attention重みだけからは正当化できません。

単一ヘッドの結果を一般化する: 144個のヘッドのうち1つだけ見て、モデル全体の振る舞いを語るのは不適切です。

[SEP]や[CLS]への高い注意を意味のある注意と解釈する: 特殊トークンへの注意集中は「no-op」の可能性が高く、モデルの判断根拠とは別の現象です。

Attentionの解釈をハイステークスな意思決定に使う: 医療診断や法的判断の根拠としてAttention可視化を提示するのは、現段階では科学的に十分な根拠がありません。

最近の発展

Attention解釈性の研究は現在も活発に進んでおり、いくつかの新しい方向性が模索されています。

Attention Flow(Abnar & Zuidema, 2020)はAttention Rolloutをグラフ理論の最大フロー問題として再定式化し、情報の「ボトルネック」を特定する手法です。Generic Attention Explainability(Chefer et al., 2021)はAttention重みと勾配を組み合わせた手法で、Vision Transformerでも成功を収めています。またProbing Classifierアプローチは、Attention重みを直接解釈するのではなく、各層の隠れ状態に線形分類器を学習させ、特定の言語的特徴がどの層で符号化されているかを調べます。

まとめ

本記事では、Attentionの解釈性に関する議論を体系的に解説しました。

  • Attention重みは「無料の説明」ではない: Valueベクトル、残差接続、フィードフォワード層を無視しているため、単一層のAttention重みだけでモデルの判断を説明することには根本的な限界があります
  • Jain & Wallace (2019) の批判: 代替的Attention分布の存在と勾配との低い相関から、Attentionを「説明」として使うことへの疑問が体系的に示されました
  • Wiegreffe & Pinter (2019) の反論: 代替分布の存在は反証にはならず、「説明」の定義を明確にする必要性が提起されました。Adversarial実験はAttentionパターンに一定の意味があることを示唆しています
  • Attention Rollout: 全層のAttention行列を畳み込むことで、単一層のAttentionよりも情報フローの正確な追跡が可能になります。ただしValueとFFNは依然として無視されます
  • 勾配ベースの手法: Vanilla Gradient、Gradient × Input、Integrated Gradientsは計算グラフ全体を通じた因果的影響を反映し、Attention重みとは異なる(しばしばより信頼性の高い)重要度推定を提供します
  • 実践的な推奨: 複数の解釈手法を組み合わせた「三角測量」アプローチが最も信頼性が高く、Attention重みはあくまで仮説生成ツールとして位置づけるべきです

Attentionの解釈性の議論は、より広い「説明可能AI」の文脈の中に位置づけられます。モデルの判断根拠を理解するという課題は、社会的にますます重要になっており、今後もこの分野の発展が期待されます。

次のステップとして、以下の記事も参考にしてください。