作者:太子長琴(NLP算法工程师)


AINLP公众号作者,也是知识星球“AINLP芝麻街”的嘉宾太子长琴同学发布了一个开源项目:https://github.com/hscspring/NLM :Memory for Knowledge Graph, using Neo4j. 知识图谱存储与查询。

以下是来自他博客的中文介绍,感兴趣的同学欢迎一起探讨。

项目地址:https://github.com/hscspring/NLM

博文地址:https://yam.gift/2019/12/02/2019-12-02-NLM/


本文主要介绍自然语言的记忆(存储与查询)模块,初衷是作为 chatbot 的 Layer 之一,主要功能是记忆(存储)从对话或训练数据学到的 “知识”,然后在需要时唤起(查询) 。目前成熟的方法是以图数据库作为载体,将知识存储为一系列的 ”节点“ 和 ”关系“。之后再基于这些存储的 ”节点“ 和 ”关系“ 进行相关查询。也可以理解为构建 Data Model 的问题。

设计思想

图数据库的典型代表是 Neo4j,Neo4j 中有几个很重要的概念:标签、节点和关系。标签是一类节点,可以看作是节点的类别,节点一般是某一个实体;关系存在于两个实体间,可以有多种不同的关系。节点和关系可以有多个属性。实践来看,Python 语言可以使用社区的 technige/py2neo,当然还可以使用官方的 neo4j/neo4j-python-driver: Neo4j Bolt driver for Python,两者的目的都是将数据 import 进 database 并进行相应的查询。

Neo4j 的特点要求导入的数据尽量是结构化的,也就是我们要事先有实体和它的类别(实体的属性可有可无),实体与实体间的关系(关系的属性可有可无)。我们期待能从对话或无监督的语料中自动提取实体和关系,然后自动 import 进 Neo4j。为了避免导入数据的混乱,自然最好能有先验的 “类别”,比如节点类别 Person,Movie 等,关系类别 LOVES,ACTS 等。所以,对于文本输入,我们需要一个信息提取器,将文本中的符合先验类别的节点和关系提取出来。如果输入是 NLU 模块输出的 ”意图和实体“ ,则需要一个分类器,将意图分类到对应的 Relation 类别,将实体分类到 Node 类别。

接下来的问题是:“我们如何确定先验的类别?”设想当然是能包括所有可能的类别,比如我们可以在大规模语料上使用 LDA 之类的模型自动获取 topic,每个 topic 作为一个类别标签。对话中的句子使用该模型预测 topic 并在 query 无结果时加入 Database。但这样可能导致知识图谱比较泛,无法 “专注” 在特定领域。因此实际可能还是需要针对垂直领域手动设计好 Node 和 Relation 的类别。

综上所述,我们的 NLM 模块需要具备以下基本功能:

  • 批量导入结构化数据,根据设计好的 Node 和 Relation(统一在 scheme 中设计)自动创建实体和关系

  • 自动根据文本或 NLU 的输出存储或查询实体和关系(无论是否有事先设计的实体和关系类别)

    • 将 NLU 的结果(实体和意图)自动分类到知识图谱已有的 Node 和 Relation

    • 从文本中自动提取事先设计好类别的 Node 和 Relation

    • 对于商业应用,建议事先设计好,实际就变成:

    • 对于闲聊机器人,不妨让它自由进化,看看最后能成什么样子

  • 自动根据文本或 NLU 的输出 Query

  • 对 Query 结果进行解析,输出为 NLG 或 NLI 模块需要的结构

  • 模块高内聚,可以作为独立的 Layer 对外提供服务;低耦合,分类器、提取器、解析器均可以自由更换

基本流程如下:

NLU Output/TEXT => Classifier/Extractor => Graph Input => Query/Add/Update => Parser => NLG (NLI) Input

批量导入

主要是明确一下规范,这个规范是看过几个项目后的感悟,暂时没有想到更好的,等有了更好的再来调整吧。最好 Input 不依赖某个具体的数据库(如 Neo4j)。核心思想是这样的:

  • 首先假设每次导入的数据是某一个类别,而这些数据每条对应一个不重复的 item name。比如类别 “电影” 每条 item 的 name 是电影名;再比如类别 “疾病” 每条 item 的 name 是疾病名。

  • item name 相关的其他信息均作为该 item 的属性。比如某个电影的属性可能包括:上映时间、导演、演员、类别,甚至豆瓣评分都可以。

  • 每个 item name 就是一个 Node,label 自然就是类别,item 的属性是 Node 的属性,这个可以动态调整。Relation 除了两个 Node 和它的 name 外还可以有自己的属性。比如演员 Y 是一个 Person,“演员 Y act A 电影” 这是一个关系,它同时可以有一个 “角色” 的属性(即在电影里演了谁)。

  • Node 和 Relation 均通过 scheme 创建。

举个栗子,首先是数据:

# 结构化的 data
[{"id": "1", "title": "Wall Streat", "year": "1987", "actors": ["Charlie->Bud", "Martin->Gordon"], "director": "Robot"},{"id": "2", "title": "The Matrix", "year": "1997", "actors": ["Keanu->Neo", "Tom->Forrest"], "director": "Robot"},...
]

接着是 scheme ,可以使用 GraphObject 来直接创建 Graph scheme 对象,比如:

# batch scheme
class Movie(GraphObject):__primarykey__ = "title"title = Property()released = Property("year")actors = RelatedFrom(Person, "ACTED_IN")directors = RelatedFrom(Person, "DIRECTED")class Person(GraphObject):__primarykey__ = "name"name = Property()

然后将结构化的数据处理后批量导入:

# execute
def batch():
for item in data:movie = Movie()movie.title=item["title"]movie.released=item["year"]director = Person()director.name = item["director"]movie.directors.add(director, {"name": "执导了"})
for iitem in item["actors"]:actor = Person()actor.name, role = iitem.split("->")movie.actors.add(actor, {"role": role, "name": "扮演了"})graph.push(movie)

具体可参考这里的例子。

实时处理

从 NLU Output 或文本到 Graph Input 这步一般就是深度学习模型 + 传统的信息提取方法 + Naive 的兜底(比如类别字符串匹配)。如果看过《思考,快与慢》的话,这个 NLM 记忆层相当于系统 2,进到这里后出去是需要做一系列推理和判断的。至于系统 1,则直接从历史对话中得到,这方面可以借鉴这个项目,这时候就不需要图数据库了。

目前从 Graph Input 到存储、Query 这步已经完成了,并且两步自动合并为一步,即 NLM 会根据输入的 Node 或 Relation 的部分信息找到存储的对应的完整信息,同时它会自动判断(可以全局配置或在 Query 时配置)是不是要添加或更新。项目主页在这里,需要说明的是:属性不作为 Query 信息,仅作为对 Query 结果排序的依据。NLM 可以作为Python 模块使用,也可以作为 RPC 服务使用。在使用前需要做一些配置和操作,具体如下:

第一步:安装依赖

# 使用 pipenv
$ pipenv install --dev
# 没有 pipenv
$ python3 -m venv env
$ source env/bin/activate
$ pip install -r requirements.txt

第二步:启动一个 Neo4j 实例

$ docker run --rm -it -p 7475:7474 -p 7688:7687 neo4j

这里我们使用 7475 和 7688 两个端口,和正式环境区分开,并且也不持久化存储数据。启动 docker 后,在浏览器中打开 http://localhost:7475/browser/,端口改成 7688,密码输入 neo4j,然后将密码改为 password

如果你是在正式的环境下使用,可以这样:

$ docker run --rm -it \--p=7474:7474 --p=7687:7687 \--v=/your/persist/path/to/neo4j/data:/data \neo4j

同时你需要创建环境变量

NEO_SCHE:scheme
NEO_HOST:host
NEO_PORT:port
NEO_USER:username
NEO_PASS:password

举个例子:

NEO_SCHE:bolt
NEO_HOST:localhost
NEO_PORT:7687
NEO_USER:neo4j
NEO_PASS:complex_password_for_neo4j

如果你不是通过配置文件,那建议使用 inishchith/autoenv: Directory-based environments.,将配置写到 .env 文件下,切换目录会自动加载目录下 .env 中的环境变量。注意,不要把 .env 文件提交到代码仓库。

测试环境下不需要配置环境变量,用的都是上面的默认值,比如端口用 7688,密码用 password 等。

第三步:运行测试

这步的主要目的是生产一点数据:

$ pytest

运行完后打开 http://localhost:7475/browser/,在 Query 框中输入查询语句就能看到节点和关系信息了,一共 8 个节点和 8 个关系:

MATCH (_) RETURN _

作为模块使用

from py2neo.database import Graph
from nlm import NLMLayer, GraphNode, GraphRelation
# 这里的配置可以在具体运行时覆盖
mem = NLMLayer(graph=Graph(port=7688), fuzzy_node=False,add_inexistence=False,update_props=False)########## 节点 ########### 基本查询
node = GraphNode("Person", "AliceThree")
mem(node)# 添加一个新节点,如果不是新节点,就会返回查到的那个节点
new = GraphNode("Person", "Bob")
mem(new, add_inexistence=True)# 模糊查询,只支持 name 上的模糊
node = GraphNode("Person", "AliceT")
mem(node, fuzzy_node=True)# 更新属性,Node 的属性会同步返回更改后的
node = GraphNode("Person", "AliceThree", props={"age": 24})
mem(node, update_props=True)# 多个节点,辅助功能
node = GraphNode("Person", "AliceT")
mem(node, fuzzy_node=True, topn=2)########## 关系 ########### 基本查询
start = GraphNode("Person", "AliceThree")
end = GraphNode("Person", "AliceOne")
relation = GraphRelation(start, end, "LOVES")
mem(relation)# 添加新关系
start = GraphNode("Person", "AliceThree")
end = GraphNode("Person", "Bob")
relation = GraphRelation(start, end, "KNOWS")
mem(relation, add_inexistence=True)# 模糊查询
start = GraphNode("Person", "AliceTh")
end = GraphNode("Person", "AliceO")
relation = GraphRelation(start, end, "LOVES")
mem(relation, fuzzy_node=True)# 多个关系
start = GraphNode("Person", "AliceThree")
end = GraphNode("Person", "AliceOne")
relation = GraphRelation(start, end)
mem(relation, topn=3)# 更新属性,Relation 属性不会同步返回,需再次调用后返回
start = GraphNode("Person", "AliceThree")
end = GraphNode("Person", "Bob")
relation = GraphRelation(start, end, "KNOWS", {"roles": "classmate"})
mem(relation, update_props=True)# 同时更新 Node 和 Relation 的属性
start = GraphNode("Person", "AliceThree")
end = GraphNode("Person", "Bob", {"sex": "male"})
relation = GraphRelation(start, end, "KNOWS", {"roles": "friend"})
mem(relation, update_props=True)# 没有关系类别的查询
start = GraphNode("Person", "AliceThree")
end = GraphNode("Person", "Bob")
mem(GraphRelation(start, end), topn=2)############ 数据库 ############# 所有的 label,即实体类别
mem.labels# 所有的关系类别
mem.relationship_types# 实体数量
mem.nodes_num# 关系数量
mem.relationships_num# 所有的实体,是一个 generator
mem.nodes# 所有的关系,是一个 generator
mem.relationships# CQL 查询
mem.query("MATCH (a:Person) RETURN a.age, a.name LIMIT 5")
[{'a.age': 21, 'a.name': 'AliceTwo'},{'a.age': 23, 'a.name': 'AliceFour'},{'a.age': 22, 'a.name': 'AliceOne'},{'a.age': 24, 'a.name': 'AliceFive'},{'a.age': None, 'a.name': 'Bob'}
]# CQL 执行
mem.excute("MATCH (a:Person) RETURN a.age, a.name LIMIT 5")

NLMLayer 本质上是继承了 py2neo.Graph,所有 py2neo.Graph 的函数和方法,mem 都可以使用,比如:

# 一个 Node Matcher
mem.nmatcher

详细可参考:3. py2neo.matching – Entity matching — The Py2neo v4 Handbook。

另外,如果模糊查询开启,则不会自动更新属性(即便配置了也不会),因为不确定模糊查到的节点是不是具备这些属性。但会自动添加节点,因为模糊查询都找不到的话,自动添加肯定是没问题的。

作为服务使用

作为 RPC 服务,必须在启动时将 NLMLayer 的参数给配置好(当然不配置的话默认都是 False),因为你不能像模块那样在实际调用时覆盖。这样设计的目的是让接口简单、清晰,客户端不用(也不需要)考虑这些东西。

$ python server.py [OPTIONS]Options:-fn fuzzy_node-ai add_inexistence-up update_props

客户端可以使用任何编程语言,详细情况可以阅读 gRPC 相关知识。

目前只有四个接口,但其实后两个并不能提供真正的服务:

  • NodeRecall

  • RelationRecall

  • StrRecall

  • NLURecall

仓库里有一个 Python 版本的客户端使用代码:client.py

特别说明

如果考虑到初衷,项目其实是个半成品,之所以发布出来是想听听更多人的建议,看看实际中到底有哪些应用场景,然后再做针对性地开发。这个毕竟是比较新的领域,我自己也没有很多实践经验。

对返回的结果数量,最开始的想法是只返回一个,后来给留了个 topn 的参数。这个功能在 RPC 中给取消了,主要还是因为后续没有完成,还有是考虑到最终其实只要一个结果,并不需要返回多个,以及 proto 写起来稍微清晰一些。设计的出发点是尽量让使用傻瓜式,比如模块主要功能的入口只有一个。

由于考虑到 Query 中可能有 props,而 props 实际上是 key value 都不确定的字典,这在 proto 中定义起来比较麻烦,一直没找到很合适的方法。所以干脆统一将 props 给序列化了,这样的做法导致 RPC Server 处理起来有一点点复杂。

Resources

  • The Py2neo v4 Handbook — The Py2neo v4 Handbook

  • liuhuanyong/CrimeKgAssitant

  • gunthercox/ChatterBot

  • liuhuanyong/QASystemOnMedicalKG

  • machinalis/iepy: Information Extraction in Python


个人微信:加时请注明 (昵称+公司/学校+方向)

历史精品文章推荐

1、知否?知否?一文看懂深度文本分类之DPCNN原理与代码

2、CCL“中国法研杯”相似案例匹配评测竞赛 - TOP队伍攻略分享

3、推荐|机器学习入门方法和资料合集

知识图谱存储与查询:自然语言记忆模块(NLM)相关推荐

  1. 技术动态 | eBay开源分布式知识图谱存储Beam,支持类SPARQL查询

    本文转载自公众号:AI前线. 作者 | Diego Ongaro,Simon Fell 译者 | 盖磊 编辑 | Natalie AI 前线导读:eBay 工程人员于 5 月 1 日在 官方技术博客 ...

  2. 【Query Embedding on Hyper-relational Knowledge Graphs】 超关系知识图谱上的查询嵌入 论文结果复现

    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言 一.背景知识 二.写作动机 三.技术概述 1.查询嵌入: 2.超关系知识图谱: 3. 超关系查询: 四.模型概述: 五 ...

  3. 人工智能-知识图谱的进阶一

    第一部分:概论 本文主要分为三个部分.第一个部分介绍我们为什么需要知识图谱,第二个部分介绍知识图谱的相关概念及其形式化表示.最后,作一个简单的总结,并介绍该专栏后续文章会涉及的内容. 一.看到的不仅仅 ...

  4. AI人必看!89页全网最全清华知识图谱报告(附PDF)

    来源:智东西 知识图谱(Knowledge Graph)是人工智能的重要分支技术,它在2012年由谷歌提出,成为建立大规模知识的杀手锏应用,在搜索.自然语言处理.智能助手.电子商务等领域发挥着重要作用 ...

  5. AI人必看!89页全网最全清华知识图谱报告

    来源:智东西 摘要:谷歌冲锋,淘宝猛追,这个AI秘密武器强在哪? 知识图谱(Knowledge Graph)是人工智能的重要分支技术,它在2012年由谷歌提出,成为建立大规模知识的杀手锏应用,在搜索. ...

  6. 请问知识图谱有哪些研究点是可以进行深入研究的?

    漆桂林 东南大学 计算机软件与理论教授 收录于编辑推荐 · 130 人赞同了该回答 知识图谱这两年研究很火,因为在业界和政府部门都取得了很好的应用,不过大部分的工作还是集中在NLP,还有很多问题没有得 ...

  7. 连载 | 知识图谱发展报告 2018 -- 前言

    OpenKG 将开始连载<知识图谱发展报告(2018)>,希望该连载能够让更多的人深入了解知识图谱.欢迎各位读者留言讨论. 1. 知识图谱的研究目标与意义 知识图谱(Knowledge G ...

  8. 知识图谱综述及技术地图概览(智能问答系统)

    知识图谱(Knowledge Graph)的概念由谷歌于2012年正式提出,旨在实现更智能的搜索引擎,并且于 2013 年以后开始在学术界和业界普及,并在智能问答.情报分析.反欺诈等应用中发挥重要作用 ...

  9. 从零学习知识图谱——01(知识图谱技术介绍)

    今年研一,导师给的大范围是知识图谱.从零开始学习知识图谱相关知识,内容大多为笔记和心得,仅供参考.欢迎各位大牛来指导. 知识图谱 (Knowledge Graph,KG) 以结构化的形式描述客观世界中 ...

最新文章

  1. grub legacy
  2. 2021年春季学期-信号与系统-第七次作业参考答案-第十小题
  3. PostgreSQL9中stream同步与Slony同步的比较
  4. LeetCode Algorithm 268. 丢失的数字
  5. How is a Batch request handled in the backend
  6. 开源纯C#工控网关+组态软件(六)图元组件
  7. 动态规划训练24 [Phalanx HDU - 2859 ]
  8. java义一个方法,返回一组双色球票数
  9. libcurl 多线程使用注意事项 - Balder~专栏 - 博客频道 - CSDN.NET
  10. 鸿蒙OS的指纹储存在哪里,鸿蒙OS发布以后,我们去哪里学习?
  11. Linux常用解压文件
  12. Bind 一些有趣的东西 【未完待续】
  13. 工程模式写入imei_开发初期IMEI号的写入
  14. docker视频教程下载
  15. 计算机c盘能格式化吗,c盘可以直接格式化吗?C盘怎么格式化?
  16. 图神经网络的直推式(Transductive)学习与归纳(Inductive)学习
  17. postgres 坐标_用postgreSQL做基于地理位置的app(zz)
  18. Mysql --分表、分库、分区(横向纵向、分区列)的区别与详解
  19. python怎么筛选并列关系数据_一文带你学会如何用Python生成带误差棒的并列和堆积柱状图...
  20. 纯CSS实现回到顶部的功能

热门文章

  1. 扒开python,看透python的内在,python的应用领域
  2. 中石油集团不会整体上市
  3. 淮北师范大学的计算机科学与技术怎么样,淮北师范大学和安庆师范大学怎么样?两个大学哪个更好?...
  4. [渝粤教育] 浙江工业大学 运动控制系统 参考 资料
  5. 【原创】一个计算斗地主谁必赢谁必输的程序
  6. Matlab中pause函数用法
  7. 页面布局--上下固定中间自适应出现滚动条布局
  8. android 调用系统行程,-基于Android的行程安排系统的设计与实现(整理版).doc
  9. C语言运用函数求长方体的表面积和体积
  10. Python语言程序设计 习题3