Self-Attentionの記事を書いたとき、実はAttentionそのものの歴史や、Self-Attention以外のAttentionにはほとんど触れなかった。書き終えてから「そもそも何の"Self"なんだろう」と自分でも整理できていない部分があると気づいた。この記事で考えてほしいのは、QueryとKey・Valueが「どこから来ているか」という1つの質問だけで、Self-AttentionとCross-Attentionが区別できるということだ。手元で試したのは、音声認識や翻訳のように入力と出力が別の系列になるタスクで、Self-Attentionの実装をそのまま流用しようとして詰まった経験だった。
この記事の対象読者
- Self-Attentionの記事は読んだが、「そもそもAttentionって何だったか」を整理したい人
- 翻訳・音声認識・画像生成など、入力と出力が別の系列になるタスクを扱いたい人
- Query/Key/Valueという言葉は知っているが、それがどこから来るかで名前が変わることを知らなかった人
この記事で得られること
Query・Key・Valueがどこから来ているかを見るだけで、目の前のAttentionがSelf-AttentionかCross-Attentionかを判別できるようになり、自分のタスクにどちらの構成が必要かを判断できるようになります。
- Attentionが2014年にどんな問題を解決するために生まれたかを説明できるようになります
- 加算的Attention(Bahdanau)と内積Attention(Luong/Transformer)の違いを数式で説明できるようになります
- Self-AttentionとCross-AttentionをNumPyコードの違いとして説明できるようになります
- なぜWhisperのような現役モデルが今でもCross-Attentionを使っているかを説明できるようになります
この記事で扱わないこと
- Self-Attentionの内部計算の再解説
- Multi-Head Attention、Flash Attentionの実装詳細
- KVキャッシュの詳細な計算
1. Attentionは何を解決するために生まれたのか
このセクションで分かること:Attentionが2014年に解決しようとした「固定長ベクトルのボトルネック」問題と、この記事で使う図書館の比喩。
Attention以前の機械翻訳は、RNNのEncoder-Decoderモデルが主流だった。Encoderが入力文をすべて読み終えたあと、その内容を1本の固定長ベクトルに圧縮し、Decoderはそのベクトルだけを頼りに翻訳文を生成する。
Bahdanau, Cho, Bengioは2014年の論文で、この構造に問題があると指摘した。入力文が長くなるほど、1本のベクトルに情報を詰め込みきれず翻訳品質が落ちる。そこで提案されたのが、Decoderが単語を生成するたびに、入力文の中から関連する部分を(ハードに区切るのではなく)確率的に「探しにいく」仕組みだった。これが最初のAttention機構であり、論文のタイトルどおり「align(対応づけ)しながらtranslate(翻訳)する」ことを可能にした。
この記事では、Attentionの動きを図書館の比喩で説明する。
- Query(検索質問):「これに関連する情報が欲しい」という質問そのもの
- Key(索引):棚にある本につけられた検索用のラベル
- Value(中身):本の実際の中身
質問(Query)を出す人と、棚(Key・Value)がある部屋が同じ部屋ならSelf-Attention、別の部屋ならCross-Attentionと呼ぶ。次のセクションで、この違いを詳しく見ていく。
2. Query・Key・Valueは「誰のものか」で名前が変わる
前節で、Attentionが固定長ベクトルのボトルネックを解決するために生まれたと確認した。だがAttentionの説明では毎回Query・Key・Valueという言葉が出てくる。これがどこから来るかを整理しないと、実装のときに「なぜこのコードはSelf-Attentionと呼ばれないのか」で混乱する。このセクションで分かること:Self-AttentionとCross-Attentionを分ける、たった1つの判定基準。
判定基準はこうだ。QueryとKey・Valueが同じ系列から来ているか、それとも別の系列から来ているか(同じ部屋か、別の部屋か)。
| 種類 | Queryの出どころ | Key・Valueの出どころ | 系列長の制約 | 代表例 |
|---|---|---|---|---|
| Self-Attention | 自分の部屋 | 自分の部屋(Queryと同じ) | QueryとKey・Valueは常に同じ長さ | GPT・Claude等のdecoder-only LLM |
| Cross-Attention | 自分の部屋(Decoder) | 別の部屋(Encoder) | QueryとKey・Valueの長さが異なってよい | 翻訳・音声認識のEncoder-Decoderモデル |
Self-Attentionでは、質問する人自身が棚の本でもあるので、Query・Key・Valueはすべて同じ入力Xから作られる。だからQueryの本数(系列長)とKey・Valueの本数は常に一致する。一方Cross-Attentionでは、Decoder側の人がEncoder側の別の部屋に質問を投げるので、Query側の本数とKey・Value側の本数が一致している必要はない。
コードで確認する。
import numpy as np
def softmax(x):
e = np.exp(x - np.max(x, axis=-1, keepdims=True))
return e / np.sum(e, axis=-1, keepdims=True)
def attention(Q_source, KV_source, W_Q, W_K, W_V):
"""
Q_source と KV_source が同じ配列ならSelf-Attention、
違う配列(違う部屋)ならCross-Attentionになる。
"""
Q = Q_source @ W_Q
K = KV_source @ W_K
V = KV_source @ W_V
d_k = K.shape[-1]
scores = Q @ K.T / np.sqrt(d_k)
weights = softmax(scores)
return weights @ V, weights
np.random.seed(0)
d_model, d_k = 8, 4
W_Q = np.random.randn(d_model, d_k)
W_K = np.random.randn(d_model, d_k)
W_V = np.random.randn(d_model, d_k)
decoder_tokens = np.random.randn(3, d_model) # Decoder側: 3トークン
encoder_tokens = np.random.randn(5, d_model) # Encoder側: 5トークン(長さが違ってよい)
# Self-Attention: QueryもKey・Valueも同じ部屋(decoder_tokens)
self_out, self_w = attention(decoder_tokens, decoder_tokens, W_Q, W_K, W_V)
# Cross-Attention: Queryはdecoder、Key・Valueはencoderという別の部屋
cross_out, cross_w = attention(decoder_tokens, encoder_tokens, W_Q, W_K, W_V)
print(f"Self-Attention 重み行列shape: {self_w.shape}") # (3, 3)
print(f"Cross-Attention 重み行列shape: {cross_w.shape}") # (3, 5)
実行結果は次のとおり。
Self-Attention 重み行列shape: (3, 3)
Cross-Attention 重み行列shape: (3, 5)
Self-Attentionの重み行列は正方形(3×3)になるが、Cross-Attentionは長方形(3×5)になる。Decoderの3トークンそれぞれが、Encoderの5トークンすべてに注目度を配分するからだ。attention()関数のコード自体は1つも変えていない。変わったのは、KV_sourceに何を渡したかだけ、というのがこの記事の核心になる。
3. 加算的Attentionと内積Attention ── なぜTransformerは内積を選んだのか
前節で、Q/K/Vがどこから来るかで名前が変わると整理した。だがそもそも、QueryとKeyから「どれだけ注目すべきか」というスコアをどう計算するかにも複数の流儀がある。この計算方法の選択が、現代のAttentionの計算効率に直結している。このセクションで分かること:加算的Attentionと内積Attentionの違いと、Transformerが内積を採用した理由。
Bahdanauらの2014年の論文が使ったのは、小さなニューラルネットワークでスコアを計算する加算的Attentionだった。
\text{score}(s, h) = v^T \tanh(W_1 s + W_2 h)
この式が言っているのは、DecoderのQuery側の状態 $s$ とEncoderのKey側の状態 $h$ を、それぞれ別の重み行列 $W_1$・$W_2$ で変換してから足し合わせ、$\tanh$ を通してもう1段のベクトル $v$ で1つのスコアに潰す、ということだ。層をもう1つ挟むぶん表現力は高いが、系列長ぶんだけこの小さなネットワークを繰り返し計算する必要がある。
2015年、Luong, Pham, Manningは、これをもっと単純な内積で置き換えられると示した。
\text{score}(s, h) = s^T h
この式が言っているのは、QueryとKeyのベクトルをそのまま掛け合わせるだけ、ということだ。追加の重み行列も$\tanh$もいらない。Luongらはこの内積形式(および$s^T W h$という一般化形式)が、加算的Attentionと同等以上の翻訳品質を、より少ない計算で達成できることを示した。
2017年のTransformerが採用したScaled Dot-Product Attentionは、この内積にスケーリング $1/\sqrt{d_k}$ を加えたものだ。内積は行列積1回で全系列分を並列計算できるので、GPUと相性がよい。これが、Transformer以降のAttentionがほぼ例外なく内積ベースになった理由になる。
| 方式 | 提案 | スコアの計算 | 追加パラメータ | GPUでの並列化 |
|---|---|---|---|---|
| 加算的Attention | Bahdanau et al. 2014 | 小さなネットワーク+$\tanh$ | あり($W_1, W_2, v$) | しにくい |
| 内積Attention | Luong et al. 2015 | ベクトルの内積 | なし | しやすい |
| Scaled Dot-Product | Vaswani et al. 2017 | 内積 ÷ $\sqrt{d_k}$ | なし | しやすい |
ここまでのまとめ
Attentionは、Encoder-Decoderモデルが1本の固定長ベクトルに情報を詰め込みすぎる問題を解くために2014年に生まれた。QueryとKey・Valueが同じ部屋から来ればSelf-Attention、別の部屋から来ればCross-Attentionと呼ばれ、この違いはコード上ではKV_sourceに何を渡すかだけの差になる。スコアの計算方法にも加算的(Bahdanau)と内積(Luong、Transformer)の2系統があり、内積のほうがGPUでの並列計算に向くため現在の主流になっている。
4. 現役モデルは今でもCross-Attentionを使っている ── Whisperの例
ここまではAttentionの歴史と計算方法を見てきた。だが2017年以降、GPTのようなdecoder-onlyモデルの流行で、Cross-Attentionは「過去の技術」に見えるかもしれない。実際にはどうなのか、を確認しないと、自分が今から作るモデルの構成を正しく選べない。このセクションで分かること:現役のモデルがどこでCross-Attentionを使い続けているか。
OpenAIの音声認識モデルWhisperは、Transformerのencoder-decoder構成をそのまま踏襲している。
Decoder側は自分が書いた文字だけを見るSelf-Attentionで文脈を保ちつつ、Cross-Attention層で「今どの音声区間に対応する文字を書いているか」をEncoder側に問い合わせる。この構成のおかげで、音声という入力と文字という出力が、系列長も種類もまったく違っていても対応づけられる。
| モデル・用途 | Cross-Attention | 備考 |
|---|---|---|
| Whisper(音声認識) | 使う | Decoderが音声Encoderの出力を毎ステップ参照する |
| 翻訳向けTransformer(原論文の構成) | 使う | Decoderが原文Encoderの出力を参照する |
| GPT・Claude等のdecoder-only LLM | 使わない | 入力と出力が同じ1本の系列なのでSelf-Attentionのみで足りる |
| Stable Diffusionの画像生成U-Net | 使う | 画像側のQueryがテキストEncoderの出力に問い合わせる |
Cross-AttentionのKey・Valueは、Encoder側の出力から一度だけ計算すればよく、Decoderが1トークン生成するたびに増え続けることはない。これに対しSelf-AttentionのKey・Valueは生成のたびに1つずつ積み上がっていく。この「増えない記憶」と「増え続ける記憶」の違いは、KVキャッシュのサイズを見積もるときに効いてくる。詳しくは別記事で扱う予定。
decoder-onlyのLLMが主流になったのは、入力(プロンプト)と出力(続き)が同じ1本の系列として自然に扱えるからで、Cross-Attentionという仕組み自体が不要になったわけではない。入力と出力が別の種類・別の系列になるタスクに出会ったら、Self-Attentionだけで無理に押し通そうとせず、Cross-Attentionという選択肢を思い出してほしい。
トラブルシューティング
| 症状 | 原因 | 対処 |
|---|---|---|
| Self-Attention用のコードにEncoder出力を渡すとエラーになる |
Q_sourceとKV_sourceの系列長が違うのにSelf-Attention用の実装(QもKVも同じ変数)を使っている |
Cross-Attention用にKV_sourceを別の配列にできる実装に切り替える |
| Attention重みの行列が正方形にならない | Cross-Attentionを使っているので正常。QueryとKey・Valueの系列長が異なるため長方形になる | 想定どおりの挙動なので対処不要 |
| 加算的Attentionの実装で学習が遅い | 系列長ぶんだけ小さなネットワークを逐次計算している | 内積(Scaled Dot-Product)ベースの実装に切り替える |
| Whisper系のモデルでCross-Attention重みが可視化できない |
output_attentions=Trueのような明示的なフラグを立てていない |
使用しているライブラリのAPIでattention出力を有効化するオプションを確認する |
用語集
- Attention|入力の一部に動的に注目度(重み)を割り振り、その重みで情報を集約する仕組みの総称|たとえでは「図書館で必要な本を検索する行為」全般
- Self-Attention|Query・Key・Valueが同じ系列(同じ部屋)から作られるAttention|たとえでは「同じ部屋の住人同士が互いに参照し合う」こと
- Cross-Attention|QueryがKey・Valueと別の系列(別の部屋)から作られるAttention|たとえでは「別の部屋に質問を投げて索引と中身を借りてくる」こと
- 加算的Attention(Additive Attention)|小さなニューラルネットワークでスコアを計算する方式。Bahdanau Attentionとも呼ばれる
- 内積Attention(Dot-Product Attention)|QueryとKeyの内積でスコアを計算する方式。Luong Attention、Scaled Dot-Product Attentionの基礎
- KVキャッシュ|Attention層が過去に計算したKey・Valueを保持しておく記憶領域|Cross-Attentionでは一度計算すれば増えないが、Self-Attentionでは生成のたびに増える
学習ロードマップ
- Self-Attention:Q/K/Vが同じ系列から来る場合の内部計算を詳しく追う
- Transformer:Self-AttentionとCross-Attentionを組み合わせた全体構成を理解する
- KVキャッシュ(別記事):Self-AttentionとCross-Attentionでキャッシュの増え方がどう違うかを数値で確認する
- Jamba:AttentionをMambaと組み合わせるとKVキャッシュがどう変わるかを実測する
まとめ
Self-Attentionの記事を書いていたときは、正直「Self」という接頭辞をあまり意識していなかった。だが元をたどれば、AttentionはEncoder-Decoderという「別々の部屋」を橋渡しするために生まれた仕組みで、Self-Attentionはその特殊ケース(同じ部屋の中で完結させる)にすぎない。QueryとKey・Valueがどこから来ているかという1つの質問に立ち返るだけで、自分が今書いているコードがSelf-AttentionなのかCross-Attentionなのかを、迷わず判別できるようになった。
参考文献
- Bahdanau, D., Cho, K., Bengio, Y. "Neural Machine Translation by Jointly Learning to Align and Translate" arXiv:1409.0473(2014、ICLR 2015採択)(邦題:ニューラル機械翻訳における対応づけと翻訳の同時学習)── 加算的Attentionの初出。 https://arxiv.org/abs/1409.0473
- Luong, T., Pham, H., Manning, C. D. "Effective Approaches to Attention-based Neural Machine Translation" arXiv:1508.04025(2015、EMNLP)(邦題:Attentionベースのニューラル機械翻訳への効果的アプローチ)── 内積Attentionとglobal/local Attentionの提案。 https://arxiv.org/abs/1508.04025
- Vaswani, A. et al. "Attention Is All You Need" arXiv:1706.03762(2017)(邦題:Attentionこそが必要なすべて)── Scaled Dot-Product AttentionとTransformerのEncoder-Decoder構成の出典。 https://arxiv.org/abs/1706.03762
- Radford, A. et al. "Robust Speech Recognition via Large-Scale Weak Supervision" arXiv:2212.04356(2022)(邦題:大規模弱教師あり学習による頑健な音声認識)── Whisperのencoder-decoder・Cross-Attention構成の出典。 https://arxiv.org/abs/2212.04356