This commit is contained in:
louzefeng
2024-07-11 05:50:32 +00:00
parent bf99793fd0
commit d3828a7aee
6071 changed files with 0 additions and 0 deletions

View 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兴趣最相似的nTop 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的用户向量最近的两个视频向量它们分别是《Oceans 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(&quot;userIdInt&quot;)
.setItemCol(&quot;movieIdInt&quot;)
.setRatingCol(&quot;ratingFloat&quot;)
//训练模型
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分以下。你觉得这样的偏好对于推荐结果有影响吗我们能不能在算法中消除这种偏好呢
关于矩阵分解算法的实现你学会了吗?欢迎把你的疑问和思考分享到留言区,也欢迎你能把这节课转发出去,我们下节课见!

View 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&amp;Deep、阿里的MLR到现在影响力非常大的模型DIN、DIENYouTube的深度推荐模型等等。因此对于算法工程师来说紧跟业界的脚步去了解和掌握深度学习推荐模型是非常必要的。
那你可能想问了,深度学习推荐模型这么多,发展这么快,也没有一个统一的模板,我们该学哪个,怎么学呢?我想说的是,算法工程师的工作是一个持续优化和迭代的过程,如果想要追求更好的推荐效果,我们的思路不应该只局限于某一个被成功应用的模型,而是应该把眼光放得更高、更宽,去思考这些成功的推荐模型在业界下一步的发展方向是什么?有没有哪些其他的模型结构的思路可以借鉴。这些都是你在这个岗位上取得持续成功的关键。
那怎么才能做到这一点呢?我认为,只有建立起一个比较全面的深度学习模型知识库,我们才能在工作中做出正确的技术选择,为模型的下一步改进方向找到思路。
因此,这节课,我想和你深入聊一聊业界影响力非常大的深度学习推荐模型,以及它们之间的发展关系,带你从整体上建立起深度学习推荐模型的发展脉络。这不仅是我们建立行业知识储备的必需,也为我们后面实现深度推荐模型打下了基础。
## 深度学习对推荐系统的影响详解
在第一节课中,我们曾说过,**深度学习给推荐系统带来了革命性的影响**,能够显著提升推荐系统的效果,原因主要有两点,一是深度学习极大地增强了推荐模型的拟合能力,二是深度学习模型可以利用模型结构模拟用户兴趣的变迁、用户注意力机制等不同的用户行为过程。接下来,我们就结合这两点,来说说深度学习模型到强在哪里。
### 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 主流深度学习推荐模型的演化图谱">
首先我们来看整个演化图最中心部分这是深度学习最基础的结构我们叫它“多层神经网络”或者“多层感知机”简称MLPMultiLayer Perceptron。多层感知机的原理我们在[第3讲](https://time.geekbang.org/column/article/291245)中讲过,它就像一个黑盒,会对输入的特征进行深度地组合交叉,然后输出对兴趣值的预测。其他的深度推荐模型全都是在多层感知机的基础上,进行结构上的改进而生成的,所以**“多层感知机”是整个演化图的核心**。
从多层感知机向上还有一个重点模型我们需要知道那就是Deep Crossing。Deep Crossing实际上是一类经典深度学习模型的代表相比于MLPDeep Crossing在原始特征和MLP之间加入了Embedding层。这样一来输入的稀疏特征先转换成稠密Embedding向量再参与到MLP中进行训练这就解决了MLP不善于处理稀疏特征的问题。可以说**Embedding+MLP的结构是最经典也是应用最广的深度学习推荐模型结构**。
从MLP向下我们看到了Google提出的推荐模型Wide&amp;Deep。它把深层的MLP和单层的神经网络结合起来希望同时让网络具备很好的“记忆性”和“泛化性”。对“记忆性”和“泛化性”这两个名词陌生的同学也不用着急我们后面的课程会专门来讲解Wide&amp;Deep。
Wide&amp;Deep提出以来凭借着“易实现”“易落地”“易改造”的特点获得了业界的广泛应用。围绕着Wide&amp;Deep还衍生出了诸多变种比如通过改造Wide部分提出的Deep&amp;Cross和DeepFM通过改造Deep部分提出的AFM、NFM等等。总之**Wide&amp;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&amp;Deep模型为代表的一系列把不同结构组合在一起的改进思路。它通过组合两种甚至多种不同特点、优势互补的深度学习网络来提升模型的综合能力。
**四是让深度推荐模型和其他领域进行交叉。** 我们从DIN、DIEN、DRN等模型中可以看出深度推荐模型无时无刻不在从其他研究领域汲取新的知识。事实上这个过程从未停歇我们从今年的推荐系统顶会Recsys2020中可以看到NLP领域的著名模型Bert又与推荐模型结合起来并且产生了非常好的效果。一般来说自然语言处理、图像处理、强化学习这些领域都是推荐系统经常汲取新知识的地方。
总的来说,深度学习推荐模型的发展快、思路广,但每种模型都不是无本之木,它们的发展脉络都有迹可循。想要掌握好这些模型,在实际工作中做到拿来就用,我们就需要让这些模型脉络图像知识树一样扎根在心中,再通过不断地实践来掌握技术细节。
## 小结
这节课,我们通过学习深度学习对推荐系统的影响要素,以及经典深度学习模型之间的关系,初步建立起了深度学习模型的知识库。
我们知道,深度学习能够提升推荐系统的效果有两个关键因素,分别是它的“强拟合能力”和“结构的灵活性”。
对于“强拟合能力”来说,深度学习模型可以大大增加模型的“非线性”拟合能力,对复杂数据模型进行更准确的分类,避免“欠拟合”现象的发生,从而提升推荐效果。
对于“结构的灵活性”来说,深度学习模型可以通过灵活调整自身的结构,更轻松恰当地模拟人们的思考过程和行为过程,把用户猜得更透。
而整个深度学习推荐模型的演化过程是从最经典的多层神经网络向不同方向开枝散叶比如结合协同过滤发展出了NerualCF加入Embedding层发展出以Deep Crossing为代表的Embedding+MLP的结构以及把深度神经网络和单层网络结合起来发展出Wide&amp;Deep模型等等。
在这节课我们可以先忽略每个模型的细节着重建立一个整体的知识框架。之后的课程中我不仅会带你一一揭晓它们的技术细节还会利用TensorFlow实现其中几个经典的模型。期待继续与你一起学习
最后,我还是把这节课的重点知识梳理成了表格的形式,你可以借助它来复习巩固。
<img src="https://static001.geekbang.org/resource/image/be/ce/be5a662d3abd6a8dd5411038718d6cce.jpeg" alt="">
## 课后思考
有的同学说,深度学习这么流行,我把一些经典的深度模型结构实现好,肯定能提升我们公司推荐系统的效果,你觉得这种观点有问题吗?你觉得除了模型结构,还有哪些影响推荐效果的因素?为什么?
好啦,关于深度学习模型的知识库你建立起来了吗?欢迎把你的疑问和思考分享到留言区,也欢迎你能把这节课转发出去,我们下节课见!

View File

@@ -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 = &quot;file:///Users/zhewang/Workspace/SparrowRecSys/src/main/resources/webroot/sampledata/modelSamples.csv&quot;
samples_file_path = tf.keras.utils.get_file(&quot;modelSamples.csv&quot;, 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=&quot;?&quot;,
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实现深度推荐模型的朋友我们下节课见

View File

@@ -0,0 +1,117 @@
<audio id="audio" title="18Wide&Deep怎样让你的模型既有想象力又有记忆力" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/39/92/392da33b177987db0364c30d3efbc292.mp3"></audio>
你好,我是王喆。
今天我们来学习一个在业界有着巨大影响力的推荐模型Google的Wide&amp;Deep。可以说只要掌握了Wide&amp;Deep我们就抓住了深度推荐模型这几年发展的一个主要方向。那Wide&amp;Deep模型是什么意思呢我们把它翻译成中文就是“宽并且深的模型”。
这个名字看起来好像很通俗易懂但你真的理解其中“宽”和“深”的含义吗上一节课我们讲过Embedding+MLP的经典结构因为MLP可以有多层神经网络的结构所以它是一个比较“深”的模型但Wide&amp;Deep这个模型说的“深”和MLP是同样的意思吗“宽”的部分又是什么样的呢宽和深分别有什么不同的作用呢以及我们为什么要把它们结合在一起形成一个模型呢
这节课就让我们就带着这诸多疑问从模型的结构开始学起再深入到Wide&amp;Deep在Google的应用细节中去最后亲自动手实现这个模型。
## Wide&amp;Deep模型的结构
首先我们来看看Wide&amp;Deep模型的结构理解了结构再深入去学习细节我们才能掌握得更好。
<img src="https://static001.geekbang.org/resource/image/fb/e0/fb17112c951ebb2a515f12dace262de0.jpg" alt="" title="图1 Wide&amp;Deep模型结构 [br]出自Wide &amp; Deep Learning for Recommender Systems ">
上图就是Wide&amp;Deep模型的结构图了它是由左侧的Wide部分和右侧的Deep部分组成的。Wide部分的结构太简单了就是把输入层直接连接到输出层中间没有做任何处理。Deep层的结构稍复杂但我相信你也不会陌生因为它就是我们上节课学习的Embedding+MLP的模型结构。
知道了Wide&amp;Deep模型的结构之后我们先来解决第一个问题那就是Google为什么要创造这样一个混合式的模型结构呢这我们还得从Wide部分和Deep部分的不同作用说起。
简单来说Wide部分的主要作用是让模型具有较强的“记忆能力”Memorization而Deep部分的主要作用是让模型具有“泛化能力”Generalization因为只有这样的结构特点才能让模型兼具逻辑回归和深度神经网络的优点也就是既能快速处理和记忆大量历史行为特征又具有强大的表达能力这就是Google提出这个模型的动机。
那么问题又来了所谓的“记忆能力”和“泛化能力”到底指什么呢这我们就得好好聊一聊了因为理解这两种能力是彻底理解Wide&amp;Deep模型的关键。
### 模型的记忆能力
**所谓的 “记忆能力”,可以被宽泛地理解为模型直接学习历史数据中物品或者特征的“共现频率”,并且把它们直接作为推荐依据的能力 。** 就像我们在电影推荐中可以发现一系列的规则比如看了A电影的用户经常喜欢看电影B这种“因为A所以B”式的规则非常直接也非常有价值。
但这类规则有两个特点一是数量非常多一个“记性不好”的推荐模型很难把它们都记住二是没办法推而广之因为这类规则非常具体没办法或者说也没必要跟其他特征做进一步的组合。就像看了电影A的用户80%都喜欢看电影B这个特征已经非常强了我们就没必要把它跟其他特征再组合在一起。
现在我们就可以回答开头的问题了为什么模型要有Wide部分就是因为Wide部分可以增强模型的记忆能力让模型记住大量的直接且重要的规则这正是单层的线性模型所擅长的。
### 模型的泛化能力
接下来,我们来谈谈模型的“泛化能力”。**“泛化能力”指的是模型对于新鲜样本、以及从未出现过的特征组合的预测能力。** 这怎么理解呢我们还是来看一个例子。假设我们知道25岁的男性用户喜欢看电影A35岁的女性用户也喜欢看电影A。如果我们想让一个只有记忆能力的模型回答“35岁的男性喜不喜欢看电影A”这样的问题这个模型就会“说”我从来没学过这样的知识啊没法回答你。
这就体现出泛化能力的重要性了。模型有了很强的泛化能力之后,才能够对一些非常稀疏的,甚至从未出现过的情况作出尽量“靠谱”的预测。
回到刚才的例子有泛化能力的模型回答“35岁的男性喜不喜欢看电影A”这个问题它思考的逻辑可能是这样的从第一条知识“25岁的男性用户喜欢看电影A“中我们可以学到男性用户是喜欢看电影A的。从第二条知识“35岁的女性用户也喜欢看电影A”中我们可以学到35岁的用户是喜欢看电影A的。那在没有其他知识的前提下35岁的男性同时包含了合适的年龄和性别这两个特征所以他大概率也是喜欢电影A的。这就是模型的泛化能力。
事实上,我们学过的矩阵分解算法,就是为了解决协同过滤“泛化能力”不强而诞生的。因为协同过滤只会“死板”地使用用户的原始行为特征,而矩阵分解因为生成了用户和物品的隐向量,所以就可以计算任意两个用户和物品之间的相似度了。这就是泛化能力强的另一个例子。
从上节课中我们学过深度学习模型有很强的数据拟合能力在多层神经网络之中特征可以得到充分的交叉让模型学习到新的知识。因此Wide&amp;Deep模型的Deep部分就沿用了上节课介绍的Embedding+MLP的模型结构来增强模型的泛化能力。
清楚了记忆能力和泛化能力是什么之后让我们再回到Wide&amp;Deep模型提出时的业务场景上去理解Wide&amp;Deep模型是怎么综合性地学习到记忆能力和泛化能力的。
## Wide&amp;Deep模型的应用场景
Wide&amp;Deep模型是由Google的应用商店团队Google Play提出的在Google Play为用户推荐APP这样的应用场景下Wide&amp;Deep模型的推荐目标就显而易见了就是应该尽量推荐那些用户可能喜欢愿意安装的应用。那具体到Wide&amp;Deep模型中Google Play团队是如何为Wide部分和Deep部分挑选特征的呢下面我们就一起来看看。
<img src="https://static001.geekbang.org/resource/image/4b/be/4b2f89d82768ba851e3f7392b17df2be.jpg" alt="" title="图2 Google Play Wide&amp;Deep模型的细节 [br]出自Wide &amp; Deep Learning for Recommender Systems ">
我们先来看看图2它补充了Google Play Wide&amp;Deep模型的细节让我们可以清楚地看到各部分用到的特征是什么。我们先从右边Wide部分的特征看起。这部分很简单只利用了两个特征的交叉这两个特征是“已安装应用”和“当前曝光应用”。这样一来Wide部分想学到的知识就非常直观啦就是希望记忆好“如果A所以B”这样的简单规则。在Google Play的场景下就是希望记住“如果用户已经安装了应用A是否会安装B”这样的规则。
接着我们再来看看左边的Deep部分它就是一个非常典型的Embedding+MLP结构了。我们看到其中的输入特征很多有用户年龄、属性特征、设备类型还有已安装应用的Embedding等等。我们把这些特征一股脑地放进多层神经网络里面去学习之后它们互相之间会发生多重的交叉组合这最终会让模型具备很强的泛化能力。
比如说我们把用户年龄、人口属性和已安装应用组合起来。假设样本中有25岁男性安装抖音的记录也有35岁女性安装抖音的记录那我们该怎么预测25岁女性安装抖音的概率呢这就需要用到已有特征的交叉来实现了。虽然我们没有25岁女性安装抖音的样本但模型也能通过对已有知识的泛化经过多层神经网络的学习来推测出这个概率。
总的来说Wide&amp;Deep通过组合Wide部分的线性模型和Deep部分的深度网络取各自所长就能得到一个综合能力更强的组合模型。
## Wide&amp;Deep模型的TensorFlow实现
在理解了Wide&amp;Deep模型的原理和技术细节之后就又到了“show me the code”的环节了。接下来我们就动手在Sparrow Recsys上实现Wide&amp;Deep模型吧
通过上节课的实战我相信你已经熟悉了TensorFlow的大部分操作也清楚了载入训练数据创建TensorFlow所需的Feature column的方法。因此Wide&amp;Deep模型的实践过程中我们会重点关注定义模型的部分。
这里我们也会像上节课一样继续使用TensorFlow的Keras接口来构建Wide&amp;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&amp;Deep是非常方便的Deep部分基本没有变化我们只需要加上Wide部分的特征和设置就可以了。Wide&amp;Deep的全部相关代码我都实现在了Sparrow Recsys的WideNDeep.py文件中你可以直接参考源代码。但我更希望你能尝试设置不同的特征以及不同的参数组合来真实地体验一下深度学习模型的调参过程。
## 小结
这节课我们一起实现了业界影响力非常大的深度学习模型Wide&amp;Deep它是由Wide部分和Deep部分组成的。其中Wide部分主要是为了增强模型的“记忆能力”让模型记住“如果A那么B”这样的简单但数量非常多的规则。Deep部分是为了增强模型的“泛化能力”让模型具备对于稀缺样本、以及从未出现过的特征组合的预测能力。Wide&amp;Deep正是通过这样取长补短的方式让模型的综合能力提升。
在具体实践的时候我们继续使用TensorFlow的Keras接口实现了Wide&amp;Deep模型。相比上节课Embedding MLP模型的实现我们新加入了“用户已好评电影”和“当前评价电影”组成的交叉特征crossed_feature让Wide部分学习“一个喜欢电影A的用户也会喜欢电影B”这样的规则。
好了,这就是我们这节课的主要内容,同样,我也把重要的知识点总结在了表格里,你可以利用它来巩固复习。
<img src="https://static001.geekbang.org/resource/image/1d/12/1d5985d8e9b7d92a87baa80f619a8a12.jpeg" alt="">
## 课后思考
对于Deep部分来说你觉得我们一股脑地把所有特征都扔进MLP中去训练这样的方式有没有什么改进的空间比如说“用户喜欢的电影风格”和“电影本身的风格”这两个特征我们能不能进一步挖掘出它们之间的相关性而不是简单粗暴地扔给神经网络去处理呢
欢迎把你的思考和疑问写在留言区如果的你朋友也正在为Wide&amp;Deep模型的实现而困扰欢迎你把这节课转发给他我们下节课见

View File

@@ -0,0 +1,122 @@
<audio id="audio" title="19NeuralCF如何用深度学习改造协同过滤" 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&amp;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&amp;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&amp;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&amp;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存起来就可以进行线上服务了。但如果我们把一些场景特征比如当前时间、当前地点加到用户侧或者物品侧那我们还能用这种方式进行模型服务吗为什么
欢迎把你的思考和疑惑写在留言区,也欢迎你把这节课转发出去,我们下节课见!

View 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&amp;Deep、NerualCF等几种不同的模型结构。你有没有深入思考过这样一个问题这几种模型都是怎么处理特征交叉这个问题的
比如说模型的输入有性别、年龄、电影风格这几个特征在训练样本中我们发现有25岁男生喜欢科幻电影的样本有35岁女生喜欢看恐怖电影的样本那你觉得模型应该怎么推测“25岁”的女生喜欢看的电影风格呢
事实上,这类特征组合和特征交叉问题非常常见,而且在实际应用中,特征的种类还要多得多,特征交叉的复杂程度也要大得多。**解决这类问题的关键,就是模型对于特征组合和特征交叉的学习能力,因为它决定了模型对于未知特征组合样本的预测能力,而这对于复杂的推荐问题来说,是决定其推荐效果的关键点之一。**
但无论是Embedding MLP还是Wide&amp;Deep其实都没有对特征交叉进行特别的处理而是直接把独立的特征扔进神经网络让它们在网络里面进行自由组合就算是NeuralCF也只在最后才把物品侧和用户侧的特征交叉起来。那这样的特征交叉方法是高效的吗深度学习模型有没有更好的处理特征交叉的方法呢
这节课我们就一起来解决这些问题。同时我还会基于特征交叉的思想带你学习和实现一种新的深度学习模型DeepFM。
## 为什么深度学习模型需要加强处理特征交叉的能力?
不过在正式开始今天的课程之前我还想和你再深入聊聊为什么深度学习需要加强处理特征交叉的能力。我们刚才说Embedding MLP和Wide&amp;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&amp;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&amp;Deep组合模型的思想用FM替换了Wide&amp;Deep左边的Wide部分加强了浅层网络部分特征组合的能力而右边的部分跟Wide&amp;Deep的Deep部分一样主要利用多层神经网络进行所有特征的深层处理最后的输出层是把FM部分的输出和Deep部分的输出综合起来产生最后的预估结果。这就是DeepFM的结构。
## 特征交叉新方法:元素积操作
接下来我们再思考一个问题FM和DeepFM中进行特征交叉的方式都是进行Embedding向量的点积操作那是不是说特征交叉就只能用点积操作了
答案当然是否定的。事实上还有很多向量间的运算方式可以进行特征的交叉比如模型NFMNeural 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&amp;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&amp;Deep模型的Deep部分完全一样。只不过最终我们会使用concatenate层去把FM部分的输出和Deep部分的输出连接起来输入到输出层的sigmoid神经元从而产生最终的预估分数。那关于DeepFM的全部代码你可以参照SparrowRecsys项目中的DeepFM.py文件。
## 小结
DeepFM模型在解决特征交叉问题上非常有优势它会使用一个独特的FM层来专门处理特征之间的交叉问题。具体来说就是使用点积、元素积等操作让不同特征之间进行两两组合再把组合后的结果输入的输出神经元中这会大大加强模型特征组合的能力。因此DeepFM模型相比于Embedding MLP、Wide&amp;Deep等模型往往具有更好的推荐效果。
实现DeepFM模型的过程并不困难我们主要记住三点就可以了
- 它是由FM和Deep两部分组成的
- 在实现FM部分特征交叉层的时候我们使用了多个Dot Product操作单元完成不同特征的两两交叉
- Deep部分则与Wide&amp;Deep模型一样负责所有输入特征的深度拟合提高模型整体的表达能力
刚才说的重点知识,我都整理在了下面的表格中,你可以看一看。
<img src="https://static001.geekbang.org/resource/image/4d/76/4dbb2c9760199311b38b32a15daba176.jpeg" alt="">
好了到今天这节课我们已经在SparrowRecsys中实现了四个深度学习模型相信你对TensorFlow的Keras接口也已经十分熟悉了。我希望你不只满足于读懂、用好SparrowRecsys中实现好的模型而是真的在课后自己多去尝试不同的特征输入不同的模型结构甚至可以按照自己的理解和思考去改进这些模型。
因为深度学习模型结构没有标准答案,我们只有清楚不同模型之间的优缺点,重点汲取它们的设计思想,才能在实际的工作中结合自己遇到的问题,来优化和改造已有的模型。也只有这样,你们才能成为一名能真正解决实际问题的算法工程师。
## 课后思考
你觉得除了点积和元素积这两个操作外还有没有其他的方法能处理两个Embedding向量间的特征交叉
关于深度学习中特征交叉问题的处理方法你是不是学会了欢迎把你的思考和疑问写在留言区如果你的朋友也对DeepFM这个模型感兴趣那不妨也把这节课转发给他我们下节课见

View 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&amp;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)
价值是非常大的。比如说,我们要做一个新闻推荐的模型,让这个模型根据用户已经看过的新闻做推荐。那我们在分析用户已浏览新闻的时候,是把标题、首段、全文的重要性设置成完全一样比较好,还是应该根据用户的注意力不同给予不同的权重呢?当然,肯定是后者比较合理,因为用户很可能都没有注意到正文最后的几段,如果你分析内容的时候把最后几段跟标题、首段一视同仁,那肯定就把最重要的信息给淹没了。
事实上近年来注意力机制已经成功应用在各种场景下的推荐系统中了。其中最知名的要数阿里巴巴的深度推荐模型DINDeep 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模型的演化版本也就是深度兴趣进化网络DIENDeep 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&amp;rep=rep1&amp;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使用了一个结构比较复杂的激活单元来计算注意力权重你觉得有没有更简单、更实用的方式来生成注意力权重呢其实计算注意力权重就是为了计算历史行为物品和广告物品的相关性在这个过程中你觉得能不能利用到特征交叉的知识呢为什么
欢迎把你的思考和疑问写在留言区,如果你的朋友们也在关注注意力机制和兴趣演化序列模型的发展,那不妨也把这节课转发给他们,我们下节课见!

View 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模型为例来给你讲一讲在推荐系统中强化学习的六大要素都是什么强化学习具体又是怎样应用在推荐系统中的。
## 强化学习推荐系统框架
强化学习推荐模型DRNDeep 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的微更新用到了竞争梯度下降算法你觉得这个算法有没有弊端你还知道哪些可以进行模型增量更新或者实时更新的方法吗
欢迎把你的思考和疑问写在留言区,如果你的朋友们也在关注强化学习在推荐系统上的发展,那不妨也把这节课转发给他们,我们下节课见!

View File

@@ -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&amp;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&lt;Movie&gt; candidates, HashMap&lt;Movie, Double&gt; 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(&quot;userId&quot;, user.getUserId());
instance.put(&quot;movieId&quot;, m.getMovieId());
instances.put(instance);
}
JSONObject instancesRoot = new JSONObject();
instancesRoot.put(&quot;instances&quot;, instances);
//请求TensorFlow Serving API
String predictionScores = asyncSinglePostRequest(&quot;http://localhost:8501/v1/models/recmodel:predict&quot;, instancesRoot.toString());
//获取返回预估值
JSONObject predictionsObject = new JSONObject(predictionScores);
JSONArray scores = predictionsObject.getJSONArray(&quot;predictions&quot;);
//将预估值加入返回的map
for (int i = 0 ; i &lt; 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,
&quot;file:///Users/zhewang/Workspace/SparrowRecSys/src/main/resources/webroot/modeldata/neuralcf/002&quot;,
overwrite=True,
include_optimizer=True,
save_format=None,
signatures=None,
options=None
)
```
其次是模型的导入导入命令非常简单就是TensorFlow Serving API的启动命令我们直接看下面命令中的参数。
```
docker run -t --rm -p 8501:8501 -v &quot;/Users/zhewang/Workspace/SparrowRecSys/src/main/resources/webroot/modeldata/neuralcf:/models/recmodel&quot; -e MODEL_NAME=recmodel tensorflow/serving &amp;
```
这里面最重要的参数就是指定载入模型的路径和预估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中全部排序层的代码。
```
{
&quot;predictions&quot;: [[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&amp;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的部分还是在推荐服务器内部还是在离线部分完成你有什么好的想法吗
期待在留言区看到你的想法和思考,我们下一节课见!

View 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 -&gt; Preferences -&gt; Plugins -&gt; 输入Python -&gt; 选择插件 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-&gt;Project Structure -&gt; SDKs -&gt;点击+号-&gt;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的环境配置。在配置、试验过程中遇到任何问题你都可以在留言区提问我会尽力去解答也欢迎同学们分享自己的实践经验互相帮助。我们下节课见

View 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(&quot;movieId&quot;))
.agg(count(lit(1)).as(&quot;movieRatingCount&quot;),
avg(col(&quot;rating&quot;)).as(&quot;movieAvgRating&quot;),
stddev(col(&quot;rating&quot;)).as(&quot;movieRatingStddev&quot;))
```
计算统计型特征的典型方法就是利用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(&quot;csv&quot;).option(&quot;header&quot;, &quot;true&quot;).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(&quot;userAvgRating&quot;, avg(col(&quot;rating&quot;))
.over(Window.partitionBy(&quot;userId&quot;)
.orderBy(col(&quot;timestamp&quot;)).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](&quot;userId&quot;)
val valueMap = mutable.Map[String, String]()
valueMap(&quot;userRatedMovie1&quot;) = sample.getAs[String](&quot;userRatedMovie1&quot;)
valueMap(&quot;userRatedMovie2&quot;) = sample.getAs[String](&quot;userRatedMovie2&quot;)
...
valueMap(&quot;userAvgRating&quot;) = sample.getAs[String](&quot;userAvgRating&quot;)
valueMap(&quot;userRatingStddev&quot;) = sample.getAs[String](&quot;userRatingStddev&quot;)
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函数方法你觉得还有哪些方法也能避免引入未来信息吗
欢迎把你的思考和答案写在留言区,如果你觉得今天的内容对你有所帮助,也欢迎你分享给你的朋友或同事。我们下节课见!

View 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&amp;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&amp;Deep模型中可不可以加入新的用户特征”“要不要在模型中加入正则化项Drop out”等等。这些问题都没有标准答案你只有通过实践中的尝试才能得到最优的设定。
其实这也是我在讲解这门课时一直秉承的原则,我希望把业界的主流方法告诉你,**期望你建立起来的是一套知识体系和方法论**,而不是一套能让你一劳永逸的模型参数,因为“银弹”并不存在。
## 算法工程师正确的工作方法
我们刚刚否定了“学生思维”,那该怎么建立起一套正确的算法工程师思维呢?下面是我总结出的一套通用的算法工程师的工作流程,虽然不能说我一定掌握了“正确”的方法,但这些工作方法都是从我多年的工作经验中总结出来的,也都得到了验证,你完全可以借助它们来完善自己的工作方法。
1. **问题提出** 清楚领导提出的问题,或者自己发现问题。
1. **数据和业务探索** 在动手解决这个问题之前,我们一定要花时间弄清楚业务的相关逻辑,并且动手用一些脚本程序弄清楚自己可利用数据的数据量、数据特点、提取出一些特征并分析特征和标签之间的相关性。
1. **初始解决方案** 根据探索结果提出初始解决方案。
1. **解决方案调优** :在初始解决方案之上,进行技术选型和参数调优,确定最终的解决方案。
1. **工程落地调整** :针对工程上的限制调整技术方案,尽量做到能简勿繁,能稳定不冒险。
1. **生产环境上线** 进行最终的调整之后,在生产环境上线。
1. **迭代与复盘** 根据生产环境的结果进行迭代优化,并复盘这个过程,继续发现问题,解决问题。
最后我还想补充一点,我一直认为,**做算法工程师,首先要有扎实全面的技术功底,但更重要的其实是自信和务实的精神,不迷信所谓的权威模型,不试图寻找万能的参数,从业务出发,从用户的真实行为出发,才能够构建出最适合你业务场景的推荐模型** 。
## 小结
在解决推荐问题上,我认为是没有“银弹”的,特别是在模型结构这个点上,我们必须综合考虑自己的业务特点和数据特点,在实践中不断进行参数调优,才能找到最合适自己业务场景的模型。
事实上,不仅仅是推荐问题,对于其他问题来说,我也不建议同学们去追求所谓的“银弹”。换句话说,我们必须要尽量避免学生思维,不要总是试图去寻找标准答案,而是应该尽可能多地掌握业界的主流技术手段,丰富自己的“武器库”,建立自己的知识框架。这样,我们才能在面对实际工作中复杂问题的时候,找到最合适的兵器。
除此之外,作为一名算法工程师,我建议你在工作中按照“问题提出”,“数据和业务探索”,“提出初始解决方案”,“解决方案调优”,“工程落地调整”,“生产环境上线”,“迭代与复盘”的顺序,去完成整个的项目周期。这是能帮助你快速建立起正确方法论的有效途径。
总的来说,算法工程师是一份极有挑战的工作,相比研发岗有非常确定的项目目标,算法的优化工作往往需要我们自己去找到那个可以提升的点,自己去找到那组最合适的参数,并且可以完成生产环境的工程实现。这给了我们极大的灵活性,也给了我们不小的业绩压力。希望这节课可以帮助你纠正一些误区,与你共勉。
## 课后思考
推荐模型的研究可谓层出不穷很多同学都热衷于追求实现最前沿的技术最fancy的模型你这样的吗你认可这种现象吗
欢迎把你的想法写在留言区,我们一起讨论!