3. 统计分词模型:HMM与CRF的实战对决
聊完了规则和词典,咱们进入真正的「统计时代」。
说实话,我刚入行那会儿,觉得分词嘛,有词典就够了。直到我遇到一个项目——用户输入的是「南京市长江大桥」,词典怎么切都别扭。后来我才明白,统计模型才是解决歧义的终极武器。
3.1 隐马尔可夫模型(HMM)分词
HMM 是什么?说白了,它假设每个字背后藏着一个「状态」。这个状态就是:词的开头、中间、结尾,还是单独成词。
我习惯用 B(Begin)、M(Middle)、E(End)、S(Single)来表示。比如「我爱北京天安门」:
我/S 爱/S 北/B 京/E 天/B 安/M 门/E
你看,每个字对应一个状态。HMM 要做的,就是根据观测到的字序列,推断出最可能的状态序列。
3.1.1 HMM 的三要素
HMM 有三个核心参数,我当年背得滚瓜烂熟:
| 参数 | 含义 | 我的理解 |
|---|---|---|
| 初始概率 π | 第一个字的状态分布 | 句子开头,大概率是 B 或 S |
| 转移概率 A | 状态之间的转移 | B→M→E 是常见路径,B→S 几乎不可能 |
| 发射概率 B | 状态生成某个字的概率 | 状态 B 下,「北」的概率高,「的」的概率低 |
嗯,这里要注意:转移概率矩阵是稀疏的。比如 M 后面只能跟 M 或 E,不能跟 S。这个约束大大简化了计算。
3.1.2 Viterbi 解码
有了模型参数,怎么找到最优状态序列?用 Viterbi 算法。
我给大家写个简化版代码,当年我在项目里就是这么干的:
def viterbi(obs, states, start_p, trans_p, emit_p):
V = [{}]
path = {}
# 初始化第一步
for s in states:
V[0][s] = start_p[s] * emit_p[s][obs[0]]
path[s] = [s]
# 递推
for t in range(1, len(obs)):
V.append({})
new_path = {}
for s in states:
# 找概率最大的前一个状态
(prob, state) = max(
(V[t-1][s0] * trans_p[s0][s] * emit_p[s][obs[t]], s0)
for s0 in states
)
V[t][s] = prob
new_path[s] = path[state] + [s]
path = new_path
# 回溯
(prob, state) = max((V[-1][s], s) for s in states)
return prob, path[state]
3.1.3 HMM 的局限性
说实话,HMM 有两个硬伤:
- 强独立性假设:当前观测只依赖当前状态,忽略了上下文。你想想看,「我在银行存钱」和「我在银行钓鱼」,同一个「银行」字,HMM 处理起来一样,但实际意思完全不同。
- 无法利用特征:HMM 只能看字本身,不能看字的拼音、词性、前后搭配等特征。
所以,后来业界慢慢转向了 CRF。
3.2 条件随机场(CRF)分词
CRF 是什么?我个人的理解是:HMM 的升级版,去掉了独立性假设,可以自由定义特征。
说白了,CRF 不再假设「当前字只跟当前状态有关」,而是允许你定义任意特征函数。比如:
- 当前字是「北」,前一个字是「河」—— 这大概率是词尾
- 当前字是数字,后一个字是「年」—— 这可能是时间词
- 当前字是大写字母 —— 可能是英文单词开头
3.2.1 特征模板
我在项目中常用的特征模板长这样:
# 一元特征
U00:%x[-1,0] # 前一个字
U01:%x[0,0] # 当前字
U02:%x[1,0] # 后一个字
# 二元特征
U03:%x[-1,0]/%x[0,0] # 前一个字+当前字
U04:%x[0,0]/%x[1,0] # 当前字+后一个字
# 组合特征
U05:%x[-1,0]/%x[0,0]/%x[1,0] # 三字组合
你想想看,HMM 只能看到当前字,而 CRF 能看到前后三个字。这差距有多大?
3.2.2 CRF 的训练与解码
CRF 的训练比 HMM 复杂得多。它需要:
- 定义特征模板
- 从标注数据中提取特征
- 用 L-BFGS 等优化算法学习特征权重
- 用 Viterbi 解码(没错,解码还是 Viterbi)
我记得第一次用 CRF++ 工具包时,被它的特征模板语法搞晕了。后来我总结了一个经验:特征不是越多越好,关键是特征要有区分度。
核心区别总结:
- HMM 是生成式模型,CRF 是判别式模型
- HMM 假设观测独立,CRF 没有这个假设
- HMM 只能利用字本身,CRF 可以自定义任意特征
- HMM 训练简单,CRF 训练复杂但效果更好
3.2.3 实战对比
我拿「结婚的和尚未结婚的」这个经典歧义句来对比:
| 模型 | 分词结果 | 说明 |
|---|---|---|
| HMM | 结婚/的/和/尚未/结婚/的 | 容易把「和尚」切出来 |
| CRF | 结婚/的/和/尚未/结婚/的 | 如果特征里包含「尚未」这个搭配,就能正确切分 |
为什么会这样?因为 HMM 只看局部,而 CRF 可以通过特征捕捉到「尚未」这个模式。
3.3 我的选择建议
说了这么多,到底用哪个?我个人的经验是:
- 数据量小(<1万句):用 HMM,简单快速,不容易过拟合
- 数据量中等(1万-10万句):用 CRF,效果好,但要注意特征设计
- 数据量大(>10万句):可以考虑深度学习模型,但 CRF 仍然是强基线
嗯,最后说一句:别迷信模型。我在实际项目中见过太多人,一上来就上 CRF,结果连词典都没加。先跑通基线,再优化模型,这才是正道。
下一章,咱们聊聊深度学习分词。那又是另一个世界了。