第3章 · 三元组、RDF、属性图模型

聊知识图谱,绕不开三个词:三元组、RDF、属性图。

很多人觉得它们是同一回事,或者认为RDF就是知识图谱的全部。其实不是。我一开始做药物靶点项目时也被绕晕过,用RDF搭了一个多月才发现有些场景根本不适合。嗯,先别急,我们一个一个拆开看。

3.1 三元组:知识图谱的“原子”

知识图谱最底层的单元就是三元组。说白了,就是三样东西拼在一起,表达一个事实。

  • 主语(Subject) —— 你要描述的对象,比如“阿司匹林”
  • 谓语(Predicate) —— 对象之间的关系或属性,比如“靶向”
  • 宾语(Object) —— 关系的另一端,可以是实体或者值,比如“COX1”

举个例子:

<阿司匹林>  <靶向>  <COX1>
<COX1>  <所属通路>  <花生四烯酸代谢通路>

这样一条一条串起来,就构成了一张网。我在做药物重定位项目时,早期从公开数据库抽出来的三元组就超过2000万条——但你必须先保证每一条都正确,否则后面推理会崩。

3.2 RDF:标准化了三元组的写法

RDF全称Resource Description Framework,是W3C制定的标准。它没发明新东西,只是给三元组分了名词:用IRI(国际资源标识符)来来标识节点和关系,避免重名。

比如“COX1”可能出现在不同数据库里。RDF要求你写成完整IRI:

<http://dbpedia.org/resource/COX1>  <http://example.org/targets>  <http://dbpedia.org/resource/Aspirin>

看着挺啰嗦,但好处是全局唯一,不会搞混。RDF有多种序列化格式,我最常用Turtle,因为它可读性最好:

@prefix drug: <http://example.org/drug/> .
@prefix target: <http://example.org/target/> .

drug:Aspirin  drug:targets  target:COX1 .
target:COX1  rdf:type  drug:Protein .

有个坑我踩过——RDF的rdf:type默认是rdfs:Class,但很多工具对owl:Class也支持,不统一的话推理会报错。所以你构建本体前缀时一定要检查OWL profile。

3.3 属性图模型:更懂“程序员”的表达

属性图模型(Property Graph Model)是另一个流派,Neo4j、JanusGraph、ArangoDB都用它。它跟RDF最大的区别是什么?

节点和关系都可以带键值对属性。

比如一个药物节点,我能在同一结构里直接写上它的分子量、CAS号、SMILES;一条靶向关系,我也可以带上结合亲和力IC50值。在RDF里你得为每个属性再写一个三元组,关联关系会变得很瘦长。

举个例子(JSON风格表示属性图):

节点: {id: "阿司匹林", label: "Drug", properties: {smiles: "CC(=O)OC1=CC=CC=C1C(=O)O", mw: 180.16}}
边: {from: "阿司匹林", to: "COX1", label: "targets", properties: {ic50: 0.5, assay: "in vitro"}}

看到没?所有信息集中在一条记录里,查询起来路径短。我个人在工程实践中更倾向属性图做OLTP系统,RDF做数据交换和推理。

3.4 RDF vs 属性图:一个表看清楚

维度 RDF(三元组模型) 属性图模型
基本单元 主体-谓词-客体(每行一个事实) 节点 + 关系(可带属性)
标识方式 IRI(全局唯一) 内部ID(通常自动生成)
属性存储 需要额外三元组,产生大量中间节点 直接挂在节点或关系上
推理支持 强(RDFS/OWL规则引擎) 弱(需要应用层实现)
查询语言 SPARQL Cypher / Gremlin / GraphQL
典型用途 知识图谱集成、开放数据、本体推理 实时图分析、社交网络、推荐系统

我曾经有一个项目,客户坚持用RDF存所有药物-靶点-通路数据,结果查询“找到靶向BRCA1且在乳腺癌中过表达的所有药物”需要十几跳,SPARQL写完人都傻了。后来切到属性图,同样的意图一条Cypher搞定。

我的经验法则:需要推理和跨系统共享时选RDF;需要高性能遍历和频繁更新属性时选属性图。

3.5 一张图理解三者关系

下面的SVG把三元组、RDF、属性图的核心关系画出来了。你可以看到三元组是底层事实表达,RDF给三元组穿上标准化外衣,属性图则是另一种更“胖”但更灵活的结构。

三元组 · RDF · 属性图模型关系 三元组 (S, P, O) 阿司匹林 → 靶向 → COX1 COX1 → 所属通路 → 花生四烯酸 阿司匹林 → 分子量 → 180.16 每行一个原子事实 RDF标准化 IRI标识全局唯一 <drug:Aspirin> <drug:targets> <target:COX1> 支持RDFS/OWL推理 序列化:Turtle, RDF/XML 查询:SPARQL 属性图模型 节点 + 关系 + 属性 节点:{id, label, props} 边:{src, dst, label, props} IC50直接挂在边上 查询:Cypher / Gremlin 赋予IRI 标准化 转为 属性打包 核心区别:RDF用多个三元组表达属性,属性图直接在节点/边上放属性集合。 选型时看你的查询模式:多跳遍历?大量属性筛选?还是需要本体推理? RDF 适用场景 · 开放数据整合(Wikidata、Bio2RDF) · 本体推理与一致性检查 · 跨数据库联邦查询 属性图适用场景 · 实时交互式图分析 · 推荐算法 / 路径发现 · 属性频繁变更的业务系统

3.6 到底怎么选?

如果你正在搭一个药物靶点知识图谱,我建议你按这个流程思考:

  1. 是否需要跨本体推理?(比如根据靶点家族预测新靶点)——选RDF。
  2. 你的数据源是不是各种异构格式?(CSV, XML, API, Excel)——RDF有标准映射工具。
  3. 用户是否主要做图遍历和属性过滤?(比如“找分子量<500且IC50<10nM的化合物”)——属性图更爽。
  4. 团队熟悉SQL还是SPARQL? 属性图的Cypher对SQL背景更友好。
小技巧:实在纠结的话,可以两个都存。先用属性图做快速原型和交互查询,再定期导出为RDF做开放共享。我参与的一个DRKG项目就是这么干的。
注意:不要试图在RDF里用“空白节点”当中间节点来模拟属性图——我曾经这么干过,导致SPARQL查询时出现性能灾难,最后靠改模型才解决。

讲到这儿,我想再提一个例子:我们在构建药物-副作用知识图谱时,原始数据来自SIDER数据库,每个副作用关联频率、占比、类型。如果用纯RDF,每个副作用-药物对要写5~6条三元组,数据量翻五倍。后来我把频率和占比作为属性塞到关系里,用属性图单条搞定,查询速度提升了20倍。当然,共享给外部合作方时,我们还是转成了RDF Turtle格式——因为对方只接受标准RDF。

所以,别把模型当信仰,它们是工具。选最合适的,存活的才久。