Attentionの数式を行列の形で完全に理解する — QK^T・softmax・重み付き和を図で追う

Transformer を学ぼうとして、この式の前で固まった経験はありませんか。

$$ \mathrm{Attention}(Q, K, V) = \mathrm{softmax}\!\left(\frac{QK^{\top}}{\sqrt{d_k}}\right)V $$

$Q$ に $K$ の転置を掛けて、$\sqrt{d_k}$ で割って、softmax して、$V$ を掛ける……記号は追えても、「結局この式は何をしているのか」がつかめない。多くの人がつまずくのはここです。

つまずきの原因は、たいてい行列の「形(shape)」を追っていないことにあります。$Q$ はどんな形の行列で、$QK^{\top}$ で何行何列の行列ができて、その各マスは何を意味するのか——これを1ステップずつ絵にして追いかけると、Attention は驚くほど単純な操作に見えてきます。やっていることは、たった3つ。①全トークンの間で「似ている度」を測り、②それを正規化して、③その重みで混ぜ合わせる。それだけです。

この「行列の形を追う」という見方が身につくと、いいことがたくさんあります。

  • Transformer の理解が一気に進む:BERT も GPT も、心臓部はこの1つの式の繰り返しです
  • 自分で実装できる:どこで何次元になるかを把握できれば、shape mismatch のエラーで悩まなくなります
  • Multi-Head や Cross-Attention も怖くない:形の分割・組み替えとして見通せます

本記事では、具体的な小さな行列(5トークン)を使い、$X$ から出力までのすべての行列を形と意味つきで可視化します。数式が苦手な人こそ、絵で腹落ちさせましょう。

Attentionの数式と、類似度・正規化・加重和という3ステップの対応

上の図が全体の地図です。恐ろしげな式も、分解すれば「①類似度($QK^{\top}$)→②正規化(softmax)→③加重和($\times V$)」の3手にすぎません。この3つを、行列の形を見ながら順にたどっていきます。

本記事の内容

  • 入力 $X$・$Q$・$K$・$V$ がそれぞれどんな形の行列か
  • $QK^{\top}$ が作る「スコア行列」の形と、各マスの意味
  • なぜ $\sqrt{d_k}$ で割るのか(softmax の飽和を防ぐ)
  • softmax を「行ごと」にかける意味と、重み行列 $A$
  • $A \times V$ の加重和が「新しい表現」を作る仕組み

前提知識

以下を先に読むと、本記事がスムーズです。

行列の積の計算ルール($(n\times d)$ と $(d\times m)$ を掛けると $(n\times m)$)と、softmax(ベクトルを合計1の確率に変換)を知っていれば十分です。

登場人物:入力 X は「行がトークン」の行列

まず、Attention に入ってくるデータの形を確認します。文は「トークン(単語や部分文字列)」の列です。各トークンは、埋め込みによって長さ $d$ のベクトルになっています。それを縦に積んだのが入力行列 $X$ です。

入力Xは各トークンを行ベクトルとして縦に並べた n×d の行列

図のとおり、$X$ は $(n \times d)$ の行列です。$n$ はトークンの数(この記事の例では「私・は・犬・を・見た」の5個)、$d$ は各トークンの埋め込み次元です。1行が1トークンに対応する——この対応を最後まで握っておくことが、Attention 理解の一番の鍵です。これから行列がいくつも登場しますが、「行 = トークン」はずっと変わりません。

さて、この $X$ から、Attention はまず3つの別バージョンを作ります。$Q$(query)、$K$(key)、$V$(value)です。次はその形を見ましょう。

Q・K・V:同じ入力を3つの役割に変換する

Attention は、各トークンに3つの役割を持たせます。「自分は何を探しているか(query)」「自分はどんな目印を持つか(key)」「自分はどんな中身を渡すか(value)」の3つです。この3つは、入力 $X$ に別々の重み行列を掛けて作ります:

$$ Q = X W_Q, \qquad K = X W_K, \qquad V = X W_V $$

QKVはXにそれぞれの重み行列を掛けて作る。形は n×d_k

図で形を追いましょう。$X$ は $(n \times d)$、重み行列 $W_Q$ は $(d \times d_k)$。行列積のルールで、$Q = X W_Q$ は $(n \times d_k)$ になります。$K$、$V$ も同じで、すべて $(n \times d_k)$ の形です(厳密には $V$ の列数は $d_v$ と別にできますが、簡単のため同じにします)。ここでも行はトークンのままです。$Q$ の第1行は「私」の query ベクトル、$K$ の第3行は「犬」の key ベクトル、という具合です。

重み行列 $W_Q, W_K, W_V$ は学習で決まるパラメータです。同じトークンでも、query として見るか key として見るかで別のベクトルになる——この「役割の分離」が Attention の柔軟さを生みます。

なぜ、わざわざ同じ $X$ から3つも作るのでしょうか。図書館での本探しにたとえると分かりやすいでしょう。query は「探している内容」、key は各本の「背表紙の見出し」、value は本の「中身」です。あなたの探し物(query)と各本の見出し(key)を照らし合わせて、合致した本の中身(value)を取り出す——Attention はこれを全トークン間でやっています。探す側の表現(query)と探される側の目印(key)を別々に学習できるからこそ、「主語は述語を探す」「代名詞は指示先を探す」といった多様な関係を、それぞれに適した向きで捉えられるのです。もし1種類のベクトルで済ませたら、この非対称な「探す・探される」の関係を表せません。

3つの行列がそろったので、いよいよ本題の $QK^{\top}$ に進みます。

ステップ①:QK^T で「全トークン対の類似度」を測る

Attention の第一歩は、「どのトークンがどのトークンに注目すべきか」を数値化することです。それには、query と key の内積を使います。

なぜ内積が「似ている度」になるのか、少しだけ確認しておきましょう。2つのベクトル $q$ と $k$ の内積は、幾何的には $q \cdot k = |q|\,|k|\cos\theta$ と書けます($\theta$ は2つのベクトルの間の角度)。向きがそろっている($\theta$ が小さい)ほど $\cos\theta$ は1に近づき、内積は大きくなります。逆に向きが直交すれば0、反対を向けば負。つまり内積は「2つのベクトルがどれだけ同じ方向を向いているか=どれだけ似ているか」を1つの数にまとめた量なのです。query と key が「似た向き」に学習されたトークン同士は、大きなスコアで結ばれます。

すべての query と、すべての key の組で内積を取れば、全トークン対の類似度が一気に求まります。これがちょうど $Q K^{\top}$ です。行列の積は「左の行と右の列の内積を並べる」演算なので、$Q$ の各行(query)と $K^{\top}$ の各列(= $K$ の各行 = key)の内積が、そのまま全部のマスに詰まります。

QK^Tは全トークン対の内積を並べたn×nのスコア行列

形を追います。$Q$ は $(n \times d_k)$、$K^{\top}$ は $K$ を転置した $(d_k \times n)$。掛けると $(n \times n)$ の正方行列になります。これをスコア行列 $S$ と呼びましょう。$d_k$ という次元は内積で「潰れて」消え、代わりにトークン数 $n$ が両側に並ぶことに注目してください。

このスコア行列の各マス $S_{ij}$ は、$q_i \cdot k_j$——「トークン $i$ の query」と「トークン $j$ の key」の内積です。意味は「トークン $i$ が、トークン $j$ にどれだけ注目するか」。つまり第 $i$ 行を横に読むと、「トークン $i$ が他のすべてのトークンに向ける関心の強さ」が並んでいます。実際の数値で見てみましょう。

スコア行列のヒートマップ。行が注目する側、列が注目される側

これは例のデータで実際に $QK^{\top}/\sqrt{d_k}$ を計算したヒートマップです(スケーリングは次節で説明します)。縦軸が「注目する側(query)」、横軸が「注目される側(key)」。赤いマスほどスコアが高く、青いほど低い。たとえば「犬」の行を見ると「を」のマスが濃い赤(3.0)で、「犬」が「を」に強く注目していることが読み取れます。この時点ではまだ生のスコアで、大きさもバラバラです。これを「注目度の配分」に変えるのが、次のスケーリングと softmax です。

ステップ①.5:なぜ √d_k で割るのか

softmax に入る前に、スコアを $\sqrt{d_k}$ で割る「スケーリング」が入ります。地味な操作ですが、これには明確な理由があります。

内積 $q_i \cdot k_j$ は $d_k$ 個の積の和です。各成分が独立でだいたい分散1くらいだとすると、その和である内積の分散は $d_k$ に比例して大きくなります。つまり次元 $d_k$ が大きいほど、スコアは極端な値(大きな正・負)を取りやすくなるのです。

内積は次元とともに広がり、スケーリングなしだとsoftmaxが一点集中する

左の図は、$d_k=64$ のときの内積の分布です。標準偏差が8前後まで広がっています。この大きなスコアをそのまま softmax に入れると何が起きるか——右の図が答えです。スケーリングなし(赤)だと、softmax はたった1つのトークンにほぼ確率1を集中させ、他はほぼ0。これでは「その1つしか見ない」硬直した注目になり、しかも softmax の勾配がほぼ消えて学習が進まなくなります。$\sqrt{d_k}$ で割ってスコアの大きさを抑える(緑)と、なだらかで学習しやすい注目度になります。標準偏差が $\sqrt{d_k}$ 倍に膨らむのを、ちょうど打ち消す割り算なのです。

スコアの大きさが整ったので、これを確率に変換します。それが softmax です。

ステップ②:softmax を「行ごと」にかける

softmax は、実数のベクトルを「すべて正で、合計が1」の確率分布に変換します。Attention では、これをスコア行列の各行に独立にかけます。なぜ行ごとかというと、第 $i$ 行は「トークン $i$ が各トークンに向ける関心」でした。それを合計1に正規化すれば、「トークン $i$ の注目度の配分(どこにどれだけ注意を割くか)」になるからです。

$$ A_{ij} = \frac{\exp(S_{ij})}{\sum_{j’} \exp(S_{ij’})} $$

行ごとに正規化するので、$A$ の各行は横に足すと必ず1になります。この $A$ をアテンション重み行列と呼びます。

softmax後の重み行列A。各行が注目度の分布で横に足すと1

実データの重み行列 $A$ です。先ほどの生スコアが、行ごとにきれいな確率分布に変わりました。「は」の行を見ると「は」自身に0.78が集中し、「犬」の行は「を」に0.64が向いています。各行を横に足すと、どの行も1.00になっているのを確かめてください。形は $(n \times n)$ のまま、中身が「関心の強さ」から「注目度の割合」に変わったわけです。

この重み $A$ が決まれば、あとは仕上げです。この割合で value を混ぜ合わせます。

ステップ③:A×V で value を加重平均する

最後のステップは、注目度 $A$ を重みにして、value ベクトル $V$ を混ぜ合わせることです。式では $O = A V$ とシンプルに書けます。

A×Vで出力Oを作る。各出力行はvalueの加重平均

形を追います。$A$ は $(n \times n)$、$V$ は $(n \times d_v)$。掛けると $(n \times d_v)$ の出力 $O$ になります。ここで $n$ が1つ潰れて、また「行=トークン、列=次元」の形に戻るのが美しいところです。入力 $X$ と同じ「$n$ 行」の形で出てくるので、この操作を何層も積み重ねられるのです。

出力の第 $i$ 行 $o_i$ は、次の加重和です:

$$ o_i = \sum_{j=1}^{n} A_{ij}\, v_j $$

「トークン $i$ の注目度 $A_{ij}$ を重みにして、全トークンの value $v_j$ を平均したもの」。注目度が高いトークンの value ほど強く反映されます。1つの出力行を、実際に分解してみましょう。

犬の出力行が各valueの加重和として作られる様子

左は「犬」が各トークンに向ける注目度($A$ の該当行)です。「を」に0.64、「犬」自身に0.20が向いています。右がその加重和の中身です。$o_{\text{犬}} = 0.07\,v_{私} + 0.01\,v_{は} + 0.20\,v_{犬} + 0.64\,v_{を} + 0.08\,v_{見た}$。つまり「犬」の新しい表現は、強く注目した「を」の value を主成分に、自分自身の value を少し混ぜた合成ベクトルになります。これが「文脈を取り込む」ということの正体です。周りのどのトークンをどれだけ見るかを $A$ が決め、その配合で新しい意味ベクトルを作っているのです。

これで3ステップがすべて出そろいました。最後に、行列の形の流れを1枚にまとめて俯瞰しましょう。

全体を1枚で俯瞰する

Attention全体の行列形状フロー。n×dからn×d_vへ

この図が、Attention の行列変形の全景です。形の流れはこうです:

$$ \underbrace{X}_{n\times d} \;\to\; \underbrace{Q,K,V}_{n\times d_k} \;\xrightarrow{QK^{\top}}\; \underbrace{S}_{n\times n} \;\xrightarrow{\text{softmax}}\; \underbrace{A}_{n\times n} \;\xrightarrow{\times V}\; \underbrace{O}_{n\times d_v} $$

注目すべきは、トークン数 $n$ が最初から最後まで保たれ、次元だけが $d \to d_k \to (n) \to d_v$ と移り変わることです。$QK^{\top}$ でいったん $(n \times n)$ という「トークン対の関係」の世界に移り、$\times V$ でまた「トークン×次元」の世界に戻ってくる。この往復が Attention の骨格です。入力と同じ $(n \times \cdot)$ の形で出てくるからこそ、Transformer はこのブロックを何十層も積み重ねられます。

行列の形さえ追えれば、この式はもう怖くありません。仕上げに、実装での確認と、Multi-Head への一歩を見ておきましょう。

Pythonで確かめる

これまで図で見た計算を、そのままコードで再現します。図と同じ5トークンの例です。

import numpy as np

tokens = ["私", "は", "犬", "を", "見た"]
n, d, dk = len(tokens), 6, 4
rng = np.random.default_rng(7)

X  = rng.normal(0, 1, (n, d))          # 入力 (n×d)
WQ = rng.normal(0, 0.6, (d, dk))       # 重み (d×dk)
WK = rng.normal(0, 0.6, (d, dk))
WV = rng.normal(0, 0.6, (d, dk))

Q, K, V = X @ WQ, X @ WK, X @ WV       # すべて (n×dk)
print("Q, K, V の形:", Q.shape, K.shape, V.shape)

S = (Q @ K.T) / np.sqrt(dk)            # スコア (n×n)
print("スコア S の形:", S.shape)

def softmax_rows(M):                   # 行ごとに softmax
    M = M - M.max(axis=1, keepdims=True)
    e = np.exp(M)
    return e / e.sum(axis=1, keepdims=True)

A = softmax_rows(S)                    # 重み (n×n)
O = A @ V                              # 出力 (n×dv)
print("重み A の形:", A.shape, " 出力 O の形:", O.shape)
print("A の各行の合計:", A.sum(axis=1))   # すべて 1

このコードを実行すると、次のように出力されます。

Q, K, V の形: (5, 4) (5, 4) (5, 4)
スコア S の形: (5, 5)
重み A の形: (5, 5)  出力 O の形: (5, 4)
A の各行の合計: [1. 1. 1. 1. 1.]

形の流れ $(5,4) \to (5,5) \to (5,4)$ が、図で見たとおりに再現できました。重み行列 $A$ の各行の合計がぴったり1になっているのも、softmax を行ごとにかけた証拠です。数式・図・コードの3つが、同じ1つの操作を指していることが確認できます。コードで ASprint してヒートマップにすれば、本記事の図がそのまま得られます。

最後に、実際の Transformer で使われる Multi-Head Attention に一歩だけ踏み込みます。

一歩先へ:Multi-Head も「形の分割」で見える

実際の Transformer は、1つの Attention を単独で使うのではなく、複数のヘッド(head)に分けて並列に行います。これが Multi-Head Attention です。難しそうに聞こえますが、行列の形で見れば「次元 $d$ を分割するだけ」です。

マルチヘッドは次元dをヘッド数で分割して並列にAttentionする

図のように、次元 $d$ をヘッド数 $h$ で割り、$(n \times d_k)$ の小さな $Q,K,V$ を $h$ 組作ります。各ヘッドで独立に本記事の3ステップ($QK^{\top}$ → softmax → $\times V$)を行い、最後に出力を横に連結して元の次元に戻します。ヘッドごとに違う重み行列を持つので、「あるヘッドは文法的な係り受けに、別のヘッドは意味的な関連に注目する」といった役割分担が生まれます。1ヘッドの計算さえ形で追えていれば、Multi-Head は「それを $h$ 回並べて連結する」だけだとわかります。

まとめ

本記事では、Attention の数式を行列の形を追いながら可視化しました。要点を整理します。

  • 行 = トークン:入力 $X$ から出力 $O$ まで、行がトークンに対応する関係は保たれる
  • ステップ①($QK^{\top}$):query と key の内積で、全トークン対の類似度スコア $(n\times n)$ を作る。$S_{ij}$ は「$i$ が $j$ にどれだけ注目するか」
  • スケーリング($/\sqrt{d_k}$):内積の分散が $d_k$ に比例して膨らむのを抑え、softmax の飽和と勾配消失を防ぐ
  • ステップ②(softmax):スコア行列を行ごとに確率へ。各行が合計1の注目度分布 $A$ になる
  • ステップ③($\times V$):注目度を重みに value を加重平均。$o_i = \sum_j A_{ij} v_j$ が新しい文脈表現
  • 形の流れ:$n\times d \to n\times d_k \to n\times n \to n\times d_v$。$n$ は保たれ、次元だけが移り変わる

「難しい式」に見えても、行列の形と各マスの意味を追えば、Attention は「似てる度を測る→正規化する→混ぜる」の3手にすぎません。この見方は、Multi-Head・Cross-Attention・Transformer 全体を理解する土台になります。

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