离线评估是推荐系统开发的基础环节,用历史数据评估模型效果。但离线指标和在线效果之间常有差距,理解各种指标的适用场景和局限至关重要。
| 类别 | 指标 | 关注点 |
|---|---|---|
| 准确率 | Precision/Recall/HR | 推荐是否命中 |
| 排序质量 | NDCG/MRR/AUC | 排序是否正确 |
| 覆盖率 | Coverage/Gini | 是否发掘长尾 |
| 多样性 | ILD/Entropy | 推荐是否多样 |
| 新颖性 | Novelty/MeanSelfInfo | 是否推荐新内容 |
Precision@K = |命中|/K
NDCG@K = DCG@K/IDCG@K, DCG=Σ(2^rel-1)/log2(i+1)
AUC = P(score_pos>score_neg)
Coverage = |被推荐物品|/|总物品|
import numpy as np
np.random.seed(42)
print("="*60+"\n离线评估指标\n"+"="*60)
n_users=100;n_items=50
# 生成预测分数和真实标签
y_true=np.random.random((n_users,n_items))>0.85
y_scores=np.random.random((n_users,n_items))
# 让分数和标签有一定相关性
y_scores=y_scores*0.3+y_true*0.7+np.random.random((n_users,n_items))*0.3
print(f"用户:{n_users} 物品:{n_items} 正例率:{y_true.mean():.4f}")
K=5
# Precision@K
def precision_at_k(y_true,y_scores,k=5):
precs=[]
for u in range(len(y_true)):
top_k=np.argsort(-y_scores[u])[:k]
precs.append(y_true[u,top_k].mean())
return np.mean(precs)
# Recall@K
def recall_at_k(y_true,y_scores,k=5):
recs=[]
for u in range(len(y_true)):
pos=np.where(y_true[u])[0]
if len(pos)==0:continue
top_k=set(np.argsort(-y_scores[u])[:k])
recs.append(len(top_k&set(pos))/len(pos))
return np.mean(recs) if recs else 0
# NDCG@K
def ndcg_at_k(y_true,y_scores,k=5):
ndcgs=[]
for u in range(len(y_true)):
top_k=np.argsort(-y_scores[u])[:k]
rel=y_true[u,top_k].astype(float)
dcg=np.sum((2**rel-1)/np.log2(np.arange(len(rel))+2))
ideal_rel=sorted(y_true[u].astype(float),reverse=True)[:k]
idcg=np.sum((2**np.array(ideal_rel)-1)/np.log2(np.arange(len(ideal_rel))+2))
ndcgs.append(dcg/idcg if idcg>0 else 0)
return np.mean(ndcgs)
# HitRate@K
def hit_rate_at_k(y_true,y_scores,k=5):
hits=0;n=0
for u in range(len(y_true)):
pos=np.where(y_true[u])[0]
if len(pos)==0:continue
top_k=set(np.argsort(-y_scores[u])[:k])
if top_k&set(pos):hits+=1
n+=1
return hits/n if n else 0
# AUC
def auc_score(y_true,y_scores):
aucs=[]
for u in range(len(y_true)):
pos=np.where(y_true[u])[0];neg=np.where(~y_true[u])[0]
if len(pos)==0 or len(neg)==0:continue
sp=y_scores[u,pos];sn=y_scores[u,neg]
aucs.append(np.mean(sp[:,None]>sn[None,:]))
return np.mean(aucs) if aucs else 0.5
# MRR
def mrr_score(y_true,y_scores):
mrrs=[]
for u in range(len(y_true)):
pos=np.where(y_true[u])[0]
if len(pos)==0:continue
order=np.argsort(-y_scores[u])
for rank,idx in enumerate(order):
if y_true[u,idx]:mrrs.append(1/(rank+1));break
return np.mean(mrrs) if mrrs else 0
# 覆盖率
def coverage(y_scores,k=5):
recommended=set()
for u in range(len(y_scores)):
top_k=np.argsort(-y_scores[u])[:k]
recommended.update(top_k)
return len(recommended)/y_scores.shape[1]
# 计算所有指标
print(f"\n--- 离线评估指标(K={K}) ---")
for k in [3,5,10,20]:
p=precision_at_k(y_true,y_scores,k)
r=recall_at_k(y_true,y_scores,k)
n=ndcg_at_k(y_true,y_scores,k)
h=hit_rate_at_k(y_true,y_scores,k)
c=coverage(y_scores,k)
print(f" K={k}:P={p:.4f} R={r:.4f} NDCG={n:.4f} HR={h:.4f} Cov={c:.2%}")
print(f" AUC={auc_score(y_true,y_scores):.4f}")
print(f" MRR={mrr_score(y_true,y_scores):.4f}")
# 随机基线对比
y_random=np.random.random((n_users,n_items))
print(f"\n--- 与随机基线对比 ---")
for k in [5,10]:
p1=precision_at_k(y_true,y_scores,k)
p0=precision_at_k(y_true,y_random,k)
print(f" K={k}:模型P={p1:.4f} 随机P={p0:.4f} 提升={p1/max(p0,1e-6):.1f}x")
✅ 验证通过
本课代码涵盖了从数据生成、模型训练到效果评估的完整流程。以下是关键步骤的详细解读:
代码设计原则:简洁可读、关键步骤有注释、结果可复现(固定随机种子)。
| 组件 | 版本 | 说明 |
|---|---|---|
| Python | 3.8+ | 推荐3.10版本 |
| NumPy | 1.21+ | 核心数值计算库 |
| SciPy | 1.7+ | 统计检验(部分课程) |
本课是第19课:离线评估指标,属于评估与优化阶段。我们系统学习了本课的核心概念、数学原理和代码实现,并通过实机运行验证了算法的正确性。
练习1:实现 unbiased 评估(去除位置偏差)
练习2:实现 α-NDCG(考虑多样性)
练习3:对比全局评估和分组评估(按用户活跃度分组)
练习4:实现时间序列交叉验证评估
| 公司 | 场景 | 核心技术 |
|---|---|---|
| 字节跳动 | 抖音/TikTok | 多目标排序+实时特征+双塔召回 |
| 阿里巴巴 | 淘宝推荐 | DIN/DIEN序列模型+MIND多兴趣 |
| 腾讯 | 微信看一看 | DeepFM+图神经网络召回 |
| 美团 | 本地生活推荐 | 多场景多目标+时空特征 |
| Netflix | 视频推荐 | 矩阵分解+深度学习混合 |
✅ 掌握完整的离线评估指标体系
✅ 实现Precision/Recall/NDCG/AUC等指标
✅ 理解离线评估的局限与偏差
✅ 设计合理的离线评估方案