所以,协同过滤算法主要分为两个步骤:
1、寻找相似的用户集合;
2、寻找集合中用户喜欢的且目标用户没有的进行推荐。
Jaccard系数主要用于计算符号度量或布尔值度量的个体间的相似度,因为个体的特征属性都是由符号度量或者布尔值标识,因此无法衡量差异具体值的大小,只能获得“是否相同”这个结果,所以Jaccard系数只关心个体间共同具有的特征是否一致这个问题。如果比较X与Y的Jaccard相似系数,只比较xn和yn中相同的个数。
上述四个公式等价,其中E是数学期望,cov表示协方差,N表示变量取值的个数。
假定两个用户X、Y,均为n维向量,表示用户对n个商品的评分,那么X与Y的欧几里德距离就是:
数值越小则代表相似度越高,但是对于不同的n,计算出来的距离不便于控制,所以需要进行如下转换:
使得结果分布在(0,1]上,数值越大,相似度越高。
余弦距离,也称为余弦相似度,是用向量空间中两个向量余弦值作为衡量两个个体间差异大小的度量值。
与前面的欧几里德距离相似,用户X、Y为两个n维向量,套用余弦公式,其余弦距离表示为:
即两个向量夹角的余弦值。但是相比欧式距离,余弦距离更加注意两个向量在方向上的相对差异,而不是在空间上的绝对距离,具体可以借助下图来感受两者间的区别:
在选取上述方法中的一种得到各个用户之间相似度后,针对目标用户u,我们选出最相似的k个用户,用集合S(u,k)表示,将S中所有用户喜欢的物品提取出来并去除目标用户u已经喜欢的物品。然后对余下的物品进行评分与相似度加权,得到的结果进行排序。最后由排序结果对目标用户u进行推荐。其中,对于每个可能推荐的物品i,用户u对其的感兴趣的程度可以用如下公式计算:
rvi表示用户v对i的喜欢程度,即对i的评分,wuv表示用户u和v之间的相似度。
要从用户的行为和偏好中发现规律,并基于此给予推荐,如何收集用户的偏好信息成为系统推荐效果最基础的决定因素。用户有很多方式向系统提供自己的偏好信息,而且不同的应用也可能大不相同,下面举例进行介绍:
表1用户行为和用户偏好
以上列举的用户行为都是比较通用的,推荐引擎设计人员可以根据自己应用的特点添加特殊的用户行为,并用他们表示用户对物品的喜好。
在一般应用中,我们提取的用户行为一般都多于一种,关于如何组合这些不同的用户行为,基本上有以下两种方式:
收集了用户行为数据,我们还需要对数据进行一定的预处理,其中最核心的工作就是:减噪和归一化。
进行的预处理后,根据不同应用的行为分析方法,可以选择分组或者加权处理,之后我们可以得到一个用户偏好的二维矩阵,一维是用户列表,另一维是物品列表,值是用户对物品的偏好,一般是[0,1]或者[-1,1]的浮点数值。
基于用户的CF的基本思想相当简单,基于用户对物品的偏好找到相邻邻居用户,然后将邻居用户喜欢的推荐给当前用户。计算上,就是将一个用户对所有物品的偏好作为一个向量来计算用户之间的相似度,找到K邻居后,根据邻居的相似度权重以及他们对物品的偏好,预测当前用户没有偏好的未涉及物品,计算得到一个排序的物品列表作为推荐。图2给出了一个例子,对于用户A,根据用户的历史偏好,这里只计算得到一个邻居–用户C,然后将用户C喜欢的物品D推荐给用户A。
图2基于用户的CF的基本原理
基于物品的CF的原理和基于用户的CF类似,只是在计算邻居时采用物品本身,而不是从用户的角度,即基于用户对物品的偏好找到相似的物品,然后根据用户的历史偏好,推荐相似的物品给他。从计算的角度看,就是将所有用户对某个物品的偏好作为一个向量来计算物品之间的相似度,得到物品的相似物品后,根据用户历史的偏好预测当前用户还没有表示偏好的物品,计算得到一个排序的物品列表作为推荐。图3给出了一个例子,对于物品A,根据所有用户的历史偏好,喜欢物品A的用户都喜欢物品C,得出物品A和物品C比较相似,而用户C喜欢物品A,那么可以推断出用户C可能也喜欢物品C。
图3基于物品的CF的基本原理
前面介绍了UserCF和ItemCF的基本原理,下面我们分几个不同的角度深入看看它们各自的优缺点和适用场景:
ItemCF和UserCF是基于协同过滤推荐的两个最基本的算法,UserCF是很早以前就提出来了,ItemCF是从Amazon的论文和专利发表之后(2001年左右)开始流行,大家都觉得ItemCF从性能和复杂度上比UserCF更优,其中的一个主要原因就是对于一个在线网站,用户的数量往往大大超过物品的数量,同时物品的数据相对稳定,因此计算物品的相似度不但计算量较小,同时也不必频繁更新。但我们往往忽略了这种情况只适应于提供商品的电子商务网站,对于新闻,博客或者微内容的推荐系统,情况往往是相反的,物品的数量是海量的,同时也是更新频繁的,所以单从复杂度的角度,这两个算法在不同的系统中各有优势,推荐引擎的设计者需要根据自己应用的特点选择更加合适的算法。
相反的,在现今很流行的社交网络站点中,UserCF是一个更不错的选择,UserCF加上社会网络信息,可以增加用户对推荐解释的信服程度。
研究推荐引擎的学者们在相同的数据集合上分别用UserCF和ItemCF计算推荐结果,发现推荐列表中,只有50%是一样的,还有50%完全不同。但是这两个算法确有相似的精度,所以可以说,这两个算法是很互补的。
关于推荐的多样性,有两种度量方法:
第一种度量方法是从单个用户的角度度量,就是说给定一个用户,查看系统给出的推荐列表是否多样,也就是要比较推荐列表中的物品之间两两的相似度,不难想到,对这种度量方法,ItemCF的多样性显然不如UserCF的好,因为ItemCF的推荐就是和以前看的东西最相似的。
前面我们大部分都是从推荐引擎的角度考虑哪个算法更优,但其实我们更多的应该考虑作为推荐引擎的最终使用者—应用用户对推荐算法的适应度。
对于UserCF,推荐的原则是假设用户会喜欢那些和他有相同喜好的用户喜欢的东西,但如果一个用户没有相同喜好的朋友,那UserCF的算法的效果就会很差,所以一个用户对的CF算法的适应度是和他有多少共同喜好用户成正比的。
ItemCF算法也有一个基本假设,就是用户会喜欢和他以前喜欢的东西相似的东西,那么我们可以计算一个用户喜欢的物品的自相似度。一个用户喜欢物品的自相似度大,就说明他喜欢的东西都是比较相似的,也就是说他比较符合ItemCF方法的基本假设,那么他对ItemCF的适应度自然比较好;反之,如果自相似度小,就说明这个用户的喜好习惯并不满足ItemCF方法的基本假设,那么对于这种用户,用ItemCF方法做出好的推荐的可能性非常低。
1,显式矩阵分解
要找到和“用户物品”矩阵近似的k维(低阶)矩阵,最终要求出如下两个矩阵:一个用于表示用户的U×k维矩阵,以及一个表征物品的I×k维矩阵。
这两个矩阵也称作因子矩阵。它们的乘积便是原始评级矩阵的一个近似。值得注意的是,原始评级矩阵通常很稀疏,但因子矩阵却是稠密的。
特点:因子分解类模型的好处在于,一旦建立了模型,对推荐的求解便相对容易。但也有弊端,即当用户和物品的数量很多时,其对应的物品或是用户的因子向量可能达到数以百万计。
这将在存储和计算能力上带来挑战。另一个好处是,这类模型的表现通常都很出色。
2,隐式矩阵分解(关联因子分确定,可能随时会变化)
隐式模型仍然会创建一个用户因子矩阵和一个物品因子矩阵。但是,模型所求解的是偏好矩阵而非评级矩阵的近似。类似地,此时用户因子向量和物品因子向量的点积所得到的分数
也不再是一个对评级的估值,而是对某个用户对某一物品偏好的估值(该值的取值虽并不严格地处于0到1之间,但十分趋近于这个区间)
3,最小二乘法(AlternatingLeastSquaresALS):解决矩阵分解的最优化方法
ALS的实现原理是迭代式求解一系列最小二乘回归问题。在每一次迭代时,固定用户因子矩阵或是物品因子矩阵中的一个,然后用固定的这个矩阵以及评级数据来更新另一个矩阵。
之后,被更新的矩阵被固定住,再更新另外一个矩阵。如此迭代,直到模型收敛(或是迭代了预设好的次数)。
基于用户相似度片段代码:
valmovieFile=sc.textFile(fileName)valRatingDatas=movieFile.map(_.split("\t").take(3))//转为Ratings数据valratings=RatingDatas.map(x=>Rating(x(0).toInt,x(1).toInt,x(2).toDouble))//获取用户评价模型,设置k因子,和迭代次数,隐藏因子lambda,获取模型valmodel=ALS.train(ratings,50,10,0.01)//基于用户相似度推荐println("userNumber:"+model.userFeatures.count()+"\t"+"productNum:"+model.productFeatures.count())//指定用户及商品,输出预测值println(model.predict(789,123))//为指定用户推荐的前N商品model.recommendProducts(789,11).foreach(println(_))//为每个人推荐前十个商品model.recommendProductsForUsers(10).take(1).foreach{case(x,rating)=>println(rating(0))}基于商品相似度代码:
valitemFactory=model.productFeatures.lookup(567).headvalitemVector=newDoubleMatrix(itemFactory)//求余弦相似度valsim=model.productFeatures.map{case(id,factory)=>valfactorVector=newDoubleMatrix(factory)valsim=cosineSimilarity(factorVector,itemVector)(id,sim)}valsortedsim=sim.top(11)(Ordering.by[(Int,Double),Double]{case(id,sim)=>sim})println(sortedsim.take(10).mkString("\n"))defcosineSimilarity(vec1:DoubleMatrix,vec2:DoubleMatrix):Double={vec1.dot(vec2)/(vec1.norm2()*vec2.norm2())}均方差评估模型代码:
//模型评估,通过均误差//实际用户评估值valactualRatings=ratings.map{caseRating(user,item,rats)=>((user,item),rats)}valuserItems=ratings.map{case(Rating(user,item,rats))=>(user,item)}//模型的用户对商品的预测值valpredictRatings=model.predict(userItems).map{case(Rating(user,item,rats))=>((user,item),rats)}//联合获取rate值valrates=actualRatings.join(predictRatings).map{casex=>(x._2._1,x._2._2)}//求均方差valregressionMetrics=newRegressionMetrics(rates)//越接近0越佳println(regressionMetrics.meanSquaredError)全局准确率评估(MAP):
使用MLlib的RankingMetrics类来计算基于排名的评估指标。类似地,需要向我们之前的平均准确率函数传入一个键值对类型的RDD。其键为给定用户预测的推荐物品的ID数组,而值则是实际的物品ID数组。