离线评估指标(一):Precision@K、Recall@K 的定义、计算方式与适用场景

聊召回评估,我习惯先从两个最基础的指标入手——Precision@K 和 Recall@K。

说白了,这两个指标就是回答两个问题:

  • 你推荐给用户的 K 个东西里,有多少是用户真正喜欢的?
  • 用户所有喜欢的东西里,你成功捞出来了多少?

嗯,听起来很简单对吧?但实际落地的时候,坑比想象中多。我刚开始做召回时,就吃过「只看 Precision 不看 Recall」的亏,结果线上指标涨了,用户满意度反而掉了……

咱们今天就把这两个指标掰开揉碎,讲清楚。

1. 先搞懂几个基本概念

在讲公式之前,有几个术语得先对齐。不然你后面看代码会懵。

术语 含义 我习惯怎么记
Ground Truth 用户真实喜欢的物品集合 「用户心里那杆秤」
推荐列表 模型返回的 Top-K 个物品 「你递给用户的菜单」
命中 推荐列表 ∩ Ground Truth 「猜中了」
K 截断位置,比如 Top-10、Top-20 「你给用户看几个?」
一个小建议:K 的取值不是拍脑袋定的。我一般会先看业务场景——搜索结果页 K=5 就够了,信息流推荐 K=20 起步。你想想看,用户翻两屏还没看到想看的,早就划走了。

2. Precision@K:你给的,准不准?

定义

Precision@K 衡量的是:在模型返回的 Top-K 个物品中,有多少是用户真正感兴趣的。

公式长这样:

Precision@K = (推荐列表 ∩ Ground Truth) 中的物品数 / K

举个例子:

  • 用户真实喜欢的物品:{A, B, C, D, E}
  • 模型返回 Top-5:{A, B, F, G, H}
  • 命中:{A, B} → 2 个
  • Precision@5 = 2 / 5 = 0.4

说白了,你给了用户 5 个,只有 2 个是他想要的。剩下 3 个是噪音。

适用场景

我个人觉得,Precision@K 最适合的场景是:用户容忍度低、翻看成本高的地方。

  • 搜索推荐:用户搜「机械键盘」,你前几个必须是机械键盘。如果混进去一个鼠标垫,用户可能直接走了。
  • Push 推送:你给用户推 3 条消息,如果 3 条都不感兴趣,用户可能直接关通知。
  • 广告召回:广告位就那么多,每个位置都要精准。
避坑指南:我曾经在一个电商项目中,把 Precision@10 从 0.3 优化到了 0.6,结果线上转化率反而降了。后来一查,是因为模型只敢推荐用户历史行为里「安全」的品类,完全不敢探索新领域。Precision 高了,但 Recall 崩了。所以——别只看一个指标。

3. Recall@K:你捞得,全不全?

定义

Recall@K 衡量的是:用户所有感兴趣的物品中,模型成功捞出来了多少。

公式:

Recall@K = (推荐列表 ∩ Ground Truth) 中的物品数 / Ground Truth 总数

还是刚才的例子:

  • 用户真实喜欢的物品:{A, B, C, D, E} → 5 个
  • 模型返回 Top-5:{A, B, F, G, H}
  • 命中:{A, B} → 2 个
  • Recall@5 = 2 / 5 = 0.4

你看,同样的命中数,Precision 和 Recall 的分母不一样。Precision 看的是「你给的里面有多少对的」,Recall 看的是「对的里面你捞了多少」。

适用场景

Recall@K 更适合用户愿意多翻、内容池大的场景:

  • 视频推荐:用户刷抖音,刷 100 条才找到 3 条喜欢的,但他愿意刷。这时候你更关心「他喜欢的有没有被捞出来」。
  • 商品库召回:用户想买一件「蓝色、纯棉、圆领」的 T 恤,你召回阶段漏掉了,后面排序再怎么调也救不回来。
  • 长尾内容分发:新作者的内容没人看,但内容本身不错。Recall 能帮你衡量「这些冷门好内容有没有被捞到」。
我的经验:在新闻推荐里,我一般把 Recall@20 作为核心指标。因为用户对新闻的容忍度很高,翻 20 条才看到感兴趣的也正常。但如果 Recall 低于 0.3,说明模型根本没理解用户兴趣。

4. 一张图看懂 Precision 和 Recall 的关系

我画了一张图,帮你直观理解这两个指标到底在衡量什么。

Precision@K vs Recall@K 直观对比 Ground Truth 用户真实喜欢的物品 推荐列表 模型返回的 Top-K 命中 推荐 ∩ 真实 Precision@K = 命中数 / K Recall@K = 命中数 / Ground Truth 总数 💡 关键区别 分母不同,含义不同

你看这张图就明白了:

  • Precision 关心的是「红色圈里有多少绿色」——你给的列表里,命中占比高不高。
  • Recall 关心的是「蓝色圈里有多少绿色」——用户喜欢的,你捞出来多少。

5. 代码实现:Python 版

理论讲完了,咱们直接上代码。这是我项目里常用的实现方式:

def precision_at_k(recommended, ground_truth, k):
    """
    计算 Precision@K
    
    Args:
        recommended: list, 模型返回的推荐列表(已排序)
        ground_truth: set, 用户真实喜欢的物品集合
        k: int, 截断位置
    
    Returns:
        float, Precision@K 值
    """
    if k <= 0:
        return 0.0
    
    # 截断推荐列表到 Top-K
    top_k = recommended[:k]
    
    # 计算命中数
    hits = len([item for item in top_k if item in ground_truth])
    
    return hits / k

def recall_at_k(recommended, ground_truth, k):
    """
    计算 Recall@K
    
    Args:
        recommended: list, 模型返回的推荐列表(已排序)
        ground_truth: set, 用户真实喜欢的物品集合
        k: int, 截断位置
    
    Returns:
        float, Recall@K 值
    """
    if not ground_truth:
        return 0.0
    
    top_k = recommended[:k]
    hits = len([item for item in top_k if item in ground_truth])
    
    return hits / len(ground_truth)

# 使用示例
recommended = ['A', 'B', 'F', 'G', 'H', 'C', 'D', 'E']
ground_truth = {'A', 'B', 'C', 'D', 'E'}

print(f"Precision@5 = {precision_at_k(recommended, ground_truth, 5):.3f}")
print(f"Recall@5 = {recall_at_k(recommended, ground_truth, 5):.3f}")
print(f"Precision@10 = {precision_at_k(recommended, ground_truth, 10):.3f}")
print(f"Recall@10 = {recall_at_k(recommended, ground_truth, 10):.3f}")

输出结果:

Precision@5 = 0.400
Recall@5 = 0.400
Precision@10 = 0.500
Recall@10 = 1.000
注意:K 越大,Recall 一般越高(因为捞出来的东西多了),但 Precision 可能会下降(因为后面塞进去的都是「凑数」的)。我一般会同时看 P@5、P@10、P@20 和 R@5、R@10、R@20,画一条曲线来评估模型。

6. 实际项目中的坑与经验

最后,分享几个我踩过的坑:

  1. Ground Truth 怎么定义?

    我曾经直接用「用户点击」作为 Ground Truth,结果 Precision 虚高。为什么?因为用户点不点,还取决于排序和展示位置。后来我改用「用户点击 + 停留时长 > 30s」作为正反馈,指标才靠谱。

  2. K 的取值影响巨大。

    同样的模型,P@5=0.8,P@20=0.3。你说模型好不好?其实两个都对,只是视角不同。我建议你根据业务场景定 K,然后固定下来做对比实验。

  3. 不要只看均值。

    我见过一个项目,整体 Precision@10=0.6,但拆开看,头部用户 P@10=0.9,尾部用户 P@10=0.1。均值掩盖了问题。所以,我习惯按用户活跃度分桶看指标。

  4. Precision 和 Recall 是跷跷板。

    你调高 Precision,Recall 大概率会降。反过来也一样。没有「最好」的模型,只有「最适合业务」的模型。我一般会跟产品经理对齐一个「可接受的最低 Recall」,然后在这个约束下最大化 Precision。

一个血的教训:有一次我上线了一个新模型,离线 P@10 提升了 15%,R@10 下降了 2%。我觉得「问题不大」,结果线上用户留存掉了 3 个点。后来复盘发现,那 2% 的 Recall 下降,恰好是用户最核心的兴趣点。所以——Recall 的下降,哪怕只有一点点,也要警惕。