图书馆参考咨询处的陈主任今天很头疼。一位读者递来需求单:“想找一本讲机器学习排序算法的书,最好通俗一点。”他面前摆着几十本书:有的标题恰好带“排序”二字却只讲 Excel 表格,有的内容极好但封面破旧,有的被借到卷边、口碑极佳却与读者要的差了一口气。陈主任必须在几秒内排出推荐顺序。他琢磨:能不能把“哪些书该排前面”这件事,总结成一套可以学习的规则?这个念头,就是 Learning to Rank(相关性排序,简称 LTR)的起源。LTR 把“排顺序”从人工经验变成监督学习任务:用历史标注数据训练打分函数,让最相关的结果排在最前面。它依赖三类信号(文档本身、查询本身、整个候选列表的上下文)做特征工程,也演化出 Pointwise、Pairwise、Listwise 三种训练策略。接下来,就跟着陈主任把这一课学完。

岗位关联

排序是搜索引擎与推荐系统的命脉。掌握 LTR 的问题定义与特征工程,是 AI 从业者从“会调模型”走向“能搭通搜索推荐链路”的关键一步。

目录
  1. 可视化演示
  2. 排序问题定义
  3. 三层次特征体系
  4. 文档级特征
  5. 查询级特征
  6. 列表级特征
  7. Pointwise方法
  8. Pairwise方法
  9. Listwise方法
  10. AI 应用场景
  11. Python 演示
  12. 常见误区
  13. 面试题

可视化演示

展示LTR三大方法对比:Pointwise逐点(每个文档独立看待用回归预测相关性分数f(x)=w·x+b损失MSE忽略排序关系)、Pairwise逐对(构建文档对约束f(a)>f(b) Hinge损失max(0,1-f(a)+f(b))关注相对顺序)、Listwise列表(整个列表为单位优化LambdaRank梯度λ=ΔNDCG·σ(Δf)直接优化排序指标效果最优),三模式切换理解LTR全貌。

本节配有交互动效(进入视口后自动加载,约 3KB)。

'+C1+'

排序问题定义

图书馆参考咨询处的陈主任每天收到读者的需求单:“想找机器学习入门书”“要最薄的排序算法书”。他必须在几十本书里排出一个推荐顺序。“哪些书该排前面”,表面看是经验,其实可以学出来——这就是 Learning to Rank 的出发点。它把排序问题转化为监督学习:用一批“查询-文档-相关性”标注数据训练一个打分函数,让相关度高的文档拿到更高的分,最终按分数排成有序列表。

在传统检索里,系统常用关键词是否命中决定顺序,但“相关性”远比命中复杂:两本书都出现“排序”二字,一本是 Excel 教程、一本是机器学习专著,读者的感受天差地别。LTR 不再手工定义规则,而是让模型从数据里学出一个打分函数 \(f(q,d)\):输入查询 \(q\) 和文档 \(d\),输出一个相关性得分。得到得分后,对所有候选文档降序排列,就得到了最终清单。

训练 LTR 模型需要明确的输入输出形式。输入是查询 \(q\) 与候选文档集合 \(D=\{d_1,d_2,\dots,d_n\}\),每个文档由特征向量描述;输出是一个排序列表。训练阶段会准备标注数据:对同一查询下的每篇文档标注相关性等级 \(y\),例如 0 表示不相关、1 表示相关、2 表示非常相关,然后最小化预测得分与标注之间的差距。

陈主任要是把几十年荐书经验浓缩成一个公式,贴到工位上,那这个公式就是 LTR 的问题定义:输入一张需求单和一排书,输出一份让读者满意的清单。

min⁡θ∑(q,d)∈DL(f(q,d;θ), yq,d)\displaystyle \min_{\theta} \sum_{(q,d) \in \mathbb{D}} \mathcal{L}\left(f(q,d;\theta),\, y_{q,d}\right)
这个公式的意思是:找到一组模型参数 \(\theta\),让所有样本上“预测分和人工标注的差距”加起来最小。差距越小,模型越会优先给相关文档打高分。
  1. 求和符号对训练数据里每一个“查询-文档”组合都计算一次损失,最后累加。
    ∑(q,d)∈D\displaystyle \sum_{(q,d) \in \mathbb{D}}
  2. 打分函数模型 \(f(q,d;\theta)\) 输入查询和文档,输出预测的相关性得分。
    f(q,d;θ)\displaystyle f(q,d;\theta)
  3. 损失函数\(\mathcal{L}(\cdot,\cdot)\) 衡量预测分 \(\hat{y}\) 与人工标注 \(y_{q,d}\) 的距离,差距越大惩罚越重。
    L(f(q,d;θ), yq,d)\displaystyle \mathcal{L}\left(f(q,d;\theta),\, y_{q,d}\right)
  4. 最小化参数通过梯度下降等方法不断调整 \(\theta\),让总损失越来越小,模型就“学会”了什么该排前面。
    min⁡θ\displaystyle \min_{\theta}

三层次特征体系

陈主任带新来的实习生,第一课提醒对方:“荐书有三层信息要同时看。第一层是书自己;第二层是读者那张纸片;第三层是整张书桌的布局。”只看任何一层都会看走眼。这和 LTR 的特征工程完全一致:特征是模型的“眼睛”,必须分层供给信号。

对应到 LTR,特征工程分成三个典型层级。文档级特征描述单个文档自身的属性,比如标题词匹配、被点击次数、内容质量分;查询级特征描述查询自身的属性,比如查询类型、词数、歧义度;列表级特征描述文档在整个候选集里的上下文信息,比如候选集规模、文档排名位置、相邻文档的相似度。三者回答的问题截然不同:这本书好不好(文档级)、读者要什么(查询级)、这桌菜怎么摆才合理(列表级)。

实际操作中,特征工程不是把各种值胡乱拼在一起,而是按层级组织成特征向量再统一送入模型。对当前查询 \(q\)、文档 \(d\) 和候选集 \(D\),完整特征向量由三个子向量拼接而成:

三层信息都齐了,陈主任才敢提笔写推荐语。同理,LTR 模型只有同时看到文档、查询和列表三面镜子,才可能在复杂场景里做出正确排序。

ϕ(q,d,D)=[ ϕdoc(d), ϕquery(q), ϕlist(q,d,D) ]\displaystyle \phi(q,d,D) = [\,\phi_{doc}(d),\ \phi_{query}(q),\ \phi_{list}(q,d,D)\,]
这个公式是说:模型看到的每个样本,都是“书档案 + 读者需求 + 书架上下文”三段信息的拼接。少一段,视野就缺一块。
  1. 文档子向量\(\phi_{doc}(d)\) 只依赖文档自身,与查询无关,比如文档长度、质量分、历史点击量。
    ϕdoc(d)\displaystyle \phi_{doc}(d)
  2. 查询子向量\(\phi_{query}(q)\) 只描述查询本身,比如查询词数量、是否属于导航型意图。
    ϕquery(q)\displaystyle \phi_{query}(q)
  3. 列表子向量\(\phi_{list}(q,d,D)\) 把文档放进候选集里看,比如该文档在初排中的位置、与集合平均分的差距。
    ϕlist(q,d,D)\displaystyle \phi_{list}(q,d,D)
  4. 向量拼接方括号表示把三段向量首尾相连,形成一个更高的特征向量,交给模型统一建模。
    [ ⋅ , ⋅ , ⋅ ]\displaystyle [\,\cdot\ ,\ \cdot\ ,\ \cdot\,]

文档级特征

陈主任指点实习生:“判断一本书值不值得推荐,先翻开它的档案卡。”档案卡上写着书名与简介里出现的关键词、出版年份、页数、被借次数。这些只跟书本身有关、不随读者变化的信息,就是文档级特征——它回答“这本书自身怎么样”。

最经典的文档级特征之一是 TF-IDF,它常被用来刻画“文档内容与主题词的相关性指纹”。TF 是词频,衡量一个词在文档里出现得多频繁;IDF 是逆文档频率,衡量这个词在整个书库里的稀有程度。一个词出现得多、又在书库里罕见,那它几乎就是这本书的专属标签,比如“Transformer“比满大街的”学习”更有辨识度。

除了文本特征,文档级特征还包括数值型信号,比如被借次数可能上千、质量评分只有 0 到 5 分,尺度差异巨大,通常要做归一化处理,才能让模型稳定学习。档案卡再厚,终究是“见书不见人”——只看文档级特征,陈主任还没接住读者的目光。

TF-IDF(t,d)=TF(t,d)×log⁡NDF(t)\displaystyle \text{TF-IDF}(t,d) = \text{TF}(t,d) \times \log\frac{N}{\text{DF}(t)}
这个公式用“词多常见”乘“词多稀有”来给文档打内容标签:又常见又稀有的词,才是这本书的指纹。
  1. 词频 TF统计词 \(t\) 在文档 \(d\) 中出现的次数或比例,出现越多,文档越“像”在讲这个词。
    TF(t,d)\displaystyle \text{TF}(t,d)
  2. 逆文档频率 IDF\(N\) 是语料中文档总数,\(\text{DF}(t)\) 是包含词 \(t\) 的文档数。包含越少,\(\log\frac{N}{\text{DF}(t)}\) 越大,说明这个词区分度越高。
    log⁡NDF(t)\displaystyle \log\frac{N}{\text{DF}(t)}
  3. 相乘合成两部分相乘,让“文档里常见”且“全局罕见”的词获得高权重,普通词被压下去。
    TF(t,d)×log⁡NDF(t)\displaystyle \text{TF}(t,d) \times \log\frac{N}{\text{DF}(t)}

查询级特征

档案卡再漂亮,也不能对牛弹琴。陈主任常说:“来借书的是小学生还是研究生,同样要机器人科普,推荐清单完全不同。”读者的需求单本身就是信息,在 LTR 里对应查询级特征——回答“读者到底想要什么”。

查询级特征刻画查询自身的属性。查询词数量是最直观的例子:“排序”这种短查询意图模糊,可能是求 Excel 排序、也可能是学排序算法;而“Learning to Rank 算法对比教程”这种长查询意图就很明确。再比如查询类型:导航型查询(找某个已知网站)和交易型查询(想下单)要的结果形态完全不同。还有查询的历史点击偏好:这个查询过去点开的多是视频还是图文,都能作为先验信号。

我们可以用信息熵量化查询的“模糊程度”。熵越大,词的概率分布越均匀,查询的意图就越分散:

读懂了需求单,陈主任才算接住读者的目光。查询级特征让模型学会“同一本书、不同读者、不同排法”,这是排序真正贴近人的开始。但只看需求单也不够——还得看桌上这一摞候选书的整体局面。

H(q)=−∑w∈qP(w∣q)log⁡P(w∣q)\displaystyle H(q) = -\sum_{w \in q} P(w \mid q) \log P(w \mid q)
这个公式计算查询内部词的分布有多“乱”:词越均匀地分散在多个义项上,查询歧义度越高,排序时越需要靠其他信号兜底。
  1. 遍历查询词对查询 \(q\) 中出现的每个词 \(w\) 累加计算。
    ∑w∈q\displaystyle \sum_{w \in q}
  2. 词的概率\(P(w \mid q)\) 表示词 \(w\) 在查询中的占比或权重,占比越均匀,说明查询越没有重点。
    P(w∣q)\displaystyle P(w \mid q)
  3. 负熵求和每个词贡献 \(-P \log P\),所有词加起来得到熵值,熵越大,查询歧义越高。
    −∑w∈qP(w∣q)log⁡P(w∣q)\displaystyle -\sum_{w \in q} P(w \mid q) \log P(w \mid q)

列表级特征

陈主任给实习生上了最后一课:即便把书和需求单都看完,也不能急着拍板。还要看桌上这摞书的全貌——一共 5 本候选还是 50 本候选?里面类似的题材是不是扎堆了?前排位置是不是把好书给盖住了?这些只有把文档放进整个候选集里才看得到的信息,就是列表级特征。

典型的列表级特征包括:候选集规模,规模越大排序越需要精细的区分能力;多样性分布,比如查询"Transformer"时,若前几名全是同质论文,就需要在相关性之外做打散;位置偏差校正,用户习惯只看前三名,第一名的曝光机会天然远大于第十名,要在特征里显式建模这种偏差;还有上下文关联,比如当前文档与相邻文档的主题重叠度。

评估排序质量时,同样要用列表级的指标。最常用的是 NDCG(归一化折损累计增益),它把相关性放入“位置”这个上下文中计算:排得越靠前,贡献越应该被珍惜;排得越靠后,增益要打越大的折扣。

列表级特征让陈主任从“选对单一本书”升级到“排好一整桌书”。排序不是孤立的逐本判断,而是整体棋局的较量——这正是 LTR 与普通分类回归最大的不同。理解了问题定义与特征体系,接下来该看看模型怎么从数据里学排序了。

NDCG@k=∑i=1k2reli−1log⁡2(i+1)∑i=1k2reli∗−1log⁡2(i+1)\displaystyle \text{NDCG}@k = \frac{\sum_{i=1}^{k} \frac{2^{rel_i} - 1}{\log_2(i+1)}}{\sum_{i=1}^{k} \frac{2^{rel_i^*} - 1}{\log_2(i+1)}}
NDCG 比较的是“模型给出的清单”和“最理想的清单”在前 \(k\) 名的折损增益之比。比值越接近 1,说明模型把相关结果排得越靠前。
  1. 位置增益第 \(i\) 位的文档相关性为 \(rel_i\),用 \(2^{rel_i}-1\) 把高相关性的增益指数放大;第 1 名放一本“非常相关”的书,增益是 3,而“相关”的书只有 1。
    2reli−1log⁡2(i+1)\displaystyle \frac{2^{rel_i} - 1}{\log_2(i+1)}
  2. 位置折扣分母 \(\log_2(i+1)\) 让位置越靠后的文档贡献越小——第 1 名折扣为 1,第 4 名折扣约为 2,读者耐心是递减的。
    log⁡2(i+1)\displaystyle \log_2(i+1)
  3. 累计求和把前 \(k\) 名的折损增益全部加起来,得到 DCG,衡量整份清单的质量。
    ∑i=1k2reli−1log⁡2(i+1)\displaystyle \sum_{i=1}^{k} \frac{2^{rel_i} - 1}{\log_2(i+1)}
  4. 理想归一化分母用理想排序下的 \(rel_i^*\)(即按真实相关性降序)算出最大可能 DCG,两者相除得到 0 到 1 之间的 NDCG。
    DCG@kIDCG@k\displaystyle \frac{\text{DCG}@k}{\text{IDCG}@k}

Pointwise方法

现在陈主任教实习生三种训练打分公式的思路。第一种最简单:给每本书单独估一个分——内容契合打 8 分、口碑 6 分、主题跑偏打 3 分,然后按分数排。这就是 Pointwise,逐点方法:把排序问题拆成“单本打分”问题。

Pointwise 把每一对“查询-文档”当成一个独立的训练样本,标签就是人工标注的相关性等级。模型可以用回归(预测等级数值)也可以用分类(预测相关/不相关),损失函数取预测分与真实标注的差距。最常见的是平方损失:

Pointwise 的优点很突出:实现简单、训练快,任何一个回归或分类模型都能直接套用。但致命弱点也很明显——它假设样本互相独立,割裂了“排序是相对比较”的本质。三个文档的绝对分是 8/7/6 和 5/4/3,平方损失完全一样,可读者只关心谁在最前面。盖章再漂亮,也不能替读者回答哪本书该放第一,这正是 Pairwise 登场的理由。

Lpoint=∑(q,d)(yq,d−y^q,d)2\displaystyle \mathcal{L}_{point} = \sum_{(q,d)} \left( y_{q,d} - \hat{y}_{q,d} \right)^2
这个公式惩罚“预测分偏离人工标注”的程度:标了 2 分你预测 1.2 分,误差 0.8,平方后变成 0.64,偏离越远惩罚越重。
  1. 真实标注\(y_{q,d}\) 是人工给这个查询-文档对打的相关性等级,比如 0、1、2。
    yq,d\displaystyle y_{q,d}
  2. 模型预测\(\hat{y}_{q,d}\) 是模型输出的预测分数,可能是 0 到 2 之间的任意实数。
    y^q,d\displaystyle \hat{y}_{q,d}
  3. 平方惩罚\((y-\hat{y})^2\) 让误差被放大惩罚,且误差不可为负,保证梯度方向一致。
    (yq,d−y^q,d)2\displaystyle \left( y_{q,d} - \hat{y}_{q,d} \right)^2

Pairwise方法

陈主任换了个思路,对实习生说:“别管一本书值几分,你只需要回答,这两本里哪本更合适。”把排序拆成一对一对的比较,再汇总出全局次序,这就是 Pairwise,成对方法。

Pairwise 构造“文档对”样本来训练:对同一个查询,取一篇相关性高的文档 \(i\) 和一篇相关性低的文档 \(j\),要求模型给 \(i\) 的分数高于 \(j\)。经典实现是 RankNet:用神经网络或树模型给每篇文档打分,再用 logistic 函数描述“\(i\) 比 \(j\) 更相关”的概率,最后优化这个概率的负对数似然。简化后的成对损失如下:

因为关注的是相对序,Pairwise 比 Pointwise 更贴近排序目标,训练出的模型通常也更可靠。但它把所有文档对等看待——第 1 名和第 2 名的比较,与第 29 名和第 30 名的比较权重相同。可读者的耐心都集中在前几页,这显然还不够合理。两两比试,胜者向前一步,队列渐渐有了秩序;但要真正量化“整份清单好不好”,还得看 Listwise。

Lpair=∑(i,j)∈Plog⁡(1+e−σ(si−sj))\displaystyle \mathcal{L}_{pair} = \sum_{(i,j) \in P} \log\left( 1 + e^{-\sigma (s_i - s_j)} \right)
这里 \(s_i\) 是模型给文档 \(i\) 的分数,\(s_j\) 是给文档 \(j\) 的分数。若模型把更相关的 \(i\) 排得比 \(j\) 高很多,则 \(s_i-s_j\) 很大,损失趋近于 0;若排反了,损失就很大。
  1. 文档对集合\(P\) 是“相关文档在前、不相关文档在后”的文档对集合,只有标注等级不同的对才参与训练。
    P={(i,j)∣reli>relj}\displaystyle P = \{(i,j) \mid rel_i > rel_j\}
  2. 分数差\(s_i - s_j\) 是模型给出的分数差,分数差越大,表示模型越认为 \(i\) 应该排在 \(j\) 前。
    si−sj\displaystyle s_i - s_j
  3. logistic 平滑\(\log(1 + e^{-z})\) 把分数差映射成一个平滑的损失:正确排序时损失小,错误排序时损失大,且处处可导,方便梯度下降。
    log⁡(1+e−σ(si−sj))\displaystyle \log\left( 1 + e^{-\sigma (s_i - s_j)} \right)

Listwise方法

最高级的一课来了。陈主任不再拆开比较,而是直接说:“我要一整份清单。”读者体验取决于整个列表——前 3 名里有没有绝世好书,远比第 29 名和第 30 名谁在前更重要。直接以“整份列表的质量”为优化目标,就是 Listwise,列表级方法。

Listwise 使用列表级损失,最著名的代表是 LambdaRank。它不直接求一个光滑的列表损失函数,而是把成对梯度按“交换位置带来的 NDCG 增益”加权:对 NDCG 提升越大的文档对,训练时给越大的推动力。单个文档 \(i\) 的梯度由所有相关对累加得到:

Listwise 效果通常最好,因为它直接优化我们真正关心的列表指标;代价是训练计算量大,需要反复重排文档、计算 NDCG 增量。工业界常用它的工程化版本 LambdaMART(LambdaRank 加 GBDT)作为网页搜索排序主模型。真正的馆员老手明白:没有银弹。时间紧就逐点快评,求稳就对比较量,要极致体验就整单排布——按场景选方法,才能让最合适的书稳居榜首。

λij=∣ΔNDCGij∣⋅−σ1+eσ(si−sj)\displaystyle \lambda_{ij} = |\Delta \text{NDCG}_{ij}| \cdot \frac{-\sigma}{1 + e^{\sigma (s_i - s_j)}}
\(\lambda_{ij}\) 是文档对 \((i,j)\) 贡献给文档 \(i\) 的梯度。若交换这两个文档的位置能显著提升 NDCG,\(|\Delta \text{NDCG}_{ij}|\) 就大,模型就会用更大的力气把顺序修正过来。
  1. NDCG 增益\(\Delta \text{NDCG}_{ij}\) 是交换文档 \(i\) 和 \(j\) 后 NDCG 的变化量,取绝对值作为梯度权重——越关键的位置交换,权重越大。
    ∣ΔNDCGij∣\displaystyle |\Delta \text{NDCG}_{ij}|
  2. sigmoid 惩罚\(\frac{\sigma}{1 + e^{\sigma (s_i - s_j)}}\) 表示当前排序置信度:如果 \(s_i\) 已经远大于 \(s_j\),这一项趋近于 0,不再需要大幅调整。
    σ1+eσ(si−sj)\displaystyle \frac{\sigma}{1 + e^{\sigma (s_i - s_j)}}
  3. 负号方向负号保证梯度方向是“把更相关的文档往更高分推、把不相关的往下压”,最终让整份列表的 NDCG 上升。
    −σ1+eσ(si−sj)\displaystyle \frac{-\sigma}{1 + e^{\sigma (s_i - s_j)}}

AI 应用场景

搜索引擎结果排序(网页搜索与大模型 RAG 排序)

阶段:离线训练与在线推理阶段

解决的问题:传统布尔检索只做关键词匹配,无法体察“相关性”的细微差别。搜索结果第一屏的排序,直接决定用户是否满意。LTR 通过引入用户点击、停留时长、内容质量、查询意图等特征,把排序训练成一个监督学习任务,让搜索结果从“命中词多”进化到“真懂用户”。

具体案例:某搜索团队积累了大量“查询-网页-用户行为反馈”日志,用点击率、无点击率、最后点击位置等构造训练样本,人工抽取文档质量分、查询词 TF-IDF、候选集位置等特征,用 LambdaMART 训练排序模型。上线后,搜索首屏的 NDCG 提升了 12%,无点击率下降了 5%。大模型 RAG 场景同样用 LTR 对检索回来的候选段落做二次精排,让最相关的上下文片段出现在 Prompt 最前面。

电商搜索与推荐排序

阶段:特征工程与精排模型训练阶段

解决的问题:电商搜索中,“销量最高”和“最匹配意图”经常打架。用户搜“连衣裙”,既要款式匹配,还要兼顾价格带、店铺信誉、物流时效。LTR 把商品点击、加购、转化作为多级标签,融合商品类目偏好、价格带偏差等特征,用 Pairwise 或 Listwise 方法训练排序模型。

具体案例:某电商平台在搜索排序中引入查询级特征(用户历史偏好类目)和列表级特征(同屏商品价格分散度),训练 Pairwise 排序模型。实验结果显示,商品加购转化率提升 8%,同时头部商品的曝光集中度下降,长尾商品获得了更多展示机会。

招聘平台的人才推荐排序

阶段:简历特征工程与匹配排序阶段

解决的问题:招聘平台中,企业搜索“3 年经验的推荐算法工程师”,反馈的是几百份简历。LTR 将简历匹配度转化为监督学习:用 HR 的“查看简历—发起沟通—录用推进”行为作为多级反馈标签,抽取简历技能词匹配度、工作年限、候选人活跃度、与职位地点的距离等特征训练排序模型。

具体案例:某招聘 App 用三层次特征工程改造简历检索:文档级特征(简历技能 TF-IDF、学历质量)、查询级特征(职位类型、薪酬带宽)、列表级特征(当前职位候选人池规模、候选人平均活跃度)。上线后 HR 简历沟通通过率提升 15%,候选人被查看后主动投递的转化率也有显著改善。

Python 演示

python70 行
# -*- coding: utf-8 -*-"""Simplified LTR demo: compare three losses on ranking quality."""import numpy as np # 5 个文档,每个有 2 个特征:内容匹配度、历史热度DOCS = np.array([    [0.9, 0.2],    [0.6, 0.8],    [0.3, 0.5],    [0.8, 0.4],    [0.2, 0.9],])# 人工标注的相关性:0=不相关 1=相关 2=非常相关LABELS = np.array([2, 1, 0, 2, 1])  def ndcg(rank, labels, k=5):    """根据排名顺序计算 NDCG@k."""    dcg = 0.0    for pos, doc_id in enumerate(rank[:k]):        dcg += (2 ** labels[doc_id] - 1) / np.log2(pos + 2)    ideal = sorted(labels, reverse=True)    idcg = sum((2 ** rel - 1) / np.log2(pos + 2)               for pos, rel in enumerate(ideal[:k]))    return dcg / idcg if idcg > 0 else 0.0  class Trainer(object):    """Linear scoring model trained by different LTR losses."""     def __init__(self, mode):        self.mode = mode        self.train_count = 0     def gradient(self, W):        """Return gradient for current weights W."""        if self.mode == "point":  # Pointwise squared loss gradient            err = DOCS.dot(W) - LABELS            return 2 * DOCS.T.dot(err) / len(DOCS)        grad = np.zeros_like(W)        for i in range(len(DOCS)):            for j in range(len(DOCS)):                if LABELS[i] > LABELS[j]:  # doc i is more relevant                    diff = DOCS[i].dot(W) - DOCS[j].dot(W)                    weight = 1.0                    if self.mode == "list":  # Listwise NdCG-gain weighting                        weight = 1.0 + abs(LABELS[i] - LABELS[j])                    grad += weight / (1.0 + np.exp(diff)) * (DOCS[i] - DOCS[j])        return grad / (len(DOCS) * len(DOCS))     def fit(self, epochs=300, lr=0.2):        W = np.zeros(DOCS.shape[1])        for _ in range(epochs):            W -= lr * self.gradient(W)            self.train_count += 1        return W  def main():    for mode in ("point", "pair", "list"):        model = Trainer(mode)        W = model.fit()        rank = sorted(range(len(DOCS)),                      key=lambda i: DOCS[i].dot(W), reverse=True)        print("{} loss -> rank: {}, NDCG: {:.3f}".format(            mode, rank, ndcg(rank, LABELS)))  if __name__ == "__main__":    main()
目的:用 5 个文档的小样本实验,直观对比 Pointwise、Pairwise、Listwise 三种损失训练出的打分器对 NDCG 的影响,验证“Listwise 更贴合排序指标”的结论。
  1. DOCS = np.array([...])把 5 本书的档案卡片写成特征矩阵,每行是一本书、每列是一个特征。
  2. LABELS = np.array([2, 1, 0, 2, 1])人工标注的真实相关性等级,是监督学习的“标准答案”。
  3. dcg += (2 ** labels[doc_id] - 1) / np.log2(pos + 2)NDCG 的核心:位置越靠前,相关性带来的增益越完整地保留。
  4. if self.mode == "point":走 Pointwise 分支,直接优化预测分与标注的平方误差。
  5. if LABELS[i] > LABELS[j]:只挑选“i 比 j 更相关”的文档对,这是 Pairwise 的训练样本来源。
  6. grad += weight / (1.0 + np.exp(diff)) * (DOCS[i] - DOCS[j])成对 logistic 梯度的简化形式,Listwise 模式会额外乘上权重。
  7. W -= lr * self.gradient(W)梯度下降更新打分权重,让模型逐步学会把相关文档排前。
  8. rank = sorted(range(len(DOCS)), key=...)用学到的权重对全部文档打分,并按分数降序生成最终推荐清单。

常见误区

误区我觉得 LTR 就是训练一个模型预测每篇文档的相关性打分,分数算准了,按分排序自然就对了。所以我只要把平方损失做到最小就行。

正解逐点打分准,不等于排序对。陈主任给 A 书打 8 分、B 书打 6 分,换个标准打 5 分和 4 分,绝对分变了但 A 始终排在 B 前面——读者根本不关心分数是 8 还是 6,只关心谁在前。Pointwise 的平方损失只惩罚分数误差,不直接惩罚“排错顺序”,这正是它的天花板。

为何常见:因为平时学回归、分类时习惯了“预测准确”的思维,忽略排序本质上是相对比较问题。

误区我准备了 500 个特征,点击量上万、质量分 0 到 5、布尔特征 0/1,全部拼起来丢给模型,信息量最大,效果一定更好。

正解尺度差异大的特征会让梯度更新被“数值大”的特征主导,相当于陈主任只看“被借次数上万”这一栏,完全忽略了“内容匹配度”这种 0 到 1 规模、对排序极关键的信号。特征要按照文档级、查询级、列表级分层组织,数值型特征做归一化或离散化,模型才能稳定学到信号。

为何常见:机器学习入门课总说“特征越多信息越丰富”,却忽略了 LTR 场景里特征尺度失衡和层级混乱会直接压垮训练。

误区Pairwise 和 Listwise 都在比较文档对,还要计算 sigmoid,训练出来的模型应该差不太多,选实现简单的就行。

正解差别很大。Pairwise 把每个文档对看得一样重,相当于陈主任比较第 1 名和第 2 名、第 29 名和第 30 名都倾注同等精力;Listwise 的 LambdaRank 用 NDCG 增益给文档对加权——排在前面的“神仙打架”远比后面的“菜鸡互啄”重要。所以 Listwise 直接优化列表指标,效果更好,代价是训练更慢。

为何常见:看公式都觉得是成对 sigmoid,忽略了 Listwise 公式里的权重项 \(|\Delta \text{NDCG}_{ij}|\) 才是它脱颖而出的关键。

误区模型 NDCG@10 从 0.5 涨到 0.8,说明每篇文档的相关性标签都预测得更准了,这是个逐样本准确率指标。

正解NDCG 是“位置折扣后的累积增益”,它奖励的是“把相关的东西放前面”,而不是“逐项猜中评级”。模型可能完全预测错第 8、9、10 名的相关性,只要前 3 名都是相关文档,NDCG 依然可以很高。它是列表级指标,衡量的是整份清单的质量,不是逐点准确率。

为何常见:看到“指标”二字就默认它和准确率一样是逐样本统计,忽略了 NDCG 公式里那个重要的位置折扣 \(\log_2(i+1)\)。

面试题

Q1. LTR 将排序问题转化为哪种学习任务?

单选题 · easy

  • A.无监督聚类任务
  • B.有监督学习任务← 正确答案
  • C.强化试错任务
  • D.文本生成任务

答案:B。LTR 的核心是用“查询-文档-相关性标注”三元组训练打分函数,属于监督学习。无监督聚类没有标签,强化学习需要奖励环境,文本生成产出的是文本而非排序,均不符合 LTR 的定义。

Q2. 下列哪项属于文档级特征?

单选题 · mid

  • A.单篇文档的点击量← 正确答案
  • B.查询包含的词数
  • C.候选文档的规模
  • D.位置偏差的程度

答案:A。文档级特征只描述文档自身属性,点击量、质量分、TF-IDF 都属于这类。查询词数属于查询级特征,候选规模与位置偏差属于列表级特征。

Q3. TF-IDF 中 IDF 部分的主要作用是?

单选题 · mid

  • A.突出常见高频词
  • B.降低罕见词权重
  • C.突出罕见判别词← 正确答案
  • D.计算词出现次数

答案:C。IDF 是逆文档频率,包含某个词的文档越少,它的 IDF 越大,说明该词越有区分度。它用来放大罕见但有判别力的词、压低人人都在用的词,而不是突出高频词或计算出现次数。

Q4. Pointwise 方法的主要不足是?

单选题 · mid

  • A.训练过程过于慢
  • B.忽略相对顺序性← 正确答案
  • C.需要构造文档对
  • D.无法利用数值特征

答案:B。Pointwise 把每个样本独立做回归或分类,只惩罚“分数偏离标注”,不惩罚“排错顺序”。它的训练速度其实很快,也不需要构造文档对,更没有放弃数值特征;真正的短板是把排序的相对本质给忽略了。

Q5. RankNet 方法的优化对象是?

单选题 · easy

  • A.单篇文档回归分
  • B.文档对的相对序← 正确答案
  • C.整份列表的 NDCG
  • D.查询的类型意图

答案:B。RankNet 是 Pairwise 经典方法,构造“相关文档 vs 不相关文档”的对,用 logistic 概率描述谁该排前,优化的是文档对的相对顺序。单篇回归是 Pointwise,整份列表的 NDCG 是 Listwise 的目标。

Q6. NDCG 公式里的 log2(i+1) 表示什么?

单选题 · mid

  • A.位置越靠后折扣大← 正确答案
  • B.相关性等级权重
  • C.文档对比较次数
  • D.特征向量维度数

答案:A。\(\log_2(i+1)\) 是位置折扣项:第 1 位折扣为 1,第 4 位折扣约等于 2。位置越靠后,相关文档带来的增益被打折越狠,体现“用户只看前排”的耐心衰减。

Q7. Pairwise 相比 Pointwise 更贴近排序目标的原因是?

单选题 · hard

  • A.减少模型训练时间
  • B.放弃使用任何特征
  • C.关注文档间的相对序← 正确答案
  • D.直接优化整份列表

答案:C。Pairwise 把目标从“预测绝对分数”换成“判断两篇文档谁更相关”,这与排序的相对本质一致,所以效果通常更好。减少训练时间不是它的优势;直接优化整份列表是 Listwise 的特点。

Q8. 下列属于列表级特征的有?

多选题 · mid

  • A.候选文档集规模← 正确答案
  • B.主题多样性的分布← 正确答案
  • C.单篇文档的字数
  • D.排序位置偏差程度← 正确答案

答案:ABD。列表级特征需要在候选集上下文里才能计算:集规模、多样性、位置偏差都是典型代表。单篇文档的字数只与文档自身有关,属于文档级特征。

Q9. 关于 LTR 三层次特征体系说法正确的有?

多选题 · easy

  • A.文档级描述书籍自身← 正确答案
  • B.查询级描述读者需求← 正确答案
  • C.列表级描述候选上下文← 正确答案
  • D.三层次特征互不相关

答案:ABC。三层次特征各司其职:文档级看单本书、查询级看读者需求单、列表级看整张书桌的布局。它们并不互不相关,而是会拼接成完整特征向量共同输入模型,所以 D 错误。

Q10. 关于三种 LTR 方法说法正确的有?

多选题 · hard

  • A.Pointwise 训练快速← 正确答案
  • B.Pairwise 使用成对损失← 正确答案
  • C.Listwise 优化整体指标← 正确答案
  • D.Listwise 计算最轻量

答案:ABC。Pointwise 用回归/分类逐点打分,训练最快;Pairwise 构造文档对并用 logistic 损失优化相对序;Listwise 用 NDCG 增益加权梯度直接优化列表质量,效果最好但计算最重。D 说反了。