2. 知识图谱表示:实体、关系、属性;三元组;RDF与SPARQL基础

好的,我们开始第2章。上一章我们聊了知识图谱能帮靶点发现做什么,那这一章就得先弄明白——知识图谱到底是什么样的数据?它怎么表示知识的?

说白了,知识图谱不是存整篇文献,也不是存二维表格。它用的一种非常简单、非常原子化的方式:三元组。我刚开始接触时也觉得太简单了,不就三个东西嘛。但真正用起来才发现,这种简单才是它的力量所在。

2.1 实体、关系、属性——知识图谱的三原色

我们先看三个基本概念。我习惯把它们叫作知识图谱的“三原色”。

  • 实体(Entity):现实世界中的事物。比如“EGFR基因”、“吉非替尼”、“非小细胞肺癌”。每个实体有唯一标识。
  • 关系(Relation):实体之间的连接。比如“靶向”、“治疗”、“位于”。关系是有方向的。
  • 属性(Attribute):实体的特征描述。比如“EGFR的UniProt ID是P00533”,“吉非替尼的分子量是446.9 g/mol”。属性可以理解为实体自身的“属性-值”对。
我的实战体会: 刚开始建靶点图谱时,我总搞混“关系”和“属性”。举个例子:“吉非替尼的IC50值是5.2 nM”——这个“IC50值”是属性还是关系?
答案是:如果你要跟别的药物比IC50、做筛选,它就是属性。如果你要表达“吉非替尼和EGFR之间有一个IC50测定实验”这个事件,那它就是关系。怎么选?问自己一句:“这个信息未来会作为筛选条件吗?”

2.2 三元组——知识的最小单元

知识图谱的基本存储单元就是三元组,格式是:(主体, 谓词, 客体)。也叫 (Subject, Predicate, Object),或者 (头实体, 关系, 尾实体)。

来看一个靶点领域的例子:

<EGFR>  <is_target_of>  <Gefitinib>
<Gefitinib>  <treats>  <Non-Small-Cell-Lung-Cancer>
<EGFR>  <has_uniprot_id>  "P00533"

你会发现,前两个三元组连接的是实体。第三个三元组的客体是字面量(字符串),表达的是属性。

这一点非常关键:

  • 客体是实体——表达关系,可以继续往外连其他实体。
  • 客体是字面量(数字、字符串)——表达属性,是终点,不能再往外连了。
注意: 不要把属性值当实体连出去。比如“IC50=5.2 nM”,你非要把“5.2 nM”当实体,再连一个“has_unit”到“nM”上——这种过度建模会让图谱爆炸,查询效率暴跌。我曾经见过一个团队这么干,结果一个药物节点连出几千个属性节点,查询慢得没法用。

2.3 RDF——万维网联盟的标准

三元组是一种思路,那在计算机里怎么标准化表示?这就是RDF(Resource Description Framework)的出场。

RDF是W3C制定的标准。它要求每个实体(资源)用URI来标识。还是上面的例子,写成标准RDF/Turtle格式长这样:

@prefix dto: <http://example.org/drugtarget/> .
@prefix uniprot: <http://purl.uniprot.org/uniprot/> .

dto:EGFR  rdf:type          dto:Protein .
dto:EGFR  dto:hasUniprotId  "P00533" .
dto:EGFR  dto:isTargetOf    dto:Gefitinib .
dto:Gefitinib  dto:treats   dto:NSCLC .

你看,每个实体都带了命名空间前缀。这有什么好处?我举个例子:不同数据库里都有EGFR。UniProt里叫P00533,NCBI Gene里叫1956。用URI一标识,系统就知道“哦,它俩说的是同一个东西”——这就是实体对齐的基础。

2.4 SPARQL——给知识图谱写的SQL

数据存好了,怎么查?RDF的查询语言就叫SPARQL。我刚开始学的时候觉得它长得怪,后来发现它的核心就是“用三元组模板去匹配”。

举个例子:查所有靶向EGFR的药物。

PREFIX dto: <http://example.org/drugtarget/>

SELECT ?drug
WHERE {
  dto:EGFR  dto:isTargetOf  ?drug .
}

WHERE里面的东西,就是一个带变量的三元组。?drug是变量,系统会在图里找所有满足dto:EGFR dto:isTargetOf ?drug的三元组,把?drug的值返回给你。

再复杂一点:查EGFR的靶向药,以及这些药治疗的疾病。

PREFIX dto: <http://example.org/drugtarget/>

SELECT ?drug ?disease
WHERE {
  dto:EGFR  dto:isTargetOf  ?drug .
  ?drug    dto:treats      ?disease .
}

这就是图遍历。EGFR -> 药物 -> 疾病。两层关系,一句查询搞定。换成关系数据库你得JOIN两张表。

核心思路一句话: SPARQL查询本质上是在图里做子图匹配。你定义一个模板,系统找出所有能匹配上这个模板的子图。

下面这张图,是我做靶点图谱项目时经常给学生画的核心结构。它展示了从“基因”到“药物”再到“疾病”的典型三元组链条:

知识图谱三元组结构示例(靶点领域) EGFR 基因 实体(头节点) 吉非替尼 实体(尾节点) 非小细胞肺癌 实体(尾节点) is_target_of treats UniProt ID "P00533" (字面量) has_uniprot_id 📌 核心要点 • 头节点 → 关系 → 尾节点:构成一个三元组 • 尾节点为实体:可继续作为头节点,形成图遍历链 • 尾节点为字面量:属性终点,不能再外连(如上图中的UniProt ID)

2.5 实战——给靶点知识建一个迷你RDF库

光说不练假把式。我建议你在自己的机器上跑一下这个例子。

首先,装一个轻量级RDF数据库。我用Apache Jena Fuseki比较多:

# 下载并启动Fuseki(假设你已经装了Java)
wget https://dlcdn.apache.org/jena/binaries/apache-jena-fuseki-4.9.0.zip
unzip apache-jena-fuseki-4.9.0.zip
cd apache-jena-fuseki-4.9.0
./fuseki-server --port 3030

启动后浏览器打开 http://localhost:3030,创建一个数据集(比如叫 "target")。

然后把我刚才那段Turtle数据存成一个文件 target_data.ttl:

@prefix dto: <http://example.org/drugtarget/> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .

dto:EGFR  rdf:type          dto:Protein .
dto:EGFR  dto:hasUniprotId  "P00533" .
dto:EGFR  dto:isTargetOf    dto:Gefitinib .
dto:Gefitinib  rdf:type     dto:Drug .
dto:Gefitinib  dto:treats   dto:NSCLC .
dto:NSCLC  rdf:type         dto:Disease .

通过Fuseki的Web界面上传这个文件。上传后,你就可以在SPARQL查询面板里跑查询了。试着跑一下这个:

PREFIX dto: <http://example.org/drugtarget/>

SELECT ?drug ?disease
WHERE {
  ?drug  rdf:type      dto:Drug .
  ?drug  dto:treats    ?disease .
}

返回结果应该就是吉非替尼治疗非小细胞肺癌。嗯,这里要注意:我故意只存了一条数据,但它的结构是完整的。如果你从DrugBank、UniProt、CTD这些数据库批量导入,查询价值就出来了。

2.6 RDF vs 属性图——选哪个?

聊到这里,可能有人会问:图和Neo4j用的属性图模型有啥区别?我直接给你个对照表:

对比维度 RDF (标准知识图谱) 属性图 (如Neo4j)
存储单元 三元组 (S,P,O) 节点 + 关系 + 属性键值对
实体标识 全局URI 内部ID + 标签
查询语言 SPARQL (W3C标准) Cypher (厂商标准)
推理能力 原生支持RDFS/OWL推理 需插件或用APOC手动实现
互操作性 强(Linked Data原则) 弱(需导出转换)
典型场景 多源异构数据融合、知识推理 实时推荐、社交网络、图分析
我的选型建议: 做靶点发现这种需要融合多个公开数据库(UniProt、DrugBank、CTD、GO)的项目,我无脑选RDF。因为它天生的URI机制方便做实体对齐,SPARQL的联邦查询可以直接跨库查。但如果你的场景是快速搭建一个内部应用的图数据库、不做复杂推理,属性图上手更快。

2.7 避坑指南——我踩过的三个坑

最后,分享几个实战中容易翻车的地方:

  1. URI乱起名:我曾经在一个项目里,给基因实体起了不同的URI前缀,比如有些用http://mykb/gene/EGFR,有些用http://mykb/protein/EGFR。结果查询时同样的基因查不出来。建议:项目一开始就定好URI设计规范。
  2. 字面量语言标签:字符串属性如果不加语言标签,未来做多语言匹配时会出问题。比如"lung cancer"@en和"肺癌"@zh在SPARQL里不会自动等价。所以,如果你的图谱需要跨语言,记得打标签。
  3. RDF的空白节点:刚开始用RDF时,我特别喜欢用空白节点(blank node)来表示复杂属性。比如某个药物-靶点关系的结合亲和力实验,我用空白节点把实验条件、IC50值、pH值都挂上去。结果查询复杂度剧增,后来全改成了具名节点。建议:尽量用具名节点,别偷懒用空白节点。

好了,这一章的内容就到这里。核心就是记住:三元组是原子,URI是身份证,SPARQL是图上的SQL。搞清楚了这三个东西,知识图谱对你来说就是个工具,不是什么玄学。