评估先行:搭 golden set、算召回与排序指标、用模型当裁判判忠实度
第二周的每一项优化都要拿数据说话,所以先建评估。这一天从零构造一份标准问答集,实现召回率、平均倒数排名与归一化折损累计增益三个检索指标,再用模型当裁判评忠实度与答案相关性,最后跑出第一周系统的基线报告。
今日目标
- 能从真实语料出发构造一份 golden set,并说明为什么它必须包含无答案问题和多跳问题
- 能实现召回率、平均倒数排名与归一化折损累计增益三个指标,并说清各自敏感于什么
- 能设计一个模型裁判的提示词并用人工抽检验证它靠不靠谱,而不是无条件信任它的分数
第一周你把系统跑起来了,但一直没法回答一个问题:它到底好不好?今天就是来装这把秤的。读完回到页面顶部把三条目标勾掉。
小白版讲解
没有秤就别谈减肥
一个人下决心减肥,第一个月改吃沙拉,第二个月开始跑步,第三个月又戒了夜宵。三个月后他瘦了吗?他自己也说不清——因为家里根本没有秤。他只能凭"裤子好像松了一点"来判断,而这种判断在心情好的日子里格外准确。
第一周结束时,你的检索问答系统就处在这个状态。D4 你换了切法,D5 你调了索引参数,D7 你把两路检索融合到一起,每一步你都觉得"应该更好了"。但如果我现在问你:把向量那一路的权重从 0.6 调到 0.8,会更好还是更差?你只能猜。
秤的价值不在于告诉你现在多少斤,而在于让"这个改动有没有用"变成一个可以回答的问题。所以第二周的第一天不做任何优化——先把秤造出来。从今天起,这门课里"该不该上重排""该切多大""要不要做查询改写"这类问题,唯一合法的答案是:跑一遍评估,看数字。
一把秤要有两样东西:一组固定的被测对象,和一套读数的规则。前者叫标准答案集(golden set),后者就是指标。可惜这两样都不能直接拿来用——现成的公开评测集里全是维基百科问答,跟你的公司知识库没有半点关系;而指标看起来有一堆现成的名字,实际选错了会让你看着数字往上走、用户体验往下掉。
最后说一件最容易被跳过的事:造秤是有成本的。20 道题标好答案文档,我在实验里花了两小时。很多团队跳过它,理由是"先做起来再说",结果三个月后系统改了二十版,没有一版说得清自己强在哪——那不是省了两小时,是把三个月的工作变成了不可验证的。
golden set 怎么造:从语料反向出题
标准答案集的构造方向,跟大多数人的直觉是反的。直觉是"先想用户会问什么,再去看语料里有没有",正确做法是反过来:打开语料,从每一篇里读出它能回答什么问题。
理由很简单:前一种做法你会写出一堆自己都不知道答案的问题,标注时无从下手;后一种做法,答案文档在你出题的那一刻就已经确定了。实验里的 20 道题就是这么出来的,每一道的 answerDocIds 都是我出题时正在读的那一篇。
一条题目长这样:
{
"id": "q17",
"question": "把某个工作区的链路追踪采样率临时调到 100%,属于几级变更?按规范最晚要提前几个工作日提单?",
"answerDocIds": ["doc-022", "doc-021"],
"type": "multi"
}type 这个字段决定了这道题怎么算命中,一共三类,缺一类你的秤就是瞎的:
single:单篇文档就能答。答案文档列了多篇时,任意一篇进上下文就算命中。multi:多跳(multi-hop),必须全部答案文档都进上下文才算命中。凑齐一半等于答不出来,算"命中一半"是给自己发安慰奖。none:语料里根本没有答案。它不参与召回率,它考的是系统会不会闭嘴。
第三类是最容易被跳过、也最不能跳过的一类。原因是这样的:一个只会硬答的系统,在只有可答问题的评估集上能拿到接近满分——它每次都塞一堆材料给模型,模型每次都编一段话出来,而你的评估集根本没有"应该拒答"这一栏,于是这个致命缺陷在报表上完全不可见。无答案问题是唯一能让"乱编"这个故障显形的东西。
出无答案问题也有讲究:必须留强干扰词。实验里问"网页端支持哪些浏览器?对版本有什么最低要求",语料里恰好有一句"提交工单时请附上浏览器与版本";问"私有化部署需要多少 CPU 和内存",语料里既有"私有化部署"也有"CPU、内存",只是从来没写过硬件配置。没有干扰词的无答案题太好答了,检索器一条都捞不到,你测出来的是分词器而不是系统。
覆盖度给一个可以直接抄的配比:20 题起步,多跳不少于 3 道、无答案不少于 3 道,其余是单文档题。20 题就够把"完全不能用"和"基本能用"分开;上线系统建议做到 100–200 题,每次线上出问题就把那个问题补进去——评估集是长出来的。
检索侧三个指标,各自对什么故障敏感
检索侧我们只算三个指标。它们不是三种口味,是三层不同的故障。
召回率(recall)问的是"答案有没有进上下文"。 注意口径:这里的"进上下文"不是"排进前 5 名",而是在 600 token 的上下文预算里被真的装进去了。按固定条数比较是不公平的——大块切法取 5 条能塞两千多 token,小块切法取 5 条只有几百,赢的那一方只是买了更多货。这个口径与 D4 完全一致,两天的数字才能放在一起看。
平均倒数排名(mean reciprocal rank)问的是"第一条相关结果排在第几"。 排第 1 得 1 分,排第 2 得 0.5,排第 10 得 0.1,一条都没有得 0,全部问题上取平均。它对"答案还在、只是被挤到后面"极其敏感——而召回率在预算被挤爆之前完全看不出这件事。
归一化折损累计增益(normalized discounted cumulative gain)问的是"前十名整体质量好不好"。 每一名的收益按名次打折,越靠后折得越狠,再除以理想排序能拿到的分数归一化到 0 到 1。它和倒数排名的区别在于:倒数排名只认第一条,"一条命中加九条垃圾"和"五条全命中"在它眼里一模一样;而这个指标会把前十名里每一条都算进去。重排(rerank)优化的正是它。
三个指标的实现全在实验的 metrics.ts 里,核心就这么长:
// 召回率:single 任意一篇进上下文即命中,multi 必须全部进
export function isHit(t) {
if (t.type === 'none') return false
if (t.type === 'multi') return t.answerDocIds.every((id) => t.contextDocIds.includes(id))
return t.answerDocIds.some((id) => t.contextDocIds.includes(id))
}
// 倒数排名:第一条相关结果的名次取倒数。下标从 0 起、名次从 1 起,差一位分数就全偏高
export function reciprocalRank(t) {
const rank = t.rankedDocIds.findIndex((id) => t.answerDocIds.includes(id))
return rank === -1 ? 0 : 1 / (rank + 1)
}
// 归一化折损累计增益:前 k 名各自按 1 / log2(名次 + 1) 折算,再除以理想排序的分数
export function ndcgAt(t, k = 10) {
const gains = t.rankedDocIds.slice(0, k).map((id) => (t.answerDocIds.includes(id) ? 1 : 0))
const dcg = gains.reduce((sum, g, i) => sum + g / Math.log2(i + 2), 0)
let idcg = 0
for (let i = 0; i < Math.min(t.relevantChunkCount, k); i += 1) idcg += 1 / Math.log2(i + 2)
return idcg === 0 ? 0 : dcg / idcg
}from math import log2
def is_hit(t: dict) -> bool:
"""召回率:single 任意一篇进上下文即命中,multi 必须全部进"""
if t["type"] == "none":
return False
if t["type"] == "multi":
return all(doc_id in t["context_doc_ids"] for doc_id in t["answer_doc_ids"])
return any(doc_id in t["context_doc_ids"] for doc_id in t["answer_doc_ids"])
def reciprocal_rank(t: dict) -> float:
"""第一条相关结果的名次取倒数。enumerate 从 1 起算,省掉一次差一位的机会"""
for rank, doc_id in enumerate(t["ranked_doc_ids"], start=1):
if doc_id in t["answer_doc_ids"]:
return 1 / rank
return 0.0
def ndcg_at(t: dict, k: int = 10) -> float:
"""前 k 名各自按 1 / log2(名次 + 1) 折算,再除以理想排序的分数"""
gains = [1 if d in t["answer_doc_ids"] else 0 for d in t["ranked_doc_ids"][:k]]
dcg = sum(g / log2(i + 2) for i, g in enumerate(gains))
idcg = sum(1 / log2(i + 2) for i in range(min(t["relevant_chunk_count"], k)))
return dcg / idcg if idcg else 0.0无答案那一类不进这三个指标,它有自己的一项:拒答率——四道无答案题里,系统有几道真的说了"资料里没有"。实验跑出来的基线是这样的:
召回率(可答题) 87.5%
召回率(single) 100.0%
召回率(multi) 50.0%
平均倒数排名 1.0000
nDCG@10 0.6711
无答案题拒答率 0.0%这份数字信息量很大。单文档题全中,多跳题只中一半——那道"主备切换要谁审批、他叫什么名字",第二篇文档一次都没捞上来。而拒答率是 0%:四道无答案题一道都没闭嘴,全被塞了不相干的材料。只算召回率的话,87.5% 看着还挺不错。
生成侧两个指标:有没有编,有没有答
检索找对了,模型还可能把话说歪。生成侧看两个指标,必须成对看。
忠实度(faithfulness):回答里的每一句陈述,材料里有没有支撑。它抓的是编造。算法是把回答拆成一句一条,逐条判定,最后取"有支撑的条数除以总条数"。
答案相关性(answer relevancy):回答有没有答到问题上。它抓的是"说了一堆但没回答"。
为什么必须成对:一句"资料中没有找到能支撑回答的内容"忠实度满分——它一个字都没编——但相关性为零。只看忠实度,你的系统学会一直拒答就能刷满分。反过来只看相关性,一段编得头头是道、完全切题的假话也能拿高分。这两个指标是互相看着对方的。
实验的基线里有一组特别值得看的数字:忠实度 1.0000,答案相关性 0.3931。忠实度满分是因为离线模式下的"回答"是把命中块里的句子摘出来拼的,逐字来自材料,当然不会编;但相关性只有 0.39,说明摘出来的多半不是问题要的那句。忠实度高不等于回答对,这是这一节最值得记住的一句话。
逐句判定长这样:
export function faithfulness(answer, contextText) {
const unsupported = []
let decidable = 0
for (const claim of claimsOf(answer)) {
const ratio = overlapRatio(claim, contextText)
if (ratio === null) continue // 判不了的不进分母,见下面的坑
decidable += 1
if (ratio < MIN_OVERLAP) unsupported.push(claim)
}
return { score: decidable === 0 ? 0 : 1 - unsupported.length / decidable, unsupported }
}def faithfulness(answer: str, context_text: str) -> tuple[float, list[str]]:
unsupported: list[str] = []
decidable = 0
for claim in claims_of(answer):
ratio = overlap_ratio(claim, context_text)
if ratio is None: # 判不了的不进分母,见下面的坑
continue
decidable += 1
if ratio < MIN_OVERLAP:
unsupported.append(claim)
score = 0.0 if decidable == 0 else 1 - len(unsupported) / decidable
return score, unsupported模型当裁判的坑
忠实度这类判断,最终得靠一个模型来做——把材料、问题、回答一起发过去,让它打分。这就是模型裁判(LLM-as-judge)。它便宜、快、能规模化,也有三个必须知道的系统性偏差。
位置偏好。 让裁判在两个回答之间二选一时,它更倾向选排在前面那个。缓解办法是不做两两比较,改成逐条独立打分;非要做比较,就把两个回答的顺序交换再跑一遍,两次结论不一致的直接判平局。
偏爱长答案。 同样的信息量,写得长的更容易拿高分。缓解办法就是上一节那个逐句判定:忠实度是"有支撑的条数除以总条数",写得越长分母越大,多写的每一句都得自己挣支撑,长度红利自动消失。
自己评自己。 用同一个模型既生成又评判,它对自己的输出会更宽容。缓解办法是换一家或换一档:生成用 claude-sonnet-5,评判就换另一个供应商的模型;预算紧张时用 claude-haiku-4-5-20251001 当裁判也可以,但换完必须重新做一次人工抽检。
除了这三条,提示词本身还有两个要求:给死评分锚点(1.0 是什么、0.6 是什么、0.3 是什么,不给锚点它每天给的分都不一样),以及强制结构化输出,并要求把没支撑的句子原样列出来。第二条是为人工抽检准备的——只给你一个 0.83,你无从复核;把争议句子列出来,你三十秒就能判断裁判对不对。
而校准的唯一办法是人工抽检算一致率:
- 跑一次评估,打开报告,分层抽 10 条——各类型都要有,命中的和没命中的都要有,裁判给高分和给低分的都要有。
- 每条只回答一个问题:这段回答里有没有材料里没写的东西。只判忠实度,不判好不好。
- 把判断写进
human-labels.json,跟裁判的结论比对,算出一致率。 - 低于 0.8 就别再拿这个裁判的分数拦合并,先去改提示词,改完重抽一次。
把评估接进流程
评估脚本写完只是第一步,它必须变成一条命令、一个文件、一道闸门,否则三周之后没人会再跑它。
一条命令:pnpm start 跑完全套,输出 reports/latest.json 与一份人能读的 Markdown。一个文件:把认可的那一版存成 eval/baseline-report.json,它就是往后所有对比的锚点。一道闸门:
// 盯住五项,各守一层:召回率守"找得到",倒数排名与 nDCG 守"排得好",
// 忠实度守"不乱说",拒答率守"没有答案时闭嘴"。少一项就有一类退步能溜过去
export function gate(current, baseline, tolerance = 0.01) {
const regressions = METRICS.map(({ name, pick }) => ({
name,
delta: Number((pick(current) - pick(baseline)).toFixed(4)),
})).filter((r) => r.delta < -tolerance)
return { passed: regressions.length === 0, regressions }
}def gate(current: dict, baseline: dict, tolerance: float = 0.01) -> tuple[bool, list[dict]]:
"""盯住五项,各守一层:找得到、排得好、不乱说、没有答案时闭嘴"""
regressions = [
{"name": name, "delta": round(current[key] - baseline[key], 4)}
for name, key in METRICS
if current[key] - baseline[key] < -tolerance
]
return not regressions, regressions在持续集成里跑它,退步就让流水线失败:
MOCK=1 GATE=1 pnpm start # 有指标退步就以非 0 退出码结束容差别设大。同一份数据、同一份配置本该逐位相同,留 0.01 只是为了给换 embedding 后端留点余量;设成 0.05,一次真实的退步就能大摇大摆走过去。
最后说成本。20 题跑一轮模型裁判只要几分钱,随便跑;扩到 200 题、每天十次,账单就该有人管了。常见做法是分两档:每次提交只跑检索侧指标(纯本地计算,几秒钟、零成本),模型裁判留给合并前和每晚各一次。评估本身也要报三笔账:指标、延迟、钱。
源码导读
动手实验
starter/ 里挖了 5 个练习点,分别是三个检索指标、忠实度的逐句判定、以及回归闸门。原样跑有 5 项验收是 ❌,每做完一个就有一项变 ✅——这是今天的进度条。语料和 20 题标准答案集都已经准备好了,eval/golden-set.json 的前 10 题与 D4 逐字相同,一个字都不要改,改了两天的数字就不能比了。
- 打开
eval/golden-set.json通读 20 道题,对着corpus/抽查三道,确认答案文档确实答得上;再确认四道无答案题在语料里是真的没有答案。 - 在
src/metrics.ts里实现三个检索指标,跑一次,看召回率从 0.0% 变成有意义的数字,并注意 single 与 multi 两档的差距。 - 在
src/judge.ts里补上忠实度的逐句判定,跑一次,看第四段的裁判探针从 ❌ 变 ✅。 - 照 README 的做法分层抽 10 条改写
eval/human-labels.json,重跑,看第五段的一致率和分歧列表。 - 在
src/report.ts里接上回归闸门,跑一次确认「闸门拦住了」,再用MOCK=1 GATE=1 pnpm start体会退步时的非 0 退出码。
面试题
今天 4 道题在下方题库区,侧重评估集构造、检索指标的选择、模型裁判的可靠性与偏差。展开后先看"分析过程"再看要点——照着推导练,比背要点管用。标注"国内高频 / 海外高频"方便按目标市场取舍。
检查清单与明日预告
- 能从真实语料出发构造一份 golden set,并说明为什么它必须包含无答案问题和多跳问题
- 能实现召回率、平均倒数排名与归一化折损累计增益三个指标,并说清各自敏感于什么
- 能设计一个模型裁判的提示词并用人工抽检验证它靠不靠谱,而不是无条件信任它的分数
- 能说清召回率、倒数排名、归一化折损累计增益三者分别在什么故障下先掉下来
- 能解释为什么忠实度和答案相关性必须成对看,只看一个会被怎么骗
- 实验的 5 条验收标准全部通过,手里有一份第一周系统的基线报告
- 4 道面试题不看要点也能答出至少 3 道
明天(D9)做第二周的第一项优化:混合检索与重排——两路召回用倒数排名融合合起来,再拿一个交叉编码器把前几十条重排一遍。先建评估再优化的理由是:混合检索的收益完全取决于语料和问题分布,有的场景涨十几个点,有的一个点都不涨甚至倒退。没有今天这份基线,你明天只能说"感觉好了一点"。
面试题库
让你从零给一个公司知识库的 RAG 系统建评估集,你会怎么做?多少题才算够用?You need to build an evaluation set from scratch for a RAG system over a company knowledge base. How would you do it, and how many questions are enough?
国内高频海外高频进阶#evaluation#golden-set#rag分析过程 · 先想清楚再作答
- 这题的区分度在「出题方向」和「规模的理由」两处。开口就说「找几百个用户真实问题」的,多半没真做过——真实问题的答案在哪篇文档里,没人标得出来。
- 先给方向:从语料反向出题,打开每一篇读它能回答什么,出题的那一刻答案文档就已经确定了,标注成本几乎为零。反方向(先想问题再找答案)会得到一堆自己都不知道答案的题。
- 再给结构:每题记问题、答案文档列表、类型三个字段;类型至少分单文档、多跳、无答案三类,并说明多跳必须全部答案文档命中才算命中,无答案不参与召回率而是考拒答。
- 规模的理由要给出来,不能只报一个数字:20 题能把「完全不能用」和「基本能用」分开,够做冒烟;100 到 200 题才有资格判断「涨了两个点」是真的还是噪声。上线之后每次线上出问题就把那个问题补进集合——评估集是长出来的。
- 补一句成本与保鲜:出题是人力活,20 题两小时是正常量级;语料更新后要复核答案文档还在不在,否则集合会悄悄腐烂,指标下跌你会误以为是系统坏了。
- 可预期的追问是「怎么防止评估集被过拟合」。答案是留一份不参与调优的保留集,并且定期从线上真实问题里补充新题,只用来验收不用来调参。
How to reason about it · think before answering
- The discriminator here is the direction you generate questions in, and whether you can justify a size rather than name one.
- Go corpus-first: read each document and write the questions it can answer. The answer document is fixed at authoring time, so labeling is nearly free. Question-first gives you items whose answers nobody can locate.
- Give the schema: question, answer document ids, and a type. At minimum three types - single-document, multi-hop, and unanswerable. Multi-hop counts as a hit only when every answer document makes it into the context; unanswerable items are scored on abstention, not recall.
- Justify the size: 20 items separate 'broken' from 'usable' and are enough for a smoke gate; 100 to 200 are needed before a two-point delta means anything. Then grow the set - every production failure becomes a new item.
- Mention cost and decay: roughly two hours for 20 items, and answer labels must be rechecked whenever the corpus changes, or the set rots and you misread the drop as a system regression.
- Expected follow-up: how do you avoid overfitting to the eval set? Keep a held-out slice that never informs tuning, and refresh it from real production questions.
答题要点
- 从语料反向出题,出题时答案文档就已确定,标注成本最低。
- 每题标类型:单文档、多跳、无答案,三类缺一不可。
- 多跳要求全部答案文档命中;无答案不算召回率,考的是拒答。
- 20 题够冒烟,100 到 200 题才能判断小幅变化;线上故障持续补题。
- 留一份不参与调优的保留集,防止对评估集过拟合。
Key points
- Author corpus-first so the answer document is known at authoring time.
- Label every item with a type: single-document, multi-hop, unanswerable.
- Multi-hop requires all answer documents; unanswerable items score abstention, not recall.
- 20 items for a smoke gate, 100 to 200 to trust small deltas, and keep growing it from production failures.
- Hold out a slice that never informs tuning to avoid overfitting the set.
召回率、平均倒数排名、归一化折损累计增益,这三个检索指标分别在什么故障下会先掉下来?只盯一个会漏掉什么?Recall, mean reciprocal rank, and normalized discounted cumulative gain - which failure mode does each one catch first, and what do you miss by watching only one?
国内高频海外高频进阶#retrieval-metrics#evaluation#ranking分析过程 · 先想清楚再作答
- 这题考的是「知不知道指标之间的盲区」,不是背定义。能把三者按「有没有 / 靠不靠前 / 整体好不好」分层的,基本就答对了一半。
- 推导链是这样的:召回率是布尔的——答案文档在不在最终上下文里。它对「压根没捞到」最敏感,但答案从第 1 名掉到第 8 名它一动不动,只要还在预算内。
- 倒数排名只看第一条相关结果的名次,所以「答案还在但被挤到后面」它立刻掉。反过来它有个盲区:前十条里有一条命中还是五条命中,它给的分完全一样。
- 归一化折损累计增益把前 k 名里每一条相关结果都按名次折算再累加,所以它对「整体排序质量」敏感,是重排最直接的优化目标。它的盲区是不告诉你「有没有」——召回率为零时它也是零,看不出是没捞到还是排得差。
- 结论:三个一起看才能定位故障层。召回率掉说明检索或切块出了问题,要动召回策略;召回率不动而倒数排名掉,说明排序退化,该上重排;两者都稳而 nDCG 掉,说明前几名里混进了更多噪声。
- 可预期的追问是「指标顶格了怎么办」。真实答案是把题目做难:指标撞天花板说明评估集失去区分度,这时候继续优化系统是在瞎调。
How to reason about it · think before answering
- This tests whether you know each metric's blind spot, not whether you can recite definitions. Layer them as 'did it show up / how high / how good overall' and you are halfway there.
- Recall is boolean: is the answer document in the final context. It catches 'never retrieved', but it does not move when the answer slips from rank 1 to rank 8, as long as it still fits the budget.
- MRR looks only at the rank of the first relevant hit, so ranking degradation shows up immediately. Its blind spot: one relevant item in the top ten scores exactly the same as five.
- nDCG discounts every relevant hit in the top k by its position, so it tracks overall ranking quality and is the direct optimization target for reranking. Its blind spot is existence - it is zero both when nothing was retrieved and when ranking is terrible.
- Conclusion: together they localize the failure. Recall drops means retrieval or chunking; recall flat but MRR down means ranking degraded, reach for a reranker; both stable but nDCG down means more noise crept into the top results.
- Expected follow-up: what if a metric saturates? Make the questions harder - a saturated metric means the eval set lost its discriminative power, and further tuning is blind.
答题要点
- 召回率管「有没有进上下文」,对完全没捞到最敏感,对名次变化不敏感。
- 平均倒数排名管「第一条排第几」,对排序退化最敏感,但分不清命中一条还是五条。
- 归一化折损累计增益管「前 k 名整体质量」,是重排的直接优化目标,但看不出有没有。
- 三者组合才能定位故障在召回层、排序层还是噪声层。
- 命中口径要说清:按 token 预算装上下文,不是按固定条数取前 k。
Key points
- Recall answers 'did it make it into the context', sensitive to total misses, blind to rank shifts.
- MRR answers 'how high is the first hit', sensitive to ranking degradation, blind to how many hits there are.
- nDCG answers 'how good is the top k overall', the direct target for reranking, blind to existence.
- Only the combination localizes the failure to retrieval, ranking, or noise.
- State the hit criterion: context is packed against a token budget, not a fixed top-k.
用模型当裁判来评 RAG 的忠实度,有哪些系统性偏差?你怎么发现它们、又怎么证明你的裁判可信?What systematic biases does an LLM judge have when scoring RAG faithfulness, and how do you detect them and prove your judge is trustworthy?
国内高频海外高频深入#llm-as-judge#evaluation#faithfulness分析过程 · 先想清楚再作答
- 这题的题眼在后半句。能背出「位置偏好、长度偏好、自我偏好」三个名词的人很多,能说出「怎么证明可信」的很少——面试官要的是后者。
- 先把三个偏差和各自的缓解手段一一对应:位置偏好用逐条独立打分代替两两比较,非要比较就交换顺序跑两遍、结论不一致判平局;长度偏好用逐句判定加比例计分,写得越长分母越大,长度红利自动消失;自我偏好用跨供应商或跨档位的模型评判,生成和评判不同源。
- 再补两条提示词层面的:给死评分锚点,1.0 / 0.6 / 0.3 各自是什么必须写明,否则同一份输入不同天给的分都不一样;强制结构化输出并要求把没支撑的句子原样列出,这是人工复核的抓手。
- 证明可信只有一条路:人工抽检算一致率。分层抽十到三十条——各类型都要有、命中和没命中都要有、裁判给高分和低分都要有,只判一个二元问题(有没有材料外的内容),跟裁判的结论比对。低于 0.8 就不能拿它的分数做拦合并这类决策。
- 一个能加分的细节:一致率很高不一定是好消息。如果抽的十条都是「答案原样抄自材料」的简单题,判对是理所当然的,这时候 100% 说明的是抽检没难度,不是裁判可靠。
- 可预期的追问是「裁判本身会不会坏」。答案是给裁判写探针:喂几组已知正确答案的输入(照抄材料的、明显编造的),每次跑评估都验一遍——评估系统坏掉的方式最阴险,分数照常输出,只是不再有意义。
How to reason about it · think before answering
- The second half of the question is the discriminator. Plenty of people can name position, length, and self-preference bias; few can say how they prove the judge is trustworthy.
- Pair each bias with its mitigation: position bias - score pointwise instead of pairwise, and if you must compare, swap the order and call disagreement a tie; length bias - decompose into claims and score a ratio, so a longer answer grows its own denominator; self-preference - judge with a different vendor or tier than the generator.
- Add two prompt-level requirements: fixed rubric anchors (spell out what 1.0, 0.6 and 0.3 mean, or the same input scores differently on different days) and forced structured output that quotes the unsupported sentences verbatim, which is what makes human review possible.
- Proving trust has exactly one route: human spot-checks and an agreement rate. Stratify ten to thirty items across types, hits and misses, high and low judge scores; answer one binary question only - is anything here not in the material - and compare. Below 0.8 the judge's scores cannot gate a merge.
- A detail that scores points: a very high agreement rate may mean your spot-check was too easy. If all ten sampled answers copy the material verbatim, agreeing is trivial and 100% says nothing about the judge.
- Expected follow-up: can the judge itself break? Add probes - fixed inputs with known verdicts, one faithful and one obviously fabricated, checked on every run. An evaluation system fails silently: the numbers keep coming, they just stop meaning anything.
答题要点
- 三个偏差:位置偏好、偏爱长答案、自己评自己,各自有对应的缓解手段。
- 逐条独立打分代替两两比较;逐句判定按比例计分抵消长度红利;生成与评判不同源。
- 提示词要给死评分锚点,并强制结构化输出、列出没支撑的句子。
- 可信度靠人工分层抽检算一致率,低于 0.8 不能用它做拦合并的决策。
- 给裁判本身写探针,每次跑评估都验一遍它有没有坏。
Key points
- Three biases: position, verbosity, and self-preference, each with a matching mitigation.
- Score pointwise rather than pairwise; decompose into claims and score a ratio to kill the length premium; never let the generator judge itself.
- Pin rubric anchors in the prompt and force structured output that quotes unsupported sentences.
- Establish trust through stratified human spot-checks and an agreement rate; below 0.8 the judge cannot gate merges.
- Add probes with known verdicts so a broken judge is caught on every run.
RAG 的评估集里为什么一定要放语料里没有答案的问题?不放会掩盖什么?Why must a RAG evaluation set include questions the corpus cannot answer, and what does leaving them out hide?
国内高频海外高频基础#evaluation#abstention#golden-set分析过程 · 先想清楚再作答
- 这题看着简单,实际是在问「你有没有想过评估集本身也会说谎」。答成「为了测试拒答功能」只算及格,答出「不放会让某个故障在报表上完全不可见」才是满分。
- 推导只有一步:一个只会硬答的系统,在只有可答问题的评估集上能拿到很高的分——它每次都塞材料给模型,模型每次都编一段话,而评估集根本没有「应该拒答」这一栏。于是最危险的故障在报表上是不存在的。
- 结论:无答案问题是唯一能让「乱编」显形的东西。它不参与召回率,它的指标是拒答率——检索侧有没有把不够格的候选全挡下来,生成侧有没有真的说出「资料里没有」。
- 出题上有个必须说的细节:无答案问题必须留强干扰词,比如问「网页端支持哪些浏览器」而语料里恰好有一句「提交工单请附上浏览器与版本」。没有干扰词的无答案题检索器一条都捞不到,你测出来的是分词器不是系统。
- 可预期的追问是「拒答率低怎么办」。分两层查:先看检索侧的门槛是不是形同虚设(分数阈值定得太低,不相干的块也过关),再看生成侧的提示词有没有明确的拒答指令,两层都要有,只靠提示词兜是不牢的。
How to reason about it · think before answering
- It looks easy but really asks whether you have considered that the eval set itself can lie. 'To test the refusal path' is a pass; 'without them the worst failure is invisible in the report' is a full mark.
- The derivation is one step: a system that always answers scores well on a set of answerable questions only. It stuffs context in, the model writes something, and the set has no column for 'should have refused'. The most dangerous failure simply does not appear.
- Conclusion: unanswerable questions are the only thing that makes fabrication visible. They are excluded from recall and scored on abstention instead - did retrieval gate out every weak candidate, and did generation actually say the material does not cover this.
- One authoring detail worth stating: unanswerable questions need strong distractor terms. Ask which browsers the web client supports when the corpus only says 'attach your browser and version when filing a ticket'. Without distractors retrieval returns nothing and you are testing your tokenizer, not your system.
- Expected follow-up: what if the abstention rate is low? Check two layers - whether the retrieval score gate is effectively a no-op, and whether the generation prompt carries an explicit refusal instruction. You need both; a prompt alone is not a reliable gate.
答题要点
- 只有可答问题的评估集,会让「不知道也硬答」这个故障完全不可见。
- 无答案问题不算召回率,它的指标是拒答率,检索侧和生成侧各看一层。
- 出题必须留强干扰词,否则检索器一条都捞不到,测的是分词器。
- 建议无答案题占比不低于评估集的一成五,跟多跳题一起构成覆盖度底线。
- 拒答率低要分两层查:检索门槛是否形同虚设,生成提示词有没有拒答指令。
Key points
- An all-answerable eval set makes 'answers confidently when it should not' completely invisible.
- Unanswerable items are scored on abstention, not recall, and you check both the retrieval gate and the generation refusal.
- Author them with strong distractor terms, or retrieval returns nothing and you are testing the tokenizer.
- Keep them at roughly 15% or more of the set, alongside multi-hop items, as the coverage floor.
- A low abstention rate splits into two causes: a no-op retrieval score gate, or a missing refusal instruction in the prompt.
评论
登录后即可参与讨论
还没有评论,来说第一句。