一文要約: Q は「何を探しているか」、K は「どんなラベルを掲げるか」、V は「どんな中身を提供するか」を表します。Query-Key の内積で学習済み適合度を出し、正規化した重みで許可された Value を混ぜます。
10.1 本章で扱うこと
前章では、内積が方向と大きさを使う、学習済み Query-Key 適合度スコアになり得ることを見ました。
しかし、まだいくつかの問いが残っています。
- 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. 「蒔絵師は硯箱を工房へ運び…」
2. 「能面師は胡粉を薄く塗って…」
3. 「版木職人は摺りの具合を見て…」
4. 「琵琶法師は撥を袋へ収め…」
この教材例では、各プロンプトを切り詰めるか Padding して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 @ W_Q
K = X @ W_K
V = X @ W_V
これらの重み行列 W_Q, W_K, W_V は、学習中に更新される 学習可能なパラメータ です。
10.3.2 次元の計算
Q を生成する場合を例にとります。
X: [4, 16, 512] (batch_size, ctx_length, d_model)
W_Q:[512, 512] (d_model, h × d_k)
Q: [4, 16, 512] (batch_size, ctx_length, h × d_k)
行列積のルール [..., A, B] @ [B, C] = [..., A, C] に従うと、
[4, 16, 512] @ [512, 512] = [4, 16, 512]
この一般的な例では h × d_k = h × d_v = d_model = 512 なので、結合した Q, K, V はたまたま X と同じ形状になります。これは設計上の選択であって必須条件ではありません。同じヘッドの Q と K は d_k を共有する必要がありますが、V は異なる d_v を使えます。
10.3.3 なぜ 3 つの異なる行列なのか?
出力の形状が同じなら、わざわざ 3 つに分ける意味はあるのでしょうか?
それは Q, K, V がそれぞれ異なる役割を担うからです。
- Q (Query): 「私はどんな情報を探しているのか?」
- K (Key): 「私はどんな情報として見つけられるのか?」
- V (Value): 「選ばれたら私はどんな中身を提供するのか?」
別々の W_Q, W_K, W_V を学習することで、モデルは同じ入力に三つの見方を作ります。Q と K は内積できる互換なヘッド内空間に入り、V は出力へ混ぜる特徴を運びます。
10.3.4 覚えておきたいアナロジー
図書館の検索を考えてみてください。
| 役割 | アナロジー | 機能 |
|---|---|---|
| Query (Q) | 読者の検索語 | 「蒔絵の修復について読みたい」 |
| Key (K) | 各本の目録タグ | 「漆芸、硯箱、修復」 |
| Value (V) | 本の実際の内容 | 最終的に取り出す本文 |
検索の流れはこうです。
- 読者のQueryを各本のKeyと照合する
- マッチスコアの高い本ほど大きな正規化重みを受け取る
- その重みに従ってValueの内容を混ぜる
Attention も同じ仕組みで動きます。
10.4 第一の行列積: Q @ K^T
10.4.1 適合度スコア行列の計算
結合射影をヘッドに分けたら、次は各ヘッドの中で Query-Key 適合度を計算します。
scores = Q @ K^T
K は 転置 (K^T) する必要があります。こうすることで、Q の各行 (トークンごと) と K の各行 (こちらもトークンごと) の内積が取れるようになります。
10.4.2 次元の変化
Q: [4, 4, 16, 128] (batch, heads, seq, d_k)
K^T: [4, 4, 128, 16] (batch, heads, d_k, seq)
scores: [4, 4, 16, 16] (batch, heads, query_seq, key_seq)
この例では
h = 4、したがってd_k = d_model / h = 512 / 4 = 128です。分割そのものは次章で扱いますが、ここでは次元を正直に追うためヘッド軸を明示します。
10.4.3 結果の意味
結果は [4, 4, 16, 16] のテンソルになります。
- 4 シーケンス
- 各シーケンスに4つのヘッド
- 各ヘッドに16×16の生スコア行列
- 要素
(i,j)は Query 位置iと Key 位置jの内積
短いプロンプト「蒔絵師 修復 硯箱 。」を使った、手作りの4×4例を見てみましょう。以下はスケーリングやマスク前の計算を示すための数値で、学習済みモデルから測った値ではありません。
蒔絵師 修復 硯箱 。
蒔絵師 [ 8.2, 3.1, 6.5, 1.2 ]
修復 [ 3.4, 11.7, 7.8, 2.0 ]
硯箱 [ 6.8, 7.2, 12.3, 1.9 ]
。 [ 1.3, 2.1, 1.7, 9.4 ]
この表は算術を見せるために選んだだけです。実際のヘッドで対角成分が大きいとは限らず、高い生スコアが意味的関連性の真値を表すわけでもありません。スケーリング、マスク、行方向の Softmax を経て初めて、許可された Key 位置を混ぜる重みになります。
10.5 Scale: なぜ で割るのか
10.5.1 スケーリングの一手
Q @ K^T で得られる生のスコアは、ヘッド幅に応じてスケーリングします。
scaled_scores = (Q @ K^T) / sqrt(d_k)
目的はスコアを固定区間に押し込むことではなく、d_k が変わっても典型的な尺度をそろえることです。
10.5.2 なぜスケールが必要か?
問題: d_k が大きくなると、未スケールの内積の典型的な大きさも増えます。
dot product = sum(q_i × k_i) # 128 個の積和
Q と K の各成分が独立で平均0、分散1なら、内積の分散は d_k、標準偏差は √d_k になります。
結果: 値が大きいと Softmax が極端に振れます。
Softmax([100, 1, 2]) ≈ [1.000, 0.000, 0.000] # 一人勝ち
Softmax([1.0, 0.1, 0.2]) ≈ [0.539, 0.219, 0.242] # 飽和が弱い
Softmax が飽和すると、多くの微分値が非常に小さくなり、最適化が難しくなります。
対処: で割り、上の理想化した条件ではスコアの標準偏差を1前後に保ちます。
dot product / sqrt(128) ≈ dot product / 11.3
10.5.3 数式の中での位置づけ
が Scale、M が Softmax 前に加える Causal / Padding などのマスクです。
10.6 Mask: 未来を「のぞき見」させない
10.6.1 なぜマスクが必要か
GPT 系の自己回帰モデルでは、次のトークンを予測するときに未来のトークンを使ってはいけません。Q @ K はまず すべての位置の組に生スコアを作るため、Causal マスクで未来を遮断します。別の構造では Padding や構造マスクも使います。
たとえば「蒔絵師は硯箱を運び、それを修復した。」で学習しているモデルが「修復」を処理するとき、その後ろの「した」を見せてはなりません。
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, 1.87, 0.94, -inf]
After Softmax: [0.132, 0.622, 0.246, 0.000]
10.7.2 Softmax の働き
- 正規化: 各行の総和が 1 になる
- 順序を保つ: 大きいスコアほど大きい重みを受け、比率は指数で決まる
- -inf の処理: そのまま 0 に写る
10.7.3 パターンの読み取り方
因果的シーケンスの最初のトークンは自身しか見られないので、行は [1.00, 0.00, 0.00, ...] になります。2番目は位置0と1を見られるので、たとえば [0.32, 0.68, 0.00, ...] です。後ろの位置ほど選択肢は増えますが、学習済みヘッドが一つか二つの位置に大半の重みを置くこともあります。
これが Attention の重み行列 です。許可された Value を混ぜるための行方向の係数であって、トークンが意味的に関連している確率ではありません。
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, key_seq, d_v)
head_output: [4, 4, 16, 128] (batch, heads, query_seq, d_v)
ここに登場するマルチヘッド構造 (4 ヘッド) は次章のテーマです。
10.8.3 このステップが行うこと
各出力位置は、行方向の Softmax 重みを使った、許可された 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 出力の次元
1つのヘッドで weights @ V を計算すると、各位置はヘッドごとの出力ベクトルを得ます。
head_output: [batch_size, ctx_length, d_v] = [4, 16, 128]
4つのヘッドを連結すると [4, 16, 512] になり、さらに W_O で d_model に投影します。次章でこの結合を開きます。
10.9.2 意味の変化
ここが肝心な点です。最初のブロックでは X はトークンと位置の情報から始まります。後続ブロックでは X 自体がすでに文脈化されています。各 Attention ヘッドは、さらに 許可された V 行 の重み付き混合を加えます。
たとえば「修復」の位置は、先にある「蒔絵師」や「硯箱」の投影済み情報を受け取れます。各V行がどれだけ寄与するかは、学習された重みで決まります。
これはモデルが文脈表現を作る仕組みの一つです。特定のヘッドが主語を解決した、あるいは文を「理解した」ことの証明ではありません。
10.9.3 ループ
ヘッド出力を連結して W_O で射影した後、Attention 分岐は Residual Stream と加算されます。その結果が次のサブレイヤーへ進み、ブロックごとに洗練されます。
各ブロックは、各トークンの表現にさらに多くの文脈を積み上げていきます。
10.10 Attention の計算全体像
10.10.1 ステップごとの整理
Step 1: 結合した Q, K, V を生成
Q = X @ W_Q [4, 16, 512]
K = X @ W_K [4, 16, 512]
V = X @ W_V [4, 16, 512]
↓
4ヘッドへ reshape。本例では d_k = d_v = 128
Q, K, V: [4, 4, 16, 128]
↓
Step 2: 適合度スコアを計算 (ヘッドごと)
scores = Q @ K^T [4, 4, 16, 16]
↓
Step 3: スケーリング
scores = scores / sqrt(d_k) [4, 4, 16, 16]
↓
Step 4: 必要なマスク M を加える (Causal / Padding など)
scores = scores + M [4, 4, 16, 16]
↓
Step 5: Softmax
weights = softmax(scores) [4, 4, 16, 16]
↓
Step 6: 重み付き和 (ヘッドごと)
head_output = weights @ V [4, 4, 16, 128]
↓
ヘッドを連結: [4, 16, 512]
output = concat @ W_O: [4, 16, 512]
10.10.2 PyTorch 実装
import torch
import torch.nn.functional as F
def attention(Q, K, V, allowed_mask=None):
"""
Scaled Dot-Product Attention.
Q: [..., query_len, d_k]
K: [..., key_len, d_k]
V: [..., key_len, d_v]
allowed_mask: [..., query_len, key_len] に broadcast できる bool。
True は読み取り可能を表す。
Returns:
output: [..., query_len, d_v]
attention_weights: [..., query_len, key_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 allowed_mask is not None:
# 各 Query 行には、少なくとも1つ読み取り可能な Key が必要。
scores = scores.masked_fill(~allowed_mask, 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 @ W_Q | 「私が探しているもの」 | Q @ K^T |
| K | X @ W_K | 「私が掲げるラベル」 | Q @ K^T |
| V | X @ W_V | 「私が運ぶ中身」 | weights @ V |
10.11.2 なぜ K と V を分けるのか?
K も V も同じ入力から生まれます。それなのに、なぜ別々の行列を使うのでしょうか?
マッチングと取り出しを切り離すためです。
- K は どの 位置に注意が向くかを制御する
- V は注意が向いたとき どんな情報 が流れるかを制御する
この分離がモデルに柔軟性を与えます。経路を決める特徴と、その経路で運ぶ特徴を分けられるからです。どの Query-Key の組が高得点になるかはデータから学ばれ、アナロジーで固定されるものではありません。
10.11.3 例
手作りの文「蒔絵師は硯箱を修復した。」を考えましょう。
「修復」を処理するとき、
- Q("修復") は「この動作を行ったのは誰か」を探すかもしれない
- K("蒔絵師") は「私は動作の主語だ」という特徴を持つかもしれない
- V("蒔絵師") は、その位置の現在の投影済み特徴を運ぶ
これは学習済みヘッドが 学べるかもしれない パターンの直観であり、すべてのモデルやヘッドがこの役割をそのまま符号化するという主張ではありません。
10.12 章のまとめ
10.12.1 重要な概念
| 概念 | 形状 | 意味 |
|---|---|---|
| X | [batch, seq, d_model] | 入力テンソル |
| W_Q / W_K | [d_model, h × d_k] | 学習可能な Query / Key 射影 |
| W_V | [d_model, h × d_v] | 学習可能な Value 射影 |
| Q, K | [batch, h, seq, d_k] | ヘッドごとの Query / Key |
| V | [batch, h, seq, d_v] | ヘッドごとの Value |
| Scores | [batch, h, query_seq, key_seq] | 生の適合度スコア |
| Weights | Scores と同じ | マスク + Softmax 後の行方向の混合重み |
| Head output | [batch, h, query_seq, d_v] | ヘッドごとの V の重み付き混合 |
| Block-width output | [batch, seq, d_model] | ヘッド連結後に W_O で射影した出力 |
10.12.2 計算の流れ
X → [W_Q, W_K, W_V] → 結合 Q, K, V → ヘッド分割
↓
Q @ K^T (適合度スコア)
↓
/ sqrt(d_k) (スケール)
↓
+ M (必要に応じて Causal / Padding マスク)
↓
Softmax (正規化)
↓
@ V (ヘッドごとの重み付き和)
↓
ヘッド連結 → W_O → Output
10.12.3 押さえておきたい核心
Q, K, V は Attention の三主役です。Q が問い、K が特徴を掲げ、その内積が学習済み適合度になります。スケーリング、マスク、Softmax で行ごとの経路重みに変え、その重みで V を混ぜます。最後に各ヘッドを結合し、
W_Oで Residual Stream の幅へ戻します。
章末チェックリスト
本章を読み終えた後、次のことができるようになっているはずです。
- Q, K, V がそれぞれ何を表すのかを説明できる。
- それらが同じ入力 X からどのように生成されるかを説明できる。
- Attention の各ステップにおける次元変化をたどれる。
- 因果マスクと、なぜ -inf を使うのかを説明できる。
- スケール因子 がなぜ存在するのかを説明できる。
次章でお会いしましょう
ここまでで QKV の経路を端から端まで追いました。次元を正しく保つため、ヘッド軸も必要な分だけ先に見せています。
実際の Transformer は、この一連の処理を複数の視点から同時に走らせます。第 11 章では Multi-Head Attention を扱います。モデルがどのように関係性を並列に眺め、それらをまとめ直すのか、ゆっくり見ていきましょう。それでは、また次の章で。