
简介本资源为基于Python的医疗知识图谱问答系统完整源码面向计算机相关专业学生与开发者可用于毕业设计、期末大作业或课程设计场景帮助解决知识图谱构建与智能问答落地的实际问题。压缩包共70个文件约51.62MB以32个py源码文件为核心辅以json数据、pkl模型、docx文档、bat启动脚本及txt说明等覆盖知识抽取、意图识别、实体识别与图谱构建等模块结构清晰便于二次开发。目前已有95人学习下载。项目含详细代码注释新手也能看懂作者自述为手打98分高分项目经严格调试可稳定运行下载后简单部署即可使用。读者可获得完整的医疗知识图谱问答实现方案、可复用的NLP处理脚本与排错思路适合作为毕设参考或课程实践模板具有较高的实际应用与学习价值。1. 从一份医疗问答源码说起这套东西到底能跑出什么如果你正在找一份能直接跑起来的 Python 毕业设计又不想拿那种“增删改查 登录注册”的烂大街模板交差那这套基于医疗知识图谱的问答系统源码值得花时间拆一遍。它的核心链路很清晰用户输入一句自然语言问诊式的问题系统先做意图识别再从医疗知识图谱里检索实体和关系最后拼出一句人话回答。整条链路涉及 BERT 意图分类、BiLSTM 命名实体识别、Neo4j 图数据库查询、Flask 接口封装技术栈覆盖了 NLP 和知识图谱两个方向答辩的时候能讲的东西比纯 Web 项目多得多。这套源码的目录结构里能看到nlu、intent_recg_bert、knowledge_extraction、build_kg、itchat_app.py这些模块说明它不是只有一个空壳界面而是把“知识抽取 → 图谱构建 → 语义理解 → 问答推理”整条流水线都写进去了。适合谁适合计算机相关专业做毕设的学生也适合想入门知识图谱问答但不知道从哪下手的新手。下面我按实际部署和调试的顺序把这份资源拆开讲。2. 环境准备与依赖安装先把 requirements.txt 读明白2.1 这份源码的技术栈拆解拿到压缩包解压之后先别急着pip install花五分钟把目录结构和依赖文件过一遍能省掉后面很多返工。从文件清单来看这套系统大致分成四层第一层是数据层data和processed_data目录存放原始医疗文本和预处理后的数据build_baiduie_data.py负责把原始语料转成模型训练需要的格式。第二层是知识抽取层knowledge_extraction下面有bilstm模块用 BiLSTM 做命名实体识别把医疗文本里的疾病、症状、药物、检查项等实体抽出来。第三层是图谱构建层build_kg和build_kg_utils.py负责把抽取出来的实体和关系写入图数据库。第四层是应用层nlu做意图识别intent_recg_bert用 BERT 做分类itchat_app.py和local.py提供问答入口。requirements.txt里通常会包含torch、transformers、flask、py2neo、sklearn、pandas、numpy这些包。这里有个血泪经验transformers和torch的版本必须匹配否则加载 BERT 模型时会报ImportError或者AttributeError。我一般会先建一个干净的虚拟环境再按requirements.txt装装完手动验证一下torch.cuda.is_available()是否可用。2.2 虚拟环境与依赖安装实操不要用系统 Python 直接装依赖冲突会让你怀疑人生。下面是我习惯的流程# 创建虚拟环境Python 版本建议 3.8 或 3.9 python -m venv medkg_env # 激活环境Windows medkg_env\Scripts\activate # 激活环境Linux / macOS source medkg_env/bin/activate # 升级 pip避免旧版解析依赖出问题 pip install --upgrade pip # 安装项目依赖 pip install -r requirements.txt装完之后重点检查三个包torch能不能正常 import、transformers版本是否和torch兼容、py2neo能不能连上 Neo4j。如果requirements.txt里没锁版本建议手动固定比如transformers4.6.0配torch1.8.0是比较稳的组合。参数上torch的 CUDA 版本要和本机显卡驱动匹配没显卡就用 CPU 版训练会慢但推理能跑。提示如果pip install卡在torch下载可以先单独装torch再装其余依赖避免超时导致整个安装回滚。2.3 Neo4j 图数据库的安装与配置这套系统的知识图谱存在 Neo4j 里所以必须先把图数据库跑起来。常见做法是下载 Neo4j Community 版解压后改conf/neo4j.conf把dbms.default_listen_address设成0.0.0.0然后启动服务。默认端口是 7474浏览器和 7687Bolt 协议。启动之后在build_kg相关脚本里会看到连接配置通常是这样的from py2neo import Graph # 连接 Neo4j地址、用户名、密码按实际改 graph Graph(bolt://localhost:7687, auth(neo4j, your_password)) # 测试连接 print(graph.run(RETURN 1).data())这里的关键参数是bolt://localhost:7687如果你把 Neo4j 装在另一台机器上就改成对应 IP。密码是首次登录时强制修改的别用默认的neo4j/neo4j否则服务会拒绝连接。跑通这一步图谱构建才有地方落。3. 知识图谱构建从医疗文本到图数据库的完整链路3.1 BiLSTM 命名实体识别的训练与推理knowledge_extraction/bilstm这个目录是整条链路的起点。它的任务是从医疗文本里识别出实体比如“高血压”“头痛”“阿司匹林”分别对应疾病、症状、药物。BiLSTM 做 NER 的思路是把句子转成词向量正向和反向各跑一层 LSTM拼接后接 CRF 或 softmax 做序列标注。训练脚本一般长这样import torch import torch.nn as nn class BiLSTMNER(nn.Module): def __init__(self, vocab_size, embed_dim, hidden_dim, num_tags): super().__init__() # 词嵌入层把词索引映射成稠密向量 self.embedding nn.Embedding(vocab_size, embed_dim) # 双向 LSTM捕捉上下文信息 self.lstm nn.LSTM(embed_dim, hidden_dim, bidirectionalTrue, batch_firstTrue) # 输出层映射到标签空间 self.fc nn.Linear(hidden_dim * 2, num_tags) def forward(self, x): emb self.embedding(x) out, _ self.lstm(emb) return self.fc(out)参数说明vocab_size是词表大小embed_dim一般设 128 或 256hidden_dim设 128 到 256 之间num_tags是实体标签数量比如 B-Disease、I-Disease、O 等。训练时学习率常用 1e-3batch size 设 32 或 64。如果显存不够把hidden_dim降到 64 也能跑只是精度会掉一点。训练完模型会保存成.pth文件推理时加载权重对输入句子做前向计算再解码成实体列表。这一步的输出会作为图谱构建的输入。3.2 实体关系抽取与 Neo4j 写入实体识别出来之后还要抽关系。医疗领域常见的关系有“疾病-症状”“疾病-药物”“疾病-检查”“疾病-科室”等。源码里build_kg_utils.py大概率封装了关系抽取和写入逻辑。常见做法是用规则匹配加模板比如“XX 的症状包括 YY”就抽取出(XX, has_symptom, YY)三元组。写入 Neo4j 的代码通常是这样from py2neo import Graph, Node, Relationship graph Graph(bolt://localhost:7687, auth(neo4j, your_password)) def create_entity(name, label): # 查找或创建实体节点 node graph.nodes.match(label, namename).first() if not node: node Node(label, namename) graph.create(node) return node def create_relation(head, rel_type, tail): # 创建两个实体之间的关系 rel Relationship(head, rel_type, tail) graph.create(rel) # 示例写入一条疾病-症状关系 disease create_entity(高血压, Disease) symptom create_entity(头痛, Symptom) create_relation(disease, HAS_SYMPTOM, symptom)这里要注意Neo4j 的节点标签和关系类型一旦写入后续查询语句必须严格对应。比如你写的是HAS_SYMPTOM查询时就不能写成has_symptom大小写敏感。另外批量写入时建议用事务否则几万条数据一条条 create 会非常慢。注意如果build_kg脚本跑完发现图谱是空的先检查 Neo4j 服务是否启动、密码是否正确、Bolt 端口是否被防火墙拦住。3.3 图谱数据校验怎么确认构建没白干写完数据别急着跑问答先在 Neo4j 浏览器里执行几条 Cypher 验证一下// 统计各标签节点数量 MATCH (n) RETURN labels(n) AS label, count(*) AS cnt ORDER BY cnt DESC; // 查看某疾病关联的症状 MATCH (d:Disease {name: 高血压})-[:HAS_SYMPTOM]-(s:Symptom) RETURN s.name; // 检查孤立节点 MATCH (n) WHERE NOT (n)--() RETURN n LIMIT 10;如果Disease节点数量是 0说明实体识别或写入环节断了如果关系数量远少于节点数量说明关系抽取规则覆盖不够。这一步是很多新手会跳过的但跳过之后问答效果差你根本不知道是模型问题还是图谱问题。4. 意图识别与问答推理BERT 分类器怎么接进问答链路4.1 BERT 意图分类的训练与调参intent_recg_bert目录负责意图识别也就是判断用户这句话到底在问什么。比如“高血压吃什么药”是问药“头痛挂什么科”是问科室“糖尿病有哪些症状”是问症状。意图分类做不准后面的图谱查询就会查错方向。BERT 做分类的典型代码from transformers import BertTokenizer, BertForSequenceClassification import torch # 加载预训练 BERT 和分词器 tokenizer BertTokenizer.from_pretrained(bert-base-chinese) model BertForSequenceClassification.from_pretrained(bert-base-chinese, num_labels8) # 编码输入 inputs tokenizer(高血压吃什么药, return_tensorspt, paddingTrue, truncationTrue, max_length64) # 前向计算 outputs model(**inputs) # 取 logits 最大值对应的类别 pred torch.argmax(outputs.logits, dim1) print(pred.item())参数上num_labels要和你的意图类别数一致常见有 8 到 12 类。max_length设 64 够用医疗问句一般不长。训练时学习率设 2e-5 到 5e-5epoch 设 3 到 5 轮太多会过拟合。如果训练集只有几百条建议做数据增强或者用规则兜底。4.2 问答主流程从 NLU 到图谱查询再到答案生成local.py和nlu模块串起了整个问答流程。用户输入一句话系统先走意图识别再走实体识别然后根据意图选择对应的 Cypher 查询模板从 Neo4j 取数据最后拼成自然语言回答。核心逻辑大概是这样def answer_question(question): # 第一步意图识别 intent intent_model.predict(question) # 第二步实体识别 entities ner_model.predict(question) # 第三步根据意图和实体构造查询 if intent query_drug: cypher MATCH (d:Disease {name: $name})-[:HAS_DRUG]-(drug) RETURN drug.name elif intent query_symptom: cypher MATCH (d:Disease {name: $name})-[:HAS_SYMPTOM]-(s) RETURN s.name else: return 抱歉我暂时无法回答这个问题。 # 第四步查询图谱 result graph.run(cypher, nameentities[0]).data() # 第五步组织回答 if result: return f{entities[0]}的{intent}包括 、.join([r[drug.name] for r in result]) return 知识库中暂未收录相关信息。这段代码的关键在于意图和查询模板的映射关系。意图分类错了查询模板就错了答案自然不对。所以调试的时候要分开验证先单独测意图识别准确率再单独测实体识别 F1 值最后再端到端测问答。哪一环掉链子一目了然。4.3 接口封装与前端联调itchat_app.py看起来是接微信的入口local.py是本地命令行入口。如果你要做 Web 演示常见做法是用 Flask 包一层from flask import Flask, request, jsonify app Flask(__name__) app.route(/qa, methods[POST]) def qa(): data request.get_json() question data.get(question, ) answer answer_question(question) return jsonify({answer: answer}) if __name__ __main__: app.run(host0.0.0.0, port5000)前端随便写个输入框和展示区POST 请求发到/qa就行。注意跨域问题如果前端单独跑在另一个端口Flask 这边要加 CORS 头或者用 Nginx 做反向代理。5. 避坑与排查这套源码跑不起来时先看这几条5.1 模型加载报错版本不匹配是头号杀手现象运行intent_recg_bert或bilstm推理脚本时报ImportError: cannot import name xxx from transformers或者AttributeError: module torch has no attribute xxx。原因transformers和torch版本不兼容或者requirements.txt里写的版本和你实际装的不一致。BERT 相关 API 在不同版本间变动很大比如BertForSequenceClassification的返回结构在 3.x 和 4.x 里就不一样。解决先pip show transformers torch看实际版本再对照源码里用的 API 反推应该装哪个版本。常见稳定组合是transformers4.6.0torch1.8.0或者transformers4.10.0torch1.9.0。实在拿不准就按requirements.txt严格装别自己升级。5.2 Neo4j 连接失败密码和端口是两个高频坑现象跑build_kg脚本时卡住或者报py2neo.errors.ConnectionUnavailable: Cannot connect to Neo4j。原因Neo4j 服务没启动、密码不对、Bolt 端口 7687 没开放或者连接地址写成了http://而不是bolt://。解决先在浏览器打开http://localhost:7474确认服务活着。然后检查脚本里的Graph(bolt://localhost:7687, auth(neo4j, 密码))密码是你首次登录时改的那个不是默认的neo4j。如果 Neo4j 装在远程服务器确认 7687 端口在防火墙里放行了。5.3 中文编码问题Windows 下的老毛病现象读取医疗语料时乱码或者写入 Neo4j 的中文变成问号。原因Windows 默认编码是 GBK而源码里的文件可能是 UTF-8open()没指定encoding参数就会出问题。解决所有文件读写都显式加encodingutf-8比如open(data/medical.txt, r, encodingutf-8)。Neo4j 本身支持 UTF-8不用额外配置。如果控制台输出乱码在脚本开头加import sys; sys.stdout.reconfigure(encodingutf-8)。5.4 图谱查询返回空先查数据再查代码现象问答接口不报错但永远返回“知识库中暂未收录相关信息”。原因可能是图谱里根本没数据也可能是实体识别出来的名字和图谱里的节点名字对不上比如图谱里存的是“高血压病”用户问的是“高血压”。解决先在 Neo4j 浏览器里跑MATCH (n) RETURN count(n)确认节点数量。如果数量正常再检查实体识别输出和节点名称是否一致。常见做法是加一层别名映射把“高血压”映射到“高血压病”或者用模糊匹配MATCH (d:Disease) WHERE d.name CONTAINS $name。5.5 训练 loss 不下降学习率和数据质量都要看现象BiLSTM 或 BERT 训练时 loss 震荡或者一直不降。原因学习率太大、batch size 太小、标注数据有错、或者词表和模型不匹配。解决先把学习率降到 1e-4 或 2e-5 试一轮再检查标注数据里有没有标签错位最后确认词表覆盖了医疗领域的高频词如果词表太小很多词会变成UNK模型学不到东西。6. 进阶玩法把问答系统从“能跑”推到“能演示”6.1 用规则兜底提升问答命中率模型不是万能的尤其是训练数据只有几百条的时候意图识别和实体识别都会翻车。我一般会在模型前面加一层规则兜底如果用户问句里出现了明确的疾病名和“吃什么药”“什么症状”“挂什么科”这些关键词直接走规则匹配不经过模型。这样能保证高频问题的准确率模型只处理规则覆盖不到的边角情况。import re def rule_based_answer(question): # 匹配“XX吃什么药”模式 drug_pattern re.compile(r(.?)吃什么药) match drug_pattern.search(question) if match: disease match.group(1) return query_graph(disease, HAS_DRUG) # 匹配“XX有哪些症状”模式 symptom_pattern re.compile(r(.?)(有哪些|有什么)症状) match symptom_pattern.search(question) if match: disease match.group(1) return query_graph(disease, HAS_SYMPTOM) return None规则兜底的好处是可控坏处是覆盖有限。实际部署时我会先跑规则规则返回 None 再走模型这样既保证高频问题稳定又保留模型的泛化能力。6.2 图谱查询优化加索引和限制返回条数Neo4j 默认不给节点属性建索引数据量大了之后MATCH (d:Disease {name: 高血压})会全图扫描慢得让人想砸键盘。上线前记得给常用查询字段建索引CREATE INDEX disease_name_index FOR (d:Disease) ON (d.name); CREATE INDEX symptom_name_index FOR (s:Symptom) ON (s.name);另外查询结果别一次性全返回加个LIMIT 20避免某个疾病关联了几百个症状直接把接口拖死。如果要做分页用SKIP和LIMIT配合。6.3 答辩演示的加分技巧毕设答辩不是比谁模型精度高是比谁讲得清楚、演示得流畅。我建议准备三个层次的演示第一层展示知识图谱的可视化在 Neo4j 浏览器里跑几条 Cypher让老师看到实体和关系第二层展示问答接口的端到端效果准备十个高频问题现场输入看回答第三层展示代码结构重点讲 NLU 和图谱查询怎么衔接的这是整套系统的技术核心。还有一个细节提前把 Neo4j 和 Flask 服务都启动好别现场等加载。模型加载慢的话可以先把模型预热或者用缓存把常见问题的答案存起来。从那以后我每次做演示项目都会提前跑一遍完整流程确认没有“在我机器上能跑”的玄学问题。希望帮到你。本文还有配套的精品资源点击获取