实时增量学习在云音乐直播推荐系统中的工程实践
作者:易云天1 背景 在云音乐早期机器学习推荐场景中,大多数是以离线机器学习为主,模型是天级别(T+1)更新的。 随着用户、主播、ugc内容等变动频繁,以及外部环境发生突变如产品形态、热点爆点等情况下,离线方式存在严重的滞后性,而模型实时化能从全局快速捕捉变化,提高流量转化效率,减少流量曝光浪费,更加实时精准个性化推荐。具体背景可详见上一篇:https://mp.weixin.qq.com/s/uu...
【实时增量学习在云音乐直播推荐系统中的工程实践】云音乐模型实时化过程主要包含三个阶段:实时样本生成、实时模型训练、实时推送上线。 我们可以看到首当其冲的就是实时样本生成,也称之为在线样本。只有样本实时秒级别生成,模型才能实时训练,最后才能实时推送上线进行预估排序。
本篇重点介绍下实时样本生成过程,实时模型训练以及实时推送上线后续文章再分享。在介绍之前我们先了解下什么是样本。
2 什么是样本 样本是机器学习训练模型所需要的数据集,它通常是由user与item对象的不同属性组成并带有标注的数据集。
这里的对象属性即为特征,是经过处理后能直接给模型学习训练所需要的特征,并非是原始特征;这里的标注代表着每条数据属于哪种结果,这就衍生出正负样本之分了。
- 正样本:依据训练学习的目标决定样本的归属为正向的则为正样本,例如:首页直播场景,如果学习目标是ctr,那么主播曝光后有点击即可为正样本,标记label = 1
- 负样本:依据训练学习的目标决定样本的归属为负向的则为负样本,例如:首页直播场景,如果学习目标是ctr,那么主播曝光后没有点击即可为负样本,标记label = 0
文章图片
而样本的生成方式与周期不同,又可以分为离线样本与实时样本。
- 离线样本:以离线机器学习为主,采用spark批处理生成样本,生成的样本是T+1周期的样本。
- 实时样本:以实时(增量) 机器学习为主,采用flink方式实时生成样本,生成的样本是秒级周期的样本。
3 不一致性 ? 线上模型预估过程(predict)与线下模型训练(train)过程其实是因果相对过程,其逻辑、数据结构是相同的。可以简单理解为如下示意图:
文章图片
这里Feature1....n指的经过抽取后的模型特征,它是从原始特征开始经过一系列的特征抽取计算得出的。如下示意图:
文章图片
从上面两个示意图,我们可以看出来,train与predict的共同点都是Feature1...n,所以线上线下不一致引起的变量就在于这里。而不一致会直接造成算法模型效果有损失的,最直接的一种表现就是线下模型评估AUC效果不错,但模型上线后实际AB效果表现却往往不尽人意,如下图所示:
文章图片
而引起feature这个变量不一致,主要可能由特征输入(上图的原始特征X)与特征抽取计算不一致(上图的F(X)),其中特征穿越是造成特征输入不一致的典型情况。
3.1 特征穿越
? 在样本特征拼接时候,应该是T时刻的样本关联T时刻的特征,而在原来实际做法中,无法避免特征穿越发生。我们来看下特征穿越是如何产生的:
文章图片
发生穿越的情况主要有两种:
- 实时特征穿越:实时特征不断在变化,通常是统计类特征或者序列特征,这种也是难以控制的,有可能上一分钟是这个特征值,下一分钟这个特征的值已经发生变化了。比如用户U1的在t-1预估推荐时刻的点击item序列特征: u_click_seq:[1001,1002,1005],模型预估出item:1006,1008 推荐曝光后,用户U1点击了item:1008,在ua回流拼接样本时,可能实时特征任务已提前完成更新,而这时拿到的U1点击序列特征: u_click_seq : [1001,1002,1005,1008],而合理的样本中U1点击序列特征本应该依旧是:u_click_seq:[1001,1002,1005] 。这是其中一种特征穿越的现象。
- 系统延迟穿越:一般离线特征都是按照天处理的,考虑各种数据 Pipeline 的流程,处理时间一般都会有延迟,离线特征处理完之后导到线上供线上模型预估时请求使用。例如3月18日这天,线上预估请求用的特征是3月17号的特征数据。到了3月19日凌晨0点,特征 Pipeline 开始处理数据,到了凌晨5点,离线特征处理完了导到线上存储。那幺在3月19日0点-5点,这段时间线上请求的特征使用的是老的特征数据,也就是3月17日的特征数据。3月19日5点-24点,线上特征使用的是3月18日的数据。在离线样本生成过程中,如果是按天拼接的,那么3月19号这天的所有样本,都会使用3月18日的特征。这也是一种特征穿越的现象。
3.2 计算不一致
除了特征穿越外,线上线下特征计算不一致也会造成线下auc不错线上实验效果差的情况。我们看一下特征计算不一致是如何发生的:
文章图片
我们可以看出线上线下同样都要做特征查询、特征计算过程。而线上线下之间没有任何关联的,是相互独立的,正因为两套代码不同的计算过程,往往会导致计算逻辑出现不一致现象发生。
- 线上计算:采用c++语言在预估计擎中进行特征计算的
- 线下计算:采用scala或者java在flink、spark引擎中进行特征计算的。
我们先看第一种方式:Feature Point-In-Time
文章图片
这种方式就是要求每一份特征都带有timestamp,每次变化时不时直接覆盖,而是以多版本方式存在,当离线进行关联时,会根据timestamp来去选择关联哪个时刻的特征。这种方式优点就是:准确率高、容易回溯。 但是由于历史包袱情况,云音乐的数据特征体系还非常不成熟,不具备带有timestamp能力,并且从底层数仓体系开始做,成本巨大。基于这种原因,我们从线上预估着手。
采用第二种做法:预估快照回流(predict snapshot)
文章图片
综合了不一致性问题,以及模型实时化的诉求,要实现样本实时化生成,我们基于snapshot做了模型实时化完整工程方案实践。
4 实时样本 以下是实时模型工程的基本架构图,我们可以看到线上预估、线下训练是形成了数据流闭环的。
文章图片
就实时样本这块主要包含三个阶段:实时特征快照、实时样本归因、实时样本拼接。
4.1 实时特征快照(snapshot)
实时特征快照,是指将线上预估时所用的特征给落下,经过采集处理后实时写入kv中,然后与生成好的label进行实时关联,这个就是样本中特征生成部分。但具体在实施过程中,也面临了不少问题:
1、特征快照超大
当我们直接将线网预估时候几百上千item与user的特征以日志的方式进行本地落盘,然后再通过采集器实时采集本地日志发送到kafka,经过Flink处理后实时写入KV存储中。但是云音乐的场景流量很大,每秒超5W的写入,单条体积都超过50KB了,每小时就达到上TB的数据,本地磁盘及网络IO根本扛不住,KV消耗也大,而且还影响正常请求线网预估。
为了解决这个问题,我们提出了旁路TopN的方案,具体如下:
文章图片
由于最终与label进行关联的只可能是最后被推荐给用户的TopN item对象,因此我们在线投放系统最后一环节将胜出的TopN item异步构造同样的请求再次转发到旁路环境的排序预估系统,与线网环境的排序预估系统唯一区别是不做inference模型计算,这样将原来几百上千的Item缩小到TopN 10以内的量,几乎缩小了50倍~100倍,并且与线网请求进行解耦,不再影响正常的请求。另一方面,去掉本地日志,直连kafka,减少了IO的压力,预估快照采用protobuf协议,结合snappy压缩,单条体积也缩小了50%以上。
在kv存储方面,结合实时的特性,我们在也做了基于rocksdb之上进行了定制化-Tair-FIFO-RDB,它的优点很明显,具体的详细介绍且听下回分享。
文章图片
2、特征选择多样性
不同场景对特征的选择是不同的,有些场景需要原始特征,有些场景需要特征抽取后的特征,甚至两种情况都同时存在,还有就是有些特征是不需要给到线下的。如果每次都需要代码去开发选择什么的特征进行落快照,极大的影响了开发效率。面对这种情况,我们将特征选择进行DSL配置化,具体如下:
文章图片
我们可以看到,用户通过配置这样一份XML去灵活选择什么特征,极大的满足快速上线变更的需求,无需进行代码开发。
3、recId
? recId是snapshot实时特征快照与Label准确关联的重要手段,如果仅仅通过userId+itemId的话,可能存在重复情况或者关联错误情况,极大影响着样本的准确置信度。
? recId是由在线投放系统来生成的,代表着每一个用户请求唯一ID。在算法推荐链路最后一个重排层环节生成recId, 一方面将recId填充到转给算法预留的埋点字段alg中透出到上游客户端,客户端会在每个item曝光、用户对item发生点击、播放等行为上报,在上报ua中都会带上recId;另一方面将recId传到snapshot旁路预估系统中,在随着特征快照一起dump到kafka中,随后按照recId_userId_itemId落到kv存储中。
? 在实时样本拼接阶段时,通过key=recId_userId_itemId,就能将label与snapshot关联拼接成功。
4.2 实时样本归因(Label)
? 样本归因即是样本打标过程,根据用户的对item所表现的行为结果来判断是正样本还是负样本,样本归因对于样本的真实准确是极为重要的,也是直接影响到学习到的模型是否偏置。
业内常见的样本归因方式有两种:
- 负样本cache:facebook提出的负样本cache归因法,国内蘑菇街采用这种方式。负样本Cache,等待潜在正样本做选择, 样本归因准确,训练成本较低, 存在窗口,准实时。
- Twice Fast-Train:twitter提出的样本矫正法(FN校准/PU loss),国内爱奇艺也是采用这种方式。正负两次快速更新训练,样本无需显示归因、无等待窗口,更加实时,对流式训练要求高,且强依赖矫正策略,准确性难以保证。
我们以一个首页直播场景为例,这个场景的学习目标是ctcvr,ctr_label、cvr_label分别是是否有点击、是否有效观看的样本标签,对于每条样本来说,会存在以下三种情况:
ctr_label | cvr_label | feature{1...n} | 样本说明 |
---|---|---|---|
0 | 0 | {fea1:[0,1,3],fea2:[100]} | 曝光无点击 |
1 | 0 | {fea1:[0,1,3],fea2:[100]} | 曝光有点击,但无有效观看 |
1 | 1 | {fea1:[0,1,3],fea2:[100]} | 曝光有点击,且有效观看 |
文章图片
在延迟处理环节(延迟处理根据目标转化时间来暂时评估的,后续可通过监控统计来调整或者离线统计如果95%都能在设定的时间窗口完成转化即可),比如首页直播场景评估为10分钟。
- 预存kv: 只存曝光之后的行为标志,如click、playend,key:recId_userId_itemId_action,value: 1。
- 当impress ua 来到时,根据recId_userId_itemId_1查预存KV,如果能查到有click行为,就丢弃当前的ua,否则标记ctr_label=0,cvr_label=0;
- 当click ua来到时,然后根据recId_userId_itemId_2查预存KV,如果能查到有play行为,就丢弃当前的ua, 否则标记ctr_label=1,cvr_label=0;
- 当play ua来到时,根据播放时长,如果播放时长小于有效播放时长,则标记ctr_label=1,cvr_label=0. 如果播放时长大于等于有效播放时长,则标记ctr_label=1,cvr_label=1;
- 对输入的流按照join的key:recId_userId_itemId_ts进行keyBy操作,如果keyBy操作有数据倾斜情况,可在操作之前加一个随机数。
- 对keyBy后的流进行KeyedProcessFunction处理,在KeyedProcessFunction中定义一个ValueState,重写processElement方法,在processElement方法中判断,如果value state为空,则new 一个新的state,并将数据写到value state中,并且为这条数据注册一个timer(timer会由Flink按key+timestamp自动去重)。
- 重写onTimer方法,在onTimer方法中主要是定义定时器触发时执行的逻辑 : 处理逻辑即为如上的归因逻辑。
文章图片
当然也存在一些特殊情况,如某些用户对item存在跨窗口的行为,或者因为埋点上报的机制有关,比如首页息屏再开启,可能不会重新请求刷新item,而item是上一次推荐的结果。这个直接影响到实时归因样本的效果,可能对同一个用户同一个item来说,上一个窗口它可以归因到负样本,下一个窗口可能归因到正样本。通常我们可以在离线再进行一次小批量归因处理。
- 统计在groupby(recid,userid,itemId)后,是否存在>1 。 如果有,只保留Max(sum(ctr_label,cvr_label))那条样本。
有了实时特征快照Snapshot与实时Label,接下来就是进行实时样本拼接,主要工作包含Join关联,特征抽取处理,样本输出。
1、Join关联
在Flink任务中,拿到label的一条记录后,按照key=recId_userId_itemId查询kv,查到snapshot后拼成一条宽记录。
2、特征抽取
join关联拼接成宽记录后,但有的snapshot特征它是原始特征,需要进行特征抽取计算,计算的逻辑要保持与线上预估时候的一致。因此需要将线上的特征抽取打成JAR给到线下Flink中使用,这样保证了上面提到的线上线下特征计算一致性。
文章图片
3、样本输出
按照不同的训练格式进行Format,常见的如tfrecord、parquet,然后输出到hdfs中去。
4.4 Flink任务开发
由于实时样本的流程及任务比较多,想要快速实现实时样本快速落地,快速覆盖更多的业务场景,我们也在任务开发上也做了一些工作。
1、模板化开发,CICD
将任务抽象成四个,分别为:snapshot采集任务、Label任务、Join任务、Sampling采样任务(可选),并每个任务进行接口封装,实现模板化开发,简化任务开发流程,自动化创建工程、编译打包、创建任务,具备可持续集成CICD的能力。
文章图片
2、任务血缘
同一个场景,DAG串联,让任务血缘更加清晰。
文章图片
4.5 样本监控
? 实时样本生成过程是实时化的,涉及到环节特别多,而样本的稳定性、样本的内容如何对于样本最后的效果及其重要的。 所以样本监控是主动发现样本的异常情况,那么监控可分为系统监控、内容监控两个方面。这里重点强调内容监控。
- 系统监控:线上旁路系统监控、kv存储监控、Flink监控。
- 内容监控:拼接率监控、特征分布监控、时间跨度监控
- 拼接率监控:即label与snapshot能通过recId关联成功率,反映了样本真实有效的水平。
文章图片
- 特征分布监控:比如性别、年龄分布的监控,可以反映特征内容特征分布情况,来判断当前的样本是否可靠。
文章图片
- 时间跨度分布:推荐出去一次item到ua回流的时间差、snapshot旁路采集到落kv的时间差
文章图片
并且我们针对不同的模型更新频率进行了多种方案的测试,如下图,ABTest t1 组为离线日更模型,每天更新替换模型文件;t2 组为 2 小时更新模型,模型每两个小时增量训练;t8 组为 15 分钟更新模型,模型每 15 分钟增量训练模型。经过我们多次测试,发现模型更新越快效果更佳也更佳稳定。
文章图片
6 总结与展望 直播推荐业务有着其不同于其他业务的场景特色,推荐的不仅是一个 Item 更是状态,进而直播推荐需要更快、更高、更强的推荐算法来支持业务的发展。本文从工程落地角度来阐述模型实时化中实时样本生产的过程,分享我们云音乐在工程实践过程中遇到的一些问题的经验,当然不同公司的背景可能不同,找到一些适合自己能实际落地的才是正确的路线。接下来我们会做更多的业务覆盖,持续为业务实现突破,另外也希望从工程上为算法建模流程提供更多的能力。
参考文献
- Cheng Li, Yue Lu, Qiaozhu Mei, Dong Wang, and Sandeep Pandey. 2015. Click-through Prediction for Advertising in Twitter Timeline. In Proceedings of the 21th ACM SIGKDD International Conferen
- Xinran He, Junfeng Pan, Ou Jin, Tianbing Xu, Bo Liu, Tao Xu, Yanxin Shi, Antoine Atallah, Ralf Herbrich, Stuart Bowers, and Joaquin Qui?onero Candela. 2014. Practical Lessons from Predicting Clicks on Ads at Facebook. In Proceedings of the Eighth International Workshop on Data Mining for Online Advertising (ADKDD’14). ACM, New York, NY, USA, , Article 5 , 9 pages.
- 淘宝搜索模型如何全面实时化?首次应用于双11[1]
- 蚂蚁金服核心技术:百亿特征实时推荐算法揭秘.[2]
- 在线学习在爱奇艺信息流推荐业务中的探索与实践.[3]
- 蘑菇街首页推荐视频流——增量学习与wide&deepFM实践(工程+算法)[4]
[1]淘宝搜索模型如何全面实时化?首次应用于双11: https://developer.aliyun.com/...[2]蚂蚁金服核心技术:百亿特征实时推荐算法揭秘.: https://zhuanlan.zhihu.com/p/...[3]在线学习在爱奇艺信息流推荐业务中的探索与实践.: https://www.infoq.cn/article/...[4]蘑菇街首页推荐视频流——增量学习与wide&deepFM实践(工程+算法): https://zhuanlan.zhihu.com/p/...
本文发布自网易云音乐技术团队,文章未经授权禁止任何形式的转载。我们常年招收各类技术岗位,如果你准备换工作,又恰好喜欢云音乐,那就加入我们staff.musicrecruit@service.ne...
推荐阅读
- 深度学习|深度学习中的激活函数
- 机器学习基础|深度学习中的激活函数(一)
- Java学习心得(更新中)
- JavaWeb学习记录2——JDBC
- 华为云MRS基于Hudi和HetuEngine构建实时数据湖最佳实践
- fpga|FPGA SDRAM和DDR的学习(DDR部分)
- 计算机网络|《计算机网络——自顶向下方法》学习笔记——应用层
- 计算机网络|计算机网络学习——TCP/IP四层模型之应用层
- tcp/ip|TCP、IP 、HTTP——深入学习TCP协议
- dataframe|dataframe python写入数据_Pandas 学习 第9篇(DataFrame - 数据的输入输出)