ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于RAG与知识图谱的医疗问答系统:从毕设源码到LoRA微调实战

基于RAG与知识图谱的医疗问答系统:从毕设源码到LoRA微调实战 简介这是一套面向计算机相关专业学生与项目实战学习者的医疗问答系统完整源码包以RAG检索增强生成结合大模型为核心技术路线适合用作毕业设计、课程大作业或进阶练手项目。资源共75个文件包含10个Python脚本、7个Jupyter Notebook、19个txt数据与说明、18张png界面截图以及json、yaml、md等配置与文档压缩包约84.66MB覆盖数据处理、模型微调、知识图谱构建与Web界面等模块。已有138人学习关注。项目经导师指导并通过评审源码本地编译可运行、调试完整读者可据此掌握RAG问答链路搭建、医疗语料处理、LoRA微调与前端交互实现并借助文档与截图快速理解整体架构与运行方式。1. 从一份 98 分毕设拆起RAG 医疗问答系统到底能跑出什么效果如果你正在做计算机方向的毕业设计或者想找一个能把 RAG、大模型微调、知识图谱三样东西串起来的实战项目这套基于 RAG 与大模型的医疗问答系统源码值得认真拆一遍。它不是那种只跑通一个 demo 就交差的玩具工程而是把 Neo4j 知识图谱、LangChain 检索链路、ChatGLM 大模型、LoRA 微调、NER 实体识别全部揉进了一个可运行的 Web 系统里。评审分 98 分这个数字背后反映的是工程完整度——有登录注册、有管理后台、有图谱可视化、有微调脚本、有推理入口前后端和数据处理链路都齐了。我拿到这份源码的第一反应是目录结构比想象中规整。webui.py负责界面build_up_graph.py建图ner_model.py做实体抽取finetune_hf.py和lora_finetune.ipynb管微调nl2cypher系列文件处理自然语言转图查询。这套组合基本覆盖了当前医疗问答系统的主流技术栈。适合谁一是毕设需要完整工程支撑的同学二是想练手 RAG 全链路但不知道从哪下手的开发者三是需要理解知识图谱怎么和大模型配合的从业者。下面我按实际拆解顺序把这份资源从环境搭建到微调推理再到踩坑排查一层层讲透。2. 环境搭建与依赖安装把 Python 环境、Neo4j 和模型权重一次配到位2.1 先看清技术栈和目录分工这套系统的技术栈可以拆成四层。第一层是数据层medical.json和medical_new_2.json是医疗知识原始数据rel_aug.txt和ner_data_aug.txt是关系增强和实体增强后的训练语料。第二层是图谱层build_up_graph.py负责把结构化数据灌进 Neo4jnl2cypher.py负责把用户问题转成图查询语句。第三层是模型层model目录放 ChatGLM 权重finetune_hf.py和lora_finetune.ipynb做 LoRA 微调inference_hf.py做推理。第四层是应用层webui.py和login.py搭 Web 服务user_data_storage.py管用户数据。理解这个分层很重要因为后面排错时你需要快速定位问题出在哪一层。比如问答答非所问可能是检索层没召回正确实体也可能是模型层微调数据质量不够还可能是图谱层关系没建对。先分清层再逐层排查比盲目改代码高效得多。常见做法是先跑通最小闭环装依赖、起 Neo4j、灌数据、跑推理脚本、再起 Web 界面。不要一上来就微调微调是最耗时且最容易翻车的一步放在最后做。2.2 依赖安装与 Neo4j 配置requirements.txt在根目录和model目录下各有一份先装根目录的。我一般会建一个独立虚拟环境避免和系统 Python 冲突。# 创建虚拟环境Python 版本建议 3.8 到 3.10 python -m venv med_qa_env source med_qa_env/bin/activate # Windows 用 med_qa_env\Scripts\activate # 安装核心依赖 pip install -r requirements.txt # 单独确认几个容易版本冲突的包 pip install torch2.0.1 transformers4.30.2 langchain0.0.200 pip install py2neo2021.2.3 neo4j5.8.0 pip install gradio3.39.0 # webui.py 依赖这里有几个参数需要说明。torch版本要和你的 CUDA 版本匹配如果只有 CPU 就用torch2.0.1cpu。transformers版本不能太低ChatGLM 的trust_remote_code加载方式在 4.28 以下会有兼容问题。py2neo和neo4j驱动建议都装上因为不同脚本可能用了不同驱动。Neo4j 的配置是这套系统里最容易卡住新手的地方。你需要先安装 Neo4j Desktop 或者社区版然后修改build_up_graph.py里的连接参数# build_up_graph.py 中的连接配置按你的实际环境改 from py2neo import Graph # 默认 Bolt 端口 7687HTTP 端口 7474 # 用户名密码是你首次启动 Neo4j 时设置的 graph Graph(bolt://localhost:7687, auth(neo4j, 你的密码)) # 建图前先清空旧数据避免重复灌入 graph.run(MATCH (n) DETACH DELETE n)逻辑说明DETACH DELETE n会删除所有节点和关系第一次跑可以加上后续增量更新时要去掉这行。参数方面bolt://是 Neo4j 的二进制协议端口比 HTTP 快很多灌数据时优先用 Bolt。如果你的 Neo4j 跑在远程服务器上把localhost换成对应 IP同时确认 7687 端口没有被防火墙拦掉。灌数据的顺序也有讲究。先跑processjson.py把medical.json清洗成图谱需要的格式再跑build_up_graph.py建节点和关系。我见过有人直接拿原始 JSON 往 Neo4j 里塞结果字段名对不上建出来一堆空节点。processjson.py里会做字段映射和去重这一步不能跳。提示Neo4j 首次启动后必须改默认密码否则py2neo连接会报AuthError。改完密码记得同步更新所有脚本里的连接配置包括nl2cypher.py和webui.py。3. RAG 检索链路与知识图谱联动从用户提问到 Cypher 查询的完整走法3.1 RAG 在这里到底怎么用很多人一提 RAG 就想到向量数据库加相似度检索但这套系统的 RAG 链路更偏「图谱增强检索」。它的核心思路是用户提问后先用 NER 模型从问题里抽医疗实体比如「糖尿病」「胰岛素」然后拿这些实体去 Neo4j 里查关联节点和关系把查到的结构化知识作为上下文再拼进 ChatGLM 的 prompt 里生成回答。这样做的好处是回答有据可依不会像纯向量检索那样召回一堆语义相似但事实错误的内容。医疗领域对事实准确性要求高图谱增强比纯向量更稳。代价是依赖 NER 抽取的准确率如果实体没抽出来后面整条链路就断了。ner_model.py和ner_result目录下的 notebook 是这套链路的关键。ner_model.py定义了模型结构结果1.ipynb到结果4.ipynb记录了不同阶段的抽取实验。tag2idx.npy是标签映射文件roberta.txt里应该是 RoBERTa 的配置或词表路径。我一般会先跑ner_model.py确认模型能加载再用ner_data.py里的测试样本验证抽取效果。3.2 nl2cypher 的实现细节与参数调整nl2cypher.py和nl2cypher_data.txt这套文件解决的是「自然语言转图查询」的问题。用户问「高血压吃什么药」系统需要生成类似MATCH (d:Disease {name:高血压})-[:HAS_DRUG]-(drug) RETURN drug.name的 Cypher 语句。# nl2cypher.py 核心逻辑简化示意 def nl_to_cypher(question, ner_result): # 第一步从 NER 结果里提取实体和意图 entities ner_result.get(entities, []) intent classify_intent(question) # 分类查药物、查症状、查科室等 # 第二步根据意图选择 Cypher 模板 if intent query_drug: template MATCH (d:Disease {{name:{disease}}})-[:HAS_DRUG]-(drug) RETURN drug.name elif intent query_symptom: template MATCH (d:Disease {{name:{disease}}})-[:HAS_SYMPTOM]-(s) RETURN s.name else: template MATCH (d:Disease {{name:{disease}}}) RETURN d.description # 第三步填充实体返回 Cypher return template.format(diseaseentities[0]) if entities else None逻辑说明这段代码的关键在classify_intent它决定了走哪个查询模板。实际项目里意图分类可能是一个独立的小模型或者规则匹配nl2cypher_data.txt里应该存了训练样本。参数方面entities[0]这种写法假设第一个实体就是疾病名但实际提问可能包含多个实体比如「糖尿病和高血压哪个更严重」这时候需要更复杂的实体消歧逻辑。nl2cypher_data_test.txt是测试集nl2cypher_data-Copy1.txt看起来是备份或实验版本。我建议先用测试集跑一遍看生成的 Cypher 能不能在 Neo4j 里正确执行。常见失败情况是实体名和数据库里的节点属性对不上比如数据库存的是「2型糖尿病」用户问的是「二型糖尿病」这时候需要加一层别名映射。3.3 把检索结果拼进大模型 prompt检索到图谱知识后下一步是拼 prompt。webui.py里应该有这段逻辑我把它单独拎出来说明# 构造 RAG prompt 的典型写法 def build_rag_prompt(question, graph_context): prompt f你是一个医疗问答助手。请根据以下知识回答用户问题。 知识 {graph_context} 用户问题{question} 回答要求只基于上述知识回答不要编造。如果知识中没有相关信息直接说不知道。 return prompt逻辑说明graph_context是从 Neo4j 查出来的结构化文本可能是一组药物名、症状描述或科室信息。prompt 里明确要求「只基于上述知识回答」这是减少大模型幻觉的关键。参数方面graph_context的长度要控制太长会挤占模型上下文窗口一般建议不超过 1000 字。如果查出来的关系太多需要做排序和截断优先保留直接关联的节点。langchainchatglm.png这张图应该展示了 LangChain 和 ChatGLM 的集成方式。从文件名推测项目用了 LangChain 的LLMChain或RetrievalQA来串联检索和生成。如果你对 LangChain 不熟可以先看webui.py里实际调用的部分比看文档快。注意图谱查询返回空结果时不要直接把空上下文丢给模型否则模型会自由发挥。我一般会在代码里加判断如果graph_context为空直接返回「未找到相关信息」不走模型生成。4. LoRA 微调与推理finetune_hf.py 和 lora_finetune.ipynb 的实操参数4.1 微调数据准备与格式检查微调数据在lora_data目录下questions.csv和dev.txt是问答对peft_data.txt可能是 PEFT 格式的训练数据。finetune_demo目录下应该有示例数据。微调前必须确认数据格式和finetune_hf.py里的读取逻辑匹配。# 检查微调数据格式的快速脚本 import pandas as pd # questions.csv 通常是 question, answer 两列 df pd.read_csv(lora_data/questions.csv) print(df.columns.tolist()) print(df.head(3)) # 确认没有空值空值会导致训练时 loss 异常 print(df.isnull().sum()) # 如果 answer 列太长需要截断ChatGLM 的 max_length 一般设 512 或 1024 df[answer] df[answer].str[:512]逻辑说明questions.csv的列名要和finetune_hf.py里dataset.map的字段对应不对应会报KeyError。dev.txt是验证集用来监控训练过程中是否过拟合。peft_data.txt如果是 JSON 行格式每行一个{instruction:..., input:..., output:...}需要确认finetune_hf.py用的是哪种解析方式。微调运行.ipynb是微调的入口 notebook里面应该记录了完整的训练命令和参数。我建议先在这个 notebook 里跑一遍小规模训练比如只取 100 条数据、训练 1 个 epoch确认流程能走通再上全量。4.2 LoRA 参数怎么设finetune_hf.py里 LoRA 的配置直接决定微调效果和显存占用。以下是关键参数和我常用的取值参数含义建议值说明lora_rankLoRA 秩8 或 16越大拟合能力越强显存也越大lora_alpha缩放系数32通常是 rank 的 2 到 4 倍lora_dropoutDropout 率0.1防止过拟合learning_rate学习率2e-4LoRA 常用 1e-4 到 5e-4num_train_epochs训练轮数3 到 5医疗数据量小轮数不宜多per_device_train_batch_size单卡 batch4 或 8根据显存调不够就减gradient_accumulation_steps梯度累积4等效增大 batch# 微调启动命令示例实际参数以 finetune_hf.py 的 argparse 为准 python finetune_hf.py \ --model_name_or_path ./model \ --data_path ./lora_data/questions.csv \ --lora_rank 8 \ --lora_alpha 32 \ --lora_dropout 0.1 \ --learning_rate 2e-4 \ --num_train_epochs 3 \ --per_device_train_batch_size 4 \ --gradient_accumulation_steps 4 \ --output_dir ./lora_output逻辑说明model_name_or_path指向model目录里面是 ChatGLM 的权重文件。data_path指向问答对 CSV。output_dir是 LoRA 权重保存位置训练完会生成adapter_model.bin和adapter_config.json。参数方面如果显存不够优先降per_device_train_batch_size再降lora_rank。gradient_accumulation_steps可以在不增加显存的情况下等效增大 batch size但训练速度会变慢。lora_finetune.ipynb里可能有更详细的训练过程记录包括 loss 曲线和验证集评估。我一般会重点看 loss 是否稳定下降如果 loss 震荡厉害可能是学习率太高或者数据噪声太大。4.3 推理验证与效果对比微调完成后用inference_hf.py做推理验证。这个脚本应该支持加载原始模型和 LoRA 权重两种模式方便对比微调前后的效果。# inference_hf.py 推理调用示意 from peft import PeftModel from transformers import AutoModel, AutoTokenizer # 加载基座模型 tokenizer AutoTokenizer.from_pretrained(./model, trust_remote_codeTrue) base_model AutoModel.from_pretrained(./model, trust_remote_codeTrue).cuda() # 加载 LoRA 权重 model PeftModel.from_pretrained(base_model, ./lora_output) model model.eval() # 推理 response, history model.chat(tokenizer, 糖尿病患者应该注意什么, history[]) print(response)逻辑说明PeftModel.from_pretrained会把 LoRA 权重合并到基座模型上。trust_remote_codeTrue是 ChatGLM 系列必须的参数因为它的模型定义不在 transformers 标准库里。model.chat是 ChatGLM 的对话接口返回response和更新后的history。参数方面history用于多轮对话单轮问答传空列表即可。验证微调效果时我一般会准备 20 到 30 个测试问题分别用原始模型和微调后模型跑一遍人工对比回答质量。重点看三个方面是否还胡编乱造、是否更贴合医疗术语、是否更简洁。如果微调后反而变差大概率是数据质量有问题或者训练轮数太多导致过拟合。提示LoRA 权重文件不大通常几十 MB方便备份和切换。但加载 LoRA 需要基座模型一致换基座模型后 LoRA 权重不能直接用。5. 避坑与排查登录、图谱、微调、WebUI 四个高频翻车点5.1 登录模块报错或用户数据丢失现象运行login.py或webui.py后注册用户提示成功但登录失败或者重启服务后用户数据消失。原因user_data_storage.py和user_credentials.json负责用户数据持久化。如果user_credentials.json路径写的是相对路径而启动脚本的工作目录不对就会读写到错误位置。另外如果 JSON 文件没有写权限注册时写入会静默失败。解决把user_credentials.json的路径改成绝对路径或者在启动脚本里先os.chdir到项目根目录。检查文件权限确保运行用户有读写权限。我一般会在user_data_storage.py里加一行print(os.path.abspath(credential_file))启动时确认路径正确。5.2 Neo4j 连接超时或建图失败现象跑build_up_graph.py时报Connection refused或ServiceUnavailable或者建图过程中断只灌了一半数据。原因Neo4j 服务没启动、端口不对、密码错误、或者数据量太大导致单次事务超时。medical.json如果节点数上万一次性CREATE会撑爆事务内存。解决先确认 Neo4j 服务状态用浏览器打开http://localhost:7474能访问说明服务正常。密码错误就重置密码并同步更新脚本。数据量大时改成分批灌入每 500 条提交一次# 分批建图避免单事务过大 batch_size 500 for i in range(0, len(nodes), batch_size): batch nodes[i:ibatch_size] tx graph.begin() for node in batch: tx.run(CREATE (n:Disease {name: $name, desc: $desc}), **node) graph.commit(tx) print(f已灌入 {ilen(batch)} 条)5.3 微调时显存溢出或 loss 不下降现象finetune_hf.py跑到一半报CUDA out of memory或者训练几个 epoch 后 loss 一直在 2.0 以上不降。原因显存溢出通常是 batch size 太大或max_length太长。loss 不降可能是学习率太低、数据格式不对、或者标签没对齐。解决先把per_device_train_batch_size降到 1gradient_accumulation_steps升到 16确认能跑通再逐步加。max_length从 512 开始试不要一上来就 2048。loss 不降时检查数据里answer字段是否为空空标签会导致模型学不到东西。学习率可以尝试从 2e-4 调到 5e-4但不要超过 1e-3。5.4 WebUI 启动后页面空白或接口 500现象webui.py启动后浏览器打开是空白页或者提问后返回 500 错误。原因Gradio 版本不兼容、静态文件路径不对、或者后端推理接口抛异常。img目录下的图片如果路径写错页面加载会卡住。解决先看终端报错信息Gradio 常见问题是gradio3.x和4.x的 API 不兼容确认requirements.txt里的版本并严格安装。静态图片路径用os.path.join(os.path.dirname(__file__), img, logo.jpg)这种写法避免相对路径问题。接口 500 一般是推理代码抛异常在webui.py的问答函数里加try...except把错误打印出来定位到具体是哪一层挂了。注意这四个坑里Neo4j 连接和显存溢出是最常见的。我建议第一次跑的时候按「先通链路、再调效果」的顺序不要一上来就追求完美效果。6. 进阶技巧用 NER 结果反哺图谱查询与微调数据筛选这套系统里有一个容易被忽略的联动点ner_result目录下的抽取结果不仅能用于 RAG 检索还能反过来优化图谱和微调数据。我拆完结果1.ipynb到结果4.ipynb后发现不同阶段的 NER 实验其实记录了模型在医疗实体识别上的迭代过程。把最终版的 NER 结果导出可以做两件很有价值的事。第一件是用 NER 结果做图谱补全。build_up_graph.py建图时依赖medical.json里的结构化字段但原始数据可能漏掉一些实体关系。用 NER 模型对medical.json里的描述文本做一遍抽取把新发现的「疾病-症状」「疾病-药物」关系补进图谱能提升检索召回率。具体做法是遍历medical.json的desc字段用ner_model.py抽实体再和已有节点做匹配# 用 NER 结果补全图谱关系的思路 from ner_model import predict_entities for item in medical_data: desc item.get(desc, ) entities predict_entities(desc) for ent in entities: # 如果实体在图中已存在建立关系 graph.run( MATCH (d:Disease {name: $disease}), (e {name: $entity}) MERGE (d)-[:RELATED_TO]-(e), diseaseitem[name], entityent[text] )逻辑说明MERGE而不是CREATE避免重复建关系。predict_entities是ner_model.py里的抽取函数返回实体列表。参数方面ent[text]是实体文本实际使用时还要做实体对齐比如「心梗」和「心肌梗死」要映射到同一个节点。第二件是用 NER 结果筛选微调数据。questions.csv里的问答对质量参差不齐有些问题根本不包含医疗实体这种数据对微调帮助不大。我一般会用 NER 模型过一遍问题只保留至少包含一个医疗实体的样本这样微调数据更聚焦训练效率也更高。peft_data.txt和nl2cypher_data.txt也可以用同样方式清洗。还有一个实用技巧是缓存图谱查询结果。nl2cypher.py每次都要连 Neo4j 查一遍如果同一个问题被反复问可以加一层内存缓存把question - graph_context的映射存起来。医疗问答场景下高频问题很集中缓存能明显降低响应延迟。我一般用functools.lru_cache或者简单的字典缓存注意设置过期时间避免图谱更新后缓存不一致。从那以后我每次拆这类 RAG 项目都会先把 NER 模块单独跑一遍确认实体抽取的覆盖率和准确率再往下走检索和生成。因为 NER 是整条链路的入口入口不准后面再优化也是白费功夫。希望帮到你。本文还有配套的精品资源点击获取
返回列表