Hermes Agent Wiki 非公式・日本語wiki
この skill をそのまま使う: GitHub で原文を見る

英語原文・frontmatter 込みで、Hermes が読み込む実体そのままです(このページの本文は日本語版)。

Huggingface Tokenizers

目次

高速な BPE/WordPiece のトークン分割と、独自語彙の学習を行います。

skill の情報

提供元 追加インストール — hermes skills install official/mlops/huggingface-tokenizers で導入します
パス optional-skills/mlops/huggingface-tokenizers
バージョン 1.0.0
作者 Orchestra Research
ライセンス MIT
依存関係 tokenizers, transformers, datasets
対応プラットフォーム linux, macos, windows
タグ Tokenization, HuggingFace, BPE, WordPiece, Unigram, Fast Tokenization, Rust, Custom Tokenizer, Alignment Tracking, Production

参考: SKILL.md 全文

HuggingFace Tokenizers - 自然言語処理のための高速なトークン分割

Rust 並みの速さと Python の書きやすさを兼ね備えた、実運用で使えるトークナイザです。

HuggingFace Tokenizers を使う場面

HuggingFace Tokenizers を使うとよい場面:

  • とても速いトークン分割が要るとき(1GB のテキストを <20s で処理)
  • 独自のトークナイザを一から学習させるとき
  • 位置の対応づけ(トークン → 元のテキストの位置)を追いたいとき
  • 実運用の自然言語処理のパイプラインを組むとき
  • 大きなコーパスを効率よくトークン分割したいとき

性能:

  • 速さ: CPU で 1GB を <20 秒
  • 実装: Rust で書かれた中核に、Python/Node.js から使える口が付いています
  • 効率: 純粋な Python の実装より 10〜100 倍速い

ほかの手段のほうが向いている場面:

  • SentencePiece: 言語に依存しない方式。T5/ALBERT で使われています
  • tiktoken: GPT 向けの、OpenAI による BPE のトークナイザ
  • transformers の AutoTokenizer: 学習済みのものを読み込むだけのとき(内部でこのライブラリを使っています)

はじめの一歩

導入

次のコマンドで導入します。transformers と組み合わせる場合は 2 つ目のほうを使います。

# Install tokenizers
pip install tokenizers

# With transformers integration
pip install tokenizers transformers

学習済みのトークナイザを読み込む

次のコードは、HuggingFace Hub からトークナイザを取得し、文をトークンに分けてから元に戻します。

from tokenizers import Tokenizer

# Load from HuggingFace Hub
tokenizer = Tokenizer.from_pretrained("bert-base-uncased")

# Encode text
output = tokenizer.encode("Hello, how are you?")
print(output.tokens)  # ['hello', ',', 'how', 'are', 'you', '?']
print(output.ids)     # [7592, 1010, 2129, 2024, 2017, 1029]

# Decode back
text = tokenizer.decode(output.ids)
print(text)  # "hello, how are you?"

独自の BPE トークナイザを学習させる

次のコードは、BPE のトークナイザを用意し、手元のファイルで学習させて保存します。

from tokenizers import Tokenizer
from tokenizers.models import BPE
from tokenizers.trainers import BpeTrainer
from tokenizers.pre_tokenizers import Whitespace

# Initialize tokenizer with BPE model
tokenizer = Tokenizer(BPE(unk_token="[UNK]"))
tokenizer.pre_tokenizer = Whitespace()

# Configure trainer
trainer = BpeTrainer(
    vocab_size=30000,
    special_tokens=["[UNK]", "[CLS]", "[SEP]", "[PAD]", "[MASK]"],
    min_frequency=2
)

# Train on files
files = ["train.txt", "validation.txt"]
tokenizer.train(files, trainer)

# Save
tokenizer.save("my-tokenizer.json")

学習にかかる時間: 100MB のコーパスで 1〜2 分、1GB で 10〜20 分ほど

まとめて処理し、長さをそろえる

次のコードは、パディングを有効にしてから複数の文をまとめてトークン分割します。

# Enable padding
tokenizer.enable_padding(pad_id=3, pad_token="[PAD]")

# Encode batch
texts = ["Hello world", "This is a longer sentence"]
encodings = tokenizer.encode_batch(texts)

for encoding in encodings:
    print(encoding.ids)
# [101, 7592, 2088, 102, 3, 3, 3]
# [101, 2023, 2003, 1037, 2936, 6251, 102]

トークン分割のアルゴリズム

BPE(Byte-Pair Encoding)

仕組み:

  1. 文字単位の語彙から始めます
  2. いちばんよく出る文字の組を探します
  3. それを 1 つのトークンにまとめ、語彙に加えます
  4. 語彙の大きさが目標に届くまで繰り返します

採用例: GPT-2、GPT-3、RoBERTa、BART、DeBERTa

from tokenizers import Tokenizer
from tokenizers.models import BPE
from tokenizers.trainers import BpeTrainer
from tokenizers.pre_tokenizers import ByteLevel

tokenizer = Tokenizer(BPE(unk_token="<|endoftext|>"))
tokenizer.pre_tokenizer = ByteLevel()

trainer = BpeTrainer(
    vocab_size=50257,
    special_tokens=["<|endoftext|>"],
    min_frequency=2
)

tokenizer.train(files=["data.txt"], trainer=trainer)

よいところ:

  • 未知語にうまく対処できます(部分語に分けられます)
  • 語彙の大きさを自由に決められます
  • 語形の変化が多い言語に向いています

引き換えになるところ:

  • 分け方が、まとめた順番に左右されます
  • よく使う語が思わぬところで分割されることがあります

WordPiece

仕組み:

  1. 文字単位の語彙から始めます
  2. まとめる組に点数を付けます: frequency(pair) / (frequency(first) × frequency(second))
  3. 点数のいちばん高い組をまとめます
  4. 語彙の大きさが目標に届くまで繰り返します

採用例: BERT、DistilBERT、MobileBERT

from tokenizers import Tokenizer
from tokenizers.models import WordPiece
from tokenizers.trainers import WordPieceTrainer
from tokenizers.pre_tokenizers import Whitespace
from tokenizers.normalizers import BertNormalizer

tokenizer = Tokenizer(WordPiece(unk_token="[UNK]"))
tokenizer.normalizer = BertNormalizer(lowercase=True)
tokenizer.pre_tokenizer = Whitespace()

trainer = WordPieceTrainer(
    vocab_size=30522,
    special_tokens=["[UNK]", "[CLS]", "[SEP]", "[PAD]", "[MASK]"],
    continuing_subword_prefix="##"
)

tokenizer.train(files=["corpus.txt"], trainer=trainer)

よいところ:

  • 意味のあるまとめ方が優先されます(点数が高い=意味のつながりが強い)
  • BERT で実際に成果を上げています(当時の最高性能)

引き換えになるところ:

  • 部分語にも当てはまらない未知語は [UNK] になります
  • まとめ方の規則ではなく語彙を保存するので、ファイルが大きくなります

Unigram

仕組み:

  1. 大きな語彙(あらゆる部分文字列)から始めます
  2. いまの語彙でコーパスの損失を計算します
  3. 損失への影響がいちばん小さいトークンを取り除きます
  4. 語彙の大きさが目標に届くまで繰り返します

採用例: ALBERT、T5、mBART、XLNet(SentencePiece 経由)

from tokenizers import Tokenizer
from tokenizers.models import Unigram
from tokenizers.trainers import UnigramTrainer

tokenizer = Tokenizer(Unigram())

trainer = UnigramTrainer(
    vocab_size=8000,
    special_tokens=["<unk>", "<s>", "</s>"],
    unk_token="<unk>"
)

tokenizer.train(files=["data.txt"], trainer=trainer)

よいところ:

  • 確率にもとづくので、いちばんありそうな分け方を見つけられます
  • 語の切れ目がない言語にうまく合います
  • 幅広い言語の状況に対応できます

引き換えになるところ:

  • 学習に計算量がかかります
  • 調整するパラメータが多めです

トークン分割の流れ

全体の流れは 正規化 → 事前分割 → モデル → 後処理 です。

正規化

テキストをきれいに整えます。

from tokenizers.normalizers import NFD, StripAccents, Lowercase, Sequence

tokenizer.normalizer = Sequence([
    NFD(),           # Unicode normalization (decompose)
    Lowercase(),     # Convert to lowercase
    StripAccents()   # Remove accents
])

# Input: "Héllo WORLD"
# After normalization: "hello world"

よく使う正規化:

  • NFD, NFC, NFKD, NFKC - Unicode の正規化形式
  • Lowercase() - 小文字にそろえます
  • StripAccents() - アクセント記号を外します(é → e)
  • Strip() - 前後の空白を落とします
  • Replace(pattern, content) - 正規表現で置き換えます

事前分割

テキストを語のようなまとまりに分けます。

from tokenizers.pre_tokenizers import Whitespace, Punctuation, Sequence, ByteLevel

# Split on whitespace and punctuation
tokenizer.pre_tokenizer = Sequence([
    Whitespace(),
    Punctuation()
])

# Input: "Hello, world!"
# After pre-tokenization: ["Hello", ",", "world", "!"]

よく使う事前分割:

  • Whitespace() - 空白、タブ、改行で分けます
  • ByteLevel() - GPT-2 と同じバイト単位の分け方
  • Punctuation() - 記号を切り出します
  • Digits(individual_digits=True) - 数字を 1 桁ずつに分けます
  • Metaspace() - 空白を ▁ に置き換えます(SentencePiece のやり方)

後処理

モデルに入れるための特別なトークンを足します。

from tokenizers.processors import TemplateProcessing

# BERT-style: [CLS] sentence [SEP]
tokenizer.post_processor = TemplateProcessing(
    single="[CLS] $A [SEP]",
    pair="[CLS] $A [SEP] $B [SEP]",
    special_tokens=[
        ("[CLS]", 1),
        ("[SEP]", 2),
    ],
)

よくある書き方:

# GPT-2: sentence <|endoftext|>
TemplateProcessing(
    single="$A <|endoftext|>",
    special_tokens=[("<|endoftext|>", 50256)]
)

# RoBERTa: <s> sentence </s>
TemplateProcessing(
    single="<s> $A </s>",
    pair="<s> $A </s> </s> $B </s>",
    special_tokens=[("<s>", 0), ("</s>", 2)]
)

位置の対応づけ

トークンが元のテキストのどこにあったかを追えます。

output = tokenizer.encode("Hello, world!")

# Get token offsets
for token, offset in zip(output.tokens, output.offsets):
    start, end = offset
    print(f"{token:10} → [{start:2}, {end:2}): {text[start:end]!r}")

# Output:
# hello      → [ 0,  5): 'Hello'
# ,          → [ 5,  6): ','
# world      → [ 7, 12): 'world'
# !          → [12, 13): '!'

使いどころ:

  • 固有表現の抽出(予測を元のテキストに戻す)
  • 質問応答(答えの範囲を切り出す)
  • トークン単位の分類(ラベルを元の位置に合わせる)

transformers との組み合わせ

AutoTokenizer で読み込む

次のコードは、AutoTokenizer で読み込み、内側の高速なトークナイザに触ります。

from transformers import AutoTokenizer

# AutoTokenizer automatically uses fast tokenizers
tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased")

# Check if using fast tokenizer
print(tokenizer.is_fast)  # True

# Access underlying tokenizers.Tokenizer
fast_tokenizer = tokenizer.backend_tokenizer
print(type(fast_tokenizer))  # <class 'tokenizers.Tokenizer'>

独自のトークナイザを transformers 向けに包む

次のコードは、学習させたトークナイザを保存し、transformers のトークナイザとして使える形に包みます。

from tokenizers import Tokenizer
from transformers import PreTrainedTokenizerFast

# Train custom tokenizer
tokenizer = Tokenizer(BPE())
# ... train tokenizer ...
tokenizer.save("my-tokenizer.json")

# Wrap for transformers
transformers_tokenizer = PreTrainedTokenizerFast(
    tokenizer_file="my-tokenizer.json",
    unk_token="[UNK]",
    pad_token="[PAD]",
    cls_token="[CLS]",
    sep_token="[SEP]",
    mask_token="[MASK]"
)

# Use like any transformers tokenizer
outputs = transformers_tokenizer(
    "Hello world",
    padding=True,
    truncation=True,
    max_length=512,
    return_tensors="pt"
)

よく使う型

イテレータから学習させる(大きなデータ向け)

次のコードは、データセットを少しずつ取り出しながらトークナイザを学習させます。

from datasets import load_dataset

# Load dataset
dataset = load_dataset("wikitext", "wikitext-103-raw-v1", split="train")

# Create batch iterator
def batch_iterator(batch_size=1000):
    for i in range(0, len(dataset), batch_size):
        yield dataset[i:i + batch_size]["text"]

# Train tokenizer
tokenizer.train_from_iterator(
    batch_iterator(),
    trainer=trainer,
    length=len(dataset)  # For progress bar
)

性能: 1GB を 10〜20 分ほどで処理します

打ち切りとパディングを有効にする

次のコードは、最大の長さを 512 に決めて、足りない分を埋め、あふれる分を切ります。

# Enable truncation
tokenizer.enable_truncation(max_length=512)

# Enable padding
tokenizer.enable_padding(
    pad_id=tokenizer.token_to_id("[PAD]"),
    pad_token="[PAD]",
    length=512  # Fixed length, or None for batch max
)

# Encode with both
output = tokenizer.encode("This is a long sentence that will be truncated...")
print(len(output.ids))  # 512

複数のプロセスで処理する

次のコードは、大きなコーパスを分割し、8 つのプロセスで同時にトークン分割します。

from tokenizers import Tokenizer
from multiprocessing import Pool

# Load tokenizer
tokenizer = Tokenizer.from_file("tokenizer.json")

def encode_batch(texts):
    return tokenizer.encode_batch(texts)

# Process large corpus in parallel
with Pool(8) as pool:
    # Split corpus into chunks
    chunk_size = 1000
    chunks = [corpus[i:i+chunk_size] for i in range(0, len(corpus), chunk_size)]

    # Encode in parallel
    results = pool.map(encode_batch, chunks)

速くなる度合い: 8 コアで 5〜8 倍

性能の測定結果

学習の速さ

コーパスの大きさ BPE(語彙 30k) WordPiece(30k) Unigram(8k)
10 MB 15 秒 18 秒 25 秒
100 MB 1.5 分 2 分 4 分
1 GB 15 分 20 分 40 分

測定環境: 16 コアの CPU、英語版 Wikipedia で測定

トークン分割の速さ

実装 1 GB のコーパス 処理量
純粋な Python 約 20 分 約 50 MB/分
HF Tokenizers 約 15 秒 約 4 GB/分
倍率 80 倍 80 倍

測定条件: 英語のテキスト、1 文あたり平均 20 語

メモリの使用量

作業 メモリ
トークナイザの読み込み 約 10 MB
BPE の学習(語彙 30k) 約 200 MB
100 万文のトークン分割 約 500 MB

対応しているモデル

from_pretrained() で読み込める学習済みのトークナイザです。

BERT 系:

  • bert-base-uncased, bert-large-cased
  • distilbert-base-uncased
  • roberta-base, roberta-large

GPT 系:

  • gpt2, gpt2-medium, gpt2-large
  • distilgpt2

T5 系:

  • t5-small, t5-base, t5-large
  • google/flan-t5-xxl

その他:

  • facebook/bart-base, facebook/mbart-large-cc25
  • albert-base-v2, albert-xlarge-v2
  • xlm-roberta-base, xlm-roberta-large

すべての一覧: https://huggingface.co/models?library=tokenizers

関連ドキュメント

参考資料

  • ドキュメント: https://huggingface.co/docs/tokenizers
  • GitHub: https://github.com/huggingface/tokenizers ⭐ 9,000 以上
  • バージョン: 0.20.0 以降
  • 講座: https://huggingface.co/learn/nlp-course/chapter6/1
  • 論文: BPE(Sennrich ら、2016)、WordPiece(Schuster と Nakajima、2012)