服务器 频道

百度推荐排序技术的思考与实践

  01 背景

  首先来介绍一下百度综合信息流推荐的业务背景、数据背景,以及基本的算法策略。

  1. 百度综合信息流推荐  

  百度的综合信息流包括手百 APP 中搜索框的列表页以及沉浸页的形态,有着非常多的产品类型。从上图中可以看到,有类似于抖音的沉浸态的推荐,也有单列的推荐,以及类似于小红书笔记的双列推荐。交互形态也是多种多样的,可以在落地页上进行评论、点赞、收藏,也可以点进作者页中查看作者的相关信息并进行交互,当然也可以有负向的 feedback 等等。

  2. 数据背景

  从建模的角度上看,主要面对三方面的挑战:  

  大规模。每天的展现量级超过了百亿级别,因此模型需要有天级别百亿的吞吐能力。每天的 DAU 过亿,这也决定了整个模型需要有高吞吐、高可扩展性的设计。对于排序模型来说,在线每秒钟有数亿次的计算,因此模型设计时不仅要考虑效果,同时也要考虑性能,需要做到很好的性能和效果的折中。用户交互形态以及场景的多样化,还要求模型可以预估多类型任务。

  高要求。整个系统的响应时间要求非常高,端到端都是毫秒级的计算,超过了预定的时间,就会返回失败。这也造成了另一个问题,就是复杂结构上线困难。

  马太效应强。从数据样本角度上来看,马太效应非常强,少量的头部活跃用户贡献了大多数的分发量,头部的热门资源也覆盖到了大多数的展现量。无论是用户侧还是资源侧,马太效应都是非常强的。因此,系统设计时就需要弱化马太效应,使得推荐更加公平。

  3. 基本算法策略  

  在整个工业界的推广搜场景上,特征设计通常都是采用离散化的设计,需要保证两方面的效果,一方面是记忆,另一方面是泛化。特征都是通过哈希的方式做成 one hot 的离散化。对于头部用户需要有精细刻画,准确记忆。而对于占比更大的稀疏长尾,则需要很好地泛化。另外,对于用户的点击以及消费决策序列,session 是非常重要的。

  模型设计需要平衡很大的头部,使整个数据呈现出 Beta distribution 的分布,需要平衡头部的准确以及长尾的泛化。由于特征设计已经做了这方面的考量,模型的设计也需要同时兼顾泛化以及准确。百度整个的推荐漏斗对性能要求是非常极致的,所以要考虑到架构跟策略的联合设计,需要考虑性能与效果的折中,另外也需要考虑平衡模型的高吞吐性以及精度。

  架构的设计也要从性能和效果这两个维度来综合考量。一个模型算不动所有数千万的资源库,所以必须要做分层的设计,核心思想是分治法。当然各层之间是有关联的,所以会做多阶段的联训,来提升多阶段漏斗之间的效率。另外,还有弹性计算法,保证在资源几乎不变的情况下,上线非常复杂的模型。

  上图中右侧的汉诺塔项目,在粗排这一层非常巧妙地实现了用户与资源的分离建模。还有 CTR3.0 联合训练,实现了多层多阶段的联训,比如精排,是整个系统中最复杂、最精致的模型,精度是相当高的,重排是在精排之上做 list wise 的建模,精排跟重排的关系是很紧密的,我们提出的基于这两个模型联训的方式,取得了非常好的线上效果。

  接下来,将分别从特征、算法和架构三个角度进一步展开介绍。

  02 特征

  1. 用户-系统交互决策过程

  特征描述了用户与系统之间的交互决策过程。

  下图中展示了用户-资源-场景-状态时空关系交互矩阵图。  

  首先将所有信号切分为用户、资源、场景和状态这四个维度,因为本质上是要建模用户与资源之间的关系。在每个维度上,可以做各种各样的画像数据。

  用户维度上,最基础的年龄、性别、兴趣点画像。在此基础上还会有一些细粒度的特征,比如相似用户,以及用户历史上对不同资源类型的偏好行为等。session 特征,主要是长短期行为序列。业界有很多做序列的模型,在此不作赘述。但无论做何种类型的序列模型,都缺少不了特征层面的离散 session 特征。在百度的搜索广告上,从 10 多年前就已经引入了这一种细粒度的序列特征,对用户在不同的时间窗口上,对不同资源类型的点击行为、消费行为等等都细致地刻画了多组序列特征。

  资源维度上,也会有 ID 类特征来记录资源本身的情况,主导的是记忆。还有明文画像特征来实现基础的泛化能力。除了粗粒度的特征以外,也会有更为细致的资源特征,比如 Embedding 画像特征,是基于多模态等预训练模型产出的,更细致地建模离散 embedding 空间中资源之间的关系。还有统计画像类的特征,描述资源各种情况下的后验如何。以及 lookalike 特征,通过用户来反向表征资源进而提升精度。

  在场景维度上,有单列、沉浸式、双列等不同的场景特征。

  用户在不同的状态下,对于 feed 信息的消费也是不同的。比如刷新状态是如何的,是从什么样的网络过来的,以及落地页上的交互形态是怎样的,都会影响到用户未来的决策,所以也会从状态维度来描述特征。

  通过用户、资源、状态、场景四个维度,全面刻画用户与系统交互的决策过程。很多时候也会做多个维度之间的组合。

  2. 离散特征设计原理

  接下来介绍离散特征设计原理。  

  优质的特征通常具有三个特点:区分度高、覆盖率高、鲁棒性强。

  区分度高:加入特征后,后验有着很大差异。比如加入 a 特征的样本,后验点击率跟没有命中 a 特征的后验点击率差距是非常大的。

  覆盖率高:如果加入的特征在整个样本中的覆盖率只有万分之几、十万分之几,那么即使特征很有区分度,但大概率也是没有效果的。

  鲁棒性强:特征本身的分布要是相对稳定的,不能随着时间发生非常剧烈的变化。

  除了上述三个标准,还可以做单特征的 AUC 判断。比如只用某一特征来训练模型,看特征跟目标之间的关系。也可以去掉某特征,看少了特征之后的 AUC 变化情况。

  基于上述设计原则,我们来重点讨论三类重要特征:即交叉、偏置和序列特征。

  交叉特征方面,业界有数百篇的相关工作,实践中发现无论任何类型的隐式特征交叉都无法完全替代显示特征交叉,也不可能把所有的交叉特征全部删掉,只用隐式表征来做。显示特征交叉能够刻画出隐式特征交叉所无法表达的相关信息。当然如果做得更深,可以用 AutoML 来进行自动搜索可能的特征组合空间。因此在实践中,以显式特征交叉为主,隐式特征交叉为辅的方式来做特征之间的 cross。

  偏置类特征指的是,用户的点击不等于用户满意,因为资源的展示有各种各样的偏置,比如最普遍的就是 position bias,展现在头部的资源天然更容易被点击。还有 system bias,系统优先展现出认为最优的,但不一定是真的最优,比如新发布的资源,可能会因为缺少后验信息而处于劣势。

  对于偏置特征有一个很经典的结构,就是谷歌提出的 Wide&Deep 结构,Wide 侧通常会放各种偏置的特征,线上直接可以裁剪掉,通过这种偏序排序的方式来达到无偏估计的效果。

  最后是序列特征,是非常重要的一类用户个性化特征。业界现在主流的都是做超长序列的建模,具体的实验中会发现,通常长序列的存储开销是非常大的。前文中提到我们要达到性能与效果的折中。长序列可以通过离线预计算好,短序列可以在线实时计算,所以我们往往会结合两种方式。通过门控网络来决策用户当前更偏向于短序列还是长序列的方式,来 balance 长期兴趣以及短期兴趣。同时需要注意,随着序列拉长其边际收益是递减的。

  3. 推荐漏斗最优化的特征体系  

  整个推荐漏斗是分层设计的,每一层都做了过滤与截断。如何在过滤截断的分层设计中达到效率最高呢?前面也提到会做模型的联合训练。另外,特征设计的维度上也可以做相关的设计。这里也存在一些问题:

  首先,为了提升漏斗通过率,召回和粗排直接拟合精排打分或者精排序,会导致马太效应进一步加强,此时,召回/粗排模型并非用户行为驱动学习过程,而是拟合漏斗。这不是我们希望看到的结果。正确的做法是推荐漏斗各层模型解耦合设计,而不是直接拟合下层的漏斗。

  第二是粗排方面,理论上与召回靠得更近,本质上相当于是统一召回的出口。所以粗排这一层,可以引入更多召回的信号,例如协同推荐的人群投票信号,图索引的路径等等,以便粗排能够与召回队列联合优化,使得进入精排的资源的召回效率能够最优化。

  第三是计算复用,在降低计算量的同时又能够提升模型的鲁棒性。此处要注意的是,常有级联类的模型,第二级模型使用第一级模型的打分作为特征,这种做法的风险很大,因为模型最终的预估值是不稳定的分布,如果直接使用第一级模型的预估值当做特征,会使得下层模型有非常严重的耦合,造成系统的不稳定。

  03 算法

  接下来介绍核心算法的设计。

  1. 系统视角下的排序模型

  首先来看推荐排序模型。一般认为,精排是推荐系统中精度最高的模型。业界有一种观点认为粗排附属于精排,对着精排学就可以了,但具体实践中发现粗排并不能直接对着精排来学,可能会带来很多问题。  

  从上图可以看出,粗排与精排的定位不同。一般来说,粗排的训练样本与精排一样,也是展现样本。每次召回候选供粗排打分的结果有数万条之多,这里面 99% 以上的资源是没有被展现的,而模型仅使用最终展现的十几条资源来做训练,这就打破了独立同分布的假设,在离线模型分布差异极大。这种情况在召回是最为严重的,因为召回的候选集都是数百万、数千万甚至数亿,最终返回的结果大多数也都是没有被展现的,粗排一样相对也比较严重,因为候选集通常也在数万级别。而精排就相对好很多,通过了召回与粗排两层漏斗后,资源的基础质量是有保证的,它主要做优中选优的工作。因此,精排在离线分布不一致问题不是那么严重,不需要过多地考虑样本选择偏差(SSB)的问题,同时由于候选集合小,可以做重计算,精排重点在于特征交叉,序列建模等。

  但是粗排这一层,并不能直接对着精排学,也不能直接做类似于精排的重计算,因为其计算量是精排的数十倍,如果直接用精排的设计思路,线上的机器是完全不可承受的,所以粗排需要高度的技巧平衡性能与效果,它是一个轻量级模块。粗排迭代的重点与精排不同,主要解决样本选择偏差,召回队列优化等问题。由于粗排与召回关系紧密,更关注的是返回精排的数千资源的平均质量,而不是精确的排序关系。精排则是与重排关系更紧密,更关注的是单点的 AUC 精度。

  因此在粗排的设计上,更多的是做样本的选择与生成,和泛化特征与网络的设计。而精排的设计可以做复杂的多阶交叉特征、超长序列建模等等。

  2. 超大规模离散 DNN 的泛化

  前面介绍的是宏观层面的,下面来看一下微观层面。  

  具体到模型的训练过程,目前业界主流的是使用超大规模的离散 DNN,泛化问题会是比较严重的。因为超大规模离散 DNN,通过 embedding 层,主要做的是记忆的功能。参见上图,整个 embedding 空间是非常庞大的矩阵,通常都是千亿或者万亿行,1000 列。所以模型训练都是全分布式,数十乃至上百台 GPU 做分布式训练。

  理论上,对于这么大的矩阵,并不会直接做暴力计算,而是采用类似矩阵分解的操作。当然这个矩阵分解和标准的 SVD 矩阵分解并不一样,这里的矩阵分解是先学到低维的表征,通过 slot 之间的 parameter 的 share 来降低计算跟存储量,也就是分解成两个矩阵的 learning 的过程。首先是特征、表征矩阵,会学习特征跟低维嵌入的关系,这个嵌入很低,通常会选择十维左右的嵌入。另外一个是嵌入和神经元矩阵,每个槽位之间的权重是共享的。通过这种方式既降低了存储量,又能够提升效果。

  低维的嵌入学习是离线 DNN 优化泛化能力的关键,它等价于做稀疏矩阵分解,因此,整个模型泛化能力提升的关键就在于如何使得参数规模与样本数能够更好地匹配。

  从多个方面来进行优化:

  首先是从嵌入维度方面,因为不同特征的展现量差异是很大的,有些特征的展现量非常高,比如头部的资源、头部的用户,可以使用更长的嵌入维度,这就是常见的动态嵌入维度的思想,即展现越充分嵌入维度越长。当然如果要做得更 fancy,可以用 autoML 等方式做强化学习,自动搜索最优嵌入长度。

  第二个方面是创建阈值,由于不同资源展现量不同,因此,何时为特征创建嵌入表征也是需要考量的。

  3. 过拟合问题  

  业界通常是采用两阶段训练抗过拟合的方式。整个模型由两层组成,一个是很大的离散矩阵层,另一个是很小的稠密参数层。离散矩阵层是非常容易过拟合的,所以业界实践通常都是采用 One Pass Training,即 online learning,所有的数据都过一遍,并不会像学术界一样的做 batch training。

  另外,业界通常会利用时序 Validation Set 来解决稀疏层的 overfitting 问题。把整个训练数据集按时间维度切分成很多个 Delta,T0,T1,T2,T3 不同的 Delta。每次训练是用前几小时训练好的离散参数层固定住,再用下一个 Delta 的数据 finetune dense 网络。也就是通过固定稀疏层、重训其它参数的方式来缓解模型的过拟和问题。

  这种做法也会带来另外一个问题,因为训练是切分开的,并且每次都需要固定 T0 时刻的离散参数,再用 t+1 时刻重训 join 阶段,这样会拖累整个训练速度,带来扩展性方面的挑战。所以近年来都是采用单阶段训练,即将离散表征层与稠密网络层在一个 Delta 中同时更新。而单阶段训练也存在一个问题,因为整个模型除了 embedding 特征之外,还有很多连续值特征,这些连续值特征会统计每个离散特征的展现点击情况,因此,可能带来数据穿越的风险。所以在具体实践时,第一步会先除掉统计量的特征,第二步使得稠密网络与离散表征一起训练,使用单阶段的方式训练。另外整个嵌入的长度,都是自动可伸缩的方式。通过这一系列方法,可以使得模型训练提速 30% 左右。实践表明,该方法过拟合程度很轻微,训练跟测试的 AUC 的差距也都是 1/ 1000 或者更低的程度。

  04 架构

  接下来介绍架构设计上的思考和经验。

  1. 系统分层设计原理  

  系统设计的核心原则是分治法。召回需要有多个通道,核心的目标是要提升召回率,以及召回资源的丰富程度。同时召回也要考虑探索跟利用的问题,是推荐效果的基础保证。粗排做第一层的过滤,主要做轻量级点预估,承上启下。精排通常是做重计算,也是做点预估,跟重排之间的关系非常紧密,通常会使用非常复杂的结构,也是业界研究的重点。重排是最后一层,重排是具体面对用户的,决定了最终的展现序列,基于精排的结果考虑上下文然后来做复杂的序列预估,即 list wise 的排序。重排序需要考虑很多业务的约束,里面有很多规则,包括打散、LCN、退场等等,是规则与模型双重驱动的模块。

  推荐系统各层的目标基本一致,但是各层侧重不太一样。召回和粗排侧重的是泛化以及召回率,精排侧重的是单点 AUC 的精度,重排侧重的是整体序列最优。从数据上来看,越靠近召回粗排,越泛化,越靠近精排重排,越要求精度。越靠近召回源,性能受限越严重,因为候选资源越多计算量越大。粗排只需要对齐精排是一个误区,粗排需要考虑与精排的一致性,但是并不能只对齐精排。如果粗排什么都不做,只是做对齐精排,会带来非常严重的马太效应。因为精排不是 ground truth,用户的行为才是,需要学习好用户行为,而不是学习精排,这是很重要的一点提示。

  2. 多阶段模型联合训练  

  精排跟重排之间的关系是非常紧密的,早年重排是直接用精排的打分来做训练的,一方面耦合很严重,另一方面直接使用精排打分来做训练,很容易产生线上的波动。

  百度凤巢 CTR 3.0 精排跟重排联合训练项目,就非常巧妙地利用模型同时训练避免打分耦合的问题。该项目将精排子网络的隐层及内部打分,都作为重排子网络的特征,然后,将精排与重排子网络拆开,分别部署于各自模块。一方面可以很好地复用中间结果,不会出现打分耦合带来的波动问题,同时对于重排的精度又会有百分位的提升。这也是当年百度最高奖的子项目之一。

  另外,注意该项目并不是 ESSM,ESSM 是 CTCVR 建模,是多目标建模,而 CTR3.0 联合训练主要解决打分耦合和重排模型精度的问题。

  此外,要对召回和粗排做解耦合,因为新队列加入进来,对于新队列可能会不太公平。因此,提出了随机掩码的方式,即随机 mask 掉一部分特征,使得耦合度不会那么强。

  3. 稀疏路由网络  

  最后再来看一下部署在线上的过程。模型参数规模都是千亿到万亿量级,目标也非常多,直接进行线上部署开销是非常大的,不能只考虑效果,不考虑性能。有一种比较好的方式就是弹性计算,类似于 Sparse MOE 的思想。

  粗排接入了非常多的队列,有数十个甚至数百个队列。这些队列对线上的价值(LTV)是不一样的,由流量价值层来计算不同召回队列对线上点击时长的价值。其核心思想是召回队列整体的贡献度越大,越可以享受更复杂的计算。从而使得有限的算力能够服务于更高价值的流量。所以我们也没有采用传统的蒸馏的方式,而是采用类似 Sparse MOE 的思想来做弹性计算,即策略跟架构 co-design 的设计,使得不同的召回队列能够使用最适合的资源网络进行计算。

  05 未来计划

  众所周知,现在已经进入 LLM 大模型时代。百度对下一代基于 LLM 大语言模型的推荐系统的探索将会从三个方面来展开。  

  第一方面是希望模型从基础的预测升级到能够做决策。比如经典的冷启资源高效率探索,沉浸式序列推荐反馈,以及从搜索到推荐的决策链等等重要的问题,都可以借助大模型来进行决策。

  第二方面是从判别到生成,现在整个模型都是判别式的,未来会探索生成式推荐的方式,比如自动生成推荐理由,对长尾数据基于 prompt 来做数据自动增强,以及生成式的检索模型。

  第三方面是从黑盒到白盒,传统做推荐系统,大家常说神经网络是炼丹术,是黑盒的,是否有可能向白盒化方向探索,也是未来的重要工作之一。比如基于因果,探究用户行为状态迁移背后的原因,推荐公平性方面做更好的无偏估计,以及 Multi Task Machine Learning 的场景上能够做更好的场景自适应。

0
相关文章