文章作者:薛海霞 网易传媒
编辑整理:郑智献
内容来源:DataFun AI Talk
出品社区:DataFun
注:欢迎转载,转载请注明出处
大家好,今天分享的主题是:网易新闻客户端信息流的个性化推荐中,深度学习排序系统及模型,这也是我们团队在研发实践中的一些经验总结。
首先看一下在信息流场景中,个性化推荐的产品形态。左边是网易新闻的头条频道,右边是短视频频道,在经过召回、排序、重排之后信息流的最终呈现。作为流量较大的两个频道,我们在对它构建排序系统和排序模型的时候,通常会根据文章、视频、图像相关的内容特征,用户画像,场景,关注关系等对内容候选集进行排序。接下来,问题来了,如何衡量排序的效果呢?线上优化指标通常包括:CTR、阅读时长、留存、刷新次数等,其中最容易建模的就是点击率了,如果要用一个范式进行表示,就是图中这个概率公式。
接下来考虑一个问题,当面临一个具体的推荐业务场景时,如何高效地构建排序系统和排序模型?
一般来说,排序系统由三部分构成:pipeline、排序模型、模型计算服务。
线下阶段,pipeline 将客户端传过来的实时反馈日志,按照不同 session 进行聚合,曝光和点击数据进行 label match,然后经过特征填充生成原始样本,接着特征预处理后,生成用于模型训练所需的训练样本。线上阶段的 pipeline 跟线下基本相同,不同之处在于各自依赖的上游数据源,一个来自客户端的实时反馈,一个来自召回请求及预测候选集。
下图为排序系统的基本流程。然而,在具体实践中,处于对各模块的计算平台、流程一致性、组件通用性以及模型训练性能等各方面的考虑,我们需要对这个 basic 的排序系统做对应的改造。而我们的改造主要集中在两块:特征预处理 & 模型。
为什么要对基础流程进行改造以及具体如何改造?这里需要了解一下整个排序系统研发过程中我们所面临的核心问题和挑战。
首先关于 pipeline,我们知道,排序模型在训练和预测之间的差异会对模型的准确性产生很大的影响。而差异的产生来源于:特征、特征处理。只有保证 pipeline 线上线下的一致性,才能保证线上模型能够得出正确的预测结果。特征方面,我们将进行在线预测时的特征保存下来,然后填充到线下的训练样本中,这样就保证了训练和预测阶段的特征是一致的;特征处理方面,实践中一般采用 tf.data 和 feature_column 进行读取样本数据和特征处理,这种做法虽然避免了线上线下一致性的问题,但是这种基于原生 TensorFlow 的实现也带来了线上计算和线下训练的性能问题。如何在特征处理模块即保证线上线下的一致性又能同时保障系统整体的性能,这是我们所面临的一个挑战。
然后对于深度学习排序模型,我们需要支持网易新闻不断增多的业务需求,这就要求排序模型框架有足够的通用性和可扩展性,以支持模型的快速迭代和迁移。同时要求模型有足够好的灵活性,以支持业务定制化。
针对 pipeline 中的特征处理模块以及排序模型框架,我们做了如下优化:
首先特征处理库,将模型训练阶段跑在 CPU 上的特征处理任务转移到 pipeline 阶段的大规模计算集群上 ( Haddop Streaming ),以免影响模型训练阶段对 GPU 计算的影响,同时也避免了训练阶段的重复计算。基于这样的思想,我们将特征处理库从模型中独立出来,线下阶段在 pipeline 中而非模型中调用此库,线上阶段在模型中调用此库,但是这样会带来线上线下不一致的问题。我们的解决方案是将特征处理库独立出来之后,线上和线下调用对应的我们自定义 OP ( 样本读取和数据处理模块 ) 即可。
关于自定义 OP,我们主要对样本读取和数据处理模块进行了对应的改造。原生 TensorFlow 通常使用 tf.data 进行数据读取,通过 feature_column 进行数据预处理,但是在大规模数据场景中,存在性能瓶颈,因此我们重新实现了数据读取和预处理模块,并优化了性能;另外,为了支持多值带权的特征,我们使用了自定义的样本格式,而原生接口对样本格式的解析也并不友好,所以自定义了解析模块;或者想要手动融合一些操作或 TensorFlow 原生不支持的一些操作,也需要我们通过自定义 OP 来实现。
完成 pipeline 的底层改造之后,如何快速使用特征处理框架呢?很简单,在配置文件中将特征处理流程描述出来即可。
在配置文件中,对于每个特征,描述它对应的特征处理流程。如下图,圆形代表原始特征值和经过各个算子处理后的特征中间值,方形表示特征处理库内的算子,所以特征处理流程可以视为各个 DAG 图。右边的两个例子是用 DAG 图来描述特征处理流程的相关配置。
1)特征 age,先经过校正算子(将上游传入为0的 age 特征字段校正为 Null)得到中间值 age_corrected,再经过算子 CDF 或者 Bucket 处理得到可以作为模型输入的特征值 age_cdf 和 age_bucket。
2)特征 Doc_POI 和 User_POI,经过算子 StrToVec 处理之后再经过相似度算子,得到二者的相似度。
以上经过一系列算子处理后的特征将作为排序模型的输入。若在训练样本中增加特征,只需要在特征处理框架中实现对应的算子,并在配置文件来描述新增特征的处理流程。
我们使用 DAG 图来描述特征处理过程的思想,源自于 TensorFlow,先实现底层算子 API,然后将对特征的处理流程构建 Graph,再通过上层 API 接口 session.run 启动运行 Graph。而解析配置文件的过程就相当于解析 DAG 图。
至此,完成 pipeline 的优化。回看一下,排序系统经过改造之后的模样。排序系统变成由一个特征处理框架进行特征处理,而这个框架既可以跑在线下阶段的 hadoop streaming 上,也可以跑在线上 TensorFlow 深度学习框架之上。
深度学习排序模型方面,同样面临一系列的问题。如何构建通用 & 可扩展的推荐算法库框架,来支持新的业务场景,保证模型的快速迭代?如何保证框架的灵活性,根据变化的业务需求对模型做定制化?如何通过高度可配置的方式来构建模型?
先看几个经典的深度学习排序模型的网络结构,左边是 DNN,右边为它的几种变体:FNN、PNN、Wide&Deep。虽然4种模型结构各异,但抽象一下会发现共通之处,基本单元包括:linear、cross、deep 模块,从层次结构来看包括: 特征表征层、特征交叉层、全连接层,其中特征交叉既可以通过人工方式 ( Wide 部分 ) 也可以通过内积或矩阵乘法的方式来进行。经过以上分析,其实提供了一种思路:我们可以将深度学习模型的各个基本单元进行模块化来构建模型。
沿着以上的抽象和分析,接下来用两个网络结构相对复杂的深度学习排序模型来进行验证。
下图左边为 DCN ( Deep & Cross NetWork ) 模型,右边为 DIEN ( Deep Interest Evolution Network ),可以观察到,层次结构同上:特征表征层、特征交叉层、全连接层,与上面不同的是:特征交叉不再受限于人工交叉或各种方式的2阶交叉,而是让 NN 网络去进行更高阶的特征交叉表达。网络的基本单元仍然包括:linear、cross、deep。
根据前面对几种深度学习排序模型的网络结构的分析,总结起来如下表所示:
输入层到 Embedding 层,该过程把大规模的稀疏特征通过 embedding 操作或其它表征方式映射为低维稠密的 embedding 向量;对于生成的 embedding 向量,我们可以对它进行包括特征交叉在内的其它各种操作,比如:concat、sum or average pooling 等操作,大部分排序模型改造主要集中在这一层;最后将经过各种处理之后的 embedding 向量输入至全连接框架中。
有了深度学习排序模型的通用范式,我们便针对网易新闻推荐业务设计了通用模型框架。
用层次结构图来表示的话,即下图:
与特征处理框架的思路相同,这里对模型框架也采用子模块可配置化的方式。比如输入特征为用户点击历史 clk_history 和目标 docid,首先两个特征经过 Embedding 后得到对应的向量表示,再经过 Attention 得到权重分配,随后 Pooling 操作将变长向量转化为定长,最后输入全连接网络。底层实现各个模型子模块,具体的业务场景对应的模型只需在其配置文件中将模型结构描述出来即可。
经过对 pipeline 及模型框架的优化,我们的排序系统最终能够很好的解决了 pipeline 线上线下一致性、系统整体的性能问题、深度学习模型框架的通用性&可扩展、灵活性等问题。
同时模型效果在各个推荐业务中相对 baseline 也有比较显著的提升。