位置エンコーディングの進化――絶対位置から「RoPE(回転幾何学)」へ。なぜ最新AIはベクトルを『回転』させて語順を理解するのか?
1. 素朴な疑問:単語の順番をシャッフルしても、計算結果が変わらない「Transformerの盲点」
ここに、全く同じ3つの単語で構成された、意味が正反対の2つの文章があります。
- 「犬 が 人 を 噛んだ」 (日常のニュース)
- 「人 が 犬 を 噛んだ」 (大事件のニュース)
人間なら、単語の「並び順(語順)」を見た瞬間に、どちらが加害者でどちらが被害者かを瞬時に見分けます。
しかし、前話(第4〜7話)までで徹底解剖してきたTransformerの心臓部(Self-AttentionとFFN)の数式を、じっくりと思い出してみてください。
Self-Attentionは、すべての単語同士の内積を「一網打尽の総当たり」で計算します。 FFNは、すべての単語を「他の単語を見ずに個別の部屋」で計算します。
つまり、入力する単語の順番をどれほどグチャグチャにシャッフルしても、Transformerの内部で計算される数値の集合は、数学的に1ビットの狂いもなく完全に同一になってしまうのです。この数学的性質を**「置換対称性(Permutation Invariance)」**と呼びます。
このままでは、AIは「主語」と「目的語」の区別すらつかず、文法を理解することなど絶対に不可能です。
「単語の順番」という時間軸の情報を、どうやってモデルに教え込むのか? この課題に対して、AI研究者たちが辿り着いた究極の解答――それこそが、Llama 3やMistralなど現代の全トップモデルを支配する回転の幾何学、**「RoPE(Rotary Position Embedding:回転位置埋め込み)」**です。
2. 第1世代の妥協:絶対位置加算(サイン・コサイン波)の罪
2017年の元祖Transformer論文『Attention Is All You Need』で採用された最初の解決策は、驚くほど力づくで素朴なものでした。
「単語の埋め込みベクトルに、その単語の場所(1番目、2番目…)を表す『位置ベクトル』を、上からそのまま足し算してしまえ!」
単語「cat」が3番目に登場したなら、「cat」の4096次元ベクトルに、「3番目」を表す特別な4096次元ベクトル $p_3$ を直接足し算します($x_{\text{final}} = x + p_3$)。
この位置ベクトル $p_{\text{pos}}$ を作るために、元祖論文では波長の異なる「サイン波($\sin$)」と「コサイン波($\cos$)」を組み合わせた数式(Sinusoidal Positional Encoding)が使われました。
$$PE_{(pos, 2i)} = \sin\left(\frac{pos}{10000^{2i/d}}\right), \quad PE_{(pos, 2i+1)} = \cos\left(\frac{pos}{10000^{2i/d}}\right)$$
なぜ「足し算」は限界を迎えたのか?
この初期のアプローチは、一見うまく機能しました。しかし、モデルが進化し長文を扱うようになるにつれて、2つの深刻な物理的欠陥を露呈します。
欠陥①:意味ベクトルの「物理的汚染」
第3話で学んだ通り、単語の埋め込みベクトルは、高次元空間の中で「意味」を表す繊細な座標です。そこに「位置」という全く異質な数値を力づくで足し算すると、単語本来の意味情報がノイズまみれになって濁ってしまいます。
欠陥②:未知の長さに対する「外挿性の絶望」
これが最大の致命傷でした。 学習時に「最大2048トークン」の長さで学習したモデルに、2049トークン目以降の文章を入力した瞬間、モデルは完全に崩壊します。 モデルにとって「2049番目以降の位置ベクトル」は宇宙で一度も見たことがない未知の数値であり、Softmaxのスコアが異常発散して意味不明な文字列を吐き出し続けるのです。
3. パラダイムシフト:絶対位置から「相対位置(二者の距離)」へ
研究者たちは根本的な問いに立ち返りました。 「そもそも、人間は文章を読むとき、単語の『絶対番地』を意識しているだろうか?」
小説を読んでいるとき、「この単語は、本の先頭から数えて第1,482番目の単語だ」などと意識して読んでいる人はいません。 人間が文脈を理解するために本当に必要なのは、絶対的な位置ではなく、**「二者の相対的な距離関係」**だけです。
【相対位置の本質】
・「この代名詞(it)の【2単語前】に、主語(animal)がある」
・「この動詞(sat)の【すぐ後ろ】に、前置詞(on)がある」
文章の先頭にあろうが、1万文字後ろにあろうが、「2つの単語の間の距離が『2』離れている」という相対的な関係性は全く同一です。
この理想を叶えるため、T5(Relative Position Bias)やALiBi(Attention with Linear Biases)など、アテンションスコア行列に相対距離のペナルティを足し算する手法が次々と考案されました。
しかし、それらの手法にも「推論時のKVキャッシュ(次話で解説)と相性が悪く、計算が重い」という工学的な弱点がありました。
「元の意味ベクトルを汚染せず、メモリを食い潰さず、内積を計算した瞬間に自然と『相対距離($m - n$)』だけが浮かび上がるような、完璧な幾何学的構造は作れないか?」
この難問に、数学の最も美しい道具である**「複素数平面とオイラーの公式」を引っ提げて現れたのが、当時中国の研究者だった蘇剣林(Jianlin Su)らが2021年に発表したRoPE(Rotary Position Embedding)**でした。
4. RoPEの幾何学:足すな、ベクトルを「回転」させろ!
RoPEの着想は、これまでの常識を180度ひっくり返すものでした。 「位置情報を、ベクトルに『足し算』するのをやめろ。位置情報に応じて、ベクトルを『回転』させろ!」
時計の針をイメージしてください。 12時を指している針と、3時を指している針。針の「長さ(ベクトルのノルム=意味の強さ)」は一切変えずに、「角度(向き)」だけをくるりと回すのです。
複素数平面の魔法:オイラーの公式
数学において、2次元平面上のベクトル $(x_1, x_2)$ を、複素数 $z = x_1 + i x_2$ として表現することは広く知られています。 オイラーの公式によれば、この複素数に $e^{i\theta} = \cos\theta + i\sin\theta$ を掛け算することは、**「原点を中心に、角度 $\theta$ だけ反時計回りに回転させること」**と完全に一致します。
RoPEは、この物理を利用します。 位置 $m$ にある単語のベクトルを、**「角度 $m\theta$」**だけ回転させるのです。
なぜ回転させると「相対距離」だけが残るのか?
ここがRoPEの最大の数学的クライマックスです。 第4話で学んだ通り、アテンションの計算とは、位置 $m$ にある Query ベクトル $q$ と、位置 $n$ にある Key ベクトル $k$ の**「内積(ドット積)」**でした。
複素数の世界において、2つのベクトルの内積は、**「一方のベクトルに、もう一方の複素共役(角度をマイナスにしたもの)を掛けること」**に相当します。
位置 $m$ だけ回転させた $q$ と、位置 $n$ だけ回転させた $k$ の内積を計算してみましょう。
$$\text{回転させた } q = q \cdot e^{i m \theta}$$
$$\text{回転させた } k = k \cdot e^{i n \theta}$$
この2つの内積を取ると、指数の足し算・引き算により、奇跡のような現象が起きます。
$$\langle \text{回転 } q, \text{回転 } k \rangle = \text{Re}\left[ (q \cdot e^{i m \theta}) \times (k \cdot e^{i n \theta})^* \right] = \text{Re}\left[ q \cdot k^* \times e^{i (m - n) \theta} \right]$$
数式を見てください。 内積を計算した瞬間、個別の絶対位置 $m$ と $n$ が跡形もなく消滅し、二者の「相対距離($m - n$)」だけが綺麗な角度の差として残ったのです!
- もし2つの単語が全く同じ位置にあれば($m - n = 0$):回転差はゼロ($e^{i 0} = 1$)となり、普通の内積になる。
- もし2つの単語が1単語だけ離れていれば($m - n = 1$):内積には常に「角度 $1\theta$ の回転」がかかる。
- 文章の先頭($m=1, n=2$)であろうが、10万文字後($m=10001, n=10002$)であろうが、差が 1 であれば、内積の値は寸分違わず完全に同じ数値になる!
絶対位置を教え込もうとベクトルを回転させた結果、副産物として完璧な「相対位置関係」が内積の中に自動的に結晶化したのです。
触って試す:回転する位置の体験
🧭 回転する位置の体験
位置ボタンを押すと、その位置 m での回転角 mθ だけベクトルが回転します。左は「速く回るペア(平面0)」、右は「ゆっくり回るペア(平面16)」です。破線は回転前の元のベクトル(0°)です。
平面 0(高速):θ = 1 rad ≒ 57.3°/単語
平面 16(低速):θ = 0.1 rad ≒ 5.7°/単語
まずは位置ボタンを押してみてください。
※実際の平面63では θ ≒ 0.0001 rad とさらに極端にゆっくりで、数千単語進んでも針はほとんど動きません。
5. 高次元への拡張:4096次元を「2次元のペア」に切り分ける
「でも、モデルの隠れ層は2次元ではなく、4096次元や128次元(各ヘッド)の巨大なベクトルのはずだ。どうやって回転させるのか?」
RoPEの実装は極めて実用的です。 128次元のベクトルを、**「2次元の極小平面が64個並んだもの」**としてペアにします。
128次元ベクトル: [ (x0, x1), (x2, x3), (x4, x5), ..., (x126, x127) ]
↓ ↓ ↓ ↓
回転する平面 : 平面 0 平面 1 平面 2 平面 63
そして、それぞれの平面に対して、**異なる回転スピード(周波数 $\theta_i$)**を割り当てます。
$$\theta_i = 10000^{-2(i-1)/d} \quad (i = 1, 2, \dots, d/2)$$
- 手前の次元ペア(高周波): 1単語進むごとに猛スピードで角度が変わります。これにより、モデルは「直前の単語」「2個前の単語」という**局所的なミクロの語順(文法関係)**を鋭敏に嗅ぎ分けます。
- 奥の次元ペア(低周波): 1単語進んでも、針がほとんど動きません。何千単語、何万単語と進んでようやく針が半周します。これにより、モデルは**長大なマクロの文脈(長距離のトピック関係)**を大局的に見失わずに捉え続けることができるのです。
6. なぜV(Value)にはRoPEを掛けないのか?
ここで、実装上の最も重要で本質的な疑問を解消しておきましょう。
コードを見ると、RoPEの回転行列は $Q$(Query)と $K$(Key)には掛け算されていますが、$V$(Value)には一切掛けられていません。
なぜ $V$ には位置情報を与えなくていいのでしょうか?
答えは、「QとKの仕事」と「Vの仕事」の違いにあります。
- $Q$ と $K$ の目的: 「どの単語が、どの位置にある単語に、どれくらい強く注意を向けるべきか」という位置関係の強弱(アテンション重み $A$)を決めることです。ここには絶対に語順(位置)の情報が含まれていなければなりません。
- $V$ の目的: アテンション重み $A$ が決まった後に、「その単語が持っている純粋な意味情報」を相手に手渡すことです。 もし $V$ まで回転させてしまうと、相手に手渡される意味ベクトルそのものが位置によってあらぬ方向へねじ曲げられ、意味が破壊されてしまうのです。
位置を測る「物差し($Q$ と $K$)」だけを回転させ、運ばれる「荷物($V$)」はそのまま大切に運ぶ。 この冷徹な役割分担こそが、RoPEの設計の美しさです。
7. 完全実装:NumPyで作る「最小のRoPE」
それでは、ここまでの理論を完全にコード化しましょう。 複素数の演算ライブラリを使わず、行列演算として直感的に理解できるように、2次元ごとの回転行列を適用するRoPEをNumPyだけで実装します。
ファイル名を min_rope.py として保存してください。
import numpy as np
def precompute_freqs_cis(dim: int, max_seq_len: int, theta: float = 10000.0) -> np.ndarray:
"""
各次元ペアの回転周波数 (cos, sin) を事前計算する
戻り値: Shape [max_seq_len, dim // 2, 2] (最後の次元が [cos, sin])
"""
# dim は偶数である必要がある
assert dim % 2 == 0
# 1. 各次元ペアの周波数 θ_i を計算: 1 / (theta ** (2i / dim))
i = np.arange(0, dim, 2)
freqs = 1.0 / (theta ** (i / dim)) # Shape: [dim // 2]
# 2. 位置インデックス m: [0, 1, ..., max_seq_len - 1]
positions = np.arange(max_seq_len) # Shape: [max_seq_len]
# 3. 外積により、すべての位置と周波数の組み合わせ角度 (m * θ_i) を計算
# angles Shape: [max_seq_len, dim // 2]
angles = np.outer(positions, freqs)
# 4. cos と sin を計算してスタック
# Shape: [max_seq_len, dim // 2, 2]
cos_sin = np.stack([np.cos(angles), np.sin(angles)], axis=-1)
return cos_sin
def apply_rotary_emb(x: np.ndarray, cos_sin: np.ndarray) -> np.ndarray:
"""
ベクトル x に対して RoPE(回転)を適用する
x: [Batch, Seq_Len, Dim]
cos_sin: [Seq_Len, Dim // 2, 2]
"""
B, S, D = x.shape
# 1. ベクトルを2次元ずつのペアに分解: [B, S, D // 2, 2]
x_pairs = x.reshape(B, S, D // 2, 2)
x1 = x_pairs[..., 0] # 各ペアの第1成分
x2 = x_pairs[..., 1] # 各ペアの第2成分
# 2. 事前計算した cos と sin を取得
cos = cos_sin[:S, :, 0] # [S, D // 2]
sin = cos_sin[:S, :, 1] # [S, D // 2]
# ブロードキャスト用に次元を追加: [1, S, D // 2]
cos = np.expand_dims(cos, axis=0)
sin = np.expand_dims(sin, axis=0)
# 3. 2次元の回転行列の掛け算:
# [ x1' ] = [ cos -sin ] [ x1 ] = [ x1*cos - x2*sin ]
# [ x2' ] [ sin cos ] [ x2 ] [ x1*sin + x2*cos ]
x1_rot = x1 * cos - x2 * sin
x2_rot = x1 * sin + x2 * cos
# 4. 元の [B, S, D] の形に再結合
x_rot = np.stack([x1_rot, x2_rot], axis=-1)
return x_rot.reshape(B, S, D)
# -------------------------------------------------------------
# 実験:内積における「相対距離の保存」を数学的に証明する
# -------------------------------------------------------------
if __name__ == "__main__":
dim = 4 # 4次元ベクトル(2つの回転平面)
max_len = 100 # 最大系列長
# 回転テーブルの生成
freqs_table = precompute_freqs_cis(dim=dim, max_seq_len=max_len)
# 2つのランダムな基本ベクトル q と k
np.random.seed(42)
q_base = np.random.randn(1, 1, dim)
k_base = np.random.randn(1, 1, dim)
print("=== 実験:異なる絶対位置での内積比較 ===")
print("二者の相対距離(差)を常に『3』に固定して位置をスライドさせる\n")
# ケースA: 位置 m=5 と 位置 n=2 (差は 3)
q_pos5 = apply_rotary_emb(q_base, freqs_table[5:6])
k_pos2 = apply_rotary_emb(k_base, freqs_table[2:3])
dot_A = np.sum(q_pos5 * k_pos2)
print(f"ケースA (位置 5 と 位置 2 の内積 / 距離 3): {dot_A:.8f}")
# ケースB: 位置 m=53 と 位置 n=50 (差は 3)
q_pos53 = apply_rotary_emb(q_base, freqs_table)
k_pos50 = apply_rotary_emb(k_base, freqs_table)
dot_B = np.sum(q_pos53 * k_pos50)
print(f"ケースB (位置 53 と 位置 50 の内積 / 距離 3): {dot_B:.8f}")
# ケースC: 位置 m=98 と 位置 n=95 (差は 3)
q_pos98 = apply_rotary_emb(q_base, freqs_table)
k_pos95 = apply_rotary_emb(k_base, freqs_table)
dot_C = np.sum(q_pos98 * k_pos95)
print(f"ケースC (位置 98 と 位置 95 の内積 / 距離 3): {dot_C:.8f}")
# 差分の検証
diff_AB = abs(dot_A - dot_B)
diff_AC = abs(dot_A - dot_C)
print(f"\n【検証結果】")
print(f"ケースAとBの差: {diff_AB:.1e}")
print(f"ケースAとCの差: {diff_AC:.1e}")
print("-> 浮動小数点誤差を除き、内積値は『完全に一致』しました!")
実行結果
=== 実験:異なる絶対位置での内積比較 ===
二者の相対距離(差)を常に『3』に固定して位置をスライドさせる
ケースA (位置 5 と 位置 2 の内積 / 距離 3): -0.46747124
ケースB (位置 53 と 位置 50 の内積 / 距離 3): -0.46747124
ケースC (位置 98 と 位置 95 の内積 / 距離 3): -0.46747124
【検証結果】
ケースAとBの差: 0.0e+00
ケースAとCの差: 0.0e+00
-> 浮動小数点誤差を除き、内積値は『完全に一致』しました!
出力結果を見てください。
文章の序盤(位置5と2)で計算した内積も、中盤(位置53と50)で計算した内積も、終盤(位置98と95)で計算した内積も、-0.46747124 という小数点以下8桁まで全く同一の数値になりました。
絶対位置がどれほど大きくなろうとも、相対的な距離が「3」であれば、アテンション回路は全く同じ注意の強さを向ける。 数式の上だけでなく、実際のNumPyコード上でも、相対位置の保存が完璧に成立していることが物理的に証明された瞬間です。
8. コンテキスト長100万への跳躍:RoPEのスケーリング物理
RoPEが現代のAI界にもたらした最大の恩恵は、「超長文コンテキスト(128k〜100万トークン)への拡張」が極めて容易になったことです。
昔の絶対位置エンコーディングでは、学習長を超えた瞬間に破綻していました。 しかし、RoPEにおける位置情報は単なる「角度」です。
「もし4096トークンで学習したモデルを、32,000トークンまで伸ばしたいなら、どうすればいいか?」 答えはシンプルです。**「回転の針が進むスピード(周波数)を、あらかじめ $\frac{1}{8}$ にゆっくり回してやればいい」**のです。
時計の針の進みをスローモーションにして、32kトークン進んだところでちょうど針が1周するように調整する(Position Interpolation)。 さらに最新工学では、局所的な文法を司る高周波はそのまま維持し、遠距離を司る低周波だけを圧縮する**「NTK-aware Scaling」や「YaRN」**といった高度な周波数変調技術が開発されました。
この回転幾何学の柔軟性があったからこそ、現代のLLMは「本1冊」「コードベース全体」を一度にプロンプトとして飲み込む超長文コンテキストを実現できたのです。
9. まとめ:RoPEとは「高次元空間で時を刻む羅針盤」である
RoPE(回転位置埋め込み)の真実を総括すると、以下のようになります。
- 置換対称性の呪縛: Transformerは本質的に語順を理解できないため、何らかの方法で位置情報を外部から注入しなければならない。
- 足し算から回転へ: ベクトルに位置を「足す」と意味が汚染されるが、2次元ごとに「回転」させればベクトルのノルム(意味の強さ)を一切傷つけない。
- 内積における相対性の奇跡: $Q$ と $K$ を回転させて内積を取ると、オイラーの公式の性質により絶対位置 $m, n$ が消滅し、相対距離 $(m - n)$ だけが美しく残る。
- $V$ の聖域: 視線の強弱を決める $Q$ と $K$ だけを回転させ、運ばれる意味そのものである $V$ は一切回転させない。
単語たちは、埋め込み層で意味の座標を獲得し、RoPEによって時間の羅針盤を手に入れました。 これで、文章を「理解」するためのすべての準備は完全に整いました。
しかし、この完成されたTransformerを実際に手元のPCやサーバーで動かし、文章を「生成」させようとした瞬間、エンジニアは実務上の最も過酷な物理の壁に激突します。
「なぜChatGPTは、文章を書き出すときに1文字ずつポツポツとしか出せないのか?」 「なぜ会話が長くなると、GPUメモリが激しく枯渇し、速度が低下するのか?」
その原因である、推論時の巨大なメモリ食い虫――**「KVキャッシュ」の正体。 次回、「第9話:KVキャッシュと推論のメモリ壁――なぜChatGPTは1文字ずつしか出せないのか?」**へと進みましょう。
📝 3行で頭に入る『試験・実務の要点まとめ』
- 置換対称性とRoPEの解: 単語の順序を認識できないTransformerに対し、ベクトルを2次元ずつペアにして位置 $m$ に応じた角度 $m\theta$ で回転させることで語順を埋め込む。
- 相対距離の数理的出現: 回転させた $Q$ と $K$ の内積を計算すると絶対位置 $m, n$ が相殺され、二者の相対距離 $(m - n)$ の角度差だけが自然とアテンションスコアに残る。
- Q・K限定の適用と長文外挿: 意味を運ぶ $V$ は回転させず $Q$ と $K$ のみに適用する。周波数 $\theta$ を変調(NTK-aware等)することで、学習長を超える10万トークン超への外挿・拡張を可能にする。