推荐算法可以分为三大类,基于用户的,基于物品的和基于内容的,前两者均属于协同过滤的范畴,仅仅通过用户与物品之间的关系进行推荐,无需了解物品自身的属性。而几乎内容的推荐技术很有用,但是必须与特定领域相结合,比如推荐一本书就必须了解书的属性,作者,颜色,内容等等。但是这些知识无法转移到其他领域,比如基于内容的图书推荐就对推荐哪道菜比较好吃毫无用处。
所有mahout对基于内容的推荐涉及很少。
基于用户的推荐算法来源与对相似用户爱好的总结,一般过程如下:
for (用户u尚未表达偏好的) 每个物品i
for(对i有偏好的)每个其他用户v
计算u和v之间的相似度s
按权重为s将v对i的偏好并入平均值
return 值最高的物品(按加权平均排序)
看上去挺简单,但是,每个物品都检查速度太慢,一般会先计算出一个最相似用户的领域,然后仅考虑这些用户评价过的物品。
for(每个其他用户w)
计算用户u与用户w的相似度s
按相似度排序后,将位置靠前的用户作为领域n
for(n中用户有偏好,而用户u无偏好的)每个物品i
for(n中用户对i有偏好的)每个其他用户v
计算用户u与v的相似度s
按权重s将v对i的偏好计入平均值
return 值最高的物品
根据以上算法,可以具体化为以下步骤:
1. 数据模型,由DataModel实现
2. 用户间的相似性度量,由UserSimilarity实现
3. 用户邻域的定义,由UserNeighborhood实现
4. 推荐引擎,由一个Recommender实现
一个具体的例子如下:
//存储并计算提供计算所需的偏好,用户以及物品数据
DataModel model =
new FileDataModel(new File("D:\\mahoutData\\intro.csv"));
//比较两个用户之间的相似度
UserSimilarity similarity =
new PearsonCorrelationSimilarity(model);
//明确与给定用户最相似的一组用户
UserNeighborhood neighborhood = new NearestNUserNeighborhood(2, similarity, model);
//合并上述所有组件为用户推荐物品
Recommender recommender = new GenericUserBasedRecommender(
model, neighborhood, similarity);
List recommendations =
recommender.recommend(1, 1);
for (RecommendedItem recommendation : recommendations) {
System.out.println(recommendation);
}
要理解Pearson相关系数,首先要理解协方差(Covariance),协方差是一个反映两个随机变量相关程度的指标,如果一个变量跟随着另一个变量同时变大或者变小,那么这两个变量的协方差就是正值,反之相反,公式如下:
Pearson相关系数公式如下:
由公式可知,Pearson相关系数是用协方差除以两个变量的标准差得到的,虽然协方差能反映两个随机变量的相关程度(协方差大于0的时候表示两者正相关,小于0的时候表示两者负相关),但是协方差值的大小并不能很好地度量两个随机变量的关联程度 ,为了更好的度量两个随机变量的相关程度,引入了Pearson相关系数,其在协方差的基础上除以了两个随机变量的标准差,pearson是一个介于-1和1之间的值,当两个变量的线性关系增强时,相关系数趋于1或-1;当一个变量增大,另一个变量也增大时,表明它们之间是正相关的,相关系数大于0;如果一个变量增大,另一个变量却减小,表明它们之间是负相关的,相关系数小于0;如果相关系数等于0,表明它们之间不存在线性相关关系。
一个简单的例子:
用户 | 物品 | 喜欢的程度 |
---|---|---|
1 | 101 | 5.0 |
1 | 102 | 3.0 |
1 | 103 | 2.5 |
2 | 101 | 2.0 |
2 | 102 | 2.5 |
2 | 103 | 5.0 |
2 | 104 | 2.0 |
3 | 101 | 2.5 |
3 | 104 | 4.0 |
3 | 105 | 4.5 |
3 | 107 | 5.0 |
4 | 101 | 5.0 |
4 | 103 | 3.0 |
4 | 104 | 4.5 |
4 | 106 | 4.0 |
5 | 101 | 4.0 |
5 | 102 | 3.0 |
5 | 103 | 2.0 |
5 | 104 | 4.0 |
5 | 105 | 3.5 |
5 | 106 | 4.0 |
第一列代表用户,第二列代表物品,第三列代表喜欢的程度,0为不喜欢,5为很喜欢。
计算他们的皮尔逊相关系数,得到如下表格:
皮尔逊相关系数也并不是总靠谱,比如两个人只看过2部相同电影,评价相同或者两个人看过200部相同电影,绝大部分评分相同。依据后者推荐明显比前者靠谱,但是前者的皮尔逊相关系数就是高于后者。
为了解决上述问题,mahout的PearsonCorrelationSimilarity 方法引入了权重的概念,将Weighting.WEIGHTED作为第二个参数传递进去即可使得统计较多数量的物品时,相关值向1.0或-1.0偏移,而物品数量少的时候,向均值偏移。
使用这个方法将代码中的UserSimilarity改为 new EuclideanDistanceSimilarity(model)即可。
欧几里得距离不多赘述,用户即为多维空间中的点,不同的物品即为不同的维度,用户对物品的偏好向量即为坐标,然后算一下距离即可,为了结果与相似度正相关,通常取1/(1+d)为结果,d为欧几里得距离。
和欧式距离类似,一个多维坐标系中,两个点越近,其夹角越小。但是mahout中并没有具体的方法实现,因为当两个输入序列均值为0时,余弦相似度和皮尔逊距离归结为同一个计算过程。所以在协同过滤的时候直接使用皮尔逊相似度即可。
斯皮尔曼相关系数本质上时是皮尔逊相关系数的一个变体,他不是基于原始数据,而是只保留了原始数据的相对顺序,比如(1.5,5.0,2.6)就变为(3,1,2)。这种办法无法说好或不好,只能视情况而定。
和前面一样,把UserSimilarity换为SpearmanCorrelationSimilarity即可。值得注意的一点事,这个算法执行非常慢。。。。慢到mahout in action这本书是这么说的:
Run it, and take a long coffee break. Turn in for the night. It won’t finish anytime soon.
数据量较大的话可以喝杯咖啡或者睡觉了。。。
这种机制不关心偏好值的大小,只关心是否表达过相似度。
谷本系数即为交集与并集的比值,当用户偏好完全重合,比值为1,当完全不重合,比值为0,可以用简单的数学变换让结果落到[-1 , 1]上,即相似度 = 2* 相似度 -1。
用TanimotoCoefficientSimilarity 替换UserSimilarity即可。
这个涉及的数学知识。。。搞不明白。但是计算的结果可以解释为发送发生重叠的非偶然概率。用 TanimotoCoefficientSimilarity替换UserSimilarity即可。其结果往往优于谷本相似度。
因为计算相似度需要耗费大量时间,所以可以直接缓存在内存中:
UserSimilarity similarity = new CachingUserSimilarity(new SpearmanCorrelationSimilarity(model), model);
当然这回占用很多内存。
选择用户邻域直接影响到最后的结果,一般有如下两种选择邻域的方法:
这种算法其实就是knn(k nearest neighborhood),根据一些方法找到两个用户之间的距离,比如根据属性向量得到欧氏距离,然后寻找距离最近的k个。
这种算法下k的定义很重要,并不是越大越好,比如下图这种情况:
选择k为3就比k为5精度要高,所以往往需要细致的调参来确定一个合适的k值。上面的代码即为使用此种方法。
这种方法直接选择那些很相似的用户而忽略其他人,设置一个阈值,选择所有超过这个阈值的用户
与上面的最大不同点在于,只要满足这个阈值,不管有多少个,都会被统计在内。通常使用皮尔逊相关系数作为阈值的根据。
使用这种算法需要简单修改下代码,将
//明确与给定用户最相似的一组用户
UserNeighborhood neighborhood = new NearestNUserNeighborhood(2, similarity, model);
改为:
UserNeighborhood neighborhood = new ThresholdUserNeighborhood(0.7, similarity, model);
阈值的设置同样关键,也需要多次测试后得出。
基于物品的推荐与基于用户的推荐的本质区别在于,以物品的相似度为基础,而非用户。
for(用户u尚未表达偏好的)每个物品i
for(用户u表达偏好的)每个物品j
计算i与j之间的相似度s
按s为权重将j与i的相似度并入平均值
return 值最高的物品(按加权平均排序)
和基于用户的推荐的最主要的差别在于,用户的喜好在不停的变化,但是物品本身不容易变化。因为变化不大,所以适合预先计算相似度,可以大大提升运行时候的推荐效率。GenericItemSimilarity可以用来存储ItemSimilarity的计算结果。
public Recommender buildRecommender(DataModel model)
throws TasteException {
ItemSimilarity similarity = new PearsonCorrelationSimilarity(model);
return new GenericItemBasedRecommender(model, similarity);
}
相似度的判别方法和基于用户的一致,大部分的相似度判别方法都同时实现了两种接口。
同时依照算法并没有计算邻域的步骤,因为已经有了一些用户表达偏好的物品,这和邻域的计算结果是类似的。
以下是不同的相似度方法的结果:
当物品数量小于用户数量时候(比如电影的数量远小于看电影的人),基于物品的推荐快于基于用户的推荐。
这时另外一种基于物品的推荐算法。它基于新物品与用户评估过的物品之间的偏好值差异来预测用户对新物品的偏好值。可以看作用已经评估过的物品做参数求出一个线性函数,然后把新的物品代入,得到新的偏好值。
//预处理,完成所有物品之间偏好值差异
for (每个物品i)
for(每个其他物品j)
for(对i和j均有偏好的每个用户u)
将物品对(i,j)的偏好值差异加入u
//然后执行推荐算法
for(用户u未表达偏好的每个物品i)
for(用户u表达过偏好的每个物品j)
找到i与j之间的平均偏好值差异
添加该差异到u对j的偏好值
添加其至平均值
return 值最高的物品
由算法可以得出,其性能不受用户数目影响,仅依赖于物品偏好值之间的平均差异,可以预先计算好,当一个偏好值改变,只需改变其相关的差异值,但内存消耗较大,O(n^2),n为物品数。
DiffStorage diffStorage = new MemoryDiffStorage(
model, Weighting.UNWEIGHTED, Long.MAX_VALUE));
return new SlopeOneRecommender(
model,
Weighting.UNWEIGHTED,
Weighting.UNWEIGHTED,
diffStorage);
slope-one算法和皮尔逊相关系数有一样的问题,忽略了数量对差异可靠性的影响,同样也可以使用加权的方式来补偿,上述代码手动关闭了权重,默认开启。
因为占用内存太大,所以需要把偏好值差异序列化到磁盘,这时候可以使用JDBC连接mysql,不多做描述。
这两者虽然现在并未在mahout里实现,但却是未来的发展方向,读者有兴趣自行百度~
https://www.zhihu.com/question/19734616?sort=created
《Mahout In Action》
http://blog.csdn.net/xidianliutingting/article/details/51916578