AIによる日本語トークナイズの最適化とモデル精度への影響

日本語LLMの性能を左右するトークナイズ戦略:SentencePieceによる語彙最適化と実装ロードマップ

この記事は急速に進化する技術について解説しています。最新情報は公式ドキュメントをご確認ください。

約13分で読めます
文字サイズ:
日本語LLMの性能を左右するトークナイズ戦略:SentencePieceによる語彙最適化と実装ロードマップ
目次

この記事の要点

  • 日本語LLMにおけるトークナイズの根本的重要性
  • 単語区切りがない日本語のトークナイズ課題
  • SentencePieceなどによる語彙最適化手法

導入

日本語のLLM(大規模言語モデル)を扱う際、トークナイザーについてどれくらい意識されているでしょうか。

「とりあえずHugging Faceにある日本語BERTのトークナイザーを使っている」
「Llamaをそのまま使っているけれど、日本語の生成が遅い気がする」

開発現場では、このような声がよく聞かれます。実は、トークナイズの戦略一つで、モデルの学習効率や推論速度、最終的なタスク精度、さらには運用時の費用対効果までが劇的に変わることがあります。特に日本語のような膠着語においては、英語圏のモデルで標準的なトークナイズ手法がそのまま通用しないケースも多々あります。

本記事では、理論的な背景を押さえつつ、実際にPythonコードを動かして独自のトークナイザーを作る工程を解説します。単なるツールの使い方だけでなく、システム受託開発やAI導入の現場で必須となる「語彙拡張」の実用的なテクニックまでカバーします。

ブラックボックスになりがちなトークナイザーの仕組みを、現場目線で分かりやすく紐解いていきましょう。

本学習パスのゴール:トークナイズを制する者は日本語LLMを制す

なぜトークナイザーがモデル精度を左右するのか

LLMにとってトークナイザーは、人間でいう「耳」や「目」にあたります。私たちが音声や文字を知覚して意味を理解するように、モデルはトークナイザーによって数値化されたID列を入力として受け取ります。

もし、この入力段階で情報が欠落したり、非効率な分割が行われていたらどうなるでしょうか。

  1. 意味の断絶: 「東京都」という単語が「東」「京」「都」とバラバラに分割されると、モデルはそれぞれの文字の意味から「日本の首都」という意味を再構築しなければならず、学習の負担が増えます。
  2. コンテキストの浪費: 1つの単語が多くのトークンに分割されると、LLMの入力制限(コンテキストウィンドウ)をすぐに使い果たしてしまいます。これはRAG(検索拡張生成)で多くのドキュメントを読み込ませたい場合に致命的であり、API利用コストの増加にも直結します。
  3. 未知語(UNK)の発生: 語彙に含まれない単語が「UNK」トークンとして処理されると、その情報は完全に失われます。

適切なトークナイズ戦略を持つことは、モデルの基礎体力を底上げし、実運用における費用対効果を高めることと同義です。

学習のロードマップと到達目標

この記事では、以下のステップで実践的なスキルを習得します。

  • Step 1: 日本語処理における形態素解析とサブワードアルゴリズム(BPE/Unigram)の違いを理解する。
  • Step 2: SentencePieceを用いて、生テキストからカスタムトークナイザーを学習させる。
  • Step 3: 語彙数(Vocab Size)の違いが分割結果や効率(Fertility Rate)にどう影響するか検証する。
  • Step 4: 英語ベースのLLMに対して日本語の語彙を追加・拡張する実務テクニックを学ぶ。

必要な環境とライブラリ

まずはハンズオン環境を整えましょう。Google ColabやローカルのPython環境(Python 3.8以上推奨)を用意してください。今回は、デファクトスタンダードである sentencepiecetransformers ライブラリを使用します。

# 必要なライブラリのインストール
!pip install sentencepiece transformers pandas

準備が整いましたら、具体的な内容に入っていきましょう。

Step 1:基礎理論 - 形態素解析とサブワードアルゴリズムの違い

Step 1:基礎理論 - 形態素解析とサブワードアルゴリズムの違い - Section Image

コードを書く前に、少しだけ理論の整理をしておきます。「なぜSentencePieceを使うのか」を論理的に理解することが、実際のシステム開発における応用力につながります。特に最新のLLM開発において、トークナイザーの選択はモデルの性能だけでなく、推論コストやコンテキストウィンドウの利用効率にも直結する重要な要素です。

MeCab/Sudachiによる単語分割のメリット・デメリット

日本語NLPの歴史において、MeCabやSudachiといった形態素解析エンジンは長らく主役でした。これらは辞書ベースで動作し、文法的に正しい単語区切りを提供してくれます。現在でも、データ分析基盤における検索インデックス作成や、特定のテキスト分析タスクでは非常に強力なツールです。

  • メリット: 言語学的に正しい分割が可能。「品詞」の情報が得られるため、精密なテキスト解析に向く。
  • デメリット: 辞書にない言葉(新語、造語、スラング)に弱い。辞書のメンテナンスコストが高い。また、語彙数が膨大になりやすく、LLMの学習効率を下げる要因になる。

Web上のあらゆるテキストを扱うLLMの時代において、「辞書にない言葉」への対応力不足は実務上大きな課題となります。そのため、生成AIのモデル内部では、次述するサブワード方式が標準となっています。

BPE (Byte Pair Encoding) の仕組みとアルゴリズム

そこで登場したのが「サブワード」という考え方です。単語そのものではなく、「頻出する文字の並び」をトークンとして扱うアプローチです。GPTシリーズやLlamaなど、多くの現代的なLLMで採用されている基礎技術です。

BPE(Byte Pair Encoding)はその代表格です。仕組みはシンプルです。

  1. データを全て文字単位に分解する。
  2. 隣り合う文字のペアの中で、最も頻出するものを結合して新しいトークンとして登録する。
  3. 指定した語彙数になるまで2を繰り返す。

例えば、「low」「lowest」「newer」という単語データがあった場合、「er」や「est」といった接尾辞が頻出するため、これらが一つのトークンとして登録されます。これにより、未知語であっても「既知のサブワードの組み合わせ」として表現できる可能性が高まります。

Unigram Language ModelとSentencePieceの革命

BPEは強力ですが、決定論的(貪欲法)に結合していくため、必ずしも確率的に最適な分割とは限りません。そこで考案されたのが Unigram Language Model です。

Unigramは逆に、「巨大な語彙候補から、全体の尤度(もっともらしさ)が下がるものを削っていく」というアプローチを取ります。これにより、複数の分割候補の中から確率的に最も妥当なものを選択できるようになります。

そして、これらを使いやすくパッケージングしたのがGoogleの SentencePiece です。SentencePieceの革新的な点は、「空白(スペース)」も一つの文字(アンダースコア _ などで表現)として扱うことです。

日本語のように単語間にスペースがない言語でも、英語と同じアルゴリズムで統一的に処理できる。これが、現在の多言語LLMでSentencePiece(およびその派生)が採用されている最大の理由です。特に日本語を含む多言語モデルを構築する際、前処理の複雑さを劇的に下げ、モデルの汎化性能を高める鍵となっています。

Step 2:実装演習 - カスタムトークナイザーの学習と構築

理論の確認はここまでとし、実際に手を動かしてみましょう。ここでは、SentencePieceを使って独自のトークナイザーを作成します。

コーパスの準備と前処理パイプライン

まずは学習データ(コーパス)が必要です。実際のAI導入プロジェクトでは社内ドキュメントや業務データを使いますが、ここでは動作確認用にダミーテキストを生成してファイルに保存します。

import sentencepiece as spm
import os

# 学習用ダミーデータの作成
# 実際には数MB〜数GBのテキストデータを用意します
dummy_text = """
人工知能(AI)は、人間の知的振る舞いを一部ソフトウェアを用いて人工的に再現したものです。
機械学習はAIの一分野であり、データからルールを学習します。
深層学習(ディープラーニング)は、機械学習の一手法であり、多層のニューラルネットワークを用います。
自然言語処理(NLP)は、人間が日常的に使っている言語をコンピュータに処理させる技術です。
トークナイズは、テキストをモデルが扱える最小単位に分割するプロセスです。
東京都は日本の首都であり、世界最大級の都市圏を形成しています。
"""

# テキストファイルとして保存
with open("corpus.txt", "w", encoding="utf-8") as f:
    # データ量を擬似的に増やすためにループ
    for _ in range(100):
        f.write(dummy_text)

print("学習用コーパス 'corpus.txt' を作成しました。")

SentencePieceを用いたトークナイザーの学習手順

次に、SentencePieceTrainer を使ってモデルを学習させます。ここで重要なのが vocab_size(語彙数)と model_type です。

# SentencePieceモデルの学習関数
def train_spm(vocab_size=1000, model_prefix='spm_custom'):
    spm.SentencePieceTrainer.Train(
        input='corpus.txt',       # 入力ファイル
        model_prefix=model_prefix,# 出力モデルの接頭辞
        vocab_size=vocab_size,    # 語彙数(実験用に小さく設定)
        character_coverage=0.9995,# カバーする文字の割合(日本語なら0.9995推奨)
        model_type='unigram',     # アルゴリズム(unigram または bpe)
        user_defined_symbols=['<sep>', '<cls>'] # 特殊トークンの定義
    )
    print(f"モデル {model_prefix}.model (vocab_size={vocab_size}) の学習が完了しました。")

# 実験:語彙数を変えて2つのモデルを作る
train_spm(vocab_size=100, model_prefix='spm_vocab100')
train_spm(vocab_size=1000, model_prefix='spm_vocab1000')

このコードを実行すると、ディレクトリに .model.vocab ファイルが生成されます。これが作成したトークナイザーです。

語彙数(Vocab Size)の設定とモデルサイズへの影響

vocab_size はモデルの性能とサイズのトレードオフを決める最重要パラメータです。

  • 語彙数が小さい場合: トークン化の粒度が細かくなります(文字単位に近づく)。未知語は減りますが、トークン列が長くなり、推論コストが増加します。
  • 語彙数が大きい場合: 長い単語を一発でトークン化できます。トークン列は短くなりますが、モデルのEmbedding層(パラメータ数)が肥大化し、学習データに含まれないレアな単語への対応力が落ちます。

一般的な日本語LLMでは、32,000〜64,000程度が設定されることが多いですが、ドメイン特化(例えば医療用語や特定の業務システム用語が多いなど)の場合は、現場の課題に合わせてこれを調整する必要があります。

Step 3:検証実験 - 分割粒度がモデル性能に与える影響を測定する

Step 3:検証実験 - 分割粒度がモデル性能に与える影響を測定する - Section Image

作成したモデルを使って、実際にトークナイズしてみましょう。ここでの比較が、実務において最適な設定を見つけるための重要なプロセスとなります。

同じテキストを異なるトークナイザーでエンコード比較

先ほど作成した「語彙数100(極小)」と「語彙数1000(中程度)」のモデルで、同じ文章を分割してみます。

# モデルのロード
sp_100 = spm.SentencePieceProcessor(model_file='spm_vocab100.model')
sp_1000 = spm.SentencePieceProcessor(model_file='spm_vocab1000.model')

text = "東京都は日本の首都です。"

# トークン分割結果の表示
print(f"Original: {text}")
print(f"Vocab 100 : {sp_100.encode_as_pieces(text)}")
print(f"Vocab 1000: {sp_1000.encode_as_pieces(text)}")

実行結果のイメージ(データ量によって変わります):

  • Vocab 100: [' ', '東', '京', '都', 'は', '日', '本', 'の', '首', '都', 'で', 'す', '。']
    • 語彙が少なすぎるため、ほとんど文字単位に分解されています。
  • Vocab 1000: [' ', '東京都', 'は', '日本の', '首都', 'です', '。']
    • 「東京都」や「日本の」といった塊がトークンとして認識されています。

トークン化効率(Fertility Rate)の計算と評価

この違いを定量的に評価する指標の一つに Fertility Rate(生起率) があります。これは「単語数あたりのトークン数」を示す指標ですが、日本語の場合は「文字数あたりのトークン数」で考えると分かりやすいです。

def calculate_fertility(sp_model, text):
    tokens = sp_model.encode_as_pieces(text)
    # 文字数に対するトークン数の比率(低いほど効率的)
    return len(tokens) / len(text)

rate_100 = calculate_fertility(sp_100, text)
rate_1000 = calculate_fertility(sp_1000, text)

print(f"Fertility Rate (Vocab 100): {rate_100:.2f}")
print(f"Fertility Rate (Vocab 1000): {rate_1000:.2f}")

通常、Fertility Rateが低い(トークン数が少ない)方が、LLMにとっては有利です。同じコンテキスト長(例えば4096トークン)の中に、より多くの情報を詰め込めるため、APIの利用効率も向上します。

しかし、低ければ良いというわけではありません。もし「東京都」というトークンが学習データに1回しか出てこないなら、モデルはそのトークンの意味(ベクトル表現)を十分に学習できません。逆に「東」「京」「都」と分かれていれば、それぞれの文字の頻度は高いため、個別の意味から合成して理解できる可能性があります。

実務上のポイント:
実際のプロジェクトでは、自社データをトークナイズしてみて、「意味の塊」として不自然すぎないかを目視確認することをお勧めします。特に専門用語(製品名や技術用語)が細かく分割されている場合は、語彙数を増やすか、その用語を強制的に辞書に追加することを検討すべきです。

Step 4:実務応用 - 既存LLMへの語彙拡張と継続事前学習への接続

Step 4:実務応用 - 既存LLMへの語彙拡張と継続事前学習への接続 - Section Image

ここからが応用編です。LlamaシリーズやMistralといった高性能な英語モデルをベースに、日本語能力を追加学習(継続事前学習)させるケースが増えています。日本語強化モデルの開発でも、このアプローチが重要な役割を果たしています。

英語圏で開発されたモデルのトークナイザーをそのまま日本語に使用すると、多くの漢字やひらがながUTF-8のバイト列として細切れに分割され、トークン効率が著しく低下するという課題があります。これを解決する技術的な鍵が 「語彙拡張(Vocabulary Expansion)」 です。

既存モデルへの語彙追加手法

Hugging Faceの transformers ライブラリを使用して、既存のトークナイザーに新しい日本語トークンを追加する標準的な手順を示します。ここではデモとして軽量なモデルを使用していますが、最新のLLMでも基本的なプロセスは共通です。

from transformers import AutoTokenizer, AutoModelForCausalLM

# ベースモデルのトークナイザーをロード
# ※実際の開発では、対象モデルを指定し、必要に応じてHugging Faceのトークン設定を行います
base_tokenizer = AutoTokenizer.from_pretrained("gpt2") # デモ用にgpt2を使用

# 追加したい日本語トークン(実際にはSentencePiece等でドメインデータから学習した語彙リストを使用)
# 頻出する専門用語や複合語を追加することでトークン化効率を改善します
new_tokens = ["東京都", "人工知能", "深層学習"]

# トークナイザーにトークンを追加
num_added_toks = base_tokenizer.add_tokens(new_tokens)
print(f"追加されたトークン数: {num_added_toks}")
print(f"現在の語彙サイズ: {len(base_tokenizer)}")

# テスト
text = "東京都は人工知能の研究が盛んです。"
encoded = base_tokenizer.tokenize(text)
print(f"拡張後のトークナイズ: {encoded}")

埋め込み層(Embedding Layer)のサイズ変更と初期化

トークナイザーの語彙を拡張したら、必ずモデル本体のEmbedding層(入力層および出力層)のサイズを新しい語彙数に合わせる必要があります。これを忘れると、学習時や推論時に次元数の不一致によるエラーが発生します。

model = AutoModelForCausalLM.from_pretrained("gpt2")

# モデルのEmbedding層をリサイズ
model.resize_token_embeddings(len(base_tokenizer))

print(f"モデルのEmbeddingサイズを {len(base_tokenizer)} に変更しました。")

実装時の重要なテクニック:
単にリサイズしただけでは、新しく追加されたトークンのベクトルはランダムに初期化されています。これでは学習の初動が不安定になり、モデルが新しい語彙の意味を学習するのに時間がかかります。

より実践的なアプローチとして、追加したトークンのベクトルを、既存の関連するトークンのベクトルの平均値などで初期化するという方法が効果的です。例えば、「東京都」のベクトルを初期化する際、既存の語彙にある「Tokyo」や「City」のベクトル情報を参照値として利用することで、学習の収束を早め、初期段階からの性能低下を防ぐことが期待できます。

学習リソースと次のステップ

ここまで、トークナイザーの作成から応用までを解説してきました。さらに深く学びたい方のために、信頼できるリソースを紹介します。

推奨書籍と論文リスト

  • SentencePiece論文: "SentencePiece: A simple and language independent subword tokenizer and detokenizer for Neural Text Processing" (Kudo et al., 2018)。原典を読むことで、正規化(Normalization)の処理などの理解が深まります。
  • Hugging Face Course: 特に「Tokenizers」の章は必読です。BPEとUnigramの実装の違いが視覚的に解説されています。

自社データでの検証計画の立て方

実務で活用できるアクションプランの例を挙げます。

  1. 現状分析: 自社で使っているモデルのトークナイザーで、業務ドキュメントをトークナイズし、Fertility Rateを計算する。
  2. 専門用語チェック: 社内用語や業界用語がどう分割されているかリストアップする。
  3. 語彙拡張のPoC: 上記Step 4のコードを参考に、主要な専門用語を100個ほど追加して、数エポックだけファインチューニングしてみる。これだけでも、その用語に関する生成精度や処理効率の向上が期待できます。

まとめ

Step 4:実務応用 - 既存LLMへの語彙拡張と継続事前学習への接続 - Section Image 3

トークナイズは、派手なモデルアーキテクチャやプロンプトエンジニアリングの影に隠れがちですが、日本語LLMの性能と運用コストを底支えする極めて重要な「インフラ」です。

  • 適切なアルゴリズム選定: SentencePiece (Unigram) は日本語と相性が良い。
  • 語彙数のバランス: トークン化効率と学習効率のトレードオフを意識する。
  • 語彙拡張: 既存モデルを日本語化し、業務適用する際の必須テクニック。

これらを論理的に理解し、プロジェクトの要件に合わせてコントロールできるようになることは、モデルのポテンシャルを最大限に引き出し、現実的な課題解決につなげるための強力な武器となります。

まずは手を動かし、文字がトークンに変わるプロセスを実際のデータで確認してみてください。

コメント

コメントは1週間で消えます
コメントを読み込み中...