实体抽取:教 AI 像人类一样"读懂"文本

季度报表整理的地狱周,老李端着保温杯走到小王工位,把一沓合同复印件拍在桌上。

“小王,帮个忙。过去五年的客户合同,把里面的人名、金额、日期都摘出来,整理成一张表。周六前给我。”老李的语气很轻松,像是在说“帮我把垃圾带下去”。

小王看了一眼那沓纸,又看了一眼老李:“老李,这是五百多份合同,每份十几页。你让我手工摘?”

“Ctrl+F 搜关键词嘛,不就那几个字段。我们以前整档案,三个人三天就搞完了。”

小王深吸一口气,把椅子转过来正对老李:“老李你想啊,合同里日期有十几种写法——‘2024年3月15日’、‘3/15/2024’、‘三月中旬’、‘本合同自签署之日起生效’。金额也是——‘伍拾万元整’、‘¥500,000’、‘五十万’。你让我用Ctrl+F搜什么关键词?”

老李的保温杯在嘴边停住了。

“但我有个办法,让AI帮你读。”小王打开终端,“这玩意儿叫NER,全称是命名实体识别。简单说,就是让机器像学生划重点一样,自动把文本里的关键信息标出来。”


NER:给文本划重点

“划重点?”老李放下保温杯,“机器怎么知道哪些是重点?”

“你想想你是怎么教实习生看合同的。你告诉他——‘看到人名圈出来,看到金额画横线,看到日期打三角’。NER模型做的是一样的事,只不过你教的是规则,它学的是海量标注数据里的模式。”

小王打开一个在线NER演示页面,把一段合同文本贴了进去。屏幕上瞬间出现了彩色标记——人名标蓝,金额标绿,日期标橙。

“你看这段——‘甲方张三与乙方李四于2024年3月15日签署本合同,合同金额为伍拾万元整’。模型自动把‘张三’、‘李四’标成人名,‘2024年3月15日’标成日期,‘伍拾万元整’标成金额。”

老李凑近屏幕,眼睛眯了起来:“张三李四这种常见名好认,但咱们合同里有各种奇怪的公司名——‘上海啥啥科技有限公司’——它也能认?”

“这就是NER模型的核心能力。它不是靠背字典,而是靠理解上下文。人名前后通常跟着‘甲方’、‘乙方’、‘签署’,公司名后面常跟‘有限公司’、‘集团’,日期有数字加‘年月日’的模式。模型从几百万条标注数据里学会了这些上下文规律。”

老李若有所思:“那它是怎么学会的?你给我讲讲。”


从规则到BERT:NER的三次进化

“NER技术经历了三个阶段。”小王在玻璃上画了一条时间线。

“第一代是规则匹配。你写一堆正则表达式——‘\d{4}年\d{1,2}月\d{1,2}日’匹配日期,‘\d+元’匹配金额。优点是快、精准,缺点是你得穷举所有写法。刚才说的‘三月中旬’、‘签署之日’,正则就抓瞎了。”

“第二代是统计模型,比如条件随机场。它不再靠死规则,而是看词性和上下文来预测。但需要人工设计特征,很吃经验。”

“第三代就是现在的深度学习方案。你把标注好的数据喂给BERT这样的预训练模型,它自动从上下文里学模式。不需要你写规则,只需要你给够标注样本。”

老李追问:“那用大语言模型直接抽呢?GPT不是更强吗?”

“可以用,但各有优劣。”小王打开笔记本跑了两段对比代码:

python
# 方法一:专用NER模型(SpaCy)
import spacy
nlp = spacy.load("zh_core_web_trf")  # 中文Transformer模型

text = "甲方张三与乙方上海科技公司于2024年3月15日签署合同,金额50万元。"
doc = nlp(text)

for ent in doc.ents:
    print(f"{ent.text} -> {ent.label_}")
# 张三 -> PERSON
# 上海科技公司 -> ORG
# 2024年3月15日 -> DATE
# 50万元 -> MONEY
python
# 方法二:用LLM做实体抽取
from openai import OpenAI
client = OpenAI()

response = client.chat.completions.create(
    model="gpt-4o",
    messages=[{
        "role": "user",
        "content": f"""从以下文本中提取所有人名、公司名、日期、金额。
以JSON格式输出,如{{"人名":[],"公司":[],"日期":[],"金额":[]}}

文本:{text}"""
    }]
)
print(response.choices[0].message.content)

“SpaCy 方案是本地运行,延迟低、免费,适合量大且模式固定的场景。LLM 方案更灵活,能处理各种复杂写法,但每条都要调 API,成本高、速度慢。”

老李盯着两段输出对比了一下:“那准确率呢?哪个更准?”


92% 的准确率够不够?

“这取决于你的容忍度。”小王调出一张测试结果表,“我们在五百份合同上做了对比。SpaCy 的专用模型,人名和日期的准确率大约 95%,但金额识别只有 88%——因为‘伍拾万元整’这种大写金额它容易漏。GPT-4 整体准确率 97%,几乎不会漏,但成本是 SpaCy 的一百倍,速度慢十倍。”

老李皱起眉头:“那也不是 100% 啊。万一漏了一个金额,合同出纠纷怎么办?”

“所以实际生产中的做法是——机器粗筛,人工复核。模型把 95% 的内容标好,剩下的交给人工校对。原来三个人干三天,现在一个人花半天校对模型的输出就行。”

小王补充道:“而且你可以设一个置信度阈值。模型对自己确定的结果打高分,不太确定的打低分。高分的自动过,低分的亮红灯让人工看。这样既不漏,又不累。”

python
# 实体抽取 + 置信度过滤
def extract_with_confidence(text):
    doc = nlp(text)
    entities = []
    for ent in doc.ents:
        # SpaCy 没有直接给置信度,但可以用概率近似
        # 实际项目中可结合多个模型投票或使用模型内置置信度
        entities.append({
            "text": ent.text,
            "label": ent.label_,
            "start": ent.start_char,
            "end": ent.end_char
        })
    return entities

# 低置信度的实体标记出来让人工复核
# 例如:用多个模型投票,如果只有部分模型识别出来,标记为低置信度

老李看完代码,沉默了一会儿:“那你说,如果我只让你抽人名和日期,准确率够高,是不是就不用人工复核了?”

“理论上可以。但你得先想清楚——漏掉一个人名的代价是什么?如果是内部报表整理,错了也就错了。如果是合同的甲方名称搞错了,对方公司名字少了一个字——那可能是法律风险。所以准确率多少才够,不取决于模型,取决于场景。”


不止是找到实体,还要搞懂关系

“找到实体只是第一步。”小王趁热打铁,“更关键的是第二步——关系抽取。”

“合同里写着‘张三支付李四伍拾万元’。NER告诉你这里有三个人——张三、李四、五十万。关系抽取会告诉你——‘张三’是付款方,‘李四’是收款方,‘五十万’是金额。三个孤立的点被连成了一条线。”

老李眼睛亮了:“这不就是我们上次说的知识图谱的原材料?”

“对!实体抽取 + 关系抽取 = 自动构建知识图谱。你把五百份合同跑一遍,自动生成几千条三元组——‘张三,支付,李四’、‘合同A,签署于,2024年3月15日’、‘上海科技公司,采购,API网关’。这些三元组直接就能灌进图数据库。”

图:从原始文本到知识图谱——实体抽取是第一步,关系抽取是第二步

老李盯着这张图看了好一会儿,然后拧开保温杯喝了一口:“那这五百份合同,你是不是已经跑完了?”

小王嘿嘿一笑,把屏幕转过来。一张整理好的表格赫然在目——人名、公司、日期、金额、合同编号,整整齐齐,前面几行还标着“待复核”。

“昨天就跑了。SpaCy 跑完花了一个小时,错误集中在大写金额和特殊日期格式上。你用今天下午校对一下,明天就能交。”

老李的表情在几秒内经历了震惊、欣慰、尴尬,最后定格在一个别扭的微笑上。

“嗯……还行。下不为例啊——以后这种事你提前跟我说一声,别让我在季度会上出丑。”

他站起来,拿起那份合同复印件,走到门口又停住:“那什么,你说的大写金额识别不准,是因为训练数据里没有吗?如果我把公司历史合同都标注一遍,能不能训一个更准的模型?你给我讲讲怎么做。”

小王比了个 OK 的手势,看着老李走出工位。保温杯里的枸杞晃晃悠悠,像在点头。那份合同复印件还留在桌上,纸角微微卷起,像一个时代的尾巴,正在被一行行代码轻轻拽进数字的世界里。

设计一个不会把你逼疯的知识分类体系
Graph RAG:当你的知识有了'关系网'