ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

重排技术演进:从LTR到深度学习交叉模型

重排技术演进:从LTR到深度学习交叉模型

做搜索和推荐系统的人,基本都绕不开“重排”(Re-ranking)这个阶段。它是承接精排打分、最终决定用户看到什么内容的最后一层排序技术;而重排技术本身这些年从经典的机器学习LTR(Learning to Rank)一路演进到深度学习交叉模型,里面有不少值得掰开揉碎讲的细节。这篇文章我想结合自己的经验,把这套演进脉络梳理一遍:LTR三大流派到底解决什么问题,双塔表示型模型为什么在重排里不够用,交叉模型又是从哪个角度切入的,以及真正落到线上时那些文档里很少写的坑。适合正在做搜索排序、刚接触重排的算法工程师,也适合想理解排序技术演进逻辑的产品和技术同学。

1. 重排在搜索链路里的位置:先搞清楚它在解决什么问题

1.1 搜索排序全流程:召回、粗排、精排、重排各司其职

一个完整的搜索请求进来,大致要经历几个阶段:先是Query理解,做分词、纠错、意图识别,然后从索引里把候选捞出来,这一步叫召回,规模往往在十万级甚至百万级;紧接着是粗排,用轻量模型或向量相似度把候选压到几千甚至几百;然后是精排,通常用一个比较复杂的CTR/CVR模型给每个候选单独打分,产出几百个带分的Item;最后才是重排,在精排分数的基础上,对几十到一百多个候选做全局优化,决定用户最终看到的顺序。

我一直觉得,理解重排最好的方式,是把它和精排放在一起看。精排的核心逻辑是“单点打分”——不管列表里其他Item是什么,模型只管把当前这个候选的分算准。但真实搜索里用户的满意度从来不是单个Item决定的,而是整个列表的质量。你首页前五条如果全是同一个款式、同一个作者、同一类内容的变体,单看每个Item分数可能都不低,但用户会觉得这个结果“很重复、很没意思”。重排干的就是这个事:把精排给的一堆分,放到“列表”这个维度重新考虑。

1.2 精排和重排的本质差异:局部最优不等于全局最优

精排到重排的转换,本质上是“从局部最优到全局最优”的视角切换。精排模型中常见的特征,比如CTR、CVR、相关性分,都是针对单个候选与Query的关系建模;但重排要考虑的是候选与候选之间的关系——两篇文章是否同质化、列表整体的类目分布是否失衡、是否有足够的新鲜内容、是否需要插入运营位和商业约束。

举个常见的例子:用户搜“手机”,精排结果里头部全是某品牌旗舰机型,因为该机型点击率确实最高。但如果不做重排,用户看到的就是一整屏相似产品,反而容易产生“搜不出东西”的感觉。重排的价值恰恰在于通过去重、打散、多样性控制,让列表在保证相关性的前提下,尽量覆盖不同价位、不同品牌、不同卖点。这个诉求是精排模型很难直接建模的,因为精排的损失函数通常只看单个Item预测得准不准。

1.3 重排不止模型能力,更是一套业务规则和技术约束的平衡

很多人以为重排就是从精排结果里换个顺序,其实它背后是一套非常复杂的约束系统。一方面要照顾相关性这个底线,另一方面要融合多样性、时效性、商业策略、新手保护、内容生态等一堆目标。早期工业界的重排,大量依赖规则,比如MMR(最大边缘相关)打散、按类目硬限制、运营人工调权;后来才开始用LTR模型去学习“什么样的顺序好”。

这一点直接决定了重排技术演进的方向:规则方法的问题是维护成本高、无法泛化;LTR能用数据自动学出排序逻辑,但它偏重特征工程和浅层模型;到了深度学习交叉模型阶段,才真正把Query和Item的文本级交互纳入模型,让重排有能力理解“语义匹配而不只是词面匹配”。所以下面聊的LTR和深度学习交叉模型,不是互相替代的突变关系,而是沿着“从规则到学习、从特征到语义、从单点到列表”这条线一路走过来的。

2. 传统机器学习排序(LTR):三大流派与核心模型

2.1 Pointwise:把排序当成回归或分类,问题出在哪里

LTR里的Pointwise流派,思路最直接:给每条Query-Doc样本打一个分数标签,然后把排序问题当成回归或分类来做。比如预测用户是否点击(二分类),或者预测观看时长(回归)。早期的LR、GBDT模型很多都是这么用的,特征是Query侧、Doc侧、匹配侧的人工统计量。

这个方案的好处是工程简单,坏处是它压根没在“排序”。同一个Query下的多个文档,在Pointwise看来是相互独立的样本,而真实场景里它们有强烈的竞争关系——用户点了A,说明B相对没那么好。Pointwise模型学到的往往是孤立的“绝对分”,跟着排序指标比如NDCG的关联很弱。我见过不少团队拿点击率模型直接当排序模型用,离线AUC不差,一上线上NDCG就是不涨,原因基本都是这个。

2.2 Pairwise:把目标从绝对分数改成相对关系

Pairwise的出现,就是冲着“排序是相对关系”这一点来的。它不直接预测每个文档的分数,而是构造文档对:同一个Query下,Doc A的相关性比Doc B高,模型就要学到“A排在B前面的概率更大”。经典的代表是RankNet,它用神经网络给每篇文档打基础分,再用sigmoid函数把两篇文档的分差转换为“A胜于B”的概率,损失采用交叉熵。

这个思路比Pointwise高明在哪?简单说,它让模型把注意力放到“谁该在谁前面”上,而这才是排序的本质。实现上也不难:训练数据从“单条样本”变成“一对样本”,同一个Query下选择一对相关度不同的文档,喂给模型判断相对顺序。当然问题也跟着来——文档对的数量是平方级的,同一个Query下如果有100个候选,构造全量Pair对就是几千对,数据量爆炸、训练时间拉长,而且它优化的还是两两关系,和NDCG这种整个列表指标不完全对齐。

2.3 Listwise:直接优化整个列表,LambdaRank与LambdaMART

Listwise流派的思路更进一步:模型不要只看一两对文档,而是优化整个列表的排序效果。这里面最经典、也最经得起时间考验的是LambdaRank和它的GBDT版本LambdaMART。

LambdaRank的核心巧思是:它不直接定义列表的损失函数,而是给每个文档定义一个“Lambda梯度”。这个梯度的含义是——“如果我把这篇文档往上挪一位,或者往下挪一位,NDCG会变化多少”。模型训练时就按这个梯度去更新,相当于每一步都在朝着提升NDCG的方向走。LambdaMART就是把Lambda梯度用到GBDT的树模型上,在工业界用了很多年,直到今天LightGBM都还内置了LambdaRank目标,很多团队的基线模型仍然是它。

我个人对LambdaMART的评价是:它把“特征工程”和“排序优化”这两件事结合得非常务实。在深度学习大规模应用之前,搜索排序团队里有大量时间花在特征设计上——特征怎么跨Query归一化、怎么处理缺失值、怎么做非线性交叉,树模型对这些东西容忍度很高。可以说,LambdaMART是传统LTR时代的集大成者,今天聊深度学习交叉模型,也终究绕不开它定下的“优化排序指标”这个基调。

2.4 评价指标的选择:NDCG、MAP、MRR到底怎么用

聊LTR必然绕不开排序指标。搜索场景里最常看的是NDCG:它假设排在后面的相关结果带来的增益要折损,位置越靠后折损越大。公式上,DCG是每个位置的增益按位置折扣累加,NDCG是拿当前排序的DCG除以理想排序的IDCG,把结果归一化到0到1之间。

MAP更侧重相关文档在整个列表里的平均精度,如果用户只关心前几条结果,MAP会非常敏感;MRR只看第一个相关结果出现在第几位,适合问答、导航类搜索,用户找的是“唯一正确的那一个”。实际工作中,NDCG@K是最常用的,K通常取5、10、20;如果搜索结果天然只有单一最佳答案,MRR更合适。需要注意的是,不同指标对模型优化的信号完全不同,Pairwise损失往往和NDCG有gap,而Listwise方法天然对着NDCG使劲,这也是为什么后者在工业界长期是首选。

3. 深度学习排序的两条路线:表示型双塔与交叉型模型

3.1 从GBDT到深度学习的动机:特征工程的瓶颈与语义匹配的缺失

传统LTR做得再好,有一个绕不过去的天花板:特征工程。比如用户搜“秋天的第一杯奶茶”,一篇写“秋日新品饮品推荐”的文章,词面上和Query几乎没有重叠,传统匹配特征BM25算出来会很低。但人一看就知道这俩是相关的,因为“奶茶”和“饮品”存在语义关联,“秋天”和“秋日”是同义表达。这类语义级匹配信号,单靠统计特征很难刻画。

深度学习带来的变化,首先是自动学习文本表征。通过大规模语料预训练的词向量,可以把“奶茶”和“饮品”映射到相近的向量空间,让模型具备基本的泛化能力。所以从深度学习切入排序的第一波浪潮,基本都围绕“怎么把文本语义建模进排序模型”,这也是DSSM这类向量模型盛行的原因。

3.2 双塔表示型模型(DSSM):召回和粗排的好手,却是重排的短板

DSSM(Deep Structured Semantic Model)是表示型模型的代表。它的结构很清晰:Query过一层网络编码成一个向量,Doc过另一层网络编码成一个向量,最后用两个向量的内积(或余弦相似度)作为匹配分。训练时正样本是点击的Query-Doc对,负样本可以是随机采样或曝光未点,损失用pairwise或softmax。

双塔的最大优点是工程友好:Doc侧向量可以离线算好存进索引,在线只有Query需要实时编码,然后做向量检索就行。正因如此,DSSM及其变体(比如Sentence-BERT式的表示模型)大量用于召回和粗排阶段,速度快、性价比高。但它放在重排阶段有明显短板:Query和Doc的交互发生得太晚,只是在最后的向量层做了一次内积。词与词之间的细粒度对齐、同义消歧、句法结构这些信号,全被压缩在一个向量里了。换句话说,双塔擅长捕捉“整体语义相似”,但不擅长捕捉“哪里相关、哪里不相关”这种精确匹配细节,而后者恰恰是重排场景区分度最高的信号。

3.3 交叉模型的核心思想:让Query和Doc尽早相互看对方

针对双塔“交互太晚”的问题,交叉模型(Cross-Attention / Interaction-based Model)提出一个可以说非常直觉的思路:与其把两段文本各自编码成向量后再比较,不如让它们从底层就开始相互“看”。人可以判断“秋天的第一杯奶茶”和“秋日饮品新品”相关,不是因为在脑子里生成两个标签再对比,而是逐词对照——“秋天”对“秋日”、“奶茶”对“饮品”,天然就能找出来。

交叉模型在结构上的表现,通常是先构建Query和Doc词之间的相似度矩阵,或者把二者拼接成一段序列后一起过编码器,让模型在早期就能同时看到两边的文本信息。这样做的好处是匹配信号非常充分,坏处是计算量上去了、以往能离线预处理的Doc表示不能直接用了,每一次打分都要临场算交互,这也成了重排场景落地时需要重点解决的成本问题。

3.4 代表性交叉模型盘点:MatchPyramid、KNRM、Conv-KNRM、DRMM与BERT时代

学术界对交叉模型的探索,有几个绕不开的代表作。MatchPyramid的思路是先把Query和Doc的每个词两两算相似度,构造一个相似度矩阵,然后把它当成一张“图”交给CNN去卷积——不同大小的卷积核可以捕捉不同粒度的词对齐模式,比如一元匹配、词组匹配、短语级匹配。

KNRM则换了一个思路:它也构造词对相似度矩阵,但不是用CNN去卷,而是用一组高斯核把矩阵里的相似度值做“软化分桶”。比如“完全匹配”“近义匹配”“弱相关”各算一个桶,统计每个桶里有多少词对,形成一组统计特征再交给输出层打分。这个方法对短文本匹配非常有效,而且核参数的个数有限,模型小而快。Conv-KNRM是在KNRM前加了卷积层,先提取n-gram特征再做匹配矩阵,处理短语级匹配更强。DRMM则是把Query和Doc的匹配分数按区间分桶成直方图,然后接MLP,思路是保留全局统计信息、减少位置噪声。

到了预训练语言模型时代,交叉模型最典型的形态是Cross-Encoder:把Query和Doc拼成“[CLS] Query [SEP] Doc [SEP]”喂给BERT类的Transformer,用[CLS]向量接MLP输出分数。效果通常是最强的,因为自注意力机制能让Query的每个词attend到Doc的每个词,深度建模交互关系;但代价也最大——每个候选都要完整过一次Transformer,延迟压力非常大。中间路线则如ColBERT,采用后期交互(Late Interaction):Query和Doc各自编码成token级别的向量序列,但打分时让Query每个token与Doc所有token算最大相似度再加总,兼顾了一部分交叉能力与预计算能力,这个思路在工程上也是值得借鉴的。

我用一个表格把这几类模型的特点总结一下:

模型类型代表方法交互强度计算成本适用阶段
表示型双塔DSSM、Sentence-BERT低(只交互一次)低,Doc可预计算召回、粗排
浅层交叉MatchPyramid、KNRM、DRMM中(词级矩阵交互)中精排、重排候选数较少时
深度交叉BERT Cross-Encoder高(自注意力全交互)高精排、强语义匹配
后期交互ColBERT中高(token级MaxSim)中重排与召回之间的折中

4. 深度学习交叉模型落地实践:从样本构造到在线服务

4.1 训练样本怎么构造:Pair采样、负样本策略与位置偏置

模型再先进,样本不对也白搭。重排模型的训练样本,核心来源是用户行为日志:一个Session里用户搜索了什么Query、看到了哪些结果、点击了哪些、跳过了哪些。常规做法是,同一个Query下,把有点击的文档当成正样本,把曝光未点击的文档当成负样本;如果做Pairwise,就构造“点击文档应该排在未点文档前面”的文档对。

这里有一个搜索场景特有的坑:位置偏置(Position Bias)。排在前面的结果天然更容易被用户看到,所以点击概率本身就高,如果不处理,模型学到的可能不是“内容更好”,而是“位置更靠前”。一个相对实用的做法是:在模型特征里加入位置特征,让模型学习位置带来的影响并尝试剔除;或者更严格一点,用同一位置的曝光未点作为该位置点击样本的负样本,构造“同位置对比”。实际工作中我建议至少保留位置特征,否则线上和离线的偏差会很大。

负样本的策略也有讲究。只用随机负样本,模型学到的区分度太弱,容易把“完全不相关”的都归一类,但在头部候选之间没有判别力;只用难负样本(和Query有部分词重叠但不相关的文档),训练难度大、收敛慢。常见的做法是混合采样——一部分随机负样本保证基础区分度,一部分难负样本提升模型鉴别力,比例大概在7比3到8比2之间。这个比例不是拍脑袋定的,要在验证集上观察NDCG的变化来微调。

4.2 损失函数的选择:Pairwise还是Listwise Softmax

交叉模型在训练时,损失函数的选择会直接影响优化方向和效果。Pointwise方式(把点击率当作标签做分类)不是不能用,但和排序指标的一致性差一些,尤其当候选列表内部的分数差异不大时,Pointwise很容易把“都差不多”的文档拉不开距离。实践中效果较好的两种是:Pairwise损失(RankNet式交叉熵)和Listwise Softmax损失。

Listwise Softmax的直观理解是:把同一个Query下所有候选文档的模型得分放进一个Softmax里,变成“哪篇文档最可能被用户选中”的概率分布,然后用真实点击位置作为标签做交叉熵。这种损失天然关注列表内候选之间的相对排序,与在线排序目标非常贴近。我们团队最终采用了Listwise Softmax加上光照样本负采样,离线NDCG和线上CTR提升都比较稳定。需要提醒的是,Listwise损失对同一个Query下的候选集合比较敏感,构造Batch时尽量保证每个Batch内的Query总量充足,否则梯度噪声会偏大。

4.3 特征体系与训练配置:文本特征加统计特征的融合

交叉模型能直接吃原始文本,但不代表传统统计特征没用了。我见过有些团队一开始只给BERT Cross-Encoder喂token序列,想着模型自己能学一切,结果发现效果提升有限,尤其是CTR、价格、时效这些纯数值型信息,模型很难从文本里无中生有地推断出来。

比较务实的做法是:把分好词的Query和Doc文本通过Embedding层进入交互模块,同时在模型末尾把文本交互层的输出与统计特征拼接,再经过几层MLP得到最终分数。统计特征包括BM25、传统的Query-Doc共现、Doc的离线CTR、时效性分数、类目匹配度等。这样做的好处是:语义信号由交叉模型负责,统计与业务信号由显式特征负责,两者互补。训练时,一般先用预训练好的词向量或预训练语言模型做初始化,再在业务数据上finetune;如果数据量不大,冻结底层Embedding、只训练上层,也是个避免过拟合的稳妥选择。

4.4 在线延迟优化:蒸馏、级联过滤与预计算

交叉模型效果虽好,但线上延迟是重排落地最大的坎。重排阶段的候选数量通常在几十到一两百,算力有限时每个候选都过一次BERT确实不现实。工程上有几类常用的优化手段。

第一种是蒸馏。用强大的交叉模型当Teacher,在业务数据上蒸馏出一个轻量模型当Student,Student往往可以是一个双塔模型、一个浅层交叉网络,或者一个SIMD优化过的线性层模型。蒸馏时不仅学Teacher的分数,也可以学Teacher中间层的匹配信号,上线效果逼近Teacher,但耗时可能降到原来的十分之一以内。第二种是级联过滤。重排阶段再做一次粗筛:先用双塔或轻量模型从一两百候选里筛出Top 30到50,再只对这部分候选跑交叉模型,精度损失有限,延迟压力大幅下降。第三种是预计算。凡是和Query无关的Doc侧计算,尽量离线做好;即使是交叉模型,也可以通过缓存Doc侧的某些表达,配合后期交互思路,减少在线计算量。再加上批量推理、TensorRT量化这些通用手段,把交叉模型部署到高QPS的线上并不是遥不可及的事。

4.5 离线评估与在线评估:别只看AUC和NDCG

交叉模型离线评估,常规指标是NDCG、MAP、MRR等排序指标,这些要一直盯。但只盯着这些还不够——重排影响的是整个列表的体验,建议离线时同时看一眼列表多样性指标,比如类目覆盖率、来源分散度,确认模型把列表“打散了”而不是“收敛了”。

在线评估则建议分成两层看:第一层是核心业务指标,比如搜索点击率、点击到转化的转化率、人均点击次数;第二层是体验指标,比如搜索无结果率、首屏同质化程度、用户反馈率。这里要特别提醒,离线NDCG涨不一定代表线上CTR涨,原因很多,最常见的是样本偏差——离线训练数据来自旧排序策略下的曝光,而线上模型改变排序后,曝光分布立刻变了,这就是所谓的反馈闭环。缓解方法是加一定随机探索流量,或者用无偏学习(比如ES-DFM这类位置与选择偏置校正框架)来训练,至少要在实验阶段留出一部分探索流量,否则你的模型会一直在“自己创造的数据”里打转。

5. 重排技术演变中的常见问题与避坑经验

5.1 离线指标涨了,线上不涨甚至下跌

这是搜索排序里最经典的问题。排查顺序一般是:先看特征一致性——离线训练用的特征和在线打分用的特征是否完全一致,日期、归一化参数、Embedding版本有没有锁死;再看采样偏差——如果离线样本全来自旧策略的曝光,新模型排序打出来后,会展示一批旧策略下用户没见过的东西,这部分效果离线根本看不到。

我踩过的一个具体坑是:某个统计特征按天更新,离线测试时用的是当天更新后的值,在线服务却因为缓存原因用了前一天的旧值,导致模型打分系统性偏移。后来把特征时间戳纳入了监控,离线在线Diff测试跑进CI流程,这类问题才基本杜绝。建议每个搜索团队都把“离线在线一致性校验”当成标配,而不是出了线上问题再回头查。

5.2 样本偏置、位置偏置和数据爆炸三重麻烦

样本偏置的根源是曝光数据本身有偏。用户看到的永远是历史排序策略认为好的东西,那些没被展示过的内容,你永远拿不到它们的真实反馈。缓解方式之一是在线分配探索流量,比如1%的请求走随机排序或弱化排序,长期积累能显著改善样本覆盖;之二是在损失层面做无偏校正,把曝光倾向性估计出来并加权。

位置偏置我们也聊过了,最实用的处理是加位置特征、同位置负样本对比。数据爆炸则是Pairwise的固有问题:一个Query下有120个候选,全量Pair就是7140对,处理起来很慢。我的习惯是每个Query最多采样20到30个Pair对,优先选分数差距大的(正样本与高置信负样本之间),这样既保留了学习信号,又不会把训练时间拖到不可接受。

5.3 交叉模型延迟打不下来怎么办

如果一上来就上BERT Cross-Encoder做重排,大概率延迟会告警。建议的路线是分步骤降级:先用双塔或者浅层交叉模型(比如KNRM、Conv-KNRM)跑通线上链路,把特征、样本、评估闭环都跑顺;然后在这个基线上用蒸馏方案把大模型能力压缩进去;最后再考虑对延迟最敏感的Top候选用全量交叉模型做精排。

这里有个容易被忽视的细节:交叉模型的延迟优化不能只看单次打分耗时,还要看整体吞吐。比如某个大模型单条推理要3毫秒,但100个候选可以组成一个Batch并行推理,分摊到每条可能只有零点几毫秒,前提是你的服务端把Batch推理做好了。很多团队明明模型不慢,却卡在循环里一条一条地调用,白白浪费了并行能力。

5.4 双塔与交叉模型的分工协作:不是非此即彼

回顾全篇,你会发现重排技术不是简单地从LTR跳到深度学习交叉模型,而是层层分工、互相协作。传统LTR(尤其LambdaMART)在今天仍是很多团队靠谱的基线;双塔模型承担召回和粗排、把候选规模降下来;浅层交叉模型承担重排的候选精排;深度交叉模型在这个基础上进一步做语义把关;最后再用业务规则和人工策略保证生态指标。

我见过一些团队试图用一个大模型包打天下,结果延迟扛不住、样本也不够、出了问题还不好排查。更推荐的做法是:把重排做成级联系统——粗排(双塔)→ 重排候选精排(浅层交叉/蒸馏模型)→ 兜底语义模型(需要时才上),每层都有明确分工和可回退的兜底方案。这样模型迭代可以独立进行,不会因为某一层升级失败导致整个线上链路瘫痪。

5.5 几个值得长期坚持的习惯

最后分享几个我一直在坚持的做法:第一,任何新模型必须先跑离线-在线一致性校验,再谈效果;第二,重排模型的样本和特征版本必须做严格版本管理,否则你复现不了一个月前的线上效果;第三,把NDCG、多样性、延迟三个指标同时挂在看板上,任何一次迭代都同步观察三个指标的变化,避免单一指标导向导致体验失衡;第四,别盲目追求大模型,先把数据质量、评估体系、线上链路这三件事做扎实。

重排技术从机器学习LTR演进到深度学习交叉模型,表面上是模型越来越复杂,本质上是大家对“搜索到底在优化什么”的理解越来越深——从单点相关,走向整个列表的全局满意。我个人做了几年搜索排序,最大的体会是:交叉模型本身不会自动带来效果,真正拉开差距的,永远是样本质量、特征一致性和评估闭环这些基本功。先把这些做扎实,再谈上不上大模型,会少走很多弯路。如果让我给一个演进路线,那就是:规则基线 → LambdaMART → 双塔粗排 → 浅层交叉重排 → 蒸馏与级联优化,一步一个脚印,反而比直接堆模型走得更快。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进