TECH ARTICLES
搜索系统 评测指标 信息检索

搜索评测指标全解析:从 Recall@K 到 CTR,一条链路上的 10 个数字

Jackie Zhan 2026-06-26
目录
一、召回阶段:「漏没漏」怎么量化? 二、排序阶段:「排没排对」怎么量化? 三、在线指标:用户用脚投票,靠谱吗? 四、F1 和 MAPE:那两个总被叫错场的指标 五、总结:一张表搞定指标选用逻辑

上周,一个做搜索的朋友跟我吐槽:「我们模型 nDCG 涨了 3 个点,老板很高兴,结果上线 AB 测试,CTR 纹丝不动,差点被骂。」

我问他:「那你线下评测的时候,看召回率了吗?」

他愣了一下:「nDCG 不就够了吗?」

这就是问题所在。很多人聊搜索评测,张口就是 nDCG、MRR,好像这几个洋缩写就能代表一切。但你有没有想过:一次搜索从你按下回车到看到结果,中间至少经过了三道关卡,每道关卡考的根本不是同一件事。

召回阶段考的是「该捞的东西有没有捞上来」;排序阶段考的是「捞上来的东西有没有排对顺序」;上线之后考的是「真实用户买不买账」。你拿一个排序指标去衡量召回好坏,就像拿尺子量体重——工具没错,但量错了维度。

所以这篇文章,我不打算给你罗列十个公式让你背。我想带你顺着一次搜索的真实链路走一遍,看清楚每一个数字到底在回答哪一个问题。读完你会明白,为什么我朋友的 nDCG 涨了,CTR 却不动——答案,藏在他没看的那个指标里。

① 召回 Recall@K:从千万文档里捞出几百个候选 ② 排序 MRR / MAP / nDCG:把候选排出先后顺序 ③ 在线 CTR / 有点比:用户真实反馈 链路越往下,离用户越近,离「真相」也越近
搜索评测指标,本质上是给链路的每一层装一个「体温计」

一、召回阶段:「漏没漏」怎么量化?

先说召回。这是整条链路的第一关,也是最容易被低估的一关。

想象一下,你在一个有一千万件商品的电商库里搜「无线降噪耳机」。系统不可能把这一千万件逐一精细打分——太慢了。它得先用一套又快又糙的方法,从一千万里粗筛出几百个「八成相关」的候选,扔给后面的排序模型。

这个「粗筛」就是召回。而衡量召回好坏的核心问题只有一个:那些本该被捞上来的相关文档,你到底捞上来了多少?

召回率(Recall):分母是「所有该找到的」

召回率的定义很朴素:在所有真正相关的文档里,你成功找回了多大比例。

# 召回率 = 找回的相关文档数 / 全部相关文档数
recall = len(retrieved & relevant) / len(relevant)

# 例:库里一共有 50 件真正相关的耳机
# 你的召回模块捞回来的候选里,命中了 40 件
# recall = 40 / 50 = 0.8

关键在分母——它是所有相关文档的总数,不是你返回结果的数量。这一点恰恰是召回率和准确率的分水岭,后面我们会专门掰扯。

召回率为什么这么重要?因为它是整条链路的「天花板」。一个残酷的事实:召回阶段漏掉的文档,后面再牛的排序模型也救不回来。排序模型只能在候选集里挑挑拣拣,它没法把一个根本没进候选集的好结果变出来。召回率是 0.8,意味着 20% 的好东西从一开始就出局了,这 20% 你这辈子都别想排上去。

Recall@K:现实世界只看前 K 个

但纯召回率有个不接地气的地方:它不管位置。理论上你把全库一千万件全返回,召回率必然是 100%——可这有什么意义?

所以工程上真正用的是 Recall@K:只看排在前 K 个的结果里,命中了多少相关文档。

# Recall@K = 前 K 个结果里的相关文档数 / 全部相关文档数
# K 通常取 100、500、1000,对应召回返回的候选集大小

# 库里 50 件相关,召回模块返回 Top-500 候选
# 这 500 个里命中了 45 件
# Recall@500 = 45 / 50 = 0.9

这个 K 怎么定?它直接等于你给后面排序模型留多大的「池子」。K 越大,召回率越容易冲高,但排序的计算压力也越大;K 越小越省算力,但漏掉好结果的风险越高。Recall@K 的本质,是在「别漏」和「别贪」之间找一个平衡点。

数据说话
在向量检索(RAG、语义搜索)里,Recall@K 几乎是召回质量的唯一硬指标。业界常用 Recall@10、Recall@100 来对比不同 embedding 模型或向量索引(如 HNSW、IVF)的优劣——同样的 K,谁的 Recall 高,谁的「捞回能力」就强。这也是为什么向量数据库的 benchmark 报告里,永远把 Recall 摆在第一栏。

记住这一层的核心:召回阶段,你只关心一件事——别让好东西在第一道门就被挡在外面。排序的精彩,得先有召回的全面兜底。

二、排序阶段:「排没排对」怎么量化?

候选捞上来了,假设有 500 个。现在轮到排序模型登场,它的任务是:把这 500 个排出一个先后顺序,最相关的放最前面。

到了这一层,光看「漏没漏」就不够了,你得看「顺序对不对」。而衡量顺序,有一组听起来很唬人、其实逻辑很清晰的指标。我们一个一个拆。

准确率(Precision):召回率那个「冤家」

先把准确率(精确率,Precision)讲清楚,因为它老跟召回率搞混。

一句话区别:召回率的分母是「所有该找的」,准确率的分母是「你实际返回的」。

# 准确率 = 返回结果中的相关文档数 / 返回结果总数
precision = len(retrieved & relevant) / len(retrieved)

# 同样用 Precision@K:前 K 个结果里有多少是相关的
# 前 10 个结果里,8 个相关
# Precision@10 = 8 / 10 = 0.8
常见误解
「准确率高 = 搜索好」,这是新手最容易踩的坑。你只返回一个最有把握的结果,准确率轻松 100%,可召回率低得可怜——用户要的另外 49 个相关结果,你一个没给。反过来,把全库都返回,召回率 100%,准确率惨不忍睹。这两个指标天生互相拉扯,单看任何一个都会骗你。这也正是后面 F1 要出场的原因。

所以在排序场景里,单纯的准确率/召回率都太「粗」——它们把相关性当成「是/否」的二值判断,而且不在乎你把相关结果排在第 1 位还是第 10 位。可对用户来说,第 1 位和第 10 位的体验天差地别。于是,更精细的位置敏感指标登场了。

MRR:你只关心「第一个对的」排多前

MRR(Mean Reciprocal Rank,平均倒数排名)是这组里最简单的。它只问一件事:第一个相关结果,排在第几位?

它取这个名次的倒数。第一个相关结果排第 1 位,得分 1;排第 2 位,得分 1/2;排第 5 位,得分 1/5。然后把所有查询的得分平均一下,就是 MRR。

# 单条查询的 RR = 1 / 第一个相关结果的排名
# 三条查询,第一个正确结果分别排在第 1、第 3、第 2 位
rr = [1/1, 1/3, 1/2]
mrr = sum(rr) / len(rr)   # = 0.611

MRR 的脾气很「专一」:它只盯着第一个命中的结果,第一个之后排得再好再烂,它一概不管。这听起来很偏激,但对某些场景恰恰完美——比如问答、导航类搜索。你搜「Python 官网」,要的就是第一条直接给对,后面九条好不好你压根不看。这种「只要第一个对就行」的场景,MRR 就是最贴切的尺子。

MAP:把每个相关结果的位置都算进去

但更多时候,用户要的不止一个结果。你搜「机器学习入门资料」,希望前几条都是好货。这时 MRR 就不够用了——它只看第一个。

MAP(Mean Average Precision,平均精度均值)补上了这个缺口。它的思路是:每命中一个相关结果,就在那个位置算一次准确率,再把这些准确率平均。相关结果排得越靠前,这一串准确率就越高。

# 假设返回 5 条,相关性为 [✓, ✗, ✓, ✗, ✓](共 3 个相关)
# 在每个命中位置算 Precision@该位置:
#   位置1 命中 → 1/1 = 1.00
#   位置3 命中 → 2/3 = 0.67
#   位置5 命中 → 3/5 = 0.60
ap = (1.00 + 0.67 + 0.60) / 3   # AP = 0.756
# MAP = 把所有查询的 AP 再平均

MAP 的好处是它同时照顾了「全不全」和「靠不靠前」:漏掉相关结果会拉低 AP,把相关结果排在后面也会拉低 AP。它是召回率和准确率在「排序」这件事上的一次握手言和。所以经典信息检索论文里,MAP 长期是综合排序质量的标配指标。

nDCG:当「相关」不再只有 0 和 1

MAP 还有一个隐藏假设:相关性是二值的——要么相关,要么不相关。可现实哪有这么干脆?

你搜「iPhone 充电器」,一个原装充电器是「高度相关」,一个第三方兼容充电器是「中度相关」,一根纯数据线是「沾点边」。这三者显然不该被一视同仁。

nDCG(normalized Discounted Cumulative Gain,归一化折损累计增益)就是为分级相关性而生的。它的名字很吓人,但拆开看就三个动作:

import numpy as np
# 相关性等级(按你模型排出的顺序)
rels = [3, 2, 3, 0, 1]
def dcg(r):
    return sum((2**rel - 1) / np.log2(i + 2) for i, rel in enumerate(r))
idcg = dcg(sorted(rels, reverse=True))   # 理论最优排序的 DCG
ndcg = dcg(rels) / idcg                    # 归一化到 0~1
print(round(ndcg, 3))   # ≈ 0.948

为什么 nDCG 是当今搜索/推荐排序的「头牌指标」?因为它最接近用户的真实感受:既在乎结果有多相关(分级 Gain),又在乎好结果排得有多靠前(位置折损)。一个把「中度相关」误排到第一、「高度相关」压到第五的模型,nDCG 会毫不留情地扣分——而这正是用户会皱眉的地方。

关键区别
三个排序指标,一句话各自记住:MRR 只问「第一个对的排多前」(适合问答/导航);MAP 问「所有相关的排得整体好不好」(二值相关性);nDCG 问「考虑相关程度后,排序有多接近完美」(分级相关性,最贴近用户体验)。从 MRR 到 MAP 再到 nDCG,是一条「越来越在乎细节」的进化线。

到这里,离线评测的工具箱基本齐了。但你有没有发现一个问题——上面所有指标,都建立在一个前提上:我们事先知道哪些文档是「相关」的。可这个「相关」是谁说了算?真实的用户,会同意我们的判断吗?

三、在线指标:用户用脚投票,靠谱吗?

前面两层,nDCG 也好 MAP 也好,本质上都是「考试」——题目和标准答案都是我们人工标注出来的。标注员说这条相关,它就相关。

但标注员不是用户。真正的考官,是那个坐在屏幕前、烦躁地搜东西的真人。他点不点你的结果,才是终极裁决。于是有了在线指标——它们不依赖任何人工标注,直接读用户的行为。

文档点击率(CTR):最基础的那一票

CTR(Click-Through Rate,点击率)是最广为人知的在线指标:某个结果被展示了 N 次,被点击了 M 次,CTR 就是 M/N。

# 文档点击率 = 该文档的点击次数 / 该文档的曝光(展现)次数
doc_ctr = clicks / impressions
# 一条结果展现了 1000 次,被点了 120 次 → CTR = 12%

CTR 高,通常说明这条结果对用户有吸引力。它是广告、推荐、搜索通吃的硬通货。但注意,CTR 衡量的是单个文档的吸引力,它回答不了「这一整次搜索,用户满不满意」。要回答这个,得换个口径。

有点比:这次搜索,用户究竟点没点

这就是国内搜索团队常说的「有点比」——有点击的搜索次数,占总搜索次数的比例。

# 有点比 = 有点击行为的搜索次数 / 总搜索次数
clickthrough_rate = searches_with_click / total_searches
# 它衡量的是"一次搜索"层面,而非单个文档

差别在哪?文档 CTR 站在「文档」视角,有点比站在「搜索会话」视角。一次搜索只要用户点了任意一条结果,就算「有点」。有点比低,意味着大量搜索是「点都不想点」的——这往往是结果烂到用户失望的信号。业界经验里,有点比通常在 70% 上下。

再往细里抠,还有个「首屏有点比」:用户在第一屏(不翻页、不下滑)就发生点击的搜索占比。它比有点比更严格,因为好的搜索应该让用户在第一屏就找到答案,不用费劲翻。所以三者关系永远是:

有点比 ≈ 70%(最宽松) > 首屏有点比 ≈ 60%(更严格) > 文档点击率 单文档维度,最低
口径越严格,数值越低:有点比 > 首屏有点比 > 文档点击率

除了点击类,在线指标还有一大家子,因业务而异:电商看 CVR(转化率,点了之后买没买)、看 GMV;内容平台看停留时长、看跳出率;通用搜索还会看「换词率」「翻页率」——用户频繁换词、不停翻页,恰恰说明他没找到,这些是反向指标。

常见误解
「CTR 高就是搜索质量好」——这个等号也不成立。一个标题党、一张擦边封面,能把 CTR 拉得很高,但用户点进去发现货不对板,转头就走。这就是为什么 CTR 和 CVR 经常不同向:吸引点击是一回事,满足需求是另一回事。点击率衡量「诱惑力」,转化率衡量「满足度」,别拿前者冒充后者。

所以在线指标的真正价值,不是某一个数字漂亮,而是它们共同拼出用户的真实满意度——这是任何离线标注都模拟不出来的。但它也有代价:它只能上线后才拿得到,而且容易被外部因素(流量、热点、UI 改版)污染。离线和在线,注定是一对需要互相校准的搭档。

四、F1 和 MAPE:那两个总被叫错场的指标

最后聊两个「编外」指标。它们不是搜索链路的主角,但经常在评测报告里露脸,而且特别容易被用错地方。

F1:给准确率和召回率做个「和事佬」

还记得前面那对冤家吗——准确率和召回率,一个升另一个就容易降。那评测时到底该信谁?

F1 的答案是:谁都别偏袒,取它俩的调和平均

# F1 = 2 × (Precision × Recall) / (Precision + Recall)
# 调和平均的特点:任何一个特别低,F1 就被拖低
precision, recall = 0.9, 0.3
f1 = 2 * precision * recall / (precision + recall)   # = 0.45

为什么用调和平均,而不是简单的算术平均?因为调和平均「怕短板」。上面例子里准确率 0.9 很漂亮,但召回率只有 0.3,算术平均是 0.6 看着还行,F1 却只有 0.45——它逼着你不能靠一个指标偏科。F1 的潜台词是:准确和全面,你得两手都硬。

insider 视角
F1 在搜索的「排序」环节其实用得不多——因为排序更在乎位置,nDCG/MAP 更合适。F1 真正的主场是相关性判断、意图分类这类二分类任务:判断一个 query 是不是导航类、一个文档与 query 相不相关。这种「是/否」的判定,才是 F1 的标准战场。把 F1 硬套到排序质量上,就是叫错了场。

MAPE:它根本不是搜索质量指标

MAPE(Mean Absolute Percentage Error,平均绝对百分比误差)更是个「外来户」。它衡量的是预测值和真实值差了百分之几,是个回归(预测)指标。

# MAPE = 平均( |真实值 - 预测值| / |真实值| ) × 100%
# 衡量预测偏离真实的百分比,越小越好
# 预测某 query 明天的搜索量 1000,实际 1200 → 误差 16.7%

那它跟搜索有什么关系?关系在「外围」:搜索系统里有大量预测任务——预估某条结果的 CTR、预测一个 query 未来的搜索量、预测库存或流量。这些预测准不准,就用 MAPE 来量。它不评价「搜得好不好」,它评价「预测准不准」。

关键区别
千万别把 MAPE 和搜索相关性指标混为一谈。Recall、nDCG、CTR 回答的是「搜索结果好不好」;MAPE 回答的是「我对某个数值的预测准不准」。一个是排序质量,一个是预测精度,俩世界。它出现在搜索评测里,通常是在评估 CTR 预估模型、流量预测模型这些「幕后辅助系统」,而不是搜索结果本身。

把这两个「编外」指标的边界划清楚,你就不会再犯「拿 F1 评排序、拿 MAPE 评相关性」的错。用对工具,比知道工具更重要。

五、总结:一张表搞定指标选用逻辑

绕了一大圈,我们回到开头那个问题:为什么我朋友 nDCG 涨了 3 个点,CTR 却不动?

现在答案很清楚了。nDCG 衡量的是「在已召回的候选里,排序排得好不好」;但如果召回阶段就漏掉了用户真正想要的结果,排序再优化也是在一堆次品里挑最好的。他优化的是排序层,瓶颈却在召回层——线下排序指标涨了,线上用户却根本没看到那个本该出现的好结果。他该看的,是 Recall@K。

这就是搜索评测的第一性原理:指标没有好坏,只有「考的是不是这一层」。顺着链路对号入座,是唯一不会出错的姿势:

链路阶段核心指标回答的问题
召回Recall@K、召回率该捞的有没有捞上来(链路天花板)
排序MRR、MAP、nDCG、Precision捞上来的有没有排对顺序
在线CTR、有点比、首屏有点比、CVR真实用户买不买账
外围/辅助F1(分类)、MAPE(预测)判定准不准、预测偏多少

几个最该刻进脑子的要点:

我的看法:太多团队把精力 all in 在排序模型上,反复打磨 nDCG 那零点几个点,却对召回率视而不见。这是典型的「在天花板下面装修」。我的建议恰恰相反——先把召回的天花板顶高,再去抠排序的精装修。顺序反了,努力会大打折扣。

下一步建议:

  1. 翻出你手上搜索系统的评测报告,按「召回 / 排序 / 在线」三栏重新归类一遍指标——大概率你会发现某一层是空白的,那就是你的盲区。
  2. 下次再看到「nDCG 涨了」的喜报,先追问一句:「召回率动了吗?线上有点比验证了吗?」一个指标的涨跌,永远要放回它所在的那一层去解读。