2. 基于规则的分词方法:最大匹配法(前向、后向、双向)、最少分词法
聊到中文分词,很多人第一反应就是深度学习模型。但说实话,在工业界摸爬滚打这些年,我发现基于规则的方法依然有它的用武之地。尤其是当你手头没有标注数据,或者需要快速上线一个分词服务时,这些经典方法往往能救急。
今天我就带大家把这几块硬骨头啃下来。嗯,咱们从最直观的「最大匹配法」开始。
2.1 前向最大匹配法(FMM)
这个方法的思想特别简单。说白了就是:从左往右,尽可能匹配最长的词。
我习惯用一个例子来说明。假设词典里有这些词:我们、在、研究、自然、语言、自然语言。待切分的句子是:
我们在研究自然语言处理
设定最大词长为4。算法会这样走:
- 取前4个字「我们在研」,查词典——没有匹配。
- 去掉最后一个字,取「我们在」——还是没有。
- 再去掉,取「我们」——匹配成功!切出第一个词。
- 从「在」开始,取4个字「在研究自」——没有匹配。
- 逐步缩短,直到「在」——匹配成功。
- 继续,取「研究自然」——没有匹配。
- 缩短到「研究」——匹配成功。
- 取「自然语言」——匹配成功!
- 最后剩下「处理」,取4个字「处理」——匹配成功。
最终结果:我们 / 在 / 研究 / 自然语言 / 处理
你看,这个结果挺合理的。但我在项目中遇到过一个问题:如果词典里没有「自然语言」这个词,结果就会变成 自然 / 语言 / 处理。这其实也没错,但语义上就差了点意思。
核心要点:前向最大匹配法的关键在于「最大词长」的设定。设得太小,长词切不开;设得太大,匹配效率会下降。我个人习惯设为词典中最长词的长度,一般不超过10个字。
2.2 后向最大匹配法(BMM)
后向匹配和前向正好相反。它从右往左扫描,也是优先匹配最长的词。
还是刚才那个句子,用后向匹配试试:
- 取最后4个字「语言处理」,查词典——没有。
- 缩短到「言处理」——没有。
- 缩短到「处理」——匹配成功。
- 往前取4个字「自然语言」——匹配成功。
- 再往前取4个字「研究自然」——没有。
- 缩短到「研究」——匹配成功。
- 取「我们在」——没有。
- 缩短到「我们在」——没有。
- 缩短到「在」——匹配成功。
- 取「我们」——匹配成功。
结果:我们 / 在 / 研究 / 自然语言 / 处理
咦,这次前后向结果一样。但别高兴太早,我给你们看个经典的反例。
句子是「南京市长江大桥」。前向匹配会得到:南京市 / 长江大桥。后向匹配呢?它会得到:南京 / 市长 / 江大桥。
为什么会这样?因为后向匹配从右往左,先匹配到了「江大桥」?不对,词典里没有这个词。实际上后向会先匹配「大桥」,然后「长江」,最后「南京」和「市长」?嗯,这里要仔细分析一下。
假设词典里有:南京、南京市、市长、长江、大桥、长江大桥。最大词长设为4。
后向匹配过程:
- 取最后4字「江大桥」——没有。
- 取「大桥」——匹配成功。
- 往前取4字「市长江大」——没有。
- 取「长江大」——没有。
- 取「长江」——匹配成功。
- 往前取4字「南京市市」——没有。
- 取「京市市」——没有。
- 取「市市」——没有。
- 取「市」——匹配成功?不对,词典里没有单字「市」。
- 继续往前,取「南京市」——匹配成功。
- 最后剩下「南京」——匹配成功。
结果:南京 / 南京市 / 长江 / 大桥?这显然不对。实际上后向匹配会得到 南京 / 市长 / 江大桥 这个经典错误。
避坑指南:我曾经在做一个新闻标题分词任务时,就栽在了「南京市长江大桥」这个句子上。前后向结果不一致,导致后续的关键词提取全乱了。后来我养成了一个习惯:凡是遇到地名+职务+地名的组合,一定要用双向匹配来交叉验证。
2.3 双向最大匹配法(Bi-MM)
既然前向和后向各有优缺点,那把它们结合起来不就行了?这就是双向最大匹配法的思路。
具体做法是:
- 分别用FMM和BMM对句子进行分词。
- 比较两个结果,选择更优的那个。
那怎么判断「更优」呢?我总结了三条规则:
| 规则 | 说明 |
|---|---|
| 词数最少 | 分词结果中,词的数量越少越好 |
| 单字词最少 | 单字词越少,说明切分越合理 |
| 词长方差小 | 各词长度差异越小,结果越稳定 |
拿「南京市长江大桥」来说:
- FMM结果:
南京市 / 长江大桥(2个词,0个单字词) - BMM结果:
南京 / 市长 / 江大桥(3个词,0个单字词)
按词数最少原则,FMM胜出。所以双向匹配会选择前向的结果。
个人经验:双向匹配的正确率通常能达到95%以上。但要注意,它解决不了词典里没有的词。比如「新冠」这个词,如果词典里没有,无论怎么匹配都会切错。这时候就需要结合统计方法了。
2.4 最少分词法
最少分词法,也叫最短路径法。它的思路很优雅:把句子看作一个图,每个字是节点,可能的词是边,然后找一条从起点到终点的最短路径。
你想想看,这其实就是在所有可能的切分方案中,选词数最少的那一个。
举个例子,句子「研究生命科学」。可能的切分方案有:
- 研究 / 生命 / 科学(3个词)
- 研究生 / 命 / 科学(3个词)
- 研究 / 生命科学(2个词)
最少分词法会选择第3种方案,因为词数最少。
实现上,我一般用动态规划来做。核心代码大概是这样:
def min_segmentation(sentence, dictionary):
n = len(sentence)
# dp[i] 表示前i个字的最少词数
dp = [float('inf')] * (n + 1)
dp[0] = 0
for i in range(1, n + 1):
for j in range(i):
word = sentence[j:i]
if word in dictionary:
dp[i] = min(dp[i], dp[j] + 1)
# 回溯得到分词结果
# ... 省略回溯代码
return dp[n]
这段代码的时间复杂度是O(n²),对于短文本完全够用。但如果句子很长,比如几百个字,那就有点吃力了。我建议用字典树(Trie)来优化匹配过程,能把复杂度降到O(n * max_len)。
重要提醒:最少分词法追求的是「词数最少」,但词数最少不一定语义最合理。比如「乒乓球拍卖完了」,最少分词会得到「乒乓球 / 拍卖 / 完了」(3个词),但实际应该是「乒乓球拍 / 卖完了」(2个词)。你看,这里就出现了歧义。
2.5 四种方法的对比总结
说了这么多,我给大家整理一个对比表,方便你快速选择:
| 方法 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 前向最大匹配 | 实现简单,速度快 | 容易漏掉长词 | 词典覆盖率高时 |
| 后向最大匹配 | 对后缀词敏感 | 容易切出歧义 | 与FMM互补使用 |
| 双向最大匹配 | 准确率较高 | 速度慢一倍 | 需要高精度时 |
| 最少分词法 | 理论最优 | 复杂度高,有歧义 | 短文本、学术研究 |
最后说句掏心窝子的话:这些规则方法虽然古老,但它们是理解中文分词的基础。我建议每个NLP工程师都亲手实现一遍,哪怕只是用Python写个demo。你会在实现过程中发现很多细节问题,比如词典的存储结构、未登录词的处理、性能优化等等。这些经验,是直接调包学不到的。
下一章,我会讲基于统计的分词方法,包括HMM和CRF。到时候你会发现,规则方法和统计方法其实可以很好地结合起来。嗯,咱们下回见。