Mahout 入门
Mahout 的入门相对比较简单。首先,您需要安装以下软件:
- JDK 1.6 或更高版本
- Ant 1.7 或更高版本
- 如果要编译 Mahout 源代码,还需要安装 Maven 2.0.9 或 2.0.10
您还需要本文的示例代码(见 下载部分),其中包括一个 Mahout 副本及其依赖关系。依照以下步骤安装示例代码:
-
解压缩 sample.zip
-
cd apache-mahout-examples
-
ant install
步骤 3 将下载必要的 Wikipedia 文件将编译代码。所使用的 Wikipedia 文件大约为 2.5 GB,因此下载时间将由您的宽带决定。
建立一个推荐引擎
Mahout 目前提供了一些工具,可用于通过 Taste 库建立一个推荐引擎 —针对 CF 的快速且灵活的引擎。Taste 支持基于用户和基于项目的推荐,并且提供了许多推荐选项,以及用于自定义的界面。Taste 包含 5 个主要组件,用于操作 用户
、项目
和 首选项
:
-
DataModel
:用于存储用户
、项目
和首选项
-
UserSimilarity
:用于定义两个用户之间的相似度的界面 -
ItemSimilarity
:用于定义两个项目之间的相似度的界面 -
Recommender
:用于提供推荐的界面 -
UserNeighborhood
:用于计算相似用户邻近度的界面,其结果随时可由Recommender
使用
借助这些组件以及它们的实现,开发人员可以构建复杂的推荐系统,提供基于实时或者离线的推荐。基于实时的推荐经常只能处理数千用户,而离线推荐具有更好的适用性。Taste 甚至提供了一些可利用 Hadoop 离线计算推荐的工具。在许多情况中,这种合适的方法可以帮助您满足包含大量用户、项目和首选项的大型系统的需求。
为了演示如何构建一个简单的推荐系统,我需要一些用户、项目和评分。为此,我们会使用 cf.wikipedia.GenerateRatings
中的代码(包含在示例代码的源代码中)为 Wikipedia 文档(Taste 称之为 项目
)随机生成大量 用户
和 首选项
,然后再手动补充一些关于特定话题(Abraham Lincoln)的评分,从而创建示例中的最终 recommendations.txt 文件。此方法的内涵是展示 CF 如何将对某特定话题感兴趣的人导向相关话题的其他文档。此示例的数据来源于 990(标记为从 0 到 989)个随机用户,他们随机为集合中的所有文章随机分配了一些评分,以及 10 个用户(标记为从 990 到 999),他们对集合中包含 Abraham Lincoln关键字的 17 篇文章中的部分文章进行了评分。
首先,我将演示如何为在 recommendations.txt 文件中指定了分数的用户创建推荐。这是 Taste 最为常见的应用,因此首先需要载入包含推荐的数据,并将它存储在一个DataModel
中。Taste 提供了一些不同的 DataModel
实现,用于操作文件和数据库。在本例中,为简便起见,我选择使用 FileDataModel
类,它对各行的格式要求为:用户 ID、项目 ID、首选项 —其中,用户 ID 和项目 ID 都是字符串,而首选项可以是双精度型。建立了模型之后,我需要通知 Taste 应该如何通过声明一个 UserSimilarity
实现来比较用户。根据所使用的 UserSimilarity
实现,您可能还需要通知 Taste 如何在未指定明确用户设置的情况下推断首选项。清单 1 实现了以上代码。(示例代码 中的cf.wikipedia.WikipediaTasteUserDemo
包含了完整的代码清单)。
清单 1. 创建模型和定义用户相似度
//create the data model FileDataModel dataModel = new FileDataModel(new File(recsFile)); UserSimilarity userSimilarity = new PearsonCorrelationSimilarity(dataModel); // Optional: userSimilarity.setPreferenceInferrer(new AveragingPreferenceInferrer(dataModel));
在 清单 1中,我使用了 PearsonCorrelationSimilarity
,它用于度量两个变量之间的关系,但是也可以使用其他 UserSimilarity
度量。应该根据数据和测试类型来选择相似度度量。对于此数据,我发现这种组合最为合适,但仍然存在一些问题。有关如何选择相似度度量的更多信息,请访问 Mahout 网站(见 参考资料)。
为了完成此示例,我需要构建一个 UserNeighborhood
和一个 Recommender
。UserNeighborhood
可以识别与相关用户类似的用户,并传递给Recommender
,后者将负责创建推荐项目排名表。清单 2 实现了以下想法:
清单 2. 生成推荐
//Get a neighborhood of users UserNeighborhood neighborhood = new NearestNUserNeighborhood(neighborhoodSize, userSimilarity, dataModel); //Create the recommender Recommender recommender = new GenericUserBasedRecommender(dataModel, neighborhood, userSimilarity); User user = dataModel.getUser(userId); System.out.println("-----"); System.out.println("User: " + user); //Print out the users own preferences first TasteUtils.printPreferences(user, handler.map); //Get the top 5 recommendations List<RecommendedItem> recommendations = recommender.recommend(userId, 5); TasteUtils.printRecs(recommendations, handler.map);
您可以在命令行中运行整个示例,方法是在包含示例的目录中执行 ant user-demo
。运行此命令将打印输出虚构用户 995 的首选项和推荐,该用户只是 Lincoln 的爱好者之一。清单 3 显示了运行 ant user-demo
的输出:
清单 3. 用户推荐的输出
[echo] Getting similar items for user: 995 with a neighborhood of 5 [java] 09/08/20 08:13:51 INFO file.FileDataModel: Creating FileDataModel for file src/main/resources/recommendations.txt [java] 09/08/20 08:13:51 INFO file.FileDataModel: Reading file info... [java] 09/08/20 08:13:51 INFO file.FileDataModel: Processed 100000 lines [java] 09/08/20 08:13:51 INFO file.FileDataModel: Read lines: 111901 [java] Data Model: Users: 1000 Items: 2284 [java] ----- [java] User: 995 [java] Title: August 21 Rating: 3.930000066757202 [java] Title: April Rating: 2.203000068664551 [java] Title: April 11 Rating: 4.230000019073486 [java] Title: Battle of Gettysburg Rating: 5.0 [java] Title: Abraham Lincoln Rating: 4.739999771118164 [java] Title: History of The Church of Jesus Christ of Latter-day Saints Rating: 3.430000066757202 [java] Title: Boston Corbett Rating: 2.009999990463257 [java] Title: Atlanta, Georgia Rating: 4.429999828338623 [java] Recommendations: [java] Doc Id: 50575 Title: April 10 Score: 4.98 [java] Doc Id: 134101348 Title: April 26 Score: 4.860541 [java] Doc Id: 133445748 Title: Folklore of the United States Score: 4.4308662 [java] Doc Id: 1193764 Title: Brigham Young Score: 4.404066 [java] Doc Id: 2417937 Title: Andrew Johnson Score: 4.24178
从清单 3 中可以看到,系统推荐了一些信心级别不同的文章。事实上,这些项目的分数都是由其他 Lincoln 爱好者指定的,而不是用户 995 一人所为。如果您希望查看其他用户的结构,只需要在命令行中传递 -Duser.id=USER-ID
参数,其中 USER-ID
是 0
和 999
之间的编号。您还可以通过传递 -Dneighbor.size=X
来更改邻近空间,其中,X
是一个大于 0 的整型值。事实上,将邻近空间更改为 10
可以生成极为不同的结果,这是因为阾近范围内存在一个随机用户。要查看邻近用户以及共有的项目,可以向命令行添加 -Dcommon=true
。
现在,如果您所输入的编号恰好不在用户范围内,则会注意到示例生成了一个 NoSuchUserException
。确实,应用程序需要处理新用户进入系统的情况。举例来说,您可以只显示 10 篇最热门的文章,一组随机文章,或者一组 “不相关” 的文章 —或者,与其这样,还不如不执行任何操作。
如前所述,基于用户的方法经常不具有可伸缩性。在本例中,使用基于项目的方法是更好的选择。幸运的是,Taste 可以非常轻松地实现基于项目的方法。处理项目相似度的基本代码并没有很大差异,如清单 4 所示:
清单 4. 项目相似度示例(摘录自 cf.wikipedia.WikipediaTasteItemItemDemo
)
//create the data model FileDataModel dataModel = new FileDataModel(new File(recsFile)); //Create an ItemSimilarity ItemSimilarity itemSimilarity = new LogLikelihoodSimilarity(dataModel); //Create an Item Based Recommender ItemBasedRecommender recommender = new GenericItemBasedRecommender(dataModel, itemSimilarity); //Get the recommendations List<RecommendedItem> recommendations = recommender.recommend(userId, 5); TasteUtils.printRecs(recommendations, handler.map);
与 清单 1相同,我根据推荐文件创建了一个 DataModel
,但这次并未实例化 UserSimilarity
实例,而是使用 LogLikelihoodSimilarity
创建了一个 ItemSimilarity
,它可以帮助处理不常见的事件。然后,我将 ItemSimilarity
提供给一个 ItemBasedRecommender
,最后请求推荐。完成了!您可以通过 ant item-demo
命令在示例中代码运行它。当然,在此基础上,您可以让系统支持离线执行这些计算,您还可以探索其他的 ItemSimilarity
度量。注意,由于本示例中的数据是随机的,所推荐的内容可能并不符合用户的期望。事实上,您应该确保在测试过程中计算结果,并尝试不同的相似度指标,因为许多常用指标在一些边界情况中会由于数据不足而无法提供合适的推荐。
我们再来看新用户的例子,当用户导航到某个项目之后,缺少用户首选项时的操作就比较容易实现了。对于这种情况,您可以利用项目计算并向 ItemBasedRecommender
请求与相当项目最相似的项目。清单 5 展示了相关代码:
清单 5. 相似项目演示(摘录自 cf.wikipedia.WikipediaTasteItemRecDemo
)
//create the data model FileDataModel dataModel = new FileDataModel(new File(recsFile)); //Create an ItemSimilarity ItemSimilarity itemSimilarity = new LogLikelihoodSimilarity(dataModel); //Create an Item Based Recommender ItemBasedRecommender recommender = new GenericItemBasedRecommender(dataModel, itemSimilarity); //Get the recommendations for the Item List<RecommendedItem> simItems = recommender.mostSimilarItems(itemId, numRecs); TasteUtils.printRecs(simItems, handler.map);
您可以通过在命令中执行 ant sim-item-demo
来运行 清单 5。它与 清单 4之间的唯一差异就是,清单 5并没有请求推荐,而是请求输出最相似的项目。
现在,您可以继续深入探索 Taste。要了解更多信息,请阅读 Taste 文档和 mahout-user@lucene.apache.org 邮件列表(见 参考资料)。接下来,我将讨论如何通过利用 Mahout 的集群功能来查找相似文章。
使用 Mahout 实现集群
Mahout 支持一些集群算法实现(都是使用 Map-Reduce 编写的),它们都有一组各自的目标和标准:
- Canopy:一种快速集群算法,通常用于为其他集群算法创建初始种子。
- k-Means(以及 模糊 k-Means):根据项目与之前迭代的质心(或中心)之间的距离将项目添加到 k 集群中。
- Mean-Shift:无需任何关于集群数量的 推理知识的算法,它可以生成任意形状的集群。
- Dirichlet:借助基于多种概率模型的集群,它不需要提前执行特定的集群视图。
从实际的角度来说,名称和实现并不如它们生成的结果重要。了解了这一点之后,我将展示 k-Means 的运行原理,而其余内容将由您自己去研究。请记住,要有效运行每个算法,您需要满足它们各自的的需求。
简单来说(详细信息见下文),使用 Mahout 创建数据集群的步骤包括:
- 准备输入。如果创建文本集群,您需要将文本转换成数值表示。
- 使用 Mahout 中可用的 Hadoop 就绪的驱动程序运行所选集群算法。
- 计算结果。
- 如果有必要,执行迭代。
首先,集群算法要求数据必需采用适合处理的格式。在机器学习中,数据通常被表示为 矢量,有时也称作 特征矢量。在集群中,矢量是表示数据的一组权重值。我将使用通过 Wikipedia 文档生成的矢量来演示集群,但是也可以从其他地方获取矢量,比如说传感器数据或用户资料。Mahout 随带了两个 Vector
表示:DenseVector
和 SparseVector
。根据所使用的数据,您需要选择合适的实现,以便实现良好的性能。通常而言,基于文本的问题是很少的,因此应该使用 SparseVector
来处理文本。另一方面,如果大多数矢量的大多数值都是非零的,则比较适合使用DenseVector
。如果您对此不确定,可以尝试这两种实现来处理数据的一个子集,然后确定哪种实现的运行速度更快。
通过 Wikipedia 内容生成矢量的方法如下(我已经完成了此工作):
- 将内容索引编入 Lucene,确保存储相关字段(用于生成矢量的字段)的 term 矢量。我不会讨论这方面的详细信息 —不在本文讨论范围之内 —但我会提供一些简要提示以及 Lucene 上的一些参考资料。Lucene 提供了一个称为
EnWikiDocMaker
的类(包含在 Lucene 的contrib/benchmark
包中),该类可以读取 Wikipedia 文件块中的内容并生成编入 Lucene 索引的文档。 - 使用
org.apache.mahout.utils.vectors.lucene.Driver
类(位于 Mahout 的utils
模块中)通过 Lucene 索引创建矢量。此驱动程序提供了大量用于创建矢量的选项。Mahout wiki 页面 “Creating Vectors from Text” 提供了更多信息(见 参考资料)。
运行这两个步骤的结果是生成一个文件,该文件类似于与您从 Getting started with Mahout 入门部分下载的 n2.tar.gz 文件。需要说明一下,n2.tar.gz 文件中的矢量是通过由 ant install
方法之前下载的 Wikipedia “块” 文件中的所有文件的索引创建的。矢量将被格式化为 Euclidean 格式(或者 L2格式;请参见 参考资料)。在使用 Mahout 时,您可能希望尝试采用不同的方法来创建矢量,以确定哪种方法的效果最好。
创建了一组矢量之后,接下来需要运行 k-Means 集群算法。Mahout 为所有集群算法都提供了驱动程序,包括 k-Means 算法,更合适的名称应该是 KMeansDriver
。可以直接将驱动程序作为单独的程序使用,而不需要 Hadoop 的支持,比如说您可以直接运行 ant k-means
。有关 KMeansDriver
可接受的参数的更多信息,请查看 build.xml 中的 Ant k-means 目标。完成此操作之后,您可以使用 ant dump
命令打印输出结果。
成功在独立模式中运行驱动程序之后,您可以继续使用 Hadoop 的分布式模式。为此,您需要 Mahout Job JAR,它位于示例代码的 hadoop 目录中。Job JAR 包可以将所有代码和依赖关系打包到一个 JAR 文件中,以便于加载到 Hadoop 中。您还需要下载 Hadoop 0.20,并依照 Hadoop 教程的指令,首先在准分布式模式(也就是一个集群)中运行,然后再采用完全分布式模式。有关更多信息,请参见 Hadoop 网站及资源,以及 IBM 云计算资源(参见 参考资料)。