TensorFlow Ranking框架在海外推荐业务中的实践与应用

2020 年 11 月 16 日

TensorFlow Ranking框架在海外推荐业务中的实践与应用

前言

在当今互联网世界,推荐系统在内容分发领域扮演着至关重要的角色。如何尽可能的提升推荐系统的推荐效果,是每个推荐算法同学工作的核心目标。在爱奇艺海外推荐业务,引入TensorFlow Ranking(TFR)框架,并在此基础上进行了研究和改进,显著提升了推荐效果。本文将分享TFR框架在海外推荐业务中的实践和应用。


01 算法的迭代:从传统 CTR 预估到 LTR


长期以来,在推荐系统排序阶段广泛应用的 CTR 预估算法的研究重点在于,如何更加准确的估计一个用户对于一个 item 的点击概率。在这类算法中,我们将一组同时曝光在用户面前的 items,当做一个一个单独的个例看待,将用户的特征、环境特征和一个一个 item 的特征分别组合成为一条条训练数据,将用户对这个 item 的反馈(点击、未点击、播放时长等)作为训练数据的标签。这样看似合理的问题抽象其实并不能准确的表征推荐场景。


严格来讲,排序问题的本质(尤其是以瀑布流形式呈现的业务)并不是研究估计一个用户对于一个单独的 item 的点击概率,而是研究在一组 items 同时曝光的情况下,用户对这组 items 中哪个的点击概率更大的问题。


Learning-To-Rank(LTR)算法正是为解决这个问题而出现的。LTR 算法在训练时采用 pairwise 或者 listwise 的方式组织训练数据,将一组同时曝光在用户面前的 items,两两(pairwise)或者多个(listwise)items 和用户特征环境特征共同组成数据对,作为一条条的训练数据。相应的,在评估模型的指标上,LTR 算法更多采用 NDCG、ARP、MAP 等能够反映 items 顺序影响的指标。


同时,由于 LTR 算法的这种训练数据组织形式,使得这类算法在用户量相对不大的场景下,更容易取得比较好的效果。同样得益于这种数据组织形式,也很方便的实现更好的负样本采样。


(关于推荐业务中采样和模型评估指标之间还有一个有趣的研究可以参考,2020 KDD Best Paper Award ,On Sampled Metrics for Item Recommendation)


02 框架的设计:TensorFlow Ranking(TFR)


TensorFlow Ranking(TFR)是 Tensorflow 官方开发的 LTR 框架,旨在基于 TensorFlow 开发和整合 LTR 相关的技术,使开发人员可以更加方便的进行 LTR 算法的开发。


在实际使用过程中,可以体会到 TFR 框架为我们带来的收益。框架内抽象出了训练中不同层级的类,并开发了相关的 loss 函数,以方便我们进行 pairwise 和 listwise 的训练,同时整合了 arp、ndcg 等模型评估的 metrics,再结合 tf 高阶 api(Estimator),可以非常方便快捷的进行开发,而不用挣扎于各种实施上的细节。



如上图所示,蓝色框图中是在使用 TensorFlow Estimator 时,modelfn 参数内需要自己设计和开发的算法模型模块。在这个 modelfn 中,需要自行设计模型结构(Scoring Function),然后用模型计算的 logit 和 label 来计算 Loss 和 Metrics,最后利用 Optimizer 来进行模型的优化。图中,红色虚曲线下方的部分为使用 TFR 框架的整个流程。


从图中可以看出,其实 TFR 框架主要是做了两方面的工作:


  1. 把原有model_fn中Scoring Function和Loss、Metrics的计算进行了拆分,然后将原有流程中我们自行实现的Loss和Metrics替换为TFR框架中的LTR相关的Loss和Metrics。

  2. 为了配合TFR框架中的LTR相关的Loss和Metrics来实现LTR的训练,训练数据需要以listwise的形式组织。但由于需要使用原有model_fn中Scoring Function,在数据输入的部分通过LTR框架中的数据转换函数来对模型输入的训练数据进行转化,使得以listwise形式组织的数据能够利用Scoring Function来计算logit。


所以,在 TFR 框架中,从数据到模型训练完整的流程是:训练数据--》用户定义 featurecolumns--》transformfn 特征转换--》Scoring Function 计算 score--》rankinghead 的 lossfn 计算 loss–》rankinghead 的 evalmetric_fns 计算评价指标–》optimizer 进行优化。


从使用层面看,TFR 框架就是做了上面两件事,看起来似乎并不复杂。但是从框架开发的角度上看,为了实现上述流程,TFR 框架内在 losses.py 和 metrics.py 中开发了多个 LTR 相关的 Loss 和 Metrics,在 data.py 内实现了读取和解析以 listwise 形式组织数据的 tfrecords 文件的工具,还在 feature.py 中开发了兼容 TensorFlow 特征转换函数的特征处理工具。最后通过 head.py 和 model.py 中的类对上述功能进行了层层封窗和抽象,并与 TensorFlow Estimator 很好的结合起来。


具体一些来看,代码组织上,TFR 框架主要这样实现的:


第一:


tfr 通过


tfr.model.makegroupwiserankingfn 来对 Estimator 的 modelfn 进行了整体的封装。我们原有的基于 tf 的开发,在 Estimator 的 modelfn 这个参数内需要定义包括 Loss、Metrics 在内的完整的模型函数,但是在 tfr 这里就不需要了,makegroupwiserankingfn 会整体返回一个 Estimator 接收的 model_fn。


第二:


tfr.model.makegroupwiserankingfn 函数的第一个参数 groupscore_fn,这里是需要传入我们设计和开发的模型结构(Scoring Function),但是这个模型是之前我们提到的,只需要计算出 logit 的模型。


第三:


tfr.model.makegroupwiserankingfn 函数的第三个参数 transformfn,对应调用 feature.py 中开发了兼容 TensorFlow 特征转换函数来对以 listwise 形式组织的数据(由 data.py 中的工具读取进来的 Dataset)进行转换,确保输入 Scoring Function 的数据格式正确。


第四:


tfr.model.makegroupwiserankingfn 函数的第四个参数 rankinghead,对应调用了 tfr.head.createrankinghead 函数,里面的三个参数分别定义了 loss、metrics 和 optimizer。loss 和 metrics 分别从 TFR 的 losses.py 和 metrics.py 中选择我们需要的,而 optimizer 还是使用 TensorFlow 中的 optimizer。


以上就是 TFR 框架的整体架构,其实这个框架整体设计和代码实现,还是非常优雅和巧妙的。


03 遇到的问题和实践


TFR 框架的精巧设计和实现解决了我们基于 TensorFlow 做 LTR 算法中的 80%到 90%的问题。但是作为一个 2019 年才发布第一个版本的框架,TFR 还是存在一些待优化的地方。


在分享 TFR 框架上的实践前,首先介绍一下 TFR 框架的版本情况,目前 TFR 框架发布的版本中,0.1.x 版本支持 TensorFlow 1.X 版本,而 0.2.x 和 0.3.x 版本都只支持 TensorFlow 2.X 版本。考虑到 TensorFlow 2.X 版本还存在一些不确定性(如前段时间爆出使用 Keras 功能 API 创建的模型自定义层中的权重无法进行梯度更新的问题。


https://github.com/tensorflow/tensorflow/issues/40638),目前大量的算法开发人员其实还在用 TensorFlow 1.X 版本。我们目前也在使用 TensorFlow 1.X 版本,所以本文介绍的内容,描述的问题和给出的解决方案,都是基于 TensorFlow 1.14 版本,对应最新的 TFR 0.1.6 版本。


我们最开始使用 TFR 框架是 2019 年年中的时候,当时 TFR 框架的最新版本是 0.1.3。在使用的过程中,我们发现这个版本无法支持 sparse/embedding features。但是推荐的特征中,稀疏特征是不可或缺的一部分,并且可能大部分特征都是稀疏的,所以我们不得不放弃使用。但是很快,在稍后发布的 0.1.4 版本中这个问题就得到了解决。


我们正式开始使用 TFR 框架是从 0.1.4 版本开始的。但是到目前最新的 0.1.6 版本,还是有两个我们不得不用的特性还是没有在 TFR 0.1.x 版本上得到支持:


  • 训练过程中无法实施正则化


如前所述,TFR 框架通过 makegroupwiserankingfn 来对 Estimator 的 modelfn 进行了整体的封装。


我们自己设计和开发的模型(Scoring Function),定义了网络,输入输出节点,最后只需要输出一个 logit。这个和传统 TensorFlow Estimator 下 modelfn 模型开发不一样,传统的模型不仅仅要输出一个 logit,模型里面还需要定义如何计算 loss,怎样优化等内容。但是 TFR 框架将这部分内容已经进行了封装和整合,所以这里的 scorefn 就不需要这些了。这就带来一个问题,原来的模型设计中,我们可以直接拿出网络中需要正则化的参数,放在 loss 的计算中进行优化就可以了。但是使用 TFR 框架后,由于模型的设计和正向的计算在我们自己设计的模型函数中,而 loss 的计算在 TFR 框架内(rankinghead 中的 lossfn)进行,这样就没办法加入正则化项了。


已经有人提出了这个 issue,但是也没有很好的解决方案:(https://github.com/tensorflow/ranking/issues/52


当我们使用比较复杂的网络时,正则化是我们优化过程中必不可少的一环。不加入正则化项进行优化,将无法避免的陷入到严重的过拟合中,如下图所示:



为了能够方便的利用 TFR 框架其他功能,我们深入 TFR 框架源码中试图解决正则化问题。正如上面分析,TFR 框架无法实施正则化的原因在于,模型(Scoring Function)是我们自己设计和开发的,但是 loss 的计算是 TFR 框架帮我们封装好的。所以解决这个问题的核心就是如何在我们自己开发的模型中取出需要正则化的参数并传递给 TFR 框架中计算 loss 的部分就可以了。在 TFR 框架中,我们的模型计算好的 logit,是通过 rankinghead 的 createestimatorspec 方法,把 logit,labels 与 TFR 框架中定义的 loss 函数整合一起,来完成整个优化过程的。而在 0.1.5 版本的 TFR 框架中,这个 createestimatorspec 方法其实已经支持传入 regularizationlosses 了(估计未来版本一定会支持),而由于初始化 ranking_model 对象(我们的 Scoring Function)。


GroupwiseRankingModel 不支持我们拿到自己模型的正则化项,所以才无法实现。


理论上,只要我们重写 TFR 源码中 GroupwiseRankingModel 类的 computelogits 方法,就能够让 TFR 支持正则化了。具体的代码上如何处理可以参考这里(如何解决 TensorFlow Ranking 框架中的正则化问题)。在加入正则化项后,跟上图同样的模型训练时就没有那么严重的过拟合现象了:



  • 特征输入不支持Sequence Features


前边介绍过,在 TFR 框架中,模型输入的特征分为 contextfeatures 和 examplefeatures,分别对应于一次请求公共的特征(上下文特征、用户特征等)和 item 独有的特征。以 listwise 形式组织的数据(一般是由 data.py 中的工具读取 tfrecords 文件生成的 Dataset)需要经过 TFR 的特征转换函数(transformfn)转换后,再送入到我们的模型(Scoring Function)中。


而目前的特征转换函数(transformfn)只支持 numericcolumn、categoricalcolumn 等经典类型特征的转换,尚不支持 sequencecategoricalcolumn 类型特征的转换。要解决的 TFR 无法支持 SequenceFeatures 问题,主要是对 transform_fn 特征转换这一步进行调整。


在 transformfn 中,特征转换时用到的 tfr.feature.encodelistwise_features 和


tfr.feature.encodepointwisefeatures 函数都在 feature.py 中定义。


这两个函数的作用是在 listwise 或者 pointwise 模式下利用用户定义的 feature columns 生成输入模型的 dense tensors。这两个函数都是调用 encodefeatures 函数来具体执行 feature columns 生成输入模型的 dense tensors,而 encodefeatures 函数只支持 numericcolumn、categoricalcolumn 等经典类型特征的转换,尚不支持 sequencecategoricalcolumn 类型特征的转换。通过这里的分析,我们可以看到特征转换的过程全部是在 feature.py 中完成的,因此,解决 TFR 框架支持 SequenceFeatures 的问题核心思路就是修改 feature.py 中的几个涉及特征转换的函数,使这些函数能够实现 sequencecategoricalcolumn 类型特征的转换。


我们用到的 sequencecategoricalcolumn 类型特征都在 contextfeatures 中,所以我们的思路是,在处理特征的转换时,我先将 sequencecategoricalcolumn 从其中拿出来,处理完经典特征的转换后,单独增加一段处理 sequencecategoricalcolumn 转换的代码。待转换完成后,再合并回 contextfeatures 中,最终仍然保持 contextfeatures 和 examplefeatures 两部分输入到模型中。具体的代码上如何处理可以参考这里。(让 TensorFlow Ranking 框架支持 SequenceFeatures)


以上两个问题的解决方案都涉及到 TFR 框架对源码的修改。稍有不慎很容易引起稳定性兼容性问题以及意想不到的 bug。为了尽量保障代码的稳定可靠,我们主要考虑了两个主要的代码组织原则:


第一, 尽量缩小代码改动的范围,所有的改动都在尽可能少的几个函数内完成,不涉及 TFR 框架的其他模块代码。


第二, 对于不涉及上述两个问题的项目要做到完全的兼容。对于不使用 feature columns 的项目或者不使用正则化(应该很少),保证原有逻辑和计算结果不变。


04 实验:LTR 模型和原生模型的效果对比


究竟 TFR 框架训练的 LTR 排序模型对比同样网络结构的原生模型,能够带来多大的效果提升呢,我们也专门做了线上实验来分析。选取了一个业务场景,取出三个流量组分别做以下模型:


  • BaseB: 没有排序服务,为每个召回渠道配置优先级,系统按照优先级给出推荐结果。

  • Ranking: TensorFlow原生Estimator开发的排序算法。

  • TfrRankingB: 基于TFR框架开发的LTR排序算法。


其中,TfrRankingB 相比较于 Ranking,模型结构完全一致,也就是采用同一个 Scoring Function,训练数据集也完全一致。但是由于 TfrRankingB 采用 TFR 框架训练的 LTR 模型,模型优化上有以下几处不同:



以上的几处不同是模型训练方式和评估指标上的不同,这也正是采用 TFR 框架带给我们的。而训练数据和模型本身,包括正则化项在内,TfrRankingB 和 Ranking 是完全一样。两个模型训练后,与 BaseB 一起在线上真实流量环境下测试完整 4 天,其中 day1 和 day2 为平日,day3 和 day4 是休息日。线上实验考查用户的 CTR(点击率)、UCTR(用户点击率)和 LPLAY(长播放占比),效果如下:





考虑到业务保密性,我们对横纵坐标的具体取值不做展示。但是结论显而易见:


  1. 在CTR和UCTR指标上,TfrRankingB显著优于Ranking,Ranking显著优于BaseB。

  2. 在LPLAY指标上,TfrRankingB优于Ranking,Ranking优于BaseB。


总结

使用TFR框架后,可以非常方便的基于TensorFlow开发LTR模型或者将现有模型改造为LTR模型。同时,TFR框架的模块设计、代码逻辑都非常巧妙,诸如高内聚低耦合等大家常常挂在嘴边的规范也实实在在的落在了代码上。在接下来的工作中,逐步将现有的TensorFlow 1.X版本升级到2.X版本,并观察TFR框架对TensorFlow 2.X的支持情况。


参考文献


  1. Burges C J C. From ranknet to lambdarank to lambdamart: An overview[J]. Learning, 2010, 11(23-581): 81.

  2. Liu T Y. Learning to rank for information retrieval[M]. Springer Science & Business Media, 2011.

  3. Pasumarthi R K, Bruch S, Wang X, et al. Tf-ranking: Scalable tensorflow library for learning-to-rank[C]//Proceedings of the 25th ACM SIGKDD International Conference on Knowledge Discovery & Data Mining. 2019: 2970-2978.

  4. Krichene W, Rendle S. On Sampled Metrics for Item Recommendation[C]//Proceedings of the 26th ACM SIGKDD International Conference on Knowledge Discovery & Data Mining. 2020: 1748-1757.

  5. https://github.com/tensorflow/ranking


本文转载自公众号爱奇艺技术产品团队(ID:iQIYI-TP)。


原文链接


TensorFlow Ranking框架在海外推荐业务中的实践与应用


2020 年 11 月 16 日 10:11631

评论

发布
暂无评论
发现更多内容

架构师训练营 - 第 8周命题作业

红了哟

边缘计算,如何啃下集群管理这块硬骨头?

博云技术社区

云计算 容器 边缘计算 PaaS 博云

阿里P8Java架构师呕心沥血整理出来的[史上最全Java面试题精选集锦]

Java成神之路

Java 编程 程序员 面试

商业通识 : 商业从哪里来?

Walker

学习 得到 个人成长 商业

20张图,大学四年都没整明白的操作系统就这么学会了

小Q

Java 程序员 架构 操作系统

[翻译]Go Concurrency Patterns[Go 并发模式]

卓丁

golang Rob Pike Go Concurrency Patterns Concurrency

市值做市机器人,操盘做市系统搭建

13823153121

“新基建”与“双循环”的二重奏:2020服贸会靠什么推动经济复苏

脑极体

工作好多年有可能还未真正了解接口和抽象类

架构师修行之路

接口 抽象

用技术的“信条”,开启AI to B的产业位移

脑极体

我的大厂面试经历

老大哥

Java 程序员 后端

合约跟单app,永续合约交易所带单交易软件

13823153121

阿里年薪百万的P7架构师简历都长什么样?他们都是怎么学习的?

周老师

Java 编程 程序员 架构 面试

面试官为什么会问你,如何设计一个高并发系统?

老大哥

Java 程序员 后端

大厂面试题:集群部署时的分布式 session 如何实现? 面试官心理分析

老大哥

Java 程序员 后端

XSKY ClickHouse如何实现存算分离

XSKY融合存储

[翻译]The Go scheduler[Go调度]

卓丁

golang golang调度 Go scheduler

Java架构师JVM启动流程和内存结构,程序员必看!

老大哥

Java 程序员 后端

从用户输入手机验证码开始

架构师修行之路

阿里P8忠告:这些技术,哪怕不用微服务架构,你也应该会

小Q

Docker 架构 微服务 springboot SpringCloud

甲方日常10

句子

工作 随笔杂谈 日常

Flink通过官网创建自己的工程-20

小知识点

scala 大数据 flink

架构师第十三周作业

傻傻的帅

架构师

【真实面试经历】我和阿里面试官的一次“邂逅”

老大哥

第十三周作业

olderwei

极客大学架构师训练营

一步搞定任意圆角背景

mengxn

android xml 圆角

indexOf原理,Java,javascript,python实现

叫练

算法

架构师训练营作业(大数据与机器学习)

qihuajun

Polkadot初识,不止于跨链

QTech

polkadot 跨链

呕心沥血,整理200+最新面试题,面试之前看一下,事半功倍

小Q

Java 学习 程序员 架构 面试

筹备半年时间,四面阿里终于如愿拿到P7级offer【Java岗】。

Java成神之路

Java 编程 程序员 面试

TensorFlow Ranking框架在海外推荐业务中的实践与应用-InfoQ