资讯动态

推荐系统多任务建模实践:从结构选型到Loss融合的调优指南

发布时间:2026/10/17 7:31:36 来源:尧图企业网站定制
做了几年推荐系统回头看大部分项目里最难调的不是召回、不是粗排而是把多个目标同时优化这件事做好。多任务建模Multi-Task LearningMTL在推荐系统里已经不算新鲜概念但每次换场景、换数据、换业务目标踩坑的地方都不一样。这篇内容从动机讲到 Loss 融合把我实际落地过程中的思考、方案选择、参数细节和踩过的坑一并写出来希望能给正在折腾多任务模型的朋友一些参考。1. 为什么非要多任务建模还原业务真实诉求1.1 单一目标模型的结构性缺陷很多团队一开始做推荐都是从单一目标入手的。比如信息流场景先优化 CTR电商场景先优化 CVR。单一目标模型结构简单、训练快、线上效果也好量化早期阶段完全够用。但做到一定程度会发现只优化一个目标无法支撑业务整体指标——用户点击了但没购买模型只学点击信号就会产出大量标题党内容用户购买了但退单率极高只学购买信号就会忽略后续体验。单一目标模型本质上只刻画了用户行为链中的一个环节而推荐系统面对的是完整的行为链路曝光到点击、点击到转化、转化到复购甚至包含收藏、分享、评论等互动行为。把链路拆开单独优化每一环看着都还行串起来就出问题。这就是多任务建模最原始的动机——用多个目标函数去拟合用户行为的多个环节让模型在同一个参数空间里去寻找一个多目标都相对较优的解。注意多任务建模不是简单的多个 Loss 相加这是最容易踩的误区。后面的内容会具体聊。1.2 数据稀疏是硬约束电商场景下 CTR 样本和 CVR 样本的规模差距可以相差一到两个数量级。点击是大样本转化是极小样本如果单独训练一个 CVR 模型正样本量太少模型基本学不到稳定的模式。多任务建模在这里的最大价值是参数共享——点击和转化虽然是不同目标但底层用户兴趣和商品特征的表达是共通的共享参数部分用海量的点击样本做充分训练塔层再用少量转化样本做区分能显著缓解稀疏目标学习不充分的问题。我做过一个模拟项目X训练数据里点击样本有千万级加购样本只有几十万级。单训练加购目标模型时验证集 AUC 卡在 0.62 上下怎么调特征都上不去。后来改成 CTR 和加购双目标共享底层参数加购目标的 AUC 直接涨到 0.69 以上。这里面没有引入任何新特征纯粹是借用了点击任务学到的信息。1.3 训练与服务的成本约束线上系统对推理时延有硬性要求尤其是首页推荐这类高并发场景单次请求的模型推理预算往往是几十毫秒。如果每个目标单独维护一个模型特征工程要重复做、模型文件要重复存储、推理要重复计算资源开销成倍上涨。多任务建模把多个目标打包进一个模型共享特征工程和底层计算训练成本、存储成本、推理成本都大幅降低。这也是很多团队从技术选型上偏向多任务建模的务实原因。2. 多任务模型的底层结构选型从 Shared-Bottom 到 MMoE2.1 最朴素的 Shared-Bottom 结构及其局限最经典的多任务结构是 Shared-Bottom底层若干层全连接网络由所有任务共享上层每个任务接独立的 Tower 网络输出各自的预估值。早期业界用得最多实现也最简单。但 Shared-Bottom 有个明显的短板当多个任务之间的相关性不高甚至相互冲突时共享底层会被不同的拉拽方向拉扯模型很难同时把多个任务学好。典型例子电商场景同时优化点击率和下单率时博眼球的低质商品点击可能很高但转化很差两类样本在特征空间里的分布模式差异很大强行共享底层会导致两个任务都学不深。用业内行话说叫负迁移——任务 A 的噪声干扰了任务 B 的学习。2.2 MMoE 的专家分工思路MMoEMulti-gate Mixture-of-Experts的核心改动是把共享底层拆成多个专家网络每个任务配一个单独的 Gate 网络Gate 根据当前输入样本动态地为不同专家分配权重各任务从多个专家中各取所需地组合特征表达。用生活化的类比Shared-Bottom 像是一个什么都干的全能实习生CTR 任务让他整理数据、CVR 任务也让他整理数据忙不过来就两头都做不好MMoE 像是组建了一个三人小团队一个擅长处理用户行为序列、一个擅长处理商品属性、一个擅长处理上下文信息CTR 任务通过自己的 Gate 侧重于我要序列和上下文的输出CVR 任务通过自己的 Gate 侧重于我要商品属性的输出专业分工后整体效率明显提升。实际落地 MMoE 时有两个细节值得注意专家数量一般取 4~8 个太多会导致每个专家学到的信息碎片化太少退化成带注意力的 Shared-Bottom。Gate 的输入可以不只是底层共享特征也可以结合任务相关的特征输入。比如 CTR Gate 输入融入用户实时兴趣信号CVR Gate 输入融入商品价格敏感度信号效果往往比共用同一份 Gate 输入更好。2.3 结构选型的判断依据结构不是越复杂越好。我的经验判断标准很简单任务间相关性高如点击和有效播放、浏览时长和点赞Shared-Bottom 就够用加复杂结构反而容易过拟合。任务间相关性中低或存在一定冲突如点击和转化、观看和购买MMoE 是稳妥选择。训练数据规模较小不要一上来上复杂模型。参数多了、数据喂不饱效果可能还不如朴素的 Shared-Bottom。提示做方案时先跑 Shared-Bottom 作为 Baseline再上 MMoE 做对比。一次实验就能看出复杂结构的增量到底值不值避免靠感觉做技术选型。3. Loss 融合多任务建模里最考验工程功底的一环3.1 朴素加权 Loss 的问题出在哪多任务建模最直接的 Loss 融合方式就是线性加权L w1 * L1 w2 * L2。很多初学者会直接 w1、w2 都设为 1.0 开跑这时很容易出现一个现象Loss 大的任务主导梯度更新Loss 小的任务学不到东西。因为不同任务的 Loss 量纲天然不同——CTR 用 LogLoss 可能是 0.3 量级CVR 用 MSE 可能是 0.01 量级——两个 Loss 直接相加CTR 任务对梯度的贡献就会碾压 CVR。如果任务里还混着分类损失和回归损失量纲差异会更夸张。分类任务 LogLoss 在 0.1~1 之间浮动回归任务 MSE 可能是几十甚至上百简单加权会让模型把几乎全部精力放在最小化回归 loss 上分类任务的效果就崩了。解决方案之一是对每个任务的 Loss 做归一化。常见做法每个任务用验证集上的 Loss 初始值作为缩放基准把 Loss 除以其基准值后再加权。这样相当于把所有任务拉到目标值都是 1 附近的量纲上再调权重就有意义。3.2 不确定性加权一种更优雅的自适应方案固定权重的问题在于——权重需要人工调参且不同训练阶段、不同数据分布下任务的最优权重也在变化。业界常用的更优雅方案是不确定性加权Uncertainty Weighting核心思路是让模型在训练过程中学习每个任务的不确定性不确定大的任务自动降低权重。数学上对分类任务和回归任务的处理稍有差别。对回归任务假设每个任务的不确定性为 sigma则加权 Loss 可以写成L_total Σ (1 / (2 * sigma^2)) * L_task Σ log(sigma)。其中 sigma 是网络可学习参数1 / (2 * sigma^2)是自适应权重log(sigma)是正则项防止 sigma 无限增大。对分类任务形式略有不同需要将预测分布代入交叉熵再推导权重项实践上通常直接对分类 loss 乘以1 / sigma^2也能得到接近的效果。这个方案我听某位做过多任务调优的同行讲过一个很形象的例子团队里有两个实习生一个水平高但输出波动小另一个水平低但输出波动大。固定权重相当于给两人派固定比例的活水平高的也被拖累。不确定加权则像每星期根据两人近期的表现动态分配任务量——输出稳定的人承担更多关键任务输出波动大的人先干边角料。推荐系统多任务场景就是典型的不确定度各有差异的环境这种方法往往能让弱任务的指标被明显拉起来。3.3 动态权重调整与梯度平衡策略除了不确定性加权还有一类方法是基于梯度方向的动态调权。核心观察是多任务冲突的本质是梯度冲突——任务 A 的参数更新方向对任务 B 有害。常见策略有两种GradNorm计算每个任务梯度范数与平均梯度范数的比值按比值对 Loss 权重做动态缩放让各任务的梯度范数量级保持一致。实现上需要额外小二层网络去拟合梯度范数目标逻辑稍复杂但效果稳定。PCGrad在梯度更新时检测任务间梯度方向是否冲突如果冲突则把其中一个任务的梯度往另一个任务梯度的正交方向投影消除相互干扰。PCGrad 实现比 GradNorm 简单只需修改训练循环里的梯度计算逻辑对已有模型结构没有侵入。我在模拟项目X上对比过固定加权、不确定加权、PCGrad 三种方案点击任务基本持平稀疏转化任务的 AUC 分别提升了 0.011、0.019、0.023。PCGrad 略优但工程上也多了一些复杂度。3.4 Loss 融合的实战参数建议综合多个项目经验推荐几组经过验证的参数设定方案适用场景优点缺点固定权重Loss 归一化后任务量纲差异小实现最简单线上稳定可控需要人工调参不确定加权任务量纲差异大自动学权重省人工需要增加 sigma 参数初期收敛可能不稳GradNorm梯度量级差异明显梯度级自适应较精准实现较复杂多一层计算PCGrad任务冲突明显方向级纠偏适合跷跷板敏感场景训练耗时略增实践中我的优先顺序是先做 Loss 归一化 固定加权跑通整个链路如果发现弱任务指标不够第二步换不确定加权或 GradNorm如果出现明显跷跷板一个任务好另一个就掉再上 PCGrad。不要一开始就上最复杂的方案调起来会很费劲。4. 实操过程多任务推荐模型的完整落地路径4.1 样本构建与特征选择的注意点多任务模型的样本构建比单任务要复杂不少。首先要明确不同任务的正负样本定义。比如点击任务正样本是曝光且点击负样本是曝光未点击转化任务正样本是点击且转化负样本不能直接用曝光未转化——因为从曝光到转化中间隔着点击没点击的样本根本没有进入转化候选池直接当负样本会引入严重的样本选择偏差。我通常会为转化任务构建样本时做两层处理基础样本沿用曝光未点击作为负样本用点击预测分数做样本加权让未被点击的样本权重降低。训练转化塔时只用有点击行为的样本集合即点击正样本 点击后未转化的样本负样本保证正负样本的语义一致性。特征选择上共享底层特征要选任务间共性强的特征比如用户基础画像、商品类目、上下文时间每个任务 Tower 可以额外用一些任务特有特征比如转化塔用价格敏感度、用户历史累计消费金额点击塔用标题内容特征、视觉特征。4.2 一个可直接跑的 Loss 融合代码骨架这里给一个基于 TensorFlow / Keras 风格的多任务模型训练核心代码骨架结构是 MMoE 两个任务 Towerimport tensorflow as tf from tensorflow.keras import layers def mmoe_multi_task_model(inputs, num_experts4, tasks[ctr, cvr]): # 底层共享输入 shared_input layers.Flatten()(inputs) # 构建专家网络 experts [] for i in range(num_experts): expert layers.Dense(64, activationrelu)(shared_input) expert layers.Dense(32, activationrelu)(expert) experts.append(expert) experts_stack tf.stack(experts, axis1) # (batch, num_experts, expert_dim) # 每个任务一个 Gate task_outputs {} for task in tasks: gate_input shared_input gate_weights layers.Dense(num_experts, activationsoftmax)(gate_input) gate_weights tf.expand_dims(gate_weights, axis-1) # (batch, num_experts, 1) # 加权聚合专家输出 task_embedding tf.reduce_sum(experts_stack * gate_weights, axis1) tower_out layers.Dense(32, activationrelu)(task_embedding) tower_out layers.Dense(1, activationsigmoid, nametask)(tower_out) task_outputs[task] tower_out return task_outputs少量说明专家网络和 Gate 的实现都写出来了实际项目里 Gate 输入和专家输入可以做成不同分支上述代码用的是同一输入方便理解。更复杂的做法是 Gate 单独接特征效果通常会更好但代码结构会复杂一些。训练时的 Loss 融合部分import tensorflow as tf # 假设 output[ctr] 和 output[cvr] 是两个 sigmoid 输出 bce tf.keras.losses.BinaryCrossentropy() def custom_train_step(model, inputs, labels, log_varsNone): with tf.GradientTape() as tape: preds model(inputs, trainingTrue) loss_ctr bce(labels[ctr], preds[ctr]) loss_cvr bce(labels[cvr], preds[cvr]) if log_vars is None: # 固定权重方案 total_loss loss_ctr 0.8 * loss_cvr else: # 不确定性加权方案 precision_ctr tf.exp(-log_vars[ctr]) precision_cvr tf.exp(-log_vars[cvr]) total_loss ( precision_ctr * loss_ctr 0.5 * log_vars[ctr] precision_cvr * loss_cvr 0.5 * log_vars[cvr] ) grads tape.gradient(total_loss, model.trainable_variables) optimizer.apply_gradients(zip(grads, model.trainable_variables)) return total_loss不确定性加权里的 log_vars 是每个任务的可学习参数log(sigma^2) 形式训练时需要把 log_vars 加入梯度更新集合。上面的代码已经体现了核心思路实际项目里还要注意 GPU 上的数值稳定性log_vars 初始化不要太大否则前期权重过小任务学不动。4.3 评估体系与线上监控的落地方法离线评估多任务模型不能只盯着单个任务的 AUC。我的经验是搭建组合评估指标点击任务看 AUC GAUC转化任务看 AUC 校准度预测均值 / 真实均值。除此之外必须做一次线上 AB 实验观察整体业务指标比如人均点击次数、点击转化率、人均 GMV。有些多任务模型离线 AUC 都有提升但线上人均 GMV 反而掉了——原因是模型优化的目标权重和业务核心指标匹配度不高。所以我在方案设计阶段会先明确一个业务北极星指标让 Loss 融合权重的最终确定以北极星指标的线上结果为准。固定权重也好、自动学习权重也好最终都要回到业务导通率上来验证。线上监控方面多任务模型需要额外关注各任务的预测分布漂移。我给每个任务都配置了监控预测均值、预测方差、正样本率如果某个任务在线上连续几个小时的预测均值与训练集统计值偏差超过阈值大概率是特征分布变化或者数据管线出了断层需要及时告警。多任务模型参数共享一个任务的特征出问题可能连带影响其他任务的效果监控务必分任务独立。4.4 特征对齐与样本权重的细节处理多任务模型里不同任务往往需要不同时间长度的特征窗口。比如点击任务用最近 7 天的行为序列就够复购任务可能需要最近 90 天的历史订单数据。在特征对齐时要按每个任务单独切分特征时间窗口同时保证共享底层输入的时间一致性。有些团队图省事直接一套特征喂进去最后发现点击和转化的最优特征窗口天然不同共享底层被这个矛盾拉扯整体效果受损。样本权重上一个实用技巧是给不同任务的正样本分别设权重缓解正负样本不平衡。比如 CVR 任务正样本过少时可以给正样本乘一个 5~10 的权重让梯度更新更关注正样本的学习。这个操作在单任务里也需要做多任务中尤其重要因为共享底层的梯度来自两个任务正样本权重不足的任务在共享参数上几乎没有发言权。5. 实战中的问题排查与调试实录5.1 跷跷板现象点击涨转化跌问题出在哪训练多任务模型最常遇到的就是跷跷板现象CTR 指标稳步上升CVR 指标却纹丝不动甚至下跌。排查路径我一般按以下顺序检查任务 Loss 量纲是否做了归一化。没做归一化先补上很多时候跷跷板就是 Loss 量纲差异导致的量纲统一后自然缓解。检查两个任务的梯度更新频率。把训练日志里各任务的 Loss 曲线单独画出来看是否有一个任务的 Loss 在训练中后期基本不再下降说明该任务被另一个任务压制了。此时引入不确定性加权弱任务的权重会自动提升。检查数据分布的冲突程度。把点击正样本和转化正样本在共享特征上的分布做可视化对比如果明显分叉说明共享底层难以同时表达两类模式。此时考虑把冲突大的特征从共享底层中拆到任务 Tower 侧。检查 Gate 的权重输出。MMoE 场景下如果某个任务的 Gate 输出几乎集中在一个专家上说明专家分化不够适当增加专家数量或加大专家间的结构差异。5.2 弱任务训练不动的常见原因任务 Loss 一直不降排除数据问题后常见原因有几个共享底层容量不足。底层网络层数或宽度不够两个任务抢特征表达先到先得弱任务分不到容量。解决方式加宽共享层或者换更强的结构如 CGC、PLE 这类带任务独立专家分区的结构。学习率不合适。多任务模型对学习率比单任务更敏感。不同任务的最优学习率可能差一个量级统一学习率会让快任务收敛过快、慢任务跟不上。实践中可以用分层学习率——共享底层用较大学习率任务 Tower 用小一点的学习率。任务 Tower 过深。Tower 层数太多时弱任务的梯度在回传时被大量稀释。我踩过这个坑Tower 从三层削到一层弱任务 AUC 反而涨了几个点。5.3 多任务效果波动的归因方法论做多任务实验时效果波动比单任务更大归因也更难。双目标模型改一处特征两个任务的指标可能出现交叉变化。我的归因方法是每次实验只做一个变量变更。同时记录各任务的离线指标和样本级预测分布变化不只记 AUC。实验周期拉长观察至少 3~5 天的线上指标避免短期波动误判。如果出现波动先确认是否是数据管线侧的影响样本日志断流、特征覆盖下降都可能导致再确认是不是模型本身的问题。5.4 多任务建模避坑速查表常见问题典型表现推荐排查动作Loss 量纲不一致一个任务 Loss 始终占主导各 Loss 除以验证集初始值归一化负迁移共享参数被强任务带偏弱任务指标下跌加任务独立参数区或改用 PLE 结构梯度过量冲突两个任务来回拉扯整体指标停滞尝试 PCGrad 梯度方向纠偏Tower 容量过大弱任务梯度被稀释训练不动减小 Tower 层数或维度Gate 收敛退化MMoE 各专家权重几乎一样增加专家差异性检查 Gate 初始化数据样本偏差转化任务负样本混入未点击重造转化任务样本空间过滤未点击样本线上分布漂移任务指标集体下滑分任务监控预测均值与特征分布多任务建模做到最后我的体会是模型结构决定上限Loss 融合决定能不能摸到上限数据质量决定上限本身是低还是高。结构从 Shared-Bottom 到 MMoE 再到更复杂的 PLE每一层复杂度都意味着更多的调试成本推荐的做法是两个任务先 Shared-Bottom 跑通然后逐个加复杂度每一步都有明确收益再往上走别为了技术炫技牺牲稳定性和排查效率。另外还有一个容易被忽视的点多任务模型训练完成后一定要做一次任务间相关性的事后分析计算各专家在两类样本上的激活差异以及各任务在共享层上的梯度分布相似度。这些分析不仅可以验证当前结构是否合理也是后续如果要切换 Loss 融合策略时的决策依据。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑