ARTICLE DETAIL

资讯详情

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

把脏语料喂进 RAG 会怎样:一个能跑的最小复现

把脏语料喂进 RAG 会怎样:一个能跑的最小复现 版权与内容来源声明本文为原创整理。文中涉及官方文档、开源仓库、论文与公开报道的内容均在附表 A 中标注来源引用官方原文保持原样不作改写。文中命令、版本号与界面截图以本文成文时的实测/核验结果为准标注「待验证」的部分请以你本地环境实际输出为判断依据。本文不推荐任何不合规的软件获取方式也不对任何收益结果作承诺。转载请注明出处。第 1 章先把脏语料喂进去看它是怎么塌的1.1 一行输出暴露的问题本机Python 3.13.12macOS上我构造了一小段语料一共 9 条其中 5 条来自同一个来源 A1 条原文外加 4 条只把一个词改短的近重复另外 4 条分别来自来源 B、C、D、E。用一个最朴素的词频加权打分做 Top-5 检索程序打印出来的第一行是去重前 Top-5 来源: [A, A, A, A, A] 不同来源数: 1直观感觉是「检索完全成功」——它规规矩矩返回了 5 条。可把这 5 条拼进提示词以后模型读到的其实是同一句话说了五遍剩下四个来源一个字都没进来。问题不在于检索算法写错了而在于我们验收的口径选错了。返回 5 条、K 值拉满这两个事实同时成立却说明不了任何有用的信息。1.2 判据换一个数「不同来源的条数」本文只用一个判据来衡量语料质量Top-K 里不同来源的条数去重前的语料里我给每条都挂了来源标签。注意它和「召回条数」是两个量。召回条数是你设定的 K永远等于 K除非候选不够不同来源条数才会随着语料变脏而往下掉。看什么去重前去重后能不能看出问题召回条数等于 K55看不出两边都是 5Top-K 里不同来源的条数15差距全在这一格拼进提示词的有效信息条数15同上一格换个说法这也是本文与「换个更大的模型试试」这类思路的分界模型再强也变不出语料里本来就不存在的第二份信息。第 2 章把这个判据跑成一段代码2.1 最小复现已实跑下面这段只用 Python 标准库不联网、不装包、不调用任何 embedding 服务可以直接复制运行。importre,mathfromcollectionsimportCounterdefg(s,n):sre.sub(r\s,,s)return{s[i:in]foriinrange(len(s)-n1)}defjac(a,b):returnlen(ab)/len(a|b)B{A:RAG检索增强生成需要先把文档切块再建立向量索引然后召回相关段落,B:Agent智能体靠工具调用完成多步任务并且需要设置权限边界,C:模型微调要准备高质量指令数据并划分训练集与验证集,D:向量数据库用近似最近邻搜索来显著降低检索阶段的延迟,E:提示词工程通过少样本示例来约束模型输出的字段格式}docslist(B.items())[(A,B[A].replace(建立,建))for_inrange(4)]deftopk(q,ds,k):Qg(q,2)dfCounter(xfor_,tindsforxinset(g(t,2)))scsorted(((sum(math.log(len(ds)/(1df[x]))forxinQg(t,2)),s)fors,tinds),reverseTrue)return[sfor_,sinsc[:k]]keep[]fors,tindocs:ifall(jac(g(t,3),g(k,3))0.5for_,kinkeep):keep.append((s,t))q文档切块之后怎么建立向量索引并召回fortag,dsin((去重前,docs),(去重后,keep)):rtopk(q,ds,5);print(tag,Top-5 来源:,r,不同来源数:,len(set(r)))print(语料条数:,len(docs),- 去重后:,len(keep))运行环境Python 3.13.12macOSDarwin 25.6.0arm64。它的原始输出如下一个字都没有改过去重前 Top-5 来源: [A, A, A, A, A] 不同来源数: 1 去重后 Top-5 来源: [A, D, E, C, B] 不同来源数: 5 语料条数: 9 - 去重后: 5去重前Top-5 被来源 A 的 5 个版本占满不同来源数是 1去重后语料从 9 条降到 5 条Top-5 里出现了 5 个不同来源。召回条数两次都是 5唯一的差别在最后一列。2.2 这段代码里其实只有三件事第一件字符 n-gram也叫分片。n-gram 就是把一段文本切成固定长度的连续片段。g(s, 3)表示把字符串切成所有长度为 3 的连续子串装进一个集合——集合会自动丢掉重复片段。它不需要中文分词也就绕开了「切词切错了怎么办」这个坑。第二件Jaccard 相似度。它衡量两个集合的重合程度等于「交集大小 ÷ 并集大小」取值 0 到 1。两条文本的 3-gram 集合越重合值越接近 1。本文实测同一来源、只改了一个词的近重复3-gram Jaccard 是 0.8438两条不同来源的文本是 0.0。中间的差距足够大所以一个很粗的阈值就能把近重复挑出来。第三件一个够用的打分函数。检索部分我用了「字符 2-gram 逆文档频率」的加权求和某个片段在语料里越常见权重越低。这只是为了让 Top-K 有一个排序它本身不承担判断语料质量的职责换成别的打分也不影响上面的结论。2.3 为什么这么小的代码也能复现真实故障因为它复现的是词面层面的近重复而这类脏数据根本不需要语义向量才能被发现。反向说也成立如果你连字面高度重合的重复都没清掉却指望靠调 embedding 模型或换检索器解决问题方向就错了。第 3 章脏语料是怎么一步步把 Top-K 挤满的3.1 近重复是从哪儿溜进来的语料库里的近重复基本都来自四个很普通的工程动作同一份文档被多次导入先传了 PDF又传了转出来的 Markdown两份内容一样、格式不同正文和模板被一起抓下来页眉页脚、导航栏、免责声明、操作步骤被重复抽取一段模板文本进库几百次问答对拆条扩充为了凑数据量把一段话改写成多条高度相似的条目版本迭代不清旧同一份制度文件的新旧版本同时留在库里。这四种情况的共同点是它们都不是「错的数据」只是同一份信息的多个副本。而检索算法并不知道它们是副本。3.2 机制为什么「多」会变成「少」RAG 原论文把它的非参数记忆描述成「a dense vector index of Wikipedia」维基百科的稠密向量索引并用「models which combine pre-trained parametric and non-parametric memory for language generation」来概括这类模型的构造。检索阶段做的事就是在索引里按相似度取回最靠前的 K 条交给生成模型当外部证据。于是机制就清楚了近重复文本在向量空间里几乎挨在一起一旦查询命中这一簇Top-K 的位置就会被同一簇的不同副本占满。系统会老实报告「已召回 K 条」但那 K 条里的信息量只有 1 条。这不是检索器坏了是它在忠实执行「按相似度取前 K 条」这个指令。换句话说脏语料的代价不是让检索失败而是让检索「看起来很成功」。这比直接报错更难发现。3.3 顺带损失的两件事第一是上下文预算。模型的输入长度是有限的被重复内容占掉的位置原本可以放另外四条不同来源的信息。第二大且更隐蔽的是某个来源如果因为重复而在索引里占比过高它的语义邻域会被自己的副本填满检索结果会系统性地向它倾斜——同一份文档反复出现本身就成了一种隐形的加权。官方文档其实早就把「分块」列为 RAG 的关键阶段并提示分块与检索策略会直接影响最终效果「召回片段数」在文档里的定义就是多路召回策略中的 K 值并且明确写到 K 值不合适会导致正确的切片被漏掉。这两句话合起来读正好对应本文实测里那个「K 永远是 5但第 5 条根本换不进来」的现象。第 4 章去重前后 Top-K 命中分布数据实测4.1 口径先写清楚这份表的所有数字都来自本机实跑取数日期 2026-10-07环境为 Python 3.13.12 / macOS语料构造5 个来源 A–E各 1 条基准文本约 28 字对来源 A 追加 N 条近重复仅把「建立」替换为「建」其余一字不改分片与相似度字符 3-gram 集合Jaccard 相似度去重方式单遍顺序去重新条目与所有已保留条目的相似度都小于阈值才保留去重阈值0.5检索打分字符 2-gram 逆文档频率加权求和查询文档切块之后怎么建立向量索引并召回K 5。来源 A 的近重复条数语料总条数去重后条数去重前 Top-5 不同来源数去重后 Top-5 不同来源数055552753549515611515813515读这张表只看最右两列。第一行是干净语料的对照组没有近重复时Top-5 本来就给出 5 个不同来源。从第三行开始语料涨到 9 条召回条数依然是 5但不同来源数掉到 1——多出来的全是同一个来源的副本。语料继续涨到 13 条指标一动不动。这就是本文开头那个判据的价值如果验收指标是「返回了几条」这五行的表现完全一致你会以为语料一直很健康。4.2 阈值改一个字去重直接失效在上一段代码的基础上把写死的阈值换成变量循环其余不动print(去重阈值 | 去重后条数)forthrin(0.2,0.5,0.8,0.95,1.0):keep[]fors,tindocs:ifall(jac(g(t,3),g(k,3))thrfor_,kinkeep):keep.append((s,t))print(f{thr:^8}|{len(keep):^8})原始输出同一台机器同一批语料去重阈值 | 去重后条数 0.2 | 5 0.5 | 5 0.8 | 5 0.95 | 6 1.0 | 6阈值取 0.5 到 0.8 时去重后都是 5 条一旦提到 0.95去重后变成 6 条——有一条近重复被放过了。对照第 2 章量到的 0.8438原因很清楚那条近重复的真实相似度就在 0.84 附近阈值定在 0.95等于要求「几乎一模一样才算重复」改一个词它就混过去了。所以阈值不是越高越安全定得过高会让去重形同虚设。反过来定得过低会把只是同主题的正常文档也误删。公开教材里也提到这一点不存在一个通用的分界值能自动区分「重复」和「相关但不同」阈值必须结合分片定义、语料本身和下游效果一起校准。4.3 什么时候该回去清语料什么时候不该给自己三条能立刻执行的判据连续几批查询Top-K 不同来源数与 K 的比值都接近 1/K→ 语料有系统性重复先回去清洗别急着调检索器某个来源的条目数占语料总量的比例明显高于其他来源→ 它会在 Top-K 里形成压倒性优势即使相似度不是最高去掉重复后回答质量没有变化→ 说明这批重复本来就没被检索到问题不在这里别做无用功。至于什么时候停手清洗不是为了把语料压到最小。把一份制度文件的不同章节合并成一条反而会让检索粒度变粗。只删除同一份信息的高度重合副本保留信息不同的部分。这份资料是什么一张把 RAG 全链路切块、向量化、检索、生成串起来的学习路线图本章讲的语料清洗正好是其中「数据准备」那一段。放在资料包里扫码即可获取第 5 章落地取舍四种近重复检测方式怎么选5.1 四种方式对照方式抓的是什么重复成本主要短板适合的场景精确哈希如 SHA-256一字不差的完全相同极低改一个词就抓不住同一文件被重复导入字符 n-gram Jaccard字面高度重合低本文实测方式同义改写、换词转述抓不住中文近重复、模板文本MinHash / LSH大规模集合相似度中概率近似会有误判十万条以上的大语料语义向量相似度意思相近高要调 embedding语义相近不等于重复同义改写、多来源转述MinHash 这套做法最早由 Andrei Broder 在 1997 年提出最初被用在 AltaVista 搜索引擎里检测重复网页并把它们从结果中剔除。它解决的正是「语料太大两两比对算不完」的问题先用一组哈希函数给每篇文档算出一个固定长度的签名再比较签名就不必做全量两两比较了。本文用的是第二行的办法原因很实际它不需要任何外部服务也不需要 GPU十分钟就能在本地跑完一次全量体检。截至 2026-10-07对多数中小规模的中文知识库来说先用它扫一遍性价比最高。5.2 一条可以直接照做的处理顺序先用精确哈希去掉完全相同的文件这一步最便宜顺手就做了再用字符 n-gram Jaccard 扫一遍近重复阈值从 0.8 起步然后按自己语料的分片长度和实际命中情况上下校准语料规模上到十万条以上、或者每次导入都要跑一遍的时候再换 MinHash / LSH 之类的近似方案语义向量只用来「捞候选」交给人工看不要拿它当重复的判定依据——意思相近不等于重复可能是两份内容互补的文档。5.3 上线前的最小验收动作固定一批有代表性的查询覆盖你的主要业务问题每次语料更新后重跑一次 Top-K记录不同来源条数的分布。这个分布就是你语料库的质量水位线平时稳定在一个区间某天突然掉下去基本可以断定是这次新导入的数据带进了重复内容而不是检索器出了问题。这件事的价值在于它把「语料干不干净」从主观感觉变成了一个能打印出来的数字。一句经验不值得记住一个能重跑的数字才值得。这份资料是什么一套从私有化部署讲到 EmbeddingRAG 的视频课第 4 个模块专门讲语料入库与检索调优和本章的方法可以对照着看。放在资料包里扫码即可获取附表 A本文引用事实与出处对照表#事实出处本文位置1RAG 原论文《Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks》作者含 Patrick Lewis 等2020-05-22 提交、2021-04-12 修订至 v4摘要中「combine pre-trained parametric and non-parametric memory for language generation」「a dense vector index of Wikipedia」等表述为原文arXiv:2005.11401 摘要页 https://arxiv.org/abs/2005.11401第 3 章2Jaccard 相似度系数定义为两个集合交集大小与并集大小之比取值 0不相交到 1相等维基百科 MinHash 条目 https://en.wikipedia.org/wiki/MinHash第 2 章3MinHash 由 Andrei Broder 于 1997 年提出最初用于 AltaVista 搜索引擎检测重复网页并从搜索结果中剔除维基百科 MinHash 条目原始论文 Broder, “On the resemblance and containment of documents”, SEQUENCES 1997, doi:10.1109/SEQUEN.1997.666900第 5 章4分块chunking是 RAG 的关键阶段分块与搜索策略会直接影响最终效果微软官方文档《RAG 分块阶段》https://learn.microsoft.com/zh-cn/azure/architecture/ai-ml/guide/rag/rag-chunking-phase第 3 章5「召回片段数」即多路召回策略中的 K 值K 值不合适会导致正确的文本切片被漏掉阿里云百炼官方文档《RAG 效果优化》https://www.alibabacloud.com/help/zh/model-studio/rag-optimization第 1、3、5 章6去重阈值没有通用取值须结合分片定义、语料与下游质量校准公开教材非标准文件供参考Machine Learning Systems公开在线教材https://mlsysbook.ai/vol1/data_selection/data_selection.html第 4 章7语料构造方式、去重前后 Top-K 命中分布表、阈值敏感性结果、各样本的 3-gram Jaccard 实测值0.8438 / 0.0本文 2026-10-07 本机实测Python 3.13.12 / macOS第 2、4 章附表 B术语速查表术语一句话解释在本文哪里用到RAG检索增强生成先从外部知识库检索证据再让模型基于证据生成回答的做法第 3 章分块chunk把长文档切成一段段可被单独检索的小单元第 3、5 章n-gram分片一段文本里所有长度为 n 的连续片段比如 3-gram 就是所有 3 字连续串第 2 章Jaccard 相似度两个集合交集除以并集衡量重合程度0 到 1第 2、4 章MinHash用一小段签名快速估算两个集合的 Jaccard 相似度适合大语料第 5 章逆文档频率IDF片段在语料里越常见、权重越低用来给检索打分第 2 章Top-K检索返回的相似度最高的前 K 条结果全篇写在最后这篇用到的资料写这篇文章时把相关的官方文档和源码又翻了一遍顺手也整理了几份配套的东西大模型学习路线图从零基础到能自己动手做 Agent按阶段说明每一步该学什么、哪些可以先跳过《LangChain LangGraph MCP 智能体开发实战》视频课7 个模块从私有化部署、EmbeddingRAG 到 MCPAgent 全流程AI 大模型知识库在线可查Agent Skills 从入门到落地、Claude Skills 完全指南等专题按目录浏览即可640 套 AI 大模型行业报告 经典 PDF 书籍看行业落地案例和别人怎么做的时候用得上大模型零基础到精通教学视频跟着敲一遍比只读文档快得多资料是我自己整理的放在下面这个码上扫码即可获取添加时备注「AI」优先通过。资料按「先路线、再动手、最后查漏」的顺序整理好了建议先看学习路线那一份照着它挑一条适合自己当前基础的路径再往下看。
返回列表