新闻详情

新闻详情

首页 / 资讯中心 / 详情

排序模型实战:从GBDT+LR到深度学习的设计与优化

发布时间:2026/9/28 17:44:25来源:尧图网络
排序模型实战:从GBDT+LR到深度学习的设计与优化
1. 排序模型到底在解决什么问题排序模型这四个字听起来像是推荐系统或者搜索引擎的专属名词但实际上它的应用范围远比大多数人想象的宽。电商里搜索运动鞋之后那一列结果怎么排、短视频信息流里下一条推什么、招聘平台上简历和岗位怎么匹配、甚至外卖App里商家列表的先后顺序背后都是排序模型在干活。说白了排序模型的核心任务就一句话给定一个用户和一个候选集合把最可能让用户满意的那些item挑出来并且按满意度从高到低排好序。这件事看起来简单做起来极其复杂。因为满意度这个东西没法直接测量你只能通过用户的行为去间接推断——点击了、停留了多久、收藏了、下单了、划走了、举报了每一种行为都携带不同的信号强度。排序模型要做的就是把这些信号整合起来对每一个候选item打出一个分数然后按分数排列。这个分数不需要有绝对意义只要保证相对顺序正确就行这也是排序模型和回归模型、分类模型在目标上的本质区别。我刚开始接触排序模型的时候最容易犯的一个错误就是把它当成一个普通的二分类问题来做——预测用户会不会点击。这么做不是不行但效果往往差强人意。原因在于排序是一个相对问题用户面对的是一屏结果他是在几个候选之间做选择而不是独立判断每个item的好坏。你单独预测每个item的点击率然后按点击率排序理论上可行但实际中会遇到位置偏差、样本选择偏差等一系列问题。所以后来才有了pairwise、listwise这些专门为排序设计的损失函数和训练方式。这篇文章适合谁看如果你正在做推荐系统、搜索、广告相关的项目或者你在准备机器学习相关的面试再或者你只是好奇为什么我搜出来的东西顺序是这样的那这篇内容应该能给你一些实在的参考。我会从整体设计思路讲到具体实现细节再把我自己踩过的坑和排查问题的经验都倒出来尽量让你看完就能上手。2. 排序模型的整体设计思路与方案选型2.1 从业务目标倒推技术方案做排序模型最忌讳的一件事就是上来就选模型、调参数而不去想业务目标是什么。不同的业务目标会导致完全不同的技术选型。比如电商搜索的排序核心指标可能是GMV成交额那你在设计模型的时候就要把转化率、客单价这些因素考虑进去而内容信息流的排序核心指标可能是用户停留时长或者互动率那模型优化的方向就完全不一样。我一般会先问三个问题第一这个排序场景下用户的核心行为是什么是点击、购买、还是停留第二不同行为之间的优先级怎么定比如点击和购买显然购买更重要但点击数据量远大于购买数据怎么平衡第三有没有硬性约束比如某些内容必须置顶、某些商品不能出现在前几位这些约束必须在排序之后单独处理不能指望模型自己学会。这三个问题想清楚了你才能决定用什么样的模型结构、什么样的损失函数、什么样的特征体系。我见过太多团队一上来就搞深度学习模型结果发现业务目标都没定义清楚最后模型效果没法评估项目直接烂尾。2.2 排序模型的三代演进路线从技术演进的视角看排序模型大致经历了三个阶段每个阶段解决的核心问题不一样。第一代是线性模型时代代表是LR逻辑回归加上大量的手工特征交叉。这个阶段的核心思路是既然线性模型学不会特征之间的交互那我就人工把交叉特征构造出来。比如用户性别女 且 商品类目美妆这个组合特征人工构造好之后喂给LR。这种做法可解释性强工程上也好维护但问题也很明显——特征工程的工作量巨大而且很多高阶交叉根本没法穷举。第二代是树模型和因子分解机时代代表是GBDTLR、FM、FFM。GBDT负责自动做特征交叉和离散化LR负责最终的融合。FM和FFM则是通过隐向量的方式来自动学习二阶交叉特征大大减少了手工工作量。这个阶段我在实际项目里用得最多的是GBDTLR的组合效果稳定训练速度也快特别适合中小规模的排序场景。第三代是深度学习时代代表是WideDeep、DeepFM、DIN、DIEN这些模型。深度学习最大的优势是能端到端地学习高阶特征交叉而且可以很方便地引入序列特征、注意力机制。但代价是模型复杂度高、训练成本大、线上 serving 的延迟也更高。我的经验是如果你的候选集规模在几百到几千QPS要求不是特别高深度学习模型值得一试但如果候选集上万、QPS要求几千甚至上万那还是得在模型复杂度和推理速度之间做权衡。2.3 为什么我推荐从GBDTLR起步虽然现在深度学习排序模型很火但我依然建议刚接触排序模型的人从GBDTLR开始。原因有三第一这个方案的工程链路清晰从特征工程到模型训练到线上部署每个环节你都能看得见摸得着不像深度学习模型那样像个黑盒第二GBDTLR的效果在很多场景下并不比深度学习差太多尤其是当你的特征体系还不够丰富的时候第三这个方案对数据量的要求相对较低几百万条样本就能训出一个可用的模型而深度学习模型往往需要上千万甚至上亿的样本才能发挥优势。具体来说GBDTLR的流程是这样的先用GBDT在原始特征上训练然后把GBDT每个叶子节点的输出作为一个新的特征所有叶子节点的输出拼接成一个稀疏向量再把这个向量喂给LR做最终训练。GBDT在这里的作用是自动做特征交叉和离散化LR的作用是给这些交叉特征分配权重。这个方案的精妙之处在于GBDT的树结构天然地捕捉了特征之间的非线性关系而LR又保证了最终输出的概率校准性。3. 核心细节解析与实操要点3.1 样本构造排序模型的地基排序模型的样本构造和普通分类模型有本质区别。普通分类模型一条样本就是一个独立的样本但排序模型的样本往往是以组的形式出现的——同一个请求下的所有候选item构成一个组组内的样本之间有相互竞争的关系。我刚开始做的时候没注意这一点直接把所有曝光日志打平当成独立样本训练结果模型学出来的分数在不同请求之间没有可比性。后来才明白排序模型必须保留组结构尤其是在用pairwise或listwise损失的时候。具体操作上我一般会这样构造样本每个请求ID对应一个组组内包含该请求下所有被曝光的item每个item标注用户是否点击、是否转化等行为。正样本是有点击或转化的item负样本是曝光未点击的item。这里有个坑曝光未点击的item不一定真的是负样本用户可能只是没看到那个位置或者看到了但不感兴趣。所以有些团队会做未曝光采样把那些没被曝光但可能相关的item也作为负样本这样能缓解位置偏差的问题。还有一个细节是样本的时间窗口。排序模型对时效性很敏感用户昨天的兴趣和今天的兴趣可能完全不同。我一般会用最近7到14天的数据来训练太老的数据反而会引入噪声。如果业务变化快比如电商大促期间那时间窗口还要进一步缩短。3.2 特征体系决定排序效果上限的关键特征工程是排序模型里最耗时间但也最出效果的部分。我习惯把特征分成四大类用户侧特征、item侧特征、上下文特征、交叉特征。用户侧特征包括用户ID、年龄、性别、历史行为统计比如过去7天点击了多少次、购买了多少次、兴趣标签等。item侧特征包括item ID、类目、价格、历史CTR、历史转化率等。上下文特征包括时间、地点、设备、网络环境等。交叉特征则是用户侧和item侧的组合比如用户对某个类目的历史点击率。这里我要特别强调一点历史统计特征一定要做时间窗口的切分。比如过去1天点击次数、过去7天点击次数、过去30天点击次数这三个特征携带的信息完全不同。过去1天的反映短期兴趣过去30天的反映长期偏好。如果你只用一个历史点击次数模型就没法区分短期和长期兴趣。另外特征的时间对齐极其重要。你在构造训练样本的时候特征只能使用样本发生时间之前的数据绝对不能用到未来的信息。这个坑我踩过——有一次不小心把item的未来曝光量作为特征离线AUC高得离谱上线之后效果一塌糊涂。后来排查了半天才发现是特征穿越了。3.3 损失函数选择Pointwise、Pairwise还是Listwise排序模型的损失函数有三种主流选择每种都有各自的适用场景。Pointwise是把排序当成回归或分类问题对每个item独立预测一个分数。优点是实现简单缺点是忽略了item之间的相对关系。我一般只在候选集很小、或者只需要粗排的场景下用pointwise。Pairwise是把排序当成一个二分类问题但样本是成对的——一个正样本和一个负样本组成一对模型要学习的是正样本的分数应该高于负样本。代表算法是BPR和RankNet。Pairwise的好处是直接优化了相对顺序更贴近排序的本质。但缺点是样本对的数量是O(n^2)级别的如果候选集很大样本对会爆炸。Listwise是把整个候选列表作为一个整体来优化代表算法是ListNet和LambdaRank。Listwise理论上最优因为它直接优化了列表级别的指标比如NDCG但实现复杂度也最高。我在实际项目中用得最多的是pairwise因为它在效果和复杂度之间取得了比较好的平衡。这里给一个我常用的pairwise损失实现思路对于每个请求取一个正样本和一个负样本组成一对计算两个样本的分数差然后用sigmoid函数把分数差映射成概率最后用交叉熵损失来训练。关键代码如下import torch import torch.nn as nn class PairwiseLoss(nn.Module): def __init__(self): super().__init__() self.sigmoid nn.Sigmoid() def forward(self, pos_score, neg_score): # pos_score: 正样本的预测分数 # neg_score: 负样本的预测分数 diff pos_score - neg_score prob self.sigmoid(diff) # 目标是让prob接近1即正样本分数高于负样本 loss -torch.log(prob 1e-10).mean() return loss这个实现很简单但效果很稳。注意这里加了一个极小值1e-10防止log(0)的情况。3.4 评估指标离线看什么线上看什么排序模型的评估指标分离线 and 线上两套。离线常用的有AUC、GAUC、NDCG、MAP、MRR。线上则看CTR、CVR、人均点击次数、停留时长这些业务指标。AUC是最常用的离线指标但它有个致命缺陷AUC是全局的不区分请求。也就是说AUC高不代表每个请求内部的排序都好。所以我更推荐用GAUCGroup AUC也就是按请求分组计算AUC然后加权平均。GAUC能更好地反映模型在每个请求内部的排序能力。NDCGNormalized Discounted Cumulative Gain是另一个我常用的指标它考虑了位置的影响——排在前面的item权重更高。NDCG的计算公式是NDCGK DCGK / IDCGK其中DCGK Σ(rel_i / log2(i1))rel_i是第i个位置的相关性分数IDCGK是理想情况下的DCG。这个指标特别适合评估Top-K排序的质量。线上评估就简单直接得多主要看业务指标的变化。但要注意线上评估一定要做A/B实验而且实验周期要足够长至少覆盖一个完整的用户行为周期比如一周。我见过太多团队A/B实验只跑了一天就下结论结果第二天指标就反转了。4. 实操过程与核心环节实现4.1 数据准备与特征工程实操假设我们现在要做一个电商搜索的排序模型候选集是搜索运动鞋之后返回的500个商品。第一步是准备数据。原始数据一般包含三张表曝光日志表记录每个请求下曝光了哪些item、用户行为表记录用户的点击、购买等行为、item属性表记录item的类目、价格等信息。我一般会先用SQL把这些表join起来生成一张宽表每一行是一个请求-item对包含用户特征、item特征、上下文特征和label。特征工程阶段我会重点构造以下几类特征用户历史统计特征过去1/7/30天的点击次数、购买次数、点击率、购买率按类目、品牌、价格区间分别统计。item历史统计特征过去1/7/30天的曝光量、点击量、点击率、转化率。用户-item交叉特征用户对该item所属类目的历史点击率、用户对该品牌的购买次数、用户历史购买价格均值与item价格的差值。上下文特征请求时间的小时、星期几、是否节假日、用户所在城市等级。这里有个实操技巧对于连续值特征我一般会做分桶离散化。比如价格我会分成0-50、50-100、100-200、200-500、500这几个桶。离散化的好处是能捕捉非线性关系而且对异常值更鲁棒。分桶的边界可以通过等频分桶或者业务经验来确定。4.2 模型训练与调参实录数据准备好之后就可以开始训练了。我用的是LightGBMLR的方案LightGBM负责特征交叉LR负责最终融合。LightGBM的训练参数我一般这样设置import lightgbm as lgb params { objective: binary, metric: auc, boosting_type: gbdt, num_leaves: 64, learning_rate: 0.05, feature_fraction: 0.8, bagging_fraction: 0.8, bagging_freq: 5, max_depth: 6, min_data_in_leaf: 100, lambda_l2: 1.0, verbose: -1 } train_data lgb.Dataset(X_train, labely_train) valid_data lgb.Dataset(X_valid, labely_valid, referencetrain_data) model lgb.train( params, train_data, num_boost_round500, valid_sets[valid_data], callbacks[lgb.early_stopping(50), lgb.log_evaluation(50)] )这里有几个参数值得说明。num_leaves64和max_depth6是为了控制模型复杂度防止过拟合。min_data_in_leaf100保证每个叶子节点至少有100条样本避免学出太细碎的规则。learning_rate0.05配合num_boost_round500和早停是比较稳妥的组合。LightGBM训练完之后我把每棵树的叶子节点输出作为新特征拼接成一个稀疏向量然后用LR训练。LR的训练我用的是sklearn的LogisticRegressionC0.1做正则化。这里有个细节LightGBM的叶子节点编号需要做one-hot编码。比如第1棵树有64个叶子那这64个叶子就对应64个one-hot特征。所有树的叶子特征拼接起来维度可能上万。这个稀疏向量直接喂给LR就行LR对稀疏特征的处理效率很高。4.3 线上部署与性能优化模型训练好之后下一步是部署到线上。排序模型的线上 serving 有两个核心要求低延迟和高吞吐。低延迟方面我一般会把LightGBM和LR的推理逻辑用C重写或者用ONNX Runtime来加速。LightGBM的推理其实很快主要瓶颈在特征获取上。如果特征需要实时从多个数据源拉取那延迟就很难控制。我的做法是把特征分成实时特征和离线特征离线特征提前算好存到KV存储里线上直接查实时特征只保留最关键的几个比如用户最近一次点击的item ID。高吞吐方面我一般会用批量推理的方式。把同一个请求下的所有候选item组成一个batch一次性推理完而不是一个一个来。这样能充分利用CPU的向量化能力吞吐量能提升好几倍。还有一个工程上的坑特征版本管理。线上模型用的特征必须和训练时的特征完全一致包括特征的顺序、类型、缺失值处理方式。我见过因为特征顺序搞错导致线上效果暴跌的案例。所以我现在都会在模型文件里附带一份特征配置文件线上加载模型的时候同时加载特征配置确保一致性。5. 常见问题与排查技巧实录5.1 离线指标好但线上效果差这是排序模型最常见的问题没有之一。原因通常有以下几种特征穿越训练时用了未来的信息。比如你用了item的未来曝光量作为特征离线AUC会虚高但线上根本拿不到这个特征。排查方法是逐特征检查时间对齐确保每个特征的计算时间都早于样本时间。样本选择偏差训练样本只用了曝光过的item但线上需要对所有候选item排序。曝光过的item往往是经过上一版模型筛选的分布和全量候选集不一样。解决方法是在训练时加入未曝光样本或者用IPSInverse Propensity Scoring做纠偏。位置偏差用户倾向于点击排在前面的item导致前面的item正样本率天然更高。模型学到的可能是位置而不是相关性。解决方法是在训练时把位置作为一个特征但在线上推理时把位置特征置为同一个值这样模型就不会依赖位置信息。线上线下特征不一致这个最隐蔽也最致命。排查方法是做特征一致性校验把线上实时计算的特征和离线计算的特征做对比看看有没有差异。5.2 模型分数分布异常有时候你会发现模型输出的分数都集中在一个很窄的区间里比如都在0.4到0.6之间。这种情况通常是因为特征区分度不够或者模型欠拟合。如果是特征区分度不够那就要检查特征工程是不是做得太粗糙。比如用户历史点击次数这个特征如果大部分用户都是0那这个特征就没什么区分度。可以考虑做更细粒度的统计或者引入更多特征。如果是模型欠拟合那可以尝试增加模型复杂度比如增加LightGBM的树数量、增大num_leaves或者增加特征数量。还有一种可能是样本不均衡。如果正样本比例极低比如低于1%模型可能会倾向于预测一个接近先验概率的分数。解决方法是对正样本做上采样或者用focal loss之类的损失函数。5.3 线上延迟过高排序模型的延迟主要花在特征获取和模型推理上。如果延迟过高可以按以下顺序排查排查项可能原因解决方案特征获取实时特征太多或者KV存储查询慢减少实时特征数量增加缓存模型推理模型太大或者没有做批量推理模型剪枝、量化或者改批量推理网络传输特征和模型分散在不同机器上把特征和模型部署在同一台机器上并发竞争多个请求同时查询同一个特征加本地缓存或者做请求合并我自己的经验是80%的延迟问题都出在特征获取上。所以优化延迟的第一步永远是精简特征把那些重要性低、获取成本高的特征砍掉。5.4 常见问题速查表问题现象可能原因排查方法解决方案离线AUC高线上CTR低特征穿越或样本偏差检查特征时间对齐对比线上线下特征分布修正特征计算逻辑加入未曝光样本模型分数区分度低特征区分度不够或欠拟合检查特征分布看是否大部分值相同增加特征或增加模型复杂度线上延迟高特征获取慢或模型推理慢打点统计各环节耗时精简特征批量推理加缓存模型效果随时间下降数据分布漂移监控特征分布和模型分数分布定期重新训练加入时间衰减权重某些item永远排不上去特征覆盖不足检查这些item的特征是否缺失补充item侧特征或做冷启动处理5.5 我踩过的三个坑第一个坑是特征穿越。有一次我做了一个item过去7天点击率的特征离线AUC提升了3个点我特别高兴。结果上线之后效果反而下降了。排查了半天才发现我在计算这个特征的时候用的是包含样本当天的数据也就是说样本当天的点击也计入了过去7天的统计里。这导致模型在训练时看到了未来信息离线指标虚高。后来我把特征计算的时间窗口改成样本时间之前7天问题就解决了。第二个坑是样本不均衡。有一个场景正样本率只有0.5%我直接拿全量样本训练结果模型输出的分数全部集中在0.005左右完全没有区分度。后来我对正样本做了10倍上采样同时调整了LR的class_weight模型才正常起来。第三个坑是线上线下特征不一致。线上用的是Java计算特征离线用的是Python两边对缺失值的处理方式不一样——Java把缺失值填了0Python填了-1。结果线上效果比离线差了5个点。后来我统一了缺失值的处理逻辑问题才解决。这件事之后我养成了一个习惯任何特征的上线都必须做线上线下一致性校验抽样1000条样本对比两边计算出来的特征值完全一致才能上线。6. 排序模型的扩展方向与个人体会排序模型做到一定程度之后你会发现单纯的排序已经不够用了。因为排序的前提是有一个候选集而候选集的质量直接决定了排序的天花板。所以现在越来越多的团队把排序和召回、重排放在一起做端到端的优化。比如用双塔模型做召回用深度学习模型做排序再用MMR之类的算法做重排保证多样性。另一个方向是多目标排序。用户的行为是多元的有点击、有购买、有收藏、有分享单一目标的排序模型很难同时优化所有这些行为。所以现在很多团队在做多目标模型比如MMOE、PLE这些结构同时预测多个行为然后在线上根据业务需求调整不同目标的权重。还有一个方向是实时排序。传统的排序模型是离线训练、线上推理模型更新频率可能是天级甚至周级。但用户的兴趣是实时变化的所以现在有些团队在做在线学习模型能够实时吸收用户的反馈分钟级甚至秒级更新。这个方向的技术挑战很大但效果提升也很明显。我个人在实际操作中的体会是排序模型的效果提升从来不是靠单一的技术突破而是靠数据、特征、模型、工程四个方面的协同优化。你很难说哪个方面最重要因为任何一个方面拖后腿整体效果都上不去。我见过特征做得很好但工程跟不上导致延迟太高没法上线的也见过工程很牛但特征粗糙导致效果一般的。所以做排序模型一定要有全局视角不能只盯着模型结构调来调去。最后分享一个小技巧每次上线新模型之前一定要做小流量实验。不要一上来就全量哪怕你离线指标再好。因为线上环境太复杂了总会有你意想不到的问题。小流量实验能帮你把风险控制在可接受范围内而且能给你一个真实的线上效果评估。我现在的习惯是新模型先上1%流量观察一天没问题再逐步扩到5%、10%、50%最后全量。这个过程虽然慢但稳。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

Python基于LDA主题模型的电商评论情感分析实战 2026/9/28 18:24:44

Python基于LDA主题模型的电商评论情感分析实战

简介:这份资源面向Python数据分析与文本挖掘的学习者,尤其是需要完成课程设计或电商评论分析项目的学生与开发者。它围绕LDA主题模型展开,完整覆盖从爬虫源数据预处理、评论特征名词提取,到情感副词与情感词加权打分、构建特征名词…

阅读更多 →
tsm-hub:为LLM统一Tools、MCP与Skills接入的网关架构与实战 2026/9/28 18:24:43

tsm-hub:为LLM统一Tools、MCP与Skills接入的网关架构与实战

真正让我下决心写 tsm-hub,是一次差点放弃的联调经历。当时我在做一个 LLM 驱动的自动化助手,需要同时接上自研的 Tools、两个 MCP Server,还想把 Claude Code 里那套 Skills 沿用过来。每个模块的接入方式完全不一样:Tools 要走函…

阅读更多 →
Java图书销售系统毕设全解析:业务设计、技术选型与答辩准备 2026/9/28 18:24:42

Java图书销售系统毕设全解析:业务设计、技术选型与答辩准备

每年到这个时间点,总有不少同学拿着同一个问题来找我:“博主,毕设选什么题?能不能推荐一个工作量够、答辩能说清、还不至于把自己整崩溃的题目?”如果你也在为这事发愁,那“Java图书销售系统”这个方向&…

阅读更多 →
AI辅助开发实战:构建高密度PR交付的自动化工作流 2026/9/28 18:24:42

AI辅助开发实战:构建高密度PR交付的自动化工作流

最近很多人在聊 AI 编程,GrokBot 核心成员 Lauren Tan 的分享却让我停下来反复看了很久——她一个人一个月交付 2000 个 PR。这不是团队指标,不是小组产出,是落在一个人头上的数字。你可能第一反应是这个数是不是吹的。我第一反应也是。但把细…

阅读更多 →
RAG基础构建实战:为AI Agent打造可靠的知识获取管道 2026/9/28 18:24:36

RAG基础构建实战:为AI Agent打造可靠的知识获取管道

写这篇的时候,我刚从一个大模型项目的坑里爬出来。当时我们的 AI Agent 已经能流畅聊天、调用工具,但只要问到企业内部的具体制度、产品参数、历史项目细节,它就答得吞吞吐吐,甚至睁眼说瞎话。问题很明显:模型的参数记…

阅读更多 →
Kimi K3 新手快速上手与实战指南:TaoToken 统一 Key 配置与 IDE 接入 2026/9/28 18:24:29

Kimi K3 新手快速上手与实战指南:TaoToken 统一 Key 配置与 IDE 接入

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉