教程:使用 NLP 和本体构建知识图谱
简介
在本教程中,我们将基于以下内容构建一个软件知识图谱:
-
从 dev.to(一个开发者博客平台)获取的文章,以及从这些文章中提取的实体(使用 NLP 技术)。
-
从 Wikidata(一个免费且开放的知识库,充当维基百科结构化数据的中央存储库)中提取的软件本体。
完成后,我们将学习如何查询知识图谱以查找由组合 NLP 和本体启用的有趣见解。
本指南中使用的查询和数据可以在 neo4j-examples/nlp-knowledge-graph GitHub 存储库中找到。 |
视频
Jesús Barrasa 和 Mark Needham 在 2020 年 8 月 25 日的 Neo4j Connections:知识图谱 活动中发表了基于本教程的演讲。演讲视频如下所示
工具
我们将在本教程中使用几个插件库,因此如果您想按照示例操作,则需要安装这些库。
neosemantics (n10s)
neosemantics 是一个插件,它允许在 Neo4j 中使用 RDF 及其关联的词汇表,如 OWL、RDFS、SKOS 等。我们将使用此工具将本体导入 Neo4j。
neosemantics 仅支持 Neo4j 4.0.x 和 3.5.x 系列。它尚不支持 Neo4j 4.1.x 系列。 |
我们可以按照项目安装指南中的说明安装neosemantics。
这两种工具都可以在Docker环境中安装,项目仓库包含一个docker-compose.yml文件,展示了如何操作。
什么是知识图谱?
关于知识图谱的定义有很多种。在本教程中,知识图谱的定义是指包含以下内容的图:
- 事实
-
实例数据。这包括从任何数据源导入的图数据,可以是结构化的(例如JSON/XML)或半结构化的(例如HTML)。
- 显式知识
-
实例数据之间关系的显式描述。这来自本体、分类法或任何类型的元数据定义。
导入维基数据本体
维基数据是一个免费且开放的知识库,人类和机器都可以读取和编辑。它充当其维基媒体姊妹项目(包括维基百科、维基导游、维基词典、维基文库等)结构化数据的中央存储库。
维基数据SPARQL API
维基数据提供了一个SPARQL API,允许用户直接查询数据。下面的截图展示了一个SPARQL查询示例及其运行结果。

此查询从实体Q2429814(软件系统)开始,然后传递地查找该实体的子节点,直到找到所有子节点。如果我们运行查询,我们将获得一系列三元组(主语、谓语、宾语)。
现在我们将学习如何使用neosemantics将维基数据导入Neo4j。
CREATE CONSTRAINT n10s_unique_uri ON (r:Resource) ASSERT r.uri IS UNIQUE;
CALL n10s.graphconfig.init({handleVocabUris: "MAP"});
call n10s.nsprefixes.add('neo','neo4j://voc#');
CALL n10s.mapping.add("neo4j://voc#subCatOf","SUB_CAT_OF");
CALL n10s.mapping.add("neo4j://voc#about","ABOUT");
现在我们将导入维基数据分类法。我们可以通过点击“代码”按钮直接从维基数据SPARQL API获取可导入的URL。
然后,我们将该URL传递给n10s.rdf.import.fetch
过程,该过程将三元组流导入Neo4j。
下面的示例包含从软件系统、编程语言和数据格式开始导入分类法的查询。
WITH "https://query.wikidata.org/sparql?query=prefix%20neo%3A%20%3Cneo4j%3A%2F%2Fvoc%23%3E%20%0A%23Cats%0A%23SELECT%20%3Fitem%20%3Flabel%20%0ACONSTRUCT%20%7B%0A%3Fitem%20a%20neo%3ACategory%20%3B%20neo%3AsubCatOf%20%3FparentItem%20.%20%20%0A%20%20%3Fitem%20neo%3Aname%20%3Flabel%20.%0A%20%20%3FparentItem%20a%20neo%3ACategory%3B%20neo%3Aname%20%3FparentLabel%20.%0A%20%20%3Farticle%20a%20neo%3AWikipediaPage%3B%20neo%3Aabout%20%3Fitem%20%3B%0A%20%20%20%20%20%20%20%20%20%20%20%0A%7D%0AWHERE%20%0A%7B%0A%20%20%3Fitem%20(wdt%3AP31%7Cwdt%3AP279)*%20wd%3AQ2429814%20.%0A%20%20%3Fitem%20wdt%3AP31%7Cwdt%3AP279%20%3FparentItem%20.%0A%20%20%3Fitem%20rdfs%3Alabel%20%3Flabel%20.%0A%20%20filter(lang(%3Flabel)%20%3D%20%22en%22)%0A%20%20%3FparentItem%20rdfs%3Alabel%20%3FparentLabel%20.%0A%20%20filter(lang(%3FparentLabel)%20%3D%20%22en%22)%0A%20%20%0A%20%20OPTIONAL%20%7B%0A%20%20%20%20%20%20%3Farticle%20schema%3Aabout%20%3Fitem%20%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20schema%3AinLanguage%20%22en%22%20%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20schema%3AisPartOf%20%3Chttps%3A%2F%2Fen.wikipedia.org%2F%3E%20.%0A%20%20%20%20%7D%0A%20%20%0A%7D" AS softwareSystemsUri
CALL n10s.rdf.import.fetch(softwareSystemsUri, 'Turtle' , { headerParams: { Accept: "application/x-turtle" } })
YIELD terminationStatus, triplesLoaded, triplesParsed, namespaces, callParams
RETURN terminationStatus, triplesLoaded, triplesParsed, namespaces, callParams;
终止状态 | 加载的三元组数 | 解析的三元组数 | 命名空间 | 调用参数 |
---|---|---|---|---|
"OK" |
1630 |
1630 |
NULL |
{headerParams: {Accept: "application/x-turtle"}} |
WITH "https://query.wikidata.org/sparql?query=prefix%20neo%3A%20%3Cneo4j%3A%2F%2Fvoc%23%3E%20%0A%23Cats%0A%23SELECT%20%3Fitem%20%3Flabel%20%0ACONSTRUCT%20%7B%0A%3Fitem%20a%20neo%3ACategory%20%3B%20neo%3AsubCatOf%20%3FparentItem%20.%20%20%0A%20%20%3Fitem%20neo%3Aname%20%3Flabel%20.%0A%20%20%3FparentItem%20a%20neo%3ACategory%3B%20neo%3Aname%20%3FparentLabel%20.%0A%20%20%3Farticle%20a%20neo%3AWikipediaPage%3B%20neo%3Aabout%20%3Fitem%20%3B%0A%20%20%20%20%20%20%20%20%20%20%20%0A%7D%0AWHERE%20%0A%7B%0A%20%20%3Fitem%20(wdt%3AP31%7Cwdt%3AP279)*%20wd%3AQ9143%20.%0A%20%20%3Fitem%20wdt%3AP31%7Cwdt%3AP279%20%3FparentItem%20.%0A%20%20%3Fitem%20rdfs%3Alabel%20%3Flabel%20.%0A%20%20filter(lang(%3Flabel)%20%3D%20%22en%22)%0A%20%20%3FparentItem%20rdfs%3Alabel%20%3FparentLabel%20.%0A%20%20filter(lang(%3FparentLabel)%20%3D%20%22en%22)%0A%20%20%0A%20%20OPTIONAL%20%7B%0A%20%20%20%20%20%20%3Farticle%20schema%3Aabout%20%3Fitem%20%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20schema%3AinLanguage%20%22en%22%20%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20schema%3AisPartOf%20%3Chttps%3A%2F%2Fen.wikipedia.org%2F%3E%20.%0A%20%20%20%20%7D%0A%20%20%0A%7D" AS programmingLanguagesUri
CALL n10s.rdf.import.fetch(programmingLanguagesUri, 'Turtle' , { headerParams: { Accept: "application/x-turtle" } })
YIELD terminationStatus, triplesLoaded, triplesParsed, namespaces, callParams
RETURN terminationStatus, triplesLoaded, triplesParsed, namespaces, callParams;
终止状态 | 加载的三元组数 | 解析的三元组数 | 命名空间 | 调用参数 |
---|---|---|---|---|
"OK" |
9376 |
9376 |
NULL |
{headerParams: {Accept: "application/x-turtle"}} |
WITH "https://query.wikidata.org/sparql?query=prefix%20neo%3A%20%3Cneo4j%3A%2F%2Fvoc%23%3E%20%0A%23Cats%0A%23SELECT%20%3Fitem%20%3Flabel%20%0ACONSTRUCT%20%7B%0A%3Fitem%20a%20neo%3ACategory%20%3B%20neo%3AsubCatOf%20%3FparentItem%20.%20%20%0A%20%20%3Fitem%20neo%3Aname%20%3Flabel%20.%0A%20%20%3FparentItem%20a%20neo%3ACategory%3B%20neo%3Aname%20%3FparentLabel%20.%0A%20%20%3Farticle%20a%20neo%3AWikipediaPage%3B%20neo%3Aabout%20%3Fitem%20%3B%0A%20%20%20%20%20%20%20%20%20%20%20%0A%7D%0AWHERE%20%0A%7B%0A%20%20%3Fitem%20(wdt%3AP31%7Cwdt%3AP279)*%20wd%3AQ24451526%20.%0A%20%20%3Fitem%20wdt%3AP31%7Cwdt%3AP279%20%3FparentItem%20.%0A%20%20%3Fitem%20rdfs%3Alabel%20%3Flabel%20.%0A%20%20filter(lang(%3Flabel)%20%3D%20%22en%22)%0A%20%20%3FparentItem%20rdfs%3Alabel%20%3FparentLabel%20.%0A%20%20filter(lang(%3FparentLabel)%20%3D%20%22en%22)%0A%20%20%0A%20%20OPTIONAL%20%7B%0A%20%20%20%20%20%20%3Farticle%20schema%3Aabout%20%3Fitem%20%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20schema%3AinLanguage%20%22en%22%20%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20schema%3AisPartOf%20%3Chttps%3A%2F%2Fen.wikipedia.org%2F%3E%20.%0A%20%20%20%20%7D%0A%20%20%0A%7D" AS dataFormatsUri
CALL n10s.rdf.import.fetch(dataFormatsUri, 'Turtle' , { headerParams: { Accept: "application/x-turtle" } })
YIELD terminationStatus, triplesLoaded, triplesParsed, namespaces, callParams
RETURN terminationStatus, triplesLoaded, triplesParsed, namespaces, callParams;
终止状态 | 加载的三元组数 | 解析的三元组数 | 命名空间 | 调用参数 |
---|---|---|---|---|
"OK" |
514 |
514 |
NULL |
{headerParams: {Accept: "application/x-turtle"}} |
查看分类法
让我们看看导入了什么。我们可以通过运行以下查询来概述数据库的内容。
CALL apoc.meta.stats()
YIELD labels, relTypes, relTypesCount
RETURN labels, relTypes, relTypesCount;
标签 | 关系类型 | 关系类型计数 |
---|---|---|
{Category: 2308, _NsPrefDef: 1, _MapNs: 1, Resource: 3868, _MapDef: 2, WikipediaPage: 1560, _GraphConfig: 1} |
{ |
{SUB_CAT_OF: 7272, _IN: 2, ABOUT: 3120} |
任何具有_prefix
的标签或关系类型都可以忽略,因为它们表示n10s库创建的元数据。
我们可以看到,我们导入了超过2000个Category
节点和1700个WikipediaPage
节点。我们使用n10s创建的每个节点都将具有Resource
标签,这就是为什么我们有超过4000个具有此标签的节点。
我们还有超过7000个SUB_CAT_OF
关系类型连接Category
节点,以及3000个ABOUT
关系类型连接WikipediaPage
节点到Category
节点。
现在让我们看看我们导入的一些实际数据。我们可以通过运行以下查询查看版本控制节点的子类别。
MATCH path = (c:Category {name: "version control system"})<-[:SUB_CAT_OF*]-(child)
RETURN path
LIMIT 25;
到目前为止,一切顺利!
导入dev.to文章
dev.to是一个开发者博客平台,包含各种主题的文章,包括NoSQL数据库、JavaScript框架、最新的AWS API、聊天机器人等等。下面是主页的截图。
我们将从dev.to导入一些文章到Neo4j。articles.csv
包含30篇感兴趣的文章列表。我们可以使用Cypher的LOAD CSV
子句查询此文件。
LOAD CSV WITH HEADERS FROM 'https://github.com/neo4j-examples/nlp-knowledge-graph/raw/master/import/articles.csv' AS row
RETURN row
LIMIT 10;
行 |
---|
{uri: "https://dev.to/lirantal/securing-a-nodejs—rethinkdb—tls-setup-on-docker-containers"} |
{uri: "https://dev.to/setevoy/neo4j-running-in-kubernetes-e4p"} |
{uri: "https://dev.to/divyanshutomar/introduction-to-redis-3m2a"} |
{uri: "https://dev.to/zaiste/15-git-commands-you-may-not-know-4a8j"} |
{uri: "https://dev.to/alexjitbit/removing-files-from-mercurial-history-1b15"} |
{uri: "https://dev.to/michelemauro/atlassian-sunsetting-mercurial-support-in-bitbucket-2ga9"} |
{uri: "https://dev.to/shirou/back-up-prometheus-records-to-s3-via-kinesis-firehose-54l4"} |
{uri: "https://dev.to/ionic/farewell-phonegap-reflections-on-my-hybrid-app-development-journey-10dh"} |
{uri: "https://dev.to/rootsami/rancher-kubernetes-on-openstack-using-terraform-1ild"} |
{uri: "https://dev.to/jignesh_simform/comparing-mongodb—mysql-bfa"} |
我们将使用APOC的apoc.load.html
过程从每个URI中抓取有趣的信息。让我们首先看看如何在单个文章上使用此过程,如下面的查询所示。
(1)
MERGE (a:Article {uri: "https://dev.to/lirantal/securing-a-nodejs--rethinkdb--tls-setup-on-docker-containers"})
WITH a
(2)
CALL apoc.load.html(a.uri, {
body: 'body div.spec__body p',
title: 'h1',
time: 'time'
})
YIELD value
UNWIND value.body AS item
(3)
WITH a,
apoc.text.join(collect(item.text), '') AS body,
value.title[0].text AS title,
value.time[0].attributes.datetime AS date
(4)
SET a.body = body , a.title = title, a.datetime = datetime(date)
RETURN a;
1 | 创建具有Article 标签和uri 属性的节点(如果不存在)。 |
2 | 使用提供的CSS选择器从URI中抓取数据。 |
3 | 对从抓取URI返回的值进行后处理。 |
4 | 使用body 、title 和datetime 属性更新节点。 |
a |
---|
(:Article {processed: TRUE, datetime: 2017-08-21T18:41:06Z, title: "Securing a Node.js + RethinkDB + TLS setup on Docker containers", body: "We use RethinkDB at work across different projects. It isn’t used for any sort of big-data applications, but rather as a NoSQL database, which spices things up with real-time updates, and relational tables support.RethinkDB features an officially supported Node.js driver, as well as a community-maintained driver as well called rethinkdbdash which is promises-based, and provides connection pooling. There is also a database migration tool called rethinkdb-migrate that aids in managing database changes such as schema changes, database seeding, tear up and tear down capabilities.We’re going to use the official RethinkDB docker image from the docker hub and make use of docker-compose.yml to spin it up (later on you can add additional services to this setup).A fair example for docker-compose.yml:The compose file mounts a local tls directory as a mapped volume inside the container. The tls/ directory will contain our cert files, and the compose file is reflecting this.To setup a secure connection we need to facilitate it using certificates so an initial technical step:Important notes:Update the compose file to include a command configuration that starts the RethinkDB process with all the required SSL configurationImportant notes:You’ll notice there isn’t any cluster related configuration but you can add them as well if you need to so they can join the SSL connection: — cluster-tls — cluster-tls-key /tls/key.pem — cluster-tls-cert /tls/cert.pem — cluster-tls-ca /tls/ca.pemThe RethinkDB drivers support an ssl optional object which either sets the certificate using the ca property, or sets the rejectUnauthorized property to accept or reject self-signed certificates when connecting. A snippet for the ssl configuration to pass to the driver:Now that the connection is secured, it only makes sense to connect using a user/password which are not the default.To set it up, update the compose file to also include the — initial-password argument so you can set the default admin user’s password. For example:Of course you need to append this argument to the rest of the command line options in the above compose file.Now, update the Node.js driver settings to use a user and password to connect:Congratulations! You’re now eligible to “Ready for Production stickers.Don’t worry, I already mailed them to your address.", uri: "https://dev.to/lirantal/securing-a-nodejs—rethinkdb—tls-setup-on-docker-containers"}) |
现在我们将导入其他文章。
我们将使用apoc.periodic.iterate
过程,以便我们可以并行化此过程。此过程接收一个数据驱动语句和一个操作语句。
-
数据驱动语句包含要处理的一系列项目,这些项目将是URI流。
-
操作语句定义对每个项目执行的操作,这将是调用
apoc.load.html
并创建具有Article
标签的节点。
最后一个参数用于提供配置。我们将告诉过程以可以并发运行的5个批次处理这些项目。
我们可以在以下示例中看到对过程的调用。
CALL apoc.periodic.iterate(
"LOAD CSV WITH HEADERS FROM 'https://github.com/neo4j-examples/nlp-knowledge-graph/raw/master/import/articles.csv' AS row
RETURN row",
"MERGE (a:Article {uri: row.uri})
WITH a
CALL apoc.load.html(a.uri, {
body: 'body div.spec__body p',
title: 'h1',
time: 'time'
})
YIELD value
UNWIND value.body AS item
WITH a,
apoc.text.join(collect(item.text), '') AS body,
value.title[0].text AS title,
value.time[0].attributes.datetime AS date
SET a.body = body , a.title = title, a.datetime = datetime(date)",
{batchSize: 5, parallel: true}
)
YIELD batches, total, timeTaken, committedOperations
RETURN batches, total, timeTaken, committedOperations;
批次 | 总数 | 花费时间 | 已提交的操作 |
---|---|---|---|
7 |
32 |
15 |
32 |
现在我们有两个断开的子图,我们可以在下面的图中看到它们。
左侧是维基数据分类法图,它表示知识图谱中的显式知识。右侧是文章图,它表示知识图谱中的事实。我们希望将这两个图连接在一起,我们将使用NLP技术来实现。
文章实体提取
2020年4月,APOC标准库添加了封装每个大型云提供商(AWS、GCP和Azure)的NLP API的过程。这些过程从节点属性中提取文本,然后将该文本发送到提取实体、关键短语、类别或情感的API。
我们将对我们的文章使用GCP实体提取过程。GCP NLP API返回存在维基百科页面的实体的维基百科页面。
在执行此操作之前,我们需要创建一个具有自然语言API访问权限的API密钥。假设我们已经创建了一个GCP帐户,我们可以按照console.cloud.google.com/apis/credentials中的说明生成密钥。创建密钥后,我们将创建一个包含它的参数。
:params key => ("<insert-key-here>")
我们将使用apoc.nlp.gcp.entities.stream
过程,该过程将返回节点属性中包含的文本内容中找到的实体流。在针对所有文章运行此过程之前,让我们先针对其中一篇文章运行它,看看返回了哪些数据。
MATCH (a:Article {uri: "https://dev.to/lirantal/securing-a-nodejs--rethinkdb--tls-setup-on-docker-containers"})
CALL apoc.nlp.gcp.entities.stream(a, {
nodeProperty: 'body',
key: $key
})
YIELD node, value
SET node.processed = true
WITH node, value
UNWIND value.entities AS entity
RETURN entity
LIMIT 5;
实体 |
---|
{name: "RethinkDB", salience: 0.47283632, metadata: {mid: "/m/0134hdhv", wikipedia_url: "https://en.wikipedia.org/wiki/RethinkDB"}, type: "ORGANIZATION", mentions: [{type: "PROPER", text: {content: "RethinkDB", beginOffset: -1}}, {type: "PROPER", text: {content: "RethinkDB", beginOffset: -1}}, {type: "PROPER", text: {content: "RethinkDB", beginOffset: -1}}, {type: "PROPER", text: {content: "RethinkDB", beginOffset: -1}}, {type: "PROPER", text: {content: "pemThe RethinkDB", beginOffset: -1}}]} |
{name: "connection", salience: 0.04166339, metadata: {}, type: "OTHER", mentions: [{type: "COMMON", text: {content: "connection", beginOffset: -1}}, {type: "COMMON", text: {content: "connection", beginOffset: -1}}]} |
{name: "work", salience: 0.028608896, metadata: {}, type: "OTHER", mentions: [{type: "COMMON", text: {content: "work", beginOffset: -1}}]} |
{name: "projects", salience: 0.028608896, metadata: {}, type: "OTHER", mentions: [{type: "COMMON", text: {content: "projects", beginOffset: -1}}]} |
{name: "database", salience: 0.01957906, metadata: {}, type: "OTHER", mentions: [{type: "COMMON", text: {content: "database", beginOffset: -1}}]} |
每一行都包含一个name
属性,用于描述实体。salience
是该实体对整个文档文本的重要性或中心性的指标。
某些实体还包含一个维基百科 URL,可以通过metadata.wikipedia_url
键找到。第一个实体 RethinkDB 是此列表中唯一具有此类 URL 的实体。我们将过滤返回的行,仅包含具有维基百科 URL 的行,然后将Article
节点连接到具有该 URL 的WikipediaPage
节点。
让我们看看我们将如何为一篇文章执行此操作。
MATCH (a:Article {uri: "https://dev.to/lirantal/securing-a-nodejs--rethinkdb--tls-setup-on-docker-containers"})
CALL apoc.nlp.gcp.entities.stream(a, {
nodeProperty: 'body',
key: $key
})
(1)
YIELD node, value
SET node.processed = true
WITH node, value
UNWIND value.entities AS entity
(2)
WITH entity, node
WHERE not(entity.metadata.wikipedia_url is null)
(3)
MERGE (page:Resource {uri: entity.metadata.wikipedia_url})
SET page:WikipediaPage
(4)
MERGE (node)-[:HAS_ENTITY]->(page)
1 | node 是文章,value 包含提取的实体。 |
2 | 仅包含具有维基百科 URL 的实体。 |
3 | 查找与维基百科 URL 匹配的节点。如果不存在,则创建一个。 |
4 | 在Article 节点和WikipediaPage 之间创建HAS_ENTITY 关系。 |
我们可以通过查看以下 Neo4j 浏览器可视化来了解如何运行此查询来连接文章和分类法子图。
现在,我们可以再次借助apoc.periodic.iterate
过程对其余文章运行实体提取技术。
CALL apoc.periodic.iterate(
"MATCH (a:Article)
WHERE not(exists(a.processed))
RETURN a",
"CALL apoc.nlp.gcp.entities.stream([item in $_batch | item.a], {
nodeProperty: 'body',
key: $key
})
YIELD node, value
SET node.processed = true
WITH node, value
UNWIND value.entities AS entity
WITH entity, node
WHERE not(entity.metadata.wikipedia_url is null)
MERGE (page:Resource {uri: entity.metadata.wikipedia_url})
SET page:WikipediaPage
MERGE (node)-[:HAS_ENTITY]->(page)",
{batchMode: "BATCH_SINGLE", batchSize: 10, params: {key: $key}})
YIELD batches, total, timeTaken, committedOperations
RETURN batches, total, timeTaken, committedOperations;
批次 | 总数 | 花费时间 | 已提交的操作 |
---|---|---|---|
4 |
31 |
29 |
31 |
查询知识图谱
现在是时候查询知识图谱了。
语义搜索
我们将执行的第一个查询是语义搜索。n10s.inference.nodesInCategory
过程允许我们从顶级类别开始搜索,查找其所有传递子类别,然后返回附加到任何这些类别的节点。
在我们的图中,连接到类别节点的节点是WikipediaPage
节点。因此,我们需要在查询中添加一个额外的MATCH
子句,以通过HAS_ENTITY
关系类型查找连接的文章。我们可以在以下查询中看到如何执行此操作。
MATCH (c:Category {name: "NoSQL database management system"})
CALL n10s.inference.nodesInCategory(c, {
inCatRel: "ABOUT",
subCatRel: "SUB_CAT_OF"
})
YIELD node
MATCH (node)<-[:HAS_ENTITY]-(article)
RETURN article.uri AS uri, article.title AS title, article.datetime AS date,
collect(n10s.rdf.getIRILocalName(node.uri)) as explicitTopics
ORDER BY date DESC
LIMIT 5;
uri | 标题 | 日期 | 显式主题 |
---|---|---|---|
"https://dev.to/arthurolga/newsql-an-implementation-with-google-spanner-2a86" |
"NewSQL:使用 Google Spanner 的实现" |
2020-08-10T16:01:25Z |
["NoSQL"] |
"https://dev.to/goaty92/designing-tinyurl-it-s-more-complicated-than-you-think-2a48" |
"设计 TinyURL:比你想象的更复杂" |
2020-08-10T10:21:05Z |
["Apache_ZooKeeper"] |
"https://dev.to/nipeshkc7/dynamodb-the-basics-360g" |
"DynamoDB:基础知识" |
2020-06-02T04:09:36Z |
["NoSQL", "Amazon_DynamoDB"] |
"https://dev.to/subhransu/realtime-chat-app-using-kafka-springboot-reactjs-and-websockets-lc" |
"使用 Kafka、SpringBoot、ReactJS 和 WebSockets 的实时聊天应用程序" |
2020-04-25T23:17:22Z |
["Apache_ZooKeeper"] |
"https://dev.to/codaelux/running-dynamodb-offline-4k1b" |
"如何离线运行 DynamoDB" |
2020-03-23T21:48:31Z |
["NoSQL", "Amazon_DynamoDB"] |
尽管我们搜索了 NoSQL,但我们可以从结果中看到,一些文章没有直接链接到该类别。例如,我们有一些关于 Apache Zookeeper 的文章。我们可以通过编写以下查询来查看此类别如何连接到 NoSQL。
match path = (c:WikipediaPage)-[:ABOUT]->(category)-[:SUB_CAT_OF*]->(:Category {name: "NoSQL database management system"})
where c.uri contains "Apache_ZooKeeper"
RETURN path;

因此,Apache Zookeeper 实际上距离 NoSQL 类别还有几个级别。
类似文章
我们使用知识图谱可以做的另一件事是根据文章共有的实体查找类似的文章。此查询的最简单版本是查找共享公共实体的其他文章,如以下查询所示。
MATCH (a:Article {uri: "https://dev.to/qainsights/performance-testing-neo4j-database-using-bolt-protocol-in-apache-jmeter-1oa9"}),
path = (a)-[:HAS_ENTITY]->(wiki)-[:ABOUT]->(cat),
otherPath = (wiki)<-[:HAS_ENTITY]-(other)
return path, otherPath;
Neo4j 性能测试文章是关于 Neo4j 的,还有两篇关于 Neo4j 的文章,我们可以推荐给喜欢这篇文章的读者。
我们也可以在查询中使用类别分类法。我们可以通过编写以下查询查找共享公共父类别的文章。
MATCH (a:Article {uri: "https://dev.to/qainsights/performance-testing-neo4j-database-using-bolt-protocol-in-apache-jmeter-1oa9"}),
entityPath = (a)-[:HAS_ENTITY]->(wiki)-[:ABOUT]->(cat),
path = (cat)-[:SUB_CAT_OF]->(parent)<-[:SUB_CAT_OF]-(otherCat),
otherEntityPath = (otherCat)<-[:ABOUT]-(otherWiki)<-[:HAS_ENTITY]-(other)
RETURN other.title, other.uri,
[(other)-[:HAS_ENTITY]->()-[:ABOUT]->(entity) | entity.name] AS otherCategories,
collect([node in nodes(path) | node.name]) AS pathToOther;
other.title | other.uri | otherCategories | pathToOther |
---|---|---|---|
"使用 ASP.NET Core 的 Couchbase GeoSearch" |
"https://dev.to/ahmetkucukoglu/couchbase-geosearch-with-asp-net-core-i04" |
["ASP.NET", "Couchbase Server"] |
[["Neo4j", "专有软件", "Couchbase Server"], ["Neo4j", "免费软件", "ASP.NET"], ["Neo4j", "免费软件", "Couchbase Server"]] |
"最终的 Postgres 与 MySQL 博文" |
"https://dev.to/dmfay/the-ultimate-postgres-vs-mysql-blog-post-1l5f" |
["YAML", "Python", "JavaScript", "NoSQL 数据库管理系统", "结构化查询语言", "JSON", "可扩展标记语言", "逗号分隔值", "PostgreSQL", "MySQL", "Microsoft SQL Server", "MongoDB", "MariaDB"] |
[["Neo4j", "专有软件", "Microsoft SQL Server"], ["Neo4j", "免费软件", "PostgreSQL"]] |
"2020 年学习 Apache Kafka 的 5 个最佳课程" |
"https://dev.to/javinpaul/5-best-courses-to-learn-apache-kafka-in-2020-584h" |
["Java", "Scratch", "Scala", "Apache ZooKeeper"] |
[["Neo4j", "免费软件", "Scratch"], ["Neo4j", "免费软件", "Apache ZooKeeper"]] |
"使用 Neo4j 和 NestJS 构建现代 Web 应用程序" |
"https://dev.to/adamcowley/building-a-modern-web-application-with-neo4j-and-nestjs-38ih" |
["TypeScript", "JavaScript", "Neo4j"] |
[["Neo4j", "免费软件", "TypeScript"]] |
"在 Docker 容器上保护 Node.js + RethinkDB + TLS 设置" |
"https://dev.to/lirantal/securing-a-nodejs—rethinkdb—tls-setup-on-docker-containers" |
["NoSQL 数据库管理系统", "RethinkDB"] |
[["Neo4j", "免费软件", "RethinkDB"]] |
请注意,在此查询中,我们还将返回从初始文章到其他文章的路径。因此,对于“使用 ASP.NET Core 的 Couchbase GeoSearch”,有一条路径从初始文章到 Neo4j 类别,从那里到专有软件类别,该类别也是 Couchbase Server 类别的父类别,“使用 ASP.NET Core 的 Couchbase GeoSearch”文章已连接到该类别。
这展示了知识图谱的另一个不错的功能——除了提出建议之外,也很容易解释为什么提出该建议。
添加自定义本体
我们可能认为专有软件不是衡量两种技术产品之间相似度的良好标准。我们不太可能根据这种类型的相似性寻找类似的文章。
但是,软件产品连接的常用方法是通过技术栈。因此,我们可以创建自己的本体,其中包含一些这些栈。
nsmntx.org/2020/08/swStacks包含 GRANDstack、MEAN Stack 和 LAMP Stack 的本体。在导入此本体之前,让我们在 n10s 中设置一些映射。
CALL n10s.nsprefixes.add('owl','http://www.w3.org/2002/07/owl#');
CALL n10s.nsprefixes.add('rdfs','http://www.w3.org/2000/01/rdf-schema#');
CALL n10s.mapping.add("http://www.w3.org/2000/01/rdf-schema#subClassOf","SUB_CAT_OF");
CALL n10s.mapping.add("http://www.w3.org/2000/01/rdf-schema#label","name");
CALL n10s.mapping.add("http://www.w3.org/2002/07/owl#Class","Category");
现在,我们可以通过运行以下查询来预览本体上的导入。
CALL n10s.rdf.preview.fetch("http://www.nsmntx.org/2020/08/swStacks","Turtle");
看起来不错,所以让我们通过运行以下查询来导入它。
CALL n10s.rdf.import.fetch("http://www.nsmntx.org/2020/08/swStacks","Turtle")
YIELD terminationStatus, triplesLoaded, triplesParsed, namespaces, callParams
RETURN terminationStatus, triplesLoaded, triplesParsed, namespaces, callParams;
终止状态 | 加载的三元组数 | 解析的三元组数 | 命名空间 | 调用参数 |
---|---|---|---|---|
"OK" |
58 |
58 |
NULL |
{} |
现在我们可以重新运行相似性查询,它现在将返回以下结果。
other.title | other.uri | otherCategories | pathToOther |
---|---|---|---|
"GraphQL 入门指南" |
"https://dev.to/leonardomso/a-beginners-guide-to-graphql-3kjj" |
["GraphQL", "JavaScript"] |
[["Neo4j", "GRAND Stack", "GraphQL"]] |
"了解如何构建基于微服务架构的无服务器 GraphQL API,第一部分" |
"https://dev.to/azure/learn-how-you-can-build-a-serverless-graphql-api-on-top-of-a-microservice-architecture-233g" |
["Node.js", "GraphQL"] |
[["Neo4j", "GRAND Stack", "GraphQL"]] |
"最终的 Postgres 与 MySQL 博文" |
"https://dev.to/dmfay/the-ultimate-postgres-vs-mysql-blog-post-1l5f" |
["结构化查询语言", "可扩展标记语言", "PostgreSQL", "MariaDB", "JSON", "MySQL", "Microsoft SQL Server", "MongoDB", "逗号分隔值", "JavaScript", "YAML", "Python", "NoSQL 数据库管理系统"] |
[["Neo4j", "专有软件", "Microsoft SQL Server"], ["Neo4j", "免费软件", "PostgreSQL"]] |
"使用 ASP.NET Core 的 Couchbase GeoSearch" |
"https://dev.to/ahmetkucukoglu/couchbase-geosearch-with-asp-net-core-i04" |
["ASP.NET", "Couchbase Server"] |
[["Neo4j", "专有软件", "Couchbase Server"], ["Neo4j", "免费软件", "ASP.NET"], ["Neo4j", "免费软件", "Couchbase Server"]] |
"使用 Neo4j 和 NestJS 构建现代 Web 应用程序" |
"https://dev.to/adamcowley/building-a-modern-web-application-with-neo4j-and-nestjs-38ih" |
["JavaScript", "TypeScript", "Neo4j"] |
[["Neo4j", "免费软件", "TypeScript"]] |
这次,我们在顶部添加了几篇关于 GraphQL 的额外文章,GraphQL 是 GRANDstack 中的工具之一,Neo4j 也是其中的一部分。