
大模型 RAG 知识库核心原理听起来并不复杂把文档切块、向量化、存进向量库用户提问时检索相关内容塞进 Prompt 让大模型回答。几乎所有教程都在讲这条链路。但只要你拿着这套流程去接一个真实的企业项目很快就会发现文档格式五花八门PDF 里全是扫描件和表格检索回来的片段经常答非所问向量库里堆了几十万条脏数据线上问题根本无法定位。如果你正在从 Demo 走向生产我想先给一个明确判断请把 70% 的精力放在数据管线和检索质量上而不是纠结该用哪个大模型。大多数 RAG 项目效果好不是模型选得好而是数据被处理对了、检索被调优了、评估闭环真正起作用了。这篇文章不会只讲“什么是 RAG”而是围绕一套企业级项目实战的完整链路把文档加载、分块、向量化、检索、重排、生成、评估这些环节逐步拆开。你会看到哪些选择会导致项目返工哪些细节决定了上线后的可用性以及一套可以直接落地的代码骨架。读完你能少走的弯路比再看十个纯概念教程多得多。1. 这篇文章真正要解决的问题Demo 与生产之间的鸿沟RAG 知识库的入门门槛确实不高一个下午就能搭出能回答问题的聊天机器人。但很多人在这一步就误以为自己已经会了接下来进入真实项目时才会被连续击穿文档解析阶段扫描版 PDF 一个字符都提不出来分块策略不合理一个完整知识点被切成碎片检索回来永远只有一半Embedding 模型选错导致相似语义完全检索不到没有评估集每次调整都靠“感觉变好了”上线后效果无法证明权限隔离没做A 部门用户通过 RAG 接口查到了 B 部门的内部文档。这些问题没有一个是“换个大模型”能解决的。它们都发生在 RAG 链路的数据层和检索层。所以这篇文章最应该服务的是两类读者一类是刚学会 LangChain、想进一步理解企业级落地细节的开发者另一类是在公司里负责搭知识库、但已经被文档解析和检索效果折磨过的后端工程师。如果你是后者你会发现文中很多“坑”都踩过只是之前不知道问题出在哪一环。2. RAG 核心原理与技术全景2.1 RAG 到底解决了什么问题RAGRetrieval-Augmented Generation检索增强生成解决的是大模型的三个天然短板知识时效性大模型的训练数据存在截止时间无法回答训练结束后发生的事情私有知识缺失企业内部制度、产品手册、项目经验这些数据根本不在公开训练语料里幻觉问题模型不知道答案时会编造尤其是在需要精准引用业务文档的场景这是无法接受的。RAG 的思路不是让模型“背下来”而是在回答前先查资料。从产品角度理解这相当于给员工配了一个随时可查的内部资料库而不是把整个资料库塞进员工脑子里。员工不需要记住每个细节只需要知道去哪里查、查到后怎么用。2.2 一条完整的 RAG 流水线企业级 RAG 知识库的完整链路通常包含七个环节环节主要任务常见工具/方案文档加载从 PDF、Word、Markdown、HTML 等文件中提取文本pypdf、pdfplumber、python-docx、BeautifulSoup数据清洗去除页眉页脚、重复内容、噪声字符自研规则、正则分块把长文本切成适合检索的片段RecursiveCharacterTextSplitter、语义分块向量化将文本转换为向量OpenAI Embedding、BGE、M3E 等存储存储向量和原始文本提供相似度检索Chroma、Milvus、pgvector、Qdrant检索与重排召回候选片段精排后送入 Prompt向量检索 Rerank 模型生成大模型基于 Prompt 生成最终答案GPT、Claude、Qwen、DeepSeek 等很多人把 RAG 理解成“向量数据库 大模型”但从上图可以看出真正决定效果上限的是前四个环节。后面所有优化都是建立在高质量数据之上的。2.3 RAG 与微调、长上下文模型的边界这是初学者最容易混淆的一组关系。微调让模型改变行为方式或学习固定格式适合让模型输出 JSON 结构、调整语气但不适合灌入大量事实知识成本高且知识更新困难长上下文模型把整篇文档一次性塞进上下文适合单文档深度分析但面对百万级知识库时输入 token 成本会爆炸且大模型对放在中间位置的细节记忆并不稳定RAG按需检索成本随访问量线性增长而不是随知识量爆炸增长是构建企业级知识库的最优解。这不是说微调和长上下文没有价值而是在“企业知识库”这个场景下RAG 的工程可行性最强。理解这一点你就不会被“长上下文已经取代 RAG”这类说法带偏。3. 先建评估体系RAG 知识库的衡量指标很多团队做 RAG 项目代码写完了才想起来“怎么知道效果好不好”。正确的顺序应该反过来先定义如何评估再开始做 RAG。你连“好”都没定义清楚优化就是无底洞。3.1 检索质量的指标检索阶段是信息输入它决定了大模型能不能拿到正确的依据。常见指标包括Hit Rate命中率在 Top K 个检索结果中是否出现了正确片段。计算方式是把包含标准答案的片段放入测试集然后统计检索结果命中该片段的比例MRRMean Reciprocal Rank正确片段出现在第几位。第一位得分 1第二位 0.5第三位 0.33取平均后越接近 1 越好RecallKTop K 结果覆盖了多少相关片段PrecisionKTop K 结果中真正相关的比例。在实际项目中Hit Rate 和 MRR 最常用。因为它们可以直接告诉开发人员如果检索失败了再怎么优化 Prompt 都没用。3.2 生成质量的指标检索没问题不代表最终答案没问题。生成质量通常看三个维度忠实度Faithfulness答案中的每个事实是否都能从检索片段中找到依据核心是衡量“有没有幻觉”答案相关性Answer Relevancy答案是否直接回答了用户的问题还是绕来绕去上下文相关性Context Relevancy检索回来的片段中有多少是真正和问题相关的不相关的片段越多越容易干扰模型。这三个指标在 RAGASRAG 评估框架等开源工具中都有对应实现。你也可以自己构建一个简单的评估脚本用另一套更强的模型当裁判逐条给答案打分。3.3 业务层面的评估方法指标只能告诉你“效果好不好”业务评估才能告诉你“能不能上线”。最务实的做法是建立一份评估问答集Golden Set包含 50 到 100 条覆盖典型业务场景的问题并为每个问题写好参考回答和参考片段。每次改动后跑一遍评估集对比指标变化。这份评估集是 RAG 项目最重要的资产比代码还珍贵。因为它把所有“感觉效果变好了”变成可验证的数据。团队里任何人做优化都拿这套集子说话。3.4 评估集怎么建建议从以下几个来源收集问题客服聊天记录中的常见问题业务方提供的 FAQ内部同事对知识库的试用反馈已有搜索平台的搜索日志。每条问题尽量标注难度单片段可回答、多片段拼接、跨文档推理。覆盖不同难度才能暴露真实问题。4. 环境准备与前置条件在实际动手之前先确认环境。本文以 Python 为主版本请以当前稳定版为准这里重点演示通用思路。4.1 运行环境操作系统Windows / macOS / Linux 均可Python 3.9 以上版本建议使用虚拟环境隔离依赖如果没有独立 GPUEmbedding 模型和大模型都建议走 API 调用开发机内存 8GB 以上即可。4.2 核心依赖# requirements.txt langchain0.1.0 langchain-community0.1.0 langchain-openai0.1.0 chromadb0.4.0 pypdf4.0.0 pdfplumber0.11.0 python-docx1.1.0 beautifulsoup44.12.0 fastapi0.110.0 uvicorn0.29.0 openai1.0.0 sentence-transformers2.2.0 numpy1.24.0版本以实际安装为准如果遇到 API 变更优先检查对应官方文档。4.3 模型选择与向量数据库对比Embedding 模型的选择直接决定检索质量。常见选择OpenAI Embedding效果稳定适合快速验证数据会发送到 API敏感数据需要谨慎BGE 系列如 BAAI/bge-large-zh中文效果领先的开源模型可本地部署适合企业内网环境M3Emoka-ai/m3e-base中文场景常见选择轻量易用。向量数据库选型也需要结合团队技术栈数据库适合场景说明Chroma开发测试、小规模原型轻量但生产稳定性一般Milvus大规模生产环境分布式能力强运维成本较高Qdrant中等规模Rust 实现性能好API 简洁pgvector已有 PostgreSQL 基础设施不需要引入额外组件Elasticsearch已有 ES 技术栈需要全文检索向量检索是加分项建议首次跑通 Demo 用 Chroma进入生产验证后再迁移到 Milvus 或 pgvector避免过早引入太重的基础设施。5. 文档加载与解析企业项目的第一个大坑如果说教程里只讲一句话带过的部分那就是文档加载。但实际上企业知识库的文档格式混乱程度会直接决定项目走向。5.1 文档类型决定解析策略不同格式的解析方式完全不同Markdown / TXT直接读文本最简单PDF需要判断是文本型还是扫描型扫描型必须做 OCRDOCX用 python-docx 提取段落表格和图片需要单独处理HTML用 BeautifulSoup 抽取正文过滤掉导航和脚本Excel / CSV结构化数据需要决定是按行转为文本还是作为查询接口。5.2 PDF 扫描件与表格的处理这是最容易踩坑的地方。很多 PDF 在视觉上是正常的但用 pypdf 提取后全是空白原因是整篇文件就是一张张图片。处理方式先用 pdfplumber 尝试提取文本如果某页提取内容为空或字符数极少触发 OCR 流程OCR 可以调用云服务也可以本地部署 PaddleOCR 等开源方案扫描件 OCR 后往往会有识别错误需要在数据清洗阶段额外处理。表格处理同样棘手。直接按文本提取表格的行列结构会丢失。建议用 pdfplumber 的extract_table()将表格转为结构化数据再转成 Markdown 表格格式这样 Embedding 模型更容易理解行列关系。# 示例用 pdfplumber 提取表格并转为 Markdown import pdfplumber def extract_table_to_markdown(pdf_path, page_num): with pdfplumber.open(pdf_path) as pdf: page pdf.pages[page_num] table page.extract_table() if not table: return None markdown_lines [] for row in table: cells [cell.replace(\n, ) if cell else for cell in row] markdown_lines.append(| | .join(cells) |) return \n.join(markdown_lines)这段代码把表格转成了 Markdown后续就可以作为普通文本进入分块环节。5.3 数据清洗清洗规则决定了向量库的数据质量。最基础的清洗包括删除页眉页脚、页码、水印文字压缩连续空白和多余换行删除网址、邮箱等无关信息视业务场景而定去除重复段落比如免责声明、统一页脚。如果跳过清洗页脚文字会进入每个分块占用 Embedding 的空间还会导致相似度计算被噪声干扰。5.4 文档加载常见错误新手最容易犯的错误是直接把整个 PDF 内容当作一个块塞给向量数据库。文档越长检索效果越差。原因很简单Embedding 模型对长文本的语义压缩能力有限一个包含 50 页内容的向量根本无法表达其中某个具体条款的含义。正确做法永远是在加载后先分块再向量化。6. 分块策略与向量化决定检索精度的关键6.1 为什么分块如此关键分块的核心目的是让“一个片段只表达一个完整意思”。如果一块中混入多个主题检索时即使命中了也会带进来大量无关信息。如果一块把一个完整知识点截断回答时就缺少必要上下文。可以类比做读书笔记好的笔记是每张卡片记录一个独立知识点而不是把整章内容抄在一张卡片上。6.2 分块大小的选择分块大小没有绝对标准但可以遵循以下经验中文场景chunk_size建议 300 到 800 字符chunk_overlap建议控制在 10% 到 20%避免关键句恰好被切断业务文档如果段落结构清晰优先按段落分块而不是机械按字符数切代码类或表格类内容建议按逻辑结构分块。在 LangChain 中RecursiveCharacterTextSplitter是比较可靠的选择。它会优先按分隔符切分尽量保证语义完整。# 示例按递归字符分块 from langchain.text_splitter import RecursiveCharacterTextSplitter from langchain.schema import Document text_splitter RecursiveCharacterTextSplitter( chunk_size500, chunk_overlap50, separators[\n\n, \n, 。, , , , , ] ) def split_documents(text: str, source: str): chunks text_splitter.split_text(text) return [ Document( page_contentchunk, metadata{source: source, chunk_index: i} ) for i, chunk in enumerate(chunks) ]这里把。都加入分隔符是为了让中文文本的断句更自然这是一个很实用的小优化。6.3 语义信息与元数据分块时一定要保留元数据例如来源文档、页码、章节号、上传时间、权限级别等。这些字段在后续有两个重要用途回答问题时可以返回来源方便用户溯源做权限隔离时可以按元数据过滤检索范围实现多租户隔离。如果在入库阶段没有保留元数据后期再想加就非常痛苦可能需要全量重建向量库。6.4 Embedding 模型的选择原则几条硬性原则全链路只能使用同一个 Embedding 模型。查询和文档如果用了不同模型向量空间不一致相似度完全没有意义中文场景不要直接用面向英文的通用模型。尽量使用针对中文训练过的模型轻量化模型要平衡效果与性能。BGE-M3 效果更好但模型体积大bge-small-zh 体积小方便快速验证模型升级后必须重建向量库。不能混用新旧模型生成的向量。7. 完整示例从零搭建一个 RAG 项目下面用一个最小可运行的 RAG 项目把前面的知识串起来。项目结构如下rag_project/ ├── data/ # 原始文档 │ ├── employee_handbook.pdf │ └── product_manual.md ├── src/ │ ├── __init__.py │ ├── load.py # 文档加载与清洗 │ ├── chunk.py # 分块 │ ├── store.py # 向量化与入库 │ ├── retrieve.py # 检索 │ ├── generate.py # 生成 │ └── api.py # FastAPI 服务 ├── app.py # 聚合入口 └── requirements.txt这套结构虽然简单但已经考虑了模块化加载、分块、入库、检索、生成各自独立后续替换组件不用改整个项目。7.1 文档加载与分块# src/load.py from pathlib import Path def load_text_file(path: str) - str: with open(path, r, encodingutf-8) as f: return f.read() def load_pdf_with_pdfplumber(path: str) - str: import pdfplumber text_parts [] with pdfplumber.open(path) as pdf: for page in pdf.pages: text page.extract_text() or text text.replace(\n, ) text_parts.append(text) return \n.join(text_parts) def load_document(path: str) - str: suffix Path(path).suffix.lower() if suffix .pdf: return load_pdf_with_pdfplumber(path) elif suffix in (.txt, .md): return load_text_file(path) elif suffix .docx: import docx doc docx.Document(path) return \n.join(p.text for p in doc.paragraphs) else: raise ValueError(f不支持的文档格式: {suffix})# src/chunk.py from langchain.text_splitter import RecursiveCharacterTextSplitter from langchain.schema import Document def split_documents(text: str, source: str, chunk_size500, chunk_overlap50): splitter RecursiveCharacterTextSplitter( chunk_sizechunk_size, chunk_overlapchunk_overlap, separators[\n\n, \n, 。, , , , , ] ) chunks splitter.split_text(text) return [ Document(page_contentchunk, metadata{source: source, chunk_index: i}) for i, chunk in enumerate(chunks) ]7.2 向量化与入库# src/store.py from langchain_openai import OpenAIEmbeddings from langchain_community.vectorstores import Chroma def create_embeddings(api_key: str, model: str text-embedding-ada-002): return OpenAIEmbeddings(openai_api_keyapi_key, modelmodel) def build_vectorstore(documents, embeddings, persist_dir./chroma_db): vectorstore Chroma.from_documents( documentsdocuments, embeddingembeddings, persist_directorypersist_dir ) vectorstore.persist() return vectorstore def load_vectorstore(embeddings, persist_dir./chroma_db): return Chroma(persist_directorypersist_dir, embedding_functionembeddings)这里有一个细节如果缺少load_vectorstore每次启动都要重新入库。实际项目中应该先检查向量库是否已存在存在就直接加载否则执行构建。7.3 检索与生成# src/retrieve.py def get_retriever(vectorstore, k5): return vectorstore.as_retriever(search_kwargs{k: k})# src/generate.py from langchain_openai import ChatOpenAI from langchain.chains import RetrievalQA def create_qa_chain(retriever, api_key: str, model: str gpt-4o-mini): llm ChatOpenAI(openai_api_keyapi_key, modelmodel, temperature0) qa RetrievalQA.from_chain_type( llmllm, retrieverretriever, return_source_documentsTrue, chain_typestuff ) return qa7.4 API 封装# src/api.py from fastapi import FastAPI, HTTPException from pydantic import BaseModel app FastAPI(titleRAG Knowledge Base API) class QueryRequest(BaseModel): question: str class QueryResponse(BaseModel): answer: str sources: list[str] qa_chain None def set_qa_chain(chain): global qa_chain qa_chain chain app.post(/query, response_modelQueryResponse) def query(req: QueryRequest): if qa_chain is None: raise HTTPException(status_code503, detail知识库尚未初始化) result qa_chain.invoke(req.question) sources list({doc.metadata.get(source, ) for doc in result[source_documents]}) return QueryResponse(answerresult[result], sourcessources)7.5 聚合入口# app.py from src.load import load_document from src.chunk import split_documents from src.store import create_embeddings, build_vectorstore, load_vectorstore from src.retrieve import get_retriever from src.generate import create_qa_chain from src.api import set_qa_chain, app import uvicorn import os API_KEY os.getenv(OPENAI_API_KEY, sk-你的API Key) EMBEDDING_MODEL text-embedding-ada-002 LLM_MODEL gpt-4o-mini def init_rag(): embeddings create_embeddings(API_KEY, EMBEDDING_MODEL) persist_dir ./chroma_db if os.path.exists(persist_dir) and os.listdir(persist_dir): vectorstore load_vectorstore(embeddings, persist_dir) else: all_docs [] for path in [data/employee_handbook.pdf, data/product_manual.md]: text load_document(path) chunks split_documents(text, sourcepath) all_docs.extend(chunks) vectorstore build_vectorstore(all_docs, embeddings, persist_dir) retriever get_retriever(vectorstore, k5) qa_chain create_qa_chain(retriever, API_KEY, LLM_MODEL) set_qa_chain(qa_chain) print(RAG 初始化完成) if __name__ __main__: init_rag() uvicorn.run(app, host0.0.0.0, port8000)这段代码做了一个实用优化如果chroma_db目录已经存在就直接加载避免每次启动重复构建向量库。8. 运行结果与效果验证8.1 本地启动服务python app.py服务启动后用 curl 发起一次查询请求curl -X POST http://localhost:8000/query \ -H Content-Type: application/json \ -d {question: 公司年假政策是什么}预期返回类似下面的 JSON{ answer: 根据 employee_handbook.pdf正式员工每年享有 10 天带薪年假……, sources: [data/employee_handbook.pdf] }具体回答内容取决于你导入的知识文档。判断成功的关键不是答案是否完美而是answer 内容来自检索片段而不是模型编造sources 字段正确标出了来源。8.2 检索质量的人工检查生成结果不理想时先不要改 Prompt而是检查检索这一步。可以写一个简单的检索调试脚本# debug_retriever.py from src.store import create_embeddings, load_vectorstore from src.retrieve import get_retriever API_KEY sk-你的API Key EMBEDDING_MODEL text-embedding-ada-002 embeddings create_embeddings(API_KEY, EMBEDDING_MODEL) vectorstore load_vectorstore(embeddings, ./chroma_db) retriever get_retriever(vectorstore, k5) while True: q input(输入问题输入 exit 退出) if q exit: break docs retriever.get_relevant_documents(q) for i, doc in enumerate(docs): print(f\n--- 片段{i1} ---) print(doc.page_content[:200]) print(来源:, doc.metadata.get(source))逐条查看检索回来的片段是否与问题相关。如果片段不相关说明问题出在分块、Embedding 或数据清洗而不是 Prompt。这是定位 RAG 问题最有用的调试工具。8.3 用评估指标做回归当项目有一定规模后人工逐条检查不再现实。这时应该建立评估集每次修改后用脚本跑一遍指标# 伪代码评估流程 questions [ {question: 公司年假政策是什么, expected_fragment: annual_leave_policy}, # 更多测试题 ] hit_count 0 for item in questions: docs retriever.get_relevant_documents(item[question]) is_hit any(item[expected_fragment] in doc.page_content for doc in docs) hit_count int(is_hit) print(fHit Rate: {hit_count / len(questions):.2%})这个脚本虽然不是完整评估框架但足以让你在每次迭代后量化判断“检索效果是变好还是变差”。9. 企业级 RAG 进阶从 Demo 到生产环境的硬核问题从本地 Demo 到企业生产环境中间隔着大量工程问题。下面挑几个最常见的展开说明。9.1 关系数据库数据如何变成大模型能用的知识这是企业项目里最容易被忽略的问题。RAG 的输入是文本但企业内部大量知识存储在关系数据库里。直接把 SQL 查询结果塞给大模型模型很难理解字段含义。推荐的做法是把关系数据转成“自然语言描述”后再入库。例如一条员工信息表记录可以转换为# 将数据行转换为可检索的知识文本 def build_knowledge_chunk(row: dict, table_name: str) - str: fields , .join([f{k}: {v} for k, v in row.items()]) return f数据表[{table_name}]中的一条记录{fields}。更重要的是在入库前先建立“业务字典”让模型知道每个字段的业务含义。例如leave_balance字段要补充说明是“年假剩余天数单位天”。9.2 知识更新与增量入库企业知识是持续变化的。每次全量重建向量库成本太高需要支持增量入库# 增量新增文档 vectorstore.add_documents(new_chunks) # 按来源删除旧文档后再重新入库 # 先根据 source 删除再 add_documents需要注意增量入库要设计好元数据标记策略确保同一文档的新版本可以正确替换旧版本。建议在元数据中加入doc_version字段。9.3 多租户与权限隔离知识库一旦开放给多个部门使用权限隔离就是安全底线。如果在检索时不加过滤用户可能检索到无权限访问的文档。解法是入库时在文档元数据中写入tenant_id和permission_level检索时强制携带过滤条件# 检索时按租户过滤 def get_retriever_with_tenant(vectorstore, tenant_ids: list, k5): return vectorstore.as_retriever( search_kwargs{ k: k, filter: {tenant_id: {$in: tenant_ids}} } )这里的核心原则是过滤条件必须写在服务端而不是依赖客户端传入。所有经过 API 的检索请求都要强制带上当前用户可见的租户范围。9.4 重排序Rerank与检索优化向量检索适合粗召回但相似度分数并不总是准确。引入 Rerank 是提升检索精度最有效的手段之一。基本思路先用向量检索召回 Top 50 候选再用 Cross-Encoder 模型逐条判断“问题和片段的相关性”取 Top 5 进入生成环节。# 伪代码Rerank 流程 from sentence_transformers import CrossEncoder reranker CrossEncoder(BAAI/bge-reranker-large) def rerank(query, candidates, top_k3): pairs [(query, doc.page_content) for doc in candidates] scores reranker.predict(pairs) scored sorted(zip(candidates, scores), keylambda x: x[1], reverseTrue) return [doc for doc, score in scored[:top_k]]Rerank 能明显提升准确率但也会增加响应延迟。生产环境中需要根据业务容忍度权衡 Top 召回数和 Rerank 模型的大小。9.5 Agentic RAG 的演进方向传统 RAG 是一次“查资料→回答”的直线流程。面对复杂问题时一次检索往往不够。Agentic RAG 的思路是让大模型作为 Agent自主决定“要不要检索”“检索几次”“要不要调用数据库或 API”。例如用户问“对比一下产品 A 和产品 B 的售后政策”Agent 可能需要先检索产品 A 的政策再检索产品 B 的政策最后拼接回答。这是传统 RAG 很难一次完成的。当前业界已经有部分开源框架支持这种能力但这个方向还在快速演进中。建议先跑通传统 RAG再根据业务复杂度逐步引入 Agent 能力。10. 常见问题与排查思路问题现象可能原因排查方式解决方案检索结果和问题完全无关分块颗粒度太大或分块边界不合理打印检索到的片段查看内容是否语义完整调整 chunk_size优先按段落分块答案出现幻觉和资料不符检索片段不完整或 Prompt 约束弱检查 source_documents 是否覆盖答案所需信息增加 Top K增加 Prompt 引用要求PDF 解析后内容乱码或空白扫描件未做 OCR打开 PDF 确认是否为图片型接入 OCR 流程或换用文本型 PDF响应时间太长召回块数过多、Rerank 模型过大统计各环节耗时减少 Top K改用更轻量的模型增加缓存新增文档后检索不到增量入库未执行或 filter 条件拦截检查入库日志和 metadata 内容执行 add_documents检查 filter 条件多租户下出现越权访问检索时未按租户过滤检查 retriever 的 filter 参数服务端强制携带过滤条件同一个问题多次回答不一致模型 temperature 偏高或检索结果不稳定查看多次检索片段是否一致调低 temperature检查文档是否有版本混用11. 最佳实践与工程建议11.1 先建立评估集再谈优化这是本文最想强调的一点。没有评估集的 RAG 项目优化过程就是“凭感觉调参”。有了评估集每次改动都能用数据证明是否有效。评估集不需要一开始就很大50 条