recall@k 应手动实现:对每个用户,用真实物品集与模型top-k推荐列表求交集大小除以真实正样本数;sklearn的recall_score因不考虑排序而错误。

Recall@K 在 Python 中怎么算才对?
Recall@K 衡量的是:用户真实喜欢的物品中,有多少落在了模型推荐的前 K 个结果里。关键不是“推荐对了多少”,而是“漏掉了多少真实正样本”。
常见错误是直接用 sklearn.metrics.recall_score —— 它默认按二分类标签计算,不考虑排序和截断,结果完全不对。
正确做法是手动实现:
- 对每个用户,拿到其真实交互物品集合(如测试集中的
user_items) - 拿到模型输出的 top-K 推荐列表(
topk_items,按预测分数降序) - 计算交集大小除以该用户真实正样本总数
def recall_at_k(y_true_set, y_pred_topk):
if len(y_true_set) == 0:
return 0.0
hits = len(set(y_pred_topk) & y_true_set)
return hits / len(y_true_set)
<h1>示例:用户真实有 {1,3,5,7},推荐 [3,8,1,9,2](K=5)</h1><p>recall_at_k({1,3,5,7}, [3,8,1,9,2]) # 返回 2/4 = 0.5</p>
注意:如果用户在测试集中没任何正样本(冷启动用户),跳过或返回 0,别让分母为 0。
NDCG@K 必须带 ranking score 才有意义
NDCG@K 不只看“是否命中”,还惩罚位置靠后的相关项。ndcg_score(来自 sklearn.metrics)要求你提供每个候选 item 的“相关性得分”,不能只给 0/1 标签。
典型误区:把 top-K 推荐列表直接喂给 ndcg_score(y_true=[1,0,0,1,0], y_score=[0.9,0.8,0.7,0.6,0.5]) —— 这里 y_true 必须是对所有候选 item 的相关性标注(比如测试集全量 item),而不仅是 top-K。
更实用的做法是自己实现,只处理 top-K:
- 构建长度为 K 的
relevance列表:命中真实 item 就标 1,否则 0 - 计算 DCG:sum( rel[i] / log2(i+2) ),i 从 0 开始
- 计算 IDCG:把
relevance排序成降序再算一次 DCG - NDCG = DCG / (IDCG or 1e-8)
import numpy as np
def ndcg_at_k(relevance_list, k):
dcg = sum((2 ** r - 1) / np.log2(i + 2) for i, r in enumerate(relevance_list[:k]))
idcg = sum((2 ** r - 1) / np.log2(i + 2) for i, r in enumerate(sorted(relevance_list, reverse=True)[:k]))
return dcg / (idcg or 1e-8)
用 1/0 相关性时,(2 ** r - 1) 等价于 r,可简化;但若后续支持多级相关性(如 0/1/2/3),这个形式能直接复用。
LightFM、Implicit、Surprise 这些库怎么取 top-K?
不同训练框架输出格式差异大,容易卡在“怎么拿到推荐列表”这步:
-
LightFM:用model.predict(user_id, item_ids, user_features=..., item_features=...)手动打分,再np.argsort(...)[::-1][:K] -
Implicit:调model.recommend(userid, user_items, N=K),注意user_items是 CSR 矩阵中该用户的行(即训练时交互过的 items),否则推荐结果含训练数据 -
Surprise:没有内置 top-K 接口,得遍历所有 item 调algo.predict(uid, iid).est,再排序——线上不可行,仅用于离线评估
统一建议:
- 测试时,对每个用户,只在未训练过的 item 上预测(即 train-test split 后的 test set candidate)
- 避免用
model.get_all_ratings()类接口(不存在),别幻想一键导出全量排序
为什么 batch 评估时 Recall 和 NDCG 结果忽高忽低?
这不是随机性问题,大概率是以下三个原因:
- 用户级指标未加权平均:有的用户有 100 个测试正样本,有的只有 1 个,直接对所有用户
recall_at_k取算术平均,会偏向小样本用户。应按真实正样本数加权 - NDCG 分母 IDCG 为 0:当用户真实正样本数 relevance_list 全为 0(即一个都没命中),IDCG=0,NDCG 未定义。需显式跳过或设为 0
- 推荐列表去重没做:模型可能对同一 item 输出多个近似分数,
argsort后取 top-K 包含重复 item ID,导致分母虚高。务必在取索引后用np.unique(..., return_index=True)或 Pythondict.fromkeys()去重
最常被忽略的一点:评估必须严格隔离训练/测试 item。哪怕只混入一个训练 item 到测试推荐池,Recall 就会虚高 5%~20%,尤其在稀疏数据上。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











