Huggingface Tokenizers
目次
- skill の情報
- 参考: SKILL.md 全文
- HuggingFace Tokenizers を使う場面
- はじめの一歩
- 導入
- 学習済みのトークナイザを読み込む
- 独自の BPE トークナイザを学習させる
- まとめて処理し、長さをそろえる
- トークン分割のアルゴリズム
- BPE(Byte-Pair Encoding)
- WordPiece
- Unigram
- トークン分割の流れ
- 正規化
- 事前分割
- 後処理
- 位置の対応づけ
- transformers との組み合わせ
- AutoTokenizer で読み込む
- 独自のトークナイザを transformers 向けに包む
- よく使う型
- イテレータから学習させる(大きなデータ向け)
- 打ち切りとパディングを有効にする
- 複数のプロセスで処理する
- 性能の測定結果
- 学習の速さ
- トークン分割の速さ
- メモリの使用量
- 対応しているモデル
- 関連ドキュメント
- 参考資料
高速な 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 つのトークンにまとめ、語彙に加えます
- 語彙の大きさが目標に届くまで繰り返します
採用例: 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
仕組み:
- 文字単位の語彙から始めます
- まとめる組に点数を付けます:
frequency(pair) / (frequency(first) × frequency(second)) - 点数のいちばん高い組をまとめます
- 語彙の大きさが目標に届くまで繰り返します
採用例: 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
仕組み:
- 大きな語彙(あらゆる部分文字列)から始めます
- いまの語彙でコーパスの損失を計算します
- 損失への影響がいちばん小さいトークンを取り除きます
- 語彙の大きさが目標に届くまで繰り返します
採用例: 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-caseddistilbert-base-uncasedroberta-base,roberta-large
GPT 系:
gpt2,gpt2-medium,gpt2-largedistilgpt2
T5 系:
t5-small,t5-base,t5-largegoogle/flan-t5-xxl
その他:
facebook/bart-base,facebook/mbart-large-cc25albert-base-v2,albert-xlarge-v2xlm-roberta-base,xlm-roberta-large
すべての一覧: https://huggingface.co/models?library=tokenizers
関連ドキュメント
- 学習の手引き - 独自のトークナイザの学習、トレーナーの設定、大きなデータの扱い方
- アルゴリズムの詳細 - BPE、WordPiece、Unigram のくわしい解説
- 流れを構成する部品 - 正規化、事前分割、後処理、復元
- transformers との組み合わせ - AutoTokenizer、PreTrainedTokenizerFast、特別なトークン
参考資料
- ドキュメント: 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)