1. 统计模型分词技术演进之路第一次接触中文分词时我被武汉市长江大桥这个经典案例难住了。到底该分成武汉/市/长江/大桥还是武汉市/长江/大桥传统词典分词在这里束手无策而统计模型却给出了令人信服的解决方案。统计模型分词的核心思想很简单字与字之间的组合概率会说话。当长江这两个字频繁共同出现时它们就很可能是同一个词。这种思路最早可以追溯到N-gram语言模型。记得2013年我做搜索引擎项目时N-gram还是主流选择。它的优势在于计算简单只需要统计相邻字的共现频率。比如在1亿字的语料库中长出现50万次江出现30万次但长江一起出现了28万次这种高共现率就很能说明问题。但随着业务复杂度提升我们发现N-gram有两个致命伤一是无法处理长距离依赖比如人工智能中的人工和智能间隔较远仍有关联二是容易受数据稀疏问题困扰。这时候HMM隐马尔可夫模型开始进入视野它通过引入隐藏状态B/M/E/S标签建立了更精细的建模框架。2. N-gram分词实战指南在实际工程中实现N-gram分词我推荐使用KenLM这个开源库。它的内存效率极高能轻松处理十亿级别的语料。下面是用Python训练的典型代码from kenlm import Model # 训练语料预处理 corpus open(corpus.txt).read() with open(processed.txt,w) as f: for sent in corpus.split(\n): f.write( .join(sent) \n) # 字间加空格 # 使用KenLM训练二元语法模型 !bin/lmplz -o 2 processed.txt bigram.arpa model Model(bigram.arpa) def segment(text): # 构建全切分有向无环图 dag {} for i in range(len(text)): dag[i] [] for j in range(i1, min(i5, len(text)1)): # 最大词长设为4 word text[i:j] dag[i].append((j, model.score( .join(word)))) # 动态规划找最优路径 route {len(text): (0.0, None)} for i in reversed(range(len(text))): route[i] max((score route[j][0], j) for j,score in dag[i]) # 回溯切分 result [] i 0 while i len(text): j route[i][1] result.append(text[i:j]) i j return result这个实现有几个工程优化点限制最大词长为4中文超过4字的词极少使用对数概率避免浮点数下溢动态规划时间复杂度O(n^2)在电商搜索场景实测时N-gram对热门商品名的识别准确率能达到92%但对新词如冰墩墩识别率骤降至65%。这时就需要引入回退机制当二元组概率低于阈值时退回到一元组频率判断。3. HMM分词的工程实践HMM需要解决三个关键问题状态转移概率B-M, M-E等观测概率某状态下出现特定字的概率初始状态分布以人民日报语料为例状态转移矩阵统计结果通常是B后接E的概率约30%双字词B后接M的概率约60%多字词M后接M的概率约20%M后接E的概率约80%用Python实现Viterbi算法时我习惯用numpy做向量化计算import numpy as np def viterbi(obs, states, start_p, trans_p, emit_p): V [{}] for st in states: V[0][st] {prob: start_p[st] * emit_p[st].get(obs[0],1e-10), prev: None} for t in range(1, len(obs)): V.append({}) for st in states: max_tr_prob max(V[t-1][prev_st][prob]*trans_p[prev_st].get(st,1e-10) for prev_st in states) for prev_st in states: if V[t-1][prev_st][prob] * trans_p[prev_st].get(st,1e-10) max_tr_prob: max_prob max_tr_prob * emit_p[st].get(obs[t],1e-10) V[t][st] {prob: max_prob, prev: prev_st} break # 回溯 opt [] max_prob max(value[prob] for value in V[-1].values()) previous None for st, data in V[-1].items(): if data[prob] max_prob: opt.append(st) previous st break for t in range(len(V)-2, -1, -1): opt.insert(0, V[t1][previous][prev]) previous V[t1][previous][prev] return opt实际应用中要注意数据平滑问题。我常用Good-Turing平滑处理未登录词将出现次数r的词概率估计为(r1)*N(r1)/N(r)其中N(r)是出现r次的词数。4. CRF分词的进阶技巧CRF相比HMM的最大优势是可以自由设计特征模板。在智能客服系统中我设计的特征模板包括当前字及其前后2个字当前字的偏旁部首是否数字/英文/标点在常用姓氏列表中在地名词典中使用CRF训练时的特征模板示例# Unigram U00:%x[-2,0] U01:%x[-1,0] U02:%x[0,0] U03:%x[1,0] U04:%x[2,0] # Bigram B在金融领域文本中我发现加入这些特征后F1值提升了7%是否在股票名称列表是否在金融术语词典是否包含货币符号如¥,$对于实时性要求高的场景可以用模型裁剪技术。通过移除权重绝对值小于阈值的特征能使模型大小减少40%而精度仅下降1-2%。5. 算法选型决策树根据多年实战经验我总结出这个选型流程图考虑维度N-gramHMMCRF训练数据量1亿字5000万字1000万字实时性要求5ms20ms50ms新词发现能力弱中强领域适应性需要重新训练部分可迁移特征可复用硬件资源内存1GB内存4GB需要GPU加速具体建议搜索引擎建议用N-gramHMM混合模型兼顾速度与精度智能客服首选CRF应对大量口语化表达社交媒体文本分析可用HMM平衡性能和资源消耗在硬件部署方面N-gram模型可以轻松部署到嵌入式设备。去年我们就把一个2-gram模型移植到了智能手表上内存占用仅18MB分词速度达到5000字/秒。6. 最新技术演进观察近年来预训练语言模型给传统统计方法带来了新思路。我在实验中发现用BERT提取的字向量作为CRF的输入特征能使OOV未登录词识别率提升35%。具体做法是from transformers import BertModel import torch bert BertModel.from_pretrained(bert-base-chinese) def get_bert_features(text): inputs tokenizer(text, return_tensorspt) with torch.no_grad(): outputs bert(**inputs) return outputs.last_hidden_state.squeeze(0) # 将BERT输出与传统特征拼接 crf_features np.concatenate([bert_features, handcraft_features], axis1)这种混合方法在医疗文本分词任务中达到了96.3%的F1值比纯CRF提高了4.2个百分点。不过需要注意这会增加10倍以上的计算开销。另一个趋势是统计方法与深度学习融合。比如LSTM-CRF模型既保留了CRF的序列建模能力又通过LSTM自动学习特征表示。在2023年的实验中这种架构在微博文本分词上取得了当前最佳效果。