本章包括
Mahout里面的推荐引擎
实践第一个推荐引擎
评估推荐引擎的准确性和质量
使用GroupLens真实数据评估推荐引擎
每天我们都对事物表达喜好,不喜欢和不关心。它无意识的发生了。你从电台听到一首歌并注意到它了,因为它吸人,或者难听或者你根本没有注意到它。同样的事发生在衬衫,沙拉,发型,面孔等。虽然人们爱好不一样,但都遵循这个模式。人们喜好和他们所喜好的类似的东西。比如某人喜欢牛肉拉面,你可以猜到他同样喜欢羊肉泡馍,因为都是清真食品。同样的,人们喜欢和他相似的人喜欢的东西。这个可以用来判定喜欢和不喜欢。推荐系统就是用来判定这些模式的爱好,被用来发现你不知道但想要的东西。在这个观点更深入的研究后,你将运行一个简单的推荐引擎并明白它怎么工作的。
2.1 定义一个推荐系统
比如你去书架找这本书,你可以看到挨着它放的书你可能也喜欢。因为人们喜欢把相关的书放在一块儿。如果要发现你可能喜欢的东西,可以从和你有类似喜好的人喜欢的东西里面找,也可以从类似你已经拥有的东西里面去找。实际上有两类推荐算法引擎:基于用户的和基于物品的,这两者Mahout都含有。严格的讲,上述就是采用协同过滤来推荐的。这种技术不需要项的属性。也就是说,这个推荐引擎框架不关心项是书,花还是其他人,即不需要输入数据的属性。
当然也有其它依赖与属性的算法即基于内容的推荐引擎技术。比如,你朋友推荐这本书给你是因为它是Manning的书,而你的朋友喜欢其它的Manning的书,所以你朋友喜欢什么东西更像基于内容的推荐。这种推荐就是基于书的属性:出版商。如果要实现基于内容的推荐引擎,你必须决定使用项的什么属性以及每个属性的权值,而且换个领域,算法就不行了。
基于这个原因,Mahout不想多关心基于内容的推荐引擎。Mahout提供的就是协调过滤框架。具体参见第5章关于dating网站的例子。现在我们来看一个简单的例子。
2.2 运行第一个推荐引擎
Mahout含有多种类型的推荐引擎,我们以一个简单的基于用户的推荐引擎开始吧。
创建输入
数据输入采用参考数据的形式,每条参考数据包含用户ID,物品ID和对应的参考值,因为我们是要把物品推荐给用户,这样表达输入数据比较合理。用户ID和物品ID是一个整形的ID,参考值是一个范围的数值,比如可以是1-5,其中数值越大表示越喜欢。
按照这个逻辑先创建一个如下形式的输入文件intro.csv。依次为用户ID,物品ID,参考值。
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 |
可以看到用户1和5具有相似的喜欢,他们都喜欢101>102>103。用户1和用户4也类似,他们都喜欢101和103。用户1和用户2好像就相反了,用户1喜欢102但是用户2不喜欢。用户1和用户3就看不出来了,因为他们重复喜欢的就只有101。
创建推荐系统
如果要给用户1推荐,该推荐什么呢?不能是101,102和103,因为用户已经有了。推荐系统是要发现新的东西给用户1。用户4和用户5喜欢类似用户1,看来可以从他们喜欢的东西中去找到可能推荐的东西。剩下的104,105,106中,104更有可能。
现在,运行下面的代码:
class RecommenderIntro { public static void main(String[] args) throws Exception { DataModel model = new FileDataModel(new File("intro.csv")); UserSimilarity similarity = new PearsonCorrelationSimilarity(model); UserNeighborhood neighborhood = new NearestNUserNeighborhood(2, similarity, model); Recommender recommender = new GenericUserBasedRecommender(model, neighborhood, similarity); List<RecommendedItem> recommendations = recommender.recommend(1, 1); for (RecommendedItem recommendation : recommendations) { System.out.println(recommendation); } } } |
在接下来2章中将详细介绍,现在稍微解释下。DataModelimplementation存储和提供怎么去获取参考数据,UserSimilarityimplementation用来计算2个用户的相似度,UserNeighborhoodimplementation用来定义用户相似的邻居关系,最后Recommenderimplementation计算并推荐结果。
分析输出
运行代码会有如下输出:
RecommendedItem [item:104, value:4.257081]
2.3 评估一个推荐系统
现在我们要解决一个问题,即对一个用户来说,什么推荐系统是最好的。现在我们就要来评估推荐系统。
一个最好的推荐应该是它可以很好推荐你所没有表达喜好的物品,并对这些推荐进行评级。其实推荐系统就是估计这些参考值,所以评估这些推荐系统就可以计算它的参考值和实际值的吻合度。
训练数据和评分
实际的参考值不存在,任何人都不可能知道,但是我们可以模通过把已有的输入数据分成真实数据和测试数据。通过推荐系统,处理分出来的真实数据,产生推荐值,然后和分出来的测试数据进行比较,差异越小的越好。这种差异的计算可以通过均值,也可以通过平方的均值。如下表所示:
Item 1 | Item 2 | Item 3 | |
测试数据值 | 3.0 | 5.0 | 4.0 |
推荐数据值 | 3.5 | 2.0 | 5.0 |
差异 | 0.5 | 3.0 | 1.0 |
差异平均值 | = (0.5 + 3.0 + 1.0) / 3 = 1.5 | ||
差异的平方开放值 | = √((0.5^2+ 3.0^2+ 1.0^2) / 3) = 1.8484 |
运行推荐评估系统
参考如下代码:
代码2.3
RandomUtils.useTestSeed(); DataModel model = new FileDataModel(new File("intro.csv")); RecommenderEvaluator evaluator = new AverageAbsoluteDifferenceRecommenderEvaluator(); RecommenderBuilder builder = new RecommenderBuilder() { @Override public Recommender buildRecommender(DataModel model) throws TasteException { UserSimilarity similarity = new PearsonCorrelationSimilarity(model); UserNeighborhood neighborhood = new NearestNUserNeighborhood(2, similarity, model); return new GenericUserBasedRecommender(model, neighborhood, similarity); } }; double score = evaluator.evaluate(builder, null, model, 0.7, 1.0); System.out.println(score); |
RecommenderEvaluator把数据分成训练数据和测试数据,构建一个数据模型和推荐系统进行测试,最好比较推荐值和实际值。
获取结果
运行测试,输出结果为1.0。因为RandomUtils.useTestSeed(),每次使用同样的随机数,所以多次运行都能得到同一个结果,这方便测试。这儿用的是AverageAbsoluteDifferenceRecommenderEvaluator,即产生的结果是推荐值和测试值差异的均值。你也可以用RMSRecommenderEvaluator替代AverageAbsoluteDifferenceRecommenderEvaluator而得到平方开方平均值。evaluator.evaluate方法中的1.0代表需要使用的输入数据即100%,0.7表示这些输入数据中多少用于推荐计算即70%。
更一般的说,我们可以用经典的信息检索指标来评估推荐系统:精度和重现度。就像搜索引擎从查询到的很多可能的结果中返回最好的结果一样。搜素引擎不能返回相关度不高的结果到最上面,应该返回最相关的结果。比如精度10意味着前面的结果在相关里的比例为10%,重现度表示所有相关的结果在最前的比例。用在推荐引擎上就是:精度为最前的推荐引擎是好的推荐引擎的比例,重现度是好的推荐引擎在最前的比例。
2.4 评估精度和重现能力
运行RecommenderIRStatsEvaluator。如下代码:
RandomUtils.useTestSeed(); DataModel model = new FileDataModel(new File("intro.csv")); RecommenderIRStatsEvaluator evaluator = new GenericRecommenderIRStatsEvaluator(); RecommenderBuilder recommenderBuilder = new RecommenderBuilder() { @Override public Recommender buildRecommender(DataModel model) throws TasteException { UserSimilarity similarity = new PearsonCorrelationSimilarity(model); UserNeighborhood neighborhood = new NearestNUserNeighborhood(2, similarity, model); return new GenericUserBasedRecommender(model, neighborhood, similarity); } }; IRStatistics stats = evaluator.evaluate(recommenderBuilder, null, model, null, 2, GenericRecommenderIRStatsEvaluator.CHOOSE_THRESHOLD, 1.0); System.out.println(stats.getPrecision()); System.out.println(stats.getRecall()); |
输出应为:
0.75
1.0
精度对用户2是0.75表示平均3/4的推荐是好的,即推荐的结果中占实际结果的75%,重现度是1.0,在他们推荐的结果中都是好的推荐,即推荐的结果中100%在实际结果中。
精度和重现能力问题
2.5 评估GroupLens数据集
工具在手,我们不仅要讨论推荐系统的速度,而且还有质量。
分解输入
GroupLens (http://grouplens.org/)是一个提供了不同大小数据的研究项目,每个数据集都是从真实用户的评分中获取的。到http://www.grouplens.org/node/73下载100K数据集吧,解压找到ua.base文件,它是用制表符Tab分割的数据文件。把ua.base应用与代码2.3中,通过几分钟后,你会得到结果在0.9左右。难道我们实现的推荐引擎不是最好的?
使用其它推荐系统试验
我们用org.apache.mahout.cf.taste.impl.recommender.slopeone.SlopeOne-Recommender试试吧。
RecommenderBuilder recommenderBuilder = new RecommenderBuilder() { @Override public Recommender buildRecommender(DataModel model) throws TasteException { return new SlopeOneRecommender(model); } }; |
再次运行,结果在0.748左右,好像更好了。每个算法都有它的优势。比如,slope-one可以更快的计算推荐,但是它需要先去计算产生内部数据结构。
2.6 总结
本章,我们介绍了推荐引擎的概率。我们可以创建一个小量数据集的Mahout推荐引擎,运行并解释结果。评估推荐系统并解释输出。最好用真实数据评估了推荐系统。本作品采用知识共享署名 4.0 国际许可协议进行许可。