学习排序(Learning to Rank)是信息检索和推荐系统的核心技术。Listwise方法直接优化整个排序列表的质量,比Pointwise和Pairwise更符合排序的本质需求。
| 维度 | ListNet | ListMLE |
|---|---|---|
| 损失函数 | Top-1概率分布KL散度 | 在真实排序下的似然 |
| 优化目标 | 概率分布接近 | 正确排序概率最大 |
| 计算 | 对每对文档计算 | 对排列计算 |
| 优势 | 简单高效 | 更直接地优化排序 |
L = -Σ P_φ(y_i)·log(P_φ(f(x_i)))
Top-1概率分布的交叉熵
L = -log P(f(x);π*)
在真实排列π*下的似然概率
MAP = (1/|Q|)Σ(1/|R_q|)Σ Precision@k
import numpy as np
np.random.seed(42)
print("="*60+"\n学习排序(LTR)深入\n"+"="*60)
n_queries=15;n_docs=8;n_features=6
# 生成数据
all_X=[];all_y=[];all_qids=[]
for q in range(n_queries):
q_feat=np.random.randn(n_features)*0.3
for d in range(n_docs):
feat=np.random.randn(n_features)*0.2+q_feat
rel=max(0,int(np.dot(feat,np.random.randn(n_features)*0.5)+2))
all_X.append(feat);all_y.append(min(rel,4));all_qids.append(q)
X=np.array(all_X);y=np.array(all_y);qids=np.array(all_qids)
print(f"查询:{n_queries} 文档:{len(X)} 特征:{n_features}")
# ListNet
print("\n--- ListNet ---")
def softmax(x):e=np.exp(x-x.max());return e/e.sum()
w_list=np.random.randn(n_features)*0.01;b_list=0
lr_list=0.01
for ep in range(80):
total_loss=0
for q in range(n_queries):
mask=qids==q;q_X=X[mask];q_y=y[mask]
scores=q_X@w_list+b_list
pred_probs=softmax(scores)
true_probs=softmax(q_y.astype(float))
loss=-np.sum(true_probs*np.log(pred_probs+1e-10))
total_loss+=loss
grad=pred_probs-true_probs
w_list-=lr_list*(q_X.T@grad/len(q_y)+0.01*w_list)
b_list-=lr_list*np.mean(grad)
if(ep+1)%20==0:print(f" Epoch{ep+1}:Loss={total_loss/n_queries:.4f}")
# ListMLE
print("\n--- ListMLE ---")
w_mle=np.random.randn(n_features)*0.01;b_mle=0
lr_mle=0.01
for ep in range(80):
total_loss=0
for q in range(n_queries):
mask=qids==q;q_X=X[mask];q_y=y[mask]
scores=q_X@w_mle+b_mle
# 按真实相关性排序
true_order=np.argsort(-q_y)
# ListMLE: 在真实排序下的概率
log_prob=0
remaining=scores.copy()
for pos in range(len(true_order)):
probs=softmax(remaining)
chosen=true_order[pos]
log_prob+=np.log(probs[chosen]+1e-10)
remaining[chosen]=-999
loss=-log_prob
total_loss+=loss
# 简化梯度
grad_probs=softmax(scores)
w_mle-=lr_list*(q_X.T@(grad_probs-softmax(q_y.astype(float)))/len(q_y)+0.01*w_mle)
b_mle-=lr_list*np.mean(grad_probs-softmax(q_y.astype(float)))
if(ep+1)%20==0:print(f" Epoch{ep+1}:Loss={total_loss/n_queries:.4f}")
# 评估
def eval_ltr(name,w,b):
ndcgs=[];map_vals=[]
for q in range(n_queries):
mask=qids==q;q_X=X[mask];q_y=y[mask]
scores=q_X@w+b
order=np.argsort(-scores)
# NDCG@5
dcg=sum((2**q_y[order[i]]-1)/np.log2(i+2)for i in range(min(5,len(order))))
idcg=sum((2**r-1)/np.log2(i+2)for i,r in enumerate(sorted(q_y,reverse=True)[:5]))
ndcgs.append(dcg/idcg if idcg>0 else 0)
# MAP
hits=0;prec_sum=0
for i,idx in enumerate(order):
if q_y[idx]>0:
hits+=1;prec_sum+=hits/(i+1)
map_vals.append(prec_sum/max(hits,1))
return np.mean(ndcgs),np.mean(map_vals)
print(f"\n{'方法':<15}{'NDCG@5':<10}{'MAP':<10}")
print("-"*35)
for name,w,b in [("ListNet",w_list,b_list),("ListMLE",w_mle,b_mle)]:
ndcg,map_v=eval_ltr(name,w,b)
print(f"{name:<15}{ndcg:<10.4f}{map_v:<10.4f}")
# 排序一致性分析
print("\n排序一致性:")
for q in range(3):
mask=qids==q;q_y=y[mask]
s1=X[mask]@w_list+b_list
s2=X[mask]@w_mle+b_mle
o1=np.argsort(-s1);o2=np.argsort(-s2)
print(f" Query{q}:ListNet={o1} ListMLE={o2} 真实={np.argsort(-q_y)}")
✅ 验证通过
本课代码涵盖了从数据生成、模型训练到效果评估的完整流程。以下是关键步骤的详细解读:
代码设计原则:简洁可读、关键步骤有注释、结果可复现(固定随机种子)。
| 组件 | 版本 | 说明 |
|---|---|---|
| Python | 3.8+ | 推荐3.10版本 |
| NumPy | 1.21+ | 核心数值计算库 |
| SciPy | 1.7+ | 统计检验(部分课程) |
本课是第14课:学习排序(LTR),属于排序与重排阶段。我们系统学习了本课的核心概念、数学原理和代码实现,并通过实机运行验证了算法的正确性。
练习1:实现ApproxNDCG损失函数
练习2:对比ListNet/ListMLE/LambdaMART
练习3:用位置折扣函数改进ListMLE
练习4:实现groupwise排序模型
| 公司 | 场景 | 核心技术 |
|---|---|---|
| 字节跳动 | 抖音/TikTok | 多目标排序+实时特征+双塔召回 |
| 阿里巴巴 | 淘宝推荐 | DIN/DIEN序列模型+MIND多兴趣 |
| 腾讯 | 微信看一看 | DeepFM+图神经网络召回 |
| 美团 | 本地生活推荐 | 多场景多目标+时空特征 |
| Netflix | 视频推荐 | 矩阵分解+深度学习混合 |
✅ 深入理解Listwise方法的数学基础
✅ 掌握ListNet和ListMLE算法
✅ 实现LTR评估框架
✅ 理解LTR与推荐系统的深度结合