BPE
貪心提取所有出現頻率高的成為詞。
BPE的訓練流程
1.初始化:將所有單個字符作為初始詞匯表的元素。
2.迭代合并:
- 統計語料中所有相鄰符號對(包括字符和合并后的符號)的出現頻率。
- 找到出現頻率最高的符號對,將其合并為新的符號,并添加到詞匯表中。
- 在語料中用新符號替換所有該符號對的出現。
3.重復迭代,直到達到預設的詞匯表大小或到達合并次數。
輸入:語料D,預設的合并次數N
初始化:詞匯表V為語料中的所有字符
for i from 1 to N do:統計語料中所有相鄰符號對的頻率,得到列表P選擇頻率最高的符號對(s1, s2) ∈ P合并符號對s1、s2為新符號s_new將s_new添加到詞匯表V用s_new替換語料D中所有的s1 s2
end for
輸出:詞匯表V
WordPiece
WordPiece目標是在固定大小的詞表預算下,選擇一組子詞單元,使整個訓練語料在當前詞表下的分詞似然最大化。它的核心思想是:初始詞匯表包含所有單個字符,通過迭代合并帶來最大似然增益的高頻字符對,逐步擴展為更長的子詞單元。
在 WordPiece 的建模假設中,一個單詞的概率是其分解后子詞序列概率的乘積:
P(word)=∏i=1nP(tokeni)P(\text{word}) = \prod_{i=1}^{n} P(\text{token}_i)P(word)=i=1∏n?P(tokeni?)
因此,WordPiece 的訓練目標是最大化整個訓練語料的對數似然:
max?∑wordslog?P(word)\max \sum_{\text{words}} \log P(\text{word})maxwords∑?logP(word)
WordPiece 的訓練流程
1.初始化詞匯表:從所有單個字符開始,非開頭的token會加上前綴/后綴(例如 ##ing
、##ed
)。
2.計算相鄰字符對頻率:統計整個訓練語料中所有相鄰字符對的出現頻率。
3. 迭代合并:在每次迭代中,計算增益Gain=loglikelihood(new)?loglikelihood(old)Gain=loglikelihood(new)-loglikelihood(old)Gain=loglikelihood(new)?loglikelihood(old),選擇能夠使語言模型對數似然增益最大的字符對進行合并,并更新詞表。重復此過程直到詞匯表達到預設大小。
4. 得到最終詞匯表
輸入:語料D,預設的詞匯表大小N
初始化:詞匯表V為所有字符
while |V| < N do:初始化增益字典G = {}for 語料D中的每個可能的子詞s(由現有詞匯表中的符號組合而成):計算將s添加到詞匯表V后,語料D的似然增益ΔL將(s, ΔL)加入G從G中選擇ΔL最大的子詞s_max將s_max添加到詞匯表V更新模型參數(如概率)
end while
輸出:詞匯表V
eg.給定初始詞表 ["a", "b", "c", "w"]
和詞匯 v = ["abc", "abw"]
,各字符的概率分布如下:
字符概率分布:p(a)=p(b)=13p(a) = p(b) = \frac{1}{3}p(a)=p(b)=31?,p(c)=p(w)=16p(c) = p(w) = \frac{1}{6}p(c)=p(w)=61?
假如合并ab,合并后詞表更新為 ["ab", "c", "w"]
,重新歸一化概率 p(ab)=12,p(c)=p(w)=14p(ab) = \frac{1}{2},p(c) = p(w) = \frac{1}{4}p(ab)=21?,p(c)=p(w)=41?
詞匯序列概率計算:p(v)=p(ab)×p(c)+p(ab)×p(w)=14p(v) =p(ab) \times p(c) + p(ab) \times p(w) = \frac{1}{4}p(v)=p(ab)×p(c)+p(ab)×p(w)=41?
原始未合并概率p(old?v)=p(a)×p(b)×p(c)+p(a)×p(b)×p(w)=127p(old\ v) =p(a) \times p(b) \times p(c) + p(a) \times p(b) \times p(w) = \frac{1}{27}p(old?v)=p(a)×p(b)×p(c)+p(a)×p(b)×p(w)=271?
Gain=p(v)?p(old?v)=14?127=23108 Gain = p(v) - p(old\ v) = \frac{1}{4} - \frac{1}{27} = \frac{23}{108}Gain=p(v)?p(old?v)=41??271?=10823?
GPT寫的一個demo
from tokenizers import Tokenizer, models, trainers, pre_tokenizers
# #BPE
# from tokenizers import models, trainers
# tokenizer = Tokenizer(models.BPE())
# trainer = trainers.BpeTrainer(vocab_size=30, special_tokens=["[UNK]"])# 1?? 定義模型類型:WordPiece
tokenizer = Tokenizer(models.WordPiece(unk_token="[UNK]"))# 2?? 定義預分詞規則
tokenizer.pre_tokenizer = pre_tokenizers.Whitespace()# 3?? 定義訓練器
trainer = trainers.WordPieceTrainer(vocab_size=100,special_tokens=["[PAD]", "[UNK]", "[CLS]", "[SEP]", "[MASK]"]
)# 4?? 訓練文本
corpus = ["the quick brown fox jumps over the lazy dog","the quick brown dog jumps over the lazy fox","i love natural language processing","wordpiece helps with unknown words"
]# 5?? 訓練
tokenizer.train_from_iterator(corpus, trainer)# 6?? 測試編碼
output = tokenizer.encode("the quick brown fox")
print("Tokens:", output.tokens)
print("IDs:", output.ids)# 7?? 保存分詞器
tokenizer.save("wordpiece-tokenizer.json")
Q:為什么 vocab_size 越大,分詞后句子里的 token 數會更少?
A:因為 vocab_size 越大,分詞器詞表能收錄更多子詞單元,包括更長的子詞或整個單詞。這讓分詞器在處理文本時可以用更大的塊來覆蓋文本,減少切分次數,所以最后得到的 token 數會更少。
Q:vocab_size 和合并次數是一個概念嗎?
A:不是。vocab_size 是詞表大小的上限,但并不保證最后一定會有 vocab_size 那么多的子詞單元。分詞器會盡量把詞表填滿到這個上限,但如果語料規模很小、可合并的模式有限,最終學到的詞表可能會小于設定的 vocab_size。