1028 words
5 minutes

Tokenization

2026-01-22

Basics#

將自然語言輸入給模型需要經過轉換 (變成 embedding), 主要有以下兩步驟:

  1. 分詞, 並把詞轉為 Token
  2. 把 Token 轉換為 Embedding

Tokenize 分為三種粒度:

  • Word: 最簡單的語言單元, 常見的語言可以用空格或是標點符號(e.g. 英文) 拆分詞彙, 並在 Embedding Matrix 中查詢對應 Token 的 Embedding Vector, 實作相對簡單, 缺點為 Embedding Matrix 的大小會很大, 且容易 OOV
  • Char: 基本的字, 數量相對很少, 缺點為因為 Embedding 的基數太小, 在學習時每個 Vector 都會學到太多語義, 且無法保留詞彙的語義與邊界, 更會顯著增加模型輸入計算壓力
  • Subword: 介於 Word 和 Char 之間, 平衡了詞彙量與每個 Vector 的語義獨立性, 對於常用詞保留原狀, 對於生僻詞則使用拆分, 共享 Token 壓縮空間, 整體相對較優

BPE (Byte-Pair Encoding)#

一種Subword tokenize算法, 參考 Neural Machine Translation of Rare Words with Subword Units 的程式碼:

import re, collections
def get_stats(vocab):
pairs = collections.defaultdict(int)
for word, freq in vocab.items():
symbols = word.split()
for i in range(len(symbols)-1):
pairs[symbols[i],symbols[i+1]] += freq
return pairs
def merge_vocab(pair, v_in):
v_out = {}
bigram = re.escape(' '.join(pair))
p = re.compile(r'(?<!\S)' + bigram + r'(?!\S)')
for word in v_in:
w_out = p.sub(''.join(pair), word)
v_out[w_out] = v_in[word]
return v_out
vocab = {'l o w </w>' : 5, 'l o w e r </w>' : 2, 'n e w e s t </w>':6, 'w i d e s t </w>':3}
num_merges = 10
for i in range(num_merges):
pairs = get_stats(vocab)
best = max(pairs, key=pairs.get)
vocab = merge_vocab(best, vocab)
print(best)

首先初始化詞彙表: 把所有的 word 拆分成 char 列表:

'l o w </w>' : 5
'l o w e r </w>' : 2
'n e w e s t </w>' : 6
'w i d e s t </w>' : 3

接著計算 pair 的頻率, 再合併頻率最高的 pair

當詞彙表到達指定的數量則停下, 算是一種 greedy 算法

在比較近代的模型 (GPT-2, RoBERTa, …) 中, 為避免新的詞被標記成 [UNK], 不使用 Unicode 字元, 使用 Byte, 來保證任何文本都可以輸入, 也能處理複雜符號 (emoji)

WordPiece#

與 BPE 類似, 都是由小到大合併的策略, 但在合併階段選擇最大分數的 pair, 而非最大頻率. 分數的計算方式類似互信息(Mutual Information):

Score=Count(AB)Count(A)×Count(B)\text{Score} = \frac{\text{Count}(AB)}{\text{Count}(A) \times \text{Count}(B)}

這種方式傾向合併那些「共同出現頻率高, 但各自單獨出現頻率低」的 pair, 能更好地捕捉有意義的子詞組合

使用 WordPiece 的模型:BERT、DistilBERT 等

Unigram Language Model#

與 BPE「由小到大合併」不同, Unigram 採用「由大到小剪枝」的策略

核心假設#

假設每個子詞獨立出現, 序列機率為各子詞機率的乘積:

P(x)=i=1Mp(xi)P(x) = \prod_{i=1}^{M} p(x_i)

詞彙表建立#

給定語料庫, Unigram 的目標是最大化以下似然:

L=i=1Nlog(xS(xi)P(x))L = \sum_{i=1}^{N} \log \left( \sum_{x \in S(x_i)} P(x) \right)

其中 S(xi)S(x_i) 是單詞 xix_i 所有可能的分割集合

建立流程:

  1. 初始化一個大型種子詞彙表(所有字元 + 高頻子字串)
  2. 用 EM 演算法估計每個子詞的機率 p(x)p(x)
  3. 計算每個子詞的 loss(移除後似然度下降量)
  4. 移除 loss 最小的子詞(保留前 η%, 如 80%)
  5. 重複步驟 2-4, 直到達到目標詞彙大小

因為始終保留單一字元,確保不會有 OOV 問題

與 Subword Regularization 的關係#

Unigram 的優勢在於能輸出多種分割及其機率, 這使它可以搭配 Subword Regularization 訓練策略使用

傳統 NMT 使用固定分割, 優化:

L(θ)=s=1DlogP(y(s)x(s);θ)L(\theta) = \sum_{s=1}^{|D|} \log P(y^{(s)}|x^{(s)}; \theta)

Subword Regularization 改為對所有可能分割取期望:

Lmarginal(θ)=s=1DExP(xX),yP(yY)[logP(yx;θ)]L_{marginal}(\theta) = \sum_{s=1}^{|D|} \mathbb{E}_{x \sim P(x|X), y \sim P(y|Y)} \left[ \log P(y|x; \theta) \right]

這讓模型在訓練時看到同一句子的不同分割方式, 增強魯棒性


SentencePiece#

  • 把句子視為原始字元流(包含空格),不依賴預先的詞邊界切分
  • 用特殊符號 標記詞的開頭(空格位置)
  • 支援 BPE 和 Unigram 兩種演算法

例如 “Hello World” → “▁Hello” “▁World” 或 “▁He” “llo” “▁Wor” “ld”

使用 SentencePiece + Unigram 的模型:XLNet、T5、ALBERT 等

Reference#

Tokenization
https://blog.cyberangel.work/posts/ml-tokenization/
Author
Ethan Lai
Published at
2026-01-22
License
CC BY-NC-SA 4.0

Comments

Table of Contents