Mahout 物品推荐 协同过滤

秒速五厘米 2021-06-11 15:10 549阅读 0赞

来源

前言

用Mahout来构建推荐系统,是一件既简单又困难的事情。简单是因为Mahout完整地封装了“协同过滤”算法,并实现了并行化,提供非常简单的API接口;困难是因为我们不了解算法细节,很难去根据业务的场景进行算法配置和调优。

本文将深入算法API去解释Mahout推荐算法底层的一些事。

目录

  1. Mahout推荐算法介绍
  2. 算法评判标准:召回率与准确率
  3. Recommender.java的API接口
  4. 测试程序:RecommenderTest.java
  5. 基于用户的协同过滤算法UserCF
  6. 基于物品的协同过滤算法ItemCF
  7. SlopeOne算法
  8. KNN Linear interpolation item–based推荐算法
  9. SVD推荐算法
  10. Tree Cluster-based 推荐算法
  11. Mahout推荐算法总结

1. Mahout推荐算法介绍

Mahoutt推荐算法,从数据处理能力上,可以划分为2类:

  • 单机内存算法实现
  • 基于Hadoop的分步式算法实现

1). 单机内存算法实现

单机内存算法实现:就是在单机下运行的算法,是由cf.taste项目实现的,像我的们熟悉的UserCF,ItemCF都支持单机内存运行,并且参数可以灵活配置。单机算法的基本实例,请参考文章:用Maven构建Mahout项目

单机内存算法的问题在于,受限于单机的资源。对于中等规模的数据,像1G,10G的数据量,有能力进行计算,但是超过100G的数据量,对于单机来说是不可能完成的任务。

2). 基于Hadoop的分步式算法实现

基于Hadoop的分步式算法实现:就是把单机内存算法并行化,把任务分散到多台计算机一起运行。Mahout提供了ItemCF基于Hadoop并行化算法实现。基于Hadoop的分步式算法实现,请参考文章:
Mahout分步式程序开发 基于物品的协同过滤ItemCF

分步式并行算法的问题在于,如何让单机算法并行化。在单机算法中,我们只需要考虑算法,数据结构,内存,CPU就够了,但是分步式算法还要额外考虑很多的情况,比如多节点的数据合并,数据排序,网路通信的效率,节点宕机重算,数据分步式存储等等的很多问题。

2. 算法评判标准:召回率(recall)与查准率(precision)

Mahout提供了2个评估推荐器的指标,查准率和召回率(查全率),这两个指标是搜索引擎中经典的度量方法。

precision\_recall

  1. 相关 不相关
  2. 检索到 A C
  3. 未检索到 B D
  • A:检索到的,相关的 (搜到的也想要的)
  • B:未检索到的,但是相关的 (没搜到,然而实际上想要的)
  • C:检索到的,但是不相关的 (搜到的但没用的)
  • D:未检索到的,也不相关的 (没搜到也没用的)

被检索到的越多越好,这是追求“查全率”,即A/(A+B),越大越好。
被检索到的,越相关的越多越好,不相关的越少越好,这是追求“查准率”,即A/(A+C),越大越好。

在大规模数据集合中,这两个指标是相互制约的。当希望索引出更多的数据的时候,查准率就会下降,当希望索引更准确的时候,会索引更少的数据。

3. Recommender的API接口

1). 系统环境:

  • Win7 64bit
  • Java 1.6.0_45
  • Maven 3
  • Eclipse Juno Service Release 2
  • Mahout 0.8
  • Hadoop 1.1.2

2). Recommender接口文件:
org.apache.mahout.cf.taste.recommender.Recommender.java

mahout-Recommender-class

接口中方法的解释:

  • recommend(long userID, int howMany): 获得推荐结果,给userID推荐howMany个Item
  • recommend(long userID, int howMany, IDRescorer rescorer): 获得推荐结果,给userID推荐howMany个Item,可以根据rescorer对结构重新排序。
  • estimatePreference(long userID, long itemID): 当打分为空,估计用户对物品的打分
  • setPreference(long userID, long itemID, float value): 赋值用户,物品,打分
  • removePreference(long userID, long itemID): 删除用户对物品的打分
  • getDataModel(): 提取推荐数据

通过Recommender接口,我可以猜出核心算法,应该会在子类的estimatePreference()方法中进行实现。

3). 通过继承关系到Recommender接口的子类:

mahout-Recommender-hierarchy

推荐算法实现类:

  • GenericUserBasedRecommender: 基于用户的推荐算法
  • GenericItemBasedRecommender: 基于物品的推荐算法
  • KnnItemBasedRecommender: 基于物品的KNN推荐算法
  • SlopeOneRecommender: Slope推荐算法
  • SVDRecommender: SVD推荐算法
  • TreeClusteringRecommender:TreeCluster推荐算法

下面将分别介绍每种算法的实现。

4. 测试程序:RecommenderTest.java

测试数据集:item.csv

  1. 1,101,5.0
  2. 1,102,3.0
  3. 1,103,2.5
  4. 2,101,2.0
  5. 2,102,2.5
  6. 2,103,5.0
  7. 2,104,2.0
  8. 3,101,2.5
  9. 3,104,4.0
  10. 3,105,4.5
  11. 3,107,5.0
  12. 4,101,5.0
  13. 4,103,3.0
  14. 4,104,4.5
  15. 4,106,4.0
  16. 5,101,4.0
  17. 5,102,3.0
  18. 5,103,2.0
  19. 5,104,4.0
  20. 5,105,3.5
  21. 5,106,4.0

测试程序:org.conan.mymahout.recommendation.job.RecommenderTest.java

  1. package org.conan.mymahout.recommendation.job;
  2. import java.io.IOException;
  3. import java.util.List;
  4. import org.apache.mahout.cf.taste.common.TasteException;
  5. import org.apache.mahout.cf.taste.eval.RecommenderBuilder;
  6. import org.apache.mahout.cf.taste.impl.common.LongPrimitiveIterator;
  7. import org.apache.mahout.cf.taste.model.DataModel;
  8. import org.apache.mahout.cf.taste.recommender.RecommendedItem;
  9. import org.apache.mahout.common.RandomUtils;
  10. public class RecommenderTest {
  11. final static int NEIGHBORHOOD_NUM = 2;
  12. final static int RECOMMENDER_NUM = 3;
  13. public static void main(String[] args) throws TasteException, IOException {
  14. RandomUtils.useTestSeed();
  15. String file = "datafile/item.csv";
  16. DataModel dataModel = RecommendFactory.buildDataModel(file);
  17. slopeOne(dataModel);
  18. }
  19. public static void userCF(DataModel dataModel) throws TasteException{}
  20. public static void itemCF(DataModel dataModel) throws TasteException{}
  21. public static void slopeOne(DataModel dataModel) throws TasteException{}
  22. ...

每种算法都一个单独的方法进行算法测试,如userCF(),itemCF(),slopeOne()….

5. 基于用户的协同过滤算法UserCF

基于用户的协同过滤,通过不同用户对物品的评分来评测用户之间的相似性,基于用户之间的相似性做出推荐。简单来讲就是:给用户推荐和他兴趣相似的其他用户喜欢的物品。

举例说明:

image015

基于用户的 CF 的基本思想相当简单,基于用户对物品的偏好找到相邻邻居用户,然后将邻居用户喜欢的推荐给当前用户。计算上,就是将一个用户对所有物品的偏好作为一个向量来计算用户之间的相似度,找到 K 邻居后,根据邻居的相似度权重以及他们对物品的偏好,预测当前用户没有偏好的未涉及物品,计算得到一个排序的物品列表作为推荐。图 2 给出了一个例子,对于用户 A,根据用户的历史偏好,这里只计算得到一个邻居 – 用户 C,然后将用户 C 喜欢的物品 D 推荐给用户 A。

上文中图片和解释文字,摘自: https://www.ibm.com/developerworks/cn/web/1103_zhaoct_recommstudy2/

算法API: org.apache.mahout.cf.taste.impl.recommender.GenericUserBasedRecommender

  1. @Override
  2. public float estimatePreference(long userID, long itemID) throws TasteException {
  3. DataModel model = getDataModel();
  4. Float actualPref = model.getPreferenceValue(userID, itemID);
  5. if (actualPref != null) {
  6. return actualPref;
  7. }
  8. long[] theNeighborhood = neighborhood.getUserNeighborhood(userID);
  9. return doEstimatePreference(userID, theNeighborhood, itemID);
  10. }
  11. protected float doEstimatePreference(long theUserID, long[] theNeighborhood, long itemID) throws TasteException {
  12. if (theNeighborhood.length == 0) {
  13. return Float.NaN;
  14. }
  15. DataModel dataModel = getDataModel();
  16. double preference = 0.0;
  17. double totalSimilarity = 0.0;
  18. int count = 0;
  19. for (long userID : theNeighborhood) {
  20. if (userID != theUserID) {
  21. // See GenericItemBasedRecommender.doEstimatePreference() too
  22. Float pref = dataModel.getPreferenceValue(userID, itemID);
  23. if (pref != null) {
  24. double theSimilarity = similarity.userSimilarity(theUserID, userID);
  25. if (!Double.isNaN(theSimilarity)) {
  26. preference += theSimilarity * pref;
  27. totalSimilarity += theSimilarity;
  28. count++;
  29. }
  30. }
  31. }
  32. }
  33. // Throw out the estimate if it was based on no data points, of course, but also if based on
  34. // just one. This is a bit of a band-aid on the 'stock' item-based algorithm for the moment.
  35. // The reason is that in this case the estimate is, simply, the user's rating for one item
  36. // that happened to have a defined similarity. The similarity score doesn't matter, and that
  37. // seems like a bad situation.
  38. if (count <= 1) {
  39. return Float.NaN;
  40. }
  41. float estimate = (float) (preference / totalSimilarity);
  42. if (capper != null) {
  43. estimate = capper.capEstimate(estimate);
  44. }
  45. return estimate;
  46. }

测试程序:

  1. public static void userCF(DataModel dataModel) throws TasteException {
  2. UserSimilarity userSimilarity = RecommendFactory.userSimilarity(RecommendFactory.SIMILARITY.EUCLIDEAN, dataModel);
  3. UserNeighborhood userNeighborhood = RecommendFactory.userNeighborhood(RecommendFactory.NEIGHBORHOOD.NEAREST, userSimilarity, dataModel, NEIGHBORHOOD_NUM);
  4. RecommenderBuilder recommenderBuilder = RecommendFactory.userRecommender(userSimilarity, userNeighborhood, true);
  5. RecommendFactory.evaluate(RecommendFactory.EVALUATOR.AVERAGE_ABSOLUTE_DIFFERENCE, recommenderBuilder, null, dataModel, 0.7);
  6. RecommendFactory.statsEvaluator(recommenderBuilder, null, dataModel, 2);
  7. LongPrimitiveIterator iter = dataModel.getUserIDs();
  8. while (iter.hasNext()) {
  9. long uid = iter.nextLong();
  10. List list = recommenderBuilder.buildRecommender(dataModel).recommend(uid, RECOMMENDER_NUM);
  11. RecommendFactory.showItems(uid, list, true);
  12. }
  13. }

程序输出:

  1. AVERAGE_ABSOLUTE_DIFFERENCE Evaluater Score:1.0
  2. Recommender IR Evaluator: [Precision:0.5,Recall:0.5]
  3. uid:1,(104,4.333333)(106,4.000000)
  4. uid:2,(105,4.049678)
  5. uid:3,(103,3.512787)(102,2.747869)
  6. uid:4,(102,3.000000)

用R语言重写UserCF的实现,请参考文章:用R解析Mahout用户推荐协同过滤算法(UserCF)

6. 基于物品的协同过滤算法ItemCF

基于item的协同过滤,通过用户对不同item的评分来评测item之间的相似性,基于item之间的相似性做出推荐。简单来讲就是:给用户推荐和他之前喜欢的物品相似的物品。

举例说明:

image017

基于物品的 CF 的原理和基于用户的 CF 类似,只是在计算邻居时采用物品本身,而不是从用户的角度,即基于用户对物品的偏好找到相似的物品,然后根据用户的历史偏好,推荐相似的物品给他。从计算的角度看,就是将所有用户对某个物品的偏好作为一个向量来计算物品之间的相似度,得到物品的相似物品后,根据用户历史的偏好预测当前用户还没有表示偏好的物品,计算得到一个排序的物品列表作为推荐。图 3 给出了一个例子,对于物品 A,根据所有用户的历史偏好,喜欢物品 A 的用户都喜欢物品 C,得出物品 A 和物品 C 比较相似,而用户 C 喜欢物品 A,那么可以推断出用户 C 可能也喜欢物品 C。

上文中图片和解释文字,摘自: https://www.ibm.com/developerworks/cn/web/1103_zhaoct_recommstudy2/

算法API: org.apache.mahout.cf.taste.impl.recommender.GenericItemBasedRecommender

  1. @Override
  2. public float estimatePreference(long userID, long itemID) throws TasteException {
  3. PreferenceArray preferencesFromUser = getDataModel().getPreferencesFromUser(userID);
  4. Float actualPref = getPreferenceForItem(preferencesFromUser, itemID);
  5. if (actualPref != null) {
  6. return actualPref;
  7. }
  8. return doEstimatePreference(userID, preferencesFromUser, itemID);
  9. }
  10. protected float doEstimatePreference(long userID, PreferenceArray preferencesFromUser, long itemID)
  11. throws TasteException {
  12. double preference = 0.0;
  13. double totalSimilarity = 0.0;
  14. int count = 0;
  15. double[] similarities = similarity.itemSimilarities(itemID, preferencesFromUser.getIDs());
  16. for (int i = 0; i < similarities.length; i++) {
  17. double theSimilarity = similarities[i];
  18. if (!Double.isNaN(theSimilarity)) {
  19. // Weights can be negative!
  20. preference += theSimilarity * preferencesFromUser.getValue(i);
  21. totalSimilarity += theSimilarity;
  22. count++;
  23. }
  24. }
  25. // Throw out the estimate if it was based on no data points, of course, but also if based on
  26. // just one. This is a bit of a band-aid on the 'stock' item-based algorithm for the moment.
  27. // The reason is that in this case the estimate is, simply, the user's rating for one item
  28. // that happened to have a defined similarity. The similarity score doesn't matter, and that
  29. // seems like a bad situation.
  30. if (count <= 1) {
  31. return Float.NaN;
  32. }
  33. float estimate = (float) (preference / totalSimilarity);
  34. if (capper != null) {
  35. estimate = capper.capEstimate(estimate);
  36. }
  37. return estimate;
  38. }

测试程序:

  1. public static void itemCF(DataModel dataModel) throws TasteException {
  2. ItemSimilarity itemSimilarity = RecommendFactory.itemSimilarity(RecommendFactory.SIMILARITY.EUCLIDEAN, dataModel);
  3. RecommenderBuilder recommenderBuilder = RecommendFactory.itemRecommender(itemSimilarity, true);
  4. RecommendFactory.evaluate(RecommendFactory.EVALUATOR.AVERAGE_ABSOLUTE_DIFFERENCE, recommenderBuilder, null, dataModel, 0.7);
  5. RecommendFactory.statsEvaluator(recommenderBuilder, null, dataModel, 2);
  6. LongPrimitiveIterator iter = dataModel.getUserIDs();
  7. while (iter.hasNext()) {
  8. long uid = iter.nextLong();
  9. List list = recommenderBuilder.buildRecommender(dataModel).recommend(uid, RECOMMENDER_NUM);
  10. RecommendFactory.showItems(uid, list, true);
  11. }
  12. }

程序输出:

  1. AVERAGE_ABSOLUTE_DIFFERENCE Evaluater Score:0.8676552772521973
  2. Recommender IR Evaluator: [Precision:0.5,Recall:1.0]
  3. uid:1,(105,3.823529)(104,3.722222)(106,3.478261)
  4. uid:2,(106,2.984848)(105,2.537037)(107,2.000000)
  5. uid:3,(106,3.648649)(102,3.380000)(103,3.312500)
  6. uid:4,(107,4.722222)(105,4.313953)(102,4.025000)
  7. uid:5,(107,3.736842)

7. SlopeOne算法

这个算法在mahout-0.8版本中,已经被@Deprecated。

SlopeOne是一种简单高效的协同过滤算法。通过均差计算进行评分。SlopeOne论文下载(PDF)

1). 举例说明:
用户X,Y,Z,对于物品A,B进行打分,如下表,求Z对B的打分是多少?

slopeone

Slope one算法认为:平均值可以代替某两个未知个体之间的打分差异,事物A对事物B的平均差是:((5 - 4) + (4 - 2)) / 2 = 1.5,就得到Z对B的打分是,3-1.5 = 1.5。

Slope one算法将用户的评分之间的关系看作简单的线性关系:

  1. Y = mX + b

2). 平均加权计算:
用户X,Y,Z,对于物品A,B,C进行打分,如下表,求Z对A的打分是多少?

slopeone2

    1. 计算A和B的平均差, ((5-3)+(3-4))/2=0.5
    1. 计算A和C的平均差, (5-2)/1=3
    1. Z对A的评分,通过AB得到, 2+0.5=2.5
    1. Z对A的评分,通过AC得到,5+3=8
    1. 通过加权平均计算Z对A的评分:A和B都有评价的用户数为2,A和C都有评价的用户数为1,权重为别是2和1, (2*2.5+1*8)/(2+1)=13/3=4.33

通过这种简单的方式,我们可以快速计算出一个评分项,完成推荐过程!

算法API: org.apache.mahout.cf.taste.impl.recommender.slopeone.SlopeOneRecommender

  1. @Override
  2. public float estimatePreference(long userID, long itemID) throws TasteException {
  3. DataModel model = getDataModel();
  4. Float actualPref = model.getPreferenceValue(userID, itemID);
  5. if (actualPref != null) {
  6. return actualPref;
  7. }
  8. return doEstimatePreference(userID, itemID);
  9. }
  10. private float doEstimatePreference(long userID, long itemID) throws TasteException {
  11. double count = 0.0;
  12. double totalPreference = 0.0;
  13. PreferenceArray prefs = getDataModel().getPreferencesFromUser(userID);
  14. RunningAverage[] averages = diffStorage.getDiffs(userID, itemID, prefs);
  15. int size = prefs.length();
  16. for (int i = 0; i < size; i++) {
  17. RunningAverage averageDiff = averages[i];
  18. if (averageDiff != null) {
  19. double averageDiffValue = averageDiff.getAverage();
  20. if (weighted) {
  21. double weight = averageDiff.getCount();
  22. if (stdDevWeighted) {
  23. double stdev = ((RunningAverageAndStdDev) averageDiff).getStandardDeviation();
  24. if (!Double.isNaN(stdev)) {
  25. weight /= 1.0 + stdev;
  26. }
  27. // If stdev is NaN, then it is because count is 1. Because we're weighting by count,
  28. // the weight is already relatively low. We effectively assume stdev is 0.0 here and
  29. // that is reasonable enough. Otherwise, dividing by NaN would yield a weight of NaN
  30. // and disqualify this pref entirely
  31. // (Thanks Daemmon)
  32. }
  33. totalPreference += weight * (prefs.getValue(i) + averageDiffValue);
  34. count += weight;
  35. } else {
  36. totalPreference += prefs.getValue(i) + averageDiffValue;
  37. count += 1.0;
  38. }
  39. }
  40. }
  41. if (count <= 0.0) {
  42. RunningAverage itemAverage = diffStorage.getAverageItemPref(itemID);
  43. return itemAverage == null ? Float.NaN : (float) itemAverage.getAverage();
  44. } else {
  45. return (float) (totalPreference / count);
  46. }
  47. }

测试程序:

  1. public static void slopeOne(DataModel dataModel) throws TasteException {
  2. RecommenderBuilder recommenderBuilder = RecommendFactory.slopeOneRecommender();
  3. RecommendFactory.evaluate(RecommendFactory.EVALUATOR.AVERAGE_ABSOLUTE_DIFFERENCE, recommenderBuilder, null, dataModel, 0.7);
  4. RecommendFactory.statsEvaluator(recommenderBuilder, null, dataModel, 2);
  5. LongPrimitiveIterator iter = dataModel.getUserIDs();
  6. while (iter.hasNext()) {
  7. long uid = iter.nextLong();
  8. List list = recommenderBuilder.buildRecommender(dataModel).recommend(uid, RECOMMENDER_NUM);
  9. RecommendFactory.showItems(uid, list, true);
  10. }
  11. }

程序输出:

  1. AVERAGE_ABSOLUTE_DIFFERENCE Evaluater Score:1.3333333333333333
  2. Recommender IR Evaluator: [Precision:0.25,Recall:0.5]
  3. uid:1,(105,5.750000)(104,5.250000)(106,4.500000)
  4. uid:2,(105,2.286115)(106,1.500000)
  5. uid:3,(106,2.000000)(102,1.666667)(103,1.625000)
  6. uid:4,(105,4.976859)(102,3.509071)

8. KNN Linear interpolation item–based推荐算法

这个算法在mahout-0.8版本中,已经被@Deprecated。

算法来自论文:
This algorithm is based in the paper of Robert M. Bell and Yehuda Koren in ICDM ‘07.

(TODO未完)

算法API: org.apache.mahout.cf.taste.impl.recommender.knn.KnnItemBasedRecommender

  1. @Override
  2. protected float doEstimatePreference(long theUserID, PreferenceArray preferencesFromUser, long itemID)
  3. throws TasteException {
  4. DataModel dataModel = getDataModel();
  5. int size = preferencesFromUser.length();
  6. FastIDSet possibleItemIDs = new FastIDSet(size);
  7. for (int i = 0; i < size; i++) {
  8. possibleItemIDs.add(preferencesFromUser.getItemID(i));
  9. }
  10. possibleItemIDs.remove(itemID);
  11. List mostSimilar = mostSimilarItems(itemID, possibleItemIDs.iterator(),
  12. neighborhoodSize, null);
  13. long[] theNeighborhood = new long[mostSimilar.size() + 1];
  14. theNeighborhood[0] = -1;
  15. List usersRatedNeighborhood = Lists.newArrayList();
  16. int nOffset = 0;
  17. for (RecommendedItem rec : mostSimilar) {
  18. theNeighborhood[nOffset++] = rec.getItemID();
  19. }
  20. if (!mostSimilar.isEmpty()) {
  21. theNeighborhood[mostSimilar.size()] = itemID;
  22. for (int i = 0; i < theNeighborhood.length; i++) {
  23. PreferenceArray usersNeighborhood = dataModel.getPreferencesForItem(theNeighborhood[i]);
  24. int size1 = usersRatedNeighborhood.isEmpty() ? usersNeighborhood.length() : usersRatedNeighborhood.size();
  25. for (int j = 0; j < size1; j++) {
  26. if (i == 0) {
  27. usersRatedNeighborhood.add(usersNeighborhood.getUserID(j));
  28. } else {
  29. if (j >= usersRatedNeighborhood.size()) {
  30. break;
  31. }
  32. long index = usersRatedNeighborhood.get(j);
  33. if (!usersNeighborhood.hasPrefWithUserID(index) || index == theUserID) {
  34. usersRatedNeighborhood.remove(index);
  35. j--;
  36. }
  37. }
  38. }
  39. }
  40. }
  41. double[] weights = null;
  42. if (!mostSimilar.isEmpty()) {
  43. weights = getInterpolations(itemID, theNeighborhood, usersRatedNeighborhood);
  44. }
  45. int i = 0;
  46. double preference = 0.0;
  47. double totalSimilarity = 0.0;
  48. for (long jitem : theNeighborhood) {
  49. Float pref = dataModel.getPreferenceValue(theUserID, jitem);
  50. if (pref != null) {
  51. double weight = weights[i];
  52. preference += pref * weight;
  53. totalSimilarity += weight;
  54. }
  55. i++;
  56. }
  57. return totalSimilarity == 0.0 ? Float.NaN : (float) (preference / totalSimilarity);
  58. }
  59. }

测试程序:

  1. public static void itemKNN(DataModel dataModel) throws TasteException {
  2. ItemSimilarity itemSimilarity = RecommendFactory.itemSimilarity(RecommendFactory.SIMILARITY.EUCLIDEAN, dataModel);
  3. RecommenderBuilder recommenderBuilder = RecommendFactory.itemKNNRecommender(itemSimilarity, new NonNegativeQuadraticOptimizer(), 10);
  4. RecommendFactory.evaluate(RecommendFactory.EVALUATOR.AVERAGE_ABSOLUTE_DIFFERENCE, recommenderBuilder, null, dataModel, 0.7);
  5. RecommendFactory.statsEvaluator(recommenderBuilder, null, dataModel, 2);
  6. LongPrimitiveIterator iter = dataModel.getUserIDs();
  7. while (iter.hasNext()) {
  8. long uid = iter.nextLong();
  9. List list = recommenderBuilder.buildRecommender(dataModel).recommend(uid, RECOMMENDER_NUM);
  10. RecommendFactory.showItems(uid, list, true);
  11. }
  12. }

程序输出:

  1. AVERAGE_ABSOLUTE_DIFFERENCE Evaluater Score:1.5
  2. Recommender IR Evaluator: [Precision:0.5,Recall:1.0]
  3. uid:1,(107,5.000000)(104,3.501168)(106,3.498198)
  4. uid:2,(105,2.878995)(106,2.878086)(107,2.000000)
  5. uid:3,(103,3.667444)(102,3.667161)(106,3.667019)
  6. uid:4,(107,4.750247)(102,4.122755)(105,4.122709)
  7. uid:5,(107,3.833621)

9. SVD推荐算法

(TODO未完)

算法API: org.apache.mahout.cf.taste.impl.recommender.svd.SVDRecommender

  1. @Override
  2. public float estimatePreference(long userID, long itemID) throws TasteException {
  3. double[] userFeatures = factorization.getUserFeatures(userID);
  4. double[] itemFeatures = factorization.getItemFeatures(itemID);
  5. double estimate = 0;
  6. for (int feature = 0; feature < userFeatures.length; feature++) {
  7. estimate += userFeatures[feature] * itemFeatures[feature];
  8. }
  9. return (float) estimate;
  10. }

测试程序:

  1. public static void svd(DataModel dataModel) throws TasteException {
  2. RecommenderBuilder recommenderBuilder = RecommendFactory.svdRecommender(new ALSWRFactorizer(dataModel, 10, 0.05, 10));
  3. RecommendFactory.evaluate(RecommendFactory.EVALUATOR.AVERAGE_ABSOLUTE_DIFFERENCE, recommenderBuilder, null, dataModel, 0.7);
  4. RecommendFactory.statsEvaluator(recommenderBuilder, null, dataModel, 2);
  5. LongPrimitiveIterator iter = dataModel.getUserIDs();
  6. while (iter.hasNext()) {
  7. long uid = iter.nextLong();
  8. List list = recommenderBuilder.buildRecommender(dataModel).recommend(uid, RECOMMENDER_NUM);
  9. RecommendFactory.showItems(uid, list, true);
  10. }
  11. }

程序输出:

  1. AVERAGE_ABSOLUTE_DIFFERENCE Evaluater Score:0.09990564982096355
  2. Recommender IR Evaluator: [Precision:0.5,Recall:1.0]
  3. uid:1,(104,4.032909)(105,3.390885)(107,1.858541)
  4. uid:2,(105,3.761718)(106,2.951908)(107,1.561116)
  5. uid:3,(103,5.593422)(102,2.458930)(106,-0.091259)
  6. uid:4,(105,4.068329)(102,3.534025)(107,0.206257)
  7. uid:5,(107,0.105169)

10. Tree Cluster-based 推荐算法

这个算法在mahout-0.8版本中,已经被@Deprecated。

(TODO未完)

算法API: org.apache.mahout.cf.taste.impl.recommender.TreeClusteringRecommender

  1. @Override
  2. public float estimatePreference(long userID, long itemID) throws TasteException {
  3. DataModel model = getDataModel();
  4. Float actualPref = model.getPreferenceValue(userID, itemID);
  5. if (actualPref != null) {
  6. return actualPref;
  7. }
  8. buildClusters();
  9. List topRecsForUser = topRecsByUserID.get(userID);
  10. if (topRecsForUser != null) {
  11. for (RecommendedItem item : topRecsForUser) {
  12. if (itemID == item.getItemID()) {
  13. return item.getValue();
  14. }
  15. }
  16. }
  17. // Hmm, we have no idea. The item is not in the user's cluster
  18. return Float.NaN;
  19. }

测试程序:

  1. public static void treeCluster(DataModel dataModel) throws TasteException {
  2. UserSimilarity userSimilarity = RecommendFactory.userSimilarity(RecommendFactory.SIMILARITY.LOGLIKELIHOOD, dataModel);
  3. ClusterSimilarity clusterSimilarity = RecommendFactory.clusterSimilarity(RecommendFactory.SIMILARITY.FARTHEST_NEIGHBOR_CLUSTER, userSimilarity);
  4. RecommenderBuilder recommenderBuilder = RecommendFactory.treeClusterRecommender(clusterSimilarity, 10);
  5. RecommendFactory.evaluate(RecommendFactory.EVALUATOR.AVERAGE_ABSOLUTE_DIFFERENCE, recommenderBuilder, null, dataModel, 0.7);
  6. RecommendFactory.statsEvaluator(recommenderBuilder, null, dataModel, 2);
  7. LongPrimitiveIterator iter = dataModel.getUserIDs();
  8. while (iter.hasNext()) {
  9. long uid = iter.nextLong();
  10. List list = recommenderBuilder.buildRecommender(dataModel).recommend(uid, RECOMMENDER_NUM);
  11. RecommendFactory.showItems(uid, list, true);
  12. }
  13. }

程序输出:

  1. AVERAGE_ABSOLUTE_DIFFERENCE Evaluater Score:NaN
  2. Recommender IR Evaluator: [Precision:NaN,Recall:0.0]

11. Mahout推荐算法总结

算法及适用场景:

recommender-intro

算法评分的结果:

recommender-score

通过对上面几种算法的一平分比较:itemCF,itemKNN,SVD的Rrecision,Recall的评分值是最好的,并且itemCF和SVD的AVERAGE_ABSOLUTE_DIFFERENCE是最低的,所以,从算法的角度知道了,哪个算法是更准确的或者会索引到更多的数据集。

另外的一些因素:

    1. 这3个指标,并不能直接决定计算结果一定itemCF,SVD好
    1. 各种算法的参数我们并没有调优
    1. 数据量和数据分布,是影响算法的评分

程序源代码下载

https://github.com/bsspirit/maven_mahout_template/tree/mahout-0.8/src/main/java/org/conan/mymahout/recommendation/job

可用相似函数:

皮尔森相关度

类名:PearsonCorrelationSimilarity

原理:用来反映两个变量线性相关程度的统计量

范围:[-1,1],绝对值越大,说明相关性越强,负相关对于推荐的意义小。

说明:1、 不考虑重叠的数量;2、 如果只有一项重叠,无法计算相似性(计算过程被除数有n-1);3、 如果重叠的值都相等,也无法计算相似性(标准差为0,做除数)。

  1. 该相似度并不是最好的选择,也不是最坏的选择,只是因为其容易理解,在早期研究中经常被提起。使用Pearson线性相关系数必须假设数据是成对地从正态分布中取得的,并且数据至少在逻辑范畴内必须是等间距的数据。Mahout中,为皮尔森相关计算提供了一个扩展,通过增加一个枚举类型(Weighting)的参数来使得重叠数也成为计算相似度的影响因子。

欧式距离相似度

类名:EuclideanDistanceSimilarity

原理:利用欧式距离d定义的相似度s,s=1 / (1+d)。

范围:[0,1],值越大,说明d越小,也就是距离越近,则相似度越大。

说明:同皮尔森相似度一样,该相似度也没有考虑重叠数对结果的影响,同样地,Mahout通过增加一个枚举类型(Weighting)的参数来使得重叠数也成为计算相似度的影响因子。

余弦相似度

类名:PearsonCorrelationSimilarity和UncenteredCosineSimilarity

原理:多维空间两点与所设定的点形成夹角的余弦值。

范围:[-1,1],值越大,说明夹角越大,两点相距就越远,相似度就越小。

说明:在数学表达中,如果对两个项的属性进行了数据中心化,计算出来的余弦相似度和皮尔森相似度是一样的,在mahout中,实现了数据中心化的过程,所以皮尔森相似度值也是数据中心化后的余弦相似度。另外在新版本中,Mahout提供了UncenteredCosineSimilarity类作为计算非中心化数据的余弦相似度。

Spearman秩相关系数

类名:SpearmanCorrelationSimilarity

原理:Spearman秩相关系数通常被认为是排列后的变量之间的Pearson线性相关系数。

范围:{-1.0,1.0},当一致时为1.0,不一致时为-1.0。

说明:计算非常慢,有大量排序。针对推荐系统中的数据集来讲,用Spearman秩相关系数作为相似度量是不合适的。

曼哈顿距离

类名:CityBlockSimilarity

原理:曼哈顿距离的实现,同欧式距离相似,都是用于多维数据空间距离的测度

范围:[0,1],同欧式距离一致,值越小,说明距离值越大,相似度越大。

说明:比欧式距离计算量少,性能相对高。

Tanimoto系数

类名:TanimotoCoefficientSimilarity

原理:又名广义Jaccard系数,是对Jaccard系数的扩展,等式为

范围:[0,1],完全重叠时为1,无重叠项时为0,越接近1说明越相似。

说明:处理无打分的偏好数据。

对数似然相似度

类名:LogLikelihoodSimilarity

原理:重叠的个数,不重叠的个数,都没有的个数

范围:具体可去百度文库中查找论文《Accurate Methods for the Statistics of Surprise and Coincidence》

说明:处理无打分的偏好数据,比Tanimoto系数的计算方法更为智能。

发表评论

表情:
评论列表 (有 0 条评论,549人围观)

还没有评论,来说两句吧...

相关阅读

    相关 协同过滤推荐

    协同过滤推荐 1. 什么是协同过滤 协同过滤(collaborative filtering)是通过将用户和其他用户的数据进行对比来实现推荐的算法。 2. 协同

    相关 mahout基于物品推荐

    《Mahout实战》中对样本规模有一定的推荐建议 ![1][] 机器不能完成人类所有的思维和决策,但在特定条件下,机器可以模仿人的决策,高效地进行分类。 分类算法基