第二章 医学知识表示:实体、关系与属性的建模方法
做知识图谱这行,有个事儿早晚得面对——怎么把医生脑子里那些「药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属性,查询时都不知道该用哪个。
下面是整个知识表示的核心结构图。它帮你一眼看清实体、关系、属性的组织方式:
看到这张图的布局了吗?
- 上半部分是三个核心实体,以及它们之间的两种关键关系(INHIBITS 和关联)
- 下半部分用虚线展示了药物与疾病的直接治疗关系,跨越了靶点
- 底部说明框给出了属性应该挂在哪里的具体建议
我实际建图时,会把这张图打印出来贴在工位上。每次新加一个实体或关系,先对照图看放在哪个位置合适。
小技巧:属性建模时,可以预留一个「metadata」字段,存JSON格式的元数据。比如实验日期、文献引用、QC状态等。这样主属性保持干净,元数据又不会丢失。我一直在用这个套路,效果不错。