推荐引擎构建指南-全-

推荐引擎构建指南(全)

原文:annas-archive.org/md5/0de504a8ea0346c6ac53b857fb263062

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

构建推荐引擎 是一本全面指南,用于实现推荐引擎,如协同过滤、基于内容的推荐引擎、使用 R、Python、Spark、Mahout、Neo4j 技术构建的上下文感知推荐引擎。本书涵盖了在各个行业中广泛使用的各种推荐引擎及其实现。本书还涵盖了一章关于在构建推荐时常用的数据挖掘技术,并在书末简要讨论了推荐引擎的未来。

本书涵盖内容

第一章, 推荐引擎简介,将为数据科学家提供复习,并为推荐引擎初学者提供入门介绍。本章介绍了人们日常生活中常用的流行推荐引擎,并涵盖了这些推荐引擎的常用方法和优缺点。

第二章, 构建你的第一个推荐引擎,是一章简短的章节,介绍了如何构建电影推荐引擎,以便我们在进入推荐引擎的世界之前有一个良好的开端。

第三章, 推荐引擎解析,介绍了流行的不同推荐引擎技术,例如基于用户的协同过滤推荐引擎、基于物品的协同过滤、基于内容的推荐引擎、上下文感知推荐者、混合推荐者、基于机器学习模型和数学模型的模型推荐系统。

第四章, 推荐引擎中使用的数据挖掘技术,介绍了在构建推荐引擎中使用的各种机器学习技术,如相似度度量、分类、回归和降维技术。本章还涵盖了评估指标,以测试推荐引擎的预测能力。

第五章, 构建协同过滤推荐引擎,介绍了如何在 R 和 Python 中构建基于用户的协同过滤和基于物品的协同过滤。我们还将了解在构建推荐引擎中广泛使用的 R 和 Python 的不同库。

第六章, 构建个性化推荐引擎,介绍了如何使用 R 和 Python 构建个性化推荐引擎,以及用于构建基于内容的推荐系统和上下文感知推荐引擎的各种库。

第七章, 使用 Spark 构建实时推荐引擎,介绍了构建实时推荐系统所需的 Spark 和 MLlib 的基础知识。

第八章, 使用 Neo4j 构建实时推荐引擎,介绍了图数据库和 Neo4j 概念的基础,以及如何使用 Neo4j 构建实时推荐系统。

第九章, 使用 Mahout 构建可扩展推荐引擎,介绍了构建可扩展推荐系统所需的 Hadoop 和 Mahout 的基本构建块。它还涵盖了构建可扩展系统所使用的架构,以及使用 Mahout 和 SVD 的逐步实现。

第十章, 未来在哪里? 是最后一章,总结了到目前为止我们所学的知识:在构建决策系统时所采用的最佳实践,以及推荐系统未来的发展方向。

本书所需条件

要开始使用 R、Python、Spark、Neo4j、Mahout 等不同实现方式的推荐引擎,我们需要以下软件:

章节编号 所需软件(含版本) 软件下载链接 操作系统要求
2,4,5 R Studio 版本 0.99.489 www.rstudio.com/products/rstudio/download/ WINDOWS 7+/Centos 6
2,4,5 R 版本 3.2.2 cran.r-project.org/bin/windows/base/ WINDOWS 7+/Centos 6
5,6,7 Python 3.5 的 Anaconda 4.2 www.continuum.io/downloads WINDOWS 7+/Centos 6
8 Neo4j 3.0.6 neo4j.com/download/ WINDOWS 7+/Centos 6
7 Spark 2.0 spark.apache.org/downloads.html WINDOWS 7+/Centos 6
9 Hadoop 2.5 - Mahout 0.12 hadoop.apache.org/releases.htmlmahout.apache.org/general/downloads.html WINDOWS 7+/Centos 6
7,9,8 Java 7/Java 8 www.oracle.com/technetwork/java/javase/downloads/jdk7-downloads-1880260.html WINDOWS 7+/Centos 6

本书面向对象

本书面向初学者和经验丰富的数据科学家,他们希望了解和构建复杂的预测决策系统、使用 R、Python、Spark、Neo4j 和 Hadoop 构建的推荐引擎。

规范

在本书中,您将找到许多文本样式,用于区分不同类型的信息。以下是一些这些样式的示例及其含义的解释。

文本中的代码单词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 标签显示如下:“我们可以通过使用 include 指令来包含其他上下文。”

代码块设置如下:

export MAHOUT_HOME = /home/softwares/ apache-mahout-distribution-0.12.2 
export MAHOUT_LOCAL = true #for standalone mode 
export PATH = $MAHOUT_HOME/bin 
export CLASSPATH = $MAHOUT_HOME/lib:$CLASSPATH 

任何命令行输入或输出都按如下方式编写:

[cloudera@quickstart ~]$ hadoop fs –ls
Found 1 items
drwxr-xr-x - cloudera cloudera 0 2016-11-14 18:31 mahout

新术语重要单词以粗体显示。您在屏幕上看到的单词,例如在菜单或对话框中,在文本中显示如下:“点击下一步按钮将您带到下一个屏幕。”

注意

警告或重要注意事项以如下框的形式出现。

小贴士

小贴士和技巧看起来像这样。

读者反馈

读者的反馈总是受欢迎的。告诉我们您对这本书的看法——您喜欢或不喜欢什么。读者反馈对我们来说很重要,因为它帮助我们开发出您真正能从中获得最大收益的标题。

要向我们发送一般反馈,只需发送电子邮件至 feedback@packtpub.com,并在邮件主题中提及书的标题。

如果您在某个主题上具有专业知识,并且您对撰写或为书籍做出贡献感兴趣,请参阅我们的作者指南www.packtpub.com/authors

客户支持

现在,您已经是 Packt 图书的骄傲拥有者,我们有一些事情可以帮助您从购买中获得最大收益。

下载示例代码

您可以从www.packtpub.com的账户下载本书的示例代码文件。如果您在其他地方购买了这本书,您可以访问www.packtpub.com/support并注册,以便将文件直接通过电子邮件发送给您。

您可以通过以下步骤下载代码文件:

  1. 使用您的电子邮件地址和密码登录或注册我们的网站。

  2. 将鼠标指针悬停在顶部的支持标签上。

  3. 点击代码下载与勘误表

  4. 搜索框中输入书的名称。

  5. 选择您想要下载代码文件的书籍。

  6. 从下拉菜单中选择您购买这本书的地方。

  7. 点击代码下载

文件下载完成后,请确保您使用最新版本的以下软件解压缩或提取文件夹:

  • 适用于 Windows 的 WinRAR / 7-Zip

  • 适用于 Mac 的 Zipeg / iZip / UnRarX

  • 适用于 Linux 的 7-Zip / PeaZip

书籍的代码包也托管在 GitHub 上,网址为github.com/PacktPublishing/building-recommendation-engines。我们还有其他来自我们丰富图书和视频目录的代码包可供选择,网址为github.com/PacktPublishing/。查看它们吧!

下载本书的颜色图像

我们还为您提供了一个包含本书中使用的截图/图表彩色图像的 PDF 文件。彩色图像将帮助您更好地理解输出的变化。您可以从www.packtpub.com/sites/default/files/downloads/BuildingRecommendationEngines_ColorImages.pdf下载此文件。

勘误

尽管我们已经尽一切努力确保内容的准确性,但错误仍然会发生。如果您在我们的某本书中发现错误——可能是文本或代码中的错误——如果您能向我们报告这一点,我们将不胜感激。通过这样做,您可以节省其他读者的挫败感,并帮助我们改进本书的后续版本。如果您发现任何勘误,请通过访问www.packtpub.com/submit-errata,选择您的书籍,点击勘误提交表单链接,并输入您的勘误详情来报告它们。一旦您的勘误得到验证,您的提交将被接受,勘误将被上传到我们的网站或添加到该标题的勘误部分下的现有勘误列表中。

要查看之前提交的勘误表,请访问www.packtpub.com/books/content/support,并在搜索字段中输入书籍名称。所需信息将在勘误部分显示。

侵权

互联网上对版权材料的侵权是一个持续存在的问题,涉及所有媒体。在 Packt,我们非常重视保护我们的版权和许可证。如果您在互联网上发现任何形式的我们作品的非法副本,请立即提供位置地址或网站名称,以便我们可以追究补救措施。

请通过版权@packtpub.com 与我们联系,并提供疑似侵权材料的链接。

我们感谢您在保护我们的作者和我们为您提供有价值内容的能力方面的帮助。

问题

如果您对本书的任何方面有问题,您可以通过 questions@packtpub.com 联系我们,我们将尽力解决问题。

第一章。推荐引擎简介

我们如何购买东西?我们在日常生活中如何做出决策?在做出决策之前,我们会向朋友或亲戚寻求建议。当涉及到在线购买产品的决策时,我们会阅读匿名用户对产品的评论,比较产品的规格与其他类似产品,然后我们决定是否购买。在信息呈指数级增长的网络世界中,寻找有效信息将是一个挑战。赢得用户对搜索结果的信心将是一个更具挑战性的任务。推荐系统来帮助我们提供相关和所需的信息。

推荐引擎的实施流行起来,是由于互联网大玩家成功实施的结果。一些现实世界的例子包括亚马逊上的产品推荐、社交应用(如 Facebook、Twitter 和 LinkedIn)上的朋友推荐、YouTube 上的视频推荐、Google 新闻上的新闻推荐等。这些推荐引擎的成功实施为其他领域如旅游、医疗保健和银行领域指明了道路。

推荐引擎定义

推荐引擎,作为信息检索和人工智能的一个分支,是分析大量数据(尤其是产品信息和用户信息)的强大工具和技术,然后基于数据挖掘方法提供相关建议。

在技术术语中,推荐引擎问题是要开发一个数学模型或目标函数,可以预测用户将喜欢某个物品的程度。

如果 U = {用户}I = {物品},则 F = 目标函数,它衡量物品 I 对用户 U 的有用性,由以下公式给出:

推荐引擎定义

其中 R = {推荐物品}

对于每个用户 u,我们希望选择能够最大化目标函数的物品 i

推荐引擎定义

推荐系统的主要目标是向在线用户提供相关建议,以便从网络上的众多替代方案中做出更好的决策。更好的推荐系统更倾向于个性化推荐,在做出推荐之前会考虑用户可用的数字足迹,例如用户人口统计信息、交易细节、交互日志以及有关产品的信息,如规格、用户反馈、与其他产品的比较等:

推荐引擎定义

图片来源:toptal

构建一个好的推荐引擎对系统中的参与者,即消费者和卖家,都提出了挑战。从消费者的角度来看,从可信来源获得相关建议对于决策至关重要。因此,推荐引擎需要以这种方式构建,以便赢得消费者的信任。从卖家的角度来看,在个性化层面上向消费者生成相关推荐更为重要。随着在线销售的兴起,大玩家现在正在收集大量用户的交易交互日志,以比以往任何时候都更深入地分析用户行为。此外,实时推荐的需求也在增加挑战。随着技术和研究的进步,推荐引擎正在基于大数据分析和人工智能克服这些挑战。以下图表说明了组织如何使用推荐引擎:

推荐引擎定义

对推荐系统的需求

由于构建推荐引擎的复杂性和挑战,构建推荐系统需要大量的思考、技能、投资和技术。这样的投资是否值得?让我们看看一些事实:

  • Netflix 用户观看的电影中有三分之二是由推荐系统推荐的

  • 在 Google 新闻上的点击率中有 38%是推荐链接

  • 亚马逊 35%的销售额来自推荐产品

  • ChoiceStream 声称,如果人们找到他们喜欢的东西,28%的人愿意购买更多音乐

大数据推动推荐系统的发展

近来,推荐系统在许多方面成功影响了我们的生活。这种影响的明显例子之一就是我们的在线购物体验被重新定义了。当我们浏览电子商务网站并购买产品时,底层的推荐引擎会立即、实时地向消费者提供各种相关的建议。无论从商业参与者还是消费者的角度来看,推荐引擎都带来了巨大的益处。毫无疑问,大数据是推荐系统背后的驱动力。一个好的推荐引擎应该是可靠的、可扩展的、高度可用的,并且能够为包含的大量用户提供实时、个性化的推荐。

一个典型的推荐系统如果没有足够的数据就无法高效地完成其工作。大数据技术的引入使得公司能够捕捉到大量的用户数据,例如过去的购买记录、浏览历史和反馈信息,并将这些数据输入到推荐引擎中,以实时生成相关和有效的推荐。简而言之,即使是最先进的推荐系统,如果没有大数据的供应也无法有效。大数据和技术改进的作用,无论是在软件还是硬件方面,都不仅仅是提供大量数据。它还快速提供有意义的、可操作的数据,并提供了快速处理实时数据的必要设置。

来源:www.kdnuggets.com/2015/10/big-data-recommendation-systems-change-lives.html

推荐系统类型

既然我们已经定义了推荐系统,它们的客观性、有用性和推动推荐系统发展的动力,在本节中,我们将介绍不同类型的流行推荐系统。

协同过滤推荐系统

协同过滤推荐系统是推荐引擎的基本形式。在这种类型的推荐引擎中,通过用户的偏好协同地从大量替代品中过滤物品。

在协同过滤推荐系统中,基本假设是:如果两个用户在过去有相同的兴趣,那么他们将来也会有相似品味。例如,如果用户 A 和用户 B 有相似的影片偏好,而用户 A 最近观看了用户 B 尚未看过的电影《泰坦尼克号》,那么推荐的思路就是向用户 B 推荐这部未见的新电影。Netflix 上的电影推荐就是一个很好的这种推荐系统的例子。

协同过滤推荐系统有两种类型:

  • 基于用户的协同过滤:在基于用户的协同过滤中,推荐是通过考虑用户邻域中的偏好来生成的。基于用户的协同过滤分为两个步骤:

    • 根据相似的用户偏好识别相似用户

    • 根据相似用户对活跃用户未评价物品的评价,向活跃用户推荐新物品。

  • 基于物品的协同过滤:在基于物品的协同过滤中,推荐是通过使用物品的邻域来生成的。与基于用户的协同过滤不同,我们首先找到物品之间的相似性,然后推荐那些与活跃用户过去评价过的物品相似的未评价物品。基于物品的推荐系统分为两个步骤:

    • 根据物品偏好计算物品相似度

    • 通过找到活跃用户未评价物品的最相似物品,并推荐它们

我们将在第三章推荐引擎解释中深入了解这两种推荐形式。

在构建协同过滤推荐系统时,我们将了解以下方面:

  • 如何计算用户之间的相似性?

  • 如何计算项目之间的相似性?

  • 推荐是如何生成的?

  • 如何处理未知数据的新项目和用户?

协同过滤系统的优点是它们易于实现且非常准确。然而,它们也有自己的局限性,例如冷启动问题,这意味着协同过滤系统无法向系统内没有信息的首次用户推荐:

协同过滤推荐系统

基于内容的推荐系统

在协同过滤中,我们只考虑用户-项目-偏好,并构建推荐系统。尽管这种方法是准确的,但如果我们在构建推荐引擎时考虑用户属性和项目属性,则更有意义。与协同过滤不同,我们在构建基于内容的推荐引擎时使用项目属性和用户偏好来考虑项目属性。

如其名称所示,基于内容的推荐系统使用项目的相关内容信息来构建推荐模型。基于内容的推荐系统通常包含一个用户档案生成步骤、项目档案生成步骤和模型构建步骤,以生成活跃用户的推荐。基于内容的推荐系统通过考虑项目的相关内容或特征以及用户档案来向用户推荐项目。例如,如果你在 YouTube 上搜索过莱昂内尔·梅西的视频,那么基于内容的推荐系统将学习你的偏好,并推荐其他与莱昂内尔·梅西相关的视频以及其他与足球相关的视频。

简而言之,系统推荐与用户过去喜欢的项目相似的项目。项目的相似性是基于与其他比较项目的相关特征计算的,并与用户的历史偏好相匹配。

在构建基于内容的推荐系统时,我们考虑以下问题:

  • 我们如何选择产品的内容或特征?

  • 我们如何创建与产品内容偏好相似的用户档案?

  • 我们如何根据其特征在项目之间创建相似性?

  • 我们如何持续创建和更新用户档案?

前面的考虑将在第三章中解释,即推荐引擎解析。这种技术不考虑用户的邻域偏好。因此,它不需要大量用户对物品的偏好来提高推荐准确性。它只考虑用户的过去偏好和物品的属性/特征。在第三章中,即推荐引擎解析,我们将详细了解这个系统,以及它的优缺点:

基于内容的推荐系统

混合推荐系统

这种类型的推荐引擎是通过结合各种推荐系统来构建一个更健壮的系统。通过结合各种推荐系统,我们可以用另一个系统的优点来弥补一个系统的缺点,从而构建一个更健壮的系统。例如,通过结合协同过滤方法(当新物品没有评分时模型会失败)和基于内容的系统(其中包含有关物品的特征信息),可以更准确、更有效地推荐新物品。

例如,如果你是 Google 新闻的常读新闻读者,底层的推荐引擎会通过结合与你相似的人阅读的流行新闻文章以及使用你的个人偏好(通过你的先前点击信息计算得出)来向你推荐新闻文章。在这种类型的推荐系统中,在推送推荐之前,会结合协同过滤推荐和基于内容的推荐。

在构建混合模型之前,我们应该考虑以下问题:

  • 应该结合哪些推荐技术来实现业务解决方案?

  • 我们应该如何结合各种技术和它们的结果以获得更好的预测?

混合推荐引擎的优势在于,与单个推荐技术相比,这种方法将提高推荐的效率。这种方法还向用户推荐了良好的推荐组合,无论是在个性化层面还是在邻域层面。在第三章中,即推荐引擎解析,我们将了解更多关于混合推荐的内容:

混合推荐系统

上下文感知推荐系统

个性化推荐系统,如基于内容的推荐系统,效率低下;它们未能根据上下文提出推荐。例如,假设一位女士非常喜欢冰淇淋。也假设这位女士去了一个寒冷的地方。现在有很大可能性,一个个性化的推荐系统会建议一个流行的冰淇淋品牌。现在让我们问自己一个问题:向一个在寒冷地方的人推荐冰淇淋是正确的事情吗?相反,建议一杯咖啡更有意义。这种既个性化又上下文感知的推荐称为上下文感知推荐系统。在上面的例子中,地点就是上下文。

用户偏好可能因上下文而异,例如一天中的时间、季节、心情、地点、系统提供的选项等等。在不同的地点、不同时间与不同的人在一起的人可能需要不同的东西。上下文感知推荐系统在计算或提供推荐之前会考虑上下文。这个推荐系统在不同的上下文中以不同的方式满足人们的不同需求。

在构建上下文感知模型之前,我们应该考虑以下问题:

  • 我们应该如何定义推荐系统中使用的上下文?

  • 应该使用哪些技术来构建推荐以实现商业解决方案?

  • 我们如何提取用户对产品的偏好上下文?

  • 我们应该使用哪些技术来结合上下文偏好与用户配置文件偏好以生成推荐?上下文感知推荐系统

前面的图片展示了不同的人、在不同的时间和地点,以及与不同的公司,需要不同的着装推荐。

随着技术的进步,推荐系统的发展演变

随着技术、研究和基础设施的进步,推荐系统正在迅速发展。推荐系统正从基于简单相似度测量的方法,转向机器学习方法,再到非常先进的深度学习等高级方法。从商业角度来看,客户和组织都在寻求更个性化的推荐,以满足即时需求。为了满足大量用户和产品,我们需要复杂的系统,这些系统能够轻松扩展并快速响应。以下是可以帮助解决这一挑战的推荐类型。

Mahout 用于可扩展的推荐系统

如前所述,大数据主要推动推荐系统的发展。大数据平台使研究人员能够访问大量数据集并在个体层面进行分析,为构建个性化推荐系统铺平了道路。随着互联网使用的增加和数据的不间断供应,高效的推荐系统不仅需要大量数据,还需要能够扩展且具有最小停机时间的基础设施。为了实现这一点,像 Apache Hadoop 生态系统这样的大数据技术提供了基础设施和平台,以提供大量数据。为了在这庞大的数据供应上构建推荐系统,Mahout,一个基于 Hadoop 平台构建的机器学习库,使我们能够构建可扩展的推荐系统。Mahout 提供了构建、评估和调整不同类型的推荐算法的基础设施。由于 Hadoop 是为离线批处理设计的,我们可以构建可扩展的离线推荐系统。在第九章《使用 Mahout 构建可扩展的推荐引擎》中,我们进一步了解了如何使用 Mahout 构建可扩展的推荐引擎。

下图显示了如何使用 Mahout 设计一个可扩展的推荐系统:

Mahout for scalable recommender systems

Apache Spark for scalable real-time recommender systems

我们在很多电商网站上多次看到,“你可能也喜欢”这一功能。这是一个表面上简单却蕴含着客户关系管理新时代的实时服务的新时代。商业组织开始投资这样的系统,这些系统能够根据客户个性化生成推荐,并能实时交付。构建这样的系统不仅能带来良好的投资回报,而且高效的系统还能赢得用户的信心。构建一个可扩展的实时推荐系统不仅需要捕捉用户的购买历史、产品信息、用户偏好,并提取模式和推荐产品,还需要根据用户的在线互动和多标准搜索偏好即时响应。

这种能力使得提出令人信服的建议需要新一代的技术。这种技术必须考虑用户之前的购买历史、他们的偏好以及在线互动信息,如页面导航数据和多标准搜索,然后实时分析所有这些信息,并根据用户的当前和长期需求准确响应。在这本书中,我们考虑了内存和基于图系统,这些系统能够处理大规模、实时的推荐系统。

最流行的推荐引擎协同过滤在生成推荐时需要考虑用户和产品信息的全部。假设我们有一个场景,其中我们对 10,000 个产品有 100 万条用户评分。为了构建一个能够处理这种大量计算并在线响应的系统,我们需要一个与大数据兼容的系统,并在内存中处理数据。实现可扩展、实时推荐的关键技术是 Apache Spark Streaming,这是一种利用大数据可扩展性的技术,可以实时生成推荐,并在内存中处理数据:

Apache Spark 用于可扩展的实时推荐系统

Neo4j 用于实时基于图的推荐系统

图数据库已经彻底改变了人们发现新产品、信息等方式。在人类大脑中,我们以图、关系和网络的形式记住人、事物、地点等。当我们尝试从这些网络中获取信息时,我们会直接前往所需的连接或图,并准确获取信息。以类似的方式,图数据库允许我们将用户和产品信息以节点和边(关系)的形式存储在图中。在图数据库中进行搜索速度快。近年来,由图数据库驱动的推荐系统使组织能够构建实时且个性化的建议。

使使用图数据库进行实时推荐的关键技术之一是 Neo4j,这是一种 NoSQL 图数据库,在提供客户洞察和产品趋势方面可以轻松超越任何其他关系型和非关系型系统。

一种名为“不仅 SQL”的 NoSQL 数据库,提供了一种不同于关系格式(如行和列)的新方式来存储和管理数据,例如列式、图、键值对存储的数据。这种新的存储和管理数据方式使我们能够构建可扩展的实时系统。

图数据库主要由节点和边组成,其中节点代表实体,边代表它们之间的关系。边是连接节点的有向线或箭头。在前面的图像中,圆圈是节点,代表实体,连接节点的线条称为边,代表关系。箭头的方向遵循信息流。通过展示图中的所有节点和链接,它帮助用户对结构有一个全局的视角。

以下图像展示了用户电影评分信息的图形表示。绿色和红色圆圈分别表示代表用户和电影的节点。用户对电影的评分以显示用户和电影之间关系的边来表示。每个节点和关系都可能包含属性以存储数据的更多详细信息。

在这个图表示中,我们应用图论的概念来实时生成推荐,因为检索和搜索非常快。在第八章,《使用 Neo4j 构建实时推荐引擎》中,我们处理了使用 Neo4j 构建实时推荐引擎的问题:

Neo4j 用于实时基于图的推荐系统

摘要

在本章中,我们介绍了各种流行的推荐引擎类型,如协同过滤、基于内容的推荐引擎、混合推荐器、上下文感知系统、可扩展推荐器和基于图、实时推荐器。

我们还学习了大数据如何推动推荐引擎的兴起以及一些主要 IT 巨头所采用的某些真实世界用例。在第三章,《推荐引擎详解》中,我们将更详细地了解这些推荐。在第二章,《构建你的第一个推荐引擎》中,我们学习了如何使用 R 构建基本的推荐引擎。

第二章. 构建你的第一个推荐引擎

在上一章中,我们介绍了各种类型的推荐引擎,这些推荐引擎将在后续章节中构建。现在我们已经了解了推荐引擎,让我们使用 R 构建我们的第一个推荐引擎。

在我们进一步进行实现之前,让我们简要讨论一下构建我们第一个 R 推荐引擎所需的软件和包。

对于这个练习,我们使用了 R 3.2.2 版本和 RStudio 0.99 或更高版本。有关 R 和 RStudio 的安装和设置,请参阅书籍中的软件和硬件列表部分。

我们在这个练习中使用的 R 包如下:

  • dplyr

  • data.table

  • reshape2

安装 R 包的代码如下:

#for online installation
Install.packages("dplyr")  

对于离线安装,首先从 CRAN 仓库下载所需的gz文件到本地文件夹,然后执行以下代码:

install.packages("path/to/file/dplyr_0.5.0.tar.gz", repos=NULL) 

我们将要构建的推荐引擎基于协同过滤方法。正如在第一章中解释的,推荐引擎简介基于用户的邻域,如下图中所示:

构建你的第一个推荐引擎

构建我们的基本推荐引擎

构建我们的基本推荐引擎的步骤如下:

  1. 加载和格式化数据。

  2. 计算用户之间的相似度。

  3. 预测用户未知的评分。

  4. 根据用户相似度评分向用户推荐项目。

这些步骤可以在以下图中看到:

构建我们的基本推荐引擎

加载和格式化数据

用于本章的数据集可以从raw.githubusercontent.com/sureshgorakala/RecommenderSystems_R/master/movie_rating.csv下载。

本章选择的数据集是一个包含六个用户对六部电影评分(评分范围为 0 到 5,以 5 为步长)的电影评分数据集:

加载和格式化数据

在我们加载数据之前,让我解释一下关于数据的一些事情。选择的数据集是一个逗号分隔的文件,包含六个用户对六部电影从 1 到 5 的评分(以 5 为步长)。并非所有评论家都对该数据集中的所有标题进行了评分。

我们的目标是构建一个推荐引擎,根据相似用户的评分向用户推荐未知电影。

从 R 中的csv文件加载数据的代码是read.csv()

ratings = read.csv("~/movie_rating.csv") 

使用 R 中的内置函数head()可以查看数据的前六行:

head(ratings) 

加载和格式化数据

要查看数据集的维度,我们使用 R 中的内置函数dim()

dim(ratings) 
[1] 31  3 

要查看输入数据的结构,我们可以在 R 中使用str()函数,如下所示:

Str(ratings) 

加载和格式化数据

我们可以看到,我们有一个包含 31 个观测值和三个变量(如评论、标题和评分)的数据集。此外,我们还看到有六位评论家对六部电影进行了评分。评分介于 1 到 5 之间。

要查看变量的属性级别,我们使用 R 中的levels()

加载和格式化数据

要构建推荐系统,我们需要一个矩阵,其中行包含用户,列包含项目,单元格包含用户对项目的评分。

下一步是将数据排列成对构建推荐引擎有用的格式。当前数据包含一行,包含评论家、标题和评分。这必须转换为包含评论家作为行、标题作为列和评分作为单元格值的矩阵格式。

以下代码帮助我们实现这一点。我们使用acast()函数,该函数在reshape2包中可用。reshape2包是一个流行的 R 包,用于重构数据。reshape2包中的acast()函数将数据框转换为矩阵表示。

cast函数接受评分数据集作为输入,title作为行属性,critic作为列属性,rating作为值。

#data processing and formatting
movie_ratings = as.data.frame(acast(ratings, title~critic,
    value.var="rating"))

转换后的数据可以如下查看:

View(movie_ratings) 

加载和格式化数据

从格式化数据中,我们看到Toby评了三部电影。Lisa RoseMick LaSalleGene Seymour评了所有电影。Claudia PuigJack Matthews每人未评一部电影。在这里,让我们回顾一下我们最初在本节开头定义的目标:我们将根据相似用户推荐给评论家他们未评分的电影。例如,我们将根据其他与Toby相似的评论家的评分向Toby推荐电影。

计算用户之间的相似度

这是一个非常重要的步骤,因为我们需要根据其他相似评论家对电影的评分来推荐之前未见过的电影。有各种相似度度量,如欧几里得距离、余弦距离、皮尔逊系数、Jaccard 距离等。这些度量或相似度指标的详细信息在第四章推荐引擎中使用的数据挖掘技术中进行了详细解释。

在本章中,我们将使用相关性作为用户之间的相似度度量。选择相关性的原因是相关性表示两个项目之间的关联或两个项目向量如何协变或相互关联。因此,对于本章,我们选择了相关性值作为矩阵中两个项目之间相似度的度量。

在 R 中,我们有cor()函数来查找数据集中变量的相关性。以下代码计算了评论家之间的相似度:

在寻找托比与其他评论家之间的相似性时,使用cor()函数的use="complete.obs"属性来考虑完整的观测值:

sim_users = cor(movie_ratings[,1:6],use="complete.obs") 
View(sim_users) 

计算用户之间的相似度

从前面的代码中,我们可以观察到Lisa RoseToby的相似度为0.99,与Mick LaSalle的相似度为0.92

预测用户未知评分

在本节中,我们将使用相似用户的评分来预测托比的未评分电影。以下是实现这一目标的步骤:

  1. 提取托比未评分的标题。

  2. 对于这些标题,分别提取其他评论家给出的所有评分。

  3. 将这些电影给出的评分与除托比以外的所有评论家的相似度值相乘。

  4. 对每部电影的总评分进行汇总,然后将这个汇总值除以相似度评论家值的总和。

在我们进入代码之前,让我们了解一下data.table包和我们在以下代码中使用的setDT()方法。

Data.table是一个流行的 R 包,它提供了一个增强的data.frame版本,允许我们以闪电般的速度对数据进行操作。data.table包的另一个优点是它可以处理非常大的数据集,高达 100 GB 的数据在 RAM 中。各种操作,如创建数据表、数据框的增强版本、数据子集、数据操作、连接等。

对于这个练习,我们使用了data.table中可用的setDT()方法。data.table中的set*函数通过引用而不是值来操作输入数据,也就是说,在转换数据时,不会有数据的物理副本。

前面的解释被编写成如下代码:

  1. 提取托比未评分的标题。我们使用了data.table包中可用的setDT()函数来提取未评分的标题并创建一个data.tabledata.frame对象,rating_criticsetDT()方法提取列值和相应的行名,并创建一个二维data.framedata.table对象:

            rating_critic  = setDT(movie_ratings[colnames(movie_ratings)
                [6]],keep.rownames = TRUE)[]
            names(rating_critic) = c('title','rating')
            View(rating_critic)
    

    预测用户未知评分

  2. 从上述列表中隔离未评分的电影:

            titles_na_critic =
                rating_critic$title[is.na(rating_critic$rating)]
            titles_na_critic
    

    预测用户未知评分

    注意

    请注意,is.na()函数用于过滤掉 NA 值。

    根据原始数据集取评分,并子集所有已对上述显示的电影进行评分的评论家。

    在以下代码中,%in%充当 SQL 中的 where 条件:

            ratings_t =ratings[ratings$title %in% titles_na_critic,]
            View(ratings_t)
    

    预测用户未知评分

    对于上述数据框,现在让我们添加一个新的变量similarity,使用每个critic相对于托比的相似度值:

            x = (setDT(data.frame(sim_users[,6]),keep.rownames = TRUE)[])
            names(x) = c('critic','similarity')
            ratings_t =  merge(x = ratings_t, y = x, by = "critic", all.x = 
                TRUE)
            View(ratings_t)
    

    预测用户未知评分

  3. 将评分与相似度值相乘,并将结果作为新变量sim_rating添加:

            ratings_t$sim_rating = ratings_t$rating*ratings_t$similarity
                ratings_t
    

    预测用户未知评分

  4. 将前一步计算中每个标题的所有评分值汇总,然后除以每个评论家相似度值的总和,即对于Just My Luck标题,Toby 的评分是通过将Just My Luck的所有sim_rating值相加,然后除以所有评价Just My Luck标题的评论家的相似度值总和来计算的:

    (2.6802154+0.5718696+2.9737221+1.8489469)/(0.8934051+0.3812464+0.9912407+0.9244735) = 2.530981

    之前对所有标题的计算都是在 R 中使用dplyr包中的两个函数group_by()summarise()完成的。

    dplyr包是一个用于数据操作的 R 包。这个包非常有用,就像data.table一样;它在探索性分析和数据处理方面非常方便。summarise()函数在dply包中用于汇总结果。group_by()函数用于根据一个或多个变量对数据进行分组。

    dply包中可用的%>%操作符是一个非常有用的函数,用于将多个代码组合在一起。在以下代码中,我们使用%>%代码将group_by()summarise()函数组合在一起,并计算结果,而无需编写中间结果:

            result = ratings_t %>% group_by(title) %>%
                summarise(sum(sim_rating)/sum(similarity))
            result
            Source: local data frame [3 x 2]
            title sum(sim_rating)/sum(similarity)
            (fctr) (dbl)
            1 Just My Luck 2.530981
            2 Lady in the Water 2.832550
            3 The Night Listener 3.347790
    

    您可以看到为 Toby 未评分的三个标题计算或预测的评分。现在您可以推荐这些新标题,其评分高于 Toby 给出的平均评分。例如,Toby 对三个标题的平均评分如下代码所示:

            mean(rating_critic$rating,na.rm = T)
            3.166667
    

    既然我们知道 Toby 的平均评分是3.16,我们就可以推荐评分高于平均值的电影。从预测值中,我们可以推荐电影《The Night Listener》,其评分高于他的平均值。

    所述为所有用户生成推荐的方法可以通过编写以下函数轻松扩展:

            generateRecommendations <- function(userId){
            rating_critic = setDT(movie_ratings[colnames(movie_ratings)
                [userId]],keep.rownames = TRUE)[]
            names(rating_critic) = c('title','rating')
            titles_na_critic =
                rating_critic$title[is.na(rating_critic$rating)]
            ratings_t =ratings[ratings$title %in% titles_na_critic,]
            #add similarity values for each user as new variable
            x = (setDT(data.frame(sim_users[,userId]),keep.rownames = TRUE)
                [])
            names(x) = c('critic','similarity')
            ratings_t = merge(x = ratings_t, y = x, by = "critic", all.x = 
                TRUE)
            #mutiply rating with similarity values
            ratings_t$sim_rating = ratings_t$rating*ratings_t$similarity
            #predicting the non rated titles
            result = ratings_t %>% group_by(title) %>%
                summarise(sum(sim_rating)/sum(similarity))
            return(result)
            }
    

    现在为每个用户进行预测将非常容易,下面将展示:

    预测用户未知评分

为我们构建了第一个基本推荐系统而感到自豪。现在让我们把到目前为止所写的全部代码放在一起。以下是完全版本的代码:

library(reshape2)
library(data.table)
library(dplyr)
#data loading
ratings = read.csv("C:/Users/Suresh/Desktop/movie_rating.csv")
#data processing and formatting
movie_ratings = as.data.frame(acast(ratings, title~critic, value.var="rating"))
#similarity calculation
sim_users = cor(movie_ratings[,1:6],use="complete.obs")
#sim_users[colnames(sim_users) == 'Toby']
sim_users[,6]
#predicting the unknown values
#seperating the non rated movies of Toby
rating_critic = setDT(movie_ratings[colnames(movie_ratings)[6]],keep.rownames = TRUE)[]
names(rating_critic) = c('title','rating')
titles_na_critic = rating_critic$title[is.na(rating_critic$rating)]
ratings_t =ratings[ratings$title %in% titles_na_critic,]
#add similarity values for each user as new variable
x = (setDT(data.frame(sim_users[,6]),keep.rownames = TRUE)[])
names(x) = c('critic','similarity')
ratings_t = merge(x = ratings_t, y = x, by = "critic", all.x = TRUE)
#mutiply rating with similarity values
ratings_t$sim_rating = ratings_t$rating*ratings_t$similarity
#predicting the non rated titles
result = ratings_t %>% group_by(title) %>% summarise(sum(sim_rating)/sum(similarity))
#function to make recommendations
generateRecommendations <- function(userId){
rating_critic = setDT(movie_ratings[colnames(movie_ratings)[userId]],keep.rownames = TRUE)[]
names(rating_critic) = c('title','rating')
titles_na_critic = rating_critic$title[is.na(rating_critic$rating)]
ratings_t =ratings[ratings$title %in% titles_na_critic,]
#add similarity values for each user as new variable
x = (setDT(data.frame(sim_users[,userId]),keep.rownames = TRUE)[])
names(x) = c('critic','similarity')
ratings_t = merge(x = ratings_t, y = x, by = "critic", all.x = TRUE)
#mutiply rating with similarity values
ratings_t$sim_rating = ratings_t$rating*ratings_t$similarity
#predicting the non rated titles
result = ratings_t %>% group_by(title) %>% summarise(sum(sim_rating)/sum(similarity))
return(result)
}

摘要

恭喜!我们已经使用 R 构建了一个非常基本的推荐引擎。我们看到了构建推荐引擎的逐步方法。在接下来的章节中,我们将学习不同类型的推荐引擎及其在各种技术中的实现,例如 Spark、Mahout、Neo4j、R 和 Python。在下一章中,我们将深入了解各种类型的推荐引擎。

第三章. 推荐引擎解析

在第二章《构建你的第一个推荐引擎》中,我们学习了如何使用 R 构建基本的推荐系统。在第一章《推荐引擎简介》中介绍了各种推荐系统,我们已经对推荐系统是什么以及为什么在数据爆炸的当前时代推荐系统很重要有了相当的了解。在本章中,我们将详细了解各种类型的推荐系统。本章解释了基于邻域相似度的推荐、个性化推荐引擎、基于模型的推荐系统和混合推荐引擎。

本章涵盖了以下不同子类型的推荐系统:

  • 基于邻域的推荐引擎:

    • 基于用户的协同过滤

    • 基于物品的协同过滤

  • 个性化推荐引擎:

    • 基于内容的推荐引擎

    • 上下文感知推荐引擎

  • 基于模型的推荐引擎:

    • 基于机器学习的推荐引擎

    • 分类 - SVM/KNN

    • 矩阵分解

    • 单值分解

    • 交替最小二乘法

    • 混合推荐引擎

推荐引擎的演变

多年来,推荐系统已经从基本的最近邻方法发展到个性化推荐,再到上下文感知推荐,从批量推荐到实时推荐,从基本的启发式方法,如相似度计算,到更精确、更复杂的机器学习方法。

在这些推荐系统的早期阶段,仅使用产品上的用户评分来生成推荐。在这个时候,研究人员只使用了可用的评分信息。他们简单地应用了启发式方法,如使用欧几里得距离、皮尔逊系数、余弦相似度等相似度计算方法。这些方法得到了良好的反响,并且令人惊讶的是,即使今天,它们的性能仍然相当不错。

这一代推荐引擎被称为协同过滤或邻域方法推荐器。尽管它们表现非常出色,但这些推荐器也带来了一系列限制,例如冷启动问题;也就是说,它们未能向没有评分信息的新用户推荐产品,也未能向用户推荐没有评分的新产品。此外,这些推荐器还未能处理数据非常稀疏的场景,因此产品上的用户评分要少得多。

为了克服这些限制,已经开发出新的方法。例如,为了处理具有高数据稀疏性的非常大的用户评分,已经使用了如矩阵分解和单值分解等数学方法。

为了处理冷启动问题,已经开发出新的方法,如基于内容的推荐系统。这些推荐系统为许多更多机会打开了大门,例如个性化推荐系统,这使得它们能够针对每个用户在个体层面上推荐产品。在这种方法中,不是考虑评分信息,而是考虑用户的个人偏好和产品特征。

推荐引擎的演变

在最初,基于内容的推荐者使用了相似度计算,但随着技术和基础设施的进步,更先进的方法,如机器学习模型,已经取代了启发式方法。这些新的机器模型提高了推荐的准确性。

尽管基于内容的推荐者解决了协同过滤的许多缺点,但它们也有自己的固有缺点,如偶然性,也就是说,无法推荐超出用户偏好范围的新项目,这是协同过滤可以做到的。

为了解决这个问题,研究人员开始结合不同的推荐模型,以提出混合推荐模型,这些模型比任何单个模型都要强大得多。

随着个性化推荐引擎的成功实施,人们开始将个性化扩展到其他维度,称为上下文,例如添加位置、时间、群体等,并针对每个上下文改变推荐集。

随着大数据生态系统、内存分析工具(如 Apache Spark)和实时推荐等技术的进步,处理非常大的数据库的能力已成为可能。

目前,我们正在向更多个性化方面发展,如时间维度和无处不在的推荐方式。

在技术方面,推荐系统的建议正从机器学习方法转向更高级的神经网络深度学习方法。

基于最近邻的推荐引擎

正如其名所示,基于最近邻的推荐系统在向活跃用户做出建议或推荐之前,会考虑活跃用户社区或邻居用户的偏好或喜好。基于最近邻推荐者的想法非常简单:给定用户的评分,找到所有过去有相似偏好的与活跃用户相似的用户,然后对活跃用户尚未评分但在其邻居中被评分的所有未知产品做出预测:

基于最近邻的推荐引擎

在考虑邻居的偏好或口味时,我们首先计算其他用户与活跃用户之间的相似度,然后根据预测向用户推荐用户社区中未评分的项目。在这里,活跃用户是系统提供推荐的人。由于涉及相似度计算,这些推荐系统也被称为基于相似度的推荐系统。此外,由于偏好或口味是从一组用户中协同考虑的,因此这些推荐系统也被称为协同过滤推荐系统。在这些类型的系统中,主要角色是用户、产品和用户的偏好信息,如对产品的评分/排名/喜欢。

以下图像是亚马逊的一个示例,展示了邻域案例:

基于最近邻推荐引擎

这些基于启发式的方法基于以下假设:

  • 过去有相似偏好的用户在未来也会有相似偏好

  • 人们的偏好在未来将保持稳定和一致

协同过滤系统分为两种类型:

  • 基于用户的协同过滤

  • 基于物品的协同过滤

当我们只有产品的用户交互数据(如评分、喜欢/不喜欢、查看/不查看)时,会采用这些邻域方法。与下一节将要解释的内容相关的基于内容的推荐不同,它们不考虑产品的任何特征或用户对产品的个人偏好:

基于最近邻推荐引擎

基于用户的协同过滤

如前所述,基于用户的协同过滤系统背后的基本直觉是,过去有相似口味的用户将来也会喜欢相似的项目。例如,如果用户 A 和用户 B 有非常相似的购买历史,并且如果用户 A 购买了一本用户 B 尚未看过的书,那么我们可以向用户 B 推荐这本书,因为他们有相似的口味。

让我们通过一个例子来尝试理解基于用户的协同过滤:

问题陈述:想象我们有一个数据集,用于第二章“构建你的第一个推荐引擎”,其中包含电影评论网站上评论者对电影的评分。当前的任务是为评论者推荐电影:

基于用户的协同过滤

在我们学习推荐方法之前,第一步是分析手头的资料。让我们按以下步骤逐步分析数据:

  • 与应用程序互动的用户集合

  • 所有可用电影的目录

  • 我们有个人用户对电影的评分

小贴士

注意,每个用户并没有对所有电影进行评分,而只是对整个目录中的一些电影进行了评分。

第一步是找到活跃用户的相似用户,然后推荐该活跃用户尚未观看但相似用户已观看的新电影。

这可以总结为两个步骤:

  1. 使用电影的评分信息计算用户之间的相似度。

  2. 对于每个活跃用户,考虑那些他们未评分但其他用户已评分的电影。预测活跃用户对未评分电影的未知评分。

在前面的表格数据中,让我们为我们的活跃用户 Jack Mathews 推荐新电影:

  1. 第一步是寻找与 Jack 相似的活跃用户。通过观察数据集,我们发现 Gene Seymour 和 Mick Lasalle 与 Jack Mathews 非常相似。

  2. 用户之间的相似度是基于用户给出的评分计算的。计算相似度的最常见方法包括欧几里得距离和皮尔逊相关系数。

  3. 目前我们选择使用以下方程给出的欧几里得距离来计算相似度:

基于用户的协同过滤

使用欧几里得距离背后的直觉是,我们将用户、电影和评分表示为向量空间中的点,用户位于x轴上,电影位于y轴上,评分作为向量空间中的点。现在我们已经将数据投影到向量空间中,可以使用欧几里得距离和皮尔逊相关系数来计算两点之间的相似度或接近度。相似度度量方法的详细解释将在第四章,推荐引擎中使用的数据挖掘技术中解释。

基于用户的协同过滤

使用前面的方程,我们可以计算所有评论者之间的相似度,如表所示。从表中我们可以观察到,我们的活跃用户 Toby 与 Lisa Rose 最为相似。

作为第二步,我们通过计算以下其他评论者对《Just My Luck》给出的评分的加权平均值来预测 Jack 对未知电影《Just My Luck》的评分:

Jack 对电影《Just my Luck》的评分如下:

(30.9285+1.50.944+30.755+20.327)/(0.8934051+0.3812464+0.9912407+0.9244735)= 2.23

在上述方程中,我们将 Jack 与所有评论者的相似度值乘以他们对《Just My Luck》电影的评分,然后对所有值求和。这个总和除以相似度值的总和,以归一化最终的评分。同样,我们可以预测所有评论的未知电影评分,然后进行推荐。

基于项目的协同过滤

在基于项目的协同过滤推荐系统中,与基于用户的协同过滤不同,我们使用项目之间的相似度而不是用户之间的相似度。基于项目的推荐系统的基本直觉是,如果用户过去喜欢项目 A,他们可能会喜欢与项目 A 相似的项目 B:

基于项目的协同过滤

在基于用户的协同过滤中,存在一些缺点:

  • 如果用户评分非常稀疏,系统性能会受到影响,这在现实世界中非常常见,用户只会对大量目录中的少数项目进行评分。

  • 如果数据量很大,计算所有用户相似度值的计算成本非常高。

  • 如果用户配置文件或用户输入快速变化,那么我们必须重新计算伴随高计算成本的相似度值。

基于项目的推荐引擎通过计算项目或产品之间的相似度而不是计算用户之间的相似度来处理这些缺点,从而降低计算成本。由于项目目录变化不快,我们不必经常重新计算。

与基于用户的协同过滤方法一样,基于项目的协同方法有两个步骤:

  1. 计算项目之间的相似度。

  2. 通过利用对其他相似项目给出的先前评分来预测活跃用户的未评分项目的评分。

用于此方法的最常见的相似度度量是余弦相似度。余弦相似度通过向量空间中两个 n 维向量之间的角度来计算两个向量之间的相似度。余弦相似度由以下方程给出:

基于项目的协同过滤

当将余弦相似度应用于推荐系统时,我们将项目列视为 n 维向量,并将两个项目之间的相似度视为它们之间的角度。角度越小,项目越相似。

例如,在先前的数据集中,如果我们想预测 Toby 对电影《水之女》的评分,首先我们必须识别与《水之女》相似的电影。使用先前的余弦方程,我们可以计算所有项目的相似度。下表显示了所有电影的相似度值:

提示

基于项目的相似度仅计算共同评分的项目。

基于项目的协同过滤

从先前的表中,我们可以看到《你、我和杜普里》与《水之女》(0.8897565)最为相似。

我们现在通过计算 Toby 对与《水之女》相似的电影的评分的加权总和来预测《水之女》电影的评分。也就是说,我们取《水之女》对每个 Toby 评过分的电影的相似度得分,乘以相应的评分,然后将所有评分电影的所有得分加起来。这个最终的总和除以下列给出的《水之女》的相似度得分总和:

《水之女》评分:

(0.7954.5 + 0.8144 + 0.889*1)/(0.795+0.814+0.889) = 3.09

同样,我们可以使用前面的方程式计算所有其他用户对电影的评分。在第四章《推荐引擎中使用的数据挖掘技术》中,我们讨论了可以用于基于项目的推荐的其他相似度度量。

优点

  • 容易实现

  • 构建推荐时不需要产品的内容信息或用户的个人资料信息

  • 向用户推荐新项目,给用户带来惊喜因素

缺点

  • 这种方法在计算上非常昂贵,因为所有用户、产品和评分信息都需要加载到内存中进行相似度计算。

  • 这种方法对于新用户无效,因为我们没有关于用户的任何信息。这个问题被称为冷启动问题。

  • 如果我们数据很少,这种方法的表现非常差。

  • 由于我们没有用户或产品的内容信息,我们无法仅基于评分信息准确生成推荐。

基于内容的推荐系统

在上一节中,我们看到推荐是通过仅考虑用户对产品的评分或交互信息来生成的,也就是说,为活跃用户推荐新项目是基于活跃用户对那些新项目的评分。

让我们以一个给电影评了 4 星的人为例。在协同过滤方法中,我们只考虑这个评分信息来生成推荐。在现实生活中,一个人是根据电影的特征或内容来评分的,比如它的类型、演员、导演、故事和剧本。此外,一个人是根据自己的个人选择来看电影的。当我们构建一个针对个人层面的推荐引擎时,推荐不应该基于其他类似人的口味,而应该基于个人用户的口味和产品的内容。

一种针对个性化水平且考虑个人偏好和产品内容的推荐称为基于内容的推荐系统。

构建基于内容的推荐引擎的另一个动机是它们解决了协同过滤方法中新用户面临的新手问题。当一个新用户到来时,基于该人的偏好,我们可以建议一些与他们的口味相似的新项目。

构建基于内容的推荐系统涉及三个主要步骤,如下:

  1. 为产品生成内容信息。

  2. 生成用户配置文件和与产品特征相关的偏好。

  3. 生成推荐并预测用户可能喜欢的项目列表:

基于内容的推荐系统

项目配置文件生成:在这个步骤中,我们提取代表产品的特征。最常见的是,产品的内容以行表示产品名称,列表示特征的空间模型表示。通常,产品的内容将是结构化数据或非结构化数据。结构化数据将从数据库中获得;非结构化特征将包括网站中关联的评论、标签或文本属性。在项目配置文件生成步骤中,我们必须提取相关特征及其与产品相关的相对重要性分数。

为了生成项目配置文件,我们使用词频逆文档频率tf-idf)来计算与项目相关的特征相对重要性。由于我们用向量表示来表示项目特征,我们可以使用 tf-idf,这将在第四章推荐引擎中使用的数据挖掘技术中详细解释。

让我们通过一个例子来更好地理解。正如我们之前提到的,对于基于内容的推荐引擎,我们需要关于电影等额外内容信息,如下所示:

基于内容的推荐系统

我们首先要做的是使用 tf-idf 创建一个项目配置文件,通过以下步骤:

创建一个包含每个文档中每个术语频率计数的术语频率矩阵;也就是说,在我们的案例中,每个电影中每个类别的存在情况。数字 1 代表类别的存在,0 代表类别的缺失:

基于内容的推荐系统

下一步是创建以下公式给出的逆文档频率:

Idf = Log(文档总数/文档频率)

这里,文档总数是电影的数量,文档频率是它们在所有文档中出现的总次数:

基于内容的推荐系统

最后一步是创建以下公式给出的tf-idf矩阵:

tfidf*

基于内容的推荐系统

生成用户配置文件

在这一步,我们构建与产品内容相匹配的用户配置文件或偏好矩阵。一般来说,我们构建与产品内容共有的用户配置文件或特征,因为这样比较用户和项目配置文件并计算它们之间的相似度更有意义。

让我们考虑以下数据集,它显示了每个用户的观看历史。如果矩阵单元格中的值为 1,则表示用户已经看过这部电影。这些信息给出了他们对电影的偏好:

用户配置文件生成

从前面的信息中,我们将创建一个用户配置文件,可以与项目配置文件进行比较;也就是说,我们现在创建一个包含用户对项目特征偏好的用户配置文件,在我们的例子中是对类型的偏好。tf-idf 和用户偏好矩阵的点积将给出每个类型的用户亲和度,如下表所示:

dotProduct(Tf-idf, userPreference matrix)

用户配置文件生成

在手头有用户配置文件和项目配置文件的情况下,下一步将是估计用户将更喜欢每个项目的程度。我们现在可以使用余弦相似度来计算每个项目的用户偏好。在我们的例子中,用户和项目配置文件之间的余弦相似度给出了以下结果:

cosineSimilarity(userProfile,ItemProfile)

用户配置文件生成

从前面的表格中我们可以得出结论,余弦角度越大,用户越有可能喜欢一部电影,因此可以推荐给用户。

现在我们已经做出了推荐,让我们退一步,从收集用户偏好数据的角度来看。通常有两种方法来捕获用户数据;如下所述:

  • 明确询问用户对产品特征的偏好,并将它们存储起来。

  • 隐式地捕获用户在产品上的交互数据,如浏览历史、评分历史和购买历史,并将用户偏好构建到产品特征中。在第四章《推荐引擎中使用的数据挖掘技术》和第五章《构建协同过滤推荐引擎》中,我们使用显式和隐式的用户活动示例构建推荐引擎。

我们至今为止所采用的基于内容推荐系统构建的方法是基于相似度计算的。我们还可以应用诸如分类等监督机器学习方法来预测用户可能喜欢的最可能的产品。

使用机器学习或任何其他数学、统计模型来生成推荐的推荐系统被称为基于模型的系统。在分类方法中,这些方法属于基于模型的推荐系统,我们首先使用用户配置文件和项目配置文件构建机器学习模型,以预测用户是否喜欢/不喜欢某个项目。监督分类任务,如逻辑回归、KNN 分类方法、概率方法等,都可以使用。基于模型的推荐引擎将在下一节讨论。

优点

  • 基于内容的推荐系统针对个人层面

  • 推荐是仅使用用户偏好生成的,而不是像协同过滤那样使用用户社区

  • 这些方法可以实时应用,因为推荐模型不需要加载所有数据来处理或生成推荐

  • 与仅处理评分信息的协同方法相比,准确性较高,因为它们处理的是产品的内容

  • 可以轻松处理冷启动问题

缺点

  • 随着系统更加个性化,生成的推荐将仅限于用户偏好,当更多用户信息进入系统时,推荐将变得更加狭窄

  • 因此,不会向用户展示与用户偏好无关的新产品

  • 用户将无法查看他们周围发生的事情或当前的趋势

上下文感知推荐系统

多年来,推荐系统从邻域方法发展到针对个人用户的个性化推荐系统。这些个性化推荐系统取得了巨大成功,因为这对终端用户是有用的,对于组织来说,这些系统成为增加其业务的催化剂。

尽管个性化推荐系统针对的是个人用户级别,并根据用户的个人偏好提供推荐,但仍有改进系统的空间。例如,同一个人在不同的地方可能有不同的需求。同样,同一个人在不同时间也有不同的需求:

上下文感知推荐系统

我们智能的推荐系统应该足够进化,以满足用户在不同地点、不同时间的需要。推荐系统应该足够稳健,能够在夏天向某人推荐棉质衬衫,在冬天推荐皮夹克。同样,根据一天中的时间,建议提供符合个人早餐或晚餐选择的餐厅将非常有帮助。这类考虑位置、时间、心情等因素,定义用户上下文并建议个性化推荐的推荐系统,被称为上下文感知推荐系统

上下文感知推荐系统

前面的图像说明了在寒冷天气中推荐咖啡的推荐引擎。

上下文定义

那么上下文究竟是什么?一般来说,上下文代表用户的当前状态。用户的上下文可以是任何东西,比如地点、时间、日期、季节、心情、设备、用户是否独自一人、在办公室、度假、与家人、与朋友在一起、生活事件等等。由于人们在不同的上下文中会有不同的需求,推荐系统可以捕捉用户的上下文信息,并据此细化他们的建议。

例如,一个旅行度假系统可能会考虑季节、地点和时间作为上下文来细化建议;一个电子商务网站可以考虑生活事件和用户购买行为作为上下文感知推荐,而一个食品网站在推荐餐厅时可能会考虑一天中的时间和地点信息。

上下文感知系统是如何设计的?到目前为止,我们已经将推荐视为一个二维问题,即用户偏好和项目表示。通过包含上下文作为新的维度,我们可以将上下文感知推荐构建为一个三维问题:

推荐 = 用户 x 项目 x 上下文

上下文定义

让我们回顾一下我们在基于内容的推荐中使用的相同例子,在那里我们考虑了用户概要和项目概要,通过计算用户概要和项目概要之间的相似性来生成每个项目的用户评分。现在,在上下文感知系统中,我们包括上下文来根据用户偏好和上下文生成项目的排名。

例如,我们可以假设我们的推荐已经捕捉到了用户在工作日、周末和假期的观影模式。从这个上下文信息中,我们提取了每个用户对电影内容的亲和度。例如,考虑 TOBY 在每个电影内容上下文中的以下偏好:

上下文定义上下文定义

让我们首先为 TOBY 创建每个上下文的所有电影内容的用户概要。上下文矩阵和用户概要矩阵之间的点积给出了所有上下文的用户概要:

点积(user profile,context matrix) 对于 TOBY:

上下文定义

我们现在已经计算了 TOBY 对每个电影上下文的偏好。下一步将是计算所有上下文中每个电影的排名。

余弦相似度(上下文电影内容偏好矩阵,项目概要):

上下文定义

现在我们已经为 TOBY 有了电影的上下文级别排名,我们可以根据上下文来推荐电影。

从前面的例子中,我们了解到上下文感知推荐系统是包含一个称为上下文的新维度的基于内容的推荐系统。在上下文感知系统中,推荐是分两步生成的,如下所示:

  1. 根据用户的偏好为每个用户生成产品推荐列表;即基于内容的推荐。

  2. 过滤掉特定于当前上下文的推荐。

构建上下文感知推荐系统最常见的方法如下:

  • 后过滤方法

  • 预过滤方法

预过滤方法

在预过滤方法中,上下文信息应用于用户配置文件和产品内容。这一步骤将过滤掉所有非相关特征,并在剩余的特征集上生成最终的个人推荐。由于特征过滤是在生成个性化推荐之前进行的,因此这些方法被称为预过滤方法:

预过滤方法

后过滤方法

在后过滤方法中,首先根据用户配置文件和产品目录生成个性化推荐,然后应用上下文信息来过滤出当前上下文中对用户相关的产品:

后过滤方法

优点

  • 上下文感知系统比基于内容的个性化推荐系统更先进,因为这些系统将始终与用户动作保持同步,并根据当前上下文生成推荐。

  • 这些系统具有更多的实时性

缺点

  • 与其他个性化推荐系统一样,这些类型的推荐也将缺少偶然性或惊喜因素

混合推荐系统

协同过滤系统和基于内容的推荐系统既有效又满足广泛的需求。它们有相当成功的实施案例,但每个系统独立使用时都有自己的局限性。研究已经开始向结合协同过滤和基于内容的推荐的方向发展。这种通过结合协同过滤和基于内容的方法形成的新类型的推荐系统被称为混合推荐系统。

选择结合不同的推荐方法取决于研究人员或根据问题陈述和业务需求实施混合推荐引擎的人员。

构建混合系统最常见的方法如下:

  • 加权方法

  • 混合方法

  • 切换方法

  • 级联方法

  • 特征组合方法

  • 特征增强

  • 元级

加权方法

在这种方法中,最终的推荐结果将是所有可用推荐引擎推荐结果的组合,主要是线性的。在部署此加权混合推荐引擎的初期,将给每个可用推荐引擎的结果分配相同的权重,然后通过评估用户对推荐的响应来逐渐调整权重。

混合方法

混合方法适用于我们可以混合所有可用推荐器结果的地方。这些方法大多用于数据稀疏,无法通过所有可用推荐系统为产品评分的地方。因此,推荐是独立生成的,在发送给用户之前会混合。

级联方法

在这种方法中,使用协同过滤生成推荐。应用基于内容的推荐技术,然后最终推荐/排名列表将作为输出。

特征组合方法

特征组合方法,其中我们将不同推荐系统的特征和最终推荐方法结合起来,应用于组合特征集。在这种技术中,我们结合了从基于内容的推荐系统提取的用户-项目偏好特征和用户-项目评分信息,并考虑了一种新的策略来构建混合推荐系统。

特征组合方法

优点

  • 可以处理冷启动问题和数据稀疏等问题

  • 这些系统比任何单个模型都要更加健壮和可扩展。

  • 方法组合提高了准确性

基于模型推荐系统

迄今为止,我们一直专注于涉及用户或产品之间相似度计算的邻近方法,这些方法用于协同过滤方法,或者在向量空间模型中表示用户和项目内容,并找到相似度度量来识别与用户偏好相似的项目。基于相似度的方法的主要目标是计算用户对产品或产品内容的偏好权重,然后使用这些特征权重来推荐项目。

这些方法在过去的几年中非常成功,甚至至今仍然如此。但这些方法有其自身的局限性。由于整个数据必须加载到环境中进行相似度计算,这些方法也被称为基于内存的模型。这些基于内存的模型在实时场景中响应速度非常慢,因为所有数据都必须加载。另一个局限性是,计算出的权重没有像机器学习应用那样自动学习。冷启动问题是基于内存或基于邻近的方法常见的另一个局限性。

为了解决这些局限性,研究人员开始应用更高级的方法来提高推荐引擎的性能,例如概率模型、机器学习模型(如监督和非监督模型)以及矩阵方法(如矩阵分解和单值分解)。在基于模型的方法中,使用可用的历史数据,构建一个带有自动学习到的权重的模型。将使用学习到的权重对新产品进行预测,然后在做出推荐之前将最终结果按特定顺序排序。

概率方法

在概率方法中,我们使用可用数据中的先验概率构建一个概率模型,并通过计算每个用户对产品的喜欢/不喜欢概率来生成一个推荐排名列表。在概率方法中最常用的是朴素贝叶斯方法,这是一种简单但强大的技术。

机器学习方法

正如内容推荐系统中所解释的,我们可以将推荐问题视为一个机器学习问题。利用历史用户和产品数据,我们可以提取特征并输出类别,然后构建一个机器学习模型。使用生成的模型生成最终的产品推荐排名列表。可以使用许多机器学习方法,如逻辑回归、KNN 分类、决策树、SVM、聚类等。这些机器学习方法应用于协同、基于内容、上下文感知和混合推荐系统。在第四章《推荐引擎中使用的数据挖掘技术》中,我们详细学习了每种机器学习方法。

数学方法

在这些方法中,我们假设用户对产品的评分或交互信息是简单的矩阵。在这些矩阵上,我们应用数学方法来预测用户的缺失评分。最常用的方法包括矩阵分解模型和单值分解模型:

数学方法

通过应用矩阵分解方法,我们假设将原始评分矩阵(R)分解为两个新的矩阵(U,V),这两个矩阵代表了用户和电影的潜在特征。

从数学的角度来看,我们可以将一个矩阵分解为两个低秩矩阵。在前面的例子中,矩阵 R 被分解为矩阵 U 和 V。现在当我们乘以 U 和 V 时,我们得到原始矩阵 R 的近似值。这个概念在推荐引擎中用于填充原始评分矩阵中的未知评分,然后对推荐进行排名并建议给用户。

在第四章《推荐引擎中使用的数据挖掘技术》中,我们更详细地讨论了这两种方法。

优点

  • 基于模型的推荐比基于启发式的方法(如邻域方法)更准确。

  • 在启发式方法中,产品/产品内容的权重更静态,而在基于模型推荐中,权重是通过自动学习建立的。

  • 基于模型的方法使用数据驱动方法提取许多未见过的模式。

摘要

在本章中,我们学习了流行的推荐引擎技术,如协同过滤、基于内容的推荐、上下文感知系统、混合推荐和基于模型的推荐系统,以及它们的优缺点。存在不同的相似度方法,如余弦相似度、欧几里得距离和皮尔逊系数。每个推荐中的子类别也进行了解释。

在下一章中,我们将学习不同的数据挖掘技术,例如邻域方法、用于推荐引擎的机器学习方法,以及它们的评估技术,如 RMSE 和精确-召回率。

第四章:推荐引擎中使用的数据挖掘技术

数据挖掘技术位于推荐引擎的核心。这些数据挖掘技术帮助我们提取模式、分组用户、计算相似度、预测偏好、处理稀疏输入数据、评估推荐模型等。在前一章中,我们详细学习了推荐引擎。尽管我们没有深入研究推荐引擎的实现,但我们学习了不同类型推荐引擎背后的理论,例如基于邻域、个性化、上下文推荐者、混合推荐者等。在本章中,我们将探讨目前用于构建推荐引擎的流行数据挖掘技术。我们之所以将这一内容单独成章,是因为在后续章节实现推荐引擎时,我们将遇到许多技术。

本章大致分为以下几部分:

  • 基于邻域的技术

    • 欧几里得距离

    • 余弦相似度

    • Jaccard 相似度

    • 皮尔逊相关系数

  • 数学建模技术

    • 矩阵分解

    • 交替最小二乘法

    • 奇异值分解

  • 机器学习技术

    • 线性回归

    • 分类模型

  • 聚类技术

    • K-means 聚类
  • 维度降低

    • 主成分分析
  • 向量空间模型

    • 词语频率

    • 词语频率-逆文档频率

  • 评估技术

    • 均方根误差

    • 均绝对误差

    • 精确率和召回率

每个部分都通过基本技术和其在 R 中的实现进行了说明。

让我们从推荐引擎中最常用的基础知识开始复习。

基于邻域的技术

如前几章所述,邻域方法是简单的技术,从构建推荐引擎的开始就被使用。这些是最古老但也是最广泛使用的方法,即使在今天也是如此。这些广泛使用的方法之所以受欢迎,是因为它们在生成推荐方面的准确性。我们知道几乎每个推荐系统都是基于物品或用户之间相似性的概念。这些邻域方法将两个用户或物品之间的可用信息视为两个向量,并在这些向量之间应用简单的数学计算来查看它们有多接近。在本节中,我们将讨论以下邻域技术:

  • 欧几里得距离

  • 余弦相似度

  • Jaccard 相似度

  • 皮尔逊相关系数

欧几里得距离

欧几里得距离相似度是用于计算两点或两个向量之间距离的最常见相似度度量之一。它是向量空间中两点或向量之间的路径距离。

在以下图中,我们看到向量 a 和 b 之间的路径距离是欧几里得距离:

欧几里得距离

欧几里得距离是基于毕达哥拉斯定理来计算两点之间的距离。

在数据集中,两点或物体(点 x 和点 y)之间的欧几里得距离由以下方程定义:

欧几里得距离

在这里,xy 是两个连续的数据点,n 是数据集的属性数量。

欧几里得距离在推荐引擎中是如何应用的?

考虑一个包含用户 ID 作为行,项目 ID 作为列,偏好值作为单元格值的评分矩阵。两个行之间的欧几里得距离给出了用户相似度,两个列之间的欧几里得距离给出了项目相似度。当数据由连续值组成时,使用此度量。

计算欧几里得距离的 R 脚本如下:

x1 <- rnorm(30) 
x2 <- rnorm(30) 
Euc_dist = dist(rbind(x1,x2) ,method="euclidean") 

欧几里得距离

余弦相似度

余弦相似度是内积空间中两个向量之间相似度的度量,它衡量的是它们之间角度的余弦值;它由以下方程给出:

余弦相似度

a 为一个向量 (a1, a2, a3, a4),b 为另一个向量 (b1, b2, b3, b4)。这两个向量 ab 的点积如下:

a.b = a1b1 + a2b2 + a3b3 + a4b4

结果将是一个单一值,一个标量常数。两个向量之间的点积意味着什么?为了回答这个问题,让我们定义两个向量之间点积的几何定义:

余弦相似度

重新排列前面的方程,我们得到以下方程:

余弦相似度

在前面的方程中,cosθ 是两个向量之间的角度,acosθ 是向量 A 在向量 B 上的投影。

两个向量点积的视觉向量空间表示如下:

余弦相似度

当两个向量之间的余弦角度为 90 度时,cos 90 将变为零,整个点积也将为零,即它们将相互垂直。我们可以推断出的逻辑结论是它们彼此非常遥远:

余弦相似度

当我们减小两个向量之间的余弦角度时,它们的方向看起来将非常相似。

当两个向量之间的角度为零时,cos 0 将为 1,两个向量将重合,如下面的图像所示。因此,我们可以说这两个向量在方向上相似:

余弦相似度

所以总结一下,我们可以得出结论,当我们计算两个向量之间的余弦角度时,得到的标量值将指示两个向量在方向上的接近程度:

余弦相似度

现在,让我们重新审视我们的原始问题:点积意味着什么?当我们对两个向量取点积时,得到的标量值代表它们之间的余弦角。如果标量为零,则两个向量是正交的且无关。如果标量为 1,则两个向量是相似的。

现在,这是如何在推荐引擎中应用的?

如前所述,考虑一个包含用户 ID 作为行和物品 ID 作为列的评分矩阵。我们可以假设每一行是用户向量,每一列是物品向量。

行向量之间的余弦角将给出用户相似度,列向量之间的余弦角给出物品相似度。

计算余弦距离的 R 脚本如下:

vec1 = c( 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 ) 
vec2 = c( 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0 ) 
library(lsa) 
cosine(vec1,vec2) 

在这里,x是包含数据集中所有变量的矩阵;余弦函数在lsa包中可用。lsa是 R 中用于在文本中查找潜在特征或主题的文本挖掘包。此包提供cosine()方法来计算两个向量之间的余弦角。

Jaccard 相似度

Jaccard 相似度是推荐引擎中使用的另一种相似度度量。Jaccard 相似度系数是两个用户或物品之间特征交集与特征并集的比值。

从数学上讲,如果AB是两个向量,Jaccard 相似度由以下方程给出:

Jaccard 相似度

Jaccard 相似系数指标是一种用于在样本集中寻找相似性和多样性的统计量。由于用户和物品可以被表示为向量或集合,我们可以轻松地将 Jaccard 系数应用于推荐系统,以找到用户或物品之间的相似性。

计算 Jaccard 相似度的 R 脚本如下:

vec1 = c( 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 ) 
vec2 = c( 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0 ) 
library('clusteval') 
cluster_similarity(vec1, vec2, similarity = "jaccard") 

R 中的clusteval包是一个用于评估聚类技术的流行包。Cluster_similarity()方法提供了计算 Jaccard 相似度的良好实现。

皮尔逊相关系数

找到上述相似性的另一种方法是找到两个向量之间的相关性。在这种方法中,我们不是使用距离度量作为在向量中寻找相似性的方式,而是使用向量之间的相关性。

皮尔逊相关系数可以按以下方式计算:

皮尔逊相关系数

在这里,r是相关系数,n是数据点的总数,x[i]是 x 向量的第i个向量点,y[i]是 y 向量的第i个向量点,x-bar是向量 x 的均值,y-bar是向量 y 的均值,s[x]是向量 x 的标准差,s[y]是向量 y 的标准差。

计算两个变量之间相关系数的另一种方法是,通过将两个变量的协方差除以它们标准差的乘积,

皮尔逊相关系数(rho) 给出:

皮尔逊相关系数

让我们通过以下图像中的示例来理解这一点。我们绘制了两个向量 a, b 的值;自然地假设如果向量的所有点都一起变化,它们之间存在正相关关系。这种一起变化的趋势,或者说协方差,在简单术语中可以称为相关性。看看以下图表:

皮尔逊相关系数

现在我们来检查以下图像。我们可以观察到向量不是一起变化的,相应的点随机分布。因此,一起变化的趋势,或者说协方差,较小,或者说是较低的相关性:

皮尔逊相关系数

从相似度计算的角度来看,我们可以得出结论:两个向量之间的相关性越大,它们就越相似。

那么,皮尔逊相关系数在推荐引擎中是如何应用的?

如前所述,考虑一个包含用户 ID 作为行和项目 ID 作为列的评分矩阵。我们可以假设每一行是用户向量,每一列是项目向量。

行向量之间的相关系数将给出用户相似度,列向量之间的相关系数将给出项目相似度,使用以下方程:

R 脚本由以下方程给出:

Coef = cor(mtcars, method="pearson") 

在这里,mtcars 是数据集。

数学模型技术

如矩阵分解和奇异值分解等数学模型在构建基于相似度计算的推荐引擎时已被证明非常准确。另一个优点是它们可以轻松地缩小规模,也允许轻松设计系统。在本章中,我们将学习如后所述的数学模型。

矩阵分解

一个矩阵可以被分解成两个低秩矩阵,当它们相乘时,将得到一个与原始矩阵近似相等的单个矩阵。

假设 R,一个大小为 U X M 的评分矩阵可以被分解成两个低秩矩阵,PQ,分别大小为 U X KM X K,其中 K 被称为矩阵的秩。

在以下示例中,原始矩阵的大小为 4 X 4 被分解成两个矩阵,P (4 X 2)Q (4 X 2);将 PQ 相乘将带给我原始矩阵的大小为 4 X 4 和与原始矩阵近似相等的值:

矩阵分解

矩阵分解方法的一个主要优点是我们可以使用低秩矩阵 PQ 之间的点积来计算原始矩阵 R 中的空单元格。这由以下方程给出:

矩阵分解

当我们应用前面的方程时,我们可以重新生成原始矩阵 R,所有空单元格都被填充。

为了使预测值尽可能接近原始矩阵,我们必须最小化原始值和预测值之间的差异,这也就是误差。原始值和预测值之间的误差可以用以下方程表示:

矩阵分解

为了最小化上述误差项并尽可能准确地重现原始矩阵,我们必须使用梯度下降技术——一种寻找目标函数最优参数并迭代最小化函数的算法,并在方程中引入正则化项。

矩阵分解是如何应用于推荐引擎的?

这是一个核心问题,我们对此比矩阵分解中的数学更感兴趣。我们将看到如何将矩阵分解技术应用于构建推荐引擎。

回顾构建推荐引擎的核心任务:找到相似的用户或项目,然后预测未评分的偏好,最后向活跃用户推荐新项目。简而言之,我们正在预测未评分的项目偏好。回顾一下,这正是矩阵分解所做的:预测原始评分矩阵中的空单元格。

现在,我们如何证明将矩阵分解应用于低秩矩阵在推荐引擎中的方法?为了回答这个问题,我们将讨论用户如何评分。人们评分电影是因为故事、演员或电影的类型,也就是说,用户评分项目是因为项目的特征。当给定一个包含用户 ID、项目 ID 和评分值的评分矩阵时,我们可以假设用户在评分项目时会有一些固有的偏好,项目也会有一些固有的特征,这些特征有助于用户评分。这些用户和项目的特征被称为潜在特征

考虑到之前的假设,我们将矩阵分解技术应用于评分矩阵,得到两个低秩矩阵,这些矩阵被假定为用户潜在特征矩阵和项目潜在特征矩阵:

矩阵分解

考虑到这些假设,研究人员开始将矩阵分解技术应用于构建推荐系统。矩阵分解方法的优势在于,由于它是一个机器学习模型,特征权重会随着时间的推移而学习,从而提高模型精度:

以下代码解释了使用 R 中的nmf包实现矩阵分解的实现:

#MF 
library(recommenderlab) 
data("MovieLense") 
dim(MovieLense) 

#applying MF using NMF 
mat  = as(MovieLense,"matrix") 
mat[is.na(mat)] = 0 
res = nmf(mat,10) 
res 

#fitted values 
r.hat <- fitted(res) 
dim(r.hat) 

p <- basis(res) 
dim(p) 
q <- coef(res) 
dim(q) 

交替最小二乘法

回顾上一节中的误差最小化方程。在引入正则化项以避免过拟合后,最终的误差项将类似于以下方程:

交替最小二乘法

为了优化前面的方程,有两种流行的技术:

  • 随机梯度下降SGD):一种小批量优化技术,类似于梯度下降,用于在大规模数据或稀疏数据中寻找最优参数。

  • 交替最小二乘ALS):与 SGD 相比,ALS 方法的主要优势是它可以在分布式平台上轻松并行化。

在本节中,我们将探讨 ALS 方法。

前面的方程涉及两个未知数,我们需要求解。由于涉及两个未知数,上述方程是一个非凸问题。如果我们固定其中一个未知项的常数,这个优化问题将变为二次的,并且可以最优地解决。

交替最小二乘是一种迭代方法,它涉及通过固定另一个特征向量项为常数,使用最小二乘函数计算一个特征向量项,直到我们最优地解决前面的方程。

为了计算用户特征向量,我们将物品特征向量固定为一个常数并求解最小二乘。同样,在计算物品特征向量时,我们将用户特征向量固定为一个常数并求解最小二乘。

采用这种方法,我们可以将非凸问题转化为二次问题,从而可以最优地解决。

大多数开源分布式平台,如 Mahout 和 Spark,都使用 ALS 方法来实现可扩展的推荐系统,因为它们可以并行化。

奇异值分解

奇异值分解SVD)是另一种非常流行的矩阵分解方法。简单来说,SVD 方法将一个大小为 m x n 的实矩阵 A 分解为三个矩阵 U,奇异值分解,V,它们满足以下方程:

奇异值分解

在前面的方程中,r 被称为矩阵 A 的秩,UV 是正交矩阵,而奇异值分解是一个对角矩阵,包含矩阵 A 的所有奇异值。如果 A 是实矩阵,则 UV 的值是实数。矩阵奇异值分解的值是正实数,并且按递减顺序排列。

SVD 也可以用作降维技术,遵循以下两个步骤:

  • 选择一个小于 r 的秩 k

  • 重新计算或缩小 U奇异值分解V 矩阵到 (m x k)(k x k)(k x n)

奇异值分解

应用 SVD 得到的矩阵非常适合推荐系统,因为它们提供了原始矩阵的最佳低秩近似。我们如何将 SVD 方法应用于推荐?让我们以一个大小为 m x n 的评分矩阵 R 为例,其中包含许多空单元格。类似于矩阵分解,我们的目标是计算一个尽可能接近原始矩阵的近似评分矩阵,其中缺失的值被预测。

在 R 上应用 SVD 将产生三个矩阵,U奇异值分解V,大小分别为,比如说,m x rr x rr x n。在这里,U代表用户潜在特征向量表示,V代表项目潜在特征向量表示,奇异值分解代表用户和项目的独立特征表示,r。通过将独立特征表示的值设置为小于rk,我们选择了 k 个最优的潜在特征,从而减少了矩阵的大小。k 值可以通过交叉验证方法选择,因为k的值定义了模型的性能。

注意

选择值k的一个更简单的方法是奇异值分解,即取一个包含奇异值的对角矩阵,选择对角线上具有更高值的值,并消除非常小的对角线值。

在选择 k 值之后,我们现在调整或选择矩阵U奇异值分解V中的第一 k 列的大小。这一步将使矩阵U奇异值分解V分别变为m x kk x kk x n的大小,请参考下面的图片。调整矩阵大小后,我们继续进行最后一步。

在最后一步中,我们将计算以下一系列矩阵的点积,以计算近似评分矩阵 奇异值分解

奇异值分解

以下代码片段展示了 R 中 SVD 的实现,下面的代码创建了一个样本矩阵,然后使用 r 中基础包中的svd()函数对样本数据进行 SVD,创建 3 个矩阵,三个矩阵的点积将得到我们的近似原始矩阵。

sampleMat <- function(n) { i <- 1:n; 1 / outer(i - 1, i, "+") } 
original.mat <- sampleMat(9)[, 1:6] 
(s <- svd(original.mat)) 
D <- diag(s$d) 
#  X = U D V' 
s$u %*% D %*% t(s$v) 

注意

请参阅第七章,使用 Spark 构建实时推荐引擎,了解 Spark-python 中 ALS 的实现。

机器学习技术

在本节中,我们将学习最重要的或最常用的机器学习技术,这些技术在构建推荐引擎中得到了广泛应用。

线性回归

线性回归可能被视为解决预测问题的简单、流行和最基本的方法。我们使用线性回归,我们的目标是预测给定输入特征的未来结果,输出标签是一个连续变量。

在线性回归中,给定历史输入和输出数据,模型将试图找出独立特征变量和由以下方程和图表给出的依赖输出变量之间的关系:

线性回归

在这里,y 代表输出连续的依赖变量,x 代表独立特征变量,β0β1 是未知数或特征权重,e 代表误差。

使用普通最小二乘法 (OLS),我们将估计前面方程中的未知数。我们不会深入探讨线性回归方法,但在这里我们将讨论如何在推荐引擎中使用线性回归。

推荐引擎中的核心任务之一是为用户预测未评分的项目。例如,在基于项目的推荐引擎中,用户 u 对项目 i 的预测是通过计算用户 u 对与项目 i 类似的项目的评分总和来完成的。然后,每个评分都通过其相似性值进行加权:

线性回归

我们可以不用这种加权平均方法来做出预测,而是可以使用线性回归方法来计算用户 u 对项目 i 的偏好值。在使用回归方法时,我们不是使用相似项目的原始评分值,而是使用基于线性回归模型的近似评分值。例如,为了预测用户 u 对项目 i 的评分,我们可以使用以下方程:

线性回归

使用 R 进行线性回归的代码如下:

library(MASS) 
data("Boston") 
set.seed(0) 
which_train <- sample(x = c(TRUE, FALSE), size = nrow(Boston), 
                      replace = TRUE, prob = c(0.8, 0.2)) 
train <- Boston[which_train, ] 
test <- Boston[!which_train, ] 
lm.fit =lm(medv~. ,data=train ) 
summary(lm.fit) 

Call: 
lm(formula = medv ~ ., data = train) 

Residuals: 
     Min       1Q   Median       3Q      Max  
-15.2631  -2.7614  -0.5243   1.7867  24.6306  

Coefficients: 
              Estimate Std. Error t value Pr(>|t|)     
(Intercept)  39.549376   5.814446   6.802 3.82e-11 *** 
crim         -0.090720   0.040872  -2.220  0.02701 *   
zn            0.050080   0.015307   3.272  0.00116 **  
indus         0.032339   0.070343   0.460  0.64596     
chas          2.451235   0.992848   2.469  0.01397 *   
nox         -18.517205   4.407645  -4.201 3.28e-05 *** 
rm            3.480574   0.469970   7.406 7.91e-13 *** 
age           0.012625   0.015786   0.800  0.42434     
dis          -1.470081   0.223349  -6.582 1.48e-10 *** 
rad           0.322494   0.077050   4.186 3.51e-05 *** 
tax          -0.012839   0.004339  -2.959  0.00327 **  
ptratio      -0.972700   0.148454  -6.552 1.77e-10 *** 
black         0.008399   0.003153   2.663  0.00805 **  
lstat        -0.592906   0.058214 -10.185  < 2e-16 *** 
--- 
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1 

Residual standard error: 4.92 on 396 degrees of freedom 
Multiple R-squared:  0.7321,     Adjusted R-squared:  0.7233  
F-statistic: 83.26 on 13 and 396 DF,  p-value: < 2.2e-16 

#predict new values 
pred = predict(lm.fit,test[,-14]) 

R 中的 stats 包提供的 lm() 函数通常用于拟合线性回归模型。

分类模型

分类模型属于监督学习形式的机器学习类别。这些模型通常用于预测问题,其中响应是二元或多类标签。在本章中,我们将讨论许多类型的分类模型,例如逻辑回归、KNN 分类、SVM、决策树、随机森林、bagging 和 boosting。分类模型在推荐系统中起着非常关键的作用。尽管分类模型在邻域方法中不起很大作用,但它们在构建个性化推荐、上下文感知系统和混合推荐器中起着非常重要的作用。此外,我们可以将分类模型应用于关于推荐的反馈信息,这可以进一步用于计算用户特征的权重。

线性分类

逻辑回归是分类模型中最常见的。逻辑回归也被称为线性分类,因为它与线性回归非常相似,只是在回归中,输出标签是连续的,而在线性分类中,输出标签是类别变量。在回归中,模型是最小二乘函数,而在逻辑回归中,预测模型是以下方程给出的 logit 函数:

线性分类

在前面的方程中,e 是自然对数,x 是输入变量,β[0] 是截距,β[1] 是变量 x 的权重。

我们可以将前面的方程解释为响应变量相对于输入变量线性组合的条件概率。logit 函数允许将任何连续变量转换为范围在(0,1)之间的响应,如下面的图所示:

线性分类

以下是用 R 实现的逻辑回归:

set.seed(1) 
x1 = rnorm(1000)           # sample continuous variables  
x2 = rnorm(1000) 
z = 1 + 4*x1 + 3*x2        # data creation 
pr = 1/(1+exp(-z))         # applying logit function 
y = rbinom(1000,1,pr)      # bernoulli response variable 

  #now feed it to glm: 
df = data.frame(y=y,x1=x1,x2=x2)   
glm( y~x1+x2,data=df,family="binomial") 

R 中的glm()函数用于拟合广义线性模型,通常用于分类问题。

KNN 分类

k 近邻分类通常被称为 KNN 分类。这是最受欢迎的分类技术之一。KNN 分类的基本概念是算法考虑围绕特定数据点的 k 个最近邻项,并尝试根据其 k 个最近邻数据点将该数据点分类到输出标签之一。与逻辑回归、SVM 或其他任何分类算法等其他分类技术不同,KNN 分类是一个非参数模型,不涉及任何参数估计。KNN 中的 k 是考虑的最近邻的数量:

KNN 分类

考虑 10 个数据点。我们需要将一个测试数据点(如前图所示)分类到两个类别之一,蓝色或橙色。在这个例子中,我们使用 KNN 分类来分类测试数据点。假设 k 是 4;这意味着通过考虑围绕活动数据点的四个数据点,我们需要通过以下步骤来分类它:

  • 作为第一步,我们需要计算每个点与测试数据点的距离。

  • 识别与测试数据点最近的四个数据点。

  • 使用投票机制,将多数类标签计数分配给测试数据点。

KNN 分类在高度非线性问题中表现良好。尽管这种方法在大多数情况下都表现良好,但作为一种非参数方法,这种方法不能找到特征重要性或权重。

与 KNN 分类类似,还有一个回归版本的 KNN,可以用来预测连续输出标签。

KNN 分类和回归方法在协同过滤推荐系统中都有广泛的应用。

以下代码片段展示了使用 R 的 KNN 分类,在下面的代码片段中,我们使用caret包中的knn3()来拟合 KNN 分类,以及使用dplyr包中的sample_n()来从数据框中选择随机行。

data("iris") 
library(dplyr) 
iris2 = sample_n(iris, 150) 
train = iris2[1:120,] 
test = iris2[121:150,] 
cl = train$Species 
library(caret) 
fit <- knn3(Species~., data=train, k=3) 
predictions <- predict(fit, test[,-5], type="class") 
table(predictions, test$Species) 

支持向量机

支持向量机算法是一种用于解决分类问题的监督学习算法。SVM 通常被视为处理分类问题的最佳算法之一。给定一组训练示例,其中每个数据点属于两个类别之一,SVM 训练算法构建一个模型,将新数据点分配到其中一个类别。该模型是将示例表示为空间中的点,映射得使不同类别的示例通过尽可能宽的间隔分开,如图所示。然后,将新示例映射到相同的空间,并根据它们落在间隔的哪一侧预测它们属于哪个类别。在本节中,我们将概述 SVM 的实现,而不涉及数学细节。

当 SVM 应用于 p 维数据集时,数据被映射到 p-1 维超平面,算法在类别之间找到具有足够间隔的清晰边界。与其他也创建用于分类数据点的分离边界的分类算法不同,SVM 试图选择一个具有最大间隔的边界来分离类别,如图所示:

支持向量机

考虑一个具有两个类别的二维数据集,如图所示。现在,当应用 SVM 算法时,首先检查是否存在一个一维超平面来映射所有数据点。如果超平面存在,线性分类器创建一个具有间隔的决定边界来分离类别。在前面的图中,粗红色线是决定边界,较细的蓝色和红色线是每个类别与边界之间的间隔。当使用新的测试数据预测类别时,新数据将落入两个类别之一。

以下是一些需要注意的关键点:

  • 虽然可以创建无限多个超平面,但支持向量机(SVM)只选择具有最大间隔的一个超平面,即离训练观察结果最远的分离超平面。

  • 这个分类器仅依赖于位于超平面边缘的数据点,即在图中的细边缘,而不是数据集中的其他观察结果。这些点被称为支持向量。

  • 决策边界仅受支持向量的影响,而不受远离边界的其他观察结果的影响,也就是说,如果我们改变除了支持向量之外的数据点,决策边界不会有任何影响,但如果支持向量改变,决策边界会发生变化。

  • 训练数据上的大间隔也会在测试数据上产生大间隔,以便正确分类测试数据。

  • 支持向量机在非线性数据集上也表现良好。在这种情况下,我们使用径向核函数。

以下为在iris数据集上实现 SVM 的 R 代码。我们使用e1071包来运行 SVM。在 R 中,SVM()函数包含了e1071包中存在的支持向量机实现。

注意

交叉验证方法用于在测试未来未见数据之前评估预测模型的准确性。

我们可以看到,SVM 方法使用tune()方法调用,该方法执行交叉验证并在不同的成本参数值上运行模型:

library(e1071) 
data(iris) 
sample = iris[sample(nrow(iris)),] 
train = sample[1:105,] 
test = sample[106:150,] 
tune =tune(svm,Species~.,data=train,kernel ="radial",scale=FALSE,ranges =list(cost=c(0.001,0.01,0.1,1,5,10,100))) 
tune$best.model 

Call: 
best.tune(method = svm, train.x = Species ~ ., data = train, ranges = list(cost = c(0.001,  
    0.01, 0.1, 1, 5, 10, 100)), kernel = "radial", scale = FALSE) 

Parameters: 
   SVM-Type:  C-classification  
 SVM-Kernel:  radial  
       cost:  10  
      gamma:  0.25  

Number of Support Vectors:  25 

summary(tune) 

Parameter tuning of 'svm': 
- sampling method: 10-fold cross validation  
- best parameters: 
 cost 
   10 
- best performance: 0.02909091  
- Detailed performance results: 
   cost      error dispersion 
1 1e-03 0.72909091 0.20358585 
2 1e-02 0.72909091 0.20358585 
3 1e-01 0.04636364 0.08891242 
4 1e+00 0.04818182 0.06653568 
5 5e+00 0.03818182 0.06538717 
6 1e+01 0.02909091 0.04690612 
7 1e+02 0.07636364 0.08679584 

cost =10 is chosen from summary result of tune variable 
model =svm(Species~.,data=train,kernel ="radial",cost=10,scale=FALSE) 

tune$best.model告诉我们,模型在成本参数为10和总支持向量数为25时表现最佳:

pred = predict(model,test) 

决策树

决策树是一种简单、快速且基于树的监督学习算法,用于解决分类问题。虽然与其它逻辑回归方法相比,其准确性不高,但在处理推荐系统时,这个算法非常有用。

让我们通过一个例子来定义决策树。想象一下,你必须根据花瓣长度、花瓣宽度、萼片长度和萼片宽度等特征来预测花的类别。我们将决策树方法应用于解决这个问题:

  1. 我们在算法开始时考虑整个数据集。

  2. 现在我们选择一个合适的问题/变量来将数据分成两部分。在我们的例子中,我们选择根据花瓣长度>2.45 和<=2.45 来划分数据。这把花类setosa与其他类别分开。

  3. 我们进一步根据花瓣长度>2.45 的数据进行划分,基于与花瓣长度<4.5 和>=4.5 相同的变量,如下所示。

  4. 这种数据分割将进一步细分,直到我们达到所有底部点代表响应变量或无法在数据上进一步进行逻辑分割的点。

在以下决策树图中,我们有一个根节点,四个发生数据分割的内部节点,五个无法进一步分割数据的终端节点,它们如下定义:

  • 花瓣长度<2.5 作为根节点

  • 花瓣长度<2.5、花瓣长度<4.85、萼片长度<5.15 和花瓣宽度<1.75 被称为内部节点

  • 具有花朵类别的最终节点被称为终端节点

  • 连接节点的线条被称为树的分支

当使用上述构建的模型预测新数据上的响应时,每个新数据点都会通过每个节点,提出一个问题,并采取逻辑路径到达其逻辑类别:

决策树

查看使用 CRAN 上可用的 tree 包在iris数据集上实现的决策树 R 代码。

下一个模式总结的概述告诉我们,误分类率为 0.0381,这表明模型非常准确:

library(tree) 
data(iris) 
sample = iris[sample(nrow(iris)),] 
train = sample[1:105,] 
test = sample[106:150,] 
model = tree(Species~.,train) 
summary(model) 

决策树

以下代码显示了绘制决策树:

plot(model) #plot trees 
text(model) #apply text 

以下代码显示了决策树模型:

决策树

pred = predict(model,test[,-5],type="class") 

以下图像显示了使用pred()方法做出的预测值:

决策树

集成方法

在数据挖掘中,我们使用集成方法,这指的是使用多个学习算法来获得比在任何一个统计问题中应用任何单个学习算法更好的预测结果。本节讨论了流行的集成方法概述,如 Bagging、Boosting 和随机森林。

随机森林

随机森林指的是一种改进的监督算法,虽然基于类似的方法,但不同于自助聚合或 Bagging 方法。与 Bagging 中从使用自助技术生成的所有 B 个样本中选择所有变量不同,我们只为每个 B 个样本从总变量中随机选择几个预测变量,然后使用这些样本进行训练。预测是通过平均每个模型的预测结果来进行的。每个样本中的预测变量数量是通过公式随机森林决定的,其中p是原始数据集中的总变量数。

注意

此方法消除了数据集中强预测变量的依赖性条件,因为我们故意选择比每个迭代的所有变量更少的变量。

  • 此方法还可以消除变量之间的相关性,从而在模型中减少变异性,因此更加可靠。

查看以下使用 CRAN 上可用的randomForest包在鸢尾花数据集上实现的随机森林的 R 实现:

library(randomForest) 
data(iris) 
sample = iris[sample(nrow(iris)),] 
train = sample[1:105,] 
test = sample[106:150,] 
model =randomForest(Species~.,data=train,mtry=2,importance =TRUE,proximity=TRUE) 

以下图像将显示上述构建的随机森林模型的详细信息:

随机森林

pred = predict(model,newdata=test[,-5]) 

随机森林

Bagging

Bagging也称为自助聚合。它旨在提高机器学习算法的稳定性和准确性。它有助于避免过拟合并减少方差。这通常与决策树一起使用。

Bagging 涉及从数据集中随机生成自助样本,随机替换样本,并单独训练模型。然后通过汇总或平均所有响应变量来做出预测。

例如,考虑一个数据集(Xi, Yi),其中i=1 ...n,包含 n 个数据点。以下是对此数据集进行 Bagging 的步骤:

  • 现在从原始数据集中使用自助法随机选择 B 个样本进行替换。

  • 接下来,独立地对 B 个样本进行回归/分类模型的训练,并在测试集上通过平均所有生成的 B 个模型的响应来做出预测(在回归的情况下)或在分类的情况下从 B 个样本中选择最常出现的类别。

Boosting

与在 bagging 中创建多个 bootstrap 样本副本并针对每个数据集副本拟合新模型,然后将所有单个模型组合以创建单个预测模型不同,在 boosting 中,每个新模型都是使用先前构建的模型的信息构建的。Boosting 可以理解为涉及两个步骤的迭代方法:

  1. 新模型是基于先前模型的残差而不是响应变量构建的。

  2. 现在从该模型计算残差并更新为前一步中使用的残差。

前面的两个步骤重复多次迭代,允许每个新模型从其先前的错误中学习,从而提高模型精度。

以下代码片段展示了使用 R 的梯度提升,gbm()包在 R 中通常用于执行各种回归任务:

library(gbm) 
data(iris) 
sample = iris[sample(nrow(iris)),] 
train = sample[1:105,] 
test = sample[106:150,] 
model = gbm(Species~.,data=train,distribution="multinomial",n.trees=5000,interaction.depth=4) 
summary(model) 

Boosting

以下图像以可视方式显示了模型摘要,显示了每个特征的相对重要性:

Boosting

前面的总结说明了模型变量的相对重要性。

pred = predict(model,newdata=test[,-5],n.trees=5000) 

Boosting

从结果pred矩阵中选择概率最高的响应,通过在预测的向量输出上执行apply(pred, 1, which.max)

p.pred <- apply(pred,1,which.max) 

Boosting

在前面的代码片段中,predict()函数的输出值用于apply()函数中,以从预测矩阵的每一行中选择概率最高的响应,apply()函数的结果输出是响应变量的预测。

聚类技术

聚类分析是将对象分组的过程,使得同一组中的对象比其他组中的对象更相似。

例如,识别和分组在旅行门户上有相似预订活动的客户,如图所示。

在前面的例子中,每个组被称为,簇中的每个成员(数据点)的行为与其组成员相似:

聚类技术

聚类分析是一种无监督学习方法。在监督方法,如回归分析中,我们有输入变量和响应变量;我们将统计模型拟合到输入变量以预测响应变量,而在无监督学习方法中,我们没有响应变量来预测;我们只有输入变量。我们不是将模型拟合到输入变量以预测响应变量,而是试图在数据集中找到模式。有三种流行的聚类算法:层次聚类分析、k-means 聚类分析和两步聚类分析。在本节中,我们将学习 k-means 聚类。

K-means 聚类

K-means 是一种无监督的迭代算法,其中 k 是从数据中形成的簇的数量。聚类分为两个步骤,如下所示:

  • 簇分配步骤:在这个步骤中,我们随机选择两个簇点(红色点 & 绿色点)并将每个数据点分配给两个簇点中的一个,无论哪个更接近它(查看以下图像的顶部部分)。

  • 移动质心步骤:在这个步骤中,我们取每个组中所有示例点的平均值,并将质心移动到新位置,即计算出的平均位置(查看以下图像的底部部分)。

重复前面的步骤,直到所有数据点都被分组到两个组中,并且在移动质心步骤结束时数据点的均值没有变化:

K-means 聚类

之前的图显示了聚类算法如何在数据上工作以形成簇。请查看以下使用鸢尾花数据集的 R 实现 k-means 聚类的代码。

使用 R 进行 k-means 聚类的代码如下:

library(cluster) 
data(iris) 
iris$Species = as.numeric(iris$Species) 
kmeans<- kmeans(x=iris, centers=5) 
clusplot(iris,kmeans$cluster, color=TRUE, shade=TRUE,labels=13, lines=0) 

cluster包中可用的Clustplot()方法用于绘制 IRIS 数据集形成的簇,如图所示:

K-means 聚类

之前的图显示了鸢尾花数据上的簇形成,簇占数据量的 95%。在之前的例子中,簇的数量 k 值是通过肘部方法选择的。

以下代码片段解释了 k-means 聚类的实现,如图所示:

library(cluster) 
library(ggplot2) 
data(iris) 
iris$Species = as.numeric(iris$Species) 
cost_df <- data.frame() 
for(i in 1:100){ 
kmeans<- kmeans(x=iris, centers=i, iter.max=50) 
cost_df<- rbind(cost_df, cbind(i, kmeans$tot.withinss)) 
} 
names(cost_df) <- c("cluster", "cost") 
#Elbow method to identify the idle number of Cluster 
#Cost plot 
ggplot(data=cost_df, aes(x=cluster, y=cost, group=1)) + 
theme_bw(base_family="Garamond") + 
geom_line(colour = "darkgreen") + 
theme(text = element_text(size=20)) + 
ggtitle("Reduction In Cost For Values of 'k'\n") + 
xlab("\nClusters") + ylab("Within-Cluster Sum of Squares\n") 

K-means 聚类

从之前的图中,我们可以观察到成本函数的方向在簇数为 5 时发生变化,因此我们选择 5 作为我们的簇数 k。由于最佳簇数是在图表的肘部找到的,所以我们称之为肘部方法

维度降低

在构建推荐系统时,最常见的问题之一是高维和稀疏数据。很多时候,我们会遇到一个特征集很大而数据点很少的情况。在这种情况下,当我们将模型拟合到数据集时,模型的预测能力会降低。这种情况通常被称为维度诅咒。一般来说,增加数据点或减少特征空间,也称为降维,通常可以减少维度诅咒的影响。在本节中,我们将讨论主成分分析,这是一种流行的降维技术,用于减少维度诅咒的影响。

主成分分析

主成分分析PCA)是一种经典的降维统计技术。PCA 算法将高维空间中的数据转换到低维空间。该算法将 m 维输入空间线性转换到 n 维(n<m)输出空间,目标是通过丢弃(m-n)维来最小化丢失的信息/方差。PCA 允许我们丢弃具有较小方差的变量/特征。

从技术上讲,PCA 使用高度相关的变量的正交投影到一组称为主成分的线性不相关变量的值。主成分的数量小于或等于原始变量的数量。这种线性变换被定义为,第一个主成分具有最大的方差,即通过考虑高度相关的特征,尽可能多地解释数据中的变异性,而每个后续组件则通过使用与第一个主成分较少相关的特征来具有最高的方差,该主成分与前一个组件正交。

让我们用简单的话来理解这个概念。假设我们有一个三维数据空间,其中有两个特征比第三个特征更相关。现在我们想使用 PCA 将数据减少到二维空间。

第一个主成分是以一种方式创建的,它使用数据中的两个相关变量来解释最大的方差。在以下图中,第一个主成分(较粗的线)沿着数据解释了大部分方差。为了选择第二个主成分,我们需要选择另一条具有最高方差、不相关且与第一个主成分正交的线。PCA 的实现和技术细节超出了本书的范围,因此我们将讨论如何在 R 中使用它。

以下图像解释了主成分的空间表示:

主成分分析

我们使用USArrests数据集来说明 PCA。USArrests数据集包含与犯罪相关的统计数据,如AssaultMurderRapeUrbanPop(每 10 万人)在美国 50 个州的统计数据。

R 中的 PCA 实现如下:

data(USArrests) 
head(states) 
[1] "Alabama"    "Alaska"     "Arizona"    "Arkansas"   "California" "Colorado"  

names(USArrests) 
[1] "Murder"   "Assault"  "UrbanPop" "Rape"  

让我们使用apply()函数对USArrests数据集按行应用,以计算方差,看看每个变量是如何变化的:

apply(USArrests , 2, var) 

Murder    Assault   UrbanPop       Rape  
  18.97047 6945.16571  209.51878   87.72916  

我们观察到Assault的方差最大。在此需要注意的是,在应用 PCA 时对特征进行缩放是一个非常关键的步骤。

在缩放特征后应用 PCA,如下所示:

pca =prcomp(USArrests , scale =TRUE) 

pca 
Standard deviations: 
[1] 1.5748783 0.9948694 0.5971291 0.4164494 

Rotation: 
                PC1        PC2        PC3         PC4 
Murder   -0.5358995  0.4181809 -0.3412327  0.64922780 
Assault  -0.5831836  0.1879856 -0.2681484 -0.74340748 
UrbanPop -0.2781909 -0.8728062 -0.3780158  0.13387773 
Rape     -0.5434321 -0.1673186  0.8177779  0.08902432 

现在,让我们了解 PCA 输出的组成部分:

names(pca) 
[1] "sdev"     "rotation" "center"   "scale"    "x" 

Pca$rotation包含主成分载荷矩阵,它解释了每个变量在每个主成分上的比例。

现在,让我们学习如何使用双图来解释 PCA 的结果。双图用于显示每个变量沿两个主成分的比例。

以下代码改变了双图的方向;如果我们不包括以下两行,则图将是以下图的镜像:

pca$rotation=-pca$rotation 
pca$x=-pca$x 
biplot (pca , scale =0) 

以下图像显示了数据集的主成分图:

主成分分析

在之前的图中,称为双图,我们可以看到USArrests数据集的两个主成分(PC1,PC2)。红色箭头代表加载向量,它显示了特征空间如何沿着主成分向量变化。

从图中,我们观察到第一个主成分向量,PC1,大致对三个特征:强奸、攻击和谋杀给予相等的权重。这意味着这三个特征彼此之间比与 UrbanPop 特征更相关。第二个主成分,PC2,比 UrbanPop 给予更多的权重,并且与剩余的三个特征的相关性较低。

向量空间模型

向量空间模型是代数模型,在文本分析应用中最常用于使用单词作为向量来表示文本文档。这在信息检索应用中得到了广泛的应用。在文本分析中,假设我们想要找到两个句子之间的相似度。我们如何着手呢?我们知道,为了计算相似度度量指标,数据应该是全部数值的。当涉及到一个句子时,我们有的只是单词而不是数字。向量空间模型允许我们将句子中的单词以数值形式表示,这样我们就可以应用任何相似度计算指标,如余弦相似度。

这种以数值形式表示句子中单词的表示方法可以有两种流行的方式:

  • 词频

  • 词频逆文档频率

让我们用一个例子来理解之前提到的方法:

  • 句子 1:THE CAT CHASES RAT.

  • 句子 2:THE DOG CHASES CAT.

  • 句子 3:THE MAN WALKS ON MAT.

给定三个句子,我们的目标是找到句子之间的相似度。很明显,我们不能直接应用如余弦相似度这样的相似度度量。所以现在让我们学习如何用数值格式来表示它们。

注意

作为一般记号,向量空间模型中的每个句子都被称为文档

词频

词频简单来说就是单词在文档中的频率。为了找到频率,我们需要执行以下步骤:

  1. 第一步是找到所有文档中存在的所有唯一关键词,表示为 V:

    V =

  2. 下一步是创建文档向量,如下所示:

    D1 = {THE, CAT, CHASES, RAT}

    D2 = {THE, DOG, CHASES, CAT}

    D3 =

  3. 在这一步,我们必须计算每个文档中所有术语的词频:

    D1 = {(THE,1),(CAT,1),(CHASES,1),(RAT,1)}

    D2 = {(THE,1),(DOG,1),(CHASES,1),(CAT,1)}

    D3 =

  4. 现在,我们将创建一个以文档 ID 为行,唯一术语为列,以词频为单元格值的词-文档矩阵,如下所示:词频

花点时间理解这里发生的事情:我们在句子中出现单词的地方放置了 1,而在单词没有出现的地方放置了 0。

现在观察,我们已经使用每个文档中的词频将我们的文档表示为数值矩阵。

现在,在这个称为 TDM 的词矩阵文档中,我们也可以直接应用如余弦相似度这样的相似性度量。

词频

之前的图示展示了计算余弦角后文档之间的相似性。从图中,我们可以推断出 D1 和 D2 之间的角度较小,而 D1 和 D3 之间的角度较大,这表明 D1 比 D3 更接近 D2。

词频逆文档频率

之前的方法也被称为词袋方法,其中我们只需找到每个文档中每个词的频率,并在 TDM 中以数值形式表示它。但是,这种方法本质上存在一个缺陷。这种方法给出现频率较高的词更多的权重或重要性,而给出现频率较低的词较少的重要性。重要的是要理解,如果一个词在大多数文档集中出现频率较高,那么这个词在识别文档时不会作为区分因素。同样,一个在文档中出现频率较高而在整个文档集中出现频率较低的词将有助于识别特定文档。通过在文档集中降低频繁出现词的权重,并在文档中提高频繁出现词的权重(但在整个文档集中出现频率较低),可以使用词频逆文档频率tf-idf)来实现这种权重的调整。

tf-idf可以计算为文档词频与词的逆文档频率的乘积:

tf-idf = tf X idf

这里,idf的定义如下:

idf = log(D/(1+ n(d,t)))

这里,D是文档集的总数,n(d,t)是词t在所有文档中出现的次数。

让我们用tf-idf来计算之前的一组文档(D1D2D3)的 TDM:

  1. 将 TDM 应用于每个文档中每个词的词频计算。这与我们在 TF 部分所做的是一样的:词频逆文档频率

  2. 在这一步,我们需要计算文档频率DF),即一个词在所有文档集中出现的次数。例如,让我们计算词 THE 的 DF。该词出现在所有三个文档中,因此其 DF 将为 3。同样,对于词 CAT,DF 为 2:词频逆文档频率

  3. 在这一步,我们将使用上述 IDF 公式计算文档频率的倒数(IDF)。

    • 因此,对于词 THE,idf 将如下计算:idf(THE) = log(3/(1+3)) = -0.12494

    词频逆文档频率

  4. 计算 tf-idf,即将整个文档集中每个术语的 tf 和 idf 相乘,如下所示:

    • 例如,对于 D1 中的术语 RAT,tf-idf 的计算方式为1 X 0.4777121 = 0.4777121

    术语频率逆文档频率

现在我们已经计算了tf-idf,我们可以比较基于tf-df的先前 TDM 与基于tf的 TDM。我们可以得出的主要比较是 TDM 中每个术语的权重差异。在整个文档集中出现频率较高的单词与文档中很少出现的单词相比,权重较低。

现在,基于tf-idf表示的 TDM 之上,我们可以直接应用相似度度量。

在本节中,我们学习了向量空间模型以及tftf-idf概念,这些在文本分析中得到了广泛应用。现在真正的问题是:我们如何在推荐引擎中应用这些技术?

在构建基于内容的推荐引擎时,很多时候我们会以文本格式获取用户偏好数据或项目特征。在这种情况下,我们可能能够应用上述技术将文本数据表示为数值向量。

同时,在构建基于内容的个性化推荐引擎时,我们很多时候需要找到特征重要性或特征权重,以对项目特征进行评估。在这种情况下,向量空间模型的概念非常有用。

以下代码片段展示了如何在 R 中计算tfidf。在代码中,我们使用TermDocumentMatrix()weightTFidf()来计算术语文档矩阵和tfidf,这些功能都包含在 R 的tm包中。使用inspect()方法来获取结果:

library(tm) 
data(crude) 
tdm <- TermDocumentMatrix(crude,control=list(weighting =   function(x) weightTfIdf(x, normalize =TRUE), stopwords = TRUE)) 
inspect(tdm) 

以下截图仅显示了大量文档术语中的一小部分:

术语频率逆文档频率

评估技术

在前面的章节中,我们看到了在推荐系统中使用的各种数据挖掘技术。在本节中,我们将学习如何评估使用数据挖掘技术构建的模型。任何数据分析模型的最终目标是在未来的数据上表现良好。只有当我们构建一个在开发阶段既高效又健壮的模型时,才能实现这一目标。

在评估任何模型时,我们需要考虑的最重要的事情如下:

  • 模型是否过拟合或欠拟合

  • 模型与未来数据或测试数据拟合的程度如何

欠拟合,也称为偏差,是指模型甚至在训练数据上表现不佳的情况;这意味着我们在数据上拟合了一个不那么健壮的模型;例如,让数据呈非线性分布,并用线性模型拟合它。从以下图像中,我们看到数据是非线性分布的。假设我们拟合了一个线性模型(橙色线)。在这种情况下,在模型构建阶段本身,预测能力就会很低。

过拟合是一种模型在训练数据上表现良好,但在测试数据上表现极差的场景。这种场景发生在模型记住数据模式而不是从数据中学习时;例如,让数据以非线性分布,并拟合一个复杂模型(绿色线)。在这种情况下,我们观察到模型非常接近数据分布进行拟合,注意每一个起伏。在这种情况下,模型最有可能在之前未见过的数据上失败:

评估技术

前面的图显示了简单、复杂和适当拟合的模型训练数据。绿色拟合代表过拟合,橙色线代表欠拟合,黑色和蓝色线代表适当的模型,这是欠拟合和过拟合之间的权衡。

任何拟合模型都通过交叉验证、正则化、剪枝、模型比较、ROC 曲线、混淆矩阵等方法进行评估,以避免上述场景。

交叉验证

这是一种几乎适用于所有模型的模型评估的非常流行的技术。在这个技术中,我们将原始数据分成多个折叠/集合(比如说 5 个)的训练数据集和测试数据集。在每个折叠迭代中,使用训练数据集构建模型,并使用测试数据集进行评估。这个过程对所有折叠重复进行。计算每个迭代的测试误差。计算平均测试误差以概括所有迭代结束时的模型准确度。

交叉验证的实现方法在第五章《构建协同过滤推荐引擎》中解释。

正则化

在这个技术中,数据变量被惩罚以降低模型的复杂性,目的是最小化成本函数。有两种最流行的正则化技术:岭回归和 Lasso 回归。在这两种技术中,我们试图将变量系数减少到零,这样更少的变量将能够最优地拟合数据。

推荐引擎的流行评估指标如下:

  • 均方根误差 (RMSE)

  • 均方绝对误差 (MAE)

  • 精确率和召回率

均方根误差 (RMSE)

均方根误差是寻找模型准确度最流行、最常用、最简单的度量之一。在一般意义上,它是实际值和预测值之间的差异。根据定义,它是均方误差的平方根,如下面的公式所示:

均方根误差 (RMSE)

在这里,X[act] 指的是观测值,而 X[pred] 指的是预测值。

RMSE 如何适用于推荐引擎?

推荐引擎中的一个核心任务是预测特定用户的未评分物品的偏好值。我们使用前面章节中讨论的许多方法来预测这些未评分的偏好值。考虑以下用于构建推荐模型的评分矩阵。假设我们的推荐引擎模型已经预测了以下图中所有空单元格的值,用 r hat 表示。同时假设我们知道这些预测空单元格的实际值;用 r 表示。

均方根误差 (RMSE)

现在将前面方程中的r hatr的值用于计算推荐引擎预测能力的模型准确度。

平均绝对误差 (MAE)

对于数据挖掘模型,另一种流行的评估技术是平均绝对误差MAE)。这个评估指标与 RMSE 非常相似,其公式如下:

平均绝对误差 (MAE)

这是一个非常简单的度量,它是预测值和实际值之间平均误差的计算。MAE 在推荐引擎中作为一种评估模型的方式被应用。

精确率和召回率

在我们将推荐引擎部署到生产环境中后,我们只对用户是否接受建议的推荐感兴趣。我们如何衡量推荐引擎在模型是否生成有效推荐方面的有效性?为了衡量有效性,我们可以借用精确率-召回率评估技术,这是一种在评估分类模型时流行的技术。关于是否为用户提供有价值的推荐的前面讨论可以被视为分类模型的二元标签,然后我们可以计算精确率-召回率。

要理解精确率-召回率,我们应该了解一些与精确率-召回率一起使用的更多指标,例如真正例、真反例、假正例和真反例。

要构建通常所说的混淆矩阵,如下所示,让我们以一个包含 50 个网页的在线新闻推荐网站为例。

假设我们为用户 A 生成了 35 条推荐。在这些推荐中,A 点击了 25 个建议的网页,有 10 个网页未被点击。现在有了这些信息,我们创建了一个包含点击次数的表格,如下所示:

  • 在右上方的列中,输入 A 点击的建议链接数量

  • 在右上方的列中,输入 A 未点击的建议链接数量

  • 在左下方的列中,输入 A 点击但未被推荐的链接数量

  • 在右下方的列中,输入 A 未点击且未被推荐的链接数量:精确率和召回率

  • 左上方的计数称为真正例tf),它表示实际响应为正且模型预测为正的所有响应的计数

  • 顶右边的计数称为假阳性fp),表示所有实际响应为负但模型预测为正的响应计数,换句话说,是一个虚假警报

  • 底左边的计数称为假阴性fn),表示所有实际响应为正但模型预测为负的响应计数;通常我们称之为A MISS

  • 底右边的计数称为真阴性tn),表示所有实际响应为负且模型预测为负的响应计数。

看一下下面的表格:

精度和召回率

使用前面的信息,我们将计算精度-召回率指标,如下所示:

精度和召回率

精度是通过真阳性除以真阳性和假阳性的总和来计算的。精度表示总推荐中有多少百分比是有用的。

召回率是通过真阳性除以真阳性和假阴性的总和来计算的。召回率表示在总推荐中有多少百分比是有用的。

在评估推荐模型时,精度和召回率都是必需的。有时我们可能对生成具有高精度的良好推荐感兴趣,而有时我们可能对生成具有高召回率的推荐感兴趣。但这两个指标的问题是,如果我们专注于提高一个指标,另一个指标就会受到影响。我们需要根据我们的需求在精度和召回率之间选择一个最佳权衡。精度-召回率的实现将在第五章构建协同过滤推荐引擎中介绍。

摘要

在本章中,我们了解了在构建推荐引擎中常用的各种数据挖掘步骤。我们首先学习了相似度计算,例如欧几里得距离度量,然后是数学模型,例如矩阵分解技术。接着,我们涵盖了监督学习和无监督机器学习技术,如回归、分类、聚类技术和降维技术。在章节的最后几节,我们讨论了如何将自然语言处理中的信息检索方法,如向量空间模型,用于推荐引擎。我们通过介绍流行的评估指标来结束本章。到目前为止,我们已经涵盖了构建推荐引擎所需的理论背景。在下一章,我们将学习如何在 R 和 Python 中构建协同过滤推荐引擎。

第五章:构建协同过滤推荐引擎

在本章中,我们学习如何使用流行的数据分析编程语言 R 和 Python 实现协同过滤推荐系统。我们将学习如何在 R 和 Python 编程语言中实现基于用户的协同过滤和基于物品的协同过滤。

在本章中,我们将学习以下内容:

  • 我们将在本章中使用 Jester5k 数据集

  • 探索数据集和理解数据

  • R 和 Python 中可用的推荐引擎包/库

  • 在 R 中构建基于用户的协同过滤

  • 在 R 环境中构建基于物品的协同过滤

  • 在 Python 中构建基于用户的协同过滤

  • 在 Python 中构建基于物品的协同过滤

  • 评估模型

recommenderlab,R 包是一个用于开发和使用包括基于用户的协同过滤、基于物品的协同过滤、SVD 和基于关联规则的算法在内的推荐算法的框架,这些算法用于构建推荐引擎。此包还提供了基本的基础设施或机制来开发我们自己的推荐引擎方法。

在 RStudio 中安装 recommenderlab 包

以下代码片段将安装recommenderlab包到 RStudio 中,如果尚未安装:

if(!"recommenderlab" %in% rownames(installed.packages())){ 
install.packages("recommenderlab")} 

首先,r 环境检查是否有任何先前安装的 recommender lab 包,如果没有找到,则按以下方式安装:

Loading required package: recommenderlab 
Error in .requirePackage(package) :  
  unable to find required package 'recommenderlab' 
In addition: Warning message: 
In library(package, lib.loc = lib.loc, character.only = TRUE, logical.return = TRUE,  : 
  there is no package called 'recommenderlab' 
Loading required package: recommenderlab 
install.packages("recommenderlab") 
Installing package into 'path to installation folder/R/win-library/3.2' 
(as 'lib' is unspecified) 
trying URL 'https://cran.rstudio.com/bin/windows/contrib/3.2/recommenderlab_0.2-0.zip' 
Content type 'application/zip' length 1405353 bytes (1.3 MB) 
downloaded 1.3 MB 
package 'recommenderlab' successfully unpacked and MD5 sums checked 

以下代码片段使用library()recommenderlab包加载到 r 环境中:

library(recommenderlab) 

Loading required package: Matrix 
Loading required package: arules 

Attaching package: 'arules' 

The following objects are masked from 'package:base': 

    abbreviate, write 

Loading required package: proxy 

Attaching package: 'proxy' 

The following object is masked from 'package:Matrix': 

    as.matrix 

The following objects are masked from 'package:stats': 

    as.dist, dist 

The following object is masked from 'package:base': 

    as.matrix 

Loading required package: registry 

要使用帮助函数获取recommenderlab包的帮助,请在 Rstudio 中运行以下命令:

help(package = "recommenderlab") 

通过点击提供的链接检查帮助页面以获取有关包使用的详细信息:

在 RStudio 中安装 recommenderlab 包

recommenderlab 包中可用的数据集

与 R 中可用的任何其他包一样,recommenderlab也附带默认数据集。运行以下命令以显示可用的包:

data_package <- data(package = "recommenderlab") 
data_package$results[,c("Item","Title")] 

recommenderlab 包中可用的数据集

在所有可用的数据集中,我们选择使用Jester5k数据集来实现使用 R 的基于用户的协同过滤和基于物品的协同过滤推荐引擎。

探索 Jester5K 数据集

在本节中,我们将按以下方式探讨Jester5K数据集:

描述

该数据集包含来自 Jester 在线笑话推荐系统匿名评分数据的 5000 个用户样本,收集时间介于 1999 年 4 月到 2003 年 5 月之间。

使用

data(Jester5k) 

格式

Jester5k的格式是:Formal class 'realRatingMatrix' [package "recommenderlab"]

JesterJokes的格式是一个字符字符串向量。

详细信息

Jester5k包含一个5000 x 100的评分矩阵(5000 个用户和 100 个笑话),评分介于-10.00 到+10.00 之间。所有选定的用户都评分了 36 个或更多的笑话。

数据还包含 JesterJokes 中的实际笑话。

实际评分矩阵中存在的评分数量表示如下:

nratings(Jester5k) 

[1] 362106 

Jester5k 
5000 x 100 rating matrix of class 'realRatingMatrix' with 362106 ratings. 

您可以通过运行以下命令来显示评分矩阵的类别:

class(Jester5k) 
[1] "realRatingMatrix" 
attr(,"package") 
[1] "recommenderlab" 

recommenderlab 包以紧凑的方式高效地存储评分信息。通常,评分矩阵是稀疏矩阵。因此,realRatingMatrix 类支持稀疏矩阵的紧凑存储。

让我们比较 Jester5k 与相应的 R 矩阵的大小,以了解实际评分矩阵的优势,如下所示:

object.size(Jester5k) 
4633560 bytes 
#convert the real-rating matrix into R matrix 
object.size(as(Jester5k,"matrix")) 
4286048 bytes 
object.size(as(Jester5k, "matrix"))/object.size(Jester5k) 
0.925001079083901 bytes 

我们观察到实际评分矩阵存储的空间比 R 矩阵少 0.92 倍。对于基于内存的协同过滤方法,这些方法是内存中的模型,在生成推荐时将所有数据加载到内存中,因此高效地存储数据非常重要。recommenderlab 包有效地完成了这项工作。

The recommenderlab 包暴露了许多可以通过评分矩阵对象操作的功能。运行以下命令以查看可用方法:

methods(class = class(Jester5k)) 

详细信息

运行以下命令以查看 recommenderlab 包中可用的推荐算法:

names(recommender_models) 

详细信息

以下代码片段显示的结果与上一张图片相同,lapply() 函数将函数应用于列表的所有元素,在我们的案例中,对于 recommender_models 对象中的每个项目,lapply 将提取描述并按以下方式显示结果:

lapply(recommender_models, "[[", "description") 
$IBCF_realRatingMatrix 
[1] "Recommender based on item-based collaborative filtering (real data)." 

$POPULAR_realRatingMatrix 
[1] "Recommender based on item popularity (real data)." 

$RANDOM_realRatingMatrix 
[1] "Produce random recommendations (real ratings)." 

$RERECOMMEND_realRatingMatrix 
[1] "Re-recommends highly rated items (real ratings)." 

$SVD_realRatingMatrix 
[1] "Recommender based on SVD approximation with column-mean imputation (real data)." 

$SVDF_realRatingMatrix 
[1] "Recommender based on Funk SVD with gradient descend (real data)." 

$UBCF_realRatingMatrix 
[1] "Recommender based on user-based collaborative filtering (real data)." 

探索数据集

在本节中,让我们更详细地探索数据。要查找数据的维度和数据类型,请运行以下命令:

5000 个用户和 100 个项目:

dim(Jester5k) 

[1] 5000  100 

数据是 R 矩阵:

class(Jester5k@data) 

[1] "dgCMatrix" 
attr(,"package") 
[1] "Matrix" 

探索评分值

以下代码片段将帮助我们了解评分值的分布:

评分分布如下:

hist(getRatings(Jester5k), main="Distribution of ratings") 

探索评分值

上一张图片显示了 Jester5K 数据集中可用的评分的频率。我们可以观察到负评分大致呈均匀分布或相同的频率,而正评分频率较高,并向图表的右侧递减。这可能是用户给出的评分引入的偏差所致。

使用 recommenderlab 构建基于用户的协同过滤

运行以下代码以将 recommenderlab 库和数据加载到 R 环境中:

library(recommenderlab) 
data("Jester5k") 

让我们查看前六个用户在第一个 10 个笑话上的样本评分数据。运行以下命令:

head(as(Jester5k,"matrix")[,1:10]) 

使用 recommenderlab 构建基于用户的协同过滤

我们在上一节中已经探讨了数据探索,因此我们将直接进入构建基于用户的协同推荐系统。

本节分为以下几部分:

  • 通过将数据分为 80%的训练数据和 20%的测试数据来构建基准推荐模型。

  • 使用 k 折交叉验证方法评估推荐模型

  • 调整推荐模型的参数

准备训练数据和测试数据

为了构建和评估推荐模型,我们需要训练数据和测试数据。运行以下命令来创建它们:

我们使用种子函数来生成可重复的结果:

set.seed(1) 
which_train <- sample(x = c(TRUE, FALSE), size = nrow(Jester5k),replace = TRUE, prob = c(0.8, 0.2)) 
head(which_train) 
[1]  TRUE  TRUE  TRUE  TRUE FALSE  TRUE 

之前的代码创建了一个与用户数量相等的逻辑对象。真实的索引将是训练集的一部分,而假的索引将是测试集的一部分。

rec_data_train <- Jester5k[which_train, ] 
rec_data_test <- Jester5k[!which_train, ] 

dim(rec_data_train) 
[1] 4004  100 

dim(rec_data_test) 
[1] 996  100 

创建一个基于用户的协作模型

现在,让我们在Jester5k的全部数据上创建一个推荐模型。在此之前,让我们探索recommenderlab包中可用的推荐模型及其参数,如下所示:

recommender_models <- recommenderRegistry$get_entries(dataType = "realRatingMatrix") 

recommender_models 

创建基于用户的协作模型

我们刚才看到的图像显示了 6 种不同的推荐模型及其参数。

运行以下代码来构建基于用户的协同过滤模型:

recc_model <- Recommender(data = rec_data_train, method = "UBCF") 
recc_model 

Recommender of type 'UBCF' for 'realRatingMatrix'  
learned using 4004 users. 
recc_model@model$data 

4004 x 100 rating matrix of class 'realRatingMatrix' with 289640 ratings. 
Normalized using center on rows. 

recc_model@model$data对象包含评分矩阵。这是因为 UBCF 是一种懒学习技术,这意味着它需要访问所有数据来进行预测。

在测试集上的预测

现在我们已经构建了模型,让我们在测试集上预测推荐。为此,我们将使用库中可用的predict()函数。我们为每个用户生成 10 个推荐。请参阅以下代码以获取预测结果:

n_recommended <- 10 
recc_predicted <- predict(object = recc_model,newdata = rec_data_test, n = n_recommended) 
recc_predicted 
Recommendations as 'topNList' with n = 10 for 996 users.  

#Let's define list of predicted recommendations: 
rec_list <- sapply(recc_predicted@items, function(x){ 
  colnames(Jester5k)[x] 
}) 

以下代码给出的结果是列表类型:

class(rec_list) 
[1] "list" 

前两个推荐如下:

rec_list [1:2] 
$u21505 
 [1] "j81"  "j73"  "j83"  "j75"  "j100" "j80"  "j72"  "j95"  "j87"  "j96"  

$u5809 
 [1] "j97" "j93" "j76" "j78" "j77" "j85" "j89" "j98" "j91" "j80" 

我们可以观察到,对于用户u21505,前 10 个推荐是j81, j73, j83, ... j96

以下图像显示了四个用户的推荐:

在测试集上的预测

让我们运行以下代码来查看为所有测试用户生成了多少推荐:

number_of_items = sort(unlist(lapply(rec_list, length)),decreasing = TRUE) 
table(number_of_items) 

0   1   2   3   4   5   6   7   8   9  10  
286   3   2   3   3   1   1   1   2   3 691  

从上述结果中,我们看到对于286个用户,没有生成任何推荐。原因是他们已经对原始数据集中的所有电影进行了评分。对于691个用户,每个用户生成了 10 个评分,原因是他们在原始数据集中没有对任何电影进行评分。其他收到 2、3、4 等推荐的用户意味着他们推荐的电影非常少。

分析数据集

在我们评估模型之前,让我们退一步分析数据。通过分析所有用户对笑话给出的评分数量,我们可以观察到有1422个人对所有的100个笑话进行了评分,这似乎很不寻常,因为很少有人对 80 到 99 个笑话进行了评分。进一步分析笑话,我们发现,有221364312131个用户分别对71727374个笑话进行了评分,这与其他笑话评分相比似乎很不寻常。

运行以下代码以提取每个笑话获得的评分数量:

table(rowCounts(Jester5k)) 

分析数据集

在下一步中,让我们删除评分了 80 个或更多笑话的用户的记录。

model_data = Jester5k[rowCounts(Jester5k) < 80] 
dim(model_data) 
[1] 3261  100 

维度已从5000减少到3261条记录。

现在让我们分析每个用户给出的平均评分。箱线图显示了笑话评分的平均分布。

boxplot(model_data) 

分析数据集

前面的图像显示,很少有评分偏离正常行为。从前面的图像中我们可以看到,平均评分在 7(大约)以上和-5(大约)以下的是一些异常值,数量较少。让我们通过运行以下代码来查看计数:

boxplot(rowMeans(model_data [rowMeans(model_data)>=-5 & rowMeans(model_data)<= 7])) 

分析数据集

删除给出了非常低平均评分和非常高平均评分的用户。

model_data = model_data [rowMeans(model_data)>=-5 & rowMeans(model_data)<= 7] 
dim(model_data) 
[1] 3163  100 

让我们按照以下方式检查数据中前 100 个用户的评分分布:

image(model_data, main = "Rating distribution of 100 users") 

分析数据集

使用 k 交叉验证评估推荐模型

recommenderlab包提供了一个使用evaluationScheme()函数评估模型的框架。根据 Cran 网站的定义,evaluationScheme 可以从数据集创建一个 evaluationScheme 对象。方案可以是简单的训练和测试数据分割,k 折交叉验证或使用 k 个独立的自助样本。

以下为evaluationScheme()函数的参数:

使用 k 交叉验证评估推荐模型

我们使用交叉验证方法来分割数据,例如,5 折交叉验证方法将训练数据分成五个更小的集合,其中四个集合用于训练模型,剩下的一个集合用于评估模型。让我们定义以下参数:最小良好评分、交叉验证方法的折数和分割方法:

items_to_keep <- 30 
rating_threshold <- 3 
n_fold <- 5 # 5-fold  
eval_sets <- evaluationScheme(data = model_data, method = "cross-validation",train = percentage_training, given = items_to_keep, goodRating = rating_threshold, k = n_fold) 

Evaluation scheme with 30 items given 
Method: 'cross-validation' with 5 run(s). 
Good ratings: >=3.000000 
Data set: 3163 x 100 rating matrix of class 'realRatingMatrix' with 186086 ratings. 

让我们按照以下方式检查由交叉验证方法形成的五个集合的大小:

size_sets <- sapply(eval_sets@runsTrain, length) 
 size_sets 
[1] 2528 2528 2528 2528 2528 

为了提取集合,我们需要使用getData()。有三个集合:

  • train:这是训练集

  • known:这是测试集,用于构建推荐的项目

  • unknown:这是测试集,用于测试推荐的项目

让我们看一下以下代码中的训练集:

getData(eval_sets, "train") 
2528 x 100 rating matrix of class 'realRatingMatrix' with 149308 ratings. 

评估基于用户的协同过滤

现在让我们评估模型,让我们将model_to_evaluate参数设置为基于用户的协同过滤,并将model_parameters设置为NULL以使用默认设置,如下所示:

model_to_evaluate <- "UBCF" 
model_parameters <- NULL 

下一步是使用recommender()函数构建推荐模型,如下所示:

eval_recommender <- Recommender(data = getData(eval_sets, "train"),method = model_to_evaluate, parameter = model_parameters) 

Recommender of type 'UBCF' for 'realRatingMatrix'  
learned using 2528 users 

我们已经看到,基于用户的推荐模型已经使用2528个用户的训练数据学习。现在我们可以预测eval_sets中的已知评分,并使用前面描述的未知集来评估结果。

在对已知评分进行预测之前,我们必须设置要推荐的物品数量。接下来,我们必须将测试集提供给predict()函数进行预测。评分的预测是通过运行以下命令完成的:

items_to_recommend <- 10 
eval_prediction <- predict(object = eval_recommender, newdata =getData(eval_sets, "known"), n = items_to_recommend, type = "ratings") 

eval_prediction 
635 x 100 rating matrix of class 'realRatingMatrix' with 44450 ratings 

执行predict()函数将花费时间,因为基于用户的协同过滤方法是基于内存的、在运行时实现的懒惰学习技术,以表明在预测过程中整个数据集被加载。

现在我们将使用未知集评估预测,并使用精确率、召回率和 F1 度量等指标来估计模型精度。运行以下代码通过调用calcPredictionAccuracy()方法来计算模型精度指标:

eval_accuracy <- calcPredictionAccuracy(  x = eval_prediction, data = getData(eval_sets, "unknown"), byUser = TRUE) 
head(eval_accuracy) 
           RMSE       MSE      MAE 
u17322 4.536747 20.582076 3.700842 
u13610 4.609735 21.249655 4.117302 
u5462  4.581905 20.993858 3.714604 
u1143  2.178512  4.745912 1.850230 
u5021  2.664819  7.101260 1.988018 
u21146 2.858657  8.171922 2.194978 

通过设置byUser = TRUE,我们正在计算每个用户的模型精度。取平均值将给出整体精度,如下所示:

apply(eval_accuracy,2,mean) 
     RMSE       MSE       MAE  
 4.098122 18.779567  3.377653  

通过设置byUser=FALSE,在先前的calcPredictionAccuracy()中,我们可以计算由以下给出的整体模型精度:

eval_accuracy <- calcPredictionAccuracy(  x = eval_prediction, data = getData(eval_sets, "unknown"), byUser = 
    FALSE) 

eval_accuracy 
    RMSE       MSE       MAE  
 4.372435 19.118191  3.431580  

在先前的方法中,我们使用均方根误差RMSE)和平均绝对误差MAE)来评估模型精度,但我们也可以使用精确率/召回率来评估模型精度。为此,我们使用evaluate()函数,然后使用evaluate()方法的结果创建一个包含精确率/召回率/f1 度量的混淆矩阵,如下所示:

results <- evaluate(x = eval_sets, method = model_to_evaluate, n = seq(10, 100, 10)) 

评估基于用户的协同过滤

head(getConfusionMatrix(results)[[1]]) 

         TP        FP        FN        TN precision    recall       TPR        FPR 
10  6.63622  3.363780 10.714961 49.285039 0.6636220 0.4490838 0.4490838 0.05848556 
20 10.03150  9.968504  7.319685 42.680315 0.5015748 0.6142384 0.6142384 0.17854766 
30 11.20787 18.792126  6.143307 33.856693 0.3735958 0.6714050 0.6714050 0.34877101 
40 11.91181 28.088189  5.439370 24.560630 0.2977953 0.7106378 0.7106378 0.53041204 
50 12.96850 37.031496  4.382677 15.617323 0.2593701 0.7679658 0.7679658 0.70444585 
60 14.82362 45.176378  2.527559  7.472441 0.2470604 0.8567522 0.8567522 0.85919995 

前四列包含真实/假阳性/阴性,如下所示:

  • 真阳性TP):这些是被正确评分的推荐项目

  • 假阳性FP):这些是没有被评分的推荐项目

  • 假阴性FN):这些是不推荐的、已经被评分的项目

  • 真阴性TN):这些是没有被推荐的、没有被评分的项目

一个完美(或过度拟合)的模型将只有TPTN

如果我们想同时考虑所有分割,我们可以将索引相加,如下所示:

columns_to_sum <- c("TP", "FP", "FN", "TN") 
indices_summed <- Reduce("+", getConfusionMatrix(results))[, columns_to_sum] 
head(indices_summed) 
         TP        FP       FN        TN 
10 32.59528  17.40472 53.22520 246.77480 
20 49.55276  50.44724 36.26772 213.73228 
30 55.60787  94.39213 30.21260 169.78740 
40 59.04724 140.95276 26.77323 123.22677 
50 64.22205 185.77795 21.59843  78.40157 
60 73.67717 226.32283 12.14331  37.85669 

由于通过上述表格很难总结模型,我们可以使用 ROC 曲线来评估模型。使用plot()来构建 ROC 图,如下所示:

plot(results, annotate = TRUE, main = "ROC curve") 

评估基于用户的协同过滤

前面的图表显示了真阳性率TPR)和假阳性率FPR)之间的关系,但我们必须选择这样的值,以便在 TPR 和 FPR 之间进行权衡。在我们的案例中,我们观察到nn=30是一个非常好的权衡点,因为当我们考虑 30 个邻居时,TPR 接近0.7,FPR 是0.4,当移动到nn=40时,TPR 仍然接近0.7,但 FPR 已经变为0.4。这意味着假阳性率已经增加。

构建基于物品的推荐模型

与 UBCF 一样,我们使用相同的 Jester5k 数据集作为基于物品的推荐系统。在本节中,我们不会探索数据,因为我们已经在上一节中这样做过了。我们首先移除那些对所有物品都进行了评分的用户数据,以及那些评分超过 80 的记录,如下所示:

library(recommenderlab) 
data("Jester5k") 
model_data = Jester5k[rowCounts(Jester5k) < 80] 
model_data 
[1] 3261  100 

现在让我们看看每个用户的平均评分是如何分布的:

boxplot(rowMeans(model_data)) 

构建基于物品的推荐模型

以下代码片段计算了每个用户给出的平均评分,并识别了给出极端评分的用户——要么是极高的评分,要么是极低的评分:

从以下结果中,我们可以观察到有 19 条记录的平均评分非常高,而与大多数用户相比,有 79 条记录的评分非常低:

dim(model_data[rowMeans(model_data) < -5]) 
[1]  79 100 
dim(model_data[rowMeans(model_data) > 7]) 
[1]  19 100 

在总共 3261 条记录中,只有 98 条记录的平均评分远低于平均值,远高于平均值,因此我们将这些记录从数据集中移除,如下所示:

model_data = model_data [rowMeans(model_data)>=-5 & rowMeans(model_data)<= 7] 
model_data 
[1] 3163  100 

从这里,我们将章节划分为以下几部分:

  • 使用训练数据和测试数据构建 IBCF 推荐模型。

  • 评估模型

  • 参数调整

构建 IBCF 推荐模型

构建任何推荐模型的第一个步骤是准备训练数据。之前,我们已经通过移除异常数据来准备了构建模型所需的数据。现在运行以下代码将可用数据分为两个集合:80% 的训练集和 20% 的测试集。我们使用训练数据构建推荐模型并在测试集上生成推荐。

以下代码首先创建了一个与原始数据集长度相同的逻辑对象,其中包含 80% 的元素为 TRUE,20% 为测试:

which_train <- sample(x = c(TRUE, FALSE), size = nrow(model_data), 
 replace = TRUE, prob = c(0.8, 0.2)) 
class(which_train) 
[1] "logical" 
head(which_train) 
[1] TRUE TRUE TRUE TRUE TRUE TRUE 

然后,我们使用 model_data 中的逻辑对象生成训练集,如下所示:

 model_data_train <- model_data[which_train, ] 
dim(model_data_train) 
[1] 2506  100 

然后,我们使用 model_data 中的逻辑对象生成测试集,如下所示:

 model_data_test <- model_data[!which_train, ] 
 dim(model_data_test) 
[1] 657 100 

现在我们已经准备好了训练集和测试集,让我们训练模型并在测试集上生成顶级推荐。

对于模型构建,如 UBCF 部分所述,我们使用 recommenderlab 包中可用的相同的 recommender() 函数。运行以下代码使用训练数据训练模型。

recommender() 函数设置参数。我们将模型设置为评估 "IBCF" 并设置 k=30k 是在计算相似度值时需要考虑的邻居数量,如下所示:

model_to_evaluate <- "IBCF" 

model_parameters <- list(k = 30) 

以下代码片段展示了使用 recommender() 函数及其输入参数(如输入数据、评估参数的模型和 k 参数)构建推荐引擎模型:

model_recommender <- Recommender(data = model_data_train,method = model_to_evaluate, parameter = model_parameters) 

IBCF 模型对象被创建为 model_recommender。该模型使用我们之前创建的 2506 条训练集进行训练和学习,如下所示:

model_recommender 
Recommender of type 'IBCF' for 'realRatingMatrix'  learned using 2506 users. 

现在我们已经创建了模型,让我们来探索一下模型。我们使用 recommenderlab 中的 getModel() 函数提取模型细节,如下所示:

构建 IBCF 推荐模型

从上述结果中,需要注意的重要参数是k值、默认相似度值和方法,即余弦相似度

最后一步是在测试集上生成推荐。在测试集上运行以下代码以生成推荐。

items_to_recommend是设置每个用户要生成的推荐数量的参数:

items_to_recommend <- 10 

调用 reocommenderlab 包中可用的predict()方法来预测测试集中的未知项:

model_prediction <- predict(object = model_recommender, newdata = model_data_test, n = items_to_recommend) 

model_prediction 
Recommendations as 'topNList' with n = 10 for 657 users.  

print(class(model_prediction)) 
[1] "topNList" 
attr(,"package") 
[1] "recommenderlab" 

我们可以使用slotNames()方法获取预测对象的槽位详情:

slotNames(model_prediction) 
[1] "items"      "itemLabels" "n"          

让我们看看测试集中第一个用户的预测结果:

 model_prediction@items[[1]] 
 [1]  89  76  72  87  93 100  97  80  94  86 

让我们在每个预测中添加项目标签:

 recc_user_1  = model_prediction@items[[1]] 

 jokes_user_1 <- model_prediction@itemLabels[recc_user_1] 

 jokes_user_1 
 [1] "j89"  "j76"  "j72"  "j87"  "j93"  "j100" "j97"  "j80"  "j94"  "j86"  

模型评估

在我们生成预测之前,让我们退一步来评估推荐器模型。正如我们在 UBCF 中看到的,我们可以使用可用的evaluationScheme()方法。我们使用交叉验证设置来生成训练集和测试集。然后我们在每个测试集上做出预测并评估模型准确性。

运行以下代码以生成训练集和测试集。

n_fold定义了 4 折交叉验证,将数据分为 4 个集合;3 个训练集和 1 个测试集:

n_fold <- 4 

items_to_keep定义了用于生成推荐的最低项目数量:

items_to_keep <- 15 

rating_threshold定义了被认为是良好评分的最小评分:

rating_threshold <- 3 

evaluationScheme方法创建测试集:

eval_sets <- evaluationScheme(data = model_data, method = "cross-validation",k = n_fold, given = items_to_keep, goodRating =rating_threshold) 
size_sets <- sapply(eval_sets@runsTrain, length) 
size_sets 
[1] 2370 2370 2370 2370 

model_to_evaluate设置为设置要使用的推荐器方法。model_parameters定义了模型参数,例如在计算余弦相似度时考虑的邻居数量。目前我们将它设置为NULL,以便模型选择默认值,如下所示:

model_to_evaluate <- "IBCF" 
model_parameters <- NULL 

使用recommender()方法生成模型。让我们了解recommender()方法的每个参数:

getDataeval_sets中提取训练数据,并将其按如下方式传递给recommender()方法:

getData(eval_sets,"train") 
2370 x 100 rating matrix of class 'realRatingMatrix' with 139148 ratings 

由于我们使用 4 折交叉验证,recommender()方法使用eval_sets中的三个集合进行训练,剩余的一个集合用于测试/评估模型,如下所示:

eval_recommender <- Recommender(data = getData(eval_sets, "train"),method = model_to_evaluate, parameter = model_parameters) 
#setting the number of items to be set for recommendations 
items_to_recommend <- 10 

现在我们使用构建的模型在eval_sets的“已知”数据集上做出预测。如前所述,我们使用predict()方法生成预测,如下所示:

eval_prediction <- predict(object = eval_recommender, newdata = getData(eval_sets, "known"), n = items_to_recommend, type = "ratings") 

class(eval_prediction) 
[1] "realRatingMatrix" 
attr(,"package") 
[1] "recommenderlab" 

使用指标评估模型准确性

到目前为止,该过程与制作初始预测的过程相同,现在我们将看到如何评估在eval_sets的“已知”测试数据集上做出的模型准确性。正如我们在 UBCF 部分所看到的,我们使用calcPredictionAccuracy()方法来计算预测准确性。

我们使用calcPredictionAccuracy()方法,并将eval_sets中可用的"unknown"数据集作为如下所示:

eval_accuracy <- calcPredictionAccuracy(x = eval_prediction, data = getData(eval_sets, "unknown"), byUser = TRUE) 

head(eval_accuracy)
           RMSE      MSE      MAE 
u238   4.625542 21.39564  4.257505 
u17322  4.953789  24.54003  3.893797 
u5462  4.685714   21.95591  4.093891 
u13120   4.977421  24.77472  4.261627 
u12519   3.875182  15.01703  2.750987 
u17883 7.660785 58.68762 6.595489 

注意

在前面的方法中使用byUser = TRUE计算每个用户的准确度。在上面的表中,我们可以看到对于用户u238RMSE4.62MAE4.25

如果我们想看到整个模型的准确度,只需计算每列的平均值,即所有用户的平均值,如下所示:

apply(eval_accuracy,2,mean) 
  RMSE      MSE      MAE  
 4.45511 21.94246  3.56437  

通过设置byUser=FALSE,我们可以计算整个模型的模型准确度:

eval_accuracy <- calcPredictionAccuracy(x = eval_prediction, data = getData(eval_sets, "unknown"), byUser = FALSE)

eval_accuracy 
     RMSE       MSE       MAE  
 4.672386 21.831190  3.555721  

使用图表展示模型准确度

现在我们可以看到使用 Precision-Recall、ROC 曲线和精度/召回曲线来展示模型准确度。这些曲线帮助我们决定在为推荐模型选择参数时 Precision-Recall 之间的权衡,在我们的案例中是 IBCF。

我们使用evaluate()方法,然后设置 n 值,该值定义了在计算项目之间的相似度时最近邻的数量,如下所示:

运行以下评估方法使模型对每个数据集运行四次:

results <- evaluate(x = eval_sets, method = model_to_evaluate, n = seq(10,100,10)) 
IBCF run fold/sample [model time/prediction time] 
 1  [0.145sec/0.327sec]  
 2  [0.139sec/0.32sec]  
 3  [0.139sec/0.32sec]  
 4  [0.137sec/0.322sec]  

让我们看看每个折的模型准确度:

results@results[1]

使用图表展示模型准确度

使用以下代码汇总所有 4 折结果:

columns_to_sum <- c("TP", "FP", "FN", "TN","precision","recall") 
indices_summed <- Reduce("+", getConfusionMatrix(results))[, columns_to_sum] 

使用图表展示模型准确度

从前一个表中,我们可以观察到,当 n 值为 30 和 40 时,模型准确率和 Precision-Recall 值都很好。同样,可以使用 ROC 曲线和 Precision-Recall 图直观地得出相同的结果,如下所示:

plot(results, annotate = TRUE, main = "ROC curve") 

使用图表展示模型准确度

plot(results, "prec/rec", annotate = TRUE, main = "Precision-recall") 

使用图表展示模型准确度

IBCF 的参数调整

在构建 IBCF 模型时,在生成最终模型推荐之前,我们可以选择一些最优值:

  • 我们必须选择计算项目之间相似度时最优的邻居数量

  • 要使用的相似度方法,无论是余弦还是 Pearson 方法

查看以下步骤:

首先设置不同的 k 值:

vector_k <- c(5, 10, 20, 30, 40)

使用lapply生成使用余弦方法和不同 k 值的模型:

 model1 <- lapply(vector_k, function(k,l){ list(name = "IBCF", param = list(method = "cosine", k = k)) })
names(model1) <- paste0("IBCF_cos_k_", vector_k)
names(model1) [1] "IBCF_cos_k_5" "IBCF_cos_k_10" "IBCF_cos_k_20" "IBCF_cos_k_30" [5] "IBCF_cos_k_40" #use Pearson method for similarities model2 <- lapply(vector_k, function(k,l){ list(name = "IBCF", param = list(method = "pearson", k = k)) })
names(model2) <- paste0("IBCF_pea_k_", vector_k)
names(model2) [1] "IBCF_pea_k_5" "IBCF_pea_k_10" "IBCF_pea_k_20" "IBCF_pea_k_30" [5] "IBCF_pea_k_40" 
#now let's combine all the methods:
models = append(model1,model2)

IBCF 的参数调整

设置要生成的推荐总数:

n_recommendations <- c(1, 5, seq(10, 100, 10))

调用评估方法到构建 4 折方法:

 list_results <- evaluate(x = eval_sets, method = models, n= n_recommendations)
IBCF run fold/sample [model time/prediction time] 1 [0.139sec/0.311sec] 2 [0.143sec/0.309sec] 3 [0.141sec/0.306sec] 4 [0.153sec/0.312sec]
IBCF run fold/sample [model time/prediction time] 1 [0.141sec/0.326sec] 2 [0.145sec/0.445sec] 3 [0.147sec/0.387sec] 4 [0.133sec/0.439sec]
IBCF run fold/sample [model time/prediction time] 1 [0.14sec/0.332sec] 2 [0.16sec/0.327sec] 3 [0.139sec/0.331sec] 4 [0.138sec/0.339sec] IBCF run fold/sample [model time/prediction time] 1 [0.139sec/0.341sec] 2 [0.157sec/0.324sec] 3 [0.144sec/0.327sec] 4 [0.133sec/0.326sec]

现在我们已经得到了结果,让我们绘制并选择最优参数,如下所示:

plot(list_results, annotate = c(1,2), legend = "topleft")  
title("ROC curve") 

IBCF 的参数调整

从前面的图中,最佳方法是使用余弦相似度的 IBCF,n 值为 30,其次是使用 Pearson 方法的 n 值为 40。

让我们用以下Precision-Recall曲线来确认这一点:

plot(list_results, "prec/rec", annotate = 1, legend = "bottomright") 
title("Precision-recall") 

IBCF 的参数调整

从上面的图中我们可以看到,当推荐数量=30 且使用余弦相似度和 n=40 时,实现了最佳的 Precision-Recall 比率。另一个好的模型是通过使用 Pearson 相似度方法和 n=10 实现的。

使用 Python 进行协同过滤

在上一节中,我们看到了使用 R 包 recommenderlab 实现基于用户的推荐系统和基于物品的推荐系统的实现。在本节中,我们将看到使用 Python 编程语言实现的 UBCF 和 IBCF 实现。

对于本节,我们使用 MovieLens 100k 数据集,其中包含 943 个用户对 1682 部电影的评分。与 R 不同,在 Python 中我们没有专门用于构建推荐引擎的 Python 包,至少没有基于邻居的推荐器,如基于用户/基于物品的推荐器。

我们有 Crab Python 包可用,但它没有得到积极支持。因此,我想使用 Python 中的科学包(如 NumPy、sklearn 和 Pandas)构建推荐引擎。

安装所需的包

对于本节,请确保您满足以下系统要求:

  • Python 3.5

  • Pandas 1.9.2 - Pandas 是一个开源的、BSD 许可的库,为 Python 编程语言提供高性能、易于使用的数据结构和数据分析工具。

  • NumPy 1.9.2 - NumPy 是 Python 科学计算的基础包。

  • sklearn 0.16.1

小贴士

安装前面提到的最佳方式是安装 Anaconda 发行版,这将安装所有所需的包,如 Python、Pandas 和 Numpy。Anaconda 可以在 www.continuum.io/downloads 找到。

数据来源

MovieLens 100k 数据可以从以下链接下载:

files.grouplens.org/datasets/movielens/ml-100k.zip

让我们从实现基于用户的协同过滤开始。假设我们已经将数据下载到本地系统,让我们将数据加载到 Python 环境中。

我们使用 Pandas 包和 read_csv() 方法通过传递两个参数,路径和分隔符来加载数据,如下所示:

path = "~/udata.csv"
df = pd.read_csv(path, sep='\t')

数据将以 DataFrame 的形式加载,这是一种类似于表格的数据结构,可以轻松用于数据处理和操作任务。

type(df) 
<class 'pandas.core.frame.DataFrame'> 

让我们通过使用 Pandas DataFrame 对象中可用的 head() 方法查看数据框的前六个结果,以了解数据似乎是如何使用的:

df.head() 
 UserID  ItemId   Rating  Timestamp 
0     196      242       3  881250949 
1     186      302       3  891717742 
2      22      377       1  878887116 
3     244       51       2  880606923 
4     166      346       1  886397596 

让我们使用 columns 属性查看数据框 df 的列名。以下代码片段的结果显示有四个列:UserIDItemIdRatingTimestamp,并且它是对象数据类型:

df.columns 
Index([u'UserID', u'ItemId ', u'Rating', u'Timestamp'], dtype='object') 

让我们通过调用 shape 属性来查看数据框的大小;我们观察到我们有 100k 条记录,4 列:

df.shape 
(100000, 4) 

数据探索

在本节中,我们将探索 MovieLens 数据集,并准备使用 Python 构建协同过滤推荐引擎所需的数据。

让我们通过以下代码片段查看评分的分布:

import matplotlib.pyplot as plt 
plt.hist(df['Rating']) 

从以下图像中我们可以看到,有更多电影获得了 4 星评级:

数据探索

使用以下代码片段,我们将通过在 DataFrame 上应用 groupby() 函数和 count() 函数来查看评分的计数:

数据探索

以下代码片段显示了电影观看的分布。在以下代码中,我们在 DataFrame 上应用 count() 函数:

plt.hist(df.groupby(['ItemId'])['ItemId'].count()) 

数据探索

从前面的图像中,我们可以观察到起始 ItemId 的评分比后来的电影多。

评分矩阵表示

现在我们已经探索了数据,让我们以评分矩阵的形式表示数据,这样我们就可以开始我们的原始任务,即构建推荐引擎。

为了创建评分矩阵,我们利用 NumPy 包的功能,如数组和矩阵中的行迭代。运行以下代码以将数据框表示为评分矩阵:

注意

在以下代码中,我们首先提取所有唯一的用户 ID,然后使用形状参数检查长度。

创建一个名为 n_users 的变量来查找数据中的总唯一用户数:

n_users = df.UserID.unique().shape[0] 

创建一个变量 n_items 来查找数据中的总唯一电影数:

n_items = df['ItemId '].unique().shape[0] 

打印唯一用户和电影的计数:

print(str(n_users) + ' users') 
943 users 

print(str(n_items) + ' movies') 
1682 movies 

创建一个大小为 (n_users x n_items) 的零值矩阵来存储矩阵中的评分:

ratings = np.zeros((n_users, n_items)) 

对于 DataFrame 中的每个元组,df 从行的每一列中提取信息,并将其存储在评分矩阵的单元格值中,如下所示:

for  row in df.itertuples(): 
ratings[row[1]-1, row[2]-1] = row[3] 

运行循环,整个 DataFrame 电影评分信息将按如下方式存储在 numpy.ndarray 类型的矩阵 ratings 中:

type(ratings) 
<type 'numpy.ndarray'> 

现在,让我们使用形状属性查看多维数组 'ratings' 的尺寸:

ratings.shape 
(943, 1682) 

让我们通过运行以下代码来查看评分多维数组的样本数据:

ratings 
array([[ 5.,  3.,  4., ...,  0.,  0.,  0.], 
       [ 4.,  0.,  0., ...,  0.,  0.,  0.], 
       [ 0.,  0.,  0., ...,  0.,  0.,  0.], 
       ...,  
       [ 5.,  0.,  0., ...,  0.,  0.,  0.], 
       [ 0.,  0.,  0., ...,  0.,  0.,  0.], 
       [ 0.,  5.,  0., ...,  0.,  0.,  0.]]) 

我们观察到评分矩阵是稀疏的,因为数据中有许多零。让我们通过运行以下代码来确定数据中的 sparsity

sparsity = float(len(ratings.nonzero()[0])) 
sparsity /= (ratings.shape[0] * ratings.shape[1]) 
sparsity *= 100 
print('Sparsity: {:4.2f}%'.format(sparsity)) 
Sparsity: 6.30% 

我们观察到稀疏度为 6.3%,也就是说,我们只有 6.3% 的数据有评分信息,其余的只是零。另外请注意,评分矩阵中我们看到的 0 值并不代表用户给出的评分,它只是表示它们是空的。

创建训练集和测试集

现在我们有了评分矩阵,让我们创建一个训练集和一个测试集,使用训练集构建推荐模型,并使用测试集评估模型。

为了将数据分为训练集和测试集,我们使用 sklearn 包的功能。运行以下代码以创建训练集和测试集:

使用以下导入功能将 train_test_split 模块加载到 Python 环境中:

from sklearn.cross_validation import train_test_split

使用 train_test_split() 方法,测试集大小为 0.33,随机种子为 42

 ratings_train, ratings_test = train_test_split(ratings,test_size=0.33, random_state=42)

让我们看看火车模型的尺寸:

 ratings_train.shape (631, 1682) 
#let's see the dimensions of the test set 
ratings_test.shape (312, 1682)

对于基于用户的协同过滤,我们预测一个用户对项目的评分是所有其他用户对该项目的评分的加权平均,其中权重是每个用户与输入用户之间的余弦相似度。

构建 UBCF 的步骤

构建 UBCF 的步骤如下:

  • 在用户之间创建相似度矩阵。

  • 通过计算所有用户对项目的评分的加权平均来预测活跃用户u对项目i的未知评分值。

    小贴士

    在这里,权重是之前步骤中用户和邻近用户之间计算的余弦相似度。

  • 向用户推荐新项目。

基于用户的相似度计算

下一步是为评分矩阵中的每个用户创建成对相似度计算,也就是说,我们必须计算矩阵中每个用户与其他所有用户的相似度。我们在这里选择的相似度计算方法是余弦相似度。为此,我们利用成对距离能力来计算sklearn包中可用的余弦相似度,如下所示:

基于用户的相似度计算

让我们看看距离矩阵的一个示例数据集:

dist_out 

基于用户的相似度计算

预测活跃用户的未知评分。

如前所述,可以通过将距离矩阵和评分矩阵之间的点积以及用评分数量对数据进行归一化来计算所有用户的未知值如下:

user_pred = dist_out.dot(ratings_train) / np.array([np.abs(dist_out).sum(axis=1)]).T 

现在我们已经预测了用于训练集的未知评分,让我们定义一个函数来检查模型的误差或性能。以下代码定义了一个函数,通过取预测值和原始值来计算均方根误差(RMSE)。我们使用sklearn的能力来计算 RMSE,如下所示:

from sklearn.metrics import mean_squared_error 
def get_mse(pred, actual): 
    #Ignore nonzero terms. 
    pred = pred[actual.nonzero()].flatten() 
    actual = actual[actual.nonzero()].flatten() 
    return mean_squared_error(pred, actual) 

我们调用get_mse()方法来检查模型预测误差率,如下所示:

get_mse(user_pred, ratings_train) 
7.8821939915510031 

我们可以看到模型的准确率或 RMSE 是7.8。现在让我们在测试数据上运行相同的get_mse()方法并检查准确率,如下所示:

get_mse(user_pred, ratings_test) 
8.9224954316965484 

基于用户的 k 近邻协同过滤

如果我们观察上述模型中的 RMSE 值,我们可以看到误差略高。原因可能在于我们在进行预测时选择了所有用户的评分信息。而不是考虑所有用户,让我们只考虑相似度最高的 N 个用户的评分信息,然后进行预测。这可能会通过消除数据中的某些偏差来提高模型准确率。

为了更详细地解释;在之前的代码中,我们通过取所有用户的评分的加权平均来预测用户的评分,而我们现在首先为每个用户选择前 N 个相似用户,然后通过考虑这些前 N 个用户的评分的加权平均来计算评分。

找到前 N 个最近邻。

首先,为了计算上的简便,我们将通过设置变量k来选择前五个相似用户。

k=5

我们使用 k-最近邻方法为活跃用户选择前五个最近邻。我们很快就会看到这一点。我们选择 sklearn.knn 功能来完成这项任务,如下所示:

from sklearn.neighbors import NearestNeighbors 

通过传递 k 和相似度方法作为参数定义NearestNeighbors对象:

neigh = NearestNeighbors(k,'cosine') 

将训练数据拟合到nearestNeighbor对象:

neigh.fit(ratings_train) 

计算每个用户的前五个相似用户及其相似度值,即每对用户之间的距离值:

top_k_distances,top_k_users = neigh.kneighbors(ratings_train, return_distance=True) 

我们可以观察到以下结果,top_k_distances ndarray 包含相似度值和训练集中每个用户的前五个相似用户:

top_k_distances.shape 
(631, 5) 
top_k_users.shape 
(631, 5) 

让我们看看训练集中与用户 1 相似的前五个用户:

top_k_users[0] 
array([  0,  82, 511, 184, 207], dtype=int64) 

下一步将是为每个用户选择前五个用户,并在预测评分时使用他们的评分信息,即使用这五个相似用户的所有评分的加权总和。

运行以下代码以预测训练数据中的未知评分:

user_pred_k = np.zeros(ratings_train.shape) 
for i in range(ratings_train.shape[0]): 
    user_pred_k[i,:] =   top_k_distances[i].T.dot(ratings_train[top_k_users][i]) 
/np.array([np.abs(top_k_distances[i].T).sum(axis=0)]).T 

让我们看看模型预测的数据如下:

user_pred_k.shape 
(631, 1682) 

user_pred_k 

以下图像显示了user_pred_k的结果:

寻找前 N 个最近邻

现在,让我们看看模型是否有所改进。运行之前定义的 get_mse()方法如下:

get_mse(user_pred_k, ratings_train) 
8.9698490022546036 
get_mse(user_pred_k, ratings_test) 
11.528758029255446 

基于物品的推荐

IBCF 与 UBCF 非常相似,但在如何使用评分矩阵方面有非常小的变化。

第一步是计算电影之间的相似度,如下所示:

由于我们必须计算电影之间的相似度,所以我们使用电影计数作为k而不是用户计数:

k = ratings_train.shape[1] 
neigh = NearestNeighbors(k,'cosine') 

我们将评分矩阵的转置拟合到NearestNeighbors对象:

neigh.fit(ratings_train.T) 

计算每对电影之间的余弦相似度距离:

top_k_distances,top_k_users = neigh.kneighbors(ratings_train.T, return_distance=True) 
top_k_distances.shape 
(1682, 1682) 

下一步是使用以下代码预测电影评分:

item__pred = ratings_train.dot(top_k_distances) / np.array([np.abs(top_k_distances).sum(axis=1)]) 
item__pred.shape 
(631, 1682) 
item__pred 

以下图像显示了item_pred的结果:

基于物品的推荐

评估模型

现在,让我们使用我们定义的get_mse()方法评估模型,通过传递预测评分以及训练和测试集如下:

get_mse(item_pred, ratings_train) 
11.130000188318895 
get_mse(item_pred,ratings_test) 
12.128683035513326 

k-最近邻的训练模型

运行以下代码以计算前 40 个最近邻的距离矩阵,然后计算所有电影的前 40 个用户的加权评分总和。如果我们仔细观察代码,它与我们之前为 UBCF 所做的工作非常相似。我们不是直接传递ratings_train,而是转置数据矩阵,并按如下方式传递给前面的代码:

k = 40 
neigh2 = NearestNeighbors(k,'cosine') 
neigh2.fit(ratings_train.T) 
top_k_distances,top_k_movies = neigh2.kneighbors(ratings_train.T, return_distance=True) 

#rating prediction - top k user based  
pred = np.zeros(ratings_train.T.shape) 
for i in range(ratings_train.T.shape[0]): 
    pred[i,:] = top_k_distances[i].dot(ratings_train.T[top_k_users][i])/np.array([np.abs(top_k_distances[i]).sum(axis=0)]).T 

评估模型

以下代码片段计算训练和测试集的均方误差。我们可以观察到训练误差为 11.12,而测试误差为 12.12。

get_mse(item_pred_k, ratings_train) 
11.130000188318895 
get_mse(item_pred_k,ratings_test) 
12.128683035513326 

摘要

在本章中,我们探讨了在 R 和 Python 中构建协同过滤方法,如基于用户和基于物品的方法,这两种是流行的数据挖掘编程语言。推荐引擎建立在 MovieLens 和 Jester5K 数据集上,这些数据集可在网上找到。

我们已经学习了如何构建模型、选择数据、探索数据、创建训练集和测试集,以及使用如 RMSE、精确率-召回率和 ROC 曲线等指标来评估模型。此外,我们还了解了如何调整参数以改进模型。

在下一章中,我们将介绍使用 R 和 Python 实现的个性化推荐引擎,例如基于内容的推荐引擎和上下文感知的推荐引擎。

第六章。构建个性化推荐引擎

推荐引擎正在快速发展,大量研究也投入到了这个领域。大型跨国公司在这个领域投入了巨额资金。如前所述,从早期的推荐引擎模型,如协同过滤,这些系统已经取得了巨大成功。随着通过推荐引擎产生的收入越来越多,越来越多的人使用互联网进行购物、阅读新闻或获取与健康、商业相关的信息,商业组织看到了从互联网上可用的用户活动中获得巨大商业机会。随着推荐引擎用户数量的增加,以及越来越多的应用程序由推荐引擎驱动,用户也开始要求个性化的建议,而不是基于社区的推荐。用户社区的这个需求被视为新的挑战,为此,已经构建了个性化的推荐引擎,以在个人层面提供建议。

几乎所有行业领域目前都在构建能够提供个性化推荐的推荐引擎。

以下是一些个性化推荐:

  • 个性化新闻推荐——Google News

  • 个性化医疗保健系统

  • 个性化旅行推荐系统

  • 亚马逊上的个性化推荐

  • YouTube 上的个性化电影推荐

以下为个性化推荐的截图:

构建个性化推荐引擎

在第三章《推荐引擎解释》中,我们详细学习了基于内容的推荐系统和基于上下文的推荐系统。在本章中,我们将简要回顾这些主题,然后继续构建基于内容和基于上下文的推荐系统。

个性化推荐系统

在本章中,我们将学习两种个性化推荐器的类型:

  • 基于内容的推荐系统

  • 基于上下文的推荐系统

基于内容的推荐系统

构建协同过滤相对简单。在第五章中,我们学习了如何构建协同过滤推荐系统。在构建这些系统时,我们只考虑了产品获得的评分以及产品是否受欢迎的信息。凭借这些最小信息,我们构建了系统。令许多人惊讶的是,这些系统表现非常出色。但这些系统也有自己的局限性,例如前几章中解释的冷启动问题。

假设有一个用户尼克给一部电影评了五星,比如《泰坦尼克号》。是什么让尼克给出了这个评价?可能是电影的故事、电影中的演员、背景音乐或剧本。这些特征的偏好使得尼克对电影进行了评分。在构建推荐时,包含产品/特征的偏好内部信息不是更有意义吗?

在协同过滤中,基本假设是过去有相似品味的人在未来也会有相似的品味。如果我们仔细观察,这个假设可能并不适用于所有情况。例如,如果我的邻居对恐怖电影《驱魔人》给出了高度评价,那么这部电影不应该被推荐给我,因为我更喜欢浪漫电影。我应该得到一部浪漫类型的电影,比如《泰坦尼克号》,作为推荐。我并不总是和我的邻居有相同的品味;如果我能得到仅基于我的偏好和行为的建议,我会很高兴。企业已经看到了在个人层面上实施这些类型推荐的大量商业机会,这些推荐被称为个性化推荐系统。

构建基于内容的推荐系统

在基于内容的推荐系统中,我们在构建推荐引擎时同时使用用户和物品的内容信息。一个典型的基于内容的推荐系统将执行以下步骤:

  1. 生成用户配置文件。

  2. 生成物品配置文件。

  3. 生成推荐引擎模型。

  4. 提供前 N 个推荐。

我们首先从可用信息中生成用户和物品配置文件。配置文件通常包含对物品和用户特征的偏好(有关详细信息,请参阅第三章,《推荐引擎解释》)。一旦创建了配置文件,我们就选择一种方法来构建推荐引擎模型。许多数据挖掘技术,如分类、文本相似度方法如tf-idf相似度,以及矩阵分解模型,都可以应用于构建基于内容的推荐引擎。

我们甚至可以采用多个推荐引擎模型并构建混合推荐引擎,作为基于内容的推荐。以下图示了一个典型的内容推荐器:

构建基于内容的推荐系统

使用 R 进行基于内容的推荐

让我们开始用 R 构建一个个性化的推荐引擎。我们选择 MovieLens 数据集来构建我们的系统。在前一节中,我们回顾了基于内容的推荐系统的概念。我们可以用多种方式构建个性化推荐者;在本节中,我们将使用多类分类方法来构建我们的基本基于内容的推荐引擎。

使用分类方法,我们试图构建一个基于模型的推荐引擎。大多数推荐系统——无论是协同过滤还是基于内容的——都使用邻域方法来构建推荐器。让我们探讨如何使用监督机器学习方法来构建推荐引擎。

在我们开始编写代码之前,让我们讨论构建个性化推荐系统的步骤。以下图示展示了我们将遵循的步骤顺序以实现我们的目标:

使用 R 进行基于内容的推荐

第一步始终是收集数据并将其拉入编程环境,以便我们可以应用后续步骤。在我们的用例中,我们下载了 MovieLens 数据集,包含三组数据,如下定义:

  • 包含 userID、itemID、评分和时间戳的评分数据

  • 包含用户信息的数据,如 userID、年龄、性别、职业、ZIP 代码等

  • 包含某些电影信息的数据,如 movieID、上映日期、URL、流派细节等

第二步将是准备构建分类模型所需的数据。在这一步中,我们提取构建分类模型所需的用户特征和类别标签:

使用 R 进行基于内容的推荐

  • 对于我们的示例案例,我们将用户给出的评分(1 到 5)定义为类别标签,例如 1-3 评分作为 0,4-5 评分作为 1。因此,我们将构建一个双类分类模型。我们的模型将根据给定用户的输入特征预测类别标签。

    小贴士

    你可能想知道为什么我们选择二元分类而不是多类分类。模型的选择留给构建推荐系统的人;在我们的情况下,由于我们选择的数据库,二元分类比多类分类更适合。鼓励读者尝试多类分类以加深理解。

  • 我们从用户数据和项目数据中选择用户人口统计信息和项目特征,以形成我们的二元分类模型的特征。我们通过包括用户评分的电影的流派信息、用户的个人信息(如年龄、性别、职业等)等特征扩展了User_item_rating数据。最终的特征和类别标签可以在前面的图中看到。

第三步将是构建二元分类模型。我们将选择 RandomForest 算法来构建类别。

第四步和最后一步将是为用户生成前 N 个推荐。在我们的例子中,我们选择一个测试用户,预测他之前未评分的电影的类别标签,并发送具有更高概率评分的前 N 部电影,这些评分是由我们的分类模型预测的。

请注意,生成前 N 个推荐的选择留给用户自己决定。

让我们使用 R 实现上述步骤。在本节中,我们将逐步实现使用 R 的内容推荐。

数据集描述

在这个练习中,我们使用两个 MovieLens 数据集文件--一个是包含对 943 到 1682 部电影评分的评分文件,评分范围为 1-5,另一个是包含内容信息的项目数据集文件,即关于电影流派、电影名称、电影 ID、URL 等信息。

小贴士

MovieLens 数据集可以从以下 URL 下载:grouplens.org/datasets/movielens/

使用 R 中可用的read.csv()函数将评分数据加载到 R 环境中:

raw_data = read.csv("~/udata.csv",sep="\t",header=F) 
Adding column names to the dataframe 
colnames(raw_data) = c("UserId","MovieId","Rating","TimeStamp") 

此代码从 DataFrame 中删除最后一列:

ratings = raw_data[,1:3] 

使用head()函数查看数据的前五行,如下所示:

head(ratings) 

使用names()函数查看评分数据框的列。

使用str()函数查看评分函数的描述。三个提到的函数的所有结果如下所示:

数据集描述

以下代码使用 R 中的read.csv()函数将项目数据加载到 R 环境中:

movies = read.csv("C:/Suresh/R&D/packtPublications/reco_engines/drafts/personalRecos/uitem.csv",sep="|",header=F) 

接下来,我们在数据框中添加列:

colnames(movies) = c("MovieId","MovieTitle","ReleaseDate","VideoReleaseDate","IMDbURL","Unknown","Action","Adventure","Animation","Children","Comedy","Crime","Documentary","Drama","Fantasy","FilmNoir","Horror","Musical","Mystery","Romance","SciFi","Thriller","War","Western") 

然后我们删除不需要的数据;在这个练习中,我们只保留流派信息:

movies = movies[,-c(2:5)] 
View(movies) 

数据集描述

电影的描述由str(movies)给出。列名可以通过names(movies)查看:

数据集描述

下一步是创建客户特征配置文件以构建分类模型。我们应该将包含 userID、movieID 和评分的评分数据框与电影属性扩展,如下所示。

在以下代码中,我们使用merge()执行连接函数以合并评分数据与项目数据:

ratings = merge(x = ratings, y = movies, by = "MovieId", all.x = TRUE) 

View(ratings) 

数据集描述

让我们使用names()方法查看列名:

数据集描述

现在我们为我们刚才创建的每个配置文件记录创建类标签。我们将为每个评分创建一个二元类标签,因此 1-3 个评分将被标记为 0,4-5 个评分将被标记为 1。以下代码为我们执行此转换。我们使用lapply()函数重塑评分:

以下代码管理将数值评分转换为二元分类变量的转换:

nrat = unlist(lapply(ratings$Rating,function(x) 
{ 
  if(x>3) {return(1)} 
  else {return(0)} 
})) 

接下来,我们将新创建的评分分类变量nrat与原始评分数据框 ratings 使用cbind()结合:

ratings = cbind(ratings,nrat) 

数据集描述

在前面的图中,我们可以观察到新的评分二元类nrat

现在让我们使用apply()函数通过将table()应用于每一列来观察将进入模型构建阶段的变量,如下所示:

apply(ratings[,-c(1:3,23)],2,function(x)table(x)) 

数据集描述

从前面的结果中,我们可以观察到与 1 的数量相比,零的数量非常高;因此,让我们从我们的特征列表中删除这个变量。此外,让我们删除评分变量,因为我们已经创建了一个新的变量nrat

scaled_ratings = ratings[,-c(3,4)] 

在构建模型之前,我们将使用 R 中的scale()函数对数据进行标准化或居中,如下面的代码片段所示。标准化将调整不同尺度的数据以适应一个共同的尺度。该函数将通过移除每个对应列的列均值来实现居中:

scaled_ratings=scale(scaled_ratings[,-c(1,2,21)]) 
scaled_ratings = cbind(scaled_ratings,ratings[,c(1,2,23)]) 

数据集描述

现在让我们使用randomForest算法来构建二元分类模型。在此之前,让我们将数据分为训练集和测试集,比例为 80:20。

以下代码首先创建所有数据的随机索引对象。然后我们使用这些索引来划分训练集和测试集。

set.seed(7) 
which_train <- sample(x = c(TRUE, FALSE), size = nrow(scaled_ratings), 
                      replace = TRUE, prob = c(0.8, 0.2)) 
model_data_train <- scaled_ratings[which_train, ] 
model_data_test <- scaled_ratings[!which_train, ] 

数据集描述

现在让我们使用randomForest库中的randomForest算法来构建模型:

注意

在下面的代码片段中,我们将整数变量nrat转换为因子格式。

library(randomForest) 
fit = randomForest(as.factor(nrat)~., data = model_data_train[,-c(19,20)]) 

只需输入fit,我们就可以看到模型构建、拟合的详细信息:

数据集描述

在前面的代码片段中,我们使用了默认值的randomforest()方法。对于随机森林,我们有两个参数可以调整以获得最佳性能;mtry是每个树分裂处的样本数,ntree是要生成的决策树的数量。使用参数调整和交叉验证方法,我们应该选择最佳参数。

我们也可以使用summary()函数查看模型的摘要,如下所示:

数据集描述

现在,让我们看看模型在测试集上的表现:

predictions <- predict(fit, model_data_test[,-c(19,20,21)], type="class") 

数据集描述

让我们使用精确率-召回率方法评估模型:

#building confusion matrix 
cm = table(predictions,model_data_test$nrat) 
(accuracy <- sum(diag(cm)) / sum(cm)) 
(precision <- diag(cm) / rowSums(cm)) 
recall <- (diag(cm) / colSums(cm)) 

数据集描述

根据前面的结果,我们对 60%的精确率和 75%的召回率感到非常满意。现在我们继续进行以下步骤,为用户 ID(943)生成前 N 个推荐:

  1. 创建一个包含所有未被活跃用户评分的电影的 DataFrame(在我们的案例中用户 ID 为 943)。

            #extract distinct movieids 
            totalMovieIds = unique(movies$MovieId) 
            #see the sample movieids using tail() and head() functions: 
    
    

    数据集描述

            #a function to generate dataframe which creates non-rated 
              movies by active user and set rating to 0; 
            nonratedmoviedf = function(userid){ 
              ratedmovies = raw_data[raw_data$UserId==userid,]$MovieId 
              non_ratedmovies = totalMovieIds[!totalMovieIds %in%  
                ratedmovies] 
               df = data.frame(cbind(rep(userid),non_ratedmovies,0)) 
               names(df) = c("UserId","MovieId","Rating") 
               return(df) 
            } 
    
            #let's extract non-rated movies for active userid 943 
            activeusernonratedmoviedf = nonratedmoviedf(943) 
    
    

    数据集描述

  2. 为此活跃用户 DataFrame 构建一个配置文件:

            activeuserratings = merge(x = activeusernonratedmoviedf, y = 
              movies, by = "MovieId", all.x = TRUE) 
    
    

    数据集描述

  3. 预测评分,排序并生成 10 个推荐:

            #use predict() method to generate predictions for movie ratings 
              by the active user profile created in the previous step. 
            predictions <- predict(fit, activeuserratings[,-c(1:4)],  
              type="class") 
            #creating a dataframe from the results 
            recommend = data.frame(movieId = 
              activeuserratings$MovieId,predictions) 
            #remove all the movies which the model has predicted as 0 and  
              then we can use the remaining items as more probable movies   
                which might be liked by the active user. 
            recommend = recommend[which(recommend$predictions == 1),] 
    
    

通过这一步,我们已经完成了扩展或改进基于内容的推荐引擎构建的内容,使用分类模型。在我们进入下一节之前,我想明确指出,模型和类标签特征的选择取决于读者来扩展或改进模型。

如前所述,我们应该使用交叉验证方法来选择最佳参数,以提高模型精度。

使用 Python 进行基于内容的推荐

在上一节中,我们使用 R 构建了一个基于模型的推荐内容引擎。在本节中,我们将使用另一种方法,使用 Python 的sklearnNumPypandas包来构建内容推荐。

让我们回顾一下本章开头讨论的构建基于内容系统的步骤:

  1. 项目配置文件生成

  2. 用户配置文件生成

  3. 生成推荐引擎模型

  4. 生成前 N 个推荐

在本节中,我们将详细学习如何使用 Python 按照上述步骤构建内容:

该方法的设计如下所示:

使用 Python 进行基于内容的推荐

  • 创建项目配置文件:在这个步骤中,我们使用关于项目的现有内容信息为每个项目创建一个配置文件。项目配置文件通常使用一种广泛使用的信息检索技术创建,称为 tf-idf。在第四章《推荐引擎中的数据挖掘技术》中,我们详细解释了tf-idf。为了回顾,tf-idf 值给出了相对于所有项目或文档的特征的相对重要性。

  • 创建用户配置文件:在这个步骤中,我们将用户活动数据集预处理成适当的格式以创建用户配置文件。我们应该记住,在基于内容的推荐系统中,用户配置文件是根据项目内容创建的,也就是说,我们必须提取或计算用户对项目内容或项目特征的偏好。通常,用户活动和项目配置文件之间的点积给出了用户配置文件。

  • 生成推荐引擎模型:现在我们已经有了用户配置文件和项目配置文件,我们将继续构建推荐模型。计算用户配置文件和项目配置文件之间的余弦相似度给出了用户对每个项目的亲和力。

  • 生成前 N 个推荐:在最后一步,我们将根据上一步计算出的值对用户-项目偏好进行排序,然后提出前 N 个推荐。

现在我们将开始使用 Python 实现上述步骤。

数据集描述

在本节中,我们将使用匿名微软 Web 数据集来构建一个基于内容的推荐系统。本节的目标是根据用户的先前网络浏览历史推荐网站给活跃用户。

MS Web 数据集指的是 38,000 名匿名用户访问的网站 www.microsoft.com 的网络日志。对于每位用户,数据集包括用户在一周时间范围内访问的所有网站的列表。

数据集可以从以下 URL 下载:

archive.ics.uci.edu/ml/datasets/Anonymous+Microsoft+Web+Data

为了简化,从现在起,我们将使用术语 items 来指代网站区域。有 5,000 个用户,它们由 10,001 到 15,000 之间的连续数字表示。项目由 1,000 到 1,297 之间的数字表示,即使它们小于 298。

该数据集是一个非结构化文本文件。每条记录包含两个到六个字段。第一个字段是一个字母,定义了记录包含的内容。主要有三种类型的记录,如下所示:

  • 属性(A):这是网站区域的描述

  • 案例(C):这是每个用户的案例,包含其 ID

  • 投票(V):这是案例的投票行

第一列的案例记录后面跟着 userID/caseID。第三列包含用户 ID/对网站区域的投票。第四列包含网站区域的描述,第五列包含网站区域的 URL。

下图显示了一组原始数据:

数据集描述

我们的目标是建议每个用户探索他们尚未探索的网站区域。

下面的列表是我们将用于此练习的包:

import pandas as pd 
import numpy as np 
import scipy 
import sklearn 

加载数据:

path = "~/anonymous-msweb.test.txt" 
import pandas as pd 

使用 pandas 包中可用的 read.csv() 函数读取数据:

raw_data = pd.read_csv(path,header=None,skiprows=7) 
raw_data.head() 

数据集描述

让我们看看更多的样本数据,以便有更清晰的概念:

数据集描述

我们可以从前面的图中观察到以下内容:

  • 第一列包含三种类型的值:A/V/C,其中 A 代表案例 ID,V代表用户,C代表用户访问的案例 ID

  • 第二列包含 ID,用于表示用户和项目

  • 第三列包含网站区域的描述

  • 第四部分包含网站区域在网站上的 URL

为了创建项目配置文件,我们使用第一列包含 A 的行,为了创建用户活动或数据集,我们使用第一列不包含 A 的行。

让我们开始配置文件生成。

在我们进行配置文件生成之前,我们必须格式化用户活动数据;以下部分解释了如何创建用户活动数据集。

用户活动

在本节中,我们将创建一个用户-项目评分矩阵,其中用户作为行,项目作为列,值作为单元格。在这里,值是 01,如果用户访问了网页,则为 1,否则为 0

首先,我们只过滤掉第一列不包含 "A" 的记录:

user_activity = raw_data.loc[raw_data[0] != "A"] 

接下来,我们从数据集中移除不需要的列:

user_activity = user_activity.loc[:, :1] 

user_activity DataFrame 的列分配名称:

user_activity.columns = ['category','value'] 

以下代码显示了样本 user_activity 数据:

用户活动

要获取数据集中的总唯一webid,请参阅以下代码:

len(user_activity.loc[user_activity['category'] =="V"].value.unique()) 
Out[73]: 236 

要获取唯一用户数,请参阅以下代码:

len(user_activity.loc[user_activity['category'] =="C"].value.unique()) 
Out[74]: 5000 

现在,让我们运行以下代码来创建一个用户-项目-评分矩阵,如下所示:

首先,我们分配变量:

tmp = 0 
nextrow = False 

然后我们获取数据集的最后一个索引:

lastindex = user_activity.index[len(user_activity)-1] 
lastindex 
Out[77]: 20484 

for 循环代码遍历每条记录,并将新的列('userid''webid')添加到user_activity数据框中,该数据框显示userid和相应的网络活动:

for index,row in user_activity.iterrows(): 
    if(index <= lastindex ): 
        if(user_activity.loc[index,'category'] == "C"): 
            tmp = 0            
            userid = user_activity.loc[index,'value'] 
            user_activity.loc[index,'userid'] = userid 
            user_activity.loc[index,'webid'] = userid 
            tmp = userid 
            nextrow = True             
        elif(user_activity.loc[index,'category'] != "C" and nextrow == True): 
                webid = user_activity.loc[index,'value'] 
                user_activity.loc[index,'webid'] = webid 
                user_activity.loc[index,'userid'] = tmp 
                if(index != lastindex and user_activity.loc[index+1,'category'] == "C"): 
                    nextrow = False 
                    caseid = 0 

用户活动

接下来,我们从前面的数据框中删除不需要的行,即删除包含类别列中"C"的行:

user_activity = user_activity[user_activity['category'] == "V" ] 

用户活动

我们子集列,并删除我们不再需要的头两列:

user_activity = user_activity[['userid','webid']] 

接下来,我们按webid对数据进行排序;这是为了确保评分矩阵生成格式良好:

user_activity_sort = user_activity.sort('webid', ascending=True) 

现在,让我们使用以下代码创建一个包含用户 _item_rating 的密集二进制评分矩阵:

首先,我们获取webid列的大小:

sLength = len(user_activity_sort['webid']) 

然后我们在user_activity数据框中添加一个新列'rating',其中只包含 1:

user_activity_sort['rating'] = pd.Series(np.ones((sLength,)), index=user_activity.index) 

接下来,我们使用 pivot 创建二进制评分矩阵:

ratmat = user_activity_sort.pivot(index='userid', columns='webid', values='rating').fillna(0) 

最后,我们创建一个密集矩阵:

ratmat = ratmat.to_dense().as_matrix() 

项目配置文件生成

在本节中,我们将从初始原始数据(raw_data)创建项目配置文件。为了创建项目数据,我们将考虑包含第一列中A的数据:

首先,我们筛选出所有第一列包含"A"的记录:

items = raw_data.loc[raw_data[0] == "A"] 

然后我们按如下方式命名列:

items.columns = ['record','webid','vote','desc','url'] 

要生成项目配置文件,我们只需要两个列,所以我们按如下方式切片数据框:

items = items[['webid','desc']] 

为了查看项目的维度,数据框被给出如下:

items.shape 
Out[12]: (294, 2) 

我们观察到数据集中有294个唯一的webid

要检查数据的样本,我们使用以下代码:

Items.head() 

项目配置文件生成

要检查唯一webid的数量,我们使用以下代码:

items['webid'].unique().shape[0] 
Out[117]: 294 

我们也可以只选择存在于user_activity数据中的webid

items2 = items[items['webid'].isin(user_activity['webid'].tolist())] 

我们可以使用以下代码检查对象类型:

type(items2) 
Out[123]: pandas.core.frame.DataFrame 

我们也可以按webid对数据进行排序:

items_sort = items2.sort('webid', ascending=True) 

让我们使用head(5)函数看看我们现在做了什么:

项目配置文件生成

现在,我们将使用 sklearn 包中可用的tf-idf函数创建项目配置文件。要生成tf-idf,我们使用TfidfVectorizer()fit_transform()方法位于sklearn包中。以下代码显示了我们可以如何创建tf-idf

在以下代码中,要包含的特征数量取决于数据集,并且可以通过交叉验证方法选择最佳特征数量:

from sklearn.feature_extraction.text import TfidfVectorizer 
v = TfidfVectorizer(stop_words ="english",max_features = 100,ngram_range= (0,3),sublinear_tf =True) 
x = v.fit_transform(items_sort['desc']) 
itemprof = x.todense() 

项目配置文件生成

用户配置文件创建

现在我们手头有项目配置文件和用户活动;这两个矩阵的点积将创建一个新矩阵,其维度等于用户数乘以项目特征数。

为了计算用户活动和项目资料之间的点积,我们使用scipy包中的方法,如linalgdot

运行以下代码以计算点积:

#user profile creation 
from scipy import linalg, dot 
userprof = dot(ratmat,itemprof)/linalg.norm(ratmat)/linalg.norm(itemprof) 

userprof 

用户资料创建

推荐引擎模型中的最后一步是计算活动用户对项目的偏好。为此,我们在用户资料和项目资料之间进行余弦相似度计算。

为了计算余弦计算,我们将使用sklearn包。以下代码将计算cosine_similarity

我们计算userprofile和项目资料之间的余弦相似度:

import sklearn.metrics 
similarityCalc = sklearn.metrics.pairwise.cosine_similarity(userprof, itemprof, dense_output=True) 

我们可以如下查看前面计算的结果:

用户资料创建

现在,让我们将之前计算的二进制数据(0,1)格式化如下:

首先,我们将评分转换为二进制格式:

final_pred= np.where(similarityCalc>0.6, 1, 0) 

然后,我们检查前三个用户的最终预测:

用户资料创建

从前面的结果中移除零值,我们得到可以推荐给用户的可能项目列表:

对于用户213,推荐的物品生成如下:

indexes_of_user = np.where(final_pred[213] == 1) 

在前面的代码中,我们正在为活动用户213生成推荐:

用户资料创建

上下文感知推荐系统

我们接下来要学习的是个性化推荐系统中的另一种类型,即上下文感知推荐系统。这些推荐系统属于下一代推荐系统,属于超个性化类别。人类的需求永远不会结束,这是很自然的。我们得到的越多,我们想要的就越多。尽管基于内容的推荐系统很高效,针对个人层面,并且在构建推荐引擎时仅考虑用户的个人偏好,但人们希望推荐引擎更加个性化。例如,一个人独自旅行时可能需要一本书来阅读,而如果他和朋友一起旅行,他可能需要啤酒。同样,如果这个人带着自己的家人出行,他可能需要尿布、药品、零食等等。不同地方、不同时间、与不同人在一起的人有不同的需求。我们的推荐系统应该足够强大,能够处理这样的场景。这种为同一人根据其当前上下文提供不同推荐的超个性化推荐系统,被称为上下文感知推荐系统。

构建上下文感知推荐系统

构建上下文感知推荐系统更像是扩展内容推荐系统。构建上下文感知系统通常涉及在内容推荐器之上添加上下文维度,如下面的图所示:

构建上下文感知推荐系统

在前面的图中,我们可以观察到上下文维度被添加到基于内容的推荐引擎模型之上,然后生成推荐。正如我们在第三章中讨论的,《推荐引擎解释》,构建上下文感知推荐有两种流行的方法:

  • 预过滤方法

  • 后过滤方法

在本节中,我们将使用后过滤技术来构建上下文感知推荐系统。

使用 R 进行上下文感知推荐

在前一节中,我们构建了一个基于内容的推荐引擎。在本节中,我们将扩展先前的模型以包括上下文信息并生成一个上下文感知推荐引擎。

构建上下文感知系统的常规做法是在基于内容的推荐中添加一个时间维度。

工作流程如下所示:

使用 R 进行上下文感知推荐

让我们尝试使用 R 构建上下文感知系统。在 R 中构建上下文感知系统的步骤如下:

  1. 定义上下文。

  2. 根据用户的内容创建上下文配置文件。

  3. 为上下文生成推荐。

定义上下文

第一步是定义我们将包括在推荐中的上下文。在前一节中,我们使用了 MovieLens 数据集来构建基于内容的推荐引擎。在数据集中,我们有一个时间组件,即评分数据中的时间戳。我们将使用此变量来构建我们的上下文感知推荐系统。

我们将扩展在构建基于内容的推荐时使用的 R 代码。

我们如下加载完整的 MovieLens 评分数据集:

raw_data = read.csv("C:/Suresh/R&D/packtPublications/reco_engines/drafts/personalRecos/udata.csv",sep="\t",header=F) 
colnames(raw_data) = c("UserId","MovieId","Rating","TimeStamp") 

使用 head() 函数查看样本数据:

定义上下文

我们加载电影数据集:

movies = read.csv("C:/Suresh/R&D/packtPublications/reco_engines/drafts/personalRecos/uitem.csv",sep="|",header=F) 

然后我们向电影数据框添加列名:

colnames(movies) = c("MovieId","MovieTitle","ReleaseDate","VideoReleaseDate","IMDbURL","Unknown","Action","Adventure","Animation","Children","Comedy","Crime","Documentary","Drama","Fantasy","FilmNoir","Horror","Musical","Mystery","Romance","SciFi","Thriller","War","Western") 

接下来,我们从数据框中删除不需要的列:

movies = movies[,-c(2:5)] 

定义上下文

我们使用 merge() 函数合并电影和评分数据集。

ratings_ctx = merge(x = raw_data, y = movies, by = "MovieId", all.x = TRUE) 

定义上下文

我们希望引入到之前基于内容的推荐中的上下文是白天的小时,也就是说,我们的推荐将根据一天中的时间进行。活跃用户的一天的推荐集合将因小时而异。通常,这些推荐的变化是由于根据小时排序的推荐顺序。我们将在下一节中看到我们是如何实现这一点的。

创建上下文配置文件

在下一节中,我们将编写代码来创建用户的上下文配置文件。我们选择了数据集中可用的日期时间信息,并计算了每个用户每天每个小时的影片类型的偏好值。此上下文配置文件信息用于生成上下文感知推荐。

我们从评分数据集中提取时间戳:

ts = ratings_ctx$TimeStamp 

然后,我们将其转换为POSIXlt日期对象,并使用小时属性提取一天中的小时:

hours <- as.POSIXlt(ts,origin="1960-10-01")$hour 

以下为样本数据:

创建上下文配置文件

我们可以将小时重新添加到评分数据集中:

ratings_ctx = data.frame(cbind(ratings_ctx,hours)) 

创建上下文配置文件

现在,让我们开始为用户 ID 为 943 的用户构建上下文配置文件:

提取活跃用户(943)的评分信息,并删除 UserId、MovieId、Rating、Timestamp 列,如下所示:

UCP = ratings_ctx[(ratings_ctx$UserId == 943),][,-c(2,3,4,5)] 

创建上下文配置文件

作为下一步,我们计算所有项目特征的列。这个列向总和用于计算每天每个小时的项目特征的偏好。

我们使用aggregate()函数计算每列的列宽总和:

UCP_pref = aggregate(.~hours,UCP[,-1],sum) 

创建上下文配置文件

从前面的图中,我们可以看到活跃用户 943 对每个电影类型的偏好时间。我们可以观察到,在每天的第九个小时,用户观看的电影更多,尤其是动作/剧情/喜剧电影:

我们可以使用以下函数将前面的数据归一化到 0-1 之间:

UCP_pref_sc = cbind(context = UCP_pref[,1],t(apply(UCP_pref[,-1], 1, function(x)(x-min(x))/(max(x)-min(x))))) 

创建上下文配置文件

生成上下文感知推荐

现在我们已经为活跃用户创建了上下文配置文件,让我们开始为用户生成上下文感知推荐。

对于此,我们将重新使用使用 R 构建的推荐对象,其中包含所有用户的基于内容的推荐。

让我们看看基于内容的系统为用户 943 做出的推荐:

recommend$MovieId 

生成上下文感知推荐

现在,我们将时间或一天中的小时维度添加到这些内容推荐中,然后根据当前上下文生成推荐。

我们使用merge()函数合并推荐和电影数据集:

UCP_pref_content = merge(x = recommend, y = movies, by = "MovieId", all.x = TRUE) 

生成上下文感知推荐

在前面的步骤中,我们已经计算了所有所需的矩阵,用户上下文配置文件(UCP_Pref_SC)和用户内容推荐(UCP_Pref_content)。

假设我们想要在第九个小时为用户生成推荐;我们只需要对用户内容推荐和UCP_pref_SC对象中一天中第九个小时的上下文行进行逐元素乘法。如下所示:

对用户内容推荐和第九个小时的上下文偏好进行逐元素乘法:

active_user =cbind(UCP_pref_content$MovieId,(as.matrix(UCP_pref_content[,-c(1,2,3)]) %*% as.matrix(UCP_pref_sc[4,2:19]))) 

结果如下;我们可以观察到,对于 MovieId 3 的偏好是 0.5,而对于 MovieId 4 的偏好是 2.8

生成上下文感知推荐

我们可以创建一个预测对象的 dataframe 对象:

active_user_df = as.data.frame(active_user) 

接下来,我们向预测对象添加列名:

names(active_user_df) = c('MovieId','SimVal') 

然后我们排序结果:

FinalPredicitons_943 = active_user_df[order(-active_user_df$SimVal),] 

摘要

在本章中,我们学习了如何使用 R 和 Python 构建基于内容的推荐引擎和上下文感知推荐引擎。我们使用 R 和 Python 对基于内容的推荐引擎进行了两种类型的建模——分类模型和 tf-idf 模型方法。为了构建上下文感知推荐,我们只需对基于内容的推荐和用户上下文配置文件进行逐元素相乘。

在下一章中,我们将探讨 Apache Spark,以构建可扩展的实时推荐引擎。

第七章:使用 Spark 构建实时推荐引擎

在这个时代,构建可扩展的实时推荐的需求日益增长。随着越来越多的互联网用户使用电子商务网站进行购买,这些电子商务网站已经意识到理解用户购买行为模式以改善其业务、在非常个性化的层面上服务客户的潜力。为了构建一个能够满足庞大用户群并实时生成推荐的系统,我们需要一个现代、快速可扩展的系统。专为分布式内存数据处理设计的Apache Spark框架为我们提供了帮助。Spark 通过对分布式数据进行一系列转换和操作来构建实时数据挖掘应用。

在前面的章节中,我们学习了实现基于相似度的协同过滤方法,例如基于用户的协同过滤和基于内容的协同过滤。尽管基于相似度的方法在商业应用中取得了巨大成功,但基于模型的推荐模型也应运而生,例如矩阵分解模型,这些模型提高了推荐引擎模型的性能。在本章中,我们将学习基于模型的协同过滤方法,远离基于启发式的相似度方法。此外,我们还将关注使用 Spark 实现基于模型的协同过滤方法。

在本章中,我们将学习以下内容:

  • Spark 2.0 有什么新功能

  • 设置 pyspark 环境

  • 基本 Spark 概念

  • MLlib 推荐引擎模块

  • 交替最小二乘算法

  • Movielens-100k 数据集的数据探索

  • 使用 ALS 构建基于模型的推荐引擎

  • 评估推荐引擎模型

  • 参数调整

关于 Spark 2.0

Apache Spark是一个快速、强大、易于使用、分布式、内存中、开源的集群计算框架,旨在执行高级分析。它最初于 2009 年在加州大学伯克利分校开发。自推出以来,Spark 已被众多行业的众多企业广泛采用。

Spark 的主要优势之一是它为我们移除了所有复杂性,例如资源调度、作业提交、执行、跟踪、节点间通信、容错以及所有并行处理固有的低级操作。Spark 框架帮助我们编写在集群上并行运行的程序。

Spark 可以以独立模式或集群模式运行。Spark 可以轻松集成到 Hadoop 平台中。

作为一种通用计算引擎,Spark 凭借其内存数据处理能力和易于使用的 API,使我们能够高效地处理各种大规模数据处理任务,例如流应用、机器学习或对大型数据集进行迭代访问的交互式 SQL 查询。

Spark 可以轻松地与许多应用程序、数据源、存储平台和环境集成,并暴露 Java、Python 和 R 的高级 API 以进行工作。Spark 已被证明在机器学习和迭代分析之外,对于广泛的规模数据处理任务具有广泛的应用价值。

关于 Spark 2.0

信用:Databricks

Spark 架构

Apache Spark 生态系统包含许多组件,用于处理分布式、内存中以及机器学习数据处理工具。Spark 的主要组件将在以下子节中讨论。Spark 基于主从架构;高级架构如下所示:

Spark 架构

信用:Databricks

Spark 集群采用主从架构。Spark Core 执行引擎接受来自客户端的请求并将它们传递给主节点。主节点中的驱动程序程序与工作节点执行器通信以完成任务,如下所示:

Spark 架构

Spark 驱动程序程序:驱动程序程序在 Spark 集群中充当主节点,为主应用提供 SparkContext。它接收客户端请求并与管理工作节点的集群管理器协调。驱动程序程序将原始请求拆分为任务并将它们调度到工作节点上的执行器上运行。Spark 中的所有进程都是 Java 进程。SparkContext 创建弹性分布式数据集RDD),这是一个不可变、可分发的数据集集合,分布在节点之间,并执行一系列转换和操作以计算最终输出。我们将在后面的章节中了解更多关于 RDD 的信息。

工作节点:工作节点包含执行器,实际任务执行以 Java 进程的形式发生。每个工作节点运行自己的 Spark 实例,是 Spark 中的主要计算节点。当创建 SparkContext 时,每个工作节点启动自己的执行器以接收任务。

执行器:这些是 Spark 应用程序的主要任务执行器。

Spark 组件

在本节中,我们将看到 Spark 生态系统的核心组件。以下图表显示了 Apache Spark 生态系统:

Spark 组件

信用:Databricks

Spark Core

Spark Core是 Spark 平台的核心部分:执行引擎。所有其他功能都是建立在 Spark Core 之上的。这提供了 Spark 的所有功能,如内存中分布式计算、快速且易于使用的 API。

使用 Spark SQL 的 Structured 数据

Spark SQL是 Spark Core 之上的一个组件。它是一个提供对结构化和半结构化数据支持的 Spark 模块。

Spark SQL 提供了一种统一的方法,允许用户以交互式 SQL 类型查询数据对象,例如应用选择,您可以通过数据抽象 API(如 DataFrame)按操作类型对数据对象进行分组。

将大量时间投入到数据探索、探索性分析和类似 SQL 的交互中。Spark SQL,它提供了 DataFrame,还充当一个分布式 SQL 查询引擎;例如,在 R 中,Spark 2.0 的 DataFrame 将数据存储为行和列,允许以 SQL 表的形式访问,其中包含所有结构信息,如数据类型。

使用 Spark Streaming 进行流式分析

Spark Streaming 是另一个 Spark 模块,它允许用户实时处理和分析批量和流式数据,以执行交互式和分析应用。Spark Streaming 提供了高级抽象离散流DStream),以表示连续的数据流。

Spark Streaming API 的主要特性如下:

  • 可扩展

  • 高吞吐量

  • 容错

  • 处理实时流入的数据流

  • 可以连接到实时数据源,并实时处理实时传入的数据

  • 可以在流式数据上应用复杂的机器学习和图处理算法

使用 Spark Streaming 进行流式分析

使用 MLlib 进行机器学习

MLlib 是 Spark 中另一个模块,建立在 Spark Core 之上。这个机器学习库的开发目标是使实用的机器学习可扩展且易于使用。

这个库为数据科学家提供了以下工具:

  • 用于回归、分类、聚类和推荐引擎的机器学习算法

  • 特征提取、特征转换、降维和特征选择

  • 用于简化机器学习过程的管道工具,包括构建、评估和调整解决机器学习问题的过程

  • 存储和加载机器学习模型和管道的持久性

  • 诸如线性代数和统计任务之类的实用工具

当启动 Spark 2.0 时,旧的 MLlib 模型被替换为使用 DataFrame API 构建的 ML 库,提供了更多优化,并使所有语言的 API 统一。

使用 GraphX 进行图计算

GraphX 是一个用于构建基于图系统的 Spark 新 API。它是一个图并行处理计算引擎和分布式框架,建立在 Spark Core 之上。该项目始于将图并行和数据分布框架统一到单个 Spark API 的目标。GraphX 允许用户以 RDD 和图的形式处理数据。

GraphX 提供了许多功能,例如以下:

  • 属性图

  • 基于图的算法,如 PageRank、连通组件和 Graph Builders,用于构建图

  • 基本图计算组件,如子图、joinVertices、aggregateMessages、Pregel API 等

尽管图模型不在此书的范围内,我们将在第八章中学习一些图应用的基本原理,使用 Neo4j 构建实时推荐

Spark 的优势

Spark 的主要优势是速度快,具有内存框架,包含多个 API,使其非常易于使用,其统一引擎适用于大量数据,以及其机器学习组件。与具有批量模式的 Map-Reduce 模型相比,Spark 更快,具有实时和易于编码的框架。

以下图表显示了上述提到的优势:

Spark 的优势

设置 Spark

Spark 可以在 Windows 和类 UNIX 系统(例如 Linux、Mac OS)上运行。在单台机器上本地运行很简单;您只需要在系统 PATH 或JAVA_HOME环境变量中安装 Java 即可。

Spark 在 Java 7+、Python 2.6+/3.4+和 R 3.1+上运行。对于 Scala API,Spark 2.0.0 使用 Scala 2.11。您需要使用兼容的 Scala 版本(2.11.x)。

从项目网站的下载页面获取 Spark:

d3kbcqa49mib13.cloudfront.net/spark-2.0.0-bin-hadoop2.7.tgz

为了访问Hadoop 分布式文件系统HDFS)以及标准和自定义 Hadoop 输入源,Spark 需要针对特定版本的 Hadoop 进行构建。

为了运行,Spark 需要 Scala 编程语言(本书撰写时为版本 2.10.4)。幸运的是,预构建的二进制包包含了 Scala 运行时包,因此您无需单独安装 Scala 即可开始。但是,您需要安装Java 运行时环境JRE)或Java 开发工具包JDK)(请参阅本书代码包中的软件和硬件列表以获取安装说明)。

下载 Spark 二进制包后,请运行以下命令解压包内容并将其更改到新创建的目录中:

tar xfvz spark-2.0.0-bin-hadoop2.7.tgz 
cd spark-2.0.0-bin-hadoop2.7

Spark 将用户脚本放置在 bin 目录中以运行 Spark。您可以通过运行 Spark 中包含的示例程序之一来测试是否一切正常:

------------------ 
./bin/run-example org.apache.spark.examples.SparkPi 
16/09/26 15:20:36 INFO DAGScheduler: Job 0 finished: reduce at SparkPi.scala:38, took 0.845103 s 
Pi is roughly 3.141071141071141 
--------------------

您可以使用以下命令使用 Scala 与 Spark 进行交互式运行:

./bin/spark-shell --master local[2]

--master选项指定了分布式集群的主 URL,或者您可以使用local在本地使用一个线程运行,或者使用local[N]在本地使用 N 个线程运行。您应该从使用local进行测试开始。要获取完整的选项列表,请使用--help选项运行 Spark shell。

来源:spark.apache.org/docs/latest/

关于 SparkSession

从 Spark 2.0 开始,SparkSession 将成为 Spark 应用的入口点。SparkSession 作为对底层 Spark 功能和 Spark 编程能力(如 DataFrame API 和 Dataset API)的主要交互访问点,我们使用 SparkSession 来创建 DataFrame 对象。

在 Spark 的早期版本中,我们通常使用 SparkConfSparkContextSQLContext 与 Spark 进行交互,但自从 Spark 2.0 以来,这已经由 SparkSession 通过封装 SparkConfSparkContext 自动处理。

当你在 shell 命令中启动 Spark 时,SparkSession 会自动创建为 spark

我们可以按如下方式程序化地创建 SparkSession

spark = SparkSession\ 
    .builder\ 
    .appName("recommendationEngine")\ 
     config("spark.some.config.option", "some-value")\ 
    .getOrCreate() 

弹性分布式数据集(RDD)

Spark 的核心是弹性分布式数据集,简称 RDD。RDD 是一个不可变分布式集合,包含你数据中某些数据类型的对象,分布在集群的节点上。这个 RDD 是容错的,也就是说,系统具有在发生故障时能够持续运行的能力,即使是通过重建失败的分区。

简而言之,我们可以这样说,RDD 是一种分布式数据集抽象,它允许以容错方式在非常大的集群系统中进行迭代操作。

RDD 可以通过多种方式创建,例如并行化现有数据对象集合,或引用外部文件系统,如 HDFS:

从现有数据对象创建 RDD:

coll = List("a", "b", "c", "d", "e") 

rdd_from_coll = sc.parallelize(coll) 

从引用的文件创建 RDD:

rdd_from_Text_File = sc.textFile("testdata.txt") 

RDD 支持两种类型的操作:

  • 转换:此操作从现有的 RDD 创建新的 RDD,这些 RDD 是不可变的

  • 动作:此操作在执行数据集上的计算后返回值

这些 RDD 转换仅在需要最终结果时才会被惰性执行。我们可以重新创建或重新计算 RDD 任意次数,或者如果我们知道我们可能需要在将来使用它们,我们可以通过在内存中缓存它们来持久化它们。

关于 ML 流程

Spark 2.0 中的 ML 流程 API 是在解决机器学习问题时使用标准工作流程的方式。每个机器学习问题都将经历一系列步骤,如下所示:

  1. 加载数据。

  2. 特征提取。

  3. 模型训练。

  4. 评估。

  5. 预测。

  6. 模型调优。

如果我们仔细观察上述步骤,我们可以看到以下内容:

  • 机器学习过程遵循一系列步骤,就像一个工作流程。

  • 在解决机器学习问题时,我们通常需要不止一个算法;例如,一个文本分类问题可能需要一个特征提取算法来进行特征提取。

  • 在测试数据上生成预测可能需要许多数据转换或数据预处理步骤,这些步骤在模型训练期间使用。例如,在文本分类问题中,在测试数据上做出预测涉及数据预处理步骤,如标记化和特征提取,然后再将这些步骤应用于在训练数据创建模型时使用的生成的分类模型。

前面的步骤是引入 ML Pipeline API 的主要动机之一。ML Pipeline 模块允许用户定义一系列阶段,使得使用起来非常方便。API 框架允许 ML 流程在分布式平台上进行扩展,并适应非常大的数据集,重用一些组件,等等。

ML Pipeline 模块的组件如下:

  • DataFrame:如前所述,DataFrame 是在 Spark 框架中表示数据的一种方式。

  • 转换器:转换器接收输入 DataFrame 并将数据转换成新的 DataFrame。转换器类包含 transform() 方法来进行转换。

  • 估算器:估算器计算最终结果。估算器类使用 fit() 方法来计算结果。

  • 管道:这是一个将转换器和估算器堆叠为工作流程的集合。

  • 参数:这指的是转换器和估算器都可能使用的参数集。

我们将以简单的文本文档工作流程为例来展示这一点。以下图是管道的训练时间使用情况:

关于 ML Pipeline

下面是对前面图中所示步骤的解释。蓝色框是转换器,红色框是估算器。

  1. Tokenizer 转换器接收 DataFrame 的文本列作为输入,并返回一个包含标记的新 DataFrame 列。

  2. HashingTF 转换器接收来自前一步骤的标记 DataFrame 作为输入,并创建新的 DataFrame 特征作为输出。

  3. 现在,LogisticRegression 估算器接收特征 DataFrame,拟合逻辑回归模型,并创建一个 PipelineModel 转换器。

首先,我们构建一个管道,这是一个估算器,然后在这个管道上应用 fit() 方法,这将产生一个 PipelineModel,这是一个转换器,可以在测试数据或预测时使用。

来源:spark.apache.org/docs/latest/ml-guide.html

下图展示了这种用法:

关于 ML Pipeline

在前面的图中,当我们想要在测试数据上做出预测时,我们观察到测试数据首先必须通过一系列数据预处理步骤,这些步骤与上述训练步骤非常相似。预处理步骤完成后,测试数据的特征应用于逻辑回归模型。

为了使数据预处理和特征提取步骤相同,我们将通过调用 transform() 将测试数据传递给 PipelineModel(逻辑回归模型)以生成预测。

使用交替最小二乘法的协同过滤

在本节中,让我们解释矩阵分解模型(MF)和交替最小二乘法。在我们了解矩阵分解模型之前,我们再次定义目标。想象一下,我们有一些用户对物品给出的评分。让我们用矩阵形式定义用户对物品给出的评分,如下所示:

使用交替最小二乘法的协同过滤

在前面的图中,我们观察到用户 Ted 分别对物品 B 和 D 给出了 4 和 3 的评分。在协同过滤方法中,在生成推荐之前的第一步是填充空白区域,即预测未评分的项。一旦填充了未评分项的评分,我们通过对新填充的项进行排序,向用户推荐新的物品。

在前面的章节中,我们已经看到了使用欧几里得距离和余弦距离的邻近方法来预测缺失值。在本节中,我们将采用一种新的方法来填充缺失的非评分项。这种方法被称为矩阵分解法。这是一个数学方法,它使用矩阵分解方法。该方法解释如下:

一个矩阵可以被分解成两个低秩矩阵,当它们相乘时,将得到一个与原始矩阵近似相等的单个矩阵。

假设一个大小为 U X M 的评分矩阵 R 可以分解成两个低秩矩阵 PQ,分别具有大小 U X KM X K,其中 K 被称为矩阵的秩。

在以下示例中,假设原始矩阵的大小为 4 X 4 被分解成两个矩阵:P (4 X 2)Q (4 X 2)。将 PQ 相乘将给出原始矩阵的大小 4 X 4 以及与原始矩阵值大致相等的值:

使用交替最小二乘法的协同过滤

矩阵分解原理用于推荐引擎中,以填充未评分的项。将上述原理应用于推荐引擎的假设是,用户对物品给出的评分基于一些潜在特征。这些潜在特征适用于用户和物品,也就是说,用户因为对某个物品的个人偏好而评分该物品。同样,用户因为物品的某些特征而评分物品。

使用这个假设,当矩阵分解方法应用于评分矩阵时,我们将原始评分矩阵分解成两个矩阵,如下所示:用户潜在因子矩阵 P 和物品潜在因子矩阵:

使用交替最小二乘法的协同过滤

现在,让我们回到机器学习方法;你一定想知道这种方法中的学习是什么。观察以下公式:

使用交替最小二乘法的协同过滤

我们已经了解到,当我们乘回两个潜在因子矩阵时,我们得到近似的原始矩阵。现在,为了提高模型的准确性,即学习最优的因子向量 P 和 Q,我们定义了一个优化函数,如前一个公式所示,该函数最小化了原始评分矩阵与潜在矩阵乘积后的结果之间的正则化平方误差。前一个方程的后半部分是施加的正则化,以避免过拟合。

交替最小二乘法是用于最小化上述损失函数的优化技术。通常,我们使用随机梯度下降来优化损失函数。对于 Spark 推荐模块,ALS 技术已被用于最小化损失函数。

在 ALS 方法中,我们通过交替固定两个因子向量中的一个来计算最优的潜在因子向量,即通过固定物品-潜在特征向量作为常数来计算用户潜在向量,反之亦然。

ALS 方法的主要优点如下:

  • 此方法可以轻松并行化

  • 在大多数情况下,我们在推荐引擎问题中处理稀疏数据集,与随机梯度下降相比,ALS 在处理稀疏性方面更有效率。

spark.ml 中推荐引擎模块的 Spark 实现具有以下参数:

  • numBlocks: 这是指用户和物品将被划分成多少个块以并行化计算(默认为 10)

  • rank: 这指的是模型中的潜在因子数量(默认为 10)

  • maxIter: 这是运行的最大迭代次数(默认为 10)

  • regParam: 此参数指定 ALS 中的正则化参数(默认为 0.1)

  • implicitPrefs: 此参数指定是否使用显式反馈的 ALS 变体或适用于隐式反馈数据的变体(默认为 false,表示使用显式反馈)

  • alpha: 这是适用于 ALS 隐式反馈变体的参数,它控制偏好观察的基线置信度(默认为 1.0)

  • nonnegative: 此参数指定是否为最小二乘法使用非负约束(默认为 false)

基于 pyspark 的模型推荐系统

用例的软件细节如下:

  • Spark 2.0

  • Python API: pyspark

  • Centos 6

  • Python 3.4

使用 pyspark 启动 Spark 会话,如下所示:

pyspark

以下截图显示了运行上述pyspark命令创建的 Spark 会话:

基于 pyspark 的模型推荐系统

要使用 Spark 构建推荐引擎,我们利用 Spark 2.0 的能力,如 DataFrame、RDD、Pipelines 和 Transforms,这些在 Spark MLlib 中已有解释。

与早期启发式方法不同,例如用于构建推荐引擎的 k-最近邻方法,在 Spark 中,使用矩阵分解方法构建推荐引擎,交替最小二乘(ALS)方法用于生成基于模型的协同过滤。

MLlib 推荐引擎模块

在本节中,我们将了解 MLlib 推荐引擎模块中存在的不同方法。当前的推荐引擎模块帮助我们使用交替最小二乘矩阵分解模型构建基于模型的协同过滤方法来生成推荐。

构建协同过滤的主要方法如下:

  • ALS(): 调用 ALS() 构造函数并使用所有必需的参数创建其实例,例如用户列名、项目列名、评分列名、排名、正则化参数(regParam)、最大迭代次数(maxIter)等。

  • fit(): 使用 fit() 方法生成模型。此方法接受以下参数:

  • Transform(): 使用 transform() 方法生成预测。transform() 方法接受以下内容:

    • 测试数据(DataFrame 数据类型)

    • 可选的附加参数,其中包含先前定义的参数。

    • 返回预测(DataFrame 对象)

推荐引擎方法

现在我们来实际实现推荐引擎。我们使用以下方法在 Spark 中构建推荐引擎:

  1. 启动 Spark 环境。

  2. 加载数据。

  3. 探索数据源。

  4. 使用 MLlib 推荐引擎模块和 ALS 实例生成推荐。

  5. 生成推荐。

  6. 评估模型。

  7. 使用交叉验证方法,应用参数调整模型调整参数并选择最佳模型,然后生成推荐。

实现

与任何其他推荐引擎一样,第一步是将数据加载到分析环境中(在我们的案例中是 Spark 环境)。当我们启动 2.0 版本的 Spark 环境时,SparkContext 和 SparkSession 将在加载时创建。

在我们进入实现部分之前,让我们回顾一下数据。在本章中,我们使用 MovieLens 100K 数据集来构建基于用户和基于物品的协同过滤推荐引擎。该数据集包含 943 个用户对 1,682 部电影的评分。评分在 1-5 的范围内。

作为第一步,我们将使用 SparkContext (sc) 将数据加载到 Spark 环境中。

数据加载

加载数据,请运行以下命令:

data = sc.textFile("~/ml-100k/udata.csv")

加载的数据将是 spark RDD 类型 - 运行以下命令以找出数据对象的数据类型:

type(data)
<class 'pyspark.rdd.RDD'>

加载数据的总长度如下所示:

data.count()
100001

要加载已加载数据中的第一条记录:

data.first()
'UserID\tItemId \tRating\tTimestamp'

我们可以看到,标题信息位于数据对象的第一行,由 \t 分隔;数据对象的列名为 UserIDItemIdRatingTimestamp

对于我们的目的,我们不需要时间戳信息,因此我们可以从数据 RDD 中删除此字段:

要检查数据 RDD 的前 5 行,我们使用 take() 动作方法:

data.take(5)
['UserID\tItemId \tRating\tTimestamp', '196\t242\t3\t881250949', '186\t302\t3\t891717742', '22\t377\t1\t878887116', '244\t51\t2\t880606923']

MLlib 推荐引擎模块期望数据不包含任何标题信息。因此,让我们删除标题信息,即从数据 RDD 对象中删除第一行,如下所示:

从数据 RDD 对象中提取第一行:

header = data.first()

使用 filter() 方法和 lambda 表达式从数据中删除第一行标题。下面的 lambda 表达式应用于每一行,并且每一行都与标题进行比较,以检查提取的行是否为标题。如果发现提取的行是标题,则该行将被过滤:

data = data.filter(lambda l:l!=header)

现在我们检查数据 RDD 对象的计数;它已从 100001 减少到 100000:

data.count()
100000

现在我们检查第一行,我们可以观察到标题已经成功删除:

data.first()
'196\t242\t3\t881250949'

现在我们已经将数据加载到 Spark 环境中,让我们将数据格式化为适当的形状,如下所示:

  1. 加载用于构建推荐引擎所需的函数,例如 ALS、矩阵分解模型和 Rating 函数,来自 MLlib 推荐模块。

  2. 从数据 RDD 中提取每一行,并使用 map() 和 lambda 表达式通过 \t 分隔每个列。

  3. 在结果集中,让我们为之前步骤中提取的每一行创建一个 Rating 行对象

  4. 当以下表达式应用于整个数据集时,将创建一个管道化的 RDD 对象:数据加载

使用 type 检查 ratings 对象的数据类型:

type(ratings)
<class 'pyspark.rdd.PipelinedRDD'>

通过运行以下代码检查 ratings PipelinedRDD 对象的前 5 条记录:

ratings.take(5)

[Rating(user=196, product=242, rating=3.0), Rating(user=186, product=302, rating=3.0), Rating(user=22, product=377, rating=1.0), Rating(user=244, product=51, rating=2.0), Rating(user=166, product=346, rating=1.0)]

我们可以从前面的结果中观察到,原始原始数据 RDD 对象中的每一行都变成了 Rating 行对象的一种列表,堆叠成 PipelinedRDD。

数据探索

现在我们已经加载数据,让我们花些时间探索数据。让我们使用 Spark 2.0 DataFrame API 功能来探索数据:

通过首先选择'user'列,然后使用distinct()函数来移除重复的userId来计算唯一用户的总数:

df.select('user').distinct().show(5)

以下截图显示了上一个查询的结果:

数据探索

唯一用户的总数:

df.select('user').distinct().count()
943

唯一物品的总数:

df.select('product').distinct().count()
1682

显示前 5 个唯一产品:

df.select('product').distinct().show(5)

以下截图显示了上一个查询的结果:

数据探索

每个用户评价的产品数量:

df.groupBy("user").count().take(5)

[Row(user=26, count=107), Row(user=29, count=34), Row(user=474, count=327), Row(user=191, count=27), Row(user=65, count=80)]

之前的结果解释说用户 26 评价了 107 部电影,用户 29 评价了 34 部电影。

每个评分类型的记录数:

df.groupBy("rating").count().show()

以下截图显示了之前查询的结果:

数据探索

在以下代码中,我们使用了 Python 中的numpy科学计算包,用于处理数组:matplotlibe - Python 中的可视化包:

import numpy as np 
import matplotlib.pyplot as plt
n_groups = 5 
x = df.groupBy("rating").count().select('count') 
xx = x.rdd.flatMap(lambda x: x).collect() 
fig, ax = plt.subplots() 
index = np.arange(n_groups) 
bar_width = 1 
opacity = 0.4 
rects1 = plt.bar(index, xx, bar_width, 
                 alpha=opacity, 
                 color='b', 
                 label='ratings') 
plt.xlabel('ratings') 
plt.ylabel('Counts') 
plt.title('Distribution of ratings') 
plt.xticks(index + bar_width, ('1.0', '2.0', '3.0', '4.0', '5.0')) 
plt.legend() 
plt.tight_layout() 
plt.show() 

数据探索

每个用户的评分统计:

df.groupBy("UserID").count().select('count').describe().show()

数据探索

每个用户的评分计数:

 df.stat.crosstab("UserID", "Rating").show() 

数据探索

每个用户给出的平均评分:

df.groupBy('UserID').agg({'Rating': 'mean'}).take(5)

[Row(UserID=148, avg(Rating)=4.0), Row(UserID=463, avg(Rating)=2.8646616541353382), Row(UserID=471, avg(Rating)=3.3870967741935485), Row(UserID=496, avg(Rating)=3.0310077519379846), Row(UserID=833, avg(Rating)=3.056179775280899)] 

每部电影的平均评分:

df.groupBy('ItemId ').agg({'Rating': 'mean'}).take(5)

[Row(ItemId =496, avg(Rating)=4.121212121212121), Row(ItemId =471, avg(Rating)=3.6108597285067874), Row(ItemId =463, avg(Rating)=3.859154929577465), Row(ItemId =148, avg(Rating)=3.203125), Row(ItemId =1342, avg(Rating)=2.5)] 

构建基本的推荐引擎

使用randomSplit()方法如下随机地将原始数据分为训练集和测试集:

(training, test) = ratings.randomSplit([0.8, 0.2])

计算训练数据集中的实例数量:

training.count()
80154    

计算测试集中的实例数量:

test.count()
19846

现在我们将使用 Spark MLlib 库中可用的 ALS 算法构建一个推荐引擎模型。

为此,我们使用以下方法和参数:

  1. 将 ALS 模块加载到 Spark 环境中。

  2. 调用ALS.train()方法来训练模型。

  3. 将所需的参数,如排名、迭代次数(maxIter)和训练数据传递给ALS.train()方法。

让我们了解这些参数:

  • Rank:此参数是模型中要使用的用户和物品的潜在因子数量。默认值为 10。

  • maxIter:这是模型必须运行的迭代次数。默认值为 10。

使用交替最小二乘法构建推荐模型:

设置 rank 和 maxIter 参数:

rank = 10
numIterations = 10

使用训练数据、排名、maxIter 参数调用train()方法,model = ALS.train(training, rank, numIterations):

16/10/04 11:01:34 WARN BLAS: Failed to load implementation from: com.github.fommil.netlib.NativeSystemBLAS
16/10/04 11:01:34 WARN BLAS: Failed to load implementation from: com.github.fommil.netlib.NativeRefBLAS
16/10/04 11:01:34 WARN LAPACK: Failed to load implementation from: com.github.fommil.netlib.NativeSystemLAPACK
16/10/04 11:01:34 WARN LAPACK: Failed to load implementation from: com.github.fommil.netlib.NativeRefLAPACK
16/10/04 11:01:37 WARN Executor: 1 block locks were not released by TID = 122:
[rdd_221_0]
16/10/04 11:01:37 WARN Executor: 1 block locks were not released by TID = 123:
[rdd_222_0]
16/10/04 11:01:37 WARN Executor: 1 block locks were not released by TID = 124:
[rdd_221_0]
16/10/04 11:01:37 WARN Executor: 1 block locks were not released by TID = 125:
[rdd_222_0]

如下检查模型,我们观察到创建了Matrixfactorizationmodel对象:

model

进行预测

现在我们已经创建了模型,让我们预测我们之前创建的测试集中的评分值。

ALS 模块在以下章节中提供了许多方法,用于进行预测、推荐用户、向用户推荐项目、用户特征、项目特征等。让我们逐一运行这些方法。

在我们进行预测之前,我们首先以预测方法可接受的方式创建测试数据,如下所示:

以下代码提取测试数据中的每一行,并提取userID, ItemID,然后将其放入testdata PipelinedRDD对象中:

testdata = test.map(lambda p: (p[0], p[1]))

type(testdata)
<class 'pyspark.rdd.PipelinedRDD'>

以下代码显示了原始测试数据样本:

test.take(5)

[Rating(user=119, product=392, rating=4.0), Rating(user=38, product=95, rating=5.0), Rating(user=63, product=277, rating=4.0), Rating(user=160, product=234, rating=5.0), Rating(user=225, product=193, rating=4.0)]

以下代码显示了用于预测的格式化数据:

testdata.take(5)

[(119, 392), (38, 95), (63, 277), (160, 234), (225, 193)]

预测方法如下:

  • predict(): 预测方法将为给定用户和项目的评分进行预测,如下所示:

当我们想要对用户和物品的组合进行预测时,使用此方法:

pred_ind = model.predict(119, 392) 

我们可以观察到,用户119和电影392的预测值为4.3926091845289275:只需查看测试数据中相同组合的原始值:

pred_ind 

4.3926091845289275 

  • predictall(): 当我们希望一次性预测所有测试数据的值时,使用此方法,如下所示:
predictions = model.predictAll(testdata).map(lambda r: ((r[0], r[1]), r[2])) 

使用以下代码检查数据类型:

type(predictions) 
<class 'pyspark.rdd.PipelinedRDD'> 

使用以下代码显示前五个预测:

 predictions.take(5) 

[((268, 68), 3.197299431949281), ((200, 68), 3.6296857016488357), ((916, 68), 3.070451877410571), ((648, 68), 2.165520614428771), ((640, 68), 3.821666263132798)] 

基于用户的协同过滤

现在,让我们为用户推荐物品(电影)。ALS 推荐模块包含recommendProductsForUsers()方法,用于为用户生成 top-N 物品推荐。

recommendProductsForUsers()方法接受整数作为输入参数,该参数表示前 N 个推荐;例如,要生成对用户的 top 10 推荐,我们将 10 作为值传递给recommendProductsForUsers()方法,如下所示:

recommedItemsToUsers = model.recommendProductsForUsers(10)

使用以下代码可以展示为所有 943 个用户生成推荐:

recommedItemsToUsers.count()  
943

让我们看看前两个用户(96 和 784)的推荐:

recommedItemsToUsers.take(2)

[
(96, (Rating(user=96, product=1159, rating=11.251653489172302), Rating(user=96, product=962, rating=11.1500279633824), Rating(user=96, product=534, rating=10.527262244626867), Rating(user=96, product=916, rating=10.066351313580977), Rating(user=96, product=390, rating=9.976996795233937), Rating(user=96, product=901, rating=9.564128162876036), Rating(user=96, product=1311, rating=9.13860044421153), Rating(user=96, product=1059, rating=9.081563794413025), Rating(user=96, product=1178, rating=9.028685203289745), Rating(user=96, product=968, rating=8.844312806737918)
)),
 (784, (Rating(user=784, product=904, rating=5.975314993539809), Rating(user=784, product=1195, rating=5.888552423210881), Rating(user=784, product=1169, rating=5.649927493462845), Rating(user=784, product=1446, rating=5.476279163198376), Rating(user=784, product=1019, rating=5.303140289874016), Rating(user=784, product=1242, rating=5.267858336331315), Rating(user=784, product=1086, rating=5.264190584020031), Rating(user=784, product=1311, rating=5.248377920702441), Rating(user=784, product=816, rating=5.173286729120303), Rating(user=784, product=1024, rating=5.1253425029498985)
))
]

模型评估

现在,让我们评估模型精度。为此,我们选择均方根误差方法来计算模型精度。我们可以手动完成,如下所示,或者调用 Spark MLlib 中可用的定义好的函数:通过连接原始评分和预测来创建ratesAndPreds对象:

ratesAndPreds = ratings.map(lambda r: ((r[0], r[1]), r[2])).join(predictions)

以下代码将计算均方误差:

MSE = ratesAndPreds.map(lambda r: (r[1][0] - r[1][1])**2).mean()

[Stage 860:>                                               (0 + 4) / 6]

Mean Squared Error = 1.1925845065690288  

from math import sqrt

rmse = sqrt(MSE)
rmse
1.092055175606539

模型选择和超参数调整

任何机器学习任务中最重要的步骤是使用模型评估或模型选择来找到最适合数据的最佳参数。Spark 提供了调整和模型评估的基础设施,用于单个算法或整个模型构建管道。用户可以调整整个管道模型或调整管道的各个组件。MLlib 提供了模型选择工具,如CrossValidator类和TrainValidationSplit类。

上述提到的类需要以下项目:

  • 估算器算法或管道进行调整

  • ParamMaps 集合:可供选择的参数,有时称为搜索的参数网格

  • 评估器:衡量拟合模型在保留的测试数据上的表现好坏的指标

在高层次上,这些模型选择工具的工作方式如下:

  • 他们将输入数据分割成独立的训练和测试数据集

  • 对于每个(训练和测试)对,他们遍历ParamMaps集合

  • 对于每个 ParamMap,他们使用这些参数拟合 Estimator,获取拟合的模型,并使用 Evaluator 评估模型性能。

  • 他们选择由表现最佳的一组参数产生的模型

MLlib 支持各种用于执行评估任务的评估类,例如用于基于回归问题的 RegressionEvaluator 类,用于二元分类问题的 BinaryClassificationEvaluator 类,以及用于多类分类问题的 MulticlassClassificationEvaluator 类。为了构建参数网格,我们可以使用 paramGridBuilder 类。

交叉验证

交叉验证 方法是在评估数据挖掘模型和选择构建最佳估计模型的最佳参数时最流行的方法之一。MLlib 提供两种类型的评估类:CrossValidatorTrainValidationSplit 类。

交叉验证

CrossValidator 类接受输入数据集并将其分割成多个数据集折,这些折可以用作训练集和测试集。使用这些数据集,CrossValidator 类构建多个模型,找到最佳参数并存储在 ParamMap 中。在确定最佳 ParamMap 之后,CrossValidator 类最终使用整个数据集计算最佳模型。例如,假设我们选择了五折交叉验证;CrossValidator 类将原始数据集分割成五个子数据集,每个子数据集包含训练集和测试集。CrossValidator 类一次选择一个折集并估计模型参数。最后,CrossValidator 计算评估指标的均值,将最佳参数存储在 ParamMap 中。

训练-验证分割

Spark MLlib 提供了另一个类 TrainValidationSplit 来使用 TrainValidationSplit 估计最佳参数。与 CrossValidator 不同,这个类在单个数据集上估计最佳参数。例如,TrainValidatorSplit 类将输入数据分成大小为 3/4 的训练集和 1/4 的测试集,并使用这些集选择最佳参数。

现在,让我们了解我们之前构建的推荐引擎模型。

调优模型存在于 Spark 2.0 的 MLlib 中,并使用 DataFrame API 功能。因此,为了适应这一点,我们的第一步是将原始数据集评分转换为 DataFrame。

对于转换,我们使用 sqlContext 对象和 createDataFrame() 方法将评分 RDD 对象转换为 DataFrame 对象,如下所示:

type(ratings) 
<class 'pyspark.rdd.PipelinedRDD'> 

使用 pyspark 启动 spark 会话时创建 SQL Context 对象:

sqlContext 
<pyspark.sql.context.SQLContext object at 0x7f24c94f7d68> 

如下从评分 RDD 对象创建 DataFrame 对象:

df = sqlContext.createDataFrame(ratings) 

type(df) 

<class 'pyspark.sql.dataframe.DataFrame'> 

显示 dataframe 对象的前 20 条记录:

df.show() 

以下截图显示了先前查询的结果:

训练-验证分割

使用 randomSplit() 方法创建训练集和测试集的随机样本:

(training, test) = df.randomSplit([0.8, 0.2]) 

加载运行参数 tuningmodel 所需的模块:

from pyspark.ml.recommendation import ALS 

调用 MLlib 中可用的 ALS 方法来构建推荐引擎。以下ALS()方法仅接受训练数据的列值,如 UserID、ItemId 和 Rating。其他参数,如 rank、迭代次数、学习参数等,将作为ParamGridBuilder对象传递给交叉验证方法。

如前所述,模型调优管道需要 Estimators、一组 ParamMaps 和 Evaluators。让我们按照以下方式创建每一个:

如前所述,Estimator 对象接受算法或管道对象作为输入。让我们按照以下方式构建一个管道对象:

调用 ALS 算法:

als = ALS(userCol="user", itemCol="product", ratingCol="rating") 
als 

ALS_45108d6e011beae88f4c 

检查als对象的类型:

type(als) 
<class 'pyspark.ml.recommendation.ALS'> 

让我们看看 ALS 模型默认参数是如何设置的:

als.explainParams()
"alpha: alpha for implicit preference (default: 1.0)\ncheckpointInterval: set checkpoint interval (>= 1) or disable checkpoint (-1). E.g. 10 means that the cache will get checkpointed every 10 iterations. (default: 10)\nfinalStorageLevel: StorageLevel for ALS model factors. (default: MEMORY_AND_DISK)\nimplicitPrefs: whether to use implicit preference (default: False)\nintermediateStorageLevel: StorageLevel for intermediate datasets. Cannot be 'NONE'. (default: MEMORY_AND_DISK)\nitemCol: column name for item ids. Ids must be within the integer value range. (default: item, current: ItemId )\nmaxIter: max number of iterations (>= 0). (default: 10)\nnonnegative: whether to use nonnegative constraint for least squares (default: False)\nnumItemBlocks: number of item blocks (default: 10)\nnumUserBlocks: number of user blocks (default: 10)\npredictionCol: prediction column name. (default: prediction)\nrank: rank of the factorization (default: 10)\nratingCol: column name for ratings (default: rating, current: Rating)\nregParam: regularization parameter (>= 0). (default: 0.1)\nseed: random seed. (default: -1517157561977538513)\nuserCol: column name for user ids. Ids must be within the integer value range. (default: user, current: UserID)"

从前面的结果中,我们观察到模型将秩设置为默认值 10,最大迭代次数 maxIter 为 10,块大小 blocksize 也为 10:

创建管道对象并将创建的 als 模型作为管道中的一个阶段:

from pyspark.ml import Pipeline

pipeline = Pipeline(stages=[als])
 type(pipeline)
<class 'pyspark.ml.pipeline.Pipeline'>

设置 ParamMaps/参数

让我们仔细和逻辑地观察ALS()方法,并推断出可用于参数调整的参数:

rank: 我们知道 rank 是用户和物品的潜在特征数量,默认值为 10,但如果我们没有给定数据集的最佳潜在特征数量,则可以通过在 8 和 12 之间给出一个值范围来调整模型,选择权留给用户。由于计算成本,我们将值限制在 8-12 之间,但读者可以自由尝试其他值。

MaxIter: MaxIter 是模型运行次数;默认设置为 10。我们可以选择这个参数进行调优,因为我们不知道模型表现良好的最佳迭代次数;我们在 10 和 15 之间进行选择。

reqParams: regParams 是介于 1 和 10 之间的学习参数集。

加载CrossValidationParamGridBuilder模块以创建参数范围:

from pyspark.ml.tuning import CrossValidator, ParamGridBuilder

paramMapExplicit = ParamGridBuilder() \
                    .addGrid(als.rank, [8, 12]) \
                    .addGrid(als.maxIter, [10, 15]) \
                    .addGrid(als.regParam, [1.0, 10.0]) \
                    .build()

设置评估器对象

如前所述,评估器对象在交叉验证方法中设置评估指标以在多次运行中评估模型:

加载RegressionEvaluator模型:

from pyspark.ml.evaluation import RegressionEvaluator

使用RegressionEvaluator()方法调用,将评估指标设置为rmse并将evaluation列设置为 Rating:

evaluatorR = RegressionEvaluator(metricName="rmse", labelCol="rating")

现在我们已经为运行交叉验证方法准备了所有必需的对象,即EstimatorparamMapsEvaluator,让我们运行模型。

交叉验证方法从所有执行过的模型中给出最佳最优模型:

cvExplicit = CrossValidator(estimator=als, estimatorParamMaps=paramMap, evaluator=evaluatorR,numFolds=5)

使用fit()方法运行模型:

cvModel = cvExplicit.fit(training)

[Stage 897:============================>                           (5 + 4) / 10]
[Stage 938:==================================================>     (9 + 1) / 10]
[Stage 1004:>(0 + 4) / 10][Stage 1005:> (0 + 0) / 2][Stage 1007:>(0 + 0) / 10]  
[Stage 1008:>                                                     (3 + 4) / 200]

preds = cvModel.bestModel.transform(test)
evaluator = RegressionEvaluator(metricName="rmse", labelCol="rating",predictionCol="prediction")
rmse = evaluator.evaluate(pred)
print("Root-mean-square error = " + str(rmse))
 rmse                                                                        

0.924617823674082

摘要

在本章中,我们学习了使用 ALS 算法的矩阵分解方法进行基于模型的协同过滤。我们使用 Python API 访问 Spark 框架并运行了 ALS 协同过滤。在本章的开头,我们回顾了 Spark 的基础知识,这些基础知识是运行推荐引擎所必需的,例如 Spark 是什么,Spark 生态系统,Spark 的组件,SparkSession,DataFrames,RDD 等等。正如当时所解释的,我们探索了 MovieLens 数据,构建了一个基本的推荐引擎,评估了模型,并使用参数调整来改进模型。在下一章中,我们将学习如何使用图数据库 Neo4j 构建推荐。

第八章:使用 Neo4j 构建实时推荐

我们生活的世界是一个庞大且相互联系的地方。这个世界上存在的任何事物都以某种方式相互连接。居住在这个世界中的实体之间存在关系和联系。

人类大脑试图以网络和关系的形式存储或提取信息。这可能是一种更优的数据表示方式,以便信息的存储和检索既快又高效。如果我们有一个以类似方式工作的系统会怎样。我们可以使用图;它们是表示数据的一种系统性和方法性的方法。

在我们继续本章内容之前,理解图的概念的背景和必要性是至关重要的。

图论背后的概念归功于 18 世纪的数学家莱昂哈德·欧拉,他解决了被称为柯尼斯堡桥问题这一古老的难题,这本质上是一个路径查找问题。尽管我们不会进一步探讨这个问题,但我建议读者尝试理解欧拉是如何提出一种新的范式方法来理解和解决这个问题的。

图在当今世界的各个方面都有所体现,并且是处理数据最有效和最自然的方式之一。

图可以表示两个或更多现实世界实体(作为节点)如何相互连接。我们还学习到它们如何相互关联,以及这如何有助于以快速、高效、直观的方式传递信息。由于图系统允许我们以表达性和结构化的方式表达任何事物,因此我们可以将这些系统应用于社交网络、医学、科学技术等多个领域。

为了更好地理解图表示,我们可以以 Facebook 上的社交网络为例。让我们假设有三个朋友JohnPaulKrish在 Facebook 上相互连接。JOHN-KRISH 是互为朋友,PAUL-KRISH 是互为朋友,PAUL 是John**FriendOf**。我们如何表示这些信息?请看以下图表:

使用 Neo4j 构建实时推荐

我们难道不觉得上述表示是表示数据和其关系最有效和最自然的方式之一吗?在先前的图表中,JOHN-KRISH-PAUL 是代表用户实体的节点,而FriendOf箭头是表示节点之间关系的边。我们还可以将用户节点的人口统计细节(如年龄和关系的细节,如 FriendSince)作为图中的属性存储。通过应用图论概念,我们可以在网络中找到类似用户,或者在朋友网络中向用户推荐新朋友。我们将在后面的章节中了解更多关于这方面的内容。

区分不同的图数据库

图数据库彻底改变了人们发现新产品和相互分享信息的方式。在人类大脑中,我们以图、关系和网络的形式记住人、事物、地点等。当我们尝试从这些网络中获取信息时,我们会直接前往所需的连接或图,并准确获取信息。以类似的方式,图数据库允许我们将用户和产品信息以节点和边(关系)的形式存储在图中。搜索图数据库的速度很快。

图数据库是一种使用图论来存储、映射和查询关系的 NoSQL 数据库。图数据库在管理高度连接的数据和管理复杂查询方面表现出色。它们主要用于分析数据之间的相互关系。在这里,我们优先考虑关系,因此我们不必像在 SQL 中那样担心外键。

图数据库主要由节点和边组成,其中节点代表实体,边代表它们之间的关系。在先前的图中,圆圈是代表实体的节点,连接节点的线条称为边 - 这些代表关系。箭头的方向遵循信息的流动。通过展示图中的所有节点和链接,它帮助用户获得结构的全局视图。

Neo4j、FlockDB、AllegroGraph、GraphDB 和 InfiniteGraph 是一些可用的图数据库。让我们看看 Neo4j,这是其中最受欢迎的之一,由 Neo Technology 制作。

Neo4j 之所以如此受欢迎,是因为它的强大、快速和可扩展性。它主要用 Scala 和 Java 实现。它提供社区版和企业版。企业版具有与社区版相同的功能,还增加了企业级可用性、管理和扩展能力。在关系型数据库管理系统中,随着关系数量的增加,性能呈指数级下降,而在 Neo4j 中则是线性的。以下图像显示了各种图数据库:

区分不同的图数据库

标签属性图

在介绍部分,我们看到了一个三个朋友的社会网络表示的例子。这种包含实体/节点之间的有向连接、节点之间的关系以及与节点和关系关联的属性的图数据表示称为标签属性图数据模型

标签属性图数据模型具有以下特性:

  • 图包含节点和关系

  • 节点可以包含属性(键值对)

  • 节点可以标记一个或多个标签

  • 关系被命名且具有方向性,并且始终有一个起始节点和一个结束节点

  • 关系也可能包含属性

列出的概念将在以下章节中解释。

理解 GraphDB 核心概念

以下列表列举了图的所有元素:

  • 节点:节点是图的基本单元。节点是图中的顶点。它主要指的是被引用的主要对象。节点可以包含标签和属性。从故事中,我们可以提取三个不同的对象并创建三个节点。其中两个是朋友,另一个是电影。

  • 标签:标签是用来区分相同类型对象的方式。标签通常被赋予具有相似特征的每个节点。节点可以有多个标签。在示例故事中,我们给出了PERSONMOVIE的标签。这优化了图遍历,并有助于高效地逻辑查询模型。

  • 关系:关系是两个节点之间的边。它们可以是单向的也可以是双向的。它们还可以包含创建关系时使用的属性。关系是有名称和方向的,并且始终有一个起始节点和一个结束节点。例如,两个朋友之间存在“Friend Of”的关系。这显示了不同节点之间的连接。每个朋友与电影节点之间也存在“Has Watched”的关系。

  • 属性:属性是键值对。属性可以用于节点和关系。它们用于保存有关特定节点或关系的详细信息。在示例中,Person 节点具有姓名和年龄属性。这些属性用于区分不同的节点。关系“Has Watched”也有日期和评分属性。

在以下图中,JOHNKRISHPAUL是映射为用户标签的节点。同时,观察显示关系的边。节点和关系都可以有属性来进一步描述它们:

理解 GraphDB 核心概念

Neo4j

Neo4j 是一个用 Java 和 Scala 实现的开源图数据库。Neo4j 高效地实现了标签属性图模型。像任何其他数据库一样,Neo4j 提供 ACID 事务、运行时故障转移和集群支持,使其能够开发生产就绪的应用程序。这种图数据库架构旨在高效存储数据并加快节点和关系之间的遍历。为了处理存储、检索和遍历数据,我们使用CYPHER 查询语言,这是基于模式的 Neo4j 查询语言。

CYPHER 查询语言

Cypher 是 Neo4j 的查询语言,它遵循类似 SQL 的查询。它是一种声明性查询语言,专注于从图中检索什么,而不是如何检索它。我们知道 Neo4j 属性图由节点和关系组成;尽管这些节点和关系是基本构建块,但图数据库的真实力量在于识别节点和关系之间存在的底层模式。这种图数据库(如 Neo4j)的模式提取能力帮助我们非常快速和高效地执行复杂操作。

Neo4j 的 Cypher 查询语言基于模式。这些模式用于匹配底层图结构,以便我们可以利用模式进行进一步处理,例如在我们的案例中构建推荐引擎。

以下是一个使用 Cypher 查询提取模式的示例。以下 Cypher 查询匹配用户对之间的所有 friendof 模式,并将它们作为图返回:

Cypher 查询语言

Cypher 查询基础

在我们开始使用 Neo4j 构建推荐之前,让我们先了解一下 Cypher 查询的基础。正如我们之前提到的,Cypher 是 Neo4j 的查询语言,它遵循类似 SQL 的查询。作为一种声明性语言,Cypher 专注于从图中检索什么,而不是如何检索。Cypher 的关键原则和能力如下:

  • Cypher 在图中的节点和关系之间匹配关键模式,以从图中提取信息。

  • Cypher 具有许多与 SQL 类似的特性,如创建、删除和更新。这些操作应用于节点和关系以获取信息。

  • 与 SQL 类似的索引和约束也存在于其中。

节点语法

Cypher 使用成对的括号 () 或带有文本的成对括号来表示节点。此外,我们可以分配标签,节点的属性作为键值对给出。

以下是一个示例,帮助你更好地理解概念。在以下查询中,节点使用 ()(user) 表示,标签使用 u(u:user),节点的属性则通过键值对分配,例如 (u:user{name:'Toby'})

() 
(user) 
(u:user) 
(u:user{name:'Toby'}) 

关系语法

Cypher 使用 -[]-> 来表示两个节点之间的关系。这些关系允许开发者表示节点之间的复杂关系,使它们更容易阅读或理解。

让我们来看以下示例:

 -[]-> 
(user) -[f:friendof]->(user) 
(user) -[f:friendof {since: 2016}]->(user) 

在前面的示例中,在两个用户节点之间建立了 friendof 关系,并且该关系具有属性 since:2016

构建你的第一个图

现在我们已经看到了节点语法和关系语法,让我们通过创建一个类似于以下图表的 Facebook 社交网络图来练习我们到目前为止所学的内容:

构建你的第一个图

为了创建上述图,我们需要以下步骤:

  1. 创建 3 个节点 Person,标签为 JOHN、PAUL、KRISH

  2. 使用CREATE子句在 3 个节点之间创建关系

  3. 设置属性

  4. 使用所有模式显示结果

创建节点

我们使用CREATE子句来创建图元素,如节点和关系。以下示例展示了如何创建一个标记为john的单个节点 Person,并具有属性名称JOHN。当我们运行以下查询在 Neo4j 浏览器中时,我们得到以下截图所示的图:

CREATE (john:Person {name:"JOHN"})  RETURN  john 

创建节点

注意

RETURN子句有助于返回结果集,即节点 - 人员

我们不仅可以创建一个节点,还可以按照以下方式创建多个节点:

CREATE (paul:Person {name:"PAUL"})  
CREATE (krish:Person {name:"KRISH"})  

早期代码将创建三个节点,标记为JOHNPAULKRISH的人员。让我们看看我们到目前为止创建了什么;为了查看结果,我们必须使用MATCH子句。MATCH子句将查找模式,如具有标签名称kpj的人员节点及其相应的标签:

MATCH(k:Person{name:'KRISH'}),(p:Person{name:'PAUL'}),(j:Person{name:'JOHN'}) RETURN k,p,j 

创建节点

创建关系

通过创建节点,我们已经完成了一半。现在,让我们通过创建关系来完成剩余部分。

创建关系的说明如下:

  • 使用MATCH子句从数据库中提取节点

  • 使用CREATE子句在Persons之间创建所需的关系

在以下查询中,我们正在提取所有Person节点,然后在节点之间创建名为FRIENDOF的关系:

MATCH(k:Person{name:'KRISH'}),(p:Person{name:'PAUL'}),(j:Person{name:'JOHN'})  
CREATE (k)-[:FRIENDOF]->(j) 
CREATE (j)-[:FRIENDOF]->(k) 
CREATE (p)-[:FRIENDOF]->(j) 
CREATE (p)-[:FRIENDOF]->(k) 
CREATE (k)-[:FRIENDOF]->(p) 

以下截图显示了运行先前查询时的结果:

创建关系

现在我们已经创建了所有必要的节点和关系。为了查看我们取得了什么成果,运行以下查询,该查询显示节点和节点之间的关系:

match(n:Person)-[f:FRIENDOF]->(q:Person) return f 

创建关系

设置关系的属性

最后一步是设置节点标签和关系的属性,具体说明如下:

我们使用SET子句来设置属性。对于设置关系的属性,我们需要遵循两个步骤:

  1. 提取所有关系,FRIENDOF

  2. 使用SET子句将这些关系的属性设置为这些关系

在以下示例中,我们将属性设置为KRISHPAUL之间的FRIENDOF关系,属性friendsince如下所示:

MATCH (k:Person{name:'KRISH'})-[f1:FRIENDOF]-> (p:Person{name:'PAUL'}), 
(k1:Person{name:'KRISH'})<-[f2:FRIENDOF]- (p1:Person{name:'PAUL'}) 
SET f1.friendsince = '2016', f2.friendsince = '2015' 

设置关系的属性

注意

在之前的查询中,()-[]->模式提取关系KrishfriendOfPaul,而() <- [] -模式提取关系PaulKrishfriendOf

让我们按照以下方式显示到目前为止的结果:

match(n:Person)-[f:FRIENDOF]->(q:Person) return f 

以下图显示了在先前查询中添加的节点、关系和属性。

设置关系的属性

在前面的图中,我们可以看到对于KRISHPAULFRIENDOF关系的属性已设置为friendsince

同样,我们可以将属性设置为节点,如下所示:

MATCH(k:Person{name:'KRISH'}),(p:Person{name:'PAUL'}),(j:Person{name:'JOHN'})  
SET k.age = '26' ,p.age='28', j.age='25',k.gender='M',p.gender='M',j.gender='M' 

设置关系的属性

使用以下查询来验证结果,该查询显示节点、关系、标签、节点属性和关系属性:

match(n:Person)-[f:FRIENDOF]->(q:Person) return f 

设置关系的属性

从 csv 加载数据

在上一节中,我们手动创建了节点、关系和属性。大多数时候,我们通过从 csv 文件加载数据来创建节点。为了实现这一点,我们使用 Neo4j 浏览器中现成的LOAD CSV命令来加载数据。

以下截图显示了本节将使用的数据集,其中包含用户电影评分数据。

从 csv 加载数据

以下查询用于加载以下 csv 数据:

LOAD CSV WITH HEADERS FROM 'file:///C:/ Neo4J/test.csv' AS RATINGSDATA RETURN RATINGSDATA 

在前面的查询中:

  • HEADERS 关键字允许我们要求查询引擎将第一行视为标题信息

  • WITH 关键字与返回关键字类似;它明确地分隔查询的部分,并允许我们定义应该将哪些值或变量携带到查询的下一部分

  • AS 关键字用于为变量创建别名

当我们运行上述查询时,会发生两件事:

  • CSV 数据将被加载到图数据库

  • RETURN 子句将显示加载的数据,如下截图所示:从 csv 加载数据

Neo4j Windows 安装

在本节中,我们将看到如何为 Windows 安装 Neo4j。我们可以从以下 URL 下载 Neo4j Windows 安装程序:

neo4j.com/download/

Neo4j Windows 安装

下载安装程序后,点击安装程序以获取以下屏幕,继续进行安装:

Neo4j Windows 安装

安装成功后,启动 Neo4j 社区版。第一次您将看到以下屏幕,要求您选择一个目录来存储图数据库,然后点击启动

Neo4j Windows 安装

在我们的案例中,我们选择了默认目录,其中创建了graphdb数据库,如下所示:

C:\Users\Suresh\Documents\Neo4J\default.graphdb 

点击前面的截图中的启动按钮后,Neo4j 将被启动,并显示如下。我们现在可以开始使用 Neo4j 工作了。

Neo4j Windows 安装

现在我们已经启动了 Neo4j,我们可以通过以下方式从浏览器访问它:

http://localhost:7474

在 Linux 平台上安装 Neo4j

在本节中,我们将学习如何在 CentOS Linux 平台上下载和安装 Neo4j。

下载 Neo4j

我们可以从 Neo4j 主页下载 Neo4j 3 Linux 源文件的最新版本:

Neo4J.com/

点击以下显示的页面上的下载 Neo4J按钮:

下载 Neo4j

注意

或者你可以直接从以下 URL 下载:info.Neo4J.com/download-thanks.html?edition=community&release=3.0.6&flavour=unix&_ga=1.171681440.1829638272.1475574249

这将下载一个tar文件 - Neo4J-community-3.0.6-unix.tar.gz,如下截图所示:

下载 Neo4j

注意

我们可以在Neo4J.com/developer/get-started/找到开发者资源

设置 Neo4j

解压tar文件,你将得到一个名为Neo4J-community-3.0.6的文件夹,其中包含以下文件:

设置 Neo4j

从命令行启动 Neo4j

确保你在你的电脑上安装了 Java 8,因为 Neo4j 3.0 版本需要 Java 8。在安装之前检查 Neo4j 的要求。

一旦你安装了 Java 8,我们就可以继续运行我们的 Neo4j 实例,但在那之前,让我们按照以下方式在bashrc文件中设置Neo4J路径:

gedit ~/.bashrc 
export NEO4J_PATH=/home/1060929/Softwares/Neo4J/Neo4J-community-3.0.6 
export PATH=$PATH:$NEO4J_PATH/bin 
source ~/.bashrc 

我们使用以下命令在命令行中启动Neo4j

Neo4J start 

从命令行启动 Neo4j

我们可以观察到 Neo4j 已经启动,并且我们可以从浏览器在http://localhost:7474/访问图dbcapabilites

第一次在浏览器中运行 Neo4j 需要你设置用户名密码

从命令行启动 Neo4j

一旦我们设置了凭证,它将重定向到以下页面:

从命令行启动 Neo4j

如果你第一次使用它,请在浏览器上花些时间熟悉其功能并探索左侧面板上的不同选项。在浏览器中输入以下命令以显示连接详情:

:server connect 

从命令行启动 Neo4j

basic usage :  
getting help on Neo4J in the browser: 
:help 

构建推荐引擎

在本节中,我们将学习如何使用三种方法生成协同过滤推荐。具体如下:

  • 对共同评分的电影进行简单计数

  • 欧几里得距离

  • 余弦相似度

我想在此处强调一个观点。在早期章节中,我们了解到在构建使用启发式方法的推荐引擎时,我们使用了如欧几里得距离/余弦距离等相似度计算。并不一定只能使用这些方法;我们可以自由选择自己的方式来计算两个用户之间的接近度或提取相似度,例如,可以通过简单计数来提取两个用户之间的相似度,例如,可以通过计算两个用户共同评分的相同电影的数量来提取两个用户之间的相似度。如果两个用户共同评分的电影更多,那么我们可以假设他们彼此相似。如果两个人共同评分的电影数量较少,那么我们可以假设他们的品味不同。

这个假设是为了构建我们的第一个推荐引擎,以下进行解释:

为了构建一个基于用户过去电影评分行为的协同电影推荐引擎,我们将构建一个系统,其步骤可以总结如下:

  1. 将数据加载到环境中

  2. 提取关系和提取用户之间的相似度

  3. 推荐步骤

将数据加载到 Neo4j

尽管我们有多种将数据加载到 Neo4j 的方法,但我们使用 Load CSV 选项将数据导入浏览器工具。以下图表显示了加载 CSV 过程的流程:

将数据加载到 Neo4j

我们在本节中使用的数据集是包含用户-电影-评分的小样本数据集,如下截图所示:

将数据加载到 Neo4j

让我们将 MovieLens 数据加载到 Neo4j 浏览器工具中,如下所示:

LOAD CSV WITH HEADERS FROM file:///ratings.csv AS line 

现在让我们创建用户和电影作为节点,以及用户对电影给出的评分作为关系。

MERGE 子句将在数据中查找查询模式,如果没有找到,它将创建一个。在以下示例中,首先查找用户节点(模式),如果不存在,则创建一个。由于我们刚刚将数据加载到 GraphDB 中,我们需要创建节点并建立关系。以下代码将首先查找提到的节点和关系;如果没有找到,它将创建新的节点和关系:

LOAD CSV WITH HEADERS FROM file:///C:/Neo4J/test.csv AS line MERGE (U:USER {USERID : line.UserID}) 
WITH line, U 
MERGE (M:MOVIE {ITEMID : line.ItemId}) 
WITH line,M,U 
MERGE (U)-[:hasRated{RATING:line.Rating}]->(M); 

当我们运行前面的查询时,节点、关系和属性将创建如下截图所示:

将数据加载到 Neo4j

现在,我们将逐行理解,以使我们的理解更加清晰。

MERGE 将从原始数据的 UserID 列创建 USER 节点:

MERGE (U:USER {USERID : line.UserID}) 

With 命令将 User 节点和行对象带到查询的下一部分,如下所示:

WITH line, U 

现在,我们将使用 MERGEline.ItemId 对象创建 Movie 节点,如下所示:

MERGE (M:MOVIE {ITEMID : line.ItemId}) 

我们将电影、用户节点和行对象带到查询的下一部分,如下所示:

WITH line,M,U 

我们创建一个关系,将 USER 节点与 MOVIE 节点连接,如下所示:

MERGE (U)-[:hasRated{RATING:line.Rating}]->(M); 

现在我们已经将数据加载到 Neo4j 中,我们可以如下可视化电影评分数据,包括用户、电影和评分:

MATCH (U:USER)-[R:hasRated]->(M:MOVIE) RETURN R 

在以下图像中,所有用户以绿色创建,电影以红色创建。我们还可以看到以箭头表示的带有方向的关联关系。

将数据加载到 Neo4j

使用 Neo4j 生成推荐

我们现在已经创建了构建我们第一个推荐引擎所需的全部图,让我们开始吧。

注意

在以下查询中,COUNT() 函数将计算实例的数量,collect() 将。

以下截图将返回对样本用户 'TOBY' 的电影推荐:

match(u1:USER)-[:hasRated]->(i1:MOVIE)<-[:hasRated]-(u2:USER)- [:hasRated]->(i2:MOVIE)  
with u1,u2, count(i1) as cnt , collect(i1) as Shareditems,i2 
where not(u1-[:hasRated]->i2) and u1.USERID='Toby' and cnt> 2  
return distinct i2.ITEMID as Recommendations 

以下查询显示了在运行早期查询时对 Toby 做出的推荐:

使用 Neo4j 生成推荐

上一查询中推荐背后的概念如下:

  • 提取评分相同电影的用户对

  • 获取每对用户共同评分电影的计数

  • 共同评分电影的数量越多,两个用户之间的相似度就越高

  • 最后一步是从所有相似用户已评分但活跃用户未评分的电影中提取所有电影,并将这些新电影作为推荐提供给活跃用户。

让我们一步一步地理解我们刚才看到的查询:

  • 在第一行,对于每个已评分电影(例如MOVIE1)的用户(例如USER1),选择所有也评分了MOVIE1的用户(例如USER2)。对于这个USER2,也提取他除MOVIE1之外评分的其他电影。

  • 在第二行,我们携带相似用户(u1,u2),计算u1,u2共同评分电影的计数,并将u1,u2共同评分的电影提取到查询的下一部分。

  • 在第三行,我们现在应用一个过滤器,选择那些未被u1评分且共同评分电影计数大于两的电影。

  • 在第 4 行,我们返回u1由相似用户评分的新电影作为推荐。

使用欧几里得距离进行协同过滤

在上一节中,我们看到了如何使用简单的基于计数的简单方法构建推荐引擎来识别相似用户,然后我们从相似用户中选择活跃用户未评分或推荐的电影。

在本节中,我们不再基于简单的共同评分电影计数来计算两个用户之间的相似度,而是利用评分信息并计算欧几里得距离,以得出相似度得分。

以下 Cypher 查询将根据欧几里得相似度为用户 Toby 生成推荐:

  1. 第一步是通过电影提取共同评分用户并计算共同评分用户之间的欧几里得距离,如下所示:

            MATCH (u1:USER)-[x:hasRated]-> (b:MOVIE)<-[y:hasRated]-
              (u2:USER) 
            WITH count(b) AS CommonMovies, u1.username AS user1,
              u2.username AS user2, u1, u2,
            collect((toFloat(x.RATING)-toFloat(y.RATING))²) AS ratings,
            collect(b.name) AS movies
            WITH CommonMovies, movies, u1, u2, ratings
            MERGE (u1)-[s:EUCSIM]->(u2) SET s.EUCSIM = 1-   
              (SQRT(reduce(total=0.0, k in extract(i in ratings | 
                i/CommonMovies) | total+k))/4)
    
    

    注意

    在此代码中,我们使用reduce()extract()来计算欧几里得距离。为了应用数学计算,我们使用以下查询中的float()函数将值转换为浮点数。

    要查看用户对之间的欧几里得距离值,请运行以下查询:

            MATCH (u1:USER)-[x:hasRated]-> (b:MOVIE)<-[y:hasRated]-
              (u2:USER) 
            WITH count(b) AS CommonMovies, u1.username AS user1,    
              u2.username AS user2, u1, u2, 
            collect((toFloat(x.RATING)-toFloat(y.RATING))²) AS ratings, 
            collect(b.name) AS movies 
            WITH CommonMovies, movies, u1, u2, ratings 
            MERGE (u1)-[s:EUCSIM]->(u2) SET s.EUCSIM = 1-
              (SQRT(reduce(total=0.0, k in extract(i in ratings |   
                i/CommonMovies) | total+k))/4) return s as SIMVAL,  
                  u1.USERID as USER,u2.USERID as Co_USER;
    

    使用欧几里得距离进行协同过滤

  2. 在第二步中,我们使用公式sqrt(sum((R1-R2)(R1-R2)))计算欧几里得距离,其中R1Tobymovie1给出的评分,而R2*是其他共同评分用户对同一movie1的评分,我们选择前三个相似用户,如下所示:

            MATCH (p1:USER {USERID:'Toby'})-[s:EUCSIM]-(p2:USER) 
            WITH p2, s.EUCSIM AS sim 
            ORDER BY sim DESC 
            RETURN distinct p2.USERID AS CoReviewer, sim AS similarity 
    
    
  3. 最后一步是向 Toby 推荐或建议来自前三个相似用户的未评分电影,如下所示:

            MATCH (b:USER)-[r:hasRated]->(m:MOVIE), (b)-[s:EUCSIM]-(a:USER  
              {USERID:'Toby'}) 
            WHERE NOT((a)-[:hasRated]->(m)) 
            WITH m, s.EUCSIM AS similarity, r.RATING AS rating 
            ORDER BY m.ITEMID, similarity DESC 
            WITH m.ITEMID AS MOVIE, COLLECT(rating) AS ratings 
            WITH MOVIE, REDUCE(s = 0, i IN ratings |toInt(s) +  
              toInt(i))*1.0 / size(ratings) AS reco 
            ORDER BY recoDESC 
            RETURN MOVIE AS MOVIE, reco AS Recommendation 
    
    

    使用欧几里得距离进行协同过滤

让我们详细解释前面的查询如下:

  1. 正如我们在第一步中解释的那样,我们提取了用户共同评分的电影及其评分,如下所示:

    在我们的例子中,Toby 已经评了三部电影:《星球上的蛇》、《超人归来》和《你、我、杜普雷》。现在我们必须提取其他共同用户,他们与 Toby 共同评了这三部电影。为此,我们使用以下查询:

            MATCH (u1:USER{USERID:'Toby'})-[x:hasRated]-> (b:MOVIE)<- 
              [y:hasRated]-(u2:USER)
            return u1, u2,
            collect(b.ITEMID) AS CommonMovies,
            collect(x.RATING) AS user1Rating,
            collect(y.RATING) AS user2Rating
    

    使用欧几里得距离的协同过滤

  2. 第二步是计算其他用户对每个共同评分电影的评分与 Toby 电影的欧几里得距离,这通过以下查询计算:

            MATCH (u1:USER)-[x:hasRated]-> (b:MOVIE)<-[y:hasRated]- 
              (u2:USER) 
            WITH count(b) AS CommonMovies, u1.username AS user1, 
              u2.username AS user2, u1, u2, 
            collect((toFloat(x.RATING)-toFloat(y.RATING))²) AS ratings, 
            collect(b.name) AS movies 
            WITH CommonMovies, movies, u1, u2, ratings 
            MERGE (u1)-[s:EUCSIM]->(u2) SET s.EUCSIM = 1- 
              (SQRT(reduce(total=0.0, k in extract(i in ratings |  
                i/CommonMovies) | total+k))/4) 
    
    

    在前面的查询中,我们使用 MERGE 子句创建并合并了每个共同评分用户之间的关系,以显示两个用户之间的距离。此外,我们使用 SET 子句将关系的属性设置为 EUCSIM(表示每个共同评分用户之间的欧几里得距离)。

    现在我们已经创建了新的关系并设置了相似度距离的值,让我们查看以下查询给出的结果:

            MATCH (p1:USER {USERID:'Toby'})-[s:EUCSIM]-(p2:USER) 
            WITH p2, s.EUCSIM AS sim 
            ORDER BY sim DESC 
            RETURN distinct p2.USERID AS CoReviewer, sim AS similarity 
    
    

    以下截图显示了 Toby 与其他用户的相似度值:

    使用欧几里得距离的协同过滤

  3. 最后一步是预测 Toby 未评分的电影,然后推荐评分最高的预测项目。为此,我们采取以下步骤:

    • 提取与 Toby 相似的用户评分的电影,但不是 Toby 自己评分的电影

    • 对所有未评分电影的评分取平均值,以预测 Toby 可能对这些电影给出的评分。

    • 按照预测的评分,以降序显示排序后的结果。

    要实现这一点,请使用以下查询:

            MATCH (b:USER)-[r:hasRated]->(m:MOVIE), (b)-[s:EUCSIM]-(a:USER  
              {USERID:'Toby'}) 
            WHERE NOT((a)-[:hasRated]->(m)) 
            WITH m, s.EUCSIM AS similarity, r.RATING AS rating ORDER BY     
              similarity DESC 
            WITH m.ITEMID AS MOVIE, COLLECT(rating) AS ratings 
            WITH MOVIE, REDUCE(s = 0, i IN ratings |toInt(s) + 
              toInt(i))*1.0 / size(ratings) AS reco 
            ORDER BY reco DESC 
            RETURN MOVIE AS MOVIE, reco AS Recommendation 
    
    

    使用欧几里得距离的协同过滤

让我们逐行理解推荐查询,如下所示:

以下查询检索了与 Toby 相似的所有用户及其相似用户评分的所有电影,如下所示:

MATCH (b:USER)-[r:hasRated]->(m:MOVIE), (b)-[s:EUCSIM]-(a:USER {USERID:'Toby'}) 

WHERE NOT子句将过滤掉所有被类似用户评分但未被 Toby 评分的电影,如下所示:

WHERE NOT((a)-[:hasRated]->(m)) 

将共同用户给出的电影、相似度值和评分传递到查询的下一部分,并按降序相似度值排序,如下所示:

WITH m, s.EUCSIM AS similarity, r.RATING AS rating ORDER BY similarity DESC 

根据相似度值对结果进行排序后,我们进一步允许将电影名称和评分等值添加到查询的下一部分,如下所示:

WITH m.ITEMID AS MOVIE, COLLECT(rating) AS ratings 

这是向 Toby 推荐电影的主要步骤,通过取与 Toby 相似的用户的电影评分的平均值,并使用REDUCE子句预测未评分电影的评分,如下所示:

WITH MOVIE, REDUCE(s = 0, i IN ratings |toInt(s) + toInt(i))*1.0 / size(ratings) AS reco 

最后,我们排序最终结果,并按如下方式返回给 Toby 的顶级电影:

ORDER BY recoDESC 
RETURN MOVIE AS MOVIE, reco AS Recommendation 

使用余弦相似度的协同过滤

现在我们已经看到了基于简单计数和欧几里得距离来识别相似用户的推荐,让我们使用余弦相似度来计算用户之间的相似度。

以下查询用于创建一个名为相似度的新关系:

MATCH (p1:USER)-[x:hasRated]->(m:MOVIE)<-[y:hasRated]-(p2:USER) 
WITH SUM(toFloat(x.RATING) * toFloat(y.RATING)) AS xyDotProduct, 
SQRT(REDUCE(xDot = 0.0, a IN COLLECT(toFloat(x.RATING)) | xDot +toFloat(a)²)) AS xLength, 
SQRT(REDUCE(yDot = 0.0, b IN COLLECT(toFloat(y.RATING)) | yDot + toFloat(b)²)) AS yLength, 
p1, p2 
MERGE (p1)-[s:SIMILARITY]-(p2) 
SET s.similarity = xyDotProduct / (xLength * yLength) 

使用余弦相似度进行协同过滤

让我们按以下方式探索相似度值:

match(u:USER)-[s:SIMILARITY]->(u2:USER) return s; 

使用余弦相似度进行协同过滤

我们按以下方式计算 Toby 的相似用户:

对于活跃用户 Toby,让我们显示与其他用户之间的相似度值,如下所示:

MATCH (p1:USER {USERID:'Toby'})-[s:SIMILARITY]-(p2:USER) 
WITH p2, s.similarity AS sim 
ORDER BY sim DESC 
LIMIT 5 
RETURN p2.USERID AS Neighbor, sim AS Similarity 

以下图像显示了运行上一个 Cypher 查询的结果;结果显示了 Toby 与其他用户之间的相似度值。

使用余弦相似度进行协同过滤

现在,让我们开始为 Toby 推荐电影。推荐过程与之前的方法非常相似,如下所示:

  • 提取与 Toby 相似但 Toby 本人未评分的电影

  • 对所有未评分电影的评分取平均值,以预测 Toby 可能对这些电影给出的评分

  • 按预测评分降序显示排序结果

我们使用以下代码:

MATCH (b:USER)-[r:hasRated]->(m:MOVIE), (b)-[s:SIMILARITY]-(a:USER  
  {USERID:'Toby'}) 
WHERE NOT((a)-[:hasRated]->(m)) 
WITH m, s.similarity AS similarity, r.RATING AS rating 
ORDER BY m.ITEMID, similarity DESC 
WITH m.ITEMID AS MOVIE, COLLECT(rating) AS ratings 
WITH MOVIE, REDUCE(s = 0, i IN ratings |toInt(s) + toInt(i))*1.0 / 
  size(ratings) AS reco 
ORDER BY reco DESC 
RETURN MOVIE AS MOVIE, reco AS Recommendation 

使用余弦相似度进行协同过滤

摘要

恭喜!我们已经使用 Neo4j 图形数据库创建了推荐引擎。让我们回顾一下本章学到的内容。我们本章开始时简要介绍了图和图数据库。我们简要介绍了 Neo4j 图形数据库的核心概念,如标记属性图模型、节点、标签、关系、Cypher 查询语言、模式、节点语法和关系语法。

我们还提到了在构建推荐时有用的 Cypher 子句,例如MATCHCREATELOADCSVRETURNASWITH

然后我们转向 Windows 和 Linux 平台上的浏览器工具中的 Neo4j 的安装和设置。

一旦整个工作环境设置完毕以构建我们的推荐引擎,我们选择了样本电影评分数据并实现了三种类型的协同过滤,如基于简单距离、基于欧几里得相似度和基于余弦相似度的推荐。在下一章中,我们将探索 Hadoop 上可用的机器学习库 Mahout,用于构建可扩展的推荐系统。

第九章:使用 Mahout 构建可扩展的推荐引擎

假设你刚刚启动了一个在线电子商务网站,用于销售你设计的服装,并且你很幸运地让你的业务顺利启动并取得成功。随着越来越多的网站流量,最明显的选择是在你的网站上实现一个具有如下功能的推荐引擎:访问过某些内容的人也访问了其他内容,与当前物品相似的物品等。由于你的网站是新且成功的,你已经使用流行的工具,如 R 和 Python,实现了一个推荐引擎。推荐功能已部署并运行良好,为业务的成功增添了更多价值。现在随着业务的增加和用户基础的扩大,你最可能遇到的问题是你的客户开始抱怨你的网站变得缓慢。

在分析根本原因后,显而易见的原因是添加到网站上的推荐功能正在减慢网站速度。这很可能会发生,因为用于提供推荐的协同过滤算法存在限制。每次我们计算用户之间的相似性时,整个用户基础都会被加载到内存中,并计算相似性值。对于小用户基础,这个操作会很快。假设有一个大用户基础,比如一百万用户,协同过滤模型将会抛出内存异常。通过增加 RAM 能力,我们可能在某种程度上解决这个问题,但这仍然无济于事。增加 RAM 是一个糟糕的主意,因为它会大幅增加基础设施成本。

最好的方法是在分布式平台上重新设计推荐引擎,例如 Hadoop。这正是 Apache Mahout 能派上用场的地方,因为它是一个为分布式平台 Apache Hadoop 构建的开源机器学习库。

在本章中,我们将涵盖以下部分:

  • Mahout 一般介绍

  • 设置 Mahout 独立和分布式模式

  • Mahout 的核心构建块

  • 使用 Mahout 构建和评估推荐引擎,例如基于用户的协同过滤、基于物品的协同过滤、SVD 推荐引擎和 ALS 推荐引擎。使用 Mahout 构建可扩展的推荐引擎

Mahout - 一般介绍

Apache Mahout是一个建立在 Apache Hadoop 之上的开源 Java 库,它提供了大规模机器学习算法。尽管这个库最初是以 MapReduce 范式开始的,但当前框架提供了对 Apache Spark、H2O 和 Apache Flink 的绑定。最新的 Mahout 版本支持协同过滤推荐引擎、聚类、分类、降维、H2O 和 Spark 绑定。

Mahout 0.12.2 的主要功能如下:

  • 一个可扩展的编程环境和框架,用于构建可扩展的算法

  • 支持 Apache Spark、Apache Flink 和 H2O 算法

  • Samsara,一个类似于 R 编程语言的矢量数学环境

如前所述,尽管 Mahout 可以做很多事情,但我们将限制我们的讨论范围到使用 Mahout 构建推荐引擎。Mahout 提供了对独立模式的支持,其中推荐模型或应用程序可以部署在单个服务器上,以及分布式模式,其中推荐模型可以部署在分布式平台上。

设置 Mahout

在本节中,我们将探讨在独立和分布式模式下设置 Mahout。

独立模式 - 将 Mahout 作为库使用

Mahout 的独立模式通常涉及两个步骤:

  • 将 Mahout 库添加到希望使用 Mahout 功能的 Java 应用程序中

  • 调用 Mahout 推荐引擎函数以构建推荐应用程序

运行使用 Mahout 的应用程序需要在您的 Java Maven 项目的 pom.xml 文件中添加以下依赖项:

独立模式 - 将 Mahout 作为库使用

前面的依赖项将下载运行 Mahout 功能所需的所有 jar 或库,如下面的截图所示:

独立模式 - 将 Mahout 作为库使用

另一步是访问官方 Apache Mahout 网站,下载所需的 Mahout jar 文件,如下所示:

最新版本的 Mahout 库可以从 Apache Mahout 官方网站 mahout.apache.org/general/downloads.html 下载。

以下图像显示了上述 URL 的截图:

独立模式 - 将 Mahout 作为库使用

下载 tar 文件(tar 文件只是可执行文件)而不是源文件,因为我们只需要 Mahout 的 jar 文件来构建推荐引擎:

独立模式 - 将 Mahout 作为库使用

下载 tar 文件后,只需提取所有文件并将所需的 jar 添加到 Java 应用程序中:

独立模式 - 将 Mahout 作为库使用

使用这种最小设置,让我们使用 Java Eclipse 构建一个非常基本的推荐引擎。

最小设置只需以下步骤:

  1. 在 Eclipse 中创建一个具有以下属性选择的 Java Maven 项目:

    以下图像显示了创建新 Maven 项目设置步骤 1 的截图:

    独立模式 - 将 Mahout 作为库使用

    在以下图像中,添加 Artifact Id "recommendations":

    独立模式 - 将 Mahout 作为库使用

  2. 将创建一个以app.java为默认类的 Maven 项目。我们可以在该类中做出更改以构建我们的独立推荐引擎:独立模式 - 将 Mahout 作为库使用

  3. 将 Java 运行时设置为 1.7 或更高,如下截图所示:独立模式 - 将 Mahout 作为库使用

  4. 设置所需的 Maven 依赖项,包括mahout-mrmahout-mathslf4j-log4jcommons-math3guava;这将下载应用程序运行所需的所有 jar 文件,如下截图所示:独立模式 - 将 Mahout 作为库使用

  5. 这些依赖关系可以在以下截图中看到:独立模式 - 将 Mahout 作为库使用

  6. 在项目中创建一个名为data的文件夹,并创建一个示例数据集,如下截图所示:独立模式 - 将 Mahout 作为库使用

  7. 现在,将app.java重命名为UserbasedRecommender.java文件。在 Java 类中编写代码以构建基本的基于用户的推荐系统:

    package com.packtpub.mahout.recommenders; 
    
    import java.io.File; 
    import java.io.IOException; 
    import java.util.List; 
    
    import org.apache.mahout.cf.taste.common.TasteException; 
    import org.apache.mahout.cf.taste.impl.model.file.FileDataModel; 
    import org.apache.mahout.cf.taste.impl.neighborhood.ThresholdUserNeighborhood; 
    import org.apache.mahout.cf.taste.impl.recommender.GenericUserBasedRecommender; 
    import org.apache.mahout.cf.taste.impl.similarity.PearsonCorrelationSimilarity; 
    import org.apache.mahout.cf.taste.model.DataModel; 
    import org.apache.mahout.cf.taste.neighborhood.UserNeighborhood; 
    import org.apache.mahout.cf.taste.recommender.RecommendedItem; 
    import org.apache.mahout.cf.taste.recommender.UserBasedRecommender; 
    import org.apache.mahout.cf.taste.similarity.UserSimilarity; 
    
    //class for generating User Based Recommendation 
    public class UserbasedRecommender  
    { 
        public static void main( String[] args ) throws TasteException, IOException 
        { 
        //creating data model 
             DataModel model = new FileDataModel(new File("data/dataset.csv"));     
        // creating pearson similarity between users  
        UserSimilarity similarity = new PearsonCorrelationSimilarity(model); 
    
             //creating user neighborhood 
               UserNeighborhood neighborhood = new ThresholdUserNeighborhood(0.1,                                             similarity, model); 
    
          // creating recommender model 
                UserBasedRecommender recommender = new       GenericUserBasedRecommender(model, neighborhood, similarity); 
    
            //generating 3 recommendations for user 2 
        List<RecommendedItem> recommendations = recommender.recommend(2, 3); 
        for (RecommendedItem recommendation : recommendations) { 
          System.out.println(recommendation); 
        } 
        } 
    } 
    
    

    运行前面的代码将为用户 2 生成推荐,如下截图所示:

    独立模式 - 将 Mahout 作为库使用

嘣!我们已经创建了我们的第一个基于用户的推荐引擎。不用担心我们已经做了什么或正在发生什么;在接下来的几节中,一切都会变得清晰。现在,只需尝试理解如何使用 Mahout 库在独立模式下构建推荐引擎。

设置 Mahout 的分布式模式

我们已经看到了如何在独立模式下使用 Mahout 库。在本节中,让我们看看如何在分布式平台(如 HDFS)上设置 Mahout。以下是要设置 Mahout 所需的要求:

  • Java 7 及以上

  • Apache Hadoop

  • Apache Mahout

设置 Java 7 和安装 Hadoop 超出了本书的范围。我们可以在网上找到非常好的资源,介绍如何设置 Hadoop。假设 Hadoop 已经设置好,按照以下步骤设置 Mahout:

下载并解压 Apache Mahout 网站上的最新 Mahout 发行版,如前所述。

让我们设置环境变量:

Export JAVA_HOME = path/to/java7 or more 
export MAHOUT_HOME = /home/softwares/ apache-mahout-distribution-0.12.2 
export MAHOUT_LOCAL = true #for standalone mode 
export PATH = $MAHOUT_HOME/bin 
export CLASSPATH = $MAHOUT_HOME/lib:$CLASSPATH 

提示

取消设置MAHOUT_LOCAL以在 Hadoop 集群上运行。

一旦设置好环境变量,请在命令行中使用以下命令在分布式平台上运行推荐引擎。

使用以下代码,我们正在使用对数似然相似度生成基于项目的推荐:

mahout recommenditembased -s SIMILARITY_LOGLIKELIHOOD -i mahout/data.txt -o mahout/output1 --numRecommendations 25 

[cloudera@quickstart ~]$ mahout recommenditembased -s SIMILARITY_LOGLIKELIHOOD -i mahout/data.txt -o mahout/output1 --numRecommendations 25 
MAHOUT_LOCAL is not set; adding HADOOP_CONF_DIR to classpath. 
Running on hadoop, using /usr/lib/hadoop/bin/hadoop and HADOOP_CONF_DIR=/etc/hadoop/conf 
MAHOUT-JOB: /usr/lib/mahout/mahout-examples-0.9-cdh5.4.0-job.jar 
16/11/10 11:05:09 INFO common.AbstractJob: Command line arguments: {--booleanData=[false], --endPhase=[2147483647], --input=[mahout/data.txt], --maxPrefsInItemSimilarity=[500], --maxPrefsPerUser=[10], --maxSimilaritiesPerItem=[100], --minPrefsPerUser=[1], --numRecommendations=[25], --output=[mahout/output1], --similarityClassname=[SIMILARITY_LOGLIKELIHOOD], --startPhase=[0], --tempDir=[temp]} 
16/11/10 11:05:09 INFO common.AbstractJob: Command line arguments: {--booleanData=[false], --endPhase=[2147483647], --input=[mahout/data.txt], --minPrefsPerUser=[1], --output=[temp/preparePreferenceMatrix], --ratingShift=[0.0], --startPhase=[0], --tempDir=[temp]} 
16/11/10 11:05:10 INFO Configuration.deprecation: mapred.input.dir is deprecated. Instead, use mapreduce.input.fileinputformat.inputdir 
16/11/10 11:05:10 INFO Configuration.deprecation: mapred.compress.map.output is deprecated. Instead, use mapreduce.map.output.compress 
16/11/10 11:05:10 INFO Configuration.deprecation: mapred.output.dir is deprecated. Instead, use mapreduce.output.fileoutputformat.outputdir 
16/11/10 11:05:11 INFO client.RMProxy: Connecting to ResourceManager at /0.0.0.0:8032 
16/11/10 11:05:20 INFO input.FileInputFormat: Total input paths to process : 1 
16/11/10 11:05:22 INFO mapreduce.JobSubmitter: number of splits:1 
16/11/10 11:05:24 INFO mapreduce.JobSubmitter: Submitting tokens for job: job_1478802142793_0003 
16/11/10 11:05:42 INFO impl.YarnClientImpl: Submitted application application_1478802142793_0003 
16/11/10 11:05:52 INFO mapreduce.Job: The url to track the job: http://quickstart.cloudera:8088/proxy/application_1478802142793_0003/ 
16/11/10 11:05:52 INFO mapreduce.Job: Running job: job_1478802142793_0003 
16/11/10 11:16:45 INFO mapreduce.Job: Job job_1478802142793_0011 running in uber mode : false 
16/11/10 11:16:45 INFO mapreduce.Job:  map 0% reduce 0% 
16/11/10 11:16:58 INFO mapreduce.Job:  map 100% reduce 0% 
16/11/10 11:17:19 INFO mapreduce.Job:  map 100% reduce 100% 
16/11/10 11:17:20 INFO mapreduce.Job: Job job_1478802142793_0011 completed successfully 
16/11/10 11:17:21 INFO mapreduce.Job: Counters: 49 
File System Counters 
------------------------------- 
------------------------------- 
Bytes Written=28 
16/11/10 11:17:21 INFO driver.MahoutDriver: Program took 732329 ms (Minutes: 12.205483333333333) 

输出如下:

设置 Mahout 的分布式模式

Mahout 的核心构建块

就像任何其他推荐引擎框架一样,Mahout 也提供了一套丰富的组件来构建定制化的、企业级、可扩展、灵活且性能良好的推荐系统。

Mahout 的关键组件如下:

  • 数据模型

    • 相似度:用户相似度,项目相似度
  • 用户邻域

  • 推荐系统

  • 推荐系统评估器

基于用户的协同推荐引擎的组件

在本节中,我们将介绍构建基于用户的协同过滤系统所需组件。

基于用户的协同推荐引擎的组件

基于用户的协同推荐引擎的组件如下:

  • DataModel:DataModel 实现允许我们存储并提供访问用于计算的用户、项目和偏好数据。DataModel 组件允许我们从数据源中提取数据。Mahout 提供了MySQLJDBCDataModel,它允许我们通过 JDBC 和 MySQL 从数据库中提取数据。在我们的示例中,我们使用FileDataModel接口从 Mahout 公开的文件中访问数据。

    Mahout 公开的其他一些 DataModel 如下:

    Mahout 期望用户数据以用户 ID、项目 ID 和偏好三元组的格式存在。偏好值可以是连续的或布尔值。Mahout 支持连续和布尔偏好值。我们提供给 DataModel 的每个包含用户 ID、项目 ID 和偏好的输入三元组,将表示为一个内存高效的Preference 对象PreferenceArray 对象

  • UserSimilarity: UserSimilarity 接口计算两个用户之间的相似度。UserSimilarity 的实现通常返回 -1.0 到 1.0 范围内的值,其中 1.0 是完美的相似度。在前面的章节中,我们看到了多种计算用户相似度的方法,例如欧几里得距离、皮尔逊系数、余弦距离等。有许多 UserSimilarity 接口的实现来计算用户相似度,如下所示:

    • CachingUserSimilarity

    • CityBlockSimilarity

    • EuclideanDistanceSimilarity

    • GenericUserSimilarity

    • LogLikelihoodSimilarity

    • PearsonCorrelationSimilarity

    • SpearmanCorrelationSimilarity

    • TanimotoCoefficientSimilarity

    • UncenteredCosineSimilarity

  • ItemSimilarity: 与 UserSimilarity 类似,Mahout 还提供了 ItemSimilarity 接口,类似于 UserSimilarity,它可以用来计算物品之间的相似度。UserSimilarity 的实现通常返回 -1.0 到 1.0 范围内的值,其中 1.0 是完美的相似度:

    • AbstractItemSimilarity

    • AbstractJDBCItemSimilarity

    • CachingItemSimilarity

    • CityBlockSimilarity

    • EuclideanDistanceSimilarity

    • FileItemSimilarity

    • GenericItemSimilarity

    • LogLikelihoodSimilarity

    • MySQLJDBCInMemoryItemSimilarity

    • MySQLJDBCItemSimilarity

    • PearsonCorrelationSimilarity

    • SQL92JDBCInMemoryItemSimilarity

    • SQL92JDBCItemSimilarity

    • TanimotoCoefficientSimilarity

    • UncenteredCosineSimilarity

  • UserNeighborhood: 在基于用户的推荐器中,为活动用户生成的推荐是通过找到相似用户的一个邻域来产生的。UserNeighborhood 通常指的是确定给定活动用户邻域的一种方式,例如,在生成推荐时考虑的十个最近邻。

    这些邻域类实现了 UserSimilarity 接口以进行其操作。以下是对邻域接口的实现:

    • CachingUserNeighborhood

    • NearestNUserNeighborhood

    • ThresholdUserNeighborhood

  • Recommender: 推荐器是 Mahout 的核心抽象。给定 DataModel 对象作为输入,它为用户生成物品推荐。推荐器接口的实现如下:

    • AbstractRecommender

    • CachingRecommender

    • GenericBooleanPrefItemBasedRecommender

    • GenericBooleanPrefUserBasedRecommender

    • GenericItemBasedRecommender

    • GenericUserBasedRecommender

    • ItemAverageRecommender

    • ItemUserAverageRecommender

    • RandomRecommender

    • RecommenderWrapper

    • SVDRecommender

使用 Mahout 构建推荐引擎

现在,我们已经涵盖了 Mahout 推荐引擎框架的核心构建块,让我们开始构建推荐。在本节中,我们将查看一系列使用独立模式实现的不同的推荐引擎。推荐引擎的功能是使用 org.apache.mahout.cf.taste.impl 包的实现。

本节中我们看到的推荐引擎如下:

  • 基于用户的协同过滤

  • 基于项目的协同过滤

  • SVD 推荐器

数据集描述

在我们深入研究推荐实现之前,让我们看看本节中使用的数据集。对于本节,我们使用从以下 URL 可获得的 UCI 机器学习数据集仓库中的餐厅和消费者数据集:

archive.ics.uci.edu/ml/datasets/Restaurant+%26+consumer+data

此数据集可用于构建使用消费者偏好信息进行协同过滤的应用程序。该数据集,从上一个链接下载的文件,包含以下图中列出的九个文件。在所有这些文件中,我们使用rating_final.csv文件,其中包含如 userID、placeID、rating、food_rating 和 service_rating 等属性。但对我们用例而言,我们只使用 userID、placeID 和 rating。我们可以将数据视为给定用户对地点给出的偏好值。

注意

我们将不得不在设置会话中利用之前创建的项目。

将输入的ratings_final.csv文件添加到当前项目结构的*data*文件夹中。

因此,首先,让我们将原始的原始数据预处理成所需的用户 ID、地点 ID 和评分三元组格式。以下是本练习使用的原始数据集:

数据集描述

以下程序将准备所需的元组数据集,实现如下:

  • ratings_final.csv文件中读取每一行

  • 提取前三个列

  • 将上一步提取的列写入新的recoDataset.csv文件

以下 Java 程序实现了之前解释的步骤:

package com.packtpub.mahout.recommenders; 

import java.io.FileReader; 
import java.io.FileWriter; 
import java.io.IOException; 
import java.util.ArrayList; 
import java.util.List; 

import au.com.bytecode.opencsv.CSVReader; 
import au.com.bytecode.opencsv.CSVWriter; 

public class Preprocessdata  { 

public static void main(String[] args) throws IOException { 
String fileName = "data/rating_final.csv"; 
String csv = "data/recoDataset.csv";          
CSVReader csvReader = new CSVReader(new FileReader(fileName)); 
String[] row = null; 
List<String[]> data = new ArrayList<String[]>(); 
CSVWriter writer = new CSVWriter(new FileWriter(csv), 
CSVWriter.DEFAULT_SEPARATOR, 
CSVWriter.NO_QUOTE_CHARACTER); 
while((row = csvReader.readNext()) != null) { 
if(!row[0].contains("userID")){ 
data.add(new String[] {row[0].substring(1), row[1],row[2]}); 
} 
} 
writer.writeAll(data); 
writer.close(); 
csvReader.close(); 
} 

} 

运行前面的 Java 程序后,我们用于构建推荐引擎的最终数据集将作为recoDataset.csv文件位于*data*文件夹下。以下是一个样本数据集:

数据集描述

现在我们已经预处理了所需的数据,让我们开始使用 Mahout 框架构建我们的推荐引擎。

基于用户的协同过滤

仅为了复习:基于用户的推荐系统基于用户之间的用户相似度计算生成推荐,然后使用用户邻域选择前 N 个用户,然后生成推荐。

让我们先执行以下代码,然后我们将逐行查看代码。我们将使用欧几里得距离相似度和最近邻方法生成推荐:

package com.packtpub.mahout.recommenders; 

import java.io.File; 
import java.io.IOException; 
import java.util.List; 

import org.apache.mahout.cf.taste.common.TasteException; 
import org.apache.mahout.cf.taste.impl.model.file.FileDataModel; 
import org.apache.mahout.cf.taste.impl.neighborhood.NearestNUserNeighborhood; 
import org.apache.mahout.cf.taste.impl.recommender.GenericUserBasedRecommender; 
import org.apache.mahout.cf.taste.impl.similarity.EuclideanDistanceSimilarity; 
import org.apache.mahout.cf.taste.model.DataModel; 
import org.apache.mahout.cf.taste.neighborhood.UserNeighborhood; 
import org.apache.mahout.cf.taste.recommender.RecommendedItem; 
import org.apache.mahout.cf.taste.recommender.UserBasedRecommender; 
import org.apache.mahout.cf.taste.similarity.UserSimilarity; 

//class for generating User Based Recommendation 
public class UserbasedRecommendations 
{ 
    public static void main( String[] args ) throws TasteException, IOException 
    { 
    //creating data model 
    DataModel model = new FileDataModel(new File("data/recoDataset.csv"));      
    // creating Euclidean distance similarity between users  
    UserSimilarity similarity = new EuclideanDistanceSimilarity(model); 
    //creating user neighborhood 
    UserNeighborhood neighborhood = new NearestNUserNeighborhood(10, similarity, model); 
    // creating recommender model 
    UserBasedRecommender recommender = new GenericUserBasedRecommender(model, neighborhood, similarity); 
    //generating 3 recommendations for user 1068 
    List<RecommendedItem> recommendations = recommender.recommend(1068, 3); 
    for (RecommendedItem recommendation : recommendations) { 
      System.out.println(recommendation); 
    } 
    } 
} 

运行此程序生成以下图所示的推荐。我们正在为UserId - 1068生成基于用户的三个项目推荐:

从结果中,我们可以得出结论,对于UserId - 1068,推荐的三个地方及其相似度值如下:

基于用户的协同过滤

现在我们一行一行地查看代码;只需回忆一下 Mahout 推荐部分的核心理念构建块。我们需要 DataModel、相似度计算、UserNeighborhood、推荐器和生成推荐。这个顺序在之前的代码中已经使用过:

  1. UserbasedRecommender.main 方法中的代码使用 org.apache.mahout.cf.taste.impl.model.file.FileDataModel.FileDataModel 类从 data/recoDataset.csv CSV 文件创建数据源。这个类的构造函数获取包含偏好数据的 Java.io.File 实例,并创建 DataModel 类实例模型:

            //creating data model 
            DataModel model = new FileDataModel(new 
              File("data/recoDataset.csv")); 
    
    
  2. 在这一步,我们创建 UserSimilarity 实例:使用 org.apache.mahout.cf.taste.impl.similarity.EuclideanDistanceSimilarity 类计算所有用户之间的相似度,它将之前步骤中创建的 FileDataModel 实例作为构造函数参数:

            // creating Euclidean distance similarity between users  
            UserSimilarity similarity = new 
              EuclideanDistanceSimilarity(model); 
    
    
  3. 在这一步,我们创建 UserNeighborhood 实例:使用 org.apache.mahout.cf.taste.impl.neighborhood.NearestNUserNeighborhood 类创建邻域,它需要三个参数:要考虑的最近邻数量、UserSimilarity 实例的相似度、作为输入的 DataModel 实例,即之前步骤中创建的模型:

            //creating user neighborhood 
            UserNeighborhood neighborhood = new 
              NearestNUserNeighborhood(10, similarity, model); 
    
    
  4. 下一步是生成推荐模型。这是通过使用 org.apache.mahout.cf.taste.impl.recommender.GenericUserBasedRecommender 类实例来实现的。创建推荐器实例时,通过将 DataModel 实例模型、UserNeighborhood 实例邻域、UserSimilarity 实例相似度作为输入传递给构造函数:

            // creating recommender model 
            UserBasedRecommender recommender = new   
              GenericUserBasedRecommender(model, neighborhood, similarity); 
    
    
  5. 恭喜!我们已经使用欧几里得距离相似度和 NearestNNeighborhhood 方法创建了一个基于用户的推荐系统,并生成了一个推荐模型。下一步将是生成推荐;为此,我们调用推荐器对象中可用的 recommend() 方法,该方法需要生成推荐的 UserId 和推荐的数量:

            //generating 3 recommendations for user 1068 
            List<RecommendedItem> recommendations = 
              recommender.recommend(1068, 3); 
    
    

此步骤已为 UserId 1068 生成三个基于物品的推荐,并附带偏好的强度。

在我们的案例中,我们生成了以下推荐:

item:132613, value:1.2205102 
item:132667, value:1.0 
item:132584, value:0.98069793 

基于物品的协同过滤

基于物品的推荐器通过考虑物品之间的相似度而不是用户之间的相似度来向用户推荐相似物品,如前节所示。

以下是一个给定的 Java 程序,用于构建基于物品的协同过滤。我们使用了 LogLikelihoodSimilarity 来计算 ItemSimilarity,然后我们使用了 GenericItemBasedRecommender 类向用户推荐物品。此外,我们还可以看到如何使用 GenericItemBasedRecommender 中的 mostSimilarItems 方法检查给定物品的相似物品:

package com.packpub.mahout.recommendationengines; 

import java.io.File; 
import java.io.IOException; 
import java.util.List; 

import org.apache.mahout.cf.taste.common.TasteException; 
import org.apache.mahout.cf.taste.impl.model.file.FileDataModel; 
import org.apache.mahout.cf.taste.impl.recommender.GenericItemBasedRecommender; 
import org.apache.mahout.cf.taste.impl.similarity.LogLikelihoodSimilarity; 
import org.apache.mahout.cf.taste.model.DataModel; 
import org.apache.mahout.cf.taste.recommender.RecommendedItem; 
import org.apache.mahout.cf.taste.similarity.ItemSimilarity; 

public class ItembasedRecommendations { 

public static void main(String[] args) throws TasteException, IOException { 
DataModel model = new FileDataModel(new File("data/recoDataset.csv")); 
    ItemSimilarity similarity = new LogLikelihoodSimilarity(model); 
    GenericItemBasedRecommender recommender = new GenericItemBasedRecommender(model, similarity); 
    System.out.println("*********Recommend Items to Users********"); 
    List<RecommendedItem> recommendations = recommender.recommend(1068, 3); 
    for (RecommendedItem recommendation : recommendations) { 
      System.out.println(recommendation); 
    } 
     System.out.println("*********Most Similar Items********"); 
    List<RecommendedItem> similarItems = recommender.mostSimilarItems(135104, 3); 
    for (RecommendedItem similarItem : similarItems) { 
      System.out.println(similarItem); 
    } 
} 

} 

运行前面的程序将生成与输入物品最相似的三个物品,在我们的例子中,对于 placeID 135104,最相似的地方 ID 属性及其相似度强度如下所示:

基于物品的协同过滤

让我们依次查看前面程序中的每个步骤,以了解前面实现中发生了什么:

  1. 第一步,就像在上一节中一样,是使用org.apache.mahout.cf.taste.impl.model.file.FileDataModel类创建DataModel实例:

            //we create DataModel instance - model  
            DataModel model = new FileDataModel(new 
              File("data/recoDataset.csv")); 
    
    
  2. 在这一步中,我们创建了一个ItemSimilarity实例,使用org.apache.mahout.cf.taste.impl.similarity.LogLikelihoodSimilarity类计算所有用户之间的相似度,该类将之前步骤中创建的FileDataModel实例作为构造函数参数:

            // creating LogLikelihood distance similarity between users  
            ItemSimilarity similarity = new LogLikelihoodSimilarity 
              (model);  
    
    
  3. 下一步是生成一个推荐模型。这是通过使用org.apache.mahout.cf.taste.impl.recommender.GenericItemBasedRecommender类实例来实现的。创建一个GenericItemBasedRecommender实例推荐器时,将数据模型实例(即模型ItemSimilarity实例的相似度)作为输入传递给构造函数,以创建推荐对象。

            // creating recommender model 
            GenericItemBasedRecommender recommender = new    
              GenericItemBasedRecommender(model, similarity); 
    
    

    注意

    相似度度量指标的选择留给你;它根据你的要求设置。

  4. 太棒了!我们已经使用LogLikelihood相似度创建了基于物品的推荐系统。下一步将是生成推荐,为此,我们调用推荐对象中可用的recommend()方法,该方法需要UserId以及要生成的推荐数量:

            //generating 3 recommendations for user 1068 
            List<RecommendedItem> recommendations = 
              recommender.recommend(1068, 3); 
    
    

    此步骤已为 UserID 1068 生成了三个物品推荐,并附带了偏好的强度。

    在我们的情况下,我们生成了以下推荐:

            item:132613, value:1.2205102 
            item:132667, value:1.0 
            item:132584, value:0.98069793 
    
    
  5. 假设我们想查看与特定物品相似的商品;在我们的示例中,推荐接口,如GenericItemBasedRecommender类,提供了mostSimilarItems()方法,该方法接受UserId和要显示的商品数量作为输入,并为给定物品提取similarItems

        List<RecommendedItem> similarItems =    
          recommender.mostSimilarItems(135104, 3); 

在我们的示例中,与PlaceId 135104 最相似的三个地方如下所示:

item:132667, value:0.96383345 
item:132732, value:0.9602005 
item:132733, value:0.9543598 

在下一节中,让我们评估到目前为止我们创建的推荐。

评估协同过滤

我们已经看到了如何使用协同过滤方法构建推荐。但关键是要构建高效的推荐。评估推荐模型的准确性——我们所构建的——是构建推荐引擎的一个非常关键步骤。在本节中,我们将探讨如何评估基于用户的推荐器和基于物品的推荐器。

Mahout 提供了组件,使我们能够评估我们迄今为止构建的推荐模型的准确率。我们可以评估我们的推荐引擎如何接近实际偏好值来估计偏好。我们可以指示 Mahout 使用原始训练数据的一部分来留出并使用这个测试数据集来计算模型的准确率。

根据我们的需求,我们可以使用 Mahout 提供的以下任何推荐器评估器:

评估协同过滤

使用 Mahout 进行推荐器评估通常需要两个步骤:

  • 创建 org.apache.mahout.cf.taste.impl.eval.RMSRecommenderEvaluator 类的实例,该类可以从前面的列表中获取,它将创建准确率分数

  • 实现 org.apache.mahout.cf.taste.eval.RecommenderBuilder 的内部接口,以便创建 RecommenderEvaluator 类实例(上一步)可以使用的推荐器,以产生准确率分数

列表显示了基于用户的推荐器模型评估的 Java 实现。对于这个练习,我们使用了均方根误差评估技术。

评估基于用户的推荐器

在本节中,我们将看到评估上一节中构建的基于用户的推荐的代码:

package com.packtpub.mahout.recommenders; 

import java.io.File; 
import java.io.IOException; 

import org.apache.mahout.cf.taste.common.TasteException; 
import org.apache.mahout.cf.taste.eval.RecommenderBuilder; 
import org.apache.mahout.cf.taste.eval.RecommenderEvaluator; 
import org.apache.mahout.cf.taste.impl.eval.RMSRecommenderEvaluator; 
import org.apache.mahout.cf.taste.impl.model.file.FileDataModel; 
import org.apache.mahout.cf.taste.impl.neighborhood.NearestNUserNeighborhood; 
import org.apache.mahout.cf.taste.impl.recommender.GenericUserBasedRecommender; 
import org.apache.mahout.cf.taste.impl.similarity.EuclideanDistanceSimilarity; 
import org.apache.mahout.cf.taste.model.DataModel; 
import org.apache.mahout.cf.taste.neighborhood.UserNeighborhood; 
import org.apache.mahout.cf.taste.recommender.Recommender; 
import org.apache.mahout.cf.taste.similarity.UserSimilarity; 

public class EvaluateUBCFRecommender { 

public static void main(String[] args) throws IOException, TasteException { 

DataModel model = new FileDataModel(new File("data/recoDataset.csv")); 
RecommenderEvaluator evaluator = new RMSRecommenderEvaluator(); 
RecommenderBuilder builder = new RecommenderBuilder() { 
public Recommender buildRecommender(DataModel model) 
throws TasteException { 
UserSimilarity similarity = new EuclideanDistanceSimilarity(model); 
UserNeighborhood neighborhood = 
new NearestNUserNeighborhood (10, similarity, model); 
return 
new GenericUserBasedRecommender (model, neighborhood, similarity); 
} 
}; 
double score = evaluator.evaluate( 
builder, null, model, 0.8, 1.0); 
System.out.println(score); 
} 

} 

执行前面的程序将给出模型准确率:0.692216091226208

评估基于物品的推荐器

以下代码片段将用于评估基于物品的推荐:

package com.packtpub.mahout.recommenders; 

import java.io.File; 
import java.io.IOException; 

import org.apache.mahout.cf.taste.common.TasteException; 
import org.apache.mahout.cf.taste.eval.RecommenderBuilder; 
import org.apache.mahout.cf.taste.eval.RecommenderEvaluator; 
import org.apache.mahout.cf.taste.impl.eval.RMSRecommenderEvaluator; 
import org.apache.mahout.cf.taste.impl.model.file.FileDataModel; 
import org.apache.mahout.cf.taste.impl.recommender.GenericItemBasedRecommender; 
import org.apache.mahout.cf.taste.impl.similarity.LogLikelihoodSimilarity; 
import org.apache.mahout.cf.taste.model.DataModel; 
import org.apache.mahout.cf.taste.recommender.Recommender; 
import org.apache.mahout.cf.taste.similarity.ItemSimilarity; 

public class EvaluateIBCFRecommender { 

public static void main(String[] args) throws IOException, TasteException { 

DataModel model = new FileDataModel(new File("data/recoDataset.csv")); 
//RMS Recommender Evaluator 
RecommenderEvaluator evaluator = new RMSRecommenderEvaluator(); 
RecommenderBuilder builder = new RecommenderBuilder() { 
public Recommender buildRecommender(DataModel model) 
throws TasteException { 
ItemSimilarity similarity = new LogLikelihoodSimilarity(model); 
return 
new GenericItemBasedRecommender(model, similarity); 
} 
}; 
double score = evaluator.evaluate(builder, null, model, 0.7, 1.0); 
System.out.println(score); 

} 

} 

执行前面的程序将给出模型准确率:0.6041129199039021

现在我们一步一步地看看这个评估实现:

  1. 第一步是使用 org.apache.mahout.cf.taste.impl.model.file.FileDataModel 类创建一个 DataModel 实例模型:

            DataModel model = new FileDataModel(new 
              File("data/recoDataset.csv")); 
    
    
  2. 在这一步,我们创建 org.apache.mahout.cf.taste.impl.eval.RMSRecommenderEvaluator 实例评估器,它将计算推荐引擎的准确率:

            // Recommendation engine model evaluator engine  
            RecommenderEvaluator evaluator = new RMSRecommenderEvaluator();     
    
    
  3. 在这一步中,我们实现 org.apache.mahout.cf.taste.eval.RecommenderBuilder 接口以创建我们选择的推荐器。

    让我们使用与上一节中用于基于用户和基于物品的推荐器相同的推荐器模型:

            // User based recommenders 
            public Recommender buildRecommender(DataModel model) 
            throws TasteException { 
            UserSimilarity similarity = new  
              EuclideanDistanceSimilarity(model); 
            UserNeighborhood neighborhood = 
            new NearestNUserNeighborhood (2, similarity, model); 
            return 
            new GenericUserBasedRecommender (model, neighborhood, 
              similarity); 
            } 
            }; 
    
            //Item based recommenders 
            public Recommender buildRecommender(DataModel model) 
            throws TasteException { 
            ItemSimilarity similarity = new LogLikelihoodSimilarity(model); 
            return 
            new GenericItemBasedRecommender(model, similarity); 
            } 
            }; 
    
    
  4. 现在我们已经准备好计算推荐准确率。为此,我们使用评估器实例的 evaluate() 方法。evaluate() 方法不接受我们直接在基于用户/基于物品推荐器中创建的推荐器实例,但它接受在步骤 3 中创建的 RecommenderBuilder,该 RecommenderBuilder 可以在给定的 DataModel 上构建推荐器以测试准确率。

    evaluate()方法接受四个参数:第 3 步创建的推荐器构建器,第 1 步创建的数据模型对象,我们不需要的示例数据模型构建器对象,训练百分比--在我们的例子中,我们使用了 0.7%作为训练数据集,0.3 作为测试数据集,评估百分比,用于评估的用户百分比。

    evaluate()方法返回模型的准确度分数,这是推荐器预测的偏好与真实值匹配得有多好。较低的值表示更好的匹配,0 表示完美匹配:

            //generating 3 recommendations for user 1068 
            double score = evaluator.evaluate(builder, null, model, 0.7, 
              1.0);   
    
    

SVD 推荐器

与前面解释的基于项目和基于用户的推荐系统类似,我们也可以在 Mahout 中使用基于模型的推荐实现,例如SVDRecommender,它使用矩阵分解方法来生成推荐。

步骤与之前的实现类似。这里需要理解的两个重要步骤如下:

  • org.apache.mahout.cf.taste.impl.recommender.svd.ALSWRFactorizer类使用带有加权λ正则化的交替最小二乘法对用户评分矩阵进行分解。ALSWRFactorizer类的构造函数接受数据模型、特征数量、正则化参数和迭代次数等输入参数。这个ALSWRFactorizer类实例作为输入参数传递给推荐对象:SVDRecommender类。

  • org.apache.mahout.cf.taste.impl.recommender.svd.SVDRecommender类通过接收DataModelALSWRFactorizer对象来生成推荐模型。

其余的步骤与我们在前面的示例中看到的基本相同:

以下代码片段展示了如何构建 SVD 推荐系统:

package com.packpub.mahout.recommendationengines; 

import java.io.File; 
import java.io.IOException; 
import java.util.List; 

import org.apache.mahout.cf.taste.common.TasteException; 
import org.apache.mahout.cf.taste.impl.model.file.FileDataModel; 
import org.apache.mahout.cf.taste.impl.recommender.svd.ALSWRFactorizer; 
import org.apache.mahout.cf.taste.impl.recommender.svd.SVDRecommender; 
import org.apache.mahout.cf.taste.model.DataModel; 
import org.apache.mahout.cf.taste.recommender.RecommendedItem; 

public class UserBasedSVDRecommender { 

public static void main(String[] args) throws TasteException, IOException { 
//MF recommender model 
    DataModel model = new FileDataModel(new File("data/dataset.csv"));    
    ALSWRFactorizer factorizer = new ALSWRFactorizer(model, 50, 0.065, 15); 
    SVDRecommender recommender = new SVDRecommender(model, factorizer);     
    List<RecommendedItem> recommendations = recommender.recommend(2, 3); 
    for (RecommendedItem recommendation : recommendations) { 
      System.out.println(recommendation); 
    } 

} 

} 

使用 Mahout 进行分布式推荐

到目前为止,我们已经看到了如何在独立模式下构建推荐引擎。在大多数情况下,独立实现非常方便,并且只要我们提供数据集格式,如 userID、itemID 和偏好三元组,它们在处理一百万条记录时效率相当高。

当数据量增加时,独立模式可能无法满足需求。我们需要寻找处理大量数据的方法,并能够处理数据以构建推荐。一种方法是将我们的独立解决方案移植到分布式模式,例如 Hadoop 平台。

将推荐解决方案移植到 Hadoop 上并不直接,因为数据将分布在各个节点上。基于内存的模型,如邻居推荐器,或基于模型的推荐器,如交替最小二乘法,在生成模型时需要整个数据可用,这在分布式平台上是不可行的。因此,我们需要一个全新的设计来构建推荐系统。

幸运的是,Mahout 已经消除了设计可分布式推荐实现所带来的麻烦。这些 Mahout 分布式推荐引擎实现作为作业提供,这些作业内部运行一系列 map-reduce 阶段。

例如,使用交替最小二乘法的 Mahout 分布式推荐包括两个作业:

  • 并行矩阵分解作业

  • 推荐作业

矩阵分解作业以用户-项目评分文件作为输入,并创建用户潜在矩阵,这是一个用户特征矩阵和一个项目特征矩阵。

推荐作业使用使用矩阵分解作业创建的潜在特征矩阵,并计算 Top-N 推荐结果。

这两个作业是顺序执行的,输入数据从 HDFS 读取,最终推荐结果写入 HDFS。

在本节中,我们将探讨如何使用基于项目的推荐引擎和 Hadoop 上的交替最小二乘法生成推荐。让我们开始。

Hadoop 上的 ALS 推荐作业

要使用 ALS 实现构建推荐,以下步骤如下:

  1. 将数据加载到 Hadoop 平台。Mahout 的 ALS 实现期望输入为一个三元组:用户 ID、项目 ID 和偏好值(显式评分/隐式评分)。

  2. 执行 ALS 推荐引擎实现作业;此作业将通过从步骤 1 中的输入数据集获取来创建用户和项目潜在矩阵。

  3. 执行推荐作业,该作业使用步骤 2 中创建的用户-项目潜在特征矩阵,并生成 Top-N 推荐结果。

让我们一步一步执行所有步骤。

注意

对于以下练习,我们使用 CDH 5 和 Centos 6。这是假设 JAVA_HOME 已设置且 Mahout 已正确安装。

  1. 按照以下方式将数据加载到 Hadoop 平台:

    #create a directory to store the input data using mkdir command
    [cloudera@quickstart ~]$ hadoop fs -mkdir mahout
    
    

    让我们使用 ls 命令检查是否正确创建了目录:

    [cloudera@quickstart ~]$ hadoop fs -ls
    Found 1 items
    drwxr-xr-x   - cloudera cloudera          0 2016-11-14 18:31 mahout
    
    

    现在,让我们使用 copyFromLocal 命令将数据加载到 HDFS:

    hadoop fs -copyFromLocal /home/cloudera/datasets/u.data mahout
    
    

    注意

    输入数据是包含一百万条评分数据的 MovieLens 数据集。

    让我们使用 ls 命令验证数据是否正确加载:

    [cloudera@quickstart ~]$ hadoop fs -ls mahout
    Found 1 items
    -rw-r--r--   1 cloudera cloudera    1979173 2016-11-14 18:32 mahout/u.data
    
    

    现在我们已经看到数据已正确加载,让我们查看输入数据的前几条记录:

    Hadoop 上的 ALS 推荐作业

  2. 创建 用户项目潜在 矩阵。要创建潜在特征矩阵,我们需要从命令行运行以下命令:

    $MAHOUT_HOME\bin\mahout parallelALS \
     --input mahout \
     --output output \
     --lambda 0.1 \
     --implicitFeedback false \
     --numFeatures 10 \
     --numIterations 1  \
     --tempDir tmp
    
    

    让我们查看每个命令参数:

    • $MAHOUT_HOME\bin\mahout:这是运行底层矩阵分解作业的可执行文件。

    • parallelALS:这是应用于输入数据集的算法名称。parallelALS 命令调用底层的 ParallelALSFactorizationJob 类对象,这是一个 《大规模并行协同过滤用于 Netflix Prize》 中描述的分解算法的 map-reduce 实现。

    • --input:这是输入评分数据的 HDFS 输入路径。

    • --output: 这是用户和项目输出潜在矩阵将被生成的路径。

    • --lambda: 这是为了避免过拟合而给出的正则化参数。

    • --alpha: 这是仅用于隐式反馈的置信度参数。

    • --implicitFeatures: 这是一个布尔值,用于表示偏好值是真是假。在我们的例子中,它们是假的。

    • --numIterations: 这是模型通过将前一个模型的学习应用到新模型来重新计算的总次数。

    • --tempDir: 这是写入中间结果的临时目录的路径。

    执行我们看到的命令后,在output目录中创建了三个数据集:

    • U: 这包含用户潜在特征矩阵。

    • M: 包含项目潜在特征矩阵。

    • userRatings: 所有输出都是序列文件格式。

  3. 为所有用户生成推荐。这一步将上一步骤存储到 HDFS 的output结果作为输入,生成推荐,并将最终推荐写入 HDFS 上的recommendations output目录。

    以下命令将调用org.apache.mahout.cf.taste.hadoop.als.RecommenderJob推荐作业,该作业内部调用org.apache.mahout.cf.taste.hadoop.als.PredictionMapper类来生成推荐:

    $MAHOUT_HOME\bin\mahout recommendfactorized \
     --input output/userRatings/  \
     --userFeatures output/U/ \
     --itemFeatures output/M/ \
     --numRecommendations 15 \
     --output recommendations/topNrecommendations \
     --maxRating 5
    
    

    让我们详细看看每个参数:

    • -- input: 这是包含用于生成推荐的 userID 列表的 HDFS 路径,以序列文件格式使用。在我们的例子中,output/userRatings目录包含所有用于生成推荐的 userID,该文件是第 2 步的输出。

    • --userFeatures: 这是包含第 2 步输出生成的用户潜在特征的 HDFS 路径。

    • --itemFeatures: 这是包含第 2 步输出生成的项目潜在特征的 HDFS 路径。

    • --numRecommendations: 每个用户要生成的推荐数量。

    • --output recommendations: 这是最终推荐结果需要生成的 HDFS 路径。

    • --maxRating: 这是生成推荐应包含的最大评分。

在命令行中运行前面的命令后,推荐被生成到 HDFS 上的推荐文件夹中,如下所示:

Hadoop 上的 ALS 推荐

在早期结果中,我们可以按顺序看到前十个用户推荐。每个用户向量包含项目 ID 和算法预测的评分。在提供推荐时,我们只需直接发送推荐即可。

现在,你可能会有这样的问题:如果我想为特定用户生成推荐怎么办?Mahout 也支持这样的场景。记住步骤 3 中的输入参数吗?只需提供包含所需用户 ID 的 HDFS 路径即可。但请确保包含用户 ID 的输入路径是序列文件格式。

可扩展系统的架构

将推荐引擎系统投入生产与任何其他系统相同。前面的图显示了在生产系统上部署的一个非常简单的推荐引擎:

  • 生产的系统是安装了 Java 8 和 Apache Tomcat 服务器的 Centos 6。CDH 5 和 Mahout 0.12 版本也安装在其上,以便我们可以部署我们迄今为止构建的推荐作业:可扩展系统的架构

  • 我们迄今为止编写的 Java 代码可以制作成 jar 文件并部署到生产系统。根据我们的要求,定期安排所有作业。

  • 在定义的预定时间,推荐作业开始执行,从数据源拉取数据,计算推荐模型,并生成推荐。

  • 推荐模块的数据将被读取并写回到 HDFS 文件系统。

  • 前端应用程序将读取 HDFS 中的最终输出。

摘要

在本章中,我们看到了如何使用 Apache Mahout 构建推荐。我们探讨了如何利用 Mahout 在独立模式和分布式模式中。我们在独立模式下为基于用户、基于项目和基于 SVD 的推荐引擎编写了 Java 代码,在分布式模式下编写了交替最小二乘推荐。我们还看到了如何评估推荐引擎模型。在最后一节中,我们探索了一个如何将 Mahout 投入生产的基本系统。

在最后一章中,我们将探讨推荐引擎的未来,包括推荐引擎的发展方向和值得关注的潜在用例。

第十章。接下来是什么 - 推荐引擎的未来

感谢您一直陪伴我走完这段美好的旅程。我希望您已经对如何使用各种技术如 R、Python、Mahout、Spark 和 Neo4j 构建推荐引擎有了清晰的认识。我们已经涵盖了诸如邻域推荐、基于模型的推荐和上下文感知、可扩展、实时、图推荐等推荐引擎。

在结论章节中,我想谈谈两件事:

  • 推动推荐引擎未来发展的技术转变和动机转变

  • 提高推荐引擎质量的热门方法

推荐引擎的未来部分,我将总结我在 2015 年一个技术会议上关于推荐引擎的一次演讲。在良好实现部分,我将讨论在构建推荐引擎时需要遵循的重要方法。

随着商业组织在推荐引擎上的大量投资,研究人员正在自由探索推荐引擎的不同方面,并应用非常先进的方法来提高其性能。了解推荐引擎的未来和研究的方向对于我们来说非常重要,这样我们就可以在构建推荐引擎的日常工作中应用这些新技术。

在推荐引擎的未来部分,我们将探讨推动推荐未来发展的技术和动机转变。在下一节中,我们将了解一些数据科学家在构建推荐引擎时应该知道的流行方法。

推荐引擎的未来

随着我们到达了本书的结尾,我觉得是时候谈谈推荐引擎的未来了。让我们首先简要回顾一下本书中到目前为止所涵盖的内容:

  • 推荐引擎的详细内容

  • 推荐引擎中使用的数据挖掘技术

  • 协同过滤:基于相似性的推荐

  • 使用 R 和 Python 的基于模型的推荐

  • 使用 R 和 Python 的内容推荐系统

  • 使用 R 和 Python 的上下文感知推荐系统

  • 使用 Scala 的可扩展实时推荐系统

  • 使用 Mahout 的可扩展推荐引擎

  • 使用 Neo4j 的基于图的推荐引擎

推荐引擎的阶段

如第一章《推荐引擎简介》中所述,如果我们仔细观察推荐系统的演变,推荐引擎已经向多个方向发展;了解推荐引擎的发展方向对于应对未来情况至关重要。

我们正进入推荐引擎演变的第三阶段,具体如下:

  • 第一阶段:通用推荐引擎

  • 第二阶段:个性化推荐引擎

  • 第三阶段:未来派推荐引擎

主要来说,推荐引擎关注的是消费者。消费者是推荐引擎的核心目标。让我们了解这些系统如何随着用户的发展而演变。随着互联网使用的增加,从购买哪种产品到观看哪部电影,再到尝试哪家餐厅,日常决策越来越多地依赖于这些推荐系统。这些推荐系统正在改变我们做决定的方式,引导我们通过一个新的数字现实,其演变正将我们带到我们真正想要的地方,即使我们还没有意识到这一点。

第一阶段 - 通用推荐引擎

这些推荐引擎是推荐引擎的早期版本。协同过滤、基于用户的推荐器和基于物品的推荐器都属于这一阶段的通用推荐。

如第三章《推荐引擎解释》中所述,协同过滤推荐器变得非常流行,并且在向用户推荐事物方面也非常有效。以下图象征性地表示了通用推荐引擎:

第一阶段 - 通用推荐引擎

第二阶段 - 个性化推荐系统

随着信息爆炸的开始和越来越多的人开始使用网络,留下了大量的数字足迹,如搜索模式、点击和浏览,公司开始研究用户对哪些物品或产品感兴趣,以及哪些物品特征使得用户寻找它。公司开始意识到每个人都是独特的,有独特的品味,然后他们开始满足他们对个性化事物的需求;这些也被称为基于内容的推荐系统。在第三章《推荐引擎解释》中,我们详细了解了基于内容的推荐器。以下图显示了如何向客户提供个性化推荐:

第二阶段 - 个性化推荐系统

随着系统转向个性化系统——也称为基于内容的推荐系统——更高级的技术,如机器学习、大数据和云计算,开始计算更适合用户的物品。随着技术的进步,矩阵分解、奇异值分解SVD)和回归分析等方法开始被采用。

如前所述,这两种方法在处理新数据(冷启动问题)和缩小信息方面有其局限性。为了解决这些问题,演进了集成或混合推荐模型,这些模型通过结合一个或多个算法来实现更高的准确性。

从这里,我们将进入深度神经网络,这是一种非常高级的神经网络算法,由多层堆叠而成,其中特征工程正在自动化。在机器学习模型中,一个主要且困难的任务是准确构建特征;因此,正在进行研究以将深度学习方法应用于推荐引擎。

注意

更多信息,请参考以下网站:

以下是一个在推荐引擎中实现深度学习的示例:

第二阶段 - 个性化推荐系统

注意

一个推荐系统的 S 曲线将推动当前最先进的技术几乎达到其弯曲的顶端。

第三阶段 - 未来派推荐系统

在这一点上,让我暂停一下,带你们进入推荐系统的未来视角:我们正在进入一种被称为无处不在的推荐系统。无处不在的推荐者会根据你的位置、时间、心情、睡眠周期和能量输出实时推荐事物。以下图展示了这样一个未来系统:

第三阶段 - 未来派推荐系统

这意味着无论你走到哪里,无论你做什么,推荐系统都会在行动中观察你,并为你推荐事物。谷歌、Facebook 和其他主要 IT 巨头是这些推荐系统的先驱,他们几乎已经完善了这些系统,并开始提供这些无处不在的推荐系统。"谷歌 Allo"是谷歌的一个无处不在的推荐系统示例:

第三阶段 - 未来派推荐系统

到目前为止,我们根据一些分类,如用户或物品,获得推荐,但未来将更加根据可用的数字足迹为用户量身定制。谷歌 Allo是当前最好的应用之一,它持续监控你,并根据你在应用上的活动,未来肯定会行动中为你推荐事物。"谷歌 Allo"是一个聊天环境,充当虚拟助手,帮助我们处理所有查询,反过来,它将随着时间的推移学习我们的偏好和兴趣,并在行动中提供智能建议。

这就是实时上下文感知推荐系统如何也属于这类未来派推荐系统:

第三阶段 - 未来派推荐系统

我们几乎生活在数字化世界中,我们依赖互联网来做几乎所有的事情,无论是与银行、医疗保健、驾驶汽车、餐馆、旅行、个人健身等相关。在不久的将来,所有公司都会共享用户信息,使用上述所有数字足迹来创建 360 度用户画像,并且将提供高度精确的个性化、实时、情境感知的推荐。在前面的图中,我们可以看到我们如何留下数字足迹,以及公司如何相互共享数据以生成针对个人级别的定制推荐。

动机已经发生了变化,这些变化正在推动推荐引擎向未来系统的发展。以下是一些例子:

  • 搜索结束

  • 留在互联网后面

  • 从网络中浮现

自从推荐引擎出现以来,主要关注点在于客户,即使是面向未来的推荐系统,这一点也保持不变,但差异在于用户留下的数字足迹数量的增加。这种大量使用数字系统导致客户寻求更复杂的解决方案,以便使信息获取比通用性更加个性化。

搜索结束

我们正在从传统的搜索和网页集成方式转向信息和内容发现。未来的搜索引擎将使用网络搜索/个性化/广告的融合理论,这将使用户能够通过推荐而不是搜索进入内容发现。以下图显示了融合理论:

搜索结束

随着越来越多的人依赖互联网或搜索引擎在线寻找合适的产品,组织通过共享来自不同平台的用户在线活动数据来共同工作,目的是通过基于最近活动的准确个性化推荐来最小化查找相关信息或内容的搜索次数。

我将这种范式称为融合理论,其中传统的搜索、广告和推荐引擎结合在一起,通过结束互联网上的搜索,将相关内容发现带到用户个性化层面。

例如,谷歌搜索引擎和 YouTube 已经开始朝着这一融合理论迈进,以帮助用户找到相关产品信息并执行内容发现,而无需明确搜索。

最近,我在一个电子商务网站上寻找最新的信用卡大小单板计算机系列——树莓派 3,为了我的个人工作购买它。几天后,当我搜索谷歌搜索上的树莓派规格时,我注意到谷歌显示与树莓派相关的广告。虽然令人担忧,但它对我更方便,因为它消除了专门前往电子商务网站购买的任务。以下截图显示了搜索结果:

搜索结束

我们可以以 YouTube 为例。YouTube 上的建议变得越来越相关,以至于我们搜索所需信息的次数越来越少;我们跟随推荐的建议以获取更多相关主题的信息。

未来,我们将看到越来越多的应用利用这种汇聚理论来最小化用户搜索次数,以及个性化内容发现的推荐引擎。

离开网络

搜索引擎和推荐系统被用来通过联系客户来丰富社交体验和用户体验。更未来的系统将不断在线倾听他们的客户,并在最早的机会解决他们的不满,即使他们的不满是在社交媒体上表达的。以下图显示了未来推荐的动机——离开网络:

离开网络

例如,想象一下,你通过在推特上标记航班官方推特账号来表达对你最近飞行体验的不满;将努力倾听这些关切,并亲自联系客户以解决他们的问题。除此之外,他们还可能通过更多个性化的优惠来取悦客户。

未来,所有组织可能会遵循这种方法,倾听他们在社交媒体上的客户投诉或反馈,并通过良好的推荐或优惠与客户联系,这对双方可能都有益。

从网络中脱颖而出

新的范例正在演变,例如互联网电视取代传统电视。那些人们必须等待在预定时间观看他们最喜欢的节目的时代已经过去了;取而代之的是,人们希望在他们方便的时间观看节目。现在时代已经改变;我们在互联网上选择对我们方便的时间观看剧集。

这种人们心态的变化使得企业重新设计他们的商业模式以增加利润。例如,Netflix 系列剧《纸牌屋》的所有剧集一次性发布,打破了每周一集的传统方式,这取得了巨大的成功,并促使其他制作公司效仿。

这种商业模式的出现是分析人们观看模式的结果。

《纸牌屋》系列的一个有趣方面是,该系列的制作方 Netflix 利用大数据分析来分析和理解其庞大用户群体的观看模式,从而创作出一个包含观众所喜爱所有元素的剧情,并制作了一部电视剧。当该剧播出时,立刻受到了欢迎。

这种方法已经进入其他组织,以便产生更多创新,以改善客户体验。

由于上述推荐动机的转变,推荐的环境正在改变。不同的人在不同的时间、与不同的人需要不同的事情。以下图表描绘了之前所解释的内容:

从网络中浮现

一个人和家人度假时可能有一套与和朋友度假时不同的需求。同样,同一个人在两个不同的地方可能同时有不同的需求。例如,一个因商务旅行到许多国家的人可能会有不同的需求,这取决于当地条件。在热带国家的人可能需要棉质连衣裙,而同样的人当在寒冷的国家时可能需要羊毛连衣裙。以下图表详细说明了这种偏好的差异,我们可以看到在不同国家、与不同人、在 不同时间穿着不同衣服的不同人:

从网络中浮现

未来推荐系统将不断积极地倾听他们的用户,以满足他们的即时需求。

下一步最佳行动

另一种未来推荐系统将是那些足够复杂,能够预测你的下一步行动并做出相关建议,而无需你明确请求的系统。

来自电影《星际穿越》的 TARS 可能很快就会成为现实,这可能会通过考虑围绕该人的所有信息来建议人类下一步的最佳行动。

尽管 TARS 将是最高级的系统,但已经实现的人形机器人可能作为第一代下一步行动预测代理发挥作用。

值得关注的用例

在本节中,我们将列出一些可能让你对推荐引擎的未来更感兴趣的潜在用例。让我们看看一些普遍未来推荐的良好用例。

智能家居

物联网和推荐系统结合在一起形成了一种非常强大的组合,可以带来未来的推荐引擎。一个完全数字化的智能家居将是最佳用例,在未来,你的冰箱可能会在你工作时通过你的手机建议你的月度购物清单,就像下面的图片所示。同样,物联网启用型的推荐引擎是未来需要关注的:

智能家居

图片来源:http://walyou.com/smart-home-gadgets/

医疗推荐系统

医疗推荐系统是我们需要密切关注的最激动人心的领域之一。研究人员正在关注我们如何通过高级分析将个性化的医疗保健带给普通人。例如,圣母大学的研究人员开发了一个名为协作评估和推荐引擎CARE)的系统,该系统使用简单的协同过滤,根据症状的相似性找到相似的病人,并为个人生成可能的危险档案。

考虑 Proteus 数字健康公司的情况,该公司使用一个物联网启用型设备,一个可吞咽的传感器,来跟踪医疗依从性。该设备检测药物摄入,跟踪生理数据,并在患者不小心错过药片时提醒患者。

新闻作为推荐

如果你观察谷歌新闻,你可以看到背后有一个推荐引擎在工作,持续监控你的点击模式,并结合你周围的趋势,基于内容个性化的推荐引擎开始为你推荐定制新闻:

新闻作为推荐

从这个应用的书中汲取灵感,许多新公司,如 Reddit 和 Rigg 等,都在使用推荐引擎来建议新闻条目或文章作为推荐。

流行的方法

在前面的章节中,我们看到了各种推荐引擎。在本节中,我们将讨论一些流行的方法,这些方法正在积极应用于构建推荐引擎,以提高推荐的鲁棒性和相关性,例如:

  • 意外发现

  • 时间因素

  • A/B 测试

  • 反馈机制

意外发现

推荐引擎的一个缺点是,推荐引擎可能会将我们推向一个角落,在那里被建议或发现的物品将完全基于我们过去搜索过的内容或我们当前正在寻找的内容:

意外发现

信用:neighwhentheyrun

它们的工作方式就像马的眼睛一样:它们保护马不被道路上的事物分散注意力。我们在网站上进行的互动越多,推荐就越狭窄,越接近用户的个人资料。这是错误吗?不,绝对不是,但这并不是生活的运作方式。如果我们回顾过去,大多数最好的发现都是偶然发现的。惊喜为生活增添了风味。当我们意外地找到我们需要的某样东西时,我们得到的快乐是无法用言语表达的。这种特性在目前以准确性为导向的推荐系统范式中是缺失的。

通过向我们的推荐系统引入偶然性和惊喜,我们可以减少上述限制。我们如何在推荐系统中引入偶然性呢?

在生成个性化的新闻文章推荐之前,谷歌新闻会将一个地区或国家内的热门新闻结合起来。这使得用户能够获得更多与他们周围趋势相关的新闻,他们也会对这类新闻产生兴趣。

推荐引擎的时间方面

考虑以下场景:一位女士在怀孕近 9 个月的时间里购买或寻找与怀孕相关的商品。在孩子出生后,她将开始寻找与新生儿相关的商品。我们的推荐引擎应该足够智能,能够捕捉到这些信息,一旦女士开始寻找新生儿商品,推荐引擎应立即移除与怀孕相关的推荐,因为它们不再相关。

以下图片展示了如果不考虑推荐引擎的时间方面,仍然向最近出家的僧侣推荐浪漫书籍的情况:

推荐引擎的时间方面

我们的选择非常具有时效性;我们可能不会喜欢今天喜欢的东西。在设计推荐引擎时,往往不会考虑时间的这一方面。我们的推荐系统捕捉用户的每一次互动,并在特定时期内积累大量的用户偏好。由于时间信息是用户偏好的固有部分,数据科学家利用时间信息来提高推荐引擎的相关性是合理的。处理时间方面的一个简单方法是在生成推荐时给予最近互动更多的权重,而给予旧互动较少的权重。

A/B 测试

对于任何数据科学家来说,最重要的事情是什么?那就是我们构建的机器学习模型来解决当前问题的准确性。我们如何确保模型是正确的呢?我们通常在构建模型时执行评估指标,最可能的是交叉验证方法和错误/评估指标,以在将模型部署到生产之前检查模型的准确性。尽管我们在构建模型时应用了最佳实践,例如RMSE、精确率-召回率和交叉验证方法,这些你在前面的章节中学到的东西,都是在历史数据上评估的。一旦模型部署到生产环境中,我们才能了解模型的表现有多好。通常,对于一个问题并没有单一的解决方案。

在设计推荐引擎时,我们应该始终牢记以下几点:

  • 一种评估模型实时性能的方法

  • 总是使用多个模型来生成推荐,并选择最适合用户组的模型

以下展示了如何在生产场景中部署简单的 A/B 测试机制:

A/B 测试

A/B 测试来救命。在 A/B 测试中,不同的推荐集将被发送给不同的用户集,并且将在一段时间内实时评估推荐的表现。尽管成本高昂,但 A/B 测试是一种有效的实时评估模型的方法。

反馈机制

在讨论了 A/B 测试评估推荐系统实时性能之后,设计包含反馈机制的推荐系统非常重要。这是为了将用户对生成的推荐的交互发送回用户,以微调在模型创建期间包含的模型特征。

讨论包含反馈机制的简单方法如下:

反馈机制

回顾我们用来生成推荐的内容基础方法。在内容基础方法中,所有特征都被赋予相同的权重。但我们应该意识到,并非所有特征都会对推荐模型产生同等程度的贡献。为了提高模型的准确性,我们应该启用一个机制来计算特征权重。引入一个反馈机制来捕捉用户对推荐的交互,然后使用这些信息构建一个分类模型来计算模型特征权重。

摘要

在本章中,我们了解了推荐引擎是如何演变的,以及影响推荐系统演变动机的因素,随后还提到了一些值得关注的潜在用例。最后,我们简要讨论了一些在设计推荐系统前应考虑的良好方法论。有了这些信息,我相信你现在已经准备好去应对构建未来就绪的推荐引擎的要求了,这些引擎是自我学习的、可扩展的、实时的,并且具有未来感。正如本章所述,深度学习在构建更具未来感的推荐系统中可以发挥非常重要的作用。

posted @ 2025-09-04 14:14  绝不原创的飞龙  阅读(10)  评论(0)    收藏  举报