推荐系统协同过滤在Spark中的实现向量iu算法余弦

作者:vivo互联网服务器团队-TangShutao

现如今推荐无处不在,例如抖音、淘宝、京东App均能见到推荐系统的身影,其背后涉及许多的技术。

本文以经典的协同过滤为切入点,重点介绍了被工业界广泛使用的矩阵分解算法,从理论与实践两个维度介绍了该算法的原理,通俗易懂,希望能够给大家带来一些启发。

笔者认为要彻底搞懂一篇论文,最好的方式就是动手复现它,复现的过程你会遇到各种各样的疑惑、理论细节。

一、背景

1.1引言

在信息爆炸的二十一世纪,人们很容易淹没在知识的海洋中,在该场景下搜索引擎可以帮助我们迅速找到我们想要查找的内容。

在电商场景,如今的社会物质极大丰富,商品琳琅满目,种类繁多。消费者很容易挑花眼,即用户将会面临信息过载的问题。

为了解决该问题,推荐引擎应运而生。例如我们打开淘宝App,JDapp,B站视频app,每一个场景下都有推荐的模块。

那么此时有一个幼儿园小朋友突然问你,为什么JD给你推荐这本《程序员颈椎康复指南》?你可能会回答,因为我的职业是程序员。

接着小朋友又问,为什么《Spark大数据分析》这本书排在第6个推荐位,而《Scala编程》排在第2位?这时你可能无法回答这个问题。

为了回答该问题,我们设想下面的场景:

在JD的电商系统中,存在着用户和商品两种角色,并且我们假设用户都会对自己购买的商品打一个0-5之间的分数,分数越高代表越喜欢该商品。

基于此假设,我们将上面的问题转化为用户对《程序员颈椎康复指南》,《Spark大数据分析》,《Scala编程》这三本书打分的话,用户会打多少分(用户之前未购买过这3本书)。因此物品在页面的先后顺序就等价于预测用户对这些物品的评分,并且根据这些评分进行排序的问题。

为了便于预测用户对物品的评分问题,我们将所有三元组(User,Item,Rating),即用户User给自己购买的商品Item的评分为Rating,组织为如下的矩阵形式:

其中,表格包含m个用户和n个物品,将表格定义为评分矩阵Rm×n"role="presentation"SegoeUI",Roboto,Oxygen,Ubuntu,"FiraSans","DroidSans","HelveticaNeue",sans-serif;font-variant-ligatures:normal;font-variant-caps:normal;orphans:2;widows:2;-webkit-text-stroke-width:0px;background-color:rgb(255,255,255);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;">Rm×nRm×n,其中的元素ru,i"role="presentation">ru,iru,i表示第u个用户对第i个物品的评分。

例如,在上面的表格中,用户user-1购买了物品item-1,item-3,item-4,并且分别给出了4,2,5的评分。最终,我们将原问题转化为预测白色空格处的数值。

1.2协同过滤

协同过滤,简单来说是利用与用户兴趣相投、拥有共同经验之群体的喜好来推荐给用户感兴趣的物品。兴趣相投使用数学语言来表达就是相似度(人与人,物与物)。因此,根据相似度的对象,协同过滤可以分为基于用户的协同过滤和基于物品的协同过滤。

以评分矩阵为例,以行方向观测评分矩阵,每一行代表每个用户的向量表示,例如用户user-1的向量为[4,0,2,5,0,0]。以列方向观测评分矩阵,每一列表示每个物品的向量表示,例如物品item-1的向量为[4,3,0,0,5]。

基于向量表示,相似度的计算有多种公式,例如余弦相似度,欧氏距离,皮尔森。这里我们以余弦相似度为例,它是我们中学学过的向量夹角(中学只涉及2维和3维)的高维推广,余弦相似度公式很容易理解和使用。给定两个向量A={a1,,an}"role="presentation"SegoeUI",Roboto,Oxygen,Ubuntu,"FiraSans","DroidSans","HelveticaNeue",sans-serif;font-variant-ligatures:normal;font-variant-caps:normal;orphans:2;widows:2;-webkit-text-stroke-width:0px;background-color:rgb(255,255,255);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;">A={a1,,an}A={a1,,an}和B={b1,,bn}"role="presentation">B={b1,,bn}B={b1,,bn},其夹角定义如下:

例如,我们计算user-3和user-4的余弦相似度,二者对应的向量分别为[0,2,0,3,0,4],[0,3,3,5,4,0]

向量夹角的余弦值越接近1代表两个物品方向越接近平行,也就是越相似,反之越接近-1代表两个物品方向越接近反向,表示两个物品相似度接近相反,接近0,表示向量接近垂直/正交,两个物品几乎无关联。显然,这和人的直觉完全一致。

我们用《血族第一部》在向量库(存储向量的数据库,该系统能够根据输入向量,用相似度公式在库中进行检索,找出TopN的候选向量)里面进行相似度检索,找到了前7部高相似度的影片,值得注意的是第一部是自己本身,相似度为1.0,其他三部是《血族》的其他3部同系列作品。

1.2.1基于用户的协同过滤(UserCF)

基于用户的协同过滤分为两步

找出用户相似度TopN的若干用户。

根据TopN用户评分的物品,形成候选物品集合,利用加权平均预估用户u对每个候选物品的评分。

例如,由用户u的相似用户{u1,u3,u5,u9}可得候选物品为

我们现在预测用户u对物品i1的评分,由于物品在两个用户{u1,u5}的购买记录里,因此用户u对物品i1的预测评分为:

其中sim(u,u1)"role="presentation"SegoeUI",Roboto,Oxygen,Ubuntu,"FiraSans","DroidSans","HelveticaNeue",sans-serif;font-variant-ligatures:normal;font-variant-caps:normal;orphans:2;widows:2;-webkit-text-stroke-width:0px;background-color:rgb(255,255,255);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;">sim(u,u1)sim(u,u1)表示用户u与用户u1"role="presentation">u1u1的相似度。

在推荐时,根据用户u对所有候选物品的预测分进行排序,取TopM的候选物品推荐给用户u即可。

1.2.2基于物品的协同过滤(ItemCF)

基于物品的协同过滤分为两步

在用户u购买的物品集合中,选取与每一个物品TopN相似的物品。

TopN相似物品形成候选物品集合,利用加权平均预估用户u对每个候选物品的评分。

例如,我们预测用户u对物品i3的评分,由于物品i3与物品{i6,i1,i9}均相似,因此用户u对物品i3的预测评分为:

1.2.3UserCF与ItemCF的比较

我们对ItemCF和UserCF做如下总结:

ItemCF给用户推荐那些和他之前喜欢的物品类似的物品,即ItemCF的推荐结果着重于维系用户的历史兴趣,推荐更加个性化,反应用户自己的兴趣。在实际应用中,图书、电影平台使用ItemCF,比如豆瓣、亚马逊、Netflix等。

除了基于用户和基于物品的协同过滤,还有一类基于模型的协同过滤算法,如上图所示。此外基于用户和基于物品的协同过滤又可以归类为基于邻域(K-NearestNeighbor,KNN)的算法,本质都是在找"TopN邻居",然后利用邻居和相似度进行预测。

二、矩阵分解

经典的协同过滤算法本身存在一些缺点,其中最明显的就是稀疏性问题。我们知道评分矩阵是一个大型稀疏矩阵,导致在计算相似度时,两个向量的点积等于0(以余弦相似度为例)。为了更直观的理解这一点,我们举例如下:

romsklearn.metrics.pairwiseimportcosine_similaritya=[[0,0,0,3,2,0,3.5,0,1],[0,1,0,0,0,0,0,0,0],[0,0,1,0,0,0,0,0,0],[4.1,3.8,4.6,3.8,4.4,3,4,0,3.6]cosine_similarity(a)#array([[1.,0.,0.,0.66209271],#[0.,1.,0.,0.34101639],#[0.,0.,1.,0.41280932],#[0.66209271,0.34101639,0.41280932,1.]])

我们从评分矩阵中抽取item1-item4的向量,并且利用余弦相似度计算它们之间的相似度。

通过相似度矩阵,我们可以看到物品item-1,item-2,item-3的之间的相似度均为0,而且与item-1,item-2,item-3最相似的物品都是item-4,因此在以ItemCF为基础的推荐场景中item-4将会被推荐给用户。

综上,我们可以看到经典的基于用户/物品的协同过滤算法有天然的缺陷,无法处理稀疏场景。为了解决该问题,矩阵分解被提出。

2.1显示反馈

我们将用户对物品的评分行为定义为显示反馈。基于显示反馈的矩阵分解是将评分矩阵Rm×n用两个矩阵Xm×k"role="presentation"SegoeUI",Roboto,Oxygen,Ubuntu,"FiraSans","DroidSans","HelveticaNeue",sans-serif;font-variant-ligatures:normal;font-variant-caps:normal;orphans:2;widows:2;-webkit-text-stroke-width:0px;background-color:rgb(255,255,255);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;">Xm×k和Yn×k的乘积近似表示,其数学表示如下:

为了求解矩阵Xm×k"role="presentation"SegoeUI",Roboto,Oxygen,Ubuntu,"FiraSans","DroidSans","HelveticaNeue",sans-serif;font-variant-ligatures:normal;font-variant-caps:normal;orphans:2;widows:2;-webkit-text-stroke-width:0px;background-color:rgb(255,255,255);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;">Xm×kXm×k和Yn×k,需要最小化平方误差损失函数,来尽可能地使得两个矩阵的乘积逼近评分矩阵Rm×n"role="presentation"SegoeUI",Roboto,Oxygen,Ubuntu,"FiraSans","DroidSans","HelveticaNeue",sans-serif;font-variant-ligatures:normal;font-variant-caps:normal;orphans:2;widows:2;-webkit-text-stroke-width:0px;background-color:rgb(255,255,255);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;">Rm×nRm×n,即

其中,λ(∑uxuTxu+∑iyiTyi)"role="presentation">λ(∑uxTuxu+∑iyTiyi)λ(∑uxuTxu+∑iyiTyi)为惩罚项,λ为惩罚系数/正则化系数,xu表示第u个用户的k维特征向量,yi"role="presentation"SegoeUI",Roboto,Oxygen,Ubuntu,"FiraSans","DroidSans","HelveticaNeue",sans-serif;font-variant-ligatures:normal;font-variant-caps:normal;orphans:2;widows:2;-webkit-text-stroke-width:0px;background-color:rgb(255,255,255);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;">yiyi表示第i个物品的k维特征向量。

全体用户的特征向量构成了用户矩阵Xm×k"role="presentation"SegoeUI",Roboto,Oxygen,Ubuntu,"FiraSans","DroidSans","HelveticaNeue",sans-serif;font-variant-ligatures:normal;font-variant-caps:normal;orphans:2;widows:2;-webkit-text-stroke-width:0px;background-color:rgb(255,255,255);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;">Xm×kXm×k,全体物品的特征向量构成了物品矩阵Yn×k。

我们训练模型的时候,就只需要训练用户矩阵中的m×k个参数和物品矩阵中的n×k个参数。因此,协同过滤就成功转化成了一个优化问题。

2.2预测评分

通过模型训练(即求解模型系数的过程),我们得到用户矩阵Xm×k和物品矩阵Yn×k"role="presentation">Yn×kYn×k,全部用户对全部物品的评分预测可以通过Xm×k(Yn×k)T"role="presentation">Xm×k(Yn×k)TXm×k(Yn×k)T获得。如下图所示。

得到全部的评分预测后,我们就可以对每个物品进行择优推荐。需要注意的是,用户矩阵和物品矩阵的乘积,得到的评分预估值,与用户的实际评分不是全等关系,而是近似相等的关系。如上图中两个矩阵粉色部分,用户实际评分和预估评分都是近似的,有一定的误差。

2.3理论推导

矩阵分解ALS的理论推导网上也有不少,但是很多推导不是那么严谨,在操作向量导数时有的步骤甚至是错误的。有的博主对损失函数的求和项理解解出现错误,例如

但是评分矩阵是稀疏的,求和并不会贯穿整个用户集和物品集。正确的写法应该是

其中,(u,i)isknown"role="presentation"SegoeUI",Roboto,Oxygen,Ubuntu,"FiraSans","DroidSans","HelveticaNeue",sans-serif;font-variant-ligatures:normal;font-variant-caps:normal;orphans:2;widows:2;-webkit-text-stroke-width:0px;background-color:rgb(255,255,255);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;">(u,i)isknown(u,i)isknown表示已知的评分项。

我们在本节给出详细的、正确的推导过程,一是当做数学小练习,其次也是对算法有更深层的理解,便于阅读SparkALS的源码。

将(u,i)isknown"role="presentation"SegoeUI",Roboto,Oxygen,Ubuntu,"FiraSans","DroidSans","HelveticaNeue",sans-serif;font-variant-ligatures:normal;font-variant-caps:normal;orphans:2;widows:2;-webkit-text-stroke-width:0px;background-color:rgb(255,255,255);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;">(u,i)isknown(u,i)isknown使用数学语言描述,矩阵分解的损失函数定义如下:

其中K为评分矩阵中已知的(u,i)集合。例如下面的评分矩阵对应的K为

求解上述损失函数存在两种典型的优化方法,分别为

交替最小二乘(AlternatingLeastSquares,ALS)

随机梯度下降(StochasticGradientDescent,SGD)

交替最小二乘,指的是固定其中一个变量,利用最小二乘求解另一个变量,以此交替进行,直至收敛或者到达最大迭代次数,这也是“交替”一词的由来。

随机梯度下降,是优化理论中最常用的一种方式,通过计算梯度,然后更新待求的变量。

在矩阵分解算法中,Spark最终选择了ALS作为官方的唯一实现,原因是ALS很容易实现并行化,任务之间没有依赖。

下面我们动手推导一下整个计算过程,在机器学习理论中,微分的单位一般在向量维度,很少去对向量的分量为偏微分推导。

首先我们固定物品矩阵Y,将物品矩阵Y看成常量。不失一般性,我们定义用户u评分过的物品集合为Iu"role="presentation"SegoeUI",Roboto,Oxygen,Ubuntu,"FiraSans","DroidSans","HelveticaNeue",sans-serif;font-variant-ligatures:normal;font-variant-caps:normal;orphans:2;widows:2;-webkit-text-stroke-width:0px;background-color:rgb(255,255,255);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;">IuIu,利用损失函数对向量xu"role="presentation">xuxu求偏导,并且令导数等于0可得:

等式两边取转置,我们有

为了化简∑i∈IuyiyiT"role="presentation"SegoeUI",Roboto,Oxygen,Ubuntu,"FiraSans","DroidSans","HelveticaNeue",sans-serif;font-variant-ligatures:normal;font-variant-caps:normal;orphans:2;widows:2;-webkit-text-stroke-width:0px;background-color:rgb(255,255,255);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;">∑i∈IuyiyTi∑i∈IuyiyiT与

∑i∈Iuru,iyi"role="presentation">∑i∈Iuru,iyi∑i∈Iuru,iyi,我们将Iu"role="presentation"SegoeUI",Roboto,Oxygen,Ubuntu,"FiraSans","DroidSans","HelveticaNeue",sans-serif;font-variant-ligatures:normal;font-variant-caps:normal;orphans:2;widows:2;-webkit-text-stroke-width:0px;background-color:rgb(255,255,255);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;">Iu"role="presentation"SegoeUI",Roboto,Oxygen,Ubuntu,"FiraSans","DroidSans","HelveticaNeue",sans-serif;font-variant-ligatures:normal;font-variant-caps:normal;orphans:2;widows:2;-webkit-text-stroke-width:0px;background-color:rgb(255,255,255);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;">Iu展开。

假设Iu={ic1,,icN},其中N表示用户u评分过的物品数量,ici"role="presentation"SegoeUI",Roboto,Oxygen,Ubuntu,"FiraSans","DroidSans","HelveticaNeue",sans-serif;font-variant-ligatures:normal;font-variant-caps:normal;orphans:2;widows:2;-webkit-text-stroke-width:0px;background-color:rgb(255,255,255);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;">iciici表示第ci"role="presentation">cici个物品对应的索引/序号,借助于Iu"role="presentation"SegoeUI",Roboto,Oxygen,Ubuntu,"FiraSans","DroidSans","HelveticaNeue",sans-serif;background-color:rgb(255,255,255);">Iu,我们有

其中,

YIu"role="presentation"SegoeUI",Roboto,Oxygen,Ubuntu,"FiraSans","DroidSans","HelveticaNeue",sans-serif;font-variant-ligatures:normal;font-variant-caps:normal;orphans:2;widows:2;-webkit-text-stroke-width:0px;background-color:rgb(255,255,255);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;">YIuYIu为以Iu={ic1,icN}"role="presentation"SegoeUI",Roboto,Oxygen,Ubuntu,"FiraSans","DroidSans","HelveticaNeue",sans-serif;font-variant-ligatures:normal;font-variant-caps:normal;orphans:2;widows:2;-webkit-text-stroke-width:0px;background-color:rgb(255,255,255);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;">Iu={ic1,icN}Iu={ic1,icN}为行号在物品矩阵Y中选取的N个行向量形成的子矩阵

Ru,Iu为以Iu={ic1,icN}为索引,在评分矩阵R的第u行的行向量中选取的N个元素,形成的子行向量

因此,我们有

网上的博客,许多博主给出类似下面形式的结论不是很严谨,主要是损失函数的理解不到位导致的。

同理,我们定义物品i被评分的用户集合为Ui={ud1,udM}"role="presentation"SegoeUI",Roboto,Oxygen,Ubuntu,"FiraSans","DroidSans","HelveticaNeue",sans-serif;font-variant-ligatures:normal;font-variant-caps:normal;orphans:2;widows:2;-webkit-text-stroke-width:0px;background-color:rgb(255,255,255);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;">Ui={ud1,udM}Ui={ud1,udM}

根据对称性可得

XUi"role="presentation"SegoeUI",Roboto,Oxygen,Ubuntu,"FiraSans","DroidSans","HelveticaNeue",sans-serif;font-variant-ligatures:normal;font-variant-caps:normal;orphans:2;widows:2;-webkit-text-stroke-width:0px;background-color:rgb(255,255,255);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;">XUiXUi为以Ui={ud1,,udM}"role="presentation"SegoeUI",Roboto,Oxygen,Ubuntu,"FiraSans","DroidSans","HelveticaNeue",sans-serif;font-variant-ligatures:normal;font-variant-caps:normal;orphans:2;widows:2;-webkit-text-stroke-width:0px;background-color:rgb(255,255,255);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;">Ui={ud1,,udM}Ui={ud1,,udM}为行号在用户矩阵X中选取的M个行向量形成的子矩阵

Ri,Ui"role="presentation"SegoeUI",Roboto,Oxygen,Ubuntu,"FiraSans","DroidSans","HelveticaNeue",sans-serif;font-variant-ligatures:normal;font-variant-caps:normal;orphans:2;widows:2;-webkit-text-stroke-width:0px;background-color:rgb(255,255,255);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;">Ri,UiRi,Ui为以Ui={ud1,,udM}"role="presentation"SegoeUI",Roboto,Oxygen,Ubuntu,"FiraSans","DroidSans","HelveticaNeue",sans-serif;font-variant-ligatures:normal;font-variant-caps:normal;orphans:2;widows:2;-webkit-text-stroke-width:0px;background-color:rgb(255,255,255);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;">Ui={ud1,,udM}Ui={ud1,,udM}为索引,在评分矩阵R的第i列的列向量中选取的M个元素,形成的子列向量

此外,Ik"role="presentation"SegoeUI",Roboto,Oxygen,Ubuntu,"FiraSans","DroidSans","HelveticaNeue",sans-serif;font-variant-ligatures:normal;font-variant-caps:normal;orphans:2;widows:2;-webkit-text-stroke-width:0px;background-color:rgb(255,255,255);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;">IkIk为单位矩阵

如果读者感觉上述的推导还是很抽象,我们也给一个具体实例来体会一下中间过程

注意到损失函数是一个标量,这里我们只展开涉及到x1,1,x1,2"role="presentation"SegoeUI",Roboto,Oxygen,Ubuntu,"FiraSans","DroidSans","HelveticaNeue",sans-serif;font-variant-ligatures:normal;font-variant-caps:normal;orphans:2;widows:2;-webkit-text-stroke-width:0px;background-color:rgb(255,255,255);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;">x1,1,x1,2x1,1,x1,2的项,如下所示

让损失函数对x1,1,x1,2"role="presentation"SegoeUI",Roboto,Oxygen,Ubuntu,"FiraSans","DroidSans","HelveticaNeue",sans-serif;font-variant-ligatures:normal;font-variant-caps:normal;orphans:2;widows:2;-webkit-text-stroke-width:0px;background-color:rgb(255,255,255);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;">x1,1,x1,2x1,1,x1,2分别求偏导数可以得到

写成矩阵形式可得

利用我们上述的规则,很容易检验我们导出的结论。

总结来说,ALS的整个算法过程只有两步,涉及2个循环,如下图所示:

算法使用RMSE(root-mean-squareerror)评估误差。

当RMSE值变化很小时或者到达最大迭代步骤时,满足收敛条件,停止迭代。

“Talkischeap.Showmethecode.”作为小练习,我们给出上述伪代码的Python实现。

#迭代10次foriterinrange(10):foruinrange(1,m+1):Iu=np.array(X_idx_dict[u])YIu=Y[Iu-1]YIuT=YIu.TRuIu=R[u-1,Iu-1]xu=linear_solve(YIuT.dot(YIu)+_lambda*np.eye(k),YIuT.dot(RuIu))X[u-1]=xuforiinrange(1,n+1):Ui=np.array(Y_idx_dict[i])XUi=X[Ui-1]XUiT=XUi.TRiUi=R.T[i-1,Ui-1]yi=linear_solve(XUiT.dot(XUi)+_lambda*np.eye(k),XUiT.dot(RiUi))Y[i-1]=yi

最终,我们打印用户矩阵,物品矩阵,预测的评分矩阵如下,可以看到预测的评分矩阵非常逼近原始评分矩阵。

#Xarray([[1.30678487,2.03300876,3.70447639],[4.96150381,1.03500693,1.62261161],[6.37691007,2.4290095,1.03465981],[0.41680155,3.31805612,3.24755801],[1.26803845,3.57580564,2.08450113]])#Yarray([[0.24891282,1.07434519,0.40258993],[0.12832662,0.17923216,0.72376732],[-0.00149517,0.77412863,0.12191856],[0.12398438,0.46163336,1.05188691],[0.07668894,0.61050204,0.59753081],[0.53437855,0.20862131,0.08185176]])#X.dot(Y.T)预测评分array([[4.00081359,3.2132548,2.02350084,4.9972158,3.55491072,1.42566466],[3.00018371,1.99659282,0.99163666,2.79974661,1.98192672,3.00005934],[4.61343295,2.00253692,1.99697545,3.00029418,2.59019481,3.99911584],[4.97591903,2.99866546,2.96391664,4.99946603,3.99816006,1.18076534],[4.99647978,2.31231627,3.02037696,4.0005876,3.5258348,1.59422188]])#原始评分矩阵array([[4,0,2,5,0,0],[3,2,1,0,0,3],[0,2,0,3,0,4],[0,3,3,5,4,0],[5,0,3,4,0,0]])

三、SparkALS应用

Spark的内部实现并不是我们上面所列的算法,但是核心原理是完全一样的,Spark实现的是上述伪代码的分布式版本,具体算法参考Large-scaleParallelCollaborativeFilteringfortheNetflixPrize。其次,查阅Spark的官方文档,我们也注意到,Spark使用的惩罚函数与我们上文的有细微的差别。

其中nu,ni"role="presentation"SegoeUI",Roboto,Oxygen,Ubuntu,"FiraSans","DroidSans","HelveticaNeue",sans-serif;font-variant-ligatures:normal;font-variant-caps:normal;orphans:2;widows:2;-webkit-text-stroke-width:0px;background-color:rgb(255,255,255);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;">nu,ninu,ni分别表示用户u打分的物品数量和物品i被打分的用户数量。即

本小节通过两个案例来了解SparkALS的具体使用,以及在面对互联网实际工程场景下的应用。

3.1Demo案例

以第一节给出的数据为例,将三元组(User,Item,Rating)组织为als-demo-data.csv,该demo数据集涉及5个用户和6个物品。

userId,itemId,rating1,1,41,3,21,4,52,1,32,2,22,3,12,6,33,2,23,4,33,6,44,2,34,3,34,4,54,5,45,1,55,3,35,4,4

使用Spark的ALS类使用非常简单,只需将三元组(User,Item,Rating)数据输入模型进行训练。

importorg.apache.spark.sql.SparkSessionimportorg.apache.spark.ml.recommendation.ALSvalspark=SparkSession.builder().appName("als-demo").master("local[*]").getOrCreate()valrating=spark.read.options(Map("inferSchema"->"true","delimiter"->",","header"->"true")).csv("./data/als-demo-data.csv")//展示前5条评分记录rating.show(5)valals=newALS().setMaxIter(10)//迭代次数,用于最小二乘交替迭代的次数.setRank(3)//隐向量的维度.setRegParam(0.01)//惩罚系数.setUserCol("userId")//user_id.setItemCol("itemId")//item_id.setRatingCol("rating")//评分列valmodel=als.fit(rating)//训练模型//打印用户向量和物品向量model.userFactors.show(truncate=false)model.itemFactors.show(truncate=false)//给所有用户推荐2个物品model.recommendForAllUsers(2).show()

上述代码在控制台输出结果如下:

|userId|itemId|rating||1|1|4||1|3|2||1|4|5||2|1|3||2|2|2|onlyshowingtop5rows|id|features||1|[-0.17339179,1.3144133,0.04453602]||2|[-0.3189066,1.0291641,0.12700711]||3|[-0.6425665,1.2283803,0.26179287]||4|[0.5160747,0.81320006,-0.57953185]||5|[0.645193,0.26639006,0.68648624]||id|features||1|[2.609607,3.2668495,3.554771]||2|[0.85432494,2.3137972,-1.1198239]||3|[3.280517,1.9563107,0.51483333]||4|[3.7446978,4.259611,0.6640027]||5|[1.6036265,2.5602736,-1.8897828]||6|[-1.2651576,2.4723763,0.51556784]||userId|recommendations||1|[[4,4.9791617],[1,3.9998217]]|//对应物品的序号和预测评分|2|[[4,3.273963],[6,3.0134287]]||3|[[6,3.9849386],[1,3.2667015]]||4|[[4,5.011649],[5,4.004795]]||5|[[1,4.994258],[4,4.0065994]]|

我们使用numpy来验证Spark的结果,并且用Excel可视化评分矩阵。

importnumpyasnpX=np.array([[-0.17339179,1.3144133,0.04453602],[-0.3189066,1.0291641,0.12700711],[-0.6425665,1.2283803,0.26179287],[0.5160747,0.81320006,-0.57953185],[0.645193,0.26639006,0.68648624]])Y=np.array([[2.609607,3.2668495,3.554771],[0.85432494,2.3137972,-1.1198239],[3.280517,1.9563107,0.51483333],[3.7446978,4.259611,0.6640027],[1.6036265,2.5602736,-1.8897828],[-1.2651576,2.4723...

THE END
1.相关商品推荐:基于协同过滤的推荐算法协同过滤推荐算法是一种根据用户之间的相互作用(例如购买、评分、喜好等)来推荐商品的算法。它可以分为基于用户的协同过滤和基于物品的协同过滤两种类型。 适用场景 协同过滤算法适用于很多领域,比如电商平台、社交网络、新闻推荐、音乐电影推荐等。通过分析用户的行为,协同过滤算法可以为用户提供个性化的推荐产品或内容,提...https://www.jianshu.com/p/396b7c403ee4
2.通过社交网络关系的图卷积协同过滤实现的产品推荐方法基于邻域的协同过滤算法主要使用用户-物品交互数据或样本数据来完成预 测,可以将其进一步分为基于用户的协同过滤算法和基于物品的协同过滤算法。 [0193] 基于用户的协同过滤算法原理是利用其相似用户对该物品的所有评分的加权平均值,以此来 预测用户对某项物品的未知评分,而基于物品的协同过滤算法是预测用户对某项物品...https://www.xjishu.com/zhuanli/55/202111235556.html
3.推荐算法——基于物品的协同过滤算法标签: 算法 收藏 基于用户的协同过滤算法在用户增长的时候,相似度计算的计算会越来越困难。基于物品的算法给用户推荐他们之前喜欢的物品相似的物品。 算法步骤 计算物品之间的相似度 根据物品的相似度和用户的历史行为给用户生成推荐列表 相似度公式如下: wij=|N(i)∩N(j)||N(i)||N(j)|...https://www.imooc.com/article/27099
1....原理以及代码实践基于物品的协同过滤算法原理基于用户(user-based)的协同过滤(UserCF) 基于物品(item-based)的协同过滤(ItemCF算法) 基于模型(model-based)的协同过滤 (ModelCF算法) 本文主要讲述基于物品的协同过滤(ItemCF)算法的原理以及代码实现。ItemCF算法跟UserCF算法十分相似,关于UserCF算法的介绍可以参考这篇文章。 https://blog.csdn.net/a15835774652/article/details/136583397
2.协同过滤推荐算法(一)原理与实现腾讯云开发者社区协同过滤推荐算法分为两类,分别是基于用户的协同过滤算法(user-based collaboratIve filtering),和基于物品的协同过滤算法(item-based collaborative filtering)。简单的说就是:人以类聚,物以群分。下面我们将分别说明这两类推荐算法的原理和实现方法。 以上是常见的几种评价方式。https://cloud.tencent.com/developer/article/2098165
3.推荐系统算法实战协同过滤CF算法(CollaborativeFiltering...协同过滤分为基于用户的协同过滤和基于标的物(物品)的协同过滤两类算法。下面我们对协同过滤的算法原理来做详细的介绍。 推荐算法种类很多,但是目前应用最广泛的就是协同过滤算法。 协同过滤简单来说是利用某兴趣相投、拥有共同经验之群体的喜好来推荐用户感兴趣的信息,个人通过合作的机制给予信息相当程度的回应(如评分...https://blog.51cto.com/u_15236724/5968286
4.基于用户相似度的随机游走社交网络事件推荐算法协同过滤是推荐领域应用最为广泛的算法[1], 主要包括3种类型:基于用户的协同过滤[2]、基于物品的协同过滤[3]及基于模型的协同过滤[4].基于用户与物品的协同过滤算法通过计算用户或物品之间的相似度完成对目标用户的推荐, 随着用户与物品的增加, 数据稀疏性和冷启动问题制约该算法的推荐质量.矩阵分解是应用最广泛的...https://xuebao.neu.edu.cn/natural/article/html/2019-11-1533.htm