资讯动态

基于Python的医疗知识图谱问答系统:课程设计资源与实战指南

发布时间:2026/9/28 9:23:39 来源:尧图企业网站定制
简介这是一份面向计算机相关专业学生与知识图谱入门者的医疗知识问答系统课程设计资源围绕Python实现从知识图谱构建到对话问答的完整链路。包内共58个文件以15张png截图、15个txt词典、6个py脚本、6个xml配置及docx设计报告为主另有json数据与pyc缓存压缩包约20.13MB结构上分为简单知识图谱、医疗知识图谱与问答机器人三个递进模块。资源包含设计报告、项目源码及数据、运行截图读者可据此了解实体关系抽取、词典构建与问句分类、答案检索的实现思路并直接运行调试。该问答系统无需训练、响应迅速但输出依赖预设模板灵活度有限作者也指出后续需结合深度学习模型优化。目前已有2569人学习下载适合作为课程设计参考与知识图谱实战入门材料。1. 医疗知识图谱问答系统一份能跑通的 Python 课程设计资源如果你正在做知识图谱方向的课程设计大概率会遇到一个尴尬局面图谱构建的代码能找到问答系统的代码也能找到但两者拼在一起就是跑不通——实体识别对不上、关系查询返回空、答案模板硬编码到没法改。这份「基于 Python 实现的医疗知识图谱的知识问答系统」资源包恰好把这条链路完整串了起来从 medical.json 原始数据到 Neo4j 图谱构建再到基于规则模板的问答推理三个模块各自独立又能协同工作。它适合两类人一是需要快速交付课程设计的学生二是想理解「不训练模型也能做问答」这条技术路线的工程师。需要提前说明的是这套系统走的是规则匹配路线不是深度学习方案它的优势和边界都很明确后面会逐层拆开讲。2. 资源结构拆解三个模块各自解决什么问题2.1 simpleKnowledgeGraph 与 medicalKnowledgeGraph 的分工资源包里有两个图谱相关目录名字看起来很像但职责完全不同。1.simpleKnowledgeGraph是一个最小化的图谱演示核心文件只有simpleKG.py配合img目录下的十几张截图用来快速验证 Neo4j 连接和基础图查询是否正常。你可以把它理解成一个「冒烟测试」模块——先跑通这个再动医疗图谱的主逻辑。2.medicalKnowledgeGraph才是真正干活的模块。它的核心文件是buildKG.py配套data/medical.json作为原始数据源dict目录下放着八类实体词典drug.txt药品、check.txt检查项目、food.txt食物、symptoms.txt症状、producer.txt药品厂商、disease.txt疾病、deny.txt忌口、department.txt科室。这些词典不是摆设它们直接决定了后续实体识别能覆盖的范围。buildKG.py的工作流程是读取medical.json按字段抽取实体和关系然后通过 Neo4j 的 Python 驱动批量写入图数据库。medical.json的结构是典型的嵌套 JSON每个疾病条目下包含symptoms、check、drug、food、producer等数组字段。构建脚本会把这些数组展开成「疾病-症状」「疾病-检查」「疾病-药品」等关系边。这里有一个容易被忽略的细节dict目录下的词典和medical.json里的实体并不是严格一一对应的。词典的作用是在问答阶段做实体匹配而medical.json决定图谱里实际有哪些节点。如果词典里收了某个症状但 JSON 里没有对应数据问答时会出现「识别到了实体但查不到答案」的情况。常见做法是先用脚本统计 JSON 中各类实体的实际取值再和词典做差集把缺失的词补进去。2.2 KGchatbot 的四个核心文件与调用链3.KGchatbot是问答系统的入口模块包含四个 Python 文件和对应的__pycache__缓存。这四个文件的调用关系是一条单向链chatbot_graph.py是主入口负责启动对话循环。它接收用户输入后依次调用question_classifier.py做问题分类调用question_parser.py做查询语句组装最后调用answer_search.py执行查询并返回答案。question_classifier.py的核心逻辑是基于关键词匹配的问题类型判定。它把医疗问题分成若干类比如「症状查询」「药品查询」「检查项目查询」「忌口查询」等。分类依据是问题中是否包含特定触发词——比如问「感冒有什么症状」触发词是「症状」问「感冒吃什么药」触发词是「药」。分类结果决定了后续走哪条查询模板。question_parser.py接收分类结果和实体识别结果组装成 Neo4j 的 Cypher 查询语句。比如分类为「症状查询」、实体为「感冒」它会生成类似MATCH (d:Disease)-[:HAS_SYMPTOM]-(s:Symptom) WHERE d.name感冒 RETURN s.name的查询。answer_search.py负责执行 Cypher 并格式化输出。它把查询结果拼成自然语言句子返回给用户比如「感冒的症状包括头痛、咳嗽、发热」。整个链路没有用到任何机器学习模型全靠词典匹配和模板查询。这也是摘要里提到的「不需要花时间训练运行速度很快」的原因。但代价是灵活度受限——用户必须用系统能识别的表达方式提问换一种说法可能就匹配不上了。2.3 设计报告与截图的实际用途资源包里的设计报告.docx和img目录下的截图在课程设计场景下不是可有可无的附件。设计报告通常包含系统架构图、模块说明、数据库设计和测试用例这些内容直接对应课程设计的评分点。截图则记录了图谱构建结果和问答运行效果答辩时用来证明系统确实跑通了。如果你需要基于这份资源做二次开发建议先通读设计报告里的架构部分再对照代码理解每个模块的输入输出。截图可以帮你快速判断自己的运行结果是否正常——比如 Neo4j 浏览器里的图谱可视化效果、问答终端的输出格式。3. 环境搭建与图谱构建从 medical.json 到 Neo4j3.1 Neo4j 安装与 Python 驱动配置这套系统依赖 Neo4j 作为图数据库。社区版就够用安装完成后需要启动服务并记住默认的 Bolt 连接地址通常是bolt://localhost:7687和认证信息。Python 侧需要安装py2neo库这是 Neo4j 的 Python 驱动。常见做法是建一个虚拟环境避免和系统 Python 的其他包冲突python -m venv kg_env source kg_env/bin/activate # Windows 用 kg_env\Scripts\activate pip install py2neopy2neo的版本选择需要注意4.x 和 2021.x 的 API 有差异。资源包里的代码如果用的是Graph()直接传参的写法对应的是较老版本。如果安装最新版报错可以指定版本pip install py2neo2021.2.3安装完成后用一段最小代码验证连接from py2neo import Graph # 替换为你自己的密码 graph Graph(bolt://localhost:7687, auth(neo4j, your_password)) result graph.run(RETURN 1 AS num).data() print(result) # 期望输出 [{num: 1}]如果这一步返回了结果说明 Neo4j 服务和 Python 驱动都正常。如果报连接拒绝检查 Neo4j 服务是否启动如果报认证失败检查密码是否正确。3.2 buildKG.py 的数据抽取逻辑与执行buildKG.py的核心任务是把medical.json里的嵌套结构拍平成图数据库的节点和关系。先看一下medical.json的典型结构{ name: 感冒, symptoms: [头痛, 咳嗽, 发热], check: [血常规, C反应蛋白], drug: [感冒灵颗粒, 布洛芬], food: [清淡食物, 温水], producer: [某制药厂], department: [呼吸内科], deny: [辛辣食物, 油腻食物] }构建脚本会为每个疾病创建一个Disease节点为每个症状创建一个Symptom节点然后建立HAS_SYMPTOM关系。其他字段同理分别对应HAS_CHECK、HAS_DRUG、HAS_FOOD、HAS_PRODUCER、BELONGS_TO、HAS_DENY等关系类型。执行构建前建议先清空数据库里的旧数据避免重复写入from py2neo import Graph, Node, Relationship graph Graph(bolt://localhost:7687, auth(neo4j, your_password)) # 清空所有节点和关系 graph.run(MATCH (n) DETACH DELETE n) print(数据库已清空)然后运行构建脚本cd 2.medicalKnowledgeGraph python buildKG.py构建过程中如果数据量大可能会遇到写入超时。常见做法是分批提交每处理 100 条疾病数据提交一次事务。如果脚本里没有做分批可以手动改一下循环逻辑在循环体内加一个计数器每 100 次调用一次graph.commit()或类似操作。构建完成后在 Neo4j 浏览器里执行MATCH (n) RETURN count(n)确认节点数量。正常情况下节点数应该等于疾病数加上所有去重后的症状、药品、检查等实体数。3.3 词典文件与实体识别的对齐dict目录下的八个词典文件每个文件一行一个词条。这些词典在问答阶段被question_classifier.py用来做实体识别——具体来说是用最大正向匹配或简单的字符串包含来判断用户输入中是否出现了已知实体。这里有一个实操中很容易翻车的地方词典里的词条和medical.json里的实体值必须对齐。比如symptoms.txt里写了「头痛」但medical.json里某个疾病的症状字段写的是「头疼」问答时用户输入「头疼」就匹配不到图谱里的「头痛」节点。解决办法是在构建图谱之前先跑一个对齐检查脚本import json # 读取 medical.json with open(data/medical.json, r, encodingutf-8) as f: data json.load(f) # 收集 JSON 中所有症状实体 json_symptoms set() for item in data: for s in item.get(symptoms, []): json_symptoms.add(s) # 读取词典 with open(dict/symptoms.txt, r, encodingutf-8) as f: dict_symptoms set(line.strip() for line in f if line.strip()) # 找出差集 only_in_dict dict_symptoms - json_symptoms only_in_json json_symptoms - dict_symptoms print(f仅在词典中: {len(only_in_dict)} 个) print(f仅在 JSON 中: {len(only_in_json)} 个) print(仅在 JSON 中的示例:, list(only_in_json)[:10])如果「仅在 JSON 中」的数量较多说明词典覆盖不全需要把缺失的词补进词典。如果「仅在词典中」的数量较多说明词典里有图谱里不存在的实体这些词会导致问答时识别到实体但查不到答案建议清理掉。4. 问答链路拆解分类、解析、搜索三步走4.1 question_classifier.py 的问题分类机制question_classifier.py做的事情本质上是关键词匹配。它维护了一组问题类型和对应的触发词列表比如# 简化示意实际代码可能更复杂 symptom_keywords [症状, 表现, 有什么症状] drug_keywords [药, 吃什么药, 用药] check_keywords [检查, 做什么检查] food_keywords [吃什么, 饮食] deny_keywords [忌口, 不能吃什么]用户输入进来后分类器遍历这些关键词列表看输入中是否包含某个触发词。如果包含就返回对应的问题类型如果都不包含返回「无法识别」。这种做法的优点是快——一次字符串包含判断微秒级完成。缺点是脆——用户换一种问法就可能匹配不上。比如「感冒有哪些症状」能匹配到「症状」但「感冒会怎么样」就匹配不到。实际使用中可以通过扩充触发词列表来提高覆盖率。常见做法是收集一批真实问法人工标注后把高频表达加进触发词列表。但要注意不要过度扩充否则不同问题类型之间会出现触发词冲突——比如「感冒吃什么药」同时包含「药」和「吃什么」如果「吃什么」被归到了饮食类就会分错类。4.2 question_parser.py 的 Cypher 组装逻辑分类完成后question_parser.py根据问题类型和实体组装 Cypher 查询。每种问题类型对应一个查询模板# 症状查询模板 symptom_template MATCH (d:Disease)-[:HAS_SYMPTOM]-(s:Symptom) WHERE d.name {entity} RETURN s.name AS answer # 药品查询模板 drug_template MATCH (d:Disease)-[:HAS_DRUG]-(dr:Drug) WHERE d.name {entity} RETURN dr.name AS answer # 忌口查询模板 deny_template MATCH (d:Disease)-[:HAS_DENY]-(f:Food) WHERE d.name {entity} RETURN f.name AS answer 组装时把实体名填进模板的{entity}位置然后交给answer_search.py执行。这里有一个参数需要特别注意实体名里如果包含单引号或特殊字符直接拼进 Cypher 会导致语法错误。虽然医疗实体名一般不会出现这种情况但稳妥的做法是用参数化查询query MATCH (d:Disease)-[:HAS_SYMPTOM]-(s:Symptom) WHERE d.name $name RETURN s.name AS answer result graph.run(query, nameentity).data()参数化查询不仅避免了转义问题还能利用 Neo4j 的查询缓存重复查询同一类问题时速度更快。4.3 answer_search.py 的结果格式化与返回answer_search.py拿到查询结果后需要把结果列表拼成自然语言。最简单的做法是直接拼接def format_answer(question_type, entity, results): if not results: return f抱歉没有找到关于「{entity}」的相关信息。 items [r[answer] for r in results] if question_type symptom: return f{entity}的症状包括{、.join(items)}。 elif question_type drug: return f{entity}的常用药品有{、.join(items)}。 elif question_type deny: return f{entity}患者忌口{、.join(items)}。 else: return f查询结果{、.join(items)}。格式化逻辑看起来简单但有几个细节会影响体验。一是结果去重——同一个疾病可能有多条记录指向同一个症状查询结果里会出现重复项需要在拼接前用set()去重。二是结果数量控制——如果某个疾病的症状有几十个全部拼出来会很长常见做法是限制返回前 10 个超出部分用「等」结尾。三是空结果处理——查不到答案时不要返回空字符串给一个友好的提示。4.4 chatbot_graph.py 的对话循环与输入预处理chatbot_graph.py是用户直接交互的入口。它的核心是一个while True循环不断接收输入、处理、返回答案直到用户输入退出指令。输入预处理这一步值得单独拿出来说。用户在终端输入的问题可能带有空格、标点、大小写混杂直接拿去做实体匹配会降低命中率。常见做法是先做一轮清洗import re def preprocess(text): # 去除首尾空白 text text.strip() # 去除常见标点 text re.sub(r[。、], , text) # 统一小写如果词典里有英文词条 text text.lower() return text清洗后再做实体识别和问题分类命中率会明显提升。另外对话循环里建议加一个异常捕获避免单次查询报错导致整个程序退出while True: user_input input(请输入问题) if user_input in [退出, exit, quit]: break try: user_input preprocess(user_input) # 后续处理... except Exception as e: print(f处理出错{e}请换个问法试试。)5. 避坑与排查跑不通时先看这几条5.1 图谱构建报错 Constraint already exists现象重复运行buildKG.py时脚本在创建唯一性约束的那一步报错提示约束已存在。原因Neo4j 的唯一性约束是持久化的第一次创建后不会因为脚本重新运行而消失。脚本里如果没有做「存在则跳过」的判断第二次运行就会报错。解决在创建约束前先检查是否存在或者用CREATE CONSTRAINT ... IF NOT EXISTS语法Neo4j 4.4 支持。如果用的是老版本可以先执行CALL db.constraints()查看已有约束手动删除后重跑。5.2 问答时实体识别到了但返回空结果现象输入「感冒有什么症状」系统识别出了问题类型是「症状查询」、实体是「感冒」但返回「没有找到相关信息」。原因大概率是图谱里根本没有「感冒」这个节点或者节点名和识别到的实体名不完全一致比如图谱里是「感冒」但词典里是「感冒病」。解决先在 Neo4j 浏览器里执行MATCH (d:Disease {name:感冒}) RETURN d确认节点是否存在。如果不存在检查medical.json里是否有这条数据以及buildKG.py是否成功写入了。如果节点存在但名字不一致需要统一词典和图谱的命名。5.3 py2neo 连接超时或认证失败现象运行buildKG.py或问答脚本时报Connection refused或Authentication failed。原因Neo4j 服务没启动或者连接地址、端口、密码配置不对。解决先确认 Neo4j 服务状态。Windows 下在服务管理器里看 Neo4j 服务是否运行Linux 下用systemctl status neo4j查看。然后检查代码里的连接参数是否和 Neo4j 的实际配置一致。默认 Bolt 端口是 7687HTTP 端口是 7474别搞混了。5.4 中文编码导致读取 JSON 或词典报错现象运行脚本时抛出UnicodeDecodeError提示gbk codec cant decode byte。原因Windows 系统默认编码是 GBK而medical.json和词典文件通常是 UTF-8 编码。用open()读取时没有指定encodingutf-8Python 会用系统默认编码去解码遇到中文字符就报错。解决所有读取中文文件的地方都显式指定编码with open(data/medical.json, r, encodingutf-8) as f: data json.load(f)写入文件时同理用encodingutf-8打开。5.5 问题分类触发词冲突导致分错类现象输入「感冒吃什么药」系统返回了饮食建议而不是药品推荐。原因「吃什么」这个短语同时出现在饮食类触发词和药品类触发词里分类器按顺序匹配时先命中了饮食类。解决调整触发词列表的匹配顺序把更具体的触发词放在前面。比如「吃什么药」应该比「吃什么」优先匹配。或者改用最长匹配策略——在所有命中的触发词中选最长的那个作为分类依据。6. 进阶技巧把规则问答的边界摸清楚这套系统的核心限制在于「规则匹配」四个字。它不理解语义只做字符串匹配和模板查询。这意味着两件事第一用户必须用系统「认识」的方式提问第二系统只能回答预设好的问题类型。想让这套系统在实际场景里更好用有几个方向可以尝试。第一个方向是扩充实体识别的方式。目前用的是词典直接匹配可以引入jieba分词做辅助——先用分词把用户输入切成词序列再和词典做交集这样能处理「感冒的症状有哪些」这种触发词不在末尾的问法。import jieba def extract_entity(text, dict_path): with open(dict_path, r, encodingutf-8) as f: vocab set(line.strip() for line in f if line.strip()) words jieba.lcut(text) candidates [w for w in words if w in vocab] # 返回最长的匹配结果避免短词误匹配 return max(candidates, keylen) if candidates else None第二个方向是给查询结果加一层排序。比如查询「感冒吃什么药」图谱里可能返回十几种药但用户通常只关心最常用的那几种。可以在关系边上加一个weight属性构建图谱时根据药品的出现频率或临床优先级赋值查询时按权重降序返回。第三个方向是引入简单的同义词映射。医疗领域里同一个概念有多种说法比如「发热」和「发烧」、「头疼」和「头痛」。可以维护一个同义词表在实体识别前先把用户输入里的同义词替换成标准词synonym_map { 发烧: 发热, 头疼: 头痛, 拉肚子: 腹泻, } def normalize(text): for k, v in synonym_map.items(): text text.replace(k, v) return text这三个方向都不需要引入深度学习模型改动量可控但能明显提升问答的覆盖率和体验。最后说一个我在实际跑这类项目时养成的习惯每次改完词典或图谱数据先跑一遍「回归测试」——准备二十条左右的标准问题覆盖所有问题类型改完后逐条跑一遍看命中率和答案是否正确。这个习惯帮我省了很多次「改了一个词结果另一个问题挂了」的后悔药。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑