文:周国睿·阿里巴巴定向广告团队的工作 ❞1.写在前面的碎碎念
这篇文章主要是介绍我们在 CTR 建模领域最新的工作 CAN,CAN 已经在双十一前全面在阿里定向广告落地,在线效果提升非常显著。一般文章会以事后的视角来写,这样思考会更完整,会屏蔽掉中途的一些旁枝末节,会显得思路更清晰也没犯啥蠢,不过这篇文章我想试试以顺序(流水账?)的视角来更真实的记录一下这个工作的诞生过程,具体对方法的描述,以及 CAN 的精简思路,大家看论文会体验更好一些:
CAN: Revisiting Feature Co-Action for Click-Through Rate Predictionarxiv.org
过去几年,我们团队一直在兴趣建模方面投入了非常多的精力,也产出了一些工作。其实我们一直还有另一条坚持的技术主线,表征建模。
「第一次冲锋」:在 17 年开始我就在表征建模上投入了大量的精力,毕竟是做 NLP 出身的,当然想蹭蹭过去学到的一些小技巧。最开始是想模仿 Google 做一个真・AI,当时 XDL 还没完全交接给工程同学,负责一部分 XDL 架构和开发的工作。当时想,如果各个业务线都在用 XDL,如果我做一个 parameter bank 用来存放各类 ID 的 embedding。各个业务线的模型训练任务通过这个 parameter bank 共享这些 embedding 参数,并且为它共享梯度,有没有可能学出一个淘宝体系里最普适的 ID representation,最后学出一些我们意想不到的知识?后来这个想法告吹了,因为它背后更新 / 维护 / 多个业务线的耦合风险无法说服所有人,同时我当时接到的任务是把图像用到 CTR 任务里,这个方案看起来并不是为了完成这个任务目标最直接和现实的方式。到最后这个初始想法演变成了减少我们迭代负担的 model bank,然后这个工作一直也没公开: )
「第二次冲锋」:18 年的时候,如何对十亿淘宝商品进行表征建模,或者简单点如何对 item id 学一个泛化性更强的 embedding 又开始来撩拨我的思想。想了一个 res-embedding 的方案,如果说直接学习每个 item 的 embedding 很难,那么把相似的 item 学习一个共享的 mid embedding,再去学习每个 item 特有的信息 res embedding,用它们相加来表达一个 item,相似 item 通过 mid embedding 共享信息可以缓解稀疏带来的学习难度,一定程度上提升泛化性。具体在如何找相似的 item,以及如何设置 mid 时,我采用了图的方法,通过用户行为把 item 连接起来,最后这个方案有点类似后来比较火的 graph sage。
不过这个方案实际使用时在样本量几亿规模时非常有效,到几十亿上百亿规模时收效甚微,这说明我还是没找到更好的表征建模方案。
「第三次冲锋」:后续陆陆续续做了非常多碎的尝试,也学习了很多研究思路,包括 GNN/Contrastive Learning/Information Bottleneck/Cognitive Rec 等等,但是每次尝试我都发现要写论文很简单,很多方法在 “小”(甚至几亿规模)数据集上总是有效,但是在我面对的实际业务问题规模上,几乎都没什么效果。我甚至开始怀疑我所解的这个业务问题,到底需不需要研究表征建模这个问题,毕竟 CV/NLP/ 语音的原始输入数据本身就是为表意而存在,天然具备一些连续相关性,而电商业务场景的数据并不是表意的。这到底是水平不够,还是说问题本身只是一个执念。后来我们做了一个简单的推演,CTR 类模型的核心参数量都在 embedding 部分,而我们组的大部分工作和模型迭代都在后面的 MLP 部分,比如 DIN/DIEN/MIMN。可是一旦 embedding 学习方式确定了,如果输入信息本身学习不够好,后面的模型空间是有限的。因此我们认为输入端信息建模应该是有一个比较大的潜在效果空间。然后就成立了一个项目组,搞了一年,没有完整的产出,成员们压力极大:(
在第三次冲锋后期,我们推断出了一个对输入端信息做交互建模的方法,算是为我们团队找到了一个新的迭代路径,我个人也认为这个方法如果算作特征交互,算是一个新的思路。
2.回到特征交互其实我对特征交互方面的工作一直以来态度都比较尴尬。一方面我认为手工交叉特征工程如果又能解决业务问题,又不影响迭代效率,其实挺好的,我们的业务模型里就有部分手工设计的交叉特征。技术都是为了解决问题而存在的,没有原罪,也没有原善,有克制的组合特征是能接受的,不能接受的是漫无目,无视迭代负担,冗余的大规模组合。另一方面,学术界一些比较熟知的工作,FM/NCF/deepFM/PNN/DCN 等等(说实话 DCN 不是很想提,不用看到实验部分,方法本身都有问题),这些工作以我浅薄的见解,我实在看不太出来和 FM 有啥区别,到效果上,我们的业务数据里确实都没效果,因为 FM 在我们的模型基础上叠加就没啥效果。虽然我一直很努力想理解这个在 CTR 建模领域比兴趣建模更为普适的路线到底在研究啥,这些工作一步步思路如何推进的。结果还是看不懂,也就不太想碰。
不过第三次冲锋,和之前一些同事的尝试到是找到了一个比较有意思的实事:「CTR 预估建模问题里,把待预估的商品信息(如 item id)和用户历史行为序列信息(如 item id sequence)做笛卡尔积,形成一个新的 id sequence,对其直接做 embedding 后 pooling 效果很好,会在 DIN 和 DIEN 的基础上再有比较明显的提升。」
3.为何笛卡尔积有效当时细想一下,笛卡尔积有效并不神奇,同时一定能找到参数量更少的模型方案来替代笛卡尔积这种 hard 的 id 组合方式。比如用户行为序列中有一个商品 ID 为 A,待预估商品为 B,笛卡尔积形成新的 ID A&B,A&B 每次在一条样本里出现,训练时都会更新独立属于自己的 embedding。而这个 A&B 的 embedding,我们认为其学习的是 A,B 两个 ID 在一条样本共现后对 Label 的 co-action 信息。
img这个 co-action 信息为什么重要其实很好理解,比如 CTR 预估问题,要解决的本来就是每条样本最后预测是否点击,其实解的问题就是所有输入信息 X 条件下点击的概率 。建模 co-action 信息就是单独建模 。具体的,如果 A 和 B 分别是待预估的商品 ID,和用户行为序列里的商品 ID。如果我们对行为序列做 SUM/AVG pooling 就等于忽视了 ID 间的 co-action,对序列做 DIN/DIEN 类似的 aggregation,在 co-action 的视角下可以看做是一个 scalar 的 co-action,没有方向,且只能对原始行为序列 ID 的 embedding 做一个纯量的修正。那么每个序列的 ID 都和预估商品 ID 做一个笛卡尔积呢,把原始的序列变成一个笛卡尔积 ID 序列,再给每个 ID 都学习一个 embedding。这个时候 co-action 就是用向量来建模,且这个新的 embedding 和原始序列的 embedding 完全独立,自由度更大,模型 capacity 更大。「如果原始 ID 的 co-action 信息建模本身有用,那么笛卡尔积就是建模 co-action 最直接的方式。」 笛卡尔积 + 端到端学习的 embedding 其实很像一个大的 memory network,只不过写入和读出的索引相同,都是笛卡尔积化后的 ID。这样的模式下这些代表 co-action 的笛卡尔积 ID 的 embedding 在训练时,具备样本穿越性。训练时,任意一个笛卡尔积 ID A&B 的 embedding 都是独立学习的,同时强保证了,在下一条 A&B 出现的样本里,这个 embedding 能把当前学到的 co-action 信息无损的带入。而简单的特征 cross 方法,比如把 的 embedding 和 的 embedding 做外积,这个时候 的 co-action 为 ,它在训练时也会被 embedding 本身的学习和 等更新,很难保证学习到的 co-action 信息在下一次出现时,还保留上一次学习的信息。
4.笛卡尔积不是终局笛卡尔积其实是非常常用也比较好理解其实现方式的一个方案。不过对用户行为序列 item seq 和待预估 item 做笛卡尔积组合其实有蛮多弊端,即使已经看到了离线的部分提升,我对着急把这样的方案推进全面生产化不是很感兴趣。
- 这种序列笛卡尔积在训练端和在线服务端其实成本蛮大的。训练可能比较好解决,但是在线服务会有比较明显的瓶颈,因为每一次预估需要生成的 ID,和查询 embedding 的 ID 会急剧膨胀,而这些操作是需要 CPU 计算的,这部分的可优化空间也比较小。算下来成本比一个计算复杂型模型要高不少,至少对于我们,一个熟知如何优化计算复杂型的团队是这样;
- 笛卡尔积意味着强记忆性,是比较 hard 的方案,对于一些样本里未出现的 ID 组合,是直接无法学习的。同时稀疏的组合和稀疏的 ID,学习效果也很差,大部分情况下只能选择过滤;
- 笛卡尔积的参数膨胀本身就会带来模型无论从性能还是维护迭代上鲁棒性的进一步降低。
下面我们来推演一下在笛卡尔积有效的情况下,我们有没有机会找到参数量更少的模型方案来替代笛卡尔积这种 hard 的 id 组合方式。「第一条线索,参数空间视角」:前文提到二维笛卡尔积的方式,如果我们局限以对 Item ID 做 co-action 建模分析,可以看做是一个全参数空间为 的方法,N 为 item ID 的数量,D 是 embedding 的维度。这个参数空间是非常大的, 在淘宝是十亿以上的规模。当然了实际上训练时不需要那么大的空间,因为并不是所有 ID 的组合都会在样本中出现,但是笛卡尔积这个方法的假设参数空间依旧是 。意味着在它有效的状态下,也是存在大量的参数空间冗余的,再考虑到稀疏出现的笛卡尔积,如出现次数个位数的笛卡尔积 embedding 无法有效学习。笛卡尔积方法,大部分的假设参数空间都是无效的。「第二条线索,学习难度视角」:前文提到,笛卡尔积的方式保障了任意一组 co-action 组合的学习是独立且强记忆能实现样本穿越的。而如直接外积的方式很难建模 co-action,因为没有参数来稳定维持对 co-action 的建模学习信息。虽然笛卡尔积的方式是有效且看上去对 co-action 建模最直接的,但是难以忽视的一点是比如在电商场景,商品与商品之间是有联系的,是有相似性的,任意两个商品之间的 co-action 信息也应该有 overlap 的部分,不应该是完全独立的。直接外积的方式呢,共享的维度过大,单侧 ID 的信息完全共享,参数空间为 。如果我们能有效利用不同 co-action 之间有信息可共享,我们就有机会找到把参数空间降低到N 的方法,其中 $T<<d$ 。<="" p="">
5.CAN: Co-Action Net最开始我们从 memory net 的视角想了一种方案,把 item id 的参数从 扩展到 ,即把 embedding 变成一个有 T 个 slot 的矩阵,这种方案下任意一个 ID 都有 T 个 slot,每个 slot 存放维度为 D 的 vector。这时候可以借如 attention 的思路来建模 co-action 并保持不同组合 co-action 学习的部分独立性。比如在建模 co-action 时,让不同的 slot 对另一个 ID 的所有 slot 做 attention aggregation,并和 aggregation 后的结果做外积(element wise 乘法):
核心的思想是,建模不同的 co-action 时,采纳 T 个 slot 中不同的参数,同时更新不同的参数,保持 co-action 建模一定的参数独立性。当然这种思路下可以去设计和尝试的具体模型方案非常多,整个交叉实验代价还蛮大的。
既然我们的核心目的是让 co-action 的建模过程中有相对比较稳定和独立的参数来维持对建模信息的学习记录,同时又想让不同的 co-action 间有一定的信息共享。不同于 memory net 的这个思路,我们组的小伙伴提出了一种更简单的方案:「把 co-action 希望建模的两个 ID,一端信息作为输入,另一端信息作为 MLP 的参数,用 MLP 的输出来表达 co-action 信息。」
「整体模型结构如下图所示,CAN 的部分在左侧」
这种方式下,由于 MLP 的有多层,再加上每一层插入非线性函数,如此, 和不同的 ID 做 co-action,如 ,其输出 co-action 有一定信息共享,在参数更新时也会不同,比如 MLP 的激活函数如果是 relu,甚至是稀疏会更新一部分,就做到了我们之前说的通过部分保留参数更新的独立性,而实现 co-action 的稳定性。当然了具体的我们的实际方法里这部分激活函数是 Tanh,这其实是个实验结论。做实验的小兄弟认为:因为我们在第一层输入时,手动增加了输入信息的多阶计算,,Tanh 会保护训练时输出更平滑。不过我没有完全接受这个观点,relu 或者 dice 都会更贴近我们最初设计的思路。
最后这个方法我们无论是在我们的业务数据集,还是公开数据集都做了比较完备的实验。CAN 确实性能优异,效果比笛卡尔积更好,同时模型参数量并不会急剧膨胀,因为这种方式需要查询参数的 ID 数不变,对在线服务 CPU 和 RT 也比较友好。至于增加的 GPU 计算部分嘛,一个是 GPU 的算力增长还是比较符合摩尔定律的,增加 GPU 负担比较便宜,另一个这部分的计算优化本来就是定向广告团队的拿手好戏: )。毕竟到现在都还有人没办法相信 DIEN 的在线服务,可能在线优化这块真的很难?在工程团队的给力配合下,我们很快就将这样的方案全面生产化,给业务带来了明显增长。
这个工作是我们团队在原始输入信息交互建模上的第一次尝试,如果真实业务情况笛卡尔积用着比较舒服,也不影响 RT 和迭代,其实笛卡尔积就是蛮好的方式。CAN 是我们希望推进找到模型化的方案替代笛卡尔积建模 co-action,背后希望的是找到一条新的路,让交互信息建模还有进一步的迭代空间。毕竟如果终结于笛卡尔积的方式,我很担心我们走上
- step1:手动加特征
- step2:无脑增加多阶特征
- step3:讲 AutoML 做特征搜索的故事
- step4:跳槽开始 step1 的循环。