首先来介绍一下百度综合信息流推荐的业务背景、数据背景,以及基本的算法策略。
百度的综合信息流包括手百 App 中搜索框的列表页以及沉浸页的形态,有着非常多的产品类型。从上图中可以看到,有类似于抖音的沉浸态的推荐,也有单列的推荐,以及类似于小红书笔记的双列推荐。交互形态也是多种多样的,可以在落地页上进行评论、点赞、收藏,也可以点进作者页中查看作者的相关信息并进行交互,当然也可以有负向的 feedback 等等。
从建模的角度上看,主要面对三方面的挑战:
在整个工业界的推广搜场景上,特征设计通常都是采用离散化的设计,需要保证两方面的效果,一方面是记忆,另一方面是泛化。特征都是通过哈希的方式做成 one hot 的离散化。对于头部用户需要有精细刻画,准确记忆。而对于占比更大的稀疏长尾,则需要很好地泛化。另外,对于用户的点击以及消费决策序列,session 是非常重要的。
模型设计需要平衡很大的头部,使整个数据呈现出 Beta distribution 的分布,需要平衡头部的准确以及长尾的泛化。由于特征设计已经做了这方面的考量,模型的设计也需要同时兼顾泛化以及准确。百度整个的推荐漏斗对性能要求是非常极致的,所以要考虑到架构跟策略的联合设计,需要考虑性能与效果的折中,另外也需要考虑平衡模型的高吞吐性以及精度。
架构的设计也要从性能和效果这两个维度来综合考量。一个模型算不动所有数千万的资源库,所以必须要做分层的设计,核心思想是分治法。当然各层之间是有关联的,所以会做多阶段的联训,来提升多阶段漏斗之间的效率。另外,还有弹性计算法,保证在资源几乎不变的情况下,上线非常复杂的模型。
上图中右侧的汉诺塔项目,在粗排这一层非常巧妙地实现了用户与资源的分离建模。还有 CTR3.0 联合训练,实现了多层多阶段的联训,比如精排,是整个系统中最复杂、最精致的模型,精度是相当高的,重排是在精排之上做 list wise 的建模,精排跟重排的关系是很紧密的,我们提出的基于这两个模型联训的方式,取得了非常好的线上效果。
接下来,将分别从特征、算法和架构三个角度进一步展开介绍。
特征描述了用户与系统之间的交互决策过程。
下图中展示了用户-资源-场景-状态时空关系交互矩阵图。
首先将所有信号切分为用户、资源、场景和状态这四个维度,因为本质上是要建模用户与资源之间的关系。在每个维度上,可以做各种各样的画像数据。
用户维度上,最基础的年龄、性别、兴趣点画像。在此基础上还会有一些细粒度的特征,比如相似用户,以及用户历史上对不同资源类型的偏好行为等。session 特征,主要是长短期行为序列。业界有很多做序列的模型,在此不作赘述。但无论做何种类型的序列模型,都缺少不了特征层面的离散 session 特征。在百度的搜索广告上,从 10 多年前就已经引入了这一种细粒度的序列特征,对用户在不同的时间窗口上,对不同资源类型的点击行为、消费行为等等都细致地刻画了多组序列特征。
资源维度上,也会有 ID 类特征来记录资源本身的情况,主导的是记忆。还有明文画像特征来实现基础的泛化能力。除了粗粒度的特征以外,也会有更为细致的资源特征,比如 Embedding 画像特征,是基于多模态等预训练模型产出的,更细致地建模离散 embedding 空间中资源之间的关系。还有统计画像类的特征,描述资源各种情况下的后验如何。以及 lookalike 特征,通过用户来反向表征资源进而提升精度。
在场景维度上,有单列、沉浸式、双列等不同的场景特征。
用户在不同的状态下,对于 feed 信息的消费也是不同的。比如刷新状态是如何的,是从什么样的网络过来的,以及落地页上的交互形态是怎样的,都会影响到用户未来的决策,所以也会从状态维度来描述特征。
通过用户、资源、状态、场景四个维度,全面刻画用户与系统交互的决策过程。很多时候也会做多个维度之间的组合。
接下来介绍离散特征设计原理。
优质的特征通常具有三个特点:区分度高、覆盖率高、鲁棒性强。
除了上述三个标准,还可以做单特征的 AUC 判断。比如只用某一特征来训练模型,看特征跟目标之间的关系。也可以去掉某特征,看少了特征之后的 AUC 变化情况。
基于上述设计原则,我们来重点讨论三类重要特征:即交叉、偏置和序列特征。
整个推荐漏斗是分层设计的,每一层都做了过滤与截断。如何在过滤截断的分层设计中达到效率最高呢?前面也提到会做模型的联合训练。另外,特征设计的维度上也可以做相关的设计。这里也存在一些问题:
接下来介绍核心算法的设计。
首先来看推荐排序模型。一般认为,精排是推荐系统中精度最高的模型。业界有一种观点认为粗排附属于精排,对着精排学就可以了,但具体实践中发现粗排并不能直接对着精排来学,可能会带来很多问题。
从上图可以看出,粗排与精排的定位不同。一般来说,粗排的训练样本与精排一样,也是展现样本。每次召回候选供粗排打分的结果有数万条之多,这里面 99% 以上的资源是没有被展现的,而模型仅使用最终展现的十几条资源来做训练,这就打破了独立同分布的假设,在离线模型分布差异极大。这种情况在召回是最为严重的,因为召回的候选集都是数百万、数千万甚至数亿,最终返回的结果大多数也都是没有被展现的,粗排一样相对也比较严重,因为候选集通常也在数万级别。而精排就相对好很多,通过了召回与粗排两层漏斗后,资源的基础质量是有保证的,它主要做优中选优的工作。因此,精排在离线分布不一致问题不是那么严重,不需要过多地考虑样本选择偏差(SSB)的问题,同时由于候选集合小,可以做重计算,精排重点在于特征交叉,序列建模等。
但是粗排这一层,并不能直接对着精排学,也不能直接做类似于精排的重计算,因为其计算量是精排的数十倍,如果直接用精排的设计思路,线上的机器是完全不可承受的,所以粗排需要高度的技巧平衡性能与效果,它是一个轻量级模块。粗排迭代的重点与精排不同,主要解决样本选择偏差,召回队列优化等问题。由于粗排与召回关系紧密,更关注的是返回精排的数千资源的平均质量,而不是精确的排序关系。精排则是与重排关系更紧密,更关注的是单点的 AUC 精度。
因此在粗排的设计上,更多的是做样本的选择与生成,和泛化特征与网络的设计。而精排的设计可以做复杂的多阶交叉特征、超长序列建模等等。
前面介绍的是宏观层面的,下面来看一下微观层面。
具体到模型的训练过程,目前业界主流的是使用超大规模的离散 DNN,泛化问题会是比较严重的。因为超大规模离散 DNN,通过 embedding 层,主要做的是记忆的功能。参见上图,整个 embedding 空间是非常庞大的矩阵,通常都是千亿或者万亿行,1000 列。所以模型训练都是全分布式,数十乃至上百台 GPU 做分布式训练。
理论上,对于这么大的矩阵,并不会直接做暴力计算,而是采用类似矩阵分解的操作。当然这个矩阵分解和标准的 SVD 矩阵分解并不一样,这里的矩阵分解是先学到低维的表征,通过 slot 之间的 parameter 的 share 来降低计算跟存储量,也就是分解成两个矩阵的 learning 的过程。首先是特征、表征矩阵,会学习特征跟低维嵌入的关系,这个嵌入很低,通常会选择十维左右的嵌入。另外一个是嵌入和神经元矩阵,每个槽位之间的权重是共享的。通过这种方式既降低了存储量,又能够提升效果。
低维的嵌入学习是离线 DNN 优化泛化能力的关键,它等价于做稀疏矩阵分解,因此,整个模型泛化能力提升的关键就在于如何使得参数规模与样本数能够更好地匹配。
从多个方面来进行优化:
业界通常是采用两阶段训练抗过拟合的方式。整个模型由两层组成,一个是很大的离散矩阵层,另一个是很小的稠密参数层。离散矩阵层是非常容易过拟合的,所以业界实践通常都是采用 One Pass TrAIning,即 online learning,所有的数据都过一遍,并不会像学术界一样的做 batch training。
另外,业界通常会利用时序 Validation Set 来解决稀疏层的 overfitting 问题。把整个训练数据集按时间维度切分成很多个 Delta,T0,T1,T2,T3 不同的 Delta。每次训练是用前几小时训练好的离散参数层固定住,再用下一个 Delta 的数据 f.NETune dense 网络。也就是通过固定稀疏层、重训其它参数的方式来缓解模型的过拟和问题。
这种做法也会带来另外一个问题,因为训练是切分开的,并且每次都需要固定 T0 时刻的离散参数,再用 t+1 时刻重训 join 阶段,这样会拖累整个训练速度,带来扩展性方面的挑战。所以近年来都是采用单阶段训练,即将离散表征层与稠密网络层在一个 Delta 中同时更新。而单阶段训练也存在一个问题,因为整个模型除了 embedding 特征之外,还有很多连续值特征,这些连续值特征会统计每个离散特征的展现点击情况,因此,可能带来数据穿越的风险。所以在具体实践时,第一步会先除掉统计量的特征,第二步使得稠密网络与离散表征一起训练,使用单阶段的方式训练。另外整个嵌入的长度,都是自动可伸缩的方式。通过这一系列方法,可以使得模型训练提速 30% 左右。实践表明,该方法过拟合程度很轻微,训练跟测试的 AUC 的差距也都是 1/ 1000 或者更低的程度。
接下来介绍架构设计上的思考和经验。
系统设计的核心原则是分治法。召回需要有多个通道,核心的目标是要提升召回率,以及召回资源的丰富程度。同时召回也要考虑探索跟利用的问题,是推荐效果的基础保证。粗排做第一层的过滤,主要做轻量级点预估,承上启下。精排通常是做重计算,也是做点预估,跟重排之间的关系非常紧密,通常会使用非常复杂的结构,也是业界研究的重点。重排是最后一层,重排是具体面对用户的,决定了最终的展现序列,基于精排的结果考虑上下文然后来做复杂的序列预估,即 list wise 的排序。重排序需要考虑很多业务的约束,里面有很多规则,包括打散、LCN、退场等等,是规则与模型双重驱动的模块。
推荐系统各层的目标基本一致,但是各层侧重不太一样。召回和粗排侧重的是泛化以及召回率,精排侧重的是单点 AUC 的精度,重排侧重的是整体序列最优。从数据上来看,越靠近召回粗排,越泛化,越靠近精排重排,越要求精度。越靠近召回源,性能受限越严重,因为候选资源越多计算量越大。粗排只需要对齐精排是一个误区,粗排需要考虑与精排的一致性,但是并不能只对齐精排。如果粗排什么都不做,只是做对齐精排,会带来非常严重的马太效应。因为精排不是 ground truth,用户的行为才是,需要学习好用户行为,而不是学习精排,这是很重要的一点提示。
精排跟重排之间的关系是非常紧密的,早年重排是直接用精排的打分来做训练的,一方面耦合很严重,另一方面直接使用精排打分来做训练,很容易产生线上的波动。
百度凤巢 CTR 3.0 精排跟重排联合训练项目,就非常巧妙地利用模型同时训练避免打分耦合的问题。该项目将精排子网络的隐层及内部打分,都作为重排子网络的特征,然后,将精排与重排子网络拆开,分别部署于各自模块。一方面可以很好地复用中间结果,不会出现打分耦合带来的波动问题,同时对于重排的精度又会有百分位的提升。这也是当年百度最高奖的子项目之一。
另外,注意该项目并不是 ESSM,ESSM 是 CTCVR 建模,是多目标建模,而 CTR3.0 联合训练主要解决打分耦合和重排模型精度的问题。
此外,要对召回和粗排做解耦合,因为新队列加入进来,对于新队列可能会不太公平。因此,提出了随机掩码的方式,即随机 mask 掉一部分特征,使得耦合度不会那么强。
最后再来看一下部署在线上的过程。模型参数规模都是千亿到万亿量级,目标也非常多,直接进行线上部署开销是非常大的,不能只考虑效果,不考虑性能。有一种比较好的方式就是弹性计算,类似于 Sparse MOE 的思想。
粗排接入了非常多的队列,有数十个甚至数百个队列。这些队列对线上的价值(LTV)是不一样的,由流量价值层来计算不同召回队列对线上点击时长的价值。其核心思想是召回队列整体的贡献度越大,越可以享受更复杂的计算。从而使得有限的算力能够服务于更高价值的流量。所以我们也没有采用传统的蒸馏的方式,而是采用类似 Sparse MOE 的思想来做弹性计算,即策略跟架构 co-design 的设计,使得不同的召回队列能够使用最适合的资源网络进行计算。
众所周知,现在已经进入 LLM 大模型时代。百度对下一代基于 LLM 大语言模型的推荐系统的探索将会从三个方面来展开。
第一方面是希望模型从基础的预测升级到能够做决策。比如经典的冷启资源高效率探索,沉浸式序列推荐反馈,以及从搜索到推荐的决策链等等重要的问题,都可以借助大模型来进行决策。
第二方面是从判别到生成,现在整个模型都是判别式的,未来会探索生成式推荐的方式,比如自动生成推荐理由,对长尾数据基于 prompt 来做数据自动增强,以及生成式的检索模型。
第三方面是从黑盒到白盒,传统做推荐系统,大家常说神经网络是炼丹术,是黑盒的,是否有可能向白盒化方向探索,也是未来的重要工作之一。比如基于因果,探究用户行为状态迁移背后的原因,推荐公平性方面做更好的无偏估计,以及 Multi Task machine Learning 的场景上能够做更好的场景自适应。