Python-本体论教程-二-

272 阅读1小时+

Python 本体论教程(二)

原文:Ontologies with Python

协议:CC BY-NC-SA 4.0

五、使用 Python 创建和修改本体

在这一章中,我们将看到如何用 Python 创建一个新的本体,以及如何修改或丰富一个已经存在的本体。在前一章中看到的 Owlready 的几乎所有对象、属性和列表都可以修改:当这些的值被修改时,Owlready 自动更新 quadstore 中相应的 RDF 三元组(但是,如果它存储在磁盘上,不要忘记保存它;参见 4.7)。

5.1 创建空的本体

get_ontology()函数允许你从它的 IRI 创建一个空的本体(最好在 IRI 的末尾标明分隔符," # "或"/",因为 Owlready 不能猜测它,因为本体是空的!):

>>> from owlready2 import *
>>> onto = get_ontology("http://test.org/onto.owl#")

注意,与我们在第四章中所做的相反,我们没有调用load()方法。它负责加载本体;如果load()没有被调用,那么本体保持为空。

随后,当创建 OWL 实体或 RDF 三元组时,指出它们放在哪个本体中是很重要的。事实上,与属于创建它们的模块的 Python 类不同,OWL 实体并不特别“属于”一个本体:一个类可以在本体 A 中定义,然后在本体 B 中丰富,例如,用新的父类。

Owlready 使用了语法“with ontology:...”要指示将接收新 RDF 三元组的本体:

with onto:
    <Python code>

在代码块“”中创建的所有 RDF 三元组都将被添加到本体onto中。

5.2 创建类

要创建 OWL 类,只需创建一个继承自Thing的 Python 类。例如,我们可以创建细菌、形状和分组类,如下所示:

>>> with onto:
...     class Bacterium(Thing): pass
...     class Shape(Thing): pass
...     class Grouping(Thing): pass

注意,由于这些类是空的(也就是说它们没有方法),我们必须使用关键字pass(见 2.9)。

为了观察 Owlready quadstore 内部发生了什么,我们可以使用函数set_log_level()来修改日志记录的级别。通过将级别设置为最大值(9),Owlready 指示在 quadstore 中添加、删除或修改的 RDF 三元组。这里有一个例子:

>>> set_log_level(9)
>>> with onto:
...     class TestClass(Thing): pass
* Owlready2 * ADD TRIPLE http://test.org/onto.owl#TestClass↲
    http://www.w3.org/1999/02/22-rdf-syntax-ns#type↲
    http://www.w3.org/2002/07/owl#Class
* Owlready2 * ADD TRIPLE http://test.org/onto.owl#TestClass↲
    http://www.w3.org/2000/01/rdf-schema#subClassOf↲
    http://www.w3.org/2002/07/owl#Thing

这里,TestClass 类的创建触发了两个 RDF 三元组的添加:第一个指示 TestClass 是一个 OWL 类,第二个指示 TestClass 从 Thing 继承。

要停止日志记录,我们只需执行以下操作:

>>> set_log_level(0)

我们也可以通过从一个类继承来创建新的 OWL 类,这个类本身从Thing继承而来,例如,Shape 或 Grouping 类:

>>> with onto:
...     class Rod(Shape): pass
...     class Isolated(Grouping): pass
...     class InPair(Grouping): pass

所创建的类的 IRI 是通过将本体的基本 IRI 与类名连接起来获得的:

>>> Bacterium.iri
'http://test.org/onto.owl#Bacterium'

父类也可以来自另一个本体,例如来自细菌本体的细菌类(它必须是先前从位于当前目录中的 OWL 文件“bacteria.owl”加载的,这里是在本体onto_chap3变量中;确保您正在运行第三章中使用的工作目录中的示例:

>>> onto_chap3 = get_ontology("bacteria.owl").load()
>>> with onto:
...     class MyBacteriumClass(onto_chap3.Bacterium): pass

多重继承是可能的,可以像在 Python 中一样使用。以下示例创建了核酶类,它继承了 RNA 和酶(核酶是一种行为类似于酶的 RNA):

>>> with onto:
...     class RNA(Thing): pass
...     class Enzyme(Thing): pass
...     class Ribozyme(RNA, Enzyme): pass

动态创建类

Python types模块允许你动态地创建类,当类的名字在编写程序时是未知的,但是在运行时在一个变量中是可用的。这里有一个例子:

>>> import types
>>> class_name = "MyClass"
>>> SuperClasses = [Thing]
>>> with onto:
...     NewClass = types.new_class(class_name, tuple↲ (SuperClasses))

请注意,new_class()函数期望父类的元组,而不是列表!这就是为什么在这个例子中我们使用了tuple()函数将列表转换成一个元组。

5.3 创建属性

在 Owlready 中,属性类似于类,因为 OWL 属性的行为类似于类(特别是继承支持)。事实上,OWL 属性实际上是“关系的类”。通过定义一个从DataPropertyObjectPropertyAnnotationProperty继承的类来创建属性。此外,类FunctionalPropertyInverseFunctionalPropertyTransitivePropertySymmetricPropertyAsymmetricPropertyReflexivePropertyIrreflexiveProperty可以用作附加的超类(使用多重继承),以便创建函数、反函数、传递和其他属性。

domainrange类属性用于以列表的形式查询或定义属性的域和范围。

以下示例创建了函数 functional 属性 has_shape:

>>> with onto:
...     class has_shape(ObjectProperty, FunctionalProperty):
...         domain = [Bacterium]
...         range  = [Shape]

对于DataProperty,可能的范围出现在表 4-1 的右栏。

此外,Owlready 提供了一个简化的符号“domain >> range ”,用于代替父属性(注意:属性的类型,DataPropertyObjectProperty,是从范围中自动推导出来的),例如:

>>> with onto:
...     class has_grouping(Bacterium >> Grouping):
...         pass
...     class has_shape(Bacterium >> Shape, FunctionalProperty):
...         pass
...     class gram_positive(Bacterium >> bool, FunctionalProperty):
...         pass

OWL 还允许您创建子属性,即从另一个属性继承的属性,如下所示:

>>> with onto:
...     class has_rare_shape(has_shape): pass

5.4 创建个人

像 Python 中的任何其他实例一样,通过调用类来创建个体:

>>> my_bacterium = Bacterium()

默认情况下,新个体以“本体名称.个体名称”的格式显示,其中本体名称是本体文件名(没有。owl 扩展名),个人名是小写的类名加上一个数字:

>>> my_bacterium
onto.bacterium1

Owlready 自动为个体分配一个新的 IRI,它是通过获取本体的 IRI 而创建的...:或者默认情况下是与该类关联的名称)并添加小写的类名,后跟一个从 1 开始的数字:

>>> my_bacterium.iri
'http://test.org/onto.owl#bacterium1'

注意不要将包含本地个体的 Python 变量的名称(这里是“my_bacterium”)与实体的名称(这里是“bacterium1”)混淆。当从本体访问个体时,必须使用实体的名称,而不是变量的名称,例如onto.bacterium1。另一方面,在 Python 中直接访问个体时,必须使用他的变量名,因为是 Python 变量,例如my_bacterium

>>> my_bacterium is onto.bacterium1
True

通过将个人的名字作为第一个参数传递给类的构造函数,可以指定个人的名字:

>>> my_bacterium = Bacterium("my_bacterium")
>>> my_bacterium
onto.my_bacterium

在创建个体时,还可以使用命名参数提供一个或多个关系的值:

>>> my_bacterium = Bacterium("my_bacterium",
...                          gram_positive = True,
...                          has_shape = Rod(),
...                          has_grouping = [Isolated()] )

这里,我们为属性 has_shape 的值创建了一个新的Rod类实例,为属性 has_grouping 创建了一个新的Isolated类实例。对于后者,我们给出了一个列表作为值,因为该属性没有功能。

最后,Owlready 还允许您创建匿名个体(由 RDF 图中的匿名节点表示)。它们是通过传递 0(零)而不是个人姓名(获得的数字是任意的,因此您可能有另一个数字)来创建的:

>>> anonymous_bacterium = Bacterium(0)
>>> anonymous_bacterium

_:52

5.5 修改实体:关系和存在限制

个体和存在限制之间的关系可以像 Python 中的任何其他属性一样进行修改。例如,可以按如下方式修改个人关系:

>>> my_bacterium.gram_positive = True

如果它是类型为ObjectProperty的属性,则可以创建预期类的新实例(这里是Rod类):

>>> my_bacterium.has_shape = Rod()
>>> my_bacterium.has_shape
onto.rod1

当属性起作用时,Owlready 需要一个值(如前两行所示)。否则,Owlready 需要一个值列表。然而,Owlready 使用的列表不是“普通的”Python 列表,通过查看这些列表的类并将其与普通的 Python 列表进行比较,我们可以看到:

>>> my_bacterium.has_grouping.__class__
<class 'owlready2.prop.IndividualValueList'>

>>> [].__class__
<classlist’>

Owlready 的列表是“CallbackList ”,它能够检测列表中元素的添加或删除,以便自动更新 quadstore。因此,可以直接修改这些列表,例如,使用append()insert()remove()方法:

>>> my_bacterium.has_grouping = [Isolated()]
>>> my_bacterium.has_grouping.append(InPair())

请注意,Owlready 已经自动将分配给关系的任何列表转换为 CallbackList(例如前面示例中的列表“[Isolated()]”)。

如前一章(第 4.5.4 节)所见,存在限制和值限制(“一些”和“值”限制)在 Owlready 中作为类属性是可访问的。它不仅适用于阅读,也适用于写作。

例如,我们可以按如下方式更改假单胞菌类的革兰氏状态:

>>> onto_chap3.Pseudomonas.gram_positive = True

并将其恢复如下:

>>> onto_chap3.Pseudomonas.gram_positive = False

我们还将在 6.3 中更详细地讨论类属性。除了存在性限制,Owlready 允许您创建任何类型的 OWL 构造函数,我们将在后面看到(6.1)。

5.6 在名称空间中创建实体

默认情况下,Owlready 在本体名称空间中创建实体,也就是说,实体的 IRI 以本体的 IRI 开始。然而,有时有必要创建其 IRI 不是从本体开始的实体。为此,您需要创建一个名称空间,然后在with块中使用该名称空间。与我们之前在 4.8 中所做的相反,这里必须从本体创建名称空间,以便 RDF 三元组被添加到给定的本体中。下面的例子在本体onto中定义了一个类,它的 IRI 是“ http://purl.obolibrary.org/obo/OBOBacterium ”:

>>> obo = onto.get_namespace("http://purl.obolibrary.org/obo/")
>>> with obo:
...     class OBOBacterium(Thing): pass
>>> OBOBacterium.iri
'http://purl.obolibrary.org/obo/OBOBacterium'

同样的方法也可以用于个人:

>>> with obo:
...     my_bacterium = OBOBacterium("my_bacterium")
>>> my_bacterium.iri
'http://purl.obolibrary.org/obo/my_bacterium'

5.7 重命名实体(重构)

可以修改任何实体的nameiri属性来改变实体的 IRI(这种操作有时被称为重构)。修改name属性允许您更改实体的名称,同时保持它在相同的名称空间中,而修改iri属性允许您更改名称空间和名称。

>>> my_bacterium.iri = "http://test.org/other_onto.↲owl#bacterium1"

注意,重命名实体会改变它在本体中的名称,但不会改变 Python 变量的名称!在前一行之后,该个体在 Python 变量my_bacterium中仍然可用。但是,它不再作为onto.my_bacterium可用,而是可以通过创建相应的名称空间来检索:

>>> get_namespace("http://test.org/other_onto.owl").bacterium1

同样要小心,重命名一个实体并不会将它移动到另一个本体。

5.8 多重定义和远期声明

当用相同的 IRI 定义了几个实体时,Owlready 不会创建新的实体,而是返回已经存在的实体。如有必要,这将被更新,例如,使用新的关系、父类(对于个人)和/或继承(对于类)。在下面的例子中,只创建了一个细菌类个体,因为bacterium_abacterium_b具有相同的 IRI。然而,第二次创建添加了带有值False的关系“gram_positive”。

>>> with onto:
...     bacterium_a = Bacterium("the_bacterium")
...     bacterium_b = Bacterium("the_bacterium",
...                             gram_positive = False)
>>> bacterium_a is bacterium_b
True

这样,就可以为类或个人进行前向声明。在下面的示例中,首先创建 Bacterium 类,然后在 has_shape 属性域中使用它。然后,细菌类的定义继续添加存在限制“至少具有一种形状”。

>>> with onto:
...     class Bacterium(Thing): pass
...     class Shape(Thing): pass
...     class has_shape(Bacterium >> Shape): pass
...     class Bacterium(Thing):
...         has_shape = Shape

这里,属性 has_shape 的定义使用了类 Bacterium,而类 Bacterium 的(完整)定义需要属性 has_shape。因此,如果没有前瞻性声明,就不可能实现这一目标。

5.9 销毁实体

全局函数destroy_entity()允许销毁一个实体(类、个人、财产等)。).

>>> temporary_bacterium = Bacterium()
>>> destroy_entity(temporary_bacterium)

5.10 销毁本体

destroy()方法允许你永久删除一个本体。这个方法释放了本体在 quadstore 中占据的空间。

>>> onto_temp = get_ontology("http://tmp.org/onto.owl")
>>> onto_temp.destroy()

5.11 保存本体

save()方法允许在磁盘上保存一个本体:

onto.save(file)

其中 file 可以是文件名或 Python 文件对象。如果没有指定文件,Owlready 将本体保存在onto_path中的相应目录下(参见 4.10 节)。

可选的format属性指定了本体文件的格式。目前,owl 已经支持两种文件格式:RDF/XML ( format = rdfxml)和 N-Triples ( format = ntriples)。默认情况下,使用 RDF/XML 格式。例如,您可以将一个本体保存为 N-Triples,如下所示:

>>> onto.save("file.owl", format = "ntriples")

5.12 导入本体

要导入一个本体,只需将其添加到目标本体的imported_ontologies列表中:

>>> onto.imported_ontologies.append(another_onto)

要删除导入,只需用remove()从列表中删除本体:

>>> onto.imported_ontologies.remove(another_onto)

5.13 同步

当一个多线程程序使用 Owlready 来创建或修改本体时,几个线程可能想同时写入 quadstore,这会导致数据库损坏。注意,即使每个线程写入不同的本体,问题仍然是一样的,因为所有的本体实际上共享同一个 quadstore。在多线程程序的情况下,因此有必要同步写入(相反,读取不需要同步)。

特别是,用 Flask 生成的 web 服务器通常是多线程的(默认情况下,Flask 使用 Werkzeug 服务器,它以多线程模式启动服务器)。在前一章中,我们没有同步问题,因为服务器只读取了本体,而没有修改它。

Owlready 自动管理同步,如下所示。Owlready 自动将写数据库锁定在具有本体的的条目处:...块,并在该块的出口处解锁它。同样,下面的函数和方法也是自动同步的:get_ontology()ontology.load()ontology.destroy()sync_reasoner()(我们将在下一章看到)。

总之,只要你使用语法“with ontology:……”,你(实际上)不用担心同步!我们将在 7.7 节的动态多线程网站中看到一个同步的例子。

5.14 示例:从 CSV 文件填充本体

填充一个本体包括创建大量的个体(或者可能的类)。这通常是通过外部资源完成的,例如电子表格文件(如 LibreOffice Calc、Excel 等)。).这些电子表格文件可以保存为 CSV 格式,在 Python 中易于阅读。

Python csv模块使得用 Python 读写 CSV 文件变得很容易。它包含两个类,csv.readercsv.writer,分别用于读和写。每个都将一个打开的 Python 文件对象作为参数。next()功能允许您从reader中获取下一行。

在接下来的两节中,我们将看到一个用个体,然后用类来填充细菌本体的例子。

用个人填充

下图显示了描述细菌类个体的 CSV 文件的简单示例。该文件命名为“population _ individuals . CSV”;它看起来是这样的:

img/502592_1_En_5_Figa_HTML.jpg

当细菌有几个类群时,可以用几行来描述(例如,上图中的细菌“bact3”)。

以下程序用于使用从 CSV 文件中的数据创建的个人来填充本体:

# File population_individuals.py
from owlready2 import *
import csv

onto = get_ontology("bacteria.owl").load()

onto_individuals = get_ontology("http://lesfleursdunormal.fr/↲static/_downloads/bacteria_individuals.owl")
onto_individuals.imported_ontologies.append(onto)

f = open("population_individuals.csv")
reader = csv.reader(f)
next(reader)

with onto_individuals:
    for row in reader:
        id, gram_positive, shape, grouping, nb_colonies = row
        individual = onto.Bacterium(id)

        if gram_positive:
            if gram_positive == "True": gram_positive = True
            else:                       gram_positive = False
            individual.gram_positive = gram_positive

        if nb_colonies:
            individual.nb_colonies = int(nb_colonies)

        if shape:
            shape_class = onto[shape]
            shape = shape_class()
            individual.has_shape = shape

        if grouping:
            grouping_class = onto[grouping]
            grouping = grouping_class()
            individual.has_grouping.append(grouping)

onto_individuals.save("bacteria_individuals.owl")

该程序包括以下步骤:

  1. 导入owlreadycsv Python 模块。

  2. 加载细菌本体。

  3. 创建一个新的本体来存储个体,命名为“细菌 _ 个体.猫头鹰”。事实上,当内容自动生成时,最好将其存储在一个单独的本体中,以便能够将其与 Protégé中手工生成的内容区分开来。这个新的本体导入了以前的“bacteria.owl”本体。

  4. 打开 CSV 文件进行读取,用next()函数跳过第一行。实际上,第一行包含列标题(“id”、“gram_positive”等)。)但不描述细菌。

  5. 开始一个“用:……”块,表明所有创建的 RDF 三元组都将保存在新的本体中。

  6. 对于 CSV 文件中剩余的每一行:

    1. 对于“形状”和“分组”属性,步骤是相似的。我们在细菌的本体中检索相应的类(语法为“本体[实体的名称]”),并创建一个新的实例。然后我们把这个值赋给个人。对于“grouping”属性,Owlready 中的值是一个列表,因为该属性不起作用,所以我们将新值添加到这个列表中。

    2. 检查 CSV 文件中是否缺少该值。

    3. 将该值转换为所需的格式。事实上,从 CSV 文件中提取的所有值都是字符串。在这里,我们将这些值转换为布尔值(表示克数)和整数(表示菌落数)。

    4. 给个人分配一个值。

    5. 检索当前行中的标识符、Gram 状态、表单、分组和菌落数。

    6. 创建具有所需标识符的细菌类个体。请注意,如果已经用相同的标识符创建了一个个体,则返回已经存在的标识符。

    7. 对于“革兰氏阳性”和“nb 菌落”属性:

  7. 保存新的本体。

随后,如果修改了 CSV 文件,很容易删除“bacteria_individuals.owl”本体,通过再次运行程序重新创建。

用类填充

本体也可以从描述类而不是个体的 CSV 文件中填充。事实上,在生物医学领域,所考虑的实体几乎总是可以细分的:例如,细菌的物种分为亚种,然后是菌株,药物根据剂量,制造商的品牌或批号,疾病根据严重程度,慢性,等等。在这种情况下,通常只使用类来建模一个域。

下图显示了描述细菌子类的 CSV 文件的简单示例:

img/502592_1_En_5_Figb_HTML.jpg

该文件被命名为“population_classes.csv”。这个文件类似于前面的用于个人的 CSV 文件,但是它有一个新的列“parent”,用于继承。此外,删除了“nb _ colonies”一栏,因为可以对给定的观察结果进行菌落计数,但不能对某一种细菌进行计数。当一个类有几个父类和/或几个分组时,可以用几行来描述它(就像上图中的双歧杆菌类)。

以下程序使用从 CSV 文件中的数据创建的类来填充本体:

# File population_classes.py
from owlready2 import *
import csv, types

onto = get_ontology("bacteria.owl").load()

onto_classes = get_ontology("http://lesfleursdunormal.fr/↲static/_downloads/bacteria_classes.owl")
onto_classes.imported_ontologies.append(onto)

f = open("population_classes.csv")
reader = csv.reader(f)
next(reader)

with onto_classes:
    for row in reader:
        id, parent, gram_positive, shape, grouping = row

        if parent: parent = onto[parent]
        else:      parent = Thing

        Class = types.new_class(id, (parent,))

        if gram_positive:
            if gram_positive == "True": gram_positive = True
            else:                       gram_positive = False
            Class.gram_positive = gram_positive

        if shape:
            shape_class = onto[shape]
            Class.has_shape = shape_class

        if grouping:
            grouping_class = onto[grouping]
            Class.has_grouping.append(grouping_class)

onto_classes.save("bacteria_classes.owl")

该程序遵循与上一个程序相同的结构,但有三点不同:

  • 父类的名称出现在“父”行中。类本身是从本体中获取的,默认为Thing

  • 新类是使用types Python 模块动态创建的,其名称可在变量中找到(见第 5.2.1 节)。如果先前已经创建了具有相同名称的类,则返回相同的类(如果指定了新的父类,则在更新之后)。

  • 对于形状和分组属性,我们直接使用该类,而不创建实例。因此,新类的属性被定义为存在限制,在 Owlready 中用作类属性(参见 4.5.4 和 5.5 节)。

我们选择在类中不使用正式的定义(使用“equivalent_to”,正如我们在第三章中对假单胞菌类所做的)。我们可以做出不同的选择,使用等价关系和构造函数来创建定义(我们将在下一章中这样做;参见 6.6)。

为了检查程序是否正常工作,我们可以在 Protégé编辑器中打开用 Python 创建的本体,如下图所示:

img/502592_1_En_5_Figc_HTML.jpg

5.15 摘要

在这一章中,你已经学习了如何在 Python 中修改现有的本体,以及如何从头开始创建新的本体。我们还讨论了多线程程序中的同步问题。最后,我们看到了如何从简单的 CSV 文件中填充一个本体,可以用任何电子表格软件访问。

六、构造、限制和类属性

在这一章中,我们将看到如何用 Owlready 处理 Python 中所有的 OWL 构造函数。我们还将看到 owl 已经提供的不同的“快捷方式”,以方便构造函数的使用,特别是限制。

6.1 创建构件

OWL 构造器允许你从类、个体和属性中定义逻辑结构(参见 3.4.6 和 3.4.7 节)。

在 Owlready 中,使用语法“property . restriction _ type(value)”创建限制,对限制类型使用与 Protected:

  • property.some(Class)对于存在性限制

  • property.only(Class)对于普遍的限制

  • property.value(individual or data)用于值限制(也称为角色填充

  • property.exactly(cardinality, Class)对于精确的基数限制

  • property.min(cardinality, Class)property.max(cardinality, Class)分别用于最小和最大基数限制

逻辑运算符 NOT(补码)、AND(交集)和 OR(并集)按如下方式获得:

  • Not(Class)

  • And([Class1, Class2,...])Class1 & Class2 & ...

  • Or([Class1, Class2,...])Class1 | Class2 | ...

如下获得一组个体:

  • OneOf([individual1, individual2,...])

属性的倒数按如下方式获得:

  • Inverse(Property)

属性链(也称为组合)按如下方式获得:

  • PropertyChain([Property1, Property2,...])

在前面的定义中,类可以是实体,也可以是其他构造函数。因此,构造函数可以相互嵌套。

构造函数可以用在类的is_aequivalent_to属性中。例如,我们可以创建Coccus类(它将圆形细菌分组;参见 3.3),完全用 Python 编写,如下所示:

>>> from owlready2 import *
>>> onto = get_ontology("bacteria.owl").load()
>>> with onto:
...     class Coccus(onto.Bacterium):
...         equivalent_to = [
...         onto.Bacterium & onto.has_shape.some(onto.Round)
...              & onto.has_shape.only(onto.Round)
...         ]

类似地,我们可以用四个限制来定义假单胞菌类:

>>> with onto:
...     class Pseudomonas(onto.Bacterium):
...         is_a = [
...           onto.has_shape.some(onto.Rod),
...           onto.has_shape.only(onto.Rod),
...           onto.has_grouping.some(onto.Isolated |↲onto.InPair),
...           onto.gram_positive.value(False)
...         ]

Owlready 将自动完成用 Python 中声明为父类的类(这里是Bacterium类)创建的类的is_a列表。我们可以验证如下:

>>> Pseudomonas.is_a
[bacteria.Bacterium,
 bacteria.has_shape.some(bacteria.Rod),
 bacteria.has_shape.only(bacteria.Rod),
 bacteria.has_grouping.some(bacteria.Isolated | bacteria.InPair),
 bacteria.gram_positive.value(False)]

6.2 访问构造参数

以下属性提供对主构造函数中包含的信息的访问(有关构造函数及其属性的完整列表,请参考《参考手册》附录 C 中的 C.6):

  • 逻辑运算符 AND 和 OR(交集和并集,分别为类AndOr):

    • 属性Classes:交集或并集相关的类的列表
  • 限制(类别Restriction):

    • 属性property:与限制相关的属性

    • 属性type:限制类型(从常量SOMEONLYVALUEMAXMINEXACTLY中选择的值)

    • 属性value:与限制相关的值(类型SOMEONLYMAXMINEXACTLY的类,个体或类型VALUE的值)

    • 属性cardinality:关系的数量(仅适用于MAXMINEXACTLY限制)

例如,如果我们采用类链球菌及其等效定义,我们可以在 Python 中对其进行如下分析:

>>> onto.Streptococcus.equivalent_to[0]
bacteria.Bacterium
& bacteria.has_shape.some(bacteria.Round)
& bacteria.has_shape.only(bacteria.Round)
& bacteria.has_grouping.some(bacteria.InSmallChain)
& bacteria.has_grouping.only(Not(bacteria.Isolated))
& bacteria.gram_positive.value(True)
>>> onto.Streptococcus.equivalent_to[0].Classes[1]
bacteria.has_shape.some(bacteria.Round)
>>> onto.Streptococcus.equivalent_to[0].Classes[1].property
bacteria.has_shape
>>> onto.Streptococcus.equivalent_to[0].Classes[1].type == SOME
True
>>> onto.Streptococcus.equivalent_to[0].Classes[1].value
bacteria.Round

如果我们不知道所使用的构造函数的类别,那么isinstance() Python 函数允许我们测试它,例如:

>>> constructor = onto.Streptococcus.equivalent_to[0]
>>> if   isinstance(constructor, And):
...     print("And", constructor.Classes)
... elif isinstance(constructor, Or):
...     print("Or", constructor.Classes)
... elif isinstance(constructor, Restriction):
...     print("Restriction", constructor.property,↲ constructor.type, constructor.value)
And [bacteria.Bacterium,
     bacteria.has_shape.some(bacteria.Round),
     bacteria.has_shape.only(bacteria.Round),
     bacteria.has_grouping.some(bacteria.InSmallChain),
     bacteria.has_grouping.only(Not(bacteria.Isolated)),
     bacteria.gram_positive.value(True)]

此外,这里列出的属性都是可修改的:当属性被修改时,Owlready 会自动更新 quadstore。例如,我们可以更改链球菌类别的革兰氏状态限制,如下所示:

>>> onto.Streptococcus.equivalent_to[0].Classes[-1].value = False

6.3 作为类别属性的限制

Owlready 提供了对所有 OWL 构造函数的访问,正如我们在前面两节中看到的。然而,创建构造函数或访问其中包含的信息通常是复杂而乏味的。这就是为什么 Owlready 也提供了几个“快捷方式”来方便构造函数的使用。在 4.5.4 中,我们已经看到了一个访问存在限制的快捷方式的例子,就好像它们是类属性一样。

事实上,限制经常被用来表示类之间的关系。两个类之间的关系比两个个体之间的关系更复杂:在两个个体之间,关系要么存在(对应于 quadstore 中的三元组),要么不存在。相反,一个类集合了几个个体,这就导致了几个场景:

  • 第一类中的所有个体都与第二类中的至少一个个体相关联:这是存在性限制(“被保护者中的一些”)。

  • 第一类的所有个人只与第二类的个人有关系:这是普遍的限制(“只”在被保护者中)。

  • 第一个类中的每个个体都与第二个类中的每个个体相关联:OWL 不直接允许在类之间创建这种类型的关系。然而,通过具体化属性,也就是说,通过将其转换成与两个属性相关联的类,可以获得等效的结果。

Owlready 允许将两个类之间的关系转换成类属性,反之亦然。这使您可以轻松地创建或读取对应于以下形式的构造函数:

  • (属性某类)

  • (个人财产价值)

  • (仅属性(类或...))

  • (仅财产({个人,...}))

  • (仅属性(类或...或者{个人,...}))

特殊注释“class_property_type”表示给定属性使用哪种类型的限制。可能的值有

  • ["some"]:使用该值时,类属性对应于存在性限制(“一些”)。如果未指定注释“class_property_type”,这是属性的默认值。

  • ["only"]:使用该值时,类属性对应通用限制(“only”)。

  • ["some, only"]:使用该值时,类属性对应于存在性和普遍性限制。

  • ["relation"]:这个值使用 RDF 三元组创建类之间的直接关系。请注意,这些直接关系在 OWL 中是无效的,推理器不会考虑它们。然而,许多 RDF 图数据库使用类之间的直接关系;这个值使得读取或产生这样的数据库成为可能。这些 RDF 数据库缺乏形式语义,因此不是 OWL 本体。

我们可以使用这些类属性来更容易地定义第三章中细菌本体中的细菌类。我们将从修改三个属性开始,gram_positivehas_shapehas_grouping,以便指定与每个属性相关的类属性的类型。我们选择保持与第三章相同的建模选择:我们将对gram_positivehas_grouping使用存在性限制,对has_shape使用存在性和普遍性限制:

>>> with onto:
...     onto.gram_positive.class_property_type = ["some"]
...     onto.has_shape.class_property_type = ["some, only"]
...     onto.has_grouping.class_property_type = ["some"]

然后,我们可以用比以前更简单的方式创建一个新的假单胞菌类,称为Pseudomonas2(见 6.1),只需定义类属性的值:

>>> with onto:
...     class Pseudomonas2(onto.Bacterium): pass
...     Pseudomonas2.gram_positive = False
...     Pseudomonas2.has_shape = onto.Rod
...     Pseudomonas2.has_grouping = [onto.Isolated | onto.InPair]

下面的语法是等效的,但是更简单。它包括在类的主体中定义类属性(参见 2.9 Python 中 class 语句的语法和类属性的使用):

>>> with onto:
...     class Pseudomonas3(onto.Bacterium):
...         gram_positive = False
...         has_shape = onto.Rod
...         has_grouping = [onto.Isolated | onto.InPair]

然后,我们可以验证是否已经按照预期创建了限制:

>>> Pseudomonas3.is_a
[bacteria.Bacterium,
 bacteria.has_shape.some(bacteria.Rod),
 bacteria.has_shape.only(bacteria.Rod),
 bacteria.has_grouping.some(bacteria.Isolated | bacteria.InPair),
 bacteria.gram_positive.value(False)]

获得的假单胞菌 3 类与这里使用构造函数定义的类相同(见 6.1)。

我们还可以使用类别属性创建新的细菌类别:

>>> with onto:
...     class Listeria(onto.Bacterium):
...         gram_positive = True
...         has_shape     = onto.Rod
...         has_grouping  = [onto.InLongChain]
>>> Listeria.is_a
[bacteria.Bacterium,
 bacteria.has_shape.some(bacteria.Rod),
 bacteria.has_shape.only(bacteria.Rod),
 bacteria.has_grouping.some(bacteria.InLongChain),
 bacteria.gram_positive.value(True)]

可以更改类及其类属性;Owlready 将自动更新 quadstore 中的限制。在下面的示例中,我们向 Listeria 类添加了一个分组:

>>> Listeria.has_grouping.append(onto.Isolated)
>>> Listeria.is_a
[bacteria.Bacterium,
 bacteria.has_shape.some(bacteria.Rod),
 bacteria.has_shape.only(bacteria.Rod),
 bacteria.has_grouping.some(bacteria.InLongChain),
 bacteria.has_grouping.some(bacteria.Isolated),
 bacteria.gram_positive.value(True)]

类属性也用于“读取”,即分析类的构造函数,即使它们不是通过 Owlready 类属性创建的。例如,如果我们像这样创建Listeria2类,使用构造函数:

>>> with onto:
...     class Listeria2(onto.Bacterium):
...         is_a = [onto.has_shape.some(onto.Rod),
...                 onto.has_shape.only(onto.Rod),
...                 onto.has_grouping.some(onto.InLongChain),
...                 onto.gram_positive.value(True)]

我们仍然可以使用类属性来分析构造函数(或者修改它们):

>>> Listeria2.has_grouping
[bacteria.InLongChain]

这解释了为什么我们能够使用第四章中的类属性来访问外部本体。

下表总结了 Owlready 支持的类属性类型及其生成的构造函数。在这个表中,CC1C2是类,i1i2是个体,p是属性。

|

p.class_property_type

|

翻译< <C.p = [C1, C2,..., i1, i2,...]>

| | --- | --- | | ["some"] | C subclassof: p some C1``C subclassof: p some C2``...``C subclassof: p value i1``C subclassof: p value i2``... | | ["only"] | C subclassof: p only (C1 or C2... or {i1, i2...}) | | ["some, only"] | C subclassof: p some C1``C subclassof: p some C2``...``C subclassof: p value i1``C subclassof: p value i2``...``C subclassof: p only (C1 or C2... or {i1, i2...}) | | ["relation"] | 断言以下 RDF 三元组:(C, p, C1)``(C, p, C2)``...``(C, p, i1)``(C, p, i2)``... |

6.4 定义的类别

Owlready 还允许您使用类属性创建已定义的类,定义如下:

  • Parent_class1 和 Parent_class2...

和(属性某些类)...

和(个人财产价值)...

和(仅属性(类...或者{个人,...}))

为此,Owlready 使用特殊的布尔注释“defined_class”来表示一个类已被定义。如果这个注释对于一个类是True,Owlready 将从类属性中生成一个定义,而不是上一节中看到的限制。该注释的默认值为False

以下示例创建了一个新定义的细菌类别:

>>> with onto:
...     class Corynebacterium(onto.Bacterium):
...         defined_class = True
...         gram_positive = False
...         has_shape = onto.Rod
...         has_grouping = [onto.InCluster]

请注意类体的第一行“defined_class = True”,这表明它是一个已定义的类。

我们可以验证定义已经创建:

>>> Corynebacterium.equivalent_to
[bacteria.Bacterium
& bacteria.gram_positive.value(False)
& bacteria.has_shape.some(bacteria.Rod)
& bacteria.has_shape.only(bacteria.Rod)
& bacteria.has_grouping.some(bacteria.InCluster)]

另一方面,Owlready 没有创建一个简单的限制(也就是说,除了定义中存在的那些限制之外):

>>> Corynebacterium.is_a
[bacteria.Bacterium]

和以前一样,可以修改类属性,Owlready 将自动更新定义。类似地,类的属性也允许访问定义中的信息,即使它不是用 Owlready 创建的。

当创建定义时,Owlready 将类属性的不同值组合成一个定义。通常,如果CP1P2S1S2O1O2是类,s是使用存在性限制的属性,o是使用普遍性限制的属性,s1s2o1o2是个体,当我们定义:

C.is_a = [P1, P2,...]
C.s = [S1, S2,..., s1, s2,...]
C.o = [O1, O2,..., o1, o2,...]

Owlready 将生成以下定义:

C equivalent_to P1 and P2...
            and (s some S1) and (s some S2)...
            and (s value s1) and (s value s2)...
            and (o only (O1 or O2... or {o1, o2...}))

6.5 示例:用 Python 创建细菌本体

下面的程序以举例说明的方式给出,它可以使用构造函数,完全用 Python 从头开始重新创建第三章的细菌本体。用 Python 创建本体可能比用 Protégé更费力,但它也有优点:特别是,它可以复制和粘贴定义,这允许您快速创建相似的类。

# File create_onto.py
from owlready2 import *

onto = get_ontology("http://lesfleursdunormal.fr/static/↲
_downloads/bacteria.owl#")

with onto:
    class Shape(Thing): pass
    class Round(Shape): pass
    class Rod(Shape): pass

    AllDisjoint([Round, Rod])

    class Grouping(Thing): pass
    class Isolated(Grouping): pass
    class InPair(Grouping): pass
    class InCluster(Grouping): pass
    class InChain(Grouping): pass
    class InSmallChain(InChain): pass
    class InLongChain(InChain): pass

    AllDisjoint([Isolated, InPair, InCluster, InChain])
    AllDisjoint([InSmallChain, InLongChain])

    class Bacterium(Thing): pass

    AllDisjoint([Bacterium, Shape, Grouping])

    class gram_positive(Bacterium >> bool, FunctionalProperty):        pass
    class nb_colonies(Bacterium >> int, FunctionalProperty):        pass

    class has_shape(Bacterium >> Shape, FunctionalProperty):        pass
    class has_grouping(Bacterium >> Grouping): pass

    class is_shape_of(Shape >> Bacterium):
        inverse = has_shape
    class is_grouping_of(Grouping >> Bacterium):
        inverse = has_grouping

    class Pseudomonas(Bacterium):
        is_a = [ has_shape.some(Rod),
                 has_shape.only(Rod),
                 has_grouping.some(Isolated | InPair),
                 gram_positive.value(False) ]

    class Coccus(Bacterium):
        equivalent_to = [ Bacterium
                        & has_shape.some(Round)
                        & has_shape.only(Round) ]

    class Bacillus(Bacterium):
        equivalent_to = [ Bacterium
                        & has_shape.some(Rod)
                        & has_shape.only(Rod) ]

    class Staphylococcus(Coccus):
        equivalent_to = [ Bacterium

                        & has_shape.some(Round)
                        & has_shape.only(Round)
                        & has_grouping.some(InCluster)
                        & gram_positive.value(True) ]

    class Streptococcus(Coccus):
        equivalent_to = [ Bacterium
                        & has_shape.some(Round)
                        & has_shape.only(Round)
                        & has_grouping.some(InSmallChain)
                        & has_grouping.only( Not(Isolated) )
                        & gram_positive.value(True) ]

    unknown_bacterium = Bacterium(
        "unknown_bacterium",
        has_shape = Round(),
        has_grouping = [ InCluster("in_cluster1") ],
        gram_positive = True,
        nb_colonies = 6
    )
onto.save("bacteria.owl")

6.6 示例:用已定义的类填充本体

在这个例子中,我们将继续细菌本体的种群。这一次,我们将用类填充本体(如 5.14.2 中一样),但是使用具有等价关系的定义。我们将像以前一样重用相同的 CSV 文件(称为“population_classes.csv”):

img/502592_1_En_6_Figa_HTML.jpg

我们可以用两种方式填充本体:要么使用类属性(这是最简单的选择),要么使用构造函数(这更复杂,但是如果您想要创建比 Owlready 生成的定义更复杂的定义,这可能是必要的)。

6.6.1 使用类属性填充

下面的程序使用类属性从前面的 CSV 文件中的数据用定义的类来填充细菌的本体:

# File population_defined_classes1.py
from owlready2 import *
import csv, types

onto = get_ontology("bacteria.owl").load()

onto.gram_positive.class_property_type = ["some"]
onto.has_shape.class_property_type = ["some", "only"]
onto.has_grouping.class_property_type = ["some"]

onto_classes = get_ontology("http://lesfleursdunormal.fr/↲static/_downloads/bacteria_defined_classes.owl")
onto_classes.imported_ontologies.append(onto)

f = open("population_classes.csv")
reader = csv.reader(f)
next(reader)

with onto_classes:
    for row in reader:
        id, parent, gram_positive, shape, grouping = row

        if parent: parent = onto[parent]
        else:      parent = Thing

        Class = types.new_class(id, (parent,))
        Class.defined_class = True

        if gram_positive:
            if gram_positive == "True": gram_positive = True
            else:                       gram_positive = False
            Class.gram_positive = gram_positive

        if shape:
            shape_class = onto[shape]
            Class.has_shape = shape_class

        if grouping:
            grouping_class = onto[grouping]
            Class.has_grouping.append(grouping_class)

onto_classes.save("bacteria_defined_classes.owl")

这个程序与我们在上一章看到的创建未定义类的程序非常相似(见 5.14.2)。只有两点不同:

  • 在程序的开始,对于三个属性中的每一个,我们指出了相关类属性的类型。

  • 当我们创建一个类时,我们指出它是一个已定义的类(带有“Class.defined_class = True”)。

为了验证程序执行后的正常运行,我们可以在 Protégé编辑器中打开用 Python 创建的本体,如下面的屏幕截图所示:

img/502592_1_En_6_Figb_HTML.jpg

使用构造填充

下面的程序还从 CSV 文件中的数据用定义的类填充细菌的本体。与前一个不同,它不使用类属性,而是直接创建构造函数。这第二个程序比上一个更复杂,可见对 Owlready 的类属性的兴趣!

# File population_defined_classes2.py
from owlready2 import *
import csv, types

onto = get_ontology("bacteria.owl").load()

onto_classes = get_ontology("http://lesfleursdunormal.fr/↲static/
_downloads/bacteria_defined_classes.owl")
onto_classes.imported_ontologies.append(onto)

f = open("population_classes.csv")
reader = csv.reader(f)
next(reader)

id_2_parents       = defaultdict(list)
id_2_gram_positive = {}
id_2_shape         = {}
id_2_groupings     = defaultdict(list)

for row in reader:
    id, parent, gram_positive, shape, grouping = row

    if parent:
        id_2_parents[id].append(onto[parent])

    if gram_positive

:
        if gram_positive == "True": gram_positive = True
        else:                       gram_positive = False
        id_2_gram_positive[id] = gram_positive

    if shape:
        shape_class = onto[shape]
        id_2_shape[id] = shape_class

    if grouping:
        grouping_class = onto[grouping]
        id_2_groupings[id].append(grouping_class)

with onto_classes:
    for id in id_2_parents:
        if id_2_parents[id]:
            Class = types.new_class(id,↲tuple(id_2_parents[id]))
        else:
            Class = types.new_class(id, (Thing,))

        conditions = []

        if id in id_2_gram_positive:
            conditions.append(onto.gram_positive.value(↲
                              id_2_gram_positive[id]))

        if id in id_2_shape

:
            conditions.append(onto.has_shape.some↲(id_2_shape[id]))
            conditions.append(onto.has_shape.only↲(id_2_shape[id]))

        for grouping in id_2_groupings[id]:
            conditions.append(onto.has_grouping.some(grouping))

        if   len(conditions) == 1:
            Class.equivalent_to.append(conditions[0])
        elif len(conditions) > 1:
            Class.equivalent_to.append( And(conditions) )

onto_classes.save("bacteria_defined_classes.owl")

该计划有两个部分。第一部分读取整个 CSV 文件并将所有数据存储在字典中,第二部分创建类和等价关系。事实上,等价关系必须“整体”定义(如我们在 3.4.8 中所见)。因此,有必要了解一个类的所有信息,以便能够创建它的定义。然而,在我们的 CSV 文件中,一个类可以在几行中定义(例如,这里的类双歧杆菌)。在这种情况下,我们不能在只读完第一行之后创建定义。

第一部分使用标准字典和defaultdict,即带有默认值的字典(见 2.4.6),以简化程序。这个字典自动创建缺失的条目,并用空列表的值初始化它们。

程序的第二部分遍历id_2_parents字典中的所有类标识符。对于每个标识符,我们创建一个从父类继承的类,或者,如果失败,从Thing继承。接下来,我们创建一个名为conditions的条件列表,最初为空。然后,我们查看字典中属性gram_positivehas_shapehas_grouping的可用值,并在列表条件中添加相应的限制:

  • 对于gram_positive属性,我们使用了一个值限制,因为它是一个数据属性。

  • 对于has_form属性,我们使用了两个限制,一个存在限制和一个普遍限制,就像我们在第三章中所做的那样。

  • 对于has_grouping属性,我们使用了一个单一的、存在的限制,以便为其他分组的可能性留有余地。

最后,我们在Class.equivalent_to中添加一个等价关系。如果conditions列表只包含一个条件,我们将这个唯一的条件添加到Class.equivalent_to中。如果conditions列表包含条件,我们用操作符And()执行这些条件的交集,然后我们将这个交集添加到Class.equivalent_to。这意味着当几个条件都存在时,它们都必须满足。

这个程序创建的本体相当于前一个程序创建的本体。

6.7 总结

在这一章中,你已经学习了如何处理 OWL 结构和类限制,这是 OWL 的一个主要特性。我们还介绍了 owl 已经提出的各种快捷方式,比如类属性或定义类。最后,我们看到了如何从 CSV 文件创建定义的类,以及如何完全用 Python 创建第三章的细菌本体。你现在可以用 Python 创建几乎所有可以用 Protégé创建的东西。

七、自动推理

在这一章中,我们将看到如何在 Python 中使用 HermiT 和 Pellet 推理机,以便检查本体的一致性,并基于逻辑构造器执行自动推理和分类。

7.1 分离

Owlready 允许使用 AllDisjoint 类在类之间创建分离。例如,我们可以将类 Isolated、InPair、InCluster 和 InChain 声明为成对分离,如下所示:

>>> from owlready2 import *
>>> onto = get_ontology("bacteria.owl").load()

>>> AllDisjoint([onto.Isolated, onto.InPair, onto.InCluster,
... onto.InChain])

注意,Owlready 和 Protégé一样,不区分两个实体之间的分离和几个实体之间的成对分离,这与 OWL 不同。Owlready 会根据不相交中涉及的实体数量自动选择正确的 OWL 方法。此外,AllDisjoint 还可以处理属性列表(不相交的属性)或个体列表(不同的个体)。

使用disjoints()方法可以找到一个类所属的所有不相交部分。它返回一个生成器来列出涉及给定实体的所有 AllDisjoints。然后,AllDisjoint 的entities属性使得获得声明为 Disjoint 的实体列表成为可能。

7.2 用开放世界假设进行推理

sync_reasoner()功能允许您运行推理器,并自动将推导出的事实应用到 quadstore。默认情况下,使用隐士推理器。sync_reasoner_pellet()sync_reasoner_hermit()函数用于指定推理机;Pellet 和隐士在 Owlready 中的工作方式是一样的。

注意:HermiT 和 Pellet 是 Java 程序,所以您需要安装一个 Java 虚拟机来使用它们。如果你没有 Java,你可以从 www.java.com/ (对于 Windows 和 Mac OS)或者从你的 Linux 发行版的包(对于 Java Runtime Environment 和 Java Development Kit,这些包通常被命名为“jre”或“jdk”)安装它。另一方面,推理器本身已经准备好了。如果 Java 安装在非标准目录中(尤其是在 Windows 上),可以按如下方式输入 Java 的路径(用您的路径替换):

import owlready2
owlready2.JAVA_EXE = "C:\\Program Files\\Java\\jre8\\bin\\↲java.exe"

举个例子,我们以细菌的本体为例,从检查个体“未知 _ 细菌”所属的类开始:

>>> onto.unknown_bacterium.__class__
bacteria.Bacterium

然后我们运行推理器:

>>> sync_reasoner()
* Owlready2 * Running HermiT...
    java [...]
* Owlready2 * HermiT took 0.5354642868041992 seconds
* Owlready * Reparenting bacteria.unknown_bacterium [...]

默认情况下,Owlready 会显示推理器的命令行和执行的重新分类(参数debug = 0可以避免显示)。

这里,我们注意到,单个“未知细菌”已被重新分类为葡萄球菌类,就像它在被保护生物中一样:

>>> onto.unknown_bacterium.__class__
bacteria.Staphylococcus

推理机推导出的事实默认存储在本体“http://inferrences /”中。可以通过使用“with:...”将它们放在另一个本体中块,如下例所示:

>>> onto_inferences = get_ontology("http://lesfleursdunormal.↲fr/
static/_downloads/bacteria_inferences.owl#")
>>> with onto_inferences:
...     sync_reasoner()

这个本体包含了推论;然后可以保存(见 5.11):

>>> onto_inferences.save("bacteria_inferences.owl")

推理因此可以从本体“细菌 _ 推理. owl”加载。这将避免再次调用推理器,从而节省时间。

Owlready 还允许您通过向sync_reasoner()函数传递一个本体列表,将推理限制到某些本体:

>>> sync_reasoner([onto])

最后,可选参数infer_property_valuesinfer_data_property_values(仅由颗粒推理器支持)使得推断个体的属性值成为可能(对于两个选项,分别为对象属性和数据属性):

>>> sync_reasoner(infer_property_values = True)
>>> sync_reasoner_pellet(infer_data_property_values = True)

这两个选项也可以同时使用:

>>> sync_reasoner_pellet(infer_property_values = True,
...                      infer_data_property_values = True)

7.3 封闭世界或局部封闭世界中的推理

OWL 推理器根据开放世界假设运行:任何不被禁止的事情都被认为是可能的(见 3.5 节)。然而,通常希望将推理局限于已知的事实,对于整个本体或者仅仅对于某些实体。这被称为“在一个封闭的世界中”的推理,或者有时被称为“否定即失败”:也就是说,没有明确知道的一切都被认为是假的。

在 Owlready 中,close_world()函数允许“封闭世界”,并将推理限制于本体中存在的事实,对于个人、类或在争论中传递的本体。这个函数以构造函数的形式自动添加必要的约束。当所有的本体都是封闭的时候,我们说“在封闭的世界中推理”,当只有一些实体是封闭的时候,我们说“在局部封闭的世界中推理”。

我们已经在第三章 3 中遇到了链球菌类的“开放或封闭世界”的问题(见 3.5 节末尾的第 2 点):一个圆形细菌,成链分组,革兰氏染色阳性,被归类为球菌,但不被归类为链球菌,因为推理者无法证明没有其他分组(未知,因此在本体中不存在)是分离的类型。

在以下示例中,我们创建了符合上述标准的 unknown_bacterium2:

>>> with onto:
...     unknown_bacterium2 = onto.Bacterium(
...               gram_positive = True,
...               has_shape     = onto.Round(),
...               has_grouping  = [onto.InSmallChain()] )

由于前面解释的原因,当推理机被执行时,细菌被重新分类到球菌类中,而不是链球菌类中:

>>> sync_reasoner()
>>> unknown_bacterium2.__class__
bacteria.Coccus

事实上,尽管这种细菌只有一个小的链群,推理者在一个开放的世界中工作。他假设可能存在另一种类型的分组,但这是未知的(has_grouping 属性不起作用;因此,对于同一个人,它可以有几个值)。然而,我们将链球菌类定义为没有独立的类群。在这里,未知细菌 2 没有一个独立的类群,但是我们可以假设这样一个类群是存在的。

要禁止这个假设,解决我们的问题,就必须“封闭世界”,也就是说,当我们说这个细菌在一个小链条上有一个分组的时候,它必然有这个分组,除了这个之外,它没有别的分组。close_world()函数实现了这一点,如下例所示:

>>> close_world(unknown_bacterium2)
>>> unknown_bacterium2.is_a
[bacteria.Coccus,
 bacteria.has_grouping.only(OneOf([bacteria.insmallchain1]))]

我们注意到close_world()给个体增加了一个普遍的限制(即 only ):这个限制表明该细菌只有 SmallChain1 中的分组。注意close_world()没有为属性“gram_positive”和“has_shape”添加任何限制,因为它们是功能性的:每个属性只能有一个值,因此不需要关闭。

现在,如果我们再次运行推理器,我们会发现这种细菌被很好地归类为链球菌:

>>> sync_reasoner()
>>> unknown_bacterium2.__class__
bacteria.Streptococcus

类似地,close_world()函数可以用来关闭整个类或本体。该函数的完整语法如下:

close_world(entity, Properties = None, close_instance_list =↲ True, recursive = True)

entity是封闭世界中要考虑的实体。Properties是要考虑的属性列表(如果值是None,这是默认值,所有属性都将被关闭)。如果close_instance_listTrue(缺省值),封闭类(或多个类)的实例被限制为在本体中断言的实例。当要关闭的实体是一个类时,如果 recursive 是True(默认值),那么close_world()将递归应用于所有的子类。

7.4 不一致的类和不一致的本体

在推理过程中,推理器可能会检测到不一致的类。这些类别是不合逻辑的,因此,不应该有个人属于这些类别。例如,在我们的细菌本体中,从链球菌类继承并与杆状形式( via a restriction)相关联的以下类将是不一致的:

>>> with onto:
...     class RodStreptococcus(onto.Streptococcus):
...         is_a = [onto.has_shape.some(onto.Rod)]

只要没有属于这个阶层的个体,这就不是问题。推理器将不一致的类重新分类为等价于Nothing。因此,可以通过测试Nothing是否是等价类来测试 Python 中的类是否不一致,例如,为了检查 RodStreptococcus 类是否一致,我们可以这样做:

>>> sync_reasoner()

>>> if Nothing in RodStreptococcus.equivalent_to:
...     print("The class is inconsistent!")
... else:
...     print("The class is consistent.")
The class is inconsistent!

此外,default_world.inconsistent_classes()方法提供了一个生成器来迭代所有不一致的类。

相反,当存在至少一个属于不一致类的个体时,整个本体变得不一致。一个不一致的本体包含一个逻辑问题,使它变得荒谬。任何关于本体论的推理都变得不可能。注意不要混淆不一致的类和不一致的本体!在第一种情况下,这并不妨碍推理者做他的工作,而在第二种情况下,推理变得不可能。

在本体不一致的情况下,sync_reasoner()函数引发异常OwlReadyInconsistentOntologyError。让我们继续前面的示例,创建一个 RodStreptococcus 类的个体:

>>> rod_strepto = onto.RodStreptococcus()
>>> sync_reasoner()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/jiba/src/owlready2/reasoning.py", line 120,↲
                                        in sync_reasoner_hermit
    raise OwlReadyInconsistentOntologyError()
owlready2.base.OwlReadyInconsistentOntologyError

这个异常可以在 Python 中捕获,如下所示:

>>> try:
...     sync_reasoner()
...     print("Ok, the ontology is consistent.")
... except OwlReadyInconsistentOntologyError:
...     print("The ontology is inconsistent!")
The ontology is inconsistent!

随着“rod_strepto”个体的加入,细菌的本体变得不一致。按照现在的情况,我们不能再对 quadstore 进行推理,直到这种不一致性得到解决!这就是为什么我们要删除细菌的本体(添加了“rod_strepto”进行修改),以便能够在本章的剩余部分继续使用推理器。

>>> onto.destroy()

然后,我们可以在接下来的章节中从 OWL 文件中重新加载细菌本体(因此没有“rod_strepto”个体)。

7.5 对数字和字符串的限制和推理

ConstrainedDatatype类用于根据一个或多个方面创建受约束的数据类型,例如,正整数或至少三个字符的字符串。语法如下:

ConstrainedDatatype(base_datatype, facet1 = value1, facet2 = value2,...)

base_datatype是初始数据类型,例如:intfloatboolstrnorm_str等等(见表 4-1 )。

以下方面由 XMLSchema 提出,并得到 Owlready 的支持:

  • 对于数值类型:

    • max_inclusive:最大值,包括在内(该值必须小于或等于给定值)

    • max_exclusive:最大值,不包含(该值必须严格小于给定值)

    • min_inclusive:最小值,包括在内(该值必须大于或等于给定值)

    • min_exclusive:最小值,不包含(该值必须严格高于给定值)

    • total_digits:存在的位数

    • fraction_digits:小数点后的位数

  • 对于字符串:

    • length:准确的字符数

    • min_length:最小字符数

    • max_length:最大字符数

    • pattern:约束字符串可能值的正则表达式

    • white_space:表示如何处理空白,有三个可能的值:

      * preserve:空格按原样保留。

      * replace:所有空格(空格、制表、换行等。)被空格替换。

      * collapse : As replace,但是链的开头/结尾的空格被删除,连续的多个空格被替换为一个空格。

请注意 Owlready 允许刻面的定义,但是不考虑它们:例如,即使使用了white_space,空格也不会被替换。另一方面,其他工具可以考虑到这一点(包括推理器)。

例如,对于范围 0,20 内的整数,我们将:

>>> ConstrainedDatatype(int, min_inclusive = 0, max_inclusive = 20)

这些受约束的数据类型可以用在 OWL 限制中(一些),特别是表示“属性值大于/小于 X”形式的范围或限制。在这种情况下,Owlready 还提供了一个缩写符号“property > value”(或> =、<、< =),它允许自动创建一个具有适当的受约束数据类型的存在性限制(“some”)。

下面的例子用一个Person类和两个agesize数字属性创建了一个本体。然后,我们创建了ElderlyTallPerson类,它们是使用对带有受约束数据类型的agesize属性的限制来定义的(对于第二个限制,我们使用了一个简化的符号来代替size.some (ConstrainedDatatype (int, max_inclusive = 1.8)))。

>>> onto_person = get_ontology("http://test.org/person.owl#")

>>> with onto_person:
...     class Person(Thing): pass
...     class age (Person >> int  , FunctionalProperty): pass
...     class size(Person >> float, FunctionalProperty): pass

...     class Elderly(Person):
...       equivalent_to = [
...         Person & age.some(ConstrainedDatatype(int,
...                                       min_inclusive = 65))
...         ]
...     class TallPerson(Person):
...       equivalent_to = [
...         Person & (size >= 1.8) # Shortened notation
...         ]
...
...     p1 = Person(age = 25, size = 2.0)
...     p2 = Person(age = 39, size = 1.7)
...     p3 = Person(age = 65, size = 1.6)
...     p4 = Person(age = 71, size = 1.9)

注意,当将运算符“&”与前面的缩写符号组合用于交集时,有必要使用括号,因为在 Python 语言中,这些运算符之间的优先级顺序不对(在 Python 中,&优先于、< =、和> =)。这就是为什么在前面的例子中,我们用括号将“size >= 1.8”括起来写为“Person & (size >= 1.8)”。

然后,我们可以运行推理器,对我们创建的四个人进行重新分类:

>>> sync_reasoner(onto_person)

最后,我们显示他们的新类(注意,个体p4现在属于两个类,即多重实例化,这在 OWL 中是允许的):

>>> print("p1", p1.is_a)
p1 [person.TallPerson]

>>> print("p2", p2.is_a)
p2 [person.Person]

>>> print("p3", p3.is_a)
p3 [person.Elderly]

>>> print("p4", p4.is_a)
p4 [person.Elderly, person.TallPerson]

注意,在 Protégé中,如果您想要使用浮点值定义具有受约束数据类型的类,请记住 Owlready 默认情况下将“decimal”XML 模式数据类型与 Python 的浮点数相关联。因此,TallPerson类应该用被保护对象中的小数来定义:

Person and (size some decimal[>= "1.8"^^decimal])

但不是用花车。

以下是《门徒》中的结果:

img/502592_1_En_7_Figa_HTML.jpg

或者,您也可以修改 Owlready 为 floats 使用的数据类型,如下所示:

>>> set_datatype_iri(float, "http://www.w3.org/2001/↲XMLSchema#float")

7.6 SWRL 规则

SWRL(语义网规则语言)是一种允许你将推理规则集成到本体中的语言。可以使用 Owlready 在 Protégé编辑器或 Python 中编写规则,然后通过集成的 HermiT 或 Pellet 推理机执行*。*

在细菌的本体中,以下示例规则使得可以将所有圆形的革兰氏阳性菌分类为葡萄球菌,并分组为簇:

Bacterium(?b),
gram_positive(?b, true),
has_shape(?b, ?f), Ronde(?f)
has_grouping(?b, ?r), InCluster(?r)
-> Staphylococcus(?b)

7.6.1 SWRL 语法

SWRL 规则包括一个或多个条件和一个或多个结果,由箭头“->”(由两个字符组成:减号和大于号)分隔。如果规则有几个条件或结果,它们之间用逗号“,”隔开,这隐含着逻辑“和”的意思。构成条件和结果的元素称为原子,同样的原子可以用在条件和结果中。

此外,SWRL 规则使用变量,其名称以“?”开头,比如,“?上例中的“b”。这些变量可以代表个体或值(整数、实数、字符串、布尔值等。),但从不包含类或属性。

可用的原子有:

  • 一个类的成员:“类(?x)”,意思是:

    • “如果换个人呢?x 属于类 class”(当原子被用作条件时)

    • “个人?除了他/她当前的类之外,x 现在属于类 class(当作为结果使用时)

  • 对象属性值:“对象 _ 属性(?x,?y)”,意思是:

    • “如果换个人呢?x 有 property_object?y "(条件)

    • “添加关系?x object_property?y”(结果)

      变量也可以由特定的单个名称替换,例如,“object_property(?x,individual1)”或“object_property (individual2,y)”。

  • 数据属性值:" data_property(?x,?y)”,意思是:

    • “要是个人呢?x 有 data_property?y "(条件)

    • “添加关系?x data_property?y”(结果)

      变量也可以用一个特定的名字来代替(对于?x)或特定值(对于?y),例如“data_property(?x,9.2)"或" data_property(?x,charactersstring)”。

  • 相同的个体:“SameAs(?x,?y)”,意思是:

    • “要是个人呢?x 和个人一样?y "(条件)

    • “个人?x 现在和个人一样?y”(结果)

  • 不同的个体:“不同于(?x,?y)”,意思是:

    • “要是个人呢?x 与个体截然不同?y "(条件)

    • “个人?x 现在不同于个体了?y”(结果)

  • 数据类型中的成员资格:"数据类型(?x)”,意思是:

    • “如果值呢?x 属于给定的数据类型"(条件)

      最常见的数据类型是“int”、“decimal”(用于浮点数)、“bool”、“string”和“normalizedString”。

  • 预定义函数(内置):“函数(?x,?y,...)".存在大量预定义的函数;以下是最常用的:

    • 添加(?结果呢?x,?y):计算机?结果=?x +?y.

    • 减去(?结果呢?x,?y):计算机?结果=?x -?y.

    • 乘(?结果呢?x,?y):计算机?结果=?x ×?y.

    • 分(?结果呢?x,?y):计算机?结果=?x /?y.

    • 相等(?x,?y):测试是否?x =?y.

    • notEqual(?x,?y):测试是否?x≦?y.

    • 小于(?x,?y):测试是否?x

    • greaterThan(?x,?y):测试是否?x >?y.

    • lessThanOrEqual(?x,?y):测试是否?x ≤?y。

    • greaterThanOrEqual(?x,?y):测试是否?x ≥?y.

    • stringConcat(?结果呢?x,?y):串联?x 和?然后把结果放进去。结果。

    • 子串(?结果呢?str,?开始,?length):测试字符串的一部分。str 开始于?开始和结束?长度等于?结果。?长度是可选的。

    • stringLength(?长度,?str):计算字符串的长度?str 然后把结果放进去?长度。

    • 包含(?str,?part):测试字符串是否。部分包含在?字符串。

    • containsIgnoreCase(?字符串,?part):与 contains()相同,但忽略大小写。

    • 以(?str,?start):测试字符串是否。str 以字符串开头?开始吧。

    • endsWith(?str,?end):测试字符串是否。str 以字符串结尾?结束。

7.6.2 受保护人的 SWRL 规则

可以在 Protégé的“活动本体”选项卡和“规则”子选项卡中输入 SWRL 规则,如下图所示。请注意,被保护人并不总是保持规则中元素的顺序;然而,这并没有改变它们的意义。注意, Owlready 只能读取以 RDF/XML 或 N-Triples 文件格式保存的本体中的 SWRL 规则。SWRL 规则不支持 OWL/XML 格式!

img/502592_1_En_7_Figb_HTML.jpg

Protégé中定义的规则可以用推理器执行(例如,使用 Owlready 的sync_reasoner()函数)。当规则的结果为对象属性创建新的关系时,有必要使用选项infer_property_value = True运行推理器。当他们为数据属性创建新的关系时,有必要运行带有infer_data_property_value = True选项的推理器,并使用颗粒推理器。HermiT 不允许推断数据属性的值(参见 7.2)。

当规则在 Protégé中定义并且使用实数时,Protégé默认为浮点数,而 Owlready 需要小数(参见 7.5)。然后有必要强制十进制类型,如下例所示:2.2^^decimal.

7.6.3 SWRL 规则已准备就绪

在 Owlready 中,Imp类允许您创建 SWRL 规则,“Imp”是“implies”的缩写。Owlready 允许您从语法与 Protégé相当的书面规则或者通过手动创建每个原子(这更复杂)来创建 SWRL 规则。

在下面的例子中,我们将创建一个人的本体,其规则是根据人的大小和体重来计算身体质量指数(身体质量指数)。计算身体质量指数的公式如下:

BMI=\frac{weight}{siz{e}²}=\frac{weight}{siz e\times size}

身体质量指数很重要,因为它决定了肥胖:如果一个人的身体质量指数大于或等于 30,就被认为是肥胖。

我们首先创建本体,有一个 Person 类和三个数据属性:体重、尺寸和 bmi。

>>> onto_person = get_ontology("http://test.org/person2.owl#")

>>> with onto_person:
...     class Person(Thing): pass
...     class weight(Person >> float, FunctionalProperty): pass
...     class size  (Person >> float, FunctionalProperty): pass
...     class bmi   (Person >> float, FunctionalProperty): pass

然后,我们为肥胖人群创建一个定义的类别:任何身体质量指数大于或等于 30 的人都将被重新划分到肥胖类别中。

>>> with onto_person:
...     class Obese(Person):
...         equivalent_to = [Person & (bmi >= 30.0)]

最后,我们创建一个 SWRL 规则。该规则将使用以下变量:

  • ?x:类人的个体

  • ?w:他的体重

  • ?s:他的尺码

  • ?s2:他的尺寸是平方

  • ?他的身体质量指数

Imp类允许你在本体中创建新的规则。然后,方法set_as_rule()使得使用类似 Protégé的语法定义规则成为可能:

>>> with onto_person:
...     imp = Imp()
...     imp.set_as_rule("Person(?x), weight(?x, ?w),↲ size(?x, ?s),↲
                         multiply(?s2, ?s, ?s),↲ divide(?b, ?w, ?s2)↲
                         -> bmi(?x, ?b)")

然后,我们可以创建 Person 类的两个个体:

>>> p1 = Person(size = 1.7, weight = 65.0)
>>> p2 = Person(size = 1.7, weight = 90.0)

运行颗粒推理器:

>>> sync_reasoner_pellet(infer_property_values = True,
...                      infer_data_property_values = True)

最后,对 BMI 和两人被重新分类的类别提出质疑:

>>> p1.bmi
22.491348
>>> p1.is_a
[person2.Person]

>>> p2.bmi
31.141868
>>> p2.is_a
[person2.Obese]

str() Python 函数以类似 Protégé的语法显示规则:

>>> str(imp)
'Person(?x), weight(?x, ?w), size(?x, ?s),↲multiply(?s2, ?s, ?s), divide(?b, ?w, ?s2) -> bmi(?x, ?b)'

Owlready 还通过body(对于条件)和head(对于结果)属性提供对规则的条件和结果的访问:

>>> imp.body
[Person(?x), weight(?x, ?w), size(?x, ?s),↲multiply(?s2, ?s, ?s), divide(?b, ?w, ?s2)]
>>> imp.head
[bmi(?x, ?b)]

然后就有可能接近每一个原子。例如,我们可以从条件部分检索第一个原子,并请求它的类和属性:

>>> atom = imp.body[0]
>>> atom
Person(?x)
>>> atom.is_a
[swrl.ClassAtom]
>>> atom.class_predicate
person2.Person
>>> atom.arguments
[?x]

参考手册(见 C.7)给出了原子类别列表和每一类的属性。注意,Owlready 系统地使用了属性“arguments”来访问原子的自变量(即括号中放置的元素),其中 SWRL 有时使用“arguments”,有时使用“argument1”和“argument2”。和 Owlready 一样,这些属性可以直接更改。

最后,rules()variables()方法使得获得一个生成器来迭代本体中的所有 SWRL 规则和 SWRL 变量成为可能。

>>> list(onto_person.variables())
[?x, ?w, ?s, ?s2, ?b]

>>> list(onto_person.rules())
[Person(?x), weight(?x, ?w), size(?x, ?s),↲multiply(?s2, ?s, ?s), divide(?b, ?w, ?s2) -> bmi(?x, ?b)]

7 . 6 . 4 SWRL 规则的优势和局限性

SWRL 规则允许涉及几个变量(称为“自由变量”)的推理。相反,类别定义(通过等价关系,见 3.4.8)没有变量,实际上对应于具有单个自由变量的“伪规则”。所以有些复杂的推理是类定义无法实现的。例如,如果我们考虑有友谊和敌意关系的人,我们可以创建“AmbiguousPerson”类,对应于任何有朋友同时也是敌人的人。这个类不能用等价关系来定义:的确,我们可以定义一类人有一个朋友和一个敌人,但是 OWL 不允许指明这个朋友和这个敌人是同一个人。为此,我们需要两个自由变量:一个是模糊的人,另一个是他的朋友/敌人。

另一方面,这种类型的推理可以很容易地用 SWRL 规则进行,如下所示:

Person(?a),
Person(?b),
friend(?a, ?b),
enemy(?a, ?b),
-> AmbiguousPerson(?a)

然而,SWRL 规则有一个主要的缺点:它们依赖于一个给定的应用,这违背了本体独立性的目标(见 3.3)。例如,假设我们之前使用 SWRL 规则来识别葡萄球菌,而不是正式定义(在 3.4.8 中创建)。在这种情况下,我们无法推断出葡萄球菌的特性:该规则规定所有革兰氏阳性菌,无论是圆形的还是成簇的,都是葡萄球菌,但并没有肯定所有的葡萄球菌都是革兰氏阳性菌,无论是圆形的还是成簇的。因此,SWRL 规则允许识别葡萄球菌,但不允许我们可以重用我们的细菌本体的其他应用。

因此,当两种选择都有可能时,最好使用正式定义,当推理对定义来说太复杂时,最好使用 SWRL 规则。

7.7 示例:基于本体的决策支持系统

决策支持系统帮助专家做出决策,例如,通过提出建议。这里,我们感兴趣的是细菌的鉴定:从细菌上观察到的特征(革兰氏状态、形状、分组等。)和细菌本体中表达的知识,系统试图确定细菌的类型。该系统也可以弃权:当数据不足时,不作出决定。

这个决策支持系统是用 Flask(我们已经在 4.12 节使用过)以动态网站的形式实现的。它包括两个页面:一个“数据输入”页面,包含一个描述观察到的细菌的表格;一个“结果”页面,执行推理并显示结果。

以下程序创建了决策支持网站:

# File decision_support.py
from owlready2 import *
onto = get_ontology("bacteria.owl").load()

from flask import Flask, request
app = Flask(__name__)

@app.route('/')
def entry_page():
    html  = """<html><body>
<h3>Enter the bacteria characteristics:</h3>
<form action="/result">
    Gram:<br/>
    <input type="radio" name="gram" value="True"/> Positive<br/>
    <input type="radio" name="gram" value="False"/> Negative<br/>
    <br/>
    Shape:<br/>

    <input type="radio" name="shape" value="Round"/> Round<br/>
    <input type="radio" name="shape" value="Rod"/> Rod<br/>
    <br/>
    Groupings:<br/>
    <select name="groupings" multiple="multiple">
        <option value="Isolated">Isolated</option>
        <option value="InPair">InPair</option>
        <option value="InCluster">InCluster</option>
        <option value="InSmallChain">InSmallChain</option>
        <option value="InLongChain">InLongChain</option>
    </select><br/>
    <br/>
    <input type="submit"/>
</form>
</body></html>"""
    return html

ONTO_ID = 0

@app.route('/result')
def page_result():
    global ONTO_ID
    ONTO_ID = ONTO_ID + 1

    onto_tmp = get_ontology("http://tmp.org/onto_%s.owl#" %↲ ONTO_ID)

    gram      = request.args.get("gram", "")
    shape     = request.args.get("shape", "")
    groupings = request.args.getlist("groupings")

    with onto_tmp:
        bacterium = onto.Bacterium()

        if   gram == "True": bacterium.gram_positive = True
        elif gram == "False": bacterium.gram_positive = False

        if shape:
            shape_class = onto[shape]
            bacterium.has_shape = shape_class()

        for grouping in groupings:
            grouping_class = onto[grouping]
            bacterium.has_grouping.append(grouping_class())

        close_world(bacterium)

        sync_reasoner([onto, onto_tmp])

    class_names = []
    for bacterium_class in bacterium.is_a:
        if isinstance(bacterium_class, ThingClass):
            class_names.append(bacterium_class.name)
    class_names = "," .join(class_names)

    html  = """<html><body>
<h3>Result: %s</h3>
</body></html>""" % class_names

    onto_tmp.destroy()

    return html

import werkzeug.serving
werkzeug.serving.run_simple("localhost", 5000, app)

该站点的第一个页面用于数据输入,是一个简单的 HTML 页面,包含三个字段(我们可以使用静态 HTML 页面)。对于克状态和形状,使用“单选按钮”字段。对于分组,它是一个“选择”字段,以便允许选择几个值。

用于显示结果的第二个页面执行以下步骤:

  1. 创建一个新的临时本体(为了不“污染”细菌的本体),使用“ http://tmp.org/onto_XXX.owl# ”形式的 IRI,其中 XXX 是一个数字。

  2. 检索表单参数的值。这是通过函数request.args.get()(当参数只能取一个值时)和request.args.getlist()(当参数可以有多个值时;这里,分组就是这种情况)。

  3. 根据输入的值创建一个单独的细菌。

  4. 关闭这种新细菌的世界。这将防止推理者对输入值之外的值做出假设(见第 7.3 节)。

  5. 在两个本体上执行推理机:细菌本体和临时本体(另一方面,由其他线程或进程创建的其他临时本体将不会被考虑)。推论放在临时本体中(原因和之前一样:为了避免污染细菌的主本体)。

  6. 推理后检索细菌所属类的名称。条件“isinstance(bacterium_class, ThingClass)”允许您将自己限制为“真正的”类,排除构造函数(特别是由close_world()创建的限制)。

  7. 破坏临时本体(这破坏了我们创造的细菌,也破坏了它可能的形状和分组,以及推论)。

在程序执行期间,可通过地址“http://127.0.0.1:5000”查阅网站。下面的截图显示了获得的站点,带有“入口”页面和“结果”页面。我们输入了一种革兰氏阳性细菌,圆形,聚集成一条小链。经过验证,系统告诉我们是链球菌。

img/502592_1_En_7_Figc_HTML.jpg

img/502592_1_En_7_Figd_HTML.jpg

只有定义的类(通过等价关系:“equivalent_to”)允许对个体进行重新分类。由于在第三章的细菌本体中定义的唯一种类是葡萄球菌、链球菌、球菌和杆菌,我们的系统目前只能识别葡萄球菌、链球菌、球菌和杆菌。然而,如果本体用其他类别和其他定义来丰富,则有可能识别更多数量的细菌。这可能需要考虑更多的参数(厌氧、其他颜色等。)来更详细地描述细菌。

注意,web 服务器是多线程的,我们在 quadstore 中编写。在这种情况下,我们在 5.13 节中看到,有必要考虑同步。Owlready 自动同步get_ontology()ontology.destroy()指令,以及“与本体:……”街区。所以我们没剩下多少时间来同步了!

我们必须考虑同步的唯一一点是:每个临时本体必须有一个不同的名称。我们用数字给它们命名(onto_1.owl,onto_2.owl 等。).这是必要的,因为page_result()函数可以被几个线程同时调用。因此,每个人都必须有自己的本体:事实上,如果他们共享相同的本体,当第一个线程破坏了本体(onto_tmp.destroy())时,第二个线程就不能再继续工作了。

7.8 摘要

在这一章中,你已经学习了如何使用 Pellet 和隐士猫头鹰推理机进行自动推理。您还学习了如何在一个封闭的世界或局部封闭的世界中进行推理,以及如何使用 SWRL 规则来补充类别定义。

八、注释、多语言文本和全文搜索

在这一章中,我们将看到如何用 Owlready 处理 Python 中的注释。我们还将了解如何处理注释中经常使用的多语言文本,以及如何优化全文搜索。

8.1 注释实体

注释允许您添加关于实体和本体关系的元数据。他们可以描述作者,修改日期,以及实体的描述,有可能有不同语言的文本。注释不同于属性,因为它们不会干扰推理。特别是,当在类上定义时,注释不会被子类继承。

OWL 中默认定义了以下注释属性:

  • 标签(实体标签)

  • 注释(您可以添加的关于实体或关系的任何注释)

  • 那就去吧

  • versionInfo(版本信息)

  • 早期版本

  • 已弃用(用于指示不应再存在且仅为兼容目的而保留的实体)

  • 与不相容

  • backwardCompatibleWith(与早期版本的实体兼容)

  • 被定义为

像任何关系一样,可以用点符号访问注释。注意,注释从来都不是功能性的,所以它们的值总是一个列表。可以用append()方法添加到列表中或重新定义整个列表:

>>> from owlready2 import *
>>> onto = get_ontology("bacteria.owl").load()

>>> onto.unknown_bacterium.label.append("An unknown bacterium")
>>> onto.unknown_bacterium.comment = [
...         "Found in the lab at the bottom of a drawer.",
...         "Remember to analyze it soon." ]

在本例中,我们重用了之前在第三章 Protégé中创建的 unknown _ bacterium 个体。

任何类型的实体都可以被注释:个体,还有类和属性。

像任何关系一样,可以用点符号获得注释。要删除注释,只需从列表中删除它(使用 Python del语句或remove()方法;参见 2.4.4)。

8.2 多语言文本

注释中的字符串可以与语言(例如,英语、法语等)相关联。).locstr对象(本地化字符串)使字符串与其语言(由两个字母的代码标识:英语为“en”,法语为“fr”,等等)相关联成为可能。):

>>> s = locstr("An unknown bacterium", "en")
>>> s.lang
'en'

locstr对象可以作为 Python 字符串使用。它们通常用在注释中:

>>> onto.unknown_bacterium.label = [
...         locstr("An unknown bacterium", "en"),
...         locstr("Une bactérie inconnue", "fr") ]

此外,可以按语言过滤注释列表,如下所示:

>>> onto.unknown_bacterium.label
['An unknown bacterium', 'Une bactérie inconnue']
>>> onto.unknown_bacterium.label.en
['An unknown bacterium']
>>> onto.unknown_bacterium.label.fr
['Une bactérie inconnue']

与其他 Owlready 列表一样,first()方法返回第一个元素(如果列表为空,则返回None):

>>> onto.unknown_bacterium.label.en.first()
'An unknown bacterium'

通过语言过滤的列表允许您添加新的注释,而不需要创建一个locstr对象。在下面的例子中,注释字符串将自动与英语相关联(就像使用了一个locstr对象一样):

>>> onto.unknown_bacterium.comment.en.append("Comment in↲ English.")

8.3 注释构造

还可以使用另一种语法“annotation[constructor]”对构造函数进行注释。以下示例创建了一个带有限制的新细菌子类,并对该限制进行了注释:

>>> with onto:
...     class GramPositiveBacterium(onto.Bacterium):
...         is_a = [onto.gram_positive.value(True)]

>>> comment[GramPositiveBacterium.is_a[-1]].append(
...         "comment on the value

restriction on gram_↲positive.")

8.4 注释属性和关系

属性和关系也可以被注释。属性可以像任何其他实体一样进行注释:

>>> onto.has_shape.comment = ["A comment on the has_shape↲ property."]

OWL 还使得注释关系成为可能,也就是说,您可以注释一个 RDF 三元组,它通过属性(或谓词)将一个主题链接到一个对象。如果您想要以元数据的形式提供关系的附加细节,例如,指示关系的作者或日期,这是很有用的。我们可以在 Owlready 中使用特殊的语法“annotation[subject,property,object]”来实现这一点,例如:

>>> shape = onto.unknown_bacterium.has_shape
>>> comment[onto.unknown_bacterium, onto.has_shape, shape] =↲
    ["A comment on the fact that the bacterium has this shape."]

对于涉及 OWL 内置属性的关系,可以使用特殊值 rdf_type、rdfs_subclassof、owl_equivalentclass 等:

>>> comment[onto.unknown_bacterium, rdf_type, onto.Bacterium] =↲
    ["a comment on belonging to the Bacterium class."]

8.5 创建新的注释类

新的注释属性可以用与其他属性相同的方式创建,继承自AnnotationProperty类,例如:

>>> with onto:
...     class observer(AnnotationProperty): pass
>>> onto.unknown_bacterium.observer = ["Observed by JB Lamy."]
>>> observer[onto.unknown_bacterium, rdf_type,↲ onto.Bacterium] = [
...      "Also observed by JB Lamy."
... ]

8.6 本体元数据

本体的元数据由直接放置在本体上的注释组成(可以在“活动本体”选项卡的“注释”列表中的 Protégé中添加)。它们可能描述版本号、本体的历史、作者的名字等等。在 Owlready 中,这些注释可以通过本体的metadata属性获得。例如,基因本体(GO)的注释“comment”是这样获得的:

>>> go = get_ontology("http://purl.obolibrary.org/obo/go.owl").↲load()

>>> go.metadata.comment
['cvs version: $Revision: 38972 $',
'Includes Ontology(OntologyID(OntologyIRI(<http://purl.↲obolibrary.org/
obo/go/never_in_taxon.owl>))) [Axioms: 18↲ Logical Axioms: 0]']

也可以用 Owlready 修改元数据:

>>> go.metadata.comment.append("Here is a new comment!")
>>> go.metadata.comment

['cvs version: $Revision: 38972 $',
'Includes Ontology(OntologyID(OntologyIRI(<http://purl.↲obolibrary.org/
obo/go/never_in_taxon.owl>))) [Axioms: 18↲ Logical Axioms: 0]',
'Here is a new comment!']

8.7 全文搜索

全文搜索允许您优化本体中的文本搜索。在大型本体上,速度增益可以达到 1000 倍。

默认情况下,不会激活全文搜索,因为它会增加 quadstore 的大小。有必要为将要使用它的每个属性激活它。default_world.full_text_search_properties列表包含启用全文搜索的属性列表。默认情况下,它是空的。要激活属性的全文搜索,只需将其添加到列表中。

例如,要激活 OWL comment属性的全文搜索:

>>> default_world.full_text_search_properties.append(comment)

我们现在可以用search()方法执行全文搜索,使用包含要搜索的文本的FTS对象(“全文搜索”的缩写)。与普通搜索不同,全文搜索是从关键字列表(而不是所搜索的确切值)开始的,并且忽略大小写(也就是说,它不区分大小写)。例如,要搜索注释中包含单词“English”的所有实体,我们将执行以下操作:

>>> default_world.search(comment = FTS("English"))
[bacteria.unknown_bacterium]

可以给出几个关键字(关键字的顺序无关紧要)并使用字符“*”作为通配符,但只能在关键字的末尾。例如,要搜索注释中包含单词“English”和以“comm”开头的单词的所有实体,我们将执行以下操作:

>>> default_world.search(comment = FTS("English comm*"))
[bacteria.unknown_bacterium]

FTS对象还允许您指定搜索的语言(默认情况下,搜索以所有语言执行)。以下示例仅在英文注释中搜索:

>>> default_world.search(comment = FTS("English comm*", "en"))

请注意,如果您没有使用FTS对象,owl 已经执行了一个普通的、非优化的搜索,如下例所示:

>>> default_world.search(comment = "English comm*") # Without FTS !
[]

要停用全文搜索,只需使用remove()default_world.full_text_search_properties列表中删除属性。

8.8 示例:在 Python 中使用 DBpedia

DBpedia 是结构化数据的自动提取,源自免费开放的维基百科全书。值得注意的是,DBpedia 不仅包含维基百科页面之间的关系,还包含更具体的数据,比如出现在维基百科上的人的出生日期。OWL 本体构建了所有的数据。因此,它是面向一般数据集的“一般文化”。最新版本是 2016 年的,可以从以下地址下载: https://wiki.dbpedia.org/downloads-2016-10 。DBpedia 的 2020 版本还在开发中,一些重要的特性仍然缺失。

dbpedia 以几个文件的形式存在(参见下面的网站截图):本体部分以 OWL 格式下载(文件“dbpedia_2016-10.owl”),数据以 TTL 格式下载(相当于 N-Triples)在它们的规范化版本中(在 DBpedia 网站上标注为“ttl *”)。有几种语言可供选择;我们将使用英文版本。

img/502592_1_En_8_Figa_HTML.jpg

 [...]

img/502592_1_En_8_Figb_HTML.jpg

8.8.1 加载 DBpedia

因为 DBpedia 非常大,所以并不是所有的文件都常用。下表列出了可以下载的主要工具(请注意数据很大:大约需要 20 GB)。

|

DBpedia 中的名称

|

文件名和描述

| | --- | --- | | 本体猫头鹰 | dbpedia_2016-10.owl 本体(包含类,但没有个体) | | 实例类型 | instance _ types _ wkd _ uri _ en . TTL . bz2 个体和类之间的“is_instance_of”关系(对应于 RDF“type”关系) | | 文章类别 | article _ categories _ wkd _ URIs _ en . TTL . bz2 维基百科文章和类别之间的关系(“主题”属性) | | 基于映射的文字 | mapping based _ literals _ wkd _ URIs _ en . TTL . bz2 维基百科信息框中的数据属性 | | 基于映射的对象 | mapping based _ objects _ wkd _ URIs _ en . TTL . bz2 维基百科信息框中的对象属性 | | 类别标签 | category _ labels _ wkd _ uri _ en . TTL . bz2-类别 _ 标签 _ wkd _ uri _ en . TTL . bz2 类别的标签 | | 标签 | labels _ wkd _ uri _ en . TTL . bz2 实体标签(全文搜索需要) | | 个人数据 | person data _ wkd _ uri _ en . TTL . bz2 与人相关的数据(出生日期等。) | | 页面链接 | 页 _ links _ wkd _ uri _ en . TTL . bz2 对应于维基百科页面之间的链接的关系(“wikiPageWikiLink”属性) |

DBpedia 是一个 OWL 本体,因此可以用 Owlready 加载。然而,DBpedia 比通常的本体要“干净”得多。特别地,实体的虹膜从一个文件到另一个文件不是恒定的,以下前缀可互换使用:

因此,在加载本体之前,预处理步骤将是必要的,以便清除这些不一致。

此外,DBpedia 是一个非常庞大的本体。这需要采取几项预防措施:

  1. 将 quadstore 保存在磁盘上(见 4.7),以避免每次使用时都要重新加载本体。

  2. 将临时文件放在磁盘上可以容纳几千兆字节数据的目录中。特别是,在 Linux 下,临时文件默认放置在/tmp 中,但是/tmp 通常存储在 RAM 中,这限制了可用空间。这可能导致“数据库或磁盘已满”类型的错误。

对于非常大的本体,最好将临时文件放在其他地方,而不是/tmp 中。这可以在定义 quadstore 时用sqlite_tmp_dir来完成:

default_world.set_backend(
    filename = "/path/to/quadstore.sqlite3",
    sqlite_tmp_dir = "/path/to/temporary/files",
)

以下程序用于将 DBpedia 加载到 Owlready quadstore 中:

# File import_dbpedia.py
from owlready2 import *
import io, bz2

# DBPEDIA_DIR is the directory where you downloaded DBpedia
DBPEDIA_DIR = "/home/jiba/telechargements/dbpedia/"
TMP_DIR     = "/home/jiba/tmp"
QUADSTORE   = "dbpedia.sqlite3"

default_world.set_backend(filename = QUADSTORE,↲ sqlite_tmp_dir = TMP_DIR)

dbpedia = get_ontology("http://wikidata.dbpedia.org/ontology/")
contenu = open(os.path.join(DBPEDIA_DIR, "dbpedia_2016-10.↲owl")).read()
contenu = contenu.replace("http://dbpedia.org/ontology,↲
          "http://wikidata.dbpedia.org/ontology")
contenu = contenu.replace("http://www.wikidata.org/entity,↲
          "http://wikidata.dbpedia.org/resource")
dbpedia.load(fileobj = io.BytesIO(contenu.encode("utf8")))

for fichier in os.listdir(DBPEDIA_DIR):
    if fichier.endswith(".ttl.bz2"):
        print("Import de %s..." % fichier)
        onto = get_ontology("http://dbpedia.org/ontology/%s/" %↲
                            fichier.replace(".ttl.bz2", ""))
        f = bz2.open(os.path.join(DBPEDIA_DIR, fichier))
        onto.load(fileobj = f)

print("Indexing...")
default_world.full_text_search_properties.append(label)

default_world.save()

在这个程序中,必须根据你的配置修改三个全局变量:DBPEDIA_DIR表示你从 DBpedia 下载文件的目录(OWL 和 TTL。BZ2 文件),TMP_DIR表示可以容纳几 GB 数据的临时目录(见前面),而QUADSTORE是将存储 Owlready quadstore 的文件的名称(注意,为 quadstore 预留 13 GB 空间用于前面的文件,并预留一个小时或更多的加载时间)。

DBpedia 本体(OWL 文件)被作为文本文件读取,并使用replace()方法进行纠正,然后用 Owlready 加载。为了避免保存文件的修正版本,我们直接从 RAM 中加载本体:本体的修正版本在作为字符串包含的变量中。我们分两个阶段将这个字符串转换成一个文件对象:首先,我们用 UTF8 对字符串进行编码,然后用io.BytesIO()将其转换成一个文件对象。最后,我们从这个文件对象中加载本体,用load(filobj = ...)(见 4.2)。

然后,一个循环遍历DBPEDIA_DIR目录中的所有文件,并处理 TTL。BZ2 档案。这些文件用 Python 模块bz2解压缩,然后从压缩文件对象中加载。我们在这里选择为每个文件创建一个单独的本体(这将允许您单独重新加载每个文件,或者删除不再需要的本体)。

注意倒数第二行,它激活了对label属性的全文搜索。

然后,我们可以通过以下方式加载结果 quadstore,例如,在 Python 控制台模式下。(注意不要重用已经加载了 Owlready2 并在 quadstore 中创建了实体的 Python 控制台;否则,您将得到一个错误。这是因为如果内存中的 quadstore 不为空,则不可能从文件中加载 quad store。)

>>> from owlready2 import *
>>> QUADSTORE   = "dbpedia.sqlite3"
>>> default_world.set_backend(filename = QUADSTORE)
* Owlready2 * WARNING: http://wikidata.dbpedia.org/ontology/senator
belongs to more than one entity types (e.g. Class, Property, Individual):
[owl.ObjectProperty, ontology.MemberOfParliament, DUL.sameSettingAs];
I'm trying to fix it...
[...]

注意,加载比从 OWL 和 TTL 导入要快得多。BZ2 档案。“警告”表示某些属性没有在 DBpedia 中正确声明;我们可以放心地忽略它们。

还要注意,DBpedia 的初始加载和处理这个本体的第一个命令可能需要很长时间才能执行,因为底层数据库非常大(大约 12 GB)。然而,一旦下了第一批订单,索引和缓存将在 RAM 中,访问 DBpedia 将会快得多。

然后我们可以加载 DBpedia 本体:

>>> dbpedia = get_ontology("http://wikidata.dbpedia.org/↲ontology/")

我们修改实体的渲染(见 4.9)来显示它们的标签:

>>> def render(e):
...     return "%s:%s" % (e.name, e.label.en.first())
>>> set_render_func(render)

我们现在可以在 DBpedia 中执行优化的全文搜索,例如,查找标签中带有“French”和“revolution”的所有文章:

>>> default_world.search(label = FTS("french Revolution"))
[Q1154330:10 August (French Revolution),
 Q207318:French Revolutionary Wars,
 Q7216178:French Revolution,
 [...] ]

请注意,最初的搜索可能会很长,需要几秒钟。但是,当您的计算机已经将索引加载到高速缓存中时,下面的搜索会进行得更快!

要访问文章,我们需要创建一个名称空间(见 4.8),因为这些是在“ http://wikidata.dbpedia.org/resource/ ”中定义的,而不是在“ http://wikidata.dbpedia.org/ontology/ ”中定义的:

>>> dbpedia_resource = default_world.get_namespace(↲
                       "http://wikidata.dbpedia.org/resource/")

我们现在可以访问文章 Q207318(“法国革命战争”,这是最完整的之一),并请求其属性列表:

>>> revolution = dbpedia_resource.Q207318
>>> list(revolution.get_properties())
[combatant:combatant, date:date, result:result, commander:commander,
 place:place of military conflict, territory:territory, label:None,
wikiPageWikiLink:Link from a Wikipage to another Wikipage]

然后我们可以显示关于法国大革命战争的文章中提到的人员列表:

>>> persons = [i for i in revolution.wikiPageWikiLink
...              if isinstance(i, dbpedia.Person)]
>>> print(persons)
[Q10088:Tipu Sultan, Q1096347:Claude Lecourbe,
 Q112009:Michael von Melas, Q128019:Pope Pius VI,
 ...]

在使用 DBpedia 本体时,有一点需要注意:DBpedia 使用经典的 RDFS“注释”标注对类进行注释;但是,它也重新定义了自己的“评论”属性。这两者之间的混淆使得使用“entity.comment”语法很难得到注释!在名称冲突的情况下,最后加载的属性优先,因此 DBpedia 的属性优先。这就是为什么在下面的例子中,我们没有得到注释:

>>> dbpedia.SongWriter.comment  # DBpedia comments
[]

要强制使用 RDFS“注释”注释,有两种选择。首先,我们可以使用替代语法“property[entity]”,如下例所示:

>>> comment[dbpedia.SongWriter]  # RDFS comments
['a person who writes songs.', 'een persoon die de muziek en/of
 de tekst voor populaire muzieknummers schrijft.']

我们还可以重新定义语法“entity.annotation”使用的属性,如下所示:

>>> default_world._props["comment"] = comment
>>> dbpedia.SongWriter.comment  # RDFS comments now!
['a person who writes songs.', 'een persoon die de muziek en/of
 de tekst voor populaire muzieknummers schrijft.']

8 . 8 . 2 DBpedia 的搜索引擎

使用之前创建的 quadstore 和全文搜索函数,我们可以轻松地为 DBpedia 制作一个搜索引擎。我们将依靠一个带有 Flask 的动态网站(我们已经在 4.12 节使用过了)。

以下程序加载 DBpedia quadstore,然后创建动态网站:

# File search_dbpedia.py
from flask import Flask, request
app = Flask(__name__)

from owlready2 import *

QUADSTORE = "dbpedia.sqlite3"
default_world.set_backend(filename = QUADSTORE)

dbpedia  = get_ontology("http://wikidata.dbpedia.org/↲ontology/")
resource = default_world.get_namespace(↲
                   "http://wikidata.dbpedia.org/resource/")

@app.route('/')
def page_query():
    html = """
<html><body>
    <form action="/result">
        <input type="text" name="keywords"/>
        <input type="submit"/>
    </form>
</body></html>"""
    return html

@app.route('/result')
def page_result():
    keywords = request.args.get("keywords", "")
    html = """<html><body>Search results for "%s":<br/>\n""" %↲ keywords

    keywords ="  ".join("%s*" % keyword for keyword in↲ keywords.split())
    articles = default_world.search(label = FTS(keywords))

    html += """<ul>"""
    for article in articles:
        html += """<li><a href=/article/%s>%s:%s</a></li>"""↲
        % (article.name, article.name, article.label.first())
    html += """</ul></body></html>"""
    return html

@app.route('/article/<name>')
def page_article(name):
    article = resource[name]

    html = """<html><body><h2>%s:%s</h2>"""↲
           % (article.name, article.label.first())
    html += """belongs to classes: %s<br/><br/>\n """↲
            % "," .join(repr(clazz) for clazz in article.is_a)
    html += """has link to page:<br/>\n"""
    html += """<ul>"""
    for cite in article.wikiPageWikiLink:
        html += """<li><a href=/article/%s>%s:%s</a></li>"""↲
                % (cite.name, cite.name, cite.label.first())
    html += """</ul></body></html>"""
    return html

import werkzeug.serving
werkzeug.serving.run_simple("localhost", 5000, app)

该网站包括三种类型的页面:

  • “查询”页面(对应于网站根目录的路径“/”)以 HTML 形式显示搜索字段。该页面本身不是动态的;我们可以完全用普通的 HTML 来制作。

  • “结果”页面(路径)/结果?关键字= ))显示搜索结果。我们获取用户输入的关键字,然后通过在每个关键字的末尾添加一个星号“*”来转换它们。然后我们用search()FTS进行搜索。最后,我们生成一个显示结果列表的 HTML 页面,为找到的每篇文章显示其标识符、标签和到相应文章页面的链接。

  • “文章”页面(path)/article//),显示文章的标识符和标题、文章所属的一个或多个类别以及它引用的其他文章(通过关系“wikiPageWikiLink”获得)。

一旦程序被执行,就可以在网址“http://127.0.0.1:5000”上查阅该网站。同样,DBpedia 的初始加载和第一次搜索可能会很长。然而,一旦第一次搜索完成,接下来的搜索将会立即进行。

下图显示了动态网站的“查询”页面和“结果”页面的屏幕截图:

img/502592_1_En_8_Figc_HTML.jpg

img/502592_1_En_8_Figd_HTML.jpg

8.9 总结

在这一章中,你已经学习了如何阅读和创建 OWL 注释,以及如何使用多语言文本。我们还看到了如何使用 Owlready 在 Python 中访问 DBpedia。