当前位置:   article > 正文

【知识图谱】08KBQA问答系统(python+fuseki+jena)_kbqa开源框架

kbqa开源框架

白嫖太严重了,提问,私信,收藏比点赞关注多太多,过分了啊兄弟们

目录

1、效果预览

2、KBQA介绍

3、KBQA实现

3.1、问答系统设计

3.2、使用python链接Fuseki

3.2、分词实现

3.2.1、实体词处理

3.2.2、分词逻辑的实现

3.3、查询实现

3.3.1、单实体查询

3.3.2、多实体查询

4、业务逻辑的整合实现

5、一些补充

6、参考


        本篇紧随之前的七篇文章,讲述了建立了知识图谱后,不希望仅仅在可视化和抽象推理的方向上得到应用,同时扩充考虑将其应用在问答系统方向;本篇属于一时兴起,写了一个简单的问答系统,通过使用自然语言处理技术,解析匹配用户问题意图,完成后生成查询模板,在知识库里进行检索,从而反馈最佳答案。

        在代码逻辑实现上,考虑到工时资源等信息,因此没有完全按照网上KBQA最常见的REfO的精准匹配去写实现,也没有使用DL技术做模型,而是使用了传统的相似度计算做模糊匹配,因此代码量少很多,算是本篇的特色,应该网上找不到和我代码一样的版本。

代码依赖:

python : 3.6+,(库:SPARQL、jieba、macropodus)

fuseki + jena (同本系列的版本)

1、效果预览

人物问答预览:

 电影问答预览:

单实体的条件问答:

多实体问答:

2、KBQA介绍

        KBQA:Knowledge Base Question Answer,由于我们将知识提取并生成了结构化数据,建立了知识库,因此在应用方向上,通过自然语言问答,通过对问题的解析和推理,利用知识库进行问答查询和分析,从而得到答案,这就是KBQA。

        详细可以阅读,这位博主在知识普及上写的更好:揭开知识库问答KB-QA的面纱1·简介篇 - 知乎

3、KBQA实现

        整体代码工程后续回传到Github上,地址后续补充,项目结构简单定义为:

3.1、问答系统设计

        

        上图是这个系统的简单架构示意,其中底层是数据层,主要是使用了TDB三元数据库,存储知识数据;倒数第二层是通信层,主要是依赖Jena+Fuseki的框架,包括规则推理机的配置等;倒数第三层是应用实现层,主要包括Fuseki的客户端实现,NLP自然语言处理,查询模板的生成应用实现等;最顶层是业务层,主要是对用户输入做提取,结果做解析展示的业务交互层,通过这四层的定义,实现我们的整体项目。

        仅仅只有架构,是不足以完善我们的想法的,因此,我们基于某种产品化的考虑,考虑设计完善的业务流程,从用户接入使用,到数据解析,分析以及反馈系统,从而形成一个较为完整的闭环应用体系,因此,设计如下业务框架:

        如上图所展示,考虑到一个完整的问答系统,必定不能百分百考虑到所有情况,因此,我们需要在业务处理上,考虑当遇到一些疑难杂症的时候,数据应该流转到哪些新模块去做处理,这样才有可能在未来不断地提高KBQA系统的正确率,上图便考虑增加新词发现,新问题发现来帮助系统完善应用广度,增加意图理解来完善深度,而考虑到需要增加KG和模板,那么就需要使用人工加机器二合一的答案判别和数据分析系统模块,这样逐渐形成一个完善的生产系统。我这里仅实现黄色部分,右边的没空实现,不过可以给大家提供一个不错的思路。

3.2、使用python链接Fuseki

利用SPARQLWrapper的接口实现对feseki的调用,测试和使用前记得启动fuseki的服务器。

代码文件fuseki_query.py内容如下:

  1. from SPARQLWrapper import SPARQLWrapper,JSON
  2. class Fuseki:
  3. def __init__(self,url ='http://localhost:3030/kg_movie/query'):
  4. self.sparql = SPARQLWrapper(url)
  5. print('sparql init successful ...')
  6. def query(self, query_str):
  7. self.sparql.setQuery(query_str)
  8. self.sparql.setReturnFormat(JSON)
  9. result = self.sparql.query().convert()
  10. return result
  11. def query_str_build(self,str):
  12. pass
  13. def test():
  14. fuseki = Fuseki()
  15. query_str = r'''
  16. PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
  17. PREFIX : <http://www.kg_movie.com#>
  18. SELECT * WHERE {
  19. ?x rdf:type :Comedian.
  20. ?x :actor_chName ?n.
  21. } LIMIT 10
  22. '''
  23. query_str = r'''
  24. PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
  25. PREFIX : <http://www.kg_movie.com#>
  26. SELECT * WHERE {
  27. ?x :actor_chName '周星驰'.
  28. ?x :actor_bio ?n.
  29. } LIMIT 10
  30. '''
  31. res = fuseki.query(query_str)
  32. print(res)
  33. if __name__ == '__main__':
  34. test()

核心在class Fuseki这里,后续会被调用到。

3.2、分词实现

3.2.1、实体词处理

        首先要导出存储在mysql中的实体词,导出其中的movie和actor两张表的chName列,可以导出成txt:

导出后效果如下:

需要导出这两个文本的原因:

        考虑到对用户话语进行关键词提取和分词,对于我们最关心的热点词,担心分词效果不好,因此将这两个实体数据提取出来,进行特殊词性标注,这样再分词环节就很容易提取出常用热点词。

        我们对这两个文件进行词性标注,打上对应的词性,有关词性的解释,可以看jieba的链接:

jieba库的GitHub地址

        这里分别使用到人名词性nr,专用词性nz。

处理actor.txt和movie.txt文件,代码文件process_word_dict.py:

  1. from pprint import pprint
  2. def append_words_in_lines(file_name,append_str):
  3. n_dict = []
  4. with open(file_name+'.txt', mode='r+' ,encoding='utf-8') as f:
  5. res = f.readlines()
  6. # pprint(res)
  7. for word in res:
  8. n_dict.append(word[:-1]+append_str)
  9. with open(file_name+"_dict.txt", mode='w+', encoding='utf-8') as f:
  10. for word in n_dict:
  11. f.write(word+'\n')
  12. # print(n_dict)
  13. if __name__ == '__main__':
  14. append_words_in_lines('actor', ' nr')
  15. append_words_in_lines('movie', ' nz')

运行一下即可,完成后生成actor_dict.txt和movie_dict.txt文件,内容如下图:

3.2.2、分词逻辑的实现

        分词我们调用了国内非常有名的开源库,jieba库,这里对齐进行了简单封装,根据我们的需求,做以下步骤:

        A.常用词字典的加载,加载actor_dict.txt和movie_dict.txt;

        B.将一些常见连续词按照需求给强制切分开,例如,“喜剧演员”会有很大几率是一个词,我们电影图谱,“喜剧”和“演员”分别都是对应关键实体的属性和实体类型,因此需要强制切分开,当然,有一些情况可以不切分,这个需要用业务逻辑来判断。

        C.分词和词性标注,不但将词且分开,也要把词性记录下来。

整体代码如下文件nlp.py:

  1. # encoding=utf-8
  2. import jieba
  3. import jieba.posseg as pseg
  4. from pprint import pprint
  5. class TextureProcess:
  6. def __init__(self):
  7. self.jieba = jieba
  8. self.pseg = pseg
  9. # 加载词典
  10. def load_dicts(self, *path):
  11. for p in path:
  12. self.jieba.load_userdict(p)
  13. # 强制分词
  14. def force_split(self,*group_list):
  15. for group in group_list:
  16. self.jieba.suggest_freq((group[0], group[1]), True)
  17. def sentence_split_tag(self,sentence):
  18. split_list = []
  19. cut_sen = self.pseg.cut(sentence)
  20. for word, root in cut_sen:
  21. split_list.append({"word":word, "root":root})
  22. return split_list
  23. if __name__ == '__main__':
  24. text_process = TextureProcess()
  25. text_process.load_dicts("actor_dict.txt",
  26. "movie_dict.txt")
  27. text_process.force_split(['喜剧', '演员'],
  28. ['出生', '日期'])
  29. text_res = text_process.sentence_split_tag("周星驰的个人简介")

        这里扩展提及一下,我们为什么要设计新词发现模块,用户一个问题,会根据习惯和地域文化,产生很多相近词,因此,新词发现也是为了让我们的自然处理以及相似度计算更准确,同时互联网造词能力太强大,如果需要我们的KBQA系统赶上热点,那么一个合格的新词发现系统是更新KBQA的必要条件,在?懂?

3.3、查询实现

        查询的时候,参考了网上很多KBQA的实现,基本上最常见的是两种,一种是基于模板规则,使用正则表达式的精准匹配方式,另外一种是基于ML或DL训练处理的有监督模型,结合我们的图谱大小和数据现状,显然使用ML或DL训练不是很好,而且还需要对数据进行标注处理,这并不符合短时间内实现一个KBQA系统的初衷,因此考虑使用模板匹配,但仔细观看,模板匹配使用了正则表达,每种问题不同问法和相近词都必须考虑到,这并不符合我的想法,因此想要使用模糊匹配解决问题。

        因此核心的考虑方案是,定义问题模板,有且仅对一种问题类型做一个示例,无论用户换几种问法,将其问句和示例进行相似度计算,相似度最高的认定为用户想要问的问题方向,如果相似度极低,那么可以简单认为要么是问题刁钻,要么是没考虑到这个问题需要再补充问题示例。

        这里提及一下,无论使用传统方法,还是基于深度学习的方法,对用户问题的判断必定是基于某种规则的,例如,如果你没有定义过天上的白云有什么形状?这种问题,自然就没有对应的答案,无论分词还是意图提取,断句以及词性做的多准确,都没有用,因此只有你定义过天上的白云的答案,当用户问题是:万米深空的棉花糖都是啥样,机器才有可能回答“心型、圆形等”,至于这种刁钻问题,如何匹配,例如,万米深空对应天上,棉花糖对应白云,这种问题传统算法匹配成功率较低,较好的办法是交给基于大数据训练的深度网络,才有可能提高匹配正确率。

3.3.1、单实体查询

首先对单实体做一个类的定义:

  1. class SingleEntityRule:
  2. def __init__(self, entity_root, segment, table_name):
  3. self.entity_root = entity_root
  4. self.segment = segment
  5. self.table_name = table_name

        这段代码在temp_match.py中,属于部分片段。 

        其中entity_root是实体的词性,segment是用户的问题,table_name是我们查询对应的数据库表名。

        接下来考虑问题模板规则的定义: 

        单实体匹配主要考虑到简单问答的匹配,如:周星驰的个人简介,刘德华的出生日期等,唐伯虎点秋香的演员有哪些?等等。

        考虑到单实体还会有一些扩展补充问法,如:周星驰演过的电影的类型有哪些?周星驰合作过的导演有哪些?因此也做了实现,区别是需要查询的数据表是连续的。

具体定义如下,将每种问题定义一种问法,然后放到一个list里:

  1. SingleEntityRules = [
  2. SingleEntityRule(entity_root='nr', segment="的个人介绍?", table_name='bio'),
  3. SingleEntityRule(entity_root='nr', segment="的中文名叫什么?", table_name='chName'),
  4. SingleEntityRule(entity_root='nr', segment="的英文名叫什么?", table_name='foreName'),
  5. SingleEntityRule(entity_root='nr', segment="的国籍是什么?", table_name='nationality'),
  6. SingleEntityRule(entity_root='nr', segment="的星座是什么?", table_name='constellation'),
  7. SingleEntityRule(entity_root='nr', segment="的出生地点在哪里?", table_name='birthplace'),
  8. SingleEntityRule(entity_root='nr', segment="的生日是哪天?", table_name='birthday'),
  9. SingleEntityRule(entity_root='nr', segment="演过哪些电影?", table_name='repWorks'),
  10. SingleEntityRule(entity_root='nr', segment="获得了哪些成就?", table_name='achiem'),
  11. SingleEntityRule(entity_root='nr', segment="的经济公司?", table_name='brokerage'),
  12. SingleEntityRule(entity_root='nz', segment="的电影简介?", table_name='bio'),
  13. SingleEntityRule(entity_root='nz', segment="的中文名叫什么?", table_name='chName'),
  14. SingleEntityRule(entity_root='nz', segment="的英文名叫什么?", table_name='foreName'),
  15. SingleEntityRule(entity_root='nz', segment="的上映时间是什么时候?", table_name='prodTime'),
  16. SingleEntityRule(entity_root='nz', segment="的制片公司是哪家?", table_name='prodCompany'),
  17. SingleEntityRule(entity_root='nz', segment="的导演是谁?", table_name='director'),
  18. SingleEntityRule(entity_root='nz', segment="的编剧是谁?", table_name='screenwriter'),
  19. SingleEntityRule(entity_root='nz', segment="属于什么类型的电影?", table_name='genre'),
  20. SingleEntityRule(entity_root='nz', segment="的参演演员有哪些?", table_name='star'),
  21. SingleEntityRule(entity_root='nz', segment="的电影时长是多少?", table_name='length'),
  22. SingleEntityRule(entity_root='nz', segment="的上映时间是哪天?", table_name='releaseTime'),
  23. SingleEntityRule(entity_root='nz', segment="的语言类型是哪种?", table_name='language'),
  24. SingleEntityRule(entity_root='nz', segment="获得了哪些成就?", table_name='achiem'),
  25. # 实体 相关 实体(无定向) 的属性
  26. SingleEntityRule(entity_root='nr', segment="演过的电影的类型是哪些?", table_name='chName&genre'),
  27. SingleEntityRule(entity_root='nr', segment="合作过的导演有哪些?", table_name='chName&director')
  28. ]

        这段代码在temp_match.py中,属于部分片段。  

        规则定义完后,需要对规则进行查询匹配,大体的思路是,我们对用户的问题进行处理,然后遍历计算用户的问题,判断问题与问题模板里哪一个相似度最高,得分最高的则认为是用户想要了解的问题,这里我们使用了macropodus库做相似度计算,默认是使用jaccard相似度系数,非常方便。

        但获取到用户正确的问题后,再对问题进行查询模板对照生成,由于我们生产的数据实体只有两种,一个是电影,一个是明星,因此,单实体只需要考虑这两种情况nr和nz,这样可被用于fuseki查询的query语句就完成了,具体代码如下:

  1. class BaseQueryTemp:
  2. def __init__(self):
  3. self.query_head = r'''
  4. PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
  5. PREFIX : <http://www.kg_movie.com#>
  6. SELECT * WHERE {'''
  7. self.query_end = r'''} LIMIT 10'''
  8. class QuerySETemp(BaseQueryTemp):
  9. def __init__(self):
  10. super().__init__()
  11. @staticmethod
  12. def _calc(sentence):
  13. max_score = 0
  14. table_name = ""
  15. fin_entity_root = ""
  16. for SingleEntityRule in SingleEntityRules:
  17. score = macropodus.sim(sentence, SingleEntityRule.entity_root + SingleEntityRule.segment)
  18. if score > max_score:
  19. max_score = score
  20. fin_entity_root = SingleEntityRule.entity_root
  21. table_name = SingleEntityRule.table_name
  22. return fin_entity_root, table_name
  23. def get_query_temp(self, entity_word_list, sentence):
  24. entity_root, table_name = self._calc(sentence)
  25. core_query = ""
  26. if entity_root == "nz":
  27. core_query = r'''
  28. ?x :movie_chName '{0}'.
  29. ?x :movie_{1} ?res_o.
  30. '''.format(entity_word_list[0]['nz'], table_name)
  31. elif entity_root == "nr":
  32. if "&" in table_name :
  33. table_list = table_name.split("&")
  34. from pprint import pprint
  35. pprint(table_list)
  36. core_query = r'''
  37. ?x :actor_chName '{0}'.
  38. ?x :hasActedIn ?o.
  39. ?o :movie_{1} ?res_0.
  40. ?o :movie_{2} ?res_1.
  41. '''.format(entity_word_list[0]['nr'], table_list[0], table_list[1])
  42. else:
  43. core_query = r'''
  44. ?x :actor_chName '{0}'.
  45. ?x :actor_{1} ?res_o.
  46. '''.format(entity_word_list[0]['nr'], table_name)
  47. return self.query_head + core_query + self.query_end

        这段代码在temp_match.py中,属于部分片段。  

        其中 BaseQueryTemp是我们定义了一个基类,直接被继承即可。

        QuerySETemp是我们的单实体模板类,其中_calc是计算相似度,get_query_temp是模板生产。

3.3.2、多实体查询

        主要考虑的是联合查询,即实体和实体一起才能满足的条件,例如人和人的条件组合,电影和电影的条件组合,人和电影的条件组合,以及自由搭配实体数量的条件组合。

首先对多实体进行定义:

  1. class MultiEntityRule:
  2. # type same 表示entity_root 相同,如 [nr nr]
  3. # type diff 表示entity_root 不相同,如 [nr nz]
  4. def __init__(self, entity_root_list, segment, link_name, table_name, belong, type='same', ):
  5. self.type = type
  6. self.entity_root_list = entity_root_list
  7. self.segment = segment
  8. self.link_name = link_name
  9. self.table_name = table_name
  10. self.belong = belong

         在之前的单实体的基础上增加了link_name和belong和type,link_name考虑的是中间查询的节点,belong判断属于那哪种类型,例如是电影还是人物方向,type主要考虑是实体和实体之间的类别是否相同,需要触发不同的调用模板逻辑。

 由于多实体我只想到了两种,因此,就定义了两个规则,如下:

  1. # diff类型由于暂时没想到
  2. MultiEntityRules = [
  3. MultiEntityRule(type='same', entity_root_list=["nr"], segment="一起演过什么电影?", link_name=["hasActedIn"], table_name="chName", belong='movie'),
  4. MultiEntityRule(type='same', entity_root_list=["nz"], segment="同时参演的演员是谁?", link_name=["hasActor"], table_name="chName", belong='actor'),
  5. ]

        这里就看出来为什么我们会设计一个新问题发现模块了,因为我们永远想不全用户会有哪些问题和哪些花式提问方法。

然后计算相似度,生成模板 :

  1. class QueryMETemp(BaseQueryTemp):
  2. def __init__(self):
  3. super().__init__()
  4. @staticmethod
  5. def _calc(type , sentence):
  6. max_score = 0
  7. # list_entity_root = []
  8. list_link_name = []
  9. table_name = ""
  10. belong = ""
  11. for MultiEntityRule in MultiEntityRules:
  12. if type == 'same':
  13. score = macropodus.sim(sentence, MultiEntityRule.entity_root_list[0] + MultiEntityRule.segment)
  14. else: # diff
  15. score = macropodus.sim(sentence, MultiEntityRule.entity_root_list[0] + MultiEntityRule.entity_root_list[1] + MultiEntityRule.segment)
  16. if score > max_score:
  17. max_score = score
  18. # list_entity_root = MultiEntityRule.entity_root_list
  19. list_link_name = MultiEntityRule.link_name
  20. table_name = MultiEntityRule.table_name
  21. belong = MultiEntityRule.belong
  22. return list_link_name, table_name, belong
  23. def get_query_temp(self, entity_word_list, sentence):
  24. type = ''
  25. if all('nr' == root for root_word in entity_word_list for root in root_word) or all('nz' == root for root_word in entity_word_list for root in root_word): # same
  26. type = 'same'
  27. else:
  28. type = 'diff'
  29. entity_num = len(entity_word_list)
  30. list_link_name, table_name, belong = self._calc(type, sentence)
  31. # 生成模板
  32. entity_query_list = []
  33. mid_query_list = []
  34. for index in range(entity_num):
  35. root_word = entity_word_list[index]
  36. for root in root_word:
  37. if root == 'nr':
  38. entity_query_list.append(r'''
  39. ?a{0} :actor_chName '{1}'.'''.format(index, root_word[root]))
  40. mid_query_list.append(r'''
  41. ?a{0} :hasActedIn ?last.'''.format(index))
  42. elif root == 'nz':
  43. entity_query_list.append(r'''
  44. ?b{0} :movie_chName '{1}'.'''.format(index, root_word[root]))
  45. mid_query_list.append(r'''
  46. ?b{0} :hasActor ?last.'''.format(index))
  47. else:
  48. print("传参类型错误 ... ")
  49. end_query = r'''
  50. ?last :{0}_{1} ?res_0.'''.format(belong,table_name)
  51. core_query = ''
  52. for entity_query in entity_query_list:
  53. core_query = core_query + entity_query
  54. for mid_query in mid_query_list:
  55. core_query = core_query + mid_query
  56. core_query = core_query + end_query
  57. return self.query_head + core_query + self.query_end
  58. if __name__ == '__main__':
  59. pass

        生成模板考虑到有的时候用户可以问超过2个实体单位,因此使用了循环结构来生成,这样可以对应2个以上的实体查询模板额生成。

 这里附上单实体和多实体查询模板的完整代码temp_match.py:

  1. import macropodus
  2. class BaseQueryTemp:
  3. def __init__(self):
  4. self.query_head = r'''
  5. PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
  6. PREFIX : <http://www.kg_movie.com#>
  7. SELECT * WHERE {'''
  8. self.query_end = r'''} LIMIT 10'''
  9. # Rule
  10. # [实体]有什么[实体]?
  11. # [实体]的[属性]?
  12. # [实体]和[实体]的[关系]?
  13. # [实体]的[条件属性]?
  14. #
  15. class WithoutEntityRule:
  16. def __init__(self):
  17. pass
  18. WithoutEntityRules = [
  19. # WithoutEntityRule(key_word= ,)
  20. ]
  21. class MultiEntityRule:
  22. # type same 表示entity_root 相同,如 [nr nr]
  23. # type diff 表示entity_root 不相同,如 [nr nz]
  24. def __init__(self, entity_root_list, segment, link_name, table_name, belong, type='same', ):
  25. self.type = type
  26. self.entity_root_list = entity_root_list
  27. self.segment = segment
  28. self.link_name = link_name
  29. self.table_name = table_name
  30. self.belong = belong
  31. # diff类型由于暂时没想到
  32. MultiEntityRules = [
  33. MultiEntityRule(type='same', entity_root_list=["nr"], segment="一起演过什么电影?", link_name=["hasActedIn"], table_name="chName", belong='movie'),
  34. MultiEntityRule(type='same', entity_root_list=["nz"], segment="同时参演的演员是谁?", link_name=["hasActor"], table_name="chName", belong='actor'),
  35. ]
  36. class QueryMETemp(BaseQueryTemp):
  37. def __init__(self):
  38. super().__init__()
  39. @staticmethod
  40. def _calc(type , sentence):
  41. max_score = 0
  42. # list_entity_root = []
  43. list_link_name = []
  44. table_name = ""
  45. belong = ""
  46. for MultiEntityRule in MultiEntityRules:
  47. if type == 'same':
  48. score = macropodus.sim(sentence, MultiEntityRule.entity_root_list[0] + MultiEntityRule.segment)
  49. else: # diff
  50. score = macropodus.sim(sentence, MultiEntityRule.entity_root_list[0] + MultiEntityRule.entity_root_list[1] + MultiEntityRule.segment)
  51. if score > max_score:
  52. max_score = score
  53. # list_entity_root = MultiEntityRule.entity_root_list
  54. list_link_name = MultiEntityRule.link_name
  55. table_name = MultiEntityRule.table_name
  56. belong = MultiEntityRule.belong
  57. return list_link_name, table_name, belong
  58. def get_query_temp(self, entity_word_list, sentence):
  59. type = ''
  60. if all('nr' == root for root_word in entity_word_list for root in root_word) or all('nz' == root for root_word in entity_word_list for root in root_word): # same
  61. type = 'same'
  62. else:
  63. type = 'diff'
  64. entity_num = len(entity_word_list)
  65. list_link_name, table_name, belong = self._calc(type, sentence)
  66. # 生成模板
  67. entity_query_list = []
  68. mid_query_list = []
  69. for index in range(entity_num):
  70. root_word = entity_word_list[index]
  71. for root in root_word:
  72. if root == 'nr':
  73. entity_query_list.append(r'''
  74. ?a{0} :actor_chName '{1}'.'''.format(index, root_word[root]))
  75. mid_query_list.append(r'''
  76. ?a{0} :hasActedIn ?last.'''.format(index))
  77. elif root == 'nz':
  78. entity_query_list.append(r'''
  79. ?b{0} :movie_chName '{1}'.'''.format(index, root_word[root]))
  80. mid_query_list.append(r'''
  81. ?b{0} :hasActor ?last.'''.format(index))
  82. else:
  83. print("传参类型错误 ... ")
  84. end_query = r'''
  85. ?last :{0}_{1} ?res_0.'''.format(belong,table_name)
  86. core_query = ''
  87. for entity_query in entity_query_list:
  88. core_query = core_query + entity_query
  89. for mid_query in mid_query_list:
  90. core_query = core_query + mid_query
  91. core_query = core_query + end_query
  92. return self.query_head + core_query + self.query_end
  93. class SingleEntityRule:
  94. def __init__(self, entity_root, segment, table_name):
  95. self.entity_root = entity_root
  96. self.segment = segment
  97. self.table_name = table_name
  98. class QuerySETemp(BaseQueryTemp):
  99. def __init__(self):
  100. super().__init__()
  101. @staticmethod
  102. def _calc(sentence):
  103. max_score = 0
  104. table_name = ""
  105. fin_entity_root = ""
  106. for SingleEntityRule in SingleEntityRules:
  107. score = macropodus.sim(sentence, SingleEntityRule.entity_root + SingleEntityRule.segment)
  108. if score > max_score:
  109. max_score = score
  110. fin_entity_root = SingleEntityRule.entity_root
  111. table_name = SingleEntityRule.table_name
  112. return fin_entity_root, table_name
  113. def get_query_temp(self, entity_word_list, sentence):
  114. entity_root, table_name = self._calc(sentence)
  115. core_query = ""
  116. if entity_root == "nz":
  117. core_query = r'''
  118. ?x :movie_chName '{0}'.
  119. ?x :movie_{1} ?res_o.
  120. '''.format(entity_word_list[0]['nz'], table_name)
  121. elif entity_root == "nr":
  122. if "&" in table_name :
  123. table_list = table_name.split("&")
  124. from pprint import pprint
  125. pprint(table_list)
  126. core_query = r'''
  127. ?x :actor_chName '{0}'.
  128. ?x :hasActedIn ?o.
  129. ?o :movie_{1} ?res_0.
  130. ?o :movie_{2} ?res_1.
  131. '''.format(entity_word_list[0]['nr'], table_list[0], table_list[1])
  132. else:
  133. core_query = r'''
  134. ?x :actor_chName '{0}'.
  135. ?x :actor_{1} ?res_o.
  136. '''.format(entity_word_list[0]['nr'], table_name)
  137. return self.query_head + core_query + self.query_end
  138. SingleEntityRules = [
  139. SingleEntityRule(entity_root='nr', segment="的个人介绍?", table_name='bio'),
  140. SingleEntityRule(entity_root='nr', segment="的中文名叫什么?", table_name='chName'),
  141. SingleEntityRule(entity_root='nr', segment="的英文名叫什么?", table_name='foreName'),
  142. SingleEntityRule(entity_root='nr', segment="的国籍是什么?", table_name='nationality'),
  143. SingleEntityRule(entity_root='nr', segment="的星座是什么?", table_name='constellation'),
  144. SingleEntityRule(entity_root='nr', segment="的出生地点在哪里?", table_name='birthplace'),
  145. SingleEntityRule(entity_root='nr', segment="的生日是哪天?", table_name='birthday'),
  146. SingleEntityRule(entity_root='nr', segment="演过哪些电影?", table_name='repWorks'),
  147. SingleEntityRule(entity_root='nr', segment="获得了哪些成就?", table_name='achiem'),
  148. SingleEntityRule(entity_root='nr', segment="的经济公司?", table_name='brokerage'),
  149. SingleEntityRule(entity_root='nz', segment="的电影简介?", table_name='bio'),
  150. SingleEntityRule(entity_root='nz', segment="的中文名叫什么?", table_name='chName'),
  151. SingleEntityRule(entity_root='nz', segment="的英文名叫什么?", table_name='foreName'),
  152. SingleEntityRule(entity_root='nz', segment="的上映时间是什么时候?", table_name='prodTime'),
  153. SingleEntityRule(entity_root='nz', segment="的制片公司是哪家?", table_name='prodCompany'),
  154. SingleEntityRule(entity_root='nz', segment="的导演是谁?", table_name='director'),
  155. SingleEntityRule(entity_root='nz', segment="的编剧是谁?", table_name='screenwriter'),
  156. SingleEntityRule(entity_root='nz', segment="属于什么类型的电影?", table_name='genre'),
  157. SingleEntityRule(entity_root='nz', segment="的参演演员有哪些?", table_name='star'),
  158. SingleEntityRule(entity_root='nz', segment="的电影时长是多少?", table_name='length'),
  159. SingleEntityRule(entity_root='nz', segment="的上映时间是哪天?", table_name='releaseTime'),
  160. SingleEntityRule(entity_root='nz', segment="的语言类型是哪种?", table_name='language'),
  161. SingleEntityRule(entity_root='nz', segment="获得了哪些成就?", table_name='achiem'),
  162. # 实体 相关 实体(无定向) 的属性
  163. SingleEntityRule(entity_root='nr', segment="演过的电影的类型是哪些?", table_name='chName&genre'),
  164. SingleEntityRule(entity_root='nr', segment="合作过的导演有哪些?", table_name='chName&director')
  165. ]
  166. if __name__ == '__main__':
  167. pass

4、业务逻辑的整合实现

        调用封装的三个类,业务逻辑的简单实现,不需要额外的赘述了,直接放完整代码。

main_logic.py:

  1. # encoding=utf-8
  2. import jieba
  3. import jieba.posseg as pseg
  4. from pprint import pprint
  5. from KBQA.fuseki_query import *
  6. from KBQA.temp_match import *
  7. from KBQA.nlp import *
  8. if __name__ == '__main__':
  9. # cut_sentences("a")
  10. # cut_tag('a')
  11. # mat()
  12. fuseki = Fuseki()
  13. query_se_temp = QuerySETemp()
  14. query_me_temp = QueryMETemp()
  15. text_process = TextureProcess()
  16. text_process.load_dicts("actor_dict.txt",
  17. "movie_dict.txt")
  18. text_process.force_split(['喜剧', '演员'],
  19. ['出生', '日期'])
  20. # 周星驰的个人简介
  21. print("输入要查询的问题:")
  22. while True:
  23. in_str = input()
  24. if in_str == "exit":
  25. break
  26. else:
  27. text_res = text_process.sentence_split_tag(in_str)
  28. segment = ""
  29. entity_word_list = []
  30. query_temp = ""
  31. for Word in text_res:
  32. if Word["root"] == "nr" or Word["root"] == "nz":
  33. segment = segment + Word["root"]
  34. entity_word_list.append({Word['root']: Word['word']})
  35. else:
  36. segment = segment + Word["word"]
  37. # print(entity_word_list)
  38. if len(entity_word_list) == 0:
  39. print("无实体或虚拟实体,》》》转人工域分析 ... ...")
  40. continue
  41. elif len(entity_word_list) == 1:
  42. # print("单实体")
  43. query_temp = query_se_temp.get_query_temp(entity_word_list, segment)
  44. # print(query_temp)
  45. elif len(entity_word_list) >= 2:
  46. # print("多实体")
  47. query_temp = query_me_temp.get_query_temp(entity_word_list, segment)
  48. # print(query_temp)
  49. # pprint(res)
  50. query_result = fuseki.query(query_temp)
  51. # pprint(query_result)
  52. parse_dict = parse(query_result)
  53. for result_info in parse_dict:
  54. if len(parse_dict[result_info]) > 0:
  55. print(parse_dict[result_info])
  56. # print(type(query_result))
  57. print("进程结束 ................. ")

5、一些补充

        本身现在也没有很大的兴趣沉浸在开发里,因此实现思路有了后也只是简单的实现一些基本框架,也没有大的兴趣全部验证一遍,至少个人项目上没那么大精力,但作为产品和架构思想,我想深挖的细节还是有很多,考虑到这段时间对KBQA做了功课,因此整理一下一些信息,对KBQA做了一下几种分类:

方法子方法人力投入资源投入其他说明
模板匹配精准匹配正则表达实现
模板匹配模糊匹配

jaccard、余弦等相似度计算

本次使用的方法更接近模糊匹配

模板匹配关键词匹配正则、也可以计算相似度
空间计算向量化问题计算相似,适合Web
语义解析利用词性进行计算
实体搜索提取实体及关键词,匹配节点

以上方法除了精准匹配外,都可以通过深度学习做大规模训练,仅此。

6、参考

1、相似度计算库macropodus

2、Jaccard系数

3、结巴分词库

4、KBQA实现

5、农业KBQA

6、中文开发知识图谱-REfO实现KBQA

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/AllinToyou/article/detail/343893
推荐阅读
相关标签
  

闽ICP备14008679号