准备语料:corpus = [
"low",
"lower",
"newest",
"widest"
]
tokens = [' '.join(word) for word in corpus]
print("Init tokens:", tokens)
xxxxxxxxxx
num_merges = 5 # 做几次 merge
for i in range(num_merges):
# 统计所有相邻字对出现频率
pairs = Counter()
for token in tokens:
symbols = token.split()
for j in range(len(symbols)-1):
pair = (symbols[j], symbols[j+1])
pairs[pair] += 1
if not pairs:
break
# 找出频率最高的 pair
best = max(pairs, key=pairs.get)
print(f"\nStep {i+1}: merge {best} freq={pairs[best]}")
# 新 token 名字
new_token = ''.join(best)
# 更新 tokens:把 best pair 合并成新 token
new_tokens = []
for token in tokens:
parts = token.split()
j = 0
new_parts = []
while j < len(parts):
# 如果当前和下一个匹配 best pair
if j < len(parts)-1 and (parts[j], parts[j+1]) == best:
new_parts.append(new_token)
j += 2
else:
new_parts.append(parts[j])
j += 1
new_tokens.append(' '.join(new_parts))
tokens = new_tokens
print("Tokens after merge:", tokens)
最后得到:
Init tokens: ['l o w', 'l o w e r', 'n e w e s t', 'w i d e s t']
Step 1: merge ('l', 'o') freq=2
Tokens after merge: ['lo w', 'lo w e r', 'n e w e s t', 'w i d e s t']
Step 2: merge ('lo', 'w') freq=2
Tokens after merge: ['low', 'low e r', 'n e w e s t', 'w i d e s t']
Step 3: merge ('e', 's') freq=2
Tokens after merge: ['low', 'low e r', 'n e w es t', 'w i d es t']
Step 4: merge ('es', 't') freq=2
Tokens after merge: ['low', 'low e r', 'n e w est', 'w i d est']
Step 5: merge ('low', 'e') freq=1
Tokens after merge: ['low', 'lowe r', 'n e w est', 'w i d est']
如果合并12次,或者更多,则会变回原来的几个单词:
xxxxxxxxxx
Step 12: merge ('wid', 'est') freq=1
Tokens after merge: ['low', 'lower', 'newest', 'widest']
但是bpe的优点就是:
能用有限大小的词表(比如 30K、50K)覆盖绝大多数常见词。
对未知词也能用几个子词组合出来,不至于完全未知。
高频词仍然会被合并成一个整体 token(效率高)。
低频词会拆成更小的单元,能共享表示,提升泛化
bpe的目的就是在双边找平衡,既能获取到新词汇,又能保持高频词快速处理(保留整体),bpe一般适用于欧美拉丁语系中,大多为字符形式,前后缀词根较多,中文而言,直接分词即可。
BPE 就是不断用高频子串替代短子串,得到更紧凑的表示,同时保证能还原原文