张俊林:推荐系统排序环节特征 Embedding 建模
分享嘉宾:张俊林博士 新浪微博
编辑整理:刘一全
出品平台:DataFunTalk
导读: 随着深度学习在推荐系统应用的发展,特征 Embedding 建模的重要性已经成为共识,同时海量特征的稀疏性及参数量过大是必须面对的难题。今天会为大家分享微博在特征 Embedding 建模方向做的一些工作。
今天的介绍会围绕下面五点展开:
- 特征建模的必要性
- 特征建模的三个技术方向
- 卡门槛:微博在特征重要性方向的工作
- 挤水分:变长特征 Embedding
- 补营养:提升特征表达质量
01 特征建模的必要性
1. 三大AI方向的领域特点
在讨论特征建模之前,我们先来对比一下不同 AI 应用领域中数据分布的特点,这里以最常见的自然语言处理、图像处理和推荐系统为例来说明。
首先我们对比自然语言处理和图像处理:NLP 最基本的数据元素是单词,每个单词有一定的含义,可能指代某个实体;图像处理最基本元素是像素,因为粒度太细,单个像素并没有实际含义。所以在输入信息基本单元粒度上,自然语言处理比图像处理更抽象,更有具体含义和所指。
在数据的组织结构上看,自然语言处理的语句是一维线性的,同时具备信息的局部相关性和远程相关性特点:局部相关性指的临近单词之间语义相关度高,远程相关性举个例子:“虽然“….”但是”是个表达语义转折关系的结构,但是它们在句中的距离可能很远,但是这是个重要特征。而图像是二维/三维立体结构,数据具有局部相关性和平移不变性等特点:所谓“局部相关性”,就是刚才说的,单个像素无含义,但是相邻一片区域的像素连在一起就能体现物体特征。平移不变性举个例子,比如上图里的鸟头,不论这个鸟头出现在图片哪个位置,你都应该看出来它是个鸟头,这就是平移不变性的含义,除此外还有旋转不变性、遮挡不变性等很多特性。
在这些数据的领域特点下,我们回顾这两个领域模型的发展历史,你会发现 RNN 结构天然匹配 NLP 数据的一维线形以及局部和远程相关性等特性,所以这是为何几年前深度学习进入 NLP 的时候,RNN 快速普及的根本原因。图像处理里 CNN 则天然适合对局部相关性和各种不变性建模,这也是为何 CNN 快速占领图像领域的根本原因。从这里可以看出,我们在设计模型的时候,应该重点考虑领域数据的特性,那些匹配领域数据特性的模型,就容易取得优势。目前 Transformer 统一了 NLP 和图像处理领域,那么很自然, 我们就可以推论 :Transformer 不仅匹配上述两个领域的数据特点,而且比 RNN 和 CNN 匹配度应该更高。
我们的主题是推荐系统,那么我们再来看 推荐领域数据分布有什么独有的特点 。首先,和NLP 及图像处理不同,推荐领域数据是异质的表格数据,一个实例一般由离散的字段构成,字段可以是数值型的,也可以是离散形态的,字段之间可能有关系,也可能完全无关。其次,推荐数据输入特征之间没有局部相关性,对于平铺特征类型的任务,并不是两个特征在输入里挨得越近,就越有关联,它们之所以挨在一起,完全是随机作用(当然,行为序列具有局部相关性)。再者,可能也是推荐数据最大的特点,是特征的海量高维稀疏,关于它的具体含义我们后面会说。另外,特征组合对于推荐来说是至关重要的数据特点,这个可以理解为远程相关性。这些都是推荐领域数据独有的特点。那么, 什么样的模型才匹配推荐领域的数据特点呢? 这也是我自己一直在思考的问题,我们做的一些工作可以看作对这个问题的部分回答。
2. 推荐系统数据的稀疏性
推荐领域特征的海量稀疏性,主要体现****在用户行为及特征数据分布极不均匀,极少量高频特征在数据总体中占比很高,长尾现象异常严重,对于绝大多数行为特征或其它特征来说频次极低,所以就没什么信息量,或者包含大量噪音数据,这就给上层模型建模带来很大难度。
3. 特征建模的必要性
在介绍后续内容前,我们先简单介绍下 DNN 模型,因为后面很多内容都要以它为基础。 上图展示了典型的 DNN 模型结构 :输入实例由 n 个特征构成,每个特征由 one-hot 形式转换成特征 Embedding,这个映射过程需要学习获得,然后特征 Embedding concat 到一起输入到上层 DNN 结构,DNN 结构一般由 2 到 3 层 MLP 构成。DNN 模型是最简单的深度 CTR 模型,也是大多数其它深度 CTR 模型的关键组件,但是如果你愿意好好调试,会发现在推荐领域,它是性能很好的强 baseline 模型,大量所谓新模型,效果未必能打过它。
我们从参数占比来说明 为何特征建模是重要的,具体网络结构配置数据可参考上图 ,在这个配置下,你把计算结果列出来一看就明白了:在 DNN 结构中,包含 200 亿参数量,特征 Embedding 占总体参数量的 93.3%,上层 MLP 结构参数量只占 6.7%。也就是说,深度 CTR 模型里,特征 Embedding 占据了参数总量的绝对多数,理论上特征处理好坏对模型整体影响应该比较大。在真实应用场景中,深度 CTR 模型一般都是“过参数化”(over-parameter)的:模型参数的规模远远超过训练数据规模,比如上面 200 亿的模型,大概率你没那么多训练数据来训练它。而深度模型这种天然的“过参数化”倾向导致它天然是个容易引起“过拟合”问题的模型结构,而造成这种现象的,主要原因是占比大的特征 Embedding。当然,你可以调整上层 MLP 大小,比如增加 MLP 的深度和宽度,但是这样做尽管对模型效果有影响,但是影响很小。所有这些现象或特点,主要原因在于深度模型中包含了海量的特征 Embedding 参数量。
特征参数量虽然看着非常巨大,容易引起过拟合问题,但是,其中大量特征 Embedding其实是没什么用的。你可以试着逐步抛掉低频特征,在实验中可以发现,开始把极低频特征比如仅仅出现 1 次 2 次的特征抛掉,模型效果会逐渐上升,到了一定数量比如出现 10 次的特征也抛掉,模型效果就开始下降。 这说明对于大量低频特征,引入它们的副作用要大于正向作用。 如果我们能知道哪些特征没用,把它们抛掉,无疑不仅能提升模型效果,还能大规模减少模型参数,一举两得。但是问题的关键是:面对如此大量特征,我们并不知道哪些是有用或者无用的。
所以,特征建模的核心问题就是:我们能否通过技术手段,知道哪些特征是有用的?哪些是没用的?对于模型认为有用的特征我们可以做些什么事情?模型认为没用的特征,我们又可以做些什么事情?
02 特征建模的三个技术方向
这个部分,会简单说明下特征建模三个方向的基本思路,我们首先来看“卡门槛”。
所谓“卡门槛”,意思是我们可以在特征 Embedding 层和上面的 DNN 层之间,加一个门控层,这个门控来决定哪些特征可以进入上层的 DNN 部分,哪些特征不能进入后续网络结构,直接在门控层就被过滤掉。如果进一步思考,其实对于允许通过的特征,这个门控机制还可以区分不同特征的重要性,对于重要特征给予大权重,不那么重要特征给予小的权重。
这就是“卡门槛”的基本思路 ,它的核心思想是过滤掉对优化目标起负面作用的大部分稀疏特征,同时通过大权重凸显重要特征的作用。如果用“教育学生学习”来打个比方,类似我们现在的高考制度,划出统一分数线,只有过线的学生才能通过高考去上大学。
第二个方向是“挤水分”。
它的含义是:通常我们会给所有特征分配相同长度的Embedding size,但是很明显这么做是不合理的。因为对于大量稀疏特征,因为没有什么知识可以学习,如果你给它分配的 Embedding size 越大,它越容易出现过拟合的现象;而对于那些高频特征,你应该给它更长的 Embedding,因为它有很多知识需要编码,如果分配的太短,则会出现欠拟合问题,就是 Embedding 容量不够大,放不下那么多要学习的知识。
所以, 合理的策略应该是 :对于特征采取变长 Embedding,对于中高频特征,分配长的 Embedding,对于大量低频特征,分配短的 Embedding。这就是在挤稀疏特征 Embedding 的水分,这是我把它称为“挤水分”的原因。如果继续拿上面的学生教育做比喻,那么“挤水分”就类似学校里面给不同学生设置快慢班,学的快的学生进快班,获得更好的教育资源,学的慢的学生进慢班,因材施教。
第三个方向是“补营养”。
这是一种最正能量的技术方法,它的意思是:既然很多稀疏特征,它没太多可学习的内容,导致特征稀疏,表达能力不足,那么我就想点办法,来提升它的特征表达能力。具体做法又可以细分为“修炼内功”和“寻求外援”,这里暂不展开,后面会用具体例子解释这两种做法的思路。如果继续拿学生教育做类比,那么“补营养”类似学校里面专门给差生进行针对性的补课,来提高他们的学习成绩,所以说这是最“正能量”的一种做法。
上述内容是特征建模的主要技术方向,接下来分享一些我 个人的想法 。
我们最近 4 年来,在排序模型方面,重心放在特征建模方向,尤其是聚焦在“特征重要性”方面,可以说这是我们做 Rank 模型的核心主线。那么,为什么我们在这里投入这么大精力呢?首先,前面介绍过,我们做模型,一定要结合具体领域的业务特性,以及领域内数据的独有特点。具体到推荐系统,我个人认为海量高维的稀疏特征以及特征组合,这两个是推荐领域最主要的领域特点,所以我们把重心放在特征建模这个方向。其次,我认为相比模型结构来说,特征建模更重要。我一直相信的一点是:尽管最近一年来图像和 NLP 领域已经被 Transformer 统一,推荐系统暂时没有类似这样有统治力的模型,但是如果未来某天出现了这种突破性的模型,我觉得应该是在特征建模方向,而不是模型结构的改进和革新。
那为什么我们又聚焦在特征重要性方向上呢?原因是在 2019 年之前,整个行业就没人做“卡门槛“这个方向,FiBiNet 中的 SENet 模块应该是第一个做这个事情的 CTR 模型。有些介绍文章把 FiBiNet 里的 SENet 仅看作一种 Attention 具体做法,也有拿 AFM 模型放在一起来说的,我认为这种理解是片面的,这并不能体现引入 SENet 的本质思想,关键你要看它用在哪里,目的是什么,具体怎么做其实是次要的,上面介绍的“卡门槛”才是它的本质思想。
03 卡门槛:微博在特征重要性方向的工作
这里对最近几年我们在特征建模方面做的工作做个概述,节奏大约是一年提出 1 到 2个新模型。最近几年微博在 Rank 模型方面做的主要工作如下:
- FiBiNet 是第一个引入特征重要性子网络结构的深度 CTR 模型,也是目前效果最好的 CTR 模型之一。
- FiBiNet++ 是对 FiBiNet 模型的改进,主要目的在于减少 bi-linear 模块设计不合理带来的暴增的参数数量。
- ContextNet 是对 Transformer 模型进行改造,将之应用在推荐的改进模型,在特征重要性模块加入了更精细的特征门控方式,这是我个人比较喜欢的一个模型。
- MaskNet 是 ContextNet 的副产品,将特征门控拓展应用到 MLP 上。这是除了我们今年做的一个还未公开的新模型外,我们自己做的模型里效果最好的。
今天主要介绍上述几个模型,至于 FAT-DeepFFM 和 GateNet,也是两个特征门控相关工作,时间原因今天就不介绍了,感兴趣的同学可以搜对应论文看看。
FiBiNet 是我们 2018 年设计、发表在 Recsys2019 的工作。相对其它模型,它的创 新点在于引入两个独立可插拔的模块 :一个是用 SENet 作为特征重要性门控,二是引入双线性模块来改进特征交互方式。这对应下图网络模型结构下方两个分支中标为橙色的三个模块。
SENet 最早 2018 年在图像处理中提出的,下面介绍一下它在推荐系统中的应用。
我们在推荐中引入 SENet 的主要目的是希望引入一个特征门控系统,它可以给输入的每个特征动态打权重,如果是没什么用的稀疏特征,我们希望这个权重是 0,这等于消除掉了这个特征的负面影响,而如果是重要特征,则希望 SENet 打出一个大权重,强调下这个特征很重要。基本出发点就是之前讲过的“卡门槛”的作用。
具体而言, 其模型结构参考上图 ,对于当前输入的每个特征 Embedding,取出一个代表 bit 位,这里取 Embedding 中根据每一位求出的均值作为代表位,假设输入有 f 个 field 或 slot,则有 f 个代表 bit,作为后续双层 MLP 网络的输入。第一层 MLP 是个窄网络,因为输入维度 f 一般比较小,实际场景中基本也就是 100 到 200 之间,所以这层 MLP 可以设计的比较窄,就不容易过拟合;第二层 MLP 的输出结果就是每个特征的重要性得分。我们原始版本的 SENet中,非线性函数都是用的 Relu,你会发现很多特征打出来的权重都是 0 分,这样就达到了前面说的“卡门槛”的目的。从网络结构可以看出,SENet 是个轻型网络,额外引入的参数很少,运算速度也比较快,作为一个可插拔的门控模块是比较轻巧的。
另外一个是 双线性特征交互模块 , 这是我们 2018 年在改造 FFM 模型时候想出来的改进模型 ,是对 FFM 模型的简化或者是 FM 模型的复杂化,看你站在哪个角度来看了。双线性交互的核心思想是:在 FM 模型基础上,当任意两个特征交互的时候引入一个参数矩阵 W,通过这个参数矩阵来更精细地表征特征交互过程。
这个参数矩阵 W,有三种做法:一种是所有特征共享同一个 W,这种方式的 W 引入的新参数量是极小的,几乎可忽略不计;第二种是每一个特征域(Field 或 Slot)内的特征共享同一个 W;第三种最细致,是每种特征域组合,共享一个 W。
这三种不同做法,越来越精细,表达能力越来越强,对应的参数量依次升高,跑起来也会越来越慢,具体用哪个要看具体场景。关于双线性这里不展开介绍了,对此感兴趣的同学,推荐看看小红书王树森老师在 B 站的解说视频(SENet 和 Bilinear 交叉:https://www.bilibili.com/video/BV1SY4y1M7bD/),介绍 FiBiNet 很到位。
从 2019 年我们提出 FiBiNet,再结合我参考最近两年的模型发展情况, 我的结论是 :FiBiNet 是目前效果最好的 CTR 模型之一,尤其是在 Avazu 数据集上,大概率 FiBiNet 到目前这个时间点就是 2022 年,尽管有一些新模型出现,我判断它在 Avazu 上很可能仍然是效果最好的 CTR 模型(有个公平对比的前提条件:Avazu 数据集上,特征 Embedding size 要放到 40 或者 50,这是绝大多数 CTR 模型在 Avazu 上效果最好时候的 Embedding size,而不是像很多工作实验部分报道的,强制把 Avazu 数据集上的 Embedding size 设置成 10,这并非一个合理设置。Criteo 数据集最合理的 Embedding size 应该在 10 到 20 之间)。
上面这张图是得出判断的依据之一,这是华为 FuxiCTR 的工作,它做了 4600 多组实验来公平对比从 2010 到 2020 年这 10 年间比较知名的 24 个 CTR 模型的效果,结果显示在 Avazu 上 FiBiNet 效果最好。当然这只是我得出上述结论的依据之一,还有其它依据,包括我们内部也对新模型做过大量实验对比,我个人觉得上述结论基本成立。
不过话说回来,最近 2 年我的一个明显感觉是:CTR 模型的效果和数据集关系比较大,目前应该不存在一个模型,它在各种数据集都能占据统治地位,这和 NLP 以及图像处理领域表现完全不同,这两个领域基本都有统治性的模型,这也一定程度上说明了,推荐模型整体缺乏突破进展,才会造成在不同数据集上最佳模型不同,类似军阀割据的这种外在表现。而在某个数据集上表现最好的 CTR 模型,很可能是因为它的某个网络结构设计,正好匹配了这个数据集合的数据分布特性。
这里顺便再说下 数据集的问题 ,如果你要测试 CTR 模型且以论文的方式呈现出来,很明显实验部分的测试数据应该带上 Criteo 和 Avazu,因为这两个数据集规模比较大,都在 4500 万左右。尽管这两个数据集也有一些问题,但是貌似除此外我们也没有更好的选择,这也是没办法的办法。而如果一个论文里的 CTR 模型,实验部分都用的是小规模数据,很明显这个工作就没太多实用价值,因为小数据上有效的技术改进,大概率换个规模大的数据就会失效。即使在 Criteo 和 Avazu 证明有效的模型,上线未必会有效,因为你面临比 4500 万更大规模的数据,技术失效也不意外,但是如果一个新模型在实验部分连这两个数据都不带,都是小规模数据,那只能说明模型作者对自己的模型效果不够自信。所以如果你的出发点是真正想提出有效的改进模型而不仅仅是水一篇论文,那我建议一定要用大规模数据来验证。
这里介绍下 使用 FiBiNet 的一个经验 :上面两张图片展示了不同模型在 Criteo 和Avazu 数据集上的效果对比,坐标横轴是不同大小的特征 Embedding size,纵轴是 Logloss 和 AUC 效果指标,图中的黄色曲线代表了 FiBiNet 的效果表现。从中可以看出,无论是 Criteo 还是 Avazu 数据集合,当逐渐放大特征 Embedding size,尤其当大小超过 30 的时候,FiBiNet 相对其它模型(DeepFM/xDeepFM/AutoInt+)的效果优势逐步扩大,而其它模型的效果要么开始往下掉,要么开始波动,只有 FiBiNet 随着 Embedding size 的继续放大,性能稳步提升。也就是说如果应用中 Embedding size 设置的比较大的时候,明显使用 FiBiNet 效果更好。
这是为什么呢?我觉得这个现象说明了一个问题:正是 FiBiNet 中的 SENet 模块导致这个现象的发生。因为当特征 Embedding size 越大的时候,稀疏问题就会越严重,而此时使用特征门控就越重要,因为此时 SENet 有更大可能将无效特征过滤掉,避免了大量特征稀疏的负面影响。
据我了解,目前 FiBiNet 里的 SENet 模块或者它的改进变体在很多一线二线互联网公司的线上推荐系统或广告系统都在应用,也取得了不错的收益,但是在现实场景中双线性模块用的就比较少,这是为什么呢?我认为主要原因是 FiBiNet 模型中,双线性模块本身以及它和上层 DNN 连接处设计思路有问题:
因为 FiBiNe 下层有两路,每路各有一个双线性模块,而双线性模块里用了哈达玛积,这就导致在双线性模块做完特征交叉之后,这层非常宽,它和 DNN 的第一层 MLP 这个连接处参数量会急剧增加。如果应用中包含的特征域(Fields或Slot)比较多,模型跑起来非常慢,这就是它的主要问题所在。
为了解决 FiBiNet 双线性模块设计的问题,我们改造出了新版本的 FibiNet++(细节信息可以 参考论文 :FiBiNet++:Improving FiBiNet by Greatly Reducing Model Size for CTR Prediction,https://arxiv.org/pdf/2209.05016.pdf),主要目的是在尽量不降低模型效果,甚至进一步提升模型效果的前提条件下,大幅减少模型参数量。关于减少模型参数的改进措施,包含几个要点:首先,更改模型结构,去掉 FiBiNet 下层右路 SENet 之上的 bi-linear 模块,拿掉基本无影响;其次,双线性模块计算中的哈达玛积变成内积,这个改变很小,但能大幅减少模型参数;再次,在左路 bilinear 特征交叉后,加入一个窄的 MLP 层,用于对特征交互结果进行维度的进一步压缩。经过上述改进,模型大小缩小了 10 几倍(不考虑特征 Embedding 的情况)。
与此同时,我们对 SENet 模块做了一些改进,来进一步提升模型效果。 主要措施包括 :
首先,SENet 的输入信息,从每个特征 Embedding 产生 1 bit代表位,改为将每个特征的 Embedding 划分成 g 个分组 Embedding ,每个分组 Embedding 产生两个代表位:一个均值 bit,一个 max bit,这两个 bit 作为分组代表信息。
其次,增加 Skip connection,原始 Embedding 和加权的 embedding 进行求和,等于保留了原始 Embedding 信息。
最后,输出的特征权重,从 vector-wise 权重到 bit-wise 权重,就是说原先是每个特征学习一个权重,现在是每个特征的每个 bit 都给予一个权重。
经过这些改造,FiBiNet++ 的效果相比 FiBiNet 有进一步的提升。
如果我们把特征门控抽象一下,你会发现各种新型的变体结构。
首先,门控的输入层,根据输入信息由粗到细,可以有三种做法:每个特征 Embedding 出一个 bit 代表位/每个特征分组,每组出 1 个或者 2 个代表位/每个特征 Embedding 的所有信息都作为门控的输入。
其次,为了简化问题,关于中间 MLP 隐层,我们假设结构不动(但是这层的宽度应该根据输入信息多少进行相应调整,总的原则是输入信息越多,这层 MLP 应该越宽,否则会存在表达不足问题)。
再者,在门控输出层,我们跟输入层一样,也有由粗到细的三种做法。
这样,三种输入搭配三种输出,一共有 3*3=9 种模型组合,这还不算中间隐层的变化。而原始版本的 SENet,取了其中输入信息、输出信息最少的一种组合搭配,它的特点是模型参数少,速度快,比较轻巧,但是有信息损失。
在此基础上,我们来介绍 ContextNet 模型,这是 2019 年在 FiBiNet 发表后的关于特征重要性的进一步改进工作,论文形成于 2020 年 2 月份左右,把论文扔出来是 2021 年年中的事情了(具体细节可以 参考论文 :ContextNet: A Click-Through Rate Prediction Framework Using Contextual information to Refine Feature Embedding,https://arxiv.org/pdf/2107.12025.pdf )。当然,我们最初设计 ContextNet 的初衷,并不是因为推断出上面的门控 9 组合,然后换一种组合来做这个事情。起因是自从 Bert 出来后,从 2018 年年底开始,我觉得 Transformer 会统一 NLP 领域,所以从那时候就想改造一个推荐版本的 Transformer 结构,当时我们用原始版本 Transformer 试过,跟 DNN 模型比,效果其实并不好,所以 ContextNet 其实是我们摸索觉得比较适合用于推荐领域的 Transformer 改造版本。
它的结构和出发点参考上图,说白了就是模仿 Bert,在 Bert 里,每个单词维护自己的 Embedding 一直往上层结构走,根据其它单词 Embedding 来修正自己的 Embedding,达到根据上下文消除单词多义词的问题 。ContextNet 思路类似,每个特征维护自己的 Embedding,不断往上层结构走,根据同一个输入里的其它特征的原始 Embedding,经过一个特征门控系统,来不断修正自己的 Embedding 内容,这个门控起到的作用其实是寻找有效特征组合。在门控起作用后,上接一个局部 MLP 来非线性整合新的特征 Embedding 内容,这点和 Transformer 没有太大不同。你可以简化理解为把 Transformer 的 Multi-head self attention 用特征门控系统给替换掉了,就是 ContextNet。
于是,绕了一圈,我们又回到了特征门控这里来了,这个特征门控就是上面说的门控 9组合里输入输出最丰富的另外一个极端情况:特征 Embedding 所有信息作为门控输入,输出则是 Bit-wise 的,就是说给特征 Embedding 每个 Bit 都打上一个权重。这里有两点需要注意:首先,不论特征 Embedding 走到哪一层,它对应的门控的输入都是所有最原始的特征 Embedding,实验结果证明这种原始特征的输入效果最好;其次,门控中间的隐层要比较宽,2 倍于输入信息是效果最好的,这是因为输入信息比较多,中间层就需要较多神经元来拟合信息。所以它和 SENet 特性正好相反,它比较重,参数多,速度慢,但是效果要更好。
另外,如果在最上层加上一个 MLP 用于整合各个特征 Embedding 的信息,ContextNet 效果会进一步提升,应该和 MaskNet 效果接近,但是当时我觉得这种“戴帽子”的混合结构看上去太丑陋,就没同意加这个帽子,于是 ContextNet 就是目前这种样子。我认为如果 CTR 模型里需要 Transformer 模型,那它应该就是长得类似 ContextNet 这样的,因为这些年来我们尝试改造过非常多不同的 Transformer 变体结构,目前看这种结构效果是最好的,当然,在用的时候,这里建议最上层可以带上个 MLP 的帽子,毕竟效果要好,长相难看就难看点吧,聪明更重要,为此我已放弃模型审美了。
最后介绍下 MaskNet,这是我们目前探索的所有模型中效果最好的(Avazu 数据除外,Avazu 上仍然是 FiBiNet 或者 FiBiNet++ 效果更好)。 MaskNet 是 ContextNet 的副产品 ,在做 ContextNet 有了效果的同时,我问自己这么一个问题:特征门控肯定是有效的组件,但是除了用在特征 Embedding 上,如果我们把它用在 DNN 模型的 MLP 结构上,会是什么效果呢?是否可以根据原始特征输入,通过门控调整 MLP 的输出,使得 MLP 更好地在原始特征输入里捕捉特征组合呢?为了回答这个问题,就做实验试了一下,发现在几个数据集合上效果都是最好的。本来我觉得既然有了 ContextNet,这个改动不值得形成一篇文献,但是考虑到效果确实比较好,就也写了论文,成文时间与 ContextNet 基本同时期,大约在 2020 年 3 月份,2021 年年初我看外界有类似的思路出来,就把文章扔出去了。
下面简单介绍一下 MaskNet 的具体做法 。这里采用的门控和 ContextNet 是完全一样的,没有任何区别,作用在特征 Embedding 上的机制和 ContextNet 也一样,不同的地方是上面加了一个全局的 MLP 结构对信息进行融合,这是 MaskBlock on Feature Embedding。
类似的,对于某个 MaskBlock 的输出结果,也就是上面说的 MLP,我们也可以产生对应的一个特征门控,然后作用到 MLP 上,之后用一个新的全局 MLP 对信息进行融合,这就是 MaskBlock on MaskBlock。
把针对特征的 MaskBlock 和针对 MLP 的 MaskBlock 进行叠加,有两种方式 ,一种是串行方式,因为不同 Block 特征门控的输入都是来自原始特征 Embedding,这个结构看起来就类似 RNN 的结构了;另外一种是多个针对特征的 MaskBlock 并行,上面接上标准 MLP,这种类似 MMOE 多专家的结构。
关于 MaskNet 的代码和效果情况,可以参考以下文章:“飞桨常规赛:点击反欺诈预测 5 月第 2 名方案”(https://aistudio.baidu.com/aistudio/projectdetail/1760738),这里证明在这个数据集合下,采用 MaskNet 效果要优于 xDeepFM 或者 DCN 等其它模型,而且有完整的代码,感兴趣的同学可参考。技术细节可 参考论文 :
MaskNet: Introducing Feature-Wise Multiplication to CTR Ranking Models by Instance-Guided Mask,https://arxiv.org/pdf/2102.07619.pdf
上面是微博在特征建模的主要工作介绍,这里打个广告:今年我们在特征建模方向做了一个新工作,我个人认为是一种全新的范式和方向,目前在几个大规模公开数据集上离线测试效果有大幅提升,过两个月如果有机会的话,和大家分享下思路和具体做法。
04 挤水分:变长特征 Embedding
接下来用具体例子,简单说明下“挤水分”可以怎么做。这里再强调下,一个理想的分配特征 Embedding Size 的原则是:中高频特征的 Embedding 应该较长,低频特征的 Embedding 应该较短。在这个原则指导下,目前常见的做法可以细分为两种 。
第一种做法是用强化学习来自主学习应该给每个特征分配多大的 Embedding size,代表模型是谷歌的 NIS 模型 ,它的核心思想是把可分配空间划成 64bit 一个 block,不断迭代尝试,看看每个特征分配多少个 block 效果最好,强化学习的 reward 就是验证集合上的 AUC 指标。这里不展开讲了,具体思路大家可以看上图列出的参考文献。这是一类方法,在 NIS 之后有不少采取强化学习思路来做变长 Embedding 的工作。
第二种做法的代表模型是阿里妈妈提出的 AMTL ,核心思想是:根据特征频次,为每个特征学习一个独有的内容为 0 或者 1 的掩码向量 ,通过学习到的掩码向量中 1 的不同个数,来控制 Embedding 实际分配的长度,1 越多则分配的 Embedding 越长。这是另外一类“挤水分”的典型做法。
05 补营养:提升特征表达质量
第三类特征建模方式是“补营养“ ,核心思路是想办法提升稀疏特征的表达质量。至于具体思路,又可以分为两个子方向:“苦练内功”提质量和“引入外援”提质量。
上面列出的谷歌使用对比学习来改进召回/粗排模型的工作,是典型的“苦练内功”提质量的思路。上图展示的模型是用于召回或者粗排的经典的双塔 DNN 模型,在 Item 侧的塔引入一个辅助的对比学习 loss,对比学习的正例通过随机 drop 掉 Item 所有特征中的一部分特征来构造,对比学习 loss 采用 infoNCE,这使得正例在投影空间之间的距离更近,正例和负例的距离更远。这样就可以提升低频特征 Embedding 的质量,缓解特征稀疏问题。
那为啥这样可以缓解特征的稀疏性,以及为啥把这种做法叫做“苦练内功”呢?其实你深入思考一下,就会发现:设想构造出的正例对为<A,B>,进一步假设有一个特征a是低频稀疏的,包含在 A 中,另外一个特征 b 是高频非稀疏的,包含在 B 中。InfoNCE鼓励正例在投影空间靠近,这是在干嘛?其实就是把高频特征 b 的 Embedding 表达迁移给稀疏特征 a,因为只有这样,A 和 B 才能在投影空间靠近。所以这种使用对比学习的本质是:把同一个输入实例中高频特征 Embedding 迁移给低频稀疏特征的 Embedding,它靠自己,不靠外力因素来丰富稀疏特征的表达质量,所以我把它叫做“苦练内功”提质量。无疑“苦练内功”这种方式是很辛苦的。
另一种提升稀疏特征表达能力的是“引入外援” 。典型的例子就是多任务/多场景模型,这个目前大家都在做,模型也有很多,比如 MMOE/PLE 等。上图展示的是 MMOE 模型的模型结构,这里我觉得最关键的是 Embedding 层,因为不同场景或任务,一般大家共享同一套特征 Embedding。你可以想想这么做有什么好处?很明显,因为大家共享同一套特征 embedding,对于在场景 A 下的某个稀疏特征 a 来说,尽管它在场景 A 下是稀疏的,因为包含特征 a 的训练数据很少,但是对于数据丰富的场景 B 来说,因为数据量多,那么经过 B 的训练数据训练出来的 a 的特征 Embedding,就不那么稀疏了,因为它见到了另外一个场景更多的数据,这样就能提升场景 A 的模型效果。对于场景 A 来说,引入了场景 B 的数据来丰富稀疏特征的表达,所以这是为何我把它叫做“引入外援”提质量的原因。很明显,相对“苦练内功”来说,“引入外援“更容易做,也更容易见成效。我个人认为,多场景多任务模型,特征 Embedding 共享,很可能是带来收益的主要原因,而上层模型结构,尽管不同模型收益不同,但是相对特征共享来说,总体而言影响相对小,当然这纯粹是个人猜测,并无实验支撑。
06 Q&A环节
Q1:推荐系统的特征数量为什么会有 10 亿这么多?
A1:主要是由于 ID 特征导致的,比如 user id,item id等,还有其它各种 id,绝对数量比较多,当然在实际实现时,未必每个 id 会给个 embedding。
Q2:卡门槛类的方案中,特征是如何被卡掉的?
A2:以 SENet 为例,如果打权重那层网络的激活函数使用 Relu,你会发现大量特征的重要性得分经过激活函数后打分都是 0,把 0 乘到对应特征的 embedding 里,那这样等于把这个特征的作用抹掉了。当然也可以用其它非线性函数或者干脆使用线形函数打分,这种情况一般被卡掉的特征表现为获得的重要性得分都很小,等于削弱了这些特征的作用。
Q3:user/item 冷启动怎么做?
A3:这是一个专门的方向,从技术角度,总体思路可以理解为在“补营养”环节提到的基本思想,就是借助其它数据迁移,来缓解数据稀疏问题。或者采取一些运营策略来做各种试投放等探索策略,让好的内容自己浮现出来。
Q4:特征重要性怎么来的?
A4:其实就是你设计一个重要性计算的专用子网络,在网络训练过程中,通过 loss 的引导,这个子网络的参数和模型的其他参数一起学习,最后学好的这个网络输出的结果就是特征重要性的得分。
Q5:FiBiNet 如果只用重要性加权之后的 embedding,去掉原始 embedding 效果如何?
A5:如果保留原始的特征 emdding 效果会稍微好一些,相差不大。
Q6:训练 epoch 一般用多少?
A6:CTR 模型大多数情况下模型在一个 epoch 内收敛。
Q7:SimCSE 的思路在推荐场景是否可用?
A7:原则上可以。SimCSE 这种对比学习的思路,原则上可以用在推荐的召回模型里,因为本质上它也是在用对比学习来解决特征稀疏性问题。和谷歌那个对比学习召回模型的主要区别是:做正例的时候,一个在输入特征角度制造差异做正例;一个用 dropout 在 MLP 隐层制造差异做正例。本质是一样的。不过,我们自己在召回模型里尝试过 SimCSE 的思路,效果不太好,这里可能需要进一步的探索。
Q8:加入新特征如何评估重要性?
A8:两种思路。一是做 AB 试验,二是把特征全部加入模型,用特征重要性模块的打分来做评估和筛选。
Q9:SENet 如果特征量大会不会速度慢?
A9:SENet 网络的输入层,因为每个特征只出 1 个 bit,而且隐层又会进一步压缩宽度,所以计算速度比较快,特征量大也没问题。
Q10:特征过滤后,特征交叉有什么思路?
A10:目前典型的做法,是在上层加一个显式的特征交叉模块。例如 xDeepFm 中的CIN,autoint 中的 self-attention,以及 FiBiNet 中的双线性交叉模块。我个人理解这样的模块目前来看确实有用,从技术发展来讲,将来没有存在的必要。
Q11:SENet 直接 RELU 输出会不会有输出不稳定的情况?例如初始化或其他问题导致 embedding 被 scale到很大。
A11:问题是存在的,所以我们在改进版本的 FiBiNet++ 里会引入特征 Norm。比如在特征 embedding 的地方引入 layerNorm 或者 batchNorm,以及在特征打上权重之后加入 Norm。
Q12:你说的在做的新模型,离线测试的进展具体指的什么?
A12:一般写论文常用的规模比较大的两个公开数据集是 avazu 和 criteo,进展指的是新模型在这些数据集合上 AUC 有大幅度的提升。
Q13:对特征建模是否会引起模型膨胀?
A13:不仅不会膨胀,情形正好相反,比如“挤水分”这种,对特征建模实际是在大幅减少模型参数,而其它两类做法,模型总参数量是不变的。
Q14:参数初始化有什么经验?
A14:不同场景效果好的初始化方法不同,建议多尝试。
Q15:对比学习用在精排是否有意义?
A15:要具体看用在哪里。如果拿对比学习来对行为序列建模,方案非常简单直接,也是比较适合的;如果是对平铺特征而非行为序列建模,则比较难做,方案不容易想。单从原则上来讲应该是有用的,不过目前还没有看到比较好的方案。
Q16:SENet 这种结构是否会抑制 embedding 的学习?
A16:谈不上抑制还是增强,从根本上讲,还是优化目标的 loss 来引导特征重要性和特征本身 embedding 的学习。如果对优化目标有用,它就会被引导到那个方向。
Q17:Norm 的思路和工作推荐?
A17:在排序模型的不同结构上用不同的 norm,这是一个可以探索的方向。我们的结论是:数值特征用 batchNorm,类别特征用 layerNorm,MLP 用 layerNorm,这样效果比较好。如果希望深入了解,可以搜索下 NormDNN 模型的论文,这是我们对排序模型如何应用 Norm 的探索,里面有相关细节。
Q18:所有特征都直接输入吗?
A18:过于低频的特征可以过滤掉,数值特征可以做离散化,这都是常规操作。
Q19:SENet 线上的收益效果如何?
A19:SENet 已经经过很多大中型互联网公司实际数据验证是有效的,而且目前线上都采用了这个模块,具体提升的数值/比例要看具体场景等情况。
Q20:如果我们场景特征数量比较小,从这几个模型里如何选择?
A20:建议采用 FiBiNet++,它综合考虑了训练线上运行的效率和效果。
Q21:特征建模有没有可能和上层网络模块单独建模?
A21:现在其实特征建模和网络建模就是分离的,比如上面介绍的特征重要性子网络,都是独立的一个网络模块,模型结构关注特征的泛化性,特征模块关注特征表达的充分性,这样各司其职,效果才会好。
Q22:关于刚才说的不稳定的问题,为什么用Norm而不是把激活函数换成sigmoid?
A22:虽然 sigmoid 从函数形态来看更符合门控的思想,但是当时我们线上测试的效果是 Relu 最好,后来做改进版本的时候,测试结果是不用非线性函数最好,所以我觉得还是以效果为准。
Q23:为什么数值和类别特征用不同的 Norm 方法?
A23:数值特征有两种常见做法。一种是离散化,和类别特征一样 one-hot。另一种是给这一个特征域学习一个 embedding,特征值乘上特征域的 embedding 作为特征表达。第二种做法需要考虑特征值的分布,避免很大的数值主导优化过程,所以要做特殊的 Norm 处理。
Q24:低频特征过滤多少合适?
A24:具体情况具体分析,要做对比实验,一次卡掉一部分频次的特征,看卡到多少时候影响开始负向,总体而言要通过实验来确定,因为不同数据集合这个数值可能不一样,我觉得这个数值大概率与数据集合的稀疏程度相关。
Q25:对比学习的特征 drop out 怎么理解?和 MLP 上做 drop out 对比呢?
A25:本质上区别不大,可以参考上面说的 SimCSE 的问题。
Q26:特征的 embedding size 如何设置?是否某个特征域下包含特征越多,那么特征 size 应该越大?
A26:不能这么简单的看。比如 id 类特征,类别下特征数量极多,但是其实它是最稀疏的,用很大的 embedding size 未必合适,对于某些高频 id,放大应该有好处,但是大量低频 id,负面影响更大些,需要平衡考虑好处和负面影响,所以我觉得很难通过简单规则来定义,参照“挤水分”做法让系统自动分配可能是更好的解决思路。