蘑菇街首页推荐视频流——增量学习与 wide&deepFM 实践(工程 + 算法)

蘑菇街首页推荐视频流——增量学习与 wide&deepFM 实践(工程 + 算法)

作者:美丽联合集团 算法工程师 琦琦 ,公众号关注:诗品算法
阅读原文:https://zhuanlan.zhihu.com/p/212647751
本文经作者授权转载,转载请联系原作者

一、楔子

害,写个这么严肃的技术话题还需要楔子么?这不是让大家放松一下嘛!毕竟是我的处女作,还是要来个看似一本正经的开场白和自我介绍的。

大家好,我是混迹于奋斗 X 之都——杭州的互联网大龄脱发女程序员一枚,大家可以关注我的公众号:“诗品算法”。我会尽量保持每个月甚至每周更新一次的频率,在此立证(更新慢你也不能打我,只能用唾沫星子淹死我了哈哈)。

下面进入正题,带你领略蘑菇街有(坎)趣(坷)的从 0 到 1 的增量学习历程。

二、背景

在 online deep learning 炒得尤其火热的今天,我们知道,实时性就是互联网的生命和活力所在。笔者前几天跟一个阿里的朋友吃饭,朋友说,ODL 现在是他们组最容易出成果的方向,众人愕然,ODL?哪篇论文里的?随即一拍大腿,原来是 deep online learning。。。

试想,如果你刷抖音时,平台捕获到了你最近偏好旅行的即时兴趣,随即在很短时间内给你推荐了旅行相关的内容,你是不是会持续嗑药般地滑动下去?从而产生了心理学中所谓的无限“心流”,但我并不推崇这种类似沉迷游戏般的"心流",这种带有引号的“心流”仅仅是感官的愉悦,与精神的满足与自我的成就感无关,与至高的纯粹的甘美的快乐无关,与灵魂真正的安宁与幸福更是无关,因这并不会让你获得实质性的进步。扯远了,但这就是推荐系统实时性的作用和平台的核心诉求,即,提高用户的停留时长。你发现平台很懂你,下次可能还会再来,从而提高了平台的留存,说白了,跟开餐馆是一个道理,我们必须要有“回头客”呀!

三、何为增量学习

增量学习,顾名思义,是指一个系统在保存大部分以前已经学到的知识基础上,仍能源源不断地从新样本中学习新知识。增量学习于人类自身的学习模式非常相似,在信息化快速发展的今天,我们每天接受大量信息,有早已烂熟于心的旧知识,也有令人耳目一新的新知识,人类会在不遗忘旧知识的基础上,接收新知识,并将这些新知识消化,成为自己的旧知识。这一过程是循序渐进的。增量算法,就是每当有新增的样本时,并不需要重建已有的知识库,而是在原有知识库的基础上,仅做由新增样本引起的更新。实践表明,增量学习可以稳定提升模型性能。

四、增量学习在蘑菇街首页推荐的必要性

我们在大量实验中发现,若一天不更新模型,线上核心指标会出现肉眼可见的下降;甚至两个完全相同的模型,更新的快慢也会在很大程度上影响算法实验的效果。

下面我们用实验来验证此论述。

我们的增量学习架构中,全量模型训练 n 天执行一次,n>=1,增量训练每小时执行一次。每次增量训练时,都会 restore 当前增量对应全量版本的 checkpoint 和上一次增量版本的 checkpoint,对当前增量样本的 validation 验证集进行评估。

我们可以得到每一次增量训练过程中的最优 AUC(或者固定 epoch 的 AUC),与全量版本的 AUC 和上次增量版本的 AUC 进行比较,得到离线的 AUC diff。这里的 AUC 评估基于同一份验证集,因此完全可比。下面给出本次增量 VS 全量以及本次增量 VS 上次增量 AUC 的 diff:


preview

从离线 AUC 的表现来看,随着时间的推移,增量学习的有效性愈发明显。若两天不更新模型,离线 AUC 的损失高达 3.5%。

五、工程实践

1、曾经踩过的坑——第一期增量实践

彼时还是落后的“解放”前,我们尚未接入精排实时打点。所有实时特征表都是从小时级别的 hive 表中解析并处理得到的。全量和增量训练分别对应不同的 hive 表,全量样本表使用天级别分区,增量样本表使用小时级别分区。增量特征既涵盖近实时特征(按小时更新),又涵盖离线特征(按天更新)。样本构建使用小时级 Spark 任务处理,生成小时级的训练样本。使用这种方式进行增量训练存在诸多痛点:

  • 所有特征和样本依赖离线数据,小时级别的原始样本表依赖于小时级别的曝光/点击/事件表。
    • 由于调度系统的不确定性和各种依赖关系的不可控,用户点击互动等正样本极易丢失。
  • 小时级增量任务的整体链路非常复杂,需要人工维护相关的配置规则。
  • 整个流程涉及多张 Hive 表之间的依赖,且依赖关系复杂。
  • 在主表 join 特征表的过程中,由于小时级别的时间窗口难以对齐,不可避免地会出现特征缺失的情况。
  • 如,新增小时特征时,需要配置两个任务,一个任务每小时写当天特征表,另外一个任务需要在第二天 overwrite 前一天的特征表。否则会导致当天 23:30~00:30 的跨天样本丢失。
  • 数据质量无法保证,新增特征困难。
  • 依赖的特征表需要以小时级任务写天级分区的规范来更新。
  • 新增特征时,无法保证链路稳定性。

我们第一期采用了以下 gay gay 的手段规避以上问题:天级别样本 join 小时级别特征/设置上游正样本调度晚于负样本调度/下游延迟一小时消费数据/小时级任务写天级任务分区等等。但这些手段治标不治本,上述问题仍旧得不到彻底解决。

2、接入精排实时特征打点——第二期增量实践

  • 线上产出的实时特征通过在线 inference 系统实时输出。
  • 新增一条数据链路,消费精排打点 + 用户真实行为数据(包含曝光/点击/停留时长等)进行实时基础样本构建,样本、特征、停留时长等数据在线 join 后,再将实时基础样本存储到小时级分区的 hive 表中。
  • 下游的增量样本构建(比如 wide 交叉编码)+ 模型训练沿用第一期的逻辑不变,增量处理小时级基础样本。
  • 增量学习的全量和增量训练全部依赖于精排实时打点数据,特征构建过程中无任何 join 逻辑,再也无需担心各表之间的时间窗口是否对齐。
  • 这种策略可以保证,视频和发表者的特征是实时变化的,用户的点击行为、点赞行为等也是实时的,同时,我们在一天内,会对线上模型进行多次更新。
  • 下图为整体数据链路:
    preview

虽然第二期增量实践解决了第一期的很多问题,但第二期增量训练仍然存在一些暂未解决的痛点,多数公司为之头痛的,应该就属多路实时数据在线 join 导致的样本归因问题了。



  • 多路数据包括用户行为数据(曝光/点击/停留时长事件)、特征等。
  • 因为行为上报的天然串行性,会导致 label 滞后。用户曝光 < 用户点击 < 用户全屏页行为,其中,< 表示早于。
  • 一个视频的负样本(曝光)一般会先到达,后续到达的是视频的点击样本,接着是视频的互动样本(比如点赞评论分享等)。
  • 调研了市面上的两种范式:
    • Facebook:负样本会先 cache,等待潜在的正样本到达,若后续正样本到达,则只保留正样本。由于等待时间可能长达 10min 之久,因此实时性会有损,但是准确性会有保证。
    • Twitter/爱奇艺的做法:两条样本都会保留,都会去更新模型,这样实时性最高。比如,在观察到一个实例的正样本到达时,除了使用正样本进行梯度下降之外,还会对相应的负样本进行反向梯度下降,抵消之前观察到的 FN 样本对 loss 的影响。其实是对 loss 做了修正。
  • 我们在实践中采用 Facebook 的处理方案:
  • 精排打点的数据,并不会全部曝光给用户,因此精排打点的数据需要与真实曝光数据进行 join,得到基础曝光集合。
  • 基础曝光集合会在固定窗口内等待 label 数据,到达等待的时间后,会与 label 进行 join(无论是否等到),得到最终的训练样本集合。

3、一些细节干货

  • 全量训练与增量训练的协同:
    • 每次启动增量样本构建流程时,会自动拉取从上一次构建完的样本(打结束标)到当前时刻的所有样本进行构建。同样的,每次启动增量模型训练时,会捞出所有迄今为止未训练样本对应的增量版本,统一在当前训练流程中进行训练,避免出现样本被漏训练的情况。
    • 全量模型训练与增量模型训练并非同一任务,因为其超参和配置不同。每次全量模型训练完成后,所保存的模型 checkpoint 路径以当前全量任务命名,增量模型训练无法 reload 全量任务下的 ckpt(任务 id 无法互传)。如何打通全量与增量模型训练呢?我们在每次完成全量模型的训练后,将全量 ckpt 拷贝到增量任务所在目录下。注意,copy 操作只允许 chief 节点操作,因为在分布式框架下,若 worker 快于 chief 完成训练,并在 export 模型后执行 copy 操作,可能会导致 chief 节点的 ckpt 保存出现异常。

六、算法实践

1、wide + 增量学习

  • 在大规模 wide 模型的基础上,全量连续类特征/点击序列 pair 对的频次过滤条件不一定适用于增量连续类特征/pair 对的频次过滤条件。
  • 比如,全量时,某些 pair 对的频次过滤数量设置为 1000,但在增量时,应该设置得小一些。全量时,使用天级别积累的样本进行训练,但在增量时,则使用 1 小时内的样本进行训练,若仍然使用与之前全量相同的频次过滤条件,会过滤掉近 1 小时内频次不足 1000 的特征,即使在后一小时内,这些特征的频次增加到 1000,也无法再追回已训练样本中的这些缺失特征值。
  • 解决方案 1:全量时,使用一定的阈值过滤低频样本,增量时,为了保证不会误过滤增量中的某些有效特征,我们使用全量 1/2 甚至 1/3 的阈值过滤增量中的低频样本,从而降低某些小时级别低频但天级别非低频特征的准入门槛。
    • 优点:可以避免过滤掉某些有效特征。
    • 缺点:这种过滤会带来另外一个问题,频次太低的特征学出的权重不够置信。
  • 解决方案 2:构建样本时,之前在全量样本构建过程中被全量阈值过滤的特征,其频次会被保留,等到下一次增量到来时,若全量中被过滤的这些特征再次出现,则会将全量 + 当前增量的频次作为当前特征的频次。即,某个特征的频次达到准入门槛后,才会进入模型训练。
  • 优点:可以解决本次样本中的特征由于频次过低导致学出来的权重不够置信的问题。
  • 缺点:仍然会过滤掉某些低频特征,损失一部分有效信息。特征在达到准入阈值之前,出现的前 n 次都被忽略了。
  • 解决方案 3:在蚂蚁金服的在线学习策略——流式频次过滤的基础上,做了一些优化改进
  • 我们称之为基于 FTRL 的动态 L1 正则策略。这部分的详述将在第 2 部分呈现。

2、动态 L1 正则

对于增量学习来说,降低 regret 和提高 sparsity 是我们的重要努力方向。而稀疏性是算法追求的重要特性,随着模型复杂度的升高,其所需的存储、时间资源也随之提高,稀疏模型则会大大减少预测时的内存和复杂度。我们的优化策略正是基于常见的稀疏性优化算法 FTRL 实现的。在经典的 FTRL 实现中,所有特征的 L1 正则参数都是一致的。过大的 L1 正则系数虽然可以过滤大量低频特征,但由于约束太强,导致部分有效特征也被 lasso,影响模型性能。在我们的样本中,大部分特征都是极其稀疏的(尤其是 pair 对特征)。这种对特征出现频次进行计数,只有达到一定阈值后再进入训练的设计方案会破坏样本完整性,如全量频次 24,增量频次 1,阈值过滤 25,则该特征出现的前 24 次都被忽略,仅会训练该特征出现的一次,导致模型训练的稳定性差。

我们参考蚂蚁的实时流技术,希望使用特征频次影响 L1 正则系数,使得不同频次的特征有不同的 lasso 效果。特征频次和参数估计的置信度相关,特征出现的频次越低,置信度也越低。因此我们在纯频率统计的基础上增加一个先验分布(正则项),频率统计置信度越低,越倾向于先验分布,相应的正则系数也会越大。

实现:

  • 动态 L1 正则公式,这里借鉴了 Momentum 中的指数加权平均思路,同时考虑当前频次和历史频次总和的影响,使这两个变量同时影响增量模型训练,历史频次在公式中的系数略低:freq(t) = sum(freq(0, t-1)) * alpha + (1 - alpha) * freq(t)。
    • 整体公式:L1(wi)= L1(wi)(1 + C * max(N - freq(wi), 0) / N),freq(wi)是当前特征的真实出现频次(包含当前增量中出现的频次以及历史总频次),从增量样本构建传给增量训练的 hdfs 文件里取。C 是惩罚倍数,全量和增量时均设置为 1.5。N 为特征最低门限,全量时设置为 1000,增量时设置为 100。(均是参考值,超参可调)
  • 修改 tensorflow 底层 ftrl 优化器代码(C++ 实现):
  • 将 FTRL 优化器中的 L1 正则标量改成向量的形式,每一维特征对应不同的 L1 参数。
  • 不同特征对应的 L1 参数是在 tf 训练时,通过样本构建产出的特征频次计算得到的,在声明 FTRL 优化器时传入。
  • 注意:由于 wide 参数矩阵(feature_size, 1)是按照 mod 方式进行 embedding_lookup 的,因此 W tensor 对应的 L1 正则参数矩阵(feature_size)也需要按照 mod 方式重新排序,保证传给 FTRL 优化器的 W 矩阵和 L1 矩阵是一一对应的。

效果:动态 L1 策略相应的模型规模(大小)减小了 34%。同时,模型的稀疏度从 78% 下降到 71%,共减少了 9% 左右的特征量。动态 L1 策略应用在 wide 侧时,过滤了大部分不置信的 pair 对连接,可大大减少由连接噪声产生的 badcase。同时减小了过拟合的风险,提高了模型的泛化性能。

3、FM/Deep + 增量学习

对于 FM/Deep 与增量学习的结合,淘宝搜索模型已有一些探索。他们“将模型权重按照 sparse 的程度分为三个部分,分别为 freezing embeddings,changing embeddings 和 changing weights。其中,freezing embeddings 对应模型中的 high sparse embedding 如 user_id、query id、item_id。changing embeddings 对应其余 embeddings,如 user profile embedding、商品品牌 embedding、商品统计特征 embedding。changing weights 对应模型中的所有 MLP 部分权重。” 搜索大模型包含 Embedding 和 MLP,embedding 包含一些 high sparse 的 id 特征,比如 itemid,其数据规模之大,是无法进行纯实时更新的。加之 embedding 参数需要充分的数据才能学习充分,未充分学习的结果就是模型在当前样本上过拟合且在未来样本上缺少泛化性。因此阿里采取固定 high sparse embedding,实时更新 MLP 参数的方式进行模型训练。

但是这不适用于我们的推荐场景。阿里的电商搜索场景不需要更新 id embedding,是因为:1)上新商品无需实时展现给用户;2)流量充足,上新商品可以进行充分 EE 后再透出;3)物料池子足够大,丰富度有很大保障。视频推荐系统则不同,推荐系统的新发表物料需要尽快曝光给用户,无论从用户还是发表者的角度来看,这点都极其重要。当天的新发表内容在上述的增量更新方案下,几乎无法出现。

我们目前的 FM/Deep embedding 仍然照常更新,但其是否能进入训练的准入门槛,由其对应 id 出现的频次决定。对于 deep 部分的训练,我们也采用与动态 L1 正则类似的技术。

4、wide / FM / Deep 的频次过滤初始值设置

需要注意的是,同样是序列的频次过滤,wide 和 fm/deep 的频次过滤阈值完全不同。我们的平台支持对每个序列设置不同的频次过滤阈值,即在当前序列中,某个特征出现次数的最低准入门槛。wide 侧,用户点击序列与当前待排内容组成了 pair 对。而 fm/deep 侧的点击序在编码时,均是单侧的,无交叉,某视频可能在这批样本里的点击序中出现了 100 次,但在当前样本曝光中只出现了一次,我们将这种特征加入训练,无疑是非常危险的。同一序列特征,在 wide 侧是经过交叉后再编码的,在 fm/deep 侧却是直接编码的。因此,fm/deep 侧的频次过滤阈值要远远大于 wide 侧的。这是一个宝贵的经验。如果 fm/deep 侧点击序列的频次过滤阈值设置得过小,会导致线上效果变差。

七、未来展望

目前我们使用实时样本进行流式训练,部署流程仍采用全量模型切换的方式,时效为小时级。后续我们将会探索流式训练和实时更新结合的方式,同步探索样本归因的有效解决方案。wide & DeepFM + 增量学习已经在蘑菇街成功上线,我们进行了长达 10 天以上的 AB 对照实验。相比于基线,增量学习实验上的效果累计提升:人均曝光和人均点击 10+%,UVCTR5+%。用户停留时长和浏览深度也有 10+% 的提升。

感谢组内优秀小伙伴的支持:未闻、厚朴、阳明、浮尘、无羡等。

下一期,我将与大家深度剖析:有趣的包了两层糖衣(weight decay + 动态正则)的 AdaDelta/Adam 优化器,请大家持续关注。

最后再来一句碎碎念!欢迎大家关注小女子的公众号: 『诗品算法』

不定期推送各种干货!

参考:

  1. 在线学习 on-line learning 和增量学习 Incremental Learning 区别?
  2. 冯扬—在线最优化求解(Online Optimization)
  3. 在线学习在爱奇艺信息流推荐业务中的探索与实践
  4. 淘宝搜索模型如何全面实时化?首次应用于双 11
  5. 蚂蚁金服核心技术:百亿特征实时推荐算法揭秘

本文地址:https://www.6aiq.com/article/1599695935343
本文版权归作者和AIQ共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出