第四章 基于规则的知识图谱实体识别:正则表达式、词典匹配与上下文规则
实体识别,说白了就是从一堆非结构化文本里,把有意义的“东西”给挑出来。 在知识图谱里,这一步要是做不好,后面的推理全是白搭。 我做过好几个异常检测项目,最后发现80%的问题都出在实体识别这个关口上。 这一章我们不讲那些高大上的深度学习,就聚焦在基于规则的方法上。 靠谱、可控、好解释,这才是工业界的硬道理。
4.1 正则表达式:最锋利的刻刀
正则表达式,也就是Regex,是做实体识别最直接的工具。 它的强项在于处理那些格式固定的实体。 比如手机号、身份证号、IP地址、订单号这些。 我曾经处理过一批日志数据,里面混着大量的IP地址和URL。 用正则一把梭,又快又准,比训练模型省事多了。
4.1.1 常见实体匹配模式
我列几个在实际项目里高频出现的模式,你直接拿来用就行:
- 手机号(中国大陆):
1[3-9]\d{9} - 固定IP地址:
((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?) - 邮箱地址:
[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,} - 订单号(字母+数字的组合):
[A-Z]{2}\d{8,12}
小提示:
写正则时,尽量用非贪婪匹配 .*?,而不是贪婪的 .*。
否则容易出现「吞掉」相邻数据的情况,我在这上面吃过亏。
4.1.2 异常检测中的正则应用
在异常实体识别里,正则不只是用来匹配“对”的东西,也可以用来匹配“错”的东西。 举个例子:如果一个字段号称是手机号,但正则匹配不上,那它要么是脏数据,要么就是伪造的实体。
import re
# 定义手机号正则
phone_pattern = re.compile(r'1[3-9]\d{9}')
# 待检测的实体列表
entities = [
"User_0321",
"13800138000",
"Order_2024_AB1234",
"12345678901", # 这玩意儿不是合法的手机号
"15011112222_ex"
]
for ent in entities:
if phone_pattern.fullmatch(ent):
print(f"正常实体: {ent}")
else:
print(f"异常/非标准实体: {ent}")
注意: 正则表达式虽然强大,但不要过度使用。 如果一个模式写出来你自己都看不懂,那它在维护阶段一定会出问题。 我个人习惯是:每个复杂正则都要写注释说明意图。
4.2 词典匹配:用大白话讲叫“看名单”
很多知识图谱里的实体,比如人名、地名、公司名,是没有固定格式的。 这时候正则就不好使了。 怎么办呢?用人肉攒出来的词典。 你想想看,如果我知道所有已知的病毒名称列表,那我在日志里看到“未知病毒_A_32”,就能判定它要么是新实体,要么是异常。
4.2.1 正向匹配
正向匹配很简单:准备一个标准实体词典,拿它去文本里找。 找到的就标注为正常实体。
standard_entities = {
"Apple Inc.", "Google", "Microsoft",
"华为", "腾讯", "阿里巴巴"
}
def match_standard(text):
found = []
for ent in standard_entities:
if ent in text:
found.append(ent)
return found
log_entry = "今天和华为开了会,之后和Google的团队做了一次技术交流。"
print(match_standard(log_entry))
# 输出 ['华为', 'Google']
4.2.2 反向匹配
这才是异常检测的核心。 如果词典里没有某个实体,但它在上下文中频繁出现,就要警觉了。
known_brands = {"可口可乐", "百事可乐", "娃哈哈", "康师傅"}
def detect_unknown_brand(text):
# 这里用简单的分词模拟,实际情况可用jieba
words = text.split()
suspicious = []
for w in words:
if w not in known_brands:
suspicious.append(w)
return suspicious
log = "系统检测到交易中出现 '快乐水' 和 '赛博可乐' 两个新品牌。"
print(detect_unknown_brand(log))
# 输出 ['系统检测到交易中出现', "'快乐水'", "和", "'赛博可乐'", "两个新品牌。"]
核心思路: 反向匹配的本质,是建立一个“白名单”,然后找出所有不在白名单里的东西。 我有个血的教训:白名单词典一定要定期更新。 有一次客户那边用了半年前的老词典,结果把正常的新产品都报成了异常,闹了个大笑话。
4.3 上下文规则:光看词不够,还得看“谁挨着谁”
正则和词典解决的是“这个词是什么”的问题。 但很多时候,一个词的意思取决于它跟着谁。 比如“苹果”,单独看可能是水果,也可能是公司。 上下文规则就是来解决这种歧义的。 这也是异常检测里最难、但也最有价值的一环。
4.3.1 前后缀模式
很多实体前面都会跟着特定的词。 比如“董事长”、“教授”后面一般是人名。 “有限公司”、“集团”前面一般是公司名。
def context_extract(text):
patterns = {
"人物": ["董事长", "教授", "博士", "先生", "女士"],
"机构": ["有限公司", "集团", "研究所", "大学"]
}
results = []
for label, keywords in patterns.items():
for kw in keywords:
if kw in text:
# 简单提取kw前面的词作为候选
idx = text.find(kw)
start = max(0, idx - 10)
prefix = text[start:idx].strip()
results.append((prefix, label))
return results
txt = "请李国栋教授审批采购方案,并抄送华创科技集团备案。"
print(context_extract(txt))
# 输出 [('李国栋', '人物'), ('华创科技', '机构')]
4.3.2 距离约束与异常触发
在异常检测场景中,我会设置一个规则: 如果某个候选实体不在标准词典里,但紧跟在上下文关键词后面,那它就极有可能是需要打标的新实体或异常实体。
known_persons = {"张三", "李四", "王五"}
def detect_unknown_person(text):
# 上下文关键词
triggers = ["教授", "博士", "先生", "小姐", "主任"]
# 简单按空格分词
words = text.split()
anomalies = []
for i, w in enumerate(words):
if w in triggers:
# 前一个词就是候选实体
if i > 0:
prev_word = words[i-1]
if prev_word not in known_persons:
anomalies.append(prev_word)
return anomalies
log = "本次会议邀请了周七教授、赵八博士以及陈九先生参会。"
print(detect_unknown_person(log))
# 输出 ['周七', '赵八', '陈九']
避坑指南: 我曾经把一个叫“王国维”的人因为不在词典里而误判为异常实体。 记住:不属于标准库 ≠ 异常。 你需要结合实体出现的频率、来源可信度和图谱联通度来综合判断。 单条上下文规则只能产生候选,不能直接下结论。
4.4 三种方法的融合策略
说实话,很少有项目只用一种方法就能搞定。 我目前最常用的组合是这样的:
- 先用正则过滤掉明显的格式异常实体(比如乱码、长度不对)
- 再用词典做正向和反向匹配,打上“已知”或“未知”标签
- 最后用上下文规则对“未知”实体做二次判定
下面这张图,就是我在项目中用到的识别规则流水线:
4.5 规则冲突与优先级
方法一多,就容易打架。 举个例子:一条文本里,正则说它是“手机号”,但词典里又没这个号码。 怎么办? 我一般在项目里定一套优先级规则:
| 优先级 | 规则类型 | 判定结果 | 典型场景 |
|---|---|---|---|
| 1(最高) | 正则异常模式 | 直接标记为异常 | IP格式不对、长度不符 |
| 2 | 反向词典匹配 | 标记为候选异常 | 公司名不在白名单 |
| 3 | 上下文规则 | 确认或排除异常 | 前缀“副”级别人名 |
| 4(最低) | 正向正则匹配 | 直接判定为正常 | 标准手机号、邮箱 |
我踩过的一个大坑: 刚开始时我把“正向正则匹配”的优先级设得过高。 结果一条包含“13800138000”的文本,明明语境是吐槽这个号码是诈骗电话,我这边依然把它当正常实体入库。 后来改了优先级,所有实体先经过异常规则筛一遍,再走正常流程,效果好了很多。 嗯,这里一定要注意:上下文优先于格式。
4.6 小结
基于规则的实体识别不是什么黑科技。 但它是整个知识图谱异常检测的基石。 你想想看,如果连实体都认不对,后面关联他的业务关系和异常行为,全是错的。 这一章我给你的三个武器:正则、词典、上下文规则,能覆盖日常项目里70%以上的场景。 剩下的30%,需要图谱推理和机器学习来补,那是后面章节的内容了。 这一章你先吃透这三招,足够解决大多数问题。