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

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

共通抽象度: トークン難易度: 入門推定学習時間: 約10分

入力トークンInput Tokens

30秒で分かる要約文字列をモデルが扱える単位(トークン)へ分割し、それぞれに整数IDを割り当てる工程です。Transformer本体の外側にあるTokenizerが担当し、以降のすべての処理はこのID列を起点にします。

texttext: strinput_ids[B, S]

前提知識

前提知識はありません。このページから読み始められます。

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

何をする機能か

文字列を、モデルが扱える離散的な単位(トークン)へ分割し、 各トークンを語彙表の整数IDに置き換えます。

この処理を行うのは Tokenizer であり、Transformer 本体(Encoder / Decoder)の外側にあります。 API サーバーでいえば、リクエストのボディをパースして内部の型に変換する層に相当します。

なぜ必要か

ニューラルネットワークは数値しか扱えません。文字列をそのまま入力できないため、 「どの単位で区切るか」「各単位にどの番号を振るか」をあらかじめ決めておく必要があります。

単位の選び方には次のようなトレードオフがあります。

  • 文字単位: 語彙数は小さいが、系列長 S が長くなり計算量が増える
  • 単語単位: 系列は短いが、未知語(語彙表にない語)を扱えない
  • サブワード単位: 上の中間。現在の主流(BPE、WordPiece、Unigram など)

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

すべての処理の最初です。Encoder 側では入力文に対して、Decoder 側では (学習時は)正解の出力文に対して実行されます。推論時の Decoder 側では、 生成済みのトークンIDがそのまま次のステップの入力として再利用されます。

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

前段から受け取るもの

アプリケーションからの生の文字列です。前処理として、 Unicode 正規化・小文字化・特殊文字の扱いなどが Tokenizer の設定に応じて行われます。

後段へ渡すもの

整数IDの二次元配列 input_ids [B, S] です。あわせて、 バッチ内でパディングした位置を示す attention_mask [B, S] を渡すのが一般的です。

input_ids は 入力埋め込み に渡され、 そこで初めて連続値のベクトルになります。

例文での処理

共通例文

The keys on the table are lost.

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

このサイトの全ページで同じ例文を使い、同じ文が各処理でどう変わるかを追えるようにしています。

概念上は、次の 8 個のトークンに分割されるものとして説明します。

  1. The
  2. keys
  3. on
  4. the
  5. table
  6. are
  7. lost
  8. .
下の数字は位置(0始まり)です。位置は Positional Encoding で使われます。

各トークンは語彙表を引いて整数IDになります。IDの値そのものに意味はありません (「近いIDだから意味が近い」ということはありません)。意味の近さは、 次の 入力埋め込み で学習されたベクトルが担います。

実際のサブワード分割では、たとえば lost が lo と st に分かれたり、 語頭の空白を含む ▁keys のような単位になったりします。 その場合は系列長 S が 8 より大きくなりますが、以降の処理の形は変わりません。

EncoderとDecoderでの違い

Encoder 側Decoder 側
対象入力文(翻訳元、プロンプトなど)出力文(翻訳先、生成結果)
学習時入力文をそのままトークン化正解文をトークン化し、右シフトして入力にする
推論時一度だけ実行1トークン生成するたびに系列が1つ伸びる

多くの実装では Encoder と Decoder で同じ Tokenizer と同じ語彙表を共有します (翻訳のように言語が異なる場合は別々にすることもあります)。

入力と出力

入力

項目名データの意味データ型Tensor形状記号の意味具体例次の渡し先
text処理したい生の文字列str(バッチなら list[str])——"The keys on the table are lost."Tokenizer の分割処理

出力

項目名データの意味データ型Tensor形状記号の意味具体例次の渡し先
input_ids各トークンの語彙表インデックスint64 Tensor[B, S]B = バッチサイズ(同時に処理する文の数) / S = 入力系列長(Encoder側のトークン数)[[1037, 6309, 2006, 1996, 2795, 2024, 2439, 1012]](例示用の値)入力埋め込み(Token Embedding)
attention_mask実トークンが 1、パディングが 0int64 Tensor[B, S]B = バッチサイズ(同時に処理する文の数) / S = 入力系列長(Encoder側のトークン数)[[1, 1, 1, 1, 1, 1, 1, 1]]Self-Attention のマスク処理
B
バッチサイズ(同時に処理する文の数)
S
入力系列長(Encoder側のトークン数)

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

よくある誤解

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

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

from dataclasses import dataclass
import re
import torch
from torch import Tensor
 
 
@dataclass
class TokenizedBatch:
    input_ids: Tensor       # [B, S] int64
    attention_mask: Tensor  # [B, S] int64(実トークン=1, パディング=0)
    tokens: list[list[str]] # 説明用。実装では通常返さない
 
 
def tokenize(text: str) -> list[str]:
    """説明用に文章をトークンへ分割する。
 
    実運用の Tokenizer はサブワード単位に分割するため、
    同じ文でも分割結果と系列長は異なる。
    """
    return re.findall(r"\w+|[^\w\s]", text)
 
 
def encode(
    texts: list[str],
    vocab: dict[str, int],
    unk_id: int = 0,
) -> TokenizedBatch:
    """文字列のリストを [B, S] の Token ID へ変換する。
 
    Args:
        texts: 入力文のリスト。長さが B になる。
        vocab: トークン文字列 -> ID の語彙表。
        unk_id: 語彙表にないトークンへ割り当てるID。
 
    Returns:
        input_ids [B, S] と attention_mask [B, S] を持つバッチ。
    """
    batch_tokens = [tokenize(text) for text in texts]
    max_len = max(len(tokens) for tokens in batch_tokens)
 
    ids = [
        [vocab.get(token, unk_id) for token in tokens] + [0] * (max_len - len(tokens))
        for tokens in batch_tokens
    ]
    mask = [
        [1] * len(tokens) + [0] * (max_len - len(tokens))
        for tokens in batch_tokens
    ]
 
    return TokenizedBatch(
        input_ids=torch.tensor(ids, dtype=torch.long),      # [B, S]
        attention_mask=torch.tensor(mask, dtype=torch.long),# [B, S]
        tokens=batch_tokens,
    )
 
 
# 最小使用例
vocab = {"The": 5, "keys": 6, "on": 7, "the": 8, "table": 9, "are": 10, "lost": 11, ".": 12}
batch = encode(["The keys on the table are lost."], vocab)
 
print(batch.tokens)            # [['The', 'keys', 'on', 'the', 'table', 'are', 'lost', '.']]
print(batch.input_ids.shape)   # torch.Size([1, 8])  == [B, S]

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

  • 実際のサブワード分割(BPE等)は行わず、空白と句読点で区切る簡易版にしています。
  • パディング、切り詰め(truncation)、特殊トークンの付与は省略しています。
  • 語彙表は説明用の小さな辞書で、実際は数万語規模です。

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

系列長が後段に与える影響

系列長 S は、この工程だけで決まります。Self-Attention の計算量は S の 2 乗に比例するため、 Tokenizer の選択は後段の性能に直接効きます。

Self-Attention の計算量=O(S2⋅D)\text{Self-Attention の計算量} = O(S^2 \cdot D)

同じ文でも、サブワード分割の細かい Tokenizer を使うと S が増え、 Attention の計算コストとメモリ使用量が増えます。

実装差

  • 特殊トークン: [CLS] [SEP](BERT系)、<s> </s>(GPT・Llama系)など、 モデルごとに付与されるトークンが異なります。これらも S に数えられます。
  • 語彙数 V: 3万〜25万程度まで幅があります。V は Linear(語彙数へ変換) の出力次元に直結します。
  • オフセット情報: 元の文字列のどこに対応するかを返す Tokenizer もあり、 固有表現抽出などで使われます。

この記事の位置づけ

前提となる知識

なし

このページ

入力トークン

関連記事