☰
把脏语料喂进 RAG 会怎样:一个能跑的最小复现
2026/10/8 5:26:50 网站建设 项目流程

版权与内容来源声明
本文为原创整理。文中涉及官方文档、开源仓库、论文与公开报道的内容,均在附表 A 中标注来源;引用官方原文保持原样,不作改写。文中命令、版本号与界面截图以本文成文时的实测/核验结果为准,标注「待验证」的部分请以你本地环境实际输出为判断依据。本文不推荐任何不合规的软件获取方式,也不对任何收益结果作承诺。转载请注明出处。

第 1 章:先把脏语料喂进去,看它是怎么塌的

1.1 一行输出暴露的问题

本机(Python 3.13.12,macOS)上,我构造了一小段语料:一共 9 条,其中 5 条来自同一个来源 A(1 条原文,外加 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,除非候选不够;不同来源条数才会随着语料变脏而往下掉。

看什么去重前去重后能不能看出问题
召回条数(等于 K)55看不出,两边都是 5
Top-K 里不同来源的条数15差距全在这一格
拼进提示词的有效信息条数15同上一格,换个说法

这也是本文与「换个更大的模型试试」这类思路的分界:模型再强,也变不出语料里本来就不存在的第二份信息。

第 2 章:把这个判据跑成一段代码

2.1 最小复现(已实跑)

下面这段只用 Python 标准库,不联网、不装包、不调用任何 embedding 服务,可以直接复制运行。

importre,mathfromcollectionsimportCounterdefg(s,n):s=re.sub(r"\s+","",s)return{s[i:i+n]foriinrange(len(s)-n+1)}defjac(a,b):returnlen(a&b)/len(a|b)B={"A":"RAG检索增强生成需要先把文档切块再建立向量索引然后召回相关段落","B":"Agent智能体靠工具调用完成多步任务并且需要设置权限边界","C":"模型微调要准备高质量指令数据并划分训练集与验证集","D":"向量数据库用近似最近邻搜索来显著降低检索阶段的延迟","E":"提示词工程通过少样本示例来约束模型输出的字段格式"}docs=list(B.items())+[("A",B["A"].replace("建立","建"))for_inrange(4)]deftopk(q,ds,k):Q=g(q,2)df=Counter(xfor_,tindsforxinset(g(t,2)))sc=sorted(((sum(math.log(len(ds)/(1+df[x]))forxinQ&g(t,2)),s)fors,tinds),reverse=True)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)):r=topk(q,ds,5);print(tag,"Top-5 来源:",r,"不同来源数:",len(set(r)))print("语料条数:",len(docs),"-> 去重后:",len(keep))

运行环境:Python 3.13.12,macOS(Darwin 25.6.0,arm64)。它的原始输出如下,一个字都没有改过:

去重前 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 不同来源数
05555
27535
49515
611515
813515

读这张表只看最右两列。第一行是干净语料的对照组:没有近重复时,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 什么时候该回去清语料,什么时候不该

给自己三条能立刻执行的判据:

  1. 连续几批查询,Top-K 不同来源数与 K 的比值都接近 1/K→ 语料有系统性重复,先回去清洗,别急着调检索器;
  2. 某个来源的条目数占语料总量的比例明显高于其他来源→ 它会在 Top-K 里形成压倒性优势,即使相似度不是最高;
  3. 去掉重复后,回答质量没有变化→ 说明这批重复本来就没被检索到,问题不在这里,别做无用功。

至于什么时候停手:清洗不是为了把语料压到最小。把一份制度文件的不同章节合并成一条,反而会让检索粒度变粗。只删除同一份信息的高度重合副本,保留信息不同的部分。

这份资料是什么:一张把 RAG 全链路(切块、向量化、检索、生成)串起来的学习路线图,本章讲的语料清洗正好是其中「数据准备」那一段。放在资料包里,扫码即可获取:

第 5 章:落地取舍:四种近重复检测方式怎么选

5.1 四种方式对照

方式抓的是什么重复成本主要短板适合的场景
精确哈希(如 SHA-256)一字不差的完全相同极低改一个词就抓不住同一文件被重复导入
字符 n-gram + Jaccard字面高度重合低(本文实测方式)同义改写、换词转述抓不住中文近重复、模板文本
MinHash / LSH大规模集合相似度中概率近似,会有误判十万条以上的大语料
语义向量相似度意思相近高(要调 embedding)语义相近不等于重复同义改写、多来源转述

MinHash 这套做法最早由 Andrei Broder 在 1997 年提出,最初被用在 AltaVista 搜索引擎里检测重复网页并把它们从结果中剔除。它解决的正是「语料太大,两两比对算不完」的问题:先用一组哈希函数给每篇文档算出一个固定长度的签名,再比较签名,就不必做全量两两比较了。

本文用的是第二行的办法,原因很实际:它不需要任何外部服务,也不需要 GPU,十分钟就能在本地跑完一次全量体检。截至 2026-10-07,对多数中小规模的中文知识库来说,先用它扫一遍,性价比最高。

5.2 一条可以直接照做的处理顺序

  1. 先用精确哈希去掉完全相同的文件,这一步最便宜,顺手就做了;
  2. 再用字符 n-gram + Jaccard 扫一遍近重复,阈值从 0.8 起步,然后按自己语料的分片长度和实际命中情况上下校准;
  3. 语料规模上到十万条以上、或者每次导入都要跑一遍的时候,再换 MinHash / LSH 之类的近似方案;
  4. 语义向量只用来「捞候选」交给人工看,不要拿它当重复的判定依据——意思相近不等于重复,可能是两份内容互补的文档。

5.3 上线前的最小验收动作

固定一批有代表性的查询(覆盖你的主要业务问题),每次语料更新后重跑一次 Top-K,记录不同来源条数的分布。这个分布就是你语料库的质量水位线:平时稳定在一个区间,某天突然掉下去,基本可以断定是这次新导入的数据带进了重复内容,而不是检索器出了问题。

这件事的价值在于它把「语料干不干净」从主观感觉变成了一个能打印出来的数字。一句经验不值得记住,一个能重跑的数字才值得。

这份资料是什么:一套从私有化部署讲到 Embedding+RAG 的视频课,第 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 个模块,从私有化部署、Embedding+RAG 到 MCP+Agent 全流程
  • AI 大模型知识库(在线可查):Agent Skills 从入门到落地、Claude Skills 完全指南等专题,按目录浏览即可
  • 640 套 AI 大模型行业报告 + 经典 PDF 书籍:看行业落地案例和别人怎么做的时候用得上
  • 大模型零基础到精通教学视频:跟着敲一遍,比只读文档快得多

资料是我自己整理的,放在下面这个码上,扫码即可获取:






添加时备注「AI」,优先通过。

资料按「先路线、再动手、最后查漏」的顺序整理好了,建议先看学习路线那一份,照着它挑一条适合自己当前基础的路径再往下看。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询