mirror of
https://github.com/cheetahlou/CategoryResourceRepost.git
synced 2025-11-16 14:13:46 +08:00
mod
This commit is contained in:
130
极客时间专栏/深度学习推荐系统实战/推荐模型篇/15 | 协同过滤:最经典的推荐模型,我们应该掌握什么?.md
Normal file
130
极客时间专栏/深度学习推荐系统实战/推荐模型篇/15 | 协同过滤:最经典的推荐模型,我们应该掌握什么?.md
Normal file
@@ -0,0 +1,130 @@
|
||||
<audio id="audio" title="15 | 协同过滤:最经典的推荐模型,我们应该掌握什么?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/a8/72/a8d1eb864a1886d6fe6b74d37ff10672.mp3"></audio>
|
||||
|
||||
你好,我是王喆。今天我们要开启推荐模型篇的学习。
|
||||
|
||||
推荐模型篇是整个课程中最重要的一个模块,因为推荐模型直接决定了最终物品排序的结果,它的好坏也直接影响着推荐效果的优劣。而且,从某种意义上讲,推荐系统的整体架构都是围绕着推荐模型搭建的,用于支持推荐模型的上线、训练、评估、服务。因此,我一直把**推荐模型称作“推荐系统这个皇冠上的明珠”**。
|
||||
|
||||
而提起推荐模型,我们就不能不提协同过滤算法。它可能是推荐系统自诞生以来最经典的算法,且没有之一。虽然我们课程的主题是“深度学习”推荐系统,但协同过滤以及它后续衍生出来的各类模型,都与深度学习推荐模型有着千丝万缕的联系。因此,在进入深度学习模型之前,掌握协同过滤及其衍生模型是非常有必要的。
|
||||
|
||||
今天,我就来给你讲讲经典协同过滤和它的衍生模型矩阵分解的原理,以及相关的Spark实现。
|
||||
|
||||
## 协同过滤算法的基本原理
|
||||
|
||||
我在特征工程篇曾经提到过:**“用户行为数据是推荐系统最常用,也是最关键的数据。用户的潜在兴趣、用户对物品的评价好坏都反映在用户的行为历史中”**。
|
||||
|
||||
而协同过滤算法,就是一种完全依赖用户和物品之间行为关系的推荐算法。我们从它的名字“协同过滤”中,也可以窥探到它背后的原理,就是 **“协同大家的反馈、评价和意见一起对海量的信息进行过滤,从中筛选出用户可能感兴趣的信息”**。
|
||||
|
||||
这么说可能还是太抽象了,接下来,我们就一起看一个电商场景下的例子。通过分析这个例子,你就能搞清楚协同过滤算法的推荐过程了。这个电商推荐系统从得到原始数据到生成最终推荐分数,全过程一共可以总结为6个步骤,如下所示。下面,我们一一来讲。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/39/42/3960001f6c049652160cb16ff3ddee42.jpg" alt="" title="图1 协同过滤的过程 (来自《深度学习推荐系统》)">
|
||||
|
||||
首先,我们可以看到,电商网站的商品库里一共有4件商品:一个游戏机、一本小说、一本杂志,以及一台电视机。假设,现在有一名用户X访问了这个电商网站,电商网站的推荐系统需要决定是否推荐电视机给用户X。
|
||||
|
||||
为了进行这项预测,推荐系统可以利用的数据有用户X对其他商品的历史评价数据,以及其他用户对这些商品的历史评价数据。我在图1(b)中用绿色“点赞”的标志表示好评,用红色“踩”的标志表示了差评。这样一来,用户、商品和评价记录就构成了带有标识的有向图。
|
||||
|
||||
接下来,为了方便计算,我们将有向图转换成矩阵的形式。这个矩阵表示了物品共同出现的情况,因此被称为“共现矩阵”。其中,用户作为矩阵行坐标,物品作为列坐标,我们再把“点赞”和“踩”的用户行为数据转换为矩阵中相应的元素值。这里,我们将“点赞”的值设为1,将“踩”的值设为-1,“没有数据”置为0(如果用户对物品有具体的评分,那么共现矩阵中的元素值可以取具体的评分值,没有数据时的默认评分也可以取评分的均值)。
|
||||
|
||||
你发现了吗,生成共现矩阵之后,推荐问题就转换成了预测矩阵中问号元素(图1(d)所示)的值的问题。由于在“协同”过滤算法中,推荐的原理是让用户考虑与自己兴趣相似用户的意见。因此,我们预测的第一步就是找到与用户X兴趣最相似的n(Top n用户,这里的n是一个超参数)个用户,然后综合相似用户对“电视机”的评价,得出用户X对“电视机”评价的预测。
|
||||
|
||||
从共现矩阵中我们可以知道,用户B和用户C由于跟用户X的行向量近似,被选为Top n(这里假设n取2)相似用户,接着在图1(e)中我们可以看到,用户B和用户C对“电视机”的评价均是负面的。因为相似用户对“电视机”的评价是负面的,所以我们可以预测出用户X对“电视机”的评价也是负面的。在实际的推荐过程中,推荐系统不会向用户X推荐“电视机”这一物品。
|
||||
|
||||
到这里,协同过滤的算法流程我们就说完了。也许你也已经发现了,这个过程中有两点不严谨的地方,一是用户相似度到底该怎么定义,二是最后我们预测用户X对“电视机”的评价也是负面的,这个负面程度应该有一个分数来衡量,但这个推荐分数该怎么计算呢?
|
||||
|
||||
### 计算用户相似度
|
||||
|
||||
首先,我们来解决计算用户相似度的问题。计算用户相似度其实并不是什么难事,因为在共现矩阵中,每个用户对应的行向量其实就可以当作一个用户的Embedding向量。相信你早已经熟悉Embedding相似度的计算方法,那我们这里依葫芦画瓢就可以知道基于共现矩阵的用户相似度计算方法啦。
|
||||
|
||||
最经典的方法就是利用余弦相似度了,它衡量了用户向量i和用户向量j之间的向量夹角大小。夹角越小,余弦相似度越大,两个用户越相似,它的定义如下:
|
||||
|
||||
除了最常用的余弦相似度之外,相似度的定义还有皮尔逊相关系数、欧式距离等等。咱们课程主要使用的是余弦相似度,因此你只要掌握它就可以了,其他的定义我这里不再多说了。
|
||||
|
||||
### 用户评分的预测
|
||||
|
||||
接下来,我们再来看看推荐分数的计算。在获得Top n个相似用户之后,利用Top n用户生成最终的用户$u$对物品$p$的评分是一个比较直接的过程。这里,我们假设的是“目标用户与其相似用户的喜好是相似的”,根据这个假设,我们可以利用相似用户的已有评价对目标用户的偏好进行预测。最常用的方式是,利用用户相似度和相似用户评价的加权平均值,来获得目标用户的评价预测,公式如下所示。
|
||||
|
||||
$$<br>
|
||||
\mathrm{R}_{u, p}=\frac{\sum_{s \epsilon S}\left(w_{u, s} \cdot R_{s, p}\right)}{\sum_{s \in S} w_{u, s}}<br>
|
||||
$$
|
||||
|
||||
其中,权重$w_{u, s}$是用户$u$和用户$s$的相似度,$R_{s, p}$ 是用户$s$对物品$p$的评分。
|
||||
|
||||
在获得用户$u$对不同物品的评价预测后,最终的推荐列表根据评价预测得分进行排序即可得到。到这里,我们就完成了协同过滤的全部推荐过程。
|
||||
|
||||
## 矩阵分解算法的原理
|
||||
|
||||
虽然说协同过滤是目前公认的最经典的推荐算法,但我们还是可以轻松找出它的缺点,那就是共现矩阵往往非常稀疏,在用户历史行为很少的情况下,寻找相似用户的过程并不准确。于是,著名的视频流媒体公司Netflix对协同过滤算法进行了改进,提出了矩阵分解算法,加强了模型处理稀疏矩阵的能力。
|
||||
|
||||
这里,我们还是用一个直观的例子来理解一下什么叫做矩阵分解。这次我从Netflix的矩阵分解论文中截取了两张示意图(图2),来比较协同过滤和矩阵分解的原理。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/63/f8/63f52fe38288a9b31c2d8e7640f8c4f8.jpg" alt="" title="图2 “协同过滤(左a)”和“矩阵分解(右b)”的原理图">
|
||||
|
||||
如图2(a)所示,协同过滤算法找到用户可能喜欢的视频的方式很直观,就是利用用户的观看历史,找到跟目标用户Joe看过同样视频的相似用户,然后找到这些相似用户喜欢看的其他视频,推荐给目标用户Joe。
|
||||
|
||||
矩阵分解算法则是期望为每一个用户和视频生成一个隐向量,将用户和视频定位到隐向量的表示空间上(如图2(b)所示),距离相近的用户和视频表明兴趣特点接近,在推荐过程中,我们就应该把距离相近的视频推荐给目标用户。例如,如果希望为图2(b)中的用户Dave推荐视频,我们可以找到离Dave的用户向量最近的两个视频向量,它们分别是《Ocean’s 11》和《The Lion King》,然后我们可以根据向量距离由近到远的顺序生成Dave的推荐列表。
|
||||
|
||||
这个时候你肯定觉得,矩阵分解不就是相当于一种Embedding方法嘛。没错,**矩阵分解的主要过程,就是先分解协同过滤生成的共现矩阵,生成用户和物品的隐向量,再通过用户和物品隐向量间的相似性进行推荐**。
|
||||
|
||||
那这个过程的关键就在于如何分解这个共现矩阵了。从形式上看,矩阵分解的过程是直观的,就是把一个mxn的共现矩阵,分解成一个mxk的用户矩阵和kxn的物品矩阵相乘的形式(如图3)。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/60/fb/604b312899bff7922528df4836c10cfb.jpeg" alt="" title="图3 矩阵分解示意图">
|
||||
|
||||
有了用户矩阵和物品矩阵,用户隐向量和物品隐向量就非常好提取了。用户隐向量就是用户矩阵相应的行向量,而物品隐向量就是物品矩阵相应的列向量。
|
||||
|
||||
那关键问题就剩下一个,也就是我们该通过什么方法把共现矩阵分解开呢?最常用的方法就是梯度下降。梯度下降的原理我们在[第3讲](https://time.geekbang.org/column/article/291245)学习过,简单来说就是通过求取偏导的形式来更新权重。梯度更新的公式是 $(w^{t+1}=w^{t}-\alpha * \frac{\partial L}{\partial w})$。为了实现梯度下降,最重要的一步是定义损失函数$L$,定义好损失函数我们才能够通过求导的方式找到梯度方向,这里我们就给出矩阵分解损失函数的定义如下。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/30/3e/3034f1205b8f9ce0d2f736957ff1933e.jpeg" alt="">
|
||||
|
||||
这个目标函数里面,${r}_{u i}$ 是共现矩阵里面用户$u$对物品$i$的评分,${q}_{i}$ 是物品向量,${p}_{u}$ 是用户向量,K是所有用户评分物品的全体集合。通过目标函数的定义我们可以看到,我们要求的物品向量和用户向量,是希望让物品向量和用户向量之积跟原始的评分之差的平方尽量小。简单来说就是,我们希望用户矩阵和物品矩阵的乘积尽量接近原来的共现矩阵。
|
||||
|
||||
在通过训练得到用户隐向量和物品隐向量之后,在服务器内部的推荐过程跟我们之前讲过的Embedding推荐是一样的,你也已经在Sparrow RecSys里面实践过,是这方面的专家了,我就不再多说了。
|
||||
|
||||
## 矩阵分解算法的Spark实现
|
||||
|
||||
基础知识学完,接下来又到了show me the code时间了。这里,我们继续使用Spark实现矩阵分解算法,我会结合下面的关键代码一起来讲。
|
||||
|
||||
我们可以看到,因为Spark MLlib已经帮我们封装好了模型,所以矩阵分解算法实现起来非常简单,还是通过我们熟悉的三步来完成,分别是定义模型,使用fit函数训练模型,提取物品和用户向量。
|
||||
|
||||
但是有一点我们需要注意,就是在模型中,我们需要在模型中指定训练样本中用户ID对应的列userIdInt和物品ID对应的列movieIdInt,并且两个ID列对应的数据类型需要是int类型的。
|
||||
|
||||
```
|
||||
// 建立矩阵分解模型
|
||||
val als = new ALS()
|
||||
.setMaxIter(5)
|
||||
.setRegParam(0.01)
|
||||
.setUserCol("userIdInt")
|
||||
.setItemCol("movieIdInt")
|
||||
.setRatingCol("ratingFloat")
|
||||
|
||||
|
||||
//训练模型
|
||||
val model = als.fit(training)
|
||||
|
||||
|
||||
//得到物品向量和用户向量
|
||||
model.itemFactors.show(10, truncate = false)
|
||||
model.userFactors.show(10, truncate = false
|
||||
|
||||
|
||||
```
|
||||
|
||||
其实,矩阵分解算法得出的结果,你完全可以把它当作Embedding来处理。具体怎么做呢?在讲Redis的时候,我们就已经实现过物品Embedding和用户Embedding的存储和线上预估的过程了,你可以直接参考它。最后,我建议你利用矩阵分解后的用户和物品隐向量,仿照其他Embedding的实现,在Sparrow RecSys中动手实现一下线上部署的过程,这样你就可以看到矩阵分解模型的实际效果了。
|
||||
|
||||
## 小结
|
||||
|
||||
这节课我们一起学习了协同过滤算法,以及它的后续算法矩阵分解,它是最经典的推荐算法。
|
||||
|
||||
总结来说,协同过滤是一种协同大家的反馈、评价和意见,对海量的信息进行过滤,从中筛选出用户感兴趣信息的一种推荐算法。它的实现过程主要有三步,先根据用户行为历史创建共现矩阵,然后根据共现矩阵查找相似用户,再根据相似用户喜欢的物品,推荐目标用户喜欢的物品。
|
||||
|
||||
但是协同过滤处理稀疏矩阵的能力比较差,因此,矩阵分解算法被提出了,它通过分解共现矩阵,生成用户向量矩阵和物品向量矩阵,进而得到用户隐向量和物品隐向量。你可以完全把最后的结果当作用户Embedding和物品Embedding来处理。
|
||||
|
||||
针对这节课的重要知识点,我把它们都列在了下面的表格里,你可以看看。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/5f/12/5f02442573af2202a85eb3e4bb895212.jpeg" alt="">
|
||||
|
||||
## 课后思考
|
||||
|
||||
1.基于协同过滤算法,你能找到进行相似“物品”推荐的方法吗?
|
||||
|
||||
2.在MovieLens数据集中,不同用户对物品打分的标准不尽相同。比如说,有的人可能爱打高分,评价的影片得分都在4分以上,有的人爱打低分,大部分影片都在3分以下。你觉得这样的偏好对于推荐结果有影响吗?我们能不能在算法中消除这种偏好呢?
|
||||
|
||||
关于矩阵分解算法的实现你学会了吗?欢迎把你的疑问和思考分享到留言区,也欢迎你能把这节课转发出去,我们下节课见!
|
||||
104
极客时间专栏/深度学习推荐系统实战/推荐模型篇/16 | 深度学习革命:深度学习推荐模型发展的整体脉络是怎样的?.md
Normal file
104
极客时间专栏/深度学习推荐系统实战/推荐模型篇/16 | 深度学习革命:深度学习推荐模型发展的整体脉络是怎样的?.md
Normal file
@@ -0,0 +1,104 @@
|
||||
<audio id="audio" title="16 | 深度学习革命:深度学习推荐模型发展的整体脉络是怎样的?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/6c/cd/6c78a245f1eebb50f2860c5d3f75fbcd.mp3"></audio>
|
||||
|
||||
你好,我是王喆。今天,我们要开始学习激动人心的深度推荐模型部分了。
|
||||
|
||||
当下,几乎所有互联网巨头的推荐业务中,都有对深度学习推荐模型的落地和应用。从早期微软的Deep Crossing、Google的Wide&Deep、阿里的MLR,到现在影响力非常大的模型DIN、DIEN,YouTube的深度推荐模型等等。因此,对于算法工程师来说,紧跟业界的脚步去了解和掌握深度学习推荐模型是非常必要的。
|
||||
|
||||
那你可能想问了,深度学习推荐模型这么多,发展这么快,也没有一个统一的模板,我们该学哪个,怎么学呢?我想说的是,算法工程师的工作是一个持续优化和迭代的过程,如果想要追求更好的推荐效果,我们的思路不应该只局限于某一个被成功应用的模型,而是应该把眼光放得更高、更宽,去思考这些成功的推荐模型在业界下一步的发展方向是什么?有没有哪些其他的模型结构的思路可以借鉴。这些都是你在这个岗位上取得持续成功的关键。
|
||||
|
||||
那怎么才能做到这一点呢?我认为,只有建立起一个比较全面的深度学习模型知识库,我们才能在工作中做出正确的技术选择,为模型的下一步改进方向找到思路。
|
||||
|
||||
因此,这节课,我想和你深入聊一聊业界影响力非常大的深度学习推荐模型,以及它们之间的发展关系,带你从整体上建立起深度学习推荐模型的发展脉络。这不仅是我们建立行业知识储备的必需,也为我们后面实现深度推荐模型打下了基础。
|
||||
|
||||
## 深度学习对推荐系统的影响详解
|
||||
|
||||
在第一节课中,我们曾说过,**深度学习给推荐系统带来了革命性的影响**,能够显著提升推荐系统的效果,原因主要有两点,一是深度学习极大地增强了推荐模型的拟合能力,二是深度学习模型可以利用模型结构模拟用户兴趣的变迁、用户注意力机制等不同的用户行为过程。接下来,我们就结合这两点,来说说深度学习模型到强在哪里。
|
||||
|
||||
### 1. 深度学习模型的强拟合能力
|
||||
|
||||
首先,我们来说说深度学习模型的强拟合能力。上一节课我们学习了经典的推荐算法,矩阵分解。在矩阵分解模型的结构(图1左)中,用户One-hot向量和物品One-hot向量分居两侧,它们会先通过隐向量层转换成用户和物品隐向量,再通过点积的方式交叉生成最终的打分预测。
|
||||
|
||||
但是,点积这种特征向量交叉的方式毕竟过于简单了,在数据模式比较复杂的情况下,往往存在欠拟合的情况。而深度学习就能大大加强模型的拟合能力,比如在NeuralCF(神经网络协同过滤)模型中,点积层被替换为多层神经网络,理论上多层神经网络具备拟合任意函数的能力,所以我们通过增加神经网络层的方式就能解决模型欠拟合的问题了。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/70/09/7063d223da013845534d3c84b7ab9409.jpg" alt="" title="图1 矩阵分解模型示意图
|
||||
">
|
||||
|
||||
如果你不知道什么是欠拟合、正确拟合和过拟合的现象,可能就无法理解神经网络到底解决了什么问题。这里我就带你看一张很经典的示意图,来详细聊聊这三种现象 。
|
||||
|
||||
**“欠拟合”指的是模型复杂度低,无法很好地拟合训练集数据的现象。** 就像图2(左)展示的那样,模型曲线没办法“准确”地找到正负样本的分界线,而深度学习模型就可以大大增加模型的“非线性”拟合能力,像图2(中)一样找到更加合适的分类面,更准确地完成分类任务。当然,过分复杂的深度学习模型存在着“过拟合”的风险,**“过拟合”是指模型在训练集上的误差很小,但在测试集上的误差较大的现象**,就像图2(右)一样,模型曲线过分精确地刻画分界线而忽略了对噪声的容忍能力,这对于未知样本的预估来说往往是不利的。
|
||||
|
||||
[<img src="https://static001.geekbang.org/resource/image/88/87/885010f805fb5290ec53f4fd6667e087.jpg" alt="" title="图2拟合能力示意图(图片来自Geeksforgeeks)">](https://www.geeksforgeeks.org/regularization-in-machine-learning/)
|
||||
|
||||
### 2. 深度学习模型结构的灵活性
|
||||
|
||||
说完了深度学习模型的强拟合能力,我们再来看看它的灵活性。这里,你可能会有疑问了,灵活性和深度学习模型模拟用户行为有什么关系呢?我们先接着往下看。
|
||||
|
||||
如果你读过一些深度学习相关的论文肯定会发现,每篇论文中的模型结构都不尽相同,就像图3展示的那样,它们有的是好多层,有的像一个串是串在一起的,而有的像一张网一样,拥有多个输入和多个输出,甚至还有的像金字塔,会从输入到输出逐层变窄。
|
||||
|
||||
虽然,模型结构的复杂性让我们难以掌握它们的规律,但也正是因为深度模型拥有这样的灵活性,让它能够更轻松地模拟人们的思考过程和行为过程,让推荐模型像一个无所不知的超级大脑一样,把用户猜得更透。
|
||||
|
||||
[<img src="https://static001.geekbang.org/resource/image/60/26/60d7f9aaa4562251c1dyy78712a2fe26.jpg" alt="" title="图3 不同深度学习模型的结构(图片来自ResearchGate)">](https://www.researchgate.net/figure/The-structures-of-different-deep-learning-models_fig2_340123883)
|
||||
|
||||
这其中典型的例子就是阿里巴巴的模型DIN(深度兴趣网络)和DIEN(深度兴趣进化网络)。它们通过在模型结构中引入注意力机制和模拟兴趣进化的序列模型,来更好地模拟用户的行为。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/20/32/202cfa968b1aa6fa4349722bb4ab4332.jpg" alt="" title="图4 DIN模型(左)和DIEN的模型(右)示意图">
|
||||
|
||||
我们重点关注图4的DIN模型,它在神经网络中增加了一个叫做“激活单元“的结构,这个单元就是为了模拟人类的注意力机制。举个例子来说,我们在购买电子产品,比如说笔记本电脑的时候,更容易拿之前购买电脑的经验,或者其他电子产品的经验来指导当前的购买行为,很少会借鉴购买衣服和鞋子的经验。这就是一个典型的注意力机制,我们只会注意到相关度更高的历史购买行为,而DIN模型就是模拟了人类的注意力特点。
|
||||
|
||||
DIN模型的改进版DIEN模型就更厉害了,它不仅引入了注意力机制,还模拟了用户兴趣随时间的演化过程。我们来看那些彩色的层,这一层层的序列结构模拟的正是用户兴趣变迁的历史,通过模拟变迁的历史,DIEN模型可以更好地预测下一步用户会喜欢什么。
|
||||
|
||||
这些通过改变模型结构来模拟用户行为的做法不胜枚举,很多重要的深度学习模型的改进动机也是基于这样的原理。也正是因为这样的灵活性,正确、全面地掌握不同深度学习模型的特点,以及它们之间的发展关系变得异常重要,只有这样,我们才能在实践中做到有的放矢、灵活应用。
|
||||
|
||||
## 深度学习推荐模型的演化关系图
|
||||
|
||||
说了这么多,我们到底该怎么掌握不同深度学习模型之间的关系呢?这里,我梳理出了一张深度学习模型5年内的发展过程图,图中的每一个节点都是一个重要的模型结构,节点之间的连线也揭示了不用模型间的联系。
|
||||
|
||||
接下来,我就带你梳理一下图中重要模型的原理,以及不同模型间的关系。而在之后的课程中,我们还会进一步学习重点模型的技术细节,并且基于TensorFlow对它们进行实现。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/10/c5/10e8105911823d96348dc7288d4d26c5.jpg" alt="" title="图5 主流深度学习推荐模型的演化图谱">
|
||||
|
||||
首先,我们来看整个演化图最中心部分,这是深度学习最基础的结构,我们叫它“多层神经网络”或者“多层感知机”,简称MLP(MultiLayer Perceptron)。多层感知机的原理我们在[第3讲](https://time.geekbang.org/column/article/291245)中讲过,它就像一个黑盒,会对输入的特征进行深度地组合交叉,然后输出对兴趣值的预测。其他的深度推荐模型全都是在多层感知机的基础上,进行结构上的改进而生成的,所以**“多层感知机”是整个演化图的核心**。
|
||||
|
||||
从多层感知机向上,还有一个重点模型我们需要知道,那就是Deep Crossing。Deep Crossing实际上是一类经典深度学习模型的代表,相比于MLP,Deep Crossing在原始特征和MLP之间加入了Embedding层。这样一来,输入的稀疏特征先转换成稠密Embedding向量,再参与到MLP中进行训练,这就解决了MLP不善于处理稀疏特征的问题。可以说,**Embedding+MLP的结构是最经典,也是应用最广的深度学习推荐模型结构**。
|
||||
|
||||
从MLP向下,我们看到了Google提出的推荐模型Wide&Deep。它把深层的MLP和单层的神经网络结合起来,希望同时让网络具备很好的“记忆性”和“泛化性”。对“记忆性”和“泛化性”这两个名词陌生的同学也不用着急,我们后面的课程会专门来讲解Wide&Deep。
|
||||
|
||||
Wide&Deep提出以来,凭借着“易实现”“易落地”“易改造”的特点,获得了业界的广泛应用。围绕着Wide&Deep还衍生出了诸多变种,比如,通过改造Wide部分提出的Deep&Cross和DeepFM,通过改造Deep部分提出的AFM、NFM等等。总之,**Wide&Deep是业界又一得到广泛应用的深度推荐模型**。
|
||||
|
||||
除此之外,我们还可以看到经典的深度学习模型跟其他机器学习子领域的交叉。这里,我给你举3个比较著名的例子:第1个是深度学习和注意力机制的结合,诞生了阿里的深度兴趣网络DIN,浙大和新加坡国立提出的AFM等等;第2个是把序列模型引入MLP+Embedding的经典结构,诞生了阿里的深度兴趣进化网络DIEN;第3个是把深度学习和强化学习结合在一起,诞生了微软的深度强化学习网络DRN,以及包括[美团](https://tech.meituan.com/2018/11/15/reinforcement-learning-in-mt-recommend-system.html)、[阿里](https://102.alibaba.com/downloadFile.do?file=1517812754285/reinforcement_learning.pdf)在内的非常有价值的业界应用。
|
||||
|
||||
看了这诸多模型的演进过程,你肯定想问模型的演化有什么规律可循吗?接下来,我就把我总结出的,关于模型改进的四个方向告诉你 。
|
||||
|
||||
**一是改变神经网络的复杂程度。** 从最简单的单层神经网络模型AutoRec,到经典的深度神经网络结构Deep Crossing,它们主要的进化方式在于增加了深度神经网络的层数和结构复杂度。
|
||||
|
||||
**二是改变特征交叉方式。** 这种演进方式的要点在于大大提高了深度学习网络中特征交叉的能力。比如说,改变了用户向量和物品向量互操作方式的NeuralCF,定义了多种特征向量交叉操作的PNN等等。
|
||||
|
||||
**三是把多种模型组合应用。** 组合模型主要指的就是以Wide&Deep模型为代表的一系列把不同结构组合在一起的改进思路。它通过组合两种甚至多种不同特点、优势互补的深度学习网络,来提升模型的综合能力。
|
||||
|
||||
**四是让深度推荐模型和其他领域进行交叉。** 我们从DIN、DIEN、DRN等模型中可以看出,深度推荐模型无时无刻不在从其他研究领域汲取新的知识。事实上,这个过程从未停歇,我们从今年的推荐系统顶会Recsys2020中可以看到,NLP领域的著名模型Bert又与推荐模型结合起来,并且产生了非常好的效果。一般来说,自然语言处理、图像处理、强化学习这些领域都是推荐系统经常汲取新知识的地方。
|
||||
|
||||
总的来说,深度学习推荐模型的发展快、思路广,但每种模型都不是无本之木,它们的发展脉络都有迹可循。想要掌握好这些模型,在实际工作中做到拿来就用,我们就需要让这些模型脉络图像知识树一样扎根在心中,再通过不断地实践来掌握技术细节。
|
||||
|
||||
## 小结
|
||||
|
||||
这节课,我们通过学习深度学习对推荐系统的影响要素,以及经典深度学习模型之间的关系,初步建立起了深度学习模型的知识库。
|
||||
|
||||
我们知道,深度学习能够提升推荐系统的效果有两个关键因素,分别是它的“强拟合能力”和“结构的灵活性”。
|
||||
|
||||
对于“强拟合能力”来说,深度学习模型可以大大增加模型的“非线性”拟合能力,对复杂数据模型进行更准确的分类,避免“欠拟合”现象的发生,从而提升推荐效果。
|
||||
|
||||
对于“结构的灵活性”来说,深度学习模型可以通过灵活调整自身的结构,更轻松恰当地模拟人们的思考过程和行为过程,把用户猜得更透。
|
||||
|
||||
而整个深度学习推荐模型的演化过程,是从最经典的多层神经网络向不同方向开枝散叶,比如结合协同过滤发展出了NerualCF,加入Embedding层发展出以Deep Crossing为代表的Embedding+MLP的结构,以及把深度神经网络和单层网络结合起来发展出Wide&Deep模型等等。
|
||||
|
||||
在这节课,我们可以先忽略每个模型的细节,着重建立一个整体的知识框架。之后的课程中,我不仅会带你一一揭晓它们的技术细节,还会利用TensorFlow实现其中几个经典的模型。期待继续与你一起学习!
|
||||
|
||||
最后,我还是把这节课的重点知识梳理成了表格的形式,你可以借助它来复习巩固。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/be/ce/be5a662d3abd6a8dd5411038718d6cce.jpeg" alt="">
|
||||
|
||||
## 课后思考
|
||||
|
||||
有的同学说,深度学习这么流行,我把一些经典的深度模型结构实现好,肯定能提升我们公司推荐系统的效果,你觉得这种观点有问题吗?你觉得除了模型结构,还有哪些影响推荐效果的因素?为什么?
|
||||
|
||||
好啦,关于深度学习模型的知识库你建立起来了吗?欢迎把你的疑问和思考分享到留言区,也欢迎你能把这节课转发出去,我们下节课见!
|
||||
@@ -0,0 +1,253 @@
|
||||
<audio id="audio" title="17 | Embedding+MLP:如何用TensorFlow实现经典的深度学习模型?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/8f/db/8f34e48405693c86345902528f4d41db.mp3"></audio>
|
||||
|
||||
你好,我是王喆。
|
||||
|
||||
今天我们正式进入深度学习模型的实践环节,来一起学习并实现一种最经典的模型结构,Embedding+MLP。它不仅经典,还是我们后续实现其他深度学习模型的基础,所以你一定要掌握好。
|
||||
|
||||
这里面的Embedding我们已经很熟悉了,那什么叫做MLP呢?它其实是Multilayer perceptron,多层感知机的缩写。感知机是神经元的另外一种叫法,所以多层感知机就是多层神经网络。
|
||||
|
||||
讲到这里啊,我想你脑海中已经有这个模型结构的大致图像了。今天,我就以微软著名的深度学习模型Deep Crossing为例,来给你详细讲一讲Embedding+MLP模型的结构和实现方法。
|
||||
|
||||
## Embedding+MLP模型的结构
|
||||
|
||||
图1 展示的就是微软在2016年提出的深度学习模型Deep Crossing,微软把它用于广告推荐这个业务场景上。它是一个经典的Embedding+MLP模型结构,我们可以看到,Deep Crossing从下到上可以分为5层,分别是Feature层、Embedding层、Stacking层、MLP层和Scoring层。
|
||||
|
||||
接下来,我就从下到上来给你讲讲每一层的功能是什么,以及它们的技术细节分别是什么样的。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/50/71/5076071d3c69d3a9fff848a9e631f371.jpeg" alt="" title="图1 经典的Embedding+MLP模型结构(图片来自 Deep Crossing - Web-Scale Modeling without Manually Crafted Combinatorial Features)">
|
||||
|
||||
我们先来看Feature层。Feature层也叫做输入特征层,它处于Deep Crossing的最底部,作为整个模型的输入。仔细看图1的话,你一定会发现不同特征在细节上的一些区别。比如Feature#1向上连接到了Embedding层,而Feature#2就直接连接到了更上方的Stacking层。这是怎么回事呢?
|
||||
|
||||
原因就在于Feature#1代表的是类别型特征经过One-hot编码后生成的特征向量,而Feature#2代表的是数值型特征。我们知道,One-hot特征太稀疏了,不适合直接输入到后续的神经网络中进行训练,所以我们需要通过连接到Embedding层的方式,把这个稀疏的One-hot向量转换成比较稠密的Embedding向量。
|
||||
|
||||
接着,我们来看Embedding层。Embedding层就是为了把稀疏的One-hot向量转换成稠密的Embedding向量而设置的,我们需要注意的是,Embedding层并不是全部连接起来的,而是每一个特征对应一个Embedding层,不同Embedding层之间互不干涉。
|
||||
|
||||
那Embedding层的内部结构到底是什么样子的呢?我想先问问你,你还记得Word2vec的原理吗?Embeding层的结构就是Word2vec模型中从输入神经元到隐层神经元的部分(如图2红框内的部分)。参照下面的示意图,我们可以看到,这部分就是一个从输入层到隐层之间的全连接网络。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/8a/29/8a26d9a531ae8bef89f3730388f59a29.jpeg" alt="" title="图2 Word2vec模型中Embedding层的部分">
|
||||
|
||||
一般来说,Embedding向量的维度应远小于原始的稀疏特征向量,按照经验,几十到上百维就能够满足需求,这样它才能够实现从稀疏特征向量到稠密特征向量的转换。
|
||||
|
||||
接着我们来看Stacking层。Stacking层中文名是堆叠层,我们也经常叫它连接(Concatenate)层。它的作用比较简单,就是把不同的Embedding特征和数值型特征拼接在一起,形成新的包含全部特征的特征向量。
|
||||
|
||||
再往上看,MLP层就是我们开头提到的多层神经网络层,在图1中指的是Multiple Residual Units层,中文叫多层残差网络。微软在实现Deep Crossing时针对特定的问题选择了残差神经元,但事实上,神经元的选择有非常多种,比如我们之前在深度学习基础知识中介绍的,以Sigmoid函数为激活函数的神经元,以及使用tanh、ReLU等其他激活函数的神经元。我们具体选择哪种是一个调参的问题,一般来说,ReLU最经常使用在隐层神经元上,Sigmoid则多使用在输出神经元,实践中也可以选择性地尝试其他神经元,根据效果作出最后的决定。
|
||||
|
||||
不过,不管选择哪种神经元,MLP层的特点是全连接,就是不同层的神经元两两之间都有连接。就像图3中的两层神经网络一样,它们两两连接,只是连接的权重会在梯度反向传播的学习过程中发生改变。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/7a/99/7a2b22c106c454af3db2edaed555e299.jpeg" alt="" title="图3 全连接神经网络">
|
||||
|
||||
MLP层的作用是让特征向量不同维度之间做充分的交叉,让模型能够抓取到更多的非线性特征和组合特征的信息,这就使深度学习模型在表达能力上较传统机器学习模型大为增强。
|
||||
|
||||
最后是Scoring层,它也被称为输出层。虽然深度学习模型的结构可以非常复杂,但最终我们要预测的目标就是一个分类的概率。如果是点击率预估,就是一个二分类问题,那我们就可以采用逻辑回归作为输出层神经元,而如果是类似图像分类这样的多分类问题,我们往往在输出层采用softmax这样的多分类模型。
|
||||
|
||||
到这里,我们就讲完了Embedding+MLP的五层结构。它的结构重点用一句话总结就是,**对于类别特征,先利用Embedding层进行特征稠密化,再利用Stacking层连接其他特征,输入MLP的多层结构,最后用Scoring层预估结果。**
|
||||
|
||||
## Embedding+MLP模型的实战
|
||||
|
||||
现在,我们从整体上了解了Embedding+MLP模型的结构,也许你对其中的细节实现还有些疑问。别着急,下面我就带你用SparrowRecsys来实现一个Embedding+MLP的推荐模型,帮你扫清这些疑问。
|
||||
|
||||
实战中,我们按照构建推荐模型经典的步骤,特征选择、模型设计、模型实现、模型训练和模型评估这5步来进行实现。
|
||||
|
||||
首先,我们来看看特征选择和模型设计。
|
||||
|
||||
### 特征选择和模型设计
|
||||
|
||||
在上一节的实践准备课程中,我们已经为模型训练准备好了可用的训练样本和特征。秉着“类别型特征Embedding化,数值型特征直接输入MLP”的原则,我们选择movieId、userId、movieGenre、userGenre作为Embedding化的特征,选择物品和用户的统计型特征作为直接输入MLP的数值型特征,具体的特征选择你可以看看下面的表格:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/af/94/af3fabdcb119c9e06ddc0f7225bbc094.jpg" alt="">
|
||||
|
||||
选择好特征后,就是MLP部分的模型设计了。我们选择了一个三层的MLP结构,其中前两层是128维的全连接层。我们这里采用好评/差评标签作为样本标签,因此要解决的是一个类CTR预估的二分类问题,对于二分类问题,我们最后一层采用单个sigmoid神经元作为输出层就可以了。
|
||||
|
||||
当然了,我知道你肯定对这一步的细节实现有很多问题,比如为什么要选三层的MLP结构,为什么要选sigmoid作为激活函数等等。其实,我们对模型层数和每个层内维度的选择是一个超参数调优的问题,这里的选择不能保证最优,我们需要在实战中需要根据模型的效果进行超参数的搜索,找到最适合的模型参数。
|
||||
|
||||
### Embedding+MLP模型的TensorFlow实现
|
||||
|
||||
确定好了特征和模型结构,就万事俱备,只欠实现了。下面,我们就看一看利用TensorFlow的Keras接口如何实现Embedding+MLP的结构。总的来说,TensorFlow的实现有七个步骤。因为这是我们课程中第一个TensorFlow的实现,所以我会讲得详细一些。而且,我也把全部的参考代码放在了Sparrow Recsys项目TFRecModel模块的EmbeddingMLP.py,你可以结合它来听我下面的讲解。
|
||||
|
||||
**我们先来看第一步,导入TensorFlow包。** 如果你按照实战准备一的步骤配置好了TensorFlow Python环境,就可以成功地导入TensorFlow包。接下来,你要做的就是定义好训练数据的路径TRAIN_DATA_URL了,然后根据你自己训练数据的本地路径,替换参考代码中的路径就可以了。
|
||||
|
||||
```
|
||||
import tensorflow as tf
|
||||
|
||||
|
||||
TRAIN_DATA_URL = "file:///Users/zhewang/Workspace/SparrowRecSys/src/main/resources/webroot/sampledata/modelSamples.csv"
|
||||
samples_file_path = tf.keras.utils.get_file("modelSamples.csv", TRAIN_DATA_URL)
|
||||
|
||||
```
|
||||
|
||||
**第二步是载入训练数据**,我们利用TensorFlow自带的CSV数据集的接口载入训练数据。注意这里有两个比较重要的参数,一个是label_name,它指定了CSV数据集中的标签列。另一个是batch_size,它指定了训练过程中,一次输入几条训练数据进行梯度下降训练。载入训练数据之后,我们把它们分割成了测试集和训练集。
|
||||
|
||||
```
|
||||
def get_dataset(file_path):
|
||||
dataset = tf.data.experimental.make_csv_dataset(
|
||||
file_path,
|
||||
batch_size=12,
|
||||
label_name='label',
|
||||
na_value="?",
|
||||
num_epochs=1,
|
||||
ignore_errors=True)
|
||||
return dataset
|
||||
|
||||
|
||||
|
||||
# sample dataset size 110830/12(batch_size) = 9235
|
||||
raw_samples_data = get_dataset(samples_file_path)
|
||||
|
||||
|
||||
test_dataset = raw_samples_data.take(1000)
|
||||
train_dataset = raw_samples_data.skip(1000)
|
||||
|
||||
|
||||
```
|
||||
|
||||
**第三步是载入类别型特征。** 我们用到的类别型特征主要有这三类,分别是genre、userId和movieId。在载入genre类特征时,我们采用了 `tf.feature_column.categorical_column_with_vocabulary_list` 方法把字符串型的特征转换成了One-hot特征。在这个转换过程中我们需要用到一个词表,你可以看到我在开头就定义好了包含所有genre类别的词表genre_vocab。
|
||||
|
||||
在转换userId和movieId特征时,我们又使用了 `tf.feature_column.categorical_column_with_identity` 方法把ID转换成One-hot特征,这个方法不用词表,它会直接把ID值对应的那个维度置为1。比如,我们输入这个方法的movieId是340,总的movie数量是1001,使用这个方法,就会把这个1001维的One-hot movieId向量的第340维置为1,剩余的维度都为0。
|
||||
|
||||
为了把稀疏的One-hot特征转换成稠密的Embedding向量,我们还需要在One-hot特征外包裹一层Embedding层,你可以看到 `tf.feature_column.embedding_column(movie_col, 10)` 方法完成了这样的操作,它在把movie one-hot向量映射到了一个10维的Embedding层上。
|
||||
|
||||
```
|
||||
genre_vocab = ['Film-Noir', 'Action', 'Adventure', 'Horror', 'Romance', 'War', 'Comedy', 'Western', 'Documentary',
|
||||
'Sci-Fi', 'Drama', 'Thriller',
|
||||
'Crime', 'Fantasy', 'Animation', 'IMAX', 'Mystery', 'Children', 'Musical']
|
||||
|
||||
|
||||
GENRE_FEATURES = {
|
||||
'userGenre1': genre_vocab,
|
||||
'userGenre2': genre_vocab,
|
||||
'userGenre3': genre_vocab,
|
||||
'userGenre4': genre_vocab,
|
||||
'userGenre5': genre_vocab,
|
||||
'movieGenre1': genre_vocab,
|
||||
'movieGenre2': genre_vocab,
|
||||
'movieGenre3': genre_vocab
|
||||
}
|
||||
|
||||
|
||||
categorical_columns = []
|
||||
for feature, vocab in GENRE_FEATURES.items():
|
||||
cat_col = tf.feature_column.categorical_column_with_vocabulary_list(
|
||||
key=feature, vocabulary_list=vocab)
|
||||
emb_col = tf.feature_column.embedding_column(cat_col, 10)
|
||||
categorical_columns.append(emb_col)
|
||||
|
||||
|
||||
movie_col = tf.feature_column.categorical_column_with_identity(key='movieId', num_buckets=1001)
|
||||
movie_emb_col = tf.feature_column.embedding_column(movie_col, 10)
|
||||
categorical_columns.append(movie_emb_col)
|
||||
|
||||
|
||||
user_col = tf.feature_column.categorical_column_with_identity(key='userId', num_buckets=30001)
|
||||
user_emb_col = tf.feature_column.embedding_column(user_col, 10)
|
||||
categorical_columns.append(user_emb_c
|
||||
|
||||
```
|
||||
|
||||
**第四步是数值型特征的处理。** 这一步非常简单,我们直接把特征值输入到MLP内,然后把特征逐个声明为 `tf.feature_column.numeric_column` 就可以了,不需要经过其他的特殊处理。
|
||||
|
||||
```
|
||||
numerical_columns = [tf.feature_column.numeric_column('releaseYear'),
|
||||
tf.feature_column.numeric_column('movieRatingCount'),
|
||||
tf.feature_column.numeric_column('movieAvgRating'),
|
||||
tf.feature_column.numeric_column('movieRatingStddev'),
|
||||
tf.feature_column.numeric_column('userRatingCount'),
|
||||
tf.feature_column.numeric_column('userAvgRating'),
|
||||
tf.feature_column.numeric_column('userRatingStddev')]
|
||||
|
||||
|
||||
```
|
||||
|
||||
**第五步是定义模型结构。** 这一步的实现代码也非常简洁,我们直接利用DenseFeatures把类别型Embedding特征和数值型特征连接在一起形成稠密特征向量,然后依次经过两层128维的全连接层,最后通过sigmoid输出神经元产生最终预估值。
|
||||
|
||||
```
|
||||
preprocessing_layer = tf.keras.layers.DenseFeatures(numerical_columns + categorical_columns)
|
||||
|
||||
|
||||
model = tf.keras.Sequential([
|
||||
preprocessing_layer,
|
||||
tf.keras.layers.Dense(128, activation='relu'),
|
||||
tf.keras.layers.Dense(128, activation='relu'),
|
||||
tf.keras.layers.Dense(1, activation='sigmoid'),
|
||||
])
|
||||
|
||||
|
||||
```
|
||||
|
||||
**第六步是定义模型训练相关的参数。** 在这一步中,我们需要设置模型的损失函数,梯度反向传播的优化方法,以及模型评估所用的指标。关于损失函数,我们使用的是二分类问题最常用的二分类交叉熵,优化方法使用的是深度学习中很流行的adam,最后是评估指标,使用了准确度accuracy作为模型评估的指标。
|
||||
|
||||
```
|
||||
model.compile(
|
||||
loss='binary_crossentropy',
|
||||
optimizer='adam',
|
||||
metrics=['accuracy'])
|
||||
|
||||
|
||||
```
|
||||
|
||||
**第七步是模型的训练和评估。** TensorFlow模型的训练过程和Spark MLlib一样,都是调用fit函数,然后使用evaluate函数在测试集上进行评估。不过,这里我们要注意一个参数epochs,它代表了模型训练的轮数,一轮代表着使用所有训练数据训练一遍,epochs=10代表着训练10遍。
|
||||
|
||||
```
|
||||
model.fit(train_dataset, epochs=10)
|
||||
|
||||
|
||||
test_loss, test_accuracy = model.evaluate(test_dataset)
|
||||
|
||||
|
||||
print('\n\nTest Loss {}, Test Accuracy {}'.format(test_loss, test_accuracy)
|
||||
|
||||
|
||||
```
|
||||
|
||||
如果一切顺利的话,你就可以看到模型的训练过程和最终的评估结果了。从下面的训练输出中你可以看到,每轮训练模型损失(Loss)的变化过程和模型评估指标Accruacy的变化过程。你肯定会发现,随着每轮训练的Loss减小,Accruacy会变高。换句话说,每轮训练都会让模型结果更好,这是我们期望看到的。需要注意的是,理论上来说,我们应该在模型accuracy不再变高时停止训练,据此来确定最佳的epochs取值。但如果模型收敛的时间确实过长,我们也可以设置一个epochs最大值,让模型提前终止训练。
|
||||
|
||||
```
|
||||
Epoch 1/10
|
||||
8236/8236 [==============================] - 20s 2ms/step - loss: 2.7379 - accuracy: 0.5815
|
||||
Epoch 2/10
|
||||
8236/8236 [==============================] - 21s 3ms/step - loss: 0.6397 - accuracy: 0.6659
|
||||
Epoch 3/10
|
||||
8236/8236 [==============================] - 21s 3ms/step - loss: 0.5550 - accuracy: 0.7179
|
||||
Epoch 4/10
|
||||
8236/8236 [==============================] - 21s 2ms/step - loss: 0.5209 - accuracy: 0.7431
|
||||
Epoch 5/10
|
||||
8236/8236 [==============================] - 21s 2ms/step - loss: 0.5010 - accuracy: 0.7564
|
||||
Epoch 6/10
|
||||
8236/8236 [==============================] - 20s 2ms/step - loss: 0.4866 - accuracy: 0.7641
|
||||
Epoch 7/10
|
||||
8236/8236 [==============================] - 20s 2ms/step - loss: 0.4770 - accuracy: 0.7702
|
||||
Epoch 8/10
|
||||
8236/8236 [==============================] - 21s 2ms/step - loss: 0.4688 - accuracy: 0.7745
|
||||
Epoch 9/10
|
||||
8236/8236 [==============================] - 20s 2ms/step - loss: 0.4633 - accuracy: 0.7779
|
||||
Epoch 10/10
|
||||
8236/8236 [==============================] - 20s 2ms/step - loss: 0.4580 - accuracy: 0.7800
|
||||
1000/1000 [==============================] - 1s 1ms/step - loss: 0.5037 - accuracy: 0.7473
|
||||
|
||||
|
||||
|
||||
|
||||
Test Loss 0.5036991238594055, Test Accuracy 0.747250020503997
|
||||
|
||||
```
|
||||
|
||||
最终的模型评估需要在测试集上进行,从上面的输出中我们可以看到,最终的模型在测试集上的准确度是0.7472,它意味着我们的模型对74.72%的测试样本作出了正确的预测。当然了,模型的评估指标还是很多,我们会在之后的模型评估篇中进行详细的讲解。
|
||||
|
||||
## 小结
|
||||
|
||||
这节课是我们深度学习模型实践的第一课,我们要掌握两个重点内容,一是Embedding+MLP的模型结构,二是Embedding+MLP模型的TensorFlow实现。
|
||||
|
||||
Embedding+MLP主要是由Embedding部分和MLP部分这两部分组成,使用Embedding层是为了将类别型特征转换成Embedding向量,MLP部分是通过多层神经网络拟合优化目标。具体来说,以微软的Deep Crossing为例,模型一共分为5层,从下到上分别是Feature层、Embedding层、Stacking层、MLP层和Scoring层。
|
||||
|
||||
在TensorFlow实践部分,我们利用上节课处理好的特征和训练数据,实现了Sparrow Recsys项目中的第一个深度学习模型。在实践过程中,我们要重点掌握类别型特征的处理方法,模型的定义方式和训练方式,以及最后的模型评估方法。
|
||||
|
||||
我也把这些重点知识总结在了一张表格里,你可以利用它来认真回顾。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/4e/67/4e34e77589d386c8924542794dyy1867.jpg" alt="">
|
||||
|
||||
今天,我们一起完成了Embedding MLP模型的实现。在之后的课程中,我们会进一步实现其他深度学习模型,通过模型的评估进行效果上的对比。另外,我们也会利用训练出的深度学习模型完成Sparrow Recsys的猜你喜欢功能,期待与你一起不断完善我们的项目。
|
||||
|
||||
## 课后思考
|
||||
|
||||
在我们实现的Embedding+MLP模型中,也有用户Embedding层和物品Embedding层。你觉得从这两个Embedding层中,抽取出来的用户和物品Embedding,能直接用来计算用户和物品之间的相似度吗?为什么?
|
||||
|
||||
欢迎把你的思考和疑惑写在留言区,也欢迎你把这节课转发给希望用TensorFlow实现深度推荐模型的朋友,我们下节课见!
|
||||
117
极客时间专栏/深度学习推荐系统实战/推荐模型篇/18|Wide&Deep:怎样让你的模型既有想象力又有记忆力?.md
Normal file
117
极客时间专栏/深度学习推荐系统实战/推荐模型篇/18|Wide&Deep:怎样让你的模型既有想象力又有记忆力?.md
Normal file
@@ -0,0 +1,117 @@
|
||||
<audio id="audio" title="18|Wide&Deep:怎样让你的模型既有想象力又有记忆力?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/39/92/392da33b177987db0364c30d3efbc292.mp3"></audio>
|
||||
|
||||
你好,我是王喆。
|
||||
|
||||
今天,我们来学习一个在业界有着巨大影响力的推荐模型,Google的Wide&Deep。可以说,只要掌握了Wide&Deep,我们就抓住了深度推荐模型这几年发展的一个主要方向。那Wide&Deep模型是什么意思呢?我们把它翻译成中文就是“宽并且深的模型”。
|
||||
|
||||
这个名字看起来好像很通俗易懂,但你真的理解其中“宽”和“深”的含义吗?上一节课我们讲过Embedding+MLP的经典结构,因为MLP可以有多层神经网络的结构,所以它是一个比较“深”的模型,但Wide&Deep这个模型说的“深”和MLP是同样的意思吗?“宽”的部分又是什么样的呢?宽和深分别有什么不同的作用呢?以及我们为什么要把它们结合在一起形成一个模型呢?
|
||||
|
||||
这节课,就让我们就带着这诸多疑问,从模型的结构开始学起,再深入到Wide&Deep在Google的应用细节中去,最后亲自动手实现这个模型。
|
||||
|
||||
## Wide&Deep模型的结构
|
||||
|
||||
首先,我们来看看Wide&Deep模型的结构,理解了结构再深入去学习细节,我们才能掌握得更好。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/fb/e0/fb17112c951ebb2a515f12dace262de0.jpg" alt="" title="图1 Wide&Deep模型结构 [br](出自Wide & Deep Learning for Recommender Systems )">
|
||||
|
||||
上图就是Wide&Deep模型的结构图了,它是由左侧的Wide部分和右侧的Deep部分组成的。Wide部分的结构太简单了,就是把输入层直接连接到输出层,中间没有做任何处理。Deep层的结构稍复杂,但我相信你也不会陌生,因为它就是我们上节课学习的Embedding+MLP的模型结构。
|
||||
|
||||
知道了Wide&Deep模型的结构之后,我们先来解决第一个问题,那就是Google为什么要创造这样一个混合式的模型结构呢?这我们还得从Wide部分和Deep部分的不同作用说起。
|
||||
|
||||
简单来说,Wide部分的主要作用是让模型具有较强的“记忆能力”(Memorization),而Deep部分的主要作用是让模型具有“泛化能力”(Generalization),因为只有这样的结构特点,才能让模型兼具逻辑回归和深度神经网络的优点,也就是既能快速处理和记忆大量历史行为特征,又具有强大的表达能力,这就是Google提出这个模型的动机。
|
||||
|
||||
那么问题又来了,所谓的“记忆能力”和“泛化能力”到底指什么呢?这我们就得好好聊一聊了,因为理解这两种能力是彻底理解Wide&Deep模型的关键。
|
||||
|
||||
### 模型的记忆能力
|
||||
|
||||
**所谓的 “记忆能力”,可以被宽泛地理解为模型直接学习历史数据中物品或者特征的“共现频率”,并且把它们直接作为推荐依据的能力 。** 就像我们在电影推荐中可以发现一系列的规则,比如,看了A电影的用户经常喜欢看电影B,这种“因为A所以B”式的规则,非常直接也非常有价值。
|
||||
|
||||
但这类规则有两个特点:一是数量非常多,一个“记性不好”的推荐模型很难把它们都记住;二是没办法推而广之,因为这类规则非常具体,没办法或者说也没必要跟其他特征做进一步的组合。就像看了电影A的用户80%都喜欢看电影B,这个特征已经非常强了,我们就没必要把它跟其他特征再组合在一起。
|
||||
|
||||
现在,我们就可以回答开头的问题了,为什么模型要有Wide部分?就是因为Wide部分可以增强模型的记忆能力,让模型记住大量的直接且重要的规则,这正是单层的线性模型所擅长的。
|
||||
|
||||
### 模型的泛化能力
|
||||
|
||||
接下来,我们来谈谈模型的“泛化能力”。**“泛化能力”指的是模型对于新鲜样本、以及从未出现过的特征组合的预测能力。** 这怎么理解呢?我们还是来看一个例子。假设,我们知道25岁的男性用户喜欢看电影A,35岁的女性用户也喜欢看电影A。如果我们想让一个只有记忆能力的模型回答,“35岁的男性喜不喜欢看电影A”这样的问题,这个模型就会“说”,我从来没学过这样的知识啊,没法回答你。
|
||||
|
||||
这就体现出泛化能力的重要性了。模型有了很强的泛化能力之后,才能够对一些非常稀疏的,甚至从未出现过的情况作出尽量“靠谱”的预测。
|
||||
|
||||
回到刚才的例子,有泛化能力的模型回答“35岁的男性喜不喜欢看电影A”这个问题,它思考的逻辑可能是这样的:从第一条知识,“25岁的男性用户喜欢看电影A“中,我们可以学到男性用户是喜欢看电影A的。从第二条知识,“35岁的女性用户也喜欢看电影A”中,我们可以学到35岁的用户是喜欢看电影A的。那在没有其他知识的前提下,35岁的男性同时包含了合适的年龄和性别这两个特征,所以他大概率也是喜欢电影A的。这就是模型的泛化能力。
|
||||
|
||||
事实上,我们学过的矩阵分解算法,就是为了解决协同过滤“泛化能力”不强而诞生的。因为协同过滤只会“死板”地使用用户的原始行为特征,而矩阵分解因为生成了用户和物品的隐向量,所以就可以计算任意两个用户和物品之间的相似度了。这就是泛化能力强的另一个例子。
|
||||
|
||||
从上节课中我们学过深度学习模型有很强的数据拟合能力,在多层神经网络之中,特征可以得到充分的交叉,让模型学习到新的知识。因此,Wide&Deep模型的Deep部分,就沿用了上节课介绍的Embedding+MLP的模型结构,来增强模型的泛化能力。
|
||||
|
||||
好,清楚了记忆能力和泛化能力是什么之后,让我们再回到Wide&Deep模型提出时的业务场景上,去理解Wide&Deep模型是怎么综合性地学习到记忆能力和泛化能力的。
|
||||
|
||||
## Wide&Deep模型的应用场景
|
||||
|
||||
Wide&Deep模型是由Google的应用商店团队Google Play提出的,在Google Play为用户推荐APP这样的应用场景下,Wide&Deep模型的推荐目标就显而易见了,就是应该尽量推荐那些用户可能喜欢,愿意安装的应用。那具体到Wide&Deep模型中,Google Play团队是如何为Wide部分和Deep部分挑选特征的呢?下面,我们就一起来看看。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/4b/be/4b2f89d82768ba851e3f7392b17df2be.jpg" alt="" title="图2 Google Play Wide&Deep模型的细节 [br](出自Wide & Deep Learning for Recommender Systems )">
|
||||
|
||||
我们先来看看图2,它补充了Google Play Wide&Deep模型的细节,让我们可以清楚地看到各部分用到的特征是什么。我们先从右边Wide部分的特征看起。这部分很简单,只利用了两个特征的交叉,这两个特征是“已安装应用”和“当前曝光应用”。这样一来,Wide部分想学到的知识就非常直观啦,就是希望记忆好“如果A所以B”这样的简单规则。在Google Play的场景下,就是希望记住“如果用户已经安装了应用A,是否会安装B”这样的规则。
|
||||
|
||||
接着,我们再来看看左边的Deep部分,它就是一个非常典型的Embedding+MLP结构了。我们看到其中的输入特征很多,有用户年龄、属性特征、设备类型,还有已安装应用的Embedding等等。我们把这些特征一股脑地放进多层神经网络里面去学习之后,它们互相之间会发生多重的交叉组合,这最终会让模型具备很强的泛化能力。
|
||||
|
||||
比如说,我们把用户年龄、人口属性和已安装应用组合起来。假设,样本中有25岁男性安装抖音的记录,也有35岁女性安装抖音的记录,那我们该怎么预测25岁女性安装抖音的概率呢?这就需要用到已有特征的交叉来实现了。虽然我们没有25岁女性安装抖音的样本,但模型也能通过对已有知识的泛化,经过多层神经网络的学习,来推测出这个概率。
|
||||
|
||||
总的来说,Wide&Deep通过组合Wide部分的线性模型和Deep部分的深度网络,取各自所长,就能得到一个综合能力更强的组合模型。
|
||||
|
||||
## Wide&Deep模型的TensorFlow实现
|
||||
|
||||
在理解了Wide&Deep模型的原理和技术细节之后,就又到了“show me the code”的环节了。接下来,我们就动手在Sparrow Recsys上实现Wide&Deep模型吧!
|
||||
|
||||
通过上节课的实战,我相信你已经熟悉了TensorFlow的大部分操作,也清楚了载入训练数据,创建TensorFlow所需的Feature column的方法。因此,Wide&Deep模型的实践过程中,我们会重点关注定义模型的部分。
|
||||
|
||||
这里,我们也会像上节课一样,继续使用TensorFlow的Keras接口来构建Wide&Deep模型。具体的代码如下:
|
||||
|
||||
```
|
||||
# wide and deep model architecture
|
||||
# deep part for all input features
|
||||
deep = tf.keras.layers.DenseFeatures(numerical_columns + categorical_columns)(inputs)
|
||||
deep = tf.keras.layers.Dense(128, activation='relu')(deep)
|
||||
deep = tf.keras.layers.Dense(128, activation='relu')(deep)
|
||||
# wide part for cross feature
|
||||
wide = tf.keras.layers.DenseFeatures(crossed_feature)(inputs)
|
||||
both = tf.keras.layers.concatenate([deep, wide])
|
||||
output_layer = tf.keras.layers.Dense(1, activation='sigmoid')(both)
|
||||
model = tf.keras.Model(inputs, output_layer)
|
||||
|
||||
```
|
||||
|
||||
从代码中我们可以看到,在创建模型的时候,我们依次配置了模型的Deep部分和Wide部分。我们先来看Deep部分,它是输入层加两层128维隐层的结构,它的输入是类别型Embedding向量和数值型特征,实际上这跟上节课Embedding+MLP模型所用的特征是一样的。
|
||||
|
||||
Wide部分其实不需要有什么特殊操作,我们直接把输入特征连接到了输出层就可以了。但是,这里我们要重点关注一下Wide部分所用的特征crossed_feature。
|
||||
|
||||
```
|
||||
movie_feature = tf.feature_column.categorical_column_with_identity(key='movieId', num_buckets=1001)
|
||||
rated_movie_feature = tf.feature_column.categorical_column_with_identity(key='userRatedMovie1', num_buckets=1001)
|
||||
crossed_feature = tf.feature_column.crossed_column([movie_feature, rated_movie_feature], 10000)
|
||||
|
||||
|
||||
```
|
||||
|
||||
在生成crossed_feature的过程中,我其实仿照了Google Play的应用方式,生成了一个由“用户已好评电影”和“当前评价电影”组成的一个交叉特征,就是代码中的crossed_feature,设置这个特征的目的在于让模型记住好评电影之间的相关规则,更具体点来说就是,就是让模型记住“一个喜欢电影A的用户,也会喜欢电影B”这样的规则。
|
||||
|
||||
当然,这样的规则不是唯一的,需要你根据自己的业务特点来设计, 比如在电商网站中,这样的规则可以是,购买了键盘的用户也会购买鼠标。在新闻网站中,可以是打开过足球新闻的用户,也会点击NBA新闻等等。
|
||||
|
||||
在Deep部分和Wide部分都构建完后,我们要使用 `concatenate layer` 把两部分连接起来,形成一个完整的特征向量,输入到最终的sigmoid神经元中,产生推荐分数。
|
||||
|
||||
总的来说,在我们上一节的Embedding MLP模型基础上实现Wide&Deep是非常方便的,Deep部分基本没有变化,我们只需要加上Wide部分的特征和设置就可以了。Wide&Deep的全部相关代码,我都实现在了Sparrow Recsys的WideNDeep.py文件中,你可以直接参考源代码。但我更希望,你能尝试设置不同的特征,以及不同的参数组合,来真实地体验一下深度学习模型的调参过程。
|
||||
|
||||
## 小结
|
||||
|
||||
这节课,我们一起实现了业界影响力非常大的深度学习模型Wide&Deep,它是由Wide部分和Deep部分组成的。其中,Wide部分主要是为了增强模型的“记忆能力”,让模型记住“如果A,那么B”这样的简单但数量非常多的规则。Deep部分是为了增强模型的“泛化能力”,让模型具备对于稀缺样本、以及从未出现过的特征组合的预测能力。Wide&Deep正是通过这样取长补短的方式,让模型的综合能力提升。
|
||||
|
||||
在具体实践的时候,我们继续使用TensorFlow的Keras接口实现了Wide&Deep模型。相比上节课Embedding MLP模型的实现,我们新加入了“用户已好评电影”和“当前评价电影”组成的交叉特征crossed_feature,让Wide部分学习“一个喜欢电影A的用户,也会喜欢电影B”这样的规则。
|
||||
|
||||
好了,这就是我们这节课的主要内容,同样,我也把重要的知识点总结在了表格里,你可以利用它来巩固复习。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/1d/12/1d5985d8e9b7d92a87baa80f619a8a12.jpeg" alt="">
|
||||
|
||||
## 课后思考
|
||||
|
||||
对于Deep部分来说,你觉得我们一股脑地把所有特征都扔进MLP中去训练,这样的方式有没有什么改进的空间?比如说,“用户喜欢的电影风格”和“电影本身的风格”这两个特征,我们能不能进一步挖掘出它们之间的相关性,而不是简单粗暴地扔给神经网络去处理呢?
|
||||
|
||||
欢迎把你的思考和疑问写在留言区,如果的你朋友也正在为Wide&Deep模型的实现而困扰,欢迎你把这节课转发给他,我们下节课见!
|
||||
122
极客时间专栏/深度学习推荐系统实战/推荐模型篇/19|NeuralCF:如何用深度学习改造协同过滤?.md
Normal file
122
极客时间专栏/深度学习推荐系统实战/推荐模型篇/19|NeuralCF:如何用深度学习改造协同过滤?.md
Normal file
@@ -0,0 +1,122 @@
|
||||
<audio id="audio" title="19|NeuralCF:如何用深度学习改造协同过滤?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/61/d6/610fb4a963d9962d01c6fd59663606d6.mp3"></audio>
|
||||
|
||||
你好,我是王喆,今天,我们来学习协同过滤的深度学习进化版本,NeuralCF。
|
||||
|
||||
在[第15讲](https://time.geekbang.org/column/article/305182)里,我们学习了最经典的推荐算法,协同过滤。在前深度学习的时代,协同过滤曾经大放异彩,但随着技术的发展,协同过滤相比深度学习模型的弊端就日益显现出来了,因为它是通过直接利用非常稀疏的共现矩阵进行预测的,所以模型的泛化能力非常弱,遇到历史行为非常少的用户,就没法产生准确的推荐结果了。
|
||||
|
||||
虽然,我们可以通过矩阵分解算法增强它的泛化能力,但因为矩阵分解是利用非常简单的内积方式来处理用户向量和物品向量的交叉问题的,所以,它的拟合能力也比较弱。这该怎么办呢?不是说深度学习模型的拟合能力都很强吗?我们能不能利用深度学习来改进协同过滤算法呢?
|
||||
|
||||
当然是可以的。2017年,新加坡国立的研究者就使用深度学习网络来改进了传统的协同过滤算法,取名NeuralCF(神经网络协同过滤)。NeuralCF大大提高了协同过滤算法的泛化能力和拟合能力,让这个经典的推荐算法又重新在深度学习时代焕发生机。这节课,我们就一起来学习并实现NeuralCF!
|
||||
|
||||
## NeuralCF模型的结构
|
||||
|
||||
在学习NeuralCF之前,我们先来简单回顾一下协同过滤和矩阵分解的原理。协同过滤是利用用户和物品之间的交互行为历史,构建出一个像图1左一样的共现矩阵。在共现矩阵的基础上,利用每一行的用户向量相似性,找到相似用户,再利用相似用户喜欢的物品进行推荐。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/60/fb/604b312899bff7922528df4836c10cfb.jpeg" alt="" title="图1 矩阵分解算法的原理">
|
||||
|
||||
矩阵分解则进一步加强了协同过滤的泛化能力,它把协同过滤中的共现矩阵分解成了用户矩阵和物品矩阵,从用户矩阵中提取出用户隐向量,从物品矩阵中提取出物品隐向量,再利用它们之间的内积相似性进行推荐排序。如果用神经网络的思路来理解矩阵分解,它的结构图就是图2这样的。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/e6/bd/e61aa1d0d6c75230ff75c2fb698083bd.jpg" alt="" title="图2 矩阵分解的神经网络化示意图">
|
||||
|
||||
图2 中的输入层是由用户ID和物品ID生成的One-hot向量,Embedding层是把One-hot向量转化成稠密的Embedding向量表达,这部分就是矩阵分解中的用户隐向量和物品隐向量。输出层使用了用户隐向量和物品隐向量的内积作为最终预测得分,之后通过跟目标得分对比,进行反向梯度传播,更新整个网络。
|
||||
|
||||
把矩阵分解神经网络化之后,把它跟Embedding+MLP以及Wide&Deep模型做对比,我们可以一眼看出网络中的薄弱环节:矩阵分解在Embedding层之上的操作好像过于简单了,就是直接利用内积得出最终结果。这会导致特征之间还没有充分交叉就直接输出结果,模型会有欠拟合的风险。针对这一弱点,NeuralCF对矩阵分解进行了改进,它的结构图是图3这样的。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/5f/2c/5ff301f11e686eedbacd69dee184312c.jpg" alt="" title="图3 NeuralCF的模型结构图 (出自论文Neural Collaborative Filtering)">
|
||||
|
||||
我想你一定可以一眼看出它们的区别,那就是NeuralCF用一个多层的神经网络替代掉了原来简单的点积操作。这样就可以让用户和物品隐向量之间进行充分的交叉,提高模型整体的拟合能力。
|
||||
|
||||
## NeuralCF模型的扩展,双塔模型
|
||||
|
||||
有了之前实现矩阵分解和深度学习模型的经验,我想你理解起来NeuralCF肯定不会有困难。事实上,NeuralCF的模型结构之中,蕴含了一个非常有价值的思想,就是我们可以把模型分成用户侧模型和物品侧模型两部分,然后用互操作层把这两部分联合起来,产生最后的预测得分。
|
||||
|
||||
这里的用户侧模型结构和物品侧模型结构,可以是简单的Embedding层,也可以是复杂的神经网络结构,最后的互操作层可以是简单的点积操作,也可以是比较复杂的MLP结构。但只要是这种物品侧模型+用户侧模型+互操作层的模型结构,我们把它统称为“双塔模型”结构。
|
||||
|
||||
图4就是一个典型“双塔模型”的抽象结构。它的名字形象地解释了它的结构组成,两侧的模型结构就像两个高塔一样,而最上面的互操作层则像两个塔尖搭建起的空中走廊,负责两侧信息的沟通。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/66/cf/66606828b2c80a5f4ea28d60762e82cf.jpg" alt="" title="图4 双塔模型结构 [br](出自论文 Sampling-Bias-Corrected Neural Modeling for Large Corpus Item Recommendations)">
|
||||
|
||||
对于NerualCF来说,它只利用了用户ID作为“用户塔”的输入特征,用物品ID作为“物品塔”的输入特征。事实上,我们完全可以把其他用户和物品相关的特征也分别放入用户塔和物品塔,让模型能够学到的信息更全面。比如说,YouTube在构建用于召回层的双塔模型时,就分别在用户侧和物品侧输入了多种不同的特征,如图5所示。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/e2/87/e2603a22ec91a9f00be4b73feyy1f987.jpg" alt="" title="图5 YouTube双塔召回模型的架构 [br](出自论文 Sampling-Bias-Corrected Neural Modeling for Large Corpus Item Recommendations)">
|
||||
|
||||
我们看到,YouTube召回双塔模型的用户侧特征包括了用户正在观看的视频ID、频道ID(图中的seed features)、该视频的观看数、被喜欢的次数,以及用户历史观看过的视频ID等等。物品侧的特征包括了候选视频的ID、频道ID、被观看次数、被喜欢次数等等。在经过了多层ReLU神经网络的学习之后,双塔模型最终通过softmax输出层连接两部分,输出最终预测分数。
|
||||
|
||||
看到这里,你可能会有疑问,这个双塔模型相比我们之前学过的Embedding MLP和Wide&Deep有什么优势呢?其实在实际工作中,双塔模型最重要的优势就在于它易上线、易服务。为什么这么说呢?
|
||||
|
||||
你注意看一下物品塔和用户塔最顶端的那层神经元,那层神经元的输出其实就是一个全新的物品Embedding和用户Embedding。拿图4来说,物品塔的输入特征向量是x,经过物品塔的一系列变换,生成了向量u(x),那么这个u(x)就是这个物品的Embedding向量。同理,v(y)是用户y的Embedding向量,这时,我们就可以把u(x)和v(y)存入特征数据库,这样一来,线上服务的时候,我们只要把u(x)和v(y)取出来,再对它们做简单的互操作层运算就可以得出最后的模型预估结果了!
|
||||
|
||||
所以使用双塔模型,我们不用把整个模型都部署上线,只需要预存物品塔和用户塔的输出,以及在线上实现互操作层就可以了。如果这个互操作层是点积操作,那么这个实现可以说没有任何难度,这是实际应用中非常容易落地的,也是工程师们喜闻乐见的,这也正是双塔模型在业界巨大的优势所在。
|
||||
|
||||
正是因为这样的优势,双塔模型被广泛地应用在YouTube、Facebook、百度等各大公司的推荐场景中,持续发挥着它的能量。
|
||||
|
||||
## NeuralCF的TensorFlow实现
|
||||
|
||||
熟悉了NerualCF和双塔模型的结构之后,我们就可以使用TensorFlow来实现它们了。通过之前Embedding+MLP模型以及Wide&Deep模型的实现,我想你对TensorFlow中读取数据,定义特征,训练模型的过程肯定已经驾轻就熟了。我们只要更改之前代码中模型定义的部分,就可以实现NeuralCF。具体的代码你可以参考SparrowRecsys项目中的NeuralCF.py,我只贴出了NeuralCF模型部分的实现。下面,我们重点讲解一下它们的实现思路。
|
||||
|
||||
```
|
||||
# neural cf model arch two. only embedding in each tower, then MLP as the interaction layers
|
||||
def neural_cf_model_1(feature_inputs, item_feature_columns, user_feature_columns, hidden_units):
|
||||
# 物品侧特征层
|
||||
item_tower = tf.keras.layers.DenseFeatures(item_feature_columns)(feature_inputs)
|
||||
# 用户侧特征层
|
||||
user_tower = tf.keras.layers.DenseFeatures(user_feature_columns)(feature_inputs)
|
||||
# 连接层及后续多层神经网络
|
||||
interact_layer = tf.keras.layers.concatenate([item_tower, user_tower])
|
||||
for num_nodes in hidden_units:
|
||||
interact_layer = tf.keras.layers.Dense(num_nodes, activation='relu')(interact_layer)
|
||||
# sigmoid单神经元输出层
|
||||
output_layer = tf.keras.layers.Dense(1, activation='sigmoid')(interact_layer)
|
||||
# 定义keras模型
|
||||
neural_cf_model = tf.keras.Model(feature_inputs, output_layer)
|
||||
return neural_cf_model
|
||||
|
||||
```
|
||||
|
||||
你可以看到代码中定义的生成NeuralCF模型的函数,它接收了四个输入变量。其中 `feature_inputs` 代表着所有的模型输入, `item_feature_columns` 和 `user_feature_columns` 分别包含了物品侧和用户侧的特征。在训练时,如果我们只在 `item_feature_columns` 中放入 `movie_id` ,在 `user_feature_columns` 放入 `user_id,` 就是NeuralCF的经典实现了。
|
||||
|
||||
通过DenseFeatures层创建好用户侧和物品侧输入层之后,我们会再利用concatenate层将二者连接起来,然后输入多层神经网络进行训练。如果想要定义多层神经网络的层数和神经元数量,我们可以通过设置 `hidden_units` 数组来实现。
|
||||
|
||||
除了经典的NeuralCF实现,我还基于双塔模型的原理实现了一个NeuralCF的双塔版本。你可以参考下面的模型定义。与上面的经典NerualCF实现不同,我把多层神经网络操作放到了物品塔和用户塔内部,让塔内的特征进行充分交叉,最后使用内积层作为物品塔和用户塔的交互层。具体的步骤你可以参考下面代码中的注释,实现过程很好理解,我就不再赘述了。
|
||||
|
||||
```
|
||||
# neural cf model arch one. embedding+MLP in each tower, then dot product layer as the output
|
||||
def neural_cf_model_2(feature_inputs, item_feature_columns, user_feature_columns, hidden_units):
|
||||
# 物品侧输入特征层
|
||||
item_tower = tf.keras.layers.DenseFeatures(item_feature_columns)(feature_inputs)
|
||||
# 物品塔结构
|
||||
for num_nodes in hidden_units:
|
||||
item_tower = tf.keras.layers.Dense(num_nodes, activation='relu')(item_tower)
|
||||
# 用户侧输入特征层
|
||||
user_tower = tf.keras.layers.DenseFeatures(user_feature_columns)(feature_inputs)
|
||||
# 用户塔结构
|
||||
for num_nodes in hidden_units:
|
||||
user_tower = tf.keras.layers.Dense(num_nodes, activation='relu')(user_tower)
|
||||
# 使用内积操作交互物品塔和用户塔,产生最后输出
|
||||
output = tf.keras.layers.Dot(axes=1)([item_tower, user_tower])
|
||||
# 定义keras模型
|
||||
neural_cf_model = tf.keras.Model(feature_inputs, output)
|
||||
return neural_cf_model
|
||||
|
||||
|
||||
```
|
||||
|
||||
在实现了Embedding MLP、Wide&Deep和NeuralCF之后,相信你可以感觉到,实现甚至创造一个深度学习模型并不难,基于TensorFlow提供的Keras接口,我们可以根据我们的设计思路,像搭积木一样实现模型的不同结构,以此来验证我们的想法,这也正是深度推荐模型的魅力和优势。相信随着课程的进展,你不仅对这一点能够有更深刻的感受,同时,你设计和实现模型的能力也会进一步加强。
|
||||
|
||||
## 小结
|
||||
|
||||
这节课,我们首先学习了经典推荐算法协同过滤的深度学习进化版本NerualCF。相比于矩阵分解算法,NeuralCF用一个多层的神经网络,替代了矩阵分解算法中简单的点积操作,让用户和物品隐向量之间进行充分的交叉。这种通过改进物品隐向量和用户隐向量互操作层的方法,大大增加了模型的拟合能力。
|
||||
|
||||
利用NerualCF的思想,我们进一步学习了双塔模型。它通过丰富物品侧和用户侧的特征,让模型能够融入除了用户ID和物品ID外更丰富的信息。除此之外,双塔模型最大的优势在于模型服务的便捷性,由于最终的互操作层是简单的内积操作或浅层神经网络。因此,我们可以把物品塔的输出当作物品Embedding,用户塔的输出当作用户Embedding存入特征数据库,在线上只要实现简单的互操作过程就可以了。
|
||||
|
||||
最后,我们继续使用TensorFlow实现了NerualCF和双塔模型,相信你能进一步感受到利用TensorFlow构建深度学习模型的便捷性,以及它和传统推荐模型相比,在模型结构灵活性上的巨大优势。
|
||||
|
||||
为了帮助你复习,我把刚才说的这些重点内容总结在了一张图里,你可以看看。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/91/5f/9196a80181f41ba4a96bb80e6286c35f.jpeg" alt="">
|
||||
|
||||
## 课后思考
|
||||
|
||||
对于我们这节课学习的双塔模型来说,把物品侧的Embedding和用户侧的Embedding存起来,就可以进行线上服务了。但如果我们把一些场景特征,比如当前时间、当前地点加到用户侧或者物品侧,那我们还能用这种方式进行模型服务吗?为什么?
|
||||
|
||||
欢迎把你的思考和疑惑写在留言区,也欢迎你把这节课转发出去,我们下节课见!
|
||||
144
极客时间专栏/深度学习推荐系统实战/推荐模型篇/20 | DeepFM:如何让你的模型更好地处理特征交叉?.md
Normal file
144
极客时间专栏/深度学习推荐系统实战/推荐模型篇/20 | DeepFM:如何让你的模型更好地处理特征交叉?.md
Normal file
@@ -0,0 +1,144 @@
|
||||
<audio id="audio" title="20 | DeepFM:如何让你的模型更好地处理特征交叉?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/32/8e/3295ff2e57ed7aaa90a885abe533c58e.mp3"></audio>
|
||||
|
||||
你好,我是王喆。
|
||||
|
||||
前面几节课,我们学习了Embedding MLP、Wide&Deep、NerualCF等几种不同的模型结构。你有没有深入思考过这样一个问题:这几种模型都是怎么处理特征交叉这个问题的?
|
||||
|
||||
比如说,模型的输入有性别、年龄、电影风格这几个特征,在训练样本中我们发现有25岁男生喜欢科幻电影的样本,有35岁女生喜欢看恐怖电影的样本,那你觉得模型应该怎么推测“25岁”的女生喜欢看的电影风格呢?
|
||||
|
||||
事实上,这类特征组合和特征交叉问题非常常见,而且在实际应用中,特征的种类还要多得多,特征交叉的复杂程度也要大得多。**解决这类问题的关键,就是模型对于特征组合和特征交叉的学习能力,因为它决定了模型对于未知特征组合样本的预测能力,而这对于复杂的推荐问题来说,是决定其推荐效果的关键点之一。**
|
||||
|
||||
但无论是Embedding MLP,还是Wide&Deep其实都没有对特征交叉进行特别的处理,而是直接把独立的特征扔进神经网络,让它们在网络里面进行自由组合,就算是NeuralCF也只在最后才把物品侧和用户侧的特征交叉起来。那这样的特征交叉方法是高效的吗?深度学习模型有没有更好的处理特征交叉的方法呢?
|
||||
|
||||
这节课,我们就一起来解决这些问题。同时,我还会基于特征交叉的思想,带你学习和实现一种新的深度学习模型DeepFM。
|
||||
|
||||
## 为什么深度学习模型需要加强处理特征交叉的能力?
|
||||
|
||||
不过,在正式开始今天的课程之前,我还想和你再深入聊聊,为什么深度学习需要加强处理特征交叉的能力。我们刚才说Embedding MLP和Wide&Deep模型都没有针对性的处理特征交叉问题,有的同学可能就会有疑问了,我们之前不是一直说,多层神经网络有很强的拟合能力,能够在网络内部任意地组合特征吗?这两个说法是不是矛盾了?
|
||||
|
||||
在进入正题前,我就带你先扫清这个疑问。我们之前一直说MLP有拟合任意函数的能力,这没有错,但这是建立在MLP有任意多层网络,以及任意多个神经元的前提下的。
|
||||
|
||||
在训练资源有限,调参时间有限的现实情况下,MLP对于特征交叉的处理其实还比较低效。因为MLP是通过concatenate层把所有特征连接在一起成为一个特征向量的,这里面没有特征交叉,两两特征之间没有发生任何关系。
|
||||
|
||||
这个时候,在我们有先验知识的情况下,人为地加入一些负责特征交叉的模型结构,其实对提升模型效果会非常有帮助。比如,在我们Sparrow RecSys项目的训练样本中其实有两个这样的特征,一个是用户喜欢的电影风格,一个是电影本身的风格,这两个特征明显具有很强的相关性。如果我们能让模型利用起这样的相关性,肯定会对最后的推荐效果有正向的影响。
|
||||
|
||||
既然这样,那我们不如去设计一些特定的特征交叉结构,来把这些相关性强的特征,交叉组合在一起,这就是深度学习模型要加强特征交叉能力的原因了。
|
||||
|
||||
## 善于处理特征交叉的机器学习模型FM
|
||||
|
||||
扫清了这个疑问,接下来,我们就要进入具体的深度学习模型的学习了,不过,先别着急,我想先和你聊聊传统的机器学习模型是怎么解决特征交叉问题的,看看深度学习模型能不能从中汲取到“养分”。
|
||||
|
||||
说到解决特征交叉问题的传统机器学习模型,我们就不得不提一下,曾经红极一时的机器学习模型因子分解机模型(Factorization Machine)了,我们可以简称它为FM。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/6b/a2/6b2868995e486943ea90cfc51c2bc0a2.jpg" alt="" title="图1 FM的神经网络化结构 [br](出自论文 DeepFM: A Factorization-Machine based Neural Network for CTR Prediction)">
|
||||
|
||||
首先,我们看上图中模型的最下面,它的输入是由类别型特征转换成的One-hot向量,往上就是深度学习的常规操作,也就是把One-hot特征通过Embedding层转换成稠密Embedding向量。到这里,FM跟其他深度学习模型其实并没有区别,但再往上区别就明显了。
|
||||
|
||||
FM会使用一个独特的层FM Layer来专门处理特征之间的交叉问题。你可以看到,FM层中有多个内积操作单元对不同特征向量进行两两组合,这些操作单元会把不同特征的内积操作的结果输入最后的输出神经元,以此来完成最后的预测。
|
||||
|
||||
这样一来,如果我们有两个特征是用户喜爱的风格和电影本身的风格,通过FM层的两两特征的内积操作,这两个特征就可以完成充分的组合,不至于像Embedding MLP模型一样,还要MLP内部像黑盒子一样进行低效的交叉。
|
||||
|
||||
## 深度学习模型和FM模型的结合DeepFM
|
||||
|
||||
这个时候问题又来了,FM是一个善于进行特征交叉的模型,但是我们之前也讲过,深度学习模型的拟合能力强啊,那二者之间能结合吗?
|
||||
|
||||
学习过Wide&Deep结构之后,我们一定可以快速给出答案,我们当然可以把FM跟其他深度学习模型组合起来,生成一个全新的既有强特征组合能力,又有强拟合能力的模型。基于这样的思想,DeepFM模型就诞生了。
|
||||
|
||||
DeepFM是由哈工大和华为公司联合提出的深度学习模型,我把它的架构示意图放在了下面。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/d0/19/d0df6ed3958byyd9529efceebba64419.png" alt="" title="图2 DeepFM模型架构图 [br](出自论文 DeepFM: A Factorization-Machine based Neural Network for CTR Prediction)">
|
||||
|
||||
结合模型结构图,我们可以看到,DeepFM利用了Wide&Deep组合模型的思想,用FM替换了Wide&Deep左边的Wide部分,加强了浅层网络部分特征组合的能力,而右边的部分跟Wide&Deep的Deep部分一样,主要利用多层神经网络进行所有特征的深层处理,最后的输出层是把FM部分的输出和Deep部分的输出综合起来,产生最后的预估结果。这就是DeepFM的结构。
|
||||
|
||||
## 特征交叉新方法:元素积操作
|
||||
|
||||
接下来我们再思考一个问题,FM和DeepFM中进行特征交叉的方式,都是进行Embedding向量的点积操作,那是不是说特征交叉就只能用点积操作了?
|
||||
|
||||
答案当然是否定的。事实上还有很多向量间的运算方式可以进行特征的交叉,比如模型NFM(Neural Factorization Machines,神经网络因子分解机),它就使用了新的特征交叉方法。下面,我们一起来看一下。
|
||||
|
||||
图3就是NFM的模型架构图,相信已经看了这么多模型架构图的你,一眼就能看出它跟其他模型的区别,也就是Bi-Interaction Pooling层。那这个夹在Embedding层和MLP之间的层到底做了什么呢?
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/a2/0c/a2c0f6751f64f50e3c628bf86cd9b00c.jpg" alt="" title="图3 NFM的模型架构图 [br](出自论文Neural Factorization Machines for Sparse Predictive Analytics)">
|
||||
|
||||
Bi-Interaction Pooling Layer翻译成中文就是“两两特征交叉池化层”。假设Vx是所有特征域的Embedding集合,那么特征交叉池化层的具体操作如下所示。
|
||||
|
||||
$$<br>
|
||||
f_{\mathrm{PI}}\left(V_{x}\right)=\sum_{i=1}^{n} \sum_{j=i+1}^{n} x_{i} \boldsymbol{v}_{i} \odot \boldsymbol{x}_{j} \boldsymbol{v}_{j}<br>
|
||||
$$
|
||||
|
||||
其中$\odot$运算代表两个向量的元素积(Element-wise Product)操作,即两个长度相同的向量对应维相乘得到元素积向量。其中,第k维的操作如下所示。
|
||||
|
||||
$$<br>
|
||||
\left(V_{i} \odot V_{j}\right)_{K}=v_{i k} v_{j k}<br>
|
||||
$$
|
||||
|
||||
在进行两两特征Embedding向量的元素积操作后,再求取所有交叉特征向量之和,我们就得到了池化层的输出向量。接着,我们再把该向量输入上层的多层全连接神经网络,就能得出最后的预测得分。
|
||||
|
||||
总的来说,NFM并没有使用内积操作来进行特征Embedding向量的交叉,而是使用元素积的操作。在得到交叉特征向量之后,也没有使用concatenate操作把它们连接起来,而是采用了求和的池化操作,把它们叠加起来。
|
||||
|
||||
看到这儿,你肯定又想问,元素积操作和点积操作到底哪个更好呢?还是那句老话,我希望我们能够尽量多地储备深度学习模型的相关知识,先不去关注哪个方法的效果会更好,至于真实的效果怎么样,交给你去在具体的业务场景的实践中验证。
|
||||
|
||||
## DeepFM的TensorFlow实战
|
||||
|
||||
接下来,又到了TensorFlow实践的时间了,今天我们将要实现DeepFM模型。有了之前实现Wide&Deep模型的经验,我想你实现起DeepFM也不会困难。跟前几节课一样,实践过程中的特征处理、模型训练评估的部分都是相同的,我也就不再重复了,我们重点看模型定义的部分。我把这部分的代码也放在了下面,你可以结合它来看我的讲解。
|
||||
|
||||
```
|
||||
item_emb_layer = tf.keras.layers.DenseFeatures([movie_emb_col])(inputs)
|
||||
user_emb_layer = tf.keras.layers.DenseFeatures([user_emb_col])(inputs)
|
||||
item_genre_emb_layer = tf.keras.layers.DenseFeatures([item_genre_emb_col])(inputs)
|
||||
user_genre_emb_layer = tf.keras.layers.DenseFeatures([user_genre_emb_col])(inputs)
|
||||
|
||||
|
||||
# FM part, cross different categorical feature embeddings
|
||||
product_layer_item_user = tf.keras.layers.Dot(axes=1)([item_emb_layer, user_emb_layer])
|
||||
product_layer_item_genre_user_genre = tf.keras.layers.Dot(axes=1)([item_genre_emb_layer, user_genre_emb_layer])
|
||||
product_layer_item_genre_user = tf.keras.layers.Dot(axes=1)([item_genre_emb_layer, user_emb_layer])
|
||||
product_layer_user_genre_item = tf.keras.layers.Dot(axes=1)([item_emb_layer, user_genre_emb_layer])
|
||||
|
||||
|
||||
# deep part, MLP to generalize all input features
|
||||
deep = tf.keras.layers.DenseFeatures(deep_feature_columns)(inputs)
|
||||
deep = tf.keras.layers.Dense(64, activation='relu')(deep)
|
||||
deep = tf.keras.layers.Dense(64, activation='relu')(deep)
|
||||
|
||||
|
||||
# concatenate fm part and deep part
|
||||
concat_layer = tf.keras.layers.concatenate([product_layer_item_user, product_layer_item_genre_user_genre,
|
||||
product_layer_item_genre_user, product_layer_user_genre_item, deep], axis=1)
|
||||
output_layer = tf.keras.layers.Dense(1, activation='sigmoid')(concat_layer)
|
||||
|
||||
|
||||
model = tf.keras.Model(inputs, output_lay)
|
||||
|
||||
|
||||
```
|
||||
|
||||
在整个实践的过程中,有两个地方需要我们重点注意,一个是FM部分的构建,另一个是FM部分的输出和Deep输出的连接。
|
||||
|
||||
在构建FM部分的时候,我们先为FM部分选择了4个用于交叉的类别型特征,分别是用户ID、电影ID、用户喜欢的风格和电影自己的风格。接着,我们使用Dot layer把用户特征和电影特征两两交叉,这就完成了FM部分的构建。
|
||||
|
||||
而Deep部分的实现,其实和我们之前实现过的Wide&Deep模型的Deep部分完全一样。只不过,最终我们会使用concatenate层,去把FM部分的输出和Deep部分的输出连接起来,输入到输出层的sigmoid神经元,从而产生最终的预估分数。那关于DeepFM的全部代码,你可以参照SparrowRecsys项目中的DeepFM.py文件。
|
||||
|
||||
## 小结
|
||||
|
||||
DeepFM模型在解决特征交叉问题上非常有优势,它会使用一个独特的FM层来专门处理特征之间的交叉问题。具体来说,就是使用点积、元素积等操作让不同特征之间进行两两组合,再把组合后的结果输入的输出神经元中,这会大大加强模型特征组合的能力。因此,DeepFM模型相比于Embedding MLP、Wide&Deep等模型,往往具有更好的推荐效果。
|
||||
|
||||
实现DeepFM模型的过程并不困难,我们主要记住三点就可以了:
|
||||
|
||||
- 它是由FM和Deep两部分组成的;
|
||||
- 在实现FM部分特征交叉层的时候,我们使用了多个Dot Product操作单元完成不同特征的两两交叉;
|
||||
- Deep部分则与Wide&Deep模型一样,负责所有输入特征的深度拟合,提高模型整体的表达能力
|
||||
|
||||
刚才说的重点知识,我都整理在了下面的表格中,你可以看一看。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/4d/76/4dbb2c9760199311b38b32a15daba176.jpeg" alt="">
|
||||
|
||||
好了,到今天这节课,我们已经在SparrowRecsys中实现了四个深度学习模型,相信你对TensorFlow的Keras接口也已经十分熟悉了。我希望你不只满足于读懂、用好SparrowRecsys中实现好的模型,而是真的在课后自己多去尝试不同的特征输入,不同的模型结构,甚至可以按照自己的理解和思考去改进这些模型。
|
||||
|
||||
因为深度学习模型结构没有标准答案,我们只有清楚不同模型之间的优缺点,重点汲取它们的设计思想,才能在实际的工作中结合自己遇到的问题,来优化和改造已有的模型。也只有这样,你们才能成为一名能真正解决实际问题的算法工程师。
|
||||
|
||||
## 课后思考
|
||||
|
||||
你觉得除了点积和元素积这两个操作外,还有没有其他的方法能处理两个Embedding向量间的特征交叉?
|
||||
|
||||
关于深度学习中特征交叉问题的处理方法,你是不是学会了?欢迎把你的思考和疑问写在留言区,如果你的朋友也对DeepFM这个模型感兴趣,那不妨也把这节课转发给他,我们下节课见!
|
||||
117
极客时间专栏/深度学习推荐系统实战/推荐模型篇/21|注意力机制、兴趣演化:推荐系统如何抓住用户的心?.md
Normal file
117
极客时间专栏/深度学习推荐系统实战/推荐模型篇/21|注意力机制、兴趣演化:推荐系统如何抓住用户的心?.md
Normal file
@@ -0,0 +1,117 @@
|
||||
<audio id="audio" title="21|注意力机制、兴趣演化:推荐系统如何抓住用户的心?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/a1/21/a105760f4e79103861027ce0c8b81421.mp3"></audio>
|
||||
|
||||
你好,我是王喆。
|
||||
|
||||
近几年来,注意力机制、兴趣演化序列模型和强化学习,都在推荐系统领域得到了广泛的应用。它们是深度学习推荐模型的发展趋势,也是我们必须要储备的前沿知识。
|
||||
|
||||
作为一名算法工程师,足够的知识储备是非常重要的,因为在掌握了当下主流的深度学习模型架构(Embedding MLP架构、Wide&Deep和DeepFM等等)之后,要想再进一步提高推荐系统的效果,就需要清楚地知道业界有哪些新的思路可以借鉴,学术界有哪些新的思想可以尝试,这些都是我们取得持续成功的关键。
|
||||
|
||||
所以,我会用两节课的时间,带你一起学习这几种新的模型改进思路。今天我们先重点关注注意力机制和兴趣演化序列模型,下节课我们再学习强化学习。
|
||||
|
||||
## 什么是“注意力机制”?
|
||||
|
||||
**“注意力机制”来源于人类天生的“选择性注意”的习惯**。最典型的例子是用户在浏览网页时,会有选择性地注意页面的特定区域,而忽视其他区域。
|
||||
|
||||
比如,图1是Google对大量用户进行眼球追踪实验后,得出的页面注意力热度图。我们可以看到,用户对页面不同区域的注意力区别非常大,他们的大部分注意力就集中在左上角的几条搜索结果上。
|
||||
|
||||
那么,“注意力机制”对我们构建推荐模型到底有什么价值呢?
|
||||
|
||||
[<img src="https://static001.geekbang.org/resource/image/3a/5f/3a3cb86da13876d679c16f4ec955645f.jpg" alt="" title="图1 Google搜索结果的注意力热度图">](https://www.researchgate.net/figure/Heat-Map-Golden-Triangle-pattern-shown-by-the-heat-map-of-how-users-focus-on-a_fig1_267025472)
|
||||
|
||||
价值是非常大的。比如说,我们要做一个新闻推荐的模型,让这个模型根据用户已经看过的新闻做推荐。那我们在分析用户已浏览新闻的时候,是把标题、首段、全文的重要性设置成完全一样比较好,还是应该根据用户的注意力不同给予不同的权重呢?当然,肯定是后者比较合理,因为用户很可能都没有注意到正文最后的几段,如果你分析内容的时候把最后几段跟标题、首段一视同仁,那肯定就把最重要的信息给淹没了。
|
||||
|
||||
事实上,近年来,注意力机制已经成功应用在各种场景下的推荐系统中了。其中最知名的,要数阿里巴巴的深度推荐模型,DIN(Deep Interest Network,深度兴趣网络)。接下来,我们就一起来学习一下DIN的原理和模型结构。
|
||||
|
||||
### 注意力机制在深度兴趣网络DIN上的应用
|
||||
|
||||
DIN模型的应用场景是阿里最典型的电商广告推荐。对于付了广告费的商品,阿里会根据模型预测的点击率高低,把合适的广告商品推荐给合适的用户,所以DIN模型本质上是一个点击率预估模型。
|
||||
|
||||
注意力机制是怎么应用在DIN模型里的呢?回答这个问题之前,我们得先看一看DIN在应用注意力机制之前的基础模型是什么样的,才能搞清楚注意力机制能应用在哪,能起到什么作用。
|
||||
|
||||
下面的图2就是DIN的基础模型Base Model。我们可以看到,Base Model是一个典型的Embedding MLP的结构。它的输入特征有用户属性特征(User Proflie Features)、用户行为特征(User Behaviors)、候选广告特征(Candidate Ad)和场景特征(Context Features)。
|
||||
|
||||
用户属性特征和场景特征我们之前也已经讲过很多次了,这里我们重点关注用户的行为特征和候选广告特征,也就是图2中彩色的部分。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/c1/6e/c1dbcc00dd17166ec53ca9e46b04006e.jpg" alt="" title="图2 阿里Base模型的架构图[br] (出自论文 Deep Interest Network for Click-Through Rate Prediction)">
|
||||
|
||||
我们可以清楚地看到,用户行为特征是由一系列用户购买过的商品组成的,也就是图上的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),这个激活单元生成了一个权重,这个权重就是用户对这个历史商品的注意力得分,权重的大小对应用户注意力的高低。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/ed/30/edd028f815ef3577553ffe045f042330.jpg" alt="" title="图3 阿里DIN模型的架构图[br] (出自论文 Deep Interest Network for Click-Through Rate Prediction)">
|
||||
|
||||
那现在问题就只剩下一个了,这个所谓的激活单元,到底是怎么计算出最后的注意力权重的呢?为了搞清楚这个问题,我们需要深入到激活单元的内部结构里面去,一起来看看图3右上角激活单元的详细结构。
|
||||
|
||||
它的输入是当前这个历史行为商品的Embedding,以及候选广告商品的Embedding。我们把这两个输入Embedding,与它们的外积结果连接起来形成一个向量,再输入给激活单元的MLP层,最终会生成一个注意力权重,这就是激活单元的结构。简单来说,**激活单元就相当于一个小的深度学习模型,它利用两个商品的Embedding,生成了代表它们关联程度的注意力权重**。
|
||||
|
||||
到这里,我们终于抽丝剥茧地讲完了整个DIN模型的结构细节。如果你第一遍没理解清楚,没关系,对照着DIN模型的结构图,反复再看几遍我刚才讲的细节,相信你就能彻底消化吸收它。
|
||||
|
||||
### 注意力机制对推荐系统的启发
|
||||
|
||||
注意力机制的引入对于推荐系统的意义是非常重大的,它模拟了人类最自然,最发自内心的注意力行为特点,使得推荐系统更加接近用户真实的思考过程,从而达到提升推荐效果的目的。
|
||||
|
||||
从“注意力机制”开始,越来越多对深度学习模型结构的改进是基于对用户行为的深刻观察而得出的。由此,我也想再次强调一下,**一名优秀的算法工程师应该具备的能力,就是基于对业务的精确理解,对用户行为的深刻观察,得出改进模型的动机,进而设计出最合适你的场景和用户的推荐模型。**
|
||||
|
||||
沿着这条思路,阿里的同学们在提出DIN模型之后,并没有停止推荐模型演化的进程,他们又在2019年提出了DIN模型的演化版本,也就是深度兴趣进化网络DIEN(Deep Interest Evolution Network)。这个DIEN到底在DIN的基础上做了哪些改进呢?
|
||||
|
||||
## 兴趣进化序列模型
|
||||
|
||||
无论是电商购买行为,还是视频网站的观看行为,或是新闻应用的阅读行为,特定用户的历史行为都是一个随时间排序的序列。既然是和时间相关的序列,就一定存在前后行为的依赖关系,这样的序列信息对于推荐过程是非常有价值的。为什么这么说呢?
|
||||
|
||||
我们还拿阿里的电商场景举个例子。对于一个综合电商来说,用户兴趣的迁移其实是非常快的。比如,上一周一位用户在挑选一双篮球鞋,这位用户上周的行为序列都会集中在篮球鞋这个品类的各个商品上,但在他完成这一购物目标后,这一周他的购物兴趣就可能变成买一个机械键盘,那这周他所有的购买行为都会围绕机械键盘这个品类展开。
|
||||
|
||||
因此,如果我们能让模型预测出用户购买商品的趋势,肯定会对提升推荐效果有益。而DIEN模型正好弥补了DIN模型没有对行为序列进行建模的缺陷,它围绕兴趣进化这个点进一步对DIN模型做了改进。
|
||||
|
||||
图4就是DIEN模型的架构图,这个模型整体上仍然是一个Embedding MLP的模型结构。与DIN不同的是,DIEN用“兴趣进化网络”也就是图中的彩色部分替换掉了原来带有激活单元的用户历史行为部分。这部分虽然复杂,但它的输出只是一个h'(T)的Embedding向量,它代表了用户当前的兴趣向量。有了这个兴趣向量之后,再把它与其他特征连接在一起,DIEN就能通过MLP作出最后的预测了。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/56/a0/56c93e11aa3503852c37cd18db740da0.jpg" alt="" title="图4 DIEN模型的架构图 (出自论文 Deep Interest Evolution Network for Click-Through Rate Prediction)">
|
||||
|
||||
好了,现在问题的焦点就在,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里就是为了预测用户的下一个兴趣。这就是序列模型的结构和作用。
|
||||
|
||||
[<img src="https://static001.geekbang.org/resource/image/f6/37/f69c0f565b5fa31023b727192d3b1d37.jpg" alt="" title="图5 RNN模型的经典结构 ">](https://towardsdatascience.com/introduction-to-recurrent-neural-networks-rnn-with-dinosaurs-790e74e3e6f6)
|
||||
|
||||
至于上面提到过的GRU序列模型,它其实是序列模型的一种,根据序列模型神经元结构的不同,最经典的有[RNN](https://arxiv.org/pdf/1406.1078.pdf)、[LSTM](http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.676.4320&rep=rep1&type=pdf)、[GRU](https://arxiv.org/pdf/1412.3555.pdf)这3种。这里我们就不展开讲了,对理论感兴趣的同学,可以点击我给出的超链接,参考这几篇论文做更深入的研究。
|
||||
|
||||
[<img src="https://static001.geekbang.org/resource/image/be/02/bed695e253a49e6f08a6627782ec9202.jpg" alt="" title="图6 序列模型中的不同单元结构">](http://dprogrammer.org/rnn-lstm-gru)
|
||||
|
||||
事实上,序列模型已经不仅在电商场景下,成功应用于推测用户的下次购买行为,在YouTube、Netflix等视频流媒体公司的视频推荐模型中,序列模型也用来推测用户的下次观看行为(Next Watch)。除此之外,音乐类应用也非常适合使用序列模型来预测用户的音乐兴趣变化。所以,掌握DIEN模型的架构对于拓宽我们的技术视野非常有帮助。
|
||||
|
||||
## 小结
|
||||
|
||||
注意力机制和兴趣演化序列模型的加入,让推荐系统能够更好地抓住用户的心。
|
||||
|
||||
对于注意力机制来说,它主要模拟了人类注意力的天性。具体到阿里的DIN模型上,它利用激活单元计算出用户对于不同历史商品的注意力权重,针对当前广告商品,作出更有针对性的预测。
|
||||
|
||||
而序列模型更注重对序列类行为的模拟和预测。典型的例子是DIEN模型对用户购买兴趣进化过程的模拟。DIEN模型可以应用的场景非常广泛,包括电商平台的下次购买,视频网站的下次观看,音乐App的下一首歌曲等等。
|
||||
|
||||
总的来说,注意力机制的引入是对经典深度学习模型的一次大的改进,因为它改变了深度学习模型对待用户历史行为“一视同仁”的弊端。而序列模型则把用户行为串联起来,让用户的兴趣随时间进行演化,这也是之前的深度学习模型完全没有考虑到的结构。
|
||||
|
||||
最后,我把今天的重要概念总结在了表格中,方便你及时查看和复习。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/c3/e0/c38e2d57f570c0e3ea5c0d616397b2e0.jpeg" alt="">
|
||||
|
||||
## 课后思考
|
||||
|
||||
DIN使用了一个结构比较复杂的激活单元来计算注意力权重,你觉得有没有更简单、更实用的方式来生成注意力权重呢?其实,计算注意力权重就是为了计算历史行为物品和广告物品的相关性,在这个过程中,你觉得能不能利用到特征交叉的知识呢?为什么?
|
||||
|
||||
欢迎把你的思考和疑问写在留言区,如果你的朋友们也在关注注意力机制和兴趣演化序列模型的发展,那不妨也把这节课转发给他们,我们下节课见!
|
||||
132
极客时间专栏/深度学习推荐系统实战/推荐模型篇/22|强化学习:让推荐系统像智能机器人一样自主学习.md
Normal file
132
极客时间专栏/深度学习推荐系统实战/推荐模型篇/22|强化学习:让推荐系统像智能机器人一样自主学习.md
Normal file
@@ -0,0 +1,132 @@
|
||||
<audio id="audio" title="22|强化学习:让推荐系统像智能机器人一样自主学习" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/9a/63/9aac277df3d25b66870664f5df053063.mp3"></audio>
|
||||
|
||||
你好,我是王喆。这节课我们继续来讲深度推荐模型发展的前沿趋势,来学习强化学习(Reinforcement Learning)与深度推荐模型的结合。
|
||||
|
||||
强化学习也被称为增强学习,它在模型实时更新、用户行为快速反馈等方向上拥有巨大的优势。自从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)等多家一线公司都已经有了强化学习的成功应用案例。
|
||||
|
||||
虽然,强化学习在推荐系统中的应用是一个很复杂的工程问题,我们自己很难在单机环境下模拟,但理解它在推荐系统中的应用方法,是我们进一步改进推荐系统的关键点之一,也是推荐系统发展的趋势之一。
|
||||
|
||||
所以这节课,我会带你重点学习这三点内容:一是强化学习的基本概念;二是,我会以微软的DRN模型为例,帮你厘清强化学习在推荐系统的应用细节;三是帮助你搞清楚深度学习和强化学习的结合点究竟在哪。
|
||||
|
||||
## 强化学习的基本概念
|
||||
|
||||
强化学习的基本原理,简单来说,就是**一个智能体通过与环境进行交互,不断学习强化自己的智力,来指导自己的下一步行动,以取得最大化的预期利益**。
|
||||
|
||||
事实上,任何一个有智力的个体,它的学习过程都遵循强化学习所描述的原理。比如说,婴儿学走路就是通过与环境交互,不断从失败中学习,来改进自己的下一步的动作才最终成功的。再比如说,在机器人领域,一个智能机器人控制机械臂来完成一个指定的任务,或者协调全身的动作来学习跑步,本质上都符合强化学习的过程。
|
||||
|
||||
为了把强化学习技术落地,只清楚它的基本原理显然是不够的,我们需要清晰地定义出强化学习中的每个关键变量,形成一套通用的技术框架。对于一个通用的强化学习框架来说,有这么六个元素是必须要有的:
|
||||
|
||||
- **智能体(Agent)**:强化学习的主体也就是作出决定的“大脑”;
|
||||
- **环境(Environment)**:智能体所在的环境,智能体交互的对象;
|
||||
- **行动(Action)**:由智能体做出的行动;
|
||||
- **奖励(Reward)**:智能体作出行动后,该行动带来的奖励;
|
||||
- **状态(State)**:智能体自身当前所处的状态;
|
||||
- **目标(Objective)**:指智能体希望达成的目标。
|
||||
|
||||
为了方便记忆,我们可以用一段话把强化学习的六大要素串起来:一个**智能体**身处在不断变化的**环境**之中,为了达成某个**目标**,它需要不断作出**行动**,行动会带来好或者不好的**奖励**,智能体收集起这些奖励反馈进行自我学习,改变自己所处的**状态**,再进行下一步的行动,然后智能体会持续这个“**行动-奖励-更新状态**”的循环,不断优化自身,直到达成设定的目标。
|
||||
|
||||
这就是强化学习通用过程的描述,那么,对于推荐系统而言,我们能不能创造这样一个会自我学习、自我调整的智能体,为用户进行推荐呢?事实上,微软的DRN模型已经实现这个想法了。下面,我就以DRN模型为例,来给你讲一讲在推荐系统中,强化学习的六大要素都是什么,强化学习具体又是怎样应用在推荐系统中的。
|
||||
|
||||
## 强化学习推荐系统框架
|
||||
|
||||
强化学习推荐模型DRN(Deep Reinforcement Learning Network,深度强化学习网络)是微软在2018年提出的,它被应用在了新闻推荐的场景上,下图1是DRN的框架图。事实上,它不仅是微软DRN的框架图,也是一个经典的强化学习推荐系统技术框图。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/2f/27/2ff55be8dea34e992bcb09e1f3c39a27.jpg" alt="" title="图1 深度强化学习推荐系统框架">
|
||||
|
||||
从这个技术框图中,我们可以清楚地看到强化学习的六大要素。接下来,我就以DRN模型的学习过程串联起所有要素,来和你详细说说这六大要素在推荐系统场景下分别指的是什么,以及每个要素的位置和作用。
|
||||
|
||||
在新闻的推荐系统场景下,DRN模型的第一步是初始化推荐系统,主要初始化的是推荐模型,我们可以利用离线训练好的模型作为初始化模型,其他的还包括我们之前讲过的特征存储、推荐服务器等等。
|
||||
|
||||
接下来,推荐系统作为智能体会根据当前已收集的用户行为数据,也就是当前的状态,对新闻进行排序这样的行动,并在新闻网站或者App这些环境中推送给用户。
|
||||
|
||||
用户收到新闻推荐列表之后,可能会产生点击或者忽略推荐结果的反馈。这些反馈都会作为正向或者负向奖励再反馈给推荐系统。
|
||||
|
||||
推荐系统收到奖励之后,会根据它改变、更新当前的状态,并进行模型训练来更新模型。接着,就是推荐系统不断重复“排序-推送-反馈”的步骤,直到达成提高新闻的整体点击率或者用户留存等目的为止。
|
||||
|
||||
为了方便你进行对比,我也把这六大要素在推荐系统场景下的定义整理在了下面,你可以看一看。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/ea/2b/eac0fea51d5033a35332cd81f653202b.jpeg" alt="">
|
||||
|
||||
到这里,你有没有发现强化学习推荐系统跟传统推荐系统相比,它的主要特点是什么?其实,就在于强化学习推荐系统始终在强调“持续学习”和“实时训练”。它不断利用新学到的知识更新自己,做出最及时的调整,这也正是将强化学习应用于推荐系统的收益所在。
|
||||
|
||||
我们现在已经熟悉了强化学习推荐系统的框架,但其中最关键的部分“智能体”到底长什么样呢?微软又是怎么实现“实时训练”的呢?接下来,就让我们深入DRN的细节中去看一看。
|
||||
|
||||
## 深度强化学习推荐模型DRN
|
||||
|
||||
智能体是强化学习框架的核心,作为推荐系统这一智能体来说,推荐模型就是推荐系统的“大脑”。在DRN框架中,扮演“大脑”角色的是Deep Q-Network (深度Q网络,DQN)。其中,Q是Quality的简称,指通过对行动进行质量评估,得到行动的效用得分,来进行行动决策。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/75/8a/75d38d425b1a72350ce85e8b676d928a.jpg" alt="" title="图2 DQN的模型架构图">
|
||||
|
||||
DQN的网络结构如图2所示,它就是一个典型的双塔结构。其中,用户塔的输入特征是用户特征和场景特征,物品塔的输入向量是所有的用户、环境、用户-新闻交叉特征和新闻特征。
|
||||
|
||||
在强化学习的框架下,用户塔特征向量因为代表了用户当前所处的状态,所以也可被视为**状态向量**。物品塔特征向量则代表了系统下一步要选择的新闻,我们刚才说了,这个选择新闻的过程就是智能体的“行动”,所以物品塔特征向量也被称为**行动向量**。
|
||||
|
||||
双塔模型通过对状态向量和行动向量分别进行MLP处理,再用互操作层生成了最终的行动质量得分Q(s,a),智能体正是通过这一得分的高低,来选择到底做出哪些行动,也就是推荐哪些新闻给用户的。
|
||||
|
||||
其实到这里为止,我们并没有看到强化学习的优势,貌似就是套用了强化学习的概念把深度推荐模型又解释了一遍。别着急,下面我要讲的DRN学习过程才是强化学习的精髓。
|
||||
|
||||
### DRN的学习过程
|
||||
|
||||
DRN的学习过程是整个强化学习推荐系统框架的重点,正是因为可以在线更新,才使得强化学习模型相比其他“静态”深度学习模型有了更多实时性上的优势。下面,我们就按照下图中从左至右的时间轴,来描绘一下DRN学习过程中的重要步骤。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/96/2a/96509410e50e019e5077f6a2a909292a.jpg" alt="" title="图3 DRN的学习过程">
|
||||
|
||||
我们先来看离线部分。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(竞争梯度下降算法)。
|
||||
|
||||
### DRN的在线学习方法:竞争梯度下降算法
|
||||
|
||||
我先把竞争梯度下降算法的流程图放在了下面。接下来,我就结合这个流程图,来给你详细讲讲它的过程和它会涉及的模型微更新操作。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/c9/14/c94b7122f71d8b7845ffca3fd239e314.jpg" alt="" title="图4 DRN的在线学习过程">
|
||||
|
||||
DRN的在线学习过程主要包括三步,我带你一起来看一下。
|
||||
|
||||
第一步,对于已经训练好的当前网络Q,对其模型参数**W**添加一个较小的随机扰动,得到一个新的模型参数,这里我们称对应的网络为探索网络Q~。
|
||||
|
||||
在这一步中,由当前网络Q生成探索网络 ,产生随机扰动的公式1如下:
|
||||
|
||||
$$<br>
|
||||
\Delta \mathrm{W}=\alpha \cdot \operatorname{rand}(-1,1) \cdot W<br>
|
||||
$$
|
||||
|
||||
其中,$\alpha$是一个探索因子,决定探索力度的大小。rand(-1,1)产生的是一个[-1,1]之间的随机数。
|
||||
|
||||
第二步,对于当前网络Q和探索网络 Q~,分别生成推荐列表L和 L~,再将两个推荐列表用间隔穿插(Interleaving)的方式融合,组合成一个推荐列表后推送给用户。
|
||||
|
||||
最后一步是实时收集用户反馈。如果探索网络Q~生成内容的效果好于当前网络Q,我们就用探索网络代替当前网络,进入下一轮迭代。反之,我们就保留当前网络。
|
||||
|
||||
总的来说,DRN的在线学习过程利用了“探索”的思想,其调整模型的粒度可以精细到每次获得反馈之后,这一点很像随机梯度下降的思路:虽然一次样本的结果可能产生随机扰动,但只要总的下降趋势是正确的,我们就能够通过海量的尝试最终达到最优点。DRN正是通过这种方式,让模型时刻与最“新鲜”的数据保持同步,实时地把最新的奖励信息融合进模型中。模型的每次“探索”和更新也就是我们之前提到的模型“微更新”。
|
||||
|
||||
到这里,我们就讲完了微软的深度强化学习模型DRN。我们可以想这样一个问题:这个模型本质上到底改进了什么?从我的角度来说,它最大的改进就是把模型推断、模型更新、推荐系统工程整个一体化了,让整个模型学习的过程变得更高效,能根据用户的实时奖励学到新知识,做出最实时的反馈。但同时,也正是因为工程和模型紧紧地耦合在一起,让强化学习在推荐系统中的落地并不容易。
|
||||
|
||||
既然,说到了强化学习的落地,这里我还想再多说几句。因为涉及到了模型训练、线上服务、数据收集、实时模型更新等几乎推荐系统的所有工程环节,所以强化学习整个落地过程的工程量非常大。这不像我们之前学过的深度学习模型,只要重新训练一下它,我们就可以改进一个模型结构,强化学习模型需要工程和研究部门通力合作才能实现。
|
||||
|
||||
在这个过程中,能不能有一个架构师一样的角色来通盘协调,就成为了整个落地过程的关键点。有一个环节出错,比如说模型在做完实时训练后,模型参数更新得不及时,那整个强化学习的流程就被打乱了,整体的效果就会受到影响。
|
||||
|
||||
所以对我们个人来说,掌握强化学习模型的框架,也就多了一个发展的方向。那对于团队来说,如果强化学习能够成功落地,也一定证明了这个团队有着极强的合作能力,在工程和研究方向上都有着过硬的实践能力。
|
||||
|
||||
## 小结
|
||||
|
||||
强化学习是近来在学术界和业界都很火的话题,它起源于机器人领域。这节课,我们要重点掌握强化学习的通用过程,以及它在深度学习中的应用细节。
|
||||
|
||||
简单来说,强化学习的通用过程就是训练一个智能体,让它通过与环境进行交互,不断学习强化自己的智力,并指导自己的下一步行动,以取得最大化的预期利益。这也让强化学习在模型实时更新,用户行为快速反馈等方向上拥有巨大的优势。
|
||||
|
||||
但强化学习的落地并不容易,整个落地过程的工程量非常大。现阶段,我们只需要以微软的DRN模型作为参考,重点掌握强化学习在推荐系统领域的应用细节就可以了。
|
||||
|
||||
一个是DRN构建了双塔模型作为深度推荐模型,来得出“行动得分”。第二个是DRN的更新方式,它利用“微更新”实时地学习用户的奖励反馈,更新推荐模型,再利用阶段性的“主更新”学习全量样本,更新模型。第三个是微更新时的方法,竞争梯度下降算法,它通过比较原网络和探索网络的实时效果,来更新模型的参数。
|
||||
|
||||
为了方便你复习,我们把这节课的重点知识总结在了下面的表格中,你可以看看。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/a6/15/a62dc5c0837f25bb8dd9bc2aa46e5c15.jpeg" alt="">
|
||||
|
||||
## 课后思考
|
||||
|
||||
DRN的微更新用到了竞争梯度下降算法,你觉得这个算法有没有弊端?你还知道哪些可以进行模型增量更新或者实时更新的方法吗?
|
||||
|
||||
欢迎把你的思考和疑问写在留言区,如果你的朋友们也在关注强化学习在推荐系统上的发展,那不妨也把这节课转发给他们,我们下节课见!
|
||||
@@ -0,0 +1,178 @@
|
||||
<audio id="audio" title="23| 实战:如何用深度学习模型实现Sparrow RecSys的个性化推荐功能?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/d9/7d/d936d8cfa7e89ee9cf3c719592defe7d.mp3"></audio>
|
||||
|
||||
你好,我是王喆。
|
||||
|
||||
今天又是一堂实战课。在这节课里,我会带你利用我们现阶段掌握的所有知识,来实现SparrowRecSys中“猜你喜欢”的功能。具体来说,我们会根据一位用户的历史行为,为TA推荐可能喜欢的电影。这个功能几乎会用到所有的推荐系统模块,包括离线的特征工程、模型训练以及线上的模型服务和推荐逻辑的实现。
|
||||
|
||||
如果说完成了[第14讲](https://time.geekbang.org/column/article/303641)的“相似电影”功能,还只是你“武功小成”的标志,那啃透了这节课的实践,就代表你掌握了推荐系统技术框架中的大部分内容,你就能在推荐系统的实际工作中做到“驾轻就熟”啦。
|
||||
|
||||
## “清点技能库”,看看我们已有的知识储备有哪些
|
||||
|
||||
正式开始实践之前,我们还是先来清点一次自己的技能库。看看经过推荐模型篇的学习,我们技能库中的“兵器”又增加了多少,哪些可以用来实现“猜你喜欢”这个功能。下面,我就按照从离线到线上,由数据到模型的顺序,为你依次梳理一下特征工程、模型离线训练、模型服务、推荐服务器逻辑这四大部分的技能点。
|
||||
|
||||
### 1. 模型特征工程
|
||||
|
||||
特征工程是所有机器学习项目的起点,咱们的推荐模型也不例外。为了训练推荐模型,我们需要准备好模型所需的样本和特征。此外,在进行模型线上推断的时候,推荐服务器也需要线上实时拼装好包含了用户特征、物品特征、场景特征的特征向量,发送给推荐模型进行实时推断。
|
||||
|
||||
在“[模型实战准备二](https://time.geekbang.org/column/article/308812)”这一讲,我们就通过Spark处理好了TensorFlow训练所需的训练样本,并把Spark处理好的特征插入了Redis特征数据库,供线上推断使用。不熟悉这部分内容的同学,最好再复习一下相关内容,把这把武器装进自己的技能库。
|
||||
|
||||
### 2. 模型离线训练
|
||||
|
||||
为了在线上做出尽量准确或者说推荐效果尽量好的排序,我们需要在离线训练好排序所用的推荐模型。
|
||||
|
||||
我们在这一篇中学习和实践的所有深度推荐模型,都是围绕着这个目的展开的。虽然这些深度推荐模型的结构各不相同,但它们的输入、输出都是一致的,输入是由不同特征组成的特征向量,输出是一个分数,这个分数的高低代表了这组特征对应的用户对物品的喜好程度。
|
||||
|
||||
具体实践的时候,我们在TensorFlow平台上实现了Embedding MLP、Wide&Deep、NeuralCF、双塔模型、DeepFM等几种不同的深度推荐模型,它们中的任何一个都可以支持“猜你喜欢”的排序功能。
|
||||
|
||||
在实际的工业级系统中,我们会通过离线、在线等不同的评估手段来挑出最好的那个模型,去支持真实的应用场景。在SparrowRecsys中,我们以NeuralCF模型为例,实现“猜你喜欢”功能。其他模型的上线方法与NeuralCF几乎一致,唯一的区别是,对于不同的模型来说,它们在模型服务的部分需要载入不同的模型文件,并且在线上预估的部分也要传入模型相应的输入特征。
|
||||
|
||||
### 3. 模型服务
|
||||
|
||||
模型服务是推荐系统中连接线上环境和线下环境的纽带之一(另一个关键的纽带是特征数据库)。
|
||||
|
||||
在离线训练好模型之后,为了让模型在线上发挥作用,做出实时的推荐排序,我们需要通过模型服务的模块把推荐模型部署上线。我们曾经在[第13讲](https://time.geekbang.org/column/article/303430)中详细介绍过主流的模型服务方法,它们是“预存推荐结果”“预训练Embedding+轻量级线上模型”“利用PMML转换和部署模型”以及“TensorFlow Serving”。因为我们这一篇的深度学习模型都是基于TensorFlow训练的,所以这节课我们也会采用TensorFlow Serving作为模型服务的方式。
|
||||
|
||||
### 4. 推荐服务器内部逻辑实现
|
||||
|
||||
模型服务虽然可以做到“猜你喜欢”中电影的排序,但要进行排序,仍然需要做大量的准备工作,比如候选集的获取,召回层的构建,特征的获取和拼装等等。这些推荐逻辑都是在推荐服务器内部实现的。推荐服务器就像推荐系统的线上的心脏,是所有线上模块的核心。
|
||||
|
||||
我们曾经在“相似电影”功能中实现过整套的推荐逻辑,今天我们重点关注其中不同的部分,就是特征的拼装,以及从推荐服务器内部请求模型服务API的方法。
|
||||
|
||||
至此,我们准备好了自己的技能库。接下来,就让我们使出十八般武艺,来打造“猜你喜欢”这个推荐功能吧。
|
||||
|
||||
## “猜你喜欢”推荐功能的技术架构
|
||||
|
||||
与“相似电影”功能一样,“猜你喜欢”相关的技术架构同样是由数据模型部分、线上部分和前端部分组成的。我们先来看看整个功能的技术架构图,再来说说每部分的具体实现细节。下图1 就是“猜你喜欢”功能的技术架构图,接下来,你就跟着我,按照从左上到右下的顺序,一起随着数据流的走向过一遍这个架构图吧。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/64/ee/642ca5a4260959fcce69b97000c3c4ee.jpg" alt="" title="图1 “猜你喜欢”功能的技术架构图">
|
||||
|
||||
首先,我们来看数据和模型部分。左上角是我们使用的数据集MovieLens,它经过Spark的处理之后,会生成两部分数据,分别从两个出口出去,特征部分会存入Redis供线上推断时推荐服务器使用,样本部分则提供给TensorFlow训练模型。
|
||||
|
||||
TensorFlow完成模型训练之后,会导出模型文件,然后模型文件会载入到TensorFlow Serving中,接着TensorFlow Serving会对外开放模型服务API,供推荐服务器调用。
|
||||
|
||||
接下来,我们再看推荐服务器部分。在这部分里,基于MovieLens数据集生成的候选电影集合会依次经过候选物品获取、召回层、排序层这三步,最终生成“猜你喜欢”的电影推荐列表,然后返回给前端,前端利用HTML和JavaScript把它们展示给用户。
|
||||
|
||||
整个过程中,除了排序层和TensorFlow Serving的实现,其他部分我们都已经在之前的实战中一一实现过。所以今天,我们会重点讲解推荐服务器排序层和TensorFlow Serving的实现。
|
||||
|
||||
## 排序层+TensorFlow Serving的实现
|
||||
|
||||
在推荐服务器内部,经过召回层之后,我们会得到几百量级的候选物品集。最后我们到底从这几百部电影中推荐哪些给用户,这个工作就交由排序层来处理。因为排序的工作是整个推荐系统提高效果的重中之重,在业界的实际应用中,往往交由评估效果最好的深度推荐模型来处理。整个的排序过程可以分为三个部分:
|
||||
|
||||
<li>
|
||||
准备线上推断所需的特征,拼接成JSON格式的特征样本;
|
||||
</li>
|
||||
<li>
|
||||
把所有候选物品的特征样本批量发送给TensorFlow Serving API;
|
||||
</li>
|
||||
<li>
|
||||
根据TensorFlow Serving API返回的推断得分进行排序,生成推荐列表。
|
||||
</li>
|
||||
|
||||
接下来,我们就详细来讲讲这三步中的实现重点。
|
||||
|
||||
**首先,第一步的实现重点在于特征样本的拼接**。因为实践例子里,我们选用了NeuralCF作为排序模型,而NerualCF所需的特征只有 `userId` 和 `itemId` ,所以特征是比较好准备的。我们下面看一下如何拼接特征形成模型推断所需的样本。详细的代码,你可以参考 com.wzhe.sparrowrecsys.online.recprocess.RecForYouProcess。
|
||||
|
||||
```
|
||||
/**
|
||||
* call TenserFlow serving to get the NeuralCF model inference result
|
||||
* @param user input user
|
||||
* @param candidates candidate movies
|
||||
* @param candidateScoreMap save prediction score into the score map
|
||||
*/
|
||||
public static void callNeuralCFTFServing(User user, List<Movie> candidates, HashMap<Movie, Double> candidateScoreMap){
|
||||
if (null == user || null == candidates || candidates.size() == 0){
|
||||
return;
|
||||
}
|
||||
//保存所有样本的JSON数组
|
||||
JSONArray instances = new JSONArray();
|
||||
for (Movie m : candidates){
|
||||
JSONObject instance = new JSONObject();
|
||||
//为每个样本添加特征,userId和movieId
|
||||
instance.put("userId", user.getUserId());
|
||||
instance.put("movieId", m.getMovieId());
|
||||
instances.put(instance);
|
||||
}
|
||||
JSONObject instancesRoot = new JSONObject();
|
||||
instancesRoot.put("instances", instances);
|
||||
//请求TensorFlow Serving API
|
||||
String predictionScores = asyncSinglePostRequest("http://localhost:8501/v1/models/recmodel:predict", instancesRoot.toString());
|
||||
//获取返回预估值
|
||||
JSONObject predictionsObject = new JSONObject(predictionScores);
|
||||
JSONArray scores = predictionsObject.getJSONArray("predictions");
|
||||
//将预估值加入返回的map
|
||||
for (int i = 0 ; i < candidates.size(); i++){
|
||||
candidateScoreMap.put(candidates.get(i), scores.getJSONArray(i).getDouble(0));
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
在代码中,我们先把 `userId` 和 `movieId` 加入了JSON格式的样本中,然后再把样本加入到Json数组中。接下来,我们又以 `http post` 请求的形式把这些JSON样本发送给TensorFlow Serving的API,进行批量预估。在收到预估得分后,保存在候选集 `map` 中,供排序层进行排序。
|
||||
|
||||
**第二步的重点在于如何建立起TensorFlow Serving API。**事实上,我们通过第13讲模型服务的实践部分,已经能够搭建起一个测试模型的API了。
|
||||
|
||||
想要搭建起我们自己的TensorFlow Serving API,只需要把之前载入的测试模型文件换成我们自己的模型文件就可以了。这里,我就以NerualCF模型为例,带你看一看模型文件是如何被导出和导入的。
|
||||
|
||||
首先是模型的导出。在NeuralCF的TensorFlow实现中,我们已经把训练好的模型保存在了 `model` 这个结构中,接下来需要调用 `tf.keras.models.save_model` 这一函数来把模型序列化。
|
||||
|
||||
从下面的代码中你可以看到,这一函数需要传入的参数有要保存的 `model` 结构,保存的路径,还有是否覆盖路径 `overwrite` 等等。其中,我们要注意的是保存路径。你可以看到,我在保存路径中加上了一个模型版本号002,这对于TensorFlow Serving是很重要的,因为TensorFlow Serving总是会找到版本号最大的模型文件进行载入,这样做就保证了我们每次载入的都是最新训练的模型。详细代码请你参考 NeuralCF.py。
|
||||
|
||||
```
|
||||
tf.keras.models.save_model(
|
||||
model,
|
||||
"file:///Users/zhewang/Workspace/SparrowRecSys/src/main/resources/webroot/modeldata/neuralcf/002",
|
||||
overwrite=True,
|
||||
include_optimizer=True,
|
||||
save_format=None,
|
||||
signatures=None,
|
||||
options=None
|
||||
)
|
||||
|
||||
```
|
||||
|
||||
其次是模型的导入,导入命令非常简单就是TensorFlow Serving API的启动命令,我们直接看下面命令中的参数。
|
||||
|
||||
```
|
||||
docker run -t --rm -p 8501:8501 -v "/Users/zhewang/Workspace/SparrowRecSys/src/main/resources/webroot/modeldata/neuralcf:/models/recmodel" -e MODEL_NAME=recmodel tensorflow/serving &
|
||||
|
||||
```
|
||||
|
||||
这里面最重要的参数,就是指定载入模型的路径和预估url,而载入路径就是我们刚才保存模型的路径:/Users/zhewang/Workspace/SparrowRecSys/src/main/resources/webroot/modeldata/neuralcf。但是在这里,我们没有加模型的版本号。这是为什么呢?因为版本号是供TensorFlow Serving查找最新模型用的,TensorFlow Serving在模型路径上会自动找到版本号最大的模型载入,因此不可以在载入路径上再加上版本号。
|
||||
|
||||
除此之外,冒号后的部分“/models/recmodel”指的是TensorFlow Serving API在这个模型上的具体url,刚才我们是通过请求http://localhost:8501/v1/models/recmodel:predict 获取模型预估值的,请求连接中的models/recmodel就是在这里设定的。
|
||||
|
||||
在正确执行上面的命令后,我们就可以在Docker上运行起TensorFlow Serving的API了。
|
||||
|
||||
**最后,我们来看第三步的实现重点:获取返回得分和排序。** 我们先来看一下TensorFlow Serving API的返回得分格式。它的返回值也是一个JSON 数组的格式,数组中每一项对应着之前发送过去的候选电影样本,所以我们只要把返回的预估值赋给相应的样本,然后按照预估值排序就可以了。详细的过程你也可以参考com.wzhe.sparrowrecsys.online.recprocess.RecForYouProcess中全部排序层的代码。
|
||||
|
||||
```
|
||||
{
|
||||
"predictions": [[0.824034274], [0.86393261], [0.921346784], [0.957705915], [0.875154734], [0.905113697], [0.831545711], [0.926080644], [0.898158073]...
|
||||
]
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
如果你已经正确建立起了Redis和TensorFlow Serving API服务,并且已经分别导入了特征数据和模型文件,我们就可以启动Sparrow Recsys Server,查看“猜你喜欢”的结果了。图2是用户ID为6的用户在NerualCF模型下的[推荐结果](http://localhost:6010/user.html?id=6&model=nerualcf),注意通过在连接中设置model变量为nerualcf,来决定产生结果的模型。
|
||||
|
||||
通过用户的评分历史(User Watched Movies)我们可以看到该用户偏向于观看动作类的电影,同时夹杂着一些爱情片和动画片,而在下方的“猜你喜欢”(Recommended For You)的结果中,我们也可以看到Sparrow Recsys为他推荐的电影也包含了这三类电影。有兴趣的话,你可以多在Sparrow Recsys里面逛一逛,看看推荐的结果在你眼中是不是合理。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/7f/7b/7f0d71486bf8ae09b18abf8a54db777b.png" alt="" title="图2 猜你喜欢功能的推荐结果">
|
||||
|
||||
## 小结
|
||||
|
||||
今天我们通过实现“猜你喜欢”功能串联起了我们之前所有学过的知识。希望在你看到推荐结果的时候,有种“武功大成,驾轻就熟”的感觉。要知道,这里面所有的代码都是你曾经学习过的,这里面每个结果都是你通过自己的所学生成的。希望你能在这里为自己鼓掌,这是一个不小的里程碑。
|
||||
|
||||
下面我们再重点总结一下今天实践用到的技术。首先,我们利用Spark对MovieLens原始数据进行了处理,生成了训练样本和特征,样本供TensorFlow进行模型训练,特征存入Redis供线上推断使用。
|
||||
|
||||
在TensorFlow平台上,我们以NeuralCF模型为例,训练并导出了NeuralCF的模型文件。然后使用TensorFlow Serving载入模型文件,建立线上模型服务API。推荐服务器的排序层从Redis中取出用户特征和物品特征,组装好JSON格式的特征数据,发送给TensorFlow Serving API,再根据返回的预估分数进行排序,最终生成“猜你喜欢”的推荐列表。
|
||||
|
||||
虽然我们实现了猜你喜欢的功能,但是课程进行到这里你一定会有一个疑问:我们的推荐结果到底是好还是坏呢?我们总不能总是人肉去查看结果好坏吧,这样效率又低,又不准确。没错,推荐系统的效果评估是有一套非常完整的评估体系的,别着急,从下一篇的模型评估篇开始,我们就会系统性地讲解推荐系统的评估方法,期待继续与你同行。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/93/3c/9364b714305ba0b26791db2805d5983c.jpg" alt="">
|
||||
|
||||
## 课后思考
|
||||
|
||||
推荐系统的特征预处理是一项很重要的工作,比如一些连续特征的归一化,分桶等等。那么这些预处理的过程,我们应该放在线上部分的哪里完成呢?是在Tensorflow Serving的部分,还是在推荐服务器内部,还是在离线部分完成?你有什么好的想法吗?
|
||||
|
||||
期待在留言区看到你的想法和思考,我们下一节课见!
|
||||
179
极客时间专栏/深度学习推荐系统实战/推荐模型篇/模型实战准备(一) | TensorFlow入门和环境配置.md
Normal file
179
极客时间专栏/深度学习推荐系统实战/推荐模型篇/模型实战准备(一) | TensorFlow入门和环境配置.md
Normal file
@@ -0,0 +1,179 @@
|
||||
<audio id="audio" title="模型实战准备(一) | TensorFlow入门和环境配置" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/be/59/bee51c9da7622cf5e0cf66a03b9d3a59.mp3"></audio>
|
||||
|
||||
你好,我是王喆。
|
||||
|
||||
我在留言区看到,很多同学对TensorFlow的用法还不太熟悉,甚至可能还没接触过它。但我们必须要熟练掌握TensorFlow,因为接下来,它会成为我们课程主要使用的工具和平台。为此,我特意准备了两堂模型实战准备课,来帮助你掌握TensorFlow的基础知识,以及为构建深度学习模型做好准备。
|
||||
|
||||
这节课,我们先来学习TensorFlow的环境配置,讲讲什么是TensorFlow,怎么安装TensorFlow,以及怎么在TensorFlow上构建你的第一个深度学习模型。下节课,我们再来学习模型特征和训练样本的处理方法。
|
||||
|
||||
## 什么是TensorFlow?
|
||||
|
||||
TensorFlow 是由 Google Brain团队开发的深度学习平台,于2015年11月首次发布,目前最新版本是2.3。因为TensorFlow自从2.0版本之后就发生了较大的变化,所以咱们这门课程会使用TensorFlow2.3作为实践版本。
|
||||
|
||||
TensorFlow这个名字还是很有意思的,翻译过来是“张量流动”,这非常准确地表达了它的基本原理,就是根据深度学习模型架构构建一个有向图,让数据以[张量](https://www.youtube.com/watch?v=f5liqUk0ZTw)的形式在其中流动起来。
|
||||
|
||||
这里的张量(Tensor)其实是指向量的高维扩展,比如向量可以看作是张量的一维形式,矩阵可以看作张量在二维空间上的特例。在深度学习模型中,大部分数据是以矩阵甚至更高维的张量表达的,为了让这些张量数据流动起来,每一个深度学习模型需要根据模型结构建立一个由点和边组成的有向图,图里面的点代表着某种操作,比如某个激活函数、某种矩阵运算等等,而边就定义了张量流动的方向。
|
||||
|
||||
这么说还是太抽象,我们来看一个简单TensorFlow任务的有向图。从图中我们可以看出,向量b、矩阵W和向量x是模型的输入,紫色的节点MatMul、Add和ReLU是操作节点,分别代表了矩阵乘法、向量加法、ReLU激活函数这样的操作。模型的输入张量W、b、x经过操作节点的变形处理之后,在点之间传递,这就是TensorFlow名字里所谓的张量流动。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/b8/f7/b80df3e43650d81ebca2608de27fdff7.jpeg" alt="" title="图1 一个简单的TensorFlow有向图">
|
||||
|
||||
事实上,任何复杂模型都可以抽象为操作有向图的形式。这样做不仅有利于操作的模块化,还可以厘清各操作间的依赖关系,有利于我们判定哪些操作可以并行执行,哪些操作只能串行执行,为并行平台能够最大程度地提升训练速度打下基础。
|
||||
|
||||
说起TensorFlow的并行训练,就不得不提GPU,因为GPU拥有大量的计算核心,所以特别适合做矩阵运算这类易并行的计算操作。业界主流的TensorFlow平台都是建立在CPU+GPU的计算环境之上的。但咱们这门课程因为考虑到大多数同学都是使用个人电脑进行学习的,所以所有的项目实践都建立在TensorFlow CPU版本上,我在这里先给你说明一下。
|
||||
|
||||
## TensorFlow的运行环境如何安装?
|
||||
|
||||
知道了TensorFlow的基本概念之后,我们就可以开始着手配置它的运行环境了。我先把这门课使用的环境版本告诉你,首先,[TensorFlow我推荐2.3版本](https://www.tensorflow.org/)也就是目前的最新版本 ,[Python我推荐3.7版本](https://www.python.org/),其实3.5-3.8都可以,我建议你在学习过程中不要随意更换版本,尽量保持一致。
|
||||
|
||||
虽然[Python环境](https://www.tensorflow.org/install/pip)和[TensorFlow环境](https://www.tensorflow.org/install)有自己的官方安装指南,但是安装起来也并不容易,所以接下来,我会带你梳理一遍安装中的重点步骤。一般来说,安装TensorFlow有两种方法,一种是采用Docker+Jupyter的方式,另一种是在本地环境安装TensorFlow所需的python环境和所需依赖库。其中,Docker+Jupyter的方式比较简单,我们先来看看它。
|
||||
|
||||
### 最简单的方法,Docker+Jupyter
|
||||
|
||||
经过[第13讲](https://time.geekbang.org/column/article/303430)模型服务的实践,我们已经把Docker安装到了自己的电脑上。这里,它就可以再次派上用场了。因为TensorFlow官方已经为我们准备好了它专用的Docker镜像,你只要运行下面两行代码,就可以拉取并运行最新的TensorFlow版本,还能在[http://localhost:8888/](http://localhost:8888/) 端口运行起 Jupyter Notebook。
|
||||
|
||||
```
|
||||
docker pull tensorflow/tensorflow:latest # Download latest stable image
|
||||
docker run -it -p 8888:8888 tensorflow/tensorflow:latest-jupyter # Start Jupyter server
|
||||
|
||||
|
||||
```
|
||||
|
||||
如果你已经能够在浏览器中打开Jupyter了,就在这个Notebook上开始你“肆无忌惮”地尝试吧。在之后的实践中,我们也可以把SparrowRecsys中的TensorFlow代码copy到Notebook上直接执行。因为不用操心非常复杂的python环境配置的过程,而且docker的运行与你的主系统是隔离的,如果把它“玩坏了”,我们再重新拉取全新的镜像就好,所以这个方式是最快捷安全的方式。
|
||||
|
||||
### 在IDEA中调试你的TensorFlow代码
|
||||
|
||||
不过,Docker+Jupyter的方式虽然非常方便,但我们在实际工作中,还是会更多地使用IDE来管理和维护代码。所以,掌握IDEA或者PyCharm这类IDE来调试TensorFlow代码的方法,对我们来说也非常重要。
|
||||
|
||||
比如说,SparrowRecSys中就包含了TensorFlow的Python代码,我把所有Python和TensorFlow相关的代码都放在了TFRecModel模块。那我们能否利用IDEA一站式地调试SparrowRecSys项目中的Python代码和之前的Java、Scala代码呢?当然是可以的。要实现这一操作,我们需要进行三步的配置,分别是安装IDEA的Python编译器插件,安装本地的Python环境,以及配置IDEA的Python环境。下面,我们一步一步地详细说一说。
|
||||
|
||||
**首先是安装IDEA的Python编译器插件。**
|
||||
|
||||
因为IDEA默认不支持Python的编译,所以我们需要为它安装Python插件。具体的安装路径是点击顶部菜单的IntelliJ IDEA -> Preferences -> Plugins -> 输入Python -> 选择插件 Python Community Edition进行安装。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/e3/bb/e394886105fyyc1d5da5f4a497d002bb.jpg" alt="" title="图2 为IDEA安装Python插件">
|
||||
|
||||
**接着是安装本地Python环境。**
|
||||
|
||||
使用过Python的同学可能知道,由于Python的流行版本比较多,不同软件对于Python环境的要求也不同,所以我们直接更改本地的Python环境比较危险,因此,我推荐你使用[Anaconda](https://www.anaconda.com/)来创建不同Python的虚拟环境,这样就可以为咱们的SparrowRecsys项目,专门创建一个使用Python3.7和支持TensorFlow2.3的虚拟Python环境了。
|
||||
|
||||
具体的步骤我推荐你参考Anaconda的[官方TensorFlow环境安装指导](https://docs.anaconda.com/anaconda/user-guide/tasks/tensorflow/)。
|
||||
|
||||
这里,我再带你梳理一下其中的关键步骤。首先,我们去[Anaconda的官方地址](https://www.anaconda.com/products/individual) 下载并安装Anaconda(最新版本使用Python3.8,你也可以去历史版本中安装Python3.7的版本)。然后,如果你是Windows环境,就打开Anaconda Command Prompt,如果是Mac或Linux环境,你就打开terminal。都打开之后,你跟着我输入下面的命令:
|
||||
|
||||
```
|
||||
conda create -n tf tensorflow
|
||||
conda activate tf
|
||||
|
||||
```
|
||||
|
||||
第一条命令会创建一个名字为tf的TensorFlow环境,第二条命令会让Anaconda为我们在这个Python环境中准备好所有Tensorflow需要的Python库依赖。接着,我们直接选择TensorFlow的CPU版本。不过,如果是有GPU环境的同学,可以把命令中的`tensorflow`替换为`tensorflow-gpu`,来安装支持GPU的版本。到这里,我们就利用Anaconda安装好了TensorFlow的Python环境。
|
||||
|
||||
**最后是配置IDEA的项目Python环境。**
|
||||
|
||||
现在IDEA的Python插件有了,本地的TensorFlow Python环境也有了,接下来,我们就要在IDEA中配置它的Python环境。这个配置的过程主要可以分成三步,我们一起来看看。
|
||||
|
||||
第一步,在IDEA中添加项目Python SDK。你直接按照我给出的这个路径配置就可以了:File->Project Structure -> SDKs ->点击+号->Add Python SDK ,这个路径在操作界面的显示如图3。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/99/bd/996c5740f59752918154c825ab0b01bd.jpg" alt="" title="图3 添加Python SDK">
|
||||
|
||||
添加完Python SDK之后,我们配置Conda Environment为项目的Python SDK。IDEA会自动检测到系统的Conda环境相关路径,你选择按照自动填充的路径就好,具体的操作你可以看下图4。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/62/cf/6220bfb6ce685d4bb2d5688e358a75cf.jpg" alt="" title="图4 选择Conda Enviroment为IDEA Python环境">
|
||||
|
||||
最后,我们为TFRecModel模块配置Python环境。我们选择Project Structure Modules部分的TFRecModel模块,在其上点击右键来Add Python。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/16/1a/164165823e10fede6114e4ac5244e71a.jpg" alt="" title="图5 为TFRecModel模块配置Python环境">
|
||||
|
||||
设置好的TFRecModel模块的Python环境应该如图6所示。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/47/c2/47105cd66e322260ae455a68cc9fc8c2.jpg" alt="" title="图6 配置好的Python环境">
|
||||
|
||||
如果你按照上面的步骤,那Python和TensorFlow的环境应该已经配置好了,但我们到底怎么验证一下所有的配置是否成功呢?下面,我们就来写一个测试模型来验证一下。
|
||||
|
||||
## 如何在TensorFlow上构建你的第一个深度学习模型?
|
||||
|
||||
这里,我们选择了TensorFlow官方教程上的例子作为我们深度学习模型的“初体验”,你可以参考[https://tensorflow.google.cn/tutorials/quickstart/beginner?hl=zh_cn](https://tensorflow.google.cn/tutorials/quickstart/beginner?hl=zh_cn)来构建这个模型。这里,我再对其中的关键内容做一个补充讲解。
|
||||
|
||||
我先把测试模型的代码放到了下面,你可以边看代码边参考我的讲解。首先,我们要清楚的是这个测试模型做了一件什么事情。因为它要处理的数据集是一个手写数字的MNIST数据集,所以模型其实是一个多分类模型,它的输入是这个手写数字的图片(一个28*28维的图片),输出是一个0-9的多分类概率模型。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/0c/44/0cfa5a28c6006d47e25fb6f1f7877f44.jpg" alt="" title="图7 MNIST数据集">
|
||||
|
||||
其次,我们要清楚如何定义这个深度学习模型的结构。从代码中我们可以看出,测试模型使用了TensorFlow的Keras接口,这样就可以很清晰方便地定义一个三层神经网络了,它包括28*28的输入层,128维的隐层,以及由softmax构成的多分类输出层。
|
||||
|
||||
之后是模型的训练,这里有3个参数的设定很关键,分别是compile函数中的optimizer、loss,以及fit函数中的epochs。其中,optimizer指的是模型训练的方法,这里我们采用了深度学习训练中经常采用的adam优化方法,你也可以选择随机梯度下降(SGD),自动变更学习率的AdaGrad,或者动量优化Momentum等等。
|
||||
|
||||
loss指的是损失函数,因为这个问题是一个多分类问题,所以这个测试模型,我们使用了多分类交叉熵(Sparse Categorical Crossentropy)作为损失函数。
|
||||
|
||||
epochs指的是训练时迭代训练的次数,单次训练指的是模型训练过程把所有训练数据学习了一遍。这里epochs=5意味着模型要反复5次学习训练数据,以便模型收敛到一个局部最优的位置。
|
||||
|
||||
最后是模型评估的过程,因为在构建模型时,我们选择了准确度(Accuracy)作为评估指标,所以model.evaluate函数会输出准确度的结果。
|
||||
|
||||
```
|
||||
import tensorflow as tf
|
||||
//载入MINST数据集
|
||||
mnist = tf.keras.datasets.mnist
|
||||
//划分训练集和测试集
|
||||
(x_train, y_train), (x_test, y_test) = mnist.load_data()
|
||||
x_train, x_test = x_train / 255.0, x_test / 255.0
|
||||
|
||||
|
||||
//定义模型结构和模型参数
|
||||
model = tf.keras.models.Sequential([
|
||||
//输入层28*28维矩阵
|
||||
tf.keras.layers.Flatten(input_shape=(28, 28)),
|
||||
//128维隐层,使用relu作为激活函数
|
||||
tf.keras.layers.Dense(128, activation='relu'),
|
||||
tf.keras.layers.Dropout(0.2),
|
||||
//输出层采用softmax模型,处理多分类问题
|
||||
tf.keras.layers.Dense(10, activation='softmax')
|
||||
])
|
||||
//定义模型的优化方法(adam),损失函数(sparse_categorical_crossentropy)和评估指标(accuracy)
|
||||
model.compile(optimizer='adam',
|
||||
loss='sparse_categorical_crossentropy',
|
||||
metrics=['accuracy'])
|
||||
|
||||
|
||||
//训练模型,进行5轮迭代更新(epochs=5)
|
||||
model.fit(x_train, y_train, epochs=5)
|
||||
//评估模型
|
||||
model.evaluate(x_test, y_test, verbose=2
|
||||
|
||||
```
|
||||
|
||||
到这里,我们就介绍完了整个测试模型的全部流程。事实上,之后利用TensorFlow构建深度学习推荐模型的过程也是非常类似的,只是把其中特征处理、模型结构、训练方法进行了替换,整体的结构并没有变化。所以,理解测试模型的每一步对我们来说是非常重要的。
|
||||
|
||||
说回我们的测试模型,如果Python和TensorFlow的环境都配置正确的话,在IDEA中执行测试模型程序,你会看到5轮训练过程,每一轮的准确度指标,以及最终模型的评估结果。
|
||||
|
||||
```
|
||||
Epoch 1/5
|
||||
1875/1875 [==============================] - 1s 527us/step - loss: 0.3007 - accuracy: 0.9121
|
||||
Epoch 2/5
|
||||
1875/1875 [==============================] - 1s 516us/step - loss: 0.1451 - accuracy: 0.9575
|
||||
Epoch 3/5
|
||||
1875/1875 [==============================] - 1s 513us/step - loss: 0.1075 - accuracy: 0.9670
|
||||
Epoch 4/5
|
||||
1875/1875 [==============================] - 1s 516us/step - loss: 0.0873 - accuracy: 0.9729
|
||||
Epoch 5/5
|
||||
1875/1875 [==============================] - 1s 517us/step - loss: 0.0748 - accuracy: 0.9767
|
||||
313/313 - 0s - loss: 0.0800 - accuracy: 0.9743
|
||||
|
||||
```
|
||||
|
||||
## 小结
|
||||
|
||||
这节课,我们一起学习了TensorFlow的基础知识,搭建起了TensorFlow的使用环境,并且编写了第一个基于Keras的深度学习模型。
|
||||
|
||||
TensorFlow的基本原理,就是根据深度学习模型架构构建一个有向图,让数据以张量的形式在其中流动起来。
|
||||
|
||||
而安装TensorFlow有两种方法,一种是采用Docker+Jupyter的方式,另一种是在本地环境安装TensorFlow所需的python环境和所需依赖库。其中,Docker+Jupyter的方式比较简单,而利用IDEA来调试TensorFlow代码的方法比较常用,我们要重点掌握。
|
||||
|
||||
想要实现在IDEA中调试TensorFlow代码,我们需要进行三步配置,分别是安装IDEA的python编译器插件,安装本地的Python环境,以及配置IDEA的Python环境。配置好了Python和TensorFlow的环境之后,我们还要验证一下是不是所有的配置都成功了。
|
||||
|
||||
在测试模型中,我们要牢牢记住实现它的四个主要步骤,分别是载入数据、定义模型、训练模型和评估模型。当然,我把这些关键知识点总结在了下面的知识表格里,你可以看看。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/d9/33/d906029b35925dd56c5c7270b330ce33.jpeg" alt="">
|
||||
|
||||
## 课后思考
|
||||
|
||||
这是一堂实践课,所以今天的课后题就是希望你能完成TensorFlow的环境配置。在配置、试验过程中遇到任何问题,你都可以在留言区提问,我会尽力去解答,也欢迎同学们分享自己的实践经验,互相帮助。我们下节课见!
|
||||
145
极客时间专栏/深度学习推荐系统实战/推荐模型篇/模型实战准备(二) | 模型特征、训练样本的处理.md
Normal file
145
极客时间专栏/深度学习推荐系统实战/推荐模型篇/模型实战准备(二) | 模型特征、训练样本的处理.md
Normal file
@@ -0,0 +1,145 @@
|
||||
<audio id="audio" title="模型实战准备(二) | 模型特征、训练样本的处理" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/6a/16/6ayyb82b166cb52af679fb562dc4e016.mp3"></audio>
|
||||
|
||||
你好,我是王喆,欢迎来到模型实战准备第二课。
|
||||
|
||||
这节课,我们来讲实战中所需的模型特征和训练样本的处理。为什么我们要专门花一节课的时间讲这些呢?因为在推荐模型篇中,我们的重点在于学习模型结构的原理和实现,而要实现并且训练这些模型,我们就必须先解决训练所需的样本和特征的处理问题。
|
||||
|
||||
这节课我们先来把模型实战的准备工作做完。具体来说,今天,我会带你用Spark来处理深度学习模型训练所需的样本和特征,再把特征存储到Redis中,供模型线上服务时调用。
|
||||
|
||||
## 为什么选择Spark为TensorFlow处理特征和样本?
|
||||
|
||||
这个时候,你可能会有疑问,我们的深度学习模型将在TensorFlow上进行训练,为什么要用Spark处理样本?可不可以直接让TensorFlow解决所有的事情呢?这是一个好问题,在我们学习具体的技术之前,先解决这个架构上的疑问是很有必要的。
|
||||
|
||||
在业界的实践中,我们需要记住一个原则,就是**让合适的平台做合适的事情**。比如说,数据处理是Spark的专长,流处理是Flink的专长,构建和训练模型是TensorFlow的专长。在使用这些平台的时候,我们最好能够用其所长,避其所短。这也是一个业界应用拥有那么多个模块、平台的原因。
|
||||
|
||||
你可能想说,TensorFlow也可以处理数据啊。没错,但是它并不擅长分布式的并行数据处理,在并行数据处理能力上,TensorFlow很难和动辄拥有几百上千个节点的Spark相比。那在面对海量数据的时候,如果我们能够利用Spark进行数据清洗、数据预处理、特征提取的话,最好的方案就是让Spark发挥它的长处,承担“繁重”但相对简单的样本和特征处理的工作,为TensorFlow减轻负担。
|
||||
|
||||
## 物品和用户特征都有哪些?
|
||||
|
||||
既然我们决定用Spark进行样本和特征处理,那下一个问题又接踵而来,我们能从MovieLens的数据集中抽取出什么特征呢?这就要用到我们在特征工程篇中学到的,关于推荐系统特征以及相关特征处理方法的知识,如果你记得还不够扎实,我建议你可以先回去复习一下。
|
||||
|
||||
MovieLens数据集中,可供我们提取特征的数据表有两个,分别是movies表和ratings表,它们的数据格式如下:
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/87/b8/87cd18e09550522e85ef81a8f30869b8.jpg" alt="" title="图1 电影基本数据movies表(左),用户评分数据ratings表(右)">
|
||||
|
||||
接下来,我们按照“物品特征”“用户特征”“场景特征”,这三大类推荐系统特征的顺序,来看一看从这两张表中能提取出什么样的特征。
|
||||
|
||||
“物品特征”在我们的项目里指的就是电影特征了,从movies表中我们可以提取出电影的基本信息,包括movieId、title(电影名)、releaseYear(发布年份)和genre(风格类型)。除此之外,我们还可以利用ratings表为每个电影提取出一些统计类的特征,包括电影的平均评分、评分标准差等等。
|
||||
|
||||
接下来是“用户特征”。乍一看,从movies和ratings表中,除了userId我们好像找不到其他直接可用的用户信息了。这个时候,千万不要忘了我们之前提到过的,用户特征最重要的部分就是历史行为特征。
|
||||
|
||||
所以,从用户的评分历史中,我们其实可以提取出非常多有价值的特征。比如,我们可以根据ratings表的历史联合movies表的电影信息,提取出用户统计类特征,它包括用户评分总数、用户平均评分、用户评分标准差、用户好评电影的发布年份均值、用户好评电影的发布年份标准差、用户最喜欢的电影风格,以及用户好评电影ID等等。
|
||||
|
||||
最后是“场景特征”。我们可用的场景特征就一个,那就是评分的时间戳,我们把它作为代表时间场景的特征放到特征工程中。
|
||||
|
||||
好了,到这儿,我们就梳理完了所有可用的特征,我把它们总结在了下面的表格里,供你参考。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/ae/d5/ae45f7d9020dcb406291b8519d0312d5.jpeg" alt="" title="图2 所有可用的特征汇总表">
|
||||
|
||||
用Spark来提取这些特征的总体实现会比较琐碎,所以我就不把全部代码贴在这里了,你可以参考SparrowRecsys项目中的com.wzhe.sparrowrecsys.offline.spark.featureeng.FeatureEngForRecModel对象,里面包含了所有特征工程的代码。这里,我们只讲几个有代表性的统计型特征的处理方法。
|
||||
|
||||
```
|
||||
val movieRatingFeatures = samplesWithMovies3.groupBy(col("movieId"))
|
||||
.agg(count(lit(1)).as("movieRatingCount"),
|
||||
avg(col("rating")).as("movieAvgRating"),
|
||||
stddev(col("rating")).as("movieRatingStddev"))
|
||||
|
||||
```
|
||||
|
||||
计算统计型特征的典型方法,就是利用Spark中的groupBy操作,将原始评分数据按照movieId分组,然后用agg聚合操作来计算一些统计型特征。比如,在上面的代码中,我们就分别使用了count内置聚合函数来统计电影评价次数(movieRatingCount),用avg函数来统计评分均值(movieAvgRating),以及使用stddev函数来计算评价分数的标准差(movieRatingStddev)。
|
||||
|
||||
特征处理具体过程,我们就讲完了。不过,这里我还想和你多分享一些我的经验。**一般来说,我们不会人为预设哪个特征有用,哪个特征无用,而是让模型自己去判断,如果一个特征的加入没有提升模型效果,我们再去除这个特征。就像我刚才虽然提取了不少特征,但并不是说每个模型都会使用全部的特征,而是根据模型结构、模型效果有针对性地部分使用它们。**在接下来的课程中,我们还会详细探讨不同模型对这些特征的具体使用方法。
|
||||
|
||||
## 最终的训练样本是什么样的?
|
||||
|
||||
特征提取之后就到了训练模型的步骤,为了训练模型,我们还需要生成模型所需的训练样本。这里我们需要明确两件事情,一是样本从哪里来,二是样本的标签是什么。这两件事情都跟我们训练模型的目标有关系。
|
||||
|
||||
对于一个推荐模型来说,它的根本任务是预测一个用户U对一个物品I在场景C下的喜好分数。所以在训练时,我们要为模型生成一组包含U、I、C的特征,以及最终真实得分的样本。在SparrowRecsys中,这样的样本就是基于评分数据ratings,联合用户、物品特征得来的。
|
||||
|
||||
其中,用户特征和物品特征都需要我们提前生成好,然后让它们与ratings数据进行join后,生成最终的训练样本,具体的实现也在FeatureEngForRecModel中,你可以先参考我在下面贴出的关键代码。这样,我们就解决了第一个关键问题。
|
||||
|
||||
```
|
||||
//读取原始ratings数据
|
||||
val ratingSamples = spark.read.format("csv").option("header", "true").load(ratingsResourcesPath.getPath)
|
||||
//添加样本标签
|
||||
val ratingSamplesWithLabel = addSampleLabel(ratingSamples)
|
||||
//添加物品(电影)特征
|
||||
val samplesWithMovieFeatures = addMovieFeatures(movieSamples, ratingSamplesWithLabel)
|
||||
//添加用户特征
|
||||
val samplesWithUserFeatures = addUserFeatures(samplesWithMovieFeatures)
|
||||
|
||||
```
|
||||
|
||||
接着,我们来看第二个关键问题,也就是样本的标签是什么,对于MovieLens数据集来说,用户对电影的评分是最直接的标签数据,因为它就是我们想要预测的用户对电影的评价,所以ratings表中的0-5的评分数据自然可以作为样本的标签。
|
||||
|
||||
但对于很多应用来说,我们基本上不可能拿到它们的评分数据,更多的是点击、观看、购买这些隐性的反馈数据,所以业界更多使用CTR预估这类解决二分类问题的模型去解决推荐问题。
|
||||
|
||||
为了让我们的实践过程更接近真实的应用场景,我也对MovieLens数据集进行了进一步处理。具体来说就是,把评分大于等于3.5分的样本标签标识为1,意为“喜欢”,评分小于3.5分的样本标签标识为0,意为“不喜欢”。这样一来,我们可以完全把推荐问题转换为CTR预估问题。
|
||||
|
||||
## 如何在生成样本时避免引入“未来信息”?
|
||||
|
||||
训练模型所需要的训练样本我们已经得到了,但是,在处理训练样本的时候,还有一个问题我们一定要注意,那就是引入未来信息(Future Information)的问题,这也是我们在实际工作中经常会遇到的问题。
|
||||
|
||||
什么叫做未来信息呢?如果我们在t时刻进行模型预测,那么t+1时刻的信息就是未来信息。这个问题在模型线上服务的时候是不存在的,因为未来的事情还未发生,我们不可能知道。但在离线训练的时候,我们就容易犯这样的错误。比如说,我们利用t时刻的样本进行训练,但是使用了全量的样本生成特征,这些特征就包含了t+1时刻的未来信息,这就是一个典型的引入未来信息的错误例子。
|
||||
|
||||
这样说可能还是有点抽象,我们用刚才讲过的特征举个例子。刚才我们说到有一个用户特征叫做用户平均评分(userAvgRating),我们通过把用户评论过的电影评分取均值得到它。假设,一个用户今年评论过三部电影,分别是11月1日评价电影A,评分为3分,11月2日评价电影B,评分为4分,11月3日评价电影C,评分为5分。如果让你利用电影B这条评价记录生成样本,样本中userAvgRating这个特征的值应该取多少呢?
|
||||
|
||||
有的同学会说,应该取评价过的电影评分的均值啊,(3+4+5)/3=4分,应该取4分啊。这就错了,因为在样本B发生的时候,样本C还未产生啊,它属于未来信息,你怎么能把C的评分也加进去计算呢?而且样本B的评分也不应该加进去,因为userAvgRating指的是历史评分均值,B的评分是我们要预估的值,也不可以加到历史评分中去,所以正确答案是3分,我们只能考虑电影A的评分。
|
||||
|
||||
因此,在处理历史行为相关的特征的时候,我们一定要考虑未来信息问题。类似的还有用户评分总数、用户评分标准差、用户最喜欢的电影风格、用户好评电影ID等一系列特征。
|
||||
|
||||
那在Spark中,我们应该如何处理这些跟历史行为相关的特征呢?这就需要用到window函数了。比如说,我在生成userAvgRating这个特征的时候,是使用下面的代码生成的:
|
||||
|
||||
```
|
||||
withColumn("userAvgRating", avg(col("rating"))
|
||||
.over(Window.partitionBy("userId")
|
||||
.orderBy(col("timestamp")).rowsBetween(-100, -1)))
|
||||
|
||||
|
||||
```
|
||||
|
||||
我们可以看到,代码中有一个`over(Window.partitionBy("userId").orderBy(col("timestamp")))`操作,它的意思是,在做rating平均这个操作的时候,我们不要对这个userId下面的所有评分取平均值,而是要创建一个滑动窗口,先把这个用户下面的评分按照时间排序,再让这个滑动窗口一一滑动,滑动窗口的位置始终在当前rating前一个rating的位置。这样,我们再对滑动窗口内的分数做平均,就不会引入未来信息了。
|
||||
|
||||
类似的操作,我使用在了所有与历史行为有关的特征中,你也可以在SparrowRecsys的源码中看到。
|
||||
|
||||
## 如何把特征数据存入线上供模型服务用?
|
||||
|
||||
在生成好特征和训练样本之后,还有一个问题需要我们解决,那就是特征的线上存储问题。因为训练样本是供离线训练使用的,而线上模型推断过程是要使用线上特征的。
|
||||
|
||||
好在,特征数据库Redis已经为我们提供了解决办法。我们把用户特征和物品特征分别存入Redis,线上推断的时候,再把所需的用户特征和物品特征分别取出,拼接成模型所需的特征向量就可以了。
|
||||
|
||||
FeatureEngForRecModel中的extractAndSaveUserFeaturesToRedis函数给出了详细的Redis操作,我把其中的关键操作放在了下面。
|
||||
|
||||
```
|
||||
val userKey = userFeaturePrefix + sample.getAs[String]("userId")
|
||||
val valueMap = mutable.Map[String, String]()
|
||||
valueMap("userRatedMovie1") = sample.getAs[String]("userRatedMovie1")
|
||||
valueMap("userRatedMovie2") = sample.getAs[String]("userRatedMovie2")
|
||||
...
|
||||
valueMap("userAvgRating") = sample.getAs[String]("userAvgRating")
|
||||
valueMap("userRatingStddev") = sample.getAs[String]("userRatingStddev")
|
||||
|
||||
|
||||
redisClient.hset(userKey, JavaConversions.mapAsJavaMap(valueMap))
|
||||
|
||||
```
|
||||
|
||||
我们可以看到,代码中使用了Redis一个新的操作hset,它的作用是把一个Map存入Redis。这样做有什么好处呢?对于这里的用户特征来说,Map中存储的就是特征的键值对,又因为这个Map本身是userId的值,所以,每个userId都拥有一组用户特征。这样一来,我们就可以在推荐服务器内部,通过userId来取出所有对应的用户特征了。当然,物品特征的储存方式是一样的。
|
||||
|
||||
到这里,我们完成了所有特征工程相关的准备工作,为之后的模型训练也做好了充足的准备。
|
||||
|
||||
## 小结
|
||||
|
||||
这节课,我们选择Spark作为特征和样本处理的平台,是因为Spark更擅长海量数据的分布式处理,为TensorFlow减轻数据处理的负担。在选择具体特征的过程中,我们遵循了“物品特征”“用户特征”“场景特征”这三大类特征分类方式,基于MovieLens的ratings表和movies表完成了特征抽取。
|
||||
|
||||
在样本处理过程中,我们选用评分和基于评分生成的好评差评标识作为样本标签,并基于ratings表的每条数据,通过联合物品和用户数据生成训练样本。在训练样本的生成中,要特别注意“未来信息”的问题,利用Spark中的window函数滑动生成历史行为相关特征。最后我们利用Redis的hset操作把线上推断用的特征存储Redis。
|
||||
|
||||
这些重点内容,我也都总结在了下面的表格里,你可以看一看。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/48/4d/48e15afe48dba3d35bef1e6f69aee84d.jpg" alt="">
|
||||
|
||||
## 课后思考
|
||||
|
||||
为了避免引入未来信息,咱们课程中讲了基于userId的window函数方法,你觉得还有哪些方法也能避免引入未来信息吗?
|
||||
|
||||
欢迎把你的思考和答案写在留言区,如果你觉得今天的内容对你有所帮助,也欢迎你分享给你的朋友或同事。我们下节课见!
|
||||
96
极客时间专栏/深度学习推荐系统实战/推荐模型篇/特别加餐 | “银弹”不可靠,最优的模型结构该怎么找?.md
Normal file
96
极客时间专栏/深度学习推荐系统实战/推荐模型篇/特别加餐 | “银弹”不可靠,最优的模型结构该怎么找?.md
Normal file
@@ -0,0 +1,96 @@
|
||||
<audio id="audio" title="特别加餐 | “银弹”不可靠,最优的模型结构该怎么找?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/1a/f1/1a5c171f40ce8405065c20debc6bf6f1.mp3"></audio>
|
||||
|
||||
你好,我是王喆。
|
||||
|
||||
推荐模型篇的课程到现在已经接近尾声了,我们也已经学习并且实践了六种深度学习推荐模型。最近,我发现很多同学会在留言区提出类似这样的问题:
|
||||
|
||||
- 老师,我的Wide&Deep模型在我的数据集上效果不好怎么办?
|
||||
- 老师,是不是DeepFM模型的效果会比NeuralCF好?
|
||||
- 老师,DIEN模型是不是现在效果最好的模型?
|
||||
|
||||
其实,我完全理解同学们提问题的心情,就是希望在工作中通过不断地改进模型找到一个最强的模型,来尽快地提升推荐效果,击败当前的模型。我们团队中所有的新人也几乎都有这样的想法。
|
||||
|
||||
但是真的存在一个万能的模型结构,能在所有推荐系统上都达成最好的推荐效果吗?这节课,我希望我们能够放缓脚步,务虚一点,好好思考一下到底什么才是最好的模型结构,以及算法工程师正确的工作方法是什么。
|
||||
|
||||
## 有解决推荐问题的“银弹”吗?
|
||||
|
||||
在软件工程领域的著作《人月神话》中,作者Brooks凭借自己在IBM管理2000人完成大型项目的经验,得出了一个结论:没有任何技术或管理上的进展, 能够独立地许诺十年内使软件系统项目生产率、 可靠性或简洁性获得数量级上的进步。
|
||||
|
||||
Brooks用“没有银弹”来形容这个结论。在欧洲的古老传说中,银色的子弹是能够一击杀死狼人这种怪物的特效武器。我们也可以把银弹理解成是最好的解决办法。“没有银弹”这个结论让很多期待寻找大型软件开发“捷径”的管理者和工程师们深感失望。但距离人月神话出版已经45年的今天,我们找到“银弹”了吗?
|
||||
|
||||
很遗憾,不仅在大型软件开发这个领域,“没有银弹”的观念深入人心,而且我要说的是,在推荐系统领域,也同样不存在能够一劳永逸解决问题的“银弹”,或者说根本不存在一种模型结构,它能够一击解决推荐效果问题,做到总是最优。
|
||||
|
||||
为什么这么讲呢?我们拿阿里的DIEN模型做一个例子。
|
||||
|
||||
我们在[第21讲](https://time.geekbang.org/column/article/313736)曾经详细介绍过DIEN模型,这里再做一个简要的回顾。DIEN模型的整体结构是一个加入了GRU序列模型的深度学习网络。其中,序列模型部分主要负责利用用户的历史行为序列,来预测用户下一步的购买兴趣,模型的其他部分则是Embedding MLP的结构,用来把用户的兴趣向量跟其他特征连接后进行预测。
|
||||
|
||||
由于阿里巴巴在业界巨大的影响力,DIEN模型自2019年提出以来,就被很多从业者认为是解决推荐问题的“银弹”,并纷纷进行尝试。
|
||||
|
||||
但是在应用的过程中,DIEN并没有体现出“银弹”的效果。在这个时候,大家又都习惯于从自身上找原因,比如说,“是不是Embedding层的维度不够”,“是不是应该再增加兴趣演化层的状态数量”,“是不是哪个模型参数没有调好”等等。包括我自己在实践的过程中,也会因为DIEN并没有产生预期的推荐效果提升,而去思考是不是因为我们没有完整的复现DIEN模型。
|
||||
|
||||
说了这么多,我想强调的是,所有提出类似问题的同行都默认了一个前提假设,就是在阿里巴巴的推荐场景下能够提高效果的DIEN模型,在其他应用场景下应该同样有效。然而,这个假设真的合理吗?DIEN模型是推荐系统领域的“银弹”吗?当然不是。接下来,我就带你一起分析一下。
|
||||
|
||||
既然DIEN的要点是模拟并表达用户兴趣进化的过程,那模型应用的前提必然是应用场景中存在着“兴趣进化”的过程。阿里巴巴的电商场景下,因为用户的购买兴趣在不同时间点有变化,所以有着明显的兴趣进化趋势。
|
||||
|
||||
比如说,用户在购买了电脑后,就有一定概率会购买电脑周边产品,用户在购买了某些类型的服装后,就会有一定概率选择与其搭配的其他服装。这些都是兴趣进化的直观例子,也是阿里巴巴的电商场景下非常典型的情况。
|
||||
|
||||
除此之外,我们还发现,在阿里巴巴的应用场景下,用户的兴趣进化路径能够被整个数据流近乎完整的保留。作为中国最大的电商集团,阿里巴巴各产品线组成的产品矩阵几乎能够完整地抓住用户购物过程中兴趣迁移的过程。当然,用户有可能去京东、拼多多等电商平台购物,从而打断阿里巴巴的兴趣进化过程,但从统计意义上讲,大量用户的购物链条还是可以被阿里巴巴的数据体系捕获的。
|
||||
|
||||
这样一来,我们就总结出了DIEN有效的前提是应用场景满足两个条件,一是应用场景存在“兴趣的进化”。二是用户兴趣的进化过程能够被数据完整捕获。如果二者中有一个条件不成立,DIEN就很可能不会带来较大的收益。
|
||||
|
||||
为什么这么说呢?还是以我自身的实践经历为例,我现在工作的公司Roku是北美最大的视频流媒体平台,在使用的过程中,用户既可以选择我们自己的频道和内容,也可以选择观看Netflix、YouTube,或者其他流媒体频道的内容。但是,一旦用户进入Netflix或者其他第三方应用,我们就无法得到应用中的具体数据了。
|
||||
|
||||
在这样的场景下,我们仅能获取用户一部分的观看、点击数据,而且这部分的数据仅占用户全部数据的10%左右,用户的整个行为过程我们无法完全获取到,那么谈何构建起整个兴趣进化链条呢?
|
||||
|
||||
另外一点也很关键,通过分析我们发现,长视频用户的兴趣点相比电商而言其实是非常稳定的。电商用户可以今天买一套衣服,明天买一套化妆品,兴趣的变化过程非常快。但你很难想象长视频用户今天喜欢看科幻电影,明天就喜欢看爱情片,绝大多数用户喜好非常稳定地集中在一个或者几个兴趣点上。这也是序列模型并不能给我们公司提高效果的另一个主要原因。
|
||||
|
||||
总的来说,通过DIEN的例子我们可以得出,**到底怎样的模型结构是最优的模型结构,跟你的业务特点和数据特点强相关。因此,在模型结构的选择上,没有“银弹”,没有最优,只有最合适。**
|
||||
|
||||
## 在工作中避免学生思维
|
||||
|
||||
那么有没有可供参考的方法论来指导模型结构的选择,或者说更广义一点,指导各种模型参数的调优呢?当然是有的,但在谈这个方法论之前,我想先纠正一个工作中非常有害的思维方式,特别是对于刚工作一两年的同学,我们最应该纠正的就是工作中的学生思维。
|
||||
|
||||
**“学生思维”最典型的表现就是总是在寻求一个问题的标准答案**。因为在我们的学生时代,所有的题目都是有标准答案的,会就是会,不会就是不会。
|
||||
|
||||
但在工作中就不一样了。举个例子来说,在讲Embedding的部分,很多同学问我Embedding到底应该取多少维?在实际的工作中,这就是一个典型的没有标准答案的问题。实际工作中,它应有的决策链条应该是下面这样的:
|
||||
|
||||
1. 先取一个初始值,比如说10维来尝试一下效果;
|
||||
1. 以10维Embedding的效果作为baseline,进行一定程度的参数调优,比如尝试5维和20维的Embedding,比较它跟10维的效果,确定更好的维度数;
|
||||
1. 如果项目时间和硬件条件允许,我们还可以尝试fine tunning(精细调参),直到找到最优的维度设置;
|
||||
1. 在上线前再次评估Embedding线上存储所需存储量的限制,如果线上存储的可用空间有限,我们可以通过适当降低维度数缩小Embedding所需的存储空间。
|
||||
|
||||
你从这个过程中肯定能够体会到,所谓Embedding的维度到底取多少这个问题。根本没有标准答案,最优的答案跟你的数据量,Embedding模型本身的结构都有关系,甚至还要受到工程环境的制约。
|
||||
|
||||
类似的问题还有“局部敏感哈希到底要选择几个桶”,“召回层的TopN中,N到底应该选择多少”,“Wide&Deep模型中可不可以加入新的用户特征”,“要不要在模型中加入正则化项,Drop out”等等。这些问题都没有标准答案,你只有通过实践中的尝试才能得到最优的设定。
|
||||
|
||||
其实这也是我在讲解这门课时一直秉承的原则,我希望把业界的主流方法告诉你,**期望你建立起来的是一套知识体系和方法论**,而不是一套能让你一劳永逸的模型参数,因为“银弹”并不存在。
|
||||
|
||||
## 算法工程师正确的工作方法
|
||||
|
||||
我们刚刚否定了“学生思维”,那该怎么建立起一套正确的算法工程师思维呢?下面是我总结出的一套通用的算法工程师的工作流程,虽然不能说我一定掌握了“正确”的方法,但这些工作方法都是从我多年的工作经验中总结出来的,也都得到了验证,你完全可以借助它们来完善自己的工作方法。
|
||||
|
||||
1. **问题提出** : 清楚领导提出的问题,或者自己发现问题。
|
||||
1. **数据和业务探索** : 在动手解决这个问题之前,我们一定要花时间弄清楚业务的相关逻辑,并且动手用一些脚本程序弄清楚自己可利用数据的数据量、数据特点、提取出一些特征并分析特征和标签之间的相关性。
|
||||
1. **初始解决方案** : 根据探索结果提出初始解决方案。
|
||||
1. **解决方案调优** :在初始解决方案之上,进行技术选型和参数调优,确定最终的解决方案。
|
||||
1. **工程落地调整** :针对工程上的限制调整技术方案,尽量做到能简勿繁,能稳定不冒险。
|
||||
1. **生产环境上线** : 进行最终的调整之后,在生产环境上线。
|
||||
1. **迭代与复盘** : 根据生产环境的结果进行迭代优化,并复盘这个过程,继续发现问题,解决问题。
|
||||
|
||||
最后我还想补充一点,我一直认为,**做算法工程师,首先要有扎实全面的技术功底,但更重要的其实是自信和务实的精神,不迷信所谓的权威模型,不试图寻找万能的参数,从业务出发,从用户的真实行为出发,才能够构建出最适合你业务场景的推荐模型** 。
|
||||
|
||||
## 小结
|
||||
|
||||
在解决推荐问题上,我认为是没有“银弹”的,特别是在模型结构这个点上,我们必须综合考虑自己的业务特点和数据特点,在实践中不断进行参数调优,才能找到最合适自己业务场景的模型。
|
||||
|
||||
事实上,不仅仅是推荐问题,对于其他问题来说,我也不建议同学们去追求所谓的“银弹”。换句话说,我们必须要尽量避免学生思维,不要总是试图去寻找标准答案,而是应该尽可能多地掌握业界的主流技术手段,丰富自己的“武器库”,建立自己的知识框架。这样,我们才能在面对实际工作中复杂问题的时候,找到最合适的兵器。
|
||||
|
||||
除此之外,作为一名算法工程师,我建议你在工作中按照“问题提出”,“数据和业务探索”,“提出初始解决方案”,“解决方案调优”,“工程落地调整”,“生产环境上线”,“迭代与复盘”的顺序,去完成整个的项目周期。这是能帮助你快速建立起正确方法论的有效途径。
|
||||
|
||||
总的来说,算法工程师是一份极有挑战的工作,相比研发岗有非常确定的项目目标,算法的优化工作往往需要我们自己去找到那个可以提升的点,自己去找到那组最合适的参数,并且可以完成生产环境的工程实现。这给了我们极大的灵活性,也给了我们不小的业绩压力。希望这节课可以帮助你纠正一些误区,与你共勉。
|
||||
|
||||
## 课后思考
|
||||
|
||||
推荐模型的研究可谓层出不穷,很多同学都热衷于追求实现最前沿的技术,最fancy的模型,你这样的吗?你认可这种现象吗?
|
||||
|
||||
欢迎把你的想法写在留言区,我们一起讨论!
|
||||
Reference in New Issue
Block a user