Transformer 内部構造ガイド入力から出力までを図・形状・コードで追う

全体図での位置処理順 3 / 11 番目 / 全 20 ブロック全体図で確認する

共通抽象度: ベクトル難易度: 標準推定学習時間: 約14分

位置情報Positional Encoding

30秒で分かる要約Self-Attentionは系列を集合として扱い語順を区別できないため、各位置の情報をベクトルに加えます。原典では固定のsin/cos関数を使い、現代のLLMではRoPEなどが主流です。

hidden_states[B, S, D]hidden_states[B, S, D]

Level 1概要役割と処理の流れ

何をする機能か

[B, S, D] の各位置のベクトルに、「これは系列の何番目か」という情報を加えます。 形は変わらず、入力も出力も [B, S, D] です。

原典 Transformer では、位置ごとに決まった値を持つ固定のベクトルを作り、 埋め込みベクトルに加算します。

なぜ必要か

Self-Attention は、すべてのトークンどうしの関係を同時に計算します。 その計算式には位置の概念が入っていません。 そのため、位置情報を加えないと次の 2 文はモデルにとって区別できません。

  • 共通例文そのもの(The keys on the table are lost.)
  • keys と table を入れ替えた文(The table on the keys are lost.)

RNN は前から順に処理するので語順が自然に反映されますが、 Transformer は並列処理のためにその性質を捨てています。捨てた分を明示的に補うのが、この工程です。

全体の中でいつ呼ばれるか

入力埋め込み の直後、最初の Attention 層の直前です。 原典の構成では1回だけ加算され、その後の各層では再度加算しません。

Level 2内部処理Tensor形状・内部ステップ・設計上の意味

前段から受け取るもの

入力埋め込み の出力 hidden_states [B, S, D]。

後段へ渡すもの

同じ形の hidden_states [B, S, D]。 Encoder では マルチヘッド自己注意 へ、 Decoder では マスク付き自己注意 へ渡されます。

例文での処理

共通例文

The keys on the table are lost.

概念上のトークン列(S = 8): [The, keys, on, the, table, are, lost, .]

  1. The
  2. keys
  3. on
  4. the
  5. table
  6. are
  7. lost
  8. .
位置0の The と位置3の the。仮に同じ ID だったとしても、位置情報を加えた後は別のベクトルになります。

on the table という句を理解するには、on(位置2)→ the(位置3)→ table(位置4)が 連続していることが手がかりになります。位置情報がなければ、この「隣り合っている」という関係を Attention が捉える足がかりがありません。

位置情報の付加前

位置3の the と、別の場所にある the は同じベクトル

付加後

位置に応じた値が加算され、位置ごとに異なるベクトルになる

原典の方式(Sinusoidal)

位置 pospos、次元 ii に対して、sin と cos を交互に使った固定値を割り当てます。 学習パラメータを持たないため、学習時に見た系列長より長い入力にもそのまま値を計算できます。

EncoderとDecoderでの違い

方式は同じで、Encoder は [B, S, D]、Decoder は [B, T, D] に対して適用します。 Decoder の推論時は 1 トークンずつ伸びていくため、 その時点の位置に対応する値を使います。

入力と出力

入力

項目名データの意味データ型Tensor形状記号の意味具体例次の渡し先
hidden_states埋め込み済みのベクトル列(位置情報なし)float32 Tensor[B, S, D]B = バッチサイズ(同時に処理する文の数) / S = 入力系列長(Encoder側のトークン数) / D = モデル内部のベクトル次元(d_model)[1, 8, 512]位置エンコーディングを加算
positional_encoding位置ごとの固定ベクトル(学習しない)float32 Tensor[S, D]S = 入力系列長(Encoder側のトークン数) / D = モデル内部のベクトル次元(d_model)[8, 512]。バッチ方向へブロードキャストされる加算

出力

項目名データの意味データ型Tensor形状記号の意味具体例次の渡し先
hidden_states位置情報を含むベクトル列float32 Tensor[B, S, D]B = バッチサイズ(同時に処理する文の数) / S = 入力系列長(Encoder側のトークン数) / D = モデル内部のベクトル次元(d_model)[1, 8, 512](形は変わらない)Self-Attention(Encoder)/Masked Self-Attention(Decoder)
B
バッチサイズ(同時に処理する文の数)
S
入力系列長(Encoder側のトークン数)
D
モデル内部のベクトル次元(d_model)

このサイトでは Tensor 形状を PyTorch の `batch_first=True` 相当、つまり `[B, S, D]`(バッチ次元が先頭)で統一しています。実装によっては `[S, B, D]` を採用するものもあります。

よくある誤解

Level 3数式・Python数式・疑似実装・計算量・実装差

数式(Sinusoidal Positional Encoding)

位置を pospos、次元インデックスを ii(0≤i<D/20 \le i < D/2)として、

PE(pos, 2i)=sin⁡ ⁣(pos100002i/D)PE_{(pos,\,2i)} = \sin\!\left(\frac{pos}{10000^{2i/D}}\right)PE(pos, 2i+1)=cos⁡ ⁣(pos100002i/D)PE_{(pos,\,2i+1)} = \cos\!\left(\frac{pos}{10000^{2i/D}}\right)

次元が大きくなるほど波長が長くなるので、 低い次元は細かい位置差を、高い次元は大まかな位置を表します。

この形の利点は、任意のオフセット kk に対して PEpos+kPE_{pos+k} が PEposPE_{pos} の線形変換で書けることです。相対的な位置関係を扱いやすくなります。

Pythonで表したインターフェース

import math
import torch
from torch import Tensor
 
 
def build_sinusoidal_encoding(seq_len: int, d_model: int) -> Tensor:
    """原典の sin/cos 位置エンコーディングを作る。
 
    Args:
        seq_len: 系列長 S。
        d_model: モデル次元 D(偶数を想定)。
 
    Returns:
        [S, D] の固定値テーブル。学習パラメータではない。
    """
    position = torch.arange(seq_len).unsqueeze(1)                  # [S, 1]
    index = torch.arange(0, d_model, 2)                            # [D/2]
    div_term = torch.exp(-math.log(10000.0) * index / d_model)     # [D/2]
 
    encoding = torch.zeros(seq_len, d_model)                       # [S, D]
    encoding[:, 0::2] = torch.sin(position * div_term)
    encoding[:, 1::2] = torch.cos(position * div_term)
    return encoding
 
 
def add_positional_information(hidden_states: Tensor) -> Tensor:
    """[B, S, D] へ位置情報を加え、同じ形状を返す。
 
    Args:
        hidden_states: [B, S, D]。Token Embedding の出力。
 
    Returns:
        [B, S, D]。位置情報が加算されたベクトル列。
    """
    _, seq_len, d_model = hidden_states.shape
    encoding = build_sinusoidal_encoding(seq_len, d_model)  # [S, D]
    # [S, D] は [B, S, D] へブロードキャストされる
    return hidden_states + encoding.to(hidden_states.device)
 
 
# 最小使用例
hidden_states = torch.zeros(1, 8, 512)          # [B=1, S=8, D=512]
out = add_positional_information(hidden_states)
 
print(out.shape)                                 # torch.Size([1, 8, 512]) == [B, S, D]
print(torch.equal(out[0, 0], out[0, 3]))         # False(位置0と位置3で値が異なる)

この例で単純化している点

  • キャッシュや最大長の事前計算を省き、毎回計算しています。
  • Dropout(原典では加算後に適用)を省略しています。
  • RoPE や Learned Position Embedding は含めていません(下の「派生」を参照)。

型と Tensor 形状の見え方を掴むための擬似実装です。そのまま学習・推論に使えるものではありません。

計算量

テーブル作成が O(S⋅D)O(S \cdot D)、加算が O(B⋅S⋅D)O(B \cdot S \cdot D) です。 Attention の O(S2D)O(S^2 D) に比べて小さく、実行時間にはほとんど影響しません。

派生:現代のLLMで使われる方式

方式概要主な採用例
Sinusoidal(原典)sin/cos の固定値を入口で加算原典 Transformer
Learned Position Embedding位置ごとのベクトルを学習する。最大長を超えられないBERT、GPT-2
RoPE (Rotary Position Embedding)Query と Key を位置に応じて回転させる。各層の Attention 内部で適用Llama、Qwen など多数
ALiBiAttention スコアに距離に比例したバイアスを加えるBLOOM など

RoPE や ALiBi は相対位置を直接扱えるため、 学習時より長い系列への外挿がしやすいという利点があります。 現在の Decoder-only LLM では RoPE が主流です。

この記事の位置づけ

前提となる知識

入力埋め込み

このページ

位置情報

次に読むとよい記事

マルチヘッド自己注意

関連記事