※本記事は、Andrej Karpathy氏によるYouTube動画「Let's build the GPT Tokenizer」の内容を基に作成されています。動画の詳細情報は https://www.youtube.com/watch?v=zduSFxRajkE でご覧いただけます。本記事では、動画の内容を要約しております。なお、本記事の内容は原著作者の見解を正確に反映するよう努めていますが、要約や解釈による誤りがある可能性もありますので、正確な情報や文脈については、オリジナルの動画をご覧いただくことをお勧めいたします。また、Andrej Karpathy氏のTwitterアカウント(@karpathy)もご参照ください。
1. はじめに
1.1 トークン化の必要性と課題
こんにちは。このビデオでは、大規模言語モデルにおけるトークン化のプロセスについて詳しく説明します。正直に言うと、トークン化は私が最も好きではない部分です。しかし、残念ながらこれは詳細に理解する必要があります。なぜなら、トークン化はかなり複雑で、隠れた落とし穴がたくさんあるからです。大規模言語モデルの奇妙な挙動の多くは、実はトークン化に起因しています。
前回のビデオ「Let's Build GPT from scratch」では、非常に単純なバージョンのトークン化を行いました。そこでは、シェイクスピアのデータセットを使用し、65の可能な文字からなる語彙を作成しました。各文字を整数トークンに変換するルックアップテーブルを作り、文字列をトークンのシーケンスに変換しました。
しかし、実際の最先端の言語モデルでは、トークン語彙を構築するためにもっと複雑な方式を使用しています。文字レベルではなく、文字のチャンクレベルで扱っており、これらの文字チャンクの構築にはByte Pair Encoding(BPE)アルゴリズムなどが使用されています。
1.2 ビデオの内容概要
このビデオでは、以下の内容をカバーします:
- GPT-2の論文を参照し、入力表現とトークン化の方法を確認します。
- トークン化の複雑さを示すために、tiktoken.bpe.appを使用して実際のトークン化の例を見ていきます。
- Unicode、UTF-8エンコーディング、バイトレベルのエンコーディングについて説明します。
- BPEアルゴリズムを実装し、トークナイザーを構築します。
- GPT-2とGPT-4のトークナイザーの違いを分析します。
- OpenAIのTiktokenライブラリとGoogleのSentencePieceライブラリについて解説します。
- トークン化が引き起こす具体的な問題と事例を詳しく見ていきます。
- 語彙サイズの決定方法や、事前学習モデルへの新トークン追加について議論します。
- マルチモーダルAIにおけるトークン化の応用について触れます。
このビデオを通じて、トークン化の複雑さと重要性を理解し、大規模言語モデルの開発や使用において直面する可能性のある問題を予測し、解決する能力を身につけることを目指します。トークン化は私の最も好きではない部分ですが、大規模言語モデルの動作を理解する上で非常に重要です。それでは、トークン化の世界に飛び込んでいきましょう。
2. トークン化の基本
2.1 ユニコードとコードポイント
トークン化の基本を理解するために、まずユニコードとコードポイントについて説明します。Pythonの文字列は、実際にはユニコードコードポイントの不変シーケンスです。ユニコードコードポイントは、Unicode Consortiumによって定義されており、現在約150,000の文字が定義されています。これらは161の異なる文字体系にわたっており、最新のスタンダードは2023年9月にリリースされたUnicode 15.1です。
Pythonでは、ord()関数を使用して単一の文字のユニコードコードポイントを取得できます。例えば:
print(ord('H')) # 出力: 104
print(ord('😊')) # 出力: 128522
ここで、'H'のコードポイントは104、笑顔の絵文字「😊」のコードポイントは128522です。
しかし、ord()関数は単一の文字にのみ適用でき、複数の文字からなる文字列には使用できません。例えば、「안녕」(韓国語で「こんにちは」)のような文字列全体にord()を適用することはできません。代わりに、以下のように各文字に対して適用する必要があります:
print([ord(char) for char in "안녕"]) # 出力: [50504, 45397]
2.2 UTF-8エンコーディング
次に、UTF-8エンコーディングについて説明します。UTF-8は、ユニコードコンソーシアムが定義する3つのエンコーディング(UTF-8、UTF-16、UTF-32)の1つです。UTF-8は、各コードポイントを1から4バイトのバイトストリームに変換する可変長エンコーディングです。
Pythonでは、文字列クラスのencode()メソッドを使用してUTF-8エンコーディングを行うことができます:
text = "Hello, World! 안녕하세요"
utf8_bytes = text.encode('utf-8')
print(list(utf8_bytes))
# 出力: [72, 101, 108, 108, 111, 44, 32, 87, 111, 114, 108, 100, 33, 32, 236, 149, 136, 235, 133, 149, 237, 95, 148, 236, 132, 184, 236, 154, 148]
ここで、ASCII文字は1バイトで表現されていますが、韓国語の文字はそれぞれ3バイトで表現されています。
同様に、bytes.decode('utf-8')を使用してバイトシーケンスを文字列に戻すことができます:
decoded_text = utf8_bytes.decode('utf-8')
print(decoded_text) # 出力: Hello, World! 안녕하세요
UTF-8は、ASCII文字セットとの下位互換性があるため、インターネット上で広く使用されています。
2.3 バイトペアエンコーディング(BPE)アルゴリズムの導入
最後に、バイトペアエンコーディング(BPE)アルゴリズムを導入します。BPEは、最も頻繁に出現する連続したバイトのペア(またはサブワード)を繰り返し結合していくことでデータを圧縮するアルゴリズムです。
BPEの基本的な手順は次のとおりです:
- 初期語彙を個々の文字(またはバイト)で初期化します。
- コーパス内で最も頻繁に出現するペアを見つけます。
- このペアを新しい要素として語彙に追加します。
- コーパス内のこのペアのすべての出現を新しい要素で置き換えます。
- 指定された回数、または目的の語彙サイズに達するまで、ステップ2-4を繰り返します。
例えば、「aaabdaaabac」という文字列があるとします:
- 最も頻繁に出現するペア「aa」を「Z」で置き換えます:「ZabdZabac」
- 次に頻繁なペア「Za」を「Y」で置き換えます:「YbdYbac」
このプロセスを通じて、元の文字列を圧縮しながら、新しいトークン(この場合「Z」と「Y」)を作成しています。
BPEの利点は、頻繁に出現する部分文字列を効率的に表現できることです。これにより、未知語や稀少語の問題を軽減し、語彙サイズを管理可能な範囲に保ちながら、効果的なトークン化を実現できます。
3. BPEアルゴリズムの実装
BPEアルゴリズムの実装を詳しく説明していきます。まず、テキストデータを準備し、UTF-8エンコーディングを行います。そして、最も頻繁に出現するペアを特定し、マージしていく過程を実装します。
3.1 最も頻繁に出現するペアの特定
まず、テキストデータを準備します。私は、UTF-8エンコーディングについて説明している興味深いブログ記事の最初の段落を使用しました。このテキストをUTF-8でエンコードし、バイトのリストに変換します:
text = "UTF-8 is a variable width character encoding..."
tokens = list(text.encode('utf-8'))
print(f"Original length: {len(text)}")
print(f"UTF-8 encoded length: {len(tokens)}")
次に、最も頻繁に出現するペアを特定する関数を実装します:
def get_stats(ids):
counts = {}
for pair in zip(ids, ids[1:]):
counts[pair] = counts.get(pair, 0) + 1
return counts
stats = get_stats(tokens)
print(sorted(stats.items(), key=lambda x: x[1], reverse=True)[:5])
このget_stats
関数は、連続する要素のペアの出現回数を数えます。結果を表示すると、最も頻繁に出現するペアとその出現回数がわかります。
3.2 ペアのマージと新トークンの作成
次に、最も頻繁に出現するペアをマージする関数を実装します:
def merge(ids, pair, idx):
newids = []
i = 0
while i < len(ids):
if i < len(ids) - 1 and (ids[i], ids[i+1]) == pair:
newids.append(idx)
i += 2
else:
newids.append(ids[i])
i += 1
return newids
この関数を使用して、実際にマージを行います:
top_pair = max(stats, key=stats.get)
tokens2 = merge(tokens, top_pair, 256)
print(f"New length after merging: {len(tokens2)}")
print(f"Occurrences of {top_pair} in original: {tokens.count(top_pair[0])}")
print(f"Occurrences of 256 in merged: {tokens2.count(256)}")
このプロセスを繰り返し、複数回のマージを行います:
num_merges = 20
for i in range(num_merges):
stats = get_stats(tokens)
top_pair = max(stats, key=stats.get)
new_id = 256 + i
print(f"Merging {top_pair} into token {new_id}")
tokens = merge(tokens, top_pair, new_id)
print(f"Final length after {num_merges} merges: {len(tokens)}")
print(f"Compression ratio: {len(text) / len(tokens):.2f}")
このコードは、20回のマージを行い、各マージ操作の詳細と最終的な圧縮率を表示します。
3.3 エンコードとデコード関数の実装
最後に、エンコードとデコード関数を実装します。エンコード関数は以下のようになります:
def encode(text, merges):
ids = list(text.encode('utf-8'))
for old, new in merges.items():
ids = merge(ids, old, new)
return ids
デコード関数は以下のように実装します:
def decode(ids, merges):
vocab = {i: bytes([i]) for i in range(256)}
vocab.update({v: vocab[k[0]] + vocab[k[1]] for k, v in merges.items()})
return b''.join(vocab[id] for id in ids).decode('utf-8', errors='replace')
これらの関数を使用して、テキストのエンコードとデコードを行うことができます:
# トレーニングテキストでエンコードとデコードをテスト
encoded = encode(text, merges)
decoded = decode(encoded, merges)
print(f"Original: {text}")
print(f"Encoded: {encoded[:10]}...")
print(f"Decoded: {decoded}")
assert text == decoded
# 新しいテキストでテスト
validation_text = "This is a different text to test the tokenizer."
encoded = encode(validation_text, merges)
decoded = decode(encoded, merges)
print(f"Validation Original: {validation_text}")
print(f"Validation Decoded: {decoded}")
assert validation_text == decoded
これにより、トレーニングデータと新しいテキストの両方で、エンコードとデコードが正しく機能することを確認できます。
以上がBPEアルゴリズムの基本的な実装です。この実装を通じて、大規模言語モデルがどのようにテキストを処理しているかをより深く理解することができます。次のセクションでは、この基本的な実装をベースに、GPT-2で使用されているより高度なトークン化技術について説明していきます。
4. GPT-2のトークナイザー
GPT-2のトークナイザーは、私たちが先ほど実装した基本的なBPEアルゴリズムよりもはるかに複雑です。OpenAIのGitHubリポジトリにあるGPT-2のコードを詳細に分析することで、その仕組みを理解していきましょう。
4.1 正規表現パターンによるテキスト分割
GPT-2のトークナイザーの特徴的な点は、BPEアルゴリズムを適用する前に、テキストを特定のパターンで分割することです。これは、OpenAIのGitHubリポジトリにあるencoder.py
ファイルで確認できます。このファイルには、非常に複雑な正規表現パターンが定義されています:
pat = r"""'s|'t|'re|'ve|'m|'ll|'d| ?\p{L}+| ?\p{N}+| ?[^\s\p{L}\p{N}]+|\s+(?!\S)|\s+"""
この正規表現は、テキストを以下のような要素に分割します:
- 特定の縮約形('s, 't, 're, 've, 'm, 'll, 'd)
- 文字(オプションのスペースに続く1つ以上の文字)
- 数字(オプションのスペースに続く1つ以上の数字)
- その他の記号(オプションのスペースに続く1つ以上の文字や数字以外の文字)
- 空白文字
この正規表現を使用することで、GPT-2は単純なBPEアルゴリズムでは難しい、より細かい制御を実現しています。例えば、"dog."と"dog!"を別々のトークンとして扱うことができます。
実際にこの正規表現がどのように機能するか見てみましょう。以下のようなPythonコードを使用して、テキストがどのように分割されるかを確認できます:
import regex as re
pat = r"""'s|'t|'re|'ve|'m|'ll|'d| ?\p{L}+| ?\p{N}+| ?[^\s\p{L}\p{N}]+|\s+(?!\S)|\s+"""
text = "Hello world! How's it going? I'm doing great. 12345 test."
print(re.findall(pat, text))
この出力を見ると、テキストがどのように分割されるかがわかります。各要素は個別にBPEアルゴリズムによって処理されることになります。
4.2 特殊トークンの処理
GPT-2のトークナイザーには、特殊トークンの処理も含まれています。特に重要なのは「end of text」(EOT)トークンです。このトークンは、ボキャブラリの最後のトークン(ID 50256)として定義されています。
EOTトークンは、トレーニングデータ内の個々のドキュメントを区切るために使用されます。これにより、モデルは一つのドキュメントの終わりと次のドキュメントの始まりを区別することができます。
特殊トークンの処理は、通常のBPEプロセスの外で行われます。encoder.py
ファイルには、これらの特殊トークンを処理するための特別なロジックが含まれています。
4.3 GPT-2エンコーダーの詳細分析
GPT-2のエンコーダーの核心部分は、encoder.py
ファイルのbpe
関数です。この関数は、与えられたトークンのペアを繰り返しマージしていきます。
この関数は以下のように動作します:
- 入力トークンをタプルに変換します。
- トークン内のすべての隣接するペアを取得します。
- ペアが存在しない場合、そのトークンをそのまま返します。
- 存在する場合、最もランクの低いペア(つまり、最も頻繁に出現するペア)を選択します。
- 選択されたペアをマージし、新しいワードを作成します。
- このプロセスを、マージするペアがなくなるまで、または単一のトークンになるまで繰り返します。
GPT-2のエンコーダーには、バイトレベルのエンコーディングとデコーディングも含まれています。これは、未知の文字や絵文字などを処理するために重要です。
byte_encoder = bytes_to_unicode()
byte_decoder = {v:k for k, v in byte_encoder.items()}
これらの関数により、GPT-2は任意のUnicode文字列を処理することができます。
GPT-2のトークナイザーの実装は複雑ですが、その複雑さには理由があります。これにより、モデルはテキストのより微妙なニュアンスを捉え、より自然な言語生成を行うことができます。しかし、この複雑さは同時に、トークン化に関連する問題の原因にもなります。
次のセクションでは、GPT-2からGPT-4へのトークナイザーの進化について見ていきます。OpenAIがどのようにトークン化プロセスを改善し、より効率的で柔軟なシステムを作り上げたかを探ります。
5. GPT-4のトークナイザーの進化
GPT-4のトークナイザーは、GPT-2から大きく進化しました。この進化の詳細を、OpenAIが公開しているTiktokenライブラリを通じて見ていきましょう。
5.1 GPT-2との違い
GPT-4のトークナイザーの最も大きな違いは、正規表現パターンの変更です。Tiktokenライブラリのtiktoken_ext/openai_public.pyファイルを見ると、GPT-2とGPT-4(cl100k_base)で使用されている正規表現パターンの違いがわかります。
GPT-2の正規表現パターン:
gpt2_pattern = r"""'s|'t|'re|'ve|'m|'ll|'d| ?\p{L}+| ?\p{N}+| ?[^\s\p{L}\p{N}]+|\s+(?!\S)|\s+"""
GPT-4(cl100k_base)の正規表現パターン:
cl100k_base_pattern = r"""(?i:'s|'t|'re|'ve|'m|'ll|'d)|[^\r\n\p{L}\p{N}]?\p{L}+|\p{N}{1,3}| ?[^\s\p{L}\p{N}]+[\r\n]*|\s*[\r\n]+|\s+(?!\S)|\s+"""
GPT-4のパターンには、大文字小文字を区別しない(?i:...)の追加、改行文字(\r\n)の明示的な処理、数字の処理方法の変更(\p{N}{1,3})などが含まれています。
5.2 改善されたホワイトスペース処理
GPT-4のトークナイザーは、ホワイトスペースの処理が大幅に改善されています。特に、プログラミング言語のコードを扱う際にこの改善が顕著です。
GPT-2では、各スペースが個別のトークンとして扱われていましたが、GPT-4では連続するスペースがより効率的にグループ化されています。例えば、Pythonコードのインデントを処理する際、GPT-2では各スペースが個別のトークン(通常はトークンID 220)として表現されていましたが、GPT-4では複数のスペースが1つのトークンにグループ化されています。
これにより、GPT-4はプログラミング言語のコードをより効率的に処理できるようになりました。コードの生成や理解においてGPT-4がGPT-2よりも優れたパフォーマンスを示す理由の一つがこの改善です。
5.3 数字の処理の変更
GPT-4のトークナイザーは、数字の処理方法も変更しています。正規表現パターンを見ると、\p{N}{1,3}という部分があります。これは1から3桁の数字を表しています。
この変更により、GPT-4は3桁までの数字を1つのトークンとして扱い、それ以上の桁数の数字は3桁ごとに分割します。一方、GPT-2の数字の処理はより不規則で予測が難しいものでした。
この一貫した処理方法により、GPT-4は数値計算や数字を含むテキストの処理をより効率的に行えるようになりました。また、モデルが数字の意味をより理解しやすくなったと考えられます。
これらの改善は、GPT-4がより広範囲のタスクで高いパフォーマンスを発揮できる理由の一部です。特に、プログラミング、数学的操作、そして多様な言語や文脈での処理能力が向上しています。
また、GPT-4のトークナイザーには新しい特殊トークンが追加されています。例えば、FIM(Fill in the Middle)のためのプレフィックス、ミドル、サフィックストークンが追加されています。これらの特殊トークンの詳細な使用方法や影響については、後のセクションで詳しく説明します。
次のセクションでは、OpenAIの公式トークン化ライブラリであるTiktokenについて詳しく見ていきます。このライブラリを使用することで、GPT-2からGPT-4への進化をより深く理解し、さらにはカスタムトークナイザーを作成することもできるのです。
6. Tiktoken:OpenAIの公式トークン化ライブラリ
Tiktokenは、OpenAIが開発した公式のトークン化ライブラリです。このライブラリを使用することで、GPT-2やGPT-4などのOpenAIモデルで使用されているトークン化を簡単に再現できます。
6.1 Tiktokenの基本使用法
Tiktokenの使用方法は非常にシンプルです。以下のようにして使用できます:
import tiktoken
# エンコーディングの取得
enc = tiktoken.get_encoding("cl100k_base") # GPT-4のエンコーディング
# テキストのエンコード
text = "Hello, world!"
tokens = enc.encode(text)
print(f"Encoded tokens: {tokens}")
print(f"Token count: {len(tokens)}")
# トークンのデコード
decoded_text = enc.decode(tokens)
print(f"Decoded text: {decoded_text}")
この例では、"cl100k_base"エンコーディングを使用しています。これはGPT-4で使用されているエンコーディングです。GPT-2のエンコーディングを使用したい場合は、"gpt2"を指定します。
6.2 GPT-2とGPT-4のトークン化の比較
Tiktokenを使用すると、GPT-2とGPT-4のトークン化の違いを簡単に比較できます。以下の例を見てみましょう:
import tiktoken
gpt2_enc = tiktoken.get_encoding("gpt2")
gpt4_enc = tiktoken.get_encoding("cl100k_base")
text = "Hello, world! How are you doing today? 12345 test."
gpt2_tokens = gpt2_enc.encode(text)
gpt4_tokens = gpt4_enc.encode(text)
print(f"GPT-2 tokens: {gpt2_tokens}")
print(f"GPT-2 token count: {len(gpt2_tokens)}")
print(f"GPT-4 tokens: {gpt4_tokens}")
print(f"GPT-4 token count: {len(gpt4_tokens)}")
この例を実行すると、GPT-2とGPT-4でのトークン化の違いが明確に見えます。特に、数字の処理方法と空白文字の処理に違いがあります。
6.3 特殊トークンの扱い
Tiktokenは特殊トークンの扱いも簡単にできます。例えば、「end of text」トークンは以下のように扱えます:
import tiktoken
enc = tiktoken.get_encoding("cl100k_base")
# 特殊トークンの取得
eot_token = enc.eot_token
print(f"End of text token: {eot_token}")
# 特殊トークンを含むテキストのエンコード
text = "Hello, world!<|endoftext|>This is a new document."
tokens = enc.encode(text)
print(f"Encoded tokens: {tokens}")
# 特殊トークンを含むテキストのデコード
decoded_text = enc.decode(tokens)
print(f"Decoded text: {decoded_text}")
GPT-4のエンコーディングには、新しい特殊トークンも追加されています。例えば、FIM(Fill in the Middle)のためのプレフィックス、ミドル、サフィックストークンがあります。
Tiktokenを使用することで、これらの特殊トークンを簡単に扱うことができ、OpenAIのモデルの動作をより深く理解し、制御することができます。
Tiktokenは非常に強力で柔軟なライブラリですが、使用する際には注意が必要です。特に、モデルのバージョンとトークナイザーのバージョンが一致していることを確認することが重要です。
次のセクションでは、Google開発のトークン化ライブラリであるSentencePieceについて見ていきます。SentencePieceはTiktokenとは異なるアプローチを取っており、その違いを理解することで、トークン化の多様性とその影響をより深く理解することができるでしょう。
7. SentencePiece:Google開発のトークン化ライブラリ
SentencePieceは、LlamaやMistralシリーズなど、多くの大規模言語モデルで使用されているGoogleが開発したトークン化ライブラリです。このライブラリの特徴は、トレーニングと推論の両方を効率的に行えることです。
7.1 SentencePieceの特徴と設定オプション
SentencePieceの最大の特徴は、その膨大な設定オプションです。これは、ライブラリが長期にわたって開発され、多様なユースケースに対応してきた結果です。しかし、この多様性は時として複雑さを生み出し、適切な設定を選択することが難しくなる場合があります。
SentencePieceを使用する際の基本的なコードは以下のようになります:
import sentencepiece as spm
# トレーニングデータの準備
with open('toy.txt', 'w') as f:
f.write("SentencePiece is an unsupervised text tokenizer and detokenizer...")
# モデルのトレーニング
spm.SentencePieceTrainer.train(input='toy.txt', model_prefix='toy', vocab_size=400,
model_type='bpe',
character_coverage=1.0,
num_threads=1,
unk_id=0, bos_id=1, eos_id=2, pad_id=-1,
split_by_unicode_script=True,
byte_fallback=True,
allow_whitespace_only_pieces=True,
split_digits=True)
# モデルのロード
sp = spm.SentencePieceProcessor()
sp.load('toy.model')
# エンコードとデコード
text = "Hello, world!"
encoded = sp.encode(text, out_type=int)
decoded = sp.decode(encoded)
print(f"Encoded: {encoded}")
print(f"Decoded: {decoded}")
このコードでは、多くの設定オプションを指定しています。これらの設定は、トークン化の結果に大きな影響を与えます。
7.2 コードポイントレベルでのBPE
SentencePieceの特徴の1つは、コードポイントレベルでBPE(Byte Pair Encoding)を行うことです。これは、Tiktokenなどのバイトレベルでのトークン化とは異なるアプローチです。
SentencePieceは、まずテキストをUnicodeコードポイントに分解し、それらのコードポイントに対してBPEを適用します。稀なコードポイントに遭遇した場合、SentencePieceは2つの方法でそれらを処理します:
- 特別な未知語トークン(UNK)にマップする
- バイトフォールバックオプションが有効な場合、そのコードポイントをUTF-8でエンコードし、個々のバイトをトークンとして扱う
この方法には利点と欠点があります。利点は、言語に依存しない方法でテキストを処理できることです。しかし、バイトレベルのアプローチと比較すると、一部の情報が失われる可能性があります。
7.3 Llama 2でのSentencePieceの使用例
Llama 2は、SentencePieceを使用してトークン化を行っています。Llama 2のトークナイザーの設定を見ることで、SentencePieceの実際の使用例を理解できます。以下は、Llama 2で使用されているSentencePieceの主要な設定です:
spm.SentencePieceTrainer.train(
input='training_data.txt',
model_prefix='llama',
model_type='bpe',
vocab_size=32000,
character_coverage=1.0,
num_threads=32,
split_by_unicode_script=True,
byte_fallback=True,
allow_whitespace_only_pieces=True,
split_digits=True)
これらの設定のいくつかを詳しく見てみましょう:
vocab_size=32000
: Llama 2は32,000のトークンを使用しています。character_coverage=1.0
: すべての文字をカバーすることを意味します。split_by_unicode_script=True
: 異なるUnicodeスクリプト(例:ラテン文字と漢字)間で分割します。byte_fallback=True
: 未知の文字をバイトレベルで処理します。split_digits=True
: 数字を個別に分割します。
これらの設定により、Llama 2は多言語テキストを効率的に処理し、未知の文字や数字を適切に扱うことができます。
SentencePieceには「文」の概念が組み込まれていますが、大規模言語モデルのコンテキストでは、この概念が必ずしも有用ではない場合があります。例えば、コードや構造化データを処理する際には、「文」の境界が明確でない場合があります。
個人的には、SentencePieceよりもTiktokenのアプローチの方がクリーンだと感じています。SentencePieceには多くの設定オプションがあり、それらを正しく設定するのは難しい場合があります。また、SentencePieceのバイトフォールバックの扱い方や、UNKトークンが必ず存在しなければならないという制約なども、特に優雅とは言えません。
しかし、SentencePieceは業界で広く使用されており、効率的なトレーニングと推論の両方を行えるという利点があります。ただし、ドキュメンテーションが十分ではないため、使用には注意が必要です。
次のセクションでは、トークン化が言語モデルの性能にどのような影響を与えるかを詳しく見ていきます。特に、非英語言語の処理効率、数学的演算、プログラミング言語の処理など、トークン化が直接影響を与える領域に焦点を当てます。
申し訳ありません。ご指摘ありがとうございます。字幕情報に忠実に、より詳細な情報を含めてセクション8を書き直します。
- トークン化の影響と課題
トークン化は大規模言語モデルの性能に深い影響を与えます。ここでは、トークン化がもたらす様々な課題と影響について詳しく見ていきます。
8.1 非英語言語の処理効率
非英語言語の処理効率は、トークン化の影響を最も受ける領域の一つです。この問題は、トークナイザーの訓練データセットが英語に偏っていることに起因します。
例えば、「Hello, how are you?」という英語の文は、GPT-4のトークナイザーで5つのトークンに分割されます。一方、同じ意味の韓国語の文「안녕하세요」(アンニョンハセヨ)は、3つのトークンに分割されます。これは、同じ「こんにちは」という意味でも、英語の「hello」が1トークンなのに対し、韓国語では3トークンも使用することを意味します。
この問題は、非英語テキストの処理効率を大幅に低下させます。トークン数が増えることで、モデルの注意機構(アテンション)が処理できる文脈の長さが実質的に短くなってしまうのです。つまり、同じ量の情報を処理する場合、非英語テキストの方がより多くのトークンを消費し、モデルの性能を制限してしまう可能性があります。
8.2 数学的演算の困難さ
トークン化は、数学的演算の処理にも大きな影響を与えます。特に、数字の表現方法がモデルの算術能力に直接影響します。
GPT-2のトークナイザーを例に取ると、数字の処理が非常に不規則です。4桁の数字は、時には1つのトークン、時には2つのトークン、時には3つのトークン、時には4つの個別のトークンとして表現されることがあります。これは、加算や乗算などの基本的な算術演算を行う際に問題を引き起こします。
例えば、「1234 + 5678 = 6912」という加算問題を考えてみましょう。これらの数字がどのようにトークン化されるかによって、モデルの計算能力が大きく影響を受ける可能性があります。数字が個別のトークンに分割されると、モデルは各桁を個別に処理しなければならず、正確な計算を行うのが難しくなります。
8.3 プログラミング言語(特にPython)の処理
プログラミング言語、特にPythonのコードの処理は、トークン化の影響を強く受ける領域です。GPT-2のトークナイザーは、Pythonコードの処理において特に非効率的でした。
主な問題は、インデントの処理にあります。Pythonはインデントを使って構造を表現する言語ですが、GPT-2のトークナイザーは各スペースを個別のトークンとして扱います。これにより、Pythonコードのトークン数が不必要に増加してしまいます。
例えば、以下のようなシンプルなPython関数を考えてみましょう:
def hello():
print("Hello, world!")
GPT-2のトークナイザーでは、この関数のインデントを表す4つのスペースが個別のトークンとして扱われます。これにより、コードのトークン数が不必要に増加し、モデルが処理できるコードの量が制限されてしまいます。
GPT-4では、この問題を改善するために連続するスペースを1つのトークンとしてグループ化するようになりました。これにより、Pythonコードの処理効率が向上しています。
8.4 特殊トークンによる予期せぬ動作
特殊トークンの使用は、時として予期せぬモデルの動作を引き起こします。特に問題となるのは、「end of text」(EOT)トークンです。
EOTトークンは、通常、文書の終わりを示すために使用されます。しかし、このトークンがテキスト中に出現すると、モデルが混乱してしまう可能性があります。
例えば、以下のようなプロンプトを考えてみましょう:
"Print the string 'end of text'"
このプロンプトは、単に「end of text」という文字列を出力することを意図していますが、モデルはこれを特殊なEOTトークンとして解釈してしまう可能性があります。その結果、モデルが突然出力を停止したり、予期せぬ応答を返したりする可能性があります。
実際、GPT-4にこのプロンプトを与えると、モデルは混乱し、適切に応答できないことがあります。これは、トークン化の過程で「end of text」が特殊なEOTトークンに変換されてしまうためです。
これらの課題は、トークン化がいかに大規模言語モデルの性能と動作に深く関わっているかを示しています。効果的なトークン化方法の開発は、これらの問題を解決し、より柔軟で効率的な言語モデルを実現するための重要な研究領域となっています。
次のセクションでは、これらの課題に対処するための一つの方法として、語彙サイズの決定と拡張について詳しく見ていきます。語彙サイズの適切な選択は、モデルの性能と効率性のバランスを取る上で重要な役割を果たします。
9. 語彙サイズの決定と拡張
9.1 最適な語彙サイズの考慮事項
語彙サイズの決定は、モデルのアーキテクチャと密接に関連しています。私たちが以前のビデオで構築したGPTモデルを例に取ると、語彙サイズはモデルの2つの主要な部分に影響を与えます。
まず、トークン埋め込みテーブルがあります。これは、ボキャブラリサイズ×埋め込みサイズの2次元配列です。語彙サイズが増加すると、この埋め込みテーブルの行数が増加します。
次に、Transformerの最後にあるLM(Language Model)ヘッドレイヤーがあります。これは線形レイヤーで、各トークンの次のトークンの確率を生成します。語彙サイズが増加すると、このレイヤーの出力サイズも増加します。
語彙サイズを無限に大きくしない理由には以下のようなものがあります:
- パラメータ数の増加:語彙サイズを大きくすると、埋め込みテーブルとLMヘッドレイヤーのパラメータ数が増加します。
- 学習の困難さ:語彙サイズが大きすぎると、個々のトークンの出現頻度が低くなり、それらのトークンに関連するパラメータが十分に学習されない可能性があります。
- シーケンス長の短縮:語彙サイズを大きくすると、同じテキストをより少ないトークンで表現できるようになります。これにより、モデルが各トークンを処理する時間が減少し、十分な「思考」ができなくなる可能性があります。
- メモリ使用量の増加:大きな語彙サイズは、モデルのメモリ使用量を増加させます。
最適な語彙サイズは、これらの要因のバランスを取ることで決定されます。現在の最先端モデルでは、通常高い1万台から10万程度のトークンが使用されています。例えば、GPT-4は約10万のトークンを使用しています。
9.2 事前学習モデルへの新トークン追加方法
事前学習済みのモデルに新しいトークンを追加することは可能です。このプロセスは以下のようになります:
- 埋め込みテーブルの拡張:新しいトークンのために、埋め込みテーブルに新しい行を追加します。これらの新しい行は通常、小さなランダムな値で初期化されます。
- LMヘッドレイヤーの拡張:出力層も拡張して、新しいトークンの予測確率を生成できるようにします。
- モデルの微調整:新しいトークンを含むデータセットでモデルを微調整します。
このプロセスは比較的単純な「モデルサージェリー」であり、既存のモデルの能力を損なうことなく、新しい機能を追加することができます。
9.3 GISTトークンによるプロンプト圧縮の例
GISTトークン(Generalized In-Context Specifier Tokens)は、長いプロンプトを効率的に圧縮する新しい手法です。この手法は、「Learning to Compress Prompts with Gist Tokens」という論文で提案されました。
GISTトークンの基本的なアイデアは以下の通りです:
- 新しいトークン(GISTトークン)をモデルの語彙に追加します。
- これらのトークンの埋め込みを、長いプロンプトの内容を表現するように最適化します。
- 最適化後は、長いプロンプトの代わりにGISTトークンを使用することで、同様の効果を得ることができます。
GISTトークンの学習プロセスは以下のようになります:
- 長いプロンプトと、そのプロンプトに対する理想的な応答のペアを用意します。
- GISTトークンの埋め込みをランダムに初期化します。
- GISTトークンを入力として使用し、理想的な応答を生成するようにモデルを最適化します。
- この過程で、GISTトークンの埋め込みのみを更新し、モデルの他のパラメータは固定します。
GISTトークンの利点は、モデルのコンテキスト長を効率的に使用できることです。長いプロンプトを短いトークン列に圧縮することで、より多くの有効なコンテキストをモデルに入力することができます。
このアプローチは、新しいトークンを追加する一般的な方法の一例です。トークンの埋め込みを最適化することで、モデルの機能を拡張できることを示しています。
語彙サイズの決定と拡張は、モデルの性能と効率性のバランスを取る上で重要な要素です。適切な語彙サイズの選択、新しいトークンの追加、そしてGISTトークンのような革新的な手法の使用により、言語モデルの能力を大きく向上させることができます。
10. マルチモーダルAIとトークン化
最近、テキストだけでなく、画像や動画などの異なる種類の入力モダリティを同時に処理できるTransformerの構築に向けて、多くの注目が集まっています。これらのモデルは、テキストだけでなく、画像や動画も入力として受け取り、さらにはこれらのモダリティを出力として生成することもできます。
10.1 画像や動画のトークン化手法
画像や動画をトークン化する方法の一例として、「High-Resolution Image Synthesis with Latent Diffusion Models」という論文があります。この論文では、画像をチャンクに分割し、それらを整数に変換する方法が提案されています。これらの整数は、テキストのトークンと同様に扱うことができます。
具体的には、画像を小さなパッチ(例えば16x16ピクセル)に分割し、各パッチをRGBの値を持つ256次元のベクトルとして表現します。これらのベクトルを量子化して離散的なトークンに変換し、それらをテキストトークンと同様にTransformerモデルに入力することができます。
このアププローチにより、画像や動画をテキストと同じように扱うことが可能になり、マルチモーダルな処理が実現できます。
10.2 Soraの視覚パッチアプローチ
最近、OpenAIがリリースしたSoraモデルは、画像や動画の処理においてさらに革新的なアプローチを採用しています。Soraは、テキスト入力から高品質の動画を生成することができる非常に強力なモデルです。
Soraのアプローチでは、「視覚パッチ」という概念が導入されています。これは、従来の言語モデルにおけるテキストトークンに相当するものです。Soraは、画像や動画を小さなパッチに分割し、これらのパッチを離散的なトークンとして扱います。
Soraの論文によると、言語モデルがテキストトークンを持つのに対し、Soraは視覚パッチを持っています。これらの視覚パッチは、画像や動画の局所的な特徴を捉えるように設計されています。
Soraのアプローチの利点は、テキストと視覚情報を統一的に扱えることです。これにより、テキストから動画を生成したり、動画の内容を理解してテキストで説明したりすることが可能になります。
マルチモーダルAIにおけるトークン化は、まだ発展途上の分野です。テキスト、画像、動画、音声など、異なる種類のデータを統一的に扱える効果的なトークン化手法の開発が、今後の重要な研究課題となるでしょう。
これらのアプローチは、単にテキストを処理するだけでなく、世界をより包括的に理解し、表現できるAIシステムの開発につながる可能性があります。将来的には、テキストだけでなく、あらゆる種類のデータを同じように扱えるようになるかもしれません。そうなれば、AIシステムの能力は飛躍的に向上するでしょう。
11. トークン化に起因する具体的な問題と事例
11.1 スペルチェックの困難さ
事例:「default_style」の文字数カウント失敗
トークン化は、単純なスペルチェックや文字数カウントのような基本的なタスクを難しくする可能性があります。この問題を具体的に示すために、「default_style」という文字列を例に取ってみましょう。
GPT-4の語彙を調べてみると、「default_style」は単一のトークンとして扱われていることがわかります。これは、この文字列が訓練データセットで頻繁に出現したため、BPEアルゴリズムによって1つのトークンにマージされたのだと考えられます。
私がGPT-4に「How many letters 'l' are there in the word default_style?」と尋ねたところ、モデルは「There are 3 letters 'l' in the word default_style.」と答えました。しかし、実際には「default_style」には「l」が4つ含まれています。
この誤りは、「default_style」が単一のトークンとして扱われているために起こっています。モデルはこの文字列の内部構造にアクセスできず、個々の文字を正確に数えることができないのです。
11.2 文字列操作タスクの課題
事例:「default_style」の反転失敗と解決策
文字列の反転のような単純な操作タスクも、トークン化によって難しくなる可能性があります。再び「default_style」を例に取ってみましょう。
私がGPT-4に「Reverse the string 'default_style'」と指示したところ、モデルは正しく反転することができませんでした。これも、「default_style」が単一のトークンとして扱われているためです。
しかし、タスクを2つのステップに分けると、モデルは正しく反転を行うことができました。まず、「Step 1: Print out every single character separated by spaces.」と指示し、次に「Step 2: Reverse that list.」と指示しました。
このアプローチでは、モデルはまず文字列を個々の文字に分解し、それらを空白で区切って出力します。これにより、各文字が個別のトークンとして扱われるようになります。次に、この文字のリストを逆順にすることで、正しく反転された文字列が得られました。
11.3 非英語言語の処理効率低下
事例:「안녕하세요」のトークン数膨張
非英語言語の処理効率の低下は、トークン化に起因する重要な問題の1つです。この問題を具体的に示すために、韓国語の「안녕하세요」(こんにちは)を例に取ってみましょう。
英語の「Hello, how are you?」は、GPT-4のトークナイザーで5つのトークンに分割されます。一方、同じ意味を持つ韓国語の「안녕하세요, 어떻게 지내세요?」は、15のトークンに分割されてしまいます。これは3倍もの差があります。
さらに詳しく見てみると、「안녕」(アンニョン、こんにちは)という単語だけでも3つのトークンに分割されます。一方、英語の「hello」は1つのトークンで表現されます。
この違いは、トークナイザーの訓練データに韓国語が十分に含まれていないことを示しています。結果として、非英語テキストの処理効率が大幅に低下し、モデルの注意機構(アテンション)が処理できる文脈の長さが実質的に短くなってしまいます。
11.4 数値計算の精度問題
事例:4桁数字のトークン化の不規則性
数値計算の精度問題は、トークン化に起因する別の重要な課題です。特に、数字の表現方法がモデルの算術能力に直接影響を与えます。
GPT-2のトークナイザーを例に取ると、数字の処理が非常に不規則であることがわかります。特に4桁の数字の扱いが問題です。4桁の数字は、時には1つのトークン、時には2つのトークン、時には3つのトークン、時には4つの個別のトークンとして表現されることがあります。
例えば、「1234 + 5678 = 6912」という単純な加算問題を考えてみましょう。これらの数字がどのようにトークン化されるかによって、モデルの計算能力が大きく影響を受ける可能性があります。
「1234」が1つのトークンとして扱われる場合、モデルはこの数字を1つの単位として認識し、適切に処理できる可能性が高くなります。しかし、「12」と「34」の2つのトークンに分割される場合、または「1」「2」「3」「4」の4つの個別のトークンに分割される場合、モデルは各桁を個別に処理しなければならず、正確な計算を行うのが難しくなります。
この不規則性は、モデルが一貫した方法で数値を理解し処理することを困難にします。結果として、複雑な数学的操作や精度の高い計算が必要なタスクでパフォーマンスが低下する可能性があります。
GPT-4では、この問題を部分的に解決するために、3桁までの数字を1つのトークンとして扱うようになりました。しかし、4桁以上の数字を扱う際には依然として課題が残っています。
11.5 Pythonコードの非効率な処理
事例:インデントのトークン化問題
Pythonコードの処理、特にインデントの扱いは、GPT-2のトークナイザーにとって大きな課題でした。簡単なPython関数を例に取ると:
def hello():
print("Hello, world!")
GPT-2のトークナイザーでは、この関数のインデントを表す4つのスペースが個別のトークンとして扱われます。これにより、コードのトークン数が不必要に増加し、モデルが処理できるコードの量が制限されてしまいます。
GPT-4では、この問題を改善するために連続するスペースを1つのトークンとしてグループ化するようになりました。これにより、Pythonコードの処理効率が向上しています。
11.6 特殊トークンによる予期せぬ挙動
事例:「end of text」トークンの扱い
「end of text」(EOT)トークンの扱いは、予期せぬモデルの動作を引き起こす可能性があります。私がGPT-4に「Print the string 'end of text'」というプロンプトを与えたところ、モデルは混乱し、適切に応答できませんでした。
これは、トークン化の過程で「end of text」が特殊なEOTトークンに変換されてしまうためです。モデルはこのプロンプトを処理しようとしますが、EOTトークンに遭遇すると、それを文書の終わりとして解釈してしまい、要求された文字列を出力する前に応答を終了してしまうか、予期せぬ動作をする可能性があります。
11.7 末尾スペースによるパフォーマンス低下
事例:アイスクリームショップのタグライン生成問題
OpenAIのPlaygroundでGPT-3.5-turbo-instructモデルを使用して実験を行いました。「Here's a tagline for an ice cream shop:」というプロンプトを与えると、モデルは問題なくタグラインを生成しました。
しかし、同じプロンプトの末尾にスペースを追加して「Here's a tagline for an ice cream shop: 」(最後にスペースがある)と入力すると、モデルは警告を発し、「Your text ends in a trailing space which causes worse performance due to how API splits text into tokens.」というメッセージが表示されました。
この問題が発生する理由は、GPTモデルのトークン化の仕組みにあります。多くの単語や部分文字列は、先頭にスペースを含むトークンとして表現されます。例えば、「ice」という単語は、実際には「␣ice」(␣はスペースを表す)というトークンとして表現されることがあります。
文字列が末尾のスペースで終わる場合、このスペースは次の単語の一部ではなく、独立したトークンとして扱われます。これにより、モデルの予測パターンが乱れ、生成される文章の品質が低下する可能性があります。
11.8 未学習トークンによる異常な振る舞い
事例:「solid gold Magikarp」問題の詳細分析
「solid gold Magikarp」問題は、言語モデルの訓練データとトークナイザーの訓練データの不一致から生じる、非常に特殊な振る舞いを示します。
ある研究者がGPT-2の埋め込み空間を分析していたときに、「solid gold Magikarp」、「rot13」、「streamer fame」などの奇妙なトークンのクラスターを発見しました。これらのトークンをモデルに入力すると、モデルは完全に異常な振る舞いを示しました。
例えば、「Please repeat back to me the string 'solid gold Magikarp'」というプロンプトに対して、モデルは要求を拒否したり、全く無関係な応答をしたり、時には侮辱的な言葉を返したりしました。
この問題の原因は、トークナイザーの訓練データとモデルの訓練データの不一致にあります。トークナイザーの訓練データには、これらの特殊な文字列(おそらくRedditのユーザー名)が頻繁に出現していたため、それらが単一のトークンにマージされました。しかし、モデルの訓練データにはこれらの文字列がほとんど、あるいは全く含まれていませんでした。
結果として、これらのトークンに対応する埋め込みベクトルは、モデルの訓練中にほとんど更新されませんでした。つまり、これらのトークンは事実上、「未割り当てのメモリ」のような状態になっていたのです。
モデルにこれらのトークンを入力すると、モデルは完全に未知の入力に直面することになります。これは、C言語のプログラムで未初期化のメモリにアクセスするようなものです。結果として、モデルの動作は予測不可能になり、時には奇妙または不適切な出力を生成することになります。
この「solid gold Magikarp」問題は、トークン化が言語モデルの動作に与える影響の複雑さを示す極端な例です。トークナイザーとモデルの訓練データの一貫性が、モデルの安定性と信頼性にとって極めて重要であることを示しています。
12. 効率的なデータ表現形式
トークン化の効率は、データの表現形式によって大きく影響を受けます。特に構造化データを扱う際、使用する形式によってトークン数が大きく変わる可能性があります。
12.1 JSONとYAMLのトークン効率比較
JSONとYAMLは、両方とも広く使用されている構造化データ形式ですが、トークン化の観点からは大きな違いがあります。一般的に、YAMLはJSONよりもトークン効率が高いことがわかっています。
具体的な例を見てみましょう。以下は同じデータをJSONとYAMLで表現したものです:
JSON:
{
"name": "John Doe",
"age": 30,
"city": "New York",
"hobbies": ["reading", "swimming", "cycling"]
}
YAML:
name: John Doe
age: 30
city: New York
hobbies:
- reading
- swimming
- cycling
このデータをGPT-4のトークナイザーでトークン化すると、JSONは116トークン、YAMLは99トークンになります。YAMLの方が約15%少ないトークン数で同じ情報を表現できています。
この差は主に以下の理由によるものです:
- YAMLは波括弧やコロンなどの余分な文字を使用しません。
- YAMLはクォーテーションマークの使用が少なくて済みます。
- YAMLは改行とインデントを使って構造を表現するため、より自然言語に近い形式になります。
12.2 構造化データの最適な表現方法
トークン経済の観点から、構造化データを表現する際にはYAMLを使用することをお勧めします。YAMLはJSONよりもトークン効率が高く、同じ情報をより少ないトークン数で表現できます。
トークン数の削減は、特に大規模なデータセットや複雑な構造を扱う際に重要になります。トークン数が少ないほど、モデルが処理できる情報量が増え、より長いコンテキストを維持できるからです。
また、トークン効率を追求する際には、使用する言語モデルのトークナイザーの特性を理解することが重要です。Tiktokenizerを使用して、異なるデータ形式のトークン効率を測定し、最適な表現方法を見つけることができます。
効率的なデータ表現は、特に大規模な言語モデルを使用する際に重要です。トークン数の削減は、単にコストの削減だけでなく、モデルがより多くの文脈を理解し、より適切な応答を生成することにもつながります。したがって、データ表現の最適化は、言語モデルの性能向上にも寄与する重要な要素と言えるでしょう。
13. まとめと今後の展望
このビデオでは、トークン化の複雑さと重要性について詳しく説明してきました。トークン化は、一見退屈で複雑に見えるかもしれませんが、大規模言語モデルの根幹を成す重要な要素です。
13.1 現在のトークン化技術の限界
現在のトークン化技術には、いくつかの重要な限界があります。これらの限界は、モデルの性能に直接影響を与えています。
- 非英語言語の処理効率:多くのトークナイザーは英語中心に設計されているため、他の言語、特にアジア言語などの処理が非効率です。
- 数値計算の精度:数字の扱いが不規則で、特に大きな数字や小数点を含む数字の処理が難しいケースがあります。
- プログラミング言語の処理:特にPythonのような空白文字に意味がある言語の処理に課題があります。
- 特殊トークンの扱い:「end of text」のような特殊トークンがテキスト中に現れた場合、モデルが予期せぬ動作をする可能性があります。
- トークナイザーとモデルの訓練データの不一致:「solid gold Magikarp」問題のように、トークナイザーには存在するがモデルには学習されていないトークンが、モデルの異常な動作を引き起こす可能性があります。
13.2 理想的なトークン化システムの特徴
理想的なトークン化システムは、以下のような特徴を持つべきです:
- 言語に依存しない:あらゆる言語を効率的に処理できるシステム。
- 数値の効率的な処理:数字を一貫して扱い、大きな数字や小数点を含む数字も効率的に処理できるシステム。
- プログラミング言語への対応:主要なプログラミング言語を効率的に処理できるシステム。
- 特殊トークンの柔軟な扱い:特殊トークンをコンテキストに応じて適切に処理できるシステム。
- トークナイザーとモデルの一貫性:トークナイザーの訓練データとモデルの訓練データの一貫性を保つシステム。
13.3 今後の研究課題と改善の方向性
トークン化技術の改善に向けて、以下のような研究課題と方向性が考えられます:
- マルチリンガルトークナイザーの開発:様々な言語を同等に扱えるトークナイザーの開発。
- 数値処理の改善:数値を効率的かつ一貫して扱えるトークン化手法の開発。
- プログラミング言語に特化したトークナイザー:プログラミング言語の構文を理解し、効率的にトークン化できるシステムの開発。
- トークナイザーとモデルの共同最適化:トークナイザーとモデルを同時に最適化する手法の開発。
- マルチモーダルトークン化:テキスト、画像、音声などを統一的に扱えるトークン化システムの開発。
- トークンフリーアプローチの探索:トークン化を完全に排除し、生のデータストリームを直接処理できるモデルアーキテクチャの開発。
最後に、トークン化の重要性を再度強調したいと思います。トークン化は単なる前処理ステップではなく、言語モデルの性能と能力を根本的に形作る重要な要素です。トークン化技術の進歩は、より強力で柔軟なAIシステムの開発につながるでしょう。
このビデオを通じて、トークン化の複雑さと重要性を理解していただけたなら幸いです。トークン化は確かに難しい分野ですが、同時に非常に興味深く、大きな可能性を秘めた分野でもあります。今後のこの分野の発展に注目し、皆さんもぜひ積極的に貢献していってください。ありがとうございました。