4、基于规则的方法:手工规则模板设计、正则表达式在关系抽取中的应用

说到关系抽取,很多人第一反应就是上深度学习模型。但我得说一句大实话——在工业级项目中,基于规则的方法从来就没退出过舞台。我做了这么多年知识图谱,见过太多上来就搞BERT、搞图神经网络的团队,结果连最基础的关系都抽不准。嗯,这里我想聊聊规则方法,它其实是个被低估的利器。

4.1 为什么规则方法还没死?

你想想看,深度学习模型再厉害,它也是个黑盒。你给它喂100万条标注数据,它可能学到一些你根本想不到的关联。但问题是——你真的有100万条标注数据吗?

我在一个金融风控项目里遇到过这种情况。客户要求抽取「担保关系」和「关联交易关系」,数据量只有2000条。你让模型去学?学出来就是个笑话。我当时的做法很简单:先写50条规则模板,把能覆盖的关系全部抽出来。结果呢?准确率92%,召回率68%。虽然召回率不高,但至少能用了。

规则方法的优势很明显:

  • 可解释性强——抽错了你知道为什么错,改规则就行
  • 零样本可用——不需要标注数据,领域专家直接写规则
  • 维护成本低——加一条规则,比重新训练一个模型快得多

核心观点:规则方法不是深度学习的替代品,而是它的「守门员」。先用规则兜底,再用模型提升召回,这是工业界最成熟的打法。

4.2 手工规则模板设计——从语言学出发

手工规则模板,说白了就是「把人类理解关系的方式,翻译成计算机能执行的逻辑」。我习惯从三个维度来设计模板:

4.2.1 基于触发词的模板

这是最基础的方法。每种关系都有一些典型的触发词。比如「雇佣关系」的触发词有:任职、担任、就职、供职于……

举个例子,句子是「张三担任CEO一职」。我只需要写一条规则:

如果句子中包含 [人物] + 担任 + [职位]
则抽取关系: (人物) - [雇佣] -> (职位)

嗯,听起来很简单对吧?但实际项目中坑很多。我曾经遇到一个句子:「张三担任CEO期间,公司业绩翻了三倍」。你看,这里的「担任」确实触发了规则,但「CEO期间」这个短语会把「CEO」和「期间」连在一起,导致实体边界识别错误。

我的经验:写触发词规则时,一定要考虑「边界词」。我习惯在触发词前后各加一个「否定词表」和「修饰词表」,比如「前CEO」「代理CEO」「原CEO」这些都需要单独处理。

4.2.2 基于句法模式的模板

光靠触发词不够,因为同一个触发词可能对应多种关系。比如「和」这个字:

  • 「张三和李四是夫妻」—— 婚姻关系
  • 「张三和腾讯合作」—— 合作关系
  • 「张三和公司解约」—— 解约关系

这时候就需要句法模式了。我一般会先做依存句法分析,然后提取「核心动词+主谓宾结构」。比如:

模式: [实体A] + 和 + [实体B] + [关系动词]
规则: 如果关系动词 ∈ {结婚, 离婚, 订婚} → 婚姻关系
      如果关系动词 ∈ {合作, 签约, 联合} → 合作关系

我记得有一次处理法律文书,里面全是「甲方乙方」这种指代。句法模式直接失效了,因为「甲方」和「乙方」在句子里根本找不到明确的触发词。后来我加了一层「指代消解」预处理,才把规则跑通。

4.2.3 基于位置约束的模板

有些关系,实体之间的位置关系本身就提供了很强的信号。比如:

  • 「位于」关系:实体A + 位于 + 实体B(北京位于中国北部)
  • 「出生于」关系:实体A + 出生于 + 实体B(爱因斯坦出生于德国)
  • 「毕业于」关系:实体A + 毕业于 + 实体B(李彦宏毕业于北京大学)

这种模板写起来最轻松,但也是最容易出错的。为什么?因为中文的语序太灵活了。「爱因斯坦出生于德国」和「德国是爱因斯坦的出生地」,这两句话表达的是同一个关系,但位置约束完全不同。

避坑指南:我曾经在写「出生于」规则时,只考虑了「实体+动词+地点」的模式。结果遇到一句「出生于德国的爱因斯坦」,直接漏掉了。后来我加了一条「动词+地点+的+实体」的变体规则,才把召回率提上来。

4.3 正则表达式在关系抽取中的应用

正则表达式,说白了就是「字符串的模式匹配」。在关系抽取里,它是最快、最轻量的实现方式。我一般用它来处理三类场景:

4.3.1 固定格式的关系抽取

有些文本格式非常固定,比如:

  • 「张三(CEO)于2019年加入腾讯」
  • 「腾讯-张三-CEO-2019」
  • 「【张三】职务:CEO」

这种直接用正则表达式提取,效率极高。我写过一条经典的正则:

# 提取 "实体A(职位)于时间加入实体B" 格式
pattern = r'([\u4e00-\u9fa5]{2,4})(([\u4e00-\u9fa5]{2,10}))于(\d{4}年)加入([\u4e00-\u9fa5]{2,10})'

# 匹配结果
# 组1: 张三
# 组2: CEO
# 组3: 2019年
# 组4: 腾讯

嗯,这里要注意中文的正则匹配。我刚开始用Python的re模块时,没加re.UNICODE标志,结果中文全匹配不上。折腾了半小时才发现这个坑。

4.3.2 半结构化文本的关系抽取

很多网页、PDF文档里,关系信息是以「列表」或「表格」形式存在的。比如:

姓名:张三
职务:CEO
公司:腾讯
入职时间:2019年

这种我一般会写一个「键值对提取器」:

pattern = r'^([\u4e00-\u9fa5]+):([\u4e00-\u9fa5a-zA-Z0-9]+)$'

# 匹配后,根据键名映射到关系类型
relation_map = {
    '职务': '雇佣关系',
    '公司': '所属关系',
    '入职时间': '时间关系'
}

你想想看,这种场景如果用深度学习模型去处理,你得先做版面分析、OCR校正、实体识别……一套流程下来,半天过去了。而正则表达式,三行代码搞定。

4.3.3 复杂场景的「正则+规则」组合

真正复杂的场景,正则表达式一个人搞不定。我一般会把它和规则模板组合使用。比如:

  1. 先用正则提取出「候选关系片段」(比如两个实体之间的所有文本)
  2. 再用规则模板判断这个片段属于哪种关系
  3. 最后用正则做后处理,清洗掉噪声

举个例子,处理「张三,腾讯CEO,2019年入职」这种逗号分隔的文本:

# 第一步:用正则提取候选片段
candidate = re.search(r'([\u4e00-\u9fa5]+),([\u4e00-\u9fa5a-zA-Z]+),(.+)', text)

# 第二步:用规则判断关系类型
if 'CEO' in candidate.group(2) or '总裁' in candidate.group(2):
    relation_type = '雇佣关系'
elif '毕业于' in candidate.group(3):
    relation_type = '教育关系'

# 第三步:用正则清洗实体
entity_a = re.sub(r'[^\u4e00-\u9fa5]', '', candidate.group(1))

我的习惯:正则表达式写完后,一定要准备一个「测试用例集」。我每次至少写20条正例和20条负例,确保规则不会误伤。曾经有一次,我写的正则把「张三的CEO朋友」也匹配成了雇佣关系,后来加了「的」字否定规则才解决。

4.4 规则方法的局限性

说了这么多规则的好处,我也得泼点冷水。规则方法不是万能的,它有明显的天花板:

问题 表现 我的应对
规则冲突 同一条文本匹配了多条规则 设置规则优先级,或使用投票机制
覆盖不全 新出现的表达方式无法识别 定期更新规则库,或结合模型做兜底
维护成本 规则数量超过1000条后,管理困难 按领域分模块管理,每个模块不超过200条
跨领域迁移 金融领域的规则在医疗领域完全失效 每个领域独立维护规则库,不做通用化

我记得有一次,一个客户要求抽取「投资关系」。我写了300条规则,覆盖了「投资」「入股」「收购」「增资」等各种表达。结果上线第一天,一条「张三向腾讯注资5000万」就漏掉了——因为「注资」这个词我没写进规则。嗯,这就是规则方法的宿命:你永远不知道下一个新词什么时候出现。

4.5 本章小结

基于规则的关系抽取,说白了就是「用人类的智慧,弥补机器的愚蠢」。它不完美,但在数据稀缺、要求高精度的场景下,它是最可靠的选择。我个人建议,每个做知识图谱的团队,都应该维护一套「规则库」,哪怕只有100条规则,也能在关键时刻救命。

最后说一句:别把规则和模型对立起来。我现在的项目里,规则和模型是并行的——规则负责高精度召回,模型负责高召回覆盖。两者互补,才是工业级的解决方案。


公众号:蓝海资料掘金营,微信deep3321