第四章 基于规则的知识图谱实体识别:正则表达式、词典匹配与上下文规则

实体识别,说白了就是从一堆非结构化文本里,把有意义的“东西”给挑出来。 在知识图谱里,这一步要是做不好,后面的推理全是白搭。 我做过好几个异常检测项目,最后发现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 三种方法的融合策略

说实话,很少有项目只用一种方法就能搞定。 我目前最常用的组合是这样的:

  1. 先用正则过滤掉明显的格式异常实体(比如乱码、长度不对)
  2. 再用词典做正向和反向匹配,打上“已知”或“未知”标签
  3. 最后用上下文规则对“未知”实体做二次判定

下面这张图,就是我在项目中用到的识别规则流水线:

基于规则的知识图谱实体识别流程 原始文本 / 日志输入 第1步:正则表达式过滤 第2步:词典匹配(白名单/黑名单) 已知实体 → 直接入库 未知实体 → 进入上下文判定 第3步:上下文规则二次判定 说明: 上下文中出现“先生、公司、型号” 等关键词时,未知实体优先判定为异常。

4.5 规则冲突与优先级

方法一多,就容易打架。 举个例子:一条文本里,正则说它是“手机号”,但词典里又没这个号码。 怎么办? 我一般在项目里定一套优先级规则:

优先级 规则类型 判定结果 典型场景
1(最高) 正则异常模式 直接标记为异常 IP格式不对、长度不符
2 反向词典匹配 标记为候选异常 公司名不在白名单
3 上下文规则 确认或排除异常 前缀“副”级别人名
4(最低) 正向正则匹配 直接判定为正常 标准手机号、邮箱

我踩过的一个大坑: 刚开始时我把“正向正则匹配”的优先级设得过高。 结果一条包含“13800138000”的文本,明明语境是吐槽这个号码是诈骗电话,我这边依然把它当正常实体入库。 后来改了优先级,所有实体先经过异常规则筛一遍,再走正常流程,效果好了很多。 嗯,这里一定要注意:上下文优先于格式。

4.6 小结

基于规则的实体识别不是什么黑科技。 但它是整个知识图谱异常检测的基石。 你想想看,如果连实体都认不对,后面关联他的业务关系和异常行为,全是错的。 这一章我给你的三个武器:正则、词典、上下文规则,能覆盖日常项目里70%以上的场景。 剩下的30%,需要图谱推理和机器学习来补,那是后面章节的内容了。 这一章你先吃透这三招,足够解决大多数问题。