蚂蚁金服核心技术:百亿特征实时引荐算法揭秘
作者头像
  • 2019-09-29 16:43:14 6

综述

在线学习因其能捕捉用户的动态行为,实现模型快速适应,已成为提升推荐系统性能的重要工具。然而,它对链路和模型的稳定性以及训练系统的性能提出了很高的要求。在基于原生TensorFlow设计在线推荐算法的过程中,我们发现了三个主要问题:

  1. 在一些资讯推荐场景中,需要大量长尾词汇作为特征,需使用featuremap对低频特征进行截断并连续编码,但这种方法耗时且激进。
  2. 使用流式数据后,特征规模无法预知,只能逐步增长。因此,需预留特征空间,否则会导致训练过程中出现越界现象。
  3. 模型稀疏性不佳,体积达到数十GB,导致上传和线上加载耗时长且不稳定。

为了应对这些问题,我们对TensorFlow进行了弹性改造,实现了弹性特征伸缩体系,提高了模型稀疏性,并显著提升了线上效果。此外,我们还实现了模型体积压缩90%,完善了特征管理和模型稳定性监控。

弹性改造及优势

在原生TensorFlow中,我们通过Variable声明变量,若变量超出单机承载能力,可使用partitionedvariables将其分配到不同的机器上。但这种方式需要预先指定维度,且声明后不可更改。由于推荐系统中大量使用稀疏特征,通常采用embeddinglookup_sparse一类的方法在一个庞大的Dense Variable中查找向量并求和,而不是进行矩阵乘法。开源TensorFlow限制了Variable在使用前必须声明维度大小,这带来了两个问题:

  1. 需要预先计算特征到维度范围内的整数值映射表,这一步操作通常在ODPS上完成,由于需要扫描所有出现的特征并编号,计算非常缓慢。
  2. 在在线学习场景中,为了容纳新出现的特征,需要预留一部分维度空间,并在线上不断修正映射表,否则会越界。

为了解决这些限制,我们对TensorFlow进行了优化,在服务器端新增了基于HashMap的HashVariable,其内存结构如下: plaintext 在声明该变量时,只需添加一句,其他训练代码皆不需改动: 每个特征都通过哈希函数映射到一个2的64次方大小的空间内。当需要计算该特征时,参数服务器会按需惰性创建并返回。通过这种方式,我们打破了固定维度的限制,实现了特征的动态添加和删除。在此基础上,我们完成了Group Lasso FTRL、频次过滤和模型压缩等一系列算法。

离线训练优化

经过这样的改造,在离线批量学习上,带来了以下变化: plaintext

在线训练优化

在线学习上,能带来如下变化: plaintext 除了性能有明显的提升外,其最大的优势是不需要提前请求空间,训练可以无缝波动运行。

特征动态增删技术

弹性架构的主要目的是特征优选,使模型自适应地选择最优特征,进而实现稀疏化,降低过拟合。本节介绍特征优选的两个核心技术:

  1. 流式频次过滤:对特征进入进行判定。
  2. Group Lasso优化器:对特征进行选择和删除。

Group Lasso 优化器

稀疏化是算法追求的重要模型特性。从简单的L1正则化和Truncated Gradient,再到讨论累积梯度平均值的RDA,再到目前常见的FTRL,这些都是针对广义线性模型优化问题提出的稀疏性优化算法。然而,它们都没有针对稀疏DNN中的特征embedding层做特殊处理。将embedding参数向量当作普通参数进行稀疏化,并不能达到在线性模型中能达到的特征选择效果,从而无法有效地进行模型压缩。

例如,当包含新特征的样本进入时,一个特征对应的一组参数(如embedding size为7,则参数数量为7)被激活,FTRL判断特征中的部分参数有效时,也不能安全地将该特征删除。因此,在L1和L2正则的基础上,引入了L21正则(group lasso)和L2正则(exclusive sparsity)。L21正则(group lasso)可以在嵌入层中删除或保留整组参数,从而提升模型泛化性。

在L21中,由于内层L2正则将一个特征的所有参数施加相同的约束,可以将整组参数清除或保留,由此决定嵌入层中某些特征对应的嵌入向量是否完全删除,提升模型泛化性。因此称为group lasso。

Group lasso是弹性计算改造后,模型性能提升和压缩的关键。在我们实现的优化器中,Variable以及accum和linear两个slot也是KV存储。

流式频次过滤

讨论完特征动态删除的方法后,我们再分析特征的准入策略。

频次过滤的必要性

在Google讨论FTRL的文章中提到,在高维数据中大部分特征都是非常稀疏的,在亿级别的样本中只出现几次。那么一个有趣的问题是,FTRL或Group FTRL优化器能否删除(lasso)极低频特征?

在RDA的优化公式中,满足以下条件的特征会被置0: plaintext 若在t步之前,该特征只出现过几次,未出现的step的梯度为0,随着步数增大,满足上述条件变得越来越容易。由此RDA是可以直观处理极稀疏特征的。但对于FTRL,要满足: plaintext 其中, plaintext 不仅与历史梯度有关,还与历史学习率和权重w有关。因此FTRL虽然也能处理极稀疏特征,但并没有RDA那么激进(此处还待详细地分析其下界,Group FTRL与此相似)。

由于FTRL在设计和推导时并未明确考虑极低频特征,虽然通过增大λ,的确能去除大量极低频特征,但由于约束太强,导致部分有效特征也被lasso,在离线实验中被证明严重影响性能。其次,对这些海量极低频特征,保存历史信息的工程代价是很高昂的(添加几倍的参数空间和存储需求)。

因此,我们提出能否在实时数据流上模拟离线频次过滤,为特征提供准入门槛,在不降低模型性能的基础上,尽量去除极低频特征,进一步实现稀疏化?

频次过滤的几种实现

由于默认的embeddinglookupsparse对特征执行了unique操作(特征归一化以简化计算),因此在参数服务器端是不能获取真实特征和标签频次的。需要Python端对占位符统计后,上传给服务器端指定的变量,优化器通过插槽获得该变量后作出结合决策。

最朴素的想法是模拟离线频次过滤,对特征进行计数,只有达到一定阈值后才进入训练,但这会破坏数据完整性。为此,我们提出了两种优化方案:

  1. 基于泊松分布的特征频次估计

    在离线shuffle后的特征满足平均分布,但对在线数据流,特征进入训练系统可视为泊松过程,符合泊松分布: plaintext 其中n为当前出现的次数,t为当前的步数,λ为单位时间发生率,是泊松分布的主要参数,T为训练总步数。 plaintext 为特征最低门限(即最少在T时间内出现的次数)。

    因此,我们可以通过前t步的特征出现的次数n,将t时刻视为单位时间,则 plaintext 。根据泊松分布,我们可以计算出剩余时间内事件发生的概率 plaintext 每次该特征出现时,都可按该概率 plaintext 做伯努利采样,特征在t步进入系统的概率用下式计算: plaintext 经过真实线上数据仿真,它能接近离线频次过滤的效果,其λ是随每次特征进入时动态计算的。它的缺陷是:

    • 当t越小时,事件发生在t内的次数的方差越大,所以会以一定概率误加或丢弃特征。
    • 未来总的训练步数T在在线学习中是未知的。
    • 频次过滤与优化器相结合,导致不能获得优化器的统计信息。
  2. 动态调整L1正则方案

    在经典的FTRL实现中,L1正则对每个特征都是相同的。这导致了在2.2.1中提到的问题:过大的L1虽然过滤了极低频特征,但也影响了模型的性能。参考各类优化器(如Adam)对learning_rate的改进,我们提出:通过调整L1正则系数,使得不同频次的特征有不同的lasso效果。

    特征频次与基于最大似然估计(MLE)的参数估计的置信度相关,出现次数越低置信度越低。如果在纯频率统计基础上加入一个先验分布(正则项),当频率统计置信度越低的时候,越倾向于先验分布,相应的正则系数要更大。我们通过多个实验,给出了以下的经验公式: plaintext 其中c是惩罚倍数, plaintext 为特征最低门限,这两者皆为超参, plaintext 是当前特征出现的频次。

    我们在实际环境中使用了动态调整L1正则的方案。在UV-CTR不降甚至有些微提升的基础上,模型特征数比不使用频次过滤减少了75%,从而从实验证明了频次过滤对稀疏化的正面作用。它的缺陷也很明显:特征频次和正则系数之间的映射关系缺乏严谨证明。

模型压缩和稳定性

模型压缩

在工程上,由于做了优化,如特征被优化器lasso后,只将其置0,并不会真正删除;在足够多步数后才删除。同时引入内存池,避免特征的反复创建和删除带来的不必要的性能损失。这就导致在训练结束后,模型依然存在大量0向量。导出时要进一步做模型压缩。

由于引入了HashPull和HashPush等非TF原生算子,需要将其裁剪后转换为原生TF的op。我们将这些步骤统称为图裁剪(GraphCut),它使得线上推理引擎不需要做任何改动即可兼容弹性改造。由于有效特征大大减少,打分速度相比原引擎提升50%以上。

我们将图裁剪看作TF-graph的静态优化问题,分为3个步骤:

  1. 第一遍遍历Graph,搜索可优化子结构和不兼容的op。
  2. 第二遍遍历,记录节点上下游和元数据,裁剪关键op,并将Variable的非0值转存至Tensorflow原生的MutableDenseHashTable。本步骤将模型体积压缩90%。
  3. 拼接新建节点,重建依赖关系,最后递归回溯下游节点,去除与推理有关的子图结构。

我们完成了残缺简约的图裁剪工具,在模型热导出时调用,将模型从原先的8GB左右压缩到几百兆大小,同时保证模型打分一致。

模型稳定性与监控

在线学习的波动性非常重要。我们将线上真实效果与实时模型生成的效果进行了严密的监控,一旦样本偏离过多,就会触发报警。

由于需捕捉时变的数据变化,因此不能用固定的离线数据集评价模型结果。我们使用阿里流式日志系统SLA最新流入的数据作为评价样本,以滑动窗口先打分后训练,既维持了不间断的训练,不浪费数据,同时尽可能高频地得到最新模型效果。

我们对如下核心指标进行了监控:

  • 样本监控:正负比例,线上打分值和online-auc(即线上模型打分得到的auc),产出速率,消费速率。
  • 训练级监控:AUC, User-AUC(参考备注),loss, 模型打分均值(与样本的正负比例对齐),异常信息。
  • 特征级管理:总特征规模,有效/0/删除特征规模,新增/插入/删除的速率。
  • 整体模型和调度:模型导出的时间,大小,打分分布是否正常,是否正常调度。
  • 业务目标:UV-CTR, PV-CTR(小时级更新,T+1报表)。

线上与训练目的之间的对应关系如下表: plaintext 通过HTTP接口,每隔一段时间发送监控数据,出现异常会及时产生钉钉和邮件报警。下图是对9月20日至27号的监控,从第二张图表来看,模型能较好地顺应当前数据流的打分分布。

User-AUC:传统的AUC并不能完全描述UV-CTR,因为模型很可能学到了不同用户间的偏序关系,而非单个用户在不同offer下的点击偏序关系。为此,我们使用了User-AUC,它尽可能地模拟了线上UV-CTR的计算过程,在真实实验中,监控系统的UV-CTR小时报表与实时模型输入的User-AUC高度一致。

工程实现与效果

目前算法已经在支付宝首页的多个推荐位上线。推荐系统根据用户的历史点击,融合用户画像和兴趣,结合实时特征,预测用户CTR,进而提升系统整体点击率。

我们以推荐位业务为例说明,其采用了经典的wide&deep网络结构,其稀疏部分包含数百级别的group(见下段备注1)。一天流入约百亿样本,标签的join窗口为固定时长。由于负样本占大多数,下游链路对正负样本做了1:8的降采样(见下文备注2)。

训练任务采用蚂蚁统一训练平台构建,并使用工作流进行定时调度,离线和在线任务的其他参数全部相同。Batchsize为512,每200步(即20万样本)评估一次结果,定时将模型通过图裁剪导出到线上系统。当任务失败时,调度系统会自动拉起,从检查点恢复。

该推荐业务的在线学习桶最近一周相比线上多模型融合最优桶提升4.23%,相比随机对照提升达34.67%。另一资讯推荐业务其最近一周,相比DNN基准UV-CTR提升+0.77%,PV-CTR提升+4.78%。实验效果相比有较大的提升。

备注1:group embedding是将相似的embedding特征分组,各自lookup求和后再concat,使得特征交叉在更高层进行。其设计是考虑到不同group的特征差异很大(如user和item),不应直接对位求和。

备注2:推理打分仅做点式排序,采样虽改变了数据分布但不改变偏序关系,因此并未在训练上做补偿。

未来工作

弹性特征已经成为蚂蚁实时强化深度学习的核心要素。这只是第一步,在处理特征空间按需创建问题后,它会带来一个充满想象力的底层架构,许多技术都可以在此基础上深入挖掘:在工程上,可以继续从分钟级向秒级优化,进一步提升链路实时性并实现模型增量更新;在算法上,我们正在探索如样本重要性采样,自动特征学习,在线线性规划与DNN的结合,优化器结合决策等技术。

由于在线学习是一个复杂的系统工程,我们在开发和调优过程中遇到了大量的困难,涉及样本回流,训练平台,模型打分,线上评价等一系列问题,尤其是波动性,但基本都逐一克服。为了保证线上结果稳定可信,我们在观察和优化两三个月后才发布这篇文章,希望和业界同仁一起交流讨论。

    本文来源:图灵汇
责任编辑: :
声明:本文系图灵汇原创稿件,版权属图灵汇所有,未经授权不得转载,已经协议授权的媒体下载使用时须注明"稿件来源:图灵汇",违者将依法追究责任。
    分享
引荐算法蚂蚁实时揭秘特征核心技术金服
    下一篇