2. 知识图谱表示:实体、关系、属性;三元组;RDF与SPARQL基础
好的,我们开始第2章。上一章我们聊了知识图谱能帮靶点发现做什么,那这一章就得先弄明白——知识图谱到底是什么样的数据?它怎么表示知识的?
说白了,知识图谱不是存整篇文献,也不是存二维表格。它用的一种非常简单、非常原子化的方式:三元组。我刚开始接触时也觉得太简单了,不就三个东西嘛。但真正用起来才发现,这种简单才是它的力量所在。
2.1 实体、关系、属性——知识图谱的三原色
我们先看三个基本概念。我习惯把它们叫作知识图谱的“三原色”。
- 实体(Entity):现实世界中的事物。比如“EGFR基因”、“吉非替尼”、“非小细胞肺癌”。每个实体有唯一标识。
- 关系(Relation):实体之间的连接。比如“靶向”、“治疗”、“位于”。关系是有方向的。
- 属性(Attribute):实体的特征描述。比如“EGFR的UniProt ID是P00533”,“吉非替尼的分子量是446.9 g/mol”。属性可以理解为实体自身的“属性-值”对。
答案是:如果你要跟别的药物比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"
你会发现,前两个三元组连接的是实体。第三个三元组的客体是字面量(字符串),表达的是属性。
这一点非常关键:
- 客体是实体——表达关系,可以继续往外连其他实体。
- 客体是字面量(数字、字符串)——表达属性,是终点,不能再往外连了。
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两张表。
下面这张图,是我做靶点图谱项目时经常给学生画的核心结构。它展示了从“基因”到“药物”再到“疾病”的典型三元组链条:
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原则) | 弱(需导出转换) |
| 典型场景 | 多源异构数据融合、知识推理 | 实时推荐、社交网络、图分析 |
2.7 避坑指南——我踩过的三个坑
最后,分享几个实战中容易翻车的地方:
- URI乱起名:我曾经在一个项目里,给基因实体起了不同的URI前缀,比如有些用
http://mykb/gene/EGFR,有些用http://mykb/protein/EGFR。结果查询时同样的基因查不出来。建议:项目一开始就定好URI设计规范。 - 字面量语言标签:字符串属性如果不加语言标签,未来做多语言匹配时会出问题。比如
"lung cancer"@en和"肺癌"@zh在SPARQL里不会自动等价。所以,如果你的图谱需要跨语言,记得打标签。 - RDF的空白节点:刚开始用RDF时,我特别喜欢用空白节点(blank node)来表示复杂属性。比如某个药物-靶点关系的结合亲和力实验,我用空白节点把实验条件、IC50值、pH值都挂上去。结果查询复杂度剧增,后来全改成了具名节点。建议:尽量用具名节点,别偷懒用空白节点。
好了,这一章的内容就到这里。核心就是记住:三元组是原子,URI是身份证,SPARQL是图上的SQL。搞清楚了这三个东西,知识图谱对你来说就是个工具,不是什么玄学。