相対位置エンコーディングの理論 — Shaw et al.からTransformer-XLまで、相対位置の系譜

Transformerに位置情報を与える方法として、sin/cosによる絶対位置エンコーディングを学びました。各トークンに「あなたは何番目です」というラベルを貼る方法です。しかし、言語を理解するうえで本当に重要なのは、「このトークンは系列の5番目にある」という絶対的な位置でしょうか?

日本語の「私は昨日公園で犬を散歩させた」という文を考えてみましょう。「昨日」が文の2番目にあるか3番目にあるかは、文の意味にほとんど影響しません。それよりも、「犬を」と「散歩させた」が隣り合っている(1トークン離れている)という相対的な近さの方が、構文を理解するうえではるかに重要です。

この「絶対位置ではなく相対位置こそが重要だ」という洞察を数学的に形式化したのが相対位置エンコーディング(Relative Position Encoding)です。

相対位置エンコーディングを理解すると、以下のことが見えてきます。

  • 長文処理への道: 訓練時の系列長を超えるテキストを処理する「外挿性」の原理と限界
  • 最新LLMの設計思想: GPT系やLLaMAで使われるRoPEやALiBiが、Shaw et al.の相対位置の発想をどう進化させたか
  • Attention機構の本質: 位置情報がAttentionスコアにどう影響するかの数学的構造
  • モデル設計の選択指針: タスクに応じて最適な位置エンコーディングを選ぶための判断軸

本記事の内容

  • 絶対位置エンコーディングの限界と相対位置の動機
  • Shaw et al. (2018) の相対位置表現 — クリッピング付き相対位置
  • Transformer-XL (2019) の4項分解 — セグメント再帰と相対位置の融合
  • T5 (2020) の相対位置バイアス — バケット化と学習可能バイアス
  • 各手法の比較(Shaw, Transformer-XL, T5, RoPE, ALiBi)
  • Pythonでの実装と注意パターンの可視化

前提知識

この記事を読む前に、以下の記事を読んでおくと理解が深まります。

絶対位置エンコーディングの限界

「何番目か」は本当に必要な情報か

位置エンコーディングの理論で学んだsin/cos位置エンコーディングでは、位置 $\text{pos}$ に対して固定のベクトル $\text{PE}(\text{pos})$ を割り当て、トークン埋め込みに加算しました。

$$ \bm{z}_i = \bm{x}_i + \text{PE}(i) $$

この方式は直感的で実装も容易ですが、いくつかの本質的な限界を抱えています。

限界1: 外挿性の欠如

学習可能な位置埋め込み(BERT, GPTなど)では、最大系列長 $L_\text{max}$ に対応する位置埋め込み行列 $\bm{E}_\text{pos} \in \mathbb{R}^{L_\text{max} \times d}$ を用意します。$L_\text{max} = 512$ で訓練したモデルに513番目のトークンを入力すると、対応する位置ベクトルが存在しないため、そのまま使えません。

sin/cos方式は理論上は任意の位置に対して値を計算できますが、実際には訓練時に見たことのない位置のパターンに対してモデルがうまく汎化できないことが知られています。

限界2: 絶対位置への過適合

「主語は1番目」「動詞は2番目」のように、特定の絶対位置とトークンの役割を結びつけて学習してしまう傾向があります。しかし実際の文では主語が必ず文頭に来るとは限りません。重要なのは主語と動詞の相対的な位置関係です。

限界3: 加算による情報の混在

位置エンコーディングをトークン埋め込みに加算するため、意味情報と位置情報が混ざります。これはSelf-Attentionの理論で見たように、Attentionスコアの計算で「意味的な類似度」と「位置的な関係」が分離できないことを意味します。

相対位置の直感

これらの限界を踏まえると、自然な発想が生まれます。「各トークンの絶対位置を覚えるのではなく、トークン間の距離をAttentionに教えればよいのではないか?」

本棚の本を探すことを想像してください。「左から17冊目の本」(絶対位置)と言われるよりも、「赤い本の2冊右隣にある本」(相対位置)と言われた方が、本棚が長くなっても通用します。本棚の長さに関係なく「2冊右隣」という距離関係は変わりません。

相対位置エンコーディングの核心は、Attentionスコア $\bm{q}_i^\top \bm{k}_j$ の計算に、絶対位置 $i, j$ そのものではなく、相対距離 $i – j$ の情報を組み込むことです。これにより、訓練時の系列長を超えた位置にも自然に一般化できる可能性が生まれます。

絶対位置と相対位置の概念比較

上の図は、同じ文に対する2つの見方を対比しています。上段の絶対位置は「各トークンが何番目か」という固定ラベルで、文頭がずれると全ての番号が変わってしまいます。下段の相対位置は「散歩」を基準にした距離($+0, +1, +2, \dots$)で、近傍($\pm 2$)を強調しています。文がどこから始まろうと「犬を」と「散歩」が距離1で隣り合うという関係は保たれます。これが相対位置の頑健さです。

この発想を最初に厳密に定式化したのがShaw et al. (2018)です。次のセクションでは、その数学的な構成を詳しく見ていきます。

Shaw et al. (2018) の相対位置表現

基本アイデア — Attentionに距離を教える

Shaw et al. (2018) “Self-Attention with Relative Position Representations” は、相対位置エンコーディングを明示的に定式化した最初の重要な論文です。

標準的なSelf-Attentionでは、位置 $i$ のQueryと位置 $j$ のKeyの内積 $\bm{q}_i^\top \bm{k}_j$ でAttentionスコアを計算します。Shaw et al.のアイデアは、ここに相対位置を表す学習可能なベクトルを追加することです。

具体的には、位置 $i$ から位置 $j$ への相対位置を表す2種類のベクトルを導入します。

  • $\bm{a}_{ij}^K \in \mathbb{R}^{d_k}$: Key側の相対位置ベクトル(Attentionスコアに影響)
  • $\bm{a}_{ij}^V \in \mathbb{R}^{d_v}$: Value側の相対位置ベクトル(出力表現に影響)

これらは相対距離 $i – j$ のみに依存します。つまり $\bm{a}_{ij}^K = \bm{a}_{i-j}^K$ です。

数学的定式化

Attentionスコアの変更

標準のAttentionスコアに相対位置ベクトルとの内積を追加します。

$$ e_{ij} = \frac{\bm{q}_i^\top \bm{k}_j + \bm{q}_i^\top \bm{a}_{ij}^K}{\sqrt{d_k}} $$

第1項 $\bm{q}_i^\top \bm{k}_j$ は通常の「内容ベースの注意」、第2項 $\bm{q}_i^\top \bm{a}_{ij}^K$ は「位置ベースの注意」です。Queryが「どの内容に注目するか」だけでなく、「どの距離のトークンに注目するか」も学習できるようになります。

Softmaxでスコアを正規化します。

$$ \alpha_{ij} = \frac{\exp(e_{ij})}{\sum_{k=1}^{n} \exp(e_{ik})} $$

出力の変更

出力の計算にも相対位置ベクトルを加えます。

$$ \bm{o}_i = \sum_{j=1}^{n} \alpha_{ij} (\bm{v}_j + \bm{a}_{ij}^V) $$

Valueベクトル $\bm{v}_j$ に相対位置ベクトル $\bm{a}_{ij}^V$ を加算してから重み付き和を取ります。これにより、出力が「何が注目されたか」(内容)と「どの距離のトークンが注目されたか」(位置)の両方を反映します。

Shaw方式の相対位置ベクトル注入の図解

この図がShaw方式の全体像です。上段が注意スコア、下段が出力の計算で、いずれも「内容の項」に「位置の項」を足し算する構造になっています。注目したいのは、相対位置ベクトルがKey側($\bm{a}^K$、スコアに影響)とValue側($\bm{a}^V$、出力に影響)の2か所に入る点です。Value側にも位置情報を残すのはShaw方式の独自色で、後続のTransformer-XLやT5はスコア側だけに絞って簡素化していきます。

クリッピング — パラメータ数の制御

全ての相対距離 $i – j$ に対して個別のベクトルを学習すると、系列長 $n$ に対して $2n – 1$ 個のベクトルが必要になり、長い系列ではパラメータが膨大になります。

Shaw et al.は、相対距離を最大値 $k$ でクリッピング(切り詰め)する方法を提案しました。

$$ \bm{a}_{ij}^K = \bm{w}_{\text{clip}(i-j, k)}^K $$

ここでクリッピング関数は次のように定義されます。

$$ \text{clip}(x, k) = \max(-k, \min(k, x)) $$

つまり相対距離が $-k$ より小さければ $-k$、$k$ より大きければ $k$ に丸めます。これにより、学習すべきベクトルの数は $2k + 1$ 個に固定されます。

相対距離クリッピングclip関数の図

クリッピング関数の形を図にしました(ここでは $k=8$)。中央の $-k \le i-j \le k$ の範囲では生の距離がそのまま保たれ(1対1対応)、外側では水平に飽和します。赤い網掛けの領域では、たとえば距離10も距離20も同じ「$+8$」に丸められ、区別されなくなります。近傍は細かく、遠方は「遠い」とだけ分かればよい、という割り切りが一目でわかります。

この設計の背景には、「十分に離れたトークン間では、正確な距離よりも『遠い』という情報だけで十分である」という直感があります。論文では $k = 16$ 程度でよい結果が得られることが報告されています。たとえば20トークン離れているか30トークン離れているかの区別は、多くのタスクでは重要ではありません。

Shaw方式の全体像

Shaw et al.の方式をまとめると、Attentionの計算は以下のようになります。

まず、標準のQuery・Key・Valueを計算します。

$$ \bm{q}_i = \bm{x}_i \bm{W}_Q, \quad \bm{k}_j = \bm{x}_j \bm{W}_K, \quad \bm{v}_j = \bm{x}_j \bm{W}_V $$

次に、相対位置ベクトルを用いてスコアと出力を計算します。

$$ e_{ij} = \frac{\bm{q}_i^\top \bm{k}_j + \bm{q}_i^\top \bm{w}_{\text{clip}(i-j, k)}^K}{\sqrt{d_k}} $$

$$ \bm{o}_i = \sum_{j=1}^{n} \alpha_{ij} \left(\bm{v}_j + \bm{w}_{\text{clip}(i-j, k)}^V\right) $$

学習可能なパラメータは、元のAttentionのパラメータ $\bm{W}_Q, \bm{W}_K, \bm{W}_V$ に加えて、相対位置ベクトル $\bm{w}_{-k}^K, \ldots, \bm{w}_k^K$ と $\bm{w}_{-k}^V, \ldots, \bm{w}_k^V$ です。

Shaw et al.はシンプルかつ効果的な手法ですが、相対位置ベクトルとQueryの内積を全ペアで計算する必要があり、計算量が増加します。Transformer-XLでは、この方式をさらに洗練し、効率的な計算を可能にする分解を導入しました。次にその詳細を見ていきましょう。

Transformer-XL の相対位置 — 4項分解

セグメント再帰と位置情報の問題

Transformer-XL(Dai et al., 2019)”Transformer-XL: Attentive Language Models Beyond a Fixed-Length Context” は、長い系列を処理するためにセグメント再帰(segment-level recurrence)を導入したモデルです。

入力系列を固定長のセグメントに分割し、前のセグメントの隠れ状態をキャッシュして次のセグメントの処理に再利用します。これにより、固定長のAttention窓を超えた長距離の文脈を利用できます。

しかし、ここで絶対位置エンコーディングの問題が顕在化します。たとえば、セグメント1の位置0とセグメント2の位置0に同じ位置エンコーディング $\text{PE}(0)$ が割り当てられてしまいます。モデルから見ると、本来は異なる位置にあるトークンが同じ位置情報を持つことになり、区別ができません。

この問題を解決するために、Transformer-XLは絶対位置エンコーディングを完全に廃止し、相対位置エンコーディングを採用しました。

Attentionスコアの4項分解

Transformer-XLの相対位置エンコーディングを理解するために、まず絶対位置エンコーディングを使った標準的なAttentionスコアを展開してみましょう。

入力として、トークン埋め込み $\bm{E}_{x_i}$ に絶対位置エンコーディング $\bm{U}_i$ を加算した $\bm{h}_i = \bm{E}_{x_i} + \bm{U}_i$ を考えます。位置 $i$ のQueryと位置 $j$ のKeyの内積を展開します。

$$ \bm{q}_i^\top \bm{k}_j = (\bm{h}_i \bm{W}_Q)^\top (\bm{h}_j \bm{W}_K) $$

$\bm{h}_i = \bm{E}_{x_i} + \bm{U}_i$ を代入すると、この内積は4つの項に分解されます。

$$ \bm{q}_i^\top \bm{k}_j = \underbrace{(\bm{E}_{x_i} \bm{W}_Q)^\top (\bm{E}_{x_j} \bm{W}_K)}_{(a)} + \underbrace{(\bm{E}_{x_i} \bm{W}_Q)^\top (\bm{U}_j \bm{W}_K)}_{(b)} + \underbrace{(\bm{U}_i \bm{W}_Q)^\top (\bm{E}_{x_j} \bm{W}_K)}_{(c)} + \underbrace{(\bm{U}_i \bm{W}_Q)^\top (\bm{U}_j \bm{W}_K)}_{(d)} $$

各項の意味を読み解きましょう。

  • (a) 内容-内容(content-content): トークン $i$ の意味とトークン $j$ の意味の類似度。純粋に「何を言っているか」で注目度を決める項
  • (b) 内容-位置(content-position): トークン $i$ の意味と位置 $j$ の絶対位置の関連。「この単語は何番目の位置に注目するか」を決める項
  • (c) 位置-内容(position-content): 位置 $i$ の絶対位置とトークン $j$ の意味の関連。「この位置にいるトークンは何の単語に注目するか」を決める項
  • (d) 位置-位置(position-position): 位置 $i$ と位置 $j$ の絶対位置同士の関連。「位置同士の関係」を決める項

絶対位置から相対位置への変換

Transformer-XLでは、上記の4項分解に対して3つの重要な変更を加えます。

変更1: 絶対位置を相対位置に置換

(b) と (d) の $\bm{U}_j$(位置 $j$ の絶対位置エンコーディング)を、相対位置エンコーディング $\bm{R}_{i-j}$ に置き換えます。$\bm{R}_{i-j}$ は sin/cos 関数で計算される固定のエンコーディングで、相対距離 $i-j$ のみに依存します。

変更2: Queryの位置依存を除去

(c) の $\bm{U}_i \bm{W}_Q$(位置 $i$ のQuery)を、全ての位置で共通の学習可能ベクトル $\bm{u}$ に置き換えます。同様に (d) の $\bm{U}_i \bm{W}_Q$ を別の学習可能ベクトル $\bm{v}$ に置き換えます。

この変更の理由は、「Queryが自分の絶対位置に依存する」必要がないからです。「私は5番目にいるから特別な注意を払う」というのは不自然で、「どの位置にいても同じ注意の傾向を持つ」方が汎用性が高くなります。

変更3: Key側の射影行列を分離

内容ベースのKeyと位置ベースのKeyで異なる射影行列を使います。内容用に $\bm{W}_{K,E}$、位置用に $\bm{W}_{K,R}$ を用意します。

これら3つの変更を適用すると、Transformer-XLのAttentionスコアは次のようになります。

$$ A_{ij} = \underbrace{\bm{E}_{x_i}^\top \bm{W}_Q^\top \bm{W}_{K,E} \bm{E}_{x_j}}_{(a)\text{: content-content}} + \underbrace{\bm{E}_{x_i}^\top \bm{W}_Q^\top \bm{W}_{K,R} \bm{R}_{i-j}}_{(b)\text{: content-position}} + \underbrace{\bm{u}^\top \bm{W}_{K,E} \bm{E}_{x_j}}_{(c)\text{: global content bias}} + \underbrace{\bm{v}^\top \bm{W}_{K,R} \bm{R}_{i-j}}_{(d)\text{: global position bias}} $$

各項を改めて解釈します。

  • (a) 内容同士の注意: 「この単語はあの単語にどれだけ注目するか」
  • (b) 相対位置に依存する注意: 「この単語は何トークン離れた位置にどれだけ注目するか」
  • (c) グローバルな内容バイアス: 「全体的に、どの単語が注目されやすいか」(全位置で共通)
  • (d) グローバルな位置バイアス: 「全体的に、どの相対距離が注目されやすいか」(全位置で共通)

Transformer-XLの注意スコア4項分解の図解

4つの項を並べると、それぞれの役割分担が見やすくなります。左上(a)が純粋な意味の照合、右上(b)が相対距離に応じた注意で、ここに $\bm{R}_{i-j}$ が入ります。下段(c)(d)は全位置で共通の「大域バイアス」で、Query側の絶対位置 $\bm{U}_i$ を学習ベクトル $\bm{u}, \bm{v}$ に置き換えた結果です。絶対位置 $\bm{U}_j$ を相対位置 $\bm{R}_{i-j}$ に差し替え、Query位置依存を消す——この2つの操作で絶対位置の式が相対位置の式へと変換されます。

効率的な計算 — 相対位置シフト

(b) の項 $\bm{q}_i^\top \bm{W}_{K,R} \bm{R}_{i-j}$ を素朴に計算すると、各 $(i, j)$ ペアに対して個別の相対位置ベクトル $\bm{R}_{i-j}$ が必要で、計算量が増大します。

Transformer-XLでは、テプリッツ行列(Toeplitz matrix)の構造を利用した効率的な計算方法を提案しています。相対位置スコアの行列は、$(i, j)$ 成分が $i – j$ のみに依存するため、対角線上で値が一定となるテプリッツ構造を持ちます。

具体的には、まず $\bm{q}_i^\top \bm{W}_{K,R} \bm{R}_r$ を全ての相対距離 $r$ に対してまとめて計算し、その結果を適切にシフトして各位置に配置する「相対位置シフト」アルゴリズムを使います。

Transformer-XLの相対位置エンコーディングは強力ですが、(a)〜(d) の4項を個別に計算する必要があるため、実装がやや複雑です。T5では、これをさらにシンプル化するアプローチが提案されました。次にT5の方式を見ていきましょう。

T5 の相対位置バイアス — バケット化と共有

極めてシンプルな設計

T5(Raffel et al., 2020)”Exploring the Limits of Transfer Learning with a Unified Text-to-Text Transformer” は、相対位置エンコーディングを驚くほどシンプルに再設計しました。

T5のアイデアは明快です。Attentionスコアに学習可能なスカラーバイアスを相対距離に応じて加算するだけです。

$$ e_{ij} = \bm{q}_i^\top \bm{k}_j + b(i – j) $$

ここで $b(i-j)$ は相対距離 $i – j$ に対応する学習可能なスカラー値です。Shaw et al.がQueryとの内積で位置スコアを計算したのに対し、T5はスカラーのバイアスを直接足します。これにより、位置情報の影響がより明示的でシンプルになります。

バケット化 — 対数スケールで距離を圧縮

全ての相対距離に対して個別のバイアスを学習すると、長い系列ではパラメータが増大します。Shaw et al.はクリッピングでこの問題を解決しましたが、T5はより洗練されたバケット化(bucketing)を導入しました。

バケット化のアイデアは、「近い距離は細かく区別し、遠い距離は大まかに区別する」というものです。人間の感覚に似ています。1メートル先と2メートル先の違いは明確に感じますが、100メートル先と101メートル先の違いはほとんど感じません。

T5のバケット化関数は、まず相対距離の符号(前方か後方か)を分離し、絶対値に対して以下のように処理します。

近距離($|i – j| \leq$ しきい値)ではそのままの距離をバケット番号とし、遠距離では対数スケールで圧縮します。

$$ b(r) = \begin{cases} r & \text{if } |r| \leq \text{threshold} \\ \text{threshold} + \left\lfloor \log_{base}\left(\frac{|r|}{\text{threshold}}\right) \cdot \frac{n_\text{buckets}/2 – \text{threshold}}{\log_{base}(\text{max\_distance}/\text{threshold})} \right\rfloor & \text{otherwise} \end{cases} $$

T5の実装では、バケット数は32、最大距離は128をデフォルトとしています。32個のバケットのうち、前半16個は正方向、後半16個は負方向(双方向の場合)に割り当てられます。各方向の16バケットのうち、最初の8個は距離0〜7に1対1対応し、残りの8個は距離8〜128を対数的にカバーします。

レイヤー間のバイアス共有

T5では、相対位置バイアスを最初のAttentionレイヤーでのみ計算し、後続のレイヤーに共有するオプションがあります。これにより、パラメータ数と計算量の両方を削減できます。

この設計が機能する理由は、位置の相対関係はレイヤーによらず普遍的であるという仮定に基づいています。「2つ隣のトークンが重要である」という位置情報は、レイヤーが深くなっても大きく変わらないだろうという考え方です。

T5方式の利点

T5の相対位置バイアスには3つの実用上の利点があります。

実装の簡潔さ: Attentionスコアにバイアスを加算するだけで済み、Query・Key・Valueの計算を変更する必要がありません。

パラメータ効率: バケット化によりバイアスパラメータは少数(32個 $\times$ ヘッド数)に抑えられます。

柔軟な距離表現: 対数スケールのバケット化により、近距離は精密に、遠距離は大まかに距離を表現できます。

T5の方式は、Shaw et al.とTransformer-XLの中間に位置する「ちょうどよい」設計と言えます。相対位置情報を明示的に導入しつつ、実装と計算の複雑さを最小限に抑えています。

ここまでで3つの相対位置エンコーディング手法を学びました。次に、これらに加えてRoPEやALiBiを含めた各手法の全体的な比較を行い、それぞれの位置づけを明確にしましょう。

各手法の比較

比較表

相対位置エンコーディングの主要手法を整理します。RoPE(回転位置埋め込み)ALiBiについては、それぞれの記事で詳しく解説しています。

特性 Shaw et al. (2018) Transformer-XL (2019) T5 (2020) RoPE (2021) ALiBi (2021)
位置情報の注入先 Attention スコア + Value Attention スコア Attention スコア Query, Key Attention スコア
学習パラメータ 相対位置ベクトル ($2k+1$個) $\bm{u}, \bm{v}, \bm{W}_{K,R}$ バケットバイアス (32個/ヘッド) なし なし
距離の処理 クリッピング sin/cos (固定) 対数バケット化 回転角度 線形減衰
外挿性 限定的 中程度 中程度 高い(拡張手法併用) 非常に高い
実装の複雑さ 非常に低
Value側の位置情報 あり ($\bm{a}^V$) なし なし なし なし
代表的な採用モデル 初期の相対位置研究 Transformer-XL, XLNet T5, mT5, FLAN-T5 LLaMA, Mistral, PaLM BLOOM, MPT

設計思想の進化

この比較表から、相対位置エンコーディングの設計思想がどのように進化してきたかが読み取れます。

Shaw et al. は「相対位置ベクトルを学習する」という直接的なアプローチで、相対位置の概念を確立しました。しかし、学習パラメータが比較的多く、クリッピングにより遠距離の解像度が失われます。

Transformer-XL は、Attentionスコアの数学的構造を丁寧に分析し、絶対位置情報を相対位置情報に正確に変換しました。理論的には最も精緻ですが、4項の分離計算が実装を複雑にします。

T5 は「スカラーバイアスで十分」という大胆な簡略化を行い、バケット化で効率的な距離表現を実現しました。

RoPE は「回転」という幾何学的操作で相対位置を暗黙的にエンコードし、追加パラメータなしで高い外挿性を達成しました。

ALiBi は「距離に線形比例するペナルティ」という最もシンプルな方式で、学習パラメータ0で強力な外挿性を実現しました。

全体として、「複雑な学習可能パラメータ → シンプルな固定パターン」へと進化しているのが興味深い傾向です。

外挿性の比較

この進化の核心にあるのが「外挿性」、すなわち訓練で見たことのない長い距離にどう振る舞うかです。図の緑の網掛けが訓練範囲、その右が外挿領域です。Shaw方式(青)はクリップ境界を超えると完全に水平になり、新しい距離をまったく区別できません。T5(緑)は対数バケットなので緩やかに延びますが、やはり飽和傾向があります。一方ALiBi(赤)は一定の傾きで素直に減衰し続けるため、訓練範囲を大きく超えた距離にも自然に外挿できます。これが、長文処理を狙う近年のモデルでALiBiやRoPEが好まれる理由です。

ここからは、理論を実装で確認します。まずShaw方式の相対位置Attentionを実装し、次にT5のバケット化を実装して、各手法の注意パターンを可視化しましょう。

Pythonでの実装 — Shaw方式

Shaw et al.の相対位置Attention

Shaw方式の相対位置Attentionをスクラッチで実装します。相対位置ベクトルがAttentionスコアにどのように影響するかを確認することが目的です。

import numpy as np
import matplotlib.pyplot as plt

np.random.seed(42)

def shaw_relative_attention(X, W_Q, W_K, W_V, rel_pos_K, rel_pos_V, max_rel_dist):
    """
    Shaw et al. (2018) の相対位置Attention

    Parameters:
    -----------
    X : (seq_len, d_model) 入力
    W_Q, W_K, W_V : 射影行列
    rel_pos_K : (2*max_rel_dist+1, d_k) 相対位置ベクトル (Key側)
    rel_pos_V : (2*max_rel_dist+1, d_v) 相対位置ベクトル (Value側)
    max_rel_dist : クリッピングの最大距離
    """
    seq_len = X.shape[0]
    d_k = W_Q.shape[1]

    # Query, Key, Value の計算
    Q = X @ W_Q  # (seq_len, d_k)
    K = X @ W_K  # (seq_len, d_k)
    V = X @ W_V  # (seq_len, d_v)

    # 標準のAttentionスコア: Q @ K^T
    content_scores = Q @ K.T  # (seq_len, seq_len)

    # 相対位置スコアの計算
    position_scores = np.zeros((seq_len, seq_len))
    for i in range(seq_len):
        for j in range(seq_len):
            # クリッピング: 相対距離を[-max_rel_dist, max_rel_dist]に制限
            rel_dist = np.clip(i - j, -max_rel_dist, max_rel_dist)
            # インデックスを [0, 2*max_rel_dist] に変換
            idx = rel_dist + max_rel_dist
            position_scores[i, j] = Q[i] @ rel_pos_K[idx]

    # スコアの合計とスケーリング
    scores = (content_scores + position_scores) / np.sqrt(d_k)

    # Softmax
    scores_exp = np.exp(scores - np.max(scores, axis=-1, keepdims=True))
    attn_weights = scores_exp / np.sum(scores_exp, axis=-1, keepdims=True)

    # Value側の相対位置も考慮した出力
    output = np.zeros_like(V)
    for i in range(seq_len):
        for j in range(seq_len):
            rel_dist = np.clip(i - j, -max_rel_dist, max_rel_dist)
            idx = rel_dist + max_rel_dist
            output[i] += attn_weights[i, j] * (V[j] + rel_pos_V[idx])

    return output, attn_weights, content_scores / np.sqrt(d_k), position_scores / np.sqrt(d_k)

# パラメータ設定
seq_len = 12
d_model = 16
d_k = d_v = 8
max_rel_dist = 4

# 入力とパラメータの初期化
X = np.random.randn(seq_len, d_model) * 0.1
W_Q = np.random.randn(d_model, d_k) * 0.1
W_K = np.random.randn(d_model, d_k) * 0.1
W_V = np.random.randn(d_model, d_v) * 0.1

# 相対位置ベクトル (2*max_rel_dist+1 = 9個)
rel_pos_K = np.random.randn(2 * max_rel_dist + 1, d_k) * 0.5
rel_pos_V = np.random.randn(2 * max_rel_dist + 1, d_v) * 0.5

# 実行
output, attn_weights, content_scores, position_scores = shaw_relative_attention(
    X, W_Q, W_K, W_V, rel_pos_K, rel_pos_V, max_rel_dist
)

# 可視化: 内容スコア vs 位置スコア vs 合計
fig, axes = plt.subplots(1, 3, figsize=(18, 5))

im0 = axes[0].imshow(content_scores, cmap='RdBu_r', aspect='auto')
axes[0].set_title('Content Scores (q_i^T k_j)', fontsize=12)
axes[0].set_xlabel('Key position j')
axes[0].set_ylabel('Query position i')
plt.colorbar(im0, ax=axes[0], shrink=0.8)

im1 = axes[1].imshow(position_scores, cmap='RdBu_r', aspect='auto')
axes[1].set_title('Relative Position Scores (q_i^T a_K)', fontsize=12)
axes[1].set_xlabel('Key position j')
axes[1].set_ylabel('Query position i')
plt.colorbar(im1, ax=axes[1], shrink=0.8)

im2 = axes[2].imshow(attn_weights, cmap='viridis', aspect='auto')
axes[2].set_title('Final Attention Weights', fontsize=12)
axes[2].set_xlabel('Key position j')
axes[2].set_ylabel('Query position i')
plt.colorbar(im2, ax=axes[2], shrink=0.8)

plt.tight_layout()
plt.savefig('shaw_attention.png', dpi=150, bbox_inches='tight')
plt.show()

Shaw方式の内容スコア位置スコア最終注意の3パネル

上のグラフから、3つの重要な観察ができます。

  1. 内容スコア(左)はほぼランダムなパターンを示しています。これはランダムに初期化した入力を使っているためで、学習前にはトークン間の意味的な関連がないことを反映しています。
  2. 位置スコア(中央)には明確な対角線構造が見えます。これはクリッピング付き相対位置ベクトルの効果で、同じ相対距離のペアが同じ位置スコアを共有しているためです。対角線に平行なバンド状のパターンが $2k+1 = 9$ 本確認でき、クリッピング範囲外の領域は一定値に飽和しています。
  3. 最終的なAttention重み(右)は、内容と位置の両方の影響を反映しています。位置スコアによる対角バンド構造が最終的なAttentionにも影響を与えており、相対位置情報がAttentionの計算に正しく組み込まれていることがわかります。

次に、もう1つの代表的な手法であるT5のバケット化を実装してみましょう。

Pythonでの実装 — T5バケット方式

T5の相対位置バケット化関数

T5のバケット化関数は、相対距離を対数スケールで圧縮してバケット番号に変換します。この関数の挙動を実装し、どのように距離がマッピングされるかを可視化します。

import numpy as np
import matplotlib.pyplot as plt

def t5_relative_position_bucket(relative_position, bidirectional=True,
                                 num_buckets=32, max_distance=128):
    """
    T5の相対位置バケット化関数

    Parameters:
    -----------
    relative_position : int or ndarray 相対距離 (i - j)
    bidirectional : bool 双方向かどうか
    num_buckets : int バケット数
    max_distance : int 最大距離

    Returns:
    --------
    バケット番号
    """
    relative_buckets = 0

    if bidirectional:
        num_buckets //= 2
        # 正方向と負方向を分離
        relative_buckets += (relative_position > 0).astype(np.int32) * num_buckets
        relative_position = np.abs(relative_position)
    else:
        relative_position = -np.minimum(relative_position, 0)

    # 近距離: そのままの距離をバケット番号に
    max_exact = num_buckets // 2
    is_small = relative_position < max_exact

    # 遠距離: 対数スケールで圧縮
    relative_position_if_large = max_exact + (
        np.log(relative_position.astype(np.float32) / max_exact)
        / np.log(max_distance / max_exact)
        * (num_buckets - max_exact)
    ).astype(np.int32)

    # 最大バケット番号でクリップ
    relative_position_if_large = np.minimum(
        relative_position_if_large, num_buckets - 1
    )

    relative_buckets += np.where(is_small, relative_position, relative_position_if_large)

    return relative_buckets

# バケット化の可視化
distances = np.arange(-150, 151)
buckets = t5_relative_position_bucket(distances, bidirectional=True)

fig, axes = plt.subplots(1, 2, figsize=(16, 5))

# 距離 → バケット番号のマッピング
axes[0].plot(distances, buckets, 'b-', linewidth=1.5)
axes[0].set_xlabel('Relative Distance (i - j)', fontsize=12)
axes[0].set_ylabel('Bucket Index', fontsize=12)
axes[0].set_title('T5 Relative Position Bucket Mapping', fontsize=13)
axes[0].axhline(y=16, color='gray', linestyle='--', alpha=0.5, label='Boundary (bucket 16)')
axes[0].axvline(x=0, color='gray', linestyle='--', alpha=0.5)
axes[0].legend()
axes[0].grid(True, alpha=0.3)

# 各バケットに含まれる距離の範囲
bucket_counts = np.zeros(32)
for d in range(-128, 129):
    b = t5_relative_position_bucket(np.array([d]), bidirectional=True)[0]
    bucket_counts[b] += 1

axes[1].bar(range(32), bucket_counts, color='steelblue', alpha=0.8)
axes[1].set_xlabel('Bucket Index', fontsize=12)
axes[1].set_ylabel('Number of Distances in Bucket', fontsize=12)
axes[1].set_title('Distance Count per Bucket', fontsize=13)
axes[1].grid(True, alpha=0.3, axis='y')

plt.tight_layout()
plt.savefig('t5_bucket.png', dpi=150, bbox_inches='tight')
plt.show()

T5相対位置バケット化マッピング

上のグラフから、T5のバケット化の特性が明確に読み取れます。

  1. 左図: 近い距離(約$\pm 8$以内)ではバケット番号が距離に対して線形に変化し、1対1の対応になっています。一方、遠い距離では対数的にバケット番号が圧縮され、距離の増加に対してバケット番号の変化が緩やかになります。距離0を境に正方向(バケット16〜31)と負方向(バケット0〜15)が対称に配置されています。
  2. 右図: 近距離のバケット(各方向の最初の8個)にはそれぞれ1〜2個の距離しか含まれないのに対し、遠距離のバケットには多数の距離が1つのバケットに圧縮されています。これは「近い距離ほど細かく区別する」という設計思想を具体的に示しています。

T5方式のAttention計算

バケット化関数を使って、T5方式のAttentionを実装し、バイアスがAttentionパターンにどう影響するかを可視化します。

import numpy as np
import matplotlib.pyplot as plt

np.random.seed(42)

def t5_relative_position_bucket(relative_position, bidirectional=True,
                                 num_buckets=32, max_distance=128):
    """T5の相対位置バケット化関数"""
    relative_buckets = 0
    if bidirectional:
        num_buckets //= 2
        relative_buckets += (relative_position > 0).astype(np.int32) * num_buckets
        relative_position = np.abs(relative_position)
    else:
        relative_position = -np.minimum(relative_position, 0)

    max_exact = num_buckets // 2
    is_small = relative_position < max_exact

    relative_position_if_large = max_exact + (
        np.log(relative_position.astype(np.float32).clip(min=1) / max_exact)
        / np.log(max_distance / max_exact)
        * (num_buckets - max_exact)
    ).astype(np.int32)
    relative_position_if_large = np.minimum(relative_position_if_large, num_buckets - 1)

    relative_buckets += np.where(is_small, relative_position, relative_position_if_large)
    return relative_buckets

def t5_attention(X, W_Q, W_K, W_V, bias_table, num_buckets=32, max_distance=128):
    """
    T5方式の相対位置バイアス付きAttention

    Parameters:
    -----------
    X : (seq_len, d_model) 入力
    W_Q, W_K, W_V : 射影行列
    bias_table : (num_buckets,) 各バケットに対応するバイアス値
    """
    seq_len = X.shape[0]
    d_k = W_Q.shape[1]

    Q = X @ W_Q
    K = X @ W_K
    V = X @ W_V

    # 内容ベースのスコア
    content_scores = Q @ K.T / np.sqrt(d_k)

    # 相対位置バイアスの計算
    positions = np.arange(seq_len)
    relative_positions = positions[:, None] - positions[None, :]
    buckets = t5_relative_position_bucket(relative_positions, bidirectional=True,
                                          num_buckets=num_buckets, max_distance=max_distance)
    position_bias = bias_table[buckets]

    # スコアにバイアスを加算
    scores = content_scores + position_bias

    # Softmax
    scores_exp = np.exp(scores - np.max(scores, axis=-1, keepdims=True))
    attn_weights = scores_exp / np.sum(scores_exp, axis=-1, keepdims=True)

    output = attn_weights @ V
    return output, attn_weights, content_scores, position_bias

# パラメータ設定
seq_len = 20
d_model = 16
d_k = d_v = 8
num_buckets = 32

X = np.random.randn(seq_len, d_model) * 0.1
W_Q = np.random.randn(d_model, d_k) * 0.1
W_K = np.random.randn(d_model, d_k) * 0.1
W_V = np.random.randn(d_model, d_v) * 0.1

# バイアステーブル: 近い距離に大きなバイアス、遠い距離に小さなバイアスを模擬
bias_table = np.zeros(num_buckets)
for b in range(num_buckets):
    if b < 16:  # 負方向 (j > i)
        dist_approx = b if b < 8 else 8 * 2**((b - 8) / 8 * np.log2(128 / 8))
        bias_table[b] = -0.3 * np.log1p(dist_approx)
    else:  # 正方向 (i > j)
        dist_approx = (b - 16) if (b - 16) < 8 else 8 * 2**(((b - 16) - 8) / 8 * np.log2(128 / 8))
        bias_table[b] = -0.3 * np.log1p(dist_approx)
# 距離0のバイアスを最大にする
bias_table[0] = 0.5
bias_table[16] = 0.5

output, attn_weights, content_scores, position_bias = t5_attention(
    X, W_Q, W_K, W_V, bias_table, num_buckets
)

fig, axes = plt.subplots(1, 3, figsize=(18, 5))

im0 = axes[0].imshow(content_scores, cmap='RdBu_r', aspect='auto')
axes[0].set_title('Content Scores', fontsize=12)
axes[0].set_xlabel('Key position j')
axes[0].set_ylabel('Query position i')
plt.colorbar(im0, ax=axes[0], shrink=0.8)

im1 = axes[1].imshow(position_bias, cmap='RdBu_r', aspect='auto')
axes[1].set_title('T5 Position Bias b(i-j)', fontsize=12)
axes[1].set_xlabel('Key position j')
axes[1].set_ylabel('Query position i')
plt.colorbar(im1, ax=axes[1], shrink=0.8)

im2 = axes[2].imshow(attn_weights, cmap='viridis', aspect='auto')
axes[2].set_title('Final Attention Weights', fontsize=12)
axes[2].set_xlabel('Key position j')
axes[2].set_ylabel('Query position i')
plt.colorbar(im2, ax=axes[2], shrink=0.8)

plt.tight_layout()
plt.savefig('t5_attention.png', dpi=150, bbox_inches='tight')
plt.show()

T5方式の内容バイアス最終注意の3パネル

上のグラフから、T5方式の特徴が明確に読み取れます。

  1. 内容スコア(左): Shaw方式と同様、ランダム初期化された入力のため特定のパターンは見られません。実際のモデルでは、学習が進むにつれて意味的に関連するトークンペアのスコアが高くなります。
  2. 位置バイアス(中央): 対角線($i = j$、つまり自分自身)で最大値を取り、対角線から離れるにつれて値が減少する美しい帯状パターンが確認できます。これはT5のバケット化による対数的な距離圧縮の効果です。近い距離ではバイアスの変化が急峻で、遠い距離では緩やかに減衰しています。
  3. 最終Attention重み(右): 位置バイアスの影響により、各トークンは近傍のトークンに高いAttention重みを割り当てる局所的な注意パターンを示しています。これは言語処理において自然な振る舞いで、構文的な依存関係は多くの場合近傍のトークン間に存在します。

次に、Shaw方式とT5方式のAttentionパターンをより詳細に比較し、各手法の注意パターンの違いを可視化してみましょう。

注意パターンの比較可視化

各手法の距離減衰特性

相対位置エンコーディングの各手法が、距離に対してどのようなバイアスパターンを生成するかを比較します。Shaw(クリッピング)、T5(対数バケット)、ALiBi(線形減衰)の3方式を同一のグラフ上で可視化します。

import numpy as np
import matplotlib.pyplot as plt

def shaw_distance_profile(distances, max_rel_dist=16):
    """Shaw方式: クリッピング後の有効距離(概念的プロファイル)"""
    clipped = np.clip(distances, -max_rel_dist, max_rel_dist)
    # クリッピングされた距離を正規化して返す(学習前の概念的なプロファイル)
    return -np.abs(clipped) / max_rel_dist

def t5_distance_profile(distances, num_buckets=32, max_distance=128):
    """T5方式: バケット番号に基づくプロファイル"""
    buckets = t5_relative_position_bucket(distances.astype(np.int64),
                                           bidirectional=True,
                                           num_buckets=num_buckets,
                                           max_distance=max_distance)
    # バケット番号を正規化(概念的なプロファイル)
    center = num_buckets // 2
    return -np.abs(buckets - center) / center

def alibi_distance_profile(distances, slope=0.125):
    """ALiBi方式: 線形減衰"""
    return -slope * np.abs(distances)

distances = np.arange(-64, 65)

fig, axes = plt.subplots(2, 2, figsize=(14, 10))

# Shaw方式
profile_shaw = shaw_distance_profile(distances, max_rel_dist=16)
axes[0, 0].plot(distances, profile_shaw, 'b-', linewidth=2)
axes[0, 0].set_title('Shaw et al. (Clipping, k=16)', fontsize=12)
axes[0, 0].set_xlabel('Relative Distance')
axes[0, 0].set_ylabel('Effective Bias (conceptual)')
axes[0, 0].axvline(x=-16, color='red', linestyle='--', alpha=0.5, label='Clip boundary')
axes[0, 0].axvline(x=16, color='red', linestyle='--', alpha=0.5)
axes[0, 0].legend()
axes[0, 0].grid(True, alpha=0.3)

# T5方式
profile_t5 = t5_distance_profile(distances)
axes[0, 1].plot(distances, profile_t5, 'g-', linewidth=2)
axes[0, 1].set_title('T5 (Log Bucketing)', fontsize=12)
axes[0, 1].set_xlabel('Relative Distance')
axes[0, 1].set_ylabel('Effective Bias (conceptual)')
axes[0, 1].grid(True, alpha=0.3)

# ALiBi方式(異なるスロープ)
slopes = [0.5, 0.25, 0.125, 0.0625]
colors = ['#e74c3c', '#e67e22', '#2ecc71', '#3498db']
for slope, color in zip(slopes, colors):
    profile = alibi_distance_profile(distances, slope)
    axes[1, 0].plot(distances, profile, color=color, linewidth=1.5, label=f'm={slope}')
axes[1, 0].set_title('ALiBi (Linear Decay, multiple slopes)', fontsize=12)
axes[1, 0].set_xlabel('Relative Distance')
axes[1, 0].set_ylabel('Bias Value')
axes[1, 0].legend()
axes[1, 0].grid(True, alpha=0.3)
axes[1, 0].set_ylim(-10, 1)

# 全手法の比較(正の距離のみ)
pos_dist = np.arange(0, 65)
axes[1, 1].plot(pos_dist, shaw_distance_profile(pos_dist, 16), 'b-',
                linewidth=2, label='Shaw (clip=16)')
axes[1, 1].plot(pos_dist, t5_distance_profile(pos_dist), 'g-',
                linewidth=2, label='T5 (log bucket)')
axes[1, 1].plot(pos_dist, alibi_distance_profile(pos_dist, 0.125), 'r-',
                linewidth=2, label='ALiBi (m=0.125)')
axes[1, 1].set_title('Comparison (Positive Direction)', fontsize=12)
axes[1, 1].set_xlabel('Relative Distance')
axes[1, 1].set_ylabel('Effective Bias (normalized)')
axes[1, 1].legend()
axes[1, 1].grid(True, alpha=0.3)

plt.tight_layout()
plt.savefig('position_encoding_comparison.png', dpi=150, bbox_inches='tight')
plt.show()

各手法の距離減衰プロファイル比較

この比較グラフから、各手法の設計哲学の違いが明確に読み取れます。

  1. Shaw方式(左上): クリッピング境界($k=16$)を超えると、それ以上離れたトークンはすべて同じバイアスになります。これは「16トークンより遠いものは全て等しく扱う」という割り切りで、長距離の区別能力が制限されます。グラフでは、境界外の平坦な領域が明確に見えます。
  2. T5方式(右上): 対数スケールのバケット化により、近距離では急峻に変化し、遠距離では緩やかに変化する滑らかなカーブを描きます。Shaw方式のような「完全に平坦な領域」はなく、遠距離でもある程度の区別が維持されています。
  3. ALiBi方式(左下): 各ヘッドが異なるスロープを持ち、スロープの大きいヘッド($m=0.5$)は近距離に強く集中し、小さいヘッド($m=0.0625$)は遠距離まで見渡します。複数のヘッドが協調して多スケールの距離感を実現する点がALiBiの巧みな設計です。
  4. 3手法の比較(右下): 同じ距離範囲で比較すると、Shaw方式は最も急峻に飽和し、ALiBi方式は一定のペースで減衰し続け、T5方式はその中間の対数的な減衰を示します。この違いは外挿性に直結します。ALiBiは訓練範囲を超えた距離にも一定の傾向で外挿でき、Shaw方式は新しい距離を区別できません。

Attention行列のヒートマップ比較

最後に、Shaw方式とT5方式のAttention行列を並べて可視化し、実際のAttentionパターンの違いを確認します。

import numpy as np
import matplotlib.pyplot as plt

np.random.seed(123)

def softmax(x, axis=-1):
    """数値安定なSoftmax"""
    x_max = np.max(x, axis=axis, keepdims=True)
    e_x = np.exp(x - x_max)
    return e_x / np.sum(e_x, axis=axis, keepdims=True)

def t5_relative_position_bucket(relative_position, bidirectional=True,
                                 num_buckets=32, max_distance=128):
    """T5バケット化関数"""
    relative_buckets = 0
    if bidirectional:
        num_buckets //= 2
        relative_buckets += (relative_position > 0).astype(np.int32) * num_buckets
        relative_position = np.abs(relative_position)
    else:
        relative_position = -np.minimum(relative_position, 0)
    max_exact = num_buckets // 2
    is_small = relative_position < max_exact
    relative_position_if_large = max_exact + (
        np.log(relative_position.astype(np.float32).clip(min=1) / max_exact)
        / np.log(max_distance / max_exact)
        * (num_buckets - max_exact)
    ).astype(np.int32)
    relative_position_if_large = np.minimum(relative_position_if_large, num_buckets - 1)
    relative_buckets += np.where(is_small, relative_position, relative_position_if_large)
    return relative_buckets

seq_len = 24
d_model = 32
d_k = 16

# 共通の入力
X = np.random.randn(seq_len, d_model) * 0.1
W_Q = np.random.randn(d_model, d_k) * 0.1
W_K = np.random.randn(d_model, d_k) * 0.1

Q = X @ W_Q
K = X @ W_K
content_scores = Q @ K.T / np.sqrt(d_k)

# --- Shaw方式のAttention ---
max_rel_dist = 6
rel_pos_K_shaw = np.random.randn(2 * max_rel_dist + 1, d_k) * 0.3
# 距離0に近いほど大きなスコアになるよう調整
for r in range(2 * max_rel_dist + 1):
    dist = abs(r - max_rel_dist)
    rel_pos_K_shaw[r] *= np.exp(-0.2 * dist)

shaw_pos_scores = np.zeros((seq_len, seq_len))
for i in range(seq_len):
    for j in range(seq_len):
        rel_dist = np.clip(i - j, -max_rel_dist, max_rel_dist) + max_rel_dist
        shaw_pos_scores[i, j] = Q[i] @ rel_pos_K_shaw[rel_dist]

shaw_scores = content_scores + shaw_pos_scores
shaw_attn = softmax(shaw_scores)

# --- T5方式のAttention ---
num_buckets = 32
bias_table_t5 = np.zeros(num_buckets)
for b in range(num_buckets):
    center = 0 if b < 16 else 16
    dist_idx = b - center
    bias_table_t5[b] = -0.5 * dist_idx  # 距離が大きいほど負のバイアス

positions = np.arange(seq_len)
rel_positions = positions[:, None] - positions[None, :]
buckets = t5_relative_position_bucket(rel_positions.astype(np.int64), bidirectional=True)
t5_bias = bias_table_t5[buckets]

t5_scores = content_scores + t5_bias
t5_attn = softmax(t5_scores)

# --- 標準Attention(位置バイアスなし)---
standard_attn = softmax(content_scores)

# 可視化
fig, axes = plt.subplots(1, 3, figsize=(18, 5))

im0 = axes[0].imshow(standard_attn, cmap='viridis', aspect='auto', vmin=0, vmax=0.2)
axes[0].set_title('Standard Attention (No Position)', fontsize=12)
axes[0].set_xlabel('Key position j')
axes[0].set_ylabel('Query position i')
plt.colorbar(im0, ax=axes[0], shrink=0.8)

im1 = axes[1].imshow(shaw_attn, cmap='viridis', aspect='auto', vmin=0, vmax=0.2)
axes[1].set_title('Shaw Relative Attention (k=6)', fontsize=12)
axes[1].set_xlabel('Key position j')
axes[1].set_ylabel('Query position i')
plt.colorbar(im1, ax=axes[1], shrink=0.8)

im2 = axes[2].imshow(t5_attn, cmap='viridis', aspect='auto', vmin=0, vmax=0.2)
axes[2].set_title('T5 Relative Bias Attention', fontsize=12)
axes[2].set_xlabel('Key position j')
axes[2].set_ylabel('Query position i')
plt.colorbar(im2, ax=axes[2], shrink=0.8)

plt.tight_layout()
plt.savefig('attention_pattern_comparison.png', dpi=150, bbox_inches='tight')
plt.show()

# 特定の位置のAttention分布を比較
fig, axes = plt.subplots(1, 2, figsize=(14, 5))

query_pos = 12
axes[0].bar(range(seq_len), standard_attn[query_pos], alpha=0.4, label='Standard', color='gray')
axes[0].bar(range(seq_len), shaw_attn[query_pos], alpha=0.6, label='Shaw', color='blue')
axes[0].bar(range(seq_len), t5_attn[query_pos], alpha=0.4, label='T5', color='green')
axes[0].axvline(x=query_pos, color='red', linestyle='--', alpha=0.5, label=f'Query pos={query_pos}')
axes[0].set_xlabel('Key Position j', fontsize=12)
axes[0].set_ylabel('Attention Weight', fontsize=12)
axes[0].set_title(f'Attention Distribution from Position {query_pos}', fontsize=12)
axes[0].legend()
axes[0].grid(True, alpha=0.3, axis='y')

# 対角線からの距離に対するAttention重みの平均
max_d = seq_len - 1
avg_attn_standard = np.zeros(max_d + 1)
avg_attn_shaw = np.zeros(max_d + 1)
avg_attn_t5 = np.zeros(max_d + 1)
counts = np.zeros(max_d + 1)

for i in range(seq_len):
    for j in range(seq_len):
        d = abs(i - j)
        avg_attn_standard[d] += standard_attn[i, j]
        avg_attn_shaw[d] += shaw_attn[i, j]
        avg_attn_t5[d] += t5_attn[i, j]
        counts[d] += 1

avg_attn_standard /= counts
avg_attn_shaw /= counts
avg_attn_t5 /= counts

axes[1].plot(range(max_d + 1), avg_attn_standard, 'gray', linewidth=2,
             marker='o', markersize=3, label='Standard')
axes[1].plot(range(max_d + 1), avg_attn_shaw, 'b-', linewidth=2,
             marker='s', markersize=3, label='Shaw')
axes[1].plot(range(max_d + 1), avg_attn_t5, 'g-', linewidth=2,
             marker='^', markersize=3, label='T5')
axes[1].set_xlabel('Absolute Distance |i - j|', fontsize=12)
axes[1].set_ylabel('Average Attention Weight', fontsize=12)
axes[1].set_title('Average Attention Weight vs Distance', fontsize=12)
axes[1].legend()
axes[1].grid(True, alpha=0.3)

plt.tight_layout()
plt.savefig('attention_distance_decay.png', dpi=150, bbox_inches='tight')
plt.show()

標準ShawT5の注意行列ヒートマップ比較

注意分布と距離減衰曲線

この2つのグラフ群から、相対位置エンコーディングの効果が視覚的に確認できます。

上段のヒートマップ比較について。標準Attention(左)は対角線構造を持たず、重みが比較的均一に分散しています。Shaw方式(中央)では対角線付近に明確な「帯」が出現し、クリッピング範囲($k=6$)内のトークンに集中した注意パターンが見られます。T5方式(右)も同様に対角線付近に集中しますが、対数バケット化の効果で遠距離にもわずかな重みが残っており、Shaw方式より「滑らかな」減衰を示しています。

下段の定量的比較について。左のバーチャートでは、位置12からの注意分布が3手法で大きく異なることがわかります。標準Attentionはほぼ均一ですが、Shaw方式とT5方式は位置12の近傍に集中しています。右の距離減衰曲線では、標準Attentionが距離に対してほぼ一定なのに対し、Shaw方式は距離6前後で急激に減衰し、T5方式は対数的に滑らかに減衰します。この「距離が近いほど注意を払う」という自然な傾向が、相対位置エンコーディングにより明示的にモデルに組み込まれていることが確認できます。

数学的な補足 — Transformer-XLの4項分解の導出

絶対位置の展開から相対位置への変換

Transformer-XLの4項分解をより丁寧に追ってみましょう。入力ベクトルを $\bm{h}_i = \bm{E}_{x_i} + \bm{U}_i$ とし、Attentionスコアの $(i, j)$ 成分を展開します。

まず、Query と Key を計算します。

$$ \bm{q}_i = (\bm{E}_{x_i} + \bm{U}_i) \bm{W}_Q, \quad \bm{k}_j = (\bm{E}_{x_j} + \bm{U}_j) \bm{W}_K $$

内積を展開するために、分配法則を適用します。

$$ \bm{q}_i^\top \bm{k}_j = [(\bm{E}_{x_i} + \bm{U}_i) \bm{W}_Q]^\top [(\bm{E}_{x_j} + \bm{U}_j) \bm{W}_K] $$

転置の性質 $(\bm{A}\bm{B})^\top = \bm{B}^\top \bm{A}^\top$ を使って整理します。

$$ = (\bm{E}_{x_i} \bm{W}_Q + \bm{U}_i \bm{W}_Q)^\top (\bm{E}_{x_j} \bm{W}_K + \bm{U}_j \bm{W}_K) $$

内積の分配法則 $(\bm{a} + \bm{b})^\top (\bm{c} + \bm{d}) = \bm{a}^\top \bm{c} + \bm{a}^\top \bm{d} + \bm{b}^\top \bm{c} + \bm{b}^\top \bm{d}$ により4項に展開します。

$$ = \underbrace{(\bm{E}_{x_i} \bm{W}_Q)^\top (\bm{E}_{x_j} \bm{W}_K)}_{(a)} + \underbrace{(\bm{E}_{x_i} \bm{W}_Q)^\top (\bm{U}_j \bm{W}_K)}_{(b)} + \underbrace{(\bm{U}_i \bm{W}_Q)^\top (\bm{E}_{x_j} \bm{W}_K)}_{(c)} + \underbrace{(\bm{U}_i \bm{W}_Q)^\top (\bm{U}_j \bm{W}_K)}_{(d)} $$

ここで、Transformer-XLが行う3つの変更を適用します。

(b) と (d) の $\bm{U}_j$ を $\bm{R}_{i-j}$ に置き換えます。これにより絶対位置 $j$ への依存が相対距離 $i – j$ への依存に変わります。

$$ (b) \to (\bm{E}_{x_i} \bm{W}_Q)^\top (\bm{R}_{i-j} \bm{W}_{K,R}), \quad (d) \to (\bm{U}_i \bm{W}_Q)^\top (\bm{R}_{i-j} \bm{W}_{K,R}) $$

次に、(c) と (d) の $\bm{U}_i \bm{W}_Q$ を学習可能ベクトル $\bm{u}$ と $\bm{v}$ に置き換えます。

$$ (c) \to \bm{u}^\top (\bm{E}_{x_j} \bm{W}_{K,E}), \quad (d) \to \bm{v}^\top (\bm{R}_{i-j} \bm{W}_{K,R}) $$

最終的に、Transformer-XLのAttentionスコアが得られます。

$$ A_{ij} = \underbrace{\bm{E}_{x_i}^\top \bm{W}_Q^\top \bm{W}_{K,E} \bm{E}_{x_j}}_{(a)} + \underbrace{\bm{E}_{x_i}^\top \bm{W}_Q^\top \bm{W}_{K,R} \bm{R}_{i-j}}_{(b)} + \underbrace{\bm{u}^\top \bm{W}_{K,E} \bm{E}_{x_j}}_{(c)} + \underbrace{\bm{v}^\top \bm{W}_{K,R} \bm{R}_{i-j}}_{(d)} $$

なぜQueryの位置依存を除去するのか

(c) と (d) で $\bm{U}_i \bm{W}_Q$ を $\bm{u}$ / $\bm{v}$ に置き換えたことには、直感的な解釈があります。

(c) は「どのトークンが全体的に注目されやすいか」を表すグローバルバイアスです。たとえば、文の動詞は他のトークンから注目されやすい傾向があるかもしれません。この傾向はQuery側の位置に依存するべきではありません。どこにいても動詞への注目度は変わらないはずです。

(d) は「どの相対距離が全体的に好まれるか」を表すグローバルバイアスです。多くの言語では直前のトークンとの関係が強い傾向があります。この傾向もQuery側の位置に依存するべきではなく、位置1にいても位置100にいても「直前のトークンが重要」であることは変わりません。

これらの変更により、パラメータ数が削減されるだけでなく、モデルの汎化性能が向上します。位置に対する過度な適合が防がれるためです。

ここまでで相対位置エンコーディングの理論的な基盤をひととおり見てきました。最後に、本記事で学んだ内容をまとめましょう。

まとめ

本記事では、相対位置エンコーディングの理論的発展をShaw et al. (2018) からTransformer-XL (2019)、T5 (2020) まで体系的に解説しました。

  • 絶対位置の限界: 訓練時の系列長を超えた外挿が困難であり、絶対位置への過適合が生じるという問題があります。相対位置エンコーディングは「何番目か」ではなく「何個離れているか」を表現することで、これらの問題に対処します

  • Shaw et al.: 相対位置を表す学習可能なベクトル $\bm{a}_{ij}^K$, $\bm{a}_{ij}^V$ を導入し、Attentionスコアと出力の両方に相対位置情報を組み込みました。クリッピングでパラメータ数を制御する設計で、相対位置エンコーディングの基盤を確立しました

  • Transformer-XL: 絶対位置によるAttentionスコアを4項(content-content, content-position, global content bias, global position bias)に分解し、それぞれを相対位置に変換する厳密な方法を示しました。セグメント再帰と組み合わせることで、長文の処理能力を大幅に向上させました

  • T5: 学習可能なスカラーバイアスと対数バケット化というシンプルな設計で、近距離を精密に、遠距離を大まかに区別する人間の距離感覚に近い表現を実現しました

  • 設計思想の進化: Shaw et al.の「学習可能な相対位置ベクトル」から、RoPEの「回転による暗黙的エンコーディング」、ALiBiの「固定の線形減衰」へと、全体としてよりシンプルでパラメータの少ない方向に進化しています

相対位置エンコーディングは、現代の大規模言語モデルの根幹を支える技術です。次のステップとして、以下の記事も参考にしてください。