Task 4用户输入->摘要的查询语句
文章编写人:王翔
github地址:
特别鸣谢:QASystemOnMedicalGraph
目录
一,引言
本部分任务主要是将用户输入问答系统的自然语言转换成合并的查询语句,因此本文将分为两部分进行介绍。
- 第一部分介绍任务所涉及的背景知识;
- 第二部分则是相应的代码和其注释
二,什么是问答系统?
2.1问答系统简介
问答系统(Question Answering System,QA System)是用来回答人提出的自然语言问题的系统。根据划分标准不同,问答系统可以被分为各种不同的类型。
-
问答系统从知识领域划分:
- 封闭领域:封闭领域系统专注于回答特定领域的问题,由于问题领域改变,系统有比较大的发挥空间,可以引入领域知识或将答案全部转换成结构性资料来有效提升系统的表现;
- 开放领域:开放领域系统则希望不设限问题的内容范围,因此其困难也相对阻碍。
-
问答系统从实现方式划分:
- 基于流水线(pipeline)实现:如下图1所示,基于流水线实现的问答系统有四大核心模块,分别由自然语言理解(NLU),对话状态跟踪器(DST),对话策略(DPL)和自然语言生成(NLG)顺序层叠构成的一条流水线,各模块可独立设计,模块间协作完成任务。
- 基于端到端(端到端)实现:基于端到端实现的问答系统,主要是结合深度学习技术,通过海量数据训练,挖掘出从用户自然语言输入到系统自然语言输出的整体映射关系,而忽略中间过程的一种方法。但就目前工业界整体应用而言,工业界的问答系统主要采用的还是基于流水线实现的方式。
图1基于流水线(pipeline)实现
- 问答系统从答案来源划分:
- 知识库(知识库基础知识问答,KB-QA)即给定自然语言问题,通过对问题进行语义理解和解析,以及利用合并进行查询,推理得出答案。如下图2所示:
- 「常问问题问答」;
- 「新闻问答」;
- 「网际网路问答」;
图2知识库问答
2.2查询理解
2.2.1查询理解介绍
查询理解(QU,Query谅解),简单来说就是从词法,句法,语义三个维度对查询进行结构化解析。
- 搜索查询理解包含的模块主要有:
- 查询预
- 查询纠错
- 查询扩展
- 查询归一
- 本质识别
- 槽值填充
- 术语预期分析;
- ...
由于本任务后面的代码主要涉及识别识别和槽位解析,因此这里仅对这两部分内容做介绍:
2.2.2预设识别
- 简介:最初识别是用于检测用户当前输入的前缀,通常其被建模为将一段自然语言文本分类为预先定义的一个或多个替代的文本分类任务。
- 所用方法:和文本分类模型的方法大同小异,主要有:
- 基于词典模板的规则分类
- 传统的机器学习模型(文本特征工程+分类器)
- 深度学习模型(文字,TextCNN,BiLSTM +自我注意,BERT等)
图3基本意义
2.2.3槽值填充
- 引入:槽值填充就是根据我们既定的一些结构化细分,将用户输入的信息中对应的对应的部分提取出来。因此,槽值填充经常被建模为序列标注的任务。
- 表示介绍:例如下图所示的查询“北京飞成都的机票”,通过排序分类模型可以识别出查询整体整体是订机票,在此基础上进一步语义解析出对应的出发地Depart =“北京” ,到达地Arrive =“成都”,所以生成的形式化表达可以是:Ticket = Order(Depart,Arrive),Depart = {北京},Arrive = {成都}。
图4槽值填充
- 序列标注的任务常用的模型有:【注:这部分内容,第二期知识图谱组队学习将进行介绍】
- 词典匹配
- BiLSTM + CRF;
- 网络
- BERT等。
三,任务实践
图5基于知识图谱的问答系统框架
四,主体类EntityExtractor框架介绍
#!/ usr / bin / env python3
#编码:UTF -8
进口OS
进口ahocorasick
从sklearn.externals进口JOBLIB
进口解霸
进口numpy的为NP
EntityExtractor类:
def __init __():
通过
#构造actree,加速过滤
def build_actree(self,wordlist):
“”“
构造actree,加速过滤
:param wordlist
::return:
”“”
通过
#模式匹配,得到匹配的词和类型。如疾病,疾病别名,并发症,症状
defentity_reg(自我,问题):
“”“
模式匹配,得到匹配的词和类型。如疾病,疾病别名,并发症,症状
:param question:str
:return:
”“”
通过
#当全匹配失败时,就采用相似度计算来找相似的词
def find_sim_words(自我,问题):
“”
当全匹配失败时,就采用相似度计算来找相似的词
:param问题
::返回:
“”“
通过
#采用DP方法计算编辑距离
def editDistanceDP(self,s1,s2):
“”“采用
DP方法计算编辑距离
:param s1
::param s2
::return:
”“”
通过
#计算单词和字典中的词的相似度
def simCal(自身,单词,实体,标志):
“”“
计算词和字典中的词的相似度
相同
字符的个数/ min(| A |,| B |)+余弦相似度 :param字词:str
:param实体:列表
:返回值:
”“”
通过
#基于特征词分类
def check_words(自己,wds,已发送):
“”“
基于特征词分类
:param wds
::param发送
::return:
”“”
通过
#提取问题的TF-IDF特征
def tfidf_features(self,text,vectorizer):
“”“
提取问题的TF-IDF特征
:param text
::param vectorizer
::return:
”“”
通过
#提取问题的关键字特征
def other_features(self,text):
“”“
提取问题的关键字特征
:param text
::return:
”“”
通过
#预测推测
def model_predict(self,x,model):
“”“
预测变量
:param x
::param model
::return:
”“”
通过
#实体撤主函数
def提取器(自己,问题):
通过
五,命名实体识别任务实践
5.1命名实体识别整体思路介绍
- 步骤1:对于用户的输入,先使用预先合并的疾病,疾病别名,并发症和症状的AC Tree进行匹配;
- 步骤2:若全都无法匹配到相应实体,则使用结巴切词库对用户输入的文本进行切分;
- 步骤3:然后将每一个单词都去与疾病词库,疾病别名词库,并发症词库和症状词库中的词计算相似度重叠(重叠分数,余弦相似度分数和编辑距离分数),如果相似度超过0.7,则认为该词是这一类实体;
- 步骤4:最后排序选取最相关的词作为实体(项目所有的实体类型如下图所示,但实体识别时仅使用了疾病,别名,关联和症状和实体)
图6实体介绍
本部分所有的代码都来自entity_extractor.py中的EntityExtractor类,为了方便讲解,对类内的内容进行重新组织注释
5.2结合代码介绍
5.2.1建立AC树
先通过entity_extractor.py中类EntityExtractor的build_actree函数编译AC树
- 功能模块
def build_actree(self,wordlist):
“”“
构造actree,加速过滤
:param wordlist
::return:
”“”
actree = ahocorasick.Automaton()
#向树中添加单词
对于 index,在enumerate(wordlist)中的单词:
actree.add_word(word,(index,word))
actree.make_automaton()
返还行为
- 函数调用模块
def __init __():
...
self.disease_path = cur_dir +'disease_vocab.txt'
self.symptom_path = cur_dir +'symptom_vocab.txt'
self.alias_path = cur_dir +'alias_vocab.txt'
self.complication_path = cur_dir +'complications_vocab.txt'
self.disease_entities = [w.strip()为 瓦特在开放(self.disease_path,编码= 'UTF8')若w.strip()]
self.symptom_entities = [w.strip()for w in open(self.symptom_path,encoding ='utf8')如果w.strip()
self.alias_entities = [w.strip()for w in open(self.alias_path,encoding ='utf8')如果w.strip()
self.complication_entities = [w.strip()for w in open(self.complication_path,如果w.strip(),编码为'utf8')]
self.region_words = list(设置(self.disease_entities + self.alias_entities + self.symptom_entities))
#构造领域
self.disease_tree = self.build_actree(list(set(self.disease_entities)))
self.alias_tree = self.build_actree(list(set(self.alias_entities)))
self.symptom_tree = self.build_actree(list(set(self.symptom_entities)))
self.complication_tree = self.build_actree(list(set(self.complication_entities)))
...
5.2.2使用AC树进行问句过滤
- 功能模块
defEntity_reg(self,问题):
“”“
模式匹配,得到匹配的词和类型。如疾病,疾病别名,并发症,症状
:param question:str
:return:
”“”
self.result = {}
为 我在self.disease_tree.iter(问题)中:
字= i [ 1 ] [ 1 ]
如果“疾病” 不在self.result中:
self.result [ “疾病” ] = [单词]
其他:
self.result [ “ Disease” ] .append(word)
为 我在self.alias_tree.iter(question)中:
字= i [ 1 ] [ 1 ]
如果“别名” 不在self.result中:
self.result [ “ Alias” ] = [word]
其他:
self.result [ “ Alias” ] .append(word)
为 我在self.symptom_tree.iter(问题)中:
wd = i [ 1 ] [ 1 ]
如果“症状” 不在self.result中:
self.result [ “症状” ] = [wd]
其他:
self.result [ “症状” ] .append(wd)
为 我在self.complication_tree.iter(问题)中:
wd = i [ 1 ] [ 1 ]
如果“并发症” 不在self.result中:
self.result [ “ complication” ] = [wd]
其他:
self.result [ “ complication” ] .append(wd)
返回自我结果
- 函数调用模块
def提取器(自己,问题):
self.entity_reg(问题)
...
5.2.3使用相似度进行实体匹配
当AC Tree的匹配都没有匹配到实体时,使用查找相似词的方式进行实体匹配
def find_sim_words(self,question):
“”“
当全匹配失败时,就采用相似度计算来找相似的词
:param question
::return:
”“”
汇入
导入字符串
从gensim.models导入KeyedVectors
#使用结巴加载自定义词典
jieba.load_userdict(self.vocab_path)
#加载词向量
self.model = KeyedVectors.load_word2vec_format(self.word2vec_path,binary = False)
#数据预处理,正则去除特殊符号
句子= re。sub(“ [{}]”,re.escape(string。标点符号),问题)
句子= re。sub(“ [,。”;:?,!【【】]“,”“,句子)
句子=句子.strip()
#使用结巴进行分词
词语= [w.strip()为 瓦特在jieba.cut(句子)如果w.strip()未在self.stopwords和LEN(w.strip())> = 2 ]
alist = []
#对每个词,都让其与每类实体词典进行相似对比,
#最终挑选分数最高的实体和其属于的实体类型
对于 单词的词:
temp = [self.disease_entities,self.alias_entities,self.symptom_entities,self.complication_entities]
对于 我在范围内(len(temp)):
标志=''
如果i == 0:
标志= “疾病”
elif i == 1:
flag = “ Alias”
elif i == 2:
标志= “症状”
其他:
标志= “并发症”
分数= self.simCal(单词,temp [i],标志)
alist.extend(分数)
temp1 =排序(alist,key = lambda k:k [ 1 ],reverse = True)
如果temp1:
self.result [temp1 [ 0 ] [ 2 ]] = [temp1 [ 0 ] [ 0 ]]
#计算单词和字典中的词的相似度
def simCal(self,word,Entity,flag):
“”“
计算词和字典中的词的相似度
相同字符的个数/ min(| A |,| B |)+余弦相似度
:param word:str
:param实体:列表
:return:
“”“
a = len(字)
分数= []
对于 实体中的实体:
sim_num = 0
b = len(实体)
c = len(set(entity + word))
临时= []
对于 w而言:
如果w在实体中:
sim_num + = 1
如果sim_num!= 0:
score1 = sim_num / c#重叠分数
temp.append(score1)
尝试:
score2 = self.model.similarity(单词,实体)#余弦相似度分数
temp.append(score2)
除:
通过score3
= 1 -self.editDistanceDP(word,实体)/(a + b)#编辑距离分数
如果score3:
temp.append(score3)
得分=总和(温度)/ len(温度)
如果分数> = 0。7:
scores.append((实体,得分,标志))
scores.sort(key = lambda k:k [ 1 ],reverse = True)
回报分数
六,旨在识别任务实践
6.1识别识别整体思路介绍
- 步骤1:利用TF-IDF文字特征,同时建立一些人工特征(每一类连续常见词在句子中出现的个数);
- 步骤2:训练朴素贝叶斯模型进行连续识别任务;
- 步骤3:使用实体资讯进行预设的校正和补充。
图7估计识别整体模仿介绍
该项目通过手工标记210条预期分类训练数据,并采用朴素贝叶斯算法训练得到分类模型。其最佳测试效果的F1值达到了96.68%。
6.2识别识别整体思路介绍
6.2.1特征建构
- TF-IDF特征
#提取问题的TF-IDF特征
def tfidf_features(self,text,vectorizer):
“”“
提取问题的TF-IDF特征
:param text
::param vectorizer
::return:
”“”
jieba.load_userdict(self.vocab_path)
词语= [w.strip()为 瓦特在jieba.cut(文本)如果w.strip()和w.strip()未在self.stopwords]
sends = [''.join(words)]
tfidf = vectorizer.transform(sents).toarray()
返回tfidf
- 人工特征
self .symptom_qwds = ['什么症状','什么样的症状','症状有什么','症状是什么','什么表征','某种表征','表征是什么',
“什么现象”,“某种现象”,“现象有什么”,“症候”,“什么表现”,“某种表现”,“表现有什么”,
'什么行为','某种行为','行为有什么','什么状况','某种状况','状况有什么','现象是什么',
'表现是什么','行为是什么']#询问症状
self .cureway_qwds = [ '药','药品','用药','胶囊','口服液','炎片','吃什么药','用什么药','怎么办',
“买什么药”,“怎么治疗”,“如何医治”,“怎么医治”,“怎么治”,“怎么医”,“如何治”,
'医治方式','疗法','咋治','咋办','咋治','治疗方法']#询问治疗方法
self .lasttime_qwds = ['周期','多久','多延长','多少时间','几天','几年','多少天','多少小时',
'几个小时','多少年','多久能好','痊愈','康复']#询问治疗周期
self .cureprob_qwds = ['多大概率能治好','多大几率能治好','治好希望大么','几率','几成','比例',
'可能','能治','可治','可以治','可以医','能治好吗','可以治好吗','会好吗',
'能好吗','治愈吗']#询问治愈率
self .check_qwds = ['检查什么','检查项目','某些检查','什么检查','检查该','项目','检测什么',
“某种检测”,“检测某些”,“化验什么”,“什么化验”,“化验某些”,“某些体检”,“如何查找”,
'如何查找','如何检查','如何检查','如何检测','如何检测']#询问检查项目
self .belong_qwds = ['属于什么科','什么科','科室','挂什么','挂哪个','该科','其中科']#询问
科室self .disase_qwds = ['什么病”,“啥病”,“得了什么”,“得了某种”,“怎么回事”,“咋回事”,“回事”,
'什么情况','什么问题','什么毛病','啥毛病','这类病']#询问疾病
def other_features(self,text):
“”“
提取问题的关键字特征
:param text
::return:
”“”
features = [ 0 ] * 7
for self.disase_qwds中的d:
如果d在文本中:
功能[ 0 ] + = 1
对于 self.symptom_qwds中的s:
如果s在文本中:
功能[ 1 ] + = 1
对于 self.cureway_qwds中的c:
如果c在文本中:
功能[ 2 ] + = 1
对于 self.check_qwds中的c:
如果c在文本中:
features [ 3 ] + = 1
for self.lasttime_qwds中的p:
如果p在文本中:
功能[ 4 ] + = 1
对于 self.cureprob_qwds中的r:
如果r在文字中:
功能[ 5 ] + = 1
对于 self.belong_qwds中的d:
如果d在文本中:
功能[ 6 ] + = 1
m =最大(特征)
n =最小值(特征)
normed_features = []
如果m == n:
normed_features =功能
其他:
对于 我而言,功能:
j =(i-n)/(m-n)
normed_features.append(j)
返回np.array(normed_features)
6.2.2使用朴素贝叶斯进行文本分类
- 项目没有提出训练过程,可参考以下sklearn的例子
#项目没有提出训练过程,可参考以下sklearn的例子
从sklearn.naive_bayes导入MultinomialNB
mnb = MultinomialNB()
mnb.fit(X_train,y_train)
y_predict = mnb.predict(X_test)
#初步分类模型文件
self.tfidf_path = os.path.join(cur_dir,'model / tfidf_model.m')
self.nb_path = os.path.join(cur_dir,'model / intent_reg_model.m')#朴素贝叶斯模型
self.tfidf_model = joblib.load(self.tfidf_path)
self.nb_model = joblib.load(self.nb_path)
#初步预测
tfidf_feature = self.tfidf_features(问题,self.tfidf_model)
other_feature = self.other_features(问题)
m = other_feature.shape
other_feature = np.reshape(other_feature,(1,m [ 0 ]))
功能= np.concatenate((tfidf_feature,other_feature),axis = 1)
预测= self.model_predict(功能,self.nb_model)
wishs.append(predicted [ 0 ])
- 根据所识别的实体进行补充和纠正
#已知疾病,查询症状,
如果self.check_words(self.symptom_qwds,问题)和(类型为'疾病'或类型为'别名'):
如果意图不是故意的,
意图= “ query_symptom”:
意图。附加(意图)
#已知疾病或症状,查询治疗方法
如果self.check_words(self.cureway_qwds,问题)和\
(“疾病”在类型或“症状”在类型或“别名”在类型或“并发症”在类型):
如果意图不是故意的,
意图= “ query_cureway”:
意图。附加(意图)
#已知疾病或症状,查询治疗周期
如果self.check_words(self.lasttime_qwds,问题)和(类型为'疾病'或类型为'别名'):
如果意图不是故意的,
意图= “ query_period”:
意图。附加(意图)
#已知疾病,查询治愈率
如果self.check_words(self.cureprob_qwds,问题)和(类型为'疾病'或类型为'别名'):
如果意图不是故意的,
意图= “ query_rate”:
意图。附加(意图)
#已知疾病,查询检查项目
如果self.check_words(self.check_qwds,问题)和(类型为'疾病'或类型为'别名'):
如果意图不是故意的,
意图= “ query_checklist”:
意图。附加(意图)
#查询科室
如果self.check_words(self.belong_qwds,问题)和\
(“疾病”在类型或“症状”在类型或“别名”在类型或“并发症”在类型):
如果意图不是故意的,
意图= “ query_department”:
意图。附加(意图)
#已知症状,查询疾病
如果self.check_words(self.disase_qwds,问题)和(类型为“症状”或 类型为“并发症”):
如果意图不是故意的,
意图= “ query_disease”:
意图。附加(意图)
#若没有检测到本质,且已知疾病,则返回疾病的描述
如果 不意图和(“疾病”在类型或“别名”在类型):
如果意图不是故意的,
意图= “ disease_describe”:
意图。附加(意图)
#若是疾病和症状同时出现,且出现了查询疾病的特征词,则必然为查询疾病
如果self.check_words(self.disase_qwds,问题)和(类型为'Disease'或类型为'Alias')\
和(类型为“ Symptom”或 类型为“ Complication”):
如果意图不是故意的,
意图= “ query_disease”:
意图。附加(意图)
#若没有识别出实体或意图则调用其他方法
如果 没有意图或 不种类型:
如果意图不是故意的,
意图= “ QA_matching”:
意图。附加(意图)
自我.result [ “意图” ] =意图
后续就是通过上述得到的预期信息和实体信息选择对应的模版,将实体信息填充入组成查询语句进行数据库查询。