准备语料:corpus = [
"low",
"lower",
"newest",
"widest"
]
tokens = [' '.join(word) for word in corpus]print("Init tokens:", tokens)xxxxxxxxxxnum_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=2Tokens 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=2Tokens after merge: ['low', 'low e r', 'n e w e s t', 'w i d e s t']
Step 3: merge ('e', 's') freq=2Tokens after merge: ['low', 'low e r', 'n e w es t', 'w i d es t']
Step 4: merge ('es', 't') freq=2Tokens after merge: ['low', 'low e r', 'n e w est', 'w i d est']
Step 5: merge ('low', 'e') freq=1Tokens after merge: ['low', 'lowe r', 'n e w est', 'w i d est']如果合并12次,或者更多,则会变回原来的几个单词:
xxxxxxxxxxStep 12: merge ('wid', 'est') freq=1Tokens after merge: ['low', 'lower', 'newest', 'widest']但是bpe的优点就是:
能用有限大小的词表(比如 30K、50K)覆盖绝大多数常见词。
对未知词也能用几个子词组合出来,不至于完全未知。
高频词仍然会被合并成一个整体 token(效率高)。
低频词会拆成更小的单元,能共享表示,提升泛化
bpe的目的就是在双边找平衡,既能获取到新词汇,又能保持高频词快速处理(保留整体),bpe一般适用于欧美拉丁语系中,大多为字符形式,前后缀词根较多,中文而言,直接分词即可。
BPE 就是不断用高频子串替代短子串,得到更紧凑的表示,同时保证能还原原文