知识图谱与 LLM 的实战应用——从本体构建你的第一个知识图谱

0 阅读35分钟

本章涵盖以下内容:

  • 根据具体用例选择最合适的 KG 技术
  • 构建一个用于支持临床医生工作的 KG
  • 在 KG 之上执行分析与基于本体的推理

构建 KG 之所以复杂,是因为必须从不同数据源中抽取并整合信息,而这些数据源在格式(XML、CSV、JSON)、存储技术(关系型或文档型)、信息语法(例如 2022-08-09 或 9 August 2022),尤其是数据含义方面都存在差异。以医疗领域为例,不同表达指向同一概念(如 type 2 diabetesketosis-resistant diabetes)、相同缩写代表不同概念(如 PE 既可能表示 physical examination,也可能表示 pulmonary embolism),以及信息粒度不同(如 necrosislobular necrosis),都会成为数据整合中的障碍。

在构建 KG 时,我们的目标是形成一种统一、扎实且有意义的数据表示方式,把来自不同来源的单条信息整合进一个连贯视图中。与数据含义相关的问题,可以通过语义整合(semantic integration) 来解决。一种常见策略是:采用一个或多个本体作为外来数据的参考模式与词汇表。本体允许你使用一种标准词汇来建模数据,而这一标准词汇包括正式名称、属性、类别以及数据中所描述实体之间的关系等元素。

本体在语义异构的信息之间充当中介。一个映射(mapping)把数据源的本地模式桥接到本体的参考模式上。我们可以把每一个数据元素映射到本体中表达的概念上;这些注释(annotations)能够把来自不同来源的数据要素联结在一起。

本章将提供一套使用参考本体来构建 KG 的指导原则,重点场景是帮助临床医生识别罕见疾病。我们将重点介绍数据理解与准备阶段,导入并处理 Human Phenotype Ontology(HPO)hpo.jax.org/app/)及一个基于 HPO 标注的数据集。HPO 数据源提供了疾病与其相关表型异常之间连接的信息。这些异常代表了可观察的、生理或生化层面的特征,它们偏离了典型人类特征,并且可能由基因突变、环境影响,或两者共同作用所导致。我们还将探讨不同 KG 技术之间的差异,并给出一套选择最合适技术方案的蓝图。最后,我们会展示一组分析方法——包括基于本体的推理——以支持临床医生诊断罕见疾病。

图 3.1 给出了本章的心理模型。图中央展示了在本章示例场景中创建 KG 的具体步骤,底部则展示了一条可适用于不同场景的抽象 KG 构建流水线。这个流水线的组件,来自第 2 章引入的 CRISP-DM 模型的一个变体(并在图 3.2 中再次展示)。

image.png

图 3.1 KG 构建过程的心理模型:它是 CRISP-DM 模型在本章场景中的具体化,从理解业务目标开始,一直到定义用于支持临床医生工作的 KG 查询。

image.png

图 3.2 适配到 KG 的 CRISP-DM 模型。本章重点描述其中若干关键阶段,包括业务理解、数据理解、数据准备,以及 KG 模型的创建/更新。

3.1 构建知识图谱:热身

在真正创建 KG 之前,我们先分析要解决的问题,建立对应用领域的整体理解,并进行数据探查。

3.1.1 业务与领域理解

我们这个 KG 的目标用户画像是临床医生(clinician) :即负责诊断与治疗疾病的医疗专业人员。临床医生最复杂的工作之一,就是根据症状(表型特征)正确识别疾病,尤其是在面对罕见综合征时(见图 3.3)。

image.png

图 3.3 理解业务领域:目标是构建一个支持临床医生工作的 KG。这个阶段与技术实现并不直接相关,但却是后续步骤的根基。

除了通过安排特定检查来完成诊断,临床医生还可以依赖一个对现有信息进行结构化组织的知识库。这个知识库应具备以下两个特征:

  • 对表型领域的上下文化描述——例如,与同一器官或系统相关的表型异常应当被显式连接起来。
  • 描述表型异常与疾病之间关系的数据。这些信息必须具备可追踪性,以便临床医生能够访问这些连接背后的来源。

我们的目标,就是构建一个具备这些特征的 KG。为了更好理解这个应用领域,先给出以下定义。

定义
患病个体的表型,可以理解为该个体所表现出的全部表型特征之和 [1]。

定义
疾病是这样一种实体,它具有以下特征:(1)导致某种特定状态的一组原因;(2)一个时间进程;(3)一组表型特征;以及(4)对特定治疗方式的典型反应。

例如,普通感冒就具有一组明确的表型特征,包括发热和疲劳。它的时间进程通常从几天到一周不等,而阿司匹林等治疗手段则可辅助康复。

但临床医生的工作也充满灰色地带。例如,糖尿病(diabetes mellitus)既可以被视为一种疾病,也可以被视为其他罕见综合征的一个表型特征(见图 3.4)。我们将以这个用例为例,说明如何帮助临床医生处理这种不确定性。

image.png

图 3.4 1 型糖尿病(Type 1 diabetes mellitus)既可以被视为一种疾病,也可以被视为一个表型特征。根据上下文不同,可以使用两个不同的标识符。

3.1.2 数据理解

我们的数据源来自 Human Phenotype Ontology(HPO) 仓库。它为本章示例提供了两类信息(见图 3.5)。第一类信息存储在一个名为 hpo.owl 的 RDF/XML 文件中(purl.obolibrary.org/obo/hp.owl)。这是一个本体,包含关于表型异常的标准化信息。这种标准化使互操作成为可能,也让我们能够把来自多个来源的数据整合起来。清单 3.1 展示了 hpo.owl 文件中与 Type I diabetes mellitus 相关的一部分内容;为了提升可读性,这里把数据序列化成了 Turtle(Terse RDF Triple Language)格式。

image.png

图 3.5 理解用于支持临床医生工作的数据。这个探索阶段用于获取构建 KG 所需的关键信息。

清单 3.1 hpo.owl 中与 Type I diabetes mellitus 相关的细节

obo:HP_0100651 a owl:Class ;   #1
    rdfs:label "Type I diabetes mellitus" ^^xsd:string ; 
    obo:IAO_0000115 "A chronic condition in which the pancreas produces 
        little or no insulin…" ^^xsd:string ;  #2
    oboInOwl:created_by "doelkens"^^xsd:string ;   #3
    oboInOwl:creation_date "2010-12-29T06:37:55Z"^^xsd:string ;
    oboInOwl:hasDbXref "MSH:D003922"^^xsd:string,  #4
        "SNOMEDCT_US:46635009" ^^xsd:string,
        "UMLS:C0011854" ^^xsd:string ;
    oboInOwl:hasExactSynonym "Diabetes mellitus Type I"^^xsd:string,
        "Juvenile diabetes mellitus" ^^xsd:string,
        "Type 1 diabetes",
        "Type I diabetes";
    oboInOwl:hasRelatedSynonym "Insulin-dependent diabetes 
        mellitus"^^xsd:string ;
    oboInOwl:id "HP:0100651"^^xsd:string ;
    rdfs:comment "The onset of type 1 diabetes is typically during 
        adolescence…" ^^xsd:string ;
    rdfs:subClassOf obo:HP_0000819 .   #5
  • #1 将由 URI obo:HP_0100651 标识的 Type I diabetes mellitus 定义为一个本体类
  • #2 用自然语言描述这一疾病
  • #3 给出与该条目作者(“doelkens”)相关的元数据
  • #4 给出其他数据源中指向这种糖尿病形式的标识符
  • #5Type I diabetes mellitus 定义为 URI obo:HP_0000819 所标识的表型特征的一个子类,而该 URI 对应的是 diabetes mellitus

直接阅读 OWL 文件往往比较困难。我们可以使用 Python 的 rdflib 库,把这个文件作为一个由三元组组成的集合来探索。每个三元组都由一个主语、一个谓语和一个宾语构成,如清单 3.2 所示。

清单 3.2 使用 rdflib Python 库处理 OWL 文件

from rdflib import Graph, URIRef
g = Graph()
g.parse("hp.owl", format="xml")

g.bind("obo", "http://purl.obolibrary.org/obo/")
g.bind("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#")
g.bind("rdfs", "http://www.w3.org/2000/01/rdf-schema#")
g.bind("xsd", "http://www.w3.org/2001/XMLSchema#")

subject_uri = URIRef("http://purl.obolibrary.org/obo/HP_0100651")
filtered_statements = g.triples((subject_uri, None, None))
for subject, predicate, obj in filtered_statements:
  print(
        f"({g.qname(subject)}, {g.qname(predicate)}, "
        f"{g.qname(obj) if isinstance(obj, URIRef) else obj})"
    )
  print()

这段脚本的输出如下(为了清晰起见,长字符串已做截断)。

清单 3.3 以三元组集合形式展示的 OWL 文件样例

(obo:HP_0410050, rdf:type, owl:Class)

(obo:HP_0410050, owl:equivalentClass, N25507ac984704bd78a0effd951947a7f)

(obo:HP_0410050, rdfs:subClassOf, obo:HP_0011013)

(obo:HP_0410050, obo:IAO_0000115, A decrease in the level of…)

(obo:HP_0410050, dc:date, 2018-01-27T00:26:24+00:00)

(obo:HP_0410050, dcterms:creator, ns1:0000-0001-5208-3432)

(obo:HP_0410050, oboInOwl:hasExactSynonym, Decreased level of 1,5-AG…)

(obo:HP_0410050, oboInOwl:hasExactSynonym, Decreased level of 1,5-anhydro…)

(obo:HP_0410050, rdfs:label, Decreased level of 1,5 anhydroglucitol in serum)

来自 HPO 仓库的第二类信息,存储在一个制表符分隔值(TSV)文件 phenotype.hpoa 中。它收集了与不同疾病(包括罕见综合征)相关、经过识别、发现并注释的表型特征。这些注释中还包含修饰符,用于说明每种特征的起病年龄与出现频率。下面的清单展示了这个注释文件中的一个样例。

清单 3.4 phenotype.hpoa 文件样例

database_id    disease_name    qualifier    hpo_id    reference    evidence  
onset    frequency    sex    modifier    aspect    biocuration

OMIM:222100    Diabetes mellitus, insulin-dependent-1    
    HP:0410050    PMID:9357814;PMID:17659063;PMID:16731998
    PCS    30/30        P    
    HPO:NicoleVasilevsky[2018-02-23];HPO:NicoleVasilevsky[2018-03-02]

OMIM:222100    Diabetes mellitus, insulin-dependent-1    
    HP:0000103    OMIM:222100    
    IEA                P    HPO:iea[2009-02-17]

这个文件包含以下字段:

  • database_id(OMIM:222100) ——疾病在 OMIM、Orphanet 等本体中的标识符。
  • disease_name(Diabetes mellitus, insulin-dependent-1) ——该疾病在对应本体中的名称。
  • hpo_id(HP:0410050) ——相关表型异常在 HPO 中的标识符。
  • reference(PMID:9357814;PMID:17659063;PMID:16731998) ——用于该注释的信息来源。例如,这可能来自某篇由 PubMed ID(PMID)标识的文献。
  • evidence(PCS) ——支持该注释的证据等级。PCS 表示 published clinical study(已发表临床研究)。
  • frequency(30/30) ——在一组具有共同统计特征的人群中,该异常出现于多少名患者。30/30 表示:在 30 名患有指定疾病的患者中,30 人都被发现具有该 HPO 术语所指的表型异常。
  • aspect(P) ——表型方面。P 表示 phenotypic abnormality(表型异常)。
  • biocuration(HPO:NicoleVasilevsky;HPO:NicoleVasilevsky) ——执行注释的研究中心或用户,以及注释创建日期。

更多细节请参见:mng.bz/EwAo

3.2 理解知识图谱技术

现在我们已经理解了数据,下一阶段就是从现有数据源中摄取并处理数据。但在此之前,我们先考察不同 KG 技术,以便为当前用例做出有依据的选择。

构建 KG 最流行的两种方法,是 资源描述框架(RDF)标签属性图(LPG) 。RDF 是由万维网联盟(W3C)定义并管理的标准框架,用于 Web 上的数据交换。在 RDF 中,每一个陈述(statement)都由三个元素组成:主语(subject)、谓语(predicate)和宾语(object),即一个三元组(triple)。主语是图中的一个节点(顶点),谓语表示一种关系(边),宾语则是另一个节点。这个框架把 KG 建模为一组陈述的集合,并允许我们使用 Web 技术来表示、存储和交换信息。RDF 特别适合用于构建描述特定知识领域的本体。

LPG 则提供了对图数据的高性能、基于查询的遍历能力,以及路径分析特性。它通过附着在图中节点与关系上的键值对结构,来保证数据存储与访问的效率。

在 RDF 中,关系(三元组)是全局定义的,因此某个谓语上的元数据会影响图中该关系的所有实例。为解决这一限制,RDF 支持例如命名图(named graphs) 这样的机制,它允许我们把一组三元组当作一个整体看待,并为其附加上下文化信息。相比之下,LPG 支持节点之间的唯一边,这使得我们可以把元数据和属性直接附着在单个关系实例上。这种模型非常适合表示“边专属”的信息。RDF-DEV Community Group 当前正在推进一种 RDF* (读作 “RDF-star”)规范,它允许用户把属性附加到边上,从而缩小 RDF 与 LPG 技术之间的差距。

LPG 无法像 RDF 那样表达高级语义。为弥补这一点,像 Neo4j 这样的厂商提供了一些工具,用于缩小 RDF 与 LPG 之间的差距。例如,Neosemantics 插件允许我们在 Neo4j 中使用 RDF 及其词汇表(如 OWL、RDFS、SKOS 等)来执行基础推理。其他厂商,比如 Amazon Neptune,则采用另一种策略,使我们能够在 RDF 数据上执行 Cypher 查询(Cypher 是 LPG 图的查询语言)。下一节将从我们的示例用例出发,讨论采用 RDF 与 LPG 的局限与机会。

3.2.1 RDF 还是 LPG?一次以目标为导向的讨论

为了选择构建 KG 的最佳技术,我们需要更好地理解现有信息(在本章中即 HPO 本体与注释数据),并明确目标。前面提到,RDF 特别适合构建本体;这也是为什么 HPO 本体会被序列化为 RDF。HPO 文件的扩展名是 .owl。OWL 是 Web Ontology Language(Web 本体语言) ,其主要目标是增强 RDF 中可用的语义信息,以支持更具表达力的类定义与属性定义。OWL 本体被广泛使用,包括 GPT 和 Claude 在内的许多 LLM 都曾在这类数据上训练过,因此这些模型更容易理解并围绕基于 OWL 的数据进行推理。

在我们的用例中,临床医生并不关心知识究竟是如何建模的:他们关心的是,对表型特征进行无歧义表示,并且最好以层级结构来呈现。注释数据中的核心信息,往往来自科学文献,并表现为某个具体表型特征与某种疾病之间的一次关联案例。例如,连接 “Diabetes Mellitus, Insulin-dependent-1”(OMIM:222100)“Decreased level of 1,5 anhydroglucitol in serum”(HP:0410050) 的这条记录,来源于一项临床研究,标题为 A kinetic mass balance model for 1,5-anhydroglucitol: applications to monitoring of glycemic control [3](PMID: 9357814),并由 Nicole Vasilevsky 于 2018 年 2 月创建。表示这类信息的最佳方式,是把这些细节直接建模为“疾病—表型特征”之间的一条关系。这样一来,我们就能创建多条关系,而每一条关系都可以表示一条特定注释,并带有来源与日期等元数据。

图 3.6 展示了如何把一张表中的一行数据转换成 KG 中的一条边。疾病和表型特征被表示为节点,而关于注释作者、创建日期和来源的信息,则作为边的属性(图中名为 HAS_PHENOTYPIC_FEATURE 的关系)进行表示。

image.png

图 3.6 从表格一行到 KG 一条边的数据转换。表中的信息被改造为图中节点和边的属性。

练习

请尝试为本章示例用例选择最合适的技术,以支持临床医生的工作。这里再次提醒主要需求:

  • 临床医生的目标,是利用现有数据,在诊断疾病(尤其是罕见疾病)时做出更有依据的判断。
  • 临床医生并不关心一个表示整个临床领域的知识库。他们更想看到这样的案例:某些异常表型特征(或其组合)与某些不易识别的疾病之间存在关联。为此,他们需要能查看这些案例的信息,包括来源与日期。
  • 借助这些元数据,临床医生希望能够方便地比较:在所有案例中,某一特定表型特征与某种疾病之间的关联情况。

选择合适技术并没有唯一答案,但选择更适合的方案,会让你更直接地达成既定目标。你也可以把这个练习迁移到其他领域与应用场景中。

3.2.2 用 RDF 与 LPG 表示边属性

从我们的视角看,LPG 是最适合表示这类数据的方案,因为它天然强调“连接某个表型特征与某种疾病的那条边本身所承载的信息”。为了更清楚地说明为什么 LPG 更适合,我们来具体比较一下 RDF 与 LPG。目标是获取与某条注释相关的全部信息(包括来源、作者与创建日期)。如前所述,RDF 也可以通过不同机制来表示这类数据,具体如下。

RDF:N 元关系(N-ARY RELATIONS)

一种用于建模特定边相关数据的标准方法,是采用 n 元关系(n-ary relations) 。使用这种方法时,我们会创建一个新概念来连接相关数据;在本章示例中,这个概念就是一个 annotation(注释) 。请看清单 3.5 中的 RDF 表示,以及清单 3.6 中对应的 SPARQL 查询。

清单 3.5 n 元关系示例

_:Annotation rdf:type :PhenotypicAnnotation ;
    :forDisease       OMIM:222100 ;
    :phenotypicFeature HP:0410050 ;
    :source           PMID:9357814 ;
    :createdBy        "Nicole Vasilevsky" ;
    :creationDate     "2018-02-23"^^xsd:date .

这段 RDF 片段使用 Turtle 语法表示一个表型注释。这个注释被表达为一个空白节点(blank node) _:Annotation,它是一种没有全局标识符的匿名资源,用来把若干相关信息归为一组。你可以把空白节点理解为“某个存在的对象,但它并不需要一个专有名字”,有点类似于编程中的匿名对象。

这个空白节点被声明为 :PhenotypicAnnotation 类型,并把某种疾病(由 OMIM ID 标识)与某个表型特征(来自 HPO)关联起来。附加元数据包括:信息来源(一个 PubMed ID)、注释创建者,以及创建日期。这种结构支持在生物医学数据集中的来源追踪与语义互操作。

清单 3.6 n 元关系场景下的 SPARQL 查询

SELECT ?source ?author ?date
WHERE {
    ?annotation a :PhenotypicAnnotation ;
        :forDisease OMIM:222100 ;
        :phenotypicFeature HP:0410050 ;
        :source ?source ;
        :createdBy ?author ;
        :creationDate ?date .
}

这个 SPARQL 查询会检索某条特定表型注释的元数据。它通过指定疾病(OMIM:222100)与表型特征(HP:0410050)来过滤注释,然后返回信息来源、创建注释的作者,以及创建日期。

在许多场景下,数据使用者能够相对容易地理解这类结构,并适应原始模式中的变化。但随着本体演化,其复杂度也可能不断增加,从而引入向后兼容与长期维护方面的问题。

RDF:命名图(NAMED GRAPHS)

RDF 的命名图会引入第四个元素,用来声明“这个陈述属于某个具名(子)图”,因此这个图本身也可以被当作 RDF 图中的一个节点来处理。这样,我们就可以创建新的陈述,把与注释相关的数据附着到这个命名图之上。清单 3.7 展示了这种方法,对应查询见清单 3.8。

清单 3.7 命名图示例

:Graph1 {
    OMIM:222100 :hasPhenotypicFeature HP:0410050 .
}

:Graph1
    :source PMID:9357814 ;
    :createdBy "Nicole Vasilevsky" ;
    :creationDate "2018-02-23"^^xsd:date .

这个 RDF 示例使用 TriG 语法定义了一个命名图 :Graph1。简单来说,TriG 允许你把一组 RDF 陈述归在一个标签(即命名图)之下,并进一步附加元数据。在这个图中,有一个三元组声明:疾病 OMIM:222100 具有表型特征 HP:0410050。而关于这一断言的元数据,则附着在 :Graph1 上,包括来源(PMID:9357814)、创建者("Nicole Vasilevsky")以及创建日期。

清单 3.8 命名图场景下的 SPARQL 查询

SELECT ?source ?author ?date
WHERE {
    GRAPH :Graph1 {
        OMIM:222100 :hasPhenotypicFeature HP:0410050 .
    }
    :Graph1 :source ?source ;
            :createdBy ?author ;
            :creationDate ?date .
}

这个 SPARQL 查询会检索存储在命名图中的某条特定表型注释的元数据。它首先在 :Graph1 中查找那条断言 “OMIM:222100 hasPhenotypicFeature HP:0410050” 的三元组,然后再查询 :Graph1 自身的元数据,返回来源、作者和创建日期。

尽管命名图在表示上下文化元数据和来源信息方面很强大,但它也会引入额外复杂性。尤其是当命名图数量非常多时,数据存储与交换都可能变得低效。而且,对命名图中单条陈述进行细粒度更新,也会变得困难。

RDF-STAR

如前所述,RDF-star 是 RDF 的一个扩展,它缩小了 RDF 与 LPG 这类属性图模型之间的差距。下面两个清单展示了这种方式。

清单 3.9 RDF-star 示例

<<OMIM:222100 :hasPhenotypicFeature HP:0410050>>
    :source PMID: 9357814 ;
    :createdBy "Nicole Vasilevsky" ;
    :creationDate2018-02-23”^^xsd:date .

清单 3.10 RDF-star 场景下的 SPARQL-star 查询示例

SELECT ?source ?author ?date {
    <<OMIM:222100 :hasPhenotypicFeature HP:0410050>>
        :source ?source ;
        :createdBy ?author ;
        :creationDate ? date .
}

RDF-star 向“给边附加属性”迈进了一步,并且其 SPARQL 查询形式也更易读。不过,它的查询性能仍有待提升;而且正如 Orlandi 等人 [2] 所指出的那样:“采用新的语法扩展,要求 RDF 引擎提供专门实现,因此会限制这种方法的普及。”

此外,RDF 中还存在其他给陈述加注释的方法,例如重述(reification)单例属性(singleton properties) 。不过在现实世界应用中,这些方法相对使用较少,开发者通常更偏好可扩展性和可维护性更高的替代方案,如命名图与 n 元关系。

LPG

LPG 方法则把注释细节直接表示在关系内部,采用键值对形式。下面给出一个这种建模方式的示例,以及相应的 Cypher 查询。

清单 3.11 LPG 表示示例

(d { id: "OMIM:222100" })
-[:HAS_PHENOTYPIC_FEATURE {
        source: "PMID:9357814"
        createdBy: "Nicole Vasilevsky";
        creationDate: "2018-02-23}]->
(p { id: "HP:0410050" })

这里两个节点分别表示两个实体:一种疾病(OMIM:222100)和一个表型(HP:0410050)。关系 :HAS_PHENOTYPIC_FEATURE 把它们连接起来,并在关系上附着键值对属性,用于描述注释来源("PMID:9357814")、创建者("Nicole Vasilevsky")以及创建日期("2018-02-23")。

清单 3.12 Cypher 查询示例

MATCH (d)-[r:HAS_PHENOTYPIC_FEATURE]->(p)
WHERE d.id = "OMIM:222100" and p.id = "HP:0410050"
RETURN r.source, r.createdBy, r.creationDate

这个 Cypher 查询会检索附着在疾病节点与表型节点之间 :HAS_PHENOTYPIC_FEATURE 关系上的元数据。它在图中匹配这一模式,基于节点 ID 进行过滤,并返回存储在关系中的注释细节。

如这些示例所示,LPG 模型非常适合以一种富有表达力且易于访问的方式来建模带有丰富元数据的关系。基于这些原因,我们将在后续系统中采用 LPG 与 Cypher 作为构建 KG 的核心工具。

3.3 构建知识图谱

现在进入真正构建第一个 KG 的细节部分。这个过程分为两个步骤:首先加载本体,然后以该本体为参考导入数据源。

注意
要构建这个 KG,你可以运行 GitHub 仓库中的代码(github.com/alenegro81/…),或者使用 Neo4j browser 测试本节中的 Cypher 查询。代码已在以下环境中测试通过:Neo4j(5.20.0 Enterprise Edition,通过 Neo4j Desktop 1.6.1 安装)、APOC 库(5.20.0)以及 Neosemantics 插件(5.20.0)。Neo4j 及其插件的安装细节见在线附录 B。我们会解释每一条查询,但默认你已经具备 Cypher 查询语言的基本理解。本节结果基于 2025 年 2 月可用的 HPO 版本。

3.3.1 使用 neosemantics 导入并处理本体

图 3.7 展示了本体导入与处理阶段。第一步,是使用如下命令创建并初始化 HPO 数据库。

清单 3.13 在 Neo4j 中创建 HPO 数据库

CREATE DATABASE hpo IF NOT EXISTS

image.png

图 3.7 本体导入与处理

接下来的清单中,我们建立约束,以保证被标记为 Resource 的节点,其 uriid 属性是唯一的。我们还会为 HpoPhenotypeHpoDisease 节点的 id 属性建立索引,以提升 KG 构建阶段及后续信息检索时的访问效率。HpoPhenotypeHpoDisease 标签分别用于定义我们的表型异常节点与疾病节点。

清单 3.14 创建约束与索引

CREATE CONSTRAINT n10s_unique_uri IF NOT EXISTS FOR (r:Resource) REQUIRE r.uri IS UNIQUE;
CREATE CONSTRAINT IF NOT EXISTS FOR (n:Resource) REQUIRE (n.id) IS UNIQUE;
CREATE INDEX disease_id IF NOT EXISTS FOR (n:HpoDisease) ON (n.id);
CREATE INDEX phenotype_id IF NOT EXISTS FOR (n:HpoPhenotype) ON (n.id);

第二步,是为 Neosemantics 组件设置初始配置。

清单 3.15 配置 Neosemantics 插件

CALL n10s.graphconfig.init();
CALL n10s.graphconfig.set({ handleVocabUris: "IGNORE" });
CALL n10s.graphconfig.set({ applyNeo4jNaming: True });

这个配置定义了两条主要规则,用于控制导入过程。第一条规则是在导入阶段忽略命名空间(命名空间常用于区分使用相似表达的不同本体)。第二条规则会把关系类型编码为大写形式,以符合 LPG 关系表示的常见规范。

下一步是加载 HPO 词汇表。

清单 3.16 将 HPO 词汇表加载到 Neo4j

CALL n10s.rdf.import.fetch("http://purl.obolibrary.org/obo/hp.owl","RDF/XML");

在我们的测试中,这条命令将 899,558 条陈述加载进了 Neo4j。在处理并加载注释数据之前,我们可以先给节点添加 HpoPhenotype 标签,并根据资源的原始 URI 计算出 id 属性。

清单 3.17 丰富节点属性

MATCH (n:Resource) 
WHERE n.uri STARTS WITH "http://purl.obolibrary.org/obo/HP" 
SET n:HpoPhenotype, 
       n.id = coalesce(n.id,
     replace(apoc.text.replace(n.uri,'(.*)obo/',''),'_', ':'))    #1
  • #1n.id 设置为类似 HP:0000001 这样的格式

现在,让我们看看此时 KG 的状态。清单 3.18 展示了用于获取这部分图数据的代码,其结果在图 3.8 中可视化。你可以在 Neo4j browser 中运行这段代码来探索。

image.png

图 3.8 使用 LPG 作为存储模型加载到图库中的一部分 HPO 本体。我们可以区分出两类信息:本体信息(左侧)与与表型特征相关的领域信息(右侧)。

清单 3.18 展示当前阶段 KG 的一部分

MATCH path1=(n:HpoPhenotype)<-[:SUBCLASSOF]-(m:HpoPhenotype)
WHERE n.label = "Diabetes mellitus"
WITH path1
MATCH path2=(i:HpoPhenotype)<-[:ANNOTATEDSOURCE]-(j)
WHERE i.label in ["Diabetes mellitus", "Type I diabetes mellitus"]
WITH path1, path2, j
MATCH path3=(j)-[:ANNOTATEDPROPERTY|HASSYNONYMTYPE]-()
RETURN path1, path2, path3

警告
清单 3.18 中的查询,只有在你严格按章节步骤一步一步执行时才会正常工作。如果你直接运行仓库中的完整导入流程代码,那么由于最后的数据清理阶段,这个查询会失败。

HPO 本体提供了多种类型的信息。图 3.8 左侧展示的是节点性质相关的本体信息,右侧则展示了与糖尿病层级连接相关的细节。

3.3.2 注释数据导入与处理

要完成 KG 的构建,我们还必须导入并处理注释文件。该文件中的表型异常与其相关疾病相连,而这些疾病术语来自其他本体。图 3.9 展示了数据处理与建模的第二阶段。

image.png

图 3.9 导入并处理注释数据集,以完成 KG 构建

与使用 RDF 数据模型生成的 hpo.owl 文件不同,接下来的文件采用 HPO annotation(HPOA) 格式(mng.bz/NwQN),其本质上是一个制表符分隔值(TSV)文件。HPOA 文件包含以下有价值的信息:

  • 疾病与多个表型特征/异常之间的显式关联
  • 支持这种关联的证据,例如它是来自电子注释推断,还是来自已发表临床研究或可追踪作者陈述
  • 起病年龄
  • 疾病与表型特征共同出现的频率
  • 描述本体来源的附加元数据

处理这个 TSV 文件,使我们能够在已有知识基础上整合不同类型的文件。清单 3.19–3.24 中的 Cypher 查询,用于从 GitHub 上的注释文件中加载、处理并整合信息。首先,我们创建疾病节点。

清单 3.19 创建 HpoDisease 节点

LOAD CSV FROM 'https://mng.bz/qRyr' AS row
FIELDTERMINATOR '\t'
WITH row
SKIP 5     #1
MERGE (dis:Resource:HpoDisease {id: row[0]})
ON CREATE SET dis.label = row[1];
  • #1 跳过文件前五行,因为它们属于文件元数据

接下来,我们创建疾病节点与表型特征节点之间的关系。

清单 3.20 在 HpoDisease 与 HpoPhenotype 节点之间创建关系

LOAD CSV FROM 'https://mng.bz/qRyr' AS row
FIELDTERMINATOR '\t'
WITH row
SKIP 5
MATCH (dis:HpoDisease)
WHERE dis.id = row[0]
MATCH (phe:HpoPhenotype)
WHERE phe.id = row[3]
MERGE (dis)-[:HAS_PHENOTYPIC_FEATURE]->(phe)

创建这些关系,就把来自 hpo.owl 与 phenotype.hpoa 两个文件中的信息整合到了一起。下面这段代码用于查询整合后的结果。

清单 3.21 查找关联关系

MERGE (dis:HpoDisease)-[:HAS_PHENOTYPIC_FEATURE]->(phe:HpoPhenotype)
RETURN dis.label, collect(phe.label)
LIMIT 3

这个查询的结果展示在表 3.1 中。

表 3.1 HpoDisease 节点与 HpoPhenotype 节点之间的样例关联

HpoDisease 条目关联的 HpoPhenotype 条目
Developmental and epileptic encephalopathy 96Hydrops fetalis, Autosomal dominant inheritance, Death in infancy, Epileptic spasm, Primary microcephaly, EEG with burst suppression, Intellectual disability, profound, Small for gestational age, Epileptic encephalopathy, Neonatal respiratory distress, Tonic seizure
Pseudohyperkalemia, familial, 2, due to red cell leakGeneralized muscle weakness, Hyperkalemia, Periodic paralysis, Muscle spasm, Hemolytic anemia, Hand tremor, Autosomal dominant inheritance
Immunoglobulin kappa light chain deficiencyChronic diarrhea, Recurrent infections, Recurrent respiratory infections, Absent circulating immunoglobulin kappa chain, Childhood onset, Diarrhea, Autosomal recessive inheritance

接下来这段代码,会以键值对形式给这些关系添加属性。

清单 3.22 给 HAS_PHENOTYPIC_FEATURE 关系添加属性

LOAD CSV FROM 'https://mng.bz/qRyr' AS row
FIELDTERMINATOR '\t'
WITH row
SKIP 5
MATCH (dis:HpoDisease)-[rel:HAS_PHENOTYPIC_FEATURE]->(phe:HpoPhenotype)
WHERE phe.id = row[3] and dis.id = row[0]
FOREACH(_ IN CASE WHEN row[4] is not null THEN [1] ELSE [] END|
  SET rel.source = row[4])
FOREACH(_ IN CASE WHEN row[5] is not null THEN [1] ELSE [] END|
  SET rel.evidence = row[5])
FOREACH(_ IN CASE WHEN row[6] is not null THEN [1] ELSE [] END|
  SET rel.onset = row[6])
FOREACH(_ IN CASE WHEN row[7] is not null THEN [1] ELSE [] END|
  SET rel.frequency = row[7])
FOREACH(_ IN CASE WHEN row[8] is not null THEN [1] ELSE [] END|
  SET rel.sex = row[8])
FOREACH(_ IN CASE WHEN row[9] is not null THEN [1] ELSE [] END|
  SET rel.modifier = row[9])
FOREACH(_ IN CASE WHEN row[10] is not null THEN [1] ELSE [] END|
  SET rel.aspect = row[10])
FOREACH(_ IN CASE WHEN row[11] is not null THEN [1] ELSE [] END| 
  SET rel.biocuration = row[11])

这是一种非常灵活的关系富化方式。这个脚本会匹配 Neo4j 图中已有的节点与关系,并根据输入文件中每一行的值是否存在,来为关系设置附加属性。每个 FOREACH 块都只会在对应 TSV 列不为 null 时,才向关系上写入一个新属性。这使脚本能够优雅应对缺失数据,并避免用 null 覆盖已有值。

接下来,我们进一步处理这些关系属性,以澄清疾病与表型特征之间关系属性的语义。

清单 3.23 给 HAS_PHENOTYPIC_FEATURE 增加更多可读属性

CALL apoc.periodic.iterate(
  "MATCH (dis:HpoDisease)-[rel:HAS_PHENOTYPIC_FEATURE]->(phe:HpoPhenotype) 
   RETURN rel",
  "SET rel.createdBy = apoc.text.regexGroups(
       rel.biocuration, 'HPO:(\w+)\['
     )[0][1],
   rel.creationDate = apoc.text.regexGroups(
       rel.biocuration, '\[(\d{4}-\d{2}-\d{2})\]'
     )[0][1],
   rel.aspectName = CASE
     WHEN rel.aspect = 'P' THEN 'Phenotypic abnormality'
     WHEN rel.aspect = 'I' THEN 'Inheritance'
   END,
   rel.aspectDescription = CASE
     WHEN rel.aspect = 'P' THEN
       'Terms with the P aspect are located in the Phenotypic abnormality ' +
       'subontology'
     WHEN rel.aspect = 'I' THEN
       'Terms with the I aspect are from the Inheritance subontology'
   END,
   rel.evidenceName = CASE
     WHEN rel.evidence = 'IEA' THEN
       'Inferred from electronic annotation'
     WHEN rel.evidence = 'PCS' THEN
       'Published clinical study'
     WHEN rel.evidence = 'TAS' THEN
       'Traceable author statement'
   END,
   rel.evidenceDescription = CASE
     WHEN rel.evidence = 'IEA' THEN
       'Annotations extracted by parsing the Clinical Features sections ' +
       'of the Online Mendelian Inheritance in Man resource are assigned ' +
       'the evidence code IEA.'
     WHEN rel.evidence = 'PCS' THEN
       'PCS is used for information extracted from articles in the medical ' +
       'literature. Generally, annotations of this type will include the ' +
       'pubmed id of the published study in the DB_Reference field.'
     WHEN rel.evidence = 'TAS' THEN
       'TAS is used for information gleaned from knowledge bases such as ' +
       'OMIM or Orphanet that have derived the information from a ' +
       'published source.'
   END,
   rel.url = CASE
     WHEN rel.source STARTS WITH 'PMID:' THEN
       'https://pubmed.ncbi.nlm.nih.gov/' + apoc.text.replace(
         rel.source, '(.*)PMID:', ''
       )
     WHEN rel.source STARTS WITH 'OMIM:' THEN
       'https://omim.org/entry/' + apoc.text.replace(
         rel.source, '(.*)OMIM:', ''
       )
   END",
  {batchSize: 1000}
)

这个查询使用 apoc.periodic.iterate 以批处理方式更新 HAS_PHENOTYPIC_FEATURE 关系。比如,它会通过正则表达式从 biocuration 属性中提取出标注者与创建日期,从而生成更清晰的元数据。它还会添加一些有助于图浏览时提升可读性的属性。注释文件中与 aspect 相关的信息使用简写(P 或 I),evidence 也是简写(IEA、PCS 或 TAS)。为了让这些信息更易于人类理解,我们增加了诸如 aspectName 这样的属性,其值可以是 'Phenotypic abnormality''Inheritance'。目标就是让信息更便于阅读和理解。

构建 KG 的最后一步,是执行清理:删除那些来自本体、但对我们的具体目标并不必要的节点和关系。

清单 3.24 清理 KG,删除不必要的节点与关系

CALL apoc.periodic.iterate(
    "MATCH (n:Resource) RETURN id(n) as id",
    "MATCH (n)
     WHERE id(n) = id AND
           NOT 'HpoPhenotype' in labels(n) AND
           NOT 'HpoDisease' in labels(n)
     DETACH DELETE n",
     {batchSize:10000})
YIELD batches, total return batches, total

3.4 查询数据

现在,临床医生可以把这个 KG 当作诊断罕见疾病的辅助工具使用,首先从识别患者的表型异常开始。通过输入具体特征,临床医生就能够查询 KG,以识别相关罕见病。这一查询阶段,正是我们心理模型中的最后一步,如图 3.10 所示。

image.png

图 3.10 查询已构建好的 KG,以支持临床医生的工作

设想这样一个场景:一位临床医生接诊了一名患有 1 型糖尿病的男孩。患者的临床病史以电子健康档案(EHR)的形式存储在医院数据库中。医院已经拥抱了 KG 驱动的范式变化,因此患者信息是使用 HPO 与 OMIM(一个关于遗传疾病与罕见病的在线目录)中的术语来存储的。由于 1 型糖尿病既可被归类为表型特征,也可被归类为疾病,所以系统中会使用两个不同标识符来保存这一信息:

临床医生已经识别出患者具有典型的 1 型糖尿病表型特征,这些特征也可以通过下列查询在 KG 中探索。图 3.11 展示了结果。

image.png

图 3.11 查询与 1 型糖尿病相关的所有表型特征后的结果

清单 3.25 查询与 1 型糖尿病相关的表型特征

MATCH path=(dis:HpoDisease)-[:HAS_PHENOTYPIC_FEATURE]->(phe:HpoPhenotype)
WHERE dis.id = "OMIM:222100"
RETURN path

中心节点定义了 1 型糖尿病,周围节点则表示与其相关联的表型特征。然而,在医疗检查过程中,临床医生还发现了一些新的症状,它们也被归类为表型特征,但并未直接连接到 1 型糖尿病:

此时,临床医生希望利用 KG 中的信息,找出与这些表型特征相关的其他疾病。为此,他执行了如下查询,而结果列在表 3.2 中。

清单 3.26 查找与特定表型特征相关的疾病

MATCH (phe:HpoPhenotype)
WHERE phe.label IN [  "Growth delay",   "Large knee",   "Sensorineural hearing impairment",   "Pruritus",   "Type I diabetes mellitus"]
WITH phe
MATCH path=(dis:HpoDisease)-[:HAS_PHENOTYPIC_FEATURE]->(phe)
UNWIND dis as nodes
RETURN
  dis.id as disease_id, 
  dis.label as disease_name,
  collect(phe.label) as features,
  count(nodes) as num_of_features
ORDER BY num_of_features DESC, disease_name
LIMIT 5

表 3.2 与临床医生识别出的表型特征最匹配的疾病

disease_iddisease_namefeaturesnum_of_features
OMIM:619269Ondontochondrodysplasia 2 with hearing loss and diabetesGrowth delay, Sensorineural hearing impairment, Pruritus, Large knee, Type I diabetes mellitus5
OMIM:618500Holoprosencephaly 12 with or without pancreatic agenesisSensorineural hearing impairment, Growth delay, Type I diabetes mellitus3
OMIM:6147003-methylglutaconic aciduria, type VIIIGrowth delay, Sensorineural hearing impairment2
OMIM:616192Alobar holoprosencephalyGrowth delay, Sensorineural hearing impairment2
OMIM:602782Alpha-Thalassemia/mental retardation syndrome, X-linkedGrowth delay, Sensorineural hearing impairment2

这些结果最终引导医生做出诊断:Ondontochondrodysplasia 2 with hearing loss and diabetes。基于这些结果,临床医生还可以继续深挖,进一步确定这些表型特征与该疾病关联的频率,并找到更多潜在信息来源。

练习

扩展清单 3.26 中的查询,使其同时返回关系属性:evidence_nameevidence_descriptionsourceurl

3.5 在 KG 之上进行推理

在前面的例子中,我们展示了如何从 KG 中已存储的信息里直接获得结果。然而,KG 最强大的能力之一,其实是推断(inference) :也就是基于逻辑规则、通过演绎推理(见第 2 章)从隐含信息中导出结果。比如,考虑这样一个问题:哪些疾病具有内分泌系统异常?

有些注释与这个表型特征是直接连接的。但临床医生往往还会关心那些更具体的、涉及甲状腺等细分部位的表型特征。为此,我们可以利用 HPO 的层级表示。下面的查询会检索代表“内分泌系统异常”(id=HP:0000818)子类的一部分表型特征。

清单 3.27 查找内分泌系统异常的子类

MATCH (p:HpoPhenotype)<-[:SUBCLASSOF*1..3]-(n:HpoPhenotype)   #1
WHERE p.id = "HP:0000818"
RETURN p,n
  • #1 查找所有比另一个表型节点 p 更具体、且位于其 1 到 3 层子类层级之下的表型节点 n

借助这一层级结构,我们就可以通过下列 Neosemantics 过程(清单 3.28)推断那些与“内分泌系统异常”存在隐式连接的注释。表 3.3 展示了其中一部分结果。

清单 3.28 查找与异常子类相关的表型特征

MATCH (cat:HpoPhenotype {label: "Abnormality of the endocrine system"})   #1
CALL n10s.inference.nodesInCategory(cat, { 
  inCatRel: "HAS_PHENOTYPIC_FEATURE", 
  subCatRel: "SUBCLASSOF"})   #2
YIELD node as dis
WHERE dis.label IN [
  "Congenital atransferrinemia",
  "Deafness, autosomal recessive 4, with enlarged vestibular aqueduct",
  "Diabetes mellitus, transient neonatal, 1",
  "Edema, familial idiopathic, prepubertal",
  "Familial dysalbuminemic hyperthyroxinemia"
]    #3
MATCH (dis)-[:HAS_PHENOTYPIC_FEATURE]->(phe:HpoPhenotype)   #4
RETURN dis.label as disease, collect(DISTINCT phe.label) as features
ORDER BY size(features) ASC, disease
  • #1 找到顶层表型节点
  • #2 获取与这一表型直接或间接相连的疾病
  • #3 只保留选定疾病,以保证输出结果可复现
  • #4 匹配这些疾病对应的表型特征

表 3.3 与“内分泌系统异常”表型特征隐式相连的一部分注释结果。直接属于这一表型特征,或可被推断为其子类的表型特征,将以加粗方式标出。

diseasefeatures
Congenital atransferrinemiaAnemia, Abnormality of the pancreas, Recurrent infections, Arthritis, Abnormality of the cardiovascular system, Hypothyroidism
Deafness, autosomal recessive 4, with enlarged vestibular aqueductEnlarged vestibular aqueduct, Congenital onset, Goiter, Autosomal recessive inheritance, Incomplete partition of the cochlea type II, Sensorineural hearing impairment
Diabetes mellitus, transient neonatal, 1Transient neonatal diabetes mellitus, Autosomal dominant inheritance, Dehydration, Hyperglycemia, Intrauterine growth retardation, Severe failure to thrive
Edema, familial idiopathic, prepubertalDiabetes mellitus, Abnormality of the genitourinary system, Irritability, Vomiting, Autosomal dominant inheritance, Edema
Familial dysalbuminemic hyperthyroxinemiaAbnormal circulating free T4 concentration, Abnormal thyroid-stimulating hormone level, Autosomal dominant inheritance, Autosomal recessive inheritance, Euthyroid hyperthyroxinemia, Increased circulating free T4 concentration

这些结果说明:通过围绕子类关系和表型特征进行推理,我们可以在一个由本体驱动的图中挖掘出有意义的疾病关联。Neosemantics 插件的使用,展示了语义推断在丰富生物医学查询方面的强大能力——它让我们不仅能停留在直接连接上,还能够真正利用领域知识的结构。

小结

  • KG 构建是一个复杂过程,它要求你首先明确要解决的问题、理解参考领域,并完成包括数据探查、探索与理解在内的阶段。
  • 最终构建出的 KG,必须是对不同来源数据的一种统一、扎实且有意义的表示,其中各条独立信息被融合为一个整体视图。
  • 资源描述框架(RDF)与标签属性图(LPG)是构建 KG 最重要的两种技术。
  • RDF 数据模型强调知识表示,尤其适合用于构建本体。
  • LPG 方法则擅长基于查询的高效图遍历与路径分析,更强调数据存储与访问效率。
  • 理解 RDF 与 LPG 之间的差异,对于针对具体目标选择最佳技术方案至关重要。