首页 / 资讯中心 / 文章详情

CAN特征协同网络:从原理到CTR预估实战落地

CAN特征协同网络:从原理到CTR预估实战落地 ★ FEATURED ARTICLE
1. 从推荐系统痛点说起为什么特征协同作用值得单独拿出来讲做点击率预测这行的朋友都有一个共识模型结构迭代到今天单纯的加深加宽已经很难带来显著收益了。DNN、DeepFM、xDeepFM、AutoInt、FiBiNET这些模型在公开数据集上的AUC差距往往在小数点后三位徘徊。真正拉开线上效果差距的往往不是用了哪个模型而是特征之间怎么交互。我在实际项目里踩过不少坑。早期做CTR预估的时候直接把几百维特征拼在一起丢进MLP离线AUC看着还行上线之后发现长尾特征几乎没被激活头部特征又过拟合得厉害。后来换成FM系列做二阶交叉效果有提升但三阶以上的高阶交叉又变成了黑盒可解释性差排查问题的时候完全靠猜。CANCo-Action Network特征协同作用网络这个思路是我近两年觉得在特征交互这个方向上比较有意思的一个方案。它的核心想法很朴素与其让模型自己去学特征之间的交叉权重不如显式地建模一个特征对另一个特征的作用力就像物理里的协同作用一样——A特征的存在会改变B特征对最终结果的贡献方式。这篇文章我会从工程落地的角度把CAN的来龙去脉、结构设计、参数选择、实操细节、踩坑经验完整讲一遍。适合已经做过基础CTR模型、想进一步优化特征交互效果的算法工程师也适合刚入门推荐系统、想理解特征交叉到底在做什么的同学。读完你应该能自己动手复现一个CAN结构并且知道在什么场景下它比FM系列更值得一试。2. CAN到底解决了什么问题特征交互的三种范式对比2.1 从加权求和到协同作用的思维转变传统特征交互的思路本质上是加权求和。FM做的是内积DeepFM做的是内积加MLPxDeepFM做的是CIN卷积交叉。它们的共同点是把两个特征的embedding拿出来通过某种运算得到一个标量或向量再汇总到最终预测里。这种做法的隐含假设是特征A和特征B的交互强度是一个固定的函数关系跟具体样本无关。但现实场景里不是这样的。举个例子在电商CTR场景中用户性别女和商品类目美妆这两个特征的交互强度对于不同年龄段的用户是完全不同的。20岁女性用户这两个特征的协同效应非常强50岁女性用户这个协同效应就弱很多。传统FM学到的只是一个全局的交互权重没法做到样本级别的动态调整。CAN的核心洞察就在这里特征之间的交互不应该是静态的权重而应该是动态的、依赖于上下文的作用力。它借鉴了物理中场的概念——一个特征的存在会在特征空间中产生一个场影响其他特征的表征方式。2.2 三种特征交互范式的横向对比我把常见的特征交互方案整理成了一张表方便大家直观对比范式代表模型交互方式样本级动态性高阶交叉能力工程复杂度静态加权FM、FFM内积/加权无仅二阶低结构交叉DeepFM、xDeepFM、AutoIntMLP/CIN/Attention部分支持高阶中协同作用CAN特征作为操作数强天然支持中高静态加权类的优点是简单、快、可解释缺点是表达能力有限。结构交叉类通过增加网络深度来提升表达能力但高阶交叉往往是隐式的你很难说清楚模型到底学到了什么。CAN的独特之处在于它把特征交互这件事从学权重变成了学函数——每个特征不再只是一个embedding向量而是一个可以作用于其他特征的操作。2.3 CAN的适用边界什么时候该用它不是所有场景都适合上CAN。根据我的经验以下几种情况CAN的收益比较明显特征之间存在明显的条件依赖关系比如用户画像特征和商品属性特征的交互会随场景特征变化特征维度较高几百到上千维传统FM的二阶交叉已经不够用业务对可解释性有一定要求需要知道哪些特征对哪些特征产生了影响有足够的训练数据支撑更复杂的结构CAN的参数效率比纯MLP高但比FM还是要重一些反过来如果你的特征维度很低几十维或者数据量很小百万级以下上CAN可能过拟合不如老老实实用FM或者LR加人工交叉特征。3. CAN的核心结构拆解从Co-Action到Field-level协同3.1 Co-Action单元的内部机制CAN的基本单元叫Co-Action Unit。它的输入是两个特征的embedding输出是一个交互后的向量。跟FM内积不同的是Co-Action Unit不是简单地把两个向量做点积而是让一个特征通过一个小型网络去变换另一个特征。具体来说假设特征A的embedding是 $e_a \in \mathbb{R}^d$特征B的embedding是 $e_b \in \mathbb{R}^d$。Co-Action Unit做的事情是把 $e_a$ 通过一个MLP映射成一个变换矩阵或变换向量用这个变换去作用于 $e_b$得到交互后的表征把交互结果汇总到最终预测这里的关键设计是变换函数是依赖于 $e_a$ 的也就是说不同的 $e_a$ 会产生不同的变换方式。这就实现了样本级动态交互——同一个特征B在不同的特征A上下文下会被以不同的方式表征。我个人的理解是这有点像Attention机制里的Query-Key-Value但CAN更强调作用而不是匹配。Attention是在找相关性CAN是在建模因果性的影响。3.2 Field-level的协同从两两交互到全局协同单个Co-Action Unit只处理两个特征的交互。实际场景中特征有几十上百个两两交互的数量是平方级的不可能全部枚举。CAN的做法是引入Field-level的协同机制。常见的设计有两种第一种是共享变换网络。所有特征对共享同一个变换函数只是输入不同。这样做参数效率高但表达能力受限。第二种是分组变换网络。把特征按业务含义分组比如用户侧、商品侧、上下文侧组内和组间使用不同的变换网络。这样做表达能力强但参数量大需要更多数据。我在实际项目中用的是折中方案对重要的特征对比如用户ID和商品ID使用独立的变换网络对次要特征对使用共享网络。这样既保证了核心交互的表达能力又控制了整体参数量。3.3 与主流模型的结构对比把CAN和几个主流模型放在一起看结构差异会更清楚模型交互层结构参数量级训练速度线上推理延迟FM内积O(nd)快低DeepFMFM MLPO(nd h)中中xDeepFMCIN MLPO(ndk h)慢中高AutoIntMulti-head AttentionO(ndh)中中CANCo-Action 汇总O(ndh m)中中从工程角度看CAN的推理延迟和AutoInt差不多比xDeepFM要轻。如果线上对延迟敏感比如要求P99在50ms以内CAN是比xDeepFM更现实的选择。4. 实操落地从数据准备到模型上线的完整流程4.1 特征工程哪些特征值得做协同不是所有特征都值得做Co-Action。我的经验是优先选择以下几类特征ID类特征用户ID、商品ID、店铺ID。这类特征维度高、稀疏性强传统FM很难学好它们的交互CAN的动态变换能更好地捕捉ID之间的协同。类别特征性别、年龄段、类目。这类特征交互模式相对稳定适合用共享变换网络。数值特征分桶后的离散特征价格区间、点击率分档。这类特征做协同时要注意分桶的粒度太细会稀疏太粗会丢失信息。具体操作上我会先做一轮特征重要性分析把特征按重要性排序然后取Top 30-50个特征做两两协同。剩下的特征走常规的embedding拼接。注意特征做Co-Action之前一定要做归一化和分桶。我见过有人直接把原始价格丢进去结果变换网络的梯度爆炸训练完全跑不动。4.2 模型搭建PyTorch实现的关键细节下面是我常用的CAN结构实现基于PyTorch简化版但保留了核心逻辑import torch import torch.nn as nn class CoActionUnit(nn.Module): def __init__(self, embed_dim, hidden_dim): super().__init__() self.transform nn.Sequential( nn.Linear(embed_dim, hidden_dim), nn.ReLU(), nn.Linear(hidden_dim, embed_dim) ) def forward(self, e_a, e_b): # e_a: [batch, embed_dim] # e_b: [batch, embed_dim] transform_weight self.transform(e_a) # [batch, embed_dim] # 用变换权重去调制e_b interacted transform_weight * e_b return interacted class CAN(nn.Module): def __init__(self, num_fields, embed_dim, hidden_dim, num_pairs): super().__init__() self.embeddings nn.Embedding(num_fields, embed_dim) self.co_action_units nn.ModuleList([ CoActionUnit(embed_dim, hidden_dim) for _ in range(num_pairs) ]) self.final_mlp nn.Sequential( nn.Linear(embed_dim * num_pairs, 128), nn.ReLU(), nn.Linear(128, 1), nn.Sigmoid() ) def forward(self, x, pairs): # x: [batch, num_fields] # pairs: list of (i, j) 特征对 embs self.embeddings(x) # [batch, num_fields, embed_dim] interacted_list [] for idx, (i, j) in enumerate(pairs): e_a embs[:, i, :] e_b embs[:, j, :] interacted self.co_action_units[idx](e_a, e_b) interacted_list.append(interacted) concat torch.cat(interacted_list, dim-1) out self.final_mlp(concat) return out这段代码有几个关键点需要说明第一transform_weight * e_b这里用的是逐元素相乘不是矩阵乘法。这样做的好处是参数量小、计算快而且保留了embedding的维度信息。如果你想要更强的表达能力可以把逐元素相乘换成一个小MLP。第二num_pairs的选择很关键。我一般从20-30对开始试太多了会过拟合太少了效果不明显。可以通过离线AUC和线上AB实验来调。第三最后的MLP层不要堆太深。CAN的交互层已经做了很多工作最后的MLP两层就够了再深容易过拟合。4.3 训练技巧学习率、正则化和负采样CAN的训练有几个坑我一个个说。学习率CAN的变换网络比普通embedding层要敏感学习率不能太大。我一般用1e-3起步如果发现loss震荡降到5e-4。embedding层的学习率可以单独设置通常是变换网络的2-3倍。正则化CAN的参数量比FM大过拟合风险高。我一般会在变换网络里加Dropout0.1-0.3在embedding上加L2正则1e-5到1e-4。如果数据量小还可以考虑加BatchNorm。负采样CTR场景正样本稀疏负采样是常规操作。但CAN对负采样的比例比FM更敏感。我试过1:1到1:10的比例发现1:3到1:5之间效果最好。比例太高会导致变换网络学不到有效的交互模式。实操心得训练CAN的时候前几个epoch的loss下降会比FM慢这是正常的。因为变换网络需要时间才能学到有意义的变换。不要因为前几个epoch效果不好就放弃至少跑10个epoch再看。4.4 线上部署延迟优化和模型压缩CAN上线的时候延迟是最大的挑战。我做过一次统计同样的特征规模下CAN的推理延迟大概是FM的2-3倍DeepFM的1.5倍左右。优化手段主要有几个变换网络量化把变换网络的权重从FP32量化到FP16甚至INT8延迟能降30%-40%AUC损失通常在0.001以内。特征对剪枝不是所有特征对都对最终结果有贡献。上线前做一轮特征对重要性分析把贡献小的对去掉能显著降低计算量。缓存机制对于高频出现的特征组合可以缓存变换结果避免重复计算。我实际项目里通过这三招把CAN的P99延迟从80ms压到了35msAUC只掉了0.0008完全可以接受。5. 常见问题与排查技巧实录5.1 训练不收敛怎么办这是最常见的问题。我遇到过几次排查下来通常是以下几个原因现象可能原因排查方法解决方案loss震荡不下降学习率太大打印每步loss降低学习率到1e-4loss下降后反弹过拟合对比训练/验证AUC加Dropout和L2loss一直很高特征未归一化检查特征分布做归一化和分桶梯度爆炸变换网络输出太大打印梯度范数加梯度裁剪我印象最深的一次loss怎么都不收敛排查了两天才发现是某个数值特征没做分桶原始值范围是0到100万直接把变换网络的权重冲爆了。所以特征预处理这一步真的不能省。5.2 线上效果不如离线怎么办离线AUC涨了线上CTR没动甚至跌了这是推荐系统的经典问题。CAN场景下常见原因有特征穿越离线训练用了未来信息线上拿不到。检查特征的时间窗口确保离线特征和线上特征的计算逻辑一致。特征对选择偏差离线选的特征对是基于历史数据的线上分布变了之后这些特征对的协同效应可能减弱。建议定期重新做特征对重要性分析。延迟导致的特征缺失CAN依赖的特征对比较多线上如果某些特征拉取超时会导致交互结果异常。要做好特征降级方案。我的做法是上线前先做一周的shadow mode把CAN的预测结果和现有模型对比确认没有系统性偏差再切流量。5.3 特征对怎么选才合理特征对的选择直接决定CAN的效果。我总结了一个三步法第一步业务先验。把业务上明显有交互的特征对先列出来比如用户年龄段和商品价格区间、用户活跃度和推送时段。第二步数据驱动。用互信息或卡方检验计算特征对之间的统计相关性取Top N。第三步模型验证。把候选特征对分成几组做消融实验看每组对AUC的贡献。我一般会保留20-40对特征对太多了训练慢且容易过拟合太少了效果不明显。避坑技巧不要盲目追求特征对的数量。我试过加到100对离线AUC涨了0.002但线上延迟翻倍ROI完全不划算。特征对的质量比数量重要得多。5.4 与其他模型的融合策略CAN不是万能的它和FM系列、DeepFM系列可以互补。我常用的融合方式有两种并行融合CAN和DeepFM各跑一遍把输出拼接后过一个MLP。这样做效果稳定但计算量翻倍。串行融合先用FM做一阶和二阶交叉把结果作为CAN的输入特征之一。这样做计算量增加不多但需要调好FM和CAN的权重。我实际项目里用的是并行融合因为CAN和DeepFM的交互模式差异比较大融合后能覆盖更多场景。代价是推理延迟增加了40%左右但AUC涨了0.003值得。6. 一些实战中的个人体会CAN这个结构我从第一次看到论文到真正上线前后折腾了大概三个月。中间踩的坑比预想的多但也确实学到了不少东西。最大的体会是特征交互这件事没有银弹。CAN在ID类特征和高维稀疏特征上的表现确实比FM好但在低维稠密特征上优势并不明显。所以不要指望换一个模型结构就能解决所有问题特征工程、样本质量、线上工程每一环都很重要。另外一个体会是CAN的可解释性比想象中好。因为变换网络是显式的你可以分析每个特征对对其他特征的作用力大小这对于业务方理解模型行为很有帮助。我有一次用这个特性发现了一个特征穿越的bug业务方一开始不信把变换权重可视化之后他们才认可。最后说一个实操小技巧训练CAN的时候可以先用FM的权重初始化embedding层这样收敛会快很多。我试过随机初始化和FM初始化后者能节省大概30%的训练时间而且最终效果还略好一点。这个技巧在数据量不大的时候特别有用。如果你正在做CTR预估并且已经用遍了FM系列和DeepFM系列想找一个有新意的方向CAN值得花时间研究一下。但记得先在离线做好充分的消融实验再考虑上线。推荐系统的线上实验成本很高不要拿线上流量做试验田。
阅读完成 · 觉得有帮助?
咨询建站