王喆《深度学习推荐系统》
文章出处:https://zhongqiang.blog.csdn.net/article/details/108173885
1. 前言
这次整理重点放在推荐系统的模型方面, 先从传统推荐模型开始, 然后到深度学习模型。 传统模型的演化关系拿书上的一张图片, 便于梳理传统推荐模型的进化关系脉络, 对知识有个宏观的把握:
今天是第二篇, 依然来自于协同过滤算法族, 前面介绍协同过滤算法的时候做过铺垫, 协同过滤的特点就是完全没有利用到物品本身或者是用户自身的属性, 仅仅利用了用户与物品的交互信息就可以实现推荐,是一个可解释性很强, 非常直观的模型, 但是也存在一些问题, 第一个就是处理稀疏矩阵的能力比较弱, 所以为了使得协同过滤更好处理稀疏矩阵问题, 增强泛化能力, 从协同过滤中衍生出矩阵分解模型(Matrix Factorization,MF), 并发展出了矩阵分解的分支模型, 比如我们听到的很多名词隐语义模型(Latent Factor Model), LDA, 隐含类别模型, PLSA等,这里面提到最多的就是潜语义模型和矩阵分解。这俩说的差不多是一回事, 就是在协同过滤共现矩阵的基础上, 使用更稠密的隐向量表示用户和物品, 挖掘用户和物品的隐含兴趣和隐含特征, 在一定程度上弥补协同过滤模型处理稀疏矩阵能力不足的问题。
矩阵分解算法在2006年Netfix的推荐算法大赛中大放异彩, 并开始流行, 它的核心思想是通过隐含特征来联系用户兴趣和物品, 所以下面一起来学习一下, 首先会解释一下我们总说的隐语义到底是啥意思, 因为每次看这个词语总是感觉会比较抽象, 这个其实类似于NLP里面的embedding, 如果你说embedding我也感觉长的抽象, 那么我后面会举一个例子, 让它变得具体一些哈哈。 然后就是为什么会用到隐语义模型? 它和协同过滤有哪些区别? 然后就是隐语义模型的核心部分,也就是原理, 隐语义简单的说就是找到隐含特征把用户的兴趣和物品联系了起来, 操作上就是把协同过滤那里的评分矩阵给分解成用户矩阵乘上物品矩阵的形式, 这里的矩阵里面都变成了隐向量, 这个东西能够保证相似的用户及它们喜欢的物品距离很近。 每一个用户和物品都会有一个隐向量, 如果是想预测某个用户对物品的评分的时候, 就直接物品向量与用户向量内积就可以得到了, 所以预测评分也是非常方便了,所以在隐语义模型里面这两个矩阵非常关键, 那么这两个矩阵是怎么得到的呢? 为了计算这两个矩阵, 出现了很多方法, 像BasicSVD, RSVD, ASVD, SVD++等, 大部分都是SVD算法,我们后面会一一来看, 最后就是用代码实现一下前面这几种SVD方法对上一篇里面用户打分的例子进行预测, 对做法做一个深入的了解。
大纲如下:
- 隐语义模型? 这家伙到底是个啥?
- 矩阵分解算法的原理
- 矩阵分解算法的求解(BacisSVD, RSVD, ASVD, SVD++)
- 用BasicSVD, RSVD, SVD++预测用户的评分
Ok, let’s go!
2. 隐语义模型? 这家伙到底是个啥?
隐语义模型算法最早在文本领域被提出,用于找到文本的隐含语义。在2006年, 被用于推荐中, 它的核心思想是通过隐含特征(latent factor)联系用户兴趣和物品(item), 基于用户的行为找出潜在的主题和分类, 然后对item进行自动聚类,划分到不同类别/主题(用户的兴趣)。
这么说依然感觉会很抽象是不是, 啥子叫隐含特征? 听到隐含这俩字又增加了一点神秘感哈哈, 下面拿项亮老师《推荐系统实践》里面的那个例子看一下:
如果我们知道了用户A和用户B两个用户在豆瓣的读书列表, 从他们的阅读列表可以看出,用户A的兴趣涉及侦探小说、科普图书以及一些计算机技术书, 而用户B的兴趣比较集中在数学和机器学习方面。 那么如何给A和B推荐图书呢?
先说说协同过滤算法, 这样好对比不同:
- 对于UserCF,首先需要找到和他们看了同样书的其他用户(兴趣相似的用户),然后给他们推荐那些用户喜欢的其他书。
- 对于ItemCF,需要给他们推荐和他们已经看的书相似的书,比如作者B看了很多关于数据挖掘的书,可以给他推荐机器学习或者模式识别方面的书。
而如果是隐语义模型的话, 它会先通过一些角度把用户兴趣和这些书归一下类, 当来了用户之后, 首先得到他的兴趣分类, 然后从这个分类中挑选他可能喜欢的书籍。
这里就看到了隐语义模型和协同过滤的不同, 这里说的角度其实就是这个隐含特征, 比如书籍的话它的内容, 作者, 年份, 主题等都可以算隐含特征,如果这个例子还不是很清晰的话, 那么下面再举个更为具体的例子, 看看是如何通过隐含特征来划分开用户兴趣和物品的。但是在这之前, 相信通过上面这个例子, 我们已经隐隐约约感受到了协同过滤和隐语义模型的区别了, 下面放上王喆老师《深度学习推荐系统》的一个原理图作为对比, 区别简直一目了然:
那下面依然是解密如何通过隐含特征把用户的兴趣和物品分类的:我们下面拿一个音乐评分的例子来看一下隐特征矩阵的含义。
假设每个用户都有自己的听歌偏好, 比如A喜欢带有小清新的, 吉他伴奏的, 王菲的歌曲,如果一首歌正好是王菲唱的, 并且是吉他伴奏的小清新, 那么就可以将这首歌推荐给这个用户。 也就是说是小清新, 吉他伴奏, 王菲这些元素连接起了用户和歌曲。 当然每个用户对不同的元素偏好不同, 每首歌包含的元素也不一样, 所以我们就希望找到下面的两个矩阵:
潜在因子—— 用户矩阵Q
这个矩阵表示不同用户对于不同元素的偏好程度, 1代表很喜欢, 0代表不喜欢, 比如下面这样:潜在因子——音乐矩阵P
表示每种音乐含有各种元素的成分, 比如下表中, 音乐A是一个偏小清新的音乐, 含有小清新的Latent Factor的成分是0.9, 重口味的成分是0.1, 优雅成分0.2…
利用上面的这两个矩阵, 我们就能得出张三对音乐A的喜欢程度:
张三对小清新的偏好 x 音乐A含有小清新的成分 + 张三对重口味的偏好 x 音乐A含有重口味的成分 + 张三对优雅的偏好 x 音乐A含有优雅的成分…,
下面是对应的两个隐向量:
根据隐向量其实就可以得到张三对音乐A的打分,即:
0.6∗0.9+0.8∗0.1+0.1∗0.2+0.1∗0.4+0.7∗0=0.68
按照这个计算方式, 每个用户对每首歌其实都可以得到这样的分数, 最后就得到了我们的评分矩阵:
这里的红色表示用户没有打分,我们通过隐向量计算得到的。
通过上面的这个例子, 差不多能明白隐含特征是一个什么样的概念了吧, 上面的小清晰, 重口味, 优雅这些就可以看做是隐含特征, 而通过这个隐含特征就可以把用户的兴趣和音乐的进行一个分类, 其实就是找到了每个用户每个音乐的一个隐向量表达形式(embedding的原理其实也是这样, 那里是找到每个词的隐向量表达), 这个隐向量就可以反映出用户的兴趣和物品的风格,并能将相似的物品推荐给相似的用户等。 有没有感觉到是把协同过滤算法进行了一种延伸, 把用户的相似性和物品的相似性通过了一个叫做隐向量的方式进行表达, 这样是不是感觉什么隐向量, 隐含特征啥的不是那么抽象了啊。
But, 真实的情况下我们其实是没有上面那两个矩阵的, 音乐那么多, 用户那么多, 难道我们还得先去找一些隐特征去表示出这些东西? 还有个问题这个东西我们表示也不一定准, 对于每个用户或者每个物品的风格,我们每个人都有不同的看法。 所以事实上, 我们有的只有用户的评分矩阵, 也就是最后的结果, 并且一般这种矩阵长这样:
也就是非常的稀疏, 上一篇协同过滤那个是为了说明原理举得一个特例, 针对这样非常稀疏的矩阵, 其实协同过滤算法想基于用户相似性或者物品相似性去填充这个矩阵是不太容易的, 并且很容易出现长尾问题, 所以才有了协同过滤的延伸 — 矩阵分解或者叫隐语义模型。
隐语义模型其实就是在想办法基于这个评分矩阵去找到上面例子中的那两个矩阵, 也就是用户兴趣和物品的隐向量表达, 然后就把这个评分矩阵分解成Q和P两个矩阵乘积的形式, 这时候就可以基于这两个矩阵去预测某个用户对某个物品的评分了。 然后基于这个评分去进行推荐。这其实就是矩阵分解算法的原理。
3. 矩阵分解算法的原理
上面我们已经简单的解密了一下隐含特征,也初步了解了隐语义模型的原理, 像上面音乐评分预测的那个例子, 如果我们能找到隐向量去表达用户和物品, 并且这隐向量能保证相似的用户及用户可能喜欢的物品距离接近, 那么我们就可以基于隐向量得到用户对于物品的评分矩阵。
但是实际上, 我们其实得到的是残缺的评分矩阵, 也就是后者,在矩阵分解的算法框架下, 我们就可以通过分解协同过滤的共现矩阵来得到用户和物品的隐向量, 就是上面的用户矩阵Q和物品矩阵P, 这也是“矩阵分解”名字的由来。 下面拿王喆老师《深度学习推荐系统》的那个图来看一下矩阵分解的过程:
矩阵分解算法将m × n 维的共享矩阵R RR分解成m × k 维的用户矩阵U 和k × n 维的物品矩阵V 相乘的形式。 其中m是用户数量, n是物品数量, k是隐向量维度, 也就是隐含特征个数, 只不过这里的隐含特征变得不可解释了, 即我们不知道具体含义了, 要模型自己去学。k的大小决定了隐向量表达能力的强弱, k越大, 表达信息就越强, 理解起来就是把用户的兴趣和物品的分类划分的越具体了嘛。
那么如果有了用户矩阵和物品矩阵的话, 我们就知道了如果想计算用户u 对物品i 的评分, 只需要
有了上面的例子作为铺垫, 感觉这个地方应该很容易理解了, 接下来的任务就是看看如何求这两个矩阵里面的具体参数了。
4. 矩阵分解算法的求解
谈到矩阵分解, 最常用的方法是特征值分解(EVD)或者奇异值分解(SVD), 关于这两个的具体原理我已经整理好了, 可以先过一下到底是怎么回事奇异值分解(SVD)的原理详解及推导, 虽然这种方法在这里不适用哈哈。
首先是EVD, 它要求分解的矩阵是方阵, 显然用户-物品矩阵不满足这个要求, 而传统的SVD分解, 会要求原始矩阵是稠密的, 而我们这里的这种矩阵一般情况下是非常稀疏的, 如果想用奇异值分解, 就必须对缺失的元素进行填充, 而一旦补全, 空间复杂度就会非常高, 且补的不一定对。 然后就是SVD分解计算复杂度非常高, 而我们的用户-物品矩阵非常大, 所以基本上无法使用。
4.1 Basic SVD
那么怎么办呢? 2006年的Netflix Prize之后, Simon Funk公布了一个矩阵分解算法叫做Funk-SVD, 后来被Netflix Prize的冠军Koren称为Latent Factor Model(LFM), 哦, 原来LFM是个具体的模型啊。 Funk-SVD的思想很简单: 把求解上面两个矩阵的参数问题转换成一个最优化问题, 可以通过训练集里面的观察值利用最小化来学习用户矩阵和物品矩阵, 这是什么意思?
我们上面已经知道了, 如果有了用户矩阵和物品矩阵的话, 我们就知道了如果想计算用户u uu对物品i ii的评分, 只需要
有了损失, 我们就可以想办法进行训练, 把SSE降到最小, 那么我们的两个矩阵参数不就算出来了吗? 看到这里, 是不是有了中醍醐灌顶的感觉了, 这不是训练深度学习模型时候用的常用套路吗? 于是乎就把这个问题转成了最优化的的问题, 而我们的目标函数就是:
这里的K KK表示所有用户评分样本的集合。
这里其实就和训练模型的思路差不多, 我们拿到了一个用户物品的评分矩阵, 而我们要去计算两个参数矩阵U和V, 我们就可以用求解神经网络模型参数的思路计算这两个矩阵:
- 首先先初始化这两个矩阵
- 把用户评分矩阵里面已经评过分的那些样本当做训练集的label, 把对应的用户和物品的隐向量当做features, 这样就会得到(features, label)相当于训练集
- 通过两个隐向量乘积得到预测值pred
- 根据label和pred计算损失
- 然后反向传播, 通过梯度下降的方式,更新两个隐向量的值
- 未评过分的那些样本当做测试集, 通过两个隐向量就可以得到测试集的label值
- 这样就填充完了矩阵, 下一步就可以进行推荐了
有了目标函数, 那么我们就可以使用梯度下降算法来降低损失。 那么我们需要对目标函数求偏导, 得到梯度。 我们的目标函数如果是上面的SSE, 我们下面来推导一下最后的导数:
这时候, 梯度就没有前面的系数了, 有了梯度, 接下来我们就可以用梯度下降算法更新梯度了:
这里的η 是学习率, 控制步长用的。得到了更新的式子,现在开始来讨论这个更新要怎么进行。有两种选择:
第一种叫批梯度下降, 第二种叫随机梯度下降。两者的区别就是批梯度下降在下一轮迭代才能使用本次迭代的更新值,随机梯度下降本次迭代中当前样本使用的值可能就是上一个样本更新的值。由于随机性可以带来很多好处,比如有利于避免局部最优解,所以现在大多倾向于使用随机梯度下降进行更新。 这就是运用梯度下降的矩阵分解算法了, 这个貌似有个名字叫做Basic SVD。当然这里的叫法都不一致, 知道有这么个概念就行了。
但上面这个有个问题就是当参数很多的时候, 就是两个矩阵很大的时候, 往往容易陷入过拟合的困境, 这时候, 就需要在目标函数上面加上正则化的损失, 就变成了RSVD。
4.2 RSVD
在目标函数中加入正则化参数(加入惩罚项),对于目标函数来说,Q矩阵和V矩阵中的所有值都是变量,这些变量在不知道哪个变量会带来过拟合的情况下,对所有变量都进行惩罚:
这样, 正则化之后, 梯度的更新公式就变成了:
4.3 消除用户和物品打分的偏差
通过上面隐向量的方式, 就可以将用户和物品联系到了一起, 但是实际情况中, 单纯的
是不够的, 还要考虑其他的一些因素, 比如一个评分系统, 有些固有的属性和用户物品无关, 而用户也有些属性和物品无关, 物品也有些属性和用户无关。 因此, Netfix Prize中提出了另一种LFM, 在原来的基础上加了偏置项, 来消除用户和物品打分的偏差, 预测公式如下:
加了用户和物品的打分偏差之后, 矩阵分解得到的隐向量更能反映不同用户对不同物品的“真实”态度差异, 也就更容易捕捉评价数据中有价值的信息, 从而避免推荐结果有偏。 此时的SSE变成了:
4.3 SVD++
前面的LFM模型中并没有显示的考虑用户的历史行为对用户评分预测的影响, 而我们知道, 如果某个用户喜欢电子产品, 并且已经买了很多个电子产品了, 如果我们预测该用户对一个电子产品和对一本书的评分, 那相应的电子产品的评分就会高一些, 而这里面, 他之前的历史行为记录也可以做一个参考。 所以Netflix Prize中提出了一个模型叫做SVD++, 它将用户历史评分的物品加入到了LFM模型里。也就是说, 上面的那些矩阵分解, 是只分解的当前的共现矩阵, 比如某个用户u uu对于某个物品i ii的评分, 就单纯的分解成用户u uu的隐向量与物品i ii的隐向量乘积再加上偏置项。 这时候注意并没有考虑该用户评分的历史物品, 所以这时候SVD++把这个考虑了进去, 但是SVD++可不是一下子就出来的, 它经过了一系列的演变过程。
首先先把ItemCF的预测算法改成一个可以学习的模型, 就行LFM那样, 怎么改? ItemCF的预测算法公式如下:
这一个就是SVD++模型了。 有了预测函数, 然后也知道真实值, 就可以由损失函数对各个参数求偏导, 和上面的一样了, 这里直接给出导数了, 不推了:
这些就是Koren在NetFlix大赛中用到的SVD算法, 除了ASVD, 这个有点复杂, 先不整理了, 感兴趣的可以看下面给出的第四个链接。
5. 编程实现
我们这里用代码实现一下上面的算法来预测上一篇文章里面的那个预测Alice对物品5的评分, 看看矩阵分解到底是怎么进行预测或者是推荐的。 我把之前的例子拿过来:
任务就是根据这个评分矩阵, 猜测Alice对物品5的打分。
在实现SVD之前, 先来回忆一下ItemCF和UserCF对于这个问题的做法, 首先ItemCF的做法, 根据已有的用户打分计算物品之间的相似度, 得到物品的相似度矩阵, 根据这个相似度矩阵, 选择出前K个与物品5最相似的物品, 然后基于Alice对这K个物品的得分, 猜测Alice对物品5的得分, 有一个加权的计算公式。 UserCF的做法是根据用户对其他物品的打分, 计算用户之间的相似度, 选择出与Alice最相近的K个用户, 然后基于那K个用户对物品5的打分计算出Alice对物品5的打分。 But, 这两种方式有个问题, 就是如果矩阵非常稀疏的话, 当然这个例子是个特例, 一般矩阵都是非常稀疏的, 那么预测效果就不好, 因为两个相似用户对同一物品打分的概率以及Alice同时对两个相似物品打分的概率可能都比较小。 另外, 这两种方法显然没有考虑到全局的物品或者用户, 只是基于了最相似的例子, 很可能有偏。
那么SVD在解决这个问题上是这么做的:
首先, 它会先初始化用户矩阵P和物品矩阵Q, P的维度是[users_num, F], Q的维度是[item_nums, F], 这个F是隐向量的维度。 也就是把通过隐向量的方式把用户的兴趣和F的特点关联了起来。 初始化这两个矩阵的方式很多, 但根据经验, 随机数需要和1/sqrt(F)成正比。 下面代码中会发现。
有了两个矩阵之后, 我就可以根据用户已经打分的数据去更新参数, 这就是训练模型的过程, 方法很简单, 就是遍历用户, 对于每个用户, 遍历它打分的物品, 这样就拿到了该用户和物品的隐向量, 然后两者相乘加上偏置就是预测的评分, 这时候与真实评分有个差距, 根据上面的梯度下降就可以进行参数的更新
这样训练完之后, 我们就可以得到用户Alice和物品5的隐向量, 根据这个就可以预测Alice对物品5的打分。 下面的代码的逻辑就是上面这两步, 由于上面整理了三个SVD算法, 这里只贴出一个来, 另外两个可以见最后的GitHub链接。 这里放出那个比较普通且常用的来,就是带有偏置项和正则项的那个SVD算法:
1 | class BasicSVD(): |
下面我建立一个字典来存放数据, 之所以用字典, 是因为很多时候矩阵非常的稀疏, 如果用pandas的话, 会出现很多Nan的值, 反而不好处理。
1 | # 定义数据集, 也就是那个表格, 注意这里我们采用字典存放数据, 因为实际情况中数据是非常稀疏的, 很少有情况是现在这样 |
通过这个方式, 得到的预测评分是3.25, 这个和隐向量的维度, 训练次数和训练方式有关, 这里只说一下这个东西应该怎么用, 具体结果可以不用纠结。 关于SVD++和RSVD的代码, 放到了下面的GitHub链接。
6. 总结
这篇文章零零散散的写了四五天, 篇幅也挺长, 所以下面先来简单的总结一下, 隐语义模型和矩阵分解是试图在协同过滤共现矩阵的基础上, 使用更稠密的隐向量表示用户和物品, 挖掘用户和物品的隐含兴趣和隐含特征, 在一定程度上弥补协同过滤模型处理稀疏矩阵能力不足的问题。
首先, 整理了一下隐向量的含义以及通过举例的方式看了一下用户矩阵和物品矩阵的隐特征到底是怎么一回事, 评分矩阵如何进行分解, 然后整理了隐语义模型的原理, 就是基于现有的评分矩阵分解成用户矩阵和物品矩阵相乘的方式, 然后学习后面两个矩阵的数字表示, 比较常用且可行的方式就是SVD, 但传统的SVD计算复杂度太高 , 这里把求解隐向量的问题转成了最优化的问题, 进行求解。 所以这里介绍了SVD, RSVD, SVD++等几种常用的训练求解隐向量的算法, 并最后通过编程简单的实现了这三个算法。
了解了矩阵分解的原理之后, 我们就差不多明白为什么矩阵分解比协同过滤有更强的泛化能力了, 矩阵分解算法中, 由于隐向量的存在, 使得任意的用户和物品之间都可以得到预测分值, 而求解隐向量的过程其实是对评分矩阵进行全局拟合的过程, 这个过程中考虑进了所有的用户和评分, 因此隐向量是利用全局信息生成的, 有更强的泛化能力, 而协同过滤算法, 还是上面说的, 只是基于相似的局部个体, 并且两个相似用户对同一物品打分的概率以及同一用户同时对两个相似物品打分的概率可能都比较小, 这就导致了没法得到全局的信息。
那么矩阵分解算法有什么优缺点呢?
BUT, 矩阵分解算法依然是只用到了评分矩阵, 没有考虑到用户特征, 物品特征和上下文特征, 这使得矩阵分解丧失了利用很多有效信息的机会, 同时在缺乏用户历史行为的时候, 无法进行有效的推荐。 所以为了解决这个问题, 逻辑回归模型及后续的因子分解机模型, 凭借其天然的融合不同特征的能力, 逐渐在推荐系统领域得到了更广泛的应用。
参考:
- 王喆 - 《深度学习推荐系统》
- 项亮 - 《推荐系统实践》
- 奇异值分解(SVD)的原理详解及推导
- SVD在推荐系统中的应用详解以及算法推导
- SVD++协同过滤
- 推荐算法5—隐语义模型
- 隐语义模型(LFM)
- 【机器学习】–隐语义模型
- 隐语义模型(简介)
- 基于矩阵分解(MF,Matrix Factorization)的推荐算法
论文:
Matrix factorization techniques for recommender systems原论文, 2009
A Singularly Valuable Decomposition The SVD of a Matrix, 2002
整理这篇文章的同时, 也刚建立了一个GitHub项目, 准备后面把各种主流的推荐模型复现一遍,并用通俗易懂的语言进行注释和逻辑整理, 今天的SVD算法, 上面例子里面的另外两种SVD, SVD++算法, 并且还简单的用SVD基于用户电影推荐的数据集分析了一下,感兴趣的可以看一下 😉
筋斗云:https://github.com/zhongqiangwu960812/AI-RecommenderSystem