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 個のトークンに分割されるものとして説明します。
- The
- keys
- on
- the
- table
- are
- lost
- .
各トークンは語彙表を引いて整数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、パディングが 0 | int64 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 の選択は後段の性能に直接効きます。
同じ文でも、サブワード分割の細かい Tokenizer を使うと S が増え、
Attention の計算コストとメモリ使用量が増えます。
実装差
- 特殊トークン:
[CLS][SEP](BERT系)、<s></s>(GPT・Llama系)など、 モデルごとに付与されるトークンが異なります。これらもSに数えられます。 - 語彙数
V: 3万〜25万程度まで幅があります。Vは Linear(語彙数へ変換) の出力次元に直結します。 - オフセット情報: 元の文字列のどこに対応するかを返す Tokenizer もあり、 固有表現抽出などで使われます。