喜欢可以到我的主页订阅专栏哟(^U^)ノ~YO
自然语言处理(Natural Language Processing, NLP)作为人工智能领域的重要分支,其核心目标是实现计算机对人类语言的理解与生成。在深度学习技术快速发展的今天,NLP面临着三大基础性挑战:
这些挑战在分词技术领域体现得尤为明显。以中文为例,传统的基于词典的分词方法需要维护庞大的词库,而英语等西方语言虽然存在天然空格分隔,但面对未登录词(OOV)时同样束手无策。
传统分词技术主要分为三大类:
# 示例:简单的正向最大匹配算法
def forward_max_match(sentence, word_dict, max_len=5):
result = []
while sentence:
for i in range(min(max_len, len(sentence)), 0, -1):
if sentence[:i] in word_dict:
result.append(sentence[:i])
sentence = sentence[i:]
break
else:
result.append(sentence[0])
sentence = sentence[1:]
return result
这种方法需要预先构建完整的词典,无法处理新词和网络用语,维护成本高且泛化能力差。
隐马尔可夫模型(HMM)和条件随机场(CRF)等概率图模型通过统计相邻字符的共现概率进行分词。虽然在一定程度上缓解了未登录词问题,但仍然受限于局部特征提取能力。
结合规则与统计方法的混合系统虽然提升了准确率,但系统复杂度呈指数增长,难以适应现代大规模语料处理需求。
2015年提出的子词分解(Subword Tokenization)技术彻底改变了传统分词范式,其核心思想是将词汇分解为更小的语义单元。这种方法的优势体现在:
图1展示了传统分词与子词分解的对比:
(此处应插入对比示意图,由于当前环境限制,描述图片内容:左侧为传统分词将"unhappiness"分为完整单词,右侧BPE分解为"un", “happiness”)
BPE算法最初由Philip Gage于1994年提出用于文本压缩,其核心思想是通过迭代合并最高频的字节对来构建压缩字典。2016年,Sennrich等人将这一算法创新性地应用于神经机器翻译的分词任务,实现了以下改进:
表1展示了BPE在NLP领域的关键发展节点:
年份 | 里程碑事件 | 贡献者 |
---|---|---|
1994 | 原始BPE压缩算法 | Philip Gage |
2016 | 首次应用于神经机器翻译 | Sennrich et al. |
2018 | 改进版BPE用于BERT预训练 | Google Research |
2020 | 动态BPE适配多语言场景 | Facebook AI |
一个完整的子词分词系统包含以下核心组件:
各模块的功能说明:
本章系统阐述了自然语言处理中的分词技术演进,重点分析了传统方法的局限性及子词分解技术的突破性优势。通过对比分析,我们明确了BPE算法在现代NLP系统中的核心地位。后续章节将深入讲解BPE的算法原理、实现细节及优化策略。
Byte Pair Encoding(BPE)是一种基于数据压缩理论的分词算法,其核心思想是通过迭代合并最高频的字符对来构建子词词表。图2-1展示了BPE算法的完整处理流程:
[原始语料] → [预处理] → [词频统计] → [初始化字符表] → [迭代合并]
↓ ↗ ↖
[编码字典] ← [终止条件判断] ← [更新词表]
该架构包含三个核心阶段:
BPE算法的数学基础可以表述为:
给定文本语料 D D D,初始字符集合 V 0 V_0 V0,目标词表大小 K K K,算法执行以下操作:
While ∣ V i ∣ < K : ( a , b ) = arg max ( x , y ) ∑ w ∈ D count i ( x y ∣ w ) V i + 1 = V i ∪ { a b } ∖ { a , b } Update merge operations \begin{aligned} & \text{While } |V_i| < K: \\ & \quad (a, b) = \underset{(x,y)}{\arg\max} \sum_{w \in D} \text{count}_i(xy|w) \\ & \quad V_{i+1} = V_i \cup \{ab\} \setminus \{a, b\} \\ & \quad \text{Update merge operations} \end{aligned} While ∣Vi∣<K:(a,b)=(x,y)argmaxw∈D∑counti(xy∣w)Vi+1=Vi∪{ ab}∖{ a,b}Update merge operations
其中 count i ( x y ∣ w ) \text{count}_i(xy|w) counti(xy∣w)表示在第 i i i次迭代时字符对 ( x , y ) (x,y) (x,y)在单词 w w w中的出现次数。
合并操作是BPE算法的核心步骤,我们通过具体示例演示其工作原理:
示例语料:
corpus = [
"low", "lower", "newest", "widest"
]
初始字符统计:
l o w (count=1)
l o w e r (count=1)
n e w e s t (count=1)
w i d e s t (count=1)
第一次合并:
最高频字符对为e s
(出现2次)
合并后新词表包含es
单元
更新后的单词表示:
low, low e r, newest → n e w es t, widest → w i d es t
第二次合并:
最高频字符对变为es t
(出现2次)
合并生成新单元est
最终词表包含原始字符和合并后的es
, est
等子词
图2-2展示了该合并过程的可视化表示(此处应插入合并过程示意图,图示应包含初始字符、合并步骤箭头、最终子词)
def train_bpe(corpus, vocab_size):
# 初始化基础词表
vocab = Counter()
for word in corpus:
vocab[' '.join(list(word)) + ' '] += 1
# 计算初始字符对频率
pairs = get_pairs(vocab)
# 迭代合并过程
while len(vocab) < vocab_size:
if not pairs:
break
best_pair = max(pairs, key=lambda x: pairs[x])
vocab = merge_vocab(best_pair, vocab)
pairs = update_pairs(best_pair, pairs)
return vocab
def get_pairs(vocab):
pairs = defaultdict(int)
for word, freq in vocab.items():
symbols = word.split()
for i in range(len(symbols)-1):
pairs[symbols[i], symbols[i+1]] += freq
return pairs
def merge_vocab(pair, vocab):
new_vocab = defaultdict(int)
bigram = re.compile(r'(? + re.escape(' '.join(pair)) + r'(?!\S)')
for word in vocab:
new_word = bigram.sub(''.join(pair), word)
new_vocab[new_word] += vocab[word]
return new_vocab
class BPETokenizer:
def __init__(self):
self.special_tokens = {
'' : 0,
'' : 1,
'': 2