快速入门 KBQA问答系统的实现

2023-11-12 00:20

本文主要是介绍快速入门 KBQA问答系统的实现,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

KBQA问答系统的实现

首先默认你是已经学会了如何构建知识图谱,并且学会用sparql语言查询里面的知识库里面的知识。如果不会,请看下面的链接

使用D2RQ把关系数据库的信息转化为rdf文件。
使用jena 构建知识数据库tdb,然后学会如何查询相关知识

源码在GitHub这里
目录结构很简单:
KBQA
/kbqa 里面有四个文件,分别是
word_tag.py ## 这个主要是用来分词的
question_temp.py ## 这个是问题模板
question2sparql.py ## 这个是用来把问题转化为sparql 语句
query_main.py ## 显然这个是最终的查询文件

另一个文件是:lsy.nt 这个是知识rdf的三元组,问答系统的知识来源

一、 把句子切分成一个个单词

这个也就是word_tag.py 的工作
在这里插入图片描述
这里主要是利用了斯坦福大学写好的 stanfordcorenlp

在你能使用这个包之前,首先要下载好一些东西
Stanford CoreNLP 官网下载

在这里插入图片描述
stanford-corenlp-full-2018-10-05
1、先下载红色按钮的,解压后,再把下面下载的jar包放进里面。

2、使用指令 安装 pip install stanfordcorenlp

需要介绍的是这里实现了一个类,word,表示一个词汇,有两个属性,token (记号) 和pos (part of speech)词性的意思

nlp.pos_tag(sentence) 会返回一个列表,元素为元组,即单词和它的词性组成。
这样我们就可以把句子给切分好了。

二、问题模板的构建

首先基于知识库,我们可以有下面的问题

sentence_list = ["What is the name of littlejun ?","What is the age of chacha ?" ,"What is the username of scc ?" ,"Whose age is larger than 18 ? " ,"What is the phone number of chacha ? " ,"What is the password of littlejun ? "
]

sparql 的模板,最后都是由下面的模式组成

# 问题模板
prefix_temp ="""PREFIX ps:<http://solicucu/person/#>PREFIX us:<http://solicucu/user/#>PREFIX vocab: <http://solicucu/vocab/>
"""
sparql_select_temp = u"""{prefix}select distinct {select} where {{{expression}}}
"""

那注意到我们只需要完成select 的填充和 expression 的填充就好了。
这个跟确却的问题有关,所以,我们要做的事情就是,如何知道,传入来的问题,是属于哪个句子呢?

REFO (Regular Expressions for Objects)

基于对象级别的正则匹配,这个跟python的很像正则表达式很像

"ab" is Literal("a") + Literal("b")"a*" is Star(Literal("a"))"(ab)+|(bb)*?" is:a = Literal("a")
b = Literal("b")
regex = Plus(a + b) | Star(b + b, greedy=False)

如上面所看见,Literal 是一个文字类,那些类支持一些基本符号:+ 连接的意思
| 为或的意思,
python 正则表达式的 + 号用Plus() 代替,表示1到多个的意思。
python 正则表达式的 * 号用Star() 代替,表达0 到多个的意思,可以选择是否采取贪婪模式。

所以,词汇的定义很重要:

# 定义一个词汇类,继承Predicate 
class W(Predicate):#token 词汇的字面符号 pos 词汇的属性def __init__(self,token=".*",pos=".*"):self.token = re.compile(token + "$")self.pos = re.compile(pos+"$")super(W, self).__init__(self.match) # 不可缺少def match(self,word):m1 = self.token.match(word.token)m2 = self.pos.match(word.pos)return m1 and m2

谓词的定义,这是一个继承了Predicate(来自refo 的类,定义另个正则匹配对象属性
match 函数,表面,对于传进来的word 要同时满足 记号和词性都符合才行。

# 定义一些规则,相当与正则表达式的某个模式
class Rule(object):#匹配的条件数 和条件,以及action 回调函数def __init__(self,condition_num,condition=None,action=None):assert condition and action self.condition = conditionself.action = action self.condition_num = condition_numdef apply(self,word_list):#因为可能满足条件的有多处,所以用matches列表存储matches = []# 用条件去找匹配的词汇,finditer 里面用到了yeild,就是每次找到一个结果返回一次,继续找# 可以理解为finditer 返回的值可以迭代for m in finditer(self.condition,word_list):i,j = m.span()matches.extend(word_list[i:j]) # 提取出被匹配的句子区间划出,其中可能有其他杂词汇return self.action(matches),self.condition_num

规则class的形式,首先必须有两个参数condition 和 action 表示这个规则适用的条件(对象正则表达式) 和采取的行动(某个回调函数)
注意里面的finditer 返回的对象是一个Match 的对象,通过span() 获取匹配的范围

当谓词定义好了,规则定义好了,就可以写匹配规则了。


# 疑问代词关键字 who what 
what = (W("what")|W("What"))
whose = (W("whose")|W("Whose"))
of = W("of")number_entity = W(pos="CD")# 属性关键字
username = W("username")
name = W("name")
phone = W("phone")
age  = W("age")
password = W("password")attr_noun  = (username | name | phone | phone | age | password)
#普通名词
common_noun = W(pos = pos_common_noun) 

看上面,定义的词汇,what 可以匹配大写或者小写 ,因为what = (W(“what”)|W(“What”))
所以一个谓词,可以是多个谓词的或,或者只有一个,就是匹配固定的单词
number_entity = W(pos=“CD”) 这里定义了 一个数字谓词,因为我们不关注它的值,所以指定属性为”CD“就好,至于为什么是”CD“

详见standfordcorenlp 英文词性标注

attr_noun :表示属性名词,在本次知识数据库中,主要涉及的属性名词如上。

规则定义


rules = [# What is the name of sb-uname?# What is the age of sb-uname ?# What is the username of sb-uname?# What is the phone number of sb-uname?# What is the password of sb-name?Rule(condition_num = 4 ,condition = what + Star(Any(),greedy = False) + attr_noun + Star(Any(),greedy = False) + of + common_noun + Star(Any(),greedy = False),action = QuestionSet.proccess_attr_noun),# Whose age is larger than 18 ?Rule(condition_num = 4,condition = whose + attr_noun + Star(Any(),greedy = False) + compare + Star(Any(),greedy = False) + number_entity + Star(Any(),greedy = False),action = QuestionSet.who_age_compare )
]

这里规则,只有两条:第一条匹配了上面5个问题,第二条匹配了它上面的问题
第一个参数 condition_num :这个是我们condition中关注的词汇数。比如第一个我们希望匹配到有what attr_noun(即name,age 等5个中的一个),common_noun(普通名词) 所以为3

第二个参数 condition:
这里可以看到 是几个谓词相加,即如前面所述,是连接的意思,所以该条件表达的意思是
任意的一个句子 模式为:what/What … name/age/username/password/phone … of comomn_noun …
也就是为什么会匹配上面的句子的原因

第三个参数 action:
回调函数,在rule 里面有这么一个属性,他是在调用了apply 后,如果匹配了,就会调用的一个函数
在这里,指向了一个QuestionSet.proccess_attr_noun 这个函数,可以继续完成确定是哪个规则。

提取词汇,填充模板

# 1 what is the name of sb-uname ?
def what_name(word_list):# if(len(word_list)):# 	print("成功匹配问题")# 	for w in word_list:# 		print(w.token,end = " ")sparql = Noneselect = "?name"for w in reversed(word_list):# 找到第一个普通名词if(w.pos == pos_common_noun): e = " ps:{person} vocab:person_name ?name .".format(person = w.token)sparql = sparql_select_temp.format(prefix = prefix_temp, select = select,expression = e)breakreturn sparql 

首先是对select 赋值,确定要查询的变量 ,比如这里就是?name 就是要查询的变量
最终要的是确定表达式的变量,这里是:
ps:{person} vocab:person_name ?name
表示某人的名字是什么,这个某人 就会是word_list 从后往前的一个名词,所以,找到之后,就可以break退出了。
然后返回,对应的sparql语句

三、把句子转化为sparql 语句

def get_sparql(sentence):word_list = word_tag.get_word_list(sentence)query = Nonequeries_dict = dict()for rule in question_temp.rules:query,num = rule.apply(word_list)if(query is not None):queries_dict[num] = queryif len(queries_dict) == 0 :return Noneelif len(queries_dict) == 1:# 要转化为list才可以用索引访问return list(queries_dict.values())[0]else:  #  key 就是对多元组的排序指定列 item的名字随便,表示列表的元素,item[0] 表示那个值                                       sorted_dict = sorted(queries_dict.iteritems(),key=lambda item:item[0],reversed = True)return sorted_dict[0][1] 

上面,就是对句子去匹配每一个规则,放到一个字典里面。
如果长度为0,那么就是没有匹配,如果长度为1 那么就是只匹配到一个句子,直接取第0个值,但是注意要转化为列表的形式才可以用索引访问。
如果长度大于1的时候,就对字典排序,按照key的大小排序,从大到小,也就是取匹配到关键字最多的一个。

四、访问endpoint,显示查询结果

sparql = SPARQLWrapper("http://localhost:3030/db/query")
sparql.setReturnFormat(JSON)if __name__ == "__main__":# sentence = "What is the username of scc ?" while True:sentence = input("please input the question ? input quit to leave\n")# print("question:",sentence)if(sentence == "quit"):break str_sparql = q2s.get_sparql(sentence)if(str_sparql is not None):sparql.setQuery(str_sparql)results = sparql.query().convert()head =  results["head"]["vars"]values = results["results"]["bindings"] # 存储的结果if(len(values)==0):print("no relevant answer")else:print("the answer is :",end = " ")for v in values:  # 对于所有value ,通过varname 获取其值for varname in head:print(v[varname]["value"])else:print("sorry,I can't understand your means")

这里要注意的就是,我们查询的变量名存在results[“head”][“vars”]
对应的值存在 results[“results”][“bindings”] 结果是一个个字典

在这里插入图片描述

这篇关于快速入门 KBQA问答系统的实现的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/393817

相关文章

SpringBoot中SM2公钥加密、私钥解密的实现示例详解

《SpringBoot中SM2公钥加密、私钥解密的实现示例详解》本文介绍了如何在SpringBoot项目中实现SM2公钥加密和私钥解密的功能,通过使用Hutool库和BouncyCastle依赖,简化... 目录一、前言1、加密信息(示例)2、加密结果(示例)二、实现代码1、yml文件配置2、创建SM2工具

Mysql实现范围分区表(新增、删除、重组、查看)

《Mysql实现范围分区表(新增、删除、重组、查看)》MySQL分区表的四种类型(范围、哈希、列表、键值),主要介绍了范围分区的创建、查询、添加、删除及重组织操作,具有一定的参考价值,感兴趣的可以了解... 目录一、mysql分区表分类二、范围分区(Range Partitioning1、新建分区表:2、分

MySQL 定时新增分区的实现示例

《MySQL定时新增分区的实现示例》本文主要介绍了通过存储过程和定时任务实现MySQL分区的自动创建,解决大数据量下手动维护的繁琐问题,具有一定的参考价值,感兴趣的可以了解一下... mysql创建好分区之后,有时候会需要自动创建分区。比如,一些表数据量非常大,有些数据是热点数据,按照日期分区MululbU

MySQL中查找重复值的实现

《MySQL中查找重复值的实现》查找重复值是一项常见需求,比如在数据清理、数据分析、数据质量检查等场景下,我们常常需要找出表中某列或多列的重复值,具有一定的参考价值,感兴趣的可以了解一下... 目录技术背景实现步骤方法一:使用GROUP BY和HAVING子句方法二:仅返回重复值方法三:返回完整记录方法四:

IDEA中新建/切换Git分支的实现步骤

《IDEA中新建/切换Git分支的实现步骤》本文主要介绍了IDEA中新建/切换Git分支的实现步骤,通过菜单创建新分支并选择是否切换,创建后在Git详情或右键Checkout中切换分支,感兴趣的可以了... 前提:项目已被Git托管1、点击上方栏Git->NewBrancjsh...2、输入新的分支的

从入门到精通MySQL联合查询

《从入门到精通MySQL联合查询》:本文主要介绍从入门到精通MySQL联合查询,本文通过实例代码给大家介绍的非常详细,需要的朋友可以参考下... 目录摘要1. 多表联合查询时mysql内部原理2. 内连接3. 外连接4. 自连接5. 子查询6. 合并查询7. 插入查询结果摘要前面我们学习了数据库设计时要满

Python实现对阿里云OSS对象存储的操作详解

《Python实现对阿里云OSS对象存储的操作详解》这篇文章主要为大家详细介绍了Python实现对阿里云OSS对象存储的操作相关知识,包括连接,上传,下载,列举等功能,感兴趣的小伙伴可以了解下... 目录一、直接使用代码二、详细使用1. 环境准备2. 初始化配置3. bucket配置创建4. 文件上传到os

关于集合与数组转换实现方法

《关于集合与数组转换实现方法》:本文主要介绍关于集合与数组转换实现方法,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1、Arrays.asList()1.1、方法作用1.2、内部实现1.3、修改元素的影响1.4、注意事项2、list.toArray()2.1、方

使用Python实现可恢复式多线程下载器

《使用Python实现可恢复式多线程下载器》在数字时代,大文件下载已成为日常操作,本文将手把手教你用Python打造专业级下载器,实现断点续传,多线程加速,速度限制等功能,感兴趣的小伙伴可以了解下... 目录一、智能续传:从崩溃边缘抢救进度二、多线程加速:榨干网络带宽三、速度控制:做网络的好邻居四、终端交互

从入门到精通C++11 <chrono> 库特性

《从入门到精通C++11<chrono>库特性》chrono库是C++11中一个非常强大和实用的库,它为时间处理提供了丰富的功能和类型安全的接口,通过本文的介绍,我们了解了chrono库的基本概念... 目录一、引言1.1 为什么需要<chrono>库1.2<chrono>库的基本概念二、时间段(Durat