本体,图形和海龟

2020-11-28 04:12:05

让我们深入了解该概念,并应用一些技术来创建实际的图形结构,并最终使用它。

在本文的最后,我们已经很好地解析了乌龟格式的Triplestore并在Go中创建了一个图结构(基于gonum的界面)

如上一篇文章所述,我们的知识数据库是一个三重存储。作为示例,我将依靠schema.org公开的本体。

Schema.org是一项协作性社区活动,其任务是创建,维护和促进Internet,网页,电子邮件以及其他内容中的结构化数据架构。由Google,Microsoft,Yahoo和Yandex创建的Schema.org词汇表是由开放的社区流程开发的[…]。您可以使用以下命令获取完整的本体:

为了解析商店,我使用了Andrei Sambra的gon3软件包。即使没有附加许可证,安德烈(Andrei)仍然允许我使用它并针对非营利代码对其进行修改。

包的入口点是解析器结构。其目的是读取字节流(io.Reader)并将其转换为称为Graph的功能结构。包中的Graph结构并不代表所有边缘。它仍然由一个三元组数组(又称为rdf图)组成:

// RDF图是一组RDF三元组类型Graph struct {Triples [] * Triple uri * IRI}

三元组是包含三个项的结构。一个是主语,一个是谓语,最后一个是宾语。

在上一篇文章中,我们看到RDF中的术语可以用不同的类型表示。到目前为止,在Go中表示通用类型的方法是使用接口。因此,术语具有基于接口的定义:

如果我们将所有概念粘合在一起,就有可能创建一个基本结构:

import“ github.com/owulveryck/gon3” //为简洁起见,其他导入省略了func main(){baseURI:=“ https://example.org/foo”解析器:= gon3。 NewParser(baseURI)gr,_:=解析器。 Parse(os.Stdin)//为简洁起见,省略了错误处理。 Printf(“图形包含%v个三元组”,len(gr。三元组()))}

我们可以通过计算文件中的rdf分隔符来检查三元组的数量是否与预期的大致匹配:

这些数字不是相同的,而是相似的(grep命令不评估文字,并且某些标点符号可能被错误地计算)

我们在内存中有一个“ RDF”图;可悲的是,这种结构不是有向图。我的意思是,实际上不可能从一个节点导航到另一个节点或识别边缘。

要创建图,Go中最好的选择是依靠gonum创建的抽象

在Gonum中,图形是一个接口,用于管理实现Node和Edge的两个对象,例如:

类型图形接口{节点(id(int64)节点节点()来自(id int64的节点)节点HasEdgeBetween(xid,yid int64)bool Edge(uid,vid int64)Edge}

一旦图形对象满足了这些接口,就可以使用gonum团队已实现的所有图形算法。如果您想了解有关功能的更多信息,请转到此链接:pkg.go.dev/gonum。 org / v1 / gonum / graph#section-directories

我们将创建一个顶层结构,它将作为图形的接收者。对于图本身,我们依赖于gonum项目提供的simple.DirectedGraph实现。

然后,我们将创建一个函数来从rdfGraph创建并填充图形。

func NewGraph(rdfGraph * gon3.Graph)* Graph {g:=简单。 NewDirectedGraph()// ...填满图表return&Graph {DirectedGraph:g,}}

请记住,rdf图包含一个三元组数组。每个三元组都是一个学期。

这表明我已经做出了选择:我想生成一个图形,其节点与在Triplestore内部声明的主题相对应。因此,在示例中,object2不是节点,因为它没有被定义为句子的主题。更改此行为并引用其他节点相对容易,但是让我们分开。

添加返回int64的方法ID()使其与gonum的Node接口兼容,因此可以将其添加到简单图形中。到目前为止,此代码可以编译(但没有用):

使用相同的原理,我们创建一个Edge结构,该结构包含两个节点From和To以及一个定义边的术语。

我们要做的第一件事是创建术语树。我们通过一个哈希映射来做到这一点,键是一个主题,值是另一个映射。地图值的键是一个谓词,并且值是一个对象数组(请记住,谓词可以指向多个对象)

但是在解析rdf图以填充树之前,我们必须解决一些难题。术语是一个接口。因此,它是一个指针。因此,在rdf图中,如果我们考虑两个条件t1和t2,例如:

然后,我们遍历rdf图的三元组以填充树和字典。

注意:为方便起见,我们还将字典设置为图形的属性,以备后用。结构变为:

现在,我们可以遍历树,并为每个主题在图中创建所有节点:

对于s,po:=范围树{n:=&节点{id:g。 NewNode()。 ID(),主题:s,PredicateObject:po,} g。 AddNode(n)引用[s] = n}

注意:为方便起见,我们再次跟踪哈希图中的节点。此参考地图的主题为键,节点为值(其类型为map [rdf.Term] * Node)。

对于s,po:=范围树{me:=参考[s]对于谓词,对象:= range po {对于_,object:=范围对象{如果me ==到{//自边缘继续}到:=参考[object] e:= Edge {F:me,T:to,Term:predicate,} g。 SetEdge(e)}}}

现在我们有了图构建器,可以使用我们先前下载的schema.org中的数据进行测试。

让我们编写一个简单的程序来创建图形并进行简单的查询。例如,我们可能想让所有节点直接链接到schema.org中的PostalAddress。

baseURI:=“ https://example.org/foo”解析器:= rdf。 NewParser(baseURI)gr,错误:=解析器。如果err!= nil {log。致命(err)} g:=图。 NewGraph(gr)postalAddress:= g。 Dict [“ http://schema.org/PostalAddress”]节点:= g。参考[postalAddress]它:= g。到(节点ID())。 Next(){n:=它。 Node()。(* graph。Node)//在这里需要推论,因为gonum的简单图返回一个graph.Node类型,它是一个接口f​​mt。 Printf(“%v->%v \ n”,节点。主题,n。主题)}

❯cat schemaorg-current-http.ttl |去运行main.go -> -> -> -> -> -> -> -> -> -> -> -> -> ->

如果我们查看schema.org的网站(https://schema.org/PostalAddress),则会在两个不同的表中找到这些元素:

请记住,我们正在处理本体。因此,链接具有含义。并且已经将该含义设置为边缘的属性。如果我们调整代码以显示边缘,如下所示:

为了它 。 Next(){n:=它。 Node()。(* graph。Node)//在这里需要推论,因为gonum的简单图返回一个graph.Node类型,它是一个接口e:= g。 Edge(n.ID(),节点ID())。(graph.Edge)fmt。 Printf(“%v-%v->%v \ n”,节点。主题,e。Term,n。主题)}

--> --> --> --> --> --> --> --> - -> --> --> --> --> - ->

我们已经在内存中快速建立了图形结构。重要的不是结构本身。重要的是它打开的视角。到目前为止,我们已经研究了模式,但是语义适用于数据本身。最重要的是,我们生成的图是相当通用的。因此,可以使用相同的原理将我们的知识图存储在诸如dgraph或neo4j之类的持久数据库中。

在下一篇文章中,我们将使用图并设置模板引擎,以使用go模板创建知识图的通用文档。