筆者看書時,看到書中提到的這個 WordPiece,只是短短的一句帶過。
于是查了些資料,做了深入了解。
BERT 使用的 WordPiece 詞元化方法是一種基于子詞分割的技術,其目的是有效地解決自然語言處理中的詞匯表問題,同時提高模型的泛化能力。本文將詳細闡述 WordPiece 的工作原理,并通過實例與代碼進行深度解析。
WordPiece 的背景與目標
在自然語言處理中,詞匯表的大小直接影響模型的性能與效率。傳統的詞級別詞元化方法存在兩個主要問題:
- 詞匯表過大:直接以單詞為單位構建詞匯表可能導致存儲和計算成本顯著增加。
- 詞匯覆蓋率不足:由于語言的多樣性和詞形變化,訓練過程中難以覆蓋所有可能的單詞,導致模型遇到未登錄詞時表現不佳。
WordPiece 通過將單詞進一步分解為子詞單元,從而在保持詞匯表規模適中的同時,顯著提高了詞匯覆蓋率。
WordPiece 的核心思想
WordPiece 基于子詞單元構建詞匯表。其基本假設是:頻繁共現的字符序列更有可能構成有意義的單元。通過頻率統計和合并規則,WordPiece 逐步生成最優的子詞分割方案。
WordPiece 的分割過程可以總結為以下幾個步驟:
- 初始化詞匯表:將所有字符(Unicode)作為初始詞匯表的基本單元。
- 統計字符對:對訓練語料中的每個單詞,統計相鄰字符對的出現頻率。
- 合并最頻繁字符對:將最頻繁的字符對作為一個新單元加入詞匯表,并更新訓練語料中的分割方式。
- 重復上述過程:直到詞匯表達到預設的大小或頻率不再增加顯著。
這種方法確保了高頻單詞能夠保留整體單元,而低頻單詞則被分解為子詞甚至單字符,從而提高了模型對未登錄詞的處理能力。
具體案例解析
為說明 WordPiece 的工作機制,以下是一個實際示例,展示如何從零構建一個簡單的詞匯表。
假設的語料
假設訓練語料包含以下單詞:
["low", "lower", "lowest", "new", "newer", "newest"]
目標是利用 WordPiece 構建一個子詞詞匯表。
初始化詞匯表
初始詞匯表包含所有可能的字符:
["l", "o", "w", "e", "r", "n", "s", "t"]
第一步:統計字符對
在語料中統計每個單詞的字符對(包括空格作為分隔符):
- "low": l-o, o-w
- "lower": l-o, o-w, w-e, e-r
- "lowest": l-o, o-w, w-e, e-s, s-t
- "new": n-e, e-w
- "newer": n-e, e-w, w-e, e-r
- "newest": n-e, e-w, w-e, e-s, s-t
字符對的頻率統計如下:
| 字符對 | 頻率 |
|---|---|
| l-o | 3 |
| o-w | 3 |
| w-e | 4 |
| e-r | 2 |
| e-s | 2 |
| s-t | 2 |
| n-e | 3 |
| e-w | 3 |
第二步:合并最頻繁字符對
找到頻率最高的字符對 w-e,將其作為新單元 we 加入詞匯表,同時更新語料:
更新后的單詞:
["low", "lower", "lowest", "new", "newer", "newest"]
分割形式變為:
- "low": l-o, o-w
- "lower": l-o, o-w, we-r
- "lowest": l-o, o-w, we-s, s-t
- "new": n-e, e-w
- "newer": n-e, e-w, we-r
- "newest": n-e, e-w, we-s, s-t
更新后的字符對頻率:
| 字符對 | 頻率 |
|---|---|
| l-o | 3 |
| o-w | 3 |
| we-r | 2 |
| e-r | 0 |
| we-s | 2 |
| s-t | 2 |
| n-e | 3 |
| e-w | 3 |
重復上述過程,逐步生成最終的詞匯表。
Python 實現代碼
以下代碼展示了 WordPiece 的簡單實現:
from collections import Counter, defaultdict
# 初始化語料
corpus = ["low", "lower", "lowest", "new", "newer", "newest"]
# 將語料分割為字符
def split_to_chars(word):
return list(word) + ["#"] # 添加特殊字符標記子詞結尾
# 構建初始語料
split_corpus = [split_to_chars(word) for word in corpus]
# 統計字符對頻率
def get_pair_statistics(corpus):
pairs = Counter()
for word in corpus:
for i in range(len(word) - 1):
pairs[(word[i], word[i + 1])] += 1
return pairs
# 合并字符對
def merge_pair(pair, corpus):
new_corpus = []
for word in corpus:
new_word = []
i = 0
while i < len(word):
if i < len(word) - 1 and (word[i], word[i + 1]) == pair:
new_word.append(word[i] + word[i + 1])
i += 2
else:
new_word.append(word[i])
i += 1
new_corpus.append(new_word)
return new_corpus
# 構建詞匯表
def build_vocab(corpus, vocab_size):
vocab = set()
for word in corpus:
vocab.update(word)
while len(vocab) < vocab_size:
pairs = get_pair_statistics(corpus)
if not pairs:
break
best_pair = max(pairs, key=pairs.get)
corpus = merge_pair(best_pair, corpus)
vocab.update(["".join(best_pair)])
return vocab
# 構建目標詞匯表
vocab = build_vocab(split_corpus, vocab_size=20)
print("Generated Vocabulary:", vocab)
運行上述代碼可以觀察到 WordPiece 詞匯表的逐步生成過程。
真實世界的應用
在 BERT 模型中,WordPiece 不僅優化了詞匯表,還極大提高了模型對多語言任務的適應能力。例如,在處理英文和中文混合的句子時,WordPiece 能夠自動將未登錄詞分解為熟悉的子詞,從而降低 OOV(Out-of-Vocabulary)問題的影響。
示例分析
輸入句子:
"I love natural language processing."
WordPiece 分割結果:
["I", "love", "natural", "lan", "##guage", "pro", "##cessing"]
這種分割方式保留了高頻詞 "I" 和 "love" 的整體性,同時將低頻詞 "language" 和 "processing" 分解為子詞單元,確保詞匯表大小適中。
總結
WordPiece 是 BERT 詞元化中的關鍵技術,其通過高效的子詞分割方法,在保證詞匯表規模適中的前提下,顯著提升了模型的泛化能力。通過分析其分割過程與實現細節,我們可以更深入地理解其工作原理和應用場景。這種方法不僅適用于 BERT,還在其他 NLP 模型中得到了廣泛應用。