推荐系统三十六式——学习笔记(四)

由于工作需要,开始学习推荐算法,参考【极客时间】->【刑无刀大牛】的【推荐系统三十六式】,学习并整理。

4 原理篇之矩阵分解

评分预测问题只是很典型,其实并不大众,毕竟在实际的应用中,评分数据很难收集到,属于典型的精英问题;与之相对的另一类问题行为预测,才是平民级推荐问题,处处可见。

4.1 矩阵分解

4.1.1 为什么要矩阵分解

近邻模型的问题:

1. 物品之间存在相关性,信息量并不随着向量维度增加而线性增加;

2. 矩阵元素稀疏,计算结果不稳定,增减一个向量维度,导致近邻结果差异很大的情况存在。

上述两个问题,在矩阵分解中可以得到解决。矩阵分解,直观上说来简单,就是把原来的大矩阵,近似分解成两个小矩阵的乘积,在实际推荐计算时不再使用大矩阵,而是使用分解得到的两个小矩阵。

具体说来就是,假设用户物品的评分矩阵 A 是 m 乘以 n 维,即一共有 m 个用户,n 个物品。我们选一个很小的数 k,这个 k 比 m 和 n 都小很多,比如小两个数量级这样,通过一套算法得到两个矩阵 U 和 V,矩阵 U 的维度是 m 乘以 k,矩阵 V 的维度是 n 乘以 k。要求就是通过下面这个公式复原矩阵 A:    

类似这样的计算过程就是矩阵分解,还有一个更常见的名字叫做 SVD;但是,SVD 和矩阵分解不能划等号,因为除了 SVD 还有一些别的矩阵分解方法。

1、基础的SVD算法

值得一说的是,SVD 全称奇异值分解,属于线性代数的知识 ; 然而在推荐算法中实际上使用的并不是正统的奇异值分解,而是一个伪奇异值分解(具体伪在哪不是本文的重点)。今天我介绍的 SVD 是由 Netflix Prize 中取得骄人成绩的 Yehuda Koren 提出的矩阵分解推荐算法。

矩阵分解,就是把用户和物品都映射到一个 k 维空间中,这个 k 维空间不是我们直接看得到的,也不一定具有非常好的可解释性,每一个维度也没有名字,所以常常叫做隐因子,代表藏在直观的矩阵数据下面的。

每一个物品都得到一个向量 q,每一个用户也得到一个向量 p。对于物品,与它对应的向量 q 中的元素,有正有负,代表着这个物品背后暗藏的一些用户关注的因素。

对于用户,与它对应的向量 p 中的元素,也有正有负,代表这个用户在若干因素上的偏好。物品被关注的因素,和用户偏好的因素,它们的数量和意义是一致的,就是我们在矩阵分解之处人为指定的 k。

举个例子,用户 u 的向量是 pu,物品 i 的向量是 qi,那么,要计算物品 i 推荐给用户 u 的推荐分数,直接计算点积即可:

这是一个机器学习问题。按照机器学习框架,一般就是考虑两个核心要素:损失函数;优化算法。

SVD 的损失函数是这样定义的:

这个损失函数由两部分构成,加号前一部分控制着模型的偏差,加号后一部分控制着模型的方差。前一部分就是:用分解后的矩阵预测分数,要和实际的用户评分之间误差越小越好。后一部分就是:得到的隐因子向量要越简单越好,以控制这个模型的方差,换句话说,让它在真正执行推荐任务时发挥要稳定。这部分的概念对应机器学习中的过拟合,有兴趣可以深入了解。

整个 SVD 的学习过程就是:

1. 准备好用户物品的评分矩阵,每一条评分数据看做一条训练样本;

2. 给分解后的 U 矩阵和 V 矩阵随机初始化元素值;

3. 用 U 和 V 计算预测后的分数;

4. 计算预测的分数和实际的分数误差;

5. 按照梯度下降的方向更新 U 和 V 中的元素值;重复步骤 3 到 5,直到达到停止条件。

过程中提到的梯度下降是优化算法的一种,想深入了解可以参见任何一本机器学习的专著。得到分解后的矩阵之后,实质上就是得到了每个用户和每个物品的隐因子向量,拿着这个向量再做推荐计算就简单了,哪里不会点哪里,意思就是拿着物品和用户两个向量,计算点积就是推荐分数了。

2、 增加偏置信息

现在来多考虑一下实际情况,试想一下:有一些用户会给出偏高的评分,比如标准宽松的用户;有一些物品也会收到偏高的评分,比如一些目标观众为铁粉的电影,甚至有可能整个平台的全局评分就偏高。

所以,原装的 SVD 就有了第一个变种:把偏置信息抽出来的 SVD。一个用户给一个物品的评分会由四部分相加:

从左至右分别代表:全局平均分、物品的评分偏置、用户评分的偏置、用户和物品之间的兴趣偏好。

针对前面三项偏置分数,我在这里举个例子,假如一个电影评分网站全局平均分是 3 分,《肖申克的救赎》的平均分比全局平均分要高 1 分。你是一个对电影非常严格的人,你一般打分比平均分都要低 0.5,所以前三项从左到右分别就是 3,1,-0.5。如果简单的就靠这三项,也可以给计算出一个你会给《肖申克的救赎》打的分数,就是 3.5。

增加了偏置信息的 SVD 模型目标函数稍有改变:

和基本的 SVD 相比,要想学习两个参数:用户偏置和物品偏置。学习的算法还是一样的。

3、增加历史行为

探讨完增加偏执信息的 SVD 后,接着你再思考一个问题:有的用户评分比较少。事实上这很常见,相比沉默的大多数,主动点评电影或者美食的用户是少数。

换句话说,显式反馈比隐式反馈少,那么能不能利用隐式反馈来弥补这一点呢?另外,再考虑多一点,对于用户的个人属性,比如性别等,是不是也可以加入到模型中来弥补冷启动的不足呢?

是的,都是可以的,在 SVD 中结合用户的隐式反馈行为和属性,这套模型叫做 SVD++。

先说隐式反馈怎么加入,方法是:除了假设评分矩阵中的物品有一个隐因子向量外,用户有过行为的物品集合也都有一个隐因子向量,维度是一样的。把用户操作过的物品隐因子向量加起来,用来表达用户的兴趣偏好。

类似的,用户属性,全都转换成 0-1 型的特征后,对每一个特征也假设都存在一个同样维度的隐因子向量,一个用户的所有属性对应的隐因子向量相加,也代表了他的一些偏好。

综合两者,SVD++ 的目标函数中,只需要把推荐分数预测部分稍作修改,原来的用户向量那部分增加了隐式反馈向量和用户属性向量:

学习算法依然不变,只是要学习的参数多了两个向量:x 和 y。一个是隐式反馈的物品向量,另一个用户属性的向量。这样一来,在用户没有评分时,也可以用他的隐式反馈和属性做出一定的预测。

4、考虑时间因素

人善变,环境善变,等等。

这是常态,因此,在 SVD 中考虑时间因素也变得顺理成章。在 SVD 中考虑时间因素,有几种做法:

1. 对评分按照时间加权,让久远的评分更趋近平均值;

2. 对评分时间划分区间,不同的时间区间内分别学习出隐因子向量,使用时按照区间使用对应的隐因子向量来计算;

3. 对特殊的期间,如节日、周末等训练对应的隐因子向量。

4.2 回溯矩阵分解

矩阵分解要将用户物品评分矩阵分解成两个小矩阵,一个矩阵是代表用户偏好的用户隐因子向量组成,另一个矩阵是代表物品语义主题的隐因子向量组成。

这两个小矩阵相乘后得到的矩阵,维度和原来的用户物品评分矩阵一模一样。比如原来矩阵维度是 m x n,其中 m 是用户数量,n 是物品数量,再假如分解后的隐因子向量是 k 个,那么用户隐因子向量组成的矩阵就是 m x k,物品隐因子向量组成的矩阵就是 n x k。

得到的这两个矩阵有这么几个特点:

1. 每个用户对应一个 k 维向量,每个物品也对应一个 k 维向量,就是所谓的隐因子向量,因为是无中生有变出来的,所以叫做“隐因子”;

2. 两个矩阵相乘后,就得到了任何一个用户对任何一个物品的预测评分,具体这个评分靠不靠谱,那就是看功夫了。

按照机器学习的套路,就是使用优化算法求解下面这个损失函数:

这个公式依然由两部分构成:加号左边是误差平方和,加号右边是分解后参数的平方。

这种模式可以套在几乎所有的机器学习训练中:就是一个负责衡量模型准不准,另一个负责衡量模型稳不稳定。行话是这样说的:一个衡量模型的偏差,一个衡量模型的方差。偏差大的模型欠拟合,方差大的模型过拟合。

有了这个目标函数后,就要用到优化算法找到能使它最小的参数。优化方法常用的选择有两个,一个是随机梯度下降(SGD),另一个是交替最小二乘(ALS)。

4.2.1 交替最小二乘原理 (ALS)

在实际应用中,交替最小二乘更常用一些,这也是社交巨头 Facebook 在他们的推荐系统中选择的主要矩阵分解方法。

交替最小二乘的核心是交替,什么意思呢?你的任务是找到两个矩阵 P 和 Q,让它们相乘后约等于原矩阵 R:

难就难在,P 和 Q 两个都是未知的,如果知道其中一个的话,就可以按照线性代数标准解法求得,比如如果知道了 Q,那么 P 就可以这样算:     

 

交替最小二乘通过迭代的方式解决了这个鸡生蛋蛋生鸡的难题:

1. 初始化随机矩阵 Q 里面的元素值;

2. 把 Q 矩阵当做已知的,直接用线性代数的方法求得矩阵 P;

3. 得到了矩阵 P 后,把 P 当做已知的,故技重施,回去求解矩阵 Q;

4. 上面两个过程交替进行,一直到误差可以接受为止。

交替最小二乘有这么几个好处:

1. 在交替的其中一步,也就是假设已知其中一个矩阵求解另一个时,要优化的参数是很容易并行化的;

2. 在不那么稀疏的数据集合上,交替最小二乘通常比随机梯度下降要更快地得到结果,事实上这一点就是我马上要说的,也就是关于隐式反馈的内容。

4.2.2 隐式反馈

矩阵分解算法,是为解决评分预测问题而生的,比如说,预测用户会给商品打几颗星,然后把用户可能打高星的商品推荐给用户,然而事实上却是,用户首先必须先去浏览商品,然后是购买,最后才可能打分。

相比“预测用户会打多少分”,“预测用户会不会去浏览”更加有意义,而且,用户浏览数据远远多于打分评价数据。也就是说,实际上推荐系统关注的是预测行为,行为也就是一再强调的隐式反馈。

那如何从解决评分预测问题转向解决预测行为上来呢?这就是另一类问题了,行话叫做 One-Class。

这是什么意思呢?如果把预测用户行为看成一个二分类问题,猜用户会不会做某件事,但实际上收集到的数据只有明确的一类:用户干了某件事,而用户明确“不干”某件事的数据却没有明确表达。所以这就是 One-Class 的由来,One-Class 数据也是隐式反馈的通常特点。

对隐式反馈的矩阵分解,需要将交替最小二乘做一些改进,改进后的算法叫做加权交替最小二乘:Weighted-ALS。

这个加权要从哪说起?用户对物品的隐式反馈,通常是可以多次的,你有心心念念的衣服或者电子产品,但是刚刚剁完手的你正在吃土买不起,只能每天去看一眼。

这样一来,后台就记录了你查看过这件商品多少次,查看次数越多,就代表你越喜欢这个。也就是说,行为的次数是对行为的置信度反应,也就是所谓的加权。

加权交替最小二乘这样对待隐式反馈:

1. 如果用户对物品无隐式反馈则认为评分是 0;

2. 如果用户对物品有至少一次隐式反馈则认为评分是 1,次数作为该评分的置信度。

那现在的目标函数在原来的基础上变成这样:

多出来的 Cui 就是置信度,在计算误差时考虑反馈次数,次数越多,就越可信。置信度一般也不是直接等于反馈次数,根据一些经验,置信度 Cui 这样计算:     

其中阿尔法是一个超参数,需要调教,默认值取 40 可以得到差不多的效果,C 就是次数了。

这里又引出另一个问题,那些没有反馈的缺失值,就是在我们的设定下,取值为 0 的评分就非常多,有两个原因导致在实际使用时要注意这个问题:

1. 本身隐式反馈就只有正类别是确定的,负类别是我们假设的,你要知道,One-Class 并不是随便起的名字;

2. 这会导致正负类别样本非常不平衡,严重倾斜到 0 评分这边。

因此,不能一股脑儿使用所有的缺失值作为负类别,矩阵分解的初心就是要填充这些值,如果都假设他们为 0 了,那就忘记初心了。应对这个问题的做法就是负样本采样:挑一部分缺失值作为负类别样本即可。挑选方法:

1. 随机均匀采样和正类别一样多;

2. 按照物品的热门程度采样。

请允许我直接说结论,第一种不是很靠谱,第二种在实践中经过了检验

还是回到初心来,你想一想,在理想情况下,什么样的样本最适合做负样本?就是展示给用户了,他也知道这个物品的存在了,但就是没有对其作出任何反馈。问题就是很多时候不知道到底是用户没有意识到物品的存在呢,还是知道物品的存在而不感兴趣呢?

因此按照物品热门程度采样的思想就是:一个越热门的物品,用户越可能知道它的存在。那这种情况下,用户还没对它有反馈就表明:这很可能就是真正的负样本。

按照热门程度采样来构建负样本,在实际中是一个很常用的技巧,我之前和你提到的文本算法 Word2Vec 学习过程,也用到了类似的负样本采样技巧。

4.2.3 推荐计算

在得到了分解后的矩阵后,相当于每个用户得到了隐因子向量,这是一个稠密向量,用于代表他的兴趣。同时每个物品也得到了一个稠密向量,代表它的语义或主题。而且可以认为这两者是一一对应的,用户的兴趣就是表现在物品的语义维度上的。

看上去,让用户和物品的隐因子向量两两相乘,计算点积就可以得到所有的推荐结果了。但是实际上复杂度还是很高,尤其对于用户数量和物品数量都巨大的应用,如 Facebook,就更不现实。于是 Facebook 提出了两个办法得到真正的推荐结果:

第一种,利用一些专门设计的数据结构存储所有物品的隐因子向量,从而实现通过一个用户向量可以返回最相似的 K 个物品。

Facebook 给出了自己的开源实现 Faiss,类似的开源实现还有 Annoy,KGraph,NMSLIB。其中 Facebook 开源的 Faiss 和 NMSLIB(Non-Metric Space Library)都用到了 ball tree 来存储物品向量。

第二种,就是拿着物品的隐因子向量先做聚类,海量的物品会减少为少量的聚类。然后再逐一计算用户和每个聚类中心的推荐分数,给用户推荐物品就变成了给用户推荐物品聚类。

得到给用户推荐的聚类后,再从每个聚类中挑选少许几个物品作为最终推荐结果。这样做的好处除了大大减小推荐计算量之外,还可以控制推荐结果的多样性,因为可以控制在每个类别中选择的物品数量。

4.3 矩阵分解不足

矩阵分解既有协同过滤的血统,又有机器学习的基因,可以说是非常优秀了;但即便如此,传统的矩阵分解无论是在处理显式反馈,还是处理隐式反馈都让人颇有微词,这一点是为什么呢?

得到这样的矩阵分解结果后,常常在实际使用时,又是用这个预测结果来排序。所以,从业者们口口声声宣称想要模型的预测误差最小化,结果绕了一大圈最后还是只想要一个好点的排序,让人不禁感叹:人心总是难测。

4.4 贝叶斯个性化排序

4.4.1 AUC 

AUC 这个值在数学上等价于:模型把关心的那一类样本排在其他样本前面的概率。最大是 1,完美结果,而 0.5 就是随机排列,0 就是完美地全部排错。

AUC 怎么计算呢?一般步骤如下:

1. 用模型给样本计算推荐分数,比如样本都是用户和物品这样一对一对的,同时还包含了有无反馈的标识;

2. 得到打过分的样本,每条样本保留两个信息,第一个是分数,第二个是 0 或者 1,1 表示用户消费过,是正样本,0 表示没有,是负样本;

3. 按照分数对样本重新排序,降序排列;

4. 给每一个样本赋一个排序值,第一位 r1 = n,第二位 r2 = n-1,以此类推;其中要注意,如果几个样本分数一样,需要将其排序值调整为他们的平均值;

5. 最终按照下面这个公式计算就可以得到 AUC 值。

公式由两部分构成:

第一部分: 分母是所有我们关心的那类样本,也就是正样本,有 M 个,以及其他样本有 N 个,这两类样本相对排序总共的组合可能性,是 M x N;

第二部分: 分子也不复杂,原本是这样算的:第一名的排序值是 r1,它在排序上不但比过了所有的负样本,而且比过了自己以外的正样本。

但后者是自己人,所以组合数要排除,于是就有 n - M 种组合,以此类推,排序值为 rM 的就贡献了 rM - 1,把这些加起来就是分子。

关于 AUC,越接近 1 越好是肯定的,但是并不是越接近 0 就越差,最差的是接近 0.5,如果 AUC 很接近 0 的话,只需要把模型预测的结果加个负号就能让 AUC 接近 1。

已经介绍完排序的评价指标了,该主角出场了,BPR 模型,它提出了一个优化准则和学习框架,使得原来传统的矩阵分解放进来能够焕发第二春。

那到底 BPR 做了什么事情呢?主要有三点:一个样本构造方法;一个模型目标函数;一个模型学习框架。

1 构造样本

前面介绍的矩阵分解,在训练时候处理的样本是:用户、物品、反馈,这样的三元组形式。

其中反馈又包含真实反馈和缺失值,缺失值充当的是负样本职责。BPR 则不同,提出要关心的是物品之间对于用户的相对顺序,于是构造的样本是:用户、物品 1、物品 2、两个物品相对顺序,这样的四元组形式,其中,“两个物品的相对顺序”,取值是:

1. 如果物品 1 是消费过的,而物品 2 不是,那么相对顺序取值为 1,是正样本;

2. 如果物品 1 和物品 2 刚好相反,则是负样本;

3. 样本中不包含其他情况:物品 1 和物品 2 都是消费过的,或者都是没消费过的。

这样一来,学习的数据是反应用户偏好的相对顺序,而在使用时,面对的是所有用户还没消费过的物品,这些物品仍然可以在这样的模型下得到相对顺序,这就比三元组 point-wise 样本要直观得多。

2 目标函数                                                                                                                                   

3 训练方法

梯度下降又有批量梯度和随机梯度下降两个选择,前者收敛慢,后者训练快却不稳定。因此 BPR 的作者使用了一个介于两者之间的训练方法,结合重复抽样的梯度下降。具体来说是这样做的:

1. 从全量样本中有放回地随机抽取一部分样本;

2. 用这部分样本,采用随机梯度下降优化目标函数,更新模型参数;

3. 重复步骤 1,直到满足停止条件。

发布了384 篇原创文章 · 获赞 110 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/qq_34732729/article/details/103254783