2. 基于规则的分词方法:最大匹配法(前向、后向、双向)、最少分词法

聊到中文分词,很多人第一反应就是深度学习模型。但说实话,在工业界摸爬滚打这些年,我发现基于规则的方法依然有它的用武之地。尤其是当你手头没有标注数据,或者需要快速上线一个分词服务时,这些经典方法往往能救急。

今天我就带大家把这几块硬骨头啃下来。嗯,咱们从最直观的「最大匹配法」开始。

2.1 前向最大匹配法(FMM)

这个方法的思想特别简单。说白了就是:从左往右,尽可能匹配最长的词。

我习惯用一个例子来说明。假设词典里有这些词:我们研究自然语言自然语言。待切分的句子是:

我们在研究自然语言处理

设定最大词长为4。算法会这样走:

  1. 取前4个字「我们在研」,查词典——没有匹配。
  2. 去掉最后一个字,取「我们在」——还是没有。
  3. 再去掉,取「我们」——匹配成功!切出第一个词。
  4. 从「在」开始,取4个字「在研究自」——没有匹配。
  5. 逐步缩短,直到「在」——匹配成功。
  6. 继续,取「研究自然」——没有匹配。
  7. 缩短到「研究」——匹配成功。
  8. 取「自然语言」——匹配成功!
  9. 最后剩下「处理」,取4个字「处理」——匹配成功。

最终结果:我们 / 在 / 研究 / 自然语言 / 处理

你看,这个结果挺合理的。但我在项目中遇到过一个问题:如果词典里没有「自然语言」这个词,结果就会变成 自然 / 语言 / 处理。这其实也没错,但语义上就差了点意思。

核心要点:前向最大匹配法的关键在于「最大词长」的设定。设得太小,长词切不开;设得太大,匹配效率会下降。我个人习惯设为词典中最长词的长度,一般不超过10个字。

2.2 后向最大匹配法(BMM)

后向匹配和前向正好相反。它从右往左扫描,也是优先匹配最长的词。

还是刚才那个句子,用后向匹配试试:

  1. 取最后4个字「语言处理」,查词典——没有。
  2. 缩短到「言处理」——没有。
  3. 缩短到「处理」——匹配成功。
  4. 往前取4个字「自然语言」——匹配成功。
  5. 再往前取4个字「研究自然」——没有。
  6. 缩短到「研究」——匹配成功。
  7. 取「我们在」——没有。
  8. 缩短到「我们在」——没有。
  9. 缩短到「在」——匹配成功。
  10. 取「我们」——匹配成功。

结果:我们 / 在 / 研究 / 自然语言 / 处理

咦,这次前后向结果一样。但别高兴太早,我给你们看个经典的反例。

句子是「南京市长江大桥」。前向匹配会得到:南京市 / 长江大桥。后向匹配呢?它会得到:南京 / 市长 / 江大桥

为什么会这样?因为后向匹配从右往左,先匹配到了「江大桥」?不对,词典里没有这个词。实际上后向会先匹配「大桥」,然后「长江」,最后「南京」和「市长」?嗯,这里要仔细分析一下。

假设词典里有:南京南京市市长长江大桥长江大桥。最大词长设为4。

后向匹配过程:

  • 取最后4字「江大桥」——没有。
  • 取「大桥」——匹配成功。
  • 往前取4字「市长江大」——没有。
  • 取「长江大」——没有。
  • 取「长江」——匹配成功。
  • 往前取4字「南京市市」——没有。
  • 取「京市市」——没有。
  • 取「市市」——没有。
  • 取「市」——匹配成功?不对,词典里没有单字「市」。
  • 继续往前,取「南京市」——匹配成功。
  • 最后剩下「南京」——匹配成功。

结果:南京 / 南京市 / 长江 / 大桥?这显然不对。实际上后向匹配会得到 南京 / 市长 / 江大桥 这个经典错误。

避坑指南:我曾经在做一个新闻标题分词任务时,就栽在了「南京市长江大桥」这个句子上。前后向结果不一致,导致后续的关键词提取全乱了。后来我养成了一个习惯:凡是遇到地名+职务+地名的组合,一定要用双向匹配来交叉验证。

2.3 双向最大匹配法(Bi-MM)

既然前向和后向各有优缺点,那把它们结合起来不就行了?这就是双向最大匹配法的思路。

具体做法是:

  1. 分别用FMM和BMM对句子进行分词。
  2. 比较两个结果,选择更优的那个。

那怎么判断「更优」呢?我总结了三条规则:

规则 说明
词数最少 分词结果中,词的数量越少越好
单字词最少 单字词越少,说明切分越合理
词长方差小 各词长度差异越小,结果越稳定

拿「南京市长江大桥」来说:

  • 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。到时候你会发现,规则方法和统计方法其实可以很好地结合起来。嗯,咱们下回见。