第三章:基于规则的方法:手工规则构建、正则表达式在NER中的应用、词典匹配方法、优缺点分析
说实话,现在很多人一上来就搞深度学习做NER。但我得说一句——规则方法,永远不过时。
我在工业界做过好几个项目,初期数据标注还没到位的时候,全靠规则顶着。你想想看,业务方催着上线,标注团队还在吵架讨论实体边界,这时候谁先出活儿?
嗯,就是规则。
3.1 手工规则构建:老工程师的看家本领
手工规则,说白了就是人肉写if-else。但别小看它。我见过一个金融项目,用规则做公司名识别,准确率干到了92%。
怎么构建?我一般分三步走:
- 观察数据——先看100条样本,找出实体出现的模式
- 抽象规则——把模式转成逻辑条件
- 迭代验证——跑一批数据,看漏了什么,补上
举个例子,识别中文日期:
# 伪代码示例
if token in ["今天", "明天", "昨天"]:
return "DATE"
if token matches r"\d{4}年\d{1,2}月\d{1,2}日":
return "DATE"
if token matches r"周[一二三四五六日]":
return "DATE"
我在项目中遇到过最头疼的事——规则冲突。比如“北京”既是地名,又是公司名的一部分。我的解决办法是:优先级排序。长实体优先,专有名词优先。
3.2 正则表达式在NER中的应用:文本挖掘的瑞士军刀
正则表达式,每个NLP工程师都得会。我面试人的时候,正则写不利索的,基本直接pass。
为什么?因为正则太实用了。尤其是在NER里,它负责干三件事:
- 模式匹配——找电话号码、邮箱、身份证号
- 边界界定——确定实体从哪里开始、到哪里结束
- 上下文约束——比如“在...中”这种结构
来看几个我常用的正则模式:
# 手机号(中国大陆)
r"1[3-9]\d{9}"
# 邮箱
r"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}"
# 身份证号(18位)
r"\d{17}[\dXx]"
# 金额
r"\d+(\.\d{2})?元"
你可能会问:这些模式网上都有,为什么还要自己写?
因为业务数据脏。我在电商项目里遇到过“电话:138xxxx1234”这种脱敏数据,标准正则根本匹配不到。怎么办?加规则:
r"1[3-9]\d{2}x{4}\d{4}"
.*到.*,会把整句话都吞掉。一定要用非贪婪模式.*?。
3.3 词典匹配方法:最朴素但最稳定
词典匹配,就是准备一个实体列表,然后去文本里找。听起来简单吧?但细节都在工程里。
我常用的词典匹配策略有三种:
| 策略 | 原理 | 适用场景 |
|---|---|---|
| 正向最大匹配 | 从前往后,优先匹配最长词 | 中文分词后的实体识别 |
| 逆向最大匹配 | 从后往前,优先匹配最长词 | 处理后缀明显的实体 |
| 双向匹配 | 正反都做,取结果交集 | 高精度场景 |
举个例子,词典里有“北京大学”和“北京”。用正向最大匹配,遇到“北京大学”会优先匹配成整体。用逆向,可能先匹配到“大学”再匹配“北京”。
我个人习惯用双向匹配+消歧。先正向跑一遍,再逆向跑一遍,不一致的地方人工定规则。
# 简单实现示例
def max_match_forward(text, dictionary):
result = []
i = 0
while i < len(text):
longest = text[i]
for j in range(i+1, len(text)+1):
if text[i:j] in dictionary:
longest = text[i:j]
result.append(longest)
i += len(longest)
return result
3.4 优缺点分析:规则方法到底行不行?
做了这么多年NLP,我总结一下规则方法的优缺点。不吹不黑,客观说。
优点
- 可解释性强——匹配上了就是匹配上了,没有黑盒
- 冷启动快——有词典就能跑,不需要标注数据
- 精度高——规则写得好,准确率可以到95%以上
- 资源消耗低——一个正则引擎就够了,GPU?不需要
缺点
- 召回率低——规则覆盖不到的就漏了
- 维护成本高——规则多了以后,改一条可能影响十条
- 泛化能力差——换个领域,规则基本要重写
- 对歧义处理弱——比如“苹果”是水果还是公司?规则很难判断
我记得有个医疗项目,用规则做症状识别,一开始召回率只有40%。后来加了300多条规则,召回率提到70%,但再往上就提不动了。最后还是上了CRF模型。
说白了,规则和模型不是对立的。我现在的做法是:规则兜底,模型主力。规则负责处理那些确定性强的实体,模型负责处理模糊的、变化的。两者互补,效果最好。
嗯,这一章就到这里。下一章我们聊聊基于统计的方法——HMM和CRF,那才是真正的硬核内容。