深度学习推荐系统实战
目录
深度学习推荐系统实战 1
01 | 深度学习推荐系统的经典技术架构长啥样? 1
1.推荐系统要解决的根本问题是什么? 1
2.推荐系统的逻辑架构 1
3.深度学习推荐系统的技术架构 2
4.Netflix 的推荐系统的经典架构图 5
02 | Sparrow RecSys:我们要实现什么样的推荐系统? 6
1.运行一个实例 6
2. “麻雀虽小,五脏俱全”的 Sparrow Recsys 7
03 | 深度学习基础:你打牢深度学习知识的地基了吗? 10
04 | 特征工程:推荐系统有哪些可供利用的特征? 10
1.什么是特征工程 10
2.构建推荐系统特征工程的原则 10
3.推荐系统中的常用特征 11
05 | 特征处理:如何利用Spark解决特征处理问题? 12
1.业界主流的大数据处理利器 -Spark 12
2.如何利用 One-hot 编码处理类别型特征 14
3.数值型特征的处理 - 归一化和分桶 14
06 | Embedding:所有人都在谈的Embedding技术到底是什么? 16
1.什么是 Embedding? 16
2.经典的 Embedding 方法,Word2vec 18
3.什么是 Word2vec? 18
4.Word2vec 的样本是怎么生成的? 19
5.Word2vec 模型的结构是什么样的? 20
6.怎样把词向量从 Word2vec 模型中提取出来? 21
7.Word2vec 对 Embedding 技术的奠基性意义 23
8.Item2Vec:Word2vec 方法的推广 23
07 | Embedding进阶:如何利用图结构数据生成Graph Embedding? 24
1.互联网中有哪些图结构数据? 25
2.基于随机游走的 Graph Embedding 方法,Deep Walk 25
3.在同质性和结构性间权衡的方法,Node2vec 26
4.Embedding 是如何应用在推荐系统的特征工程中的? 29
08 | Embedding实战:如何使用Spark生成Item2vec和Graph Embedding? 31
1.Item2vec:序列数据的处理 31
2.Item2vec:模型训练 31
3.Graph Embedding:数据准备 32
4.Graph Embedding:随机游走采样过程 33
09 | 线上服务:如何在线上提供高并发的推荐服务? 33
1.工业级推荐服务器的功能 33
2.高并发推荐服务的整体架构 34
3.搭建一个工业级推荐服务器的雏形 36
10 | 存储模块:如何用Redis解决推荐系统特征的存储问题? 37
1.推荐系统存储模块的设计原则 37
2. SparrowRecsys 的存储系统方案 39
3. 你需要知道的 Redis 基础知识 40
11|召回层:如何快速又准确地筛选掉不相关物品? 41
1. 召回层和排序层的功能特点 42
2. 如何理解“单策略召回”方法? 42
3. 如何理解“多路召回”方法 43
4. 基于 Embedding 的召回方法 44
12 | 局部敏感哈希:如何在常数时间内搜索Embedding最近邻? 45
1. 推荐系统中的“快速”Embedding 最近邻搜索问题 45
2. 使用“聚类”还是“索引”来搜索最近邻? 46
3. 局部敏感哈希的基本原理及多桶策略 47
13 | 模型服务:怎样把你的离线模型部署到线上? 50
1.业界的主流模型服务方法 50
2.实战搭建 TensorFlow Serving 模型服务 53
14 | 融会贯通:SparrowRecSys中的电影相似推荐功能是如何实现的? 56
15 | 协同过滤:最经典的推荐模型,我们应该掌握什么? 56
1.协同过滤算法的基本原理 57
2.矩阵分解算法的原理 58
3. 矩阵分解算法的 Spark 实现 59
16 | 深度学习革命:深度学习推荐模型发展的整体脉络是怎样的? 59
1.深度学习对推荐系统的影响详解 59
2. 深度学习推荐模型的演化关系图 61
3.关于模型改进的 4 个方向 61
17 | Embedding+MLP:如何用TensorFlow实现经典的深度学习模型? 62
1.如何在生成样本时避免引入“未来信息”? 62
2. Embedding+MLP 模型的结构 63
3. Embedding+MLP 模型的实战 65
18|Wide&Deep:怎样让你的模型既有想象力又有记忆力? 65
1. Wide&Deep 模型的结构 66
2. Wide&Deep 模型的应用场景 67
3.Wide&Deep 模型的 TensorFlow 实现 68
19|NeuralCF:如何用深度学习改造协同过滤? 68
1.NeuralCF 模型的结构 68
2.NeuralCF 模型的扩展,双塔模型 69
3. NeuralCF 的 TensorFlow 实现 71
20 | DeepFM:如何让你的模型更好地处理特征交叉? 71
1. 为什么深度学习模型需要加强特征交叉的能力? 71
2. 善于处理特征交叉的机器学习模型 FM 72
3.深度学习模型和 FM 模型的结合 DeepFM 73
4. 如何使用元素积的操作进行特征交叉? 73
5. DeepFM 的 TensorFlow 实战 75
21|注意力机制、兴趣演化:推荐系统如何抓住用户的心? 75
1. 什么是“注意力机制”? 75
2. 深度兴趣网络 DIN 的原理和结构 76
3.注意力机制对推荐系统的启发 78
4. 兴趣进化序列模型 79
22|强化学习:让推荐系统像智能机器人一样自主学习 81
1. 强化学习的基本概念 81
2. 强化学习推荐系统框架 82
3. 深度强化学习推荐模型 DRN 84
4. DRN 的学习过程 85
5. DRN 的在线学习方法:竞争梯度下降算法 86
特别加餐 | “银弹”不可靠,最优的模型结构该怎么找? 88
1. 有解决推荐问题的“银弹”吗? 88
2. 在工作中避免学生思维 88
3. 算法工程师正确的工作方法 89
23| 实战:如何用深度学习模型实现Sparrow RecSys的个性化推荐功能? 90

01 | 深度学习推荐系统的经典技术架构长啥样?

1.推荐系统要解决的根本问题是什么?

推荐系统要解决的问题用一句话总结就是,在“信息过载”的情况下,用户如何高效获取感兴趣的信息。

2.推荐系统的逻辑架构

推荐系统要处理的问题就可以被形式化地定义为:对于某个用户U(User),在特定场景C(Context)下,针对海量的“物品”信息构建一个函数 ,预测用户对特定候选物品I(Item)的喜好程度,再根据喜好程度对所有候选物品进行排序,生成推荐列表的问题。
*20201129 深度学习推荐系统_王喆 学习笔记 - 图1

3.深度学习推荐系统的技术架构

在实际的推荐系统中,工程师需要着重解决的问题有两类。一类问题与数据和信息相关,即“用户信息”“物品信息”“场景信息”分别是什么?如何存储、更新和处理数据?另一类问题与推荐系统算法和模型相关,即推荐系统模型如何训练、预测,以及如何达成更好的推荐效果?

一个工业级推荐系统的技术架构其实也是按照这两部分展开的,其中“数据和信息”部分逐渐发展为推荐系统中融合了数据离线批处理、实时流处理的数据流框架;“算法和模型”部分则进一步细化为推荐系统中,集训练(Training)、评估(Evaluation)、部署(Deployment)、线上推断(Online Inference)为一体的模型框架。基于此,我们就能总结出推荐系统的技术架构图。
*20201129 深度学习推荐系统_王喆 学习笔记 - 图2

3.1 第一部分:推荐系统的数据部分

推荐系统的“数据部分”主要负责的是“用户”“物品”“场景”信息的收集与处理。根据处理数据量和处理实时性的不同,我们会用到三种不同的数据处理方式,按照实时性的强弱排序的话,它们依次是客户端与服务器端实时数据处理、流处理平台准实时数据处理、大数据平台离线数据处理。在实时性由强到弱递减的同时,三种平台的海量数据处理能力则由弱到强。

因此,一个成熟推荐系统的数据流系统会将三者取长补短,配合使用。我们也会在今后的课程中讲到具体的例子,比如使用 Spark 进行离线数据处理,使用 Flink 进行准实时数据处理等等。大数据计算平台通过对推荐系统日志,物品和用户的元数据等信息的处理,获得了推荐模型的训练数据、特征数据、统计数据等。

那这些数据都有什么用呢?具体说来,大数据平台加工后的数据出口主要有 3 个:

1.生成推荐系统模型所需的样本数据,用于算法模型的训练和评估。
2.生成推荐系统模型服务(Model Serving)所需的“用户特征”,“物品特征”和一部分“场景特征”,用于推荐系统的线上推断。
3.生成系统监控、商业智能(Business Intelligence,BI)系统所需的统计型数据。

可以说,推荐系统的数据部分是整个推荐系统的“水源”,我们只有保证“水源”的持续、纯净,才能不断地“滋养”推荐系统,使其高效地运转并准确地输出。在深度学习时代,深度学习模型对于“水源”的要求更高了,首先是水量要大,只有这样才能保证我们训练出的深度学习模型能够尽快收敛;其次是“水流”要快,让数据能够尽快地流到模型更新训练的模块,这样才能够让模型实时的抓住用户兴趣变化的趋势,这就推动了大数据引擎 Spark,以及流计算平台 Flink 的发展和应用。

3.2 第二部分:推荐系统的模型部分

推荐系统的“模型部分”是推荐系统的主体。
模型的结构一般由“召回层”、“排序层”以及“补充策略与算法层”组成
其中,“召回层”一般由高效的召回规则、算法或简单的模型组成,这让推荐系统能快速从海量的候选集中召回用户可能感兴趣的物品。“排序层”则是利用排序模型对初筛的候选集进行精排序。而“补充策略与算法层”,也被称为“再排序层”,是在返回给用户推荐列表之前,为兼顾结果的“多样性”“流行度”“新鲜度”等指标,结合一些补充的策略和算法对推荐列表进行一定的调整,最终形成用户可见的推荐列表。从推荐系统模型接收到所有候选物品集,到最后产生推荐列表,这一过程一般叫做“模型服务过程”。为了生成模型服务过程所需的模型参数,我们需要通过模型训练(Model Training)确定模型结构、结构中不同参数权重的具体数值,以及模型相关算法和策略中的参数取值。

模型的训练方法根据环境的不同,可以分为“离线训练”和“在线更新”两部分
其中,离线训练的特点是可以利用全量样本和特征,使模型逼近全局最优点,而在线更新则可以准实时地“消化”新的数据样本,更快地反应新的数据变化趋势,满足模型实时性的需求。除此之外,为了评估推荐系统模型的效果,以及模型的迭代优化,推荐系统的模型部分还包括“离线评估”和“线上 A/B 测试”等多种评估模块,用来得出线下和线上评估指标,指导下一步的模型迭代优化。

我们刚才说过,深度学习对于推荐系统的革命集中在模型部分,那具体都有什么呢?我把最典型的深度学习应用总结成了 3 点:
1.深度学习中 Embedding 技术在召回层的应用。作为深度学习中非常核心的 Embedding 技术,将它应用在推荐系统的召回层中,做相关物品的快速召回,已经是业界非常主流的解决方案了。
2.不同结构的深度学习模型在排序层的应用。排序层(也称精排层)是影响推荐效果的重中之重,也是深度学习模型大展拳脚的领域。深度学习模型的灵活性高,表达能力强的特点,这让它非常适合于大数据量下的精确排序。深度学习排序模型毫无疑问是业界和学界都在不断加大投入,快速迭代的部分。
3.增强学习在模型更新、工程模型一体化方向上的应用。增强学习可以说是与深度学习密切相关的另一机器学习领域,它在推荐系统中的应用,让推荐系统可以在实时性层面更上一层楼。

4.Netflix 的推荐系统的经典架构图

*20201129 深度学习推荐系统_王喆 学习笔记 - 图3

数据部分:event distribution, hadoop, query results, netflex.hermes, user event queue, netflix.manhattan, nearline computation.
模型部分:model training, models, online computation, online service, algorithm service.

02 | Sparrow RecSys:我们要实现什么样的推荐系统?

1.运行一个实例

https://github.com/wzhe06/SparrowRecSys

然后,你可以在本地以 maven project 的形式安装,也就是导入项目到 IDE。我推荐你使用 IntelliJ IDEA 为本项目的 IDE。这样,我们直接使用 IDEA,打开本地的 Sparrow Recsys 项目根目录就能导入项目。不过有一点需要注意,如果项目没有自动识别为 maven project,你还需要右键点击 pom.xml 文件,选择将该项目设置为 maven project 才能进行后面的操作。

最后,运行 RecSysServer。等到所有库文件自动下载完毕,项目编译完毕后,我们找到项目的主函数com.wzhe.sparrowrecsys.online.RecSysServer,右键点击运行。因为推荐服务器默认运行在 6010 端口,所以我们打开浏览器,输入http://localhost:6010/,就能看到整个推荐系统的前端效果了。
*20201129 深度学习推荐系统_王喆 学习笔记 - 图4

*20201129 深度学习推荐系统_王喆 学习笔记 - 图5

2. “麻雀虽小,五脏俱全”的 Sparrow Recsys

Sparrow Recsys 的数据来源于:https://grouplens.org/datasets/movielens/

*20201129 深度学习推荐系统_王喆 学习笔记 - 图6
*20201129 深度学习推荐系统_王喆 学习笔记 - 图7

初识实践工具,走好入门第一步
咱们这门课之后的实战环节,还会涉及很多推荐系统相关的工具,比如 Spark、TensorFlow、Redis、Jetty Server 等等。为了在之后的学习中避免一些上手的困难,我建议你先熟悉一下 Spark、TensorFlow 和 Redis 这三个工具。如果你对这些工具完全没有概念,可以通过我在下面介绍进行初步的了解。

https://time.geekbang.org/column/article/292682

首先是 Spark。它是业界最流行的分布式计算平台,如果你还没有相关经验的话,我建议你按照我给出的三步来学习。首先,你可以通过这篇文章(如何用形象的比喻描述大数据的技术生态?Hadoop、Hive、Spark 之间是什么关系?)来了解一下大数据的生态,然后,你可以通过 Spark 的官方教程尝试写一个 Spark Hello World 程序(在我们的 SparrowRecSys 里面新建一个 Scala 文件就可以)。因为咱们这门课大量使用了 Spark 的机器学习库 Spark MLlib,所以最后可以通过这个官方教程来做一些初步的了解。

其次是 TensorFlow。它是我们这门课要使用的训练深度学习模型的平台。Keras 是一套 TensorFlow 支持的 API,因为它的易用性,我们主要利用 Keras API 来实现我们的推荐模型。所以,我们第一步可以先看一篇介绍 TensorFlow 和 Keras 的基本概念的文章,对它们有一个初步的认识,再通过给 TensorFlow 的 Keras 接口写一个 Hello World项目做一个基本的上手实践。最后,如果你还有时间,可以通过TensorFlow 官方教程进一步熟悉 TensorFlow 的其他功能。

最后是 Redis。Redis 是我们这门课要频繁使用的内存数据库,用来存储模型所需线上特征。你可以先在官网熟悉一下Redis的基本介绍,然后下载安装它,最后尝试使用 Redis 内置客户端 redis-cli,来执行几条基本的Redis命令

03 | 深度学习基础:你打牢深度学习知识的地基了吗?
相关书籍推荐

04 | 特征工程:推荐系统有哪些可供利用的特征?

1.什么是特征工程

特征其实是对某个行为过程相关信息的抽象表达
在这个系统之中,特征工程就是利用工程手段从“用户信息”“物品信息”“场景信息”中提取特征的过程

2.构建推荐系统特征工程的原则

尽可能地让特征工程抽取出的一组特征,能够保留推荐环境及用户行为过程中的所有“有用“信息,并且尽量摒弃冗余信息。

*20201129 深度学习推荐系统_王喆 学习笔记 - 图8

不过,并不是所有的要素都能特征化。比如,“自己当时的心情”这个要素就被我们无奈地舍弃了,这是因为我们很难找到可用的信息,更别说抽取出特征了;再比如,“电影海报是否吸引人“这个要素,我们可以利用一些图像处理的方法提取出海报中的某些要点(比如海报中有哪些演员?是什么风格?),但想面面俱到地提取出海报中所有的图像要素,几乎是不可能的。

因此,在已有的、可获得的数据基础上,“尽量”保留有用信息是现实中构建特征工程的原则。

3.推荐系统中的常用特征

  1. 用户行为数据
    用户行为数据是推荐系统最常用,也是最关键的数据。用户的潜在兴趣、用户对物品的真实评价都包含在用户的行为历史中。用户行为在推荐系统中一般分为显性反馈行为(Explicit Feedback)和隐性反馈行为(Implicit Feedback)两种,在不同的业务场景中,它们会以不同的形式体现。具体是怎么表现的呢?你可以看我下面给出的几个例子。
    *20201129 深度学习推荐系统_王喆 学习笔记 - 图9
    2. 用户关系数据

用户关系数据也可以分为“显性”和“隐性”两种,或者称为“强关系”和“弱关系”。用户与用户之间可以通过“关注”“好友关系”等连接建立“强关系”,也可以通过“互相点赞”“同处一个社区”,甚至“同看一部电影”建立“弱关系”。

  1. 属性、标签类数据

*20201129 深度学习推荐系统_王喆 学习笔记 - 图10

  1. 内容类数据
    内容类数据可以看作属性标签型特征的延伸,同样是描述物品或用户的数据,但相比标签类特征,内容类数据往往是大段的描述型文字、图片,甚至视频。

一般来说,内容类数据无法直接转换成推荐系统可以“消化”的特征,需要通过自然语言处理、计算机视觉等技术手段提取关键内容特征,再输入推荐系统。例如,在图片类、视频类或是带有图片的信息流推荐场景中,我们往往会利用计算机视觉模型进行目标检测,抽取图片特征,再把这些特征(要素)转换成标签类数据供推荐系统使用。

而文字信息则更多是通过自然语言处理的方法提取关键词、主题、分类等信息,一旦这些特征被提取出来,就跟处理属性、标签类特征的方法一样,通过 Multi-hot 编码,Embedding 等方式输入推荐系统进行训练。

  1. 场景信息(上下文信息)

最后一大类是场景信息,或称为上下文信息(Context),它是描述推荐行为产生的场景的信息。最常用的上下文信息是“时间”和通过 GPS、IP 地址获得的“地点”信息。根据推荐场景的不同,上下文信息的范围极广,除了我们上面提到的时间和地点,还包括“当前所处推荐页面”“季节”“月份”“是否节假日”“天气”“空气质量”“社会大事件”等等。

场景特征描述的是用户所处的客观的推荐环境,广义上来讲,任何影响用户决定的因素都可以当作是场景特征的一部分。但在实际的推荐系统应用中,由于一些特殊场景特征的获取极其困难,我们更多还是利用时间、地点、推荐页面这些易获取的场景特征。

05 | 特征处理:如何利用Spark解决特征处理问题?

1.业界主流的大数据处理利器 -Spark

Spark 是一个分布式计算平台。所谓分布式,指的是计算节点之间不共享内存,需要通过网络通信的方式交换数据。Spark 最典型的应用方式就是建立在大量廉价的计算节点上,这些节点可以是廉价主机,也可以是虚拟的 docker container(Docker 容器)。理解了 Spark 的基本概念,我们来看看它的架构。
从下面 Spark 的架构图中我们可以看到,Spark 程序由 Manager node(管理节点)进行调度组织,由 Worker Node(工作节点)进行具体的计算任务执行,最终将结果返回给 Drive Program(驱动程序)。在物理的 worker node 上,数据还会分为不同的 partition(数据分片),可以说 partition 是 Spark 的基础数据单元。

*20201129 深度学习推荐系统_王喆 学习笔记 - 图11

spark 计算集群能够比传统的单机高性能服务器具备更强大的计算能力,就是由这些成百上千,甚至达到万以上规模的工作节点并行工作带来的。那在执行一个具体任务的时候,Spark 是怎么协同这么多的工作节点,通过并行计算得出最终的结果呢?

这里我们用一个任务来解释一下 Spark 的工作过程。这个任务并不复杂,我们需要先从本地硬盘读取文件 textFile,再从分布式文件系统 HDFS 读取文件 hadoopFile,然后分别对它们进行处理,再把两个文件按照 ID 都 join 起来得到最终的结果。这里你没必要执着于任务的细节,只要清楚任务的大致流程就好。在 Spark 平台上处理这个任务的时候,它会将这个任务拆解成一个子任务 DAG(Directed Acyclic Graph,有向无环图),再根据 DAG 决定程序各步骤执行的方法。

从下图中我们可以看到,这个 Spark 程序分别从 textFile 和 hadoopFile 读取文件,再经过一系列 map、filter 等操作后进行 join,最终得到了处理结果。

*20201129 深度学习推荐系统_王喆 学习笔记 - 图12

其中,最关键的过程是我们要理解哪些是可以纯并行处理的部分,哪些是必须 shuffle(混洗)和 reduce 的部分。

这里的 shuffle 指的是所有 partition 的数据必须进行洗牌后才能得到下一步的数据,最典型的操作就是上图中的 groupByKey 操作和 join 操作。以 join 操作为例,我们必须对 textFile 数据和 hadoopFile 数据做全量的匹配才可以得到 join 后的 dataframe(Spark 保存数据的结构)。而 groupByKey 操作则需要对数据中所有相同的 key 进行合并,也需要全局的 shuffle 才能完成。

与之相比,map、filter 等操作仅需要逐条地进行数据处理和转换,不需要进行数据间的操作,因此各 partition 之间可以完全并行处理。

此外,在得到最终的计算结果之前,程序需要进行 reduce 的操作,从各 partition 上汇总统计结果,随着 partition 的数量逐渐减小,reduce 操作的并行程度逐渐降低,直到将最终的计算结果汇总到 master 节点(主节点)上。可以说,shuffle 和 reduce 操作的触发决定了纯并行处理阶段的边界。

除此之外,我还想强调的是,shuffle 操作需要在不同计算节点之间进行数据交换,非常消耗计算、通信及存储资源,因此 shuffle 操作是 spark 程序应该尽量避免的。

再用一句话总结 Spark 的计算过程:Stage 内部数据高效并行计算,Stage 边界处进行消耗资源的 shuffle 操作或者最终的 reduce 操作。

2.如何利用 One-hot 编码处理类别型特征

广义上来讲,所有的特征都可以分为两大类。第一类是类别、ID 型特征(以下简称类别型特征)。拿电影推荐来说,电影的风格、ID、标签、导演演员等信息,用户看过的电影 ID、用户的性别、地理位置信息、当前的季节、时间(上午,下午,晚上)、天气等等,这些无法用数字表示的信息全都可以被看作是类别、ID 类特征。第二类是数值型特征。简单来说就是能用数字直接表示的特征就是数值型特征,典型的包括用户的年龄、收入、电影的播放时长、点击量、点击率等。
我们进行特征处理的目的,是把所有的特征全部转换成一个数值型的特征向量,对于数值型特征,这个过程非常简单,直接把这个数值放到特征向量上相应的维度上就可以了。但是对于类别、ID 类特征,我们应该怎么处理它们呢?
这里我们就要用到 One-hot 编码(也被称为独热编码),它是将类别、ID 型特征转换成数值向量的一种最典型的编码方式。它通过把所有其他维度置为 0,单独将当前类别或者 ID 对应的维度置为 1 的方式生成特征向量。这怎么理解呢?我们举例来说,假设某样本有三个特征,分别是星期、性别和城市,我们用 [Weekday=Tuesday, Gender=Male, City=London] 来表示,用 One-hot 编码对其进行数值化的结果。

3.数值型特征的处理 - 归一化和分桶

归一化虽然能够解决特征取值范围不统一的问题,但无法改变特征值的分布。比如下图就显示了 Sparrow Recsys 中编号在前 1000 的电影平均评分分布。你可以很明显地看到,由于人们打分有“中庸偏上”的倾向,因此评分大量集中在 3.5 的附近,而且越靠近 3.5 的密度越大。这对于模型学习来说也不是一个好的现象,因为特征的区分度并不高

*20201129 深度学习推荐系统_王喆 学习笔记 - 图13
这该怎么办呢?我们经常会用分桶的方式来解决特征值分布极不均匀的问题。所谓“分桶(Bucketing)”,就是将样本按照某特征的值从高到低排序,然后按照桶的数量找到分位数,将样本分到各自的桶中,再用桶 ID 作为特征值。

当然,对于数值型特征的处理方法还远不止于此,在经典的 YouTube 深度推荐模型中,我们就可以看到一些很有意思的处理方法。比如,在处理观看时间间隔(time since last watch)和视频曝光量(#previous impressions)这两个特征的时,YouTube 模型对它们进行归一化后,又将它们各自处理成了三个特征(下图中红框内的部分),分别是原特征值 x,特征值的平方x^2,以及特征值的开方,这又是为什么呢?
*20201129 深度学习推荐系统_王喆 学习笔记 - 图14

其实,无论是平方还是开方操作,改变的还是这个特征值的分布,这些操作与分桶操作一样,都是希望通过改变特征的分布,让模型能够更好地学习到特征内包含的有价值信息。但由于我们没法通过人工的经验判断哪种特征处理方式更好,所以索性把它们都输入模型,让模型来做选择。

06 | Embedding:所有人都在谈的Embedding技术到底是什么?

说起 Embedding,我想你肯定不会陌生,至少经常听说。事实上,Embedding 技术不仅名气大,而且用 Embedding 方法进行相似物品推荐,几乎成了业界最流行的做法,无论是国外的 Facebook、Airbnb,还是在国内的阿里、美团,我们都可以看到 Embedding 的成功应用。因此,自从深度学习流行起来之后,Embedding 就成为了深度学习推荐系统方向最火热的话题之一。但是 Embedding 这个词又不是很好理解,你甚至很难给它找出一个准确的中文翻译,如果硬是翻译成“嵌入”“向量映射”,感觉也不知所谓。所以索性我们就还是用 Embedding 这个叫法吧。那这项技术到底是什么,为什么它在推荐系统领域这么重要?最经典的 Embedding 方法 Word2vec 的原理细节到底啥样?这节课,我们就一起来聊聊这几个问题。

1.什么是 Embedding?

简单来说,Embedding 就是用一个数值向量“表示”一个对象(Object)的方法,我这里说的对象可以是一个词、一个物品,也可以是一部电影等等。但是“表示”这个词是什么意思呢?用一个向量表示一个物品,这句话感觉还是有点让人费解。

这里,我先尝试着解释一下:一个物品能被向量表示,是因为这个向量跟其他物品向量之间的距离反映了这些物品的相似性。更进一步来说,两个向量间的距离向量甚至能够反映它们之间的关系。这个解释听上去可能还是有点抽象,那我们再用两个具体的例子解释一下。

图 1 是 Google 著名的论文 word2vec 中的例子,它利用 word2vec 这个模型把单词映射到了高维空间中,每个单词在这个高维空间中的位置都非常有意思,你看图 1 左边的例子,从 king 到 queen 的向量和从 man 到 woman 的向量,无论从方向还是尺度来说它们都异常接近。这说明什么?这说明词 Embedding 向量间的运算居然能够揭示词之间的性别关系!

比如 woman 这个词的词向量可以用下面的运算得出:
Embedding(woman)=Embedding(man)+[Embedding(queen)-Embedding(king)]

同样,图 1 右的例子也很典型,从 walking 到 walked 和从 swimming 到 swam 的向量基本一致,这说明词向量揭示了词之间的时态关系!这就是 Embedding 技术的神奇之处。

*20201129 深度学习推荐系统_王喆 学习笔记 - 图15

你可能会觉得词向量技术离推荐系统领域还是有一点远,那 Netflix 应用的电影 Embedding 向量方法,就是一个非常直接的推荐系统应用。从 Netflix 利用矩阵分解方法生成的电影和用户的 Embedding 向量示意图中,我们可以看出不同的电影和用户分布在一个二维的空间内,由于 Embedding 向量保存了它们之间的相似性关系,因此有了这个 Embedding 空间之后,我们再进行电影推荐就非常容易了。具体来说就是,我们直接找出某个用户向量周围的电影向量,然后把这些电影推荐给这个用户就可以了。这就是 Embedding 技术在推荐系统中最直接的应用。

*20201129 深度学习推荐系统_王喆 学习笔记 - 图16

2. Embedding 技术对深度学习推荐系统的重要性

  1. 首先,Embedding 是处理稀疏特征的利器。
    上节课我们学习了 One-hot 编码,因为推荐场景中的类别、ID 型特征非常多,大量使用 One-hot 编码会导致样本特征向量极度稀疏,而深度学习的结构特点又不利于稀疏特征向量的处理,因此几乎所有深度学习推荐模型都会由 Embedding 层负责将稀疏高维特征向量转换成稠密低维特征向量。所以说各类 Embedding 技术是构建深度学习推荐模型的基础性操作。

2.其次,Embedding 可以融合大量有价值信息,本身就是极其重要的特征向量
相比由原始信息直接处理得来的特征向量,Embedding 的表达能力更强,特别是 Graph Embedding 技术被提出后,Embedding 几乎可以引入任何信息进行编码,使其本身就包含大量有价值的信息,所以通过预训练得到的 Embedding 向量本身就是极其重要的特征向量。

这两个特点也是我们为什么把 Embedding 的相关内容放到特征工程篇的原因,因为它不仅是一种处理稀疏特征的方法,也是融合大量基本特征,生成高阶特征向量的有效手段。

2.经典的 Embedding 方法,Word2vec

提到 Embedding,就一定要深入讲解一下 Word2vec。它不仅让词向量在自然语言处理领域再度流行,更关键的是,自从 2013 年谷歌提出 Word2vec 以来,Embedding 技术从自然语言处理领域推广到广告、搜索、图像、推荐等几乎所有深度学习的领域,成了深度学习知识框架中不可或缺的技术点。Word2vec 作为经典的 Embedding 方法,熟悉它对于我们理解之后所有的 Embedding 相关技术和概念都是至关重要的。下面,我就给你详细讲一讲 Word2vec 的原理。

3.什么是 Word2vec?

Word2vec 是“word to vector”的简称,顾名思义,它是一个生成对“词”的向量表达的模型。

想要训练 Word2vec 模型,我们需要准备由一组句子组成的语料库。假设其中一个长度为 T 的句子包含的词有 w1,w2……wt,并且我们假定每个词都跟其相邻词的关系最密切。根据模型假设的不同,Word2vec 模型分为两种形式,CBOW 模型(图 3 左)和 Skip-gram 模型(图 3 右)。其中,CBOW 模型假设句子中每个词的选取都由相邻的词决定,因此我们就看到 CBOW 模型的输入是 wt周边的词,预测的输出是 wt。Skip-gram 模型则正好相反,它假设句子中的每个词都决定了相邻词的选取,所以你可以看到 Skip-gram 模型的输入是 wt,预测的输出是 wt周边的词。按照一般的经验,Skip-gram 模型的效果会更好一些,所以我接下来也会以 Skip-gram 作为框架,来给你讲讲 Word2vec 的模型细节。

*20201129 深度学习推荐系统_王喆 学习笔记 - 图17

4.Word2vec 的样本是怎么生成的?

我们先来看看训练 Word2vec 的样本是怎么生成的。 作为一个自然语言处理的模型,训练 Word2vec 的样本当然来自于语料库,比如我们想训练一个电商网站中关键词的 Embedding 模型,那么电商网站中所有物品的描述文字就是很好的语料库。
我们从语料库中抽取一个句子,选取一个长度为 2c+1(目标词前后各选 c 个词)的滑动窗口,将滑动窗口由左至右滑动,每移动一次,窗口中的词组就形成了一个训练样本。根据 Skip-gram 模型的理念,中心词决定了它的相邻词,我们就可以根据这个训练样本定义出 Word2vec 模型的输入和输出,输入是样本的中心词,输出是所有的相邻词。
为了方便你理解,我再举一个例子。这里我们选取了“Embedding 技术对深度学习推荐系统的重要性”作为句子样本。首先,我们对它进行分词、去除停用词的过程,生成词序列,再选取大小为 3 的滑动窗口从头到尾依次滑动生成训练样本,然后我们把中心词当输入,边缘词做输出,就得到了训练 Word2vec 模型可用的训练样本。
*20201129 深度学习推荐系统_王喆 学习笔记 - 图18

5.Word2vec 模型的结构是什么样的?

有了训练样本之后,我们最关心的当然是 Word2vec 这个模型的结构是什么样的。我相信,通过第 3 节课的学习,你已经掌握了神经网络的基础知识,那再理解 Word2vec 的结构就容易多了,它的结构本质上就是一个三层的神经网络(如图 5)。
*20201129 深度学习推荐系统_王喆 学习笔记 - 图19
它的输入层和输出层的维度都是 V,这个 V 其实就是语料库词典的大小。假设语料库一共使用了 10000 个词,那么 V 就等于 10000。根据图 4 生成的训练样本,这里的输入向量自然就是由输入词转换而来的 One-hot 编码向量,输出向量则是由多个输出词转换而来的 Multi-hot 编码向量,显然,基于 Skip-gram 框架的 Word2vec 模型解决的是一个多分类问题。
隐层的维度是 N,N 的选择就需要一定的调参能力了,我们需要对模型的效果和模型的复杂度进行权衡,来决定最后 N 的取值,并且最终每个词的 Embedding 向量维度也由 N 来决定。
最后是激活函数的问题,这里我们需要注意的是,隐层神经元是没有激活函数的,或者说采用了输入即输出的恒等函数作为激活函数,而输出层神经元采用了 softmax 作为激活函数。
你可能会问为什么要这样设置 Word2vec 的神经网络,以及我们为什么要这样选择激活函数呢?因为这个神经网络其实是为了表达从输入向量到输出向量的这样的一个条件概率关系,我们看下面的式子:
*20201129 深度学习推荐系统_王喆 学习笔记 - 图20
这个由输入词 WI 预测输出词 WO 的条件概率,其实就是 Word2vec 神经网络要表达的东西。我们通过极大似然的方法去最大化这个条件概率,就能够让相似的词的内积距离更接近,这就是我们希望 Word2vec 神经网络学到的。
如果你是一个理论派,其实 Word2vec 还有很多值得挖掘的东西,比如,为了节约训练时间,Word2vec 经常会采用负采样(Negative Sampling)或者分层 softmax(Hierarchical Softmax)的训练方法。关于这一点,我推荐你去阅读Word2vec Parameter Learning Explained这篇文章,相信你会找到最详细和准确的解释。

6.怎样把词向量从 Word2vec 模型中提取出来?

在训练完 Word2vec 的神经网络之后,可能你还会有疑问,我们不是想得到每个词对应的 Embedding 向量嘛,这个 Embedding 在哪呢?其实,它就藏在输入层到隐层的权重矩阵 WVxN 中。我想看了下面的图你一下就明白了。
*20201129 深度学习推荐系统_王喆 学习笔记 - 图21
你可以看到,输入向量矩阵 WVxN 的每一个行向量对应的就是我们要找的“词向量”。比如我们要找词典里第 i 个词对应的 Embedding,因为输入向量是采用 One-hot 编码的,所以输入向量的第 i 维就应该是 1,那么输入向量矩阵 WVxN 中第 i 行的行向量自然就是该词的 Embedding 啦。
细心的你可能也发现了,输出向量矩阵 W‘也遵循这个道理,确实是这样的,但一般来说,我们还是习惯于使用输入向量矩阵作为词向量矩阵。
在实际的使用过程中,我们往往会把输入向量矩阵转换成词向量查找表(Lookup table,如图 7 所示)。例如,输入向量是 10000 个词组成的 one hot 向量,隐层维度是 300 维,那么输入层到隐层的权重矩阵为 10000x300 维。在转换为词向量 Lookup table 后,每行的权重即成了对应词的 Embedding 向量。如果我们把这个查找表存储到线上的数据库中,就可以轻松地在推荐物品的过程中使用 Embedding 去计算相似性等重要的特征了。
*20201129 深度学习推荐系统_王喆 学习笔记 - 图22

7.Word2vec 对 Embedding 技术的奠基性意义

Word2vec 是由谷歌于 2013 年正式提出的,其实它并不完全是原创性的,学术界对词向量的研究可以追溯到 2003 年,甚至更早的时期。但正是谷歌对 Word2vec 的成功应用,让词向量的技术得以在业界迅速推广,进而使 Embedding 这一研究话题成为热点。毫不夸张地说,Word2vec 对深度学习时代 Embedding 方向的研究具有奠基性的意义。从另一个角度来看,Word2vec 的研究中提出的模型结构、目标函数、负采样方法、负采样中的目标函数在后续的研究中被重复使用并被屡次优化。掌握 Word2vec 中的每一个细节成了研究 Embedding 的基础。从这个意义上讲,熟练掌握本节课的内容是非常重要的。

8.Item2Vec:Word2vec 方法的推广

在 Word2vec 诞生之后,Embedding 的思想迅速从自然语言处理领域扩散到几乎所有机器学习领域,推荐系统也不例外。既然 Word2vec 可以对词“序列”中的词进行 Embedding,那么对于用户购买“序列”中的一个商品,用户观看“序列”中的一个电影,也应该存在相应的 Embedding 方法。
*20201129 深度学习推荐系统_王喆 学习笔记 - 图23
于是,微软于 2015 年提出了 Item2Vec 方法,它是对 Word2vec 方法的推广,使 Embedding 方法适用于几乎所有的序列数据。Item2Vec 模型的技术细节几乎和 Word2vec 完全一致,只要能够用序列数据的形式把我们要表达的对象表示出来,再把序列数据“喂”给 Word2vec 模型,我们就能够得到任意物品的 Embedding 了。Item2vec 的提出对于推荐系统来说当然是至关重要的,因为它使得“万物皆 Embedding”成为了可能。对于推荐系统来说,Item2vec 可以利用物品的 Embedding 直接求得它们的相似性,或者作为重要的特征输入推荐模型进行训练,这些都有助于提升推荐系统的效果。
*20201129 深度学习推荐系统_王喆 学习笔记 - 图24
这节课,我们主要对序列数据进行了 Embedding 化,那如果是图结构的数据怎么办呢?另外,有没有什么好用的工具能实现 Embedding 技术呢?接下来的两节课,我就会一一讲解图结构数据的 Embedding 方法 Graph Embedding,并基于 Spark 对它们进行实现。
07 | Embedding进阶:如何利用图结构数据生成Graph Embedding?

1.互联网中有哪些图结构数据?

可能有的同学还不太清楚图结构中到底包含了哪些重要信息,为什么我们希望好好利用它们,并以它们为基础生成 Embedding?下面,我就先带你认识一下互联网中那些非常典型的图结构数据(如图 1)。
*20201129 深度学习推荐系统_王喆 学习笔记 - 图25

2.基于随机游走的 Graph Embedding 方法,Deep Walk

我们先来学习一种在业界影响力比较大,应用也很广泛的 Graph Embedding 方法,Deep Walk,它是 2014 年由美国石溪大学的研究者提出的。它的主要思想是在由物品组成的图结构上进行随机游走,产生大量物品序列,然后将这些物品序列作为训练样本输入 Word2vec 进行训练,最终得到物品的 Embedding。因此,DeepWalk 可以被看作连接序列 Embedding 和 Graph Embedding 的一种过渡方法。下图 2 展示了 DeepWalk 方法的执行过程。
*20201129 深度学习推荐系统_王喆 学习笔记 - 图26
接下来,我就参照图 2 中 4 个示意图,来为你详细讲解一下 DeepWalk 的算法流程。首先,我们基于原始的用户行为序列(图 2(a)),比如用户的购买物品序列、观看视频序列等等,来构建物品关系图(图 2(b))。从中,我们可以看出,因为用户 Ui先后购买了物品 A 和物品 B,所以产生了一条由 A 到 B 的有向边。如果后续产生了多条相同的有向边,则有向边的权重被加强。在将所有用户行为序列都转换成物品相关图中的边之后,全局的物品相关图就建立起来了。
然后,我们采用随机游走的方式随机选择起始点,重新产生物品序列(图 2©)。其中,随机游走采样的次数、长度等都属于超参数,需要我们根据具体应用进行调整。最后,我们将这些随机游走生成的物品序列输入图 2(d) 的 Word2vec 模型,生成最终的物品 Embedding 向量。在上述 DeepWalk 的算法流程中,唯一需要形式化定义的就是随机游走的跳转概率,也就是到达节点 vi后,下一步遍历 vi 的邻接点 vj 的概率。如果物品关系图是有向有权图,那么从节点 vi 跳转到节点 vj 的概率定义如下:
*20201129 深度学习推荐系统_王喆 学习笔记 - 图27
其中,N+(vi) 是节点 vi所有的出边集合,Mij是节点 vi到节点 vj边的权重,即 DeepWalk 的跳转概率就是跳转边的权重占所有相关出边权重之和的比例。如果物品相关图是无向无权重图,那么跳转概率将是 (式 1) 的一个特例,即权重 Mij将为常数 1,且 N+(vi) 应是节点 vi所有“边”的集合,而不是所有“出边”的集合。
再通过随机游走得到新的物品序列,我们就可以通过经典的 Word2vec 的方式生成物品 Embedding 了。当然,关于 Word2vec 的细节你可以回顾上一节课的内容,这里就不再赘述了。

3.在同质性和结构性间权衡的方法,Node2vec

2016 年,斯坦福大学的研究人员在 DeepWalk 的基础上更进一步,他们提出了 Node2vec 模型。Node2vec 通过调整随机游走跳转概率的方法,让 Graph Embedding 的结果在网络的同质性(Homophily)和结构性(Structural Equivalence)中进行权衡,可以进一步把不同的 Embedding 输入推荐模型,让推荐系统学习到不同的网络结构特点。
我这里所说的网络的“同质性”指的是距离相近节点的 Embedding 应该尽量近似,如图 3 所示,节点 u 与其相连的节点 s1、s2、s3、s4的 Embedding 表达应该是接近的,这就是网络“同质性“的体现。在电商网站中,同质性的物品很可能是同品类、同属性,或者经常被一同购买的物品。
而“结构性”指的是结构上相似的节点的 Embedding 应该尽量接近,比如图 3 中节点 u 和节点 s6都是各自局域网络的中心节点,它们在结构上相似,所以它们的 Embedding 表达也应该近似,这就是“结构性”的体现。在电商网站中,结构性相似的物品一般是各品类的爆款、最佳凑单商品等拥有类似趋势或者结构性属性的物品。
*20201129 深度学习推荐系统_王喆 学习笔记 - 图28
理解了这些基本概念之后,那么问题来了,Graph Embedding 的结果究竟是怎么表达结构性和同质性的呢?首先,为了使 Graph Embedding 的结果能够表达网络的“结构性”,在随机游走的过程中,我们需要让游走的过程更倾向于 BFS(Breadth First Search,宽度优先搜索),因为 BFS 会更多地在当前节点的邻域中进行游走遍历,相当于对当前节点周边的网络结构进行一次“微观扫描”。当前节点是“局部中心节点”,还是“边缘节点”,亦或是“连接性节点”,其生成的序列包含的节点数量和顺序必然是不同的,从而让最终的 Embedding 抓取到更多结构性信息。
而为了表达“同质性”,随机游走要更倾向于 DFS(Depth First Search,深度优先搜索)才行,因为 DFS 更有可能通过多次跳转,游走到远方的节点上。但无论怎样,DFS 的游走更大概率会在一个大的集团内部进行,这就使得一个集团或者社区内部节点的 Embedding 更为相似,从而更多地表达网络的“同质性”。
那在 Node2vec 算法中,究竟是怎样控制 BFS 和 DFS 的倾向性的呢?
其实,它主要是通过节点间的跳转概率来控制跳转的倾向性。图 4 所示为 Node2vec 算法从节点 t 跳转到节点 v 后,再从节点 v 跳转到周围各点的跳转概率。这里,你要注意这几个节点的特点。比如,节点 t 是随机游走上一步访问的节点,节点 v 是当前访问的节点,节点 x1、x2、x3是与 v 相连的非 t 节点,但节点 x1还与节点 t 相连,这些不同的特点决定了随机游走时下一次跳转的概率。
*20201129 深度学习推荐系统_王喆 学习笔记 - 图29
这些概率我们还可以用具体的公式来表示,从当前节点 v 跳转到下一个节点 x 的概率 πvx=αpq(t,x)⋅ωvx ,其中 wvx 是边 vx 的原始权重,αpq(t,x) 是 Node2vec 定义的一个跳转权重。到底是倾向于 DFS 还是 BFS,主要就与这个跳转权重的定义有关了。这里我们先了解一下它的精确定义,我再作进一步的解释:
*20201129 深度学习推荐系统_王喆 学习笔记 - 图30
αpq(t,x) 里的 dtx是指节点 t 到节点 x 的距离,比如节点 x1其实是与节点 t 直接相连的,所以这个距离 dtx就是 1,节点 t 到节点 t 自己的距离 dtt就是 0,而 x2、x3这些不与 t 相连的节点,dtx就是 2。
此外,αpq(t,x) 中的参数 p 和 q 共同控制着随机游走的倾向性。参数 p 被称为返回参数(Return Parameter),p 越小,随机游走回节点 t 的可能性越大,Node2vec 就更注重表达网络的结构性。参数 q 被称为进出参数(In-out Parameter),q 越小,随机游走到远方节点的可能性越大,Node2vec 更注重表达网络的同质性。反之,当前节点更可能在附近节点游走。你可以自己尝试给 p 和 q 设置不同大小的值,算一算从 v 跳转到 t、x1、x2和 x3的跳转概率。这样一来,应该就不难理解我刚才所说的随机游走倾向性的问题啦。
*20201129 深度学习推荐系统_王喆 学习笔记 - 图31
毫无疑问,Node2vec 所体现的网络的同质性和结构性,在推荐系统中都是非常重要的特征表达。由于 Node2vec 的这种灵活性,以及发掘不同图特征的能力,我们甚至可以把不同 Node2vec 生成的偏向“结构性”的 Embedding 结果,以及偏向“同质性”的 Embedding 结果共同输入后续深度学习网络,以保留物品的不同图特征信息。

4.Embedding 是如何应用在推荐系统的特征工程中的?

到这里,我们已经学习了好几种主流的 Embedding 方法,包括序列数据的 Embedding 方法,Word2vec 和 Item2vec,以及图数据的 Embedding 方法,Deep Walk 和 Node2vec。那你有没有想过,我为什么要在特征工程这一模块里介绍 Embedding 呢?Embedding 又是是怎么应用到推荐系统中的呢?这里,我就来做一个统一的解答。第一个问题不难回答,由于 Embedding 的产出就是一个数值型特征向量,所以 Embedding 技术本身就可以视作特征处理方式的一种。只不过与简单的 One-hot 编码等方式不同,Embedding 是一种更高阶的特征处理方法,它具备了把序列结构、网络结构、甚至其他特征融合到一个特征向量中的能力。
而第二个问题的答案有三个。Embedding 在推荐系统中的应用方式大致有三种,分别是“直接应用”、“预训练应用”和“End2End 应用”。
其中,“直接应用”最简单,就是在我们得到 Embedding 向量之后,直接利用 Embedding 向量的相似性实现某些推荐系统的功能。典型的功能有,利用物品 Embedding 间的相似性实现相似物品推荐,利用物品 Embedding 和用户 Embedding 的相似性实现“猜你喜欢”等经典推荐功能,还可以利用物品 Embedding 实现推荐系统中的召回层等。当然,如果你还不熟悉这些应用细节,也完全不用担心,我们在之后的课程中都会讲到。
“预训练应用”指的是在我们预先训练好物品和用户的 Embedding 之后,不直接应用,而是把这些 Embedding 向量作为特征向量的一部分,跟其余的特征向量拼接起来,作为推荐模型的输入参与训练。这样做能够更好的把其他特征引入进来,让推荐模型作出更为全面且准确的预测。
第三种应用叫做“End2End 应用”。看上去这是个新的名词,它的全称叫做“End to End Training”,也就是端到端训练。不过,它其实并不神秘,指的是我们不预先训练 Embedding,而是把 Embedding 的训练与深度学习推荐模型结合起来,采用统一的、端到端的方式一起训练,直接得到包含 Embedding 层的推荐模型。这种方式非常流行,比如图 6 就展示了三个包含 Embedding 层的经典模型,分别是微软的 Deep Crossing,UCL 提出的 FNN 和 Google 的 Wide&Deep。它们的实现细节我们也会在后续课程里面介绍,你这里只需要了解这个概念就可以了。
*20201129 深度学习推荐系统_王喆 学习笔记 - 图32
*20201129 深度学习推荐系统_王喆 学习笔记 - 图33
08 | Embedding实战:如何使用Spark生成Item2vec和Graph Embedding?

1.Item2vec:序列数据的处理

好,那到这里我们明确了样本处理的思路,就是对一个用户来说,我们先过滤掉他评分低的电影,再把他评论过的电影按照时间戳排序。这样,我们就得到了一个用户的观影序列,所有用户的观影序列就组成了 Item2vec 的训练样本集。
那这个过程究竟该怎么在 Spark 上实现呢?其实很简单,我们只需要明白这 5 个关键步骤就可以实现了:
读取 ratings 原始数据到 Spark 平台。
用 where 语句过滤评分低的评分记录。
用 groupBy userId 操作聚合每个用户的评分记录,DataFrame 中每条记录是一个用户的评分序列。
定义一个自定义操作 sortUdf,用它实现每个用户的评分记录按照时间戳进行排序。
把每个用户的评分记录处理成一个字符串的形式,供后续训练过程使用。

2.Item2vec:模型训练

训练数据准备好了,就该进入我们这堂课的重头戏,模型训练了。手写 Item2vec 的整个训练过程肯定是一件让人比较“崩溃”的事情,好在 Spark MLlib 已经为我们准备好了方便调用的 Word2vec 模型接口。我先把训练的代码贴在下面,然后再带你一步步分析每一行代码是在做什么。
从上面的代码中我们可以看出,Spark 的 Word2vec 模型训练过程非常简单,只需要四五行代码就可以完成。接下来,我就按照从上到下的顺序,依次给你解析其中 3 个关键的步骤。首先是创建 Word2vec 模型并设定模型参数。我们要清楚 Word2vec 模型的关键参数有 3 个,分别是 setVectorSize、setWindowSize 和 setNumIterations。其中,setVectorSize 用于设定生成的 Embedding 向量的维度,setWindowSize 用于设定在序列数据上采样的滑动窗口大小,setNumIterations 用于设定训练时的迭代次数。这些超参数的具体选择就要根据实际的训练效果来做调整了。其次,模型的训练过程非常简单,就是调用模型的 fit 接口。训练完成后,模型会返回一个包含了所有模型参数的对象。最后一步就是提取和保存 Embedding 向量,我们可以从最后的几行代码中看到,调用 getVectors 接口就可以提取出某个电影 ID 对应的 Embedding 向量,之后就可以把它们保存到文件或者其他数据库中,供其他模块使用了。在模型训练完成后,我们再来验证一下训练的结果是不是合理。我在代码中求取了 ID 为 592 电影的相似电影。这部电影叫 Batman 蝙蝠侠,我把通过 item2vec 得到相似电影放到了下面,你可以从直观上判断一下这个结果是不是合理。
当然,因为 SparrowRecsys 在演示过程中仅使用了 1000 部电影和部分用户评论集,所以我们得出的结果不一定非常准确,如果你有兴趣优化这个结果,可以去 movieLens 下载全部样本进行重新训练。

3.Graph Embedding:数据准备

到这里,我相信你已经熟悉了 Item2vec 方法的实现。接下来,我们再来说说基于随机游走的 Graph Embedding 方法,看看如何利用 Spark 来实现它。这里,我们选择 Deep Walk 方法进行实现。
*20201129 深度学习推荐系统_王喆 学习笔记 - 图34
在 Deep Walk 方法中,我们需要准备的最关键数据是物品之间的转移概率矩阵。图 3 是 Deep Walk 的算法流程图,转移概率矩阵表达了图 3(b)中的物品关系图,它定义了随机游走过程中,从物品 A 到物品 B 的跳转概率。所以我们首先来看一下如何利用 Spark 生成这个转移概率矩阵。
生成转移概率矩阵的函数输入是在训练 Item2vec 时处理好的观影序列数据。输出的是转移概率矩阵,由于转移概率矩阵比较稀疏,因此我没有采用比较浪费内存的二维数组的方法,而是采用了一个双层 Map 的结构去实现它。比如说,我们要得到物品 A 到物品 B 的转移概率,那么 transferMatrix(itemA)(itemB) 就是这一转移概率。在求取转移概率矩阵的过程中,我先利用 Spark 的 flatMap 操作把观影序列“打碎”成一个个影片对,再利用 countByValue 操作统计这些影片对的数量,最后根据这些影片对的数量求取每两个影片之间的转移概率。在获得了物品之间的转移概率矩阵之后,我们就可以进入图 3(c)的步骤,进行随机游走采样了。

4.Graph Embedding:随机游走采样过程

随机游走采样的过程是利用转移概率矩阵生成新的序列样本的过程。这怎么理解呢?首先,我们要根据物品出现次数的分布随机选择一个起始物品,之后就进入随机游走的过程。在每次游走时,我们根据转移概率矩阵查找到两个物品之间的转移概率,然后根据这个概率进行跳转。比如当前的物品是 A,从转移概率矩阵中查找到 A 可能跳转到物品 B 或物品 C,转移概率分别是 0.4 和 0.6,那么我们就按照这个概率来随机游走到 B 或 C,依次进行下去,直到样本的长度达到了我们的要求。根据上面随机游走的过程,我用 Scala 进行了实现,你可以参考下面的代码,在关键的位置我也给出了注释:
通过随机游走产生了我们训练所需的 sampleCount 个样本之后,下面的过程就和 Item2vec 的过程完全一致了,就是把这些训练样本输入到 Word2vec 模型中,完成最终 Graph Embedding 的生成。你也可以通过同样的方法去验证一下通过 Graph Embedding 方法生成的 Embedding 的效果.
*20201129 深度学习推荐系统_王喆 学习笔记 - 图35
09 | 线上服务:如何在线上提供高并发的推荐服务?

1.工业级推荐服务器的功能

图中红色的部分就是我们要详细来讲的线上服务模块。

*20201129 深度学习推荐系统_王喆 学习笔记 - 图36

可以看到,线上服务模块的功能非常繁杂,它不仅需要跟离线训练好的模型打交道,把离线模型进行上线,在线进行模型服务(Model Serving),还需要跟数据库打交道,把候选物品和离线处理好的特征载入到服务器。而且线上服务器内部的逻辑也十分地复杂,不仅包括了一些经典的过程,比如召回层和排序层,还包括一些业务逻辑,比如照顾推荐结果多样性,流行度的一些硬性的混合规则,甚至还包括了一些 AB 测试相关的测试代码。

2.高并发推荐服务的整体架构

宏观来讲,高并发推荐服务的整体架构主要由三个重要机制支撑,它们分别是负载均衡、缓存、推荐服务降级机制。下面,我们一一来看。

首先是负载均衡。它是整个推荐服务能够实现高可用、可扩展的基础。当推荐服务支持的业务量达到一定规模的时候,单独依靠一台服务器是不可行的,无论这台服务器的性能有多强大,都不可能独立支撑起高 QPS(Queries Per Second,每秒查询次数)的需求。这时候,我们就需要增加服务器来分担独立节点的压力。既然有多个劳动力在干活,那我们还需要一个“工头”来分配任务,以达到按能力分配和高效率分配的目的,这个“工头”就是所谓的“负载均衡服务器”。

下图就很好地展示了负载均衡的原理。我们可以看到,负载均衡服务器(Load Balancer)处在一个非常重要的位置。因此在实际工程中,负载均衡服务器也经常采用非常高效的 nginx 技术选型,甚至采用专门的硬件级负载均衡设备作为解决方案。

*20201129 深度学习推荐系统_王喆 学习笔记 - 图37

这个时候,有的同学可能会问,“负载均衡”解决高并发的思路是“增加劳动力”,那我们能否从“减少劳动量”的角度来解决高并发带来的负载压力呢?这是一个非常好的角度。要知道,推荐过程特别是基于深度学习的推荐过程往往是比较复杂的,进一步来说当候选物品规模比较大的时候,产生推荐列表的过程其实非常消耗计算资源,服务器的“劳动量”非常大。这个时候,我们就可以通过减少“硬算”推荐结果的次数来给推荐服务器减负,那具体怎么做呢?比如说,当同一个用户多次请求同样的推荐服务时,我们就可以在第一次请求时把 TA 的推荐结果缓存起来,在后续请求时直接返回缓存中的结果就可以了,不用再通过复杂的推荐逻辑重新算一遍。再比如说,对于新用户来说,因为他们几乎没有行为历史的记录,所以我们可以先按照一些规则预先缓存好几类新用户的推荐列表,等遇到新用户的时候就直接返回。

因此,在一个成熟的工业级推荐系统中,合理的缓存策略甚至能够阻挡掉 90% 以上的推荐请求,大大减小推荐服务器的计算压力。

但不管是再强大的服务集群,还是再有效的缓存方案,也都有可能遭遇特殊时刻的流量洪峰或者软硬件故障。在这种特殊情况下,为了防止推荐服务彻底熔断崩溃,甚至造成相关微服务依次崩溃的“雪崩效应”,我们就要在第一时间将问题控制在推荐服务内部,而应对的最好机制就是“服务降级”

所谓“服务降级”就是抛弃原本的复杂逻辑,采用最保险、最简单、最不消耗资源的降级服务来渡过特殊时期。比如对于推荐服务来说,我们可以抛弃原本的复杂推荐模型,采用基于规则的推荐方法来生成推荐列表,甚至直接在缓存或者内存中提前准备好应对故障时的默认推荐列表,做到“0”计算产出服务结果,这些都是服务降级的可行策略。

总之,“负载均衡”提升服务能力,“缓存”降低服务压力,“服务降级”机制保证故障时刻的服务不崩溃,压力不传导,这三点可以看成是一个成熟稳定的高并发推荐服务的基石。

3.搭建一个工业级推荐服务器的雏形

首先,我们要做的就是选择服务器框架。这里,我们选择的服务器框架是 Java 嵌入式服务器 Jetty。为什么我们不选择其他的服务器呢?原因有三个。

第一,相比于 Python 服务器的效率问题,以及 C++ 服务器的开发维护难度,Java 服务器在效率和开发难度上做到了一个权衡。而且互联网上有大量开源 Java 项目可以供我们直接融合调用,所以 Java 服务器开发的扩展性比较好。第二,相比 Tomcat 等其他 Java 服务器,Jetty 是嵌入式的,它更轻量级,没有过多 J2EE 的冗余功能,可以专注于建立高效的 api 推荐服务。而 Tomcat 更适用于搭建一整套的 J2EE 项目。第三,相比于基于 Nodejs、Go 这样的服务器,Java 社区更成熟和主流一些,应用范围更广。

当然,每一种技术选择都有它的优势,C++ 的效率更高,Python 更便捷,Go 的上升势头也愈发明显,我们只要清楚 Jetty 是企业级服务的选择之一就够了,我们接下来的服务器端实践也是基于 Jetty 开展的。

作为一款嵌入式服务器框架,Jetty 的最大优势是除了 Java 环境外,你不用配置任何其他环境,也不用安装额外的软件依赖,你可以直接在 Java 程序中创建对外服务的 HTTP API,之后在 IDE 中运行或者打 Jar 包运行就可以了。下面就是我们 Sparrow Recsys 中创建推荐服务器的代码,我已经在所有关键的地方添加了注释,你可以逐句解读一下。
*20201129 深度学习推荐系统_王喆 学习笔记 - 图38
10 | 存储模块:如何用Redis解决推荐系统特征的存储问题?

1.推荐系统存储模块的设计原则

Netflix 采用了非常经典的 Offline、Nearline、Online 三层推荐系统架构。架构图中最核心的位置就是我在图中用红框标出的部分,它们是三个数据库 Cassandra、MySQL 和 EVcache,这三个数据库就是 Netflix 解决特征和模型参数存储问题的钥匙。
*20201129 深度学习推荐系统_王喆 学习笔记 - 图39

你可能会觉得,存储推荐特征和模型这件事情一点儿都不难啊。不就是找一个数据库把离线的特征存起来,然后再给推荐服务器写几个 SQL 让它取出来用不就行了吗?为什么还要像 Netflix 这样兴师动众地搞三个数据库呢?

想要搞明白这个问题,我们就得搞清楚设计推荐系统存储模块的原则。对于推荐服务器来说,由于线上的 QPS 压力巨大,每次有推荐请求到来,推荐服务器都需要把相关的特征取出。这就要求推荐服务器一定要“快”。

不仅如此,对于一个成熟的互联网应用来说,它的用户数和物品数一定是巨大的,几千万上亿的规模是十分常见的。所以对于存储模块来说,这么多用户和物品特征所需的存储量会特别大。这个时候,事情就很难办了,又要存储量大,又要查询快,还要面对高 QPS 的压力。很不幸,没有一个独立的数据库能经济又高效地单独完成这样复杂的任务。

因此,几乎所有的工业级推荐系统都会做一件事情,就是把特征的存储做成分级存储,把越频繁访问的数据放到越快的数据库甚至缓存中,把海量的全量数据放到便宜但是查询速度较慢的数据库中。

举个不恰当的例子,如果你把特征数据放到基于 HDFS 的 HBase 中,虽然你可以轻松放下所有的特征数据,但要让你的推荐服务器直接访问 HBase 进行特征查询,等到查询完成,这边用户的请求早就超时中断了。而 Netflix 的三个数据库正好满足了这样分级存储的需求。

*20201129 深度学习推荐系统_王喆 学习笔记 - 图40

总的来说,推荐系统存储模块的设计原则就是“分级存储,把越频繁访问的数据放到越快的数据库甚至缓存中,把海量的全量数据放到廉价但是查询速度较慢的数据库中”

2. SparrowRecsys 的存储系统方案

在 SparrowRecsys 中,我们把存储模块的设计问题进行了一些简化,避免由于系统设计得过于复杂导致你不易上手。我们使用基础的文件系统保存全量的离线特征和模型数据,用 Redis 保存线上所需特征和模型数据,使用服务器内存缓存频繁访问的特征。

*20201129 深度学习推荐系统_王喆 学习笔记 - 图41

根据上面的特征数据,我们一起做一个初步的分析。首先,用户特征的总数比较大,它们很难全部载入到服务器内存中,所以我们把用户特征载入到 Redis 之类的内存数据库中是合理的。其次,物品特征的总数比较小,而且每次用户请求,一般只会用到一个用户的特征,但为了物品排序,推荐服务器需要访问几乎所有候选物品的特征。针对这个特点,我们完全可以把所有物品特征阶段性地载入到服务器内存中,大大减少 Redis 的线上压力。

最后,我们还要找一个地方去存储特征历史数据、样本数据等体量比较大,但不要求实时获取的数据。这个时候分布式文件系统(单机环境下以本机文件系统为例)往往是最好的选择,由于类似 HDFS 之类的分布式文件系统具有近乎无限的存储空间,我们可以把每次处理的全量特征,每次训练的 Embedding 全部保存到分布式文件系统中,方便离线评估时使用。

经过上面的分析,我们就得到了具体的存储方案,如下表:
*20201129 深度学习推荐系统_王喆 学习笔记 - 图42

3. 你需要知道的 Redis 基础知识

一是所有的数据都以 Key-value 的形式存储。 其中,Key 只能是字符串,value 可支持的数据结构包括 string(字符串)、list(链表)、set(集合)、zset(有序集合) 和 hash(哈希)。这个特点决定了 Redis 的使用方式,无论是存储还是获取,都应该以键值对的形式进行,并且根据你的数据特点,设计值的数据结构。
二是所有的数据都存储在内存中,磁盘只在持久化备份或恢复数据时起作用。这个特点决定了 Redis 的特性,一是 QPS 峰值可以很高,二是数据易丢失,所以我们在维护 Redis 时要充分考虑数据的备份问题,或者说,不应该把关键的业务数据唯一地放到 Redis 中。但对于可恢复,不关乎关键业务逻辑的推荐特征数据,就非常适合利用 Redis 提供高效的存储和查询服务。

首先是安装 Redis。 Redis 的安装过程在 linux/Unix 环境下非常简单,你参照官方网站(http://www.redis.cn/download.html)的步骤依次执行就好。而 Windows 环境下的安装过程稍复杂一些,你可以参考这篇文章
https://www.cnblogs.com/liuqingzheng/p/9831331.html)进行安装。在启动 Redis 之后,如果没有特殊的设置,Redis 服务会默认运行在 6379 端口,没有特殊情况保留这个默认的设置就可以了,因为我们的 SparrowRecSys 也是默认从 6379 端口存储和读取 Redis 数据的。

*20201129 深度学习推荐系统_王喆 学习笔记 - 图43

11|召回层:如何快速又准确地筛选掉不相关物品?

为了弄清楚召回层是什么,我们先试着解决一下这个问题:如果你是一名快手的推荐工程师,你的任务是从 500 万个候选短视频中,为一名用户推荐 10 个他最感兴趣的。你会怎么做?

我想最直接最暴力的做法,就是对这 500 万个短视频挨个打分、排序,取出得分最高的 10 个推荐给用户。如果这个打分的算法非常靠谱的话,我们肯定能够选出用户最感兴趣的 Top 10 视频。但这个过程会涉及一个非常棘手的工程问题:如果利用比较复杂的推荐模型,特别是深度学习推荐模型,对 500 万个短视频打分,这个过程是非常消耗计算资源的。

而且你要知道,这还只是计算了一个用户的推荐结果,在工业级的线上服务中,每秒可是有几十万甚至上百万的用户同时请求服务器,逐个候选视频打分产生的计算量,是任何集群都承受不了的。那在推荐物品候选集规模非常大的时候,我们该如何快速又准确地筛选掉不相关物品,从而节约排序时所消耗的计算资源呢?这其实就是推荐系统召回层要解决的问题。今天,我就从三个召回层技术方案入手,带你一起来解决这个问题。

1. 召回层和排序层的功能特点

*20201129 深度学习推荐系统_王喆 学习笔记 - 图44
召回层就是要快速、准确地过滤出相关物品,缩小候选集,排序层则要以提升推荐效果为目标,作出精准的推荐列表排序。

再详细一点说,我们可以从候选集规模、模型复杂程度、特征数量、处理速度、排序精度等几个角度来对比召回层和排序层的特点:
*20201129 深度学习推荐系统_王喆 学习笔记 - 图45

2. 如何理解“单策略召回”方法?

你会发现,今天我多次提到一个关键字,快。那怎么才能让召回层“快”起来呢?我们知道,排序层慢的原因是模型复杂,算法计算量大,那我们能不能反其道而行之,用一些简单直观的策略来实现召回层呢?当然是可以的,这就是所谓的单策略召回。

单策略召回指的是,通过制定一条规则或者利用一个简单模型来快速地召回可能的相关物品。 这里的规则其实就是用户可能感兴趣的物品的特点,我们拿 SparrowRecSys 里面的电影推荐为例。在推荐电影的时候,我们首先要想到用户可能会喜欢什么电影。按照经验来说,很有可能是这三类,分别是大众口碑好的、近期非常火热的,以及跟我之前喜欢的电影风格类似的。

基于其中任何一条,我们都可以快速实现一个单策略召回层。比如在 SparrowRecSys 中,我就制定了这样一条召回策略:如果用户对电影 A 的评分较高,比如超过 4 分,那么我们就将与 A 风格相同,并且平均评分在前 50 的电影召回,放入排序候选集中。
*20201129 深度学习推荐系统_王喆 学习笔记 - 图46

3. 如何理解“多路召回”方法

所谓“多路召回策略”,就是指采用不同的策略、特征或简单模型,分别召回一部分候选集,然后把候选集混合在一起供后续排序模型使用的策略。
其中,各简单策略保证候选集的快速召回,从不同角度设计的策略又能保证召回率接近理想的状态,不至于损害排序效果。所以,多路召回策略是在计算速度和召回率之间进行权衡的结果。这里,我们还是以电影推荐为例来做进一步的解释。下面是我给出的电影推荐中常用的多路召回策略,包括热门电影、风格类型、高分评价、最新上映以及朋友喜欢等等。除此之外,我们也可以把一些推断速度比较快的简单模型(比如逻辑回归,协同过滤等)生成的推荐结果放入多路召回层中,形成综合性更好的候选集。具体的操作过程就是,我们分别执行这些策略,让每个策略选取 Top K 个物品,最后混合多个 Top K 物品,就形成了最终的多路召回候选集。整个过程就如下所示:
*20201129 深度学习推荐系统_王喆 学习笔记 - 图47
不过,多路召回策略虽然能够比较全面地照顾到不同的召回方法,但也存在一些缺点。比如,在确定每一路的召回物品数量时,往往需要大量的人工参与和调整,具体的数值需要经过大量线上 AB 测试来决定。此外,因为策略之间的信息和数据是割裂的,所以我们很难综合考虑不同策略对一个物品的影响。那么,是否存在一个综合性强且计算速度也能满足需求的召回方法呢?

4. 基于 Embedding 的召回方法

在第 5 节和第 6 节课中,我们已经介绍了多种离线生成物品 Embedding 的方案。事实上,利用物品和用户 Embedding 相似性来构建召回层,是深度学习推荐系统中非常经典的技术方案。我们可以把它的优势总结为三方面。

一方面,多路召回中使用的“兴趣标签”“热门度”“流行趋势”“物品属性”等信息都可以作为 Embedding 方法中的附加信息(Side Information),融合进最终的 Embedding 向量中 。因此,在利用 Embedding 召回的过程中,我们就相当于考虑到了多路召回的多种策略。

另一方面,Embedding 召回的评分具有连续性。我们知道,多路召回中不同召回策略产生的相似度、热度等分值不具备可比性,所以我们无法据此来决定每个召回策略放回候选集的大小。但是,Embedding 召回却可以把 Embedding 间的相似度作为唯一的判断标准,因此它可以随意限定召回的候选集大小。

最后,在线上服务的过程中,Embedding 相似性的计算也相对简单和直接。通过简单的点积或余弦相似度的运算就能够得到相似度得分,便于线上的快速召回。

这里,我再带你简单梳理一下整体的实现思路。总的来说,我们通过 3 步生成了最终的候选集。第一步,我们获取用户的 Embedding。第二步,我们获取所有物品的候选集,并且逐一获取物品的 Embedding,计算物品 Embedding 和用户 Embedding 的相似度。第三步,我们根据相似度排序,返回规定大小的候选集。

*20201129 深度学习推荐系统_王喆 学习笔记 - 图48
12 | 局部敏感哈希:如何在常数时间内搜索Embedding最近邻?
在深度学习推荐系统中,我们经常采用 Embedding 召回这一准确又便捷的方法。但是,在面对百万甚至更高量级的候选集时,线性地逐一计算 Embedding 间的相似度,往往会造成极大的服务延迟。这个时候,我们要解决的问题就是,如何快速找到与一个 Embedding 最相似的 Embedding?这直接决定了召回层的执行速度,进而会影响推荐服务器的响应延迟。

1. 推荐系统中的“快速”Embedding 最近邻搜索问题

在深度学习推荐系统中,我们经常会使用 Embedding 方法对物品和用户进行向量化。在训练物品和用户的 Embedding 向量时,如果二者的 Embedding 在同一个向量空间内(如图 1),我们就可以通过内积、余弦、欧式距离等相似度计算方法,来计算它们之间的相似度,从而通过用户 - 物品相似度进行个性化推荐,或者通过物品 - 物品相似度进行相似物品查找。

假设用户和物品的 Embeding 都在一个 k 维的 Embedding 空间中,物品总数为 n,那么遍历计算一个用户和所有物品向量相似度的时间复杂度是多少呢?不难算出是 O(k×n)。虽然这一复杂度是线性的,但物品总数 n 达到百万甚至千万量级时,线性的时间复杂度也是线上服务不能承受的。

换一个角度思考这个问题,由于用户和物品的 Embedding 同处一个向量空间内,因此召回与用户向量最相似的物品 Embedding 向量这一问题,其实就是在向量空间内搜索最近邻的过程。如果我们能够找到高维空间快速搜索最近邻点的方法,那么相似 Embedding 的快速搜索问题就迎刃而解了。

2. 使用“聚类”还是“索引”来搜索最近邻?

聚类这个过程还是存在着一些边界情况。比如,聚类边缘的点的最近邻往往会包括相邻聚类的点,如果我们只在类别内搜索,就会遗漏这些近似点。此外,中心点的数量 k 也不那么好确定,k 选的太大,离线迭代的过程就会非常慢,k 选的太小,在线搜索的范围还是很大,并没有减少太多搜索时间。所以基于聚类的搜索还是有一定局限性的,解决上面的问题也会增加过多冗余过程,得不偿失。

既然聚类有局限性,那索引能不能奏效呢?我们这里可以尝试一下经典的向量空间索引方法 kd-tree(k-dimension tree)。与聚类不同,它是为空间中的点 / 向量建立一个索引。这该怎么理解呢?

举个例子,你可以看下图 3 中的点云,我们先用红色的线把点云一分为二,再用深蓝色的线把各自片区的点云一分为二,以此类推,直到每个片区只剩下一个点,这就完成了空间索引的构建。如果我们能够把这套索引“搬”到线上,就可以利用二叉树的结构快速找到邻接点。比如,希望找到点 q 的 m 个邻接点,我们就可以先搜索它相邻子树下的点,如果数量不
够,我们可以向上回退一个层级,搜索它父片区下的其他点,直到数量凑够 m 个为止。
*20201129 深度学习推荐系统_王喆 学习笔记 - 图49
听上去 kd-tree 索引似乎是一个完美的方案,但它还是无法完全解决边缘点最近邻的问题。对于点 q 来说,它的邻接片区是右上角的片区,但是它的最近邻点却是深蓝色切分线下方的那个点。所以按照 kd-tree 的索引方法,我们还是会遗漏掉最近邻点,它只能保证快速搜索到近似的最近邻点集合。而且 kd-tree 索引的结构并不简单,离线和在线维护的过程也相对复杂,这些都是它的弊端。
那有没有更“完美”的解决方法呢?

3. 局部敏感哈希的基本原理及多桶策略

为了“拯救”我们推荐系统的召回层,“局部敏感哈希”(Locality Sensitive Hashing,LSH)这一方法横空出世,它用简洁而高效的方法几乎完美地解决了这一问题。那它是怎么做到的呢?我们先来看看局部敏感哈希的基本原理。
1. 局部敏感哈希的基本原理
局部敏感哈希的基本思想是希望让相邻的点落入同一个“桶”,这样在进行最近邻搜索时,我们仅需要在一个桶内,或相邻的几个桶内的元素中进行搜索即可。如果保持每个桶中的元素个数在一个常数附近,我们就可以把最近邻搜索的时间复杂度降低到常数级别。

那么,如何构建局部敏感哈希中的“桶”呢?下面,我们以基于欧式距离的最近邻搜索为例,来解释构建局部敏感哈希“桶”的过程。

首先,我们要弄清楚一个问题,如果将高维空间中的点向低维空间进行映射,其欧式相对距离是不是会保持不变呢?以图 4 为例,图 4 中间的彩色点处在二维空间中,当我们把二维空间中的点通过不同角度映射到 a、b、c 这三个一维空间时可以看到原本相近的点,在一维空间中都保持着相近的距离。而原本远离的绿色点和红色点在一维空间 a 中处于接近的位置,却在空间 b 中处于远离的位置。
*20201129 深度学习推荐系统_王喆 学习笔记 - 图50

因此我们可以得出一个定性的结论:欧式空间中,将高维空间的点映射到低维空间,原本接近的点在低维空间中肯定依然接近,但原本远离的点则有一定概率变成接近的点。

利用低维空间可以保留高维空间相近距离关系的性质,我们就可以构造局部敏感哈希“桶”。对于 Embedding 向量来说,由于 Embedding 大量使用内积操作计算相似度,因此我们也可以用内积操作来构建局部敏感哈希桶。假设 v 是高维空间中的 k 维 Embedding 向量,x 是随机生成的 k 维映射向量。那我们利用内积操作可以将 v 映射到一维空间,得到数值 h(v)=v⋅x。

且,我们刚刚说了,一维空间也会部分保存高维空间的近似距离信息。因此,我们可以使用哈希函数 h(v) 进行分桶,公式为:
*20201129 深度学习推荐系统_王喆 学习笔记 - 图51
其中, ⌊⌋ 是向下取整操作, w 是分桶宽度,b 是 0 到 w 间的一个均匀分布随机变量,避免分桶边界固化。

不过,映射操作会损失部分距离信息,如果我们仅采用一个哈希函数进行分桶,必然存在相近点误判的情况,因此,我们可以采用 m 个哈希函数同时进行分桶。如果两个点同时掉进了 m 个桶,那它们是相似点的概率将大大增加。通过分桶找到相邻点的候选集合后,我们就可以在有限的候选集合中通过遍历找到目标点真正的 K 近邻了。

刚才我们讲的哈希策略是基于内积操作来制定的,内积相似度也是我们经常使用的相似度度量方法,事实上距离的定义有很多种,比如“曼哈顿距离”“切比雪夫距离”“汉明距离”等等。针对不同的距离定义,分桶函数的定义也有所不同,但局部敏感哈希通过分桶方式保留部分距离信息,大规模降低近邻点候选集的本质思想是通用的。

  1. 局部敏感哈希的多桶策略
    刚才我们讲到了可以使用多个分桶函数的方式来增加找到相似点的概率。那你可能有疑问,如果有多个分桶函数的话,具体应该如何处理不同桶之间的关系呢?这就涉及局部敏感哈希的多桶策略。

假设有 A、B、C、D、E 五个点,有 h1和 h2两个分桶函数。使用 h1来分桶时,A 和 B 掉到了一个桶里,C、D、E 掉到了一个桶里;使用 h2来分桶时,A、C、D 掉到了一个桶里,B、E 在一个桶。那么请问如果我们想找点 C 的最近邻点,应该怎么利用两个分桶结果来计算呢?如果我们用“且”(And)操作来处理两个分桶结果之间的关系,那么结果是这样的,找到与点 C 在 h1函数下同一个桶的点,且在 h2函数下同一个桶的点,作为最近邻候选点。我们可以看到,满足条件的点只有一个,那就是点 D。也就是说,点 D 最有可能是点 C 的最近邻点。

因此,用且操作作为多桶策略,可以最大程度地减少候选点数量。但是,由于哈希分桶函数不是一个绝对精确的操作,点 D 也只是最有可能的最近邻点,却不一定是最近邻点,因此,“且”操作其实也增大了漏掉最近邻点的概率。那如果我们采用“或”(Or)操作作为多桶策略,又会是什么情况呢?具体操作就是,我们找到与点 C 在 h1函数下同一个桶的点,或在 h2函数下同一个桶的点。这个时候,我们可以看到候选集中会有三个点,分别是 A、D、E。这样一来,虽然我们增大了候选集的规模,减少了漏掉最近邻点的可能性,但增大了后续计算的开销。

当然,局部敏感哈希的多桶策略还可以更加复杂,比如使用 3 个分桶函数分桶,把同时落入两个桶的点作为最近邻候选点等等。那么,我们到底应该选择“且”操作还是“或”操作,以及到底该选择使用几个分桶函数,每个分桶函数分几个桶,这还是一个工程上的权衡问题。我虽然不能给出具体的最佳数值,但可以给你一些取值的建议:
点数越多,我们越应该增加每个分桶函数中桶的个数;相反,点数越少,我们越应该减少桶的个数;
Embedding 向量的维度越大,我们越应该增加哈希函数的数量,尽量采用且的方式作为多桶策略;相反,Embedding 向量维度越小,我们越应该减少哈希函数的数量,多采用或的方式作为分桶策略。

最后,我们再回头来解决课程开头提出的问题,局部敏感哈希能在常数时间得到最近邻的结果吗?答案是可以的,如果我们能够精确的控制每个桶内的点的规模是 C,假设每个 Embedding 的维度是 N,那么找到最近邻点的时间开销将永远在 O(C⋅N) 量级。采用多桶策略之后,假设分桶函数数量是 K,那么时间开销也在 O(K⋅C⋅N) 量级,这仍然是一个常数。

事实上,在一些超大规模的最近邻搜索问题中,索引、分桶的策略还能进一步复杂。如果你有兴趣深入学习,我推荐你去了解一下Facebook 的开源向量最近邻搜索库 FAISS(https://github.com/facebookresearch/faiss),这是一个在业界广泛应用的开源解决方案。

事实上,对于推荐系统来说,我们可以把召回最相似物品 Embedding 的问题,看成是在高维的向量空间内搜索最近邻点的过程。遇到最近邻问题,我们一般会采用聚类和索引这两种方法。但是聚类和索引都无法完全解决边缘点最近邻的问题,并且对于聚类来说,中心点的数量 k 也并不好确定,而对于 kd-tree 索引来说,kd-tree 索引的结构并不简单,离线和在线维护的过程也相对复杂。

因此,解决最近邻问题最“完美”的办法就是使用局部敏感哈希,在每个桶内点的数量接近时,它能够把最近邻查找的时间控制在常数级别。为了进一步提高最近邻搜索的效率或召回率,我们还可以采用多桶策略,首先是基于“且”操作的多桶策略能够进一步减少候选集规模,增加计算效率,其次是基于“或”操作的多桶策略则能够提高召回率,减少漏掉最近邻点的可能性。最后,我在下面列出了各种方法的优缺点,希望能帮助你做一个快速的复盘。

*20201129 深度学习推荐系统_王喆 学习笔记 - 图52

13 | 模型服务:怎样把你的离线模型部署到线上?

1.业界的主流模型服务方法

由于各个公司技术栈的特殊性,采用不同的机器学习平台,模型服务的方法会截然不同,不仅如此,使用不同的模型结构和模型存储方式,也会让模型服务的方法产生区别。总的来说,那业界主流的模型服务方法有 4 种,分别是预存推荐结果或 Embedding 结果、预训练 Embedding+ 轻量级线上模型、PMML 模型以及 TensorFlow Serving。接下来,我们就详细讲讲这些方法的实现原理,通过对比它们的优缺点,相信你会找到最合适自己业务场景的方法。

1.1预存推荐结果或 Embedding 结果
对于推荐系统线上服务来说,最简单直接的模型服务方法就是在离线环境下生成对每个用户的推荐结果,然后将结果预存到以 Redis 为代表的线上数据库中。这样,我们在线上环境直接取出预存数据推荐给用户即可。这个方法的优缺点都非常明显,我把它们总结在了下图中,你可以看看。
*20201129 深度学习推荐系统_王喆 学习笔记 - 图53

由于这些优缺点的存在,这种直接存储推荐结果的方式往往只适用于用户规模较小,或者一些冷启动、热门榜单等特殊的应用场景中。

那如果在用户规模比较大的场景下,我们该怎么减少模型存储所需的空间呢?我们其实可以通过存储 Embedding 的方式来替代直接存储推荐结果。具体来说就是,我们先离线训练好 Embedding,然后在线上通过相似度运算得到最终的推荐结果。在前面的课程中,我们通过 Item2vec、Graph Embedding 等方法生成物品 Embedding,再存入 Redis 供线上使用的过程,这就是预存 Embedding 的模型服务方法的典型应用。由于,线上推断过程非常简单快速,因此,预存 Embedding 的方法是业界经常采用的模型服务手段。但它的局限性同样存在,由于完全基于线下计算出 Embedding,这样的方式无法支持线上场景特征的引入,并且无法进行复杂模型结构的线上推断,表达能力受限。因此对于复杂模型,我们还需要从模型实时线上推断的角度入手,来改进模型服务的方法。

1.2预训练 Embedding+ 轻量级线上模型
事实上,直接预存 Embedding 的方法让模型表达能力受限这个问题的产生,主要是因为我们仅仅采用了“相似度计算”这样非常简单的方式去得到最终的推荐分数。既然如此,那我们能不能在线上实现一个比较复杂的操作,甚至是用神经网络来生成最终的预估值呢?当然是可行的,这就是业界很多公司采用的“预训练 Embedding+ 轻量级线上模型”的模型服务方式。

详细一点来说,这样的服务方式指的是“用复杂深度学习网络离线训练生成 Embedding,存入内存数据库,再在线上实现逻辑回归或浅层神经网络等轻量级模型来拟合优化目标”。

口说无凭,接下来,我们就来看一个业界实际的例子。我们先来看看下面这张模型结构图,这是阿里的推荐模型 MIMN(Multi-channel user Interest Memory Network,多通道用户兴趣记忆网络)的结构。神经网络,才是真正在线上服务的部分。仔细看这张图你会注意到,左边粉色的部分是复杂模型部分,右边灰色的部分是简单模型部分。看这张图的时候,其实你不需要纠结于复杂模型的结构细节,你只要知道左边的部分不管多复杂,它们其实是在线下训练生成的,而右边的部分是一个经典的多层神经网络,它才是真正在线上服务的部分。

*20201129 深度学习推荐系统_王喆 学习笔记 - 图54

这两部分的接口在哪里呢?你可以看一看图中连接处的位置,有两个被虚线框框住的数据结构,分别是 S(1)-S(m) 和 M(1)-M(m)。它们其实就是在离线生成的 Embedding 向量,在 MIMN 模型中,它们被称为“多通道用户兴趣向量”,这些 Embedding 向量就是连接离线模型和线上模型部分的接口。

线上部分从 Redis 之类的模型数据库中拿到这些离线生成 Embedding 向量,然后跟其他特征的 Embedding 向量组合在一起,扔给一个标准的多层神经网络进行预估,这就是一个典型的“预训练 Embedding+ 轻量级线上模型”的服务方式。它的好处显而易见,就是我们隔离了离线模型的复杂性和线上推断的效率要求,离线环境下,你可以尽情地使用复杂结构构建你的模型,只要最终的结果是 Embedding,就可以轻松地供给线上推断使用。

1.3利用 PMML 转换和部署模型

虽然 Embedding+ 轻量级模型的方法既实用又高效,但它还是把模型进行了割裂,让模型不完全是 End2End(端到端)训练 +End2End 部署这种最“完美”的方式。那有没有能够在离线训练完模型之后什么都不用做,直接部署模型的方式呢?

当然是有的,也就是我接下来要讲的脱离于平台的通用模型部署方式,PMML。PMML 的全称是“预测模型标记语言”(Predictive Model Markup Language, PMML),它是一种通用的以 XML 的形式表示不同模型结构参数的标记语言。在模型上线的过程中,PMML 经常作为中间媒介连接离线训练平台和线上预测平台。

这么说可能还比较抽象。接下来,我就以 Spark MLlib 模型的训练和上线过程为例,来和你详细解释一下,PMML 在整个机器学习模型训练及上线流程中扮演的角色。

*20201129 深度学习推荐系统_王喆 学习笔记 - 图55

图 3 中的例子使用了 JPMML 作为序列化和解析 PMML 文件的 library(库),JPMML 项目分为 Spark 和 Java Server 两部分。Spark 部分的 library 完成 Spark MLlib 模型的序列化,生成 PMML 文件,并且把它保存到线上服务器能够触达的数据库或文件系统中,而 Java Server 部分则完成 PMML 模型的解析,生成预估模型,完成了与业务逻辑的整合。JPMML 在 Java Server 部分只进行推断,不考虑模型训练、分布式部署等一系列问题,因此 library 比较轻,能够高效地完成推断过程。与 JPMML 相似的开源项目还有 MLeap,同样采用了 PMML 作为模型转换和上线的媒介。事实上,JPMML 和 MLeap 也具备 Scikit-learn、TensorFlow 等简单模型的转换和上线能力。我把JPMML和MLeap的项目地址放在这里(https://github.com/jpmmlhttps://github.com/combust/mleap),感兴趣的同学可以进一步学习和实践。

1.4TensorFlow Serving
既然 PMML 已经是 End2End 训练 +End2End 部署这种最“完美”的方式了,那我们的课程中为什么不使用它进行模型服务呢?这是因为对于具有复杂结构的深度学习模型来说,PMML 语言的表示能力还是比较有限的,还不足以支持复杂的深度学习模型结构。由于咱们课程中的推荐模型篇,会主要使用 TensorFlow 来构建深度学习推荐模型,这个时候 PMML 的能力就有点不足了。想要上线 TensorFlow 模型,我们就需要借助 TensorFlow 的原生模型服务模块,也就是 TensorFlow Serving 的支持。

从整体工作流程来看,TensorFlow Serving 和 PMML 类工具的流程一致,它们都经历了模型存储、模型载入还原以及提供服务的过程。在具体细节上,TensorFlow 在离线把模型序列化,存储到文件系统,TensorFlow Serving 把模型文件载入到模型服务器,还原模型推断过程,对外以 HTTP 接口或 gRPC 接口的方式提供模型服务。
*20201129 深度学习推荐系统_王喆 学习笔记 - 图56

2.实战搭建 TensorFlow Serving 模型服务

总的来说,搭建一个 TensorFlow Serving 的服务主要有 3 步,分别是安装 Docker,建立 TensorFlow Serving 服务,以及请求 TensorFlow Serving 获得预估结果。为了提高咱们的效率,我希望你能打开电脑跟着我的讲解和文稿里的指令代码,一块儿来安装。
1. 安装 Docker
。Docker 是一个开源的应用容器引擎,你可以把它当作一个轻量级的虚拟机。它可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中,然后发布到任何流行的操作系统,比如 Linux/Windows/Mac 的机器上。Docker 容器相互之间不会有任何接口,而且容器本身的开销极低,这就让 Docker 成为了非常灵活、安全、伸缩性极强的计算资源平台。
https://www.docker.com/get-started

  1. 建立 TensorFlow Serving 服务
    *20201129 深度学习推荐系统_王喆 学习笔记 - 图57

在命令执行完成后,如果你在 Docker 的管理界面中看到了 TenSorflow Serving 容器,如下图所示,就证明 TensorFlow Serving 服务被你成功建立起来了。

*20201129 深度学习推荐系统_王喆 学习笔记 - 图58

  1. 请求 TensorFlow Serving 获得预估结果
    *20201129 深度学习推荐系统_王喆 学习笔记 - 图59

那对于深度学习推荐系统来说,我们只要选择 TensorFlow Serving 的模型服务方法就万无一失了吗?当然不是,它也有需要优化的地方。在搭建它的过程会涉及模型更新,整个 Docker Container 集群的维护,而且 TensorFlow Serving 的线上性能也需要大量优化来提高,这些工程问题都是我们在实践过程中必须要解决的。但是,它的易用性和对复杂模型的支持,还是让它成为上线 TensorFlow 模型的第一选择。

小结
*20201129 深度学习推荐系统_王喆 学习笔记 - 图60
14 | 融会贯通:SparrowRecSys中的电影相似推荐功能是如何实现的?
*20201129 深度学习推荐系统_王喆 学习笔记 - 图61

15 | 协同过滤:最经典的推荐模型,我们应该掌握什么?

1.协同过滤算法的基本原理

用户行为数据是推荐系统最常用,也是最关键的数据。用户的潜在兴趣、用户对物品的评价好坏都反映在用户的行为历史中。

而协同过滤算法,就是一种完全依赖用户和物品之间行为关系的推荐算法。我们从它的名字“协同过滤”中,也可以窥探到它背后的原理,就是 “协同大家的反馈、评价和意见一起对海量的信息进行过滤,从中筛选出用户可能感兴趣的信息”。

这么说可能还是太抽象了,接下来,我们就一起看一个电商场景下的例子。通过分析这个例子,你就能搞清楚协同过滤算法的推荐过程了。这个电商推荐系统从得到原始数据到生成最终推荐分数,全过程一共可以总结为 6 个步骤,如下所示。下面,我们一一来讲。
*20201129 深度学习推荐系统_王喆 学习笔记 - 图62
计算用户相似度

最经典的方法就是利用余弦相似度了,它衡量了用户向量 i 和用户向量 j 之间的向量夹角大小。夹角越小,余弦相似度越大,两个用户越相似,它的定义如下:
*20201129 深度学习推荐系统_王喆 学习笔记 - 图63
除了最常用的余弦相似度之外,相似度的定义还有皮尔逊相关系数、欧式距离等等。咱们课程主要使用的是余弦相似度,因此你只要掌握它就可以了,其他的定义我这里不再多说了。

用户评分的预测
接下来,我们再来看看推荐分数的计算。在获得 Top n 个相似用户之后,利用 Top n 用户生成最终的用户 u 对物品 p 的评分是一个比较直接的过程。这里,我们假设的是“目标用户与其相似用户的喜好是相似的”,根据这个假设,我们可以利用相似用户的已有评价对目标用户的偏好进行预测。最常用的方式是,利用用户相似度和相似用户评价的加权平均值,来获得目标用户的评价预测,公式如下所示。
*20201129 深度学习推荐系统_王喆 学习笔记 - 图64
其中,权重 wu,s 是用户 u 和用户 s 的相似度,Rs,p 是用户 s 对物品 p 的评分。在获得用户 u 对不同物品的评价预测后,最终的推荐列表根据评价预测得分进行排序即可得到,到这里,我们就完成了协同过滤的全部推荐过程。

2.矩阵分解算法的原理

虽然说协同过滤是目前公认的最经典的推荐算法,但我们还是可以轻松找出它的缺点,那就是共现矩阵往往非常稀疏,在用户历史行为很少的情况下,寻找相似用户的过程并不准确。于是,著名的视频流媒体公司 Netflix 对协同过滤算法进行了改进,提出了矩阵分解算法,加强了模型处理稀疏矩阵的能力。

矩阵分解不就是相当于一种 Embedding 方法嘛。没错,矩阵分解的主要过程,就是先分解协同过滤生成的共现矩阵,生成用户和物品的隐向量,再通过用户和物品隐向量间的相似性进行推荐。

那这个过程的关键就在于如何分解这个共现矩阵了。从形式上看,矩阵分解的过程是直观的,就是把一个 mxn 的共现矩阵,分解成一个 mxk 的用户矩阵和 kxn 的物品矩阵相乘的形式(如图 3)。
*20201129 深度学习推荐系统_王喆 学习笔记 - 图65

3. 矩阵分解算法的 Spark 实现

*20201129 深度学习推荐系统_王喆 学习笔记 - 图66
16 | 深度学习革命:深度学习推荐模型发展的整体脉络是怎样的?

1.深度学习对推荐系统的影响详解

  1. 深度学习模型的强拟合能力
    2. 深度学习模型结构的灵活性

这其中典型的例子就是阿里巴巴的模型 DIN(深度兴趣网络)和 DIEN(深度兴趣进化网络)。它们通过在模型结构中引入注意力机制和模拟兴趣进化的序列模型,来更好地模拟用户的行为。
*20201129 深度学习推荐系统_王喆 学习笔记 - 图67
们重点关注图 4 的 DIN 模型,它在神经网络中增加了一个叫做“激活单元“的结构,这个单元就是为了模拟人类的注意力机制。举个例子来说,我们在购买电子产品,比如说笔记本电脑的时候,更容易拿之前购买电脑的经验,或者其他电子产品的经验来指导当前的购买行为,很少会借鉴购买衣服和鞋子的经验。这就是一个典型的注意力机制,我们只会注意到相关度更高的历史购买行为,而 DIN 模型就是模拟了人类这样的注意力特点

DIN 模型的改进版 DIEN 模型就更厉害了,它不仅引入了注意力机制,还模拟了用户兴趣随时间的演化过程。我们来看那些彩色的层,这一层层的序列结构模拟的正是用户兴趣变迁的历史,通过模拟变迁的历史,DIEN 模型可以更好地预测下一步用户会喜欢什么。

2. 深度学习推荐模型的演化关系图

*20201129 深度学习推荐系统_王喆 学习笔记 - 图68

3.关于模型改进的 4 个方向

一是改变神经网络的复杂程度。 从最简单的单层神经网络模型 AutoRec,到经典的深度神经网络结构 Deep Crossing,它们主要的进化方式在于增加了深度神经网络的层数和结构复杂度。
二是改变特征交叉方式。 这种演进方式的要点在于大大提高了深度学习网络中特征交叉的能力。比如说,改变了用户向量和物品向量互操作方式的 NeuralCF,定义了多种特征向量交叉操作的 PNN 等等。

三是把多种模型组合应用。 组合模型主要指的就是以 Wide&Deep 模型为代表的一系列把不同结构组合在一起的改进思路。它通过组合两种甚至多种不同特点、优势互补的深度学习网络,来提升模型的综合能力。

四是让深度推荐模型和其他领域进行交叉。 我们从 DIN、DIEN、DRN 等模型中可以看出,深度推荐模型无时无刻不在从其他研究领域汲取新的知识。事实上,这个过程从未停歇,我们从今年的推荐系统顶会 Recsys2020 中可以看到,NLP 领域的著名模型 Bert 又与推荐模型结合起来,并且产生了非常好的效果。一般来说,自然语言处理、图像处理、强化学习这些领域都是推荐系统经常汲取新知识的地方。

*20201129 深度学习推荐系统_王喆 学习笔记 - 图69
17 | Embedding+MLP:如何用TensorFlow实现经典的深度学习模型?

1.如何在生成样本时避免引入“未来信息”?

训练模型所需要的训练样本我们已经得到了,但是,在处理训练样本的时候,还有一个问题我们一定要注意,那就是引入未来信息(Future Information)的问题,这也是我们在实际工作中经常会遇到的问题。什么叫做未来信息呢?如果我们在 t 时刻进行模型预测,那么 t+1 时刻的信息就是未来信息。这个问题在模型线上服务的时候是不存在的,因为未来的事情还未发生,我们不可能知道。但在离线训练的时候,我们就容易犯这样的错误。比如说,我们利用 t 时刻的样本进行训练,但是使用了全量的样本生成特征,那么这些特征就包含了 t+1 时刻的未来信息,这就是一个典型的引入未来信息的错误例子。

那在 Spark 中,我们应该如何处理这些跟历史行为相关的特征呢?这就需要用到 window 函数了。比如说,我在生成 userAvgRating 这个特征的时候,是使用下面的代码生成的:
*20201129 深度学习推荐系统_王喆 学习笔记 - 图70

我们可以看到,代码中有一个 over(Window.partitionBy(“userId”).orderBy(col(“timestamp”))) 操作,它的意思是,在做 rating 平均这个操作的时候,我们不要对这个 userId 下面的所有评分取平均值,而是要创建一个滑动窗口,先把这个用户下面的评分按照时间排序,再让这个滑动窗口一一滑动滑动窗口的位置始终在当前 rating 前一个 rating 的位置。这样,我们再对滑动窗口内的分数做平均,就不会引入未来信息了

2. Embedding+MLP 模型的结构

图 1 展示的就是微软在 2016 年提出的深度学习模型 Deep Crossing,微软把它用于广告推荐这个业务场景上。它是一个经典的 Embedding+MLP 模型结构,我们可以看到,Deep Crossing 从下到上可以分为 5 层,分别是 Feature 层、Embedding 层、Stacking 层、MLP 层和 Scoring 层。接下来,我就从下到上来给你讲讲每一层的功能是什么,以及它们的技术细节分别是什么样的。
*20201129 深度学习推荐系统_王喆 学习笔记 - 图71

我们先来看 Feature 层。Feature 层也叫做输入特征层,它处于 Deep Crossing 的最底部,作为整个模型的输入。仔细看图 1 的话,你一定会发现不同特征在细节上的一些区别。比如 Feature#1 向上连接到了 Embedding 层,而 Feature#2 就直接连接到了更上方的 Stacking 层。这是怎么回事呢?

原因就在于 Feature#1 代表的是类别型特征经过 One-hot 编码后生成的特征向量,而 Feature#2 代表的是数值型特征。我们知道,One-hot 特征太稀疏了,不适合直接输入到后续的神经网络中进行训练,所以我们需要通过连接到 Embedding 层的方式,把这个稀疏的 One-hot 向量转换成比较稠密的 Embedding 向量

那 Embedding 层的内部结构到底是什么样子的呢?我想先问问你,你还记得 Word2vec 的原理吗?Embeding 层的结构就是 Word2vec 模型中从输入神经元到隐层神经元的部分(如图 2 红框内的部分)。参照下面的示意图,我们可以看到,这部分就是一个从输入层到隐层之间的全连接网络。

*20201129 深度学习推荐系统_王喆 学习笔记 - 图72

一般来说,Embedding 向量的维度应远小于原始的稀疏特征向量,按照经验,几十到上百维就能够满足需求,这样它才能够实现从稀疏特征向量到稠密特征向量的转换。

接着我们来看 Stacking 层。Stacking 层中文名是堆叠层,我们也经常叫它连接(Concatenate)层。它的作用比较简单,就是把不同的 Embedding 特征和数值型特征拼接在一起,形成新的包含全部特征的特征向量。
再往上看,MLP 层就是我们开头提到的多层神经网络层,在图 1 中指的是 Multiple Residual Units 层,中文叫多层残差网络。微软在实现 Deep Crossing 时针对特定的问题选择了残差神经元,但事实上,神经元的选择有非常多种,比如我们之前在深度学习基础知识中介绍的,以 Sigmoid 函数为激活函数的神经元,以及使用 tanh、ReLU 等其他激活函数的神经元。我们具体选择哪种是一个调参的问题,一般来说,ReLU 最经常使用在隐层神经元上,Sigmoid 则多使用在输出神经元,实践中也可以选择性地尝试其他神经元,根据效果作出最后的决定。

不过,不管选择哪种神经元,MLP 层的特点是全连接,就是不同层的神经元两两之间都有连接。就像图 3 中的两层神经网络一样,它们两两连接,只是连接的权重会在梯度反向传播的学习过程中发生改变。
*20201129 深度学习推荐系统_王喆 学习笔记 - 图73
MLP 层的作用是让特征向量不同维度之间做充分的交叉,让模型能够抓取到更多的非线性特征和组合特征的信息,这就使深度学习模型在表达能力上较传统机器学习模型大为增强。

最后是 Scoring 层,它也被称为输出层。虽然深度学习模型的结构可以非常复杂,但最终我们要预测的目标就是一个分类的概率。如果是点击率预估,就是一个二分类问题,那我们就可以采用逻辑回归作为输出层神经元,而如果是类似图像分类这样的多分类问题,我们往往在输出层采用 softmax 这样的多分类模型。

到这里,我们就讲完了 Embedding+MLP 的五层结构。它的结构重点用一句话总结就是,对于类别特征,先利用 Embedding 层进行特征稠密化,再利用 Stacking 层连接其他特征,输入 MLP 的多层结构,最后用 Scoring 层预估结果。

3. Embedding+MLP 模型的实战

参考代码放在了 SparrowRecsys 项目 TFRecModel 模块的 EmbeddingMLP.py

18|Wide&Deep:怎样让你的模型既有想象力又有记忆力?

今天,我们来学习一个在业界有着巨大影响力的推荐模型,Google 的 Wide&Deep。可以说,只要掌握了 Wide&Deep,我们就抓住了深度推荐模型这几年发展的一个主要方向。那 Wide&Deep 模型是什么意思呢?我们把它翻译成中文就是“宽并且深的模型”。这个名字看起来好像很通俗易懂,但你真的理解其中“宽”和“深”的含义吗?上一节课我们讲过 Embedding+MLP 的经典结构,因为 MLP 可以有多层神经网络的结构,所以它是一个比较“深”的模型,但 Wide&Deep 这个模型说的“深”和 MLP 是同样的意思吗?“宽”的部分又是什么样的呢?宽和深分别有什么不同的作用呢?以及我们为什么要把它们结合在一起形成一个模型呢?

1. Wide&Deep 模型的结构

*20201129 深度学习推荐系统_王喆 学习笔记 - 图74

上图就是 Wide&Deep 模型的结构图了,它是由左侧的 Wide 部分和右侧的 Deep 部分组成的Wide 部分的结构太简单了,就是把输入层直接连接到输出层,中间没有做任何处理。Deep 层的结构稍复杂,但我相信你也不会陌生,因为它就是我们上节课学习的 Embedding+MLP 的模型结构

简单来说,Wide 部分的主要作用是让模型具有较强的“记忆能力”(Memorization),而 Deep 部分的主要作用是让模型具有“泛化能力”(Generalization),因为只有这样的结构特点,才能让模型兼具逻辑回归和深度神经网络的优点,也就是既能快速处理和记忆大量历史行为特征,又具有强大的表达能力,这就是 Google 提出这个模型的动机。

那么问题又来了,所谓的“记忆能力”和“泛化能力”到底指什么呢?这我们就得好好聊一聊了,因为理解这两种能力是彻底理解 Wide&Deep 模型的关键。

Wide
模型的记忆能力所谓的 “记忆能力”,可以被宽泛地理解为模型直接学习历史数据中物品或者特征的“共现频率”,并且把它们直接作为推荐依据的能力 。就像我们在电影推荐中可以发现一系列的规则,比如,看了 A 电影的用户经常喜欢看电影 B,这种“因为 A 所以 B”式的规则,非常直接也非常有价值。
这类规则有两个特点:一是数量非常多,一个“记性不好”的推荐模型很难把它们都记住;二是没办法推而广之,因为这类规则非常具体,没办法或者说也没必要跟其他特征做进一步的组合。就像看了电影 A 的用户 80% 都喜欢看电影 B,这个特征已经非常强了,我们就没必要把它跟其他特征再组合在一起。
现在,我们就可以回答开头的问题了,为什么模型要有 Wide 部分?就是因为 Wide 部分可以增强模型的记忆能力,让模型记住大量的直接且重要的规则,这正是单层的线性模型所擅长的。

Deep
模型的泛化能力接下来,我们来谈谈模型的“泛化能力”。“泛化能力”指的是模型对于新鲜样本、以及从未出现过的特征组合的预测能力。 这怎么理解呢?我们还是来看一个例子。假设,我们知道 25 岁的男性用户喜欢看电影 A,35 岁的女性用户也喜欢看电影 A。如果我们想让一个只有记忆能力的模型回答,“35 岁的男性喜不喜欢看电影 A”这样的问题,这个模型就会“说”,我从来没学过这样的知识啊,没法回答你。
事实上,我们学过的矩阵分解算法,就是为了解决协同过滤“泛化能力”不强而诞生的。因为协同过滤只会“死板”地使用用户的原始行为特征,而矩阵分解因为生成了用户和物品的隐向量,所以就可以计算任意两个用户和物品之间的相似度了。这就是泛化能力强的另一个例子。

2. Wide&Deep 模型的应用场景

*20201129 深度学习推荐系统_王喆 学习笔记 - 图75

我们先来看看图 2,它补充了 Google Play Wide&Deep 模型的细节,让我们可以清楚地看到各部分用到的特征是什么。我们先从右边 Wide 部分的特征看起。这部分很简单,只利用了两个特征的交叉,这两个特征是“已安装应用”和“当前曝光应用”。这样一来,Wide 部分想学到的知识就非常直观啦,就是希望记忆好“如果 A 所以 B”这样的简单规则。在 Google Play 的场景下,就是希望记住“如果用户已经安装了应用 A,是否会安装 B”这样的规则。

接着,我们再来看看左边的 Deep 部分,它就是一个非常典型的 Embedding+MLP 结构了。我们看到其中的输入特征很多,有用户年龄、属性特征、设备类型,还有已安装应用的 Embedding 等等。我们把这些特征一股脑地放进多层神经网络里面去学习之后,它们互相之间会发生多重的交叉组合,这最终会让模型具备很强的泛化能力。
比如说,我们把用户年龄、人口属性和已安装应用组合起来。假设,样本中有 25 岁男性安装抖音的记录,也有 35 岁女性安装抖音的记录,那我们该怎么预测 25 岁女性安装抖音的概率呢?这就需要用到已有特征的交叉来实现了。虽然我们没有 25 岁女性安装抖音的样本,但模型也能通过对已有知识的泛化,经过多层神经网络的学习,来推测出这个概率。

3.Wide&Deep 模型的 TensorFlow 实现

WideNDeep.py

19|NeuralCF:如何用深度学习改造协同过滤?
协同过滤的深度学习进化版本,NeuralCF

1.NeuralCF 模型的结构

如果用神经网络的思路来理解矩阵分解,它的结构图就是图 2 这样的。
*20201129 深度学习推荐系统_王喆 学习笔记 - 图76
把矩阵分解神经网络化之后,把它跟 Embedding+MLP 以及 Wide&Deep 模型做对比,我们可以一眼看出网络中的薄弱环节:矩阵分解在 Embedding 层之上的操作好像过于简单了,就是直接利用内积得出最终结果。这会导致特征之间还没有充分交叉就直接输出结果,模型会有欠拟合的风险。针对这一弱点,NeuralCF 对矩阵分解进行了改进,它的结构图是图 3 这样的。
*20201129 深度学习推荐系统_王喆 学习笔记 - 图77
我想你一定可以一眼看出它们的区别,那就是 NeuralCF 用一个多层的神经网络替代掉了原来简单的点积操作。这样就可以让用户和物品隐向量之间进行充分的交叉,提高模型整体的拟合能力。

2.NeuralCF 模型的扩展,双塔模型

有了之前实现矩阵分解和深度学习模型的经验,我想你理解起来 NeuralCF 肯定不会有困难。事实上,NeuralCF 的模型结构之中,蕴含了一个非常有价值的思想,就是我们可以把模型分成用户侧模型和物品侧模型两部分,然后用互操作层把这两部分联合起来,产生最后的预测得分。
这里的用户侧模型结构和物品侧模型结构,可以是简单的 Embedding 层,也可以是复杂的神经网络结构,最后的互操作层可以是简单的点积操作,也可以是比较复杂的 MLP 结构。但只要是这种物品侧模型 + 用户侧模型 + 互操作层的模型结构,我们把它统称为“双塔模型”结构。
图 4 就是一个典型“双塔模型”的抽象结构。它的名字形象地解释了它的结构组成,两侧的模型结构就像两个高塔一样,而最上面的互操作层则像两个塔尖搭建起的空中走廊,负责两侧信息的沟通。
*20201129 深度学习推荐系统_王喆 学习笔记 - 图78

对于 NerualCF 来说,它只利用了用户 ID 作为“用户塔”的输入特征,用物品 ID 作为“物品塔”的输入特征。事实上,我们完全可以把其他用户和物品相关的特征也分别放入用户塔和物品塔,让模型能够学到的信息更全面。比如说,YouTube 在构建用于召回层的双塔模型时,就分别在用户侧和物品侧输入了多种不同的特征,如图 5 所示。
*20201129 深度学习推荐系统_王喆 学习笔记 - 图79
可以看到,YouTube 召回双塔模型的用户侧特征包括了用户正在观看的视频 ID、频道 ID(图中的 seed features)、该视频的观看数、被喜欢的次数,以及用户历史观看过的视频 ID 等等。物品侧的特征包括了候选视频的 ID、频道 ID、被观看次数、被喜欢次数等等。在经过了多层 ReLU 神经网络的学习之后,双塔模型最终通过 softmax 输出层连接两部分,输出最终预测分数。

你注意看一下物品塔和用户塔最顶端的那层神经元,那层神经元的输出其实就是一个全新的物品 Embedding 和用户 Embedding。拿图 4 来说,物品塔的输入特征向量是 x,经过物品塔的一系列变换,生成了向量 u(x),那么这个 u(x) 就是这个物品的 Embedding 向量。同理,v(y) 是用户 y 的 Embedding 向量,这时,我们就可以把 u(x) 和 v(y) 存入特征数据库,这样一来,线上服务的时候,我们只要把 u(x) 和 v(y) 取出来,再对它们做简单的互操作层运算就可以得出最后的模型预估结果了!

所以使用双塔模型,我们不用把整个模型都部署上线,只需要预存物品塔和用户塔的输出,以及在线上实现互操作层就可以了。如果这个互操作层是点积操作,那么这个实现可以说没有任何难度,这是实际应用中非常容易落地的,也是工程师们喜闻乐见的,这也正是双塔模型在业界巨大的优势所在。

正是因为这样的优势,双塔模型被广泛地应用在 YouTube、Facebook、百度等各大公司的推荐场景中,持续发挥着它的能量。

3. NeuralCF 的 TensorFlow 实现

NeuralCF.py

20 | DeepFM:如何让你的模型更好地处理特征交叉?
你有没有深入思考过这样一个问题,就是这几种模型都是怎么处理特征交叉这个问题的呢?比如说,模型的输入有性别、年龄、电影风格这几个特征,在训练样本中我们发现有 25 岁男生喜欢科幻电影的样本,有 35 岁女生喜欢看恐怖电影的样本,那你觉得模型应该怎么推测“25 岁”的女生喜欢看的电影风格呢?

事实上,这类特征组合和特征交叉问题非常常见,而且在实际应用中,特征的种类还要多得多,特征交叉的复杂程度也要大得多。解决这类问题的关键,就是模型对于特征组合和特征交叉的学习能力,因为它决定了模型对于未知特征组合样本的预测能力,而这对于复杂的推荐问题来说,是决定其推荐效果的关键点之一。

但无论是 Embedding MLP,还是 Wide&Deep 其实都没有对特征交叉进行特别的处理,而是直接把独立的特征扔进神经网络,让它们在网络里面进行自由组合,就算是 NeuralCF 也只在最后才把物品侧和用户侧的特征交叉起来。那这样的特征交叉方法是高效的吗?深度学习模型有没有更好的处理特征交叉的方法呢?
这节课,我们就一起来解决这些问题。同时,我还会基于特征交叉的思想,带你学习和实现一种新的深度学习模型 DeepFM。

1. 为什么深度学习模型需要加强特征交叉的能力?

我们之前一直说 MLP 有拟合任意函数的能力,这没有错,但这是建立在 MLP 有任意多层网络,以及任意多个神经元的前提下的。在训练资源有限,调参时间有限的现实情况下,MLP 对于特征交叉的处理其实还比较低效。因为 MLP 是通过 concatenate 层把所有特征连接在一起成为一个特征向量的,这里面没有特征交叉,两两特征之间没有发生任何关系。
这个时候,在我们有先验知识的情况下,人为地加入一些负责特征交叉的模型结构,其实对提升模型效果会非常有帮助。比如,在我们 Sparrow RecSys 项目的训练样本中其实有两个这样的特征,一个是用户喜欢的电影风格,一个是电影本身的风格,这两个特征明显具有很强的相关性。如果能让模型利用起这样的相关性,肯定会对最后的推荐效果有正向的影响。

既然这样,那我们不如去设计一些特定的特征交叉结构,来把这些相关性强的特征,交叉组合在一起,这就是深度学习模型要加强特征交叉能力的原因了。

2. 善于处理特征交叉的机器学习模型 FM

说到解决特征交叉问题的传统机器学习模型,我们就不得不提一下,曾经红极一时的机器学习模型因子分解机模型(Factorization Machine)了,我们可以简称它为 FM。
*20201129 深度学习推荐系统_王喆 学习笔记 - 图80

首先,我们看上图中模型的最下面,它的输入是由类别型特征转换成的 One-hot 向量,往上就是深度学习的常规操作,也就是把 One-hot 特征通过 Embedding 层转换成稠密 Embedding 向量。到这里,FM 跟其他深度学习模型其实并没有区别,但再往上区别就明显了。

FM 会使用一个独特的层 FM Layer 来专门处理特征之间的交叉问题。你可以看到,FM 层中有多个内积操作单元对不同特征向量进行两两组合,这些操作单元会把不同特征的内积操作的结果输入最后的输出神经元,以此来完成最后的预测。
这样一来,如果我们有两个特征是用户喜爱的风格和电影本身的风格,通过 FM 层的两两特征的内积操作,这两个特征就可以完成充分的组合,不至于像 Embedding MLP 模型一样,还要 MLP 内部像黑盒子一样进行低效的交叉。

3.深度学习模型和 FM 模型的结合 DeepFM

DeepFM 是由哈工大和华为公司联合提出的深度学习模型,我把它的架构示意图放在了下面。
*20201129 深度学习推荐系统_王喆 学习笔记 - 图81

结合模型结构图,我们可以看到,DeepFM 利用了 Wide&Deep 组合模型的思想,用 FM 替换了 Wide&Deep 左边的 Wide 部分,加强了浅层网络部分特征组合的能力,而右边的部分跟 Wide&Deep 的 Deep 部分一样,主要利用多层神经网络进行所有特征的深层处理,最后的输出层是把 FM 部分的输出和 Deep 部分的输出综合起来,产生最后的预估结果。这就是 DeepFM 的结构。

4. 如何使用元素积的操作进行特征交叉?

接下来我们再思考一个问题,FM 和 DeepFM 中进行特征交叉的方式,都是进行 Embedding 向量的点积操作,那是不是说特征交叉就只能用点积操作了?答案当然是否定的。事实上还有很多向量间的运算方式可以进行特征的交叉,比如模型 NFM(Neural Factorization Machines,神经网络因子分解机),它就使用了新的特征交叉方法。下面,我们一起来看一下。

图 3 就是 NFM 的模型架构图,相信已经看了这么多模型架构图的你,一眼就能看出它跟其他模型的区别,也就是 Bi-Interaction Pooling 层。那这个夹在 Embedding 层和 MLP 之间的层到底做了什么呢?
*20201129 深度学习推荐系统_王喆 学习笔记 - 图82

Bi-Interaction Pooling Layer 翻译成中文就是“两两特征交叉池化层”。假设 Vx 是所有特征域的 Embedding 集合,那么特征交叉池化层的具体操作如下所示。

*20201129 深度学习推荐系统_王喆 学习笔记 - 图83
其中 ⊙ 运算代表两个向量的元素积(Element-wise Product)操作,即两个长度相同的向量对应维相乘得到元素积向量。其中,第 k 维的操作如下所示。
*20201129 深度学习推荐系统_王喆 学习笔记 - 图84
在进行两两特征 Embedding 向量的元素积操作后,再求取所有交叉特征向量之和,我们就得到了池化层的输出向量。接着,我们再把该向量输入上层的多层全连接神经网络,就能得出最后的预测得分。

总的来说,NFM 并没有使用内积操作来进行特征 Embedding 向量的交叉,而是使用元素积的操作。在得到交叉特征向量之后,也没有使用 concatenate 操作把它们连接起来,而是采用了求和的池化操作,把它们叠加起来。看到这儿,你肯定又想问,元素积操作和点积操作到底哪个更好呢?还是那句老话,我希望我们能够尽量多地储备深度学习模型的相关知识,先不去关注哪个方法的效果会更好,至于真实的效果怎么样,交给你去在具体的业务场景的实践中验证。

5. DeepFM 的 TensorFlow 实战

DeepFM.py

*20201129 深度学习推荐系统_王喆 学习笔记 - 图85
21|注意力机制、兴趣演化:推荐系统如何抓住用户的心?

作为一名算法工程师,足够的知识储备是非常重要的,因为在掌握了当下主流的深度学习模型架构(Embedding MLP 架构、Wide&Deep 和 DeepFM 等等)之后,要想再进一步提高推荐系统的效果,就需要清楚地知道业界有哪些新的思路可以借鉴,学术界有哪些新的思想可以尝试,这些都是我们取得持续成功的关键。

1. 什么是“注意力机制”?

“注意力机制”来源于人类天生的“选择性注意”的习惯。最典型的例子是用户在浏览网页时,会有选择性地注意页面的特定区域,而忽视其他区域。比如,图 1 是 Google 对大量用户进行眼球追踪实验后,得出的页面注意力热度图。我们可以看到,用户对页面不同区域的注意力区别非常大,他们的大部分注意力就集中在左上角的几条搜索结果上。

那说了这么多,“注意力机制”对我们构建推荐模型到底有什么价值呢?

*20201129 深度学习推荐系统_王喆 学习笔记 - 图86
价值是非常大的。比如说,我们要做一个新闻推荐的模型,让这个模型根据用户已经看过的新闻做推荐。那我们在分析用户已浏览新闻的时候,是把标题、首段、全文的重要性设置成完全一样比较好,还是应该根据用户的注意力不同给予不同的权重呢?当然,肯定是后者比较合理,因为用户很可能都没有注意到正文最后的几段,如果你分析内容的时候把最后几段跟标题、首段一视同仁,那肯定就把最重要的信息给淹没了。事实上,近年来,注意力机制已经成功应用在各种场景下的推荐系统中了。其中最知名的,要数阿里巴巴的深度推荐模型,DIN(Deep Interest Network,深度兴趣网络)。接下来,我们就一起来学习一下 DIN 的原理和模型结构。

2. 深度兴趣网络 DIN 的原理和结构

DIN 模型的应用场景是阿里最典型的电商广告推荐。对于付了广告费的商品,阿里会根据模型预测的点击率高低,把合适的广告商品推荐给合适的用户,所以 DIN 模型本质上是一个点击率预估模型。注意力机制是怎么应用在 DIN 模型里的呢?回答这个问题之前,我们得先看一看 DIN 在应用注意力机制之前的基础模型是什么样的,才能搞清楚注意力机制能应用在哪,能起到什么作用。

下面的图 2 就是 DIN 的基础模型 Base Model。我们可以看到,Base Model 是一个典型的 Embedding MLP 的结构。它的输入特征有用户属性特征(User Proflie Features)、用户行为特征(User Behaviors)、候选广告特征(Candidate Ad)和场景特征(Context Features)。用户属性特征和场景特征我们之前也已经讲过很多次了,这里我们重点关注用户的行为特征和候选广告特征,也就是图 2 中彩色的部分。
*20201129 深度学习推荐系统_王喆 学习笔记 - 图87
我们可以清楚地看到,用户行为特征是由一系列用户购买过的商品组成的,也就是图上的 Goods 1 到 Goods N,而每个商品又包含了三个子特征,也就是图中的三个彩色点,其中红色代表商品 ID,蓝色是商铺 ID,粉色是商品类别 ID。同时,候选广告特征也包含了这三个 ID 型的子特征,因为这里的候选广告也是一个阿里平台上的商品。我们之前讲过,在深度学习中,只要遇到 ID 型特征,我们就构建它的 Embedding,然后把 Embedding 跟其他特征连接起来,输入后续的 MLP。阿里的 Base Model 也是这么做的,它把三个 ID 转换成了对应的 Embedding,然后把这些 Embedding 连接起来组成了当前商品的 Embedding。

完成了这一步,下一步就比较关键了,因为用户的行为序列其实是一组商品的序列,这个序列可长可短,但是神经网络的输入向量的维度必须是固定的,那我们应该怎么把这一组商品的 Embedding 处理成一个长度固定的 Embedding 呢?图 2 中的 SUM Pooling 层的结构就给出了答案,就是直接把这些商品的 Embedding 叠加起来,然后再把叠加后的 Embedding 跟其他所有特征的连接结果输入 MLP。

但这个时候问题又来了,SUM Pooling 的 Embedding 叠加操作其实是把所有历史行为一视同仁,没有任何重点地加起来,这其实并不符合我们购物的习惯。举个例子来说,候选广告对应的商品是“键盘”,与此同时,用户的历史行为序列中有这样几个商品 ID,分别是“鼠标”“T 恤”和“洗面奶”。从我们的购物常识出发,“鼠标”这个历史商品 ID 对预测“键盘”广告点击率的重要程度应该远大于后两者。从注意力机制的角度出发,我们在购买键盘的时候,会把注意力更多地投向购买“鼠标”这类相关商品的历史上,因为这些购买经验更有利于我们做出更好的决策。

好了,现在我们终于看到了应用注意力机制的地方,那就是用户的历史行为序列。阿里正是在 Base Model 的基础上,把注意力机制应用在了用户的历史行为序列的处理上,从而形成了 DIN 模型。那么,DIN 模型中应用注意力机制的方法到底是什么呢?我们可以从下面的 DIN 模型架构图中看到,与 Base Model 相比,DIN 为每个用户的历史购买商品加上了一个激活单元(Activation Unit),这个激活单元生成了一个权重,这个权重就是用户对这个历史商品的注意力得分,权重的大小对应用户注意力的高低。

*20201129 深度学习推荐系统_王喆 学习笔记 - 图88那现在问题就只剩下一个了,这个所谓的激活单元,到底是怎么计算出最后的注意力权重的呢?为了搞清楚这个问题,我们需要深入到激活单元的内部结构里面去,一起来看看图 3 右上角激活单元的详细结构。它的输入是当前这个历史行为商品的 Embedding,以及候选广告商品的 Embedding。我们把这两个输入 Embedding,与它们的外积结果连接起来形成一个向量,再输入给激活单元的 MLP 层,最终会生成一个注意力权重,这就是激活单元的结构。简单来说,激活单元就相当于一个小的深度学习模型,它利用两个商品的 Embedding,生成了代表它们关联程度的注意力权重。

到这里,我们终于抽丝剥茧地讲完了整个 DIN 模型的结构细节。如果你第一遍没理解清楚,没关系,对照着 DIN 模型的结构图,反复再看几遍我刚才讲的细节,相信你就能彻底消化吸收它。

3.注意力机制对推荐系统的启发

注意力机制的引入对于推荐系统的意义是非常重大的,它模拟了人类最自然,最发自内心的注意力行为特点,使得推荐系统更加接近用户真实的思考过程,从而达到提升推荐效果的目的。
从“注意力机制”开始,越来越多的对深度学习模型结构的改进是基于对用户行为的深刻观察而得出的。由此,我也想再次强调一下,一名优秀的算法工程师应该具备的能力,就是基于对业务的精确理解,对用户行为的深刻观察,得出改进模型的动机,进而设计出最合适你的场景和用户的推荐模型。

沿着这条思路,阿里的同学们在提出 DIN 模型之后,并没有停止其推荐模型演化的进程,而是又在 2019 年提出了 DIN 模型的演化版本,也就是深度兴趣进化网络 DIEN(Deep Interest Evolution Network),那这个 DIEN 到底在 DIN 基础上做了哪些改进呢?

4. 兴趣进化序列模型

无论是电商购买行为,还是视频网站的观看行为,或是新闻应用的阅读行为,特定用户的历史行为都是一个随时间排序的序列。既然是和时间相关的序列,就一定存在前后行为的依赖关系,这样的序列信息对于推荐过程是非常有价值的。为什么这么说呢?我们还拿阿里的电商场景举个例子。对于一个综合电商来说,用户兴趣的迁移其实是非常快的。比如,上一周一位用户在挑选一双篮球鞋,这位用户上周的行为序列都会集中在篮球鞋这个品类的各个商品上,但在他完成这一购物目标后,这一周他的购物兴趣就可能变成买一个机械键盘,那这周他所有的购买行为都会围绕机械键盘这个品类展开。

因此,如果我们能让模型预测出用户购买商品的趋势,那肯定会对提升推荐效果有益的。而 DIEN 模型,就正好弥补了 DIN 模型没有对行为序列进行建模的缺陷,它围绕兴趣进化这个点进一步对 DIN 模型做了改进。图 4 就是 DIEN 模型的架构图,这个模型整体上仍然是一个 Embedding MLP 的模型结构。与 DIN 不同的是,DIEN 用“兴趣进化网络”也就是图中的彩色部分替换掉了原来带有激活单元的用户历史行为部分。这部分虽然复杂,但它的输出只是一个 h’(T) 的 Embedding 向量,它代表了用户当前的兴趣向量。有了这个兴趣向量之后,再把它与其他特征连接在一起,DIEN 就能通过 MLP 作出最后的预测了。

*20201129 深度学习推荐系统_王喆 学习笔记 - 图89

好了,现在问题的焦点就在,DIEN 模型是如何生成这个兴趣向量的。关键就在于 DIEN 模型中彩色部分的三层兴趣进化网络,下面,我就按照从下到上的顺序,给你讲讲它们的名称和作用。

最下面一层是行为序列层(Behavior Layer,浅绿色部分)。它的主要作用和一个普通的 Embedding 层是一样的,负责把原始的 ID 类行为序列转换成 Embedding 行为序列。

再上一层是兴趣抽取层(Interest Extractor Layer,浅黄色部分)。它的主要作用是利用 GRU 组成的序列模型,来模拟用户兴趣迁移过程,抽取出每个商品节点对应的用户兴趣。

最上面一层是兴趣进化层(Interest Evolving Layer,浅红色部分)。它的主要作用是利用 AUGRU(GRU with Attention Update Gate) 组成的序列模型,在兴趣抽取层基础上加入注意力机制,模拟与当前目标广告(Target Ad)相关的兴趣进化过程,兴趣进化层的最后一个状态的输出就是用户当前的兴趣向量 h’(T)。

不知道你发现了吗,兴趣抽取层和兴趣进化层都用到了序列模型的结构,那什么是序列模型呢?直观地说,图 5 就是一个典型的序列模型的结构,它和我们之前看到的多层神经网络的结构不同,序列模型是“一串神经元”,其中每个神经元对应了一个输入和输出。那在 DIEN 模型中,神经元的输入就是商品 ID 或者前一层序列模型的 Embedding 向量,而输出就是商品的 Embedding 或者兴趣 Embedding,除此之外,每个神经元还会与后续神经元进行连接,用于预测下一个状态,放到 DIEN 里,就是为了预测用户的下一个兴趣。这就是序列模型的结构和作用。

*20201129 深度学习推荐系统_王喆 学习笔记 - 图90

至于上面提到过的 GRU 序列模型,它其实是序列模型的一种,根据序列模型神经元结构的不同,最经典的有RNN、LSTM、GRU这 3 种。这里我们就不展开讲了,对理论感兴趣的同学,可以点击我给出的超链接,参考这几篇论文做更深入的研究。

事实上,序列模型已经不仅在电商场景下,成功应用于推测用户的下次购买行为,在 YouTube、Netflix 等视频流媒体公司的视频推荐模型中,序列模型也用来推测用户的下次观看行为(Next Watch)。除此之外,音乐类应用也非常合适使用序列模型来预测用户的音乐兴趣变化。所以,掌握 DIEN 模型的架构对于拓宽我们的技术视野非常有帮助。
*20201129 深度学习推荐系统_王喆 学习笔记 - 图91

22|强化学习:让推荐系统像智能机器人一样自主学习
强化学习也被称为增强学习,它在模型实时更新、用户行为快速反馈等方向上拥有巨大的优势。自从 2018 年开始,它就被大量应用在了推荐系统中,短短几年时间内,微软、美团、阿里等多家一线公司都已经有了强化学习的成功应用案例。
http://www.personal.psu.edu/~gjz5038/paper/www2018_reinforceRec/www2018_reinforceRec.pdf
(https://tech.meituan.com/2018/11/15/reinforcement-learning-in-mt-recommend-system.html
https://arxiv.org/abs/1803.00710

虽然,强化学习在推荐系统中的应用是一个很复杂的工程问题,我们自己很难在单机环境下模拟,但理解它在推荐系统中的应用方法,是我们进一步改进推荐系统的关键点之一,也是推荐系统发展的趋势之一。

1. 强化学习的基本概念

强化学习的基本原理,简单来说,就是一个智能体通过与环境进行交互,不断学习强化自己的智力,来指导自己的下一步行动,以取得最大化的预期利益。

事实上,任何一个有智力的个体,它的学习过程都遵循强化学习所描述的原理。比如说,婴儿学走路就是通过与环境交互,不断从失败中学习,来改进自己的下一步的动作才最终成功的。再比如说,在机器人领域,一个智能机器人控制机械臂来完成一个指定的任务,或者协调全身的动作来学习跑步,本质上都符合强化学习的过程。为了把强化学习技术落地,只清楚它的基本原理显然是不够的,我们需要清晰地定义出强化学习中的每个关键变量,形成一套通用的技术框架。对于一个通用的强化学习框架来说,有这么六个元素是必须要有的:
*20201129 深度学习推荐系统_王喆 学习笔记 - 图92

为了方便记忆,我们可以用一段话把强化学习的六大要素串起来:一个智能体身处在不断变化的环境之中,为了达成某个目标,它需要不断作出行动,行动会带来好或者不好的奖励,智能体收集起这些奖励反馈进行自我学习,改变自己所处的状态,再进行下一步的行动,然后智能体会持续这个“行动 - 奖励 - 更新状态”的循环,不断优化自身,直到达成设定的目标。这就是强化学习通用过程的描述,那么,对于推荐系统而言,我们能不能创造这样一个会自我学习、自我调整的智能体,为用户进行推荐呢?事实上,微软的 DRN 模型已经实现这个想法了。下面,我就以 DRN 模型为例,来给你讲一讲在推荐系统中,强化学习的六大要素都是什么,强化学习具体又是怎样应用在推荐系统中的。

2. 强化学习推荐系统框架

强化学习推荐模型 DRN(Deep Reinforcement Learning Network,深度强化学习网络)是微软在 2018 年提出的,它被应用在了新闻推荐的场景上,下图 1 是 DRN 的框架图。事实上,它不仅是微软 DRN 的框架图,也是一个经典的强化学习推荐系统技术框图。
*20201129 深度学习推荐系统_王喆 学习笔记 - 图93

在新闻的推荐系统场景下,DRN 模型的第一步是初始化推荐系统,主要初始化的是推荐模型,我们可以利用离线训练好的模型作为初始化模型,其他的还包括我们之前讲过的特征存储、推荐服务器等等。接下来,推荐系统作为智能体会根据当前已收集的用户行为数据,也就是当前的状态,对新闻进行排序这样的行动,并在新闻网站或者 App 这些环境中推送给用户。用户收到新闻推荐列表之后,可能会产生点击或者忽略推荐结果的反馈。这些反馈都会作为正向或者负向奖励再反馈给推荐系统。推荐系统收到奖励之后,会根据它改变、更新当前的状态,并进行模型训练来更新模型。接着,就是推荐系统不断重复“排序 - 推送 - 反馈”的步骤,直到达成提高新闻的整体点击率或者用户留存等目的为止。为了方便你进行对比,我也把这六大要素在推荐系统场景下的定义整理在了下面,你可以看一看。

*20201129 深度学习推荐系统_王喆 学习笔记 - 图94

3. 深度强化学习推荐模型 DRN

智能体是强化学习框架的核心,作为推荐系统这一智能体来说,推荐模型就是推荐系统的“大脑”。在 DRN 框架中,扮演“大脑”角色的是 Deep Q-Network (深度 Q 网络,DQN)。其中,Q 是 Quality 的简称,指通过对行动进行质量评估,得到行动的效用得分,来进行行动决策。
*20201129 深度学习推荐系统_王喆 学习笔记 - 图95

DQN 的网络结构如图 2 所示,它就是一个典型的双塔结构。其中,用户塔的输入特征是用户特征和场景特征,物品塔的输入向量是所有的用户、环境、用户 - 新闻交叉特征和新闻特征。

在强化学习的框架下,用户塔特征向量因为代表了用户当前所处的状态,所以也可被视为状态向量。物品塔特征向量则代表了系统下一步要选择的新闻,我们刚才说了,这个选择新闻的过程就是智能体的“行动”,所以物品塔特征向量也被称为行动向量。

双塔模型通过对状态向量和行动向量分别进行 MLP 处理,再用互操作层生成了最终的行动质量得分 Q(s,a),智能体正是通过这一得分的高低,来选择到底做出哪些行动,也就是推荐哪些新闻给用户的。其实到这里为止,我们并没有看到强化学习的优势,貌似就是套用了强化学习的概念把深度推荐模型又解释了一遍。别着急,下面我要讲的 DRN 学习过程才是强化学习的精髓。

4. DRN 的学习过程

DRN 的学习过程是整个强化学习推荐系统框架的重点,正是因为可以在线更新,才使得强化学习模型相比其他“静态”深度学习模型有了更多实时性上的优势。下面,我们就按照下图中从左至右的时间轴,来描绘一下 DRN 学习过程中的重要步骤。

*20201129 深度学习推荐系统_王喆 学习笔记 - 图96
我们先来看离线部分。DRN 根据历史数据训练好 DQN 模型,作为智能体的初始化模型。而在线部分根据模型更新的间隔分成 n 个时间段,这里以 t1 到 t5 时间段为例。首先在 t1 到 t2 阶段,DRN 利用初始化模型进行一段时间的推送服务,积累反馈数据。接着是在 t2 时间点,DRN 利用 t1 到 t2 阶段积累的用户点击数据,进行模型微更新(Minor update)。最后在 t4 时间点,DRN 利用 t1 到 t4 阶段的用户点击数据及用户活跃度数据,进行模型的主更新(Major update)。时间线不断延长,我们就不断重复 t1 到 t4 这 3 个阶段的操作。这其中,我要重点强调两个操作,一个是在 t4 的时间点出现的模型主更新操作,我们可以理解为利用历史数据的重新训练,用训练好的模型来替代现有模型。另一个是 t2、t3 时间点提到的模型微更新操作,想要搞清楚它到底是怎么回事,还真不容易,必须要牵扯到 DRN 使用的一种新的在线训练方法,Dueling Bandit Gradient Descent algorithm(竞争梯度下降算法)。

5. DRN 的在线学习方法:竞争梯度下降算法

我先把竞争梯度下降算法的流程图放下了下面。接下来,我就结合这个流程图,来给你详细讲讲它的过程和它会涉及的模型微更新操作。
*20201129 深度学习推荐系统_王喆 学习笔记 - 图97

DRN 的在线学习过程主要包括三步,我带你一起来看一下。
第一步,对于已经训练好的当前网络 Q,对其模型参数 W 添加一个较小的随机扰动,得到一个新的模型参数,这里我们称对应的网络为探索网络 Q~。在这一步中,由当前网络 Q 生成探索网络 ,产生随机扰动的公式 1 如下:
*20201129 深度学习推荐系统_王喆 学习笔记 - 图98
其中,α 是一个探索因子,决定探索力度的大小。rand(-1,1) 产生的是一个[-1,1]之间的随机数。第二步,对于当前网络 Q 和探索网络 Q~,分别生成推荐列表 L 和 L~,再将两个推荐列表用间隔穿插(Interleaving)的方式融合,组合成一个推荐列表后推送给用户。最后一步是实时收集用户反馈。如果探索网络 Q~生成内容的效果好于当前网络 Q,我们就用探索网络代替当前网络,进入下一轮迭代。反之,我们就保留当前网络。

总的来说,DRN 的在线学习过程利用了“探索”的思想,其调整模型的粒度可以精细到每次获得反馈之后,这一点很像随机梯度下降的思路:虽然一次样本的结果可能产生随机扰动,但只要总的下降趋势是正确的,我们就能够通过海量的尝试最终达到最优点。DRN 正是通过这种方式,让模型时刻与最“新鲜”的数据保持同步,实时地把最新的奖励信息融合进模型中。模型的每次“探索”和更新也就是我们之前提到的模型“微更新”。

到这里,我们就讲完了微软的深度强化学习模型 DRN。我们可以想这样一个问题:这个模型本质上到底改进了什么?从我的角度来说,它最大的改进就是把模型推断、模型更新、推荐系统工程整个一体化了,让整个模型学习的过程变得更高效,能根据用户的实时奖励学到新知识,做出最实时的反馈。但同时,也正是因为工程和模型紧紧地耦合在一起,让强化学习在推荐系统中的落地并不容易。

既然,说到了强化学习的落地,这里我还想再多说几句。因为涉及到了模型训练、线上服务、数据收集、实时模型更新等几乎推荐系统的所有工程环节,所以强化学习整个落地过程的工程量非常大。这不像我们之前学过的深度学习模型,只要重新训练一下它,我们就可以改进一个模型结构,强化学习模型需要工程和研究部门通力合作才能实现。在这个过程中,能不能有一个架构师一样的角色来通盘协调,就成为了整个落地过程的关键点。有一个环节出错,比如说模型在做完实时训练后,模型参数更新得不及时,那整个强化学习的流程就被打乱了,整体的效果就会受到影响。

所以对我们个人来说,掌握强化学习模型的框架,也就多了一个发展的方向。那对于团队来说,如果强化学习能够成功落地,也一定证明了这个团队有着极强的合作能力,在工程和研究方向上都有着过硬的实践能力。

*20201129 深度学习推荐系统_王喆 学习笔记 - 图99

特别加餐 | “银弹”不可靠,最优的模型结构该怎么找?

1. 有解决推荐问题的“银弹”吗?

在软件工程领域的著作《人月神话》中,作者 Brooks 凭借自己在 IBM 管理 2000 人完成大型项目的经验,得出了一个结论:没有任何技术或管理上的进展, 能够独立地许诺十年内使软件系统项目生产率、 可靠性或简洁性获得数量级上的进步。

Brooks 用“没有银弹”来形容这个结论。在欧洲的古老传说中,银色的子弹是能够一击杀死狼人这种怪物的特效武器。我们也可以把银弹理解成是最好的解决办法。“没有银弹”这个结论让很多期待寻找大型软件开发“捷径”的管理者和工程师们深感失望。但距离人月神话出版已经 45 年的今天,我们找到“银弹”了吗?

很遗憾,不仅在大型软件开发这个领域,“没有银弹”的观念深入人心,而且我要说的是,在推荐系统领域,也同样不存在能够一劳永逸解决问题的“银弹”,或者说根本不存在一种模型结构,它能够一击解决推荐效果问题,做到总是最优。

总的来说,通过 DIEN 的例子我们可以得出,到底怎样的模型结构是最优的模型结构,跟你的业务特点和数据特点强相关。因此,在模型结构的选择上,没有“银弹”,没有最优,只有最合适。

2. 在工作中避免学生思维

“学生思维”最典型的表现就是总是在寻求一个问题的标准答案。因为在我们的学生时代,所有的题目都是有标准答案的,会就是会,不会就是不会。

但在工作中就不一样了。举个例子来说,在讲 Embedding 的部分,很多同学问我 Embedding 到底应该取多少维?在实际的工作中,这就是一个典型的没有标准答案的问题。实际工作中,它应有的决策链条应该是下面这样的:
*20201129 深度学习推荐系统_王喆 学习笔记 - 图100

其实这也是我在讲解这门课时一直秉承的原则,我希望把业界的主流方法告诉你,期望你建立起来的是一套知识体系和方法论,而不是一套能让你一劳永逸的模型参数,因为“银弹”并不存在。

3. 算法工程师正确的工作方法

我们刚刚否定了“学生思维”,那该怎么建立起一套正确的算法工程师思维呢?下面是我总结出的一套通用的算法工程师的工作流程,虽然不能说我一定掌握了“正确”的方法,但这些工作方法都是从我多年的工作经验中总结出来的,也都得到了验证,你完全可以借助它们来完善自己的工作方法。

*20201129 深度学习推荐系统_王喆 学习笔记 - 图101

后我还想补充一点,我一直认为,做算法工程师,首先要有扎实全面的技术功底,但更重要的其实是自信和务实的精神,不迷信所谓的权威模型,不试图寻找万能的参数,从业务出发,从用户的真实行为出发,才能够构建出最适合你业务场景的推荐模型 。

23| 实战:如何用深度学习模型实现Sparrow RecSys的个性化推荐功能?

24 | 离线评估:常用的推荐系统离线评估方法有哪些?
在推荐系统这个行业,所有人都在谈效果。就像我们在学习推荐模型篇的时候,你肯定也有过这样的疑问:
DIEN 这个模型的效果到底怎么样啊?我们用深度学习来构建模型到底能让推荐系统效果提高多少啊?DeepFM 的效果是不是会比 Wide&Deep 好呢?那这个所谓的“效果”到底指的是什么呢?我们一般用什么方法来衡量这个“效果”呢?我们又应该如何根据效果评估的结果来更新模型呢?

这就是模型评估篇要解决的问题。在所有推荐系统的评估方法中,离线评估是最常用、最基本的。顾名思义,“离线评估”就是我们将模型部署于线上环境之前,在离线环境下进行的评估。由于不用部署到生产环境,“离线评估”没有线上部署的工程风险,也不会浪费宝贵的线上流量资源,而且具有测试时间短,可多组并行,以及能够利用丰富的线下计算资源等诸多优点。因此,在模型上线之前,进行大量的离线评估是验证模型效果最高效的手段。这节课,我们就来讲讲离线评估的主要方法,以及怎么在 Spark 平台上实现离线评估。

离线评估的主要方法离线评估的基本原理是在离线环境下,将数据集分为“训练集”和“测试集”两部分,“训练集”用来训练模型,“测试集”用于评估模型。但是如何划分测试集和训练集,其实这里面有很多学问。我总结了一下,常用的离线评估方法主要有五种,分别是:Holdout 检验、交叉检验、自助法、时间切割、离线 Replay。接下来,我们一一来看。

1.Holdout 检验、交叉检验和自助法

首先,我们来看 Holdout 检验。 Holdout 检验是最基础,最常用的离线评估方法,它将原始的样本集合随机划分为训练集和测试集两部分,所以 Holdout 检验的关键词就是“随机”。举例来说,对于一个推荐模型,我们可以把样本按照 70%-30% 的比例随机分成两部分。其中,70% 的样本用于模型的训练,30% 的样本用于模型的评估。虽然 Holdout 检验很简单实用,但它的缺点也很明显,就是评估的结果有一定随机性,因为训练集和验证集的划分是随机的,所以如果只进行少量的 Holdout 检验,得到的评估指标会存在一定的波动。那为了消除这种随机性,我们就要使用“交叉检验”的方法。为了进行交叉检验,我们需要先将全部样本划分成 k 个大小相等的样本子集,然后依次遍历这 k 个子集,每次把当前遍历到的子集作为验证集,其余所有的子集作为训练集,这样依次进行 k 次模型的训练和评估。最后,我们再将所有 k 次评估指标的平均值作为最终的评估指标。在我们的实践中,k 经常取 10,也就是依次进行 10 次检验然后取指标均值。不管是 Holdout 检验还是交叉检验,都是基于划分训练集和测试集的方法进行模型评估的。然而,当样本规模比较小时,将样本集进行划分会让训练集进一步减小,这往往会影响模型的训练效果。那有没有能维持训练集样本规模的验证方法呢?“自助法”就可以在一定程度上解决这个问题。我这里所说的自助法(Bootstrap)是基于自助采样的检验方法,它的主要过程是:对于总数为 n 的样本集合,我们先进行 n 次有放回地随机抽样,得到大小为 n 的训练集。在 n 次采样过程中,有的样本会被重复采样,有的样本没有被抽出过,我们再将这些没有被抽出的样本作为验证集进行模型验证,这就是自助法的验证过程。虽然自主法能够保持训练集的规模,但是它的缺点也很明显,它其实改变了原有数据的分布,有可能让模型产生一定程度的偏差。至于,到底是自助采样增加训练样本规模的收益大,还是数据分布被改变带来的损失大,这就需要我们在实践中进行验证了。

2.时间切割

说完了前三种方法,我们再来看时间切割法。在“模型实战准备(二)”那节课里,我们曾经讲过一个概念,叫“未来信息”。它是说,如果我们在 t 时刻进行模型预测,那么 t+1 时刻的信息就是未来信息。在构建特征工程的时候,我们要避免引入“未来信息”。其实,在进行模型评估的时候,我们同样不应该在训练集中包含“未来”的样本。怎么理解这句话呢?比如,我们所有的样本数据分布在 t0到 tn这样的时间轴上,如果训练样本是通过随机采样得到的,那么训练数据也会分布在 t0到 tn上,同样,测试数据也会分布在 t0到 tn上。如果你细想,这个事情其实是有点反常理的。因为训练模型的时候,我们已经使用了 tn这个时间窗口的数据,结果你却用它来预测 t0的事件,这不是很荒谬吗?这就相当于你有一个时光机,已经穿越到了明天,知道股票会涨,结果你又穿越回来,预测说明天股票会涨,这哪是预测呢?这就是“作弊”。为了防止这类“信息穿越”导致的模型作弊现象发生,我们一般会使用时间切割的方案去划分训练集和测试集,它的做法很简单。比如,你一共处理了 30 天的样本,从第 25 天末开始切割,前 25 天的样本作为训练集,后 5 天的样本作为测试集,这样我们就从根源上切断了引入“未来信息”的可能。当然切割的比例到底如何,也需要根据你的实践来定,一般来说我们控制训练集跟测试集的比例在 3:1 到 10:1 之间,比例太小训练样本不够,比例太大测试结果不够稳定。

3.离线 Replay

时间切割的方法虽然能避免“信息穿越”,但也不是没有缺点的。它的缺点就在于整个评估过程是静态的,模型不会随着评估的进行而更新,这显然是不符合事实的。就拿我们刚才举的例子来说,用前 25 天的数据做训练集,用后 5 天的数据做测试集。如果在生产环境中,模型是日更新的,那后 5 天的评测过程就不准确,因为在离线测试中,我们并没有在后 5 天的评测过程中做到日更模型。那怎么解决这个问题呢?我们也可以在离线状态下对线上更新过程进行仿真,让整个评估过程“动”起来。业界把这样离线仿真式的评估方式叫做离线 Replay。下图就是动态的 Replay 评估法与静态的时间分割评估法的对比示意图。我们可以看到,“Replay 评估方法”先根据产生时间对测试样本,由早到晚地进行排序,再让模型根据样本时间的先后进行预测。在模型更新的时间点上,模型需要增量学习更新时间点前的测试样本,更新模型后,再继续评估更新点之后的样本。

*20201129 深度学习推荐系统_王喆 学习笔记 - 图102

你应该也发现了,Replay 评估的过程更接近于真实的线上环境,因为它在线下还原了模型在线上的更新、预估过程。这也让 Replay 方法的评估结果更加权威可信,毕竟,我们最终的目标是让模型在线上产生更好的效果。当然,Replay 评估方法也有弊端,因为它需要在评估过程中不断更新模型,这让评估过程的工程实现难度加大,因为包含了模型训练的时间,所以整个评估过程的总时长也会加长,影响评估和调参的效率。到底是要评估的准确性,还是要评估的效率,这又是一个需要权衡的问题,我们需要根据自己工程上的侧重点进行选择。

基于 Spark 的离线评估方法实践熟悉了离线环节的主要模型评估方法,就又到了实践的环节。其实,无论是基于 Python 的 TensorFlow 还是基于 Scala 的 Spark,都有很多支持离线评估的库,这里我们选择了 Spark 进行实践,主要是因为在业界数据集很大的情况下,Spark 在分布式环境下划分训练集和测试集的效率是最高的。下面,我就来看一下如何使用 Spark 实现 Holdout 检验、交叉检验和时间切割评估法。至于另外两种方法,由于自助法不太常用,离线 Replay 又涉及过多的附加模块,我们暂时就不在项目里实现。

实现 Holdout 检验的时候,我们要清楚如何利用 Spark 随机划分测试集和训练集。
val Array(trainingSamples, testSamples) = samples.randomSplit(Array(0.9, 0.1))

实现交叉检验的过程相对比较复杂,好在,Spark 已经提供了交叉检验的接口可以直接使用,我们直接看一下这部分的关键代码。

val cv = new CrossValidator()
.setEstimator(modelPipeline)
.setEvaluator(new BinaryClassificationEvaluator)
.setEstimatorParamMaps(paramGrid)
.setNumFolds(10) // Use 3+ in practice
val cvModel = cv.fit(training)
如果你不知道样本的时间跨度,就要按照时间求取样本的分位数。具体来说就是,通过 Spark 的 approxQuantile 函数,我们可以找到划分样本集为 8:2 的训练集和测试集的时间戳的值。那么接下来我们根据这个值通过 where 语句划分就可以了。我把这个过程的关键代码贴到了下面,供你参考。完整的源代码,你可以参考 com.wzhe.sparrowrecsys.offline.spark.featureeng.FeatureEngForRecModel 中的 splitAndSaveTrainingTestSamplesByTimeStamp 函数。

//找到时间切割点
val quantile = smallSamples.stat.approxQuantile(“timestampLong”, Array(0.8), 0.05)
val splitTimestamp = quantile.apply(0)
//切割样本为训练集和测试集
val training = smallSamples.where(col(“timestampLong”) <= splitTimestamp).drop(“timestampLong”)
val test = smallSamples.where(col(“timestampLong”) > splitTimestamp).drop(“timestampLong”)

*20201129 深度学习推荐系统_王喆 学习笔记 - 图103

25 | 评估指标:我们可以用哪些指标来衡量模型的好坏?
低阶评估指标
我按照指标计算的难易程度,和评估的全面性,把推荐系统的评估指标可以分成低阶评估指标和高阶评估指标两大类。对于低阶评估指标来说,准确率、精确率与召回率、对数损失、均方根误差,这四个指标在推荐模型评估中最常用,计算起来也最容易。所以,我们就先来学习一下这几个低阶评估指标的具体含义。

1. 准确率

准确率 (Accuracy) 是指分类正确的样本占总样本个数的比例,公式 1 就是:*20201129 深度学习推荐系统_王喆 学习笔记 - 图104
其中, ncorrect 是正确分类的样本个数, ntotal 是样本的总数。

准确率是分类任务中非常直观的评价指标,可解释性也很强,但它也存在明显的缺陷,就是当不同类别的样本比例非常不均衡的时候,占比大的类别往往成为影响准确率的最主要因素。比如,负样本占 99%,那么分类器把所有样本都预测为负样本也可以获得 99% 的准确率。

2. 精确率与召回率

我这里所说的精确率(Precision)指的是分类正确的正样本个数占分类器判定为正样本个数的比例,召回率(Recall)是分类正确的正样本个数占真正的正样本个数的比例。

在推荐列表中,通常没有一个确定的阈值来把预测结果直接判定为正样本或负样本,而是采用 Top N 排序结果的精确率(Precision@N)和召回率(Recall@N)来衡量排序模型的性能。具体操作,就是认为模型排序的前 N 个结果就是模型判定的正样本,然后分别计算 Precision@N 和 Recall@N。事实上,精确率和召回率其实是矛盾统一的一对指标。这是什么意思呢?就是,为了提高精确率,模型需要尽量在“更有把握”时把样本预测为正样本,但此时,我们往往会因为过于保守而漏掉很多“没有把握”的正样本,导致召回率降低。

那有没有一个指标能综合地反映精确率和召回率的高低呢?其实是有的,那就是 F1-score。F1-score 的定义是精确率和召回率的调和平均值,具体的定义你可以看看下面的公式 2。F1-score 的值越高,就证明模型在精确率和召回率的整体表现上越好。
*20201129 深度学习推荐系统_王喆 学习笔记 - 图105

3. 对数损失

接着,我们来说一说对数损失(Logloss)这个评估指标。首先,在一个二分类问题中,对数损失函数的定义就是下面的公式 3。
*20201129 深度学习推荐系统_王喆 学习笔记 - 图106
在这个公式中,yi 是输入实例 xi 的真实类别, pi 是预测输入实例 xi 是正样本的概率,N 是样本总数。而面对多分类问题的时候,对数损失函数定义就变成了下面公式 4 的样子:
*20201129 深度学习推荐系统_王喆 学习笔记 - 图107

如果你仔细看公式就会发现,二分类和多分类模型的 Logloss 其实就是我们之前讲过的逻辑回归和 Softmax 模型的损失函数,而大量深度学习模型的输出层正是逻辑回归或 Softmax,因此,采用 Logloss 作为评估指标能够非常直观地反映模型损失函数的变化。所以在训练模型的过程中,我们在每一轮训练中都会输出 Logloss,来观察模型的收敛情况。

4. 均方根误差

刚才我们说的准确率、精确率、召回率、LogLoss 都是针对分类模型指定的指标。分类模型就是指预测某个样本属于哪个类别的模型,最典型的就是点击率预估模型。除了这类分类模型以外,还有回归模型,它是用来预测一个连续值,比如预测某个用户对某个电影会打多少分,这就是一个回归模型。
那我们对于回归模型有什么合适的评估指标吗?对于回归模型来说,最常用的评估指标就是均方根误差(RMSE,Root Mean Square Error)。它的公式是求预测值跟真实值之间差值的均方根:
*20201129 深度学习推荐系统_王喆 学习笔记 - 图108

高阶评估指标

那在高阶评估指标部分,我会给你讲 P-R 曲线、ROC 曲线、平均精度均值,这三个最常用的评估指标。

5. P-R 曲线

首先,我要说的是 P-R 曲线,这里的 P 就是我们之前学过的精确率 Precision,R 就是召回率 Recall。刚才我们说了,为了综合评价一个推荐模型的好坏,不仅要看模型在一个 Top n 值下的精确率和召回率,还要看到模型在不同 N 取值下的表现,甚至最好能绘制出一条 n 从 1 到 N,准确率和召回率变化的曲线。这条曲线就是 P-R 曲线。P-R 曲线的横轴是召回率,纵轴是精确率。对于一个推荐模型来说,它的 P-R 曲线上的一个点代表“在某一阈值下,模型将大于该阈值的结果判定为正样本,将小于该阈值的结果判定为负样本时,整体结果对应的召回率和精确率”。整条 P-R 曲线是通过从高到低移动正样本阈值生成的。如图 1 所示,它画了两个测试模型,模型 A 和模型 B 的对比曲线。其中,实线代表模型 A 的 P-R 曲线,虚线代表模型 B 的 P-R 曲线。
*20201129 深度学习推荐系统_王喆 学习笔记 - 图109
从图中我们可以看到,在召回率接近 0 时,模型 A 的精确率是 0.9,模型 B 的精确率是 1。这说明模型 B 预测的得分前几位的样本全部是真正的正样本,而模型 A 即使是得分最高的几个样本也存在预测错误的情况。然而,随着召回率的增加,两个模型的精确率整体上都有所下降。特别是当召回率在 0.6 附近时,模型 A 的精确率反而超过了模型 B。这就充分说明了,只用一个点的精确率和召回率是不能全面衡量模型性能的,只有通过 P-R 曲线的整体表现,才能对模型进行更全面的评估。虽然 P-R 曲线能全面衡量模型的性能,但是它总归是一条曲线,不是一个数字,我们很难用它直接来判断模型的好坏。那有没有一个指标能用来衡量 P-R 曲线的优劣呢?当然是有的,这个指标就是 AUC(Area Under Curve),曲线下面积。顾名思义,AUC 指的是 P-R 曲线下的面积大小,因此计算 AUC 值只需要沿着 P-R 曲线横轴做积分。AUC 越大,就证明推荐模型的性能越好。

6. ROC 曲线

接着,我们再来介绍第二个高阶指标,ROC 曲线,它也是一个非常常用的衡量模型综合性能的指标。ROC 曲线的全称是 the Receiver Operating Characteristic 曲线,中文名为“受试者工作特征曲线”。ROC 曲线最早诞生于军事领域,而后在医学领域应用甚广,“受试者工作特征曲线”这一名称也正是来源于医学领域。ROC 曲线的横坐标是 False Positive Rate(FPR,假阳性率),纵坐标是 True Positive Rate (TPR,真阳性率)。这两个名字读上去就有点拗口,我们还是通过它们的定义来理解一下 :
*20201129 深度学习推荐系统_王喆 学习笔记 - 图110
在公式中,P 指的是真实的正样本数量,N 是真实的负样本数量;TP 指的是 P 个正样本中被分类器预测为正样本的个数,FP 指的是 N 个负样本中被分类器预测为正样本的个数。但我估计你看了这个定义,可能还是不好理解这个 ROC 曲线是怎么得到的。没关系,我们真正去画一条 ROC 曲线,你就明白了。和 P-R 曲线一样,ROC 曲线也是通过不断移动模型正样本阈值生成的。假设测试集中一共有 20 个样本,模型的输出如下表所示,表中第一列为样本序号,Class 为样本的真实标签,Score 为模型输出的样本为正的概率,样本按照预测概率从高到低排序。在输出最终的正例、负例之前,我们需要指定一个阈值,并且设定预测概率大于该阈值的样本会被判为正例,小于该阈值的会被判为负例。比如,我们指定 0.9 为阈值,那么只有第一个样本会被预测为正例,其他全部都是负例。这里的阈值也被称为 “截断点”。
*20201129 深度学习推荐系统_王喆 学习笔记 - 图111接下来,我们要做的就是动态地调整截断点,从最高的得分开始(实际上是从正无穷开始,对应着 ROC 曲线的零点),逐渐调整到最低得分。每一个截断点都会对应一个 FPR 和 TPR 的值,在 ROC 图上绘制出每个截断点对应的位置,再连接每个点之后,我们就能得到最终的 ROC 曲线了。那么 ROC 曲线上的点具体应该怎么确定呢?我们来看几个例子,当截断点选择为正无穷的时候,模型会把全部样本预测为负例,那 FP 和 TP 必然都为 0,FPR 和 TPR 也都为 0,因此曲线的第一个点就是 (0,0) 。当把截断点调整为 0.9 的时候,模型预测 1 号样本为正样本,并且这个样本也确实是正样本。因此,在 20 个样本中,当 TP=1,所有正例数量 P=10 的时候,TPR=TP/P=1/10。我们还可以看到,这个例子里没有预测错的正样本,也就是说当 FP=0,负样本总数 N=10 的时候,FPR=FP/N=0/10=0,对应着 ROC 图上的点 (0,0.1)。
*20201129 深度学习推荐系统_王喆 学习笔记 - 图112

其实,还有一种更直观的绘制 ROC 曲线的方法。首先,我们根据样本标签统计出正负样本的数量,假设正样本数量为 P,负样本数量为 N。然后,我们把横轴的刻度间隔设置为 1/N,纵轴的刻度间隔设置为 1/P。接着,我们再根据模型输出的预测概率对样本进行从高到低的排序。最后,依次遍历样本。同时,从零点开始绘制 ROC 曲线,每遇到一个正样本就沿纵轴方向绘制一个刻度间隔的曲线,每遇到一个负样本就沿横轴方向绘制一个刻度间隔的曲线,直到遍历完所有样本,曲线最终停在 (1,1) 这个点,整个 ROC 曲线就绘制完成了。在绘制完 ROC 曲线后,我们也可以像 P-R 曲线一样,计算出 ROC 曲线的 AUC,AUC 越高,推荐模型的效果就越好。

7. 平均精度均值

最后,我们来说平均精度均值 mAP(mAP,mean average precision)这个高阶指标,它除了在推荐系统中比较常用,在信息检索领域也很常用。mAP 其实是对平均精度(AP,average precision)的再次平均,因此在计算 mAP 前,我们需要先学习什么是平均精度 AP。假设,推荐系统对某一用户测试集的排序结果是 1, 0, 0, 1, 1, 1。其中,1 代表正样本,0 代表负样本。接下来,我们就按照之前学过的方法,计算这个序列中每个位置上的 precision@N。你可以自己先试着计算一下,也可以直接看我下面计算好的结果
*20201129 深度学习推荐系统_王喆 学习笔记 - 图113
计算平均精度 AP 的时候,我们只取正样本处的 precision 进行平均,根据得到的表格 AP =(1/1 + 2/4 + 3/5 + 4/6)/4 = 0.6917。接下来,我们再来看什么是 mAP。
如果推荐系统对测试集中的每个用户都进行样本排序,那么每个用户都会计算出一个 AP 值,再对所有用户的 AP 值进行平均,就得到了 mAP。也就是说,mAP 是对精确度平均的平均。
这里就需要注意了,mAP 的计算方法和 P-R 曲线、ROC 曲线的计算方法是完全不同的,因为 mAP 需要对每个用户的样本进行分用户排序,而 P-R 曲线和 ROC 曲线均是对全量测试样本进行排序。这一点在实际操作中是需要注意的。

这里的7 个评估指标我们就讲完了。如果你是第一次接触它们,可能现在已经有点茫然了。事实上,除了这些评估指标,还有很多其他的推荐系统指标,比如归一化折扣累计收益(Normalized Discounted Cumulative Gain,NDCG)、覆盖率(Coverage)、多样性(Diversity)等等。那面对这么多评估指标,你肯定想问,我们应该怎么选择它们呢?

*20201129 深度学习推荐系统_王喆 学习笔记 - 图114

特别加餐|TensorFlow的模型离线评估实践怎么做?
这里我们按照 8:2 的比例把全量样本集划分为训练集和测试集,再把它们分别存储在SparrowRecSys/src/main/resources/webroot/sampledata/trainingSamples.csv和SparrowRecSys/src/main/resources/webroot/sampledata/testSamples.csv路径中。在 TensorFlow 内部,我们跟之前载入数据集的方式一样,调用get_dataset方法分别载入训练集和测试集就可以了。

26 | 在线测试:如何在推荐服务器内部实现A/B测试?

1.如何理解 A/B 测试?

A/B 测试又被称为“分流测试”或“分桶测试”,它通过把被测对象随机分成 A、B 两组,分别对它们进行对照测试的方法得出实验结论。具体到推荐模型测试的场景下,它的流程是这样的:先将用户随机分成实验组和对照组,然后给实验组的用户施以新模型,给对照组的用户施以旧模型,再经过一定时间的测试后,计算出实验组和对照组各项线上评估指标,来比较新旧模型的效果差异,最后挑选出效果更好的推荐模型。好了,现在我们知道了什么是线上 A/B 测试。那它到底有什么优势,让几乎所有的互联网公司主要使用它来确定模型最终的效果呢?你有想过这是什么原因吗?我总结了一下,主要有三点原因。接下来,我们就一起来聊聊。
首先,离线评估无法完全还原线上的工程环境。 一般来讲,离线评估往往不考虑线上环境的延迟、数据丢失、标签数据缺失等情况,或者说很难还原线上环境的这些细节。因此,离线评估环境只能说是理想状态下的工程环境,得出的评估结果存在一定的失真现象。
其次,线上系统的某些商业指标在离线评估中无法计算。 离线评估一般是针对模型本身进行评估的,无法直接获得与模型相关的其他指标,特别是商业指标。像我们上节课讲的,离线评估关注的往往是 ROC 曲线、PR 曲线的改进,而线上评估却可以全面了解推荐模型带来的用户点击率、留存时长、PV 访问量这些指标的变化。其实,这些指标才是最重要的商业指标,跟公司要达成的商业目标紧密相关,而它们都要由 A/B 测试进行更全面准确的评估。
最后是离线评估无法完全消除数据有偏(Data Bias)现象的影响。 什么叫“数据有偏”呢?因为离线数据都是系统利用当前算法生成的数据,因此这些数据本身就不是完全客观中立的,它是用户在当前模型下的反馈。所以说,用户本身有可能已经被当前的模型“带跑偏了”,你再用这些有偏的数据来衡量你的新模型,得到的结果就可能不客观。正是因为离线评估存在这三点硬伤,所以我们必须利用线上 A/B 测试来确定模型的最终效果。明确了这一点,是不是让我们的学习更有方向了?接下来,我们再深入去学习一下 A/B 测试的核心原则和评估指标。

2. A/B 测试的“分桶”和“分层”原则

刚才,我们说 A/B 测试的原理就是把用户分桶后进行对照测试。这听上去好像没什么难的,但其实我们要考虑的细节还有很多,比如到底怎样才能对用户进行一个公平公正的分桶呢?如果有多组实验在同时做 A/B 测试,怎样做才能让它们互不干扰?

下面,我就来详细的讲一讲 A/B 测试的“分桶”和“分层”的原则,告诉你让 A/B 测试公平且高效的执行方法长什么样。在 A/B 测试分桶的过程中,我们需要注意的是样本的独立性和分桶过程的无偏性。这里的“独立性”指的是同一个用户在测试的全程只能被分到同一个桶中。“无偏性”指的是在分桶过程中用户被分到哪个实验桶中应该是一个纯随机的过程。举个简单的例子,我们把用户 ID 是奇数的用户分到对照组,把用户 ID 是偶数的用户分到实验组,这个策略只有在用户 ID 完全是随机生成的前提下才能说是无偏的,如果用户 ID 的奇偶分布不均,我们就无法保证分桶过程的无偏性。所以在实践的时候,我们经常会使用一些比较复杂的 Hash 函数,让用户 ID 尽量随机地映射到不同的桶中。说完了分桶,那什么是分层呢?要知道,在实际的 A/B 测试场景下,同一个网站或应用往往要同时进行多组不同类型的 A/B 测试。比如,前端组正在进行不同 App 界面的 A/B 测试的时候,后端组也在进行不同中间件效率的 A/B 测试,同时算法组还在进行推荐场景 1 和推荐场景 2 的 A/B 测试。这个时候问题就来了,这么多 A/B 测试同时进行,我们怎么才能让它们互相不干扰呢?你可能会说,这还不简单,我们全都并行地做这些实验,用户都不重叠不就行了。这样做当然可以,但非常低效。你如果在工作中进行过 A/B 测试的话肯定会知道,线上测试资源是非常紧张的,如果不进行合理的设计,很快所有流量资源都会被 A/B 测试占满。为了解决这个问题,我们就要用到 A/B 测试的分层原则了。Google 在一篇关于实验测试平台的论文《Overlapping Experiment Infrastructure: More, Better, Faster Experimentation》中,详细介绍了 A/B 测试分层以及层内分桶的原则。如果你没看过这篇论文,没有关系,你记住我总结出来的这句话就够了:层与层之间的流量“正交”,同层之间的流量“互斥”。它是什么意思呢?接下来,我就针对这句话做个详细的解释。首先,我们来看层与层之间的流量“正交”,它指的是层与层之间的独立实验的流量是正交的,一批实验用的流量穿越每层实验时,都会再次随机打散,然后再用于下一层的实验。这么说好像还是太抽象,我们来看下面的示意图。假设,在一个 X 层的实验中,流量被随机平均分为 X1(蓝色)和 X2(白色)两部分。当它们穿越到 Y 层的实验之后,X1 和 X2 的流量会被随机且均匀地分配给 Y 层的两个桶 Y1 和 Y2。如果 Y1 和 Y2 的 X 层流量分配不均匀,那么 Y 层的样本就是有偏的,Y 层的实验结果就会被 X 层的实验影响,也就无法客观地反映 Y 层实验组和对照组变量的影响。
*20201129 深度学习推荐系统_王喆 学习笔记 - 图115

理解了第一句话,我们再来看看什么叫同层之间的流量“互斥”。这里的“互斥”具体有 2 层含义:如果同层之间进行多组 A/B 测试,不同测试之间的流量不可以重叠,这是第一个“互斥”;一组 A/B 测试中实验组和对照组的流量是不重叠的,这是第二个“互斥”。在基于用户的 A/B 测试中,“互斥”的含义可以被进一步解读为,不同实验之间以及 A/B 测试的实验组和对照组之间的用户是不重叠的。特别是对推荐系统来说,用户体验的一致性是非常重要的。也就是说我们不可以让同一个用户在不同的实验组之间来回“跳跃”,这样会严重损害用户的实际体验,也会让不同组的实验结果相互影响。因此在 A/B 测试中,保证同一用户始终分配到同一个组是非常有必要的。A/B 测试的“正交”与“互斥”原则共同保证了 A/B 测试指标的客观性,而且由于分层的存在,也让功能无关的 A/B 测试可以在不同的层上执行,充分利用了流量资源。在清楚了 A/B 测试的方法之后,我们要解决的下一个问题就是,怎么选取线上 A/B 测试的指标。

3. 线上 A/B 测试的评估指标

一般来说,A/B 测试是模型上线前的最后一道测试,通过 A/B 测试检验的模型会直接服务于线上用户,来完成公司的商业目标。因此,A/B 测试的指标应该与线上业务的核心指标保持一致。这就需要我们因地制宜地制定最合适的推荐指标了。具体怎么做呢?实际也不难,那在实际的工作中,我们需要跟产品、运营团队多沟通,在测试开始之前一起制定大家都认可的评估指标。为了方便你参考,我在下表中列出了电商类推荐模型、新闻类推荐模型、视频类推荐模型的主要线上 A/B 测试评估指标,你可以看一看。
*20201129 深度学习推荐系统_王喆 学习笔记 - 图116
看了这些指标,我想你也发现了,线上 A/B 测试的指标和离线评估的指标(诸如 AUC、F1- score 等),它们之间的差异非常大。这主要是因为,离线评估不具备直接计算业务核心指标的条件,因此退而求其次,选择了偏向于技术评估的模型相关指标,但公司更关心的是能够驱动业务发展的核心指标,这也是 A/B 测试评估指标的选取原则。总的来说,在具备线上环境条件时,利用 A/B 测试验证模型对业务核心指标的提升效果非常有必要。从这个意义上讲,线上 A/B 测试的作用是离线评估永远无法替代的。

4. SparrowRecSys 中 A/B 测试的实现方法

搞清楚了 A/B 测试的主要方法,下一步就让我们一起在 SparrowRecSys 上实现一个 A/B 测试模块,彻底掌握它吧!既然是线上测试,那我们肯定需要在推荐服务器内部来实现这个 A/B 测试的模块。模块的基本框架不难实现,就是针对不同的 userId,随机分配给不同的实验桶,每个桶对应着不同的实验设置。比较方便的是,我们可以直接在上一篇刚实现过的“猜你喜欢”功能上进行实验。实验组的设置是算法 NerualCF,对照组的设置是 Item2vec Embedding 算法。接下来,我们说一下详细的实现步骤。首先,我们在 SparrowRecSys 里面建立了一个 ABTest 模块,它负责为每个用户分配实验设置。其中,A 组使用的模型 bucketAModel 是 emb,代表着 Item2vec Embedding 算法,B 组使用的模型 bucketBModel 是 Nerualcf。除此之外,我们还给不在 A/B 测试的用户设置了默认模型 emb,默认模型是不在实验范围内的用户的设置。模型设置完,就到了分配实验组的阶段。这里,我们使用 getConfigByUserId 函数来确定用户所在的实验组。具体怎么做呢?因为这个函数只接收 userId 作为唯一的输入参数,所以我们利用 userId 的 hashCode 把数值型的 ID 打散,然后利用 userId 的 hashCode 和 trafficSplitNumber 这个参数进行取余数的操作,根据余数的值来确定 userId 在哪一个实验组里。你可能对 trafficSplitNumber 这个参数的作用还不熟悉,我来进一步解释一下。这个参数的含义是把我们的全部用户分成几份。这里,我们把所有用户分成了 5 份,让第 1 份用户参与 A 组实验,第 2 份用户参与 B 组实验,其余用户继续使用系统的默认设置。这样的操作就是分流操作,也就是把流量划分之后,选取一部分参与 A/B 测试。

我们可以看到,这里的实现非常简单,就是调用 ABTest.getConfigByUserId 函数获取用户对应的实验设置,然后把得到的参数 model 传入后续的业务逻辑代码。需要注意的是,我设置了一个全局的 A/B 测试使能标识 Config.IS_ENABLE_AB_TEST,你在测试这部分代码的时候,要把这个使能标识改为 true。上面就是经典的 A/B 测试核心代码的实现。在实际的应用中,A/B 测试的实现当然要更复杂一些。比如,不同实验的设置往往是存储在数据库中的,需要我们从数据库中拿到它。再比如,为了保证分组时的随机性,我们往往会创建一些复杂的 hashCode 函数,保证能够均匀地把用户分到不同的实验桶中。但整个 A/B 测试的核心逻辑没有变化,你完全可以参考我们今天的实现过程。

*20201129 深度学习推荐系统_王喆 学习笔记 - 图117

27 | 评估体系:如何解决A/B测试资源紧张的窘境?

我们在进行推荐系统评估时经常会遇到两类问题。一类是在做线上 A/B 测试的时候,流量经常不够用,要排队等别人先做完测试之后才能进行自己的测试。线上 A/B 测试资源紧张的窘境,会大大拖慢我们试验的新思路,以及迭代优化模型的进度。另一类是,离线评估加上在线评估有那么多种测试方法,在实际工作中,我们到底应该选择哪一种用来测试,还是都要覆盖到呢?其实,这两个问题的答案是有深刻联系的,并不是孤立的。我认为最好的解决办法就是,建立起一套推荐系统的评估体系,用它来解决不同评估方法的配合问题,以及线上 A/B 测试资源紧张的问题。这节课,我就带你一起来厘清如何建立起一整套推荐系统评估体系。

1.什么是推荐系统的评估体系?

首先,什么是评估体系呢?我先给它下一个定义,推荐系统的评估体系指的是,由多种不同的评估方式组成的、兼顾效率和正确性的,一套用于评估推荐系统的解决方案。一个成熟的推荐系统评估体系应该综合考虑评估效率和正确性,可以利用很少的资源,快速地筛选出效果更好的模型。那对一个商业公司来说,最公正也是最合理的评估方法就是进行线上测试,来评估模型是否能够更好地达成公司或者团队的商业目标。但是,正如我们开头所说,线上 A/B 测试要占用宝贵的线上流量资源,这些有限的线上测试机会远远不能满足算法工程师改进模型的需求。所以如何有效地把线上和离线测试结合起来,提高测试的效率,就是我们迫切的需求。那我们该怎么去构建起一整套评估体系呢?图 1 就是一个典型的评估体系示意图。从图中我们可以看到,处于最底层的是传统的离线评估方法,比如 Holdout 检验、交叉检验等,往上是离线 Replay 评估方法,再往上是一种叫 Interleaving 的线上测试方法,我们等会还会详细介绍,最后是线上 A/B 测试。

*20201129 深度学习推荐系统_王喆 学习笔记 - 图118

这四层结构共同构成完整的评估体系,做到了评估效率和评估正确性之间的平衡,越是底层的方法就会承担越多筛选掉改进思路的任务,这时候“评估效率”就成了更关键的考虑因素,那对于“正确性”的评估,我们反而没有多么苛刻的要求了。总的来说,离线评估由于有着更多可供利用的计算资源,可以更高效、快速地筛选掉那些“不靠谱”的模型来改进思路,所以被放在了第一层的位置。随着候选模型被一层层筛选出来,越接近正式上线的阶段,评估方法对评估“正确性”的要求就越严格。因此,在模型正式上线前,我们应该以最接近真实产品体验的 A/B 测试,来做最后的模型评估,产生最具说服力的在线指标之后,才能够进行最终的模型上线,完成模型改进的迭代过程。

讲了这么多,你可能会觉得,道理没问题,但工作中真的是这样吗?不如,我们来看个例子。下图就是一个很形象的工作中的模型筛选过程。假设,现在有 30 个待筛选的模型,如果所有模型都直接进入线上 A/B 测试的阶段进行测试,所需的测试样本是海量的,由于线上流量有限,测试的时间会非常长。但如果我们把测试分成两个阶段,第一个阶段先进行初筛,把 30 个模型筛选出可能胜出的 5 个,再只对这 5 个模型做线上 A/B 测试,所需的测试流量规模和测试时间长度都会大大减少。这里的初筛方法,就是我们在评估体系中提到的离线评估、离线 Replay 和在线 Interleaving 等方法。

*20201129 深度学习推荐系统_王喆 学习笔记 - 图119

到这里,我想你已经清楚了什么是推荐系统的评估体系,以及评估体系是有哪些方法组成的。但在这些组成方法中,我们还有两点要重点注意:一个是离线 Replay 这个方法,虽然我们之前讲过离线 Replay 的原理,但是对于它的相关工程架构还没有讲过;第二个是上面提到过的线上 Interleaving 方法。 下面,我就借着流媒体巨头 Netflix 的实践方案,来讲解一下离线 Replay 和在线 Interleaving 的细节。

2.Netflix 的 Replay 评估方法实践

借着下图 3,我们来回顾一下,第 24 课学过的离线 Replay 方法的原理:离线 Replay 通过动态的改变测试时间点,来模拟模型的在线更新过程,让测试过程更接近真实线上环境。

*20201129 深度学习推荐系统_王喆 学习笔记 - 图120

但是在 Replay 方法的实现过程中,存在一个很棘手的工程问题,就是我们总提到的“未来信息”问题,或者叫做“特征穿越”问题。因此在 Replay 过程中,每次模型更新的时候,我们都需要用历史上“彼时彼刻”的特征进行训练,否则训练和评估的结果肯定是不准确的。我来举个例子,假设 Replay 方法要使用 8 月 1 日到 8 月 31 日的样本数据进行重放,这些样本中包含一个特征,叫做“历史 CTR”,这个特征只能通过历史数据来计算生成。比如说,8 月 20 日的样本就只能够使用 8 月 1 日到 8 月 19 日的数据来生成“历史 CTR”这个特征,绝不能使用 8 月 20 日以后的数据来生成这个特征。在评估过程中,如果我们为了工程上的方便,使用了 8 月 1 日到 8 月 31 日所有的样本数据生成这个特征,供所有样本使用,之后再使用 Replay 的方法进行评估,那我们得到的结论必然是错误的。那么问题来了,在工程上,为了方便按照 Replay 方法进行模型评估,我们应该怎么去建立一套数据处理的架构,支持这种历史特征的复现呢?接下来,我们就看一看 Netflix 是怎么解决这个问题的。Netflix 为了进行离线 Replay 的实验,建立了一整套从数据生成到数据处理再到数据存储的数据处理架构,并给它起了一个很漂亮的名字,叫做时光机(Time Machine)。下图 4 就是时光机的架构,图中最主要的就是 Snapshot Jobs(数据快照)模块。它是一个每天执行的 Spark 程序,它做的主要任务就是把当天的各类日志、特征、数据整合起来,形成当天的、供模型训练和评估使用的样本数据。它还会以日期为目录名称,将样本快照数据保存在分布式文件系统 S3 中(Snapshots),再对外统一提供 API(Batch APIs),供其他模型在训练和评估的时候按照时间范围方便地获取。

*20201129 深度学习推荐系统_王喆 学习笔记 - 图121

这个 Snapshot Jobs 主任务的源数据是从哪来的呢?你可以重点关注它上方的 Context Set 模块和左边的 Prana 模块。接下来,我再详细和你说说这两个模块的任务。Context Set 模块负责保存所有的历史当天的环境信息。 环境信息主要包括两类:一类是存储在 Hive 中的场景信息,比如用户的资料、设备信息、物品信息等数据;另一类是每天都会发生改变的一些统计类信息,包括物品的曝光量、点击量、播放时长等信息。Prana 模块负责处理每天的系统日志流。 系统日志流指的是系统实时产生的日志,它包括用户的观看历史(Viewing History)、用户的推荐列表(My List)和用户的评价(Ratings)等。这些日志从各自的服务(Service)中产生,由 Netflix 的统一数据接口 Prana 对外提供服务。因此,Snapshot Jobs 这个核心模块每天的任务就是,通过 Context Set 获取场景信息,通过 Prana 获取日志信息,再经过整合处理、生成特征之后,保存当天的数据快照到 S3。在生成每天的数据快照后,使用 Replay 方法进行离线评估就不再是一件困难的事情了,因为我们没有必要在 Replay 过程中进行烦琐的特征计算,直接使用当天的数据快照就可以了。

在时光机这个架构之上,使用某个时间段的样本进行一次 Replay 评估,就相当于直接穿越到了彼时彼刻,用当时的日志和特征进行模型训练和评估,就像进行了一次时光旅行(Time Travel)一样。

3.Interleaving 评估方法是什么

讲完了离线 Replay 的工程实现方法,我们再来聊一聊什么是 Interleaving 在线评估方法。那 Interleaving 评估方法提出的意义是什么呢?主要有两方面:首先,它是和 A/B 测试一样的在线评估方法,能够得到在线评估指标;其次,它提出的目的是为了比传统的 A/B 测试用更少的资源,更快的速度得到在线评估的结果。清楚了 Interleaving 评估方法提出的意义,我们就可以更好地理解 Interleaving 方法的具体细节了。下面,我们对比 A/B 测试,来看看 Interleaving 方法的具体实现过程。在传统的 A/B 测试中,我们会把用户随机分成两组。一组接受当前的推荐模型 A 的推荐结果,这一组被称为对照组 。另一组接受新的推荐模型 B 的推荐结果,这组被成为实验组。在 Interleaving 方法中,不再需要两个不同组的用户,只需要一组用户,这些用户会收到模型 A 和模型 B 的混合结果。也就是说,用户会在一个推荐列表里同时看到模型 A 和模型 B 的推荐结果。在评估的过程中,Interleaving 方法通过分别累加模型 A 和模型 B 推荐物品的效果,来得到模型 A 和 B 最终的评估结果。下图可以帮助我们更形象地对比 A/B 测试和 Interleaving 方法。
*20201129 深度学习推荐系统_王喆 学习笔记 - 图122

那你可能想问了,在使用 Interleaving 方法进行测试的时候,我们该怎么保证对模型 A 和模型 B 的测试是公平的呢?如果有一个模型的结果总排在第一位,这对另一个模型不就不公平了吗?这个问题很好,我们确实需要考虑推荐列表中位置偏差的问题,要想办法避免来自模型 A 或者模型 B 的物品总排在第一位。因此,我们需要以相等的概率让模型 A 和模型 B 产生的物品交替领先。这就像在野球场打球的时候,两个队长会先通过扔硬币的方式决定谁先选人,再交替来选择队员。理解了原理,我们再结合下面的图示,来进一步理解 Interleaving 方法混合模型 A 和 B 结果的过程。和刚才说的野球场选人的过程一样,我们先选模型 A 或者模型 B 的排名第一的物品作为最终推荐列表的第一个物品,然后再交替选择,直到填满整个推荐列表。所以,最后得到的列表会是 ABABAB,或者 BABABA 这样的顺序,而且这两种形式出现的概率应该是相等的,这样才能保证两个模型的公平性。

*20201129 深度学习推荐系统_王喆 学习笔记 - 图123

最后,我们要清楚推荐列表中的物品到底是由模型 A 生成的,还是由模型 B 生成的,然后统计出所有模型 A 物品的综合效果,以及模型 B 物品的综合效果,然后进行对比。这样,模型评估过程就完成了。总的来说,Interleaving 的方法由于不用进行用户分组,因此比传统 A/B 测试节约了一半的流量资源。但是 Interleaving 方法能彻底替代传统 A/B 测试吗?其实也不能,在测试一些用户级别而不是模型级别的在线指标时,我们就不能用 Interleaving 方法。比如用户的留存率,用户从试用到付费的转化率等,由于 Interleaving 方法同时使用了对照模型和实验模型的结果,我们就不清楚到底是哪个模型对这些结果产生了贡献。但是在测试 CTR、播放量、播放时长这些指标时,Interleaving 就可以通过累加物品效果得到它们。这个时候,它就能很好地替代传统的 A/B 测试了。到这里,我们就形成了一个完整、高效且准确的评估系统。希望你能从整体的角度重新审视一遍这个体系中的每个方法,如果有不清楚的,再好好回顾一下我讲的知识点。

*20201129 深度学习推荐系统_王喆 学习笔记 - 图124

28 | 业界经典:YouTube深度学习推荐系统的经典架构长什么样?