单轮对话机器人
建设图谱
连接图数据库。
创建节点
- 把所有的节点保存在list中foods=[];关系以rels_department=[[‘内科’,‘呼吸内科’],[‘呼吸科’,‘呼吸内科’]]的方式进行保存,因为关系可能是多对一,如果以dict会覆盖;节点本身以dict保存,内置有多个描述。
- 遍历每一条数据(df或者json进行解析回来如上的三个数据结构),加入到总库中,并且建立相应的关系,本实例因为每一条数据都有疾病
if 'symptom' in data_json:
#累积所有的数据,创建node
symptoms += data_json['symptom']
for symptom in data_json['symptom']:
#依次的去创建关系,使用这个数据结构不会覆盖
rels_symptom.append([disease, symptom])
- read_node()这个方法返回了所有节点(使用set去重),每条数据的具体dict描述,关系。
- 创建节点:
- 创建中心节点,带字典详细描述;
- 其他相关的节点。
def create_node(self, label, nodes):
#label是具体的名称,Nodes是list
count = 0
for node_name in nodes:
node = Node(label, name=node_name)
self.g.create(node)
count += 1
print(count, len(nodes))
return
创建关系
先分类好,一堆东西进行相同的操作。每次给点写一个标签能极大提升查询速度,尤其是点种类多的时候。
self.create_relationship('Disease', 'Symptom', rels_symptom, 'has_symptom', '症状')
def create_relationship(self, start_node, end_node, edges, rel_type, rel_name):
count = 0
# 去重处理
#不能用dict,相同key会覆盖,所以用list很合适
set_edges = []
for edge in edges:
set_edges.append('###'.join(edge))
all = len(set(set_edges))
for edge in set(set_edges):
edge = edge.split('###')
p = edge[0]
q = edge[1]
query = "match(p:%s),(q:%s) where p.name='%s'and q.name='%s' create (p)-[rel:%s{name:'%s'}]->(q)" % (
start_node, end_node, p, q, rel_type, rel_name)
try:
self.g.run(query)
count += 1
print(rel_type, count, all)
except Exception as e:
print(e)
return
可以把相关的数据保存到对应的地方。
依靠图谱建设单轮对话系统
加载数据
- 建设图谱过程已经存下来了不同类目的数据,加载进来形成list,并且拼成一个大的list以构成一个actrie,用于查询关键词(比分词好,比正则容易)
def build_actree(self, wordlist):
actree = ahocorasick.Automaton()
for index, word in enumerate(wordlist):
actree.add_word(word, (index, word))
actree.make_automaton()
return actree
- 把第一步建设好的关键词,形成字典的对应关系,eg:呼吸内科:内科。之后再反转回来。
def build_wdtype_dict(self):
wd_dict = dict()
for wd in self.region_words:
wd_dict[wd] = []
if wd in self.disease_wds:
wd_dict[wd].append('disease')
if wd in self.department_wds:
wd_dict[wd].append('department')
if wd in self.check_wds:
wd_dict[wd].append('check')
if wd in self.drug_wds:
wd_dict[wd].append('drug')
if wd in self.food_wds:
wd_dict[wd].append('food')
if wd in self.symptom_wds:
wd_dict[wd].append('symptom')
if wd in self.producer_wds:
wd_dict[wd].append('producer')
return wd_dict
- 注意,上一步和图谱建设是分离的,其次是上面是把所有的关键字对应的门类都归类好。是提问者的触发的关键(通过actire)。
建设自己的目标问题的种类
对应每一种关系都有一个问题集合。
self.symptom_qwds = ['症状', '表征', '现象', '症候', '表现']
self.cause_qwds = ['原因','成因', '为什么', '怎么会', '怎样才', '咋样才', '怎样会', '如何会', '为啥', '为何', '如何才会', '怎么才会', '会导致', '会造成']
self.acompany_qwds = ['并发症', '并发', '一起发生', '一并发生', '一起出现', '一并出现', '一同发生', '一同出现', '伴随发生', '伴随', '共现']
self.food_qwds = ['饮食', '饮用', '吃', '食', '伙食', '膳食', '喝', '菜' ,'忌口', '补品', '保健品', '食谱', '菜谱', '食用', '食物','补品']
self.drug_qwds = ['药', '药品', '用药', '胶囊', '口服液', '炎片']
self.prevent_qwds = ['预防', '防范', '抵制', '抵御', '防止','躲避','逃避','避开','免得','逃开','避开','避掉','躲开','躲掉','绕开',
'怎样才能不', '怎么才能不', '咋样才能不','咋才能不', '如何才能不',
'怎样才不', '怎么才不', '咋样才不','咋才不', '如何才不',
'怎样才可以不', '怎么才可以不', '咋样才可以不', '咋才可以不', '如何可以不',
'怎样才可不', '怎么才可不', '咋样才可不', '咋才可不', '如何可不']
self.lasttime_qwds = ['周期', '多久', '多长时间', '多少时间', '几天', '几年', '多少天', '多少小时', '几个小时', '多少年']
self.cureway_qwds = ['怎么治疗', '如何医治', '怎么医治', '怎么治', '怎么医', '如何治', '医治方式', '疗法', '咋治', '怎么办', '咋办', '咋治']
self.cureprob_qwds = ['多大概率能治好', '多大几率能治好', '治好希望大么', '几率', '几成', '比例', '可能性', '能治', '可治', '可以治', '可以医']
self.easyget_qwds = ['易感人群', '容易感染', '易发人群', '什么人', '哪些人', '感染', '染上', '得上']
self.check_qwds = ['检查', '检查项目', '查出', '检查', '测出', '试出']
self.belong_qwds = ['属于什么科', '属于', '什么科', '科室']
self.cure_qwds = ['治疗什么', '治啥', '治疗啥', '医治啥', '治愈啥', '主治啥', '主治什么', '有什么用', '有何用', '用处', '用途',
'有什么好处', '有什么益处', '有何益处', '用来', '用来做啥', '用来作甚', '需要', '要']
开始正式的处理文本
- 文本分类,提出主干信息和该词对应的类别。
'''问句过滤'''
def check_medical(self, question):
region_wds = []
#actrie使用方法,很容易直接找出了目标的词
for i in self.region_tree.iter(question):
wd = i[1][1]
region_wds.append(wd)
stop_wds = []
#如果出现短关键词在长的关键词in的关系,则去掉短关键词,因为考虑字符串的连续性约束,不过不能保证后面有一样的短的被过滤
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
- 通过ac树取得了触发词,并且做了后处理即取长不取短;
- 取得词对应的具体类别。
- 通过如下的判断:
# 症状
if self.check_words(self.symptom_qwds, question) and ('disease' in types):
question_type = 'disease_symptom'
question_types.append(question_type)
# 药品治啥病
if self.check_words(self.cure_qwds, question) and 'drug' in types:
question_type = 'drug_disease'
question_types.append(question_type)
# 若没有查到相关的外部查询信息,那么则将该疾病的描述信息返回,就是图谱的总体字典
if question_types == [] and 'symptom' in types:
question_types = ['symptom_disease']
#上面是通过actrie找到图谱触发词,这个是我们自己设定的关系触发词。
def check_words(self, wds, sent):
for wd in wds:
if wd in sent:
return True
return False
- ==我们有两组触发词,一组是图谱本身触发词,另一组是自己定义的关系触发词。==这个紧贴业务,对应的类别和触发词是匹配在一起的。
- 返回拼装好的dict如下:{‘args’: {‘百日咳’: [‘disease’]}, ‘question_types’: [‘disease_symptom’]},即将文本解析成结构化的格式。
- 形成对应的cql
- 把关系列出来,通过关系找到对应的拼接cql,能回答的问题解空间是有限的。
- 返回是{‘question_type’: ‘disease_symptom’, ‘sql’: [“MATCH (m:Disease)-[r:has_symptom]->(n:Symptom) where m.name = ‘百日咳’ return m.name, r.name, n.name”]}
的list总和。 - 加入前面的question_type是为了写一些人性化的回答。
for question_type in question_types:
sql_ = {}
sql_['question_type'] = question_type
sql = []
if question_type == 'disease_symptom':
sql = self.sql_transfer(question_type, entity_dict.get('disease'))
- 对应拼成cql:
elif question_type == 'disease_symptom':
sql = ["MATCH (m:Disease)-[r:has_symptom]->(n:Symptom) where m.name = '{0}' return m.name, r.name, n.name".format(i) for i in entities]
- 执行返回答案并且让回答更加贴近人类回答。
全部文本抽取
- 这个问题和具体的业务和贴近,使用角色识别,句子依存,以及对其他的词性判定然后在做各种组合特征可以做。
- 另外是如果要提取符合特定关系的三元组,设计专门的神经网络。但是关键都是谓词是主体。