搜索评测指标全解析:从 Recall@K 到 CTR,一条链路上的 10 个数字
上周,一个做搜索的朋友跟我吐槽:「我们模型 nDCG 涨了 3 个点,老板很高兴,结果上线 AB 测试,CTR 纹丝不动,差点被骂。」
我问他:「那你线下评测的时候,看召回率了吗?」
他愣了一下:「nDCG 不就够了吗?」
这就是问题所在。很多人聊搜索评测,张口就是 nDCG、MRR,好像这几个洋缩写就能代表一切。但你有没有想过:一次搜索从你按下回车到看到结果,中间至少经过了三道关卡,每道关卡考的根本不是同一件事。
召回阶段考的是「该捞的东西有没有捞上来」;排序阶段考的是「捞上来的东西有没有排对顺序」;上线之后考的是「真实用户买不买账」。你拿一个排序指标去衡量召回好坏,就像拿尺子量体重——工具没错,但量错了维度。
所以这篇文章,我不打算给你罗列十个公式让你背。我想带你顺着一次搜索的真实链路走一遍,看清楚每一个数字到底在回答哪一个问题。读完你会明白,为什么我朋友的 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 的本质,是在「别漏」和「别贪」之间找一个平衡点。
记住这一层的核心:召回阶段,你只关心一件事——别让好东西在第一道门就被挡在外面。排序的精彩,得先有召回的全面兜底。
二、排序阶段:「排没排对」怎么量化?
候选捞上来了,假设有 500 个。现在轮到排序模型登场,它的任务是:把这 500 个排出一个先后顺序,最相关的放最前面。
到了这一层,光看「漏没漏」就不够了,你得看「顺序对不对」。而衡量顺序,有一组听起来很唬人、其实逻辑很清晰的指标。我们一个一个拆。
准确率(Precision):召回率那个「冤家」
先把准确率(精确率,Precision)讲清楚,因为它老跟召回率搞混。
一句话区别:召回率的分母是「所有该找的」,准确率的分母是「你实际返回的」。
# 准确率 = 返回结果中的相关文档数 / 返回结果总数
precision = len(retrieved & relevant) / len(retrieved)
# 同样用 Precision@K:前 K 个结果里有多少是相关的
# 前 10 个结果里,8 个相关
# Precision@10 = 8 / 10 = 0.8
所以在排序场景里,单纯的准确率/召回率都太「粗」——它们把相关性当成「是/否」的二值判断,而且不在乎你把相关结果排在第 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,归一化折损累计增益)就是为分级相关性而生的。它的名字很吓人,但拆开看就三个动作:
- Gain(增益):每个结果按相关性等级给分,比如高度相关 = 3 分,中度 = 2 分,沾边 = 1 分,不相关 = 0 分。
- Discounted(折损):排得越靠后,分数打的折越狠(除以一个随位置增长的
log系数)。第 1 位拿满分,第 10 位的同样相关性只能拿零头。 - normalized(归一化):用你的实际得分(DCG)除以「理论最优排序」的得分(IDCG),把结果压到 0~1 之间,方便跨查询对比。
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 会毫不留情地扣分——而这正是用户会皱眉的地方。
到这里,离线评测的工具箱基本齐了。但你有没有发现一个问题——上面所有指标,都建立在一个前提上:我们事先知道哪些文档是「相关」的。可这个「相关」是谁说了算?真实的用户,会同意我们的判断吗?
三、在线指标:用户用脚投票,靠谱吗?
前面两层,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% 上下。
再往细里抠,还有个「首屏有点比」:用户在第一屏(不翻页、不下滑)就发生点击的搜索占比。它比有点比更严格,因为好的搜索应该让用户在第一屏就找到答案,不用费劲翻。所以三者关系永远是:
除了点击类,在线指标还有一大家子,因业务而异:电商看 CVR(转化率,点了之后买没买)、看 GMV;内容平台看停留时长、看跳出率;通用搜索还会看「换词率」「翻页率」——用户频繁换词、不停翻页,恰恰说明他没找到,这些是反向指标。
所以在线指标的真正价值,不是某一个数字漂亮,而是它们共同拼出用户的真实满意度——这是任何离线标注都模拟不出来的。但它也有代价:它只能上线后才拿得到,而且容易被外部因素(流量、热点、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 的潜台词是:准确和全面,你得两手都硬。
MAPE:它根本不是搜索质量指标
MAPE(Mean Absolute Percentage Error,平均绝对百分比误差)更是个「外来户」。它衡量的是预测值和真实值差了百分之几,是个回归(预测)指标。
# MAPE = 平均( |真实值 - 预测值| / |真实值| ) × 100%
# 衡量预测偏离真实的百分比,越小越好
# 预测某 query 明天的搜索量 1000,实际 1200 → 误差 16.7%
那它跟搜索有什么关系?关系在「外围」:搜索系统里有大量预测任务——预估某条结果的 CTR、预测一个 query 未来的搜索量、预测库存或流量。这些预测准不准,就用 MAPE 来量。它不评价「搜得好不好」,它评价「预测准不准」。
把这两个「编外」指标的边界划清楚,你就不会再犯「拿 F1 评排序、拿 MAPE 评相关性」的错。用对工具,比知道工具更重要。
五、总结:一张表搞定指标选用逻辑
绕了一大圈,我们回到开头那个问题:为什么我朋友 nDCG 涨了 3 个点,CTR 却不动?
现在答案很清楚了。nDCG 衡量的是「在已召回的候选里,排序排得好不好」;但如果召回阶段就漏掉了用户真正想要的结果,排序再优化也是在一堆次品里挑最好的。他优化的是排序层,瓶颈却在召回层——线下排序指标涨了,线上用户却根本没看到那个本该出现的好结果。他该看的,是 Recall@K。
这就是搜索评测的第一性原理:指标没有好坏,只有「考的是不是这一层」。顺着链路对号入座,是唯一不会出错的姿势:
| 链路阶段 | 核心指标 | 回答的问题 |
|---|---|---|
| 召回 | Recall@K、召回率 | 该捞的有没有捞上来(链路天花板) |
| 排序 | MRR、MAP、nDCG、Precision | 捞上来的有没有排对顺序 |
| 在线 | CTR、有点比、首屏有点比、CVR | 真实用户买不买账 |
| 外围/辅助 | F1(分类)、MAPE(预测) | 判定准不准、预测偏多少 |
几个最该刻进脑子的要点:
- 召回定上限,排序定体验:召回漏了的,排序永远救不回来,所以 Recall@K 是地基。
- 准确率和召回率是天生冤家:单看一个会骗你,要么用 F1 平衡,要么按场景明确取舍。
- 排序三件套各有专长:MRR 看第一个、MAP 看整体、nDCG 看分级相关性,nDCG 最贴用户。
- 离线指标是模拟考,在线指标才是高考:两者经常对不上,对不上时优先信用户行为,并回头排查离线标注。
我的看法:太多团队把精力 all in 在排序模型上,反复打磨 nDCG 那零点几个点,却对召回率视而不见。这是典型的「在天花板下面装修」。我的建议恰恰相反——先把召回的天花板顶高,再去抠排序的精装修。顺序反了,努力会大打折扣。
下一步建议:
- 翻出你手上搜索系统的评测报告,按「召回 / 排序 / 在线」三栏重新归类一遍指标——大概率你会发现某一层是空白的,那就是你的盲区。
- 下次再看到「nDCG 涨了」的喜报,先追问一句:「召回率动了吗?线上有点比验证了吗?」一个指标的涨跌,永远要放回它所在的那一层去解读。
参考资料
- Normalized Discounted Cumulative Gain (NDCG) explained — Evidently AI
- Evaluation Metrics for Search and Recommendation Systems — Weaviate
- Evaluating recommendation systems (MAP, MRR, NDCG) — Shaped
- Systematic Evaluation for Information Retrieval: MAP and nDCG
- 浅谈搜索指标 & 业务辅助应用(有点比 / CTR / CVR)— CSDN
- 搜索业务各指标(有点比、首屏有点比、文档点击率)— 博客园
- 王树森《搜索引擎技术》课程笔记 — 知乎