第二章 医学知识表示:实体、关系与属性的建模方法

做知识图谱这行,有个事儿早晚得面对——怎么把医生脑子里那些「药A能治疾病B,但和药C一起吃会出事」的模糊经验,变成计算机能理解的结构化数据。

说白了,就是给药物领域的知识画个蓝图。这块我做项目时踩过不少坑,今天跟大家掰扯清楚。

2.1 实体建模:先搞清楚「有什么东西」

实体就是知识图谱里的节点。在药物领域,核心实体其实就三大类。我习惯用一张表把它们列清楚。

实体类型 典型实例 核心标识
药物 (Drug) 阿司匹林、伊马替尼、PD-1抑制剂 通用名、商品名、结构式
靶点 (Target) EGFR、COX-2、PD-L1蛋白 基因名、UniProt ID
疾病 (Disease) 非小细胞肺癌、2型糖尿病、阿尔茨海默病 ICD-10编码、疾病名称

嗯,这里要注意:同一个实体在不同数据库里可能叫法不一样。比如「阿司匹林」,CAS号是50-78-2,PubChem CID是2244,但医生习惯叫「乙酰水杨酸」。

我的做法是——在建模时增加一个别名列表属性,把各种叫法都塞进去,查询时用统一ID做桥接。

// 一个简化的药物节点建模示例(伪代码)
{
  "entity_type": "Drug",
  "id": "CHEMBL25",
  "name": "阿司匹林",
  "aliases": ["乙酰水杨酸", "ASA", "Acetylsalicylic Acid"],
  "smiles": "CC(=O)Oc1ccccc1C(=O)O"
}

个人经验:不同来源的实体ID映射是最容易出bug的地方。我建议一上来就建一个「实体注册表」,强制所有节点通过统一API落库,避免后续花大量时间做实体对齐。

2.2 关系建模:把「怎么连接」讲清楚

光有节点没用,得知道它们之间怎么连。关系就是知识图谱里的边。为什么必须区分这三种关系?你想想看:

  • 作用 (Interacts With):药物和靶点之间的直接物理/化学作用
  • 抑制 (Inhibits):药物对靶点功能的阻断,这是药理学的核心
  • 治疗 (Treats):药物对疾病的整体疗效关联

我以前做过一个项目,把「抑制」和「治疗」混在一起存。结果做推理时闹了笑话——某药物抑制了某个靶点,但那个靶点和疾病根本没因果关系,推理出的「治疗」关系全是假的。

避坑指南:我曾经在关系类型上吃过亏。请一定把「直接作用」和「间接治疗」严格分开。我的建议是关系命名遵循「主语-谓语-宾语」的语义模式,比如:(Drug)-[INHIBITS]->(Target) 和 (Drug)-[TREATS]->(Disease) 不能共用同一个谓词。

实际建模时,关系可以加上置信度或证据来源。比如:

// Cypher 示例:创建带属性的关系
CREATE (d:Drug {name: "伊马替尼"})
      -[r:INHIBITS {
        evidence: "FDA标签",
        confidence: 0.95,
        ki_value: "0.6 nM"
      }]->(t:Target {name: "BCR-ABL"})

2.3 属性建模:给每个节点「贴标签」

属性是整个知识图谱的「血肉」。没有属性,节点就是个空架子。我通常把属性分成两类:结构属性和功能属性。

2.3.1 结构属性:SMILES、InChI 与分子描述符

药物最核心的结构属性就是 SMILES。它用一串简单的ASCII字符编码分子的拓扑结构。IC50是衡量药效的关键指标,表示抑制50%靶点活性所需的药物浓度,单位通常是 nM 或者 μM。

属性名 含义 示例值
SMILES 简化分子线性输入规范 CC(=O)Oc1ccccc1C(=O)O
InChI 国际化学标识符 1S/C9H8O4/c1-6(10)13-8-5-3-2-4-7(8)9(11)12/h2-5H,1H3,(H,11,12)
IC50 半抑制浓度(体外) 2.3 nM
Ki 抑制常数(体内/体外) 0.6 nM

我遇到过一个问题:同样的药物在不同文献里的IC50值差了一个数量级。后来发现是实验条件不同(比如酶浓度、底物浓度)。所以我在属性建模时,总会加一个实验条件字段,哪怕它暂时是空的。

2.3.2 功能属性:药理与临床特征

功能属性更贴近应用场景,比如半衰期、生物利用度、毒性等级等。这些属性往往不是孤立存在的,它们和靶点、疾病属性相互关联。

举个例子,一个属性可以这样存:

{
  "node_type": "Target",
  "id": "P00533",
  "name": "EGFR",
  "properties": {
    "uniprot_id": "P00533",
    "gene_symbol": "EGFR",
    "subcellular_location": "细胞膜",
    "druggability_score": 0.87,
    "associated_diseases": ["非小细胞肺癌", "结直肠癌"]
  }
}

核心原则:属性设计要遵循「最少冗余,最多复用」。同一个IC50值只存在一个地方,其他节点通过关系引用。比如「阿司匹林对COX-2的IC50」应该作为 INHIBITS 关系的属性,而不是挂在 Drug 节点上。

2.4 三者整合:把蓝图画出来

实体、关系、属性不是三个独立的部分。它们需要被整合到一个统一的图模型中。我给出一个实际项目中的建模模式:

// 整合示例:药物-靶点-疾病三元组
(d:Drug {
  name: "奥希替尼",
  smiles: "CN(C)C(=O)C1=CC2=C(C=C1)N=CN=C2NC3=CC(=C(C=C3)F)OC4=CC5=CC=CC=C5N=C4"
})
-[i:INHIBITS {
  ic50: "12 nM",
  ki: "1.5 nM",
  assay_type: "荧光偏振",
  reference_pmid: "29513240"
}]->(t:Target {
  name: "EGFR T790M",
  gene: "EGFR",
  mutation: "T790M"
})
-[tr:TREATS {
  approval_status: "FDA批准",
  line_of_therapy: "二线",
  label_indication: "T790M阳性NSCLC"
}]->(d:Disease {
  name: "非小细胞肺癌",
  icd10: "C34.9",
  histology: "腺癌"
})

你可能会问:「为什么要把IC50放在关系上,而不是药物节点?」

答案很简单:同一药物对不同靶点的IC50不同。如果挂在药物节点上,数据会爆炸。我见过有人这么干,结果一个药物节点上有几十个IC50属性,查询时都不知道该用哪个。

下面是整个知识表示的核心结构图。它帮你一眼看清实体、关系、属性的组织方式:

药物知识图谱:实体-关系-属性建模框架 药物 (Drug) SMILES, IC50, 半衰期 靶点 (Target) UniProt ID, 基因名, 亚细胞位置 疾病 (Disease) ICD-10, 疾病名, 分型 INHIBITS (IC50, Ki实验) 关联 (通路, 基因突变) TREATS (FDA批准, 适应证) 属性建模原则 • 结构属性(SMILES, InChI, 分子量)→ 挂在Drug节点 • 功能属性(IC50, Ki)→ 挂在关系上(因靶点而异) • 实验条件/证据来源 → 作为关系的元属性存储

看到这张图的布局了吗?

  • 上半部分是三个核心实体,以及它们之间的两种关键关系(INHIBITS 和关联)
  • 下半部分用虚线展示了药物与疾病的直接治疗关系,跨越了靶点
  • 底部说明框给出了属性应该挂在哪里的具体建议

我实际建图时,会把这张图打印出来贴在工位上。每次新加一个实体或关系,先对照图看放在哪个位置合适。

小技巧:属性建模时,可以预留一个「metadata」字段,存JSON格式的元数据。比如实验日期、文献引用、QC状态等。这样主属性保持干净,元数据又不会丢失。我一直在用这个套路,效果不错。