Skip to content

新检索系统的评估分数只有旧系统的四分之一 ​

团队换了一个更好的嵌入模型,人工抽查的感觉是明显变好了。在离线评估集上一跑,准确率从 0.78 掉到 0.18。评估集是半年前认真标注的,大家开始怀疑是不是新模型有问题。

检索系统的评估看上去很简单:准备一批查询,标出每个查询对应哪些文档是相关的,看系统返回的结果里命中了多少。麻烦在于「标出哪些文档相关」这一步几乎不可能做全,而标注是怎么来的、指标怎么选、查询有多少,都会让同一个系统得到很不一样的分数。

本文用完整标注已知的合成数据,把几种常见的偏差量出来。实验见文末,不涉及任何真实模型。

用完整的标注评估(实际情况)旧系统0.775新系统0.985标注只覆盖旧系统返回过的文档,没标注的算不相关旧系统0.775新系统0.182新系统返回的真正相关的文档里,81% 从来没有被标注过这套标注量的是「与旧系统有多像」,不是「有多好」换系统时要把新系统返回、而标注里没有的结果补标;至少先统计这部分占多少未标注的比例高,分数就不能用来比较
图 1 · 实测(固定种子的合成数据,完整标注已知):评估集的标注通常是这样来的:把旧系统返回的结果交给人判断相关不相关。新系统找到了旧系统没找到的相关文档,这些文档从来没有人看过,评估时被当成「不相关」。于是一个真实准确率 0.985 的系统被评成了 0.182,比它要替换的旧系统低得多。

一、实验的设定 ​

2000 篇文档分属 50 个主题,200 个查询各属一个主题,同主题的文档就是相关的。这样每个查询的「全部相关文档」是已知的,平均 40 篇,可以算出任何系统的真实表现。

「检索系统」用真实相关度加上随机噪声再排序来模拟:噪声小的系统排得更准,是真的更好。指标都看前 10 条。

二、标注只覆盖了旧系统见过的文档 ​

真实的评估集通常是这样建的:把当时的系统返回的前若干条结果交给人判断,相关的记下来。没有人会把 2000 篇文档对 200 个查询全部看一遍。

评估方式旧系统新系统
用完整的标注(实际情况)0.7750.985
标注只覆盖旧系统返回过的文档,没标注的算不相关0.7750.182

新系统实际上好得多。但它找到的相关文档大多是旧系统没有返回过的,这些文档没有人看过,在评估集里没有标注,被当成不相关:

text
新系统返回的真正相关的文档里,有 81% 从来没有被标注过

这套标注衡量的其实是「与旧系统的结果有多像」。旧系统在自己参与构建的评估集上天然占优,任何与它不同的系统都会吃亏,越不同越吃亏。开头的 0.78 掉到 0.18 就是这个现象。

处理办法:

  • 评估新系统之前,先统计它返回的结果里有多少是未标注的。这个比例高,分数就没有比较的意义。
  • 把新系统返回而没有标注的结果补标,再比较。评估集要随着系统一起演进,每次候选系统都贡献一批新的待标注文档。
  • 构建评估集时,用多个不同的系统(关键词检索、不同的向量模型)的结果合起来送标,减少对单一系统的偏向。
  • 只在已标注的结果上计算的指标可以作为参考,它同样有偏,方向相反。

显式反馈数据里「没有反馈不等于负反馈」是同一类问题,见没评分不等于打了零分。

三、两个指标,相反的结论 ​

构造两个系统:甲的第一条结果总是相关的,后面的质量差;乙的前两条总是不相关的,后面的质量好。

指标甲乙
首个命中的倒数排名(MRR)1.0000.333
前 10 条准确率0.5550.792

按第一个指标甲完胜,按第二个指标乙明显更好。两个指标都没有算错,它们回答的是不同的问题:

  • 首个命中的排名,关心「第一个有用的结果排在哪」。适合用户只看第一条的场景:导航类搜索、只取第一条做答案。
  • 前 k 条的准确率或召回率,关心「这 k 条里有多少有用的」。适合把前 k 条都交给下游的场景,检索增强生成就是这种:前 k 条全部拼进提示词,里面有多少相关、有多少干扰,比第一条是什么更重要。

选指标之前先确定结果是怎么被使用的。给生成模型提供上下文时,通常更关心召回(需要的内容有没有被取回来)以及取回内容里无关部分的比例;取多少条也是要一起评估的参数。检索返回的相似度分数本身能不能当作相关性来用,见相似度 0.86,这两段文字其实毫不相干。

两阶段检索:分开评估召回与排序 ​

很多检索系统分两步:召回从全部文档里取出候选,排序只在候选里调整顺序。最终的指标把两步混在一起,看不出问题出在哪一步。用一个合成查询看(5 个文档,相关性等级预先写定:c=3、d=2、b=1,a、e 不相关):

text
召回前 3 个:['a', 'b', 'e'],覆盖相关文档 1/3;排序后 ['b', 'e', 'a']
召回 3 个时 nDCG@3:排序器 0.106,候选内最好的排序 0.106(上限)
召回前 5 个:['a', 'b', 'e', 'c', 'd'],覆盖相关文档 3/3;排序后 ['b', 'c', 'd']
召回 5 个时 nDCG@3:排序器 0.736,候选内最好的排序 1.000(上限)

召回 3 个时,排序器已经做到了候选内最好的顺序,再怎么调排序也只有 0.106:最相关的 c 和 d 根本不在候选里。召回 5 个后上限变成 1.000,排序器的 0.736 才说明排序本身还有改进的空间。所以排查时要分开报告两个数:召回对相关文档的覆盖,以及排序器的得分相对候选内上限的差距。

还有一个更隐蔽的问题出在指标的分母上。nDCG 的分母是「理想排序」的得分,如果理想排序只在候选里算:

text
召回 3 个时,分母只取候选内标签:排序器 1.000,候选内最好的排序 1.000;分母取完整标签:0.106

同一个结果被报告成满分。分母只取候选内的标签,等于默认「候选之外没有相关文档」,召回的缺失就从指标里消失了。和上一节标注不全的问题一样,分母必须来自完整、固定的相关性标签,而不是来自被评估的系统自己的输出。

把上面三组数字放在一起,召回、排序和指标分母各自的作用就清楚了:

召回 3 个(候选 a、b、e)排序器0.106候选内最好的排序(上限)0.106召回 5 个(覆盖 c、d)排序器0.736候选内最好的排序(上限)1.000召回 3 个,分母只取候选内标签1.000 · 虚假满分
图 2 · 合成查询的 nDCG@3(相关性 c=3、d=2、b=1):召回 3 个时最相关的 c、d 不在候选里,排序器已做到候选内最好,也只有 0.106;召回 5 个后上限变成 1.000,0.736 才反映排序本身的差距。分母若只取候选内标签,召回 3 个的结果会被报告成满分

这个例子只有一个合成查询,用来说明指标的结构,不能推出扩大召回在真实系统里会带来多大的收益;那需要多查询的标注、按时间划分的数据和线上实验。

四、查询太少,差异不可靠 ​

两个真实水平相近的系统,乙略好一点:

text
两个相近的系统,用前 30 个查询比较:乙比甲高 +0.030,自助法 95% 区间 [+0.000, +0.060],重抽样中乙反而更差的占 2%
两个相近的系统,用前 200 个查询比较:乙比甲高 +0.029,自助法 95% 区间 [+0.009, +0.049],重抽样中乙反而更差的占 0%

30 个查询时,差异的区间下限贴着 0:换一批查询,结论可能是持平。200 个查询时区间明确在 0 以上。

几十个查询的评估集很常见,因为标注贵。在这样的集合上,0.03 的差别不足以支持「乙更好」。调参时在同一个小评估集上反复试几十组参数再挑最高的,挑出来的很可能只是碰巧适合这批查询。

  • 报告差异时带上区间。对每个查询算两个系统的指标之差,用自助法重抽样即可,不需要额外的假设。
  • 两个系统在同一批查询上比较,看逐个查询的差,而不是各自平均值的差。
  • 调参用一部分查询,最终比较用另一部分。

统计显著与实际意义的区别,见 p 值 0.03,这个功能就该上线吗。

五、平均值盖住了整类失败 ​

text
全部查询的平均准确率 0.867
其中 25 个查询(占 12%)属于系统处理不了的那一类:这一类的准确率 0.000,其余 0.991
一条相关结果都没有的查询占 12%

0.867 看上去不错,实际情况是:88% 的查询几乎完美,12% 的查询一条都没中。对那 12% 的用户,系统完全不可用;在检索增强生成里,这意味着模型在没有任何相关上下文的情况下作答。

这类整体失败通常集中在某一类查询上:专有名词和编号、很短的查询、另一种语言、需要按时间或数值过滤的问题、文档库里根本没有答案的问题。向量检索对精确的标识符不敏感是其中最常见的一种。

  • 除了平均值,报告「零命中的查询占比」和指标的分布。
  • 给查询打上类别,分组看指标。
  • 评估集里要有「库里没有答案」的查询,期望的行为是系统能表明没有找到。

六、让模型来补标:评审模型本身也是被评估的组件 ​

第二节的办法是补标新系统取回的未标注结果。补标量大时,常见的做法是让一个大模型来判断「这段文档是否回答了这个查询」。这样做没有问题,只是评估链路里多了一个会出错的组件,它的误差会直接进入最后的分数:评审模型漏判了一类相关文档,新系统在这类查询上的得分就被低估;它偏好某种写法,用这种写法组织的文档就被高估。

所以评审模型要和被评估的系统一样对待:

  • 先拿人工标注校准:抽一部分查询由人标注,计算评审模型与人工的一致率,并按查询类别分组看,而不是只看总数。一致率不够的类别,不能交给评审模型。
  • 固定评审模型与提示:换了评审模型或改了提示,历史分数就不再可比,要重新校准。
  • 留意已知的偏差:已有研究报告过大模型做评审时的位置偏好、对更长回答的偏好,以及对与自己风格相近内容的偏好。比较两个结果时交换顺序各判一次,是常见的缓解办法。
  • 抽取同理:如果评估依赖一个模型先从文档里抽取字段(例如答案片段、实体),抽取的准确率也要单独评估,否则分不清是检索错了还是抽取错了。

这一节没有配套实验,只是方法上的提醒。评审模型在具体任务上的一致率需要用自己的数据测。

七、检查清单 ​

  • 知道评估集的标注是怎么来的;评估新系统时统计未标注结果的比例,并补标。
  • 指标与结果的使用方式一致;同时看多个指标。
  • 两阶段检索分开报告召回覆盖与排序相对候选内上限的差距;nDCG 的分母用完整标签。
  • 比较两个系统时给出差异的区间。
  • 调参与最终评估用不同的查询。
  • 用模型补标或抽取时,先用人工标注校准它的一致率,并固定评审模型与提示。
  • 按查询类别分组,关注零命中的比例。
  • 离线分数与线上效果定期对照。离线评估只是代理,最终要看的是用户的问题有没有被解决。

本文只讨论检索这一步。生成的答案是否忠实于取回的内容、引用是否正确,是另外的评估问题;本文的数字来自合成数据,只说明评估方法的性质。


配套实验

参考资料

文章以 CC BY-NC-SA 4.0 授权 · 代码片段以 MIT 授权