Pythonライブラリtiktoken徹底解説:OpenAIモデルのトークン化を理解する 🤖

AI / 機械学習

はじめに:tiktokenとは何か?

近年、ChatGPTをはじめとする大規模言語モデル(LLM)の進化は目覚ましいものがありますね。これらのモデル、特にOpenAIが開発したGPTシリーズ(GPT-3.5やGPT-4など)を利用する際、避けて通れないのが「トークン化」というプロセスです。そして、このトークン化を効率的かつ正確に行うためにOpenAIが提供しているのが、Pythonライブラリ tiktoken です。

tiktoken は、OpenAIのモデルが内部で使用しているトークナイザー(テキストをトークンという単位に分割するプログラム)をPython環境で利用できるようにしたものです。もともとは内部ツールでしたが、開発者がAPI利用時のトークン数を事前に計算したり、モデルの挙動をより深く理解したりするために、オープンソースとして公開されました。

このライブラリの最大の特徴は、その速度です。C++で実装されたコア部分により、他の多くのオープンソースのトークナイザーと比較して3〜6倍高速に動作すると言われています。これにより、大量のテキストデータを扱う場合でも、効率的にトークン化処理を行うことができます。

なぜトークン数を正確に知ることが重要なのでしょうか? 主な理由は以下の2点です。

  • APIコストの管理: OpenAI APIの利用料金は、処理されるトークンの総数に基づいて計算されます。tiktoken を使えば、APIを呼び出す前に必要なトークン数を正確に把握でき、コストの見積もりや予算管理に役立ちます。
  • コンテキストウィンドウの制限: 各言語モデルには、一度に処理できるトークン数の上限(コンテキストウィンドウ)があります。入力テキストがこの上限を超えると、エラーが発生したり、テキストの一部が切り捨てられたりします。tiktoken を使って事前にトークン数を確認することで、このような問題を回避できます。

この記事では、tiktoken の仕組みから具体的な使い方、そして実用的な応用例まで、詳しく解説していきます。😊

トークン化とは? LLMがテキストを理解する仕組み

人間が自然に読み書きするテキストデータを、コンピュータ、特にLLMが処理できるようにするためには、まずテキストを数値的な形式に変換する必要があります。この最初のステップが「トークン化(Tokenization)」です。

トークン化とは、与えられたテキスト(文字列)を、モデルが理解できる最小単位である「トークン(Token)」のシーケンス(リスト)に分割するプロセスです。トークンは、単語全体、単語の一部(サブワード)、あるいは個々の文字や記号に対応することがあります。

例えば、「Hello World!」というテキストは、あるトークナイザーによっては ["Hello", " World", "!"] という3つのトークンに分割されるかもしれません。重要なのは、各トークンには一意の数値IDが割り当てられていることです。上記の例では、[15496, 2159, 0] のような整数のリストに変換されます。LLMは、この数値IDのシーケンスを入力として受け取り、処理を行います。

なぜ単純な単語分割ではダメなのか?

単純にスペースや句読点で区切るだけ(単語トークン化)では、いくつかの問題が生じます。

  • 語彙爆発: 世の中には無数の単語が存在し、新しい単語(スラング、専門用語など)も次々と生まれます。すべての単語を語彙に含めようとすると、語彙サイズが膨大になり、モデルの学習やメモリ効率が悪化します。
  • 未知語問題: 語彙に含まれていない単語(未知語、Out-Of-Vocabulary, OOV)が登場した場合、適切に処理できません。
  • 形態素の活用: 日本語のように活用がある言語や、英語の接尾辞(-ing, -edなど)を考慮すると、同じ意味を持つ語幹でも異なるトークンとして扱われてしまい、学習効率が低下します。

BPE(Byte Pair Encoding)アルゴリズム

これらの問題を解決するために、tiktoken をはじめとする多くの現代的なトークナイザーで採用されているのが「サブワードトークン化」というアプローチです。その代表的なアルゴリズムの一つがBPE(Byte Pair Encoding)です。

BPEはもともとデータ圧縮アルゴリズムとして考案されましたが、NLP分野で効果を発揮しています。基本的な考え方は以下の通りです。

  1. まず、テキストを最小単位(例えば文字やバイト)に分割します。
  2. テキスト中で最も頻繁に出現する隣接したペアを見つけ、それを新しい1つのトークン(サブワード)として結合(マージ)し、語彙に追加します。
  3. 指定された語彙サイズに達するか、マージできるペアがなくなるまで、ステップ2を繰り返します。

これにより、頻出する単語はそのまま1つのトークンになりやすく、一方で低頻度の単語や未知語は、より基本的なサブワードや文字の組み合わせとして表現されます。例えば、「tokenization」という単語は、「token」と「ization」のように、意味のあるサブワードに分割される可能性があります。

tiktoken は、このBPEアルゴリズムを高速に実行するために最適化された実装であり、OpenAIモデルのテキスト処理の根幹を支えています。

tiktokenの仕組み:エンコーディングの種類とモデル

tiktoken は単なるトークン分割ツールではありません。重要なのは、どのOpenAIモデルで使われているかによって、トークン化のルール(=エンコーディング)が異なるという点です。異なるモデルは、異なる方法でテキストをトークンに変換します。これは、モデルの訓練データやアーキテクチャの違いに起因します。

tiktoken ライブラリは、主に以下のエンコーディングをサポートしています(2025年初頭時点)。エンコーディング名は、それぞれ特定のルールセットと語彙データを指します。

エンコーディング名 主な対応OpenAIモデル 備考
o200k_base gpt-4o, gpt-4o-mini 最新世代のモデル用。語彙数が約20万に拡張され、特に多言語対応や日本語のトークン効率が改善されていると言われています。2024年5月頃にGPT-4oと共に登場しました。
cl100k_base gpt-4 シリーズ (turbo含む), gpt-3.5-turbo シリーズ, text-embedding-ada-002, text-embedding-3-small, text-embedding-3-large GPT-3.5以降の主要なモデルで広く使われてきたエンコーディング。語彙数は約10万。
p50k_base Codexモデル, text-davinci-002, text-davinci-003 主にコード生成モデルや旧世代のテキストモデルで使用。
r50k_base (または gpt2) 初期のGPT-3モデル(例:davinci GPT-2および初期GPT-3モデルで使用。p50k_baseと重複する部分が多い。

注意: モデルとエンコーディングの対応関係は、OpenAIによって更新される可能性があります。常に最新の情報を公式ドキュメント等で確認することが重要です。

tiktoken を使う際には、対象とするモデルに適したエンコーディングを指定する必要があります。これにより、API呼び出し時の実際のトークン数や、モデルが受け取る入力を正確にシミュレーションできます。

例えば、同じテキストでも、cl100k_baser50k_base では生成されるトークンの数や内容が異なる場合があります。特に日本語のような非アルファベット言語では、エンコーディングによる差が顕著に現れることがあります。新しい o200k_base では、日本語のトークン化効率が改善され、同じ文章でもより少ないトークン数で表現できるようになったと言われています。これはコスト削減や、より多くの情報をコンテキストウィンドウに含められる点でメリットがあります。

tiktokenのインストールと基本的な使い方 ⚙️

tiktoken の導入は非常に簡単です。Python環境があれば、pipを使ってインストールできます。

pip install tiktoken

あるいは、最新版にアップグレードする場合は以下のようにします。

pip install --upgrade tiktoken

インストールが完了したら、Pythonコードからライブラリをインポートして使用できます。基本的な流れは以下の通りです。

  1. tiktoken ライブラリをインポートする。
  2. 使用したいエンコーディングを取得する。
  3. 取得したエンコーディングオブジェクトのメソッドを使って、テキストとトークンIDリストを相互に変換(エンコード/デコード)する。
  4. エンコード結果(トークンIDリスト)の長さを数えてトークン数を取得する。

以下に具体的なコード例を示します。

import tiktoken

# 1. エンコーディングを取得する (例: cl100k_base)
#    get_encoding() でエンコーディング名を直接指定
encoding = tiktoken.get_encoding("cl100k_base")

#    または、encoding_for_model() でモデル名を指定 (推奨)
#    これにより、将来モデルのエンコーディングが変わってもコード修正が不要になる可能性
# encoding = tiktoken.encoding_for_model("gpt-4")

# 2. テキストをエンコードしてトークンIDリストを取得
text = "tiktoken is a powerful tool! 🚀"
token_ids = encoding.encode(text)

# 3. トークン数をカウント
num_tokens = len(token_ids)

print(f"テキスト: \"{text}\"")
print(f"トークンIDリスト: {token_ids}")
print(f"トークン数: {num_tokens}")

# 4. トークンIDリストをデコードして元のテキストに戻す
decoded_text = encoding.decode(token_ids)
print(f"デコードされたテキスト: \"{decoded_text}\"")

# 参考: 各トークンがどのバイト列に対応するかを確認
token_bytes = [encoding.decode_single_token_bytes(token_id) for token_id in token_ids]
# バイト列を可能な限りUTF-8でデコードして表示
token_strings = []
for b in token_bytes:
    try:
        token_strings.append(b.decode('utf-8'))
    except UnicodeDecodeError:
        token_strings.append(str(b)) # デコードできない場合はバイト列のまま表示

print(f"各トークンに対応する文字列(バイト列): {token_strings}")

このコードを実行すると、指定したテキストがどのようにトークンIDに変換され、その数がいくつかがわかります。また、decode メソッドで元に戻せること(可逆性)、decode_single_token_bytes で各トークンが具体的にどの部分文字列(バイト列)に対応しているかを確認できることも示しています。

日本語のテキストも同様に処理できます。

import tiktoken

# 日本語を含むテキストで試す (gpt-4oのエンコーディングを使用)
encoding_o200k = tiktoken.encoding_for_model("gpt-4o")

text_jp = "これは日本語のトークン化テストです。🍣"
token_ids_jp = encoding_o200k.encode(text_jp)
num_tokens_jp = len(token_ids_jp)

print(f"\nテキスト: \"{text_jp}\"")
print(f"エンコーディング: {encoding_o200k.name}")
print(f"トークンIDリスト: {token_ids_jp}")
print(f"トークン数: {num_tokens_jp}")

decoded_text_jp = encoding_o200k.decode(token_ids_jp)
print(f"デコードされたテキスト: \"{decoded_text_jp}\"")

token_bytes_jp = [encoding_o200k.decode_single_token_bytes(token_id) for token_id in token_ids_jp]
token_strings_jp = []
for b in token_bytes_jp:
    try:
        token_strings_jp.append(b.decode('utf-8'))
    except UnicodeDecodeError:
        token_strings_jp.append("❓") # UTF-8でデコードできないバイトを表す記号

print(f"各トークンに対応する文字列: {token_strings_jp}")

日本語の場合、特にエンコーディングによってトークン数が大きく変わることがあります。例えば、古いエンコーディングでは1文字が1トークンになることが多かったですが、o200k_base のような新しいエンコーディングでは、よく使われる単語やフレーズが1つのトークンとして扱われることが増え、全体的なトークン数が削減される傾向にあります。

エンコーディングの選択:get_encoding vs encoding_for_model

前のセクションで触れたように、tiktoken でエンコーディングオブジェクトを取得するには、主に2つの方法があります。

  • tiktoken.get_encoding("エンコーディング名")
  • tiktoken.encoding_for_model("モデル名")

どちらを使うべきでしょうか? 結論から言うと、特定のOpenAIモデルに対してトークン数を計算したい場合は encoding_for_model() を使うことが推奨されます。

tiktoken.get_encoding(“エンコーディング名”)

この関数は、エンコーディングの名前(例: "cl100k_base", "o200k_base")を直接指定して、対応するエンコーディングオブジェクトを取得します。これは、特定のエンコーディングの動作を直接調べたい場合や、OpenAIモデルとは直接関係ない文脈で特定のBPEエンコーディングを使用したい場合に便利です。

import tiktoken

# cl100k_baseエンコーディングを直接取得
enc_cl100k = tiktoken.get_encoding("cl100k_base")
print(f"取得したエンコーディング名: {enc_cl100k.name}")

# o200k_baseエンコーディングを直接取得
enc_o200k = tiktoken.get_encoding("o200k_base")
print(f"取得したエンコーディング名: {enc_o200k.name}")

ただし、この方法の欠点は、将来的にOpenAIが特定のモデルで使用するエンコーディングを変更した場合、コード内のエンコーディング名を追随して変更する必要があるかもしれない点です。

tiktoken.encoding_for_model(“モデル名”)

この関数は、OpenAIのモデル名(例: "gpt-4", "gpt-3.5-turbo", "gpt-4o")を引数として受け取り、そのモデルが現時点で(ライブラリが知る限り)使用しているエンコーディングオブジェクトを返します。

import tiktoken

# gpt-4モデルに対応するエンコーディングを取得
enc_gpt4 = tiktoken.encoding_for_model("gpt-4")
print(f"gpt-4用のエンコーディング: {enc_gpt4.name}")

# gpt-3.5-turboモデルに対応するエンコーディングを取得
enc_gpt35 = tiktoken.encoding_for_model("gpt-3.5-turbo")
print(f"gpt-3.5-turbo用のエンコーディング: {enc_gpt35.name}")

# gpt-4oモデルに対応するエンコーディングを取得
enc_gpt4o = tiktoken.encoding_for_model("gpt-4o")
print(f"gpt-4o用のエンコーディング: {enc_gpt4o.name}")

この方法の最大の利点は、コードの将来的なメンテナンス性が向上することです。もしOpenAIがあるモデル(例えば将来の “gpt-5″)で新しいエンコーディングを採用したとしても、tiktoken ライブラリが更新されれば、コードを変更することなく encoding_for_model("gpt-5") を呼び出すだけで、自動的に正しいエンコーディングが使用されるようになります(ライブラリがそのモデル名と新しいエンコーディングの対応を知っていれば)。

したがって、OpenAI APIの利用に関連してトークン数を計算したり、モデルへの入力を準備したりする際には、encoding_for_model() を使用するのが一般的で、かつ推奨される方法です。これにより、モデルとエンコーディングの間のマッピングを自分で管理する手間が省けます。👍

注意点: encoding_for_model() は、ライブラリが認識しているモデル名に対してのみ機能します。未知のモデル名やタイポがあるとエラーになります。また、ライブラリのバージョンが古いと、新しいモデルに対応していない場合があります。定期的にライブラリを更新することが推奨されます。

実用的な応用例 💡

tiktoken は単にトークンを数えるだけでなく、様々な実用的な場面で役立ちます。ここでは主な応用例をいくつか紹介します。

1. APIコストの事前見積もり 💰

OpenAI APIの利用料金は、主に入力(プロンプト)と出力(生成されたテキスト)の合計トークン数に基づいて課金されます。特に大量のテキストを処理する場合や、頻繁にAPIを利用するアプリケーションでは、コスト管理が重要になります。

tiktoken を使えば、APIにリクエストを送る前に、プロンプトが何トークンになるかを正確に計算できます。これにより、予期せぬ高額請求を防いだり、異なるプロンプト案のコストを比較検討したりすることが可能です。

import tiktoken

def estimate_prompt_cost(prompt_text, model_name, cost_per_1k_tokens):
    """プロンプトのトークン数を計算し、概算コストを見積もる"""
    try:
        encoding = tiktoken.encoding_for_model(model_name)
        num_tokens = len(encoding.encode(prompt_text))
        estimated_cost = (num_tokens / 1000) * cost_per_1k_tokens
        return num_tokens, estimated_cost
    except KeyError:
        print(f"警告: モデル '{model_name}' に対応するエンコーディングが見つかりません。")
        return None, None
    except Exception as e:
        print(f"エラーが発生しました: {e}")
        return None, None

# 例: gpt-4o を使用する場合 (コストは仮定)
prompt = "これは非常に長いテキストで、APIに送信する前にトークン数とコストを確認したいと考えています..." * 10
model = "gpt-4o"
# gpt-4o の入力コストを $5.00 / 1M tokens と仮定 = $0.005 / 1k tokens
cost_input_per_1k = 0.005

tokens, cost = estimate_prompt_cost(prompt, model, cost_input_per_1k)

if tokens is not None:
    print(f"モデル: {model}")
    print(f"プロンプトトークン数: {tokens}")
    print(f"概算コスト (入力のみ): ${cost:.6f}")

(注意:実際のAPI呼び出しでは、モデルの内部処理に伴うわずかなオーバーヘッドが加わることがあります。また、出力トークン数もコストに含まれるため、これはあくまで入力部分の概算です。)

2. コンテキストウィンドウ管理 📏

各LLMには、一度に処理できるトークンの最大数(コンテキスト長、コンテキストウィンドウ)が定められています。例えば、gpt-4 の一部バージョンは8,192トークン、gpt-4-32k は32,768トークン、gpt-4-turbogpt-4o は128,000トークンといった上限があります。

APIに送信するプロンプト(ユーザー入力、システムメッセージ、過去の会話履歴などを含む)がこの上限を超えると、エラーになるか、古い情報から順に切り捨てられてしまう可能性があります。tiktoken を使えば、プロンプト全体のトークン数を事前に計算し、上限を超えないように調整することができます。

例えば、チャットボットアプリケーションで会話履歴を保持する場合、履歴が長くなりすぎるとコンテキスト上限を超える可能性があります。その場合、tiktoken でトークン数を計算しながら、古いメッセージから削除していくといった制御が可能です。

import tiktoken

def truncate_text_to_max_tokens(text, model_name, max_tokens):
    """テキストを指定した最大トークン数に切り詰める"""
    try:
        encoding = tiktoken.encoding_for_model(model_name)
        tokens = encoding.encode(text)
        if len(tokens) > max_tokens:
            truncated_tokens = tokens[:max_tokens]
            truncated_text = encoding.decode(truncated_tokens)
            # デコード時に最後のトークンが不完全な文字になるのを防ぐため、
            # 再エンコードしてトークン数を保証する方法もある
            # final_tokens = encoding.encode(truncated_text)
            # final_text = encoding.decode(final_tokens[:max_tokens]) # 厳密には再調整が必要
            print(f"テキストを {len(tokens)} トークンから {max_tokens} トークンに切り詰めました。")
            return truncated_text, max_tokens
        else:
            print("テキストは最大トークン数内です。")
            return text, len(tokens)
    except KeyError:
        print(f"警告: モデル '{model_name}' に対応するエンコーディングが見つかりません。")
        return text, None # またはエラー処理
    except Exception as e:
        print(f"エラーが発生しました: {e}")
        return text, None # またはエラー処理

# 例: 長いテキストを gpt-3.5-turbo の上限 (仮に4000トークンとする) に収める
long_text = "ここに非常に長い文章が入ります..." * 500
model = "gpt-3.5-turbo"
max_token_limit = 4000

truncated_version, actual_tokens = truncate_text_to_max_tokens(long_text, model, max_token_limit)
# print(f"切り詰め後のテキスト(一部): {truncated_version[:200]}...")
print(f"最終的なトークン数: {actual_tokens}")

(注意:単純にトークンリストをスライスしてデコードすると、マルチバイト文字の途中で途切れるなど、テキストが壊れる可能性があります。より安全な切り詰め方法としては、単語や文単位でトークン数を計算しながら削除していくアプローチがあります。)

3. プロンプトエンジニアリングの補助 ✍️

効果的なプロンプトを作成する「プロンプトエンジニアリング」においても、トークン化の知識は役立ちます。

  • トークン効率の良い表現: 同じ意味でも、トークン数が少なくなるような言い回しを選ぶことで、コストを削減したり、より多くの情報をコンテキストウィンドウに詰め込んだりできます。例えば、冗長な表現を避け、簡潔な言葉を選ぶなどが考えられます。
  • 特殊トークンの理解: モデルによっては、特定の機能(例:Fill-in-the-Middle)のために特殊なトークンを使用することがあります。tiktoken はこれらの特殊トークンの扱いもサポートしており、モデルの挙動をより深く理解・制御するのに役立ちます。
  • 分割されにくい単語: 意図しないサブワードへの分割を避けたい場合(例えば特定の固有名詞など)、その単語がどのようにトークン化されるかを確認し、必要であれば表現を調整することができます。

tiktoken を使って様々な表現のトークン数や分割のされ方を比較することで、より洗練されたプロンプトを作成するための洞察を得ることができます。

注意点とベストプラクティス ⚠️

tiktoken は非常に便利なツールですが、利用する上でいくつか注意すべき点と、より効果的に使うためのベストプラクティスがあります。

  • エンコーディングの正確性: 最も重要なのは、対象とするモデルに適した正しいエンコーディングを使用することです。encoding_for_model() を使うのが最も安全ですが、ライブラリが最新であることを確認してください。誤ったエンコーディングを使用すると、トークン数の計算が不正確になり、コスト見積もりやコンテキスト管理で問題が発生します。
  • APIのオーバーヘッド: tiktoken で計算されるトークン数は、通常、ユーザーが提供するテキストそのものに対するものです。実際のOpenAI API呼び出しでは、リクエストの形式やモデルの内部処理(例えば、会話履歴の区切りを示す内部トークンなど)によって、わずかながら追加のトークンが消費されることがあります。特に非常に短いリクエストを多数送る場合などは、このオーバーヘッドが相対的に大きくなる可能性があります。厳密なコスト計算には、APIレスポンスに含まれる usage フィールド(prompt_tokens, completion_tokens, total_tokens)を確認するのが確実です。
  • 特殊トークンの扱い: tiktoken は、モデルが使用する特殊トークン(例:<|endoftext|>, <|im_start|> など)も認識し、エンコード・デコードできます。しかし、これらの特殊トークンをプロンプトに含める場合、その意味とモデルへの影響を理解しておく必要があります。通常、これらはモデルの訓練方法と密接に関連しており、不用意に使用すると予期せぬ動作を引き起こす可能性があります。プロンプトに含める際は、公式のドキュメントやガイドラインを参照してください。
  • ライブラリの更新: OpenAIは新しいモデルやエンコーディングをリリースすることがあります。tiktoken ライブラリを定期的に更新 (pip install --upgrade tiktoken) することで、最新のモデルやエンコーディングに対応し、常に正確なトークン計算を行えるように保つことが重要です。
  • パフォーマンス: tiktoken は高速ですが、非常に巨大なテキストファイル全体を一度にエンコードしようとすると、それなりに時間がかかり、メモリも消費します。必要に応じて、テキストをチャンクに分割して処理するなどの工夫が有効な場合があります。
  • 言語による効率差: 前述の通り、エンコーディング(特にBPE)の性質上、英語などのアルファベットベースの言語と比較して、日本語などの言語ではトークン化の効率が悪くなる(文字数に対してトークン数が多くなる)傾向がありました。最新の o200k_base エンコーディングでは改善が見られますが、依然として言語による差は存在します。この点を理解しておくことは、多言語対応アプリケーションの設計やコスト見積もりにおいて重要です。

これらの点に留意し、tiktoken を活用することで、OpenAIモデルとの連携をよりスムーズかつ効率的に行うことができるでしょう。✨

まとめ

この記事では、OpenAIのモデルと連携する上で不可欠なPythonライブラリ tiktoken について、その基本概念から具体的な使い方、実用的な応用例、そして注意点まで詳しく解説しました。

tiktoken は、高速なBPEトークナイザーであり、以下の点で開発者にとって強力な味方となります。

  • ✅ OpenAI APIのコストを事前に見積もることができる。
  • ✅ モデルのコンテキストウィンドウ上限を超えないように入力テキストを管理できる。
  • ✅ モデルがテキストをどのように「見ている」かを理解し、プロンプトエンジニアリングに役立てられる。
  • encoding_for_model() を使うことで、モデルとエンコーディングの対応関係の変化に強いコードを書ける。

OpenAIのLLMを活用したアプリケーション開発において、トークン化の仕組みを理解し、tiktoken を適切に使いこなすことは、コスト効率、パフォーマンス、そして最終的なアプリケーションの品質を向上させる上で非常に重要です。

ぜひ、tiktoken をあなたのプロジェクトに取り入れて、OpenAIモデルの能力を最大限に引き出してください!🚀 Happy coding!

コメント

タイトルとURLをコピーしました