3. 知识图谱构建流程:数据抽取、实体识别、关系抽取、知识融合

做靶点知识图谱,最核心的就是怎么把散落在论文、数据库、临床报告里的信息抓出来,再变成结构化的三元组。你可能会想,这事听起来不就是“爬虫+规则”嘛?其实远没那么简单。我做过几个靶点项目,踩过不少坑,今天就把这四步拆开讲透。

先说整体流程。说白了就是四步接力:数据抽取 → 实体识别 → 关系抽取 → 知识融合。每一步都会影响后面的质量,哪个环节偷懒,图就长歪了。

知识图谱构建流程概览 数据抽取 论文/数据库/临床 实体识别 基因/蛋白/疾病 关系抽取 抑制/激活/结合 知识融合 去重/映射/消歧 常识 数据源越多, 融合阶段工作量越大

3.1 数据抽取 —— 从哪来,怎么取

数据抽取是地基。我的习惯是先列数据源清单,按结构化程度分三类:

数据源类型示例抽取方式注意点
结构化数据库DrugBank、UniProtSQL/API直接导出权限与版本管理
半结构化文档PubMed XML、JSON-LDXPath/JSON解析字段嵌套复杂
非结构化文本论文全文、专利NLP管道(PDF解析+文本清洗)PDF表格提取是老大难

我曾在项目里遇到一个坑:从PubMed Central下载的全文XML,有的段落里嵌着<table-wrap>标签,里面的药物-靶点关系直接丢在表格里。如果你只用常规文本抽取,这些信息就漏了。所以我后来加了一个专门的表格提取模块,用pdfplumber+正则把行数据还原。

小技巧:对于靶点知识图谱,优先从专业数据库(如DrugBank、CTD)拿结构化数据,质量高、工程量小。非结构化文本主要用于补充最新文献中的发现。

3.2 实体识别 —— 把名字捡出来

实体识别是NER(Named Entity Recognition)的任务。对靶点领域来说,实体类型主要是:基因、蛋白质、疾病、药物、通路、表型。我推荐先用BioBERT等预训练模型微调,再配合词典回退。

下面是我常用的一个NER推理代码示例(基于HuggingFace):

from transformers import pipeline
ner_pipeline = pipeline(
    "token-classification",
    model="dmis-lab/biobert-base-cased-v1.1",
    aggregation_strategy="simple"
)
text = "BRAF V600E mutation activates MAPK signaling in melanoma."
result = ner_pipeline(text)
for ent in result:
    print(f"{ent['word']} -> {ent['entity_group']} (score={ent['score']:.3f})")
# 输出类似:
# BRAF -> Gene (score=0.997)
# MAPK -> Pathway (score=0.982)
# melanoma -> Disease (score=0.991
注意:NER模型在小规模标注数据上微调时,容易过拟合到训练集的命名变体。我曾经因为只用了HGNC标准基因名,结果文献里的“EGFR”和“ErbB1”被识别成两个不同实体,导致图谱里多出一堆冗余节点。解决方案是在后处理中挂载基因同义词词典,合并别名。

实体识别的输出格式一般做成JSON Lines,每一行一个文档,包含实体列表和位置。

3.3 关系抽取 —— 谁和谁有什么关系

有了实体,接下来要连接它们。关系抽取分两种路子:

  • 基于规则+模板:写正则或依存句法模式。比如“XXX inhibits YYY” -> 关系=抑制。优点是可控、可解释;缺点是泛化差,每加一个关系就要写一套模板。
  • 基于深度学习:用BERT-based关系分类模型,把实体对+上下文输入,输出关系类型。我用的是BioBERT-large在BioRel数据集上调优。

我个人经验是:先做规则打底,再用模型召回。原因很简单——靶点关系数量有限(抑制、激活、结合、磷酸化等),规则能覆盖80%的显式关系,模型用来补全那些绕弯子的表达。比如“通过激活PI3K/AKT通路,促进了肿瘤细胞增殖”——这里面“促进了”是个因果关系的表达,规则很难抓,但模型能学。

实际项目中的选择建议:

  1. 对于固定短语(“binds to”、“interacts with”):用正则足够
  2. 对于复杂长句:用BioBERT关系分类模型(注意:此处无链接,仅为示例)
  3. 做好冲突处理:同一对实体在不同句子中关系冲突时,记录置信度,让融合阶段裁决

3.4 知识融合 —— 把碎片拼成一张图

这一步是知识图谱的“合并同类项”。从多个数据源来的实体,可能指代同一个真实世界对象。比如“p53”、“TP53”、“肿瘤蛋白53”都是同一个基因。不做融合,图谱里就会有一堆孤立节点。

融合核心操作:

  • 实体对齐:使用字符串相似度(编辑距离、Jaro-Winkler) + 外部词典(UniProt ID→HGNC symbol)
  • 指代消解:处理“it”、“该蛋白”等代词,需要在关系抽取时保留上下文
  • 冲突消解:同一个关系在不同来源中给出矛盾信息(例如文献A说“抑制”,文献B说“激活”),我们通常保留高频或高权威源,并附加证据链接

我负责过的一个靶点图谱项目,从DrugBank、DisGeNET、STRING三个源整合,最初实体数量超过300万,经过融合后压缩到97万,减少了三分之二冗余。这里最关键的一步是标准化ID映射。下面是一个简单的ID映射表结构设计:

-- 示例:映射表 schema (SQLite)
CREATE TABLE entity_mapping (
    local_id TEXT PRIMARY KEY,
    source_db TEXT,
    source_id TEXT,
    unified_id TEXT,
    entity_type TEXT,
    synonym_list TEXT
);
-- 统一ID生成规则:entityType:source:sourceId
-- 例如 "Gene:UniProt:P04637" -> 统一ID "Gene:TP53"

有了映射表,后面的图谱构建就好做了——直接通过unified_id关联三元组。

我常用的融合验证方法:随机抽取100条对齐候选,人工检查。如果准确率低于95%,就退回调整阈值或补充词典。别信模型输出的概率,尤其当两个实体名称差异很小时。

3.5 回看整个流程:迭代大于一次成型

你可能会问,这四步做完一次就完了?我负责任地告诉你:不可能。知识图谱构建是一个迭代循环。我在第一个靶点图谱项目里,第一版做完实体识别,发现大量“基因-药物”关系没抽出来,原因是关系抽取模型在训练时缺少“regulation”这类宽泛关系的样本。后来我回去补充了1000条标注数据,重新训练,覆盖率才上来。

所以我的建议是:先做最小可用版本,跑通四步流程,再针对痛点局部优化。没必要一开始就追求95%以上的准确率,60%能用,就能帮你发现很多上游问题。