位置エンコーディング 入門

自己注意は系列を集合として扱う:トークンをシャッフルしても、 各々の注意出力は変わらない。言語は集合ではない —— 語順が肝心。 Transformer は各トークンに位置情報を注入することで この問題を解く。短い 4 トピック:なぜ注意は順序を知らないのか;正弦余弦エンコーディング(元の Transformer 論文);学習型位置埋め込み(GPT-2);そしてRoPE(回転位置エンコーディング)—— Llama、Qwen、DeepSeek、 現代の主要オープン LLM が皆採用する方式。

01

なぜ注意は順序を知らない

入力トークンを並べ替えると、出力もそのまま並べ替わる。注意は系列ではなく、ベクトルの袋を見ている。

ここまで、注意は catsat の前にあると「知っている」かの ように語ってきた。式 softmax(Q · Kᵀ / √d_k) · V を見直そう。 そこに無いものに注目:位置インデックス。内積 Q[i]·K[j] は 2 つのトークンの内容を使う。文中のどこかは使わない。結果として注意は置換等変(permutation equivariance)を持つ:入力を並べ替えると、 出力も同じく並べ替わって出るが、ベクトル自体は変わらない。

トークンを並べ替え —— 注意は区別できない入力:「cat sat」inputcatpos 00.800.300.500.20satpos 10.100.600.900.40元の順序の 2 トークン埋め込み。
1 / 4
位置情報がないと、自己注意は置換等変:同じトークンを任意の順序で入れても、同じ出力(順序が入れ替わるだけ)。順序は不可視。

「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(現代的折衷、両者の利点を取る)。

02

正弦余弦エンコーディング(元の 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。

正弦余弦位置エンコーディング —— pos × dim ヒートマップ空のグリッド —— 12 位置 × 8 次元dim →pos ↓0123456701234567891011PE(pos, dim) = sin か cos、引数は pos · freq(dim) で埋める。
1 / 4
各 (pos, dim) セルは sin か cos、周波数は dim に依存。隣接する 2 dim は同周波数(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)が 改善を試みる設計選択でもある。

03

学習型位置埋め込み(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]   // 加える、正弦余弦と同じ
学習型位置埋め込み —— 検索テーブル空のテーブル —— max_len 行 × d_model 列P(max_len × d_model)01234567訓練で見える可能性のある各位置に 1 行を確保。
1 / 4
単語埋め込みと同じ発想:nn.Embedding(max_len, d_model)。各位置に独自の学習済みベクトル。実装は単純。max_len を超えて外挿不可。

データ構造は単語埋め込み表と完全に同じ —— 呼び出しも同じ、「語彙」が位置に 変わるだけ。訓練時、勾配降下が各行を「位置 p について言語モデリング タスクに有用な何か」に成形する。モデルは自分で「位置の言語」を解明する。 手作りの式は不要。

うまくいく点。2 つ、いずれも実質的。

  • 単純さ。単語埋め込みを実装した人は、位置埋め込みを既に 実装している。三角関数、周波数スケジュール、考える必要なし。
  • タスクへの最適適合。「位置は正弦余弦のように見えるべき」 という設計者の仮定を強要されない。データに対し最も有用な位置パターンを 学習できる。

うまくいかない点。1 つは破滅的、もう 1 つは小さい。

  • 外挿不可、完全に。表は厳密に max_len 行。 訓練が max_len = 1024 で、推論で 1025 トークンの系列を渡すと、 検索する行がない。位置 1024 は行を持ったことがなく、勾配を受けたことも ない。訓練時も配列の境界外エラーになる。この性質 1 つ —— 訓練で見た以上の系列長を扱えないこと —— が学習型位置埋め込みから 分野を引き離した。
  • 距離構造の組み込みなし。位置 100 と位置 101 は隣接。 正弦余弦の式は低周波数次元でこれらの符号をほぼ同一にする。学習型の表は 単に 2 つの任意の行 —— パラメータ化に「これらは似ているべき」と言うものはない。 実際にはモデルは滑らかな行を学ぶことが多いが、それは訓練の副産物であり 組み込み帰納バイアスではない。

現在誰が使う?多くない。BERT と GPT-2 は使った、それらを 継承するファインチューンも。だが長文脈を扱いたいモデル —— 今や「真面目な LLM のほぼすべて」—— は相対位置符号化か RoPE に移行した。

04

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]
RoPE —— Q(と K)を p · θ で 2D ペア毎に回転pos 0 の Q —— 回転なしdim 2idim 2i+1QQ のある 2D ペア。pos 0 で +x 軸に沿わせて描く。
1 / 4
(2i, 2i+1) 各次元ペアを 2D ベクトルとして角度 p · θ_i で回転。回転後、⟨Q_m, K_n⟩ は (m − n) のみに依存 —— 相対位置が注意スコアに直接組み込まれる。

これが重要な理由。回転された 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。