位置エンコーディング 入門
自己注意は系列を集合として扱う:トークンをシャッフルしても、 各々の注意出力は変わらない。言語は集合ではない —— 語順が肝心。 Transformer は各トークンに位置情報を注入することで この問題を解く。短い 4 トピック:なぜ注意は順序を知らないのか;正弦余弦エンコーディング(元の Transformer 論文);学習型位置埋め込み(GPT-2);そしてRoPE(回転位置エンコーディング)—— Llama、Qwen、DeepSeek、 現代の主要オープン LLM が皆採用する方式。
なぜ注意は順序を知らない
入力トークンを並べ替えると、出力もそのまま並べ替わる。注意は系列ではなく、ベクトルの袋を見ている。
ここまで、注意は cat が sat の前にあると「知っている」かの ように語ってきた。式 softmax(Q · Kᵀ / √d_k) · V を見直そう。 そこに無いものに注目:位置インデックス。内積 Q[i]·K[j] は 2 つのトークンの内容を使う。文中のどこかは使わない。結果として注意は置換等変(permutation equivariance)を持つ:入力を並べ替えると、 出力も同じく並べ替わって出るが、ベクトル自体は変わらない。
「the cat sat」で具体的に:モデルに 「cat sat」と 「sat cat」を 与えても、注意は全く同じ出力ベクトル対を作る —— ただ別のトークンに 紐付くだけ。言語モデルとしては破滅的。「犬が人を噛んだ」と「人が犬を噛んだ」は別の表現でなければ、何もできない。
単純な解決策。入力が注意に入る前に、各トークンに位置に依存するベクトルを加える。つまり注意の入力は
x[i] = token_embedding(token_i) + position_encoding(i)
これで注意の入力は純粋なトークン内容ではなくなる —— 位置がベクトルに焼き込まれた。 後続の Q、K、V 射影もこの情報を継承する。注意自体は位置インデックスを読まないが、 各スロットで見る内容に「ここは系列のどこか」が既に符号化されている。
良い位置エンコーディングの条件は?一般に求められる性質:
- 位置毎に一意。同じ見た目の位置があってはならない —— エンコーディングは情報を持つべき。
- 値が有界。位置と共に無限に増えると、訓練が不安定になる。
- 距離を意識。理想的には近い位置は似た符号、遠い位置は 異なる符号にする —— 「3 トークン前」のような抽象を全ての絶対位置ペアで ゼロから記憶しなくて済む。
- 外挿性。訓練で長さ 1024 までしか見ていなくても、推論時に 2048 を扱えるか? 後の 3 方式の差が最も顕著に出る性質。
次の 3 節は「有用な位置エンコーディングをどう作るか」への 3 つの答え —— 正弦余弦(賢い数学、学習パラメータなし)、学習型(検索すれば良い)、 そして RoPE(現代的折衷、両者の利点を取る)。
正弦余弦エンコーディング(元の Transformer)
手設計の多周波数 sin/cos パターン。学習パラメータゼロ。
2017 年の「Attention Is All You Need」論文は、固定された、学習不要の位置 エンコーディングを提案した。完全に sin と cos で構成される。式:
PE(pos, 2i) = sin( pos / 10000^(2i / d_model) ) PE(pos, 2i+1) = cos( pos / 10000^(2i / d_model) )
各位置 pos ∈ [0, max_len)、各次元 d ∈ [0, d_model) で 1 つの数値 —— pos · freq の sin か cos、周波数は次元インデックスに依存。 次元ペア (2i, 2i+1) が 1 つの周波数を共有:一方が sin、もう一方が cos。
鍵となる洞察は多スケール周波数。低次元(前の列)は速く振動 —— 隣接位置を区別する。 高次元(後ろの列)は遅く振動 —— 長距離でしか位置を区別しない。 合わせて、各位置の d_model 次元ベクトル全体が一意な 「多周波数の署名」となる —— 整数の二進数符号化と発想は同じ、ビットの代わりに sin/cos を使う。
なぜこの式?2 つの良い性質:
- 線形結合で相対位置を符号化。三角恒等式sin(a + b) = sin a cos b + cos a sin b により、 位置 pos + k の符号は位置 pos の符号の固定線形変換として書ける。 原則として、モデルは 1 つの重み行列で「注意を k 位置ずらす」層を学習できる。
- 最大長なし。式は任意の位置で定義される、訓練で見たより遥かに 大きい位置も含めて。実際、正弦余弦はある程度長い系列に外挿できる —— ただし理想的にではない、モデルが見たことのない遠方の位置の解釈をまだ 学習する必要があるから。
使い方。エンコーディングはトークン埋め込みに単純に加算、 その後最初の注意層へ:
x[pos] = embed(token_pos) + PE[pos] // 同形状、要素ごとの和
機構はそれだけ。あとは注意が通常通り進む。位置情報は各 x[pos] ベクトルに 同乗して運ばれる。注意計算内部に特別な処理はない —— 順序を知らない操作のまま、 ただし入力ベクトルがもはや順序を無視しない。
難点。位置を内容に足すのは少し粗い。 加算後にモデルが「このベクトルの cat 性」と「このベクトルの位置 3 性」を 分離できることを仮定する。実際は動くが、新しい手法(RoPE、ALiBi)が 改善を試みる設計選択でもある。
学習型位置埋め込み(GPT-2)
各位置に独自の埋め込み行。各行をどうするかはオプティマイザに任せる。
BERT、GPT-2、その他多くの初期 Transformer は正弦余弦の式を捨て、 遥かに素朴な方法を採った:位置をトークンと全く同じに扱う。(max_len × d_model) の学習型埋め込み表を作り、各行が 1 位置の符号。 位置インデックスで検索。終わり。
P = nn.Embedding(max_len, d_model) // 学習可能パラメータ x[pos] = embed(token_pos) + P[pos] // 加える、正弦余弦と同じ
データ構造は単語埋め込み表と完全に同じ —— 呼び出しも同じ、「語彙」が位置に 変わるだけ。訓練時、勾配降下が各行を「位置 p について言語モデリング タスクに有用な何か」に成形する。モデルは自分で「位置の言語」を解明する。 手作りの式は不要。
うまくいく点。2 つ、いずれも実質的。
- 単純さ。単語埋め込みを実装した人は、位置埋め込みを既に 実装している。三角関数、周波数スケジュール、考える必要なし。
- タスクへの最適適合。「位置は正弦余弦のように見えるべき」 という設計者の仮定を強要されない。データに対し最も有用な位置パターンを 学習できる。
うまくいかない点。1 つは破滅的、もう 1 つは小さい。
- 外挿不可、完全に。表は厳密に max_len 行。 訓練が max_len = 1024 で、推論で 1025 トークンの系列を渡すと、 検索する行がない。位置 1024 は行を持ったことがなく、勾配を受けたことも ない。訓練時も配列の境界外エラーになる。この性質 1 つ —— 訓練で見た以上の系列長を扱えないこと —— が学習型位置埋め込みから 分野を引き離した。
- 距離構造の組み込みなし。位置 100 と位置 101 は隣接。 正弦余弦の式は低周波数次元でこれらの符号をほぼ同一にする。学習型の表は 単に 2 つの任意の行 —— パラメータ化に「これらは似ているべき」と言うものはない。 実際にはモデルは滑らかな行を学ぶことが多いが、それは訓練の副産物であり 組み込み帰納バイアスではない。
現在誰が使う?多くない。BERT と GPT-2 は使った、それらを 継承するファインチューンも。だが長文脈を扱いたいモデル —— 今や「真面目な LLM のほぼすべて」—— は相対位置符号化か RoPE に移行した。
RoPE —— 回転位置エンコーディング
位置を足さない。Q と K を位置で決まる角度で回す。内積が自然に相対的になる。
前 2 方式は何事も起こる前にトークン埋め込みに位置ベクトルを加える。RoPE(Su 等、2021)は全く別の角度から:トークン埋め込みには 触れない。代わりに注意計算の中で Q と K に回転を適用する。 回転角はトークンの位置に依存。それだけ。
具体的に:Q(と K)の連続する次元ペア (2i, 2i+1) を 2 次元ベクトル として扱う。位置 p のトークンでは、この 2D ベクトルを p · θ_i だけ回転、ペアごとの角周波数は
θ_i = 1 / 10000^(2i / d_model)
(そう、正弦余弦と同じ多周波数スケジュール、用途を変えて)。 1 ペアの回転行列は標準 2D 回転:
[q'_2i ] [cos(p·θ_i) -sin(p·θ_i)] [q_2i ] [q'_2i+1] = [sin(p·θ_i) cos(p·θ_i)] [q_2i+1]
これが重要な理由。回転された 2 つの 2D ベクトルの内積には 美しい性質がある:同じ角で両方を回しても内積は不変、異なる角で回せば内積は両角度の差の関数となる。 Q が位置 m、K が位置 n なら:
⟨ rot(Q, m·θ) , rot(K, n·θ) ⟩ = (m − n)·θ の何らかの関数
RoPE 後の注意スコアは相対位置のみに依存、絶対位置には依存しない。 モデルは「位置 4378」が単独で何を意味するかを学習する必要がない —— 位置差しか 見ない。これこそ元の Transformer の設計者がモデルに自然と学習させたかった 帰納バイアスで、今は数学に直接焼き込まれている。
なぜ皆 RoPE を使うのか。いくつか理由:
- 相対位置がタダで手に入る。追加パラメータなし、追加計算なし —— Q と K に回転を適用するだけ。各注意ヘッドが自動的にスコアで相対位置を見る。
- 長文脈への汎化。固定の検索テーブルがないので、RoPE は原則 任意の位置で動く。小さな内挿の工夫(Position Interpolation、NTK-aware scaling、 YaRN)を合わせれば、4K トークンで訓練したモデルを短い fine-tune だけで 32K、128K、1M に拡張可能。
- Q と K のみ、V にはかからない。地味だが有用な非対称: V は回転されないので、出力に混ぜ込まれる「内容」自体に位置情報は直接乗らない。 位置は誰が誰に注意するかのみに作用し、渡される内容には作用 しない。値の経路に位置を加える方式より綺麗。
- 経験的に単純に良い。2021 年の RoPE 論文では利得は控えめ、 だが長文脈時代の到来でその座は決まった —— 2022 年以降、ほぼ全てのオープン ソース LLM が採用。Llama 1、2、3。Qwen。Mistral。DeepSeek。PaLM。Gemma。 ほぼ網羅。
全体像、もう一度。各注意層の内側:
Q = x · W_Q, K = x · W_K, V = x · W_V Q ← RoPE(Q, positions) K ← RoPE(K, positions) A = softmax( Q · Kᵀ / √d_k ) out = A · V
2 行追加、新パラメータなし。結果は相対位置をネイティブに理解する注意ブロック、 max-length が焼き込まれず、軽い調整で優雅に外挿できる。 だから現代の全オープン LLM はこれを使う。
たどり着いた地点。19 個目の primer で、現代 Transformer の 注意ブロック 1 つ分のデータパスを端から端まで組み上げた:トークン化 → 埋め込み → RoPE 回転済み Q/K のヘッド分割 → スケール付き内積注意 → concat + W_O。次の primer は Transformer ブロックの残り —— layer norm、 残差接続、注意を取り囲む前向き MLP。