ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

医疗知识图谱问答系统:从图谱构建到Neo4j查询的完整链路解析

医疗知识图谱问答系统:从图谱构建到Neo4j查询的完整链路解析

从接触医疗知识图谱问答系统这个方向开始,我陆陆续续看过不少开源项目,但这个仓库给我的印象特别深。它没有堆砌复杂的模型,而是用一套完整但门槛不高的工程链路,把医学知识结构化、自然语言问句理解、图数据库查询这三件事串了起来。这篇文章就是我对 QASystemOnMedicalKG-master 的探索记录,包含代码阅读思路、知识图谱构建细节、问答链路拆解、复现时踩过的坑,以及我基于这套系统做的改进实验。如果你想快速入门医疗垂直领域的问答系统,或者正在纠结知识图谱到底怎么落地,这篇记录应该能给你一个比较完整的参考。

我先把话说在前面:这个项目不是那种开箱即用、放上生产环境就能直接服役的成品,它是很好的教学样板和学习骨架。你需要理解它的设计逻辑,再根据业务数据去做二次改造。下文我会按照我自己的阅读顺序来展开——先看整体架构,再看图谱构建,然后是问答引擎核心,最后聊查询性能和踩坑经验。

1. 先摸清楚项目到底在做什么:三大模块与一条主链路

1.1 从目录结构反推系统架构

拿到一个开源项目,我一般不会急着去跑代码,而是先看目录结构和入口脚本。这套系统最直观的划分是三个部分:数据准备、图谱构建与导入、问答服务。数据准备负责把原始的医学资料整理成结构化三元组;图谱构建部分负责把这些三元组清洗、去重、关联,最终导入图数据库;问答服务则接收用户输入的中文问句,完成意图识别、实体抽取、查询语句生成和答案返回。

这种分层非常清晰,也符合大多数知识图谱问答项目的基本套路。我建议你在看任何类似项目时都先问自己一个问题:这个项目的数据源头是什么?数据是怎么变成图的?用户输入是怎么被路由到图查询的?把这三个问题的答案找出来,整个项目的主干脉络就清楚了。

1.2 数据是医疗问答系统的血液

医疗知识图谱问答系统最核心的资产不是算法,而是数据。这个项目里的数据主要由疾病、症状、药物、食物、检查项目等实体类型,以及它们之间的语义关系组成。比如“高血压”和“头晕”之间是“表现为”的关系,“糖尿病”和“胰岛素”之间是“推荐用药”的关系。

为什么要用知识图谱而不是传统的表格?因为医疗知识本身是网状结构。一个疾病关联多种症状,一种症状又可能指向多种疾病;一种药物有适用症,也可能有禁忌症。这种多对多、跨层级、带语义标签的关系,用二维表表达非常吃力,但用图来表达就非常自然。图数据库里,节点是实体,边是关系,用户可以顺着某条路径去检索“从疾病出发,经过症状节点,再到达相关检查项目”这类多跳问题。

这个项目把这一整套流程都串了起来。我在探索时的体会是:它的价值不在某个单点算法有多强,而在让你看到一条完整的、可以被复刻的落地链路。这也是我推荐从它入手的原因。

2. 知识图谱构建:疾病、症状、药物之间如何被结构化

2.1 实体类型与关系类型的设计

先聊实体类型。这套系统把医疗领域信息抽象成六个左右的实体类别,包括疾病、症状、药物、食物、检查项目、身体部位等。每种实体在图中都有对应的节点标签,节点属性一般是名称、别名、所属科室、常见程度等。关系类型则围绕疾病展开,比如疾病和症状之间的“表现为”、疾病和药物之间的“推荐用药”、疾病和检查项目之间的“适合检查”、疾病和食物之间的“宜吃”和“忌吃”等。

这个设计看起来不复杂,但它是后续所有问答逻辑的基础。因为问答系统生成查询语句时,本质上就是把用户问句里的实体和意图映射为图上的节点和关系。比如用户问“感冒应该吃什么药”,系统要做两件事:识别出“感冒”这个疾病实体,再判断出意图是“推荐用药”,然后生成一条从疾病节点到药物节点的查询路径。如果关系类型设计得不够明确,这一步就会非常难受。

我特别想强调命名规范的重要性。这个项目里关系类型大多采用统一的动词短语,比如“HAS_SYMPTOM”“RECOMMEND_DRUG”。我建议你在自己的项目里也保持这种清晰的命名习惯,因为在写问答模板时,这些关系名会被直接拼进查询语句,命名越一致,模板维护成本越低。

2.2 数据清洗与导入Neo4j的细节

图谱构建里最花时间的往往不是设计,而是清洗。原始数据可能来自医疗百科、结构化表格或人工整理,基本都会包含重复实体、同义名称、空属性、乱码字符等问题。比如同一个药物在不同数据源里可能写作“阿司匹林”和“乙酰水杨酸”,如果不做归一化,图谱里就会出现两个孤立的节点,查询时用户说哪个都只能查到一半。

我当时复现这个项目时,数据处理部分主要做了三类操作。第一是去重:按实体名称归一化后合并重复节点。第二是补全关系:只保留两个节点都存在的三元组,避免产生悬挂节点。第三是字段清洗:把科室、药品剂量等属性中的空格、换行、特殊符号去掉,统一格式。

导入Neo4j这一步也有讲究。这个项目的数据规模不大,常规做法是用Cypher的LOAD CSV或者直接通过Python驱动批量写入。批量写入时要注意把节点先创建完,再创建边,否则会出现“关系引用了不存在的节点”这类错误。如果数据量更大,比如几十万以上节点,就得考虑使用Neo4j Admin Import这样的离线导入工具,把数据转成CSV头文件再导入,速度快好几个数量级。

3. 问答引擎核心链路:从自然语言到Cypher查询

3.1 中文问句的识别与实体链接

问答链路是整个系统最让人兴奋的部分。用户输入是一句口语化的中文问句,比如“高血压病人平时吃什么降压药比较好”,而系统内部需要把它转成一条可执行的图查询。这个转换过程大体分为两步:先抽取实体,再判断意图。

实体抽取这一层,项目采用了词典与分词工具结合的方式。思路是准备一个包含疾病名、症状名、药物名的自定义词典,在分词阶段就尽量让专业术语完整切分。比如“高血压”这个词如果按普通分词可能被切出“血压”,有了词典约束才能把它作为一个完整实体返回。我实测下来,这个方案对常见的疾病名、药物名覆盖效果不错,遇到不认识的专有名词就会失效,所以后期又补充了基于规则的回退机制:当词典匹配不到时,尝试从原文中提取包含“疾病”“药物”等后缀词的片段,再和图谱里的节点模糊匹配。

实体链接则是把抽取出的实体名映射到图谱中的具体节点。这里有个隐藏问题:一个实体可能有多个名字。我在改进时加了一张同义词映射表,把凡是节点属性中包含别名的表述都指向主节点,例如“高血压”和“essential hypertension”这样的中外文表述。就这一个改动,让问答命中率提升了不止一点点。

3.2 模板解析的多个分支

意图识别的作用是决定用户到底要问什么。这个项目把意图划分为若干类,常见的包括:疾病症状查询、疾病治疗用药查询、疾病宜吃忌吃查询、疾病检查项目查询、科室查询等。每个意图类型对应至少一个查询模板,模板里预留实体槽位,比如“{疾病}吃什么药”对应“推荐用药”。

模板匹配的具体思路,是把问句和预定义好的句式规则做比对。这里不是靠深度学习模型,而是靠规则加关键词权重。比如句子中出现了“症状”“表现”“什么感觉”等词,系统判断意图偏向症状查询;出现“吃什么药”“治疗”“用药”等词,则偏向用药查询。这种做法的优点是实现简单、完全可控,缺点是覆盖不全,换一种没见过的问法就可能误判。我在自己的改进实验里给每个意图类型都增加了正例和反例句子,通过小规模分类模型做增强,效果比纯规则稳定了一些。

3.3 一个完整问答示例

我拿“糖尿病有哪些常见并发症”这个问句走了一遍完整链路。分词阶段,自定义词典把“糖尿病”和“并发症”都识别为词条;实体抽取阶段,系统从图谱中找到疾病节点“糖尿病”;意图判断阶段,句子中出现“并发症”这个关键词,系统把它归类到并发症查询模板;紧接着模板实例化,生成一条查询语句:从糖尿病节点出发,沿着表示并发关系的边找到相关联的疾病节点,返回这些节点的名称。

这条查询在数据库里执行后,返回的结果可能是“糖尿病肾病”“糖尿病视网膜病变”等。系统再把节点名称包装成一句自然语言答案返回给用户。整个过程从输入到响应,在本地环境上大概只需要几百毫秒。这个速度对于教学演示完全够用,也说明在数据量可控时,规则加图查询的方案远比想象中高效。

4. 图数据库的选型观察与查询性能体验

4.1 为什么不能直接用关系型数据库

很多人会问:医疗数据实体不算多,用MySQL也能存,为什么非要引入图数据库?我自己探索后的理解是:图数据库的核心优势不在基础查询,而在多跳关联和路径检索。医疗领域最常问的问题之一就是“这种症状可能是哪些疾病引起的”,这个查询本质上是从症状节点出发,反查所有连到它的疾病节点,可能还要继续跳到对应的检查项目或用药建议。

这种查询如果用关系表实现,通常需要设计多张关联表,写一串JOIN语句,而且关系层级越深,SQL越复杂。在图数据库里,这只是一个沿边遍历的操作。这套项目使用的是图数据库领域最主流的Neo4j,它的查询语言Cypher对路径遍历的表达非常直接,比如匹配一条关系链就是简单地描述节点和关系的连接模式。

我个人的观察是:对于关系类型固定、查询模式清晰的垂直领域,图数据库的思维模型天然更匹配。它逼迫你把业务关系建模成图,这个建模过程本身就是对领域知识的一次梳理。

4.2 索引、参数化与查询延迟

实际跑通问答之后,我开始关注性能问题。在这个项目的小规模图谱上,直接查询几乎没有压力,但不代表可以忽略索引。医疗图谱虽然在单个实体类型上节点数量有限,但如果以后扩展到几十万实体、上百万关系,没有索引的查询性能会迅速恶化。

我在验证时给疾病名、症状名、药物名的名称属性都加了索引。加索引前,某些模糊匹配查询能明显感觉到延迟;加索引后,即便是全库扫描类的匹配也顺滑很多。这个经验可以通用到所有图数据库项目:先确认哪些实体属性会被当成查询入口,再针对这些属性建立索引,千万不要等用户来吐槽慢。

另外我还建议查询语句里尽量使用参数化写法,不要把所有实体值硬拼到查询字符串里。参数化不仅能让数据库做执行计划缓存,更重要的是能避免查询注入和特殊字符引发的解析错误。这一点很多做知识图谱问答的初学者容易忽略,觉得数据是自己导进去的,没必要防注入。但等到实体名里出现引号、括号、特殊符号时,你就知道参数化有多重要了。

5. 复现过程中踩过的坑与排查思路

5.1 CSV导入时报错的根因

第一次按文档复现时,我在数据导入环节就卡住了。现象是CSV文件里明明有数据,但导入报错说找不到字段或者节点冲突。排查链路从文件编码开始,最后定位到两个问题:一个是文件是含BOM的UTF-8编码,解析时把首列列名带上了不可见字符;另一个是同一实体在CSV里出现了多行,重复创建后触发了唯一性冲突。

这两个问题在真实项目中非常典型。我的解决方法是:先统一文件编码为无BOM的UTF-8,再在写入前对实体名做一次归一化校验,保证同一主键只写入一次。如果你想避开同类问题,这里有个实用经验:写导入脚本之前,先单独把数据读出来看一眼列名和数据类型,不要默认源文件干净。

5.2 分词与实体匹配失败

问答链路里我遇到最影响体验的问题是分词。比如“阿司匹林肠溶片”这种带剂型的药物名,标准词典可能只收录“阿司匹林”,分词后“肠溶片”变成普通词,导致实体抽取只能抽到半个名字。更麻烦的是“小儿感冒颗粒”这类包含修饰语的长词条,词典不维护完整词表的话,几乎百分百切错。

我当时的排错思路是:把问答模块单独做日志输出,把每个问句的分词结果、候选实体和最终匹配节点全部打印出来。当答案错误时,往回看分词结果基本就能定位是词典问题、实体链接问题还是模板问题。用这个办法,我一次性发现了二十多个需要补充进自定义词典的药物和疾病词条。后续迭代时,我建议你把分词结果和匹配节点也加入监控日志,这比单看最终答案好不好用要高效得多。

5.3 模板覆盖不足的应对

规则模板的天然短板是覆盖不全。这个项目内置了一些问法模板,但换成口语化表达就可能匹配失败。比如“血压高的人该注意什么”这句话,系统可能无法直接判断它等价于“高血压的注意事项”。最初我选择不断加模板,后来发现模板越加越多,维护成本急剧上升。

后来我换了一个思路:先对问句做标准化改写,把口语说法映射成核心句式,再做意图判断。比如“血压高”“血压偏高”先归一化为“高血压”,“该注意什么”“要当心什么”归一化为“注意事项”。这样模板数量不需要膨胀,意图识别准确率反而提高了。这个经验我觉得很值得分享:在垂直领域问答里,与其堆模板,不如先做问句改写。

6. 项目能怎么改进:把Demo做成可用产品的方向

6.1 同义词与实体链接增强

在探索这个项目的基础上,我做了一轮增强实验,核心是补全同义词映射。医疗领域的同义词非常多,包括中文别名、英文缩写、商品名和通用名。比如不同品牌的降压药商品名可能完全不同,但本质成分是同一种药。如果不做同义词归一,用户在问答时就会频繁遭遇“查不到”。

我的实现方式是在图谱中给实体节点增加“别名”属性,在实体链接阶段先按标准名精准匹配,匹配不到再按别名模糊匹配。这一层补充并不复杂,但对问答体验的提升非常明显。如果你也打算在医疗垂直领域做产品,建议在一开始就把同义词表设计成独立可维护的数据文件,让医学人员可以持续补充。

6.2 从单轮到多轮与澄清

原始项目的交互基本限定在单轮问答,用户问一句,系统答一句,如果实体有歧义会直接选第一个。这在真实医疗场景里风险很大。一个“心脏疼”的用户可能指的是心绞痛,也可能指的是胃食管反流引起的胸痛,系统不应该直接给答案,而应该反问澄清。

我在这套系统上增加了一个很轻量的澄清机制:当候选实体超过一个,并且各候选实体之间的答案差异较大时,先把候选实体列给用户,让用户选择后再继续查询。虽然这只是多轮交互的雏形,但已经把体验从“命令式查询”往前推了一大步。医疗问答涉及生命安全,宁可多问一句,也不要武断给结论。

6.3 评估方式与个人体会

最后聊聊评估。这类项目的效果不能只看几个例子跑通了就算完。我建议整理一份一百到两百条的真实问句集,覆盖各个意图类型,人工标注正确答案,再批量跑系统统计准确率和无答案率。我对原系统做了一轮实测,统计结果显示,在模板覆盖范围内准确率很高,但出了覆盖范围,无答案率明显上升。这正是规则系统的分水岭。

改进之后,我用同义词、问句改写和澄清机制把无答案率往下压了不少。整个过程让我对医疗知识图谱问答有了更深的理解:这类系统的天花板其实不取决于算法多先进,而取决于知识库的完整度和业务规则的合理设计。如果后续要进入生产环境,我会考虑引入更强的实体识别模型,同时保留规则系统作为兜底,让两种方案互相补位。

如果你正在研究这个项目,我的建议很直接:先别急着换模型,把知识图谱的数据质量打磨到位,把提问模板吃透,再用真实问句做一轮完整的评估。这个项目最好的地方就是给了你一个完整的基线,让你能在上面快速验证自己的想法。等这一轮跑完,你对医疗问答里那些“看似简单但藏满细节”的问题,心里就有底了。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进