协同过滤(Collaborative Filtering),简单来说是利用某兴趣相投、拥有共同经验之群体的喜好来推荐使用者感兴趣的信息,个人透过合作的机制给予信息相当程度的反馈(如评分)并记录下来以达到过滤的目的进而帮助别人筛选信息,反馈不一定局限于特别感兴趣的,特别不感兴趣信息的纪录也相当重要,比如浏览信息,收藏,分享,点击等。
在前一篇文章 使用Spark MLlib给豆瓣用户推荐电影 中,在那篇文章我我介绍了使用Spark MLlib实现了model-based 的系统过滤之推荐系统。但是Spark并没有提供user-based, item-based这两种memory-based传统的协同过滤算法,所以接下来的这两篇文章我会介绍使用 Mahout 实现user-based和item-based的电影推荐系统,数据还是基于豆瓣用户对电影的评论数据集。
Mahout是Apache的实现大规模的高性能的机器学习框架。它提供了很多的机器学习的算法和工具,以及利用Hadoop实现分布式的计算,本文将使用它的协同过滤算法(CF)实现非分布式的单机程序。
user-based协同过滤推荐算法就是通过不同用户对item的评分来评测用户之间的相似性,基于用户之间的相似性做出推荐。下一篇文章中介绍item-based协同过滤推荐算法是通过用户对不同item的评分来评测item之间的相似性,基于item之间的相似性做出推荐。
User-based 优点:
- 能够过滤机器难以自动内容分析的信息,如艺术品,音乐等。
- 共用其他人的经验,避免了内容分析的不完全或不精确,并且能够基于一些复杂的,难以表述的概念(如信息质量、个人品味)进行过滤。
- 有推荐新信息的能力。可以发现内容上完全不相似的信息,使用者对推荐信息的内容事先是预料不到的。可以发现使用者潜在的但自己尚未发现的兴趣偏好。
- 推荐个性化、自动化程度高。能够有效的利用其他相似使用者的反馈信息。加快个性化学习的速度。
缺点
- 新使用者问题(New User Problem) 系统开始时推荐质量较差
- 新项目问题(New Item Problem) 质量取决于历史资料集
- 稀疏性问题(Sparsity)
- 系统延伸性问题(Scalability)。
下面根据代码介绍具体的实现:
publicclassDoubanUserBasedRecommender{ publicstaticMap<Long, String>getMovies(String base) { Map<Long, String> movies = newHashMap<>(); try{ File file = newFile(base +"hot_movies.csv"); FileLineIterator iterator = newFileLineIterator(file,false); String line = iterator.next(); while(!line.isEmpty()) { String[] m = line.split(","); movies.put(Long.parseLong(m[0]), m[2]); line = iterator.next(); } Closeables.close(iterator, true); } catch(Exception ex) { } returnmovies; } ...... }
上面一段代码是生成电影ID和名称的字典,这个文件的每一行代表一条热门电影,如 20645098,8.2,小王子
。这样在我们输出结果的时候,可以方便的查看电影名,因为电影名比ID更有意义。
publicclassDoubanUserBasedRecommender{ ...... publicstaticvoidmain(String[] args)throwsException { String base = args[0]; File file = newFile(base +"user_movies.csv"); DoubanFileDataModel model = newDoubanFileDataModel(file); //皮尔逊相似度 UserSimilarity similarity = newPearsonCorrelationSimilarity(model); UserNeighborhood neighborhood = newNearestNUserNeighborhood(2, similarity, model); Recommender recommender = newGenericUserBasedRecommender(model, neighborhood, similarity); Recommender cachingRecommender = newCachingRecommender(recommender); //查看一些结果 Map<Long, String> movies = getMovies(base); for(longuserID =0; userID <100; userID++) { String userName = model.userIDAndNameMapping.get(userID); List<RecommendedItem> recommendations = cachingRecommender.recommend(userID, 2); System.out.print("为用户 "+ userName +" 推荐电影:"); for(RecommendedItem recommendation : recommendations) { System.out.print(recommendation.getItemID() + ","+ movies.get(recommendation.getItemID()) +" "); } System.out.println(); } //输出结果到文件 PrintWriter writer = newPrintWriter(base +"result.csv","UTF-8"); for(longuserID =0; userID < model.userIDAndNameMapping.size(); userID++) { String userName = model.userIDAndNameMapping.get(userID); List<RecommendedItem> recommendations = cachingRecommender.recommend(userID, 5); if(recommendations.size() >0) { String line = userName + ","; for(RecommendedItem recommendation : recommendations) { line += recommendation.getItemID() + ":"+ movies.get(recommendation.getItemID()) +","; } if(line.endsWith(",")) line = line.substring(0, line.length() -1); writer.println(line); } } writer.close();
第 6 行读入数据模型,因为我们的数据文件中用户的id是字符串类型的,我们需要将它转换成一个Long类型的数据,所以实现了一个定制的类 DoubanFileDataModel
。
第 8 行我们使用 PearsonCorrelationSimilarity
计算相似度,Mahout还提供了其它的计算相似度的算法:
//曼哈顿相似度 //UserSimilarity similarity = new org.apache.mahout.cf.taste.impl.similarity.CityBlockSimilarity(model); //欧几里德相似度 //UserSimilarity similarity = new org.apache.mahout.cf.taste.impl.similarity.EuclideanDistanceSimilarity(model); //对数似然相似度 //UserSimilarity similarity = new org.apache.mahout.cf.taste.impl.similarity.LogLikelihoodSimilarity(model); //斯皮尔曼相似度 //UserSimilarity similarity = new org.apache.mahout.cf.taste.impl.similarity.SpearmanCorrelationSimilarity(model); //Tanimoto 相似度 //UserSimilarity similarity = new org.apache.mahout.cf.taste.impl.similarity.TanimotoCoefficientSimilarity(model) //Cosine相似度 //UserSimilarity similarity = new org.apache.mahout.cf.taste.impl.similarity.UncenteredCosineSimilarity();
第 10 生成UserBased Recommender类。
第 13 行到第 22 行我们为前100个用户生成推荐结果,并输出到终端窗口,这样我们可以先检查一下推荐的结果。
因为矩阵是很稀疏的,这种user-based算法可能没有提供给用户推荐的电影,有的用户可能不到5个推荐电影。
这段代码剩下的部分就是把所有的用户推荐结果都输出到一个文件中。
DoubanFileDataModel
类的实现如下:
publicclassDoubanFileDataModelextendsFileDataModel{ publicstaticMap<String,Long> userNameAndIDMapping =newHashMap<>(); publicstaticMap<Long,String> userIDAndNameMapping =newHashMap<>(); privatestaticlonguserID =0; publicDoubanFileDataModel(File dataFile)throwsIOException { super(dataFile); } publicDoubanFileDataModel(File dataFile, String delimiterRegex)throwsIOException { super(dataFile, delimiterRegex); } publicDoubanFileDataModel(File dataFile,booleantranspose,longminReloadIntervalMS)throwsIOException { super(dataFile, transpose, minReloadIntervalMS); } publicDoubanFileDataModel(File dataFile,booleantranspose,longminReloadIntervalMS, String delimiterRegex)throwsIOException { super(dataFile, transpose, minReloadIntervalMS, delimiterRegex); } @Override protectedlongreadUserIDFromString(String value) { value = value.trim(); if(userNameAndIDMapping.containsKey(value)) { returnuserNameAndIDMapping.get(value); } userNameAndIDMapping.put(value, userID); userIDAndNameMapping.put(userID, value); userID++; return(userID -1); } @Override protectedlongreadItemIDFromString(String value) { value = value.trim(); returnsuper.readItemIDFromString(value); } }
下面是推荐结果的片段:
yuan521123,1866473:蚁人 58472013,26289144:滚蛋吧!肿瘤君,2973079:霍比特人3:五军之战,23761370:速度与激情7,25746375:我是路人甲 65532408,26021055:栀子花开 68333051,10533913:头脑特工队,10741643:我的个神啊,10827341:疯狂外星人,25823833:天将雄师,26289144:滚蛋吧!肿瘤君 80755814,11624706:小黄人大眼萌,10533913:头脑特工队,25723907:捉妖记,26289144:滚蛋吧!肿瘤君,24879839:道士下山 129734802,25908042:横冲直撞好莱坞 127252296,10741643:我的个神啊,25723907:捉妖记,2973079:霍比特人3:五军之战,6126442:一步之遥 aellr,6846893:超能查派,25779218:匆匆那年,24879839:道士下山 67656730,3338862:终结者:创世纪,6845667:秘密特工,6873042:明日世界,25752261:女间谍,25823833:天将雄师 ravinenoravine,25838463:像素大战
比如用户 52973703,我们为ta推荐:
user-based CF算法适用于:
- 用户较少的场合,否则用户相似度矩阵计算代价很大
- 适合时效性较强,用户个性化兴趣不太明显的领域
- 对新用户不友好,对新物品友好,因为用户相似度矩阵不能实时计算
- 很难提供令用户信服的推荐解释
item-based CF算法适用于:
- 物品数明显小于用户数的场合,否则物品相似度矩阵计算代价很大
- 适合长尾物品丰富,用户个性化需求强的领域
- 对新用户友好,对新物品不友好,因为物品相似度矩阵不需要很强的实时性
- 利用用户历史行为做推荐解释,比较令用户信服
推荐阅读项亮的 推荐系统实践 ,了解推荐系统的基础知识。