别再只算准确率了用Python手写DCG/IDCG/nDCG给你的推荐系统打分更靠谱推荐系统的评估从来不是一道简单的算术题。当你的模型在准确率(Precision)和召回率(Recall)上表现优异业务方却反馈推荐结果不够精准时问题往往出在评估维度上——我们是否过分依赖表面指标而忽视了排序质量这个直接影响用户体验的核心因素1. 为什么推荐系统需要超越准确率准确率就像考试中的选择题得分它能告诉你做对了多少但无法反映关键题目是否做对。想象两个推荐系统系统A将用户最可能购买的3个商品放在推荐列表的第8、9、10位系统B将这3个商品放在第1、2、3位它们的准确率相同但用户体验和转化率天差地别。这就是排序敏感指标存在的意义# 传统准确率计算 vs 排序敏感指标 def precision_at_k(recommended, relevant, k): return len(set(recommended[:k]) set(relevant)) / k # 系统A相关项在8-10位 rec_a [0,0,0,0,0,0,0,1,1,1] # 1表示相关商品 # 系统B相关项在1-3位 rec_b [1,1,1,0,0,0,0,0,0,0] print(precision_at_k(rec_a, [1], 10)) # 输出0.3 print(precision_at_k(rec_b, [1], 10)) # 输出0.32. 理解DCG家族从理论到业务逻辑2.1 DCG (Discounted Cumulative Gain)DCG的核心思想是位置惩罚越靠后的结果权重越低增益累积相关项带来的收益随相关性等级指数增长公式表达为 $$ DCGK \sum_{i1}^{K} \frac{2^{rel_i} - 1}{\log_2(i1)} $$其中rel_i通常取0不相关1一般相关2非常相关2.2 IDCG (Ideal DCG)理想状态下的DCG值将所有相关项按相关性降序排列后计算def ideal_dcg(relevant_items, k): # 假设所有相关项都是最高相关性(rel2) sorted_items [2] * len(relevant_items) sorted_items.extend([0]*(k - len(relevant_items))) return dcg(sorted_items[:k], k)2.3 nDCG (Normalized DCG)归一化后的DCG解决不同查询间DCG不可比的问题 $$ nDCGK \frac{DCGK}{IDCGK} $$3. 工业级Python实现与避坑指南3.1 基础实现框架import numpy as np def dcg(scores, kNone): 计算DCGK Args: scores: 相关性得分列表如[2,1,0,1,...] k: 截断位置None表示计算整个列表 k k if k is not None else len(scores) return sum((2**s - 1) / np.log2(i 2) for i, s in enumerate(scores[:k])) def idcg(ideal_scores, kNone): 计算IDCGK return dcg(sorted(ideal_scores, reverseTrue), k) def ndcg(scores, ideal_scores, kNone): 计算nDCGK dcg_val dcg(scores, k) idcg_val idcg(ideal_scores, k) return dcg_val / idcg_val if idcg_val 0 else 0.03.2 实际业务中的常见问题冷启动问题处理# 当新商品没有历史数据时赋予默认相关性 DEFAULT_RELEVANCE 0.5 # 根据业务调整 def safe_ndcg(predicted, ideal, kNone): # 处理预测列表为空的情况 if not predicted: return 0.0 # 填充冷启动商品 filled_pred [DEFAULT_RELEVANCE if x is None else x for x in predicted] filled_ideal [DEFAULT_RELEVANCE if x is None else x for x in ideal] return ndcg(filled_pred, filled_ideal, k)多评分等级处理# 将5星评分转换为DCG适用的相关性等级 RATING_MAP { 1: 0, # 1星 → 不相关 2: 1, # 2星 → 一般相关 3: 1, 4: 2, # 4星 → 非常相关 5: 2 } def convert_ratings(ratings): return [RATING_MAP.get(r, 0) for r in ratings]4. 实战用nDCG评估推荐策略假设我们对比两种推荐算法在电商场景的表现商品位置算法A预测评分算法B预测评分真实评分14.84.5524.53.8333.24.2442.12.0151.53.52# 数据准备 true_ratings [5, 3, 4, 1, 2] algo_a [4.8, 4.5, 3.2, 2.1, 1.5] algo_b [4.5, 3.8, 4.2, 2.0, 3.5] # 转换为相关性等级 true_rel convert_ratings(true_ratings) a_rel convert_ratings(algo_a) b_rel convert_ratings(algo_b) # 计算nDCG5 ndcg_a ndcg(a_rel, true_rel) ndcg_b ndcg(b_rel, true_rel) print(f算法A nDCG5: {ndcg_a:.3f}) # 输出 0.861 print(f算法B nDCG5: {ndcg_b:.3f}) # 输出 0.927结果显示算法B虽然在某些位置的预测评分不如算法A但整体排序更符合用户真实偏好。这就是为什么单纯看评分均值会误判模型效果。
别再只算准确率了!用Python手写DCG/IDCG/nDCG,给你的推荐系统打分更靠谱
别再只算准确率了用Python手写DCG/IDCG/nDCG给你的推荐系统打分更靠谱推荐系统的评估从来不是一道简单的算术题。当你的模型在准确率(Precision)和召回率(Recall)上表现优异业务方却反馈推荐结果不够精准时问题往往出在评估维度上——我们是否过分依赖表面指标而忽视了排序质量这个直接影响用户体验的核心因素1. 为什么推荐系统需要超越准确率准确率就像考试中的选择题得分它能告诉你做对了多少但无法反映关键题目是否做对。想象两个推荐系统系统A将用户最可能购买的3个商品放在推荐列表的第8、9、10位系统B将这3个商品放在第1、2、3位它们的准确率相同但用户体验和转化率天差地别。这就是排序敏感指标存在的意义# 传统准确率计算 vs 排序敏感指标 def precision_at_k(recommended, relevant, k): return len(set(recommended[:k]) set(relevant)) / k # 系统A相关项在8-10位 rec_a [0,0,0,0,0,0,0,1,1,1] # 1表示相关商品 # 系统B相关项在1-3位 rec_b [1,1,1,0,0,0,0,0,0,0] print(precision_at_k(rec_a, [1], 10)) # 输出0.3 print(precision_at_k(rec_b, [1], 10)) # 输出0.32. 理解DCG家族从理论到业务逻辑2.1 DCG (Discounted Cumulative Gain)DCG的核心思想是位置惩罚越靠后的结果权重越低增益累积相关项带来的收益随相关性等级指数增长公式表达为 $$ DCGK \sum_{i1}^{K} \frac{2^{rel_i} - 1}{\log_2(i1)} $$其中rel_i通常取0不相关1一般相关2非常相关2.2 IDCG (Ideal DCG)理想状态下的DCG值将所有相关项按相关性降序排列后计算def ideal_dcg(relevant_items, k): # 假设所有相关项都是最高相关性(rel2) sorted_items [2] * len(relevant_items) sorted_items.extend([0]*(k - len(relevant_items))) return dcg(sorted_items[:k], k)2.3 nDCG (Normalized DCG)归一化后的DCG解决不同查询间DCG不可比的问题 $$ nDCGK \frac{DCGK}{IDCGK} $$3. 工业级Python实现与避坑指南3.1 基础实现框架import numpy as np def dcg(scores, kNone): 计算DCGK Args: scores: 相关性得分列表如[2,1,0,1,...] k: 截断位置None表示计算整个列表 k k if k is not None else len(scores) return sum((2**s - 1) / np.log2(i 2) for i, s in enumerate(scores[:k])) def idcg(ideal_scores, kNone): 计算IDCGK return dcg(sorted(ideal_scores, reverseTrue), k) def ndcg(scores, ideal_scores, kNone): 计算nDCGK dcg_val dcg(scores, k) idcg_val idcg(ideal_scores, k) return dcg_val / idcg_val if idcg_val 0 else 0.03.2 实际业务中的常见问题冷启动问题处理# 当新商品没有历史数据时赋予默认相关性 DEFAULT_RELEVANCE 0.5 # 根据业务调整 def safe_ndcg(predicted, ideal, kNone): # 处理预测列表为空的情况 if not predicted: return 0.0 # 填充冷启动商品 filled_pred [DEFAULT_RELEVANCE if x is None else x for x in predicted] filled_ideal [DEFAULT_RELEVANCE if x is None else x for x in ideal] return ndcg(filled_pred, filled_ideal, k)多评分等级处理# 将5星评分转换为DCG适用的相关性等级 RATING_MAP { 1: 0, # 1星 → 不相关 2: 1, # 2星 → 一般相关 3: 1, 4: 2, # 4星 → 非常相关 5: 2 } def convert_ratings(ratings): return [RATING_MAP.get(r, 0) for r in ratings]4. 实战用nDCG评估推荐策略假设我们对比两种推荐算法在电商场景的表现商品位置算法A预测评分算法B预测评分真实评分14.84.5524.53.8333.24.2442.12.0151.53.52# 数据准备 true_ratings [5, 3, 4, 1, 2] algo_a [4.8, 4.5, 3.2, 2.1, 1.5] algo_b [4.5, 3.8, 4.2, 2.0, 3.5] # 转换为相关性等级 true_rel convert_ratings(true_ratings) a_rel convert_ratings(algo_a) b_rel convert_ratings(algo_b) # 计算nDCG5 ndcg_a ndcg(a_rel, true_rel) ndcg_b ndcg(b_rel, true_rel) print(f算法A nDCG5: {ndcg_a:.3f}) # 输出 0.861 print(f算法B nDCG5: {ndcg_b:.3f}) # 输出 0.927结果显示算法B虽然在某些位置的预测评分不如算法A但整体排序更符合用户真实偏好。这就是为什么单纯看评分均值会误判模型效果。