拓冰建站拓冰建站
首页 / 资讯中心 / 正文

基于Python的医疗知识图谱问答系统设计源码解析

简介基于Python的医疗知识图谱问答系统设计源码面向医疗信息化开发者和知识图谱/NLP学习者通过构建医疗知识图谱并实现问答分类、语义解析与答案检索解决医疗信息高效获取问题。压缩包共包含31个文件涵盖8个Python核心源码如知识图谱构建、问题分类、语义解析、答案搜索等模块、9个文本数据文件、9张PNG示意图、3个编译文件以及JSON配置和PPT演示文档整体约49.19MB目录结构清晰便于按模块研读。已有350人学习下载适合用来入门医疗知识图谱问答系统的完整实现流程。通过这套代码和数据读者可掌握医疗实体关系构建、问句意图识别、图谱查询等关键环节并借助图示与演示材料快速理解系统设计思路为进一步开发或二次改造提供扎实基础。1. 基于Python的医疗知识图谱问答系统到底在解决什么问题医生在门诊时被问最多的一句话不是疾病怎么治而是“我这个病平时要注意什么”。这类问题靠搜索引擎返回的是整页广告靠大模型回答的是生成式幻觉真正需要的是从结构化知识里把“高血压饮食禁忌”这条路径直接查出来。医疗知识图谱问答系统就是干这件事的先用图谱把疾病、症状、药品、饮食、检查方式之间的关系固化下来再用问答模块把自然语言翻译成图谱查询最后返回一条可溯源的答案。这套系统的核心难点不在Python本身而在“问句解析”和“图谱设计”这两层。网上能搜到的源码版本很多但大多数要么把实体识别写死成正则要么直接套用开放领域的HanLP模型换一个科室就废掉。可靠的做法是实体识别用规则加词典兜底意图分类用模板匹配图谱查询用参数化Cypher再留一个接口给大模型做兜底改写。本文就以这类设计为背景把从数据建模到本地跑通问答的完整路径拆开讲适合已经会用Python写脚本、但对知识图谱或NLP实战还没形成体系的人。2. 医疗知识图谱问答系统设计源码的骨架图谱存储与问句理解2.1 为什么用Neo4j存医疗数据而不是MySQL医疗问答的高频操作是“找关系”比如高血圧关联哪些药物、糖尿病的并发症有哪些。这类查询如果放关系型数据库里要么写多表JOIN要么反范式冗余查询路径一长性能就崩。图数据库把实体和关系都存成节点和边查询效率不随跳数指数级上升尤其在3跳以上的关联查询上有数量级优势。常见的源码实现里实体节点用英文或代码统一标记中文名放到属性里避免中文Key在Cypher拼接时引发编码或转义问题。CREATE (d:Disease {id: d1, name: 高血压, icd10: I10, department: 心血管内科}) CREATE (f:Food {id: f45, name: 芹菜, category: 蔬菜}) CREATE (d)-[:CAN_EAT {level: recommend, note: 富含钾}]-(f)这段Cypher创建了一个疾病节点和一个食物节点关系是CAN_EAT。属性里多放一个level字段后面的问答搜索才能根据不同意图过滤关系类型。实际源码里这条语句会被封装成create_graph(name)函数通过neo4j驱动批量执行。2.2 问句理解的两层结构先查实体再判意图医疗问句的难点在于实体常被省略比如“发烧了挂什么科”里没有药名也没有病名只有症状。所以源码里不能只做一个命名实体识别NER需要把“症状/疾病/药品/食物/检查”五类实体分开识别然后结合规则判断问句是对关系、属性还是路径的提问。import re from pyhanlp import HanLP class EntityExtractor: def __init__(self, disease_dict, symptom_dict, drug_dict): self.disease_words list(disease_dict.keys()) self.symptom_words list(symptom_dict.keys()) self.drug_words list(drug_dict.keys()) def extract(self, question): entities {} for category, words in [(disease, self.disease_words), (symptom, self.symptom_words), (drug, self.drug_words)]: for w in words: if w in question: entities[category] w break # 再通过HanLP的依存句法做一次兜底 if not entities: for term in HanLP.segment(question): if str(term.tag) in (nh, nt): entities.setdefault(disease, str(term.word)) return entities这段代码先做词典匹配匹配不到再让HanLP的命名实体识别兜底。词典匹配的优先级更高因为医疗术语在公开NLP模型里经常被切碎比如“病毒性心肌炎”会被切成“病毒”“心肌炎”两个词。参数说明disease_dict是由源码数据文件加载的字典Key是标准病名Value可以是ICD编码或相关属性集合。为什么用break而不是收集所有实体因为医疗问答里用户通常只关心一个目标实体多个实体往往是并列关系需要单独处理。这种“第一步粗识别”准确率能到90%以上剩余的疑难问句交给意图模板处理。3. 源码核心模块从问题解析到Cypher生成的完整链路3.1 QuestionParser模块把自然语言拆成语义槽一个标准的医疗问答源码会把QuestionParser设计成独立模块输入是原始问句输出是一个JSON结构包含实体名、实体类型、意图和查询参数。意图要分细symptom_of_disease、drug_for_disease、food_eat_for_disease、department_of_disease、treatment_of_disease。匹配方式用模板正则加关键词权重。import re class QuestionParser: def __init__(self): self.symptom_slot re.compile(r(什么症状|有哪些表现|什么表现)) self.food_slot re.compile(r(吃什么|忌口|饮食注意|不能吃)) self.drug_slot re.compile(r(吃什么药|用什么药|药物|治疗)) self.department_slot re.compile(r(挂什么科|去哪个科|就诊科室)) def parse(self, question, entities): intent unknown if self.symptom_slot.search(question) and disease in entities: intent symptom_of_disease elif self.food_slot.search(question) and disease in entities: intent food_eat_for_disease elif self.drug_slot.search(question): intent drug_for_disease elif self.department_slot.search(question): intent department_of_disease # 输出语义槽 return { entities: entities, intent: intent, question: question }正则模板的匹配顺序很关键food_slot要放在drug_slot之前因为“吃什么药”会被“吃什么”先命中。如果源码里顺序写反就会把用药意图误判成饮食禁忌。参数说明正则中“饮食注意”不能漏掉这是高频自然问法intent字段会被拼进Cypher的MATCH语句模板里。3.2 KnowledgeGraph模块模板查询与结果过滤有了语义槽下一步就是查图。源码中的KnowledgeGraph模块会维护一个intent_to_cypher字典每个意图对应一段Cypher模板。以“高血压吃什么水果”为例生成的Cypher如下MATCH (d:Disease {name: 高血压})-[:CAN_EAT {level: recommend}]-(f) RETURN f.name, f.category要点在于图谱关系上带了level属性比如recommend、avoid、normal。同一个食物对不同疾病可能是推荐也可能是禁忌所以不能只靠关系类型还要在模板里把level作为参数传入。Python侧的查询函数需要做结果封装from neo4j import GraphDatabase class KnowledgeGraph: def __init__(self, uri, user, password): self.driver GraphDatabase.driver(uri, auth(user, password)) def search(self, intent, entity_name): cypher_map { food_eat_for_disease: MATCH (d:Disease {name: $name})-[:CAN_EAT {level: recommend}]-(f) RETURN f.name as result, food_avoid_for_disease: MATCH (d:Disease {name: $name})-[:CAN_EAT {level: avoid}]-(f) RETURN f.name as result } cypher cypher_map[intent] with self.driver.session() as session: result session.run(cypher, nameentity_name) return [record[result] for record in result]注意session.run必须是参数化传值不能把entity_name直接拼进字符串。医疗数据里中文病名可能带有特殊括号直接拼接会触发Cypher注入或语法错误。record[result]返回的是节点或属性值源码里如果返回的是节点你需要用record[result][name]取属性这是个常见的取数坑。3.3 AnswerSearch模块把图谱结果转成自然语言答案图谱返回的是一堆列表不能直接甩给用户。源码里会维护一个答案模板字典比如“根据资料显示{disease}患者推荐食用的食物有{foods}”。如果列表为空还要给出兜底答案“未检索到相关信息建议就诊后由医生判断”。这是医疗系统里非常重要的安全边界宁可答不出来不能乱给答案。class AnswerSearch: def __init__(self): self.templates { food_eat_for_disease: 根据资料显示{disease}患者推荐食用的食物有{foods}。, drug_for_disease: 针对{disease}常用药物包括{drugs}。具体用药请遵医嘱。 } def generate(self, intent, entity_name, data): if not data: return 暂时没有检索到关于“{}”的直接答案建议到正规医院进一步检查。.format(entity_name) foods .join(data) return self.templates[intent].format(diseaseentity_name, foodsfoods)这段代码的特色是每类意图都有自己的答案模板且默认带一句“请遵医嘱”的免责声明。这是医疗问答系统与普通闲聊系统的最大区别答案安全性高于答案完整性。参数说明data列表里的元素如果带科室或者注意事项优先拼接在答案尾部不要放在句首。4. 部署运行与参数调优把源码在本地跑通的完整命令4.1 安装Python依赖与Neo4j环境源码项目通常会提供requirements.txt但有些关键库版本坑很多。以Neo4j 4.x为例驱动库neo4j需要用4.4以上版本否则Cypher语法会被识别错。推荐用虚拟环境安装避免污染系统Python。python -m venv venv source venv/bin/activate pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simplerequirements.txt里常见的依赖包括neo4j、Flask、jieba、pyhanlp。pyhanlp安装后还需要执行hanlp update下载数据模型这个过程经常失败建议直接改用pyhanlp-lite或LAC。参数说明-i指定了国内源地址这一步能避免超时。如果你是在Linux服务器上用非root用户安装需要先source venv/bin/activate再执行后续命令。4.2 Neo4j启动与图谱数据导入下载Neo4j Community版本后先修改neo4j.conf里的内存参数最小堆和最大堆设为相同值避免运行时动态扩容停顿。然后启动服务并导入源码自带的医疗数据CSV文件。cd /path/to/neo4j-community-4.4.18 bin/neo4j start bin/neo4j-admin import --databasemedical.db \ --nodesDiseaseimport/disease.csv \ --nodesDrugimport/drug.csv \ --relationshipsCAN_EATimport/disease_food.csv如果数据量不大更推荐在Python里用GraphDatabase驱动逐条写入这样可以在写入过程中做去重和数据清洗。disease.csv的字段最好是id,name,icd10,department每行一个疾病disease_food.csv包含start_id,end_id,level三个字段。导入完成后在neo4j.conf里把dbms.default_database改成medical.db。4.3 问答服务启动与接口调用源码一般会提供一个Flask服务核心路由是/qa参数是question。启动命令很简单但生产环境里需要改两个默认值FLASK_ENVproduction和HOST0.0.0.0。测试时用curl请求curl -X POST http://localhost:5000/qa \ -H Content-Type: application/json \ -d {question: 高血压患者哪些食物不能吃}返回的JSON中应包含entity、intent、answer三个字段。如果返回的intent是unknown说明模板没匹配上需要回到QuestionParser里加正则。如果intent对但结果为空则去Neo4j浏览器里手工执行对应Cypher检查关系方向是否反了。4.4 调优的三个关键参数第一LEVEL_FILTER开关早期验证阶段可以关闭所有CAN_EAT关系都返回等图谱数据质量稳定后再开启。第二实体匹配的SIMILARITY_THRESHOLD源码里如果用fuzzywuzzy做模糊匹配这个阈值默认设为85但医疗术语容错率要低一些建议设为90否则“血压高”和“高血压”容易被匹配错。第三答案列表的TOP_K返回数量不是越多越好一般限制在5个以内超过5个的答案用户根本读不完还容易暴露图谱数据不全的缺陷。提示调参时建议把每个意图的返回结果单独打印不要在终端里只看到最终答案。中间过程的调试信息可以写到日志文件里排查慢查询时很有用。5. 进阶技巧用LLM兜底和自动生成评测集传统模板问答的准确率上限在80%左右因为自然语言的变体实在太多。现在主流的做法是保留模板问答作为第一层对intent为unknown或者实体识别为空的问句转发给大模型做知识图谱外回答。比如基于DeepSeek的问答系统可以把模板匹配失败率从20%降到5%以下。from openai import OpenAI client OpenAI(base_urlhttps://api.deepseek.com, api_keyyour_key) def llm_fallback(question): prompt 你是医疗知识问答助手请基于通用医学知识回答不确定时说明需要就医。问题 question resp client.chat.completions.create( modeldeepseek-chat, messages[{role: user, content: prompt}], temperature0.1, max_tokens256 ) return resp.choices[0].message.content注意temperature必须设低医疗场景容不下创作发散。max_tokens也要限制避免回答超长。除了兜底LLM还能用来做答案验证把图谱返回的结构化答案拼成一句完整话让大模型判断“和问题是否相关”能有效拦截关系类型错误但语义错误的返回。验证系统整体效果不能靠肉眼。可以写自动化评测脚本准备100条带标准答案的问句分别跑模板和LLM兜底用F1或命中率评估。标准答案来自图谱里的标准属性值数据要在源码的测试目录里维护成JSON格式。[ {question: 高血压不能吃什么东西, expect_entities: [高血压], expect_intent: food_avoid_for_disease} ]跑完自动统计三个指标实体识别准确率、意图分类准确率、最终答案命中率。这三个指标分别对应系统三个模块的瓶颈位置。如果实体识别准但答案不中问题一定在图谱数据本身而不是代码。用这种验证方式你才能说清楚这套“基于Python语言的医疗知识图谱问答系统设计源码”到底能不能投到真实问诊场景里而不是停留在跑通演示阶段。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门