当前位置:   article > 正文

基于医疗知识图谱的问答系统_基于知识图普的医疗问答

基于知识图普的医疗问答

一、项目来源

由于之前用Rasa构建过对话系统,因此一直想脱离Rasa这个开源框架,从底层开始构建一个可以实现相似功能的对话系统,毕竟框架用的再溜,都不如自己做一遍。

恰巧在Rasa群里看到了 

@王乐

 前辈分享的一个项目:基于知识图谱的医疗诊断知识问答系统,先看了一遍视频,然后把代码下载下来,自己实现了一遍,遇到不懂得地方就再看视频,现在基本把这个项目搞明白了,写个总结分享一下,后期会在现有的基础上做一些横向拓展。

由于前辈已经有了视频讲解(项目主页有视频链接),我的总结和分享尽量避开已有的讲解内容,避免重复。

因此建议本文和前辈的视频配合食用~

二、项目架构[1]

目前实现的是最小演示版本,后期前辈可能会加上Redis、Elasticsearch、tf-serving等技术栈,但是于我而言,现在的版本已经足够了。

本文的介绍也是基于目前的这个Demo进行的,后期会不断更新。

这个项目已实现的功能

  • 闲聊类的单论对话
  • 基于知识图谱的多轮问答

先看一下对话流程,或者也叫文本解析流程,是根据代码逻辑整理出来的:

以用户输入“请问得了心脏病怎么办呢”为例:

NLU模块

1)先进入分类模型1,判断是否是闲聊类的意图,包括:greet、goodbye、deny、isbot、accept、 diagnosis:

  • 命中前四个意图,那就进入Chitchat_bot,从准备好的回复语料中随机抽取一条返回给用户,对话结束;
  • 命中accept,accept意图是在进行问题澄清时发挥作用;
  • 命中diagnosis,进入2)Medical_bot;

本例中,命中的是diagnosis ,进入 2)Medical_bot

2)在Medical_bot中,先进入分类模型2进行意图二次识别,这里包括13个医疗类的意图;然后进入NER模型进行实体识别与提取;

意图识别的结果是

{'confidence': 0.8997645974159241, 'intent': '治疗方法'}

实体识别的结果是

  1. [{'entities': [{'type': 'disease', 'word': '心脏病'}], 'string': '请问得了心脏病怎么办呢'}, {'entities': [{'recog_label': 'dict', 'type': 'disease', 'word': '心
  2. 脏病'}], 'string': '请问得了心脏病怎么办呢'}]

前面这两步相当于任务型对话机器人中的NLU模块

DST模块

3)得到意图和实体之后,先用实体填充槽位

这里说一下NLU和DST的联系与区别,NLU和DST的关系其实非常紧密,它们都在槽位填充过程中发挥了作用,但是在这个过程中扮演了不同的角色:

  • NLU模块是对用户的输入进行意图的分类,同时对输入中的实体进行标注,下面是上面实体识别结果的节选:
{'entities': [{'type': 'disease', 'word': '心脏病'}], 'string': '请问得了心脏病怎么办呢'}

在此项目中,是标出实体的类型(type),对应知识图谱中的节点,同时标出对应的字段(word),但是还没有填充,只是把实体找出来而已。

  • DST模块则是基于对话历史,为槽位列表中的每一个槽位找到一个槽位值
  1. "治疗方法":{
  2. "slot_list" : ["Disease"],
  3. "slot_values":None,
  4. "cql_template" : ["MATCH(p:疾病) WHERE p.name='{Disease}' RETURN p.cure_way",
  5. "MATCH(p:疾病)-[r:recommand_drug]->(q) WHERE p.name='{Disease}' RETURN q.name",
  6. "MATCH(p:疾病)-[r:recommand_recipes]->(q) WHERE p.name='{Disease}' RETURN q.name"],
  7. "reply_template" : "'{Disease}' 疾病的治疗方式、可用的药物、推荐菜肴有:\n",
  8. "ask_template" : "您问的是疾病 '{Disease}' 的治疗方法吗?",
  9. "intent_strategy" : "",
  10. "deny_response":"没有理解您说的意思哦~"
  11. },

这是"治疗方法"这个意图的下的信息(form),槽位列表中只有一个槽位"Disease"(下文会对这个form进行详细介绍)。

在每一轮对话中,DST模块都会查看截止目前的所有对话历史,然后确定哪个文本可以填充为槽位列表中某个特定槽位的槽位值,这个过程谓之追踪(Dialogue State Tracking)。

在这个项目中,这一步是通过遍历槽位列表实体识别的结果进行匹配完成的。

PL模块

4)然后根据意图置信度确定回复策略,这里分了三种简单的情况:

  • >=0.8,根据识别到的意图去Neo4j(知识图谱)中查询答案,返回给用户
  • 0.4~0.8,反问用户,进行问题澄清
  • <0.4,返回兜底话术

DST模块+PL模块组成了任务型对话机器人中的对话管理(DM)模块,在这个项目中界限并不是特别明显,主要实现逻辑都在modules.py 文件的 semantic_parser 函数中。

学术和工业界也有好多人在研究这一部分,一个热门的方向是用强化学习进行策略的选择,有相关的论文,感兴趣的可以去看看。

实现上述过程,需要完成以下几步工作

1)构建知识图谱作为底层数据支撑

这里构建知识图谱采用的数据集是liuhuanyong在QABasedOnMedicaKnowledgeGraph 项目中使用的医疗数据集。

一共8种实体:药品、菜谱、食物、检查、科室、药企、疾病、症状,共计4万余个

11种实体关系

  • 疾病-忌吃食物关系
  • 疾病-宜吃食物关系
  • 疾病-推荐吃食物关系
  • 疾病-通用药品关系
  • 疾病-热门药品关系
  • 疾病-检查关系
  • 科室-科室关系
  • 厂商-药物关系
  • 疾病症状关系
  • 疾病并发关系
  • 疾病与科室之间的关系

对于没有构建过知识图谱的同学来说,在有结构化数据集的情况下,不要把构建知识图谱想象成一件特别复杂,特别困难的事情;知识图谱的难点在于:

  • 数据收集阶段,从非结构化数据中提取出结构化的信息,比如从网页文本中找出实体(NER),并提取出实体间的关系
  • 图谱设计阶段,实体定义,属性定义,关系定义,需要一定领域背景知识才能更好的进行图谱设计

而在此项目中,数据集是结构化的,实体,属性,关系也都是定义好的,所以不要把这一步想的很困难。

在实际业务中,构建知识图谱的难易取决于你具体的业务场景,不能一概而论。

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 与原代码相比:

  • 对部分代码添加了注释,方便调试阅读
  • 修改了部分参数和函数的名称
  • 原作者通过itchat链接到微信,通过微信来进行交互,我自己的微信账号连接不了,因此用sanic这个轻量级的框架做了一个网页交互界面,本来还想接入到Telegram,但是实在没时间了,这个计划暂时搁置,其实用什么交互并不是最重要的,重点是算法部分的工作。

我的运行环境:

Win10+16GB内存+Pycharm

要运行这个项目你需要做以下几项工作

第一步,下载需要的数据和模型

数据是必须要用的,模型后期可以训练你自己的,下载连接在GitHub主页上

下载好后,把数据放到对应的文件夹下

这里除了Bert预训练文件别的都有了,Bert预训练文件太大了,自行在网上下载吧

第二步,构建知识图谱

1)在自己电脑上安装Neo4j

参考:

2)打开一个CMD,输入neo4j.bat console,启动服务

3)在Pycharm中运行 build_kg 文件夹下的build_kg_utils.py 构建知识图谱

  • 这个过程比较慢,我自己电脑需要2个小时左右,如果你的内存比较小,关掉一部分非必要应用
  • 把里面涉及到的路径和账号换成你自己的

第三步,启动服务

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)知识图谱的构建

  • 从结构化数据集中提取所有的实体和关系,然后构建三元组
  • 使用Neo4j 创建节点(node)和关系(edge),这一步需要你熟悉neo4j的一些常用语句

2)文本分类

文本分类在本项目中主要体现在两次意图识别上:

  • 第一次意图识别:判断用户意图是否是闲聊类,采用的是LR+GBDT多模型融合的方法
  • 第二次意图识别:识别出具体的医疗诊断意图,采用的是Bert+TextCNN

如果是抱着学习的态度,就不能止步于项目中的内容,毕竟文本分类既是NLP入门任务,又是一大分支,可以结合这个项目做些横向的拓展,基于本项目的数据集试试其它的模型,看看效果如何,也可以试试其它数据集,总之把文本分类这个任务掌握的牢固一点。

3)序列标注

  • 命名实体识别

本项目的命名实体识别部分采用的是BiLSTM+CRF模型,后面还用了AC自动机做了补充修正。

  • 槽位填充

槽位填充一般出现在任务型机器人中,用来从用户那里收集信息,先看意图中有什么槽位,然后去用户输入中做实体识别,以抽取该槽位的槽值,槽位填充本质属于一个序列标注任务。

之前看各种有关资料,都是讲理论,从来不说具体是怎么填充的,通过这个项目,算是基本明白了,重点在config.py 文件和modules.py 文件的semantic_parser()函数里,由于之前用过Rasa,立刻就联想到Rasa中的domain文件, 整个domain文件其实就是一个大字典,存着各种槽位和意图信息。

看一下这个项目是怎么实现多轮问答的,以下内容节选自config.py 文件:

  1. semantic_slot = {
  2. "定义":{
  3. "slot_list":["Disease"],
  4. "slot_values":None,
  5. "cql_template": "MATCH(p:疾病) WHERE p.name='{Disease}' RETURN p.desc",
  6. "reply_template": "'{Disease}'是这样的:\n",
  7. "ask_template": "您问的是 '{Disease}' 的定义吗?",
  8. "intent_strategy": "",
  9. "deny_response": "很抱歉没有理解你的意思呢~"
  10. },
  11. "病因":{
  12. "slot_list" : ["Disease"],
  13. "slot_values":None,
  14. "cql_template" : "MATCH(p:疾病) WHERE p.name='{Disease}' RETURN p.cause",
  15. "reply_template" : "'{Disease}' 疾病的原因是:\n",
  16. "ask_template" : "您问的是疾病 '{Disease}' 的原因吗?",
  17. "intent_strategy" : "",
  18. "deny_response":"您说的我有点不明白,您可以换个问法问我哦~"
  19. },
  20. "预防":{
  21. "slot_list" : ["Disease"],
  22. "slot_values":None,
  23. "cql_template" : "MATCH(p:疾病) WHERE p.name='{Disease}' RETURN p.prevent",
  24. "reply_template" : "关于 '{Disease}' 疾病您可以这样预防:\n",
  25. "ask_template" : "请问您问的是疾病 '{Disease}' 的预防措施吗?",
  26. "intent_strategy" : "",
  27. "deny_response":"额~似乎有点不理解你说的是啥呢~"
  28. },
  29. }

这里面的定义病因预防分别代表三个不同的医疗诊断意图,每个意图后面跟着一个字典,字典的key-value存着和意图相关的一些信息,以"定义"这个意图为例:

  • 槽位列表
"slot_list":["Disease"]
  • 槽值:被填充之后,value是一个字典,存着槽位列表中出现的槽位-槽位值
"slot_values":None
  • 图谱查询语句
"cql_template": "MATCH(p:疾病) WHERE p.name='{Disease}' RETURN p.desc",
  • 回复模板
"reply_template": "'{Disease}'是这样的:\n",
  • 澄清话术:意图置信度在0.4-.8之间是,会用此模板进行问题澄清
"ask_template": "您问的是 '{Disease}' 的定义吗?",
  • 回复策略
"intent_strategy": "",
  • 意图识别失败回复话术
"deny_response": "很抱歉没有理解你的意思呢~"

注意,这里每一个意图下都有"Disease"这个槽位,只要在最开始的时候将该槽位填充后,后面就可以不断的就填充的槽值进行多轮对话,因为槽位填充的逻辑里面有一个是获取上一步的槽值。

4)模型发布

分类模型2和NER模型都是以接口的形式提供服务的,通过这个,又可以对模型发布有所了解。

这个项目对于我最重要的是槽位填充那个环节,结合之前使用Rasa的经历,对于如何实现任务型对话机型人有了更深刻的理解,同时也再次暗叹Rasa这个框架的厉害。

本项目可扩展的地方

在算法层面,NLP领域的四大任务,本项目已经涉及到两个:序列标注文本分类

目前是采用知识图谱作为底层的数据库,对答案进行存储,可以考虑把文本生成的内容加进去,在适当的时候直接用NLG模型生成答案返给用户,至于在什么时候调用NLG模型完全看自己发挥了。

然后是句子匹配,也叫句子关系判断,关键是找一个合适的场景以及数据集,然后集成进去,具体方法可以参考这篇:21个经典深度学习句间关系模型|代码&技巧

这样一来,一个问答项目就涵盖了知识图谱+NLP四大任务,对于学习NLP的同学来说应该是一个挺不错的项目了,比市面上某些培训班的项目还要丰富。

暂时写到这里,希望能帮助到有需要的人

如果文中有什么错误,欢迎指出来~

谢谢~

参考

  1. ^https://arxiv.org/abs/2105.04387




基于医疗知识图谱的问答系统 - 知乎

GitHub - DeqianBai/KBQA-study: 基于医疗知识图谱的问答系统

GitHub - z814081807/DeepNER: 天池中药说明书实体识别挑战冠军方案;中文命名实体识别;NER; BERT-CRF & BERT-SPAN & BERT-MRC;Pytorch

【分享吧】Ahocorasick算法实现敏感词过滤

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

闽ICP备14008679号