前言
承接前文,本文介绍如何根据已有的neo4j图谱来搭建一个简单的问答系统。
ps:因为是基于neo4j图谱的,所以这个问题必须是在图谱中有答案才能进行回答。
完整项目github地址:https://github.com/Chtholly1/KG-and-QA-demo
正文
问答系统是NLP中最复杂的场景之一,根据查阅资料和个人的理解,一个简单的问答系统至少需要包含以下三个部分:
- 问题分类
- 关键词抽取
- neo4j查询
以下分别对这三部分进行介绍:
问题分类
当一个问句过来的时候,我们首先要对其进行初步的分类,比如:
问题:伊利丹·怒风的称号是什么?
解析提问的类别:title
问题:伊利丹·怒风的种族是什么?
解析提问的类别:race
这是一个典型的分类问题,我们一般有两种方法:
1.模板匹配:
比如说对于第一个问题,他的模板就应该是
xx的称号是什么?
对于每个类别我们都可以枚举一些常见的模板,如:
#race
xx是什么族的
xx是什么种族的
xx的种族是什么
xx是啥族的
xx是啥种族的
xx的种族是啥
xx是xx族的吗
xx族有哪些人
xx族有哪些角色
哪些人是xx族的
哪些角色是xx族的
哪些是xx族的
#group
xx是什么势力的
xx的势力是什么
xx是啥势力的
xx的势力是啥
xx是xx势力的吗
xx势力有哪些人
xx势力有哪些角色
哪些人是xx势力的
哪些角色是xx势力的
哪些是xx势力的
#title
xx的头衔是什么
xx的称号是什么
xx被誉为什么
匹配模板也有两种思路:
- "完美匹配"法,即模板在去掉"xx"等字符后,应该是提问的一个严格子串
- 关键词匹配法,即只需要出现模板的一些关键词就认为模板匹配成功
显然第一种方法准确率更高,但召回率更低。
2.模型分类
分类问题当然能由经典的NLP模型来解决,自不必多说。
关键就是样本的构造和收集。
一个比较常规的思路就是
- 自己先构造一些常见模板,然后把模板的xx部分填充成真实的实体作为样本进行初步的训练,得到一个分类模型。
- 模型上线后,把用户的真实的提问作为新增样本,不断迭代分类模型。
关键词抽取
判断问题的所属类别以后,我们还需要知道用来查询的关键词,
问题:伊利丹·怒风的称号是什么?
解析提问的类别:title
如上,虽然知道了在问称号,但我们需要知道在问谁(伊利丹怒风)的称号。
关键词抽取的方法其实和分类问题是差不多的,一是模板匹配,二是NER模型。
初级阶段来说,模板匹配就能做的比较好了,因为关键词的集合是图谱中存在的有限集,而且发生语义的歧义的可能性也很小。
由此我们便完成了第二步:关键词抽取。
neo4j查询
理解问题后,我们最后还需要把理解的内容转换成neo4j支持的查询语言。
首先介绍一个概念,neo4j本质上是由"节点"和"关系"组成,而每个"节点"又可以具有多个"属性"。
简单提问
细心的同学应该发现了,就算在"race"这个大类下,其中还是存在两种不同的提问方式:
1.xx是什么种族的
2.xx种族有哪些角色。
显然这两个提问的结果是不一样的,所以对应的查询语言也不一致。
本质上这里还需要再做一个分类。在这里可以用一个比较取巧的方法:
当xx是一个角色名的时候,触发第一种查询方式。
当xx是一个种族名的时候,触发第二种查询方式。
一般来说,基于图谱来查询的话,简单的提问的结构是比较固定的,一般也就分为三种:
- A(节点)的"某个属性"/"某个关系"是什么?
- “某个属性”/"某个关系"为x的"节点"是什么?
- A(节点)和B(节点)的关系是什么?
所以对于确定好的大类的提问而言,大部分都可以分到这三种结构下面。
复杂提问
但在实际中问题可能要复杂很多,比如说复合问题:
1.种族为兽人且性别为男的角色有哪些?
2.麦迪文的徒弟的徒弟是谁?
对于复合问题,一个常规的想法就是问题拆解。
比如说第一个提问就可以拆解成两个简单提问并进行求交。
另外比较前沿的方法就是text2sql,顾名思义就是用模型把自然语言转换成查询语句的,因为是端到端的所以十分便利,但目前该技术准确率还不够高,感兴趣的读者可以自己去了解学习。
除了复合问题以外,还存在其他类型的复杂提问。总之问答系统是一个对技术和业务能力都有一定要求的复杂场景,而且落地和维护也较为繁琐。
展示
最后还是直观的展示下几个步骤,首先是代码
import re
import sys
from py2neo import Graph
from template_method import TempMethod
#链接neo4j图谱
graph = Graph("http://localhost:7474/", auth=("neo4j", '123456'))
#加载问题模板
c = TempMethod(graph, 'D:/NLP相关/知识图谱/wow/code/wow/question_template.txt')
#本次的问题
question = '伊利丹·怒风的种族是什么?'
print("提问:\n%s\n"%(question))
#判断问题的大类
question_type = c.match(question)
print("类别:\n%s\n"%(question_type))
#根据问题及分类进行关键词提取,并生成答案
query_res_list = c.generate_answer(question_type, question)
print()
#打印答案
print("答案:")
for query_res in query_res_list:
for d in query_res.data():
for key, val in d.items():
sys.stdout.write(val+"、")
输出效果:
提问:
伊利丹·怒风的种族是什么?
类别:
race
关键词提取:
['伊利丹·怒风']
['伊利丹']
答案:
恶魔、暗夜精灵(前)、