知识图谱构建基础流程:数据采集、预处理、知识抽取、融合、存储、查询

做知识图谱,说白了就是搭积木。你手头有各种零散的砖块(数据),得先挑一挑、洗一洗、切一切,再按照图纸拼起来,最后才能盖成一座能住人的房子(知识图谱)。我记得刚入行那会儿,总觉得一步到位就行,结果拼出来的“房子”歪歪扭扭,根本没法用。

今天咱们就把这六个步骤掰开揉碎了讲。每一步都有坑,我踩过的,你就别踩了。

数据采集:从哪儿找“原料”

这一步决定了你后续工作的上限。原料不行,后面怎么折腾都白搭。

药物知识图谱的数据源,通常分三类:

  • 结构化数据:关系数据库、CSV表格、Excel。比如我合作的一家药企,他们的药物注册信息直接导成MySQL dump。
  • 半结构化数据:JSON、XML、HTML网页。像DrugBank、PubChem这些公开数据库,API返回的是JSON,爬虫抓下来也是带标签的HTML。
  • 非结构化数据:论文全文、临床试验报告、医生笔记。这活儿最脏,我曾经从一个PDF里提取药物相互作用关系,光OCR就折腾了两周。

我建议你一开始就做好数据源的清单记录。下面是我常用的表格模板:

数据源名称类型更新频率数据量预估值获取方式
DrugBank半结构化(JSON)季度更新~15,000 药物条目API+爬虫
医院处方系统结构化(MySQL)实时每月约500万条数据库直连
PubMed论文摘要非结构化(文本)每日3000万+摘要Bulk下载+NLP解析

我这里给出一个简单的Python脚本,用来批量下载JSON数据:

import requests
import json

url_template = "https://api.drugbank.com/v1/drugs/{}"
api_key = "your_api_key_here"
drug_ids = ["DB00001", "DB00002", "DB00003"]

for drug_id in drug_ids:
    response = requests.get(url_template.format(drug_id),
                            headers={"Authorization": f"Bearer {api_key}"})
    if response.status_code == 200:
        data = response.json()
        with open(f"{drug_id}.json", "w") as f:
            json.dump(data, f, indent=2)
        print(f"{drug_id} 下载完成")
    else:
        print(f"{drug_id} 下载失败: {response.status_code}")

数据预处理:把“脏活儿”干干净

数据拿回来,别急着用。你想想看,同一个药物名称,有的写“阿司匹林”,有的写“Acetylsalicylic Acid”,有的干脆写“Aspirin”——预处理就是把这些乱七八糟的东西统一管理。

我归纳了几个必做步骤:

  1. 格式统一:日期全部转成ISO 8601(YYYY-MM-DD),单位统一成国际标准单位(mg、ml、g)。
  2. 空值处理:缺失的字段,是补默认值,还是直接丢弃?我曾经过于激进地丢弃了30%的实体,结果图谱变得稀疏。
  3. 实体消歧预处理:先把同义词表建立起来。比如“布洛芬”和“Ibuprofen”需要映射到同一个实体ID。
  4. 噪声过滤:删除HTML标签、特殊字符、乱码。
警告: 我曾经在预处理阶段跳过数据质量控制,结果下游实体链接直接崩了。后来加了一个简单的校验脚本,检查每列的字段格式和值域范围,才把问题拦在门外。别偷懒,这块省的时间,后面十倍补回来。

一个基础的清洗脚本示例:

import pandas as pd
import re

df = pd.read_csv("drug_data_raw.csv")

# 1. 删除全空的行
df.dropna(how='all', inplace=True)

# 2. 标准化日期字段
df['approval_date'] = pd.to_datetime(df['approval_date'],
                                     errors='coerce').dt.strftime('%Y-%m-%d')

# 3. 清理文本字段的噪声
def clean_text(text):
    if pd.isna(text):
        return text
    text = re.sub(r'<[^>]+>', '', text) # 删HTML标签
    text = re.sub(r'[^\w\s\-\.\,\(\)]', '', text) # 保留常用字符
    return text.strip()

df['description'] = df['description'].apply(clean_text)

# 4. 填充缺失的类别字段
df['drug_category'].fillna('unknown', inplace=True)

# 5. 输出清洗后的数据
df.to_csv("drug_data_cleaned.csv", index=False)
print("预处理完成,保留 {} 条记录".format(len(df)))

知识抽取:从文本里“挖金子”

这一步最考验NLP功底。我们要从非结构化的文本里,抽取出实体、关系和属性。

  • 命名实体识别(NER):找出药物、疾病、基因、剂量这些关键词。比如“服用阿司匹林每天一次”里,“阿司匹林”是药物,“每天一次”是频率。
  • 关系抽取:判断两个实体之间的关系。比如“阿司匹林治疗头痛”中,“治疗”就是关系。我在项目中用过基于BERT的关系分类器,准确率能做到85%以上。
  • 属性抽取:提取实体本身的特征。比如药物“阿司匹林”的分子式、分子量、LD50值。

举个简单的正则匹配例子,抽取剂量信息:

import re

text = "患者每日口服阿司匹林100mg,每周一次注射胰岛素20IU"
pattern = r"(\d+)\s*(mg|g|ml|IU)"
matches = re.findall(pattern, text, re.IGNORECASE)

for match in matches:
    print(f"数值: {match[0]}, 单位: {match[1]}")
# 输出:
# 数值: 100, 单位: mg
# 数值: 20, 单位: IU
小技巧: 实体识别时,别忘了考虑缩写和别名。我曾在PubMed摘要里发现“MTX”指代的是“甲氨蝶呤”,如果词典里没有这个别名,就会漏掉大量实体。建议预置一个行业别名表。

知识融合:把碎片拼成整图

数据源一多,同一个实体被分成了好几个身份。知识融合就是给它们“对账”,最后合并成一个全局ID。这活儿极其考验耐心。

我通常分三步走:

  1. 实体对齐:判断两个实体是不是指代同一个真实世界对象。用属性相似度(名称、别名、指纹)、图结构相似度来算。
  2. 冲突检测:当两个数据源对同一属性的描述不一致时,怎么选?比如DrugBank说某药LD50是500mg/kg,PubChem却说是300mg/kg。我的做法是——优先权威数据源,或者取均值并标记置信度。
  3. 实体链接与消歧:将文本中抽取的实体映射到知识图谱中已有的节点。这需要用到向量化嵌入和空间搜索。

知识存储:找个好“仓库”

图谱建好了,得有个地方存。我建议你用图数据库,比如Neo4j、JanusGraph。为什么?因为药物之间的关系天然是图结构,比如“药物A抑制蛋白B,蛋白B参与通路C”,关系查询速度远快于关系型数据库。

一个Neo4j的Cypher创建语句示例:

// 创建药物节点
CREATE (d:Drug {name: '阿司匹林', smiles: 'CC(=O)OC1=CC=CC=C1C(=O)O'});

// 创建疾病节点
CREATE (dis:Disease {name: '头痛', icd10: 'R51'});

// 创建治疗关系
MATCH (d:Drug {name: '阿司匹林'})
MATCH (dis:Disease {name: '头痛'})
CREATE (d)-[:TREATS {evidence_level: 'strong'}]->(dis);

知识查询:怎么用起来

最后一步,你的知识图谱得能回答问题。常见查询方式有SPARQL(对RDF存储)或Cypher(对属性图)。

举个例子,找出所有能治疗“高血压”的药物及其证据级别:

MATCH (d:Drug)-[r:TREATS]->(dis:Disease)
WHERE dis.name = '高血压'
RETURN d.name AS 药物名称, r.evidence_level AS 证据级别
ORDER BY r.evidence_level DESC;

你可能会问,为什么一定要用SPARQL或Cypher?说白了,它把关系查询变成二维表格里几十个JOIN才能干的事,一条语句就解决了。我有个项目里,改用图查询后,原来跑几分钟的药靶关联查询,变成了毫秒级。

核心要点: 六大流程环环相扣。数据采集决定广度,预处理决定质量,抽取决定深度,融合决定正确性,存储决定性能,查询决定价值。任何一个环节掉链子,整个图谱项目都可能翻车。
药物知识图谱构建基础流程框架 数据采集 预处理 知识抽取 知识融合 知识存储 知识查询 流程说明 → 数据采集 :从多种数据源(数据库、API、爬虫)获取原始数据 → 预处理 :格式统一、空值填充、噪声过滤、实体消歧准备 → 知识抽取 :NER、关系抽取、属性抽取,从文本中提取结构化信息 → 知识融合 :实体对齐、冲突检测、实体链接,消除冗余和矛盾 → 知识存储 :图数据库导入,支持高效关系查询 → 知识查询 :SPARQL/Cypher查询,为上层应用提供接口

嗯,这里要提醒一句。上面那张图展示的是理想化的单向流程。实际项目中,你经常会往回走。比如融合完发现数据质量不够,得回去重新做预处理;查询性能不理想,可能需要调整存储模型。迭代是常态,别想着一次走通。

我个人习惯把每一步的关键输出物都留个版本快照。哪天出问题了,能快速回滚到上一个靠谱的状态。这比从头重跑便宜多了。