在本节中,我们将具体来看几家世界级的巨头公司对于推荐系统的应用实例
Facebook 于 2014 年提出了经典的 GBDT + LR 的 CTR 模型结构,之后又在 2019 年发布了最新的深度学习模型 DLRM (Deep Learning Recommender System)
Facebook 的推荐系统主要用于广告推荐,这是一个标准的 CTR 预估场景,系统输入用户 (User)、广告 (Ad)、上下文 (Context) 的相关特征,预估 CTR (即用户点击该广告的概率),以此作为广告排序和推荐的依据。
*如果考虑更具体的营收模式,Facebook 广告推荐系统还需要使用其他模块来根据 CTR 计算广告出价、回报率等一系列指标,不过我们在这里只关心 CTR 预估模型这一推荐系统的主体部分
在此之前我们已经介绍过了 GBDT + LR 模型,该模型简单来说就是使用 GBDT 自动组合特征,生成新的离散型特征向量,再将该特征向量输入给 LR 模型进行 CTR 预估。 由于 GBDT 以及 LR 这两个部分使用同样的优化目标进行训练,所以在训练的过程中,不需要考虑将 LR 的梯度回传给 GBDT,而是将它们完全作为一个整体来训练。
该模型在实际应用中,GBDT 子树规模这一超参数会对效果产生显著影响,Facebook 给出的子树规模与模型损失 (Loss) 的关系曲线如下所示:
根据这张关系图,我们可以清楚看到当子树规模超过 500 时,继续增大子树规模对于损失的减少作用微乎其微。因此 500 左右应该是一个比较合理的子树规模取值。
为了协助模型的准实时训练以及特征的准实时更新,Facebook 基于 Scribe 构建了 online data joiner 模块:
根据该模块的名字就能知晓其具体的职责:准实时地把来自各不同数据流的数据整合在一起,组成训练样本,并最终与点击数据进行整合,形成完整有标签的样本数据。 在这一过程中,有 3 点需要注意:
为了控制数据规模,Facebook 选择使用负采样,即保留所有正样本,对负样本进行降采样,提升模型训练效率,同时平衡正负样本(因为根据经验来说,负样本的数量远远高于正样本)。 根据 Facebook 的实验,负采样频率选择 0.025 会比较理想。
但是负采样有可能会导致真实 CTR 从 0.1% 漂移到 10%。为了确保 CTR 的准确性,就需要进行校正,校正公式为:
q 就是校正后的 CTR,p 是模型得到的预估 CTR,w 是采样频率。
DLRM 的模型结构图如下所示:
DLRM 融合了模型并行和数据并行(在之前的笔记 推荐系统(Recommender System)04:推荐系统工程实现 中我们已经对这两种并行训练方式进行了比较细致的了解,这里只做简单介绍):
回顾一下 Embedding 的定义,Embedding 不仅能把大量的稀疏的特征向量转换为稠密向量,同时它也会把物品/对象的语义特征纳入其中,这也是我们可以通过直接计算 Embedding 之间的内积来得到相似度的原因。Airbnb 灵活地使用了这一性质。
Airbnb 作为一个短租平台,它所提供的服务相当直观。用户 (User) 通过键入价位、位置等信息以寻求合适的房源。此时,Airbnb 会为用户提供一张房源搜索推荐列表。根据一般的租房经验,在得到房源信息之后,用户 (User) 可能会有几种不同的行为:点击查看 (Click);立即预定 (Booking);与房东取得联系看房,这实际上表达了一种租房的意愿和请求 (Book Request)。
为了同时捕捉用户的 “长期” 与 “短期” 兴趣,Airbnb 选择首先分别对用户和房源进行 Embedding,然后根据 Embedding 来构造特征,输入排序模型。在具体的 Embedding 上,也细分了用户短期兴趣 Embedding 和用户长期兴趣 Embedding,前者是为了进行房源相似性推荐以及在 Session 内进行实时个性化推荐;后者是为了生成最终推荐结果时考虑到用户之前的预订偏好,进行成功率(用户预订率)更高的个性化推荐。
Airbnb 使用一个 Session 内点击数据对房源进行 Embedding,以此捕获用户于一次搜索过程中的短期兴趣。所谓的一个 Session 内点击数据就是一位用户在一次搜索过程中点击的房源序列。 这样的一个序列要满足 2 条件:
在得到这个点击房源序列之后,就可以使用 Item2vec 将这个序列作为一个 “句子” 样本进行 Embedding。Airbnb 选择使用 Word2vec 的 skip-gram 模型来进行 Embedding。 我们之前已经了解了 skip-gram 模型的目标函数为:
在使用负样本的训练方式之后,目标函数变为:
该式中的 σ 函数代表常见的 sigmoid 函数 , D 是正样本集合 , D’ 是负样本集合 。 因此我们如果将 Sigmoid 函数直接写明,则该目标函数应为:
式子的前半部分是正样本的形式,后半部分是负样本的形式。这里需要解释一下 Airbnb 的 Embedding 过程中,什么是正样本,什么是负样本:
为了适应真实的业务场景,Airbnb 也希望能将用户的预订 (Booking) 信息引入 Embedding,这就可以让 Airbnb 提供的艘噢所列表和相似房源列表更倾向于去推荐之前预订成功 Session 中的房源。 为此,Airbnb 把点击序列分为两种:
对于每个预订会话 (Booking Session) 来说,只有最后一个房源是被预订房源 (Booked Listing),我们现在要做的就是把这个预订信息引入 Word2vec 目标函数。为此,不管这个被预订房源在不在 Word2vec 滑动窗口之中,我们都要假设该被预订房源是与滑动窗口的中心房源 (Central Listing) 相关的,换言之,我们引入了一个全局上下文信息 (Global Context) 到目标函数中:
这里多出来的最后一项中,lb 就表示被预订房源 (Booked Listing),预订和点击同为正样本行为,所以和之前的正样本一样,加上了一个负号。这个最后一项没有 ∑ 是因为被预订的房源只有一个,所以中心房源只与这一个被预订房源有关。
除此以外,为了更好地发现同一市场内部房源的差异性,Airbnb 还加入了另一组负样本,即在与中心房源同一市场的房源集合中进行随机抽样,得到一组新的副样本,同样也加入到目标函数中:
在这最后一项中,Dmn 就表示同一地区的负样本集合。上面的这个式子也就是房源 Embedding 的最终目标函数。而对于冷启动问题,Aribnb 则选择使用附近 3 个同类型、价格相似的房源向量进行平均来应对。
上面基于短期兴趣的房源 Embedding 主要基于用户的点击序列数据,但该方法难以捕捉用户的长期兴趣。为了能捕获用户的长期偏好,Airbnb 选择使用预订会话序列 (Booking Session Sequence),换言之,这是一个记录了用户历史预订的序列。
比如用户 i 在过去一年预订了 5 个房源,那么预订会话序列就是 si = (li1, li2, li3, li4, li5)
但需要格外注意的是,这个序列无法像之前的点击会话 (Click Session) 那样直接使用 Word2vec 来进行 Embedding,这是因为:
因此会有严重的数据稀疏问题。为了应对该问题,Airbnb 选择基于某些属性规则做相似用户和相似房源的聚合。
比如房源属性如下表所示:
此时就可以使用属性名称与对应的分桶 id 组成一个属性标识。比如某个房源的国家是美国 (US),房源属性为 Ent (Buckets = 1),每晚价格为 56~69 (Buckets = 3),则可以使用 US_lt1_pn3 来表示该房源的属性标识,对于用户也采用同样的方法。
在得到用户属性和房源属性之后,就可以将原本的预订序列转化为如下形式:
((utype1, ltype1), (utype2, ltype2), …, (utypeN, ltypeN))
该序列中,utype1 就表示用户在预订房源 l1 时的用户属性,因为用户的属性会随着时间变化,因此 utype1 与 utype2 未必相同。在得到这个全新的序列之后,接下来要做的就是训练生成 Embedding,使用户和房源的属性都处于同一个空间中,这里可以完全沿用之前生成短期兴趣房源 Embedding 的目标函数,唯一的不同在于使用 (user type, listing type) 这样的一个元组替代了原本的房源。
其中,Dbook 是中心词附近的用户属性与房源属性的集合。所以实际上,在训练过程中,用户属性与房源属性完全被同等对待, 这样的方式保证了 二 者自然而然地在一个向量空间中生成。虽然整个过程浪费了一些信息,但从工程角度来看并没有问题。
除了计算用户和房源的 Embedding,Airbnb 还在其搜索推荐系统中对搜索词 (query) 进行了 Embedding, 与用户 Embedding 的方法类似,通过把搜索词和房源置于同一向量空间进行 Embedding,再通过二者之间的余弦相似度进行排序。而引入 Embedding 之后,搜索结果甚至能够捕捉到搜索词的语义信息。
在之前的步骤中,Airbnb 已经得到了短期和长期兴趣的用户和房源 Embedding,但是 Airbnb 并没有直接去使用 Embedding 相似度排名作为搜索推荐结果,而是基于 Embedding 进一步得到新的用户房源相关特征:
上表中最后一个特征:UserTypeListingTypeSim - 用户属性与房源属性相似度。该特征相似度就是使用用户属性与房源属性的长期兴趣 Embedding 计算得到的。而 EmbClickSIm 指的是候选房源与用户最近点击过的房源的相似度,也正是这些与最近点击相关的特征,加强了 Airbnb 系统的 “实时” 特性
在得到这些 Embedding 特征之后,会和其他特征一起输入搜索排序模型进行训练。Airbnb 选择的是 GBDT 模型。
YouTube 与 Netflix 和国内的爱奇艺这样的流媒体平台很不相同,它没有自制的电影、电视剧这类的头部内容,同时因为各类视频数量和种类庞杂,用户很难找到自己真正感兴趣的内容。
至于 YouTube 的商业模式,我们在此之前已经说过不止一次了:利润主要来自视频广告,而广告的曝光机会与用户观看时长成正比。
所以 YouTube 的推荐团队构建了两个深度学习网络分别考虑召回率 (Recall) 和准确率 (Precision) 的要去,并构筑以用户观看市场为优化目标的排序模型,最大化用户观看时长,以此保证更高的广告曝光量。
由于 YouTube 的视频基数巨大,在如此规模下进行个性化推荐,就不得不考虑 “实时性” 的问题,为了降低在线系统推荐的延迟,不宜使用复杂的网络结构。因此 YouTube 选择使用两级深度学习模型完成推荐:
实际上这两级模型分别就对应推荐漏斗中的召回层以及排序层。事实上这两级模型的任务也确实和对应的层级完全一致。
对应推荐漏斗中的召回层,主要就是进行快速筛选,将候选视频集合从原本的百万量级降低到几百量级。
自底而上来看这个网络,最底层的输入是用户历史观看视频 Embedding 以及搜索词 Embedding 向量。这部分 Embedding 的生成同样也是利用 Word2vec 方法。在得到这两个 Embedding 之后,再把用户的地理属性特征 Embedding、年龄、性别等特征都连接起来,输入给上层的 ReLU 神经网络进行训练。
整个模型最后的输出层是一个 softmax 层,所以很显然,YouTube 将这个应用场景看作是一个多分类问题,模型最后的输出就是一个在所有候选集视频上的概率分布,也就是推断用户会喜欢每个候选视频的概率。
但是,具体到实际的业务场景中,我们不可能对于每一次推荐请求都完整运行一遍这样的一个候选集生成网络,这样的成本实在是太高了。因此,YouTube 选择在通过这个 “候选集生成模型” 得到用户和视频的 Embedding 之后,使用 Embedding 之间的 NN 方法来提供服务。这么做的好处就是不需要将整个模型都放在线上服务器,只需要将用户 Embedding 和视频 Embedding 都存储在 Redis 这样的线上内存数据库就好了。那么,我们是如何从这个模型中得到用户 Embedding 以及视频 Embedding 的呢?
我们之前已经知道,模型最后是一个 softmax 输出层,该层的参数实际上是一个 m x n 的矩阵,m 就是最后一个 ReLU 层的维度,n 则是最后分类的个数,这里也就是候选集视频的数量。因此,本质上该矩阵的每个列向量就是每个候选视频对应的 Embedding,这和 Word2vec 中词向量的生成过程完全一样。
那么,用户 Embedding 来自哪里呢?因为输入的特征向量全部都是用户相关的特征,所以在使用某用户 u 的特征向量作为模型输入时, 最后一层 ReLU 层的输出向量就可以当作该用户的 Embedding 向量。
得到这两类 Embedding 之后,将他们都导入到线上 Redis 数据库,在预测某用户的视频候选集时,先得到该用户的 Embedding 向量,再在视频 Embedding 向量空间中利用局部敏感哈希等方法搜索该用户 Embedding 向量的 TopK 近邻,就可以快速得到 k 个候选视频集合。
对应推荐漏斗中的排序层,对几百量级的候选集视频进行排序,根据排序结果确定最终的推荐列表。
由于待操作的视频量级相比召回时已经从百万降低到几百,因此,在进行排序时就可以引入更多的特征进行精排序。
比如上图中,从左至右特征分别为:
- 当前候选视频的 Embedding
- 用户观看的最后 N 个视频的平均 Embedding
…
根据上面的结构图,我们可以看到,尽管模型结构和候选集生成模型比较类似,但是在输出层上差异非常明显,而且,在模型训练和提供服务时使用的函数也不一样。这么做的原因也是从业务角度出发进行的考量。
我们在分析 YouTube 的营收模式时已经说过,增加用户观看时长才是推荐系统的最主要的优化目标。所以在排序模型训练时,每次曝光期望观看时长 (Expected duration per expose) 是一个比较合理的优化目标。为了直接预估观看时长,将正样本的观看时长作为其样本权重,用加权逻辑回归进行训练,可以让模型学习到 “用户观看时长” 这一信息。
而在服务中使用 eWx+b 作为输出,则涉及到一个新的概念:机会比 (Odds)。机会比用以表示一件事发生与不发生概率的比值。在逻辑回归中,我们想要得到一件事发生的概率 p,就需要使用 Sigmoid 函数:
这就验证了模型服务过程中的输出就是 Odds。而之所以使用 Odds 作为输出,原因也不难理解,我们只需要结合加权逻辑回归进行理解即可,我们之前说过,为了能让模型学习到 “用户观看时长” 这一信息,我们设定的权值就是观看时长,因此正样本的发生概率就是原本的 Ti 倍,所以 Odds 应变为:
而由于在当前的推荐环境中,用户打开一个视频的概率 p 是相当低的,因此可以对上面的式子进行进一步的简化和近似:
所以变量 Odds 本质上就是每次曝光期望观看时长,正符合排序模型优化的目标。
YouTube 在处理训练样本的工程中有以下 3 点值的注意:
这可能是目前为止我们在日常生活中最常使用,最熟悉的推荐系统。从登录网站到购买成功,一般有以下几个步骤:
基于经典的 Embedding + MLP 深度学习 模型架构,将用户行为历史的 Embedding 简单地通过加和池化操作叠加,再与其他用户特征、广告特征、场景特征连接后输入上层神经网络进行训练
利用注意力机制替换基础模型的 Sum Pooling 操作,根据候选广告和用户历史行为之间的关系确定每个历史行为的权重
在 DIN 的基础上, 进一步改进对用户行为历史的建模,使用序列模型在用户行为历史之上抽取用户兴趣并模拟用户兴趣的演化过程
在 DIEN 的 基础上,将用户的兴趣细分为不同兴趣通道,进一步模拟用户在不同兴趣通道上的演化过程,生成不同兴趣通道的记忆向量,再利用注意力机制作用于多层神经网络
上图中每一副图片表明一位用户购买的一件商品,我们可以用这张图来理解阿里巴巴的几个推荐模型。可以看到,基础深度学习模型对于用户的行为一视同仁,不分重点。DIN 模型引入了注意力机制,此时每个商品就有了各自的权重,该权重就是基于当前商品与候选商品的关系,借助注意力机制学习得到的,因而模型拥有了有重点地看待用户行为的能力。DIEN 开始考虑用户行为与兴趣会随着时间变化,让模型拥有了下次购买的预测能力。最后的 MIMN 模型中,开始对用户的多个 “兴趣通道” 进行建模,根据商品的种类分配了多个序列,以此更精准地把握用户兴趣的变迁过程,避免不同兴趣之间的相互干扰。
因此,“用户兴趣” 始终是指引阿里巴巴发展其推荐模型的一杆大旗。
上图中展现了两种不同的架构,用虚线分割了线下和线上环境。这两个架构的主要区别在于对用户行为的处理:
撇除这两个模块,剩余模块在两个架构中的作用基本一致,我们可以直接分为离线部分和在线部分来看具体的运行逻辑: