
从零到一构建一个智能问答助手
>>>>前言
「问答机器人」在我们日常生活中并不少见到 :像是一些电商客服、智能问诊、技术支持等人工输入与沟通界面的场景下,机器人“智能”问答系统一定程度上可以在无需人力、不需要耗费终端用户心智去做知识库、商品搜索、科室选择等等的情况下实时给出问题答案。
问答机器人系统背后的技术有多重可能:
● 基于检索,全文搜索接近的问题
● 基于机器学习阅读理解
● 基于知识图谱(Knowledge-Based Question Answering system: KBQA)
● 其他
基于知识图谱构建问答系统在以下三个情况下很有优势:
● 对于领域类型是结构化数据场景:电商、医药、系统运维(微服务、服务器、事件)、产品支持系统等,其中作为问答系统的参考对象已经是结构化数据;
● 问题的解答过程涉及多跳查询,比如“姚明的妻子今年是本命年吗?”,“你们家的产品 A 和 A+ 的区别是什么?”;
● 为了解决其他需求(风控、推荐、管理),已经构建了图结构数据、知识图谱的情况。
为了方便读者最快速了解如何构建 KBQA 系统,我写了非常简陋的小 KBQA 项目,在本文中,我会带领大家从头到尾把它搭起来。
>>>>鸟瞰 TL;DR
KBQA 用一句话说就是把问题解析、转换成在知识图谱中的查询,查询得到结果之后进行筛选、翻译成结果(句子、卡片或者任何方便人理解的答案格式)。
「问题到图谱查询的转换」有不同的方法可以实现。
可以是对语义进行分析:理解问题的意图,针对不同意图匹配最可能的问题类型,从而构建这个类型问题的图谱查询,查得结果;
也可以是基于信息的抽取:从问题中抽取主要的实体,在图谱中获取实体的所有知识、关系条目(子图),再对结果根据问题中的约束条件匹配、排序选择结果。
而在 Siwi 里,我们一切从简,单独选择了语义分析这条路,它的特点是需要人为去标注或者编码一些问题类型的查询方式,但实际上在大多数场景下,尤其单一领域图谱的场景下反而是轻量却效果不差的方案,也是一个便于新手理解 KBQA 的合适的入门方式。
除了核心的问答部分,我还为 Siwi 增加了语音识别和语音回答(感谢浏览器接口标准的发展)的功能,于是,这个项目的结构和问答调用流程就是这样的了:一个语音问题自上而下分别经过三个部分:
● 基于网页的 Siwi Frontend 语音、文字问答界面
● Python Flask 实现的 Siwi Backend/API 系统
● Nebula Graph 开源分布式高性能图数据库之上的知识图谱
>>>>知识图谱
Siwi 构建于一个篮球相关的知识图谱之上,它其实是 Siwi 采用的开源分布式图数据库 Nebula Graph 社区的官方文档里的示例数据集。
在这个非常简单的图谱之中,只有两种点:
● player,球员
● team,球队
两种关系:
● serve 服役于(比如:姚明 -服役于-> 休斯顿火箭)
● follow 关注(比如:姚明 -关注-> 奥尼尔)
下图就是这个图谱的可视化探索截图,可以看到左边的中心节点勇士队(Warriors)有杜兰特(Durant)还有其他几个队员在其中服役(serve);除了服役之外,还可以看到队员和队员之中也有关注(follow)的关系存在。
有了这个知识图谱,咱们接下来就在它之上搭一个简单的基于语法解析的 QA 系统吧😁 。
>>>>Siwi-backend
如上图的设计流程,Siwi 的后端部分需要接收问句,处理之后访问知识图谱(图数据库),然后将处理结果返回给用户。
接收 HTTP 请求(app)
对于请求,就简单地用 Flask 作为 Web Server 来接收 HTTP 的 POST 请求。
下边的代码就是告诉 Flask :
1. 如果用户发过来 http://<server>/query 的 POST 请求,提的问题就在请求的 body 里的 question 的 Key 之下。
2. 取得问题之后,调用把请求传给 siwi_bot 的 query(),得到 answer 。
代码段:src/siwi/app/__init__.py
接下来我们来实现 siwi_bot,真正处理提问的逻辑。
处理请求(bot)
前边提到过,KBQA 基本上是
a. 把问题解析、转换成在知识图谱中的查询
b. 查询得到结果之后进行筛选、翻译成结果
这里,我们把 a. 的逻辑放在 classifier 里,b. 的逻辑放在 actions(actor) 里。
a. HTTP 请求的问题句子 sentence 传过来,用 classifier 解析它的意图和句子实体
b. 用意图和句子实体构造 action,并链接图数据库执行,获取结果。
代码段:src/siwi/bot/bot/__init__.py
首先咱们来进一步实现一下 SiwiClassifier 吧。
语义解析(classifier)
classifier 需要在 get(sentence) 方法里将句子中的实体和句子的意图解析、分类出来。通常来说,这里是需要借助机器学习、NLP去分词、分类实现的,这里只是为了展示这个过程实际上只是各种 if/else。
我们这里实现了三类意图的问题:
● 关系(A,B):获得 A 和 B 在图谱中的关系路径,比如姚明和湖人队的关系是?
● 服役情况:比如乔纳森在哪里服役?
● 关注情况:比如邓肯关注了谁?
❓ 开放问题:
如果看教程的你觉得这几个问题太没意思了,这里留一个开放问题,你可以在 Siwi 里帮我们实现:「共同好友(A,B)获得 A 和 B 的一度共同好友」这个意图(或者更酷的其他句子)么?欢迎来 Github:github.com/wey-gu/nebula-siwi/ 提 PR 哦,看看谁先实现。
代码片段:
src/siwi/bot/classfier/__init__.py
意图识别(intent)
对于每一个意图来说:
● intents.<名字> 代表名字
● 名字之后的 action 代表后边在要实现的相应的 xxxAction 的类
◆ 比如 RelationshipAction 将是用来处理查询关系(A,B)这样的问题的 Action 类
● keywords 代表在句子之中匹配的关键词
◆ 比如问句里出现 serve,served,serving 的字眼的时候,将会匹配服役的问题
实体识别(entity)
类似的,实体识别的部分本质上也是 if else,只不过这里利用到了 Aho–Corasick 算法来帮助搜索实体,在生产(非玩具)的情况下,应该用 NLP 里的分词的方法来做。
至此,我们的 SiwiClassifier.get(sentence) 已经能返回解析、分类出来的意图和实体了,这时候,它们会被传给 Actions 来让 siwi_bot 知道如何去执行知识图谱的查询啦!
构造图谱查询(action)
还记得前边的 bot 代码里,最后一步,图谱查询的动作是这么被构造的:
action = self.actions.get(intent)
现在咱们就把它实现一下:
1. 在前边提到过的 intents.yaml 里获取这个意图里配置的意图的类名称
2. 导入相应的 Action 类
代码段:src/bot/actions/__init__.py
最后,我们来实现这个类吧,比如 RelationshipAction 对应的代码如下:
● 根据提供的 A 和 B,构造并执行图数据库之中的 FIND PATH
● 将 FIND PATH 的结果进行解析,通过 as_path() 方法的封装,获得 path 类型的数据,并处理一个句子返回给用户
试启动图数据库
我们在 Nebula Graph 里建立(导入数据)一个篮球的知识图谱。
本文假设我们使用 Nebula-UP 来部署一个 Nebula Graph:
之后,我们会看到这样的提示:
按照提示,我们可以通过这个命令进入到有 Nebula Console 的容器里:
然后,在 # 的提示符下就表示我们进来了,我们在里边可以执行:
这样就表示我们连接上了 Nebula Graph 图数据库:
在这里,我们就可以通过 nGQL 去操作 Nebula Graph,不过我们先退出来,执行 exit:
我们在这个容器内把基于 nGQL 语句的数据下载下来:
然后通过 Nebula Console 的 -f <file_path> 把数据导入进去
Siwi-backend
大家可以直接 clone 我的代码:git clone https://github.com/wey-gu/nebula-siwi/
然后安装、启动 Siwi Backend:
启动之后,我们可以另外开窗口,通过 cURL 去发起问题给 backend,更多细节大家可以参考 GitHub 上的 README:
至此,我们已经写好了 QA 系统的重要的代码啦,大家是不是对一个 KBQA 的构成有了更清晰的概念了呢?
接下来,我们为它增加一个界面!
>>>>Siwi-frontend
聊天界面
我们利用 Vue Bot UI 这个可爱的机器人界面的 Vue 实现可以很容易构造一个代码段:src/siwi/frontend/src/App.vue
注意到那个小飞机按钮了吧,它是发出问题请求的按键,我们要在按下它的时候对后端做出请求。
访问后端
这部分用到了Axios,它是浏览器里访问其他地址的 HTTP 客户端。
1. 在按下的时候,@msg-send="msgSender" 会触发 msgSender()
2. msgSender()去构造axios.post(this.apiEndpoint, { "question": data.text }) 的请求给 Siwi 的后端
3. 后端的结果被 push() 到界面的聊天消息里,渲染出来 this.msg.push()
代码段:src/siwi/frontend/src/App.vue
现在,我们已经有了一个图形界面的机器人啦,不过,更进一步,我们可以利用现代浏览器的接口,实现语音识别和机器人说话!
语音识别
我们借助于 Vue Web Speech, 这个语音 API 的 VueJS 的绑定,可以很容易在按下 🎙️ 的时候接收人的语音,并把语音转换成文字发出去,在回答被返回之后,它(还是他/她😁?)也会把回答的句子读出来给用户。
1. record 在 🎙️ 被按下之后,变成 👂
2. 触发 onResults() 监听
3. 把返回结果发给 this.synthText 合成器,准备读出
4. <vue-web-speech-synth> 把语音读出
代码段:src/siwi/frontend/src/App.vue
>>>>总结
至此,我们已经学会了搭建自己的第一个 KBQA:知识图谱驱动的问答系统。
回顾下它的代码结构:
● src/siwi 对应后端
◆ App 是 Flask API 处理的部分
◆ Bot 是处理请求、访问 Nebula Graph 的部分
● src/siwi_frontend 是前端
希望大家在这个简陋的基础之上,多多探索,做出来更加成熟的聊天机器人,欢迎你来给我邮件、留言告诉我呀,这里:https://siwei.io/about 有我的联系方式。
>>>>感谢用到的开源项目
这个小项目里我们用到了好多开源的项目,非常感谢这些贡献者们的慷慨与无私,开源是不是很酷呢?
Backend
KGQA on MedicalKG by Huanyong Liu
链接:https://github.com/liuhuanyong/QASystemOnMedicalKG
Flask
链接:https://github.com/pallets/flask
pyahocorasick created by Wojciech Muła
链接:https://github.com/WojciechMula/pyahocorasick
PyYaml
链接:https://pyyaml.org/
Frontend
VueJS for frontend framework
链接:https://vuejs.org/
Vue Bot UI, as a lovely bot UI in vue
链接:https://vuejs.org/
Vue Web Speech, for speech API vue wrapper
链接:https://github.com/Drackokacka/vue-web-speech
Axios for browser http client
链接:https://github.com/axios/axios
Solarized for color scheme
链接:https://en.wikipedia.org/wiki/Solarized
Vitesome for landing page design
链接:https://github.com/alvarosabu/vitesome
Graph Database
Nebula Graph 高性能、云原生的开源分布式图数据库
链接:https://github.com/vesoft-inc/nebula/
