基于知识图谱的智能问答项目

1 项目简介

1.1项目概述

问答机器人是人工智能和自然语言处理领域中一个倍受关注并且具有广泛应用前景的研究方向。本项目聚焦于特定领域的智能问答应用研究。在此基础上研究基于语义解析的问答匹配,通过对领域知识进行知识图谱表示、构建问答模型,综合应用了文本映射,SPARQL查询等技术实现智能问答机器人系统。项目中问答算法的设计与知识图谱的构建参考了GitHub中的开源项目。

1.2项目使用环境及工具

  • jdk 8(本项目使用java 1.8.0版本)
  • neo4j 3.5.X (本项目使用3.5.18版本,是基于java的nosql图形数据库)
  • Python 3.6 (本项目使用3.6.8版本)
  • py2neo 3.1.2(项目中调用neo4j数据库的必要包)
  • Django 3.1.5(项目的web应用框架)
  • jieba 0.42.1(项目中对问题进行分词时使用的包)

其余基础的包不在此处做特别说明,项目使用pycharm作为Python的集成开发环境进行开发和调试。
在这里插入图片描述

1.3项目部署

1.首先运行neo4j数据库,切换到数据库bin目录下,使用命令 neo4j console
2.将Django项目导入pycharm中,并设置项目settings.py的路径。
2.将build_kg.py中第10行的neo4j密码修改为自己的,然后python build_kg.py,构建知识图谱。
3.观察database中是否成功导入节点及关系:浏览器打开 http://localhost:7474/即可查看构建好的知识图谱。
4.修改answer_question.py中第8行的neo4j密码。
5.选择Django作为项目的解释器,并运行项目。

2 代码目录结构及原理

2.1核心代码目录结构

|——Django
|————_init_.py
|————settings.py 是 Django 项目的配置文件. 包含很多重要的配置项。
|————urls.py 存放着路由表, 声明了前端发过来的各种http请求分别由哪些函数处理。
|————wsgi.py 提供给wsgi web server调用的接口文件,变量application对应对象实现了wsgi入口。
|——QAManagement
|————migrations
|——————views.py Django的视图层,写有前端发来请求后调用的处理函数
|——QASystem
|————poetryData 用于存放从csv文件中抽取的各个实体之间联系文件的文件夹
|————answer_question.py 设置问题分类查询的CQL模板,并输出查询到的答案。
|————build_kg.py 读入csv文件并构建知识图谱
|————main.py 调用知识图谱问答算法
|————question_classifier.py 问题分词、问题分类
|——staic 存放前端依赖的css和img和js资源的文件夹
|——templates
|————test.html 项目的前端页面
|——manage.py 是一个工具脚本,用作项目管理。使用它来执行管理操作。

2.2知识图谱问答代码分析

2.2.1build_kg.py

在项目中启构建知识图谱的作用。依赖于已经收集好并格式化的csv数据文件,建立几个空的列表,用于存储各种属性之间的关系。通过遍历csv文件,将关系存入其中。定义不同实体的类别,以及给实体之间的关系进行分类,从而构建起了知识图谱的RDF三元组。在本项目中,知识实体有五个,实体关系类型有四种。在neo4j中建立结点和联系,建立图数据库。

for item in reader:
	poetry_dict = {}

	poetry_dict["name"] = item[2]
	poetry_dict["content"] = item[3]
	poetry_dict["tag"] = item[4]

	tag_info.append(item[4])
	dynasty_info.append(item[1])
	author_info.append(item[0])
	name_info.append(item[2])
	content_info.append(item[3])


	author_to_dynasty.append([item[0],item[1]])
	author_to_title.append([item[0],item[2]])
	poetry_to_tag.append([item[2],item[4]])
	poetry_to_content.append([item[2],item[3]])

将生成的结点和关系导出为txt文件,用于下一步构建用户自定义词库。

def export_data(self):
	author,dynasty,tag,name,content,rel_a_d,rel_a_t,rel_p_t,rel_p_c,rel_info = self.read_csv()
	file_author = open("poetryData/author.txt",'w+',encoding='utf-8')
	file_poetry = open("poetryData/poetry.txt",'w+',encoding='utf-8')
	file_dynasty = open("poetryData/dynasty.txt",'w+',encoding='utf-8')
	file_tag = open("poetryData/tag.txt",'w+',encoding='utf-8')

	file_author.write('\n'.join(list(author)))
	file_poetry.write('\n'.join(list(name)))
	file_dynasty.write('\n'.join(list(dynasty)))
	file_tag.write('\n'.join(list(tag)))

	file_author.close()
	file_poetry.close()
	file_tag.close()
	file_dynasty.close()

		return

2.2.2question_classifier.py

保存上述导出的txt文件的地址,作为后面的词库地址。

	cur_dir = os.path.split(os.path.realpath(__file__))[0] 
	self.stopwords_path = cur_dir + os.sep + 'poetryData'+ os.sep +  'stop_words.utf8'
	self.stopwords = [w.strip() for w in open(self.stopwords_path, 'r', encoding='utf8') if w.strip()]
	self.author_path = cur_dir + os.sep + 'poetryData'+ os.sep + 'author.txt'
	self.poetry_path = cur_dir + os.sep + 'poetryData'+ os.sep +  'poetry.txt'
	self.dynasty_path = cur_dir + os.sep + 'poetryData'+ os.sep +  'dynasty.txt'
	self.tag_path = cur_dir + os.sep + 'poetryData'+ os.sep + 'tag.txt'

通过下方代码导入知识图谱生成的文件,其中包含知识图谱结点和联系的关键字。构建起用户自定义词库,可以使关键字不被分割。当用户提出问题时,首先进行问题抽取。使用jieba对问题进行分词,并去掉设置的停用词表中的无意义停用词。

def extractor_question(self,question):
	result = {}
	# jieba.load_userdict(self.vocab_path)
	jieba.load_userdict(self.author_path)
	jieba.load_userdict(self.dynasty_path)
	jieba.load_userdict(self.tag_path)
	jieba.load_userdict(self.poetry_path)
	jieba.load_userdict(self.other_feature_path)
	words = jieba.cut(question)
	words = [w.strip() for w in jieba.cut(question) if w.strip() and w.strip() not in self.stopwords]

如下代码设置问法分类,规定不同的特征词,如果出现此类特征词,则使用固定的查询模板进行匹配答案。

self.belong_tag = ['属于什么类型', '什么类型', '类型','什么风格','风格','描写什么','描写']
self.write_wd = ['有哪些相关问题', '相关问题', '问题','有哪些诗']
self.dynasty_wd = ['什么朝代的人','哪个朝代', '朝代']
self.author_wd = ['作者是谁', '作者', '谁写的']
self.content_wd = ['内容','什么内容','内容是什么','背诵','是什么','']

对已经分好词的词语进行遍历,是否出现知识图谱中的关键词或者是上述问法的特征词。对知识图谱的关键词(实体名称),和几种问法中的关键词进行匹配。最后通过entity(e.g将进酒),确定提问的主体,即要在图谱中查找的实体名称;通过label(e.g诗词)确定提问的类型,是何种类型的实体进行提问,从而在多种CQL查询模板中进行匹配。

words = [w.strip() for w in jieba.cut(question) if w.strip() and w.strip() not in self.stopwords]
	# print(words)
	n  = len(words)
	poetry_name = ""
	author_name = ""
	feature_word = ""
	label = ""
	intent = ""
	for i in range(n):
		if words[i] == '·':
			poetry_name = words[i-1] + '·' + words[i+1]
		elif words[i] in self.poetry_word:
			poetry_name = words[i]
		if words[i] in self.author_word:
			author_name = words[i]
		if words[i] in self.belong_tag:
			feature_word = words[i]
		if words[i] in self.write_wd:
			feature_word = words[i]
		if words[i] in self.dynasty_wd:
			feature_word = words[i]
		if words[i] in self.author_wd:
			feature_word = words[i]
		if words[i] in self.content_wd:
			feature_word = words[i]
			
		if poetry_name:
			entity_word = poetry_name
			label = "Poetry"
		else:
			entity_word = author_name
			label = "Author"

如果识别到了知识图谱中保存的关键词,同时也识别到了问法中包含的特征词,则解析成功,将intent定义为特征词所对应的两个实体间的联系,到此处,我们明确了entity,label,intent(e.g匹配出了query_content),将其作为问题解析的结果返回,下一步通过这三个特征的变量,匹配CQL语句即可查询出另一个实体,即问题的答案。

		if author_name and feature_word in self.write_wd:
			intent = "query_poetry"	
		elif author_name and feature_word in self.dynasty_wd:
			intent = "query_dynasty"
		elif poetry_name and feature_word in self.author_wd:
			intent = "query_author"
		elif poetry_name and feature_word in self.belong_tag:
			intent = "query_tag"
		elif poetry_name and feature_word in self.content_wd:
			intent = "query_content"
		else:
			print("问题无法解析")
			return '-1'
			
		result["entity"] = entity_word
		result["label"] = label
		result["intention"] = intent
		return result

2.2.3answer_question.py

通过上一步中解析出的entity,label,intention,设计出以下几种常用的CQL问答模板,可实现基本问题的检索。(e.g匹配出了“询问诗的内容”)

def transfor_to_sql(self, label, entities, intent):
	sql = []
	#询问作者有哪些诗
	if intent == "query_poetry" and label == "Author":
		sql = ["MATCH (d:Author)-[r:HAS_POETRY]->(s) where d.name='{}' RETURN s.name".format(entities)]

	#询问作者的朝代
	if intent == "query_dynasty" and label == "Author":
		sql = ["MATCH (d:Author)-[r:DYNASTY_IS]->(s) where d.name='{}' RETURN s.name".format(entities)]

	#询问诗的作者
	if intent == "query_author" and label == "Poetry":
		sql = ["MATCH (s:Author)-[r:HAS_POETRY]->(d) where d.name='{}' RETURN s.name".format(entities)]

	#询问诗的类型tag
	if intent == "query_tag" and label == "Poetry":
		sql = ["MATCH (d:Poetry)-[r:TAG_IS]->(s) where d.name='{}' RETURN s.name".format(entities)]

	#询问诗的内容
	if intent == "query_content" and label == "Poetry":
		sql = ["MATCH (d:Poetry) where d.name= '{}' RETURN d.content".format(entities)]
	return sql[0]

将上述代码中查询到的问题的答案,传递到下面的函数,并且通过intent来匹配不同的回答模板,输出相对应的自然语言回答。

def answer_template(self, intent, answers,entity):
	'''
	param answers:知识图谱中查到的答案
	'''
	final_answer = ""
	if not answers:
		return "抱歉,没有找到答案"
	if intent == "query_poetry":
		poetry_set = list(set([answer['s.name'] for answer in answers]))
		final_answer = "{}的相关内容有 {}".format(entity,poetry_set)
	if intent == "query_dynasty":
		final_answer = "{}朝代是{}".format(entity,answers[0]['s.name'])
	if intent == "query_author":
		final_answer = "{}的作者是{}".format(entity,answers[0]['s.name'])
	if intent == "query_tag":
		final_answer = "{}属于{}".format(entity,answers[0]['s.name'])
	if intent == "query_content":
		final_answer = "{}:{}".format(entity,answers[0]['d.content'])
	return final_answer

通过searching函数,调用上述函数,返回最终的答案。即为知识图谱中查询、问答模板匹配出的问题最终答案。

	def searching(self,sqls):
		intent = sqls['intention']
		queries = sqls['sql']
		answers = self.graph.data(queries)
		# print(answers[0]['s.name'])
		# answers = self.graph.run(queries).data()
		entity = sqls['entity']
		final_answer = self.answer_template(intent, answers,entity)
		print("*****************************")
		print(final_answer)
		print("*****************************")
		if str(final_answer)=='null':
			return '-1'
		else:
			return final_answer

2.4项目前后端代码分析

该部分主要针对项目中两个功能为例进行分析,具体内容如下分析。

2.4.1用户提问时问题气泡显示在聊天区

首先需要使用html语言在前端展示出聊天框和发送按钮,代码如下:

<input type="text" id="question" placeholder="请输入与党建相关的问题" style="width: 560px; height: 100px;">
<button type="submit" style="width: 100px; height: 100px;" onclick="gulugulu(document.getElementById('question').value);guluanswer();">发送
{#下方js代码实现通过enter键提交问题 #}
<script>
document.getElementById("question").setAttribute("onKeyDown","keyDetection(event)");
function keyDetection(event){
    if(event.keyCode==13){
        gulugulu(document.getElementById('question').value);
        guluanswer();
    }
</script>

通过如下的JavaScript代码实现问题气泡的推送。首先通过函数调用时间,显示出提问和回答的时间。然后发送ajax请求,并设置为同步请求,只有当出现了返回值时,才会执行下面推送问题的代码。

//注意!!下面这玩意儿是实现问题气泡显示的
function gulugulu(question) {
   var time=new Date();
   var hour=time.getHours();
   var minu=time.getMinutes();
   var secend = time.getSeconds();
   var timeString =hour+":"+minu+":"+secend;
   var question = this.question;

   {#window.alert(question.value)#}
   $.ajax({
       url: '/gulugulu/',
       type: "POST",
       data: {question: question.value},
       charset: "UTF-8",
       async:false,
       success: function (data) {
           data = JSON.parse(data);
       $("  <div class='service-message J-message-wraper'> <div class='aside aside-noimg'><span class='time'>"
            +timeString+
            "</span></div> " +
            "<div class='out-content'> <div class='service-tail iconfont icon-chat-left-bottom-white'></div> <div class='service-card J-service-card'> <div class='card-header card-header-blue '> <span class='title'>"
            +"您提出的问题:" +question.value+"</div>").appendTo($("#msgStart"))
       }
   })
};

最后通过Django框架中的urls.py跳转到views.py中的answer函数执行操作,urls.py的跳转源码如下

path(r'gulugulu/', views.answer),

下方为answer函数的功能。通过gulu函数,获得通过ajax请求发来的question的内容,并将question作为HttpResponse返回。在下方的answer函数中,调用全局变量question_content,同时调用已经封装好的知识图谱问答函数end,获得问题的答案。

question_content="null"
answer_content="null"
def gulu(request):
    question = request.POST.get('question')
    global question_content
    question_content=str(question)
    return HttpResponse(json.dumps(question))

def answer(request):
    global question_content
    global answer_content
    question=gulu(request)
    # print(question_content)
    answer_content=end(question_content)
    # print(answer_content)
    return HttpResponse(json.dumps(answer_content))

2.4.2系统返回答案时回答气泡显示在聊天区

此处的处理思路同上,发送ajax请求,获得通过问答算法计算出的答案,并将请求到的data值赋给answer,并通过气泡的样式显示在聊天区中,作为问题的回答。只有此处获取了返回值,同步的请求才算结束,此时在聊天区才会出现问题和答案。
在显示的时候,由于编码问题,此处设置了讲Unicode编码方式转为utf-8方式的js函数,通过调用该函数可以正常的显示汉字。

function guluanswer() {
     var time=new Date();
     var hour=time.getHours();
     var minu=time.getMinutes();
     var secend = time.getSeconds();
     var timeString =hour+":"+minu+":"+secend;
     var answer
     $.ajax({
         url: '/guluanswer/',
         type: "POST",

         success: function (data) {
             answer = data;

             $("  <div class='service-message J-message-wraper'> <div class='aside aside-noimg'><span class='time'>"
                 +timeString+
                 "</span></div> " +
                 "<div class='out-content'> <div class='service-tail iconfont icon-chat-left-bottom-white'></div> <div class='service-card J-service-card'> <div class='card-header card-header-blue '> <span class='title'>"
                 +decodeUnicode(answer)+"</div>").appendTo($("#msgStart"))
         }
     })
 };
    function decodeUnicode(str) {
    str = str.replace(/\\/g, "%");
    var StringFirst = str;
    var splitFirst = StringFirst.split('%n');
    var splitSecond = splitFirst.join('');
    var splitThird = splitSecond.split('%"');
    var ResultString = splitThird.join('"');
    return unescape(ResultString);
}

在负责上述逻辑处理的Python函数中,同时加入了对未找到答案的问题的处理方式,会以固定语句+问题的形式返回一段话,提醒用户修改问题。

def jsons(request):
    global question_content
    global answer_content
    print(question_content)
    print(answer_content)
    if str(answer_content)=='-1':
        answer_content="不好意思鸭,没有找到关于\'"+question_content+"\'问题的答案。请您换个说法提问,知识图谱还在进一步的完善当中,敬请谅解~~"
    return HttpResponse(json.dumps(answer_content))

3 运行效果图

实际提问效果如图所示,展示了可以找到回答的问题和无法回答的问题。
在这里插入图片描述

  • 19
    点赞
  • 105
    收藏
    觉得还不错? 一键收藏
  • 9
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值