第2章 知识图谱基础
什么是知识图谱
先跑个题。2012年谷歌提这个概念的时候,很多人以为只是又一种数据库。
但说白了,知识图谱就是一种用图结构来组织知识的方法。把现实世界中的「事物」和它们之间的「关系」用图的方式存起来。
我个人特别喜欢的一个定义是:知识图谱 = 实体 + 关系 + 语义。
举个例子:
「阿司匹林」和「环氧合酶-1」之间有什么关系?
——阿司匹林通过抑制环氧合酶-1来发挥镇痛作用。
这其实就是一条知识。如果把成千上万条这样的知识连起来,就构成了一张网络。这张网络就是知识图谱。
我在项目中遇到过不少同学把知识图谱和传统的分类目录混为一谈。两者有本质区别:分类目录是树状的,知识图谱是网状的。树状结构只能上下归类,网状结构才能支持复杂的推理。
核心记忆点:知识图谱的核心不是「知识」,而是「关联」。没有关联的孤立实体,只是数据,不是知识。
三元组:知识图谱的基本单元
你想想看,一张大网是怎么织起来的?
最小单元就是「三元组」(Triple)。
三元组的结构极其简单:
主体(Subject)— 谓词(Predicate)— 客体(Object)
用自然语言描述就是:
「谁 — 做了什么 — 对谁」或者「什么事物 — 有什么属性 — 值是多少」
| 主体 | 谓词 | 客体 |
|---|---|---|
| 阿司匹林 | 抑制 | 环氧合酶-1 |
| 环氧合酶-1 | 属于 | 环氧合酶家族 |
| 布洛芬 | 治疗 | 头痛 |
| 药物靶点 | 定义 | 药物作用的生物分子 |
我刚开始做知识图谱时,总觉得三元组太简单,不以为然。但真的动手建图才发现,把自然语言中的关系拆成标准化的三元组,才是整个工程中最容易出bug的环节。
我建议:在构建三元组之前,先定义好谓词的命名规范。不要出现同义不同形的谓词,比如「抑制」和「抑制作用」同时存在,会导致查询时漏掉大量数据。
存储三元组的方式有很多种。最基础的是用CSV文件或者JSON:
{
"subject": "阿司匹林",
"predicate": "抑制",
"object": "环氧合酶-1"
},
{
"subject": "环氧合酶-1",
"predicate": "属于",
"object": "环氧合酶家族"
}
我曾经踩过的坑:在一个项目中,因为主体和客体的标识符不统一,同一个概念在A文件中叫做「COX-1」,在B文件中叫做「环氧化酶-1」,导致生成的图谱里同样一个靶点出现了两个节点,连边连不上。后来不得不花了两周时间做实体对齐。切记:统一实体ID。
实体关系抽取
有了三元组的概念,那问题就来了:这些三元组从哪里来?
答案就是:实体关系抽取。
这其实包含两个子任务:
- 命名实体识别(NER):从文本中找出实体,比如「阿司匹林」「环氧合酶-1」
- 关系分类(RC):判断实体之间存在什么关系,比如「抑制」「激活」「结合」
说白了,实体关系抽取就是把非结构化的文本,变成结构化三元组的过程。
常用的方法有这几种:
| 方法 | 原理 | 适用场景 |
|---|---|---|
| 规则模板 | 人工编写正则/模式串匹配 | 少量数据、特定格式文本 |
| 传统机器学习 | 特征工程 + CRF/SVM | 标注数据充足、特征清晰 |
| 深度学习 | BERT/GPT + 序列标注 | 大规模数据、跨领域泛化 |
我个人习惯先用规则模板快速生成一批种子三元组。比如药物的说明书里,经常会出现「适应症」「不良反应」「作用机制」这些固定字段,直接用正则抽取效率很高。
看一个最简单的规则模板例子:
import re
text = "阿司匹林通过抑制环氧合酶-1来发挥镇痛作用。"
pattern = r"(.+?)通过(.+?)([激活|抑制|结合|促进])(.+?)来"
match = re.search(pattern, text)
if match:
subject = match.group(1) # 阿司匹林
predicate = match.group(3) # 抑制
obj = match.group(4) # 环氧合酶-1
避坑指南:我曾经以为直接用预训练模型就能搞定所有关系,结果发现药物-靶点关系里有很多长尾关系,比如「调节」「修饰」「共价结合」,模型根本识别不出来。最后老老实实把高频关系单独建了规则。
实体关系抽取的质量,直接决定知识图谱的可用性。如果这一步跑偏,后面建出来的图谱就是一坨垃圾。嗯,这句话有点重,但你做一次就知道了。
图数据库简介
好了,三元组有了,怎么存?怎么查?
传统的关系数据库(比如MySQL)不是不能存,但你想想看,如果我想查「阿司匹林通过什么机制影响哪些靶点」,用SQL写关联查询,可能要JOIN四五张表,效率很低。
图数据库就是专门干这个的。
| 对比维度 | 关系数据库 | 图数据库 |
|---|---|---|
| 数据模型 | 表+行+外键 | 节点+边+属性 |
| 多跳查询性能 | 差(需要多次JOIN) | 好(索引邻接关系) |
| 典型操作 | SELECT … JOIN | MATCH … WHERE |
| 适用场景 | 事务性OLTP | 关联分析、路径搜索 |
目前最常用的图数据库是Neo4j。它的查询语言Cypher,写起来就像在画图。
举个例子。我要查阿司匹林抑制的所有靶点:
MATCH (drug:药物 {名称: '阿司匹林'})-[r:抑制]->(target:靶点)
RETURN drug, r, target
你看,跟自然语言的描述几乎一样。这也是图数据库受欢迎的原因之一——查询意图和代码结构高度一致。
我记得第一次用Neo4j时,被Cypher的简洁程度惊艳到了。以前写SQL查三度关系,要递归CTE写半屏代码。现在直接:MATCH (a)-[*1..3]-(b) 搞定。
不过图数据库也不是万能神药。它不适合频繁更新单节点属性的场景,也不适合做聚合统计分析(那种事还是交给关系数据库)。
在药物靶点知识图谱这个领域,图数据库是我们构建和检索知识的主要阵地。后面几章我会带你手把手搭一个Neo4j实例,然后把药物和靶点的三元组数据灌进去。
注意:不要一开始就追求完美的图数据库设计。先用简单模型跑通流程,再根据查询需求做优化。我见过太多人花了一个月设计图模型,最后发现查询性能还不如邻接表——白费功夫。
为了帮你建立起本章的知识体系,我把核心概念和它们之间的关系画成了一张框架图:
这张图把本章的核心知识点串起来了:知识图谱的基础单元是三元组,三元组靠实体关系抽取从文本中获得,最后存到图数据库里供查询和推理。每一步都是下一环节的前提,缺一不可。