本文將介紹以下內容:
- 1. Subword與傳統tokenization技術的對比
- 2. WordPiece
- 3. Byte Pair Encoding (BPE)
- 4. Byte-level BPE(BBPE)
- 5. SentencePiece 以及各Subword算法代碼實現
一、Subword與傳統tokenization技術的對比
1. 傳統tokenization技術
傳統tokenization技術一般有兩種方法:word(詞)粒度、char(字符)粒度
1.1 word(詞)粒度
傳統構造詞表的方法,是先對各個句子進行分詞,然后再統計并選出頻數最高的前N個詞組成詞表。通常訓練集中包含了大量的詞匯,以英語為例,總的單詞數量在17萬到100萬左右。對于英文來說,word級別分詞實現很簡單,因為本身就有分隔符號。在中文里面word粒度,需要使用一些分詞工具比如jieba、ltp等。該方案優劣勢如下:
-
優點:
- 語義明確: 以詞為粒度進行分詞可以更好地保留每個詞的語義,使得文本在后續處理中能夠更準確地表達含義。
- 上下文理解: 以詞為粒度進行分詞有助于保留詞語之間的關聯性和上下文信息,從而在語義分析和理解時能夠更好地捕捉句子的意圖。
-
缺點:
- OOV(Out-of-Vocabulary): 分詞模型只能夠使用詞表中的詞進行處理,無法處理詞表之外的詞匯。
- 長尾效應和稀有詞問題: 詞表可能變的巨大,包含很多不常見的詞匯,增加存儲和訓練成本,稀有詞的訓練數據有限,無法得到充分訓練,進而模型不能充分理解這些詞的語義。
- 形態關系和詞綴關系: 無法捕捉同一詞的不同形態,也無法學習詞綴在不同詞匯之間的共通性,限制了模型的語言理解能力。比如look和looks在詞表中將會是兩個詞。一方面增加了訓練冗余,另一方面也造成了大詞匯量問題。
1.2 char(字符)粒度
以字符為單位進行分詞,就是把文本拆分成一個個單獨的字符作為最小的基本單元,這種對多種語言都比較適用,比如英文、中文、印尼語等。英文就是26個字母以及其他的一些符號,中文常見、次常用字大約共有 6000多個。該方案優劣勢如下:
- 優點:
- 通用性: 字符粒度的分詞方法使用與不同的語言,不需要設計不同的規則和工具。
- 避免OOV問題: 這種粒度的分詞能夠處理任何字符,無需維護詞表,因此可以很好地處理一些新的詞匯、專有名詞等。
- 缺點:
- 語義信息不明確: 這種粒度的分詞無法直接表達詞的語義,可能在一些語義分析任務中效果比較差。
- 效率極低: 由于文本被拆分成字符,處理粒度較小,增加了后續處理的計算成本和時間。
2. Subword(子詞)粒度
目前有三種主流的Subword算法,它們分別是:Byte Pair Encoding (BPE), WordPiece和Unigram Language Model。
針對上述問題,Subword(子詞)模型方法橫空出世。它的劃分粒度介于詞與字符之間,比如可以將”looking”劃分為”look”和”ing”兩個子詞,而劃分出來的"look",”ing”又能夠用來構造其它詞,如"look"和"ed"子詞可組成單詞"looked",因而Subword方法能夠大大降低詞典的大小,同時對相近詞能更好地處理。
接下來將分別詳細介紹:WordPiece、BPE、BBPE、ULM。
二、WordPiece
請見筆者之前文章:NLP Subword 之 WordPiece 算法原理
三、Byte Pair Encoding (BPE)
請見筆者之前文章:NLP Subword 之 BPE(Byte Pair Encoding) 算法原理
四、Byte-level BPE(BBPE)
請見筆者之前文章:NLP Subword 之 BBPE(Byte-level BPE) 算法原理
五、SentencePiece 以及各Subword算法代碼實現
1. 什么是 SentencePiece?
在自然語言處理(NLP)中,分詞(tokenization) 是一個極為關鍵的步驟。傳統的分詞方法往往依賴語言特定的規則(比如英文依靠空格,中文依靠分詞工具),而在多語言任務或需要統一處理的場景下,這種方式就顯得繁瑣且不夠通用。
SentencePiece 是 Google 開源的一個 語言無關(language-agnostic)子詞建模工具。它的“工具屬性”主要體現在:
- 端到端訓練:直接基于原始語料,無需預分詞或去掉空格,輸出模型文件(
.model
)和詞表(.vocab
)。 - 多算法統一接口:同一套命令/接口支持 Unigram(ULM)、BPE、Char、Word 等模型類型。
- 推理一致性:同一模型可用于編碼(encode)與解碼(decode),避免預處理不一致的問題。
- OOV 友好:支持
--byte_fallback
,可以在詞表未登錄時回退為字節級 token,實現 Byte-level BPE(BBPE) 的效果。
需要特別說明的是:
- SentencePiece 并沒有直接提供 WordPiece 類型;
- 在實踐中,通常使用 Unigram 模型 來 近似/替代 WordPiece。
2. 使用 Python SentencePiece 庫分別實現 ULM、BPE、BBPE
下面通過 3 個獨立的 Demo,完整展示 訓練 + 推理 全流程。每個 Demo 都會:
- 生成一份 demo 語料;
- 調用
sentencepiece
訓練模型; - 加載模型進行推理(encode/decode);
2.1 Unigram Language Model(ULM)
Unigram 是 SentencePiece 默認與推薦的算法之一,常作為 WordPiece 的替代實現。
# demo_ulm.py
import os
import sentencepiece as spm# 0) 準備語料
workdir = "./sp_ulm_work"
os.makedirs(workdir, exist_ok=True)
corpus = os.path.join(workdir, "corpus.txt")lines = ["我愛自然語言處理。","自然語言處理是人工智能的重要分支。","Hello world! This is a tiny corpus for subword training.","Emoji 測試:🙂🚀🌍,以及混合文本 NLP。",
]
with open(corpus, "w", encoding="utf-8") as f:f.write("\n".join(lines))# 1) 訓練 Unigram 模型
model_prefix = os.path.join(workdir, "uni")
spm.SentencePieceTrainer.Train(input=corpus,model_prefix=model_prefix,vocab_size=800, # demo 用小詞表model_type="unigram", # ★ 關鍵:Unigramcharacter_coverage=1.0
)# 2) 推理
sp = spm.SentencePieceProcessor(model_file=f"{model_prefix}.model")
test_text = "我愛NLP🚀!"
pieces = sp.encode(test_text, out_type=str)
ids = sp.encode(test_text, out_type=int)
back = sp.decode(ids)print("INPUT :", test_text)
print("PIECES:", pieces)
print("IDS :", ids)
print("DECODE:", back)
2.2 Byte Pair Encoding(BPE)
BPE 是另一種常見的子詞算法,原理是迭代合并語料中頻率最高的相鄰 token 對。
# demo_bpe.py
import os
import sentencepiece as spm# 0) 準備語料
workdir = "./sp_bpe_work"
os.makedirs(workdir, exist_ok=True)
corpus = os.path.join(workdir, "corpus.txt")lines = ["BPE merges rules are learned from frequent pairs.","Hello world! Byte Pair Encoding is simple and effective.","中文測試:分詞、子詞建模。",
]
with open(corpus, "w", encoding="utf-8") as f:f.write("\n".join(lines))# 1) 訓練 BPE 模型
model_prefix = os.path.join(workdir, "bpe")
spm.SentencePieceTrainer.Train(input=corpus,model_prefix=model_prefix,vocab_size=800,model_type="bpe", # ★ 關鍵:BPEcharacter_coverage=1.0
)# 2) 推理
sp = spm.SentencePieceProcessor(model_file=f"{model_prefix}.model")
test_text = "BPE 的分詞效果如何?😊"
pieces = sp.encode(test_text, out_type=str)
ids = sp.encode(test_text, out_type=int)
back = sp.decode(ids)print("INPUT :", test_text)
print("PIECES:", pieces)
print("IDS :", ids)
print("DECODE:", back)
2.3 Byte-level BPE(BBPE)
BBPE 的核心思想是 在 BPE 上增加字節級回退(byte fallback),保證任何輸入都能被編碼。
# demo_bbpe.py
import os
import sentencepiece as spm# 0) 準備語料
workdir = "./sp_bbpe_work"
os.makedirs(workdir, exist_ok=True)
corpus = os.path.join(workdir, "corpus.txt")lines = ["Byte-level BPE ensures any input is representable.","Emoji 測試:🙂🚀🌍。","混合語言測試:NLP 和 數據。",
]
with open(corpus, "w", encoding="utf-8") as f:f.write("\n".join(lines))# 1) 訓練 BBPE 模型(本質是 BPE + byte_fallback)
model_prefix = os.path.join(workdir, "bbpe")
spm.SentencePieceTrainer.Train(input=corpus,model_prefix=model_prefix,vocab_size=800,model_type="bpe", # 仍然是 BPEcharacter_coverage=1.0,byte_fallback=True # ★ 關鍵:開啟字節回退
)# 2) 推理
sp = spm.SentencePieceProcessor(model_file=f"{model_prefix}.model")
test_text = "未登錄字符𝔘𝔫𝔦𝔠𝔬𝔡𝔢😊"
pieces = sp.encode(test_text, out_type=str)
ids = sp.encode(test_text, out_type=int)
back = sp.decode(ids)print("INPUT :", test_text)
print("PIECES:", pieces)
print("IDS :", ids)
print("DECODE:", back)
3. 小結
- SentencePiece 是一個通用的子詞建模工具,它屏蔽了語言差異,讓我們直接在原始語料上訓練子詞模型。
- 它支持 Unigram(ULM)、BPE、Char、Word 等模型類型,但 沒有直接的 WordPiece;通常用 Unigram 來近似/替代 WordPiece。
- 通過
byte_fallback
參數,SentencePiece 可以在 BPE 模型上實現 BBPE 的能力,從而覆蓋任意輸入字符(如 emoji、稀有符號)。