chenfei0 2020-06-15
协同过滤算法是一种利用集体智慧的方法,它类似与朋友推荐,当你想要看一个电影时,你会去询问跟你有着相同喜好的人有没有自己没看过的好电影。这就是协同过滤的核心思想
在开始讲解本次的前,我们先介绍下常见的几种协同过滤算法
协同过滤一般分为三类:第一种是基于用户(user-based)的协同过滤,第二种是基于项目(item-based)的协同过滤,第三种是基于模型(model based)的协同过滤。
其中第一种是考虑用户之间的相似性,先根据被推荐的用户,寻找相似的用户,找到一个相似用户后,预测被推荐用户对相似用户喜欢的物品的评分,然后再寻找相似的用户,直到满足终止条件为止。然后这些物品中评分最高的一批就是我们要推荐给被推荐的用户的物品
第二种与第一种十分相似,不同点在于,不是先寻找相似用户,再预测物品的评分,而是直接寻找相似的物品,然后预测物品的评分,评分高的一批就是推荐的物品。
基于模型的协同过滤作为目前最主流的协同过滤类型,其相关算法可以写一本书了,当然我们这里主要是对其思想做有一个归类概括。我们的问题是这样的m个物品,m个用户的数据,只有部分用户和部分数据之间是有评分数据的,其它部分评分是空白,此时我们要用已有的部分稀疏数据来预测那些空白的物品和数据之间的评分关系,找到最高评分的物品推荐给用户。
一般对于小型的推荐系统来说,基于项目的协同过滤肯定是主流。但是如果是大型的推荐系统来说,则可以考虑基于用户的协同过滤,当然更加可以考虑第三种类型,基于模型的协同过滤。
一般的基于项目的协同过滤算法的基础公式如下
I(ij)是代表用户i和用户j共同评价过的物品, R(i,x)代表用户i对物品x的评分, R(i)头上有一杠的代表用户i所有评分的平均分, 之所以要减去平均分是因为有的用户打分严有的松, 归一化用户打分避免相互影响。该公式其实就是数学中的Pearson相关系数,对于评分数据不规范时,皮尔逊相关度评价也能够给出不错的结果。
协同过滤的步骤一般分为三步
要从用户的行为和偏好中发现规律,并基于此给予推荐,如何收集用户的偏好信息成为系统推荐效果最基础的决定因素。用户有很多方式向系统提供自己的偏好信息,而且不同的应用也可能大不相同
在本项目中我们推荐的项目是回答。用户对于回答的操作有浏览、点赞、反对、评论、举报五种操作。这五种操作都需要存储在数据库中,方便推荐算法去获取用户数据。
在收集到用户数据后,我们还要对用户数据进行一定的预处理,其中最重要的就是:减噪和归一化
收集到的用户数据在经过处理后便形成一个用户偏好的二维矩阵,一维是用户列表,另一维是物品列表,值是用户对物品的偏好,是在[0,1]内浮点数值。
这里根据不同的项目有不同的加权和分组,故不展示代码
关于相似度的计算,现有的几种基本方法都是基于向量(Vector)的,其实也就是计算两个向量的距离,距离越近相似度越大。
本项目采用的是皮尔逊相关系数(Pearson Correlation Coefficient)
皮尔逊相关系数一般用于计算两个定距变量间联系的紧密程度,它的取值在 [-1,+1] 之间。
公式如下:
Java实现代码:
/** * 计算皮尔逊相关系数 * @param mapX 向量A * @param mapY 向量B * @return 两个回答的Pearson相关系数,值在[-1,1]间 */ public static double caculatePearson(Map<BigInteger, Double> mapX, Map<BigInteger, Double> mapY) { // (sum(X*Y) - (sum(X)*sum(Y)/N))/sqr((sum(pow(X))-(pow(sum(X))/N)) * ((sum(pow(Y))-(pow(sum(Y))/N)))) double sumXY = 0d; double sumX = 0d; double sumY = 0d; double sumPowX = 0d; double sumPowY = 0d; Set<BigInteger> setItem = new HashSet<>(); for (Map.Entry<BigInteger, Double> entry : mapX.entrySet()) { setItem.add(entry.getKey()); } for (Map.Entry<BigInteger, Double> entry : mapY.entrySet()) { setItem.add(entry.getKey()); } for (BigInteger bookId : setItem) { Double x = mapX.get(bookId); if (x == null) { x = 0d; } Double y = mapY.get(bookId); if (y == null) { y = 0d; } sumXY += x * y; sumX += x; sumY += y; sumPowX += Math.pow(x, 2); sumPowY += Math.pow(y, 2); } int n = setItem.size(); double pearson = (sumXY - sumX * sumY / n) / Math.sqrt((sumPowX - Math.pow(sumX, 2) / n) * (sumPowY - Math.pow(sumY, 2) / n)); return pearson; }
通过计算后,便能得到两个回答之间的相似度了,
经过了前两步,我们就可以得到物品之间的相似度。
然后我们就需要根据用户的历史偏好,找出用户喜欢的物品,然后推荐相似的物品给他。从计算的角度看,就是将所有用户对某个物品的偏好作为一个向量来计算物品之间的相似度,得到物品的相似物品后,根据用户历史的偏好预测当前用户还没有表示偏好的物品,计算得到一个排序的物品列表作为推荐。
下图 给出了一个例子,对于物品 A,根据所有用户的历史偏好,喜欢物品 A 的用户都喜欢物品 C,得出物品 A 和物品 C 比较相似,而用户 C 喜欢物品 A,那么可以推断出用户 C 可能也喜欢物品 C。
冷启动问题又称第一评价问题(first- rater),或新物品问题(New-item),从一定角度可以看成是稀疏问题的极端情况。因为传统的协同过滤推荐是基于相似用户/物品计算来得到目标用户的推荐,在一个新的项目首次出现的时候,因为没有用户对它作过评价,因此单纯的协同过滤无法对其进行预测评分和推荐。
在本项目中,对新用户没有可推荐的问题时,便推荐数据库中最新的回答。由于项目本身是一个问答网站,推荐最新的回答也有利于用户发现新的回答并做出互动。
此外还有一种方案是利用用户的注册信息/认证信息。在用户初次登录时,由用户选择适合自己的标签/风格,然后去推荐相关领域的物品
过滤协同是一种比较经典的推荐算法了,而本文所介绍的基于项目的过滤协同算法,是过滤协同中基于领域的一类。该算法实现起来相对简单,对数学知识要求也不高,容易理解其思想。但是协同过滤也有些难以避免的难题,比如令人头疼的“冷启动”问题,我们没有新用户任何数据的时候,无法较好的为新用户推荐物品。同时也没有考虑情景的差异,比如根据用户所在的场景和用户当前的情绪。当然,也无法得到一些小众的独特喜好,这块是基于内容的推荐比较擅长的。
总的来说对于软工实践普遍的项目来说,实现简单,初期先用来推荐,但“冷启动”问题是这个算法很大的弊端,毕竟项目的规模一般不会太多,冷启动问题不好解决。可以考虑采用混合推荐算法。