【翻译】owl_cpp,一个用于操作OWL本体的C++类库,owl_cpp, a C++ Library for Working with OWL Ontologies

说明:由于我近期工作涉及生物本体,然而国内有关本体的时新文章较少,因此翻译ICBO2011会议的部分论文,与本体领域的研究者和爱好者分享。本人翻译水平有限,如果大家英文水平不错,还是推荐直接看原文。

摘要:这里我们介绍owl_cpp(http://sf.net/projects/owl-cpp/),它是一个开源的C++类库,用于对OWL2本体进行语法分析、查询和推理。owl_cpp使用Raptor、FaCT++和Boost类库。它用标准C++编写而成,因此在大部分的平台均可使用。owl_cpp执行严格的语法分析,能够检测出被其他语法分析器所忽略的错误。这一类库的另外一个优点就是它有很高的性能,并且是一个紧凑的内存内部表示。

1 引言

OWL(Web Ontology Language)是语义网技术的一种,它被设计用来形式化地表示人类知识以促进对它的计算分析和解释。OWL和其他语义网技术一样,都成功地应用于生物医学的诸多领域。它们的成功部分来源于用来支持语义网的软件的广泛开发。

运用本体时经常涉及一些任务,如:对OWL文档进行语法分析,查询它们的内存内部表示,将信息传送到描述逻辑(Description Logic,DL)推理机(reasoner),以及执行DL查询等。在计算机内存中,本体或者被表示为RDF三元组(triple),或者是公理(axiom)和注释(annotation)。三元组是一种简单的描述方式,包括三个节点,其中谓词(predicate)节点表达主体(subject)节点和客体(object)节点之间的关系。虽然三元组的集合可以表示一个复杂的相互关联的实体图,在计算机内存中,还是存储为统一的数组更方便高效地搜索和查询。所以当需要高效执行相对简单的查询时,我们使用三元组存储。公理是对本体中类与实例的描述。每一个公理可以被看做是一个对应于一个或多个RDF三元组的图。因为公理比三元组更复杂,因此对它们的查询也更低效。然而,在推理机的帮助下,它可以执行更复杂的DL查询,并且发现本体包含的潜在知识。

尽管已有充分开发的软件可用,处理大量生物医学本体仍然面临挑战。我们面临的部分问题有:

a) 检测并消除OWL文档中的错误;

b) 有些高性能计算平台缺乏对Java虚拟机的支持;

c) 用C++、Python、Perl等编程语言编写的语义网工具十分有限;

d) 本体内存内部表示的开销大;

e) 语法分析和查询的性能较差。

为了处理这些问题,我们开发了owl cpp。它是一个用来对本体进行语法分析、查询和推理的类库。它的关键特色包括:

a) 严格的语法分析,能够检测OWL文档中的错误;

b) 用标准C++编写,在大部分平台上均可编译;

c) 无需虚拟机;

d) 能够为诸如Java、Perl、Python等其他编程语言创建高效的API;

e) 内存开销小;

f) 高性能。

2 实现

owl_cpp能支持下面几种基本操作:

a) 在用户提供的地点对OWL2和RDF/XML文档编制目录;

b) 对OWL2和RDF/XML文档进行语法分析;

c) 对RDF三元组表示的结果进行存储和检索;

d) 将三元组转化为公理,并将其加载到推理机中;

e) 执行描述逻辑查询。

上述功能是按下面的准则予以实现的:

  • 正确性和可重复性应当通过大量单元测试来验证
  • 严格的句法验证在语法分析和公理生成过程中应当被执行以防止可能的语法错误
  • 错误提示应当包括足够的信息以找到并纠正错误
  • 性能无论在速度上还是内存开销上都应当最大化以保证可以处理大规模本体
  • 可移植性应当通过仅使用标准C++特性来实现
  • 可维护性应当通过将owl_cpp设计为解耦合模块结构以及采用良构建的类库如C++标准类库、Boost等来实现

当前,owl_cpp由三个模块组成。语法分析模块是用C++对Raptor进行的包装。Raptor是一个流行的用C语言写成的对RDF进行语法分析的类库。据我们所知,Raptor是目前活跃开发的RDF语法分析器中唯一用C/C++编写的。为了解析XML,Raptor使用Libxml2类库的SAX接口。owl_cpp仅从STL输入流和用户指定的文件系统位置中读入本体。虽然Raptor默认试图从互联网上获取本体,但是在owl_cpp中,出于可靠性、安全性和性能的考虑,该功能被禁止。

三元组存储模块负责存储、检索和获取RDF三元组。存储内部为命名空间URI、节点和三元组提供独立的容器。容器通过将其与轻量ID映射的方式来追踪客体。通过这种ID获取客体就像数组索引一样高效。每一个RDF三元组被存储在其对应节点的三个ID中。虽然一个本体文档中同一节点可能多次出现,但同一个节点在存储时仅保存一个实例。

对三元组的检索非常频繁,因此该操作应当充分优化。虽然该任务明确易懂,然而潜在的排列组合总数却使之变得复杂。根据用户给出的节点ID,我们可以用八种不同的方式来检索该三元组:通过主体,通过谓词,通过客体,或者通过上述三者任何一个的组合,包括不提供ID时的组合和返回存储的所有三元组的组合。此外,作为检索的结果,用户可能对返回的三种类型的结果感兴趣:一个匹配给出ID的三元组的完整列表,只显示找到的第一个三元组,仅通过一个布尔值表明该检索是否成功。对上面八种检索组合的任意一种情况,用户都可能需要这三种类型的结果,因此可能的组合总数是24。很显然,要为每一个检索组合实现一个单独的方法不可避免地会造成混乱。而另一方面,要实现一个完成所有检索组合的方法又难免要牺牲性能。所以,根据“you pay only for what you use”原则,该检索使用非成员模板函数实现,它既能接受Node_id类型,又能接受Blank类型。返回值是boost::iterator_range,它可以隐式地转换为一个布尔类型或者用于对匹配三元组的进行迭代。

推理模块的功能是用FaCT++来实现的。FaCT++也是据我们所知唯一一个开源的C/C++编写的DL推理机类库。owl_cpp通过将三元组转换为公理来将三元组存储的信息传递给FaCT++。当前该转换采用了访问者设计模式。在owl_cpp提供的参与者和谓词类的辅助下,DL查询可以直接通过FaCT++接口来执行。

3 结果与讨论

owl_cpp的接口被设计用来简化本体的基本操作。例如,我们可以通过调用load("ontology.owl", store)将文件ontology.owl读入并存储为三元组。我们想要根据输入流、本体ID的目录和位置来加载本体,我们可以用这样的语句:load("ontology.owl", store, catalog)。

一旦加载入内存,三元组即可被查询。例如,表达式find_triples(blank, T_rdf_type::id(), Towl Class::id(), store)能够找到声明类的所有三元组。通过调用add(store, kernel),公理可以从三元组存储store复制到FaCT++推理内核kernel。

owl_cpp语法分析和推理的准确性可以通过许多本体来得到验证。一些较小的OWL文档被合并入单元测试,这样保证了它们的一致性和可满足性。在开发生物代谢途径本体时,我们通过执行领域专家构建的DL查询并与protege(FaCT++和HermiT推理机)得出的结果进行比较来完成了更近一步的测试。结果是一致的。

owl_cpp与其他OWL类库相比的一个优点就是它能够发现语法错误,这样就阻止不正确的语义插入本体。在我们小组的本体开发过程中,owl_cpp已经检测出不一致的输入表述,未声明的属性和注释谓词,拼写错误的标准OWL术语以及其他问题。owl_cpp的语法分析性能使用OWL/RDF格式的OpenGALEN本体(版本8)进行测试。该本体占硬盘大小0.5GB,包括970万个三元组。纠正几个错误如将owl:propertyChain替换为owl:propertyChainAxiom之后,它们的语法分析是成功的。语法分析的速率估计为每秒10万8千个三元组。该处理的瓶颈出现在将新术语插入三元组存储的时候。我们正在研究将该步骤流水线化的方法。

owl_cpp未来的开发包括下面几项任务:

a) 为语法分析、三元组存储和推理机定义更高水平的C++ API;

b) 设计基于公理的API;

c) 提高错误消息的可读性;

d) 为其他编程语言设计API;

e) 为其他OWL2句法提供支持,如Manchester,Turtle,OWL/XML;

f) 为批处理执行OWL2的测试用例设计模块。