推荐系统中模型训练及使用流程的标准化

图片1.png

文章做者:梁超 腾讯 高级工程师python

编辑整理:Hoh Xilweb

内容来源:DataFun AI Talk算法

出品社区:DataFun数组

注:欢迎转载,转载请在留言区内留言。框架

导读:本次分享的主题为推荐系统中模型训练及使用流程的标准化。在整个推荐系统中,点击率 ( CTR ) 预估模型是最为重要,也是最为复杂的部分。不管是使用线性模型仍是当前流行的深度模型,在模型结构肯定后,模型的迭代主要在于特征的选择及处理方面。于是,如何科学地管理特征,就显得尤其重要。在实践中,咱们对特征的采集、配置、处理流程以及输出形式进行了标准化:经过配置文件和代码模板管理特征的声明及追加,特征的选取及预处理等流程。因为使用哪些特征、如何处理特征等流程均在同一份配置文件中定义,于是,该方案能够保证离线训练和在线预测时特征处理使用方式的代码级一致性。函数

▌一. 推荐系统oop

1.业务简介编码

图片2.png

这是咱们的产品每天快报,会涉及首页以及数十个子频道,对于这些频道咱们都须要作召回以及排序模型。如何高效的管理这么多的频道呢?咱们就须要一个很好的系统来管理全部的特征和模型。spa

2.推荐系统流程设计

图片3.png

简单回顾下推荐系统的流程,整个推荐系统须要从数以百万计的内容池中筛选出数以十计的文章推荐给最终的用户。在这个过程当中主要涉及三个步骤:

第一步,从百万量级中经过环境特征,用户特征,物品特征等信息来找出千级别的文章。

第二步,经过排序模型来预估点击率或者预估用户对这篇文章的偏好程度。

最后,经过一些运营规则和多样性方面的考量 ( 好比用户喜欢王者荣耀,但不能推荐给用户都是这类的视频或文章 ),最终呈现给用户10篇左右的文章。

3.经常使用推荐模型

图片4.png

经常使用的推荐模型有 LR、FM、DNN、W&D、DeepFM、DIN 等模型,对于推荐系统,不管使用哪一种模型,都须要如下几个模块:

样本搜集:训练模型离不开大量的训练样本,因此须要进行样本 ( 特征和标签 ) 的搜集;

特征配置:实际的推荐系统中会有上百个特征供模型选择,在模型版本迭代的过程当中,有些特征会被舍弃,有些特征会新加进来;于是,咱们就须要配置搜集哪些特征、使用哪些特征,在迭代过程当中,还须要保证现有模型训练和预测服务的稳定性;

特征处理:对于每一个特征,好比用户 ID,该如何离散化成一个最终使用的int型的数字,就须要通过必定的特征处理;

模型训练&模型预测:特征处理完以后,如何喂给模型训练程序以及线上的预测模型,如何在修改了特征配置以后,无需人工修改训练和预测代码,从而下降工做量、减小 bug 的引入,都是咱们须要考虑的工做。

4.排序流程图

图片5.png

上图为排序系统的流程图:

以用户 ID 特征 ( userID ) 为例,在线预测时,会首先把 userID 填入某一个变量中,并经过某种 hash 函数把它变成整数类型 ( 好比 C++ long 类型 ),再输入到模型中;与此同时,咱们须要把在线的特征记录到日志中,做为模型训练的样本。

离线训练:将特征日志和用户行为 ( 是否点击、是否点赞、消费时长等 ) 日志结合起来,造成最终带有标签的训练样本,再经过一样的特征处理流程,造成训练样本进行模型的训练。

▌二. 推荐系统中模型迭代的痛点

与研究中给定的数据集不一样,推荐系统中的模型须要不断地迭代调优。在平常的工做中,咱们经常须要在保证现有模型服务稳定的前提下,不断地增长新的特征,训练新的模型。因而,咱们会面临下图所示的诸多问题。

图片6.png

▌三. Write once,run anywhere 的特征处理标准

要设计一套特征处理的标准,咱们首要面临的问题是如何描述特征处理的流程 ( C++ 代码?protobuf?XML?)。基于如下几点考虑:

  1. 尽可能减小人工写代码的量;
  2. 易于查看和维护;
  3. 易于迭代,咱们设计了一套基于 CSV 格式的特征处理框架。

首先,咱们来看一下,在模型训练方面,业界是怎么作的。

图片7.png

在工业界,对于的模型训练和预测部分,TensorFlow 等框架已经作得比较完善了。那么,TensorFlow 是如何定义整个数据流的呢?它是用计算图来定义的,以两个变量相加为例,代码很是简单,若是转化为 pb ( 上图左下角 ) 也只有这几行。但实际上呢?这里给出两组数据:229行,2.6KB;1200行,13.5KB。229行,2.6KB 为上面的加法操做转化为实际的 pb 的大小,而若是有10个加法操做的计算图,则须要1200行,13.5KB。因此,用通用的计算图来定义特征的处理流程,虽然很灵活,但却很是不利于人来阅读和管理。在系统设计的过程当中,咱们指望全部的特征定义及其处理流程均可以一目了然的看到。

图片8.png

如上图所示,在考虑到样本搜集、特征配置、特征处理、模型训练、模型预测等需求后,咱们选用了 CSV 来管理整个过程,CSV 中的每一行定义了一个特征,包含了特征的名称、类型、序列化后的位置、处理方式等信息,且能够灵活地增长列来定义新的功能。

下面沿着以前提到模型迭代的痛点,依次看下咱们是如何经过一个 CSV 来解决的:

痛点1:快速增长特征

图片9.png

首先旧的流程中,咱们都须要声明一个变量来临时存储在线所须要的特征,编写特征填充代码,同时还须要编写特征变换代码、特征序列化代码、特征反序列化代码以及特征监控代码。以上种种功能,都须要针对每个变量进行独立的编写。

咱们新的流程中,只有在 CSV 中定义变量处理方式和编写特征填充代码两个部分:

如上图右下角有4个特征,假设用户 ID,用户性别以及 itemID 是已有的特征。如今,咱们须要新加一个特征,咱们就会在这个表格第四行新加用户 Tag 特征,同时定义下这个特征的类型以及在日志中的位置,是属于用户特征仍是物品特征,剩下的步骤则经过一个 python 脚本和一个代码模板来生成新的 C++ 程序自动完成。增长了这个变量后,特征日志中会增长上图右上角所示的 tag 信息。

痛点2:在线、离线特征的一致性

图片10.png

模型训练所需的特征须要和在线预测时的特征彻底一致。在工业界中,通常会将在线特征 dump 到日志中,训练时结合标签生成完整的训练样本,从而保证在线、离线特征的一致性。然而,旧的流程中,针对每一个特征的序列化,都须要手写代码,反序列化亦然,这就大大增长了算法工程师的工做量,且容易引人 bug。

咱们的作法是把特征的类型进行了标准化,抽象出4种标准的类型 ( 整形、稀疏整形、字符串、稀疏字符串 ),它们都继承自基类 Feature,这个类会包含特征处理的方方面面,如生成特征、序列化、反序列化。咱们只须要保证每种特征类型的特征的序列化和反序列化函数是正确的,就能够保证在线的特征和离线特征是彻底一致的。

痛点3:特征配置及特征处理

① 特征配置

图片11.png

特征的配置包含两方面的内容:搜集哪些特征及模型使用哪些特征。

在实践中,咱们须要保证已有模型的稳定性,并不断地搜集新的特征。为此,咱们将特征搜集服务与 ranking 服务相分离,但复用特征填充代码。服务分离有两个好处:

  1. 在特征搜集服务中新增所需搜集的特征无需更改 ranking 服务;
  2. 在 ranking 阶段,通常有数千个物品,而咱们的特征搜集服务只搜集返回给用户的十来个物品的特征,大大减少了日志量。

搜集到的特征是模型训练和预测所需特征的超集。当须要进行模型的训练或预测时,咱们只需在 CSV 中使用 is_using 列来控制是否使用某一特征。须要进行模型迭代时,只须要 CSV 中的配置,并从新生成一份代码便可。此外,交叉特征也只须要在 CSV 中进行配置便可,而且,因为对特征类进行了标准化,咱们能够轻松支持任意个特征的交叉。

② 特征处理

图片12.png

若是咱们像 TensorFlow 那样定义一个很是灵活的计算图的话,虽然是很好的,可是不利于模型的管理。所以,咱们把单个特征的处理抽象成了3步:特征填充 ( 手工编写代码或经由其余特征变换而来 );特征值和特征权重变换;特征值和特征权重向量联合变换 ( 支持屡次变换 )。

仍以 tag 特征为例:

如上图所示,通常状况下,每一个特征都会有 tag 值 ( key ) 和权重 ( value ),咱们会先将 key 进行离散化,好比 hash;并对权重作必定的变换,好比设为1;以后,对整个 key、value 向量进行联合变换,好比,key 乘以10,value 不变 ( 举例用,通常不作变换 )。

此外,有时咱们须要一些统计信息,好比 tag 分数大于0.5的 tag 数量。那么,就能够真正用到特征值和特征权重向量联合变换,咱们只须要在这一步统计整个 key、value 向量中 value>0.5 的个数便可。

图片13.png

那么,若是咱们既须要保留 tag 信息 ( 第一种变换 ),又须要 tag 分>0.5的个数做为特征呢?咱们只须要在 CSV 中从新声明一个变量,并在特征赋值部分将其特征设为第一个变量的内容,并进行相应变换便可 ( 实际中,能够直接在赋值部分写统计函数便可 )。

痛点4:支持多种模型

图片14.png

咱们的系统支持两种训练样本格式:libsvm 和 sparse tensor 数组。

其中,libsvm 是线性模型的主流格式;而 sparse tensor 则是 tensorflow 中的支持稀疏特征的主流格式 ( tensor 能够视为 sparse tensor 的特例 )。

以上图中的样本 ( 省略了标签部分 ) 变换过程为例,该样本中包含两个物品信息,于是会生成两条样本。对于 libsvm 格式,只须要将每一个特征变换后的结果存储到一个向量中便可。对于 TensorFlow 等框架,内部都是用矩阵来进行运算的。矩阵又会分为两种:稠密的矩阵和稀疏的矩阵。同时,稠密矩阵又是稀疏矩阵的特例。因此,咱们会将全部的特征所有以稀疏矩阵的形式喂到模型中,方便程序统一的处理。仍是以稀疏特征 tag 为例,该特征会用户两个稀疏矩阵 ( 特征值矩阵和权重矩阵 ) 来进行表示,且共用下标和形状,特征值就是刚刚通过离散化的特征值,这里没有用到权重,因此所有设为1。咱们能够看到,虽然它是一个稀疏矩阵,可是它是一个2x2的矩阵,每一个都有元素,因此能够用稀疏矩阵来表示稠密矩阵。

图片15.png

有了训练样本以后,如何进行模型训练?咱们提供了3种方式:

经过将 CSV 转换为一个 hpp 文件以后,咱们会编译出一个专门用于将原始特征日志转换为训练样本的可执行程序,并经过 hadoop streaming 方式,生成 libsvm 格式的训练样本。这种方式有两个缺点:增长了流程的复杂性,且耗费存储资源。原始的特征日志至关于进行了压缩 ( 多个物品共用一组用户特征 ),展开以后至关于每条样本对应的用户特征是重复的,且会生成大量的交叉特征,这会致使文件的大小增长10倍以上。

第二种形式,则是将生成的 hpp 文件经过 JNI 编译成一个 SO,能够直接在 Spark 上调用,生成 libsvm 格式的 RDD 进行训练,该方案避免了训练样本占用磁盘空间的问题,但流程仍较为复杂。

最后,则是咱们目前使用的动态编译 so 的形式。因为 tensorflow 模型训练程序是 python 编写的,而咱们的 CSV 转 hpp 程序也是 python 编写的,于是,咱们在使用 tensorflow 训练前,会检测 CSV 是否更新,而后动态的决定是否从新编译自定义的 tensorflow 算子。在训练时,该算子会将原始特征日志转换为 sparse tensor 格式的训练样本。此外,使用配置文件还有一个好处:训练程序还会读取 CSV 中额外的配置信息,从而知道有多少个特征每一个特征 embedding 的维度、大小,是否须要 attention 机制等信息,供模型训练使用。

痛点5:特征监控

图片16.png

因为推荐系统的复杂性,咱们须要对各个环节进行必要的监控,从而保证出现问题时能够及时知道。以 tag 兴趣分分布为例:

相似于特征的变换流程,咱们会在 CSV 中配置监控函数。如上图所示,为 tag 特征的 value 分桶监控过程。

首先,对 tag 的兴趣分进行分桶,好比这里有两个兴趣分,咱们能够把它们分红10段,0.9~1是一段,0~0.1是一段等等,再把这些序列化后的字符串经过上报系统进行上报,而后展现在右边的曲线中。经过这些曲线,咱们能够对比同一区间内特征数量的同比、环比等信息,从而在特征分布变化剧烈时及时进行告警。

痛点6:样本过滤 & 加权

图片17.png

咱们实际的特征格式如上图左侧所示,咱们会在用户特征和物品特征后面分别加上几列,会统计某一段时间内用户或物品的曝光次数,点击次数,以及消费时长。

若是某一用户短期内曝光超过1000次,或者消费时长特别长,或者点击率特别高,则多是机器刷量的,咱们就会将这些样本过滤掉。

此外,对于一条样本,即便用户点击了,若是消费时长太短,咱们也会将其设为负样本或者过滤掉,或者设一个比较小的权重。

Ranking 流程图

图片18.png

最后看一下完整的系统流程图:

首先经过特征配置文件和一个代码模板,管理全部的特征。

经过脚本配置文件生成 hpp 代码,模型预测时使用该代码进行特征的变换。

在重排序肯定要展现给用户哪些物品以后,重复一遍特征填充的过程,而后再把可能产生曝光的物品特征序列化到特征日志中。

在离线过程当中,将特征日志经过反序列化的方法,从新填充整个特征类。经过一样的特征变换代码,变换成和线上彻底一致的特征 ( 针对同一版模型 ),等到样本标签从客服端返回以后,生成最终的训练样本,供模型训练。

▌四. 总结

咱们将推荐系统中特征处理的流程进行了标准化,该标准化体如今特征类型的标准化和特征处理过程的标准化两方面。咱们经过一个 CSV 文件完成了特征配置、特征搜集、特征处理、模型训练 ( 部分 )、模型预测 ( 部分 ) 的管理工做,大大下降了人工的编码量,提升了工做效率、下降了人为引入 bug 的可能性。

分享嘉宾

640.webp.jpg

梁超

腾讯 | 高级工程师
——END——

更多精彩内容欢迎关注:

640.webp.jpg

相关文章
相关标签/搜索