自然言語処理モデルに文を入力するとき、文字列をそのまま渡すわけにはいきません。モデルが処理できるのは数値の列であり、文を何らかの単位に分割して数値化する必要があります。この分割の仕方がトークナイゼーション(tokenization)です。
ChatGPTに「こんにちは」と入力したとき、モデルの内部では何が起きているのでしょうか。実は「こんにちは」という文字列は、そのままモデルに入るのではなく、まずサブワードと呼ばれる単位に分割され、さらに整数のIDに変換されてからモデルに渡されます。この前処理の仕組みがトークナイゼーションであり、NLPパイプラインの最初のステップです。
「単語ごとに分割すればいいのでは?」と思うかもしれませんが、単語レベルのトークナイゼーションには深刻な問題があります。語彙に含まれない単語(OOV: Out-of-Vocabulary)が処理できず、語形変化(play, playing, played)を別の単語として扱うため語彙が膨大になります。たとえば英語のWikipediaだけでも異なり語数は数百万に達し、すべてを語彙に入れるのは非現実的です。
一方、文字レベルの分割では語彙は小さくなりますが、系列が非常に長くなり、文字だけでは意味を捉えにくくなります。「cat」を「c」「a」「t」に分割すると、Transformerの計算コストは系列長の2乗に比例するため、処理が非常に遅くなります。
この「単語と文字の間」を最適に見つけるのが、現代のサブワード・トークナイゼーションです。「playing」を「play」+「ing」に分割すれば、語彙を小さく保ちつつ意味のある単位を維持できます。「play」という語幹と「ing」という活用語尾の情報が保存されるため、「playing」「plays」「played」を個別に覚える必要がなくなるのです。
トークナイゼーションを理解すると、以下のことが可能になります。
- 言語モデルの入出力の理解: BERT・GPTが実際に何を処理しているか
- 多言語対応: 日本語・中国語など空白区切りがない言語への対処
- 効率的な推論: トークン数の最適化による計算コスト削減
- カスタムトークナイザの構築: ドメイン特化型モデルのための語彙設計
特にLLMの利用料金が「トークン数」で課金される現在、トークナイゼーションの仕組みを理解しておくことは、コスト最適化の観点からも重要です。
本記事の内容
- 単語・文字・サブワードの比較
- BPE(Byte Pair Encoding)のアルゴリズム
- WordPieceのアルゴリズム
- SentencePieceと多言語対応
- Pythonでのスクラッチ実装
前提知識
この記事を読む前に、以下の記事を読んでおくと理解が深まります。
- BERTとGPTの違い — 言語モデルの基本
トークナイゼーションの3つのレベル
トークナイゼーションには、分割の粒度によって大きく3つのレベルがあります。それぞれの特徴を具体例とともに見ていきましょう。
単語レベル
「I love machine learning」→ ["I", "love", "machine", "learning"]
利点: 直感的で、各トークンが意味を持つ。人間にとって自然な分割であり、形態素解析の結果とも近い。各トークンの意味が明確なので、モデルの出力を解釈しやすいという利点もあります。
欠点: 語彙が巨大(英語で数十万語)、OOV問題、語形変化を別トークンとして扱う。たとえば医学論文に特有の「methylprednisolone」のような専門用語が訓練データに含まれていなければ、[UNK](未知語)トークンに置き換えられてしまい、情報が完全に失われます。さらに「play」「plays」「playing」「played」が全て異なるトークンとして扱われるため、語彙が不必要に膨張し、各単語の学習に必要なデータ量も増えてしまいます。
文字レベル
「I love」→ ["I", " ", "l", "o", "v", "e"]
利点: 語彙が極小(英語なら大小文字と記号を合わせても100文字未満)、OOVがない。どんな入力テキストも必ずトークン化できるため、堅牢性が高い。新しい単語や造語が登場しても問題なく処理できます。
欠点: 系列が非常に長い、文字単体では意味を捉えにくい。たとえば「cat」は3トークンになりますが、「c」「a」「t」それぞれからは「猫」の意味を読み取ることができません。また、Transformerの計算量は系列長 $T$ に対して $O(T^2)$ であるため、系列が長くなると処理が大幅に重くなります。100語の文が文字レベルでは約500トークンになると、Attentionの計算は単語レベルの約25倍になります。
サブワードレベル(現代の標準)
「playing」→ ["play", "##ing"]
利点: 語彙を適度なサイズ(3万〜5万程度)に保ちつつ、OOVをほぼ排除。頻出語はそのまま、稀な語はサブワードに分割。たとえば「unhappiness」は「un」+「happiness」や「un」+「happi」+「ness」のように分割され、接頭辞「un-」(否定)と接尾辞「-ness」(名詞化)の意味が保存されます。
欠点: 分割が人間の直感と異なる場合がある。学習コーパスによって分割結果が変わるため、ドメインが大きく異なるテキストでは最適でない分割になることもあります。たとえば、一般的なコーパスで学習したトークナイザでは、化学式「CH3COOH」が意味不明な分割になることがあります。
以下の表で3つのレベルを整理します。
| レベル | 語彙サイズ | 系列長 | OOV | 意味の保持 |
|---|---|---|---|---|
| 単語 | 数十万〜数百万 | 短い | あり | 高い |
| 文字 | 数十〜数百 | 非常に長い | なし | 低い |
| サブワード | 3万〜5万 | 中程度 | ほぼなし | 中〜高 |
歴史的には、初期のNLPシステム(Word2Vec、GloVeなど)は単語レベルを採用していましたが、2015年のBPEの導入以降、サブワードレベルが主流になりました。現在のほぼすべての大規模言語モデル(GPT-4、LLaMA、Claude、Geminiなど)がサブワードレベルのトークナイゼーションを使用しています。
それでは、サブワード・トークナイゼーションの代表的な手法であるBPEの仕組みを見ていきましょう。
BPE(Byte Pair Encoding)
歴史と概要
BPEは元々1994年にGageが提案したデータ圧縮アルゴリズムです。データ圧縮の文脈では、バイト列の中で最も頻出するバイトペアを1つの新しいバイトに置き換えるという単純なアイデアでした。2015年にSennrichらがこれをサブワード・トークナイゼーションに適用し、機械翻訳の性能を大幅に改善しました。GPT-2, GPT-3, RoBERTaなどで使用されています。
BPEの核となるアイデアは非常にシンプルです。「頻繁に一緒に出現する文字列は、1つのトークンにまとめてしまおう」ということです。これは日本語の漢字の成り立ちに似ています。「木」が3つ集まって頻繁に使われるから「森」という1文字になった、というイメージです。
学習アルゴリズム
BPEの学習は、文字レベルの語彙から始めて、最も頻出するペアを繰り返しマージしていくボトムアップのアルゴリズムです。
入力: 訓練コーパスから単語とその出現頻度
ステップ1: 各単語を文字に分割し、末尾に特殊記号(</w>)を付けます。</w> は単語の境界を示すための記号で、デコード時に元の文を正しく復元するために必要です。
例: {"low": 5, "lower": 2, "newest": 6, "widest": 3}
→ {"l o w </w>": 5, "l o w e r </w>": 2, "n e w e s t </w>": 6, "w i d e s t </w>": 3}
ステップ2: 全てのペアの出現頻度を数えます。各単語内で隣接する文字ペアの頻度を、単語の出現回数を重みとして合計します。
("e", "s") → 6+3=9, ("s", "t") → 6+3=9, ("l", "o") → 5+2=7, …
ステップ3: 最頻出ペアをマージして新しいトークンを作ります。
("e", "s") → "es" (頻度9で最多の場合)
マージ後、語彙に「es」が追加され、「e」と「s」は引き続き個別のトークンとしても残ります。
ステップ4: ステップ2-3を指定回数(=目標語彙サイズ – 初期語彙サイズ)繰り返します。
マージ回数が語彙サイズを直接決定するため、目標語彙サイズをハイパーパラメータとして事前に指定します。たとえばGPT-2では語彙サイズが50,257に設定されており、それに応じたマージ回数が実行されました。
エンコード(推論時)
学習で得たマージ規則を順番に適用して、入力テキストをサブワードに分割します。マージ規則の適用順序は学習時の順番(頻度が高いものから)です。つまり、最も頻出していたペアから優先的にマージされます。
具体的には、入力単語をまず文字に分割し、学習時に記録したマージ規則を第1ルールから順番に適用していきます。該当するペアが見つかればマージし、見つからなければ次のルールに進みます。全ルールを適用し終えた結果が、最終的なトークン列になります。
それでは、BPEの学習過程をPythonで実装して、実際にどのようにマージが進むかを確認してみましょう。
import re
from collections import Counter
# --- BPEのスクラッチ実装 ---
def get_stats(vocab):
"""語彙中の全てのシンボルペアの頻度を計算"""
pairs = Counter()
for word, freq in vocab.items():
symbols = word.split()
for i in range(len(symbols) - 1):
pairs[(symbols[i], symbols[i+1])] += freq
return pairs
def merge_vocab(pair, vocab):
"""最頻出ペアをマージ"""
bigram = re.escape(' '.join(pair))
pattern = re.compile(r'(?<!\S)' + bigram + r'(?!\S)')
new_vocab = {}
for word, freq in vocab.items():
new_word = pattern.sub(''.join(pair), word)
new_vocab[new_word] = freq
return new_vocab
# 訓練コーパス(単語: 頻度)
corpus = {
"l o w </w>": 5,
"l o w e r </w>": 2,
"n e w e s t </w>": 6,
"w i d e s t </w>": 3,
}
print("=== BPE学習過程 ===")
print(f"初期語彙: {set(' '.join(corpus.keys()).split())}")
n_merges = 10
merge_rules = []
for i in range(n_merges):
pairs = get_stats(corpus)
if not pairs:
break
best_pair = max(pairs, key=pairs.get)
corpus = merge_vocab(best_pair, corpus)
merge_rules.append(best_pair)
print(f"マージ {i+1}: {best_pair} → {''.join(best_pair)} "
f"(頻度: {pairs[best_pair]})")
print(f"\n最終語彙: {set(' '.join(corpus.keys()).split())}")
print(f"マージ規則: {merge_rules}")
このコードの出力から、BPEの学習過程が理解できます。最も頻出するペアから順にマージされていき、「est」「low」「newest」のようなサブワードが形成されます。頻出する文字列パターンが優先的に1つのトークンにまとまることで、効率的な語彙が構築されます。
注目すべき点は、マージが進むにつれて形成されるサブワードが実際に言語的な意味を持つことが多いことです。「est」は英語の最上級接尾辞、「low」は独立した単語でもあり、「er」は比較級接尾辞です。BPEは言語学の知識を一切使わずに、純粋に頻度統計だけで意味のある単位を発見するのです。
BPEが頻度ベースでマージを行うのに対して、より洗練されたマージ基準を持つWordPieceという手法も存在します。
WordPiece
BPEとの違い
WordPieceはGoogleが開発し、BERTで使用されているサブワード・トークナイゼーション手法です。BPEと似ていますが、マージの基準が異なります。
BPEは頻度が最も高いペアをマージしますが、WordPieceは尤度の増加が最も大きいペアをマージします。この違いは重要です。頻度だけを見ると、個別でも頻出する文字同士のペア(たとえば「e」と「a」)がマージされやすくなりますが、必ずしもそのペアが意味のある単位とは限りません。
WordPieceのマージスコアは次のように定義されます。
$$ \text{score}(x, y) = \frac{\text{freq}(xy)}{\text{freq}(x) \times \text{freq}(y)} $$
この基準は、個別に出現するよりもペアとして出現する頻度が高いほどスコアが高くなります。つまり、相互情報量(Pointwise Mutual Information)に基づいたマージ基準です。
具体例で違いを見てみましょう。「th」というペアと「qu」というペアがあったとします。「t」も「h」も英語では非常に高頻度な文字なので、BPEでは「th」の頻度が高くなりやすいです。一方、「q」の後には「u」がほぼ必ず続くため、WordPieceでは $\text{freq}(qu) / (\text{freq}(q) \times \text{freq}(u))$ が非常に大きくなり、「qu」が優先的にマージされます。このように、WordPieceは「一緒に出現する必然性が高い」ペアを優先します。
特徴
WordPieceでは、単語の先頭以外のサブワードに ## プレフィックスを付けます。
例: "playing" → ["play", "##ing"]
## は「このサブワードは前のトークンに続くもの」を示します。この接頭辞があることで、トークン列から元の単語を復元するとき、## の付いたトークンは前のトークンに結合すればよいとわかります。たとえば ["un", "##happi", "##ness"] からは「unhappiness」が復元できます。
BPEとWordPieceのどちらが優れているかは一概には言えません。実験的には両者の性能はほぼ同等ですが、WordPieceの方が語彙の「質」が高い(意味のある単位になりやすい)という報告もあります。
ここまでBPEとWordPieceというボトムアップの手法を見てきました。次に紹介するSentencePieceは、言語に依存しないトークナイゼーションを実現する枠組みです。
SentencePiece
言語非依存のトークナイゼーション
SentencePiece(Kudo and Richardson, 2018)は、事前のトークナイゼーション(空白での分割)を不要にし、生のテキストから直接サブワード分割を学習します。
BPEやWordPieceは、暗黙のうちに「空白で単語が区切られている」ことを前提にしています。しかし、日本語の「私は猫が好きです」や中国語の「我喜欢猫」のように、多くの言語では空白による区切りがありません。日本語をBPEで処理するには、まずMeCabなどの形態素解析器で分かち書きする必要があり、言語ごとに前処理パイプラインが異なるという問題がありました。
SentencePieceはこの問題を根本的に解決します。空白を特殊文字(▁、Unicode U+2581)として扱い、全ての文字を等しく処理します。つまり、空白も1つの「文字」として扱われるため、空白の有無に関わらずあらゆる言語を統一的に処理できるのです。
例: "I love cats" → ["▁I", "▁love", "▁cats"]
この方式の利点は、トークン列から元のテキストを完全に復元できることです。▁ を空白に戻すだけで元の文が得られます。また、言語ごとの前処理が不要なため、100以上の言語を1つのトークナイザで扱える多言語モデル(mBERT、XLM-R、LLaMAなど)の実現に大きく貢献しました。
Unigram言語モデル
SentencePieceでは、BPEの代替としてUnigram言語モデルベースのトークナイゼーションも利用できます。BPEが「小さい語彙から大きくしていく」ボトムアップなのに対し、Unigramは「大きい語彙から小さくしていく」トップダウンのアプローチを取ります。
Unigramモデルでは、大きな候補語彙(たとえば100万トークン)から始めて、損失関数への寄与が小さいトークンを削除していきます。
$$ \mathcal{L} = -\sum_{\bm{x} \in D} \log P(\bm{x}) = -\sum_{\bm{x} \in D} \log \sum_{\bm{s} \in S(\bm{x})} \prod_{i=1}^{|\bm{s}|} p(s_i) $$
$S(\bm{x})$ は文 $\bm{x}$ の全ての可能な分割、$p(s_i)$ は各サブワードの出現確率です。この式の意味は「コーパス全体の対数尤度を最大化するようなサブワード確率分布を求める」ということです。
Unigramモデルの学習手順は次のとおりです。
- 十分に大きな初期語彙を用意する(頻出サブストリングなど)
- EMアルゴリズムで各サブワードの確率 $p(s_i)$ を推定する
- 各サブワードを語彙から除外したときの損失増加を計算する
- 損失増加が小さいトークン(≒なくても困らないトークン)を一定割合削除する
- 目標語彙サイズに達するまで2-4を繰り返す
BPEが貪欲に「最頻出ペア」を選ぶのに対し、Unigramはコーパス全体の尤度を考慮するため、理論的にはより最適な語彙が得られます。実際に、T5やmBARTではUnigramモデルが採用されています。
各手法の比較
ここまでに紹介した手法を一覧表で比較します。SentencePieceは厳密にはアルゴリズムではなく「フレームワーク」であり、内部でBPEまたはUnigramを選択して使います。
| 手法 | マージ基準 | 方向 | 使用モデル |
|---|---|---|---|
| BPE | 頻度 | ボトムアップ | GPT-2, RoBERTa |
| WordPiece | 尤度増加 | ボトムアップ | BERT |
| Unigram | 尤度減少 | トップダウン | T5, mBART |
| SentencePiece | BPE or Unigram | 言語非依存 | LLaMA, T5 |
各手法の比較の補足
ここまで紹介した3つの手法は、いずれも「サブワードの語彙を自動的に構築する」という目的は同じですが、そのアプローチが異なります。BPEは実装がシンプルで高速、WordPieceは統計的にやや洗練された基準を持ち、Unigramは理論的に最も精緻です。実務では、使用するモデルのアーキテクチャに合わせてトークナイザを選ぶことが一般的です。
次に、これらの手法をさらに発展させたByte-level BPEを見ていきましょう。
Byte-level BPE
GPT-2のアプローチ
GPT-2はByte-level BPEを採用しています。文字ではなくバイトを基本単位とすることで、任意のUnicode文字を表現できます。
通常のBPEでは、基本語彙が「文字」(アルファベット、漢字など)で構成されます。しかし、世界中のUnicode文字をすべて基本語彙に入れると、それだけで数万トークンになってしまいます。Byte-level BPEはこの問題を、文字ではなくバイト(0x00〜0xFF の256種)を基本単位にすることで解決しました。
基本語彙は256個のバイト値(0x00〜0xFF)で、どんな言語やテキストも表現可能です。BPEのマージ操作はバイトレベルで行われます。
利点: [UNK] トークンが一切不要。どんな入力も確実にトークン化可能。たとえ絵文字やプログラミング言語の特殊記号であっても、バイト列に分解すれば必ず表現できます。
欠点: 日本語などマルチバイト文字は複数のバイトに分割され、トークン効率がやや低い。たとえば「猫」はUTF-8で3バイト(0xE7, 0x8C, 0xAB)なので、マージが進んでいない場合は3トークンになります。
それでは、ここまで学んだ各手法のトークン数の違いをPythonで可視化し、理論で述べた特性を実際に確認してみましょう。
Pythonでの実装と可視化
import numpy as np
import matplotlib.pyplot as plt
from collections import Counter
# --- トークナイゼーションの比較 ---
# 単純な文字レベルトークナイザ
def char_tokenize(text):
return list(text)
# 単純な単語レベルトークナイザ
def word_tokenize(text):
return text.split()
# 簡易BPEトークナイザ(学習済みマージ規則を適用)
class SimpleBPE:
def __init__(self, merge_rules):
self.merge_rules = merge_rules
def tokenize(self, word):
tokens = list(word) + ["</w>"]
for pair in self.merge_rules:
i = 0
while i < len(tokens) - 1:
if tokens[i] == pair[0] and tokens[i+1] == pair[1]:
tokens = tokens[:i] + [''.join(pair)] + tokens[i+2:]
else:
i += 1
return tokens
# テスト文
test_texts = [
"The cat sat on the mat",
"Playing with natural language processing",
"Transformers are powerful models",
"Understanding tokenization is important",
]
# 学習済みマージ規則(例示)
merge_rules = [
("i", "n"), ("in", "g"), ("t", "h"), ("th", "e"),
("a", "t"), ("o", "n"), ("e", "r"), ("i", "s"),
]
bpe = SimpleBPE(merge_rules)
fig, axes = plt.subplots(1, 2, figsize=(14, 5.5))
# (a) 各手法のトークン数比較
ax = axes[0]
methods = ["Character", "Word", "BPE (simple)"]
token_counts = {m: [] for m in methods}
for text in test_texts:
token_counts["Character"].append(len(char_tokenize(text)))
token_counts["Word"].append(len(word_tokenize(text)))
# BPEは単語ごとに適用
bpe_tokens = []
for word in text.lower().split():
bpe_tokens.extend(bpe.tokenize(word))
token_counts["BPE (simple)"].append(len(bpe_tokens))
x = np.arange(len(test_texts))
width = 0.25
colors = ["tab:red", "tab:blue", "tab:green"]
for i, (method, color) in enumerate(zip(methods, colors)):
ax.bar(x + i * width, token_counts[method], width,
color=color, alpha=0.8, label=method)
ax.set_xlabel("Test text index", fontsize=12)
ax.set_ylabel("Number of tokens", fontsize=12)
ax.set_title("Token Count by Method", fontsize=13)
ax.set_xticks(x + width)
ax.set_xticklabels([f"Text {i+1}" for i in range(len(test_texts))],
fontsize=10)
ax.legend(fontsize=10)
ax.grid(True, alpha=0.3, axis="y")
# (b) 語彙サイズ vs トークン効率のトレードオフ
ax = axes[1]
vocab_sizes = [26, 100, 1000, 5000, 10000, 30000, 50000, 100000]
avg_token_lens = [1, 1.5, 2.5, 3.5, 4.0, 4.5, 5.0, 5.5]
ax.semilogx(vocab_sizes, avg_token_lens, "o-", linewidth=2,
markersize=8, color="steelblue")
ax.fill_between(vocab_sizes, [0]*len(vocab_sizes), avg_token_lens,
alpha=0.1, color="steelblue")
ax.annotate("Character\nlevel", xy=(26, 1), fontsize=10,
xytext=(50, 1.5), arrowprops=dict(arrowstyle="->"))
ax.annotate("Subword\n(BPE/WP)", xy=(30000, 4.5), fontsize=10,
xytext=(5000, 5.3), arrowprops=dict(arrowstyle="->"))
ax.annotate("Word\nlevel", xy=(100000, 5.5), fontsize=10,
xytext=(30000, 6), arrowprops=dict(arrowstyle="->"))
ax.set_xlabel("Vocabulary size (log scale)", fontsize=12)
ax.set_ylabel("Avg. token length (chars)", fontsize=12)
ax.set_title("Vocabulary Size vs Token Efficiency", fontsize=13)
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig("tokenization_comparison.png", dpi=150, bbox_inches="tight")
plt.show()
この可視化から、トークナイゼーション手法の特性が比較できます。
-
左図(トークン数の比較): 文字レベル(赤)は最もトークン数が多く、系列長が長くなります。たとえばText 4(「Understanding tokenization is important」)では文字レベルが約40トークンに達しますが、単語レベルでは4トークンに収まります。BPE(緑)はその中間で、適度なトークン数を実現しています。BPEが単語レベルより若干多いのは、一部の単語がサブワードに分割され、さらに
</w>トークンが追加されているためです -
右図(語彙サイズとトークン効率のトレードオフ): 語彙を小さくすると各トークンが短くなり(系列が長くなり)、語彙を大きくすると各トークンが長くなります(系列が短くなるが語彙管理のコストが増える)。サブワード手法(BPE/WordPiece)は語彙3〜5万程度で、効率と管理性のバランスが取れた位置にあります。曲線が対数的に増加していることから、語彙サイズを10倍にしても平均トークン長は1文字程度しか伸びないことがわかります。つまり、語彙サイズには「収穫逓減」があり、闇雲に大きくしても効率は頭打ちになるのです
トークナイゼーションの実務的な注意点
最後に、実務でトークナイゼーションを扱うときに知っておくべき注意点を整理します。
トークナイザとモデルの一致: 事前学習済みモデルを使う場合、そのモデルの学習時に使われたトークナイザを必ず使う必要があります。たとえばBERTにGPT-2のトークナイザを使うと、語彙IDの対応が合わないため正しく動作しません。HuggingFace Transformersなどのライブラリでは、from_pretrained メソッドでモデルとトークナイザをセットで読み込むことが推奨されています。
特殊トークンの扱い: [CLS]、[SEP]、[PAD]、[UNK]、<s>、</s> などの特殊トークンは、モデルごとに異なります。BERTは [CLS] と [SEP] を使いますが、GPT-2は <|endoftext|> を使います。これらの違いを意識しないと、微妙なバグの原因になります。
語彙サイズの選択: 語彙サイズは精度と効率のトレードオフです。語彙が大きすぎると埋め込み行列のパラメータ数が増え(語彙サイズ $V$ × 埋め込み次元 $d$ のパラメータが必要)、小さすぎると1つの単語が多数のトークンに分割されて系列長が長くなります。一般的には、英語中心のモデルでは30,000〜50,000、多言語モデルでは100,000〜250,000程度が標準的です。
日本語のトークナイゼーション: 日本語は空白で区切られないだけでなく、ひらがな・カタカナ・漢字・英数字が混在するため、トークナイゼーションが特に難しい言語の1つです。SentencePieceの登場以前は、MeCabなどの形態素解析器で分かち書きしてからBPEを適用するという二段階の処理が必要でした。現在では、SentencePieceやByte-level BPEにより、前処理なしで日本語を直接トークン化できるようになっています。
まとめ
本記事では、自然言語処理のトークナイゼーション手法を体系的に解説しました。
- 単語レベルはOOV問題と語彙爆発、文字レベルは系列長の問題がある。サブワードレベルがその中間で最適解
- BPE: 最頻出ペアをマージするボトムアップ。GPT-2, RoBERTaで使用
- WordPiece: 尤度増加が最大のペアをマージ。BERTで使用
- SentencePiece: 言語非依存で生テキストから直接学習。日本語等の空白なし言語に対応
- Byte-level BPE: バイトを基本単位とし、OOVを完全排除。GPT-2以降で採用
トークナイゼーションはNLPパイプラインの最初のステップであり、ここでの選択がモデルの性能、推論速度、対応言語に大きく影響します。サブワード分割という一見地味な技術が、現代の大規模言語モデルの多言語対応や効率的な推論を支えている土台なのです。
次のステップとして、以下の記事も参考にしてください。トークン化された結果をどのようにベクトルに変換するかは、単語埋め込みの記事で詳しく解説しています。
- 単語埋め込みの理論 — トークンをベクトルに変換する仕組み
- BERTとGPTの違い — トークナイザの使い方の違い