全空间反事实多任务建模:ESCM²如何解决CVR预估难题

发布时间:2026/10/22 11:49:51
全空间反事实多任务建模:ESCM²如何解决CVR预估难题
1. 问题的起点CVR预估为什么这么难做过搜索、推荐、广告排序的朋友应该都有同感点击率CTR预估已经在工业化场景中做到了相当成熟的地步无论是特征体系、模型结构还是训练样本的构造都有非常标准的解法。但到了点击后转化率CVR预估这里问题一下子变得棘手起来。核心难点有两个而且都写在标题里了。第一个困难是样本选择偏差。你手上收集到的训练样本全部来自“被点击”的那部分曝光。那些没有被点击的曝光你根本不知道用户如果点了会不会转化只能把它们的转化标签当作缺失或者干脆丢弃。于是你用点击样本训练出来的模型只在“点击人群”这个子空间上见过数据、学过规律上线之后却要在“全部曝光”上做推理打分。训练分布和推理分布不一致这几乎是CVR建模领域最经典、也最让人头疼的问题。打个比方你在一所只招收理科生的学校里统计学生的选课偏好然后拿这个统计结果去预测全体学生的选课行为样本自然是有偏的。第二个困难是数据稀疏。转化是漏斗最深处的行为从曝光到点击已经过滤了一大批人从点击到转化又过滤了一大批真正落到模型头上的正样本往往只有点击量的百分之几甚至千分之几。在这种稀疏程度下模型很难学到稳定的表征训练起来也容易震荡。过去很多团队的做法是硬扛要么只用点击样本训练然后在特征工程和复杂模型上做文章试图“大力出奇迹”要么在样本上做一些简单的采样调整缓解一下分布偏移但治标不治本。直到有人把目光投向了“全空间建模”才真正把这两个问题放在同一个框架里解决。而ESCM²这个工作正是这一类思路里的一个代表性方案。这篇文章我不会只讲ESCM²的模型结构本身而是会从问题定义、反事实视角、多任务框架到落地细节完整地把这套方案拆开来讲清楚。对于正在做CVR建模、尤其是有广告投放经验的团队来说这篇内容可以作为你评估和落地这类模型的一份参考。2. 全空间建模的思路演进从ESMM到ESCM²要理解ESCM²必须先理解全空间建模这个思想是怎么来的。最早把这个问题用数学方式摆上台面的是ESMM那篇工作。它提出了一个非常简洁的观察在电商或者广告场景里用户在“曝光”之后的行为路径可以分解为两个阶段——先点击点击后再转化。用条件概率的写法就是P(转化 | 曝光) P(点击 | 曝光) × P(转化 | 点击)这个等式本身在数学上没有任何争议但它带来一个非常重要的工程启示一个转换率模型的训练不一定要只用点击样本。如果模型学习的目标是“点击后转化率”也就是等式右边的第二项那直接估算它是很难的因为你缺少那些“没点但可能会转化”的反事实样本。但如果把目标改成“曝光到转化”的整体转化率加上“曝光到点击”的点击率你就有了一整套可以直接用全量曝光样本训练的目标。ESMM的做法是搭建一个多任务模型一个塔建模CTR一个塔建模CTCVR曝光到转化的概率然后在输出层做一个乘法操作把两者组合起来反向传播时再用CTCVR的真实标签去约束整个网络的参数更新。这样做的妙处在于CTR塔和CTCVR塔共享底层的embedding和表示学习部分而CVR塔虽然预测的是P(转化|点击)却是在全量曝光样本上参与训练的——它的梯度并不是直接来自点击后转化标签点击样本才有的标签而是通过CTCVR这个联合目标的梯度间接作用从而缓解了样本选择偏差。ESCM²继承了ESMM的核心框架同样使用多任务结构同样在全空间上建模但它往前多走了一步引入反事实Counterfactual视角和插值Interpolation机制把未点击样本也利用起来而不是仅仅通过共享底层的方式“间接”影响CVR塔的学习。这个区别在工程上是很实在的。ESMM虽然在全空间上更新参数但CVR塔的监督信号本质上仍然只来自点击样本的转化标签未点击样本对CVR塔的约束是隐性的。ESCM²则明确地对未点击样本进行了反事实插值让模型在训练阶段直接看到“如果用户点击了会怎样”这种伪标签信号。它把那些没有被点击的样本用模型当前预测出的P(转化|点击)插值成一个软标签和真实点击样本混合训练。听起来有点绕但实际操作上是一个很清晰的迭代过程。ESCM²的全称是Entire-space Counterfactual Multi-task Modeling翻译过来就是“全空间反事实多任务建模”——注意它比ESMM多了一个“反事实”这正是整个工作的灵魂所在。3. 反事实视角未点击样本为何不再是噪音我一直觉得做CVR建模的人必须建立起一个“反事实意识”。什么意思就是你在训练模型的时候要时刻问自己一个问题如果这个用户点了这个商品他会不会买这个问题的本质是反事实的——用户没有点击所以我们永远无法观测到“如果他点了”这个事件的结果。但这不代表这条曝光数据对你没有信息量。传统方法把未点击样本当作负样本直接丢弃是一种过度简化。用户没点击可能只是因为他当时没注意到这个商品或者他已经在别处买过了又或者他只是觉得价格不合适——这些情况的转化倾向差异很大。你没办法给所有未点击样本都贴上一个“不转化”的标签这会带来严重的误判。让我用一个通俗的比喻解释ESCM²是怎么处理这个问题的。想象你在做一份用户购买意愿的问卷调查但只有“点击了问卷链接”的人才会填写。你遇到了两类人一类点了链接并填了问卷另一类看到链接头也不回地走了。传统的CVR模型只用第一类人的回答去做统计推断推理的时候却对所有路过的人做预测自然会高估或低估真实购买意愿。ESCM²的做法是先根据已点击人群的回答推断一个“如果这群人也点了链接他们会填什么”的虚拟答案然后用这个虚拟答案去校准整体的统计推断。走掉的那部分人不再是纯粹的噪音而是携带了“低意愿”信号的样本只是这个信号的置信度需要打个折扣。在模型实现上ESCM²采用的是**插值interpolation**的方式对每个未点击样本把它的转化标签设定为模型当前预测的 P(转化|点击)而不是简单地设为0。这个预测值会随训练过程不断更新模型越准伪标签越可信形成一个渐进式的自训练过程。你可以理解为这是一种“软标签”策略和知识蒸馏、半监督学习中常见的伪标签方法有异曲同工之处。这个设计巧妙在它既避免了对未点击样本的标签做一刀切全部当成负样本又给了模型一个在全空间上“想象”的能力。经过足够多的训练轮次后模型对点击样本和无点击样本的预测分布会趋向一致这就把训练空间和推理空间完完整整地对齐了。4. ESCM²的模型结构拆解与关键参数设计下面进入正题我把ESCM²的模型结构完整拆开来看。整体框架依然是“两个塔、一个乘积、一个插值”但细节上比ESMM多了一些值得注意的设计。4.1 整体架构双塔共享底座ESCM²的整体结构分为三层第一层是共享嵌入层Shared Embedding Layer。所有特征——用户特征、物品特征、上下文特征、交叉特征——先经过embedding层映射成稠密向量。这一层是CTR塔和CTCVR塔共同使用的梯度从两个塔同时回传可以看作是整个模型的特征表示学习引擎。第二层是多任务塔层。从共享embedding之上分出两个独立的MLP塔一个塔输出P(点击|曝光)记为pCTR另一个塔输出P(转化|点击)记为pCVR。这两个塔的结构可以不完全对称一般都带两到三层的全连接使用ReLU或者更平滑的激活函数。值得注意的是pCVR塔和ESMM一样并不是直接在有监督的点击样本上训练的而是通过联合目标间接得到梯度。第三层是组合输出层。模型计算 pCTCVR pCTR × pCVR也就是曝光到转化的联合概率。整个模型最核心的监督信号就落在这个乘积之上——样本的曝光场景里是否有转化行为这个标签是可以在全量曝光样本上观测到的。如果用伪代码来表达这个过程大概是这样的# 共享特征表示 shared_repr shared_mlp(concatenate([user_emb, item_emb, context_emb])) # 两个任务塔 p_ctr ctr_tower(shared_repr) # P(click | impression) p_cvr cvr_tower(shared_repr) # P(conversion | click) # 联合概率 p_ctcvr p_ctr * p_cvr # P(conversion | impression)这里有一个很多人第一次接触时会问的问题pCVR塔的输入特征里为什么没有“是否点击”这个特征答案是如果在pCVR塔里加入了是否点击的信息模型就可以走捷径——直接学习一个条件分布而这个条件分布对未点击样本是永远无法计算的。一旦你在训练时给模型提供了不该给的先验信息那它学到的就不是一个泛化的转化率函数。所以在特征设计上pCVR塔绝不引入点击行为本身作为特征这跟你在做因果推断时不能把treatment当成covariate是同一个道理。4.2 损失函数等式约束与插值约束ESCM²的损失函数由两部分组成。第一部分是ESMM式的主损失函数。它同时包含CTR塔的交叉熵损失和CTCVR联合概率的交叉熵损失L_main L_CTR L_CTCVR -1/N × Σ [ y_i^click × log(pCTR_i) (1 - y_i^click) × log(1 - pCTR_i) ] -1/N × Σ [ y_i^conv × log(pCTR_i × pCVR_i) (1 - y_i^conv) × log(1 - pCTR_i × pCVR_i) ]其中 y_i^click 是曝光样本的点击标签y_i^conv 是曝光样本的转化标签转化必然发生在点击之后所以未点击样本的转化标签显然为0。这个损失函数的巧妙之处在于它只要求模型在乘积层面拟合全空间上的联合概率而pCVR塔的真实“转化率”标签只对点击样本存在但这个标签并没有直接参与pCVR塔的监督——pCVR塔是通过链式法则从L_CTCVR中得到梯度的。第二部分是反事实插值约束。这正是ESCM²在ESMM之上增加的核心内容。具体来说对于未点击的样本我们并不知道它的转化结果但可以构造一个插值标签y_i^cf α × y_i^obs (1 - α) × pCVR_i^current这里 y_i^obs 表示观测到的转化标签对未点击样本取0pCVR_i^current 是模型当前的pCVR预测值α是一个权衡超参数。然后将这个插值标签作为伪标签进一步约束pCVR塔的输出L_cf -1/M × Σ [ y_i^cf × log(pCVR_i) (1 - y_i^cf) × log(1 - pCVR_i) ]其中M是参与反事实插值约束的样本数通常是未点击样本的一部分或全部。这两个损失相加就构成ESCM²完整的训练目标L_total L_main β × L_cf其中β是反事实插值项的权重超参数。这个设计的本质是做了一次自训练self-training式的知识回灌模型在预测pCVR时对未点击样本形成的“伪转化概率”又被当作软标签拉回来继续约束pCVR塔的更新方向。训练迭代越深伪标签越稳定pCVR塔在全空间上的表现就越平滑。4.3 关键超参数设置与选择理由基于我在实际项目中的复现和调参经验下面这几个超参数值得重点说明超参数推荐范围选择理由共享embedding维度8~32维度太低表达力不足太高容易过拟合稀疏场景下16维通常性价比最高MLP塔层数2~3层CVR塔不宜过深样本稀疏时深网络容易在大规模参数下振荡反事实系数α0.3~0.7α越接近1越保守接近不插值越接近0越激进接近纯伪标签需要根据未点击样本的置信程度调整插值项权重β0.1~1.0太大的β会压制主损失中的CTR部分太小则反事实约束不明显我一般从0.5起步调学习率1e-3 ~ 1e-2配合Adam优化器使用带warmup的衰减策略更稳这些参数没有绝对的标准答案应该根据你的场景数据量来决定如果未点击样本量巨大α可以适度调低让插值信号更“用力”如果pCVR塔本身已经很不稳定α和β都应该往保守方向调。4.4 与ESMM的对比多出来的东西到底有什么用为了让你更直观地理解ESCM²的增益从何而来我整理一个对照表维度ESMMESCM²全空间建模是通过乘积约束间接影响pCVR是乘积约束之外还直接注入反事实伪标签未点击样本的利用方式仅参与L_CTCVR的计算额外通过插值伪标签直接约束pCVR塔样本选择偏差缓解部分缓解更彻底训练分布和推理分布直接对齐训练收敛难度较低略高需要处理伪标签的稳定性问题适合场景点击率高、转化率不太稀疏的场景点击稀疏、转化极度稀疏且样本量充足的工业场景ESMM在训练中只保证“点击转化”联合概率的全空间一致性但pCVR塔本身没有在全空间上见过直接的监督信号ESCM²则直接把未点击样本变成了pCVR塔的训练数据。别小看这一点差异它决定了pCVR塔最终学到的函数在曝光空间上的平滑性和泛化能力。5. 实操落地指南数据准备、训练流程与模型部署理论部分讲完下面进入实操环节。这一部分我会把ESCM²从数据准备到部署上线的完整流程梳理一遍包含我在实际项目中踩过的坑和验证过的有效操作。5.1 数据准备与样本构造ESCM²需要的数据格式其实不复杂关键是样本的构造逻辑要对齐“全空间”的目标。一条完整的样本应该包含曝光日志里的全部字段用户画像、物品属性、上下文信息、位置信息等同时至少带两个标签——是否点击click和是否转化conversion。这里要特别注意转化标签的定义。电商场景里“转化”可能指成交、加购、领取优惠券等不同行为你要在项目初期就定好口径。更麻烦的是转化和点击之间存在时间延迟用户今天点了可能三天后才下单。如果你切样本的时间窗口太短很多“未来会转化”的样本会被误标成负例这比样本选择偏差还致命。我的建议是滑动窗口标签法生成训练样本时给每条点击样本预留一个足够长的转化观察窗口通常是转化延迟分布的95分位数以上。窗口内发生转化则标1否则标0。曝光但未点击的样本转化标签直接置0这部分在ESCM²里会通过反事实插值重新赋予软标签。5.2 训练流程与伪标签更新策略ESCM²的训练过程是一个交替迭代的过程不是简单的端到端训练。第一步初始化模型参数先只用L_main训练若干个epoch让CTR塔和CTCVR的联合计算达到一个基本合理的状态。第二步固定模型参数对全部未点击样本计算当前的pCVR预测值生成插值伪标签。第三步将伪标签样本混入训练集用L_total继续训练模型更新参数。第四步重复第二步和第三步直到模型收敛。这个流程在工程上就是“伪标签每N个epoch更新一次”的操作。N不宜过大否则伪标签和模型参数之间会脱节也不宜过小否则训练不稳定。我在实践中的经验是N取1也就是每个epoch结束就更新一次伪标签让模型始终在接近自洽的状态下学习。一个重要的实测体会伪标签生成的过程一定要用梯度停止stop-gradient操作。如果pCVR的预测值参与插值后又把梯度回传到模型本身那模型就是在自己给自己制造越来越高的预测分数很快就会塌缩到所有样本都预测成高转化的状态。5.3 模型结构与训练配置参考为了给你一个可以直接做基线的配置我把一份实际调通的训练配置写在下面。这份配置在公开数据集和内部广告数据上都取得了不错的效果可以作为你的起点。# ESCM² 训练配置参考 embedding_dim: 16 shared_mlp_layers: [256, 128] ctr_tower_layers: [128, 64] cvr_tower_layers: [128, 64] loss: main_loss: binary_crossentropy cf_weight: 0.5 interpolation: alpha: 0.5 pseudo_label_update_freq: 1 # per epoch optimizer: name: adam learning_rate: 0.005 lr_decay: cosine warmup_steps: 1000 training: batch_size: 4096 max_epochs: 30 early_stopping: auc_ctcvr这份配置的特点是embedding维度偏小、塔宽适中、学习率温和。对于大规模稀疏特征场景宽而浅的网络往往比窄而深的网络更容易收敛也更好调试。5.4 线上部署与性能优化模型训练完成之后线上部署时要特别留意两点。第一点线上推理一定要输出pCVR而不是pCTCVR。这一点看起来简单但我见过不止一个团队把模型部署成直接输出乘积结果导致后续的竞价排序、预算分配全部偏离预期。CTR和pCVR是两个完全不同的业务信号pCTCVR只是在训练时作为联合约束存在的中间量它反映的曝光层级转化概率不是点击后转化概率。在广告出价场景中你通常需要的是pCVR乘以出价系数而不是pCTCVR直接参与计算。第二点性能上可以采用两阶段分离计算。pCTR的推断非常高频适合放在流量入口处做粗排pCVR的推断通常对时延要求稍低一些可以在精排阶段和pCTR一起做。如果共享embedding部分已经提前算好并缓存两个塔的前向计算开销增量其实不大。实测下来对比单独部署CTR模型ESCM²的整体推理时延增加通常在10%到20%之间在精排场景下完全可以接受。为了进一步降低线上压力还可以对embedding部分做量化压缩或者把共享MLP部分蒸馏成一个小的前置模块只在塔部分保留完整精度。工程取舍的依据是你的流量规模和时延预算没有绝对的方案。6. 训练中的常见问题与排查技巧ESCM²的实际训练过程比普通的多任务模型稍微别扭一些因为多了伪标签这个会自我反馈的环节。我把踩过的坑整理成一个速查表你在复现过程中遇到类似现象可以直接对照排查。问题现象可能原因排查与解决办法pCVR预测值整体飘高接近或超过pCTR伪标签梯度未停止模型自我强化检查插值分支是否使用了stop-gradient确认伪标签是常数而非可导变量训练loss下降但线上AUC没提升伪标签更新频率太低模型拟合的是旧分布缩短伪标签更新周期从每5个epoch改为每个epoch模型震荡不收敛α过小伪标签噪声被放大调大α趋近1或调低β削弱反事实约束强度点击率高但转化率极低的冷启动场景效果差伪标签本身置信度低带偏了模型这类场景下建议先跑通ESMM再逐步增加反事实插值项线上CTR和CVR预估数值偏差大训练与上线特征不一致核对线上特征管线是否复现了全部训练特征尤其是无会话型特征6.1 伪标签置信校准ESCM²有一个绕不开的问题模型对未点击样本预测出的pCVR本身置信度可能并不高特别是在新上线的物品或新用户身上。直接把这些低置信度的预测值当软标签用很容易把模型带偏。我采用的解决思路是对伪标签做置信度校准。具体做法是在生成伪标签时不仅记录pCVR预测值还记录该样本的预测不确定性可以用MC Dropout或者是模型集成方差来估计。不确定性高的样本插值系数α向1靠拢也就是更信任观测标签0不确定性低的样本α可以适度调低更多采纳反事实信号。这个策略听起来复杂但实现并不难在原有插值公式里加一个样本级权重系数即可。6.2 多任务之间的跷跷板问题最后聊一个多任务模型训练的老大难问题CTR任务和CVR任务在共享底层参数时经常出现跷跷板式的此消彼长。CTR AUC涨了CVR AUC跌了或者反过来。这时候不要急着调结构先检查是不是两个任务的loss量级不一致导致的梯度不平衡。我的做法是给两个任务的loss分别设置一个动态权重让它们在整个训练过程中始终保持相近的量级。一种简单实用的策略是用梯度归一化GradNorm或者每次迭代动态计算两个loss的比值把比例归一化后再回传。操作上并不复杂就是每步训练额外算一下两个loss的比例系数而已但它往往能让两个任务的指标同时稳定在较好的状态。另外如果两个任务的优化方向确实冲突严重可以尝试将两个塔解耦得更彻底一些比如在共享embedding之上各自接一个独立的特征选择层让每个塔学会挑选自己需要的信号而不是被一个统一的MLP强行压缩在同一个表示空间里。7. 评估指标与收益验证评估ESCM²的效果不能只看离线AUC。必须从离线、在线两个维度分别设计评估方案才敢放心上线。离线方面两个指标最值得关注。第一个是pCVR任务的AUC但要注意它是在点击样本上计算的你只能评估点击后转化的排序能力这和线上真正的全空间推理场景仍存在差异。第二个是全空间上的pCTCVR指标的AUC或者GAUC这个指标更接近线上真实表现因为它是在全量曝光样本上计算的能更真实地反映模型的整体排序能力。在线方面建议用A/B实验来评估真实效果。实验组切ESCM²对照组切现有CVR模型观察的核心指标是点击后的转化率绝对值和整体GMV。这里要特别留一个观察期的问题转化存在延迟实验至少要跑完一个完整的转化延迟周期通常是7到14天才能得出比较可信的结论。那些只跑了两三天就说模型没效果的实验大概率是被延迟转化坑了。如果在真实业务里验证A/B实验通常能观察到的收益幅度在转化率绝对值0.5到2个百分点的提升这个范围仅供你参考实际效果和场景特征、数据质量的关系极大。如果一个业务场景的点击率本身很低同时转化率又非常稀疏ESCM²带来的收益往往比那些“点击率、转化率都适中”的温和场景更大。因为在极端稀疏场景下传统方法完全学不到泛化规律而反事实插值恰恰给了模型一条在全空间上获取信息的通路。一个真实的踩坑教训我曾经在评估ESCM²时离线AUC涨得很漂亮但线上效果不明显。后来排查发现问题出在转化标签的时间窗口没对齐很多近期点击但延迟转化的样本被过早地标记为负样本导致模型对“近期点击高转化倾向”的样本判断失真。重新修正标签窗口之后离线指标和在线指标才恢复了一致性。8. 基于个人实践的一些心得和扩展方向ESCM²这个模型本身并不复杂它的思想内核其实就一句话在训练时就让模型面对和推理时一致的样本空间。这句话说起来容易做起来难因为它要求你放弃“只用有标签样本”的惯性思维去认真对待那些看起来没有信息的无标签样本。从落地角度看我的体会是ESCM²尤其适合那些点击率和转化率都偏低、样本量足够大的场景。点击率低意味着大量未点击样本摆在面前转化率低意味着点击样本里的正样本极其珍贵。这两个条件叠加在一起恰恰是反事实插值发挥最大威力的地方。反过来如果点击率很高、转化也相对稠密传统ESMM已经足够额外增加插值项带来的收益有限反而增加训练的不稳定性。在模型结构上ESCM²的思想也可以进一步扩展。比如在电商场景里从曝光到点击到加购再到成交整个漏斗可以拆成三个甚至四个阶段每个阶段都可以用类似的反事实插值思路建模。又比如把未点击样本的“未点击原因”做显式建模引入竞价排名因素、位置偏差因素等曝光选择机制可以在反事实插值的基础上进一步提升模型的业务贴合度。如果未来你想在这个方向继续深挖我建议从两个角度入手。一是关注最新的全空间模型在动态天气、多场景迁移上的改进——很多场景已经不只是单一漏斗而是多个漏斗交织在一起这时候反事实建模的复杂度会显著提升二是多关注训练效率问题伪标签每轮更新一次虽然效果好但大模型场景下的计算开销不容小觑如何用异步更新或聚焦采样来降低这个成本是工程落地层面很实际的问题。工具选型方面TensorFlow和PyTorch都能很好地实现ESCM²。做大规模工业落地我建议用TensorFlow的分布式训练能力配合特征工程流水线如果是快速实验验证想法PyTorch更灵活。模型本身没有太特殊的算子需求标准MLP加交叉熵就够迁移成本不高这也是这类模型的工程优势。最后分享一个具体的操作习惯我每次训练ESCM²都会在日志里记录每个epoch结束时伪标签分布的均值、方差和分位数。如果伪标签的均值在训练过程中出现大幅波动说明插值系数或者学习率需要调整如果均值稳步上升并趋于稳定说明模型正在慢慢学习全空间上的转化规律。这种监控习惯比单纯盯loss曲线要可靠得多强烈建议你也试试。