4. 药物知识图谱构建:主流数据源与预处理

构建药物知识图谱,第一步就是搞数据。很多人觉得算法模型是核心,其实在工业界,数据质量直接决定了你后面所有的活儿能不能出彩。

我个人习惯,先把能拿到的数据源梳理清楚,再动手写代码。别急着跑模型,先问问自己:这些数据长什么样?哪些字段能用?怎么把它们串起来?

本章我们就来盘一盘药物KG领域的主流数据源,以及怎么把这些“脏活累活”干漂亮。

4.1 主流数据源:从哪拿数据?

药物相关数据源很多,但常用的就那么几个。我把它们分成三类:药物-靶点关系型、蛋白质功能型、相互作用网络型。

数据源 核心内容 适用场景 访问方式
DrugBank 药物-靶点关联、药物分类、ADMET 药物重定位、靶点发现 XML/JSON 下载,API
UniProt 蛋白质序列、功能注释、GO条目 靶点特征提取 SPARQL 端点,RDF 下载
STRING 蛋白质-蛋白质相互作用 (PPI) 网络传播、通路分析 TSV 下载,REST API
ChEMBL 生物活性数据、化合物-靶点亲合力 分子对接验证、SAR分析 MySQL dump,API
TTD (Therapeutic Target Database) 靶点-疾病关联、药物-靶点-通路 疾病机制研究 Excel 表格下载

我的经验:别迷信单一数据源。DrugBank 的靶点标注最权威,但更新慢;ChEMBL 的活性数据全,但清洗工作量巨大。我通常会以 DrugBank 为主干,用 UniProt 做序列对齐,再用 STRING 补全网络关系。

举个例子,从 DrugBank 抓取一个药物的靶点信息,原始 XML 长这样:

<drug type="biotech">
  <drugbank-id primary="true">DB00001</drugbank-id>
  <name>Lepirudin</name>
  <targets>
    <target>
      <id>BE0001234</id>
      <name>Thrombin</name>
      <organism>Homo sapiens</organism>
      <polypeptide>
        <id>P00734</id>
        <source>UniProt</source>
      </polypeptide>
    </target>
  </targets>
</drug>

你看,id 字段直接关联到 UniProt 的 P00734。这就是图谱构建的骨架——跨数据源的实体对齐。

避坑指南:我曾经在解析 DrugBank XML 时,没注意 target 节点下可能有多个 polypeptide 条目,导致漏丢了不少异构体数据。写 parser 的时候,一定要检查列表是不是完整的。

4.2 数据预处理:洗数据是门手艺

拿到的原始数据,说白了就是一堆半结构化文本。你得把它变成干净的三元组。

核心流程包括三步:命名实体识别、实体归一化、关系抽取。

4.2.1 命名实体识别 (NER)

找出来文本里哪些词是药物、靶点、疾病。比如一篇 PubMed 摘要里的这句话:

"Imatinib inhibits BCR-ABL tyrosine kinase in chronic myeloid leukemia."

你需要识别出:Imatinib (药物), BCR-ABL (靶点), chronic myeloid leukemia (疾病)。

我习惯用字典匹配 + 机器学习模型组合。字典用 DrugBank 和 MeSH 的术语表,模型可以用 BioBERT 微调。

4.2.2 实体归一化 (Normalization)

同一个实体在不同数据源叫法不同。比如 Thrombin 也叫 Coagulation factor II。归一化就是把它们映射到同一个标准 ID。

常用的映射表是 UniProt ID 和 Gene Symbol 的对应关系。举个例子:

{
  "Thrombin": "P00734",
  "Coagulation factor II": "P00734",
  "F2": "P00734"
}

你可以在本地维护一份这样的 JSON 映射文件:

{
  "uniprot_id": "P00734",
  "gene_symbol": ["F2", "CF2", "PT"],
  "protein_name": ["Thrombin", "Coagulation factor II", "Prothrombin"]
}

关键点:归一化是整个构建过程里最容易出错的一环。建议先跑一遍自动匹配,再做一轮人工校验,尤其是那些有歧义的基因名称。

4.3 数据融合:把碎片拼成图谱

数据融合的核心是链接预测的前置工作。你需要决定怎么把 DrugBank 的药物节点、UniProt 的蛋白质节点、STRING 的相互作用边,融合成一个统一的图。

常见策略:

  • 同构融合:所有实体都作为节点,所有关系作为边。适用于 GNN 模型。
  • 异构融合:保留实体类型(药物、靶点、疾病),关系也有类型(抑制、激活、结合)。适用于 RGCN 等异构模型。
  • 属性融合:把序列特征、化学结构指纹作为节点属性存入。

我常用的融合流程,用 Python 伪代码可以写成:

import pandas as pd

# 1. 加载 DrugBank 药物-靶点关系
drug_target = pd.read_csv('drugbank_targets.tsv', sep='\t')

# 2. 加载 UniProt 蛋白质序列信息
uniprot_protein = pd.read_csv('uniprot_human.tsv', sep='\t')

# 3. 加载 STRING PPI 网络
string_ppi = pd.read_csv('string_human_500.tsv', sep='\t', header=None, 
                         names=['protein1', 'protein2', 'score'])

# 4. 通过 UniProt ID 对齐
merged = drug_target.merge(uniprot_protein, left_on='target_uniprot', 
                           right_on='uniprot_id', how='inner')

# 5. 构建图边列表
edges = []
for _, row in merged.iterrows():
    edges.append(('Drug', row['drug_id'], 'Target', row['uniprot_id']))
    edges.append(('Protein', row['uniprot_id'], 'PPI', row['string_partner']))

我曾经踩过的坑:STRING 数据里的蛋白质标识符经常是 ENSEMBL ID,不是 UniProt ID。你需要先做一个 ID 转换。别直接拿来用,否则连边对不上,整个图就是个哑炮。

4.4 知识图谱构建流程总览

下面这张图概括了从数据源到知识图谱的完整脉络。你做完预处理后,应该得到的就是这样一个结构化的输出。

药物知识图谱构建流程 DrugBank XML / TSV UniProt SPARQL / RDF STRING TSV / API ChEMBL MySQL 数据预处理 实体识别 → 归一化 → 关系抽取 → 冲突消解 ID 对齐 / 名称归一化 / 重复过滤 数据融合 同构融合 / 异构融合 / 属性融合 药物知识图谱 三元组 (药物, 靶向, 蛋白质) ... 说明:整个流程核心是“以 ID 对齐为主线,以关系抽取为手段”

4.5 跨源冲突处理

不同数据源对同一关系的描述可能打架。比如 DrugBank 说某个药物抑制靶点 A,而 ChEMBL 说它是激动剂。怎么处理?

我的做法:

  1. 置信度加权:优先采纳权威数据源(DrugBank 的靶点关系优先级高于文献挖掘)。
  2. 版本标记:保留所有关系来源,在图中加入 source 属性。
  3. 人工审核:对于高冲突的边,生成待审列表,交给领域专家确认。

小技巧:你可以用 Neo4j 的 apoc.refactor.mergeNodes 来合并同一实体的多个表示,前提是你能保证 ID 映射绝对准确。

最后,构建好的知识图谱通常以 triples.csv 形式落盘,格式如下:

head   relation   tail
DB00001   targets   P00734
DB00001   treats   C001233
P00734   interacts_with   Q12345
DB00002   inhibits   P01374

这就是后续靶点特征学习、图神经网络训练最直接的输入。说白了,前面的所有预处理工作,都是为了打出这样一份干净的三元组清单。