游乐游手机版
首页/AI教程/文章详情

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

时间:2026-08-17 11:11
基于临床医生诊断罕见病场景,采用人体表型本体(HPO)构建知识图谱,通过语义整合映射异构数据,形成统一数据表示,并利用本体推理支持疾病分析与诊断,提升罕见病诊断效率与准确性。
这一章的内容,可以说是从“为什么要建知识图谱”一路讲到“怎么建、怎么用”,有不少实操干货。我们先快速过一下:核心是讲如何根据具体场景选择合适的图技术,然后带着大家一步一步构建一个能帮临床医生诊断罕见病的知识图谱,最后在这个图谱上做一些分析和推理。 说起构建知识图谱,这事儿之所以复杂,根源在于数据来源五花八门。不只是格式(XML、CSV、JSON)和存储方式(关系型数据库还是文档数据库)不一样,更头疼的是**含义层面**的差异。拿医疗领域举例,同一概念有不同说法(比如“2型糖尿病”和“酮症抵抗性糖尿病”),同一个缩写却指向不同概念(比如“PE”可以是“体格检查”,也可以是“肺栓塞”),还有信息粗细粒度的问题(“坏死”与“小叶坏死”的区别)。这些都是在数据整合时必然会遇到的障碍。 我们构建KG的目标,说到底就是要形成一套**统一、扎实、有意义的数据表示**,把散落在各个角落的信息碎片拼接到一个连贯的整体视图里。解决数据含义差异的钥匙,是**语义整合**。一个常见的策略就是引入本体,把它当作外来数据的参考模式和标准化词汇表。本体提供了一套标准词汇来建模数据,包括正式名称、属性、类别以及实体之间的关系等要素。 本体在语义异构的信息之间扮演着“翻译官”的角色。通过**映射**,把数据源的本地方言翻译成本体世界的普通话。每个数据元素都能映射到本体中对应的概念上,这些**注释**就像桥梁一样,把不同来源的数据要素紧密地连接在一起。 好,言归正传。这一章的目标,就是提供一套使用参考本体来构建KG的实操指南,重点场景是**帮助临床医生识别罕见疾病**。我们会重点讲解数据理解与准备阶段,具体会用到一个人体表型本体(HPO)以及基于它标注的数据集。HPO这个数据源包含了疾病和它相关表型异常之间的关联信息。所谓表型异常,指的是那些偏离了正常人类特征的、可观察的生理或生化层面的特征,可能是基因突变、环境影响,或者两者共同作用导致的。此外,我们也会聊聊不同KG技术的差异,并提供一个选择最合适技术方案的流程。最后,我们会演示一套分析方法,包括基于本体的推理,来支持临床医生诊断罕见病。 图3.1给出了这一章的心理模型,直观展示了构建KG的具体步骤,以及一条通用的抽象KG构建流水线。 图 3.1 KG构建过程的心理模型:它是CRISP-DM模型在本章场景中的具体化,从理解业务目标开始,一直到定义用于支持临床医生工作的KG查询。 图 3.2 适配到KG的CRISP-DM模型。本章重点描述其中若干关键阶段,包括业务理解、数据理解、数据准备,以及KG模型的创建/更新。 ### 3.1 构建知识图谱:热身 在真正动手敲代码之前,我们先得静下心来分析一下要解决的问题,建立对应用领域的整体理解,并进行数据探查。 #### 3.1.1 业务与领域理解 我们这个KG的目标用户很明确:**临床医生**,也就是那些负责诊断和治疗疾病的医疗专业人员。他们工作中最棘手的挑战之一,就是根据症状(也就是表型特征)来准确识别疾病,尤其是在面对那些罕见的综合征时(见图3.3)。 图 3.3 理解业务领域:目标是构建一个支持临床医生工作的KG。这个阶段与技术实现并不直接相关,但却是后续步骤的根基。 除了安排特定检查,临床医生还需要一个能把现有信息结构化组织起来的知识库。这个知识库需要具备两个特征: * 对表型领域进行**上下文化描述**。比如,和同一个器官或系统相关的表型异常,应该被显式地关联在一起。 * **描述表型异常与疾病之间关系的数据**。而且这些信息必须是可追溯的,临床医生能随时查看到这些关联背后的来源证据。 我们的目标,就是构建一个满足这些条件的KG。为了更好地理解这个应用领域,先给出两个定义。 **定义** 患病个体的表型,可以理解为该个体所表现出的全部表型特征的集合 [1]。 **定义** 疾病是这样一种实体,它具备以下特征:(1) 导致某种特定状态的一组原因;(2) 一个时间进程;(3) 一组表型特征;(4) 对特定治疗方式的典型反应。 比如普通感冒,它就有发热、疲劳这些明确的表型特征,时间进程通常几天到一周,吃点头痛药就能帮助恢复。 但临床医生的世界里充满了灰色地带。就拿糖尿病来说,它本身可以看作一种疾病,但同时又可能是其他更罕见的综合征的一个表型特征(见图3.4)。我们后面的例子就会围绕这种不确定性展开,看看KG能帮上什么忙。 图 3.4 1型糖尿病(Type 1 diabetes mellitus)既可以被视为一种疾病,也可以被视为一个表型特征。根据上下文不同,可以使用两个不同的标识符。 #### 3.1.2 数据理解 我们的数据源来自HPO仓库。它为本章的示例提供了两大类信息(见图3.5)。第一类信息存放在一个叫`hpo.owl`的RDF/XML文件里。这是一个标准化的本体,包含了关于表型异常的标准化信息。这种标准化是让不同数据源之间实现互操作,并把它们整合起来的基础。清单3.1展示了`hpo.owl`文件中与1型糖尿病相关的一部分内容,为了方便阅读,我们把数据序列化成了Turtle格式。 图 3.5 理解用于支持临床医生工作的数据。这个探索阶段用于获取构建KG所需的关键信息。 **清单 3.1 hpo.owl中与Type I diabetes mellitus相关的细节** ```turtle 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` 标识的“1型糖尿病”定义为一个本体类。 * `#2` 用自然语言描述了这种疾病。 * `#3` 给出了与该条目作者(“doelkens”)相关的元数据。 * `#4` 给出了其他数据源中指向这种糖尿病形式的标识符。 * `#5` 将“1型糖尿病”定义为URI `obo:HP_0000819` 所标识的表型特征的一个子类,而该URI对应的正是“糖尿病”。 直接阅读OWL文件常常会让人头疼。我们可以借助Python的`rdflib`库,把这个文件当作一个由三元组组成的集合来探索。每个三元组都包含主语、谓语和宾语三个部分,如清单3.2所示。 **清单 3.2 使用rdflib Python库处理OWL文件** ```python from rdflib import Graph, URIRef g = Graph() g.parse("hp.owl", format="xml") g.bind("obo", "https://purl.obolibrary.org/obo/") g.bind("rdf", "https://www.w3.org/1999/02/22-rdf-syntax-ns#") g.bind("rdfs", "https://www.w3.org/2000/01/rdf-schema#") g.bind("xsd", "https://www.w3.org/2001/XMLSchema#") subject_uri = URIRef("https://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文件样例** ```turtle (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仓库的第二类信息,存放在一个制表符分隔值文件 `phenotype.hpoa` 里。这个文件收集了与不同疾病(包括罕见综合征)相关的、已经被识别和注释过的表型特征。注释中还带有修饰符,比如发病年龄和出现频率。下面的清单展示了一个样例。 **清单 3.4 phenotype.hpoa文件样例** ```csv 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标识的文献。 * `evidence`(`PCS`):支持该注释的证据等级。`PCS`表示“已发表临床研究”。 * `frequency`(`30/30`):在一组具有相同统计特征的人群中,该异常出现在多少名患者身上。`30/30`意味着在30名患有该病的患者中,30人都表现出了这个HPO术语所指的表型异常。 * `aspect`(`P`):表型方面。`P`表示“表型异常”。 * `biocuration`(`HPO:NicoleVasilevsky;HPO:NicoleVasilevsky`):执行注释的研究中心或用户,以及注释创建日期。 更多细节可以参见:mng.bz/EwAo ### 3.2 理解知识图谱技术 理解了数据之后,下一步就是把这些数据从源头摄取并处理。但在动手之前,我们得先把不同KG技术搞清楚,这样才能为当前用例做出有依据的选择。 构建KG最流行的两种方法,是**资源描述框架(RDF)**和**标签属性图(LPG)**。RDF是由万维网联盟(W3C)定义的、用于Web数据交换的标准框架。在RDF里,每个陈述都由三个元素构成:主语、谓语和宾语,也就是一个三元组。主语是图中的一个节点(顶点),谓语表示一种关系(边),宾语是另一个节点。这个框架把KG建模为一组陈述的集合,允许我们用Web技术来表示、存储和交换信息。RDF特别擅长用来构建描述特定知识领域的**本体**。 而LPG则提供了更高效的、基于查询的图遍历能力,以及强大的路径分析功能。它通过在节点和关系上附着键值对结构来实现高效的数据存储和访问。 在RDF里,关系(三元组)是全局定义的,这意味着某个谓语上的元数据会影响图中所有该关系的实例。为了解决这个问题,RDF提供了**命名图**这样的机制,允许我们把一组三元组看作一个整体,并为它附加上下文化信息。相比之下,LPG天然支持节点之间的唯一边,所以我们可以把元数据和属性直接附着在单个关系实例上。这种模型非常适合用来表示“这条边专属”的信息。值得一提的是,RDF-DEV Community Group正在推进的RDF-star规范,允许把属性附加到边上,这正在缩小RDF和LPG之间的技术差距。 LPG的短板在于它不像RDF那样能表达高级语义。为此,像Neo4j这样的厂商提供了工具来弥补这个差距,比如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本体语言)的主要目标就是增强RDF中的语义信息,让我们能定义更富有表达力的类和属性。OWL本体被广泛应用,包括GPT和Claude在内的许多大语言模型都在这类数据上训练过,所以它们也更容易理解和推理基于OWL的数据。 在我们的用例中,临床医生其实并不关心知识具体是怎么建模的。他们关心的是:表型特征能被无歧义地表示出来,最好还能以层级结构呈现。注释数据中的核心信息,往往来自科学文献,表现为某个具体表型特征与某种疾病之间的一次关联案例。例如,将“胰岛素依赖型糖尿病1型”(OMIM:222100)与“血清1,5-脱水葡萄糖醇水平降低”(HP:0410050)连接起来的那条记录,源于一项名为《1,5-脱水葡萄糖醇的动力学质量平衡模型:在血糖控制监测中的应用》[3](PMID: 9357814)的临床研究,并由Nicole Vasilevsky在2018年2月创建。表示这类信息的最佳方式,就是把注释的细节(来源、作者、日期)直接建模为“疾病—表型特征”这条关系上的属性。这样,我们就能创建多条关系,每一条都代表一条特定注释,并携带其元数据。 图3.6展示了如何把表中的一行数据转换成KG中的一条边。疾病和表型特征被表示为节点,而注释作者、创建日期和来源等信息,则作为边(这里叫`HAS_PHENOTYPIC_FEATURE`关系)的属性来存储。 图 3.6 从表格一行到KG一条边的数据转换。表中的信息被改造为图中节点和边的属性。 **练习** 请尝试为本章的示例用例选择最合适的技术。再回顾一下主要需求: * 临床医生的目标是利用现有数据,在诊断疾病(尤其是罕见病)时做出更明智的判断。 * 他们并不需要一个覆盖整个临床领域的庞大知识库。他们更想看到这样的案例:某些异常的表型特征(或组合)与某些难以识别的疾病之间存在关联。为此,他们需要能查看这些案例的详细信息,包括来源和日期。 * 借助这些元数据,临床医生希望能方便地比较:在所有案例中,某一特定表型特征与某种疾病的关联情况。 选择哪个技术没有标准答案,但更合适的方案会让你更直接地达成目标。你也可以把这个练习迁移到其他领域和应用场景中去。 #### 3.2.2 用RDF与LPG表示边属性 从我们的需求来看,LPG是表示这类数据的最佳方案,因为它天然强调“连接某个表型特征与某种疾病的那条边本身所承载的信息”。为了把这一点说得更清楚,我们来具体比较一下RDF和LPG。目标是获取与某条注释相关的全部信息(包括来源、作者和创建日期)。RDF也能通过不同机制来表示这类数据,具体如下。 ##### RDF:N元关系 一种用于建模特定边相关数据的标准方法是采用**N元关系**。使用这种方法时,我们会创建一个新概念来连接相关数据。在我们的示例中,这个概念就是“注释”。请看清单3.5中的RDF表示,以及清单3.6中对应的SPARQL查询。 **清单 3.5 N元关系示例** ```turtle _:Annotation rdf:type :PhenotypicAnnotation ; :forDisease OMIM:222100 ; :phenotypicFeature HP:0410050 ; :source PMID:9357814 ; :createdBy "Nicole Vasilevsky" ; :creationDate "2018-02-23"^^xsd:date . ``` 这段RDF片段使用Turtle语法表示一个表型注释。这个注释被表达为一个`_:Annotation`的空白节点。空白节点是一种没有全局标识符的匿名资源,用来归拢相关信息,有点像编程中的匿名对象。这个空白节点被声明为`:PhenotypicAnnotation`类型,把某种疾病(由OMIM ID标识)与某个表型特征(来自HPO)关联起来,并附加了元数据。 **清单 3.6 N元关系场景下的SPARQL查询** ```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:命名图 RDF的命名图引入了第四个元素,用来声明“这个陈述属于某个具名(子)图”,而这个图本身也可以被当作RDF图中的一个节点来处理。这样,我们就可以创建新的陈述,把与注释相关的数据附着到这个命名图上。清单3.7展示了这种方法,对应的查询见清单3.8。 **清单 3.7 命名图示例** ```turtle :Graph1 { OMIM:222100 :hasPhenotypicFeature HP:0410050 . } :Graph1 :source PMID:9357814 ; :createdBy "Nicole Vasilevsky" ; :creationDate "2018-02-23"^^xsd:date . ``` 这个RDF示例使用了TriG语法,它允许你把一组三元组归在一个命名图的标签下。在这个图中,有一个三元组声明了疾病OMIM:222100具有表型特征HP:0410050。而这个断言的元数据则附着在`:Graph1`上。 **清单 3.8 命名图场景下的SPARQL查询** ```sparql SELECT ?source ?author ?date WHERE { GRAPH :Graph1 { OMIM:222100 :hasPhenotypicFeature HP:0410050 . } :Graph1 :source ?source ; :createdBy ?author ; :creationDate ?date . } ``` 这个SPARQL查询检索存储在命名图中的某条特定表型注释的元数据。它在`:Graph1`中查找断言,然后查询`:Graph1`自身的元数据。 尽管命名图在表示上下文化和来源信息方面很强大,但它也会引入额外复杂性。尤其是命名图数量非常多时,数据存储和交换的效率会受影响,对图中单条陈述进行细粒度更新也会更困难。 ##### RDF-STAR 如前所述,RDF-star是RDF的一个扩展,旨在缩小与LPG类属性图模型的差距。下面两个清单展示了这种方式。 **清单 3.9 RDF-star示例** ```turtle <> :source PMID:9357814 ; :createdBy "Nicole Vasilevsky" ; :creationDate "2018-02-23"^^xsd:date . ``` **清单 3.10 RDF-star场景下的SPARQL-star查询示例** ```sparql SELECT ?source ?author ?date { <> :source ?source ; :createdBy ?author ; :creationDate ?date . } ``` RDF-star向“给边附加属性”迈进了一大步,其SPARQL查询也更易读。但正如Orlandi等人[2]指出的:“采用新的语法扩展,要求RDF引擎提供专门实现,因此会限制这种方法的普及。”此外,RDF中还有重述、单例属性等其他给陈述加注释的方法,但在实际应用中相对较少。 ##### 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`把它们连起来,并在关系上附着键值对属性。 **清单 3.12 Cypher查询示例** ```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查询检索附着在关系上的元数据。它在图中匹配这个模式,基于节点ID过滤,并返回存储在关系中的注释细节。 如这些示例所示,LPG模型非常适合以一种富有表现力且易于访问的方式来建模带有丰富元数据的关系。因此,我们在后续系统中将采用LPG与Cypher作为构建KG的核心工具。 ### 3.3 构建知识图谱 现在进入真正的构建环节。这个过程分为两步:首先加载本体,然后以这个本体为参考导入数据源。 > **注意** > 要构建这个KG,你可以运行GitHub仓库中的代码,或者使用Neo4j Browser测试本节中的Cypher查询。代码已在以下环境中测试通过:Neo4j (5.20.0 Enterprise, via Neo4j Desktop 1.6.1)、APOC库 (5.20.0) 以及Neosemantics插件 (5.20.0)。安装细节见在线附录B。我们会解释每一条查询,但前提是你已经具备Cypher查询语言的基本理解。本节的运行结果基于2025年2月可用的HPO版本。 #### 3.3.1 使用neosemantics导入并处理本体 图3.7展示了本体导入与处理阶段。 图 3.7 本体导入与处理 第一步,是使用如下命令创建并初始化HPO数据库。 **清单 3.13 在Neo4j中创建HPO数据库** ```cypher CREATE DATABASE hpo IF NOT EXISTS ``` 接下来的清单中,我们建立约束,确保被标记为`Resource`的节点,其`uri`和`id`属性是唯一的。我们还会为`HpoPhenotype`和`HpoDisease`节点的`id`属性建立索引,以提升KG构建阶段和之后信息检索的效率。 **清单 3.14 创建约束与索引** ```cypher 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插件** ```cypher CALL n10s.graphconfig.init(); CALL n10s.graphconfig.set({ handleVocabUris: "IGNORE" }); CALL n10s.graphconfig.set({ applyNeo4jNaming: True }); ``` 这个配置定义了两条规则:第一条是在导入时忽略命名空间;第二条把关系类型编码为大写形式,以符合LPG的常见规范。 接下来,加载HPO词汇表。 **清单 3.16 将HPO词汇表加载到Neo4j** ```cypher CALL n10s.rdf.import.fetch("https://purl.obolibrary.org/obo/hp.owl","RDF/XML"); ``` 在我们的测试中,这条命令将899,558条陈述加载进了Neo4j。在处理并加载注释数据之前,我们先给节点添加`HpoPhenotype`标签,并根据资源的原始URI计算出`id`属性。 **清单 3.17 丰富节点属性** ```cypher MATCH (n:Resource) WHERE n.uri STARTS WITH "https://purl.obolibrary.org/obo/HP" SET n:HpoPhenotype, n.id = coalesce(n.id, replace(apoc.text.replace(n.uri,'(.*)obo/',''),'_', ':')) #1 ``` * `#1`:将`n.id`设置为类似`HP:0000001`这样的格式。 现在,看看此时KG的状态。清单3.18展示了用于获取这部分图数据的代码,结果在图3.8中可视化。你可以在Neo4j Browser中运行这段代码来探索。 图 3.8 使用LPG作为存储模型加载到图库中的一部分HPO本体。我们可以区分出两类信息:本体信息(左侧)与表型特征相关的领域信息(右侧)。 **清单 3.18 展示当前阶段KG的一部分** ```cypher 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展示了数据处理与建模的第二阶段。 图 3.9 导入并处理注释数据集,以完成KG构建 与RDF模型不同,这个文件采用HPO标注格式,本质上是一个TSV文件。它包含以下有价值的信息: * 疾病与多个表型特征/异常之间的显式关联。 * 支持这种关联的证据(比如来自电子注释推断、已发表临床研究还是可追踪作者陈述)。 * 发病年龄。 * 疾病与表型特征共同出现的频率。 * 描述本体来源的附加元数据。 处理这个TSV文件,能让我们在已有知识基础上整合不同类型的信息。清单3.19–3.24中的Cypher查询,用于从GitHub上的注释文件中加载、处理并整合信息。首先,我们创建疾病节点。 **清单 3.19 创建HpoDisease节点** ```cypher 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节点之间创建关系** ```cypher 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 查找关联关系** ```cypher 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 96 | Hydrops 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 leak | Generalized muscle weakness, Hyperkalemia, Periodic paralysis, Muscle spasm, Hemolytic anemia, Hand tremor, Autosomal dominant inheritance | | Immunoglobulin kappa light chain deficiency | Chronic diarrhea, Recurrent infections, Recurrent respiratory infections, Absent circulating immunoglobulin kappa chain, Childhood onset, Diarrhea, Autosomal recessive inheritance | 接下来这段代码,会以键值对形式给这些关系添加属性。 **清单 3.22 给HAS_PHENOTYPIC_FEATURE关系添加属性** ```cypher 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]) ``` 这是一种非常灵活的关系富化方式。每个`FOREACH`块只在对应TSV列不为null时,才向关系上写入一个新属性,保证了数据完整性。 接下来,我们进一步处理这些关系属性,让疾病与表型特征之间关系属性的语义更清晰。 **清单 3.23 给HAS_PHENOTYPIC_FEATURE增加更多可读属性** ```cypher 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 ha ve 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`以批处理方式更新关系。它从`biocuration`属性中提取出标注者和创建日期,并为`aspect`、`evidence`等简写字段添加可读的名称和描述,让图浏览时的信息更友好。 构建KG的最后一步,是执行清理:删除那些来自本体但对具体目标不必要的节点和关系。 **清单 3.24 清理KG,删除不必要的节点与关系** ```cypher 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所示。 图 3.10 查询已构建好的KG,以支持临床医生的工作 设想这样一个场景:一位临床医生接诊了一名患有1型糖尿病的男孩。患者的病史以电子健康档案的形式存储在医院数据库里。医院已经采纳了KG驱动的模式,因此患者信息使用HPO和OMIM术语来存储。由于1型糖尿病既可以看作表型特征,也可以看作疾病,所以系统中会用两个标识符来保存: * `HP:0100651`(表型特征):hpo.jax.org/app/browse/browse/HP_0100651 * `OMIM:222100`(疾病):www.omim.org/entry/222100 临床医生识别出患者具有典型的1型糖尿病表型特征,可以通过下面的查询在KG中探索。图3.11展示了结果。 图 3.11 查询与1型糖尿病相关的所有表型特征后的结果 **清单 3.25 查询与1型糖尿病相关的表型特征** ```cypher MATCH path=(dis:HpoDisease)-[:HAS_PHENOTYPIC_FEATURE]->(phe:HpoPhenotype) WHERE dis.id = "OMIM:222100" RETURN path ``` 中心节点是1型糖尿病,周围节点表示关联的表型特征。然而,在检查过程中,临床医生还发现了一些新的症状,它们也被归类为表型特征,但并未直接连接到1型糖尿病: * Growth delay:hpo.jax.org/app/browse/browse/HP_0001510 * Large knee:hpo.jax.org/app/browse/browse/HP_0030848 * Sensorineural hearing impairment:hpo.jax.org/app/browse/browse/HP_0000407 * Pruritus:hpo.jax.org/app/browse/browse/HP_0000989 这时,医生希望利用KG找出与这些表型特征相关的其他疾病。他执行了如下查询,结果列在表3.2中。 **清单 3.26 查找与特定表型特征相关的疾病** ```cypher 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_id | disease_name | features | num_of_features | | :--- | :--- | :--- | :--- | | OMIM:619269 | Ondontochondrodysplasia 2 with hearing loss and diabetes | Growth delay, Sensorineural hearing impairment, Pruritus, Large knee, Type I diabetes mellitus | 5 | | OMIM:618500 | Holoprosencephaly 12 with or without pancreatic agenesis | Sensorineural hearing impairment, Growth delay, Type I diabetes mellitus | 3 | | OMIM:614700 | 3-methylglutaconic aciduria, type VIII | Growth delay, Sensorineural hearing impairment | 2 | | OMIM:616192 | Alobar holoprosencephaly | Growth delay, Sensorineural hearing impairment | 2 | | OMIM:602782 | Alpha-Thalassemia/mental retardation syndrome, X-linked | Growth delay, Sensorineural hearing impairment | 2 | 这些结果最终引导医生做出诊断:Ondontochondrodysplasia 2 with hearing loss and diabetes。基于这些发现,医生还可以继续深挖,确定这些表型特征与该疾病关联的频率,并找到更多潜在信息来源。 **练习** 扩展清单3.26中的查询,使其同时返回关系属性:`evidence_name`、`evidence_description`、`source` 和 `url`。 ### 3.5 在KG之上进行推理 在前面的例子中,我们展示了如何从KG中已存储的信息里直接获得结果。但KG另一个强大的能力,是**推理**——基于逻辑规则,通过演绎推理从隐含信息中导出结果。比如,考虑这样一个问题:哪些疾病具有“内分泌系统异常”? 有些注释与这个表型特征是直接连接的。但临床医生往往还会关心那些更具体的子类型,比如涉及甲状腺的异常。这时,我们可以利用HPO的层级结构。下面的查询会找出代表“内分泌系统异常”(id=HP:0000818)子类的一部分表型特征。 **清单 3.27 查找内分泌系统异常的子类** ```cypher 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 查找与异常子类相关的表型特征** ```cypher 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 与“内分泌系统异常”表型特征隐式相连的一部分注释结果。直接属于这一表型特征,或可被推断为其子类的表型特征,以加粗方式标出。** | disease | features | | :--- | :--- | | Congenital atransferrinemia | Anemia, **Abnormality of the pancreas**, Recurrent infections, Arthritis, **Abnormality of the cardiovascular system**, Hypothyroidism | | Deafness, autosomal recessive 4, with enlarged vestibular aqueduct | Enlarged vestibular aqueduct, Congenital onset, **Goiter**, Autosomal recessive inheritance, Incomplete partition of the cochlea type II, **Sensorineural hearing impairment** | | Diabetes mellitus, transient neonatal, 1 | **Transient neonatal diabetes mellitus**, Autosomal dominant inheritance, Dehydration, **Hyperglycemia**, Intrauterine growth retardation, Severe failure to thrive | | Edema, familial idiopathic, prepubertal | **Diabetes mellitus**, **Abnormality of the genitourinary system**, Irritability, Vomiting, Autosomal dominant inheritance, **Edema** | | Familial dysalbuminemic hyperthyroxinemia | **Abnormal 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之间的差异,对于针对具体目标选择最佳技术方案至关重要。
来源:https://juejin.cn/post/7621088972015239195
上一篇MindSpore元学习实战指南与Meta-Learning应用教程 下一篇RAG、Agentic RAG与AI Memory的区别与应用解析
本站内容用于信息整理与展示,如有侵权或内容问题请及时联系处理。

相关推荐

补充同频道和同主题内容,方便继续浏览更多相关内容。

同类最新

继续查看同栏目最近更新的文章。

更多
CAD零基础入门教程:坐标输入、图层管理与基础绘图命令
AI教程 · 2026-09-01

CAD零基础入门教程:坐标输入、图层管理与基础绘图命令

本文面向CAD零基础学习者,系统讲解坐标输入、图层管理与基础绘图命令的核心用法。通过分步实操与常见问题排查,帮助新手建立精确绘图习惯,掌握规范出图的基础能力。

CAD从入门到项目交付:绘图、标注、图块与实战工作流
AI教程 · 2026-09-01

CAD从入门到项目交付:绘图、标注、图块与实战工作流

掌握CAD的核心在于建立“画得准、标得清、复用快、交付稳”的工作流。本文提供从环境设置、高频命令组合、标注规范、图块标准化到项目分阶段交付的完整路径,帮助初学者避免常见返工陷阱,独立完成可检查、可复用、可打印的工程图纸。

Claude Code 登录指南:个人、Teams 与企业账号区分与授权步骤
AI教程 · 2026-09-01

Claude Code 登录指南:个人、Teams 与企业账号区分与授权步骤

本文详细解析 Claude Code 登录前的账号类型区分方法,涵盖个人订阅、Teams 席位与企业 Enterprise 席位的授权路径差异。提供终端登录命令、环境变量排查及常见异常处理步骤,帮助用户快速完成正确授权并避免登录路径混淆。

Claude Code 文件修改前的权限模式配置与命令审批指南
AI教程 · 2026-09-01

Claude Code 文件修改前的权限模式配置与命令审批指南

本文详细介绍Claude Code在修改文件前的权限模式配置方法,包括defaultMode可选值、permissions allow与deny规则设置、多层级配置文件管理以及 status验证技巧,帮助开发者安全高效地使用AI编程助手。

Claude Code接入VS Code后先测扩展和终端命令
AI教程 · 2026-09-01

Claude Code接入VS Code后先测扩展和终端命令

在VS Code中接入Claude Code后,建议优先验证扩展面板与集成终端两条入口。本文提供标准检查顺序、关键命令与常见故障排查路径,帮助你快速确认环境就绪,避免后续开发受阻。