欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-06-12 20:06:56
...

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,确定提问的主体,即要在图谱中查找的实体名称;通过label确定提问的类型,是何种类型的实体进行提问,从而在多种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,intention,将其作为问题解析的结果返回,下一步通过这三个特征的变量,匹配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问答模板,可实现基本问题的检索。

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代码实现问题气泡的推送。首先通过

//注意!!下面这玩意儿是实现问题气泡显示的
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系统返回答案时回答气泡显示在聊天区

.