家庭关系本体#
此示例是 AI for Beginners Curriculum 的一部分,灵感来源于这篇博客文章。
我总是觉得记住家庭成员之间的各种关系很困难。在这个示例中,我们将使用一个定义家庭关系的本体,以及实际的家谱树,展示如何通过自动推理来找到所有的亲属关系。
获取家谱树#
作为示例,我们将使用罗曼诺夫沙皇家族的家谱。描述家庭关系最常见的格式是 GEDCOM。我们将使用 GEDCOM 格式的罗曼诺夫家族树:
!head -15 data/tsars.ged0 HEAD
1 CHAR UTF8
1 GEDC
2 VERS 5.5
0 @0@ INDI
1 NAME Mihail Fedorovich /Romanov/
1 SEX M
1 BIRT
2 DATE 1613
1 DEAT
2 DATE 1645
1 FAMS @41@
0 @1@ INDI
1 NAME Evdokija Lukjanovna /Streshneva/
1 SEX F
要使用GEDCOM文件,我们可以使用python-gedcom库:
import sys
!{sys.executable} -m pip install python-gedcomCollecting python-gedcom
Downloading python_gedcom-1.0.0-py2.py3-none-any.whl (35 kB)
Installing collected packages: python-gedcom
Successfully installed python-gedcom-1.0.0
这个库解决了一些文件解析的技术问题,但它仍然为我们提供了对树中所有个人和家庭的相当低级的访问。以下是我们如何解析文件并显示所有个人列表的方法:
from gedcom.parser import Parser
from gedcom.element.individual import IndividualElement
from gedcom.element.family import FamilyElement
g = Parser()
g.parse_file('data/tsars.ged')d = g.get_element_dictionary()
[ (k,v.get_name()) for k,v in d.items() if isinstance(v,IndividualElement)][('@0@', ('Mihail Fedorovich', 'Romanov')),
('@1@', ('Evdokija Lukjanovna', 'Streshneva')),
('@2@', ('Aleksej Mihajlovich', 'Romanov')),
('@3@', ('Marija Ilinichna', 'Miloslavskaja')),
('@4@', ('Natalja Kirillovna', 'Naryshkina')),
('@5@', ('Marfa Matveevna', 'Apraksina')),
('@6@', ('Fedor Alekseevich', 'Romanov')),
('@7@', ('Sofja Aleksevna', 'Romanova')),
('@8@', ('Ivan V Alekseevich', 'Romanov')),
('@9@', ('Praskovja Fedorovna', 'Saltykova')),
('@10@', ('Ekaterina Ivanovna', 'Romanova')),
('@11@', ('Anna Ivanovna', 'Romanova')),
('@12@', ('Fridrih Vilgelm', 'Kurlandskij')),
('@13@', ('Karl Leopold', 'Meklenburg-Shverinskij')),
('@14@', ('Anna Leopoldovna', 'Meklenburg-Shverinskaja')),
('@15@', ('Anton Ulrih', 'Braunshvejg-Volfenbjuttelskij')),
('@16@', ('Ivan VI Antonovich', 'Braunshvejg-Volfenbjuttelskij')),
('@17@', ('Petr I Alekseevich', 'Romanov')),
('@18@', ('Evdokija Fedorovna', 'Lopuhina')),
('@19@', ('Ekaterina I Alekseevna', 'Mihajlova')),
('@20@', ('Aleksej Petrovich', 'Romanov')),
('@21@', ('Sharlotta Kristina', 'Braunshvejg-Volfenbjuttelskaja')),
('@22@', ('Petr II Alekseevich', 'Romanov')),
('@23@', ('Anna Petrovna', 'Romanova')),
('@24@', ('Elizaveta Petrovna', 'Romanova')),
('@25@', ('Karl Fridrih', 'Golshtejn-Gottorpskij')),
('@26@', ('Petr III Fedorovich', 'Romanov')),
('@27@', ('Ekaterina II', 'Alekseevna')),
('@28@', ('Pavel I Petrovich', 'Romanov')),
('@29@', ('Natalja Alekseevna', 'Gessen-Darmshtadskaja')),
('@30@', ('Marija Fedorovna', 'Vjurtembergskaja')),
('@31@', ('Aleksandr I Pavlovich', 'Romanov')),
('@32@', ('Elizaveta Alekseevna', 'Baden-Durlahskaja')),
('@33@', ('Nikolaj I Pavlovich', 'Romanov')),
('@34@', ('Aleksandra Fedorovna', 'Prusskaja')),
('@35@', ('Aleksandr II Nikolaevich', 'Romanov')),
('@36@', ('Marija Aleksandrovna', 'Gessenskaja')),
('@37@', ('Aleksandr III Aleksandrovich', 'Romanov')),
('@38@', ('Marija Fedorovna', 'Datskaja')),
('@39@', ('Nikolaj II Aleksandrovich', 'Romanov')),
('@40@', ('Aleksandra Fedorovna', 'Gessenskaja'))]以下是我们获取家庭信息的方法。请注意,这会给我们一个标识符列表,如果我们想要更清楚,需要将它们转换为名称:
d = g.get_element_dictionary()
[ (k,[x.get_value() for x in v.get_child_elements()]) for k,v in d.items() if isinstance(v,FamilyElement)][('@41@', ['@0@', '@1@', '@2@']),
('@42@', ['@2@', '@3@', '@6@', '@7@', '@8@']),
('@43@', ['@8@', '@9@', '@10@', '@11@']),
('@44@', ['@13@', '@10@', '@14@']),
('@45@', ['@15@', '@14@', '@16@']),
('@46@', ['@2@', '@4@', '@17@']),
('@47@', ['@17@', '@18@', '@20@']),
('@48@', ['@20@', '@21@', '@22@']),
('@49@', ['@17@', '@19@', '@23@', '@24@']),
('@50@', ['@25@', '@23@', '@26@']),
('@51@', ['@26@', '@27@', '@28@']),
('@52@', ['@28@', '@30@', '@31@', '@33@']),
('@53@', ['@33@', '@34@', '@35@']),
('@54@', ['@35@', '@36@', '@37@']),
('@55@', ['@37@', '@38@', '@39@'])]获取家庭本体#
接下来,让我们看看家庭本体,它被定义为一组语义网三元组。这个本体定义了诸如 isUncleOf、isCousinOf 等许多关系。所有这些关系都是基于基本谓词 isMotherOf、isFatherOf、isBrotherOf 和 isSisterOf 定义的。我们将使用自动推理,通过本体推导出所有其他关系。
以下是 isAuntOf 属性的一个示例定义,它被定义为 isSisterOf 和 isParentOf 的组合(姑/姨是某人父母的姐妹)。
fhkb:isAuntOf a owl:ObjectProperty ;
rdfs:domain fhkb:Woman ;
rdfs:range fhkb:Person ;
owl:propertyChainAxiom ( fhkb:isSisterOf fhkb:isParentOf ) .
!head -20 data/onto.ttl@prefix fhkb: <http://www.example.com/genealogy.owl#> .
@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix xml: <http://www.w3.org/XML/1998/namespace> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
<http://www.example.com/genealogy.owl#> a owl:Ontology .
fhkb:DomainEntity a owl:Class .
fhkb:Man a owl:Class ;
owl:equivalentClass [ a owl:Class ;
owl:intersectionOf ( fhkb:Person [ a owl:Restriction ;
owl:onProperty fhkb:hasSex ;
owl:someValuesFrom fhkb:Male ] ) ] .
fhkb:Woman a owl:Class ;
owl:equivalentClass [ a owl:Class ;
owl:intersectionOf ( fhkb:Person [ a owl:Restriction ;
构建推理本体#
为了简化操作,我们将创建一个本体文件,其中包括家庭本体中的原始规则,以及来自我们 GEDCOM 文件的个人事实。我们将遍历 GEDCOM 文件,提取有关家庭和个人的信息,并将其转换为三元组。
!cp data/onto.ttl .
gedcom_dict = g.get_element_dictionary()
individuals, marriages = {}, {}
def term2id(el):
return "i" + el.get_pointer().replace('@', '').lower()
out = open("onto.ttl","a")
for k, v in gedcom_dict.items():
if isinstance(v,IndividualElement):
children, siblings = set(), set()
idx = term2id(v)
title = v.get_name()[0] + " " + v.get_name()[1]
title = title.replace('"', '').replace('[', '').replace(']', '').replace('(', '').replace(')', '').strip()
own_families = g.get_families(v, 'FAMS')
for fam in own_families:
children |= set(term2id(i) for i in g.get_family_members(fam, "CHIL"))
parent_families = g.get_families(v, 'FAMC')
if len(parent_families):
for member in g.get_family_members(parent_families[0], "CHIL"): # NB adoptive families i.e len(parent_families)>1 are not considered (TODO?)
if member.get_pointer() == v.get_pointer():
continue
siblings.add(term2id(member))
if idx in individuals:
children |= individuals[idx].get('children', set())
siblings |= individuals[idx].get('siblings', set())
individuals[idx] = {'sex': v.get_gender().lower(), 'children': children, 'siblings': siblings, 'title': title}
elif isinstance(v,FamilyElement):
wife, husb, children = None, None, set()
children = set(term2id(i) for i in g.get_family_members(v, "CHIL"))
try:
wife = g.get_family_members(v, "WIFE")[0]
wife = term2id(wife)
if wife in individuals: individuals[wife]['children'] |= children
else: individuals[wife] = {'children': children}
except IndexError: pass
try:
husb = g.get_family_members(v, "HUSB")[0]
husb = term2id(husb)
if husb in individuals: individuals[husb]['children'] |= children
else: individuals[husb] = {'children': children}
except IndexError: pass
if wife and husb: marriages[wife + husb] = (term2id(v), wife, husb)
for idx, val in individuals.items():
added_terms = ''
if val['sex'] == 'f':
parent_predicate, sibl_predicate = "isMotherOf", "isSisterOf"
else:
parent_predicate, sibl_predicate = "isFatherOf", "isBrotherOf"
if len(val['children']):
added_terms += " ;\n fhkb:" + parent_predicate + " " + ", ".join(["fhkb:" + i for i in val['children']])
if len(val['siblings']):
added_terms += " ;\n fhkb:" + sibl_predicate + " " + ", ".join(["fhkb:" + i for i in val['siblings']])
out.write("fhkb:%s a owl:NamedIndividual, owl:Thing%s ;\n rdfs:label \"%s\" .\n" % (idx, added_terms, val['title']))
for k, v in marriages.items():
out.write("fhkb:%s a owl:NamedIndividual, owl:Thing ;\n fhkb:hasFemalePartner fhkb:%s ;\n fhkb:hasMalePartner fhkb:%s .\n" % v)
out.write("[] a owl:AllDifferent ;\n owl:distinctMembers (")
for idx in individuals.keys():
out.write(" fhkb:" + idx)
for k, v in marriages.items():
out.write(" fhkb:" + v[0])
out.write(" ) .")
out.close()!tail onto.ttl fhkb:hasFemalePartner fhkb:i34 ;
fhkb:hasMalePartner fhkb:i33 .
fhkb:i54 a owl:NamedIndividual, owl:Thing ;
fhkb:hasFemalePartner fhkb:i36 ;
fhkb:hasMalePartner fhkb:i35 .
fhkb:i55 a owl:NamedIndividual, owl:Thing ;
fhkb:hasFemalePartner fhkb:i38 ;
fhkb:hasMalePartner fhkb:i37 .
[] a owl:AllDifferent ;
owl:distinctMembers ( fhkb:i0 fhkb:i1 fhkb:i2 fhkb:i3 fhkb:i4 fhkb:i5 fhkb:i6 fhkb:i7 fhkb:i8 fhkb:i9 fhkb:i10 fhkb:i11 fhkb:i12 fhkb:i13 fhkb:i14 fhkb:i15 fhkb:i16 fhkb:i17 fhkb:i18 fhkb:i19 fhkb:i20 fhkb:i21 fhkb:i22 fhkb:i23 fhkb:i24 fhkb:i25 fhkb:i26 fhkb:i27 fhkb:i28 fhkb:i29 fhkb:i30 fhkb:i31 fhkb:i32 fhkb:i33 fhkb:i34 fhkb:i35 fhkb:i36 fhkb:i37 fhkb:i38 fhkb:i39 fhkb:i40 fhkb:i41 fhkb:i42 fhkb:i43 fhkb:i44 fhkb:i45 fhkb:i46 fhkb:i47 fhkb:i48 fhkb:i49 fhkb:i50 fhkb:i51 fhkb:i52 fhkb:i53 fhkb:i54 fhkb:i55 ) .推理操作#
现在我们希望能够使用这个本体进行推理和查询。我们将使用 RDFLib,一个用于读取不同格式的 RDF 图、查询等操作的库。
对于逻辑推理,我们将使用 OWL-RL 库,它允许我们构建 RDF 图的闭包,即添加所有可以推导出的概念和关系。
!{sys.executable} -m pip install rdflib
!{sys.executable} -m pip install git+https://github.com/RDFLib/OWL-RL.gitRequirement already satisfied: rdflib in /home/rg/anaconda3/envs/ai4beg/lib/python3.11/site-packages (6.3.2)
Requirement already satisfied: isodate<0.7.0,>=0.6.0 in /home/rg/anaconda3/envs/ai4beg/lib/python3.11/site-packages (from rdflib) (0.6.1)
Requirement already satisfied: pyparsing<4,>=2.1.0 in /home/rg/anaconda3/envs/ai4beg/lib/python3.11/site-packages (from rdflib) (3.0.9)
Requirement already satisfied: six in /home/rg/anaconda3/envs/ai4beg/lib/python3.11/site-packages (from isodate<0.7.0,>=0.6.0->rdflib) (1.16.0)
Collecting git+https://github.com/RDFLib/OWL-RL.git
Cloning https://github.com/RDFLib/OWL-RL.git to /tmp/pip-req-build-lbfzwi3m
Running command git clone --filter=blob:none --quiet https://github.com/RDFLib/OWL-RL.git /tmp/pip-req-build-lbfzwi3m
Resolved https://github.com/RDFLib/OWL-RL.git to commit a77e1791b88b54aace609bc6000aac14c7add4ff
Preparing metadata (setup.py) ... [?25ldone
[?25hRequirement already satisfied: rdflib>=6.0.2 in /home/rg/anaconda3/envs/ai4beg/lib/python3.11/site-packages (from owlrl==6.0.2) (6.3.2)
Requirement already satisfied: isodate<0.7.0,>=0.6.0 in /home/rg/anaconda3/envs/ai4beg/lib/python3.11/site-packages (from rdflib>=6.0.2->owlrl==6.0.2) (0.6.1)
Requirement already satisfied: pyparsing<4,>=2.1.0 in /home/rg/anaconda3/envs/ai4beg/lib/python3.11/site-packages (from rdflib>=6.0.2->owlrl==6.0.2) (3.0.9)
Requirement already satisfied: six in /home/rg/anaconda3/envs/ai4beg/lib/python3.11/site-packages (from isodate<0.7.0,>=0.6.0->rdflib>=6.0.2->owlrl==6.0.2) (1.16.0)
让我们打开本体文件,看看它包含多少个三元组:
import rdflib
from owlrl import DeductiveClosure, OWLRL_Extension
g = rdflib.Graph()
g.parse("onto.ttl", format="turtle")
print("Triplets found:%d" % len(g))Triplets found:669
现在让我们构建闭包,看看三元组的数量如何增加:
DeductiveClosure(OWLRL_Extension).expand(g)
print("Triplets after inference:%d" % len(g))Triplets after inference:4246
查询亲属关系#
现在我们可以查询图谱,查看人与人之间的不同关系。我们可以结合使用 SPARQL 语言和 query 方法。在我们的例子中,让我们看看家谱中所有的叔叔:
qres = g.query(
"""SELECT DISTINCT ?aname ?bname
WHERE {
?a fhkb:isUncleOf ?b .
?a rdfs:label ?aname .
?b rdfs:label ?bname .
}""")
for row in qres:
print("%s is uncle of %s" % row)Fedor Alekseevich Romanov is uncle of Ekaterina Ivanovna Romanova
Aleksandr I Pavlovich Romanov is uncle of Aleksandr II Nikolaevich Romanov
Fedor Alekseevich Romanov is uncle of Anna Ivanovna Romanova
可以尝试不同的家庭关系。例如,可以查看 isAncestorOf 关系,它递归地定义了某个人的所有祖先。
最后,让我们整理一下!
!rm onto.ttl免责声明:
本文档使用AI翻译服务Co-op Translator进行翻译。虽然我们尽力确保翻译的准确性,但请注意,自动翻译可能包含错误或不准确之处。原始语言的文档应被视为权威来源。对于关键信息,建议使用专业人工翻译。我们不对因使用此翻译而产生的任何误解或误读承担责任。