简介:本资源为基于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里没锁版本,建议手动固定,比如transformers==4.6.0配torch==1.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(浏览器)和 7687(Bolt 协议)。
启动之后,在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, bidirectional=True, batch_first=True) # 输出层,映射到标签空间 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 或 256,hidden_dim设 128 到 256 之间,num_tags是实体标签数量(比如 B-Disease、I-Disease、O 等)。训练时学习率常用 1e-3,batch 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, name=name).first() if not node: node = Node(label, name=name) 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_labels=8) # 编码输入 inputs = tokenizer("高血压吃什么药", return_tensors="pt", padding=True, truncation=True, max_length=64) # 前向计算 outputs = model(**inputs) # 取 logits 最大值对应的类别 pred = torch.argmax(outputs.logits, dim=1) print(pred.item())参数上,num_labels要和你的意图类别数一致,常见有 8 到 12 类。max_length设 64 够用,医疗问句一般不长。训练时学习率设 2e-5 到 5e-5,epoch 设 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, name=entities[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(host="0.0.0.0", port=5000)前端随便写个输入框和展示区,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 反推应该装哪个版本。常见稳定组合是transformers==4.6.0+torch==1.8.0,或者transformers==4.10.0+torch==1.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-8,open()没指定encoding参数就会出问题。
解决:所有文件读写都显式加encoding="utf-8",比如open("data/medical.txt", "r", encoding="utf-8")。Neo4j 本身支持 UTF-8,不用额外配置。如果控制台输出乱码,在脚本开头加import sys; sys.stdout.reconfigure(encoding="utf-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 服务都启动好,别现场等加载。模型加载慢的话,可以先把模型预热,或者用缓存把常见问题的答案存起来。从那以后我每次做演示项目,都会提前跑一遍完整流程,确认没有“在我机器上能跑”的玄学问题。希望帮到你。
本文还有配套的精品资源,点击获取