
做联盟营销算法的人应该都有这种体验一个推广者突然在群里拉起一条分享链前两个小时数据平平第三个小时销量像坐了火箭一样往上蹿你正想追加预算它又掉头向下最后结算ROI跟预估差了十万八千里。传播规模预测要解决的就是这种高度波动的流量里怎么提前判断“这条推广链路最终能带来多少量”并据此做预算调整、佣金系数设置和风险防控。这次在CIKM 2025上组发的工作背景是联盟营销场景核心是把传播链路建模成一个时空动态网络用两阶段的方式预测最终规模。这篇文章主要复盘三个问题这个任务为什么难、两阶段设计背后的考虑是什么、以及从离线实验到生产落地的过程中有哪些值得警惕的坑。1. 联盟营销场景的痛点与时空动态网络为什么适用1.1 传播规模预测在联盟营销里到底解决什么问题先把这个场景说清楚。联盟营销一般涉及三方商家出佣金联盟推广者拿分成用户通过推广链接完成浏览、点击和下单转化。商家关心的是每一笔推广预算能换回多少成交平台关心的是哪些推广者值得扶持、哪些渠道该调低分佣比例、哪些传播链路存在刷单风险。这里有一个很核心的矛盾联盟营销的流量结构天然是脉冲式的。推广者一次群发、一个短视频挂车、一个直播间讲解都能让某个品类的流量瞬间激增但也可能因为平台限流、同行比价、用户新鲜感下降而快速回落。这种流量带有明显的传播属性点击会在推广网络里逐级扩散并不是像搜索广告那样相对稳定。传播规模预测要做的事就是给定当前已经观察到的推广动作和历史网络状态预测未来一段时间内由这条传播链路带来的总规模。规模可以定义为成交订单数、GMV、活跃用户数或新增可跟单用户数。不同口径对应不同决策但底层方法论是一致的。这个预测和传统销量预测差别非常大。传统销量预测可以依赖商品历史销量、季节、促销计划等平稳信号联盟营销里同样的推广者、同样的商品、同一个佣金比例上个月能跑出5000单这个月可能只有300单因为用户注意力被其他热点吸走了。目标方差大标签还要等结算后才完整延迟又长模型很容易被单个异常天带偏。1.2 为什么静态图模型不够用我们最开始也尝试过直接拉一个节点特征矩阵用XGBoost或MLP去做回归。节点是推广者特征是历史表现、类目偏好、粉丝量级等。效果在小流量池里还行一遇到大促或头部推广者集中发车误差就明显被拉起来。后来把推广关系建成了图推广者是节点合作和导流关系是边。这确实有用因为推广者之间存在明显的带动效应——A推广者先发车B看到效果后跟进C的老客通过B的评论区再被转化。这种层层传导在静态图里能体现一部分但远远不够。问题在于联盟营销的图结构是时刻变化的。今天A和B之间还有导流关系明天因为佣金分成调整这条边可能断了。用户的兴趣偏好也随时在变上午还在美妆类目里逛下午可能就跑到数码产品那里看测评。图结构、节点属性、边的权重都在随时间演化这才是“时空动态”四个字的分量。所以我们最终放弃了平均静态图改成按时间切片构建动态图序列。每个时间窗内节点、边、属性、交互频率都持续更新模型既要学图的空间结构又要学时间的演化规律。这套思路的灵感来自时空图神经网络在交通流量预测上的应用但交通路网结构相对稳定联盟营销里的传播网络随机性和突发性更强需要针对性地改造结构。2. 两阶段预测的整体思路与问题建模2.1 两阶段拆解的动机刚开始我们做的是端到端直接回归把动态图编码成向量再输出一个规模数值。实验下来MAE高得离谱尤其大促日预测值总比真实值低三分之一以上。复盘之后发现问题就出在“规模”这个目标太全局化了。如果预测的是全站整体成交订单数模型靠历史均值打底也能有个差不多的结果。但我们要的是某一条传播链路的最终规模也就是只依赖参与这条链路的推广者子集的指标。而这个子集是谁、包含多少节点、节点之间的结构如何本身就是一个高度变动的变量。直接让编码器去回归总规模等于让模型同时解决“选哪些节点”和“算总规模”两个难题两个目标在隐空间里互相干扰。两阶段设计就是把联合问题拆开。第一阶段只回答一个问题哪些推广者会真正参与进这场传播。动态图里的每个节点都会被映射成一个参与概率概率超过阈值的节点进入候选传播集合。第二阶段在这个候选集合上做规模估算把集合内所有节点的嵌入聚合起来拼接上集合规模、网络密度等统计量再过回归头输出最终的传播规模预测值。这样拆的好处是第一阶段把“谁”的问题固定住第二阶段只需要专注“多少”的回归方差被显著降下来。实验里我们发现当候选集合预测得比较准的时候第二阶段甚至不需要太复杂的模型简单聚合加一个MLP就能达到可用水平。2.2 问题定义与特征工程形式化一点说我们把业务数据切成等长时间窗口比如每小时一个快照。每个快照对应一张图G_t(V_t,E_t)节点是参与推广的推广者和涉及的商品类目边是一次跳转或导流点击事件。节点特征包含近7天点击率、转化率、佣金率、粉丝量级、历史发布频率边特征包含源节点到目标节点的跳转次数、平均点击时延、活跃时段重合度。预测目标定义成从当前时间T开始未来24小时内这条传播链路带来的有效成交订单数。标签口径上做了严格限制只统计已支付且未超时取消的订单按手机号去重排除内部测试账号。这个口径如果和财务结算不一致后面上线会非常痛苦后面会专门展开讲。另外我们还引入了一个“传播事件”的概念。每次自然传播行为比如推广者发布一条视频或一篇图文就对应一个传播事件围绕这个事件产生的所有跳转和成交都进入标签统计。这样做的好处是离线标注可以完全自动化不需要人工去定哪些流量属于哪条链路。两阶段的标签类型不一样。第一阶段是节点级二分类该节点在最终成交规模中贡献的订单量是否超过某个阈值。第二阶段是事件级回归整个传播事件的最终订单数是多少。两个阶段共享底层的动态图编码器只在预测头部分分叉。2.3 网络结构与整体流程整体流程可以用一句话概括原始点击流水 → 时间窗建图 → 时空动态编码器 → 节点嵌入节点嵌入接下来分两条路走一条经过第一阶段预测头输出每个节点的参与概率另一条进入第二阶段候选集合聚合器对规模做回归。训练时两个损失一起反传但第二阶段的选择操作要包在梯度停止里。抽样和阈值过滤都不能把梯度传到第一阶段否则模型会利用第二阶段任务去反向纠正第一阶段的选择很容易训练崩溃。具体实现顺序上先把历史动态图快照喂给一个基于GAT的消息传递模块层间加入时间位置编码和时间注意力得到每个节点的最终表示。第一阶段用sigmoid输出概率第二阶段用概率加权的soft selection做聚合避免硬阈值导致不可导。3. 核心模块实现与训练细节3.1 时空动态网络编码器这里重点说一下编码器内部结构。最开始想直接用Graph Transformer把整张图的邻接矩阵和特征一次性放进去但动态图序列太长算力扛不住。后来改成两个关键操作。第一个是动态邻接聚合。每个时间片内部用GAT层做空间消息传递时间片之间用带膨胀感受野的时间卷积捕捉趋势变化。空间和时间解耦后训练速度快很多效果也够好。如果你更想要端到端的时间Transformer也不是不行但在联盟营销的海量点击场景里时间成本和算力成本都不太划算。第二个是时间位置编码。为了让模型区分节点在不同时间窗口的状态我们参考了Transformer的位置编码思路把时间窗口编号编码进节点特征。这一步非常关键没有它模型会把昨天和今天同一时段当成完全相同状态一到跨天场景就崩。贴一个简化版的编码器伪代码基本就是我实验阶段的骨架class SpatioTemporalEncoder(nn.Module): def __init__(self, hidden_dim, num_heads, num_layers): super().__init__() self.gat_layers nn.ModuleList([ GATConv(hidden_dim, hidden_dim, headsnum_heads, concatFalse) for _ in range(num_layers) ]) self.time_conv nn.Sequential( nn.Conv1d(hidden_dim, hidden_dim, kernel_size3, padding2, dilation2), nn.GELU(), ) def forward(self, snapshots): # snapshots: [T, N, D] 节点特征序列 h snapshots for gat in self.gat_layers: # 每个时间片内部做空间消息传递 h gat(h) h h.permute(0, 1, 2) # 转成 [T, D, N] 给时间卷积 h self.time_conv(h) h h.permute(0, 2, 1) return h.mean(dim0) # 聚合时间维度得到 [N, D]这个实现非常适合作为快速基线后续需要升级成更大规模注意力机制也方便。3.2 两阶段预测头的设计第一阶段预测头很轻就是多层感知机加sigmoid。输入节点嵌入输出参与概率。概率输出后又加了一步Platt缩放校准避免概率阈值在生产环境里飘来飘去。第二阶段相对讲究。候选集合内部用了两种聚合方式一种是简单的sum pooling把候选节点的嵌入直接求和作为集合表示另一种是注意力池化给每个节点按其对规模贡献的确定性分配权重。实验里注意力池化在头部传播者主导的场景下效果更好但sum pooling更稳最后采用注意力池化加残差连接的结构。第二阶段回归头不只是吃集合嵌入还把候选节点数、平均佣金率、历史同品类GMV分位数等全局标量拼进去。这些标量特征在数据量不足时能显著压低误差相当于给模型一个先验锚点。这里踩过一个坑不能把第一阶段概率乘进节点嵌入再做加权求和。表面看很合理实际会把低概率节点的高维噪声放大效果反而不如直接对筛选后的节点做干净聚合。3.3 损失函数和训练策略第一阶段用BCEWithLogits第二阶段用SmoothL1Loss。SmoothL1在规模预测上比MSE好很多因为对离群点不敏感而联盟营销的真实规模经常出现几十倍尖峰。整体loss直接加权重1比1。也试过加权组合效果没有显著差异反而多一个超参数要调最后干脆不设权。训练策略里几个细节值得记录按时间顺序切分训练集和验证集不允许随机打散否则验证集等于开卷考试。每个batch至少包含连续8个时间窗保证时间卷积能看到趋势变化。优化器用AdamW学习率初始2e-3余弦退火到1e-4。第一阶段阈值标签需要按周期滚动更新根据最近30天各节点的贡献分布重新计算不能定死。4. 离线实验设计与结果复盘4.1 数据切分与评估指标实验数据来自我们自己平台上脱敏处理后的真实联盟营销日志连续90天粒度到小时。包含12000多个推广者节点约40万条跳转导流边。按时间顺序切分前60天训练中间15天验证最后15天测试。验证集和测试集都特意覆盖了三个大促日。只拿常规日评估没意义活动场景的预测能力才是客户真正关心的事。评估指标用了三个MAE平均绝对误差直观反映偏差量级。WAPE加权平均百分比误差对大流量节点更敏感。命中率预测值落在真实值上下25%区间内的样本比例。命中率在业务场景里非常关键因为预测稍微偏一点和偏一半对预算投放决策的影响是质变。普通回归指标无法完全表达这种差异业务方看MAE看不太懂但一说命中率他们就有感知。4.2 基线与模型结果基线选了几组历史均值、XGBoost、LSTM、静态GCN、基础Graph Transformer。完整模型就是两阶段加时空动态编码的结构。结果都对原始数值做了脱敏处理直接看绝对值没有意义相对差距能说明问题。模型MAEWAPE命中率历史均值32847.2%41.8%XGBoost24138.6%48.5%LSTM22535.9%51.2%静态GCN20833.4%54.7%基础Graph Transformer18930.1%58.3%两阶段时空动态网络本文15224.6%66.5%两组主要结论第一两阶段设计在WAPE上全面压过直接回归方法第二时空动态编码相对静态GCN的增益在大促日显著变大正常日也有提升但没有那么夸张。4.3 核心结论与原因分析为什么两阶段能赢直接回归模型最后的嵌入必须同时编码“哪些节点可能起量”和“整体规模会多大”这两类信息。这两类信息在隐空间里经常互相冲突一个传播事件可能靠单个头部节点起量也可能靠上百个长尾节点同时发力两种模式的嵌入差异非常大。两阶段把“是否参与”独立拎出来后第二阶段回归面对的其实是一个维度低得多的任务误差自然能降下来。时空动态编码能赢则是因为它抓住了传播链路“先慢热、再爆发、后回落”的节奏。静态GCN会把早高峰和晚高峰的图结构混在一起平均掉动态模型能感知到节点活跃度的突变相当于预判了一个传播信号正在变强还是变弱。实验里还有两个反直觉的发现一是候选节点数量本身是比绝大多数节点特征更能预测最终规模的信号。候选节点数每增加10%整体规模大约增加5%到8%这种结构信息在散点图上非常明显直接回归模型却很容易忽略。二是第一阶段预测得太准不一定是好事。阈值设得过高只留下头部大节点第二阶段回归会很平滑但总值严重偏低阈值设得过低大量噪声节点进来回归方差激增。最佳阈值区间大概在0.4到0.6之间必须靠验证集调节。5. 工程上线与生产环境常见问题5.1 标签口径与埋点一致性必须对齐这句话看着像废话但联盟营销的标签口径真的比想象中脏得多。推广者发的链接被用户收藏两天后才下单这个订单算不算传播规模不同平台的统计口径不一样模型结果会天差地别。我们最终定下的口径是按点击归因窗口内成交计算归因窗口设7天同一用户多个订单只取首次。这个口径要和财务结算口径对齐否则离线指标再好看上线后跟实际账单对不上账模型照样被投诉。排查标签问题时有段时间发现预测值系统性偏高后来一查训练数据里把大量加购未支付的数据也算进去了。大促期间加购未支付的量可以达到成交量的两倍这一项直接让标签抬高了一大截。所以数据落库时就要把“成交订单”定义清楚是支付成功的订单、支付后未退款的订单还是商家确认发货后的订单。定义不清楚后面所有工作都是沙地上建楼。5.2 时间泄漏比模型结构错误更致命这是所有序列预测项目最容易翻车的地方。节点特征是昨天生成的标签是明天生成的只要特征表里有任何一个字段记录了明天的活动标记模型就会准确得不像话。我们做了一次彻底的时间一致性审计给特征表所有字段都加了generated_at时间戳。模型上线前还会跑一个“伪未来特征检测”用当前可用数据预测某个历史时点如果指标好得反常就说明有字段指向了未来。这种审计值得花时间。有次排查一个线上诡异的高指标问题最后发现是取数时不小心用了全表的最终状态字段把当天后续发生的销售额提前暴露给了模型。实操建议只有一句所有用于训练的特征落地时就把时间版本冻结不要使用预测启动时刻之后才存在的字段。宁可少用一些看起来有效的特征也要保证每个字段的可用时间合法。5.3 线上阈值、干预策略与部署架构两阶段模型上线后最大的价值不是给出一个数字而是给出候选传播节点集合。预算充足时这个集合可以直接当作重点维护名单给这些推广者优先发券、提高分佣比例。推广者名单变得可解释后运营团队终于能参与进来了。以前面对黑盒回归模型运营完全不知道从哪下手。现在模型会告诉他们说这场传播主要由哪三个人带动运营再去判断这三个今天是不是真的在活跃有没有可能因为限流没起来。阈值选择还需要和干预次数绑定。如果运营每天只能联系20个推广者阈值就调高让名单精而不漏如果是一次性系统群发阈值可以降低让更多节点进入候选池。这种可调节性比一个冷冰冰的最终预测值实用得多。部署架构上也要注意性能。动态图编码器在线推理成本高我们把节点嵌入做成了缓存图快照每15分钟重建一次候选集合的预测输出走后端服务推送。模型服务用ONNX Runtime加速单次推理控制在几十毫秒以内可以支撑运营实时调整策略。生产环境还得有预测偏差监控。节假日和临时热点会突然重塑传播网络结构离线测试集不一定覆盖这些模式。我们设了滚动监控窗口每15分钟比较一次近期预测分布和实际分布偏差超过警戒线就触发模型回训或特征切换。6. 实际落地再做一轮也会少走弯路的地方6.1 模型可解释性比想象中更重要开发这个系统的早期我们组的关注点全在指标上离线数据提升了几个点激动得不行。但模型推到业务那边后第一个被问到的往往是为什么预测是这个数根据是什么两阶段模型在这个问题上天然有优势。第二阶段输出的规模数字如果不好解释可以把第一阶段候选集合拉出来看。业务方看到某个明星推广者在集合里自然就会相信这个预测值有依据。相反纯黑盒回归给不出任何决策支持即使指标再好也很难落地。6.2 冷启动和稀疏节点不能靠单一模型解决候选集合预测对头部推广者效果很好因为历史数据充足特征稳定。长尾推广者就不一样了很多节点一周只发一次推广特征极其稀疏图消息传递根本聚不到有效邻居信息。我们试过给稀疏节点拼接类目统计特征有一定帮助但本质还是不解决数据稀少的问题。最后选了个折中方案长尾节点走独立轻量模型只依赖类目和事件维度特征不强行进入图模型。否则图很容易被大量低质量节点干扰对头部节点预测反而不利。6.3 离线评估指标要加业务视角复盘整个过程最大的体会是纯技术指标并不能保证业务成功。MAE降下来了但运营还是不知道该给多少预算因为预测区间的信息缺失了。后续演进方向是在第二阶段引入蒙特卡洛模拟对候选集合做多次随机采样得到预测规模的分布而不只是点估计。分布信息可以直接给到预算系统让系统根据风险偏好决定是激进投放还是保守保底。这个方向我们还在探索但已经能看到对业务决策的直接价值。最后说一句实际体会当初把任务拆成两阶段单纯是想降误差后来发现这个设计的更大收获是让模型结果变得可解释、可干预。联盟营销的业务方不关心你用的是什么网络结构他们关心的是钱该往哪推、什么时候收手。两阶段模型恰好把“选谁”和“算多少”分成了两步每一步都能对接一个业务动作。这种特性在实际落地中可能比精度提升更值钱。