Day 27 / 共 68 天 · 阶段 4 RAG
RAG 评测:给系统装一把尺子
前两天(Day 25/26)你学了检索和精排,还反复听我说"要用数据证明变好了"。今天就把那把"尺子"造出来:RAG 评测分两层——检索层(hit rate / MRR / NDCG / precision@k,量"该捞的段捞没捞到、排没排前")和生成层(faithfulness 有没有瞎编、relevancy 答得切不切题)。有了尺子,你后面每一次调参才不是碰运气;明天(Day 28)就把 D21-27 全部串成一个能跑的 RAG 小项目。
📍 你在阶段 4(RAG D21-28)的位置
D25 检索→
D26 rerank&混合→
D27 评测→
D28 实战
💡 用一个类比兜住今天(今天全程沿用「考试与阅卷」的世界观)
评测 RAG = 给系统安排一场考试并阅卷。测试集(问题+标准答案) = 出好的试卷和参考答案;检索层指标 = "找资料"这门课的分(该翻到的资料页翻到没、排在前几);生成层指标 = "写作文"这门课的分(答得对不对、有没有胡编);faithfulness 忠实度 = 作文有没有"照着资料写"而不是瞎编;relevancy 相关度 = 有没有跑题。没有考试和分数,你根本不知道昨天改的参数是让系统变强了还是变弱了——今天你从"凭感觉调"升级成"看分数调"。
L01
为什么评测是 RAG 的命门
🤔 痛点你把 chunk 改小了、又加了 rerank,感觉"好像变好了"?但只试了两三个问题、全靠肉眼。换一批问题呢?到底是真变好还是碰巧?
💡 本质没有评测 = 盲改。你以为在优化,其实可能在退步。评测就是"考试+打分":用一套固定试题反复考,每次改动后看分数升还是降。像医生不靠"感觉你气色好点了",而是量体温、验血——拿数字说话。
这也是面试高频考点:面试官几乎必问"你怎么知道你的 RAG 好用?"。能答出"我有一套测试集 + 检索层和生成层指标,每次改动跑一遍看回归",立刻甩开一大片只会搭 demo 的人。
L02
两层指标:先看检索,再看生成
🤔 痛点RAG 答错了,到底是"资料没找对"还是"资料找对了但模型答歪了"?一个总分说不清病因。
💡 本质RAG 是"先检索、后生成"两步,所以评测也分两层:①检索层——该用的段落有没有被捞到、排得够不够前;②生成层——拿到段落后,答案答得对不对、有没有编。分层才能精准定位病因:检索层低分→去改切分/检索;生成层低分→去改提示词/模型。
图注:先确认资料找对了(左),再看答案写好了没(右)。答错时能一眼定位是哪门课拖了后腿。
L03
检索层①:hit rate 与 precision@k
🤔 痛点怎么用数字表达"该捞的段捞到了没"?
💡 本质先给每个测试问题标好"正确段落是哪几段"(标准答案)。然后:
• hit rate(命中率):top-k 里只要有一个正确段,这题就算命中。统计"多少题命中"。像考试问"翻到的 5 页资料里,起码有一页是对的吗?"。
• precision@k(前 k 精确率):top-k 里有几个是正确的 / k。像"翻的 5 页里,有几页真有用?"——更严格。
• hit rate(命中率):top-k 里只要有一个正确段,这题就算命中。统计"多少题命中"。像考试问"翻到的 5 页资料里,起码有一页是对的吗?"。
• precision@k(前 k 精确率):top-k 里有几个是正确的 / k。像"翻的 5 页里,有几页真有用?"——更严格。
# 已知每题的"标准正确段落 id",和系统检索返回的 top-k id
def hit_rate(retrieved, relevant):
# top-k 里只要碰到一个正确的,就算命中(1),否则 0
return 1 if any(r in relevant for r in retrieved) else 0
def precision_at_k(retrieved, relevant):
# 命中数 / 取回总数
hit = sum(1 for r in retrieved if r in relevant)
return hit / len(retrieved)
retrieved = ["d3", "d1", "d9"] # 系统捞回的 top-3
relevant = {"d1", "d5"} # 标准答案:正确段是 d1、d5
print(hit_rate(retrieved, relevant)) # → 1 (里面有 d1,命中)
print(precision_at_k(retrieved, relevant)) # → 0.33(3 个里只有 1 个对)
👶 hit rate 高就够了吗不一定。hit rate 只问"有没有蒙对一个",很宽松;precision 问"有用的占比",更严。还有一个没细讲的 recall(召回率)——"所有正确段里,你捞回了几成"。三者一起看才全面。刚入门先盯 hit rate 和 precision@k 即可。
L04
检索层②:MRR 与 NDCG(在意排序)
🤔 痛点hit rate 只管"有没有捞到",不管"排第几"。可正确段排第 1 和排第 5,对模型答题的帮助天差地别!
💡 本质两个"在意名次"的指标:
• MRR(平均倒数排名):看第一个正确段排在第几名,取它的倒数(排第 1 得 1、第 2 得 0.5、第 3 得 0.33…)再对所有题求平均。排得越靠前分越高。像考试"你最先翻到对的资料是第几页"。
• NDCG:更全面,不仅看名次还看"每段有多相关"(可以分很相关/有点相关),越相关的排越前得分越高。
• MRR(平均倒数排名):看第一个正确段排在第几名,取它的倒数(排第 1 得 1、第 2 得 0.5、第 3 得 0.33…)再对所有题求平均。排得越靠前分越高。像考试"你最先翻到对的资料是第几页"。
• NDCG:更全面,不仅看名次还看"每段有多相关"(可以分很相关/有点相关),越相关的排越前得分越高。
def reciprocal_rank(retrieved, relevant):
# 找第一个正确段的名次(从1数),返回其倒数;一个都没有则 0
for i, r in enumerate(retrieved, start=1):
if r in relevant:
return 1 / i
return 0
print(reciprocal_rank(["d1", "d3"], {"d1"})) # → 1.0 正确段排第1,满分
print(reciprocal_rank(["d3", "d1"], {"d1"})) # → 0.5 正确段排第2
print(reciprocal_rank(["d3", "d9"], {"d1"})) # → 0 没捞到
# 对整个测试集:把每题的 rr 求平均,就是 MRR
rrs = [1.0, 0.5, 0.0, 1.0]
print("MRR =", sum(rrs) / len(rrs)) # → 0.625
📝 举个例子:同样 hit=1,MRR 见真章
系统 A 把正确段排第 1(rr=1.0),系统 B 排第 5(rr=0.2)——两者 hit rate 都是 1,看不出差别;但 MRR 一比,A(1.0) 明显强于 B(0.2)。所以上了 rerank 之后,最该看的就是 MRR/NDCG 有没有涨——它俩正是衡量"精排效果"的尺子。
L05
生成层:忠实度与相关度
🤔 痛点资料找对了(检索层满分),模型却可能:①无视资料自己瞎编;②答得跑题;③一堆废话不回答重点。这些检索指标一个都测不出来。
💡 本质生成层看两件事:
• faithfulness(忠实度):答案里每句话,是不是都能在检索到的资料里找到依据?找不到依据的就是"幻觉/瞎编"。像判作文"有没有照着参考资料写,而不是自己编"。
• answer relevancy(答案相关度):答案切不切用户的题?有没有答非所问、绕圈子。像判作文"跑题没"。
• faithfulness(忠实度):答案里每句话,是不是都能在检索到的资料里找到依据?找不到依据的就是"幻觉/瞎编"。像判作文"有没有照着参考资料写,而不是自己编"。
• answer relevancy(答案相关度):答案切不切用户的题?有没有答非所问、绕圈子。像判作文"跑题没"。
👶 小白:这两个"答得对不对"没有标准答案,怎么打分?总不能人工一条条读吧?
👨🏫 老师:好问题!业界主流做法是 LLM-as-Judge——让另一个大模型当"阅卷老师":把"问题 + 检索到的资料 + 生成的答案"发给它,让它判断"答案的每句话在资料里有没有依据(忠实度)""答案切不切题(相关度)",打个分。这正是第 9 阶段(Day 50)的重头戏,今天你先知道"生成层可以自动评"就行。当然,关键场景仍要抽一部分人工复核,机器判官也会犯错。
📝 举个例子:检索满分也可能生成翻车
问:
年假几天? 检索层完美命中了「第4.2条:满1年享5天年假」(hit/MRR 都满分)。但生成的答案却说成 10 天——资料里根本没这数,这就是忠实度不合格(瞎编);又或者答成"年假是一种法定福利……"绕了半天不说天数,这是相关度不合格(跑题)。可见检索和生成必须分开量:光看检索层,你永远发现不了这两种错。👶 一句话记住四个词检索层看 MRR/precision(资料找得准不准),生成层看 faithfulness/relevancy(答案编没编、跑没跑题)。面试被问"RAG 怎么评测",把这两层四个词说清楚,就是漂亮的回答。
L06
现成工具 & 深潜入口
🤔 痛点这些指标都要自己手写吗?那也太累了。
💡 本质不用。成熟的 RAG 框架和评测库都内置了这些指标:你准备好"问题+标准答案"的测试集,调一个
evaluate() 就能一次性算出 hit rate、MRR、faithfulness 等一整排分数。手写(如本页 L03/L04)是为了让你看懂指标含义,真上项目直接用现成的。生态里常见的 RAG 评测方案有 RAGAS、TruLens,以及 LlamaIndex / LangChain 各自内置的评测模块——名字先混个脸熟,用到哪个查哪个的文档即可,别死记。
其中 LlamaIndex 是最主流的 RAG 框架之一,它把"切分→嵌入→检索→评测"打包得很顺手,评测模块直接给你 hit rate/MRR 和忠实度/相关度。想把今天的概念落到能跑的代码、系统吃透 RAG 全链路,去看这套深潜教程:
🔗 想深入?去看《LlamaIndex 20 天精讲》→学完记得回来继续 Day 28——明天我们要用今天造的这把"尺子",给一个真实的 PDF 问答项目验收打分。深潜是加餐,主线别断。
L07
今日小结 + 动手 10 分钟
🧠 今天你应该能回答
- 为什么"没评测=盲改"?面试被问"你怎么知道 RAG 好用"该怎么答?
- RAG 评测为什么要分检索层和生成层?分层的好处是什么?
- hit rate 和 precision@k 分别衡量什么?哪个更严格?
- MRR/NDCG 比 hit rate 多管了什么?为什么上 rerank 后要盯它俩?
- faithfulness 和 answer relevancy 各查什么?一般用什么方式自动打分?
✋ 动手 10 分钟:给自己的检索器打个分
不用装任何库。把 L03/L04 的函数拼起来,给 Day 25 的迷你检索器造 3 道题的"标准答案",算出 hit rate 和 MRR:
# 3 道测试题:每题(系统返回的top-k, 标准正确段集合)
cases = [
(["d1", "d3", "d9"], {"d1"}), # 正确段排第1
(["d5", "d2", "d1"], {"d1"}), # 正确段排第3
(["d7", "d8", "d9"], {"d1"}), # 没捞到
]
def rr(ret, rel):
for i, r in enumerate(ret, 1):
if r in rel: return 1 / i
return 0
def hit(ret, rel):
return 1 if any(r in rel for r in ret) else 0
hr = sum(hit(r, g) for r, g in cases) / len(cases)
mrr = sum(rr(r, g) for r, g in cases) / len(cases)
print(f"hit rate = {hr:.2f}") # → 0.67(3题命中2题)
print(f"MRR = {mrr:.2f}") # → 0.44
# 试试:把第2题的 d1 挪到第1位,再看 MRR 涨了没——这就是"优化后看分数"
明日预告 · Day 28:概念齐活!明天是阶段 4 的收官实战——用 llama-index/langchain 给一个 PDF 文件夹做端到端问答:摄入→切分→嵌入→建库→检索→生成,再用今天的指标验收。把 D21-27 学的全部串成一个能放进简历的小项目。