ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

RAG实战:本地知识库检索与LLM微调智能问答系统源码解析

RAG实战:本地知识库检索与LLM微调智能问答系统源码解析 简介本资源面向希望深入理解检索增强生成RAG与本地知识库问答的开发者与算法学习者提供一套基于本地知识库检索结合LLM微调的智能问答系统完整实战方案帮助解决通用大模型在垂直领域回答不精准、知识更新滞后的问题。压缩包共61个文件约30.12MB以31个txt数据与说明、13个Python脚本、7个JSON配置、3个bin模型权重及3个ipynb实验笔记为主另含PDF报告与界面截图覆盖数据预处理、向量检索、模型微调与Web交互等模块。已有1829人学习下载。源码包含ChatGLM-6B的P-Tuning v2与LoRA微调脚本、SBERT向量检索实现及WebUI界面读者可据此快速搭建原型掌握检索与生成融合的关键流程并参考目录结构进行定制化开发与优化。1. 从一份能跑通的 RAG 项目源码说起它到底解决了什么问题大模型答非所问、张口就编是很多团队落地智能问答时最先撞上的墙。这份「RAG-基于本地知识库检索LLM微调的智能问答系统实现」项目源码走的是检索增强生成加轻量微调的组合路线先把企业私有文档切块、向量化、存进本地向量库用户提问时先检索出相关片段再拼进提示词交给大模型生成答案同时用 LoRA 对底座模型做行业语料微调让模型更贴合垂直领域的表达习惯。它适合手里有一批 PDF、Word、Markdown 文档想快速搭一套能引用原文、可离线部署的问答系统的开发者。整套流程覆盖环境配置、知识库构建、检索链路、微调脚本到部署展示属于能直接照着复现的实战型资源而不是只讲概念的 demo。2. 环境配置与依赖选型先把底座搭稳再谈效果2.1 为什么优先选 Qwen2.5-7B 加 LoRA 这条路线选型这件事直接决定后面显存够不够、微调跑不跑得动。7B 量级的底座模型在消费级显卡上做 LoRA 微调是当前比较现实的方案全参数微调动辄需要多卡 A100个人和小团队基本扛不住。LoRA 只训练低秩旁路矩阵可训练参数通常压到原模型的百分之一以内一张 24G 显存的卡就能跑起来。Qwen2.5-7B 在中文指令跟随和长文本理解上表现稳定社区工具链成熟遇到问题容易搜到答案这也是我一般会优先推荐它的原因。向量库这块本地知识库检索常见的组合是 FAISS 或 Chroma。FAISS 偏底层、检索快、适合数据量大的场景Chroma 自带持久化和元数据过滤上手快适合中小规模知识库。项目里如果追求部署简单Chroma 是更省心的选择如果文档量到百万级 chunk再考虑 FAISS 加 IVF 索引。Embedding 模型建议用 bge-large-zh 或 m3e 这类中文优化过的模型别直接拿英文 embedding 硬套中文语料召回率会明显掉。2.2 环境安装与依赖锁定环境配置是最容易翻车的一步版本不对齐后面报错能查半天。建议用 conda 建独立环境把 Python 锁在 3.10很多深度学习库对 3.11 以上的兼容还在补。# 创建独立环境Python 版本锁 3.10避免依赖冲突 conda create -n rag_qa python3.10 -y conda activate rag_qa # 安装 PyTorch按自己的 CUDA 版本选对应命令 # CUDA 11.8 示例 pip install torch2.1.2 torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118 # 安装检索与微调相关依赖 pip install transformers4.40.0 peft0.10.0 accelerate0.29.0 pip install langchain chromadb sentence-transformers faiss-cpu pip install streamlit # 用于快速起一个展示界面这段命令的逻辑是先隔离环境再按 CUDA 版本装 PyTorch最后装检索和微调依赖。参数上要注意transformers和peft的版本要匹配peft 0.10 配 transformers 4.40 是验证过能跑通的组合版本跨太大容易出现LoRAConfig参数不识别的问题。faiss-cpu和faiss-gpu按机器选没有 GPU 就用 cpu 版检索速度慢一点但功能完整。提示装完先跑一句python -c import torch; print(torch.cuda.is_available())确认显卡能被识别别等训练脚本跑起来才发现用的是 CPU。2.3 目录结构与配置项说明拿到源码包后先别急着跑主程序花两分钟看清目录。典型结构里会有data/放原始文档、vector_store/存向量库持久化文件、models/放底座模型权重、scripts/放微调和检索脚本、config.yaml集中管理参数。配置文件里几个关键项要盯住chunk_size控制文本切块长度中文场景一般 300 到 500 字比较合适chunk_overlap设 50 到 100保证切块边界不丢上下文top_k决定检索返回几条片段设 3 到 5 条通常够用设太大反而会稀释提示词里的有效信息。3. 本地知识库构建与向量检索链路把文档变成能查的知识3.1 文档加载、切块与向量化知识库质量决定问答上限切块策略是这里最容易被忽视的环节。文档先统一转成纯文本PDF 用 pdfplumber 或 PyMuPDF 抽取Word 用 python-docxMarkdown 直接读。切块别按固定字符数硬切尽量按段落或标题层级切语义完整性更好。from langchain.text_splitter import RecursiveCharacterTextSplitter from langchain_community.document_loaders import PyMuPDFLoader from langchain_community.embeddings import HuggingFaceEmbeddings from langchain_community.vectorstores import Chroma # 1. 加载文档支持批量 loader PyMuPDFLoader(data/行业手册.pdf) docs loader.load() # 2. 递归切块优先按段落和标点切保证语义完整 splitter RecursiveCharacterTextSplitter( chunk_size400, # 中文单块 400 字左右 chunk_overlap80, # 相邻块重叠 80 字防止上下文断裂 separators[\n\n, \n, 。, , , , ] ) chunks splitter.split_documents(docs) # 3. 加载中文 embedding 模型并写入向量库 embedding HuggingFaceEmbeddings(model_nameBAAI/bge-large-zh-v1.5) vectordb Chroma.from_documents( documentschunks, embeddingembedding, persist_directoryvector_store/chroma ) vectordb.persist() print(f已写入 {len(chunks)} 个文本块)逻辑上分三步加载、切块、向量化入库。chunk_size和chunk_overlap是最需要调的参数文档句子长、专业术语多就适当放大 chunk_size问答偏事实查询就调小让检索更精准。separators的顺序很关键它决定切块时优先在哪断句把中文标点放进去能明显减少把一句话切成两半的情况。embedding 模型第一次运行会自动下载权重网络不通会卡住提前把模型下到本地再指定路径更稳。3.2 检索策略与提示词拼装检索不是简单 top_k 就完事召回质量差后面大模型再强也救不回来。常见做法是向量检索加关键词检索混合或者加一层 rerank 重排。项目里如果只做了向量检索可以自己补一个 bge-reranker 对召回结果重排把最相关的片段顶到前面。# 检索并拼装提示词 query 这份手册里对设备巡检周期是怎么规定的 retrieved vectordb.similarity_search(query, k4) # 把召回片段拼成上下文 context \n\n.join([doc.page_content for doc in retrieved]) prompt f你是一个严谨的行业问答助手只能依据下面的资料回答 资料里没有的内容直接说不知道不要编造。 资料 {context} 问题{query} 回答这段的关键在于提示词里的约束句。不加「只能依据资料回答」这句模型很容易自由发挥把训练时的通用知识混进来答案看着合理但和你的文档对不上。k4是召回条数配合 rerank 时可以先把 k 放大到 10 再重排取前 4。拼装时给每段资料加上来源标记方便后面做引用溯源用户看到答案能点回原文信任度会高很多。3.3 检索效果怎么验证别凭感觉说「答得还行」要有可量化的验证。准备一批带标准答案的问题集跑一遍看召回片段里是否包含正确答案所在的 chunk命中率低于八成就要回头调切块和 embedding。常见做法是记录每次检索的 query、召回 chunk 的 id 和相似度分数人工抽查前 20 条看有没有明显不相关的片段混进来。相似度阈值也可以设一道低于阈值的直接不返回宁可说不知道也别硬答。4. LoRA 微调实战让模型贴合行业表达4.1 微调数据准备与格式微调不是必须的但如果你的领域术语多、问答风格固定微调能明显提升回答的稳定性。数据格式一般是 instruction-input-output 三元组或者对话式的 messages 结构。数据来源可以是历史问答记录、人工标注、或者用强模型对文档生成问答对再清洗。import json # 构造微调数据集每条包含指令、输入和期望输出 data [ { instruction: 根据设备手册回答巡检周期问题, input: 离心泵的日常巡检周期是多久, output: 离心泵日常巡检周期为每班一次重点检查轴承温度、密封泄漏和振动情况。 }, { instruction: 根据设备手册回答维护要求, input: 离心泵润滑油多久更换一次, output: 离心泵润滑油每运行 2000 小时或每半年更换一次以先到者为准。 } ] with open(data/train.json, w, encodingutf-8) as f: for item in data: f.write(json.dumps(item, ensure_asciiFalse) \n)数据质量比数量重要几百条高质量、覆盖核心场景的样本效果往往好过几千条噪声数据。instruction写清楚任务类型output要和你期望的回答风格一致模型会模仿这个风格。数据里别混入和业务无关的闲聊会稀释微调效果。常见做法是留出 10% 做验证集训练时盯着验证 loss别只看训练 loss 一路降就以为成了。4.2 LoRA 参数配置与训练脚本LoRA 的几个核心参数直接决定训练成本和效果。r是低秩矩阵的秩设 8 到 16 比较常见越大拟合能力越强但越容易过拟合lora_alpha一般设成 r 的两倍target_modules指定给哪些层加旁路注意力层的 q_proj、v_proj 是标配想效果更好可以把 k_proj、o_proj 也加上。from transformers import AutoModelForCausalLM, AutoTokenizer, TrainingArguments from peft import LoraConfig, get_peft_model from datasets import load_dataset model_path models/Qwen2.5-7B-Instruct tokenizer AutoTokenizer.from_pretrained(model_path, trust_remote_codeTrue) model AutoModelForCausalLM.from_pretrained( model_path, device_mapauto, trust_remote_codeTrue ) # LoRA 配置r 和 alpha 控制拟合能力dropout 防过拟合 lora_config LoraConfig( r16, lora_alpha32, target_modules[q_proj, k_proj, v_proj, o_proj], lora_dropout0.05, biasnone, task_typeCAUSAL_LM ) model get_peft_model(model, lora_config) model.print_trainable_parameters() # 确认可训练参数占比 training_args TrainingArguments( output_diroutput/lora_qwen, per_device_train_batch_size2, gradient_accumulation_steps8, # 等效 batch size 16 learning_rate2e-4, num_train_epochs3, logging_steps10, save_strategyepoch, fp16True )per_device_train_batch_size受显存限制24G 卡上 7B 模型一般设 1 到 2靠gradient_accumulation_steps把等效 batch size 堆上去。learning_rate用 2e-4 是 LoRA 的常用起点太大容易训崩太小收敛慢。fp16省显存但有些卡上会出 NaN遇到就换 bf16。训练前先跑print_trainable_parameters确认可训练参数在百分之一量级如果显示接近全量说明 target_modules 配错了。4.3 微调后模型合并与加载LoRA 训练出来的是旁路权重推理时可以直接挂载也可以合并进底座模型方便部署。合并后模型体积和底座一样但推理不用再加载适配器部署更简单。from peft import PeftModel # 加载底座和 LoRA 权重后合并 base_model AutoModelForCausalLM.from_pretrained(model_path, trust_remote_codeTrue) model PeftModel.from_pretrained(base_model, output/lora_qwen/checkpoint-xxx) merged model.merge_and_unload() merged.save_pretrained(models/qwen_rag_merged) tokenizer.save_pretrained(models/qwen_rag_merged)合并前确认 checkpoint 路径对merge_and_unload会把旁路权重加进原权重并返回标准模型。合并后建议跑几条测试问题对比合并前后输出是否一致不一致通常是加载的 checkpoint 不对或 dtype 不匹配。合并后的模型可以直接接进前面的 RAG 链路把生成部分换成微调后的模型即可。5. 避坑与常见问题排查这些坑我替你踩过了5.1 检索召回全是无关片段现象是提问后召回的 chunk 和问题八竿子打不着答案自然离谱。原因多半是 embedding 模型和语料语言不匹配或者切块太大导致单块语义被稀释。解决方法是换成中文优化的 embedding 模型把 chunk_size 调小到 300 左右再补一层 rerank 重排观察召回片段的相关性是否回升。5.2 微调后模型开始胡言乱语现象是微调后模型在通用问题上反而变差甚至答非所问。原因是训练数据太单一或轮数太多模型过拟合到训练集风格丢了通用能力。解决办法是降低num_train_epochs到 1 到 2混入一部分通用指令数据或者调小r和lora_alpha让旁路影响别那么强。5.3 显存溢出训练中断现象是训练跑到一半报 CUDA out of memory。原因是 batch size 或序列长度超了显存。解决办法是把per_device_train_batch_size降到 1靠梯度累积补等效 batch同时把训练数据的最大长度截断到 512 或 1024别让超长样本拖垮显存。5.4 向量库持久化后检索为空现象是重启程序后检索不到任何结果。原因是 Chroma 持久化目录没写对或者加载时没指定persist_directory。解决办法是确认写入和加载用的是同一个目录路径加载时显式传persist_directory并检查目录下是否有chroma.sqlite3文件。5.5 提示词里资料太长导致截断现象是模型回答只覆盖了资料开头部分后面的内容像没看到。原因是拼装的 context 超过了模型上下文窗口。解决办法是控制召回条数和单块长度或者用支持更长上下文的模型把top_k和chunk_size的乘积控制在窗口的七成以内给问题和回答留出空间。6. 进阶技巧把检索和微调串成一条可验证的流水线单点跑通不难难的是让整条链路稳定可复现。我一般会把流程拆成四个可独立验证的阶段知识库构建、检索召回、微调训练、端到端问答每个阶段都留一份可对比的输出。知识库阶段记录 chunk 总数和平均长度检索阶段记录命中率和相似度分布微调阶段记录验证 loss 和几条固定问题的输出端到端阶段用同一批问题跑微调前后对比。验证检索质量有个实用技巧构造「问题-答案所在 chunk」的映射表跑检索时看正确答案所在 chunk 有没有进 top_k。命中率是比主观感受靠谱得多的指标。下面这张表是我常用的记录格式方便横向对比不同参数组合。参数组合chunk_sizetop_k召回命中率端到端可用率基线500372%65%调小块300485%80%加 rerank30010→491%88%微调这块别一上来就全量训先用几百条数据跑一轮小实验看验证 loss 和实际输出再决定要不要加数据。微调后的模型接进 RAG 时提示词里的约束句要保留微调提升的是表达风格不是事实准确性事实还得靠检索兜底。部署时把 embedding 模型和生成模型分开加载检索服务和大模型服务解耦一个挂了不影响另一个排查问题也方便。从那以后我每次搭 RAG 项目都强制先跑一遍检索命中率再动微调顺序反了就是白烧显卡。希望帮到你。本文还有配套的精品资源点击获取
返回列表