跳转到内容

LambdaRank — 跳过定义损失函数,直接把梯度写出来

是什么

LambdaRank 是 2006 年 Burges 团队的一个工程机灵——搜索排序的真实评估指标(NDCG)数学上不可导,他们干脆不写损失函数,直接告诉 SGD 该往哪走

日常类比:你想沿一座山下山,正常做法是写下山的高度公式,求导找方向。但这座山长得像台阶——每一阶平的,阶之间垂直跳——求导处处是 0 或无穷。Burges 说:管它高度公式呢,我直接告诉你”这一步往左挪 0.3 米”,下山照样能走。

这种”跳过定义代价、直接写梯度”的做法,被同团队 2008 年改造成 LambdaMART(梯度提升树版),2010 年拿下 Yahoo Learning to Rank 挑战赛冠军。今天 LightGBM / XGBoost 的 rank 目标函数仍是这条路。

为什么重要

不理解 LambdaRank,下面这些事都没法解释:

  • 为什么 NDCG / MAP / MRR 这些”理想指标”几乎从不直接当损失函数训——它们对模型分数的导数处处为 0
  • 为什么 RankNet 的 pairwise loss 和 NDCG 之间有鸿沟(ranknet-2005 已埋下这个坑),LambdaRank 是怎么填上的
  • 为什么深度学习时代之前的工业排序系统几乎清一色 LambdaMART——它把”想优化的指标”和”能优化的算法”硬接上了
  • 为什么”先写 loss、再求梯度”不是必须的——这是数学训练给人的思维惯性,工程上完全可以反过来

核心要点

LambdaRank 的全部秘密就一句:忘掉损失函数 C,直接写下你想要的梯度 lambda

  1. 观察:神经网络训练里,反向传播只需要每个分数 s_i 上的偏导 dC/ds_i——一个标量。模型从来不需要 C 本身的数值,只需要这个标量告诉它”往哪边推 s_i”。

  2. 设计:对一对文档 (i, j)(同 query、相关度不同),定义

    lambda_ij = -sigmoid(s_j - s_i) * |Delta_NDCG_ij|

    两个因子各管一件事:

    • sigmoid(s_j - s_i):方向项。来自 RankNet 的梯度公式,告诉模型”i 应该往上 / j 应该往下”
    • |Delta_NDCG_ij|:力度项。如果交换 i 和 j 的位置会让 NDCG 涨很多,这一对就是”硬骨头”,给大权重;如果交换无所谓(比如都在 100 名开外),权重接近 0
  3. 聚合:一个文档 i 受所有 pair 的合力推动

    lambda_i = sum_{j: rel_i > rel_j} lambda_ij - sum_{j: rel_i < rel_j} lambda_ji

    把 lambda_i 当成 dC/ds_i 直接喂进反向传播,照常更新网络权重。

  4. 物理直觉:把每个文档想成一个小球,每个”该排前但排后了”的 pair 给小球施加一个力,力的大小正比于”修正这对错排能让 NDCG 涨多少”。所有力合起来,把分数往让 NDCG 上升的方向拽。

实践案例

案例 1:为什么 NDCG 不能直接当 loss

NDCG 的定义是:把文档按预测分数排序,按 rank 位置给折扣(log2(rank+1)),加权求和相关度。

NDCG = sum_i rel_i / log2(rank_i + 1)

问题:rank_i 是个整数,是 s_i 在所有 s 里的位次。把 s_i 变化 0.001,rank 几乎不变,NDCG 不变;变化超过某个阈值,rank 突跳一格,NDCG 跳一段。对 s_i 求导,要么 0、要么不存在。这就是论文标题里”Nonsmooth Cost Functions”的含义。

案例 2:LambdaRank 实现长什么样(伪代码)

# 对一个 query 的 n 个文档
scores = model(features) # n 维
lambdas = torch.zeros_like(scores)
for i, j in pairs_with_different_relevance:
sig = torch.sigmoid(scores[j] - scores[i])
delta_ndcg = abs(ndcg_after_swap(i, j) - ndcg_before)
lambdas[i] -= sig * delta_ndcg # 推 i 向上
lambdas[j] += sig * delta_ndcg # 推 j 向下
scores.backward(gradient=lambdas) # 直接把 lambda 当梯度

关键就是最后一行——没有 loss.backward(),因为根本没定义 loss。PyTorch 允许你直接传梯度向量,LambdaRank 用的就是这个口子。

案例 3:从 RankNet 到 LambdaRank 的”一行改动”

# RankNet 的梯度
lambda_ij = -sigmoid(s_j - s_i)
# LambdaRank 的梯度(多乘一项)
lambda_ij = -sigmoid(s_j - s_i) * |Delta_NDCG_ij|

就是这一行。但效果完全变了:RankNet 把所有错排的对一视同仁,LambdaRank 让”top 位置错排”的代价远高于”100 名后错排”——和搜索引擎真正在乎的东西对齐。

踩过的坑

  1. lambda 不是真梯度:理论上没有一个解析的 C 满足 dC/ds = lambda(论文做了一些经验性论证说 C 大致存在但非凸)。这是个工程小聪明而非数学定理。心理上要接受:“我在用一个有用的方向,不是在最小化一个清晰的目标。”

  2. |Delta_NDCG| 计算开销:每对 (i, j) 都要算”假如交换了 NDCG 会变多少”。朴素实现 O(n^2 log n)。论文用 NDCG 的局部性(只交换两个位置,NDCG 变化只跟这两个位置和它们的折扣有关)压到 O(n^2)。

  3. 指标可换但要小心:把 |Delta_NDCG| 换成 |Delta_MAP| 就得到针对 MAP 的 LambdaRank。但有些指标的 Delta 计算不稳定或者梯度方向不一致(比如 MRR 只在意第一个相关文档,会导致大量 pair 的权重为 0,训练信号稀疏)。换指标前要先在小数据上验证。

  4. 同 query 内才配对:和 RankNet 同样的坑。跨 query 配对没意义——“煮蛋”的 doc1 和”买股票”的 doc2 比 NDCG 是无意义的。pipeline 必须按 query 分组。

适用 vs 不适用场景

适用

  • 离散评估指标(NDCG / MAP / 自定义阶梯指标)想直接优化
  • 已有可微的打分模型(神经网络 / GBDT),想换损失而不换模型
  • 工业排序、推荐精排——LambdaMART 是这类任务 2008-2018 的事实标准

不适用

  • 候选集巨大的召回阶段——pairwise O(n^2) 跑不动,用 ANN / 双塔
  • 评估指标本身可微的场景——直接对指标求导就好,不用绕路
  • 需要严格收敛性证明的研究——lambda 没有完整理论保证
  • 完整 listwise 方法更合适的场景(ListNet 2007 / LambdaLoss 2018)

历史小故事(可跳过)

  • 2005 年:Burges 团队 ICML 发表 RankNet,pairwise + sigmoid + cross-entropy,能跑但和 NDCG 错位。详见 ranknet-2005
  • 2006 年:同团队 NIPS 发表本文,提出 lambda 思想。论文 8 页,思想极简。
  • 2008 年:内部技术报告把 lambda 接到 MART(GBDT 的别名),就是 LambdaMART——把神经网络的可微打分函数换成树模型,意外地更稳更准。
  • 2010 年:Yahoo Learning to Rank Challenge,前 5 名里多支队伍用 LambdaMART,冠军方案就是它。
  • 2014 年起:LightGBM / XGBoost 内置 rank:pairwise / lambdarank 目标函数,大量电商搜索、广告排序直接调用。
  • 2018-2024 年:BERT / cross-encoder reranker 兴起,但训练 loss 仍常用 LambdaRank 风格的加权 pairwise——思想 18 年不变。

学到什么

  1. 训练只需要梯度,不需要 loss——这是反直觉但极有用的洞见。“先写损失函数”是数学训练给的惯性思维,不是必须
  2. 不可导指标可以”软化”或”绕过”——RankNet 选了软化(sigmoid),LambdaRank 选了绕过(直接定义梯度)。绕过有时更直接
  3. 方向 + 力度的乘法分解:lambda_ij = sigmoid(…) × |Delta_NDCG| 这种”一个项管方向、一个项管权重”的结构,在很多工程加权场景都能复用
  4. 理论严格性 vs 工程有效性的取舍:LambdaRank 缺乏严格的收敛证明,但实证效果好到让整个工业界用了 18 年。“能用 + 大致解释得通”在工程上常常胜过”严格但僵硬”

延伸阅读

关联

反向链接

  • anserini-2017 —— Anserini — 把工业搜索引擎 Lucene 改造成学术 IR 实验台
  • gbrank-2007 —— GBRank — 把决策树堆起来学排序,一棵树纠正一处错排
  • knrm-2017 —— K-NRM — 用核函数把交互矩阵变成可微排序信号
  • pagerank-1998 —— PageRank — 用随机游走给整个网络的页面打分
  • personalized-pagerank-2003 —— Personalized PageRank — 给每个人一份属于自己的网页排名
  • ranknet-2005 —— RankNet — 让搜索引擎学会比较两个结果谁更好
  • slim-2011 —— SLIM — 让数据自己学一张稀疏的”看了又看”权重表
  • trustrank-2004 —— TrustRank — 用一小撮可信种子把整张 Web 的信誉算出来