Tech カテゴリのイメージ

トークン化とは?LLMが文章を読む前に起きていること

LLMは文章を文字列のまま受け取りません。入力された文章は、まずトークンという単位に分けられ、それぞれ整数のIDへ変換されます。この処理がトークン化です。

トークンは必ずしも単語一つではありません。よく現れる語が一つのトークンになる場合もあれば、珍しい語が複数に分かれる場合もあります。空白や記号、文字の一部がトークンに含まれることもあります。「日本語なら一文字が一トークン」と固定して考えると、入力長や費用を見積もるときにずれが生じます。

なぜ文字や単語をそのまま使わないのか

単語単位だけにすると、未知語、固有名詞、活用形への対応が難しくなり、語彙も大きくなります。反対に一文字ずつなら未知語には強くなりますが、一つの文を表す列が長くなります。

サブワード方式は、その中間を狙います。頻出する文字列はまとめ、まれな文字列は小さく分割します。OpenAIが公開している tiktoken はBPE系のトークナイザーで、文字列をUTF-8のバイト列にしたうえで、学習済みの規則に従ってまとまりを作ります。

トークンIDは単なる番号

「猫」がトークンID 1234 になったとしても、1234という数の大小に猫らしさが入っているわけではありません。IDは語彙表の項目を指す番号です。次の段階で埋め込み表を引き、各IDを学習可能なベクトルへ置き換えます。

同じ文章でも、使うトークナイザーや語彙が違えば分割位置とIDは変わります。モデルに対応しないトークナイザーを使うと、期待する入力になりません。IDを他モデルへそのまま持ち込めないのはこのためです。

小さな例で分割と復元を見る

次は原理を観察するための簡易例です。実際のBPE学習やモデル固有の前処理を再現するものではなく、「長い登録文字列を使えなければ短い単位へ分かれる」という性質だけを取り出しています。

vocab = {
"機械学習": 10,
"機械": 11,
"学習": 12,
"を": 13,
"学ぶ": 14,
"学": 15,
"ぶ": 16,
}
def encode(text):
pieces = sorted(vocab, key=len, reverse=True)
tokens = []
position = 0
while position < len(text):
piece = next((p for p in pieces if text.startswith(p, position)), None)
if piece is None:
raise ValueError(f"語彙にない文字: {text[position]}")
tokens.append((piece, vocab[piece]))
position += len(piece)
return tokens
def decode(token_ids):
decoder = {token_id: piece for piece, token_id in vocab.items()}
return "".join(decoder[token_id] for token_id in token_ids)
text = "機械学習を学ぶ"
tokens = encode(text)
token_ids = [token_id for _, token_id in tokens]
print(tokens)
print(token_ids)
print(decode(token_ids))

出力:

[('機械学習', 10), ('を', 13), ('学ぶ', 14)]
[10, 13, 14]
機械学習を学ぶ

この語彙では「機械学習」と「学ぶ」が登録されているため、全文は3トークンになります。もし 機械学習 を語彙から外せば、先頭は 機械 と 学習 に分かれます。使えるまとまりによって列の長さが変わることが分かります。

実際のBPEでは、単純な最長一致だけで決めるわけではありません。学習時に作られた結合順位や、実装ごとの事前分割規則が使われます。この記事の出力を特定モデルのトークン数として扱わないでください。

トークン数が影響する三つの場所

一つ目はコンテキスト長です。モデルが一度に扱える長さは、通常は文字数ではなくトークン数で管理されます。日本語で同じ文字数でも、記号、固有名詞、コードなどが混ざるとトークン数は変わり得ます。

二つ目は計算量と利用料金です。APIでは入力・出力トークン数が計測や課金の単位になる場合があります。ただし料金や上限はモデルごとに変わるため、利用時点の公式仕様を確認する必要があります。

三つ目はモデルの学習です。次トークン予測では、正解となる次のIDに高い確率を割り当てるよう損失を計算します。モデルが直接予測しているのは完成した単語ではなく、トークン語彙上の次の候補です。

分割表示で気をつけること

一つのトークンを個別に文字列へ戻すと、UTF-8として不完全なバイト列になる場合があります。そのため、トークンを一個ずつ通常の文字列として表示した結果だけで分割を判断すると、置換文字が見えることがあります。調査時には各トークンのバイト列を確認するか、ID列全体を復号して元の文章へ戻ることを確かめます。

トークン化で文章がID列になったら、次は各IDを数値ベクトルに置き換えます。その埋め込みが、Transformerへ渡す実際の入力になります。

講座の前後記事

参照資料