一文要約: Q は「私が探しているもの」、K は「私が掲げているラベル」、V は「私が実際に持っている中身」です。Attention は Q を K と照合し、関連する V ベクトルを見つけ、重み付き和を計算します。
10.1 本章で扱うこと
前章では幾何学的な直感を築きました。内積は類似度の指標である、というものです。
しかし、まだいくつかの問いが残っています。
- Q, K, V はそれぞれ何を意味するのでしょうか?
- 入力からどのように生成されるのでしょうか?
- 計算の各ステップで形状はどう変わるのでしょうか?
本章では Attention の計算全体を一歩ずつたどり、すべての次元変化を追いかけます。
10.2 入力の形状
10.2.1 入力次元を理解する
実際の学習では、複数のシーケンスを同時に処理します。入力テンソルの形状は次のとおりです。
X: [batch_size, ctx_length, d_model]
図に示した具体的な数値を使うと、
batch_size = 4: 4 つのシーケンスを並列に処理ctx_length = 16: 各シーケンスは 16 個のトークンを持つd_model = 512: 各トークンは 512 次元のベクトルで表される
10.2.2 具体例
学習中に同時に流れる 4 つのプロンプトを思い浮かべてください。
1. "The agent opened a pull request for the..."
2. "The reviewer left a comment on line..."
3. "A tool call returned an error because..."
4. "The workflow passed all checks and was..."
各プロンプトは 16 トークンに切り分けられ、各トークンは 512 次元のベクトルで表されます。
入力全体の形状は [4, 16, 512] です。
- 4 シーケンス
- 各シーケンスは 16 ポジション
- 各ポジションは 512 次元
10.2.3 三つの次元
| 次元 | 名称 | 意味 |
|---|---|---|
batch_size | バッチ | 同時に処理するシーケンス数 |
ctx_length | 文脈長 | シーケンスあたりのトークン数 |
d_model | モデル次元 | 各トークンベクトルの幅 |
10.3 Q, K, V を生成する
10.3.1 中心となる考え方
Q, K, V はすべて 同じ入力 X から、3 つの異なる重み行列を経て生成されます。
Q = X @ Wq
K = X @ Wk
V = X @ Wv
これらの重み行列 Wq, Wk, Wv は 学習可能なパラメータ です。ランダムに初期化された状態から始まり、学習中に調整されていきます。
10.3.2 次元の計算
Q を生成する場合を例にとります。
X: [4, 16, 512] (batch_size, ctx_length, d_model)
Wq: [512, 512] (d_model, d_model)
Q: [4, 16, 512] (batch_size, ctx_length, d_model)
行列積のルール [..., A, B] @ [B, C] = [..., A, C] に従うと、
[4, 16, 512] @ [512, 512] = [4, 16, 512]
Q, K, V は入力 X とまったく同じ形状になります。
10.3.3 なぜ 3 つの異なる行列なのか?
出力の形状が同じなら、わざわざ 3 つに分ける意味はあるのでしょうか?
それは Q, K, V がそれぞれ異なる役割を担うからです。
- Q (Query): 「私はどんな情報を探しているのか?」
- K (Key): 「私はどんな情報として見つけられるのか?」
- V (Value): 「選ばれたら私はどんな中身を提供するのか?」
別々の Wq, Wk, Wv を学習することで、モデルは同じ入力トークンを 3 つの異なる意味空間に射影することを学びます。検索のための空間、検索される側の空間、中身を運ぶ空間です。
10.3.4 覚えておきたいアナロジー
コードレビューのワークフローを考えてみてください。
| 役割 | アナロジー | 機能 |
|---|---|---|
| Query (Q) | レビュアーのコメント | 「この関数の文脈について情報がほしい」 |
| Key (K) | 各ファイルのラベルやヘッダ | 「このファイルは認証を扱っている」 |
| Value (V) | 実際のファイル内容 | 実装の本文すべて |
レビューの流れはこうです。
- あなたの Query (コメント/質問) が各ファイルの Key (説明) と照合される
- マッチスコアの高いファイルの内容が表に出てくる
- 最も関連の深い内容が応答に集約される
Attention も同じ仕組みで動きます。
10.4 第一の行列積: Q @ K^T
10.4.1 類似度行列の計算
Q と K がそろったら、次は両者の類似度を計算します。
scores = Q @ K^T
K は 転置 (K^T) する必要があります。こうすることで、Q の各行 (トークンごと) と K の各行 (こちらもトークンごと) の内積が取れるようになります。
10.4.2 次元の変化
Q: [4, 16, 128] (batch_size, ctx_length, d_key)
K^T: [4, 128, 16] (batch_size, d_key, ctx_length)
Result: [4, 16, 16] (batch_size, ctx_length, ctx_length)
ここで
d_key = 128となっているのは、Multi-Head Attention がd_modelを複数のヘッドに分割するためです。各ヘッドはd_key = d_model / num_heads = 512 / 4 = 128を受け取ります。この分割は次章で扱います。
10.4.3 結果の意味
結果は [4, 16, 16] のテンソルになります。
- 4 シーケンス
- 各シーケンスごとに 16×16 の「スコア行列」
- 位置 (i, j) の値はトークン i がトークン j にどれだけ注意を向けるべきかを表す
短いプロンプト "agent reviews PR ." を例にした 4×4 の簡略版を見てみましょう。以下の値はスケーリングやマスクをかける前の生の内積です。
agent reviews PR .
agent [ 8.2, 3.1, 6.5, 1.2 ]
reviews [ 3.4, 11.7, 7.8, 2.0 ]
PR [ 6.8, 7.2, 12.3, 1.9 ]
. [ 1.3, 2.1, 1.7, 9.4 ]
各セルは Q の対応する行と K の対応する列の内積です。対角成分 (トークンが自身に注意を向ける) は大きくなりがちで、意味的に関連の深いペア (reviews/PR、agent/PR など) も高いスコアを示します。 でスケーリングして Softmax を適用すると、これらの生のスコアが正規化された Attention の重みになります。
10.5 Scale: なぜ で割るのか
10.5.1 スケーリングの一手
Q @ K^T で得られる生のスコアは 小さくする 必要があります。
Attention Scores = (Q @ K^T) / sqrt(d_key)
この除算によって、数十から数百に達することもあった値が、ずっと狭い範囲に収まります。
10.5.2 なぜスケールが必要か?
問題: d_key が大きい (たとえば 128) と、内積も大きくなります。
dot product = sum(q_i × k_i) # 128 個の積和
各 q_i, k_i の分散が 1 のとき、内積の分散はおおよそ d_key になります。
結果: 値が大きいと Softmax が極端に振れます。
Softmax([100, 1, 2]) ≈ [1.000, 0.000, 0.000] # 一人勝ち
Softmax([1.0, 0.1, 0.2]) ≈ [0.400, 0.300, 0.300] # 滑らかな分布
鋭い Softmax は、ほとんどの位置で勾配がほぼゼロになることを意味します。学習が止まってしまうのです。
対処: で割ることで、スコアの分散を妥当な範囲に戻します。
dot product / sqrt(128) ≈ dot product / 11.3
10.5.3 数式の中での位置づけ
この こそが Scale ステップです。
10.6 Mask: 未来を「のぞき見」させない
10.6.1 なぜマスクが必要か
GPT 系の自己回帰モデルでは、次のトークンを予測するときに未来のトークンを使ってはいけません。しかし Q @ K は すべての 位置間の類似度を計算してしまうため、未来の位置も含まれます。
たとえば "The agent opened a pull request and merged it." で学習しているモデルが "merged" を処理しているとき、その後ろに何が続くかをモデルに見せてはなりません。
10.6.2 マスクの仕組み
解決策は 三角マスク で、未来の位置を負の無限大で埋めます。
Before mask: After mask:
[0.3, 0.2, 0.1, 0.4] → [0.3, -inf, -inf, -inf]
[0.2, 0.5, 0.2, 0.1] → [0.2, 0.5, -inf, -inf]
[0.1, 0.3, 0.4, 0.2] → [0.1, 0.3, 0.4, -inf]
[0.2, 0.1, 0.3, 0.4] → [0.2, 0.1, 0.3, 0.4 ]
右上の三角形 (未来の位置) が -inf になります。
10.6.3 なぜ -inf なのか?
Softmax は -inf をちょうど 0 に写すからです。
Softmax([0.3, -inf, -inf, -inf]) = [1.0, 0.0, 0.0, 0.0]
Softmax を通すと未来の位置の重みは 0 になり、モデルはそこから情報を読み取れなくなります。
10.7 Softmax: スコアを確率に変換する
10.7.1 変換のステップ
マスク後、行ごとに Softmax を適用します。
Before Softmax: [0.32, 0.04, -inf, -inf, ...]
After Softmax: [0.52, 0.48, 0.00, 0.00, ...]
10.7.2 Softmax の働き
- 正規化: 各行の総和が 1 になる
- 差を強調: 大きい値はさらに相対的に大きくなる
- -inf の処理: そのまま 0 に写る
10.7.3 パターンの読み取り方
マスクされたシーケンスの最初のトークンは自身にしか注意を向けられないので、行は [1.00, 0.00, 0.00, ...] になります。2 番目のトークンは位置 0 と 1 を見られるので、行はたとえば [0.61, 0.39, 0.00, ...] のようになります。後ろのトークンほど注意を分散できる位置が増えるため、行はより滑らかに広がります。
これが Attention の重み行列 です。各位置が他のすべての位置からどれだけ情報を引き出すかを表します。
10.8 第二の行列積: Attention 重み @ V
10.8.1 重み付き和
Attention の重みが計算できたら、最後のステップでその重みを使って Value ベクトルを混ぜ合わせます。
Output = Attention_Weights @ V
10.8.2 次元の変化
Attention_Weights: [4, 4, 16, 16] (batch, heads, ctx_len, ctx_len)
V: [4, 4, 16, 128] (batch, heads, ctx_len, d_key)
Output: [4, 4, 16, 128] (batch, heads, ctx_len, d_key)
ここに登場するマルチヘッド構造 (4 ヘッド) は次章のテーマです。
10.8.3 このステップが行うこと
各出力位置は、Attention スコアを重みとした、すべての V ベクトルの加重平均になります。
output[i] = sum(attention_weight[i, j] × V[j])
トークン i が注意の 70% をトークン j に、30% をトークン k に向けるなら、
output[i] = 0.7 × V[j] + 0.3 × V[k]
出力は文脈を踏まえた混合であり、特定の単一トークンのコピーではありません。
10.9 Attention の出力が意味するもの
10.9.1 出力の次元
Attention @ V の後、各トークンは新しいベクトルを得ます。
Output: [batch_size, ctx_length, d_key] = [4, 16, 128]
(マルチヘッドの場合、すべてのヘッドの出力が連結されます。これは次章で扱います。)
10.9.2 意味の変化
ここが肝心な点です。Attention の前は、各トークンのベクトルは そのトークン自身 の情報しか持っていませんでした。Attention の後は、各トークンのベクトルが 文脈全体の重み付き組み合わせ になります。
"merged" のトークン埋め込みは、当初その単語単独の表現でしかありませんでした。Attention を経た後は、周囲のトークンからの情報を取り込みます。誰がその動作を行ったのか、どの PR が関係するのか、その前に何があったのか、といった情報です。
これがモデルが文脈を「理解する」仕組みです。魔法ではなく、学習された Value ベクトルの加重平均にすぎません。
10.9.3 ループ
Attention の出力は、
- その位置の入力埋め込みを置き換える
- 次のコンポーネント (FFN や次のブロック) に渡される
- レイヤーを重ねるごとに洗練される
各ブロックは、各トークンの表現にさらに多くの文脈を積み上げていきます。
10.10 Attention の計算全体像
10.10.1 ステップごとの整理
Step 1: Q, K, V を生成
Q = X @ Wq [4, 16, 512]
K = X @ Wk [4, 16, 512]
V = X @ Wv [4, 16, 512]
↓
ヘッドに分割 (4 ヘッド, d_key = 128)
Q, K, V それぞれ: [4, 4, 16, 128] (batch, heads, seq, d_key)
↓
Step 2: 類似度を計算 (ヘッドごと)
scores = Q @ K^T [4, 4, 16, 16]
↓
Step 3: スケーリング
scores = scores / sqrt(d_key) [4, 4, 16, 16]
↓
Step 4: マスク (デコーダのみのモデル)
scores[future] = -inf [4, 4, 16, 16]
↓
Step 5: Softmax
weights = softmax(scores) [4, 4, 16, 16]
↓
Step 6: 重み付き和 (ヘッドごと)
output = weights @ V [4, 4, 16, 128] (batch, heads, seq, d_key)
↓
ヘッドを連結: [4, 16, 512] (batch, seq, d_model)
10.10.2 PyTorch 実装
import torch
import torch.nn.functional as F
def attention(Q, K, V, mask=None):
"""
Scaled Dot-Product Attention.
Q: [batch, seq_len, d_k]
K: [batch, seq_len, d_k]
V: [batch, seq_len, d_v]
mask: [batch, 1, seq_len] or [batch, seq_len, seq_len]
Returns:
output: [batch, seq_len, d_v]
attention_weights: [batch, seq_len, seq_len]
"""
d_k = Q.size(-1)
# Step 2: Q @ K^T
scores = torch.matmul(Q, K.transpose(-2, -1))
# Step 3: スケーリング
scores = scores / (d_k ** 0.5)
# Step 4: マスク
if mask is not None:
scores = scores.masked_fill(mask == 0, float('-inf'))
# Step 5: Softmax
attention_weights = F.softmax(scores, dim=-1)
# Step 6: 重み付き和
output = torch.matmul(attention_weights, V)
return output, attention_weights
10.11 Q, K, V のさらに深い理解
10.11.1 役割のまとめ
| 役割 | 生成元 | 目的 | 使われる場所 |
|---|---|---|---|
| Q | X @ Wq | 「私が探しているもの」 | Q @ K^T |
| K | X @ Wk | 「私が掲げるラベル」 | Q @ K^T |
| V | X @ Wv | 「私が運ぶ中身」 | Attention @ V |
10.11.2 なぜ K と V を分けるのか?
K も V も同じ入力から生まれます。それなのに、なぜ別々の行列を使うのでしょうか?
マッチングと取り出しを切り離すためです。
- K は どの 位置に注意が向くかを制御する
- V は注意が向いたとき どんな情報 が流れるかを制御する
この分離がモデルに柔軟性を与えます。あるトークンは「見つけられやすい」自分を演出する (多くの Query と高い K の内積を取る) と同時に、運ぶ実際の内容 (V) はまったく異なるものにできます。
10.11.3 例
「The agent merged the pull request after review.」を考えましょう。
"merged" を処理するとき、
- Q("merged") は探している: 「この動作を行ったのは誰か?」
- K("agent") はシグナルを発する: 「私は動作を行う主語である」
- V("agent") は運ぶ: agent が持つ意味的内容
Q と K のマッチングが、"merged" は "agent" に注意を向けるべきだとモデルに伝えます。V が実際の情報を届けます。
10.12 章のまとめ
10.12.1 重要な概念
| 概念 | 形状 | 意味 |
|---|---|---|
| X | [batch, seq, d_model] | 入力テンソル |
| Wq / Wk / Wv | [d_model, d_model] | 学習可能な射影行列 |
| Q | [batch, seq, d_model] | Query: 私が探しているもの |
| K | [batch, seq, d_model] | Key: 私が掲げるラベル |
| V | [batch, seq, d_model] | Value: 私が運ぶ中身 |
| Scores | [batch, seq, seq] | 類似度行列 |
| Weights | [batch, seq, seq] | Attention の確率 (各行の和は 1) |
| Output | [batch, seq, d_model] | 文脈を反映したトークン表現 |
10.12.2 計算の流れ
X → [Wq, Wk, Wv] → Q, K, V
↓
Q @ K^T (類似度)
↓
/ sqrt(d_key) (スケール)
↓
マスク (未来を遮る)
↓
Softmax (正規化)
↓
@ V (重み付き和)
↓
Output
10.12.3 押さえておきたい核心
Q, K, V は Attention の三主役です。Q が問い、K がラベルを示し、V が中身を運びます。Q を K と照合して関連する位置を見つけ、そのスコアで V を加重平均すると、文脈が織り込まれた表現が得られます。これこそが Attention によってモデルが言語を理解するための仕組みです。
章末チェックリスト
本章を読み終えた後、次のことができるようになっているはずです。
- Q, K, V がそれぞれ何を表すのかを説明できる。
- それらが同じ入力 X からどのように生成されるかを説明できる。
- Attention の各ステップにおける次元変化をたどれる。
- 因果マスクと、なぜ -inf を使うのかを説明できる。
- スケール因子 がなぜ存在するのかを説明できる。
次章でお会いしましょう
ここまでがシングルヘッド Attention の全貌でした。
実際の Transformer は、この一連の処理を複数の視点から同時に走らせます。第 11 章では Multi-Head Attention を扱います。モデルがどのように関係性を並列に眺め、それらをまとめ直すのか、ゆっくり見ていきましょう。それでは、また次の章で。