4. 知识图谱构建:实体识别与关系抽取
好,到了真刀真枪干活的环节了。
前几章我们搞定了数据收集和Schema设计,说白了就是知道了要“存什么”和“存成什么样”。现在要解决最核心的问题:怎么从一堆乱七八糟的文本里,把这些信息挖出来。
实体识别和关系抽取,就是知识图谱构建的两条腿。缺一条,你就跑不起来。我个人习惯把这两步拆开来讲,虽然现在有很多联合模型,但理解清楚每一步的边界和坑,对你后面调试模型会非常有帮助。
4.1 实体识别:找到“谁”在说话
实体识别(NER),在药物图谱里找的就是药品名、疾病名、症状、基因、靶点这些。
你想想看,一段临床记录里写着:“患者服用阿托伐他汀后,转氨酶显著升高”。我们需要知道,“阿托伐他汀”是个药,“转氨酶升高”是个不良反应。机器得先学会切出来。
常用的方法就两条路:基于规则 和 基于深度学习。
| 方法 | 优点 | 缺点 | 我常用的场景 |
|---|---|---|---|
| 基于规则(正则/Dict) | 快,可解释,无标注数据也能搞 | 维护成本高,遇到新词就跪 | 小规模验证、术语表召回 |
| 基于深度学习(BiLSTM-CRF / BERT) | 泛化能力强,能处理上下文歧义 | 需要大量标注数据,有GPU门槛 | 大规模、生产级系统 |
我个人比较推荐的做法:先用规则做一轮粗召回,把高频、模式固定的实体全捞出来。然后再用模型去做精细识别。我在项目中遇到过,纯用模型时,像“阿司匹林”这种变体少的词,模型反而容易漏,因为它在训练集里出现的次数不够多。规则兜底一下,效果立竿见影。
这里给出一段基于 spaCy 的简单管道配置示例,方便你快速上手原型验证:
import spacy
from spacy.pipeline import EntityRuler
nlp = spacy.load("zh_core_web_sm")
# 添加基于词典的规则组件
ruler = nlp.add_pipe("entity_ruler", before="ner")
# 药品名和疾病名示例
patterns = [
{"label": "DRUG", "pattern": "阿托伐他汀"},
{"label": "DRUG", "pattern": [{"LOWER": "阿司匹林"}]},
{"label": "DISEASE", "pattern": [{"LOWER": "高血压"}]}
]
ruler.add_patterns(patterns)
doc = nlp("患者服用阿托伐他汀与阿司匹林,高血压症状缓解。")
for ent in doc.ents:
print(ent.text, ent.label_)
4.2 关系抽取:把“点”连成“线”
实体找到了,它们之间是什么关系?
还是上面那句:“患者服用阿托伐他汀后,转氨酶显著升高”。我们已经知道了“阿托伐他汀”是个药,“转氨酶升高”是个不良反应。但你必须告诉知识图谱:阿托伐他汀 导致了 转氨酶升高。这个“导致了”,就是关系。
关系抽取分三大流派:
- 模板匹配:写规则去匹配“服用...后,...发生”这种模式。简单直接,但写多了你懂的,维护地狱。
- 远程监督:利用已有的知识库(比如DrugBank)去自动标注文本。比如DrugBank里记录“阿托伐他汀会导致肝损伤”,那你就去文本里找同时出现这两个实体句子,强行打上“导致”关系。
- 基于BERT的端到端模型:当前工业界的首选。效果好,但计算量大。
我曾经踩过一个大坑:远程监督的“噪音”问题。你以为知识库里的关系都是对的?其实很多是旧数据。我那时候用J Med Chem上的关系数据去打标,结果发现很多药物临床后被撤市了,关系就反了。所以,远程监督出来的数据,一定要做一轮人工清洗或降噪,不然模型学到的都是错的。
下面是一个基于BERT关系分类模型的输入输出格式:
# 输入格式
{
"text": "患者服用阿托伐他汀后,转氨酶显著升高。",
"head_entity": {
"text": "阿托伐他汀",
"type": "DRUG",
"start": 2,
"end": 7
},
"tail_entity": {
"text": "转氨酶升高",
"type": "ADVERSE_EFFECT",
"start": 9,
"end": 14
}
}
# 模型输出结果
{
"relation": "cause",
"confidence": 0.92
}
4.3 一体化联合抽取:端到端的新思路
流水线方式(先做NER,再做RE)有个问题:误差会传递。NER阶段把“转氨酶升高”漏了,RE阶段必然找不到关系。
所以现在很多团队在做联合抽取,用一个模型同时完成实体和关系的抽取。主流做法有两种:基于标注策略的(如CasRel)和基于生成式预训练模型的(如UIE)。
我推荐你关注 UIE (Universal Information Extraction)。它的设计思路很有意思,把信息抽取看成一个“阅读理解”任务。你给它一个提示(比如:“从文本中抽取(药物, 副作用)对”),它就输出结果。
# UIE 使用示例 (PaddleNLP)
from paddlenlp import Taskflow
ie = Taskflow("information_extraction",
schema=[{"药物": ["不良反应"]}])
text = "患者服用阿托伐他汀后,出现转氨酶显著升高。"
result = ie(text)
print(result)
# 输出:[{"药物": [{"text": "阿托伐他汀",
# "relations": {"不良反应": [{"text": "转氨酶显著升高"}]}
# }]
# }]
说白了,联合抽取就是把两步合并成一步,避免了误差传播。但代价是模型更重,对小规模数据不太友好。
- 数据量 < 5000 条,用规则 + 流水线模型(便宜、可控)。
- 数据量 5000 - 10万条,用CasRel这样的联合模型(精度高)。
- 数据量 > 10万条,考虑UIE或类似生成式方案(通用性强,场景切换成本低)。
4.4 知识图谱构建的逻辑全貌
下面这张SVG图,是我做这个章节时梳理的核心流程。你一眼看过去,就能理解为什么我们要把实体识别和关系抽取放在一个章节里讲。
这张图把整个流程讲得很清楚。从非结构化文本进来,经过实体识别和关系抽取,最终产出结构化三元组。你可以在流水线方式和联合抽取方式之间做选择,具体取决于你的数据规模和预算。
好,这一节的内容就到这儿。记住一条原则:宁可用一个简单但跑通的系统,也不要一个完美但停在PPT里的架构。先把第一步走起来,后面的事都好说。