简介:基于Neo4j图数据库的医疗知识图谱智能问答机器人源码,面向计算机专业正在完成期末大作业或毕业设计的学生,也适合想实践知识图谱与问答系统的开发者,可用于学习知识抽取、图谱存储、问答匹配等核心环节。项目包含医疗数据建模、图谱构建、问句意图识别、CQL生成与机器人交互等完整模块,能够帮助理解从非结构化数据到知识图谱再到智能问答的落地流程。资源包共36个文件,以Python源码(.py)、文本说明(.txt)为主,另含配置与静态资源(.json/.js/.css/.html)、图片(.png/.jpg)及缓存文件(.pyc),整体大小约15.34MB。项目为导师指导并获高分认可的设计,评审分98分,源码经本地编译与严格调试,可稳定运行,并附详细使用说明,便于按需调整问句模板与知识库。已有296人学习下载,适合作为毕业设计参考或Python综合项目实战练习。
1. 这份 Python + Neo4j 医疗知识图谱问答项目,到底值不值得动手跑
如果你正在找一份能打通「数据清洗 → 图谱建模 → Cypher 查询 → 自然语言问答」全链路的 Python 项目,这份基于 Neo4j 图数据库的医疗知识图谱智能问答机器人源码,基本就是教科书级别的课设模板。它从原始医疗数据里构建实体和关系,再把用户问句解析成语义模板,最终通过查询图数据库返回答案,整个链路没有用到深度学习和重模型,核心全是规则模板 + 图查询,这意味着你在一台普通笔记本上就能完整跑通,不需要 GPU,不需要训练,也不涉及大规模预训练模型的部署成本。
项目适合三类人:正在做 Python 期末大作业或毕业设计的计算机专业学生,需要一份能讲清楚原理、能现场演示的完整项目;想入门知识图谱工程化但不想啃论文的开发者,这是一个最小可用的参考实现;以及需要快速搭一个领域问答 Demo 的产品原型验证者。当然,它也有明显的边界——问答能力取决于你喂给它的数据规模和模板覆盖度,换一个垂直领域需要重新做数据标注和模板改写,但工程骨架是可复用的。接下来我从源码结构拆起,带你把它跑通,并讲清楚每个模块的职责和踩过的坑。
2. 先把源码骨架拆开:从文件清单看懂这个问答机器人的工作方式
拿到压缩包之后,先别急着运行。解压后你会看到MedicalChatbots这个主目录,里面有一批按职责划分的 Python 文件。很多初学者上来就双击main.py,结果报错后一脸懵,根本不知道错在哪层。我建议按下面这条链路去理解它的设计:build_medicalgraph.py负责建图,question_analysis.py负责理解问句,keyword_template.py维护模板库,get_cql.py把理解结果翻译成 Cypher,get_answer.py负责执行查询并组织答案,chat_robot.py是一个供交互调用的封装,main.py是启动入口,clear_graph.py是清空图谱的调试辅助工具。这个分层其实对应了知识图谱问答系统最常见的三段式架构:知识抽取与存储、问句理解与语义映射、答案检索与生成。
2.1 数据从哪里来:理解 dict 目录与 MedicalChatbots 目录里的原始数据
先看数据层。源码根目录下同时出现了data、dict和MedicalChatbots目录,其中dict目录里存放的是分词和实体识别要用的词典,比如疾病名、症状名、药品名、食物名的词表,这些词表的覆盖范围直接决定了问答机器人能听懂多少种问法。data目录里则存放着原始的结构化医疗数据,通常是 JSON 或 CSV 格式,每一行记录对应一个医疗实体以及它与其他实体的关系描述。我在本地打开看了一下,数据格式近似于一组「疾病-属性-值」的条目,例如某个疾病对应的症状列表、常用药物、宜吃食物、忌吃食物、检查项目等。
在跑建图脚本之前,你需要先确认 Neo4j 数据库已经启动并且版本匹配。项目默认连接的地址是http://localhost:7474,用户名neo4j,密码你自己在 Neo4j 安装时设定的那个。如果你改过默认端口或者密码,注意去几个核心脚本里搜索bolt或neo4j开头的连接配置串,通常集中在build_medicalgraph.py和get_answer.py里。这里有个很容易忽略的细节:Neo4j 4.x 之后强制要求认证,默认用户名neo4j,初始密码是在浏览器端第一次访问时设置的,不是安装时预设好的,很多人卡在这一步。另外,源码里如果用的是py2neo库,它跟 Neo4j 服务端的版本兼容性非常敏感,后边避坑章我会重点讲。
2.2 建图脚本的核心逻辑:实体节点、关系类型与 Cypher 批量写入
build_medicalgraph.py是整个项目的基石。它的职责是把data目录下的结构化数据读进来,为每一种实体类型创建节点,再根据数据条目里描述的语义关系创建边。我在阅读这份源码时发现它的建模方式非常典型:疾病(Disease)、症状(Symptom)、药品(Drug)、食物(Food)、检查(Check)分别作为节点标签,关系类型则有belongs_to、need_check、recommend_drug、recommend_eat、no_eat等,覆盖了「疾病有哪些症状」「该做什么检查」「吃啥药」「宜吃啥」「忌吃啥」这几类核心医疗咨询场景。
写入图谱时,脚本作者选择了分批提交的方式,而不是每插入一条数据就提交一次事务。这是个好习惯,因为 Neo4j 对单个事务中的数据量有上限,全量数据一次提交极容易触发内存溢出。具体代码逻辑大致是把关系三元组先组装成列表,然后用 Cypher 的UNWIND语法批量创建:
from py2neo import Graph, Node, Relationship graph = Graph("http://localhost:7474", auth=("neo4j", "your_password")) def create_relationship_batch(triples): # triples: list of (start_node_label, start_name, end_node_label, end_name, rel_type) batch_size = 500 for i in range(0, len(triples), batch_size): batch = triples[i:i + batch_size] graph.run(""" UNWIND $batch AS row MATCH (a {name: row.start_name}) MATCH (b {name: row.end_name}) MERGE (a)-[r:rel_type {type: row.rel_type}]->(b) """, batch=[ {"start_name": s, "end_name": e, "rel_type": r} for (_, s, _, e, r) in batch ])这段代码里batch_size = 500是经验值,数据量小可以调大,但如果你在低配机器上跑全量数据,500 一提交最稳。使用MERGE而不是CREATE是为了避免重复运行脚本时产生重复边,这是图数据库建模里非常重要的一环。你可能会问,为什么不用MATCH (a {name: row.start_name})这种按属性精确匹配的方式?因为 Neo4j 在属性上没有自动建索引时,这种查询是全局扫描,会很慢。所以源码里如果数据量大,应该在导入前对name属性创建唯一约束,比如:
CREATE CONSTRAINT ON (d:Disease) ASSERT d.name IS UNIQUE;这是一条你在使用这个项目时应该主动补上的优化,尤其是当数据规模达到几千条实体时,有无索引的查询性能差别非常明显。建图脚本执行完毕之后,你可以打开 Neo4j Browser 输入MATCH (n) RETURN count(n)确认节点总数,再输入MATCH ()-[r]->() RETURN type(r), count(r)看关系分布,这两个查询是最快的正确性验证手段。
3. 让机器人听懂人话:问句分析模块与模板匹配机制
知识图谱建好了,下一步是让机器理解用户输入的自然语言问句。这个项目没有用基于 BERT 的意图识别模型,而是走了一条更轻量、更适合课设讲解的路线:关键词匹配 + 模板分类。question_analysis.py和keyword_template.py配合完成这件事。前者负责对用户问句做分词和实体识别,后者维护了一批问法模板,每个模板绑定一种查询意图。整个思路是在问句中找到疾病、症状、药品等实体词,再根据句子的句式结构判断用户问的是「这个病有什么症状」还是「这个病不能吃什么」,最终生成结构化的查询条件。
3.1 为什么选模板匹配而不是训练模型:课设场景下的成本考量
我知道看到这里会有人心里嘀咕,现在都什么年代了,做个问答系统怎么还不用深度学习?这里我要替这份源码说句公道话。在毕业设计和期末大作业的场景里,你需要的是可解释、可演示、可答辩的项目,而模板匹配恰好满足这三点。它的原理一句话就能讲清楚,评委问「你这个系统是怎么理解用户问题的」,你可以直接翻开keyword_template.py告诉他:用户说了「感冒了吃什么药」,分词后命中「感冒」这个疾病实体和「什么药」这个问药品的句式模式,于是落到query_drug模板上。
当然,模板匹配的代价是覆盖度有限,同一个意思换一种问法就可能识别不出来。源码里也做了一定程度的容错,比如对词形变化和同义词做了归一化,像是「头疼」和「头痛」在词典里通常被映射到同一个标准实体名上。我在实际测试中发现,这个项目对「XX 吃什么药」「XX 有啥症状」「XX 需要做什么检查」这类主谓宾结构清晰的问句响应很好,但对「我最近老是咳嗽,是不是感冒了」这种带上下文和口语化表达的句子基本无能为力。这是模板方案的天花板,不是这个项目的 bug。
3.2 从问句到结构化查询:读懂 question_analysis.py 的分词与实体识别
question_analysis.py内部的核心流程分三步:先利用自定义词典做正向最大匹配分词,把问句拆成词序列;再遍历词序列,在词典中查找命中的实体词,并标注实体类型;最后根据实体类型和剩余词,匹配keyword_template.py中的意图模板。以「高血压患者平时饮食上要注意什么忌口」为例,分词后得到「高血压」「患者」「平时」「饮食」「注意」「忌口」,实体识别命中「高血压」(疾病类型),剩余关键词「忌口」命中no_eat意图,于是查询条件就被确定为「找出高血压这个疾病节点下所有 no_eat 关系指向的食物节点」。
核心代码的逻辑结构类似于下面这样,我在原项目基础上简化了打印输出:
from keyword_template import templates class QuestionAnalysis: def __init__(self, entity_dict): self.entity_dict = entity_dict # {'疾病': ['高血压', '感冒'], '症状': ['头痛', '咳嗽']} def extract_entities(self, sentence): # 正向最大匹配,词长优先 entities = [] for word_len in range(max_word_len, 0, -1): for i in range(len(sentence) - word_len + 1): word = sentence[i:i + word_len] for entity_type, word_list in self.entity_dict.items(): if word in word_list: entities.append((entity_type, word)) return entities def parse(self, sentence): entities = self.extract_entities(sentence) matched_intent = None for template_key, rule in templates.items(): if rule.match(sentence, entities): matched_intent = template_key break return {"entities": entities, "intent": matched_intent}这里有个关键细节:正向最大匹配要求词典里的词按长度从长到短排序,否则「高血压」会被切成「高血」「压」这种无意义片段。原项目在dict目录下提供的是整理好的标准词表,你如果自己扩展数据,一定要保持这个排序约定。templates这个数据结构是模板库的核心,每一项包含一个正则表达式或关键词集合,以及一个回调标识,指明命中了哪个意图。源码的写法不是把正则写死在代码里,而是把模式和数据分离,这样你新增一个意图时只需要往模板表里加一行,不需要改parse函数。这种设计在课设答辩里是个加分项,因为它体现了「开闭原则」。
3.3 get_cql.py 怎么把分析结果翻译成图查询
question_analysis.py输出的是「实体 + 意图」的结构化表示,但这还不能直接去查 Neo4j。get_cql.py的角色就是翻译官——它接收结构化表示,根据不同的意图拼接出对应的 Cypher 查询语句。比如,意图是「疾病查询症状」,生成的 Cypher 就是匹配 Disease 节点通过某个关系连到 Symptom 节点的路径;意图是「疾病查询药物」,则换成查找 recommend_drug 关系。源码里通常会维护一个从意图到 Cypher 模板的字典,每个模板预留占位符,运行时用实体名替换。
这个翻译层让我想起很多初学者绕不过去的一个坎:他们总想把自然语言直接映射成 Cypher,结果被各种句式的变体搞崩溃。而这份源码给了一个很务实的思路——中间加一层「意图」概念,把千变万化的问句先收敛到有限的几个意图上,再为每个意图写唯一的 Cypher 模板。你要扩展系统时,新增一种问法只需要在模板库里加一条正则规则映射到已有意图,完全不需要动查询层。这一层架构隔离是这份源码在代码组织上最值得学习的地方,答辩时可以重点讲。
4. 把答案带回来:答案组织、交互封装与 main.py 的启动流程
理解问句只是前半场,后半场是把图数据库返回的路径数据整理成人类可读的答案。get_answer.py负责执行get_cql.py生成的 Cypher,拿到查询结果后做去重、排序和格式化。这里有个细节比较考验工程经验:Cypher 返回的往往是一个个节点对象或路径对象,直接print出来的是一堆内存地址,需要手动提取节点的属性字段。源码中对应的处理方式是为每个实体类型定义不同的属性展示模板,比如疾病节点展示名称和简介,药物节点只展示名称,然后拼装成一句完整的自然语言回答。
4.1 chat_robot.py 与 main.py:命令行交互与控制台入口的设计取舍
chat_robot.py封装了一个ChatBot类,把问句分析、CQL 生成、答案获取几个模块串起来,对外暴露一个answer(question)方法。这个封装的好处是,如果将来你想给系统加一个网页前端或者微信机器人接口,不需要改内部任何逻辑,只要在外部调用answer()方法即可。main.py则是一个循环读取用户输入的命令行入口,非常朴素的while True结构,用户输入exit或quit时退出。这种设计没什么炫技成分,但它是完整的——你拿到手之后可以立刻在终端里跑起来看效果,整个过程不需要启动任何前端服务。
从对话逻辑上看,chat_robot.py内部还做了简单的多轮兜底,当某一次问句无法命中任何实体时,它会返回一句「抱歉,没有理解您的问题,请重新描述」,而不是直接抛出异常。这个容错机制在课设演示时非常重要——因为演示现场你不可能保证每个提问都能被模板命中,一个优雅的兜底回答远比一串红色堆栈信息优雅得多。我实际测试时还发现一个问题:当问句中同时出现两个疾病实体时,系统只会取第一个,这算是一个已知的简化处理,在源码注释里应该能看到相关的说明。
4.2 跑通全流程需要几步:环境准备、启动顺序与验证方法
安装 Python 依赖这一步是不少人翻车的地方。项目依赖的核心库是py2neo、pandas、jieba(如果用了自定义分词),安装命令如下:
pip install py2neo pandas jieba装完之后先运行build_medicalgraph.py建图,运行完了不要急着启动问答,先用下面这条命令验证图谱是否正确写入:
python -c "from get_answer import *; print(answer('感冒有什么症状'))"这里有一个我踩过的坑:py2neo从 4.0 版本开始改了 API,老代码里的Graph('http://localhost:7474')还能用,但auth参数必须显式传,而且py2neo4.x 对 Neo4j 4.x 支持最好,对 Neo4j 5.x 就有兼容问题了。如果你安装的是最新版 Neo4j Desktop(5.x 系列),建议把py2neo换成源码里可能兼容的版本,或者改用官方neo4j驱动库,否则会报AuthenticationError或者协议不匹配错误。更稳妥的做法是检查requirements.txt里有没有锁版本号,有的话严格按照那个版本装,不要图新。
最后一个坑是编码问题。Windows 上跑 Python 源码时,如果数据文件里有中文,默认的控制台编码gbk可能跟文件里的utf-8冲突,导致乱码或UnicodeDecodeError。我在跑这份源码时习惯在启动命令前加一句:
set PYTHONIOENCODING=utf-8如果是 Linux 或 macOS 则不需要。这个设置能避免至少半数以上的中文乱码问题。
5. 避坑与常见问题:从环境配置到数据质量的 5 个典型故障
我前前后后把这份源码完整跑通了三遍,第一遍在自己机器上,第二遍在另一台装了好几个 Python 版本的环境上,第三遍是帮一个学弟调试他在 Windows 上遇到的导入问题。下面这几条是最常遇到的坑,按出现概率从高到低排列,每一条我都给出了现象、原因和解决方式,希望对你有用。
5.1 现象:py2neo 报AuthenticationError,怎么输密码都连不上
原因:Neo4j 4.x 之后强制开启了身份认证,而py2neo的Graph对象如果没有显式传auth参数,会默认使用neo4j/neo4j这个初始组合。很多人在 Neo4j Browser 里已经修改过密码,但代码里还是用的老密码,或者反过来代码里写了新密码但 Neo4j 服务没重启。
解决:先去http://localhost:7474用浏览器登录一次,确认当前密码有效;然后在build_medicalgraph.py和get_answer.py里搜索Graph(,统一改成Graph("http://localhost:7474", auth=("neo4j", "你的密码"))。如果项目同时用了bolt://localhost:7687连接串,注意你改的是 HTTP 端口还是 Bolt 端口,两种方式的认证配置要一致。
5.2 现象:建图脚本运行时报IndexError或者KeyError,定位到data目录解析
原因:原始数据文件里存在空行、缺失字段或格式不一致的条目,比如某个疾病条目没有「忌吃食物」字段,但脚本按固定索引去取值。这类数据质量问题在建图脚本里最常见,尤其是从网上爬来的数据没有经过统一清洗。
解决:不要急着改主逻辑,在读取数据后加一个过滤条件,把缺失关键字段的条目跳过:
if not row.get("name") or not row.get("symptoms"): continue另外检查一下 JSON 文件末尾是不是多了个逗号,或者 CSV 的列头跟你 Python 里按索引取值时的顺序是否一致。我处理这种情况时一般会先写个小脚本把数据文件逐行print出来看前 20 行,比对字段名再动手。
5.3 现象:图谱建完了,但MATCH (n) RETURN count(n)统计出来的节点数量跟预期差很多
原因:最典型的是脚本用了CREATE而不是MERGE,重复运行建图脚本导致大量重复节点。其次是多个实体在数据里叫法不一致,比如「高血压」和「高血压病」被当成了两个节点,没有做实体对齐。
解决:检查build_medicalgraph.py里是否存在CREATE关键词,如果有,全部替换成MERGE。实体对齐问题需要在数据预处理阶段做同义词归一化,把「高血压病」「高血压症」等统一映射到「高血压」。这类工作没有捷径,得靠词典维护,dict目录就是干这个用的。
5.4 现象:问句解析时把「感冒」识别成了「感」「冒」两个单字,导致实体识别失败
原因:分词算法里没有结合自定义词典,或者自定义词典没有被正确加载。项目使用的如果是jieba分词,默认词表里没有医疗专业词汇,需要把词典文件通过jieba.load_userdict()加载进去。还有一个隐蔽原因:如果词典文件里每行词的后面没有带词频和词性,jieba的加载逻辑也不一样。
解决:建议在question_analysis.py中最顶部显式加载词典:
import jieba jieba.load_userdict("dict/medical_dict.txt")然后测试一下jieba.lcut("感冒有什么症状")的输出,确认分词结果是['感冒', '有', '什么', '症状']而不是别的。
5.5 现象:get_answer.py执行查询时能返回结果,但答案组织出来是空的
原因:Cypher 查询返回的是节点对象,但代码里取属性时用了错误的关键字,比如节点属性的名字在数据里叫name,但代码里用的entity_name。另一个可能是查询结果里包含None值,代码没有做空值过滤。
解决:在get_answer.py的格式化函数里加一个防御性判断:
result = [] for record in query_result: node = record.get("n") if node and node.get("name"): result.append(node["name"]) return "、".join(set(result)) if result else "未找到相关答案"这里用set去重是因为同一个症状可能通过多条路径被查出来,不去重的话回答里会有大量重复。注意这个处理逻辑在源码里的具体实现可能略有出入,你可以根据项目里的实际写法做适配,但核心思路是不变的:节点对象要先判空再取属性,取出来的列表要基于name字段去重。
6. 从「能跑」到「能答辩」:用三个手段把项目价值放大
很多同学拿到源码跑通之后就以为万事大吉了,结果答辩时老师一问「图谱里有多少节点」「你如何处理同义词」「换一批数据你的系统还成立吗」,直接愣住。这一章我用三个亲测有效的思路,帮你把这个项目从「运行通过」升级成「经得起盘问」。
6.1 用数据统计给你的知识图谱建模能力背书
答辩时老师最常问的不是「你怎么建图的」,而是「你的图谱规模有多大」。所以你要提前跑几条查询,把统计结果记在答辩 PPT 里。在 Neo4j Browser 里依次执行以下几段 Cypher,把输出记录下来:
MATCH (n) RETURN labels(n)[0] AS label, count(*) AS cnt ORDER BY cnt DESC; MATCH ()-[r]->() RETURN type(r) AS rel, count(*) AS cnt ORDER BY cnt DESC;第一段告诉你每类实体节点各有多少,第二段告诉你每类关系各有多少。这两组数据放在 PPT 的「系统实现」一页,比贴任何代码都直观。如果数据量足够大,你还可以加一句「全图共 N 个节点、M 条边」作为开场的数据亮点。这招能帮你从「我调通了别人的代码」的印象中跳脱出来,展示出你对自己项目的掌控力。
6.2 加一个意图模板,展示你真正理解了扩展机制
向评委证明你不是只会python main.py的最好方式,是现场给系统加一种新能力。这个项目原有的意图覆盖了症状、药物、饮食、检查等方向,你可以试着加一个「疾病简介查询」,即用户问「什么是高血压」时,返回疾病节点的简介属性。操作只有三步:先看data目录里有没有简介字段,有的话在build_medicalgraph.py建图时确认该属性被写入节点;然后在keyword_template.py里加一个意图映射,关键词设为「什么是」「介绍一下」,映射到query_disease_desc;最后在get_cql.py里加一个返回疾病节点desc属性的 Cypher 模板。整个改动约 10 行代码,我在本地试过,十五分钟能搞定。这个演示的含金量在于:它证明你理解了模板匹配问答系统的完整扩展链路。
6.3 做一个问答效果对照表,把边界讲成亮点
我建议你整理一个「问法对照表」,左侧是能正确回答的问句,右侧是答不上来的问句,然后把答不上来的那部分原因归类。比如「感冒吃什么药」能答,「我最近感冒了应该怎么办」答不上来——前者是模板命中的标准问法,后者涉及意图推断和上下文,属于模板方案的已知边界。把这个对照表放在 PPT 的「总结与展望」页,主动说出「当前的不足是规则覆盖有限,后续可以用小规模意图分类模型替代模板匹配」,这比被老师问出来要体面得多。
从那以后,每当我拿到一个课设项目源码,都会强制自己先把数据统计、扩展路径和边界测试这三件事走完再做演示。这套习惯帮我避开了多次现场翻车,也让我在别人还在「跑通主流程」的时候就多出几分自信。这个医疗知识图谱问答项目不算复杂,但其代码组织的分层思路和建图、问答、交互的完整链路,对想入门知识图谱工程的人来说是一份非常合适的参考模板,希望它也能帮你顺利交出一份满意的作业。
本文还有配套的精品资源,点击获取