1. 从TM20K看电商广告推荐中的序列建模到底在解决什么问题电商广告推荐这个方向做过的人都知道最头疼的不是模型结构不够深也不是特征不够多而是用户行为序列的建模质量直接决定了广告点击率的天花板。TM20K这个数据集和配套的序列建模方案核心就是在回答一个问题当用户在电商平台上的行为跨度长达数周甚至数月行为类型涵盖浏览、点击、加购、收藏、下单、退款等十几种我们怎么把这些杂乱的时间线变成模型能吃的、有预测力的输入。先说清楚TM20K是什么。从标题拆解来看TM代表的是Taobao Multimodal或者Temporal Modeling这类含义20K指的是两万级别的样本规模或者序列长度基准。它本质上是一个面向广告推荐场景的电商用户行为序列数据集配套的建模方案重点解决的是长序列截断、行为类型异构、时间间隔非均匀这三个老大难问题。适合谁看如果你正在做推荐系统里的序列特征工程或者你负责的广告模型最近AUC怎么调都上不去那这套思路值得你花时间啃一啃。我先把结论撂在这儿电商广告序列建模的核心矛盾是用户历史行为的完整性与模型计算效率之间的拉锯。TM20K的价值在于它给出了一套可复现的基准让你在“用多长的序列”和“用什么方式压缩序列”这两个问题上不再拍脑袋。2. 电商广告序列建模的整体设计思路拆解2.1 为什么不能直接把原始行为序列喂给模型很多刚入行的同学会想用户行为序列不就是一条按时间排好的事件流吗我直接embedding一下扔进Transformer不就完了。这个想法在序列长度不超过50的时候勉强能跑但电商场景下一个活跃用户30天的行为事件轻松超过2000条。你拿2000个token去做self-attention计算量是O(n²)显存直接爆炸线上推理延迟也扛不住。TM20K的设计思路很明确先做序列的层次化压缩再做多尺度特征提取。具体来说它把用户行为序列分成三个层级来处理。第一层是原始事件层保留最近N条行为的完整信息包括商品ID、类目、价格、行为类型、时间戳。第二层是会话层把时间间隔小于阈值的行为聚合成一个会话会话内部做attention会话之间再做一次attention。第三层是全局层用时间衰减加权的pooling把整个序列压缩成一个固定长度的向量。这个设计的精妙之处在于它没有粗暴地截断序列而是用时间衰减函数给远期行为降权同时保留其类别级别的信息。举个例子用户三个月前买过奶粉这个具体商品ID可能已经不重要了但“母婴品类偏好”这个信号必须保留。TM20K的做法是在全局层用类目粒度的统计特征来承载这种长期兴趣。2.2 行为类型异构的处理策略电商场景下的用户行为不是单一维度的。浏览、点击、加购、收藏、下单、支付、退款、评价每种行为的语义权重完全不同。你不可能把“浏览”和“下单”同等对待。TM20K采用的是一个行为类型感知的注意力机制简单说就是给每种行为类型学一个独立的query向量让模型自己去学“什么情况下哪种行为更重要”。我实测下来的经验是加购和收藏的信号强度在短期预测中往往被低估。很多团队把重心放在点击和下单上但实际上加购未支付的行为在接下来24小时内的转化概率是普通浏览行为的8到12倍。TM20K在特征工程阶段就给这些行为打了不同的基础权重模型再在这个基础上做微调。2.3 时间间隔的非均匀性建模用户行为的时间间隔分布是极度长尾的。有的行为间隔几秒钟有的间隔几周。直接用位置编码会丢失时间信息用固定窗口又会把不同节奏的行为混在一起。TM20K的方案是时间感知的位置编码把绝对时间戳转换成相对时间差然后通过一个可学习的time embedding层映射成向量和内容embedding拼接。这里有个细节值得注意时间差的处理不能直接用原始秒数要做log变换。因为用户对“1小时前”和“2小时前”的感知差异远大于“10天前”和“11天前”的差异。TM20K在预处理阶段对时间差做了log1p变换这个操作看似简单但在实际AB测试中能带来0.3%到0.5%的CTR提升。3. 核心细节解析与实操要点3.1 序列截断策略滑动窗口还是重要性采样这是实操中第一个要做的决策。TM20K的基准方案用的是混合策略最近7天的行为全量保留7到30天的行为按重要性采样保留固定条数30天以上的只保留类目级别的统计聚合。为什么这么设计因为最近的行为对当前广告点击的预测力最强这个结论在多个公开数据集上都被验证过。但远期行为并非毫无价值它承载了用户的长期兴趣漂移信息。重要性采样的权重计算方式是行为类型权重乘以时间衰减因子再乘以商品与当前候选广告的类目匹配度。注意重要性采样的时候一定要做无放回采样否则同一个高权重行为被重复采样多次会导致序列信息冗余模型容易过拟合到少数几个事件上。3.2 Embedding层的维度选择与共享策略TM20K里商品ID的embedding维度是64类目embedding是32行为类型embedding是16时间embedding是8。这些维度不是拍脑袋定的而是根据基尼系数来选的。商品ID的基数最大分布最不均匀所以需要更高的维度来承载区分度。行为类型只有十几种16维足够了。另一个关键点是embedding共享。商品ID和类目ID的embedding在广告塔和用户塔之间是共享的但行为类型的embedding不共享。原因是行为类型的语义在用户侧和广告侧是不对称的用户侧的“点击”和广告侧的“被点击”需要不同的表示空间。3.3 注意力机制中的mask设计序列建模绕不开mask。TM20K里有三种mask需要同时处理padding mask、时间窗口mask、行为类型mask。padding mask是常规操作把补齐的0位置遮掉。时间窗口mask是防止模型看到未来信息在训练时对每个位置只允许attend到它之前的行为。行为类型mask比较特殊它允许模型在计算attention score时对某些行为类型对进行屏蔽。举个例子在预测“下单”行为时模型不应该过度关注“退款”行为因为这两者在语义上是负相关的。TM20K的做法是在attention score矩阵上叠加一个可学习的行为类型偏置项让模型自己学出来哪些行为组合是正向的哪些是负向的。# 行为类型偏置项的简化实现 class BehaviorTypeBias(nn.Module): def __init__(self, num_types, num_heads): super().__init__() self.bias nn.Parameter(torch.zeros(num_types, num_types, num_heads)) def forward(self, behavior_types): # behavior_types: [batch, seq_len] # 取出对应的偏置项 bias_matrix self.bias[behavior_types.unsqueeze(2), behavior_types.unsqueeze(1)] return bias_matrix.permute(0, 3, 1, 2)3.4 训练样本的构造与负采样TM20K的样本构造方式是滑动窗口加未来窗口。具体来说以当前时间点T为界T之前的行为作为历史序列T到T1小时内的广告点击作为正样本曝光未点击作为负样本。负采样比例是1:5也就是一个正样本配五个负样本。这里有个坑负样本不能随机采。随机采的负样本太容易区分模型学不到细粒度的偏好。TM20K用的是难负采样具体做法是从同一会话内曝光但未点击的广告中采样这些广告和正样本在上下文上高度相似模型必须学到更精细的区分能力。4. 实操过程与核心环节实现4.1 数据预处理流水线整个预处理流程我拆成五步每一步都有具体的参数和注意事项。第一步是行为去重与合并。同一用户在5秒内对同一商品的重复点击要合并成一条行为类型取最高优先级。优先级顺序是下单 支付 加购 收藏 点击 浏览。这个阈值5秒是根据用户操作的最小间隔分布定的低于5秒的重复操作大概率是误触或网络重试。第二步是时间差计算与log变换。对每条行为计算与上一条行为的时间差单位是秒然后做log1p变换。变换后的值再除以一个缩放因子TM20K里用的是10让数值落在0到5之间。第三步是序列截断与补齐。最大序列长度设为200。最近7天行为全保留如果超过200条从最早的行为开始丢弃。7到30天的行为按重要性采样保留50条。30天以上的行为聚合成20维的类目统计向量拼在序列末尾。第四步是特征编码。商品ID用hash编码类目用层级编码价格做分桶离散化行为类型用one-hot。所有离散特征过embedding层连续特征做标准化。第五步是样本对齐与负采样。按时间窗口切分训练集和验证集确保验证集的时间戳晚于训练集避免信息泄露。def preprocess_sequence(user_events, max_len200): # 去重合并 merged merge_duplicate_events(user_events, time_threshold5) # 时间差log变换 time_diffs np.diff([e.timestamp for e in merged]) time_diffs np.log1p(time_diffs) / 10.0 # 分层截断 recent [e for e in merged if e.timestamp now - 7*86400] mid [e for e in merged if now - 30*86400 e.timestamp now - 7*86400] old [e for e in merged if e.timestamp now - 30*86400] # 重要性采样 mid_sampled importance_sample(mid, k50) # 类目统计 old_stats category_stats(old, dim20) # 拼接 sequence recent[-max_len:] mid_sampled return sequence, old_stats4.2 模型结构的关键参数TM20K的基准模型是两层Transformer每层4个attention headhidden size是128FFN的中间维度是512。Dropout率在embedding层是0.1在attention层是0.2。这些参数在公开基准上表现稳定但实际业务中需要根据数据量做调整。我的经验是如果日活用户行为数据少于100万条hidden size降到64head数降到2否则过拟合非常严重。反之如果数据量超过5000万条可以把hidden size加到256head数加到8但要注意学习率要相应调小。学习率用的是warmup加cosine衰减warmup步数是总步数的10%峰值学习率是1e-3。Batch size设的是256梯度裁剪阈值是5.0。优化器用AdamWweight decay是0.01。4.3 线上推理的工程优化训练完了只是第一步线上推理的延迟才是决定能不能上线的关键。TM20K的序列建模部分在线上推理时做了三个优化。第一个是序列缓存。用户最近的行为序列在Redis里缓存每次请求只增量更新最新的一条行为而不是全量重算。缓存过期时间设为1小时因为超过1小时的行为序列变化对当前广告点击的影响已经很小了。第二个是模型蒸馏。用训练好的大模型蒸馏一个小模型小模型的hidden size只有大模型的一半但通过KL散度损失来逼近大模型的输出分布。实测下来蒸馏后AUC只掉0.1%到0.2%但推理延迟降低60%。第三个是批处理与异步。线上请求不是逐条处理的而是攒够32条或者等待超过10毫秒就组一个batch。序列建模部分放在单独的GPU推理服务上和主模型异步执行通过消息队列传递中间结果。实操心得序列建模的线上服务一定要做降级方案。当GPU推理服务超时或者不可用时直接退化成用统计特征如最近7天点击率、加购率来替代序列embedding。降级后的AUC会掉1%到2%但至少服务不会挂。5. 常见问题与排查技巧实录5.1 训练loss震荡不收敛怎么办这是最常见的问题。我排查下来八成是以下几个原因之一。第一序列里混入了未来信息。检查你的时间窗口切分逻辑确保训练样本的历史序列时间戳严格小于标签时间戳。这个错误很隐蔽因为数据预处理阶段如果不小心用了全局排序很容易把未来行为混进去。第二embedding初始化尺度不对。商品ID的embedding如果用默认的N(0,1)初始化在基数很大的时候会导致梯度爆炸。TM20K的做法是用N(0, 1/sqrt(d))初始化d是embedding维度。第三学习率太大。序列建模的attention层对学习率很敏感1e-3有时候都嫌大。可以试试3e-4配合warmup。5.2 AUC上不去但loss在降这种情况通常是验证集和训练集的分布不一致。检查一下验证集的时间窗口是不是和训练集有重叠或者验证集里的用户是不是在训练集里没出现过。TM20K的划分方式是按时间切分验证集的时间戳整体晚于训练集一周。另一个可能的原因是负采样策略有问题。如果负样本太容易区分模型在训练集上loss降得很快但学到的是“区分随机负样本”的能力而不是“区分难负样本”的能力。换成难负采样后训练loss会上升但验证AUC会明显改善。5.3 线上延迟超标怎么优化先定位瓶颈。用profiler跑一下看时间花在embedding lookup、attention计算还是后处理上。常见的情况是embedding lookup占了70%的时间因为商品ID的embedding表可能有上千万行每次lookup都要从显存里随机读。优化方案有三个。一是embedding表分片把大表拆成多个小表按商品ID的hash值路由到不同的GPU上。二是量化压缩把float32的embedding压成int8显存占用降到四分之一精度损失在0.1%以内。三是缓存热商品把点击量前10%的商品embedding常驻在离CPU更近的缓存里。5.4 常见问题速查表问题现象可能原因排查方法解决方案训练loss震荡学习率过大打印每步梯度范数降低学习率加warmup验证AUC不涨负采样太简单检查负样本来源换成难负采样线上延迟高embedding表太大profiler定位分片量化缓存模型过拟合序列长度过长对比不同max_len缩短序列加dropout特征泄露时间窗口错误检查时间戳逻辑严格按时间切分5.5 几个容易踩的坑第一个坑是时间戳的时区问题。电商平台的用户可能分布在不同时区如果时间戳没有统一到UTC序列的顺序会乱掉。TM20K在预处理阶段强制把所有时间戳转成UTC。第二个坑是行为类型的优先级冲突。同一个用户在同一个商品上先加购后下单这两条行为要不要合并TM20K的做法是不合并因为加购和下单的时间间隔本身就是一个强特征。但如果间隔小于1秒那大概率是系统自动触发的需要合并。第三个坑是序列补齐的padding值。padding的embedding不能用0向量因为0向量在attention计算中会产生均匀的attention分布引入噪声。TM20K用一个可学习的padding embedding让模型自己学出来padding位置应该被忽略。6. 序列建模的扩展方向与个人经验TM20K这套方案不是终点。我在实际业务中尝试过几个扩展方向效果还不错。第一个扩展是多模态序列。电商场景下用户不仅有点击行为还有停留时长、滑动速度、页面滚动深度这些隐式反馈。把这些信号编码进序列能提升3%到5%的CTR。具体做法是把停留时长做分桶离散化和商品embedding拼接。第二个扩展是跨域序列融合。用户在内容社区的行为和电商行为是有关联的。把内容侧的阅读序列和电商侧的购买序列做cross-attention能捕捉到一些单域模型看不到的兴趣信号。但这个方向要注意隐私合规只能做端侧聚合或者联邦学习。第三个扩展是实时序列更新。TM20K的基准是离线训练的但线上用户行为是实时产生的。用Flink或者Spark Streaming做实时特征计算把最新行为实时写入序列缓存能让模型对突发兴趣的响应速度从小时级降到秒级。我个人在实际操作中的体会是序列建模的收益不是线性的而是阶跃式的。你从无序列到有序列AUC可能涨2%。从短序列到长序列再涨1%。从单类型序列到多类型序列再涨0.5%。越往后越难但每一步的投入产出比都值得算清楚。不要盲目堆序列长度200条和500条的差距可能只有0.1%但推理成本翻倍。最后分享一个小技巧序列建模的离线评估一定要用时间序列交叉验证不能用随机K折。因为随机K折会让未来信息泄露到训练集评估结果虚高。TM20K用的是滚动窗口验证每次用前N天训练后1天验证滚动前进。这样评估出来的AUC才和线上表现一致。