赞
踩
由于之前用Rasa构建过对话系统,因此一直想脱离Rasa这个开源框架,从底层开始构建一个可以实现相似功能的对话系统,毕竟框架用的再溜,都不如自己做一遍。
恰巧在Rasa群里看到了
前辈分享的一个项目:基于知识图谱的医疗诊断知识问答系统,先看了一遍视频,然后把代码下载下来,自己实现了一遍,遇到不懂得地方就再看视频,现在基本把这个项目搞明白了,写个总结分享一下,后期会在现有的基础上做一些横向拓展。
由于前辈已经有了视频讲解(项目主页有视频链接),我的总结和分享尽量避开已有的讲解内容,避免重复。
因此建议本文和前辈的视频配合食用~
目前实现的是最小演示版本,后期前辈可能会加上Redis、Elasticsearch、tf-serving等技术栈,但是于我而言,现在的版本已经足够了。
本文的介绍也是基于目前的这个Demo进行的,后期会不断更新。
这个项目已实现的功能:
先看一下对话流程,或者也叫文本解析流程,是根据代码逻辑整理出来的:
以用户输入“请问得了心脏病怎么办呢”为例:
1)先进入分类模型1,判断是否是闲聊类的意图,包括:greet、goodbye、deny、isbot、accept、 diagnosis:
本例中,命中的是diagnosis
,进入 2)Medical_bot
2)在Medical_bot中,先进入分类模型2进行意图二次识别,这里包括13个医疗类的意图;然后进入NER模型进行实体识别与提取;
意图识别的结果是
{'confidence': 0.8997645974159241, 'intent': '治疗方法'}
实体识别的结果是
- [{'entities': [{'type': 'disease', 'word': '心脏病'}], 'string': '请问得了心脏病怎么办呢'}, {'entities': [{'recog_label': 'dict', 'type': 'disease', 'word': '心
- 脏病'}], 'string': '请问得了心脏病怎么办呢'}]
前面这两步相当于任务型对话机器人中的NLU模块
3)得到意图和实体之后,先用实体填充槽位
这里说一下NLU和DST的联系与区别,NLU和DST的关系其实非常紧密,它们都在槽位填充过程中发挥了作用,但是在这个过程中扮演了不同的角色:
{'entities': [{'type': 'disease', 'word': '心脏病'}], 'string': '请问得了心脏病怎么办呢'}
在此项目中,是标出实体的类型(type),对应知识图谱中的节点,同时标出对应的字段(word),但是还没有填充,只是把实体找出来而已。
- "治疗方法":{
- "slot_list" : ["Disease"],
- "slot_values":None,
- "cql_template" : ["MATCH(p:疾病) WHERE p.name='{Disease}' RETURN p.cure_way",
- "MATCH(p:疾病)-[r:recommand_drug]->(q) WHERE p.name='{Disease}' RETURN q.name",
- "MATCH(p:疾病)-[r:recommand_recipes]->(q) WHERE p.name='{Disease}' RETURN q.name"],
- "reply_template" : "'{Disease}' 疾病的治疗方式、可用的药物、推荐菜肴有:\n",
- "ask_template" : "您问的是疾病 '{Disease}' 的治疗方法吗?",
- "intent_strategy" : "",
- "deny_response":"没有理解您说的意思哦~"
- },
这是"治疗方法"这个意图的下的信息(form),槽位列表中只有一个槽位"Disease"(下文会对这个form进行详细介绍)。
在每一轮对话中,DST模块都会查看截止目前的所有对话历史,然后确定哪个文本可以填充为槽位列表中某个特定槽位的槽位值,这个过程谓之追踪(Dialogue State Tracking)。
在这个项目中,这一步是通过遍历槽位列表和实体识别的结果进行匹配完成的。
4)然后根据意图置信度确定回复策略,这里分了三种简单的情况:
DST模块+PL模块组成了任务型对话机器人中的对话管理(DM)模块,在这个项目中界限并不是特别明显,主要实现逻辑都在modules.py
文件的 semantic_parser
函数中。
学术和工业界也有好多人在研究这一部分,一个热门的方向是用强化学习进行策略的选择,有相关的论文,感兴趣的可以去看看。
1)构建知识图谱作为底层数据支撑
这里构建知识图谱采用的数据集是liuhuanyong在QABasedOnMedicaKnowledgeGraph 项目中使用的医疗数据集。
一共8种实体:药品、菜谱、食物、检查、科室、药企、疾病、症状,共计4万余个
11种实体关系:
对于没有构建过知识图谱的同学来说,在有结构化数据集的情况下,不要把构建知识图谱想象成一件特别复杂,特别困难的事情;知识图谱的难点在于:
而在此项目中,数据集是结构化的,实体,属性,关系也都是定义好的,所以不要把这一步想的很困难。
在实际业务中,构建知识图谱的难易取决于你具体的业务场景,不能一概而论。
2)分类模型1
分类模型1的任务是完成多意图分类,判断用户意图是否是闲聊类的,这是第一次意图分类(这个项目中一共进行了两层意图分类),这里采用的是LR+GBDT多模型融合的方法。
3)分类模型2
在Medical_bot中,首先进入分类模型2,识别出具体的医疗诊断意图:定义、病因、治疗时间、预防、治疗方法等共计13个医疗诊断意图,这里采用的是Bert+TextCNN进行多意图分类。
4)NER模型
在Medical_bot中,第二步是通过NER模型对医疗领域的实体进行识别及提取,即从用户输入中就要求提取出知识图谱中包含的实体的名称,前面提到过,在本项目中一共8种.
但是在大型项目中,实体数量数以万计,而普通的匹配方式由于每次匹配失败都需要回溯,其耗时较久,而AC自动机的时间复杂度理想状态下为O(n),n为用户输入字符串的长度。
所以一种技术路线就是,使用 AC自动机提取出包含知识库中实体的所有子串(或者最长子串)+ NER实体识别的方式对用户输入中的实体进行提取,再综合两个结果召回评分最高的Top n实体进行后续的链接操作。
本项目采用的就是AC自动机提取所有子串+NER实体识别的方式。
为了方便大家实践,我把自己的代码上传到GitHub了
地址是KBQA-study 与原代码相比:
我的运行环境:
Win10+16GB内存+Pycharm
要运行这个项目你需要做以下几项工作
数据是必须要用的,模型后期可以训练你自己的,下载连接在GitHub主页上
下载好后,把数据放到对应的文件夹下
这里除了Bert预训练文件别的都有了,Bert预训练文件太大了,自行在网上下载吧
1)在自己电脑上安装Neo4j
参考:
2)打开一个CMD,输入neo4j.bat console,启动服务
3)在Pycharm中运行 build_kg
文件夹下的build_kg_utils.py
构建知识图谱
1)启动意图识别模型服务和NER模型服务
启动命令在下面两个文件中
run_intent_recog_service.bat
run_ner_service.bat
直接双击运行这两个文件,打开后不要关闭,如果是Linux系统,将其改成 shell 脚本后运行;
奈何在我的电脑上双击后总是闪退,如果你也遇到类似的情况,可以试试以下方法:
Pycharm 中打开2个Terminal,分别运行run_intent_recog_service.bat
和 run_ner_service.bat
中的两条命令;
一般是报错了才会闪退,可以用这种方法查看报错信息,进行修改
2)启动知识图谱服务
如果你构建好知识图谱后没有关闭服务,这一步可以跳过,要是关闭了,就重新启动
打开一个CMD,输入neo4j.bat console,启动服务
3)启动主程序
我自己新建了一个local.py的文件,在里面写了sanic的服务代码
你可以先尝试使用微信版本:
Pycharm 中打开1个Terminal,输入:python itchat_app.py
如果登陆不了,可以试试输入:python local.py,然后浏览器打开http://你的IP:12348/swagger/#/default 就可以用我的这种方式和机器人进行交互了。
1)知识图谱的构建
2)文本分类
文本分类在本项目中主要体现在两次意图识别上:
如果是抱着学习的态度,就不能止步于项目中的内容,毕竟文本分类既是NLP入门任务,又是一大分支,可以结合这个项目做些横向的拓展,基于本项目的数据集试试其它的模型,看看效果如何,也可以试试其它数据集,总之把文本分类这个任务掌握的牢固一点。
3)序列标注
本项目的命名实体识别部分采用的是BiLSTM+CRF模型,后面还用了AC自动机做了补充修正。
槽位填充一般出现在任务型机器人中,用来从用户那里收集信息,先看意图中有什么槽位,然后去用户输入中做实体识别,以抽取该槽位的槽值,槽位填充本质属于一个序列标注任务。
之前看各种有关资料,都是讲理论,从来不说具体是怎么填充的,通过这个项目,算是基本明白了,重点在config.py
文件和modules.py
文件的semantic_parser()
函数里,由于之前用过Rasa,立刻就联想到Rasa中的domain文件, 整个domain文件其实就是一个大字典,存着各种槽位和意图信息。
看一下这个项目是怎么实现多轮问答的,以下内容节选自config.py
文件:
- semantic_slot = {
- "定义":{
- "slot_list":["Disease"],
- "slot_values":None,
- "cql_template": "MATCH(p:疾病) WHERE p.name='{Disease}' RETURN p.desc",
- "reply_template": "'{Disease}'是这样的:\n",
- "ask_template": "您问的是 '{Disease}' 的定义吗?",
- "intent_strategy": "",
- "deny_response": "很抱歉没有理解你的意思呢~"
- },
- "病因":{
- "slot_list" : ["Disease"],
- "slot_values":None,
- "cql_template" : "MATCH(p:疾病) WHERE p.name='{Disease}' RETURN p.cause",
- "reply_template" : "'{Disease}' 疾病的原因是:\n",
- "ask_template" : "您问的是疾病 '{Disease}' 的原因吗?",
- "intent_strategy" : "",
- "deny_response":"您说的我有点不明白,您可以换个问法问我哦~"
- },
- "预防":{
- "slot_list" : ["Disease"],
- "slot_values":None,
- "cql_template" : "MATCH(p:疾病) WHERE p.name='{Disease}' RETURN p.prevent",
- "reply_template" : "关于 '{Disease}' 疾病您可以这样预防:\n",
- "ask_template" : "请问您问的是疾病 '{Disease}' 的预防措施吗?",
- "intent_strategy" : "",
- "deny_response":"额~似乎有点不理解你说的是啥呢~"
- },
- }
这里面的定义、病因、预防分别代表三个不同的医疗诊断意图,每个意图后面跟着一个字典,字典的key-value存着和意图相关的一些信息,以"定义"这个意图为例:
"slot_list":["Disease"]
"slot_values":None
"cql_template": "MATCH(p:疾病) WHERE p.name='{Disease}' RETURN p.desc",
"reply_template": "'{Disease}'是这样的:\n",
"ask_template": "您问的是 '{Disease}' 的定义吗?",
"intent_strategy": "",
"deny_response": "很抱歉没有理解你的意思呢~"
注意,这里每一个意图下都有"Disease"这个槽位,只要在最开始的时候将该槽位填充后,后面就可以不断的就填充的槽值进行多轮对话,因为槽位填充的逻辑里面有一个是获取上一步的槽值。
4)模型发布
分类模型2和NER模型都是以接口的形式提供服务的,通过这个,又可以对模型发布有所了解。
这个项目对于我最重要的是槽位填充那个环节,结合之前使用Rasa的经历,对于如何实现任务型对话机型人有了更深刻的理解,同时也再次暗叹Rasa这个框架的厉害。
在算法层面,NLP领域的四大任务,本项目已经涉及到两个:序列标注和文本分类
目前是采用知识图谱作为底层的数据库,对答案进行存储,可以考虑把文本生成的内容加进去,在适当的时候直接用NLG模型生成答案返给用户,至于在什么时候调用NLG模型完全看自己发挥了。
然后是句子匹配,也叫句子关系判断,关键是找一个合适的场景以及数据集,然后集成进去,具体方法可以参考这篇:21个经典深度学习句间关系模型|代码&技巧
这样一来,一个问答项目就涵盖了知识图谱+NLP四大任务,对于学习NLP的同学来说应该是一个挺不错的项目了,比市面上某些培训班的项目还要丰富。
暂时写到这里,希望能帮助到有需要的人
如果文中有什么错误,欢迎指出来~
谢谢~
GitHub - DeqianBai/KBQA-study: 基于医疗知识图谱的问答系统
GitHub - z814081807/DeepNER: 天池中药说明书实体识别挑战冠军方案;中文命名实体识别;NER; BERT-CRF & BERT-SPAN & BERT-MRC;Pytorch
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。