
一、项目来源由于之前用Rasa构建过对话系统因此一直想脱离Rasa这个开源框架从底层开始构建一个可以实现相似功能的对话系统毕竟框架用的再溜都不如自己做一遍。恰巧在Rasa群里看到了王乐前辈分享的一个项目基于知识图谱的医疗诊断知识问答系统先看了一遍视频然后把代码下载下来自己实现了一遍遇到不懂得地方就再看视频现在基本把这个项目搞明白了写个总结分享一下后期会在现有的基础上做一些横向拓展。由于前辈已经有了视频讲解(项目主页有视频链接)我的总结和分享尽量避开已有的讲解内容避免重复。因此建议本文和前辈的视频配合食用~二、项目架构[1]目前实现的是最小演示版本后期前辈可能会加上Redis、Elasticsearch、tf-serving等技术栈但是于我而言现在的版本已经足够了。本文的介绍也是基于目前的这个Demo进行的后期会不断更新。这个项目已实现的功能闲聊类的单论对话基于知识图谱的多轮问答先看一下对话流程或者也叫文本解析流程是根据代码逻辑整理出来的以用户输入“请问得了心脏病怎么办呢”为例NLU模块1先进入分类模型1判断是否是闲聊类的意图包括greet、goodbye、deny、isbot、accept、 diagnosis命中前四个意图那就进入Chitchat_bot从准备好的回复语料中随机抽取一条返回给用户对话结束命中acceptaccept意图是在进行问题澄清时发挥作用命中diagnosis进入2Medical_bot本例中命中的是diagnosis进入 2Medical_bot2在Medical_bot中先进入分类模型2进行意图二次识别这里包括13个医疗类的意图然后进入NER模型进行实体识别与提取意图识别的结果是{confidence: 0.8997645974159241, intent: 治疗方法}实体识别的结果是[{entities: [{type: disease, word: 心脏病}], string: 请问得了心脏病怎么办呢}, {entities: [{recog_label: dict, type: disease, word: 心 脏病}], string: 请问得了心脏病怎么办呢}]前面这两步相当于任务型对话机器人中的NLU模块DST模块3得到意图和实体之后先用实体填充槽位这里说一下NLU和DST的联系与区别NLU和DST的关系其实非常紧密它们都在槽位填充过程中发挥了作用但是在这个过程中扮演了不同的角色NLU模块是对用户的输入进行意图的分类同时对输入中的实体进行标注下面是上面实体识别结果的节选{entities: [{type: disease, word: 心脏病}], string: 请问得了心脏病怎么办呢}在此项目中是标出实体的类型(type)对应知识图谱中的节点同时标出对应的字段(word)但是还没有填充只是把实体找出来而已。DST模块则是基于对话历史为槽位列表中的每一个槽位找到一个槽位值治疗方法:{ 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模块都会查看截止目前的所有对话历史然后确定哪个文本可以填充为槽位列表中某个特定槽位的槽位值这个过程谓之追踪(DialogueStateTracking)。在这个项目中这一步是通过遍历槽位列表和实体识别的结果进行匹配完成的。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的任务是完成多意图分类判断用户意图是否是闲聊类的这是第一次意图分类(这个项目中一共进行了两层意图分类)这里采用的是LRGBDT多模型融合的方法。3分类模型2在Medical_bot中首先进入分类模型2识别出具体的医疗诊断意图定义、病因、治疗时间、预防、治疗方法等共计13个医疗诊断意图这里采用的是BertTextCNN进行多意图分类。4NER模型在Medical_bot中第二步是通过NER模型对医疗领域的实体进行识别及提取即从用户输入中就要求提取出知识图谱中包含的实体的名称前面提到过在本项目中一共8种.但是在大型项目中实体数量数以万计而普通的匹配方式由于每次匹配失败都需要回溯其耗时较久而AC自动机的时间复杂度理想状态下为O(n)n为用户输入字符串的长度。所以一种技术路线就是使用 AC自动机提取出包含知识库中实体的所有子串(或者最长子串) NER实体识别的方式对用户输入中的实体进行提取再综合两个结果召回评分最高的Top n实体进行后续的链接操作。本项目采用的就是AC自动机提取所有子串NER实体识别的方式。三、项目运行为了方便大家实践我把自己的代码上传到GitHub了地址是KBQA-study 与原代码相比对部分代码添加了注释方便调试阅读修改了部分参数和函数的名称原作者通过itchat链接到微信通过微信来进行交互我自己的微信账号连接不了因此用sanic这个轻量级的框架做了一个网页交互界面本来还想接入到Telegram但是实在没时间了这个计划暂时搁置其实用什么交互并不是最重要的重点是算法部分的工作。我的运行环境Win1016GB内存Pycharm要运行这个项目你需要做以下几项工作第一步下载需要的数据和模型数据是必须要用的模型后期可以训练你自己的下载连接在GitHub主页上下载好后把数据放到对应的文件夹下这里除了Bert预训练文件别的都有了Bert预训练文件太大了自行在网上下载吧第二步构建知识图谱1在自己电脑上安装Neo4j参考Win10下安装Neo4j图数据库Neo4j的安装与使用教程winLinux手把手教你快速入门知识图谱 - Neo4J教程2打开一个CMD输入neo4j.bat console启动服务3在Pycharm中运行build_kg文件夹下的build_kg_utils.py构建知识图谱这个过程比较慢我自己电脑需要2个小时左右如果你的内存比较小关掉一部分非必要应用把里面涉及到的路径和账号换成你自己的第三步启动服务1启动意图识别模型服务和NER模型服务启动命令在下面两个文件中run_intent_recog_service.batrun_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文本分类文本分类在本项目中主要体现在两次意图识别上第一次意图识别判断用户意图是否是闲聊类采用的是LRGBDT多模型融合的方法第二次意图识别识别出具体的医疗诊断意图采用的是BertTextCNN如果是抱着学习的态度就不能止步于项目中的内容毕竟文本分类既是NLP入门任务又是一大分支可以结合这个项目做些横向的拓展基于本项目的数据集试试其它的模型看看效果如何也可以试试其它数据集总之把文本分类这个任务掌握的牢固一点。3序列标注命名实体识别本项目的命名实体识别部分采用的是BiLSTMCRF模型后面还用了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]槽值被填充之后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的同学来说应该是一个挺不错的项目了比市面上某些培训班的项目还要丰富。暂时写到这里希望能帮助到有需要的人如果文中有什么错误欢迎指出来~谢谢~参考^https://arxiv.org/abs/2105.04387基于医疗知识图谱的问答系统 - 知乎GitHub - DeqianBai/KBQA-study: 基于医疗知识图谱的问答系统GitHub - z814081807/DeepNER: 天池中药说明书实体识别挑战冠军方案中文命名实体识别NER; BERT-CRF BERT-SPAN BERT-MRCPytorch【分享吧】Ahocorasick算法实现敏感词过滤