鸣谢:该项目基于刘焕勇老师、IrvingBei这两位的代码启发下,才有了我这么一个辣鸡项目。期间我的学业导师,给了我很多指导帮助。站在前人的肩膀上,我们可以看得更远
前文我们已经构建了知识图谱,我们知道知识图谱的一个重要应用就是智能问答,接下来我们要基于中医药知识图谱完成智能问答系统的搭建
基于知识图谱的智能问答就是依托一个大型知识库(知识图谱、结构化数据库等),将用户的自然语言问题转化成结构化查询语句,直接从知识库中导出用户所需的答案。对于智能问答的关键在于处理用户提出的自然语言,在学术界常用研究方法可分为三大类:基于语义解析、基于信息检索、基于概率模型。项目伊始采用基于条件随机概率模型,但是经过测试发现其效率低下,严重影响用户体验。依据刘焕勇老师的模板匹配设计了中医药问答的模板,模板匹配特别适合用于小规模的垂直领域的知识图谱问答系统。
1. 基于AC自动机多模匹配算法处理自然语言问句
在自然语言处理过程中,最为关键的步骤就是实体提取和意图分类。中文相比于英文不太容易去区分词与词的界限,没有了空格的分割,加大了自然语言处理对语句分割、语义理解上的困难。在多模式匹配算法中,Aho-Corasick 算法(Aho-Corasick automaton algorithm)是最经典的算法,通过将模式串构成Trie树,将主串匹配的过程变为在 Trie 树上转移的过程,其时间复杂度是 O(N+N×M),自动机状态转移规则放在Trie树上进行搜索匹配,能对目标串进行高效式搜索增大了主串和模式串匹配的可能性,可以有效地提高处理自然问句的效率,因此本研究决定基于AC自动机算法处理自然语言问句。利用知识图谱中存在的实体名字构成特征库(语料库)。建立AC树(Aho-Corasick Tree),利用AC自动机算法,匹配问句中是否存在特征词,即查询问句中是否存在知识图谱实体名字,来实现实体提取。AC自动机用于在输入的一串字符串中匹配有限组“字典”中的子串。它与普通字符串匹配的不同点在于同时与所有字典串进行匹配。算法均摊情况下具有近似于线性的时间复杂度,约为字符串的长度加所有匹配的数量。
2. 模板匹配分析自然语言问句
分析自然语言问句是整个智能问答的核心,其实质上是一个分类问题。为了满足对自然语言问答速度的需要,本研究采用模板匹配分析自然语言问句。首先基于规则的方法,依据经验构建数种疑问句关键词字典,再根据这些关键字是否存在于问句来确定问句的类型。比如“腊雪的药性是什么”可以通过模板提取出(药性)特征词。由AC自动机,得到实体类型。根据二者组合成知识子图,并在知识图谱中查询对应的结果。由于医学领域较为严肃,要确保数据的准确性,本研究人工构建了中医药中文问句集(如表3所示)来支持智能问答的实现。
表3 中医药领域问句部分样本集
序号 | 模板问题 | 特征词 |
---|---|---|
1 | n属于在本草纲目中什么部类 | 属于、部类、什么部… |
2 | n的药性是什么 | 药性、气味、有无毒… |
3 | n的别称是什么 | 别称、别名、其他称号… |
4 | n有那些基础药方 | 基础药方、如何食用… |
5 | m该去那个科室看病 | 科室、看病、去哪里看… |
6 | m属于什么类别病症 | 类别、病症… |
7 | m的经验药方是什么 | 经验药方、如何治疗… |
import os
import ahocorasick #是一种多匹配的模块,在这里用于匹配问句里面的特征词
class QuestionClassifier:
def __init__(self):
#加载特征词路径
self.part_path=r'E:\前端\中医药\data\part.txt'
self.name_path=r'E:\前端\中医药\data\name.txt'
self.alias_path=r'E:\前端\中医药\data\alias.txt'
self.smell_path=r'E:\前端\中医药\data\smell.txt'
self.cure_path=r'E:\前端\中医药\data\cure.txt'
self.drug_path=r'E:\前端\中医药\data\drug.txt'
self.department_path=r'E:\前端\中医药\data\Department.txt'
self.prescript_path=r'E:\前端\中医药\data\prescript.txt'
self.onepart_path=r'E:\前端\中医药\data\onepart.txt'
#加载特征词
self.part_wds=[i.strip() for i in open(self.part_path,encoding="utf-8") if i.strip()]
self.name_wds = [i.strip() for i in open(self.name_path, encoding="utf-8") if i.strip()]
self.alias_wds = [i.strip() for i in open(self.alias_path, encoding="utf-8") if i.strip()]
self.smell_wds = [i.strip() for i in open(self.smell_path, encoding="utf-8") if i.strip()]
self.cure_wds = [i.strip() for i in open(self.cure_path, encoding="utf-8") if i.strip()]
self.drug_wds = [i.strip() for i in open(self.drug_path, encoding="utf-8") if i.strip()]
self.department_wds = [i.strip() for i in open(self.department_path, encoding="utf-8") if i.strip()]
self.prescript_wds = [i.strip() for i in open(self.prescript_path, encoding="utf-8") if i.strip()]
self.onepart_wds = [i.strip() for i in open(self.onepart_path, encoding="utf-8") if i.strip()]
# 创建了包含9类实体特征词的元素集
self.region_words = set(self.part_wds + self.name_wds + self.alias_wds+ self.smell_wds+ self.cure_wds+self.department_wds+self.onepart_wds+self.prescript_wds+self.drug_wds)
# 构造领域actree
self.region_tree = self.build_actree(list(self.region_words))
# 构建词典
self.wdtype_dict = self.build_wdtype_dict()
# 问句疑问词,对于不同的问题,赋予特征词
self.belong_qwds = ['属于什么部类', '什么部', '部类', '哪个部'] # 询问部类
self.smell_qwds = ['什么气味','什么味道','闻起来怎么样','什么味的','吃起来苦嘛','吃了是什么味','什么品质','有毒嘛','有毒性嘛']
self.alias_qwds = ['有其他别名嘛','别名是什么','还有其他称呼','别称','别名','还可以怎么叫','其他名字','那些别称']
self.cure_qwds = ['疗法', '咋治', '咋吃', '如何食用','食疗方法','可以治什么','治那些病','可以治什么症状','有那些食用方法','如何食用','最佳食用方法','有什么好处', '有什么益处', '有何益处', '用来', '用来做啥', '用来作甚', '需要','治愈啥', '主治啥', '主治什么', '有什么用', '有何用']
self.prescript_qwds = ['该怎么治疗','如何治疗','有什么秘方','有什么偏方','咋治','医治方式']
self.department_qwds = ['该去什么科室','去那个科室看病','应当去那个科室治疗','科室']
self.onepart_qwds = ['从属什么类秘方','隶属什么秘方']
print('问答系统启动中......')
#print(self.wdtype_dict)
return
def build_wdtype_dict(self):
# 该函数是检查问句中涉及的5类实体,并返回一个列表
wd_dict = dict()
for wd in self.region_words:
wd_dict[wd] = []
if wd in self.part_wds:
wd_dict[wd].append('part')
if wd in self.name_wds:
wd_dict[wd].append('name')
if wd in self.alias_wds:
wd_dict[wd].append('alias')
if wd in self.smell_wds:
wd_dict[wd].append('smell')
if wd in self.cure_wds:
wd_dict[wd].append('cure')
if wd in self.prescript_wds:
wd_dict[wd].append('prescript')
if wd in self.department_wds:
wd_dict[wd].append('department')
if wd in self.onepart_wds:
wd_dict[wd].append('onepart')
if wd in self.drug_wds:
wd_dict[wd].append('drug')
return wd_dict
'''基于特征词进行分类'''
def check_words(self, wds, sent):
for wd in wds:
if wd in sent:
return True
return False
#检查是否有实体类型的特征词
'''构造actree,加速过滤'''
def build_actree(self, wordlist):
# 往actree中添加数据,这是已经封装好的模块
actree = ahocorasick.Automaton()
for index, word in enumerate(wordlist):
actree.add_word(word, (index, word))
actree.make_automaton()
return actree
'''构造词对应的类型'''
def check_medical(self, question):
#该模块是通过匹配找到问句中存在的5类实体
region_wds = []
#iter()是迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退
for i in self.region_tree.iter(question):#对问句进行多匹配模式的迭代
wd = i[1][1]
region_wds.append(wd)
stop_wds = []
for wd1 in region_wds:
for wd2 in region_wds:
if wd1 in wd2 and wd1 != wd2:
stop_wds.append(wd1)
final_wds = [i for i in region_wds if i not in stop_wds]
final_dict = {i : self.wdtype_dict.get(i) for i in final_wds}
return final_dict
'''分类主函数'''
def classify(self, question):
data = {}
medical_dict = self.check_medical(question) # 问句过滤
if not medical_dict:
return {}
data['args'] = medical_dict
# 收集问句当中所涉及到的实体类型
types = []
for type_ in medical_dict.values():
types += type_
question_type = 'others'
question_types = []
# 这是对遍历选择的方式,依次判断问句中是否存在实体,以及问句中实体类型是否在types中
# 属于什么部类
if self.check_words(self.belong_qwds, question) :
question_type = 'name_part'
question_types.append(question_type)
#print(question_types)
# #别名
if self.check_words(self.alias_qwds,question) :
question_type = 'name_alias'
question_types.append(question_type)
#气味品质
if self.check_words(self.smell_qwds,question) :
question_type = 'name_smell'
question_types.append(question_type)
#主治方法
if self.check_words(self.cure_qwds,question) :
question_type = 'name_cure'
question_types.append(question_type)
#科室
if self.check_words(self.department_qwds,question):
question_type = 'drug_department'
question_types.append(question_type)
#药方
if self.check_words(self.prescript_qwds,question):
question_type = 'drug_prescript'
question_types.append(question_type)
#疾病属于什么秘方类
if self.check_words(self.onepart_qwds,question):
question_type = 'drug_onepart'
question_types.append(question_type)
# 若没有查到相关的外部查询信息,那么则将该疾病的描述信息返回
if question_types == [] :
question_types = ['找不到']
# 将多个分类结果进行合并处理,组装成一个字典
data['question_types'] = question_types
return data
if __name__ == '__main__':
handler = QuestionClassifier()
while 1:
question = input('输入您的问题:')
data = handler.classify(question)
print(data)
#{'args': {'腊雪': ['name']}, 'question_types': ['name_part']}
该项目已经上传到我的github,还是老样子,别忘了给我点star,点star,点star(重要的事说三边)
另:上传的是1.0版本,今年我又修改了web框架,由web.py框架修改为flask框架,并且已经部署到我的个人网站上,后期会出一期如何部署pythonweb在服务器的教程,敬请期待;我和我那优秀的学业导师在这此项目基础上进行了深度研究,研究成果已成功被CCC2020录用,等EI收录后我会把论文上传,供大家学习