这一周的主题是 RAG。学完之后最大的感受不是「学会了一套方法」,而是教程里那些看起来天经地义的结论,换一批数据就未必成立——我在自己的 PDF 上跑完一遍,有三条结论和课上讲的方向相反。
下面先梳理课程内容,再讲我实际跑出来的东西。
一、为什么需要 RAG
大模型有三个绕不开的局限:
- 知识截止:训练数据有截止时间,问最新的事一无所知
- 幻觉:自信地输出错误信息,还会编造不存在的引用
- 上下文窗口有限:塞不下整个知识库,长文档截断就丢信息
核心矛盾是——模型的知识是静态且有限的,现实世界的信息是动态且海量的。
幻觉是怎么来的
课上讲了三个原因,我觉得第一个最本质:
模型的训练目标是「预测下一个 token」,不是「说真话」。 它学的是语言规律,不是事实正确性。加上参数里的知识是模糊压缩的,边界信息容易混淆;而且模型没有表达不确定性的天然出口,倾向于给一个流畅的答案,而不是承认「我不知道」。
那微调能解决吗
不能,至少不划算:
| Fine-tuning | RAG | |
|---|---|---|
| 知识更新 | 重新训练 | 改知识库即可 |
| 成本 | GPU + 时间 + 标注数据 | 构建索引 |
| 风险 | 灾难性遗忘 | 无 |
| 可溯源 | ❌ | ✅ 能指到原文 |
课上用了一个很好的类比:纯 LLM 是闭卷考试,RAG 是开卷考试。闭卷只能靠记忆,没背过就编;开卷先翻资料再答,而且答案能追溯到出处。
RAG 三步:索引(Index)→ 检索(Retrieve)→ 生成(Generate),对应两条流水线——离线建库、在线查询,共享同一个向量库。
二、知识库怎么建
分块是最敏感的环节
这一节我印象最深的一句话是:块太大失去精准度,块太小丢失上下文。
三种策略:
- 固定大小分块:简单快,但可能拦腰截断句子
- 语义感知分块:按段落/句子分,语义完整但块大小不均
- 递归字符分块:最常用,兼顾速度与质量
还有 Overlap(重叠窗口)——相邻两块共享一部分文字,防止关键信息正好被切割点劈开。
经验起点是 chunk_size=512、overlap=50~100,然后靠实验调。
向量库怎么选
课上给了横向对比,简化成一句话:开发用 Chroma(零配置),生产用 Milvus 或 Qdrant(支持 HNSW + 过滤)。
索引结构有三种,Flat 精确但慢、IVF 快但有误差、HNSW 是生产首选。HNSW 的直觉是地图导航——高层是国家级高速(长距离快速跳跃),中层是省道,底层是本地街道,查询时先在高层定位大概区域,逐层下探。
三、检索技术
向量检索 vs BM25
这两个的差异是本周最有用的知识点之一:
| 向量检索 | BM25 | |
|---|---|---|
| 近义词语义 | 强 | 弱 |
| 精确词匹配 | 中 | 强 |
| 专有名词与 ID | 弱 | 强 |
BM25 的打分靠三件事:词频 TF、逆文档频率 IDF、文档长度归一化。它擅长精确词汇匹配,不擅长近义词(「手机」≠「电话」)。
混合检索与 RRF
既然两者各有盲区,就并行跑两路,再用 RRF(Reciprocal Rank Fusion) 融合:
score(d) = Σ 1 / (k + rank(d)) k 通常取 60
RRF 的妙处是只看排名不看分数——两路的分数量纲完全不同(余弦相似度 0~1,BM25 是任意正数),直接加权要调一个玄学系数,而按排名融合天然公平。
重排序:两阶段各司其职
- 第一阶段粗排:向量/BM25 召回 Top-50~100,速度优先,保召回率
- 第二阶段精排:Cross-Encoder 对每一对 (Query, Doc) 单独打分,精度优先
区别在于 Bi-Encoder 把 Query 和 Doc 分别编码(快,但丢失了交互信息),Cross-Encoder 把两者拼一起输入(精准,但每一对都要单独算)。
经验是初始 k=50、rerank 到 k=5,通常显著优于直接 k=5。
四、生成与评估
Prompt 的三个关键点
- 明确告诉模型只用提供的上下文
- 设置拒绝回答的条件(信息不足时说不知道)
- 用清晰标签分隔多段文档
还有个反直觉的点叫 Lost in the Middle——模型更关注 Prompt 的首尾,中间内容容易被忽略。所以最相关的文档要放首位。
评估要分两段看
这是我认为整周最重要的方法论:不要只看最终答案,要分别衡量检索和生成。
- 检索侧:Recall@K(有没有找到)、MRR(排得够不够前)
- 生成侧:Faithfulness(答案有多少比例有依据)、Answer Relevance(切不切题)、Context Precision(检索噪声大不大)
四种常见失败模式各有对策:
| 症状 | 指标 | 对策 |
|---|---|---|
| 答案在文档里却没检索到 | Recall ↓ | 换 Embedding、上混合检索、查询扩展 |
| 答案混乱夹杂无关信息 | Context Precision ↓ | 加 Reranker、降 Top-K、上下文压缩 |
| 模型自己编 | Faithfulness ↓ | 加强 System Prompt、压缩上下文 |
| 答非所问 | Answer Relevance ↓ | 先查检索相关性,再优化 Prompt |
五、动手:在真实 PDF 上跑一遍
课程内容到这里就完了。下面是我自己的实验——用三份山西的生态修复项目 PDF 建库,出 15 道题,把整条链路完整跑一遍。
结果和教程说的不太一样。
发现 1:BM25 明显强于向量检索
Recall@8 fixed 分块
bm25 0.808
vector 0.577
差了 0.231。原因是我这批查询全是「朔州」「2025 年度」「56713.18 万元」这类精确地名和数字——正好是笔记里说的「专有名词与 ID:向量弱、BM25 强」。
而多数 RAG 教程的默认设定是向量检索当主力、BM25 当补充。
方法的优劣取决于查询类型和数据特性,不能照搬结论。
发现 2:混合检索确实最好,但赢得不多
hybrid 0.846
bm25 0.808
vector 0.577
RRF 把两路的盲区互补上了,方向和课上讲的一致,但只高了 4.7%——因为向量那一路太弱,能补的有限。
更有意思的是 MRR 反过来了:bm25 的 MRR(0.744)高于 hybrid(0.648)。
说明 BM25 命中时排得更靠前,而 hybrid 是靠「多找回来几条」赢的。Recall 问「有没有找到」,MRR 问「排得够不够前」,两个指标看的是不同的东西。
发现 3:我把评估指标搞错了两次
这是这周最大的收获,也是最狼狈的部分。
第一版用文档级判定——只要检索结果落在正确的那份 PDF 里就算命中。结果所有配置都是 1.000,顶到天花板,完全没有区分度。没有区分度的指标提供不了任何改进信号。
第二版改成页码 ±1 判定。这次有区分度了,我据此写下一条结论:
「混合检索反而不如纯 BM25」
这是个假结论。
第三版改成证据串判定(答案的关键证据是否真的出现在检索到的文本里)。同一批数据,结论直接翻转:
页码±1(第二版) 证据串(第三版)
fixed hybrid 0.731 ← 不如bm25 0.846 ← 最佳
fixed bm25 0.808 ← 最佳 0.808
量尺歪了,结论就是反的。而且你不会收到任何报错——它会给你一个看起来完全合理的错误答案,然后你照着它去优化。
后来在别的项目里我又遇到过一次类似的事,从此养成一个习惯:指标异常时,先怀疑指标,再怀疑系统。 特别是那种「好得不像话」或者「差得莫名其妙」的数字,三次里有两次是量尺的问题。
还有两件事
差点因为一个错数字跳过重排。 我一开始算出来的收益极小,觉得不值得加,后来发现是统计口径错了——重排实际把 MRR 从 0.644 提到了 0.826,不只是把目标找回来,是把它排到了前面。
拒答阈值形同虚设。 我按教程设了相似度阈值来触发拒答,实测发现完全没起作用——因为中文 embedding 有约 0.3 的相似度底噪,两句毫不相关的中文也能到 0.3 左右。照搬 0.7/0.8 这种经验值,要么全部拒答,要么形同虚设。阈值是数据的属性,不是方法的属性,必须实测定。
这周的收获
课程内容本身是完整的,但真正让我理解 RAG 的是那几次翻车:
- 教程结论有前提。 「向量检索为主、BM25 为辅」隐含的前提是查询偏语义;我的数据全是专有名词,前提不成立,结论就反了。
- 评估比实现更难。 实现一条 RAG 链路一天就能跑通,但把评估做对我改了三版。
- 指标要分层。 只看最终答案好不好,永远不知道该改检索还是改生成。
下一周是工具调用(Function Calling),RAG 会从「必然执行的一步」变成「模型自己决定要不要调用的一个工具」——这个转变挺关键的。