R-推荐系统构建指南-全-

R 推荐系统构建指南(全)

原文:annas-archive.org/md5/5643d791b5330511d9d6cee88149f2fc

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

推荐系统是预测用户购买和偏好的机器学习技术。推荐系统有几种应用,例如在线零售商和视频分享网站。

本书教导读者如何使用 R 构建推荐系统。它首先向读者提供一些相关的数据挖掘和机器学习概念。然后,展示了如何使用 R 构建和优化推荐模型,并概述了最流行的推荐技术。最后,展示了实际应用案例。阅读本书后,你将知道如何独立构建新的推荐系统。

本书涵盖内容

第一章, 推荐系统入门,介绍了本书内容并展示了推荐引擎的一些实际应用案例。

第二章, 推荐系统中使用的数据挖掘技术,为读者提供了构建推荐模型的工具箱:R 基础知识、数据处理和机器学习技术。

第三章, 推荐系统,介绍了几个流行的推荐系统,并展示了如何使用 R 构建其中的一些。

第四章, 评估推荐系统,展示了如何衡量推荐系统的性能以及如何优化它。

第五章, 案例研究 – 构建自己的推荐引擎,展示了如何通过构建和优化推荐系统来解决商业挑战。

本书所需条件

你将需要 R 3.0.0+、RStudio(非必需)和 Samba 4.x 服务器软件。

本书面向对象

本书面向已经具备 R 和机器学习背景知识的人群。如果你对构建推荐技术感兴趣,这本书适合你。

引用

在出版物中引用recommenderlab包(R 包版本 0.1-5),请参考由Michael Hahsler编写的recommenderlab: Lab for Developing and Testing Recommender Algorithms,可在CRAN.R-project.org/package=recommenderlab找到。

LaTeX 用户可以使用以下 BibTeX 条目:

@Manual{,
  title = {recommenderlab: Lab for Developing and Testing
  Recommender Algorithms},
  author = {Michael Hahsler},
  year = {2014},
  note = {R package version 0.1-5},
  url = { http://CRAN.R-
  project.org/package=recommenderlab},
}

习惯用法

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

文本中的代码单词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 昵称将如下所示:"我们使用了e1071包来运行 SVM"。

代码块设置如下:

vector_ratings <- factor(vector_ratings)qplot(vector_ratings) + ggtitle("Distribution of the ratings")
exten => i,1,Voicemail(s0)

新术语重要词汇将以粗体显示。

注意

警告或重要提示将以这样的框显示。

小贴士

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

读者反馈

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

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

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

客户支持

现在,您已经成为 Packt 书籍的骄傲拥有者,我们有多种方式帮助您从购买中获得最大收益。

下载示例代码

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

下载本书的颜色图像

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

勘误

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

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

盗版

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

请通过 <copyright@packtpub.com> 联系我们,并提供涉嫌盗版材料的链接。

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

问答

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

第一章. 推荐系统入门

我们在日常生活中是如何购买东西的?我们会询问朋友,研究产品规格,将产品与互联网上的类似产品进行比较,阅读匿名用户的反馈,然后做出决定。如果有一种机制可以自动完成所有这些任务,并高效地推荐最适合你的产品,会怎么样呢?推荐系统或推荐引擎就是对这个问题的回答。

在本章介绍中,我们将从以下方面定义推荐系统:

  • 帮助理解其定义

  • 解释其基本功能,并对流行的推荐系统进行一般介绍

  • 强调评估技术的重要性

理解推荐系统

你是否曾想过 LinkedIn 或 Facebook 上的“你可能认识的人”功能?这个功能根据你的朋友、朋友的朋友、地理位置、技能集、群组、喜欢的页面等因素,推荐一个你可能认识的人列表,这些人与你相似。这些推荐针对你个人,并且因用户而异。

推荐系统是提供建议的软件工具和技术,例如电子商务网站上的有用产品、YouTube 上的视频、Facebook 上朋友的推荐、Amazon 上的书籍推荐、在线新闻网站上的新闻推荐等等。

推荐系统的主要目标是向在线用户提供建议,帮助他们从网络上的众多选择中做出更好的决策。一个更好的推荐系统会更多地考虑用户的数字足迹和产品信息,如规格、用户反馈、与其他产品的比较等,在做出推荐之前。

本书结构

在这本书中,我们将学习最常用的流行推荐系统。我们还将探讨在构建推荐引擎时使用的不同机器学习技术,并附上示例代码。

本书分为 5 章:

  • 在第一章《推荐系统入门》中,你将获得推荐系统的一般介绍,如协同过滤推荐系统、基于内容的推荐系统、基于知识的推荐系统和混合系统;它还将包括简短的定义、现实世界的例子以及构建推荐系统时将学习的内容的简要细节。

  • 在第二章,推荐系统中的数据挖掘技术中,为您概述了在构建推荐系统中常用到的不同机器学习概念,以及如何解决数据分析问题。本章包括数据预处理技术,如相似度度量、降维、数据挖掘技术及其评估技术。这里解释了如欧几里得距离、余弦距离、皮尔逊相关系数等相似度度量。我们还将涵盖如 k-均值聚类、支持向量机、决策树、bagging、boosting 和随机森林等数据挖掘算法,以及流行的降维技术 PCA。简要解释了交叉验证、正则化、混淆矩阵和模型比较等评估技术。

  • 在第三章,推荐系统中,我们将讨论协同过滤推荐系统,一个基于用户和物品的推荐系统示例,使用recommenderlab R 包和MovieLens数据集。我们将涵盖模型构建,包括探索数据、将其分为训练集和测试集,以及处理二元评分。您将概述基于内容的推荐系统、基于知识的推荐系统和混合系统。

  • 在第四章,评估推荐系统中,我们将了解推荐系统的评估技术,如设置评估、评估推荐系统和优化参数。

  • 在第五章,案例研究 – 构建自己的推荐引擎中,我们将了解 R 语言中的一个用例,包括准备数据、定义评分矩阵、构建推荐引擎以及评估和优化推荐引擎的步骤。

协同过滤推荐系统

这些系统的基本思想是,如果两个用户在过去有相同的兴趣,也就是说,他们喜欢同一本书,那么他们将来也会有相似的味道。例如,如果用户 A 和用户 B 有相似的购买历史,并且用户 A 最近购买了一本用户 B 尚未看过的书,那么基本思想就是向用户 B 推荐这本书。亚马逊上的书籍推荐就是一个这类推荐系统的良好例子。

在这种推荐中,从大量备选方案中过滤项目是通过用户偏好之间的协作完成的。这类系统被称为协同过滤推荐系统。

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

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

  • 如何计算物品之间的相似度

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

协同过滤方法只考虑用户偏好,不考虑被推荐项目的特征或内容。这种方法需要大量用户偏好才能获得更准确的结果。

基于内容的推荐系统

该系统通过考虑项目与用户配置文件的相似性向用户推荐项目。简单来说,系统推荐与用户过去喜欢的项目相似的项目。项目的相似性是基于与其他比较项目的相关特征计算的,并与用户的历史偏好相匹配。

例如,我们可以假设,如果一个用户对属于动作类别的电影给予了正面评价,那么系统可以学会推荐其他动作类别的电影。

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

  • 我们如何创建项目之间的相似性?

  • 我们如何持续创建和更新用户配置文件?

这种技术没有考虑用户的邻域偏好。因此,它不需要大量用户组的偏好来提高推荐准确性。它只考虑用户的过去偏好和项目的属性/特征。

基于知识的推荐系统

这类推荐系统在用户购买历史较小的特定领域中被采用。在这些系统中,算法在给出推荐之前会考虑关于项目的知识,例如特征、用户显式提出的偏好和推荐标准。模型的准确性是根据推荐项目对用户的有用性来判断的。例如,假设你正在构建一个推荐系统,该系统推荐家用电器,如空调,其中大多数用户将是第一次购买。在这种情况下,系统会考虑项目的特征,并通过从用户那里获取额外信息(如规格)来生成用户配置文件,然后进行推荐。这类系统被称为基于约束的推荐系统,我们将在后续章节中了解更多。

在构建这些类型的推荐系统之前,我们考虑以下问题:

  • 模型中包含哪些关于项目的信息?

  • 如何显式地捕捉用户偏好?

混合系统

我们通过结合各种推荐系统来构建更稳健的混合推荐系统。通过结合各种推荐系统,我们可以消除一个系统的缺点,利用另一个系统的优点,从而构建一个更稳健的系统。例如,通过结合协同过滤方法,当新项目没有评分时模型会失败,与基于内容的系统相结合,其中包含关于项目的特征信息,可以更准确、更有效地推荐新项目。

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

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

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

评估技术

在将推荐系统推广给用户之前,我们如何确保系统是高效的或准确的?我们基于什么来声明系统是好的?正如之前所述,任何推荐系统的目标都是向用户推荐更多相关和有用的项目。在开发新的方法来评估推荐系统以提高系统准确性的研究中,已经进行了大量的研究。

在第四章《评估推荐系统》中,我们将了解用于评估推荐系统的不同评估指标,包括设置评估、评估推荐系统、优化参数。本章还重点介绍了在设计和发展推荐系统阶段评估系统的重要性,以及根据关于项目和问题陈述的信息选择算法时应遵循的指南。本章还涵盖了推荐系统评估的不同实验设置。

案例研究

在第五章《案例研究 – 构建自己的推荐引擎》中,我们通过以下步骤进行案例研究并逐步构建推荐系统:

  1. 我们选取一个现实生活中的案例,理解问题陈述及其领域方面

  2. 我们接下来进行数据准备、数据源识别和数据清洗步骤

  3. 然后,我们为推荐系统选择一个算法

  4. 在构建模型的同时,我们深入研究设计和开发方面

  5. 最后,我们评估和测试推荐系统

推荐系统的实现使用 R 语言,书中将提供代码示例。在本章结束时,你将足够自信地构建自己的推荐引擎。

未来展望

在最后一章中,我将通过总结本书的内容和涵盖的主题来结束。我们将关注你将不得不进行的研究的未来范围。然后,我们将简要介绍当前推荐系统领域的研究主题和进展。我还会在本书的整个过程中列出书籍参考文献和在线资源。

摘要

在本章中,你将阅读市场上流行的推荐系统的概述。在下一章中,你将了解在推荐系统中使用的不同机器学习技术。

第二章:推荐系统中使用的数据挖掘技术

尽管本书的主要目标是构建推荐系统,但在构建推荐系统之前,了解常用的数据挖掘技术是必要的步骤。在本章中,你将了解推荐系统中常用的数据预处理技术、数据挖掘技术和数据评估技术。本章的第一部分告诉你如何解决数据分析问题,随后是数据预处理步骤,如相似度度量降维。本章的下一部分处理数据挖掘技术及其评估技术。

相似度度量包括:

  • 欧几里得距离

  • 余弦距离

  • 皮尔逊相关系数

降维技术包括:

  • 主成分分析

数据挖掘技术包括:

  • k-means 聚类

  • 支持向量机

  • 集成方法,如 bagging、boosting 和随机森林

解决数据分析问题

任何数据分析问题都涉及一系列步骤,例如:

  • 识别业务问题。

  • 在领域专家的帮助下理解问题域。

  • 识别适合分析的数据来源和数据变量。

  • 数据预处理或清洗步骤,例如识别缺失值、定量和定性变量以及转换等。

  • 执行探索性分析以理解数据,主要通过箱线图或直方图等视觉图形。

  • 执行基本统计,如均值、中位数、众数、方差、标准差、变量之间的相关性以及协方差,以了解数据的性质。

  • 将数据分为训练集和测试集,并使用机器学习算法和训练集运行模型,使用交叉验证技术。

  • 使用测试数据验证模型,以评估模型在新数据上的表现。如果需要,根据验证步骤的结果改进模型。

  • 可视化结果并将模型部署用于实时预测。

以下图像显示了数据分析问题的解决方案:

解决数据分析问题

数据分析步骤

数据预处理技术

数据预处理是任何数据分析问题的关键步骤。模型的准确性主要取决于数据的质量。通常,任何数据预处理步骤都涉及数据清洗、转换、识别缺失值以及如何处理它们。只有预处理后的数据才能输入到机器学习算法中。在本节中,我们将主要关注数据预处理技术。这些技术包括相似度度量(如欧几里得距离、余弦距离和皮尔逊系数)以及降维技术,例如广泛用于推荐系统的主成分分析PCA)。除了 PCA 之外,我们还有奇异值分解SVD)、子集特征选择方法来降低数据集的维度,但我们的研究仅限于 PCA。

相似度度量

如前一章所述,每个推荐系统都是基于物品或用户之间的相似性概念工作的。在本节中,让我们探讨一些相似度度量,例如欧几里得距离、余弦距离和皮尔逊相关系数。

欧几里得距离

计算两个物品之间相似度的最简单技术是计算其欧几里得距离。在数据集中,两个点/对象(点 x 和点 y)之间的欧几里得距离由以下方程定义:

欧几里得距离

在这个方程中,(x, y) 是两个连续的数据点,n 是数据集的属性数量。

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

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

余弦距离

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

余弦距离

计算余弦距离的 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 是包含数据集中所有变量的矩阵。cosine 函数在 lsa 包中可用。

皮尔逊相关系数

两个产品之间的相似度也可以通过它们变量之间的相关系数来表示。皮尔逊相关系数是两个变量之间的一种流行相关系数,它是两个变量的协方差除以它们标准差的乘积。这由 ƿ (rho) 给出:

皮尔逊相关系数

R 脚本如下所示:

Coef = cor(mtcars, method="pearson")
where mtcars is the dataset

实证研究表明,皮尔逊系数在基于用户的协同过滤推荐系统中优于其他相似度度量。研究还表明,余弦相似度在基于物品的协同过滤中表现始终良好。

维度约简

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

主成分分析

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

从技术角度讲,主成分分析(PCA)通过将高度相关的变量正交投影到一组线性不相关的变量(称为主成分)上来实现。主成分的数量小于或等于原始变量的数量。这种线性变换被定义为第一个主成分具有最大的可能方差。它通过考虑高度相关的特征来解释数据中的尽可能多的变异性。每个后续成分依次使用与第一个主成分相关性较低且与前一个成分正交的特征,以获得最高的方差。

让我们用简单的话来理解这个概念。假设我们有一个三维数据空间,其中有两个特征比与第三个特征更相关。现在我们想使用 PCA 将数据减少到二维空间。第一个主成分是以一种方式创建的,它使用数据中的两个相关变量来解释最大的方差。在下面的图中,第一个主成分(较粗的线)沿着数据解释了大部分的方差。为了选择第二个主成分,我们需要选择另一条具有最高方差、不相关且与第一个主成分正交的线。PCA 的实现和技术细节超出了本书的范围,因此我们将讨论如何在 R 中使用它。

主成分分析

我们将使用 USArrests 数据集来说明 PCA。USArrests 数据集包含与犯罪相关的统计数据,例如袭击、谋杀、强奸和每 10 万人中的城市人口数,这些数据来自美国 50 个州:

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

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

#let us use apply() to the USArrests dataset row wise to calculate the variance to see how each variable is varying
apply(USArrests , 2, var)

Murder    Assault   UrbanPop       Rape
  18.97047 6945.16571  209.51878   87.72916
#We observe that Assault has the most variance. It is important to note at this point that

#Scaling the features is a very step while applying PCA.

#Applying PCA after scaling the feature as below
pca =prcomp(USArrests , scale =TRUE)

pca

标准差

[1] 1.5748783 0.9948694 0.5971291 0.4164494

旋转

                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

#Now lets us understand the components of pca output.

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

#Pca$rotation  contains the principal component loadings matrix which explains

#proportion of each variable along each principal component.

#now let us learn interpreting the results of pca using biplot graph. Biplot is used to how the proportions of each variable along the two principal components.

#below code changes the directions of the biplot, if we donot include the below two lines the plot will be mirror image to the below one.
pca$rotation=-pca$rotation
pca$x=-pca$x
biplot (pca , scale =0)

上述代码的输出如下:

主成分分析

在前面的图像中,被称为双图,我们可以看到 USArrests 数据集的两个主成分(PC1PC2)。红色箭头代表载荷向量,它们表示特征空间沿着主成分向量的变化。

从图中我们可以看到,第一个主成分向量,PC1,在三个特征:强奸攻击谋杀上或多或少给予相同的权重。这意味着这三个特征之间的相关性比城市人口特征更高。在第二个主成分,PC2比其他三个特征更重视城市人口,而其他三个特征与它们的相关性较低。

数据挖掘技术

在本节中,我们将探讨常用的数据挖掘算法,如 k-均值聚类、支持向量机、决策树、bagging、boosting 和随机森林。交叉验证、正则化、混淆矩阵和模型比较等评估技术将简要介绍。

聚类分析

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

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

在前面的例子中,每个组被称为一个聚类,聚类中的每个成员(数据点)的行为与其组内成员相似。

聚类分析

聚类分析

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

解释 k-均值聚类算法

k-均值是一种无监督的迭代算法,其中 k 是要从数据中形成的聚类数量。聚类分为两个步骤:

  1. 聚类分配步骤:在这个步骤中,我们随机选择两个聚类点(红色点和绿色点)并将每个数据点分配给离它更近的聚类点(以下图像的上半部分)。

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

前面的步骤会重复进行,直到所有数据点都被分成两组,并且移动后的数据点均值不发生变化。

解释 k-means 聚类算法

聚类分析的步骤

前面的图像展示了聚类算法如何在数据上工作以形成簇。以下是如何在鸢尾花数据集上实现 k-均值聚类的 R 语言版本:

#k-means clustering
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)

前面代码的输出如下:

解释 k-means 聚类算法

聚类分析结果

前面的图像显示了在鸢尾花数据上形成簇的情况,这些簇占数据量的 95%。在先前的例子中,使用 elbow 方法选择了 k 值的簇数,如下所示:

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 值的成本降低:

解释 k-means 聚类算法

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

支持向量机

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

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

支持向量机

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

这里有一些需要注意的关键点:

  • 虽然可以创建无限多个超平面,但 SVM 只选择具有最大边界的超平面,即离训练观察结果最远的分离超平面。

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

  • 决策边界只受支持向量的影响,而不受远离边界的其他观察结果的影响。如果我们改变除了支持向量之外的数据点,决策边界不会有任何影响。然而,如果支持向量改变,决策边界会改变。

  • 训练数据上的大边界也会在测试数据上有大边界,以便正确分类测试数据。

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

在以下代码片段中,您可以查看在鸢尾花数据集上 SVM 的 R 实现。我们使用了e1071包来运行 SVM。在 R 中,SVM()函数包含了e1071包中存在的支持向量机的实现。

现在,我们将看到SVM()方法与tune()方法一起被调用,后者进行交叉验证并在成本参数的不同值上运行模型。

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

  #SVM
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

调用:

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)

参数:

   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

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

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

pred = predict(model,test)

决策树

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

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

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

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

  3. 现在,根据与花瓣长度 < 4.5>= 4.5 相同的变量,进一步将花瓣长度 > 2.45 的数据进行分割,如下面的图像所示。

  4. 这种数据分割将进一步通过缩小数据空间进行细分,直到所有底部点都代表响应变量或数据不能再进行进一步逻辑分割。

在下面的决策树图像中,我们有一个根节点,四个发生数据分割的内部节点,以及五个不能再进行数据分割的终端节点。它们定义如下:

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

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

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

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

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

决策树

请参阅使用综合 R 档案网络CRAN)提供的 tree 包在 iris 数据集上实现的决策树。

模型的总结信息在此给出。它告诉我们误分类率为 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)

分类树

tree(formula = Species ~ ., data = train, x = TRUE, y = TRUE)
Variables actually used in tree construction:
[1] "Petal.Length" "Sepal.Length" "Petal.Width"
Number of terminal nodes:  5
Residual mean deviance:  0.1332 = 13.32 / 100
Misclassification error rate: 0.0381 = 4 / 105 '
//plotting the decision tree
plot(model)text(model)
pred = predict(model,test[,-5],type="class")
> pred
 [1] setosa     setosa     virginica  setosa     setosa     setosa     versicolor
 [8] virginica  virginica  setosa     versicolor versicolor virginica  versicolor
[15] virginica  virginica  setosa     virginica  virginica  versicolor virginica
[22] versicolor setosa     virginica  setosa     versicolor virginica  setosa    
[29] versicolor versicolor versicolor virginica  setosa     virginica  virginica
[36] versicolor setosa     versicolor setosa     versicolor versicolor setosa    
[43] versicolor setosa     setosa    
Levels: setosa versicolor virginica

集成方法

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

Bagging

Bagging 也称为 Bootstrap aggregating。它旨在提高机器学习算法的稳定性和准确性。它有助于避免过拟合并减少方差。这主要用于决策树。

Bagging 涉及从数据集中随机生成 Bootstrap 样本并单独训练模型。然后通过聚合或平均所有响应变量进行预测:

  • 例如,考虑一个数据集(Xi, Yi),其中i=1 …n,包含n个数据点。

  • 现在,使用 Bootstrap 技术从原始数据集中随机选择带有替换的 B 样本。

  • 接下来,独立地对 B 样本使用回归/分类模型进行训练。然后,通过平均回归情况下所有生成的 B 模型的响应,在测试集上进行预测。或者,在分类情况下,生成 B 样本中最常出现的类别。

Random forests

随机森林是比自助聚合或袋装方法更复杂的监督算法,尽管它们基于类似的方法。与在袋装中使用 Bootstrap 技术生成的所有 B 样本中选择所有变量不同,我们从总变量中随机选择几个预测变量作为每个 B 样本。然后,这些样本使用模型进行训练。预测是通过平均每个模型的预测结果来进行的。每个样本中的预测变量数量是通过公式 m = √p 来决定的,其中 p 是原始数据集中变量的总数。

这里有一些关键点:

  • 这种方法消除了数据集中强预测变量的依赖性条件,因为我们故意选择比每次迭代的全部变量更少的变量

  • 这种方法还会去相关变量,导致模型中的变异性降低,因此更可靠

请参考 CRAN 上可用的randomForest包在鸢尾花数据集上的随机森林 R 实现:

#randomForest
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)
model

调用

 randomForest(formula = Species ~ ., data = train, mtry = 2, importance = TRUE,      proximity = TRUE)
               Type of random forest: classification
                     Number of trees: 500
No. of variables tried at each split: 2

        OOB estimate of  error rate: 5.71%
Confusion matrix:
           setosa versicolor virginica class.error
setosa         40          0         0  0.00000000
versicolor      0         28         3  0.09677419
virginica       0          3        31  0.08823529

pred = predict(model,newdata=test[,-5])
pred
pred
       119         77         88         90         51         20         96
 virginica versicolor versicolor versicolor versicolor     setosa versicolor
         1          3        118        127          6        102          5
    setosa     setosa  virginica  virginica     setosa  virginica     setosa
        91          8         23        133         17         78         52
versicolor     setosa     setosa  virginica     setosa  virginica versicolor
        63         82         84        116         70         50        129
versicolor versicolor  virginica  virginica versicolor     setosa  virginica
       150         34          9        120         41         26        121
 virginica     setosa     setosa  virginica     setosa     setosa  virginica
       145        138         94          4        104         81        122
 virginica  virginica versicolor     setosa  virginica versicolor  virginica
        18        105        100
    setosa  virginica versicolor
Levels: setosa versicolor virginica

Boosting

与袋装不同,袋装会创建多个 Bootstrap 样本副本,每个数据集副本都会拟合一个新的模型,并将所有单个模型组合成一个预测模型,每个新模型都是使用先前构建的模型的信息构建的。Boosting 可以理解为涉及两个步骤的迭代方法:

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

  • 现在,从该模型计算残差并更新为上一步中使用的残差

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

#Boosting in 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

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

//the preceding summary states the relative importance of the variables of the model.

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

pred[1:5,,]
        setosa versicolor virginica
[1,]  5.630363  -2.947531 -5.172975
[2,]  5.640313  -3.533578 -5.103582
[3,] -5.249303   3.742753 -3.374590
[4,] -5.271020   4.047366 -3.770332
[5,] -5.249324   3.819050 -3.439450

//pick the response with the highest probability from the resulting pred matrix, by doing apply(.., 1, which.max) on the vector output from prediction.
p.pred <- apply(pred,1,which.max)
p.pred
[1] 1 1 3 3 2 2 3 1 3 1 3 2 2 1 2 3 2 2 3 3 1 1 3 1 3 3 3 1 1 2 2 2 2 2 2 2 1 1 3 1 2
[42] 1 3 2 3

评估数据挖掘算法

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

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

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

  • 模型与未来数据或测试数据拟合得有多好

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

过拟合是指模型在训练数据上表现良好,但在测试数据上表现真的很差的情况。这种情况发生在模型记住数据模式而不是从数据中学习的时候。例如,假设数据以非线性模式分布,而我们拟合了一个复杂模型,用绿色线表示。在这种情况下,我们观察到模型非常接近数据分布进行拟合,注意到了所有的起伏。在这种情况下,模型最有可能在之前未见过的数据上失败。

评估数据挖掘算法

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

任何拟合模型都会通过交叉验证、正则化、剪枝、模型比较、ROC 曲线、混淆矩阵等来评估,以避免之前提到的场景。

交叉验证:这是几乎所有模型模型评估的一个非常流行的技术。在这种技术中,我们将数据分为两个数据集:一个训练数据集和一个测试数据集。模型使用训练数据集构建并使用测试数据集评估。这个过程重复多次。每次迭代都计算测试误差。计算所有迭代的平均测试误差,以概括所有迭代结束时模型的准确性。

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

混淆矩阵:这种技术在评估分类模型时非常流行。我们使用模型的输出结果构建一个混淆矩阵。我们计算精确度和召回率/灵敏度/特异性来评估模型。

精确度:这是真正分类记录是否相关的概率。

召回率/灵敏度:这是相关记录是否真正被分类的概率。

特异性:也称为真正负率,这是真正分类错误记录的比例。

下图所示的混淆矩阵是使用前一小节中讨论的分类模型的结果构建的:

评估数据挖掘算法

让我们了解混淆矩阵:

真正 (TP): 这是实际响应为负且模型预测为正的所有响应的计数。

误报 (FP): 这是实际响应为负但模型预测为正的所有响应的计数。通常,这是一个误报

漏报 (FN): 这是实际响应为正但模型预测为负的所有响应的计数。通常,这是一个漏报

真负 (TN): 这是实际响应为负且模型预测也为负的所有响应的计数。

从数学上讲,精确度和召回率/特异性计算如下:

评估数据挖掘算法

模型比较:可以使用一个或多个统计模型来解决分类问题。例如,可以使用逻辑回归、决策树、集成方法和 SVM 来解决分类问题。您如何选择哪个模型与数据拟合得更好?有几种方法可用于合适的模型选择,例如赤池信息量准则 (AIC)、贝叶斯信息准则 (BIC)和调整后的 R²、Cᵨ。对于每个模型,计算 AIC / BIC / 调整后的 R²。具有这些值中最小值的模型被选为最佳模型。

小贴士

下载示例代码

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

概述

在本章中,您学习了在推荐系统中常用的一些流行数据预处理技术、数据挖掘技术和评估技术。在下一章中,您将更详细地学习第一章中介绍的推荐系统,即“推荐系统入门”。

第三章。推荐系统

本章展示了某些流行的推荐技术。此外,我们将在 R 中实现其中的一些。

我们将处理以下技术:

  • 协同过滤:这是我们将会详细探讨的技术分支。算法基于关于相似用户或相似物品的信息。两个子分支如下:

    • 基于物品的协同过滤:这会向用户推荐与用户购买最相似的物品

    • 基于用户的协同过滤:这会向用户推荐最被相似用户偏好的物品

  • 基于内容的过滤:这是为每个用户定义的;它定义了一个用户配置文件并识别与它匹配的物品。

  • 混合过滤:这是结合不同技术。

  • 基于知识的过滤:这是使用关于用户和物品的显式知识。

推荐系统 R 包 – recommenderlab

在本章中,我们将使用 recommenderlab 构建推荐系统,这是一个用于协同过滤的 R 包。本节将简要介绍这个包。首先,如果我们还没有安装它,让我们来安装它:

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

现在,我们可以加载这个包。然后,使用 help 函数,我们可以查看其文档:

library("recommenderlab")
help(package = "recommenderlab")

当我们在 RStudio 中运行前面的命令时,将打开一个包含一些链接和函数列表的帮助文件。

本章中您将看到的示例包含一些随机成分。为了能够重现获得相同输出的代码,我们需要运行这一行:

set.seed(1)

现在,我们已准备好开始探索 recommenderlab

数据集

与许多其他 R 包一样,recommenderlab 包含一些可以用来尝试函数的数据集:

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

Jester5k、MSWeb 和 MovieLense

在我们的示例中,我们将使用 MovieLense 数据集;数据是关于电影的。表格包含用户对电影的评分。让我们加载数据并查看一下:

data(MovieLense)
MovieLense
## 943 x 1664 rating matrix of class 'realRatingMatrix' with 99392 ratings.

MovieLense 的每一行对应一个用户,每一列对应一部电影。用户和电影之间有超过 943 x 1664 = 1,500,000 种组合。因此,存储完整的矩阵将需要超过 1,500,000 个单元格。然而,并非每个用户都看过每部电影。因此,评分少于 100,000,矩阵是稀疏的。recommenderlab 包允许我们以紧凑的方式存储它。

评分矩阵的类

在本节中,我们将详细探讨 MovieLense

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

realRatingMatrix 类由 recommenderlab 定义,ojectsojectsb 包含稀疏评分矩阵。让我们看看我们可以应用于此类对象的哪些方法:

methods(class = class(MovieLense))
[ dimnames<- Recommender
binarize dissimilarity removeKnownRatings
calcPredictionAccuracy evaluationScheme rowCounts
calcPredictionAccuracy getData.frame rowMeans
colCounts getList rowSds
colMeans getNormalize rowSums
colSds getRatings sample
colSums getTopNLists show
denormalize image similarity
dim normalize
dimnames nratings

一些适用于矩阵的方法已经以更优化的方式重新定义。例如,我们可以使用 dim 来提取行数和列数,使用 colSums 来计算每列的总和。此外,还有一些专门针对推荐系统的新方法。

通常,评分矩阵是稀疏矩阵。因此,realRatingMatrix 类支持稀疏矩阵的紧凑存储。让我们比较 MovieLense 与相应的 R 矩阵的大小:

object.size(MovieLense)
## 1388448 bytes
object.size(as(MovieLense, "matrix"))
## 12740464 bytes

我们可以计算 recommenderlab 矩阵的紧凑性是原来的多少倍:

object.size(as(MovieLense, "matrix")) / object.size(MovieLense)
## 9.17604692433566 bytes

如预期的那样,MovieLense 占用的空间比等效的标准 R 矩阵要少得多。比率约为 1:9,原因是 MovieLense 的稀疏性。标准 R 矩阵对象将所有缺失值存储为 0,因此它存储了 15 倍更多的单元格。

计算相似度矩阵

协同过滤算法基于测量用户或项目之间的相似度。为此,recommenderlab 包含了 similarity 函数。支持的相似度计算方法有 cosinepearsonjaccard

例如,我们可能想确定前五个用户彼此之间有多相似。让我们使用 cosine 距离来计算这个:

similarity_users <- similarity(MovieLense[1:4, ], method = "cosine", which = "users")

similarity_users 对象包含所有的不相似度。让我们来探索它:

class(similarity_users)
## [1] "dist"

如预期的那样,similarity_users 是一个包含距离的对象。由于 dist 是一个基本的 R 类,我们可以以不同的方式使用它。例如,我们可以使用 hclust 来构建层次聚类模型。

我们还可以将 similarity_users 转换为矩阵并可视化它:

as.matrix(similarity_users)
1 2 3 4
0 0.1689 0.03827 0.06635
0.1689 0 0.09707 0.1531
0.03827 0.09707 0 0.3334
0.06635 0.1531 0.3334 0

使用 image,我们可以可视化矩阵。每一行和每一列对应一个用户,每个单元格对应两个用户之间的相似度:

image(as.matrix(similarity_users), main = "User similarity")

单元格越红,两个用户就越相似。请注意,对角线是红色的,因为它是在比较每个用户与其自身:

计算相似度矩阵

使用相同的方法,我们可以计算并可视化前四个项目之间的相似度:

similarity_items <- similarity(MovieLense[, 1:4], method = "cosine", which = "items")
as.matrix(similarity_items)
玩具总动员 (1995) 黄金眼 (1995)
玩具总动员 (1995) 0 0.4024
黄金眼 (1995) 0.4024 0
四个房间 (1995) 0.3302 0.2731
小鬼当家 (1995) 0.4549 0.5026

表格将继续如下:

四个房间 (1995) 小鬼当家 (1995)
玩具总动员 (1995) 0.3302 0.4549
黄金眼 (1995) 0.2731 0.5026
四个房间 (1995) 0 0.3249
Get Shorty (1995) 0.3249 0

与前面的截图类似,我们可以使用此图像可视化矩阵:

image(as.matrix(similarity_items), main = "Item similarity")

计算相似度矩阵

相似性是协同过滤模型的基础。

推荐模型

recommenderlab包包含一些推荐算法的选项。我们可以使用recommenderRegistry$get_entries显示适用于realRatingMatrix对象的模型:

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

recommender_models对象包含有关模型的一些信息。首先,让我们看看我们有哪些模型:

names(recommender_models)
模型
IBCF_realRatingMatrix
PCA_realRatingMatrix
POPULAR_realRatingMatrix
RANDOM_realRatingMatrix
SVD_realRatingMatrix
UBCF_realRatingMatrix

让我们看看它们的描述:

lapply(recommender_models, "[[", "description")
## $IBCF_realRatingMatrix
## [1] "Recommender based on item-based collaborative filtering (real data)."
##
## $PCA_realRatingMatrix
## [1] "Recommender based on PCA approximation (real data)."
##
## $POPULAR_realRatingMatrix## [1] "Recommender based on item popularity (real data)."
##
## $RANDOM_realRatingMatrix
## [1] "Produce random recommendations (real ratings)."
##
## $SVD_realRatingMatrix
## [1] "Recommender based on SVD approximation (real data)."
##
## $UBCF_realRatingMatrix
## [1] "Recommender based on user-based collaborative filtering (real data)."

在其中,我们将使用IBCFUBCF

recommender_models 对象还包含一些其他信息,例如其参数:

recommender_models$IBCF_realRatingMatrix$parameters
参数 默认值
k 30
method Cosine
normalize center
normalize_sim_matrix FALSE
alpha 0.5
na_as_zero FALSE

对于包的更详细描述和一些用例,您可以查看包的 vignette。您可以通过输入help(package = "recommenderlab")找到所有材料。

recommenderlab包是一个好且灵活的包,用于执行推荐。如果我们将其模型与其他 R 工具结合,我们将拥有一个强大的框架来探索数据和构建推荐模型。

在下一节中,我们将使用recommenderlab的一些工具探索其数据集。

数据探索

在本节中,我们将探索MovieLense数据集。为此,我们将使用recommenderlab构建推荐系统,并使用ggplot2可视化其结果。让我们加载包和数据:

library("recommenderlab")
library("ggplot2")
data(MovieLense)
class(MovieLense)
## [1] "realRatingMatrix"
## attr(,"package")
## [1] "recommenderlab"

MovieLense是一个包含关于电影评分数据集的realRatingMatrix对象。每一行对应一个用户,每一列对应一部电影,每个值对应一个评分。

探索数据的性质

让我们快速看一下MovieLense。正如前一部分所解释的,有一些通用方法可以应用于realRatingMatrix对象。我们可以使用dim提取它们的大小:

dim(MovieLense)
## [1]  943 1664

943个用户和1664部电影。由于realRatingMatrix是 S4 类,对象的组件包含在MovieLense槽中。我们可以使用slotNames查看所有槽,它显示对象中存储的所有数据:

slotNames(MovieLense)
## [1] "data"      "normalize"
MovieLense contains a data slot. Let's take a look at it.
class(MovieLense@data)
## [1] "dgCMatrix"
## attr(,"package")
## [1] "Matrix"
dim(MovieLense@data)
## [1]  943 1664

MovieLense@data属于从Matrix继承的dgCMatrix类。为了执行自定义数据探索,我们可能需要访问这个槽。

探索评分的值

从槽数据开始,我们可以探索矩阵。让我们看看评分。我们可以将矩阵转换为向量并探索其值:

vector_ratings <- as.vector(MovieLense@data)
unique(vector_ratings)
## [1] 5 4 0 3 1 2
The ratings are integers in the range 0-5\. Let's count the occurrences of each of them.
table_ratings <- table(vector_ratings)
table_ratings
评分 出现次数
0 1469760
1 6059
2 11307
3 27002
4 33947
5 21077

根据文档,评分等于 0 表示缺失值,因此我们可以从vector_ratings中删除它们:

vector_ratings <- vector_ratings[vector_ratings != 0]

现在,我们可以构建评分的频率图。为了可视化带有频率的条形图,我们可以使用ggplot2。让我们使用因子将它们转换为类别并构建一个快速图表:

vector_ratings <- factor(vector_ratings)

让我们使用qplot可视化它们的分布:

qplot(vector_ratings) + ggtitle("Distribution of the ratings")

以下图像显示了评分的分布:

探索评分值

大多数评分都高于2,最常见的评分是4

探索哪些电影被观看

MovieLense开始,我们可以轻松地使用以下方法等提取快速结果:

  • colCounts:这是每列非缺失值的数量

  • colMeans:这是每列的平均值

例如,哪些是最受欢迎的电影?我们可以使用colCounts来做到这一点。首先,让我们计算每部电影的观看次数:

views_per_movie <- colCounts(MovieLense)

然后,我们可以按观看次数对电影进行排序:

table_views <- data.frame(
  movie = names(views_per_movie),
  views = views_per_movie
  )
table_views <- table_views[order(table_views$views, decreasing = TRUE), ]

现在,我们可以可视化前六行并构建直方图:

ggplot(table_views[1:6, ], aes(x = movie, y = views)) + geom_bar(stat="identity") + theme(axis.text.x = element_text(angle = 45, hjust = 1)) + ggtitle("Number of views of the top movies")

以下图像显示了顶级电影的观看次数:

探索哪些电影被观看

在前面的图表中,你可以注意到星球大战(1977)是最受欢迎的电影,比其他电影多出大约 100 次观看。

探索平均评分

我们可以通过计算每部电影的平均评分来识别评分最高的电影。为此,我们可以使用colMeans;它自动忽略 0,因为它们代表缺失值。让我们看看平均电影评分的分布:

average_ratings <- colMeans(MovieLense)

让我们使用qplot构建图表:

qplot(average_ratings) + stat_bin(binwidth = 0.1) + ggtitle("Distribution of the average movie rating")

以下图像显示了平均电影评分的分布:

探索平均评分

最高值大约是 3,有几部电影的评分是 1 或 5。可能的原因是这些电影只收到了少数人的评分,因此我们不应该考虑它们。我们可以删除观看次数低于定义阈值的电影,例如,低于100

average_ratings_relevant <- average_ratings[views_per_movie > 100]

让我们构建图表:

qplot(average_ratings_relevant) + stat_bin(binwidth = 0.1) + ggtitle(paste("Distribution of the relevant average ratings"))

以下图像显示了相关平均评分的分布:

探索平均评分

所有评分都在 2.3 到 4.5 之间。正如预期的那样,我们移除了极端值。最高值有所变化,现在大约是 4。

可视化矩阵

我们可以通过构建一个颜色代表评分的热图来可视化矩阵。矩阵的每一行对应一个用户,每一列对应一部电影,每个单元格对应其评分。为此,我们可以使用通用方法:imagerecommenderlab包重新定义了realRatingMatrix对象的方法image

让我们使用image构建热图:

image(MovieLense, main = "Heatmap of the rating matrix")

以下图像显示了评分矩阵的热图:

可视化矩阵

我们可以在右上角注意到一个空白区域。原因是行和列已经排序。

由于用户和项目太多,这个图表难以阅读。我们可以构建另一个图表,聚焦于前几行和列。

让我们使用image构建热图:

image(MovieLense[1:10, 1:15], main = "Heatmap of the first rows and columns")

下图显示了前几行和列的热图:

可视化矩阵

一些用户比其他用户看了更多的电影。然而,这个图表只是显示了一些随机用户和项目。如果我们选择最相关的用户和项目会怎样?这意味着只可视化那些看过很多电影的用户和被很多用户观看的电影。为了识别和选择最相关的用户和电影,请按照以下步骤操作:

  1. 确定每个用户观看电影的最小数量。

  2. 确定每部电影观看用户的最小数量。

  3. 选择符合这些标准的用户和电影。

例如,我们可以可视化用户和电影的最高百分位数。为了做到这一点,我们使用quantile函数:

min_n_movies <- quantile(rowCounts(MovieLense), 0.99)
min_n_users <- quantile(colCounts(MovieLense), 0.99)
min_n_movies
##    99%
## 440.96
min_n_users
##    99%
## 371.07

现在,我们可以可视化符合标准的行和列。

让我们使用image构建热图:

image(MovieLense[rowCounts(MovieLense) > min_n_movies, colCounts(MovieLense) > min_n_users], main = "Heatmap of the top users and movies")

下图显示了顶级用户和电影的热图:

可视化矩阵

让我们考虑那些观看电影更多的用户。他们中的大多数都看过所有顶级电影,这并不令人惊讶。我们可以注意到一些比其他列更暗的列。这些列代表评分最高的电影。相反,较暗的行代表给予更高评价的用户。因此,我们可能需要标准化数据。

在本节中,我们已经探索了数据。在下一节中,我们将对其进行处理和转换,以便为推荐模型提供输入。

数据准备

本节将向您展示如何准备用于推荐模型的数据。按照以下步骤操作:

  1. 选择相关的数据。

  2. 标准化数据。

选择最相关的数据

在探索数据时,我们注意到表格包含:

  • 只被观看几次的电影。由于数据不足,他们的评价可能存在偏见。

  • 只评价了几部电影的用户。他们的评价可能存在偏见。

我们需要确定每部电影观看用户的最小数量,反之亦然。正确的解决方案来自于对整个数据准备、构建推荐模型和验证过程的迭代。由于我们第一次实现模型,我们可以使用一个经验法则。在构建了模型之后,我们可以回来修改数据准备。

我们将定义包含我们将使用的矩阵的ratings_movies。它考虑了:

  • 至少评价了 50 部电影的用户

  • 至少被观看 100 次的电影

以下代码定义了前面的点:

ratings_movies <- MovieLense[rowCounts(MovieLense) > 50, colCounts(MovieLense) > 100] ratings_movies
## 560 x 332 rating matrix of class 'realRatingMatrix' with 55298 ratings.

MovieLense相比,ratings_movies对象包含大约一半的用户和五分之一的电影。

探索最相关的数据

使用与上一节相同的方法,让我们在新矩阵中可视化前 2%的用户和电影:

# visualize the top matrix
min_movies <- quantile(rowCounts(ratings_movies), 0.98)
min_users <- quantile(colCounts(ratings_movies), 0.98)

让我们构建热图:

image(ratings_movies[rowCounts(ratings_movies) > min_movies, colCounts(ratings_movies) > min_users], main = "Heatmap of the top users and movies")

以下图像显示了顶级用户和电影的热图:

探索最相关的数据

如我们所注意到的,一些行比其他行更暗。这可能意味着一些用户对所有电影都给出了更高的评分。然而,我们只可视化了顶级电影。为了全面了解所有用户,让我们看看用户平均评分的分布:

average_ratings_per_user <- rowMeans(ratings_movies)

让我们可视化分布:

qplot(average_ratings_per_user) + stat_bin(binwidth = 0.1) + ggtitle("Distribution of the average rating per user")

以下图像显示了每个用户的平均评分分布:

探索最相关的数据

如所怀疑的那样,平均评分在不同用户之间差异很大。

对数据进行归一化

给所有电影都给出高(或低)评分的用户可能会影响结果。我们可以通过将数据归一化,使得每个用户的平均评分为 0 来消除这种影响。预构建的normalize函数会自动完成这项工作:

ratings_movies_norm <- normalize(ratings_movies)

让我们看看用户平均评分:

sum(rowMeans(ratings_movies_norm) > 0.00001)
## [1] 0

如预期的那样,每个用户的平均评分是0(除了近似误差)。我们可以使用image可视化新矩阵。让我们构建热图:

# visualize the normalized matrix
image(ratings_movies_norm[rowCounts(ratings_movies_norm) > min_movies, colCounts(ratings_movies_norm) > min_users], main = "Heatmap of the top users and movies")

以下图像显示了顶级用户和电影的热图:

归一化数据

我们首先能注意到的不同是颜色,这是因为数据是连续的。之前,评分是介于 1 到 5 之间的整数。归一化后,评分可以是介于-5 到 5 之间的任何数字。

仍然有一些线更蓝,有些更红。原因是我们在可视化顶级电影。我们已经检查过每个用户的平均评分都是 0。

对数据进行二值化

一些推荐模型在二进制数据上工作,所以我们可能想要将我们的数据二值化,即定义一个只包含 0 和 1 的表格。0 将被视为缺失值或差评。

在我们的情况下,我们可以:

  • 定义一个矩阵,如果用户对电影进行了评分,则为 1,否则为 0。在这种情况下,我们失去了关于评分的信息。

  • 定义一个矩阵,如果评分高于或等于一个特定的阈值(例如,3),则为 1,否则为 0。在这种情况下,给电影差评等同于没有评分。

根据上下文,一个选择可能比另一个更合适。

二值化数据的函数是binarize。让我们将其应用于我们的数据。首先,让我们定义一个矩阵,如果电影已被观看,即其评分至少为1,则为 1:

ratings_movies_watched <- binarize(ratings_movies, minRating = 1)

让我们看看结果。在这种情况下,我们将有黑白图表,这样我们就可以可视化更大一部分用户和电影,例如,5%。同样,让我们使用quantile选择这 5%。行数和列数与原始矩阵相同,因此我们仍然可以在ratings_movies上应用rowCountscolCounts

min_movies_binary <- quantile(rowCounts(ratings_movies), 0.95)
min_users_binary <- quantile(colCounts(ratings_movies), 0.95)

让我们构建热图:

image(ratings_movies_watched[rowCounts(ratings_movies) > min_movies_binary,colCounts(ratings_movies) > min_users_binary], main = "Heatmap of the top users and movies")

以下图像显示了顶级用户和电影的热图:

二值化数据

只有少数单元格包含未观看的电影。这仅仅是因为我们选择了顶级用户和电影。

让我们使用相同的方法来计算和可视化其他二进制矩阵。高于阈值的评分的单元格将具有其值等于 1,其他单元格将为 0:

ratings_movies_good <- binarize(ratings_movies, minRating = 3)

让我们构建热图:

image(ratings_movies_good[rowCounts(ratings_movies) > min_movies_binary, colCounts(ratings_movies) > min_users_binary], main = "Heatmap of the top users and movies")

以下图像显示了顶级用户和电影的热图:

二值化数据

如预期的那样,现在有更多的空白单元格。根据模型的不同,我们可以保持评分矩阵不变或对其进行归一化/二值化。

在本节中,我们准备了数据以执行推荐。在接下来的章节中,我们将构建协同过滤模型。

基于物品的协同过滤

协同过滤是推荐系统的一个分支,它考虑了不同用户的信息。单词“协同”指的是用户相互协作推荐物品的事实。实际上,算法考虑了用户的购买和偏好。起点是一个评分矩阵,其中行对应于用户,列对应于物品。

本节将展示基于物品的协同过滤的一个示例。给定一个新用户,算法考虑用户的购买并推荐相似的物品。核心算法基于以下步骤:

  1. 对于每一对物品,测量它们在收到相似用户相似评分方面的相似程度

  2. 对于每个物品,识别最相似的k个物品

  3. 对于每个用户,识别与用户购买最相似的物品

在本章中,我们将看到构建 IBCF 模型的整体方法。此外,接下来的章节将展示其细节。

定义训练集和测试集

我们将使用MovieLense数据集的一部分(训练集)来构建模型,并将其应用于另一部分(测试集)。由于这不是本章的主题,我们不会评估模型,而只会向测试集中的用户推荐电影。

以下是两个集合:

  • 训练集:这个集合包括模型从中学习的用户

  • 测试集:这个集合包括我们向其推荐电影的用户

算法自动归一化数据,因此我们可以使用包含相关用户和MovieLense电影的ratings_movies。我们在上一节中将ratings_movies定义为MovieLense用户的子集,这些用户至少评了 50 部电影,以及至少被评了 100 次的电影。

首先,我们随机定义 which_train 向量,对于训练集中的用户为 TRUE,对于其他用户为 FALSE。我们将训练集中的概率设置为 80%:

which_train <- sample(x = c(TRUE, FALSE), size = nrow(ratings_movies), replace = TRUE, prob = c(0.8, 0.2))
head(which_train)
## [1]  TRUE  TRUE  TRUE FALSE  TRUE FALSE

让我们定义训练集和测试集:

recc_data_train <- ratings_movies[which_train, ]
recc_data_test <- ratings_movies[!which_train, ]

如果我们想向每个用户推荐物品,我们可以使用 k-fold:

  • 将用户随机分为五个组

  • 使用一个组作为测试集,其他组作为训练集

  • 对每个组重复此操作

这是一个示例代码:

which_set <- sample(x = 1:5, size = nrow(ratings_movies), replace = TRUE)
for(i_model in 1:5) {
  which_train <- which_set == i_model
  recc_data_train <- ratings_movies[which_train, ]
  recc_data_test <- ratings_movies[!which_train, ]
  # build the recommender
}

为了展示这个包的工作原理,我们手动将数据分为训练集和测试集。您也可以在 recommenderlab 中使用 evaluationScheme 函数自动执行此操作。此函数还包含一些我们将用于 第四章 的工具,评估推荐系统,该章节是关于模型评估。

现在,我们有了构建和验证模型所需的输入。

构建推荐模型

构建模型的函数是 recommender,其输入如下:

  • 数据: 这是训练集

  • 方法: 这是技术的名称

  • 参数: 这是技术的可选参数

该模型称为 IBCF,代表基于物品的协同过滤。让我们看看它的参数:

recommender_models <- recommenderRegistry$get_entries(dataType = "realRatingMatrix")
recommender_models$IBCF_realRatingMatrix$parameters
参数 默认值
k 30
method Cosine
normalize center
normalize_sim_matrix FALSE
alpha 0.5
na_as_zero FALSE
minRating NA

一些相关参数如下:

  • k: 在第一步中,算法计算每对物品之间的相似度。然后,对于每个物品,它识别其 k 个最相似的物品并将其存储。

  • method: 这是相似度函数。默认情况下,它是 Cosine。另一个流行的选项是 pearson

目前,我们只需将它们设置为默认值。为了展示如何更改参数,我们将 k 设置为默认值 30。我们现在准备好构建推荐模型:

recc_model <- Recommender(data = recc_data_train, method = "IBCF", parameter = list(k = 30))recc_model
## Recommender of type 'IBCF' for 'realRatingMatrix' ## learned using 111 users.
class(recc_model)
## [1] "Recommender"
## attr(,"package")
## [1] "recommenderlab"

recc_model 类是包含模型的 Recommender 类的一个对象。

探索推荐模型

使用 getModel,我们可以提取有关模型的一些详细信息,例如其描述和参数:

model_details <- getModel(recc_model)
model_details$description
## [1] "IBCF: Reduced similarity matrix"
model_details$k
## [1] 30

model_details$sim 组件包含相似度矩阵。让我们检查其结构:

class(model_details$sim)
## [1] "dgCMatrix"
## attr(,"package")
## [1] "Matrix"
dim(model_details$sim)
## [1] 332 332

如预期,model_details$sim 是一个大小等于物品数量的正方形矩阵。我们可以使用 image 函数探索其一部分:

n_items_top <- 20

让我们构建热图:

image(model_details$sim[1:n_items_top, 1:n_items_top], main = "Heatmap of the first rows and columns")

下面的图像显示了第一行和列的热图:

探索推荐模型

大多数值都等于 0。原因是每行只包含 k 个元素。让我们检查一下:

model_details$k
## [1] 30
row_sums <- rowSums(model_details$sim > 0)
table(row_sums)
## row_sums
##  30
## 332

如预期,每行有 30 个元素大于 0。然而,矩阵不应该是对称的。实际上,每列的非空元素数量取决于相应的电影被包含在另一个电影的前 k 中的次数。让我们检查元素数量的分布:

col_sums <- colSums(model_details$sim > 0)

让我们构建分布图:

qplot(col_sums) + stat_bin(binwidth = 1) + ggtitle("Distribution of the column count")

以下图像显示了列数的分布:

探索推荐模型

如预期,有一些电影与许多其他电影相似。让我们看看哪些电影包含最多的元素:

which_max <- order(col_sums, decreasing = TRUE)[1:6]
rownames(model_details$sim)[which_max]
电影 列总和
《飞刀手》(1996) 62
《非常嫌疑犯》(1995) 60
《无间道风云》(1996) 58
《眩晕》(1958) 58
《星际之门》(1994) 57
《教父》(1972) 55

在测试集上应用推荐模型

现在,我们能够向测试集中的用户推荐电影。我们将定义 n_recommended,它指定为每个用户推荐的项目数量。本节将向您展示计算加权总和的最流行方法:

n_recommended <- 6

对于每个用户,算法提取其评价过的电影。对于每部电影,它从相似度矩阵开始,识别所有相似的项目。然后,算法以这种方式对每个相似项目进行排名:

  • 提取与该物品相关的每个购买项目的用户评分。评分用作权重。

  • 提取与该物品相关的每个购买项目的相似度。

  • 将每个权重与相关的相似度相乘。

  • 将所有内容加总。

然后,算法识别前 n 个推荐:

recc_predicted <- predict(object = recc_model, newdata = recc_data_test, n = n_recommended)
recc_predicted
## Recommendations as 'topNList' with n = 6 for 449 users.

recc_predicted 对象包含推荐。让我们看看它的结构:

class(recc_predicted)
## [1] "topNList"## attr(,"package")## [1] "recommenderlab"
slotNames(recc_predicted)
## [1] "items"      "itemLabels" "n"

插槽包括:

  • items: 这是每个用户推荐项目的索引列表

  • itemLabels: 这是项目的名称

  • n: 这是推荐的数量

例如,以下是第一个用户的推荐:

recc_predicted@items[[1]]
## [1] 201 182 254 274 193 297

我们可以从 recc_predicted@item 标签中提取推荐电影:

recc_user_1 <- recc_predicted@items[[1]]movies_user_1 <- recc_predicted@itemLabels[recc_user_1]
movies_user_1
索引 电影
201 《辛德勒的名单》(1993)
182 《秘密与谎言》(1996)
254 《猜火车》(1996)
274 《猎鹿人》(1978)
193 《洛城疑云》(1997)
297 《曼克顿候选人》(1962)

我们可以为每个用户定义一个推荐矩阵:

recc_matrix <- sapply(recc_predicted@items, function(x){
  colnames(ratings_movies)[x]
})
dim(recc_matrix)
## [1]   6 449

让我们可视化前四个用户的推荐:

recc_matrix[, 1:4]
《辛德勒的名单》(1993) 《宝贝》(1995)
《秘密与谎言》(1996) 《非常嫌疑犯》(1995)
《猜火车》(1996) 《出租车司机》(1976)
《猎鹿人》(1978) 《银翼杀手》(1982)
《洛城疑云》(1997) 《欢迎来到娃娃屋》(1995)
《曼克顿候选人》(1962) 《沉默的羔羊》(1991)
《蝙蝠侠永远的》(1995) 《严格舞会》(1992)
《星际之门》(1994) 《洛城疑云》(1997)
《星际迷航 III:寻找斯波克》(1984) 《冷舒适的农场》(1995)
Tin Cup (1996) 12 Angry Men (1957)
Courage Under Fire (1996) Vertigo (1958)
Dumbo (1941) A Room with a View (1986)

现在,我们可以识别出最推荐的影片。为此,我们将定义一个包含所有推荐的向量,并构建一个频率图:

number_of_items <- factor(table(recc_matrix))chart_title <- "Distribution of the number of items for IBCF"

让我们构建分布图:

qplot(number_of_items) + ggtitle(chart_title)

以下图像显示了 IBCF 中项目数量的分布:

在测试集上应用推荐模型

大多数电影只被推荐过几次,而少数电影被推荐过很多次。让我们看看哪些是最受欢迎的电影:

number_of_items_sorted <- sort(number_of_items, decreasing = TRUE)
number_of_items_top <- head(number_of_items_sorted, n = 4)
table_top <- data.frame(names(number_of_items_top), number_of_items_top)
table_top
names.number_of_items_top
Mr. Smith Goes to Washington (1939) Mr. Smith Goes to Washington (1939)
Babe (1995) Babe (1995)
The Maltese Falcon (1941) The Maltese Falcon (1941)
L.A. Confidential (1997) L.A. Confidential (1997)

前面的表格继续如下:

number_of_items_top
Mr. Smith Goes to Washington (1939) 55
Babe (1995) 38
The Maltese Falcon (1941) 35
L.A. Confidential (1997) 34

IBCF 基于相似性矩阵推荐项目。它是一个积极学习模型,也就是说,一旦构建完成,就不需要访问初始数据。对于每个项目,模型存储最相似的 k 个,因此一旦模型构建完成,信息量就很小。这在数据量很大的情况下是一个优势。

此外,此算法高效且可扩展,因此在大评分矩阵中表现良好。与其它推荐模型相比,其准确性相当不错。

在下一节中,我们将探索另一种技术分支:基于用户的协同过滤。

基于用户的协同过滤

在前一节中,算法基于项目,识别推荐步骤如下:

  • 识别哪些项目在由相同的人购买方面是相似的

  • 向新用户推荐与其购买相似的项目

在本节中,我们将使用相反的方法。首先,给定一个新用户,我们将识别其相似用户。然后,我们将推荐相似用户购买的最高评分项目。这种方法称为基于用户的协同过滤。对于每个新用户,这些步骤如下:

  1. 测量每个用户与新用户相似的程度。像 IBCF 一样,流行的相似性度量包括相关性和余弦相似度。

  2. 识别最相似的用户。选项包括:

    • 考虑最顶部的 k 个用户 (k-最近邻)

    • 考虑相似度高于定义阈值的用户

  3. 对最相似用户的购买项目进行评分。评分是相似用户之间的平均评分,方法如下:

    • 平均评分

    • 加权平均评分,使用相似性作为权重

  4. 选择评分最高的项目。

就像我们在上一章中所做的那样,我们将构建一个训练集和一个测试集。现在,我们可以直接开始构建模型。

构建推荐模型

构建模型的 R 命令与上一章相同。现在,这项技术被称为 UBCF:

recommender_models <- recommenderRegistry$get_entries(dataType = "realRatingMatrix")
recommender_models$UBCF_realRatingMatrix$parameters
参数 默认值
method cosine
nn 25
sample FALSE
normalize center
minRating NA

一些相关的参数包括:

  • method: 这显示了如何计算用户之间的相似度

  • nn: 这显示了相似用户的数量

让我们构建一个默认参数的推荐模型:

recc_model <- Recommender(data = recc_data_train, method = "UBCF")recc_model
## Recommender of type 'UBCF' for 'realRatingMatrix' ## learned using 451 users.

让我们使用getModel提取一些关于模型的详细信息:

model_details <- getModel(recc_model)

让我们查看模型的组成部分:

names(model_details)
元素
description
data
method
nn
sample
normalize
minRating

除了model的描述和参数外,model_details还包含一个数据槽:

model_details$data
## 451 x 332 rating matrix of class 'realRatingMatrix' with 43846 ratings.
## Normalized using center on rows.

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

在测试集上应用推荐模型

与 IBCF 一样,我们可以确定每个新用户的六个顶级推荐:

n_recommended <- 6
recc_predicted <- predict(object = recc_model, newdata = recc_data_test, n = n_recommended) recc_predicted
## Recommendations as 'topNList' with n = 6 for 109 users.

我们可以定义一个矩阵,包含对测试集用户的推荐:

recc_matrix <- sapply(recc_predicted@items, function(x){colnames(ratings_movies)[x]
})
dim(recc_matrix)
## [1]   6 109

让我们查看前四个用户:

recc_matrix[, 1:4]
The Usual Suspects (1995) Lone Star (1996)
The Shawshank Redemption (1994) This Is Spinal Tap (1984)
Contact (1997) The Wrong Trousers (1993)
The Godfather (1972) Hoop Dreams (1994)
Nikita (La Femme Nikita) (1990) Mighty Aphrodite (1995)
Twelve Monkeys (1995) Big Night (1996)
The Silence of the Lambs (1991) The Usual Suspects (1995)
The Shawshank Redemption (1994) The Wrong Trousers (1993)
Jaws (1975) Monty Python and the Holy Grail (1974)
Schindler's List (1993) GoodFellas (1990)
The Godfather (1972)
Fargo (1996) 2001: A Space Odyssey (1968)

我们还可以计算每部电影被推荐的次数,并构建相关的频率直方图:

number_of_items <- factor(table(recc_matrix))
chart_title <- "Distribution of the number of items for UBCF"

让我们构建分布图:

qplot(number_of_items) + ggtitle(chart_title)

下图显示了 UBCF 中项目数量的分布:

在测试集上应用推荐模型

与 IBCF 相比,分布有一个更长的尾部。这意味着有一些电影比其他电影被推荐得更多。最大值为 29,而 IBCF 为 11。

让我们查看顶级标题:

number_of_items_sorted <- sort(number_of_items, decreasing = TRUE)
number_of_items_top <- head(number_of_items_sorted, n = 4)
table_top <- data.frame(names(number_of_items_top), number_of_items_top)
table_top
names.number_of_items_top
Schindler's List (1993) Schindler's List (1993)
The Shawshank Redemption (1994) The Shawshank Redemption (1994)
The Silence of the Lambs (1991) The Silence of the Lambs (1991)
The Godfather (1972) The Godfather (1972)

前面的表格继续如下:

number_of_items_top
辛德勒的名单 (1993) 36
肖申克的救赎 (1994) 34
沉默的羔羊 (1991) 29
教父 (1972) 27

比较 UBCF 和 IBCF 的结果有助于更好地理解算法。UBCF 需要访问初始数据,因此它是一个懒惰学习模型。由于它需要将整个数据库保留在内存中,所以在大评分矩阵存在的情况下表现不佳。此外,构建相似度矩阵需要大量的计算能力和时间。

然而,UBCF 的准确性已被证明略高于 IBCF,所以如果数据集不是太大,它是一个不错的选择。

基于二值数据的协同过滤

在前两个部分中,我们基于用户偏好构建了推荐模型,因为数据显示了每次购买的评分。然而,这种信息并不总是可用。以下两种情况可能发生:

  • 我们知道哪些物品已被购买,但不知道它们的评分

  • 对于每个用户,我们不知道它购买了哪些物品,但我们知道它喜欢哪些物品

在这些情况下,我们可以构建一个用户-物品矩阵,如果用户购买了(或喜欢)该物品,其值将为 1,否则为 0。这种情况与之前的情况不同,因此应该单独处理。与其他情况类似,技术是基于物品和基于用户的。

在我们这个例子中,从ratings_movies开始,我们可以构建一个ratings_movies_watched矩阵,如果用户观看了电影,其值将为 1,否则为 0。我们是在“二值化数据”部分之一构建的它。

数据准备

我们可以使用二值化方法构建ratings_movies_watched

ratings_movies_watched <- binarize(ratings_movies, minRating = 1)

让我们快速查看一下数据。每个用户观看了多少部电影(共 332 部)?让我们构建分布图:

qplot(rowSums(ratings_movies_watched)) + stat_bin(binwidth = 10) + geom_vline(xintercept = mean(rowSums(ratings_movies_watched)), col = "red", linetype = "dashed") + ggtitle("Distribution of movies by user")

下图显示了按用户分布的电影:

数据准备

平均而言,每个用户观看了大约 100 部电影,只有少数用户观看了超过 200 部电影。

为了构建推荐模型,让我们定义一个训练集和一个测试集:

which_train <- sample(x = c(TRUE, FALSE), size = nrow(ratings_movies), replace = TRUE, prob = c(0.8, 0.2))
recc_data_train <- ratings_movies[which_train, ]
recc_data_test <- ratings_movies[!which_train, ]

我们现在准备好构建 IBCF 和 UBCF 模型。

基于二值数据的物品推荐过滤

IBCF 的第一步是定义项之间的相似度。在二元数据的情况下,如相关性和余弦距离等距离不适用。一个好的替代方案是 Jaccard 指数。给定两个项,该指数是购买这两个项的用户数除以至少购买其中一个项的用户数。让我们从 基于项的二元数据协同过滤基于项的二元数据协同过滤 开始,它们分别是购买第一和第二项的用户集合。符号“∩”表示两个集合的交集,即两个集合都包含的项。符号“U”表示两个集合的并集,即至少包含在一个集合中的项。Jaccard 指数是两个集合交集的元素数除以它们的并集的元素数。

基于项的二元数据协同过滤

我们可以使用与前面章节相同的命令构建 IBCF 过滤模型。唯一的区别是输入参数方法等于 Jaccard

recc_model <- Recommender(data = recc_data_train, method = "IBCF", parameter = list(method = "Jaccard"))
model_details <- getModel(recc_model)

如前几节所述,我们可以向测试集中的每个用户推荐六项:

n_recommended <- 6
recc_predicted <- predict(object = recc_model, newdata = recc_data_test, n = n_recommended)
recc_matrix <- sapply(recc_predicted@items, function(x){colnames(ratings_movies)[x]
})

让我们看看前四个用户的推荐。

recc_matrix[, 1:4]
《洛城机密 (1997)》 《篮球梦 (1994)》
《Evita (1996)》 《智力游戏 (1994)》
《存在主义 (1979)》 《严格舞会 (1992)》
《追逐艾米 (1997)》 《这是脊髓塔 (1984)》
《奇爱博士或:我如何停止担忧并爱上炸弹 (1963)》 《好家伙 (1993)》
《全裸蒙提 (1997)》 《错误的裤子 (1993)》
《飘 (1939)》 《警察局 (1997)》
《公民凯恩 (1941)》 《迷幻高速公路 (1997)》
《金色池塘 (1981)》 《科利亚 (1996)》
《艾玛 (1996)》 《秘密与谎言 (1996)》
《飞越疯人院 (1975)》 《每个人都说我爱你 (1996)》
《费城故事 (1940)》 《搏击俱乐部 (1997)》

该方法与 IBCF 类似,使用评分矩阵。由于我们没有考虑评分,结果将不太准确。

基于项的二元数据协同过滤

与 IBCF 类似,我们还需要为 UBCF 使用 Jaccard 指数。给定两个用户,该指数是两个用户都购买的项数除以至少有一个用户购买的项数。数学符号与上一节相同:

基于用户的二元数据协同过滤

让我们构建推荐模型:

recc_model <- Recommender(data = recc_data_train, method = "UBCF", parameter = list(method = "Jaccard"))

使用与 IBCF 相同的命令,为每个用户推荐六部电影,并查看前四个用户:

n_recommended <- 6
recc_predicted <- predict(object = recc_model, newdata = recc_data_test,n = n_recommended)
recc_matrix <- sapply(recc_predicted@items, function(x){colnames(ratings_movies)[x]
})
dim(recc_matrix)
## [1]   6 109
recc_matrix[, 1:4]
《肖申克的救赎 (1994)》 《泰坦尼克号 (1997)》
《卡萨布兰卡 (1942)》 《天堂电影院 (1988)》
《勇敢的心 (1995)》 《孤星 (1996)》
《终结者 (1984)》 《洛城机密 (1997)》
The Usual Suspects (1995) Singin' in the Rain (1952)
Twelve Monkeys (1995) Leaving Las Vegas (1995)
Titanic (1997) Monty Python and the Holy Grail (1974)
Usual Suspects, The (1995) The Shawshank Redemption (1994)
Groundhog Day (1993) Schindler's List (1993)
The Shawshank Redemption (1994) Young Frankenstein (1974)
The Blues Brothers (1980) The Usual Suspects (1995)
Monty Python and the Holy Grail (1974) North by Northwest (1959)

结果与 IBCF 不同。

这些技术假设 0 是缺失值。然而,也可以选择将它们视为不良评分。有一类技术仅处理二元矩阵。

大多数用户不会对项目进行评分,因此存在几个 0-1 矩阵的现实生活案例。这就是为什么了解如何在这些环境中构建推荐系统很重要。

协同过滤的结论

本书专注于协同过滤,因为它是最受欢迎的推荐分支。此外,它是唯一由recommenderlab支持的。

然而,协同过滤并不总是最合适的技巧。本章概述了其局限性以及一些替代方案。

协同过滤的局限性

协同过滤有一些局限性。在处理新用户和/或新项目时,算法存在以下潜在问题:

  • 如果新用户还没有看过任何电影,那么 IBCF 和 UBCF 都无法推荐任何项目。除非 IBCF 知道新用户购买的项目,否则它无法工作。UBCF 需要知道哪些用户与新用户有相似偏好,但我们不知道其评分。

  • 如果新项目还没有被任何人购买,它将永远不会被推荐。IBCF 匹配被相同用户购买的项目,因此它不会将新项目与任何其他项目匹配。UBCF 向每个用户推荐由相似用户购买的项目,而没有人购买新项目。因此,算法不会向任何人推荐它。

然后,我们可能无法包括它们,这个挑战被称为冷启动问题。为了包括新用户和/或新项目,我们需要考虑其他信息,例如用户资料和项目描述。

协同过滤的另一个局限性是它只考虑评分矩阵。在许多情况下,我们有一些额外的信息可以提高推荐质量。此外,用户偏好并不总是可用,或者可能是不完整的。

在接下来的章节中,我们将探讨一些其他方法。

基于内容的过滤

另一种流行的技术分支是基于内容的过滤。算法从项目的描述开始,并且不需要同时考虑不同的用户。对于每个用户,算法推荐与过去购买相似的项目。

执行推荐步骤如下:

  1. 定义商品描述。

  2. 根据购买行为定义用户资料。

  3. 向每个用户推荐与其资料匹配的商品

用户资料基于他们的购买行为,因此算法推荐与过去购买相似的商品。

混合推荐系统

在许多情况下,我们能够构建不同的协同过滤和基于内容的过滤模型。如果我们同时考虑所有这些模型会怎样呢?在机器学习中,结合不同模型的策略通常会导致更好的结果。

一个简单的例子是将协同过滤与用户和/或商品的信息相结合。在 IBCF 的情况下,商品之间的距离可以同时考虑到用户偏好和商品描述。即使在 UBCF 中,用户之间的距离也可以考虑到他们的偏好和个人数据。

在推荐的情况下,这些模型被称为混合模型。有不同方式来组合过滤模型。

并行化混合系统分别运行推荐器并合并它们的结果。以下是一些选项:

  • 定义一个规则来为每个用户选择一个结果。这个规则可以基于用户资料和/或推荐。

  • 计算排名的平均值。平均值可以是加权的。

管道化混合系统按顺序运行推荐器。每个模型的输出是下一个模型的输入。

单一混合系统将方法集成在同一个算法中。以下是一些选项:

  • 特征组合:这可以从不同类型的输入中学习。例如,一个算法可以考虑到评分、用户资料和商品描述。

  • 特征增强:这通过结合不同的数据源来构建推荐器的输入。

基于知识的推荐系统

有一些情况下,协同过滤和基于内容的过滤不起作用。

在这些情况下,我们可以使用关于用户和产品的显式知识,以及推荐标准。这一技术分支被称为基于知识的。有各种各样的技术,并且它们依赖于数据和业务问题。因此,很难定义适用于不同情境的一些技术。

摘要

在推荐的不同技术中,协同过滤是最容易实现的。此外,基于内容的过滤算法依赖于上下文,并且仍然可以在 R 中构建它们。

本章通过关注协同过滤展示了不同的推荐方法。下一章将展示如何测试和评估推荐技术。

第四章:评估推荐系统

上一章向您展示了如何构建推荐系统。有几个选项,其中一些可以使用recommenderlab包开发。此外,每种技术都有一些参数。在构建模型后,我们如何决定使用哪一个?如何确定其参数?我们可以首先测试一些模型和/或参数配置的性能,然后选择表现最好的一个。

本章将向您展示如何评估推荐模型,比较它们的性能,并选择最合适的模型。在本章中,我们将涵盖以下主题:

  • 准备数据以评估性能

  • 评估某些模型的性能

  • 选择表现最佳的模型

  • 优化模型参数

准备数据以评估模型

为了评估模型,你需要用一些数据构建它们,并在其他数据上测试它们。本章将向您展示如何准备这两组数据。recommenderlab包包含预构建的工具,有助于这项任务。

目标是定义两个数据集,如下所示:

  • 训练集:这些是从中用户学习的模型

  • 测试集:这些是用户应用和测试的模型

为了评估模型,我们需要将它们与用户偏好进行比较。为了做到这一点,我们需要忘记测试集中的某些用户偏好,看看技术是否能够识别它们。对于测试集中的每个用户,我们忽略一些购买,并根据其他购买构建推荐。让我们加载包:

library(recommenderlab)
library(ggplot2)

我们将使用的数据集被称为MovieLense。让我们定义只包含最相关用户和电影的ratings_movies

data(MovieLense)
ratings_movies <- MovieLense[rowCounts(MovieLense) > 50, colCounts(MovieLense) > 100]ratings_movies
## 560 x 332 rating matrix of class 'realRatingMatrix' with 55298 ratings.

我们现在准备好准备数据。

分割数据

构建训练集和测试集最简单的方法是将数据分成两部分。首先,我们需要决定将多少用户放入每一部分。例如,我们可以将 80%的用户放入训练集。我们可以通过指定训练集的百分比来定义percentage_training

percentage_training <- 0.8

对于测试集中的每个用户,我们需要定义使用多少个项目来生成推荐。剩余的项目将用于测试模型精度。最好这个参数低于任何用户购买的最少项目数,这样我们就不需要没有项目来测试模型的用户:

min(rowCounts(ratings_movies))
## _18_

例如,我们可以保留15个项目:

items_to_keep <- 15

评估一个模型包括将推荐与未知购买进行比较。评分在 1 到 5 之间,我们需要定义什么构成好项目和坏项目。为此,我们将定义一个阈值,即被认为是好的最低评分:

rating_threshold <- 3

有一个额外的参数定义了我们想要运行评估的次数。目前,让我们将其设置为1

n_eval <- 1

我们已经准备好分割数据。recommenderlab函数是evaluationScheme,其参数如下:

  • data: 这是初始数据集

  • method: 这是分割数据的方式。在这种情况下,它是split

  • train: 这是训练集中数据的百分比

  • given: 这是需要保留的物品数量

  • goodRating: 这是评分阈值

  • k: 这是运行评估的次数

让我们构建包含这些集的eval_sets

eval_sets <- evaluationScheme(data = ratings_movies, method = "split", train = percentage_training, given = items_to_keep, goodRating = rating_threshold, k = n_eval) eval_sets
## Evaluation scheme with 15 items given
## Method: 'split' with 1 run(s).
## Training set proportion: 0.800
## Good ratings: >=3.000000
## Data set: 560 x 332 rating matrix of class 'realRatingMatrix' with 55298 ratings.

为了提取这些集,我们需要使用getData。有三个集:

  • train: 这是训练集

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

  • unknown: 这是测试集,用于测试推荐物品

让我们看看训练集:

getData(eval_sets, "train")
## 448 x 332 rating matrix of class 'realRatingMatrix' with 44472 ratings.

它是一个realRatingMatrix对象,因此我们可以对其应用如nrowrowCounts等方法:

nrow(getData(eval_sets, "train")) / nrow(ratings_movies)
## _0.8_

如预期,大约 80%的用户在训练集中。让我们看看两个测试集:

getData(eval_sets, "known")
## 112 x 332 rating matrix of class 'realRatingMatrix' with 1680 ratings.
getData(eval_sets, "unknown")
## 112 x 332 rating matrix of class 'realRatingMatrix' with 9146 ratings.

它们都有相同数量的用户。测试集中应该有大约 20%的数据:

nrow(getData(eval_sets, "known")) / nrow(ratings_movies)
## _0.2_

一切如预期。让我们看看known集中每个用户有多少物品。它应该等于items_to_keep,即15

unique(rowCounts(getData(eval_sets, "known")))
## _15_

对于测试集中的用户来说,情况并非如此,因为剩余物品的数量取决于初始购买数量:

qplot(rowCounts(getData(eval_sets, "unknown"))) + geom_histogram(binwidth = 10) + ggtitle("unknown items by the users")

下图显示了用户未知的物品:

分割数据

如预期,用户拥有的物品数量差异很大。

Bootstrapping 数据

在前面的子节中,我们将数据分为两部分,训练集包含了 80%的行。如果我们改用带替换的行采样呢?同一个用户可以被采样多次,如果训练集的大小与之前相同,测试集中将有更多的用户。这种方法被称为自助法(bootstrapping),并且由recommenderlab支持。参数与之前的方法相同。唯一的区别是我们指定method = "bootstrap"而不是method = "split"

percentage_training <- 0.8
items_to_keep <- 15
rating_threshold <- 3
n_eval <- 1
eval_sets <- evaluationScheme(data = ratings_movies, method = "bootstrap", train = percentage_training, given = items_to_keep, goodRating = rating_threshold, k = n_eval)

训练集中的用户数量仍然等于总用户数的 80%:

nrow(getData(eval_sets, "train")) / nrow(ratings_movies)
## _0.8_

然而,对于测试集中的物品来说,情况并非如此:

perc_test <- nrow(getData(eval_sets, "known")) / nrow(ratings_movies)
perc_test
## _0.4393_

测试集比之前的集大两倍以上。

我们可以提取训练集中的独特用户:

length(unique(eval_sets@runsTrain[[1]]))
## _314_

训练集中独特用户的百分比应该与测试集中用户的百分比互补,如下所示:

perc_train <- length(unique(eval_sets@runsTrain[[1]])) / nrow(ratings_movies)
perc_train + perc_test
## _1_

我们可以计算每个用户在训练集中重复的次数:

table_train <- table(eval_sets@runsTrain[[1]])
n_repetitions <- factor(as.vector(table_train))
qplot(n_repetitions) + ggtitle("Number of repetitions in the training set")

下图显示了训练集中的重复次数:

自助数据

大多数用户被采样少于四次。

使用 k-fold 来验证模型

前两种方法在用户的一部分上测试了推荐器。如果我们相反,对每个用户的推荐进行测试,我们可以更准确地衡量性能。我们可以将数据分成一些块,取出一个块作为测试集,并评估准确性。然后,我们可以对其他每个块做同样的事情,并计算平均准确性。这种方法称为 k 折,并且由recommenderlab支持。

我们可以使用evaluationScheme,与之前的区别在于,我们不是指定放入训练集的数据百分比,而是定义我们想要多少个块。参数是k,就像前例中的重复次数一样。显然,我们不再需要指定train

n_fold <- 4
eval_sets <- evaluationScheme(data = ratings_movies, method = "cross-validation", k = n_fold, given = items_to_keep, goodRating = rating_threshold)

我们可以计算每个集合中有多少个物品:

size_sets <- sapply(eval_sets@runsTrain, length)
size_sets
## _420_, _420_, _420_ and _420_

如预期,所有集合的大小都相同。

这种方法是最准确的,尽管它计算量更大。

在本章中,我们看到了不同的方法来准备训练集和测试集。在下一章,我们将从评估开始。

评估推荐技术

本章将向您展示两种评估推荐的流行方法。它们都基于上一节中描述的交叉验证框架。

第一种方法是评估算法估计的评分。另一种方法是直接评估推荐。每种方法都有一个子节。

评估评分

为了向新用户推荐物品,协同过滤估计尚未购买的物品的评分。然后,它推荐评分最高的物品。目前,让我们忘记最后一步。我们可以通过比较估计评分与实际评分来评估模型。

首先,让我们准备验证数据,如前节所示。由于 k 折是最准确的方法,我们将在这里使用它:

n_fold <- 4
items_to_keep <- 15
rating_threshold <- 3
eval_sets <- evaluationScheme(data = ratings_movies, method = "cross-validation", k = n_fold, given = items_to_keep, goodRating = rating_threshold)

我们需要定义要评估的模型。例如,我们可以评估一个基于物品的协同过滤推荐器。让我们使用推荐器函数来构建它。我们需要指定模型名称及其参数列表。如果我们使用默认值,那么它就是 NULL:

model_to_evaluate <- "IBCF"
model_parameters <- NULL

我们现在准备好构建模型,使用以下代码:

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

IBCF 可以推荐新物品并预测它们的评分。为了构建模型,我们需要指定我们想要推荐多少个物品,例如10,即使我们不需要在评估中使用此参数:

items_to_recommend <- 10

我们可以使用predict函数使用预测评分来构建矩阵:

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

eval_prediction对象是一个评分矩阵。让我们看看我们为每个用户推荐了多少部电影。为此,我们可以可视化每个用户观看电影数量的分布:

qplot(rowCounts(eval_prediction)) + geom_histogram(binwidth = 10) + ggtitle("Distribution of movies per user")

下面的图像显示了每个用户观看电影的分布:

评估评分

每个用户观看的电影数量大约在 150 到 300 部之间。

测量准确性的函数是calcPredictionAccuracy,它计算以下方面:

  • 均方根误差 (RMSE): 这是真实评分与预测评分之间差异的标准差。

  • 平均平方误差 (MSE): 这是真实评分与预测评分之间平方差值的平均值。它是 RMSE 的平方,因此包含相同的信息。

  • 平均绝对误差 (MAE): 这是真实评分与预测评分之间绝对差值的平均值。

我们可以通过指定byUser = TRUE来计算每个用户的这些指标:

eval_accuracy <- calcPredictionAccuracy(
  x = eval_prediction, data = getData(eval_sets, "unknown"), byUser = TRUE)
head(eval_accuracy)
RMSE MSE MAE
1 1.217 1.481 0.8205
2 0.908 0.8244 0.727
6 1.172 1.374 0.903
14 1.405 1.973 1.027
15 1.601 2.562 1.243
18 0.8787 0.7721 0.633

让我们看看一个用户的 RMSE:

qplot(eval_accuracy[, "RMSE"]) + geom_histogram(binwidth = 0.1) + ggtitle("Distribution of the RMSE by user")

下图显示了用户 RSME 的分布:

评估评分

大多数的 RMSE 值在 0.8 到 1.4 之间。我们对每个用户的模型进行了评估。为了得到整个模型的表现指数,我们需要计算平均指数,指定byUser = FALSE

eval_accuracy <- calcPredictionAccuracy(x = eval_prediction, data = getData(eval_sets, "unknown"), byUser = FALSE) eval_accuracy
## _1.101_, _1.211_ and _0.8124_

这些指标有助于比较不同模型在相同数据上的性能。

评估推荐

另一种衡量准确性的方法是,通过比较具有正面评分的推荐与购买来衡量。为此,我们可以使用预构建的evaluate函数。其输入如下:

  • x: 这是一个包含评估方案的对象。

  • method: 这是推荐技术。

  • n: 这是为每个用户推荐的物品数量。如果我们指定一个n的向量,该函数将根据n评估推荐器的性能。

我们已经定义了一个阈值,rating_threshold <- 3,用于正面评分,并且这个参数已经存储在eval_sets中。progress = FALSE参数抑制了进度报告:

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

results对象是一个包含评估结果的evaluationResults对象。使用getConfusionMatrix,我们可以提取一个混淆矩阵的列表。列表中的每个元素对应于k-fold 的不同分割。让我们看看第一个元素:

head(getConfusionMatrix(results)[[1]])
TP FP FN TN 精确度 召回率 TPR FPR
10 3.443 6.557 70.61 236.4 0.3443 0.04642 0.04642 0.02625
20 6.686 13.31 67.36 229.6 0.3343 0.09175 0.09175 0.05363
30 10.02 19.98 64.03 223 0.334 0.1393 0.1393 0.08075
40 13.29 26.71 60.76 216.2 0.3323 0.1849 0.1849 0.1081
50 16.43 33.57 57.62 209.4 0.3286 0.2308 0.2308 0.1362
60 19.61 40.39 54.44 202.6 0.3268 0.2759 0.2759 0.164

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

  • 真阳性 (TP):这些是被推荐并购买的物品

  • 假阳性 (FP):这些是被推荐但没有购买的物品

  • 假阴性 (FN):这些是没有被推荐但已购买的物品

  • 真阴性 (TN):这些是没有被推荐但没有购买的物品

一个完美(或过度拟合)的模型将只有 TP 和 TN。

如果我们想同时考虑所有分割,我们只需将指标相加:

columns_to_sum <- c("TP", "FP", "FN", "TN")
indices_summed <- Reduce("+", getConfusionMatrix(results))[, columns_to_sum]
head(indices_summed)
TP FP FN TN
10 13.05 26.95 279.3 948.7
20 25.4 54.6 267 921
30 37.74 82.26 254.7 893.4
40 50.58 109.4 241.8 866.2
50 62.35 137.7 230 838
60 74.88 165.1 217.5 810.5

注意,我们本可以使用avg(results)

其他四列包含性能指标,跨所有折叠总结它们比较困难。然而,我们可以通过构建一些图表来可视化它们。

首先,让我们构建 ROC 曲线。它显示了以下因素:

  • 真阳性率 (TPR):这是被推荐购买的物品的百分比。它是 TP 数量除以购买物品的数量(TP + FN)。

  • 假阳性率 (FPR):这是被推荐但没有购买的物品的百分比。它是假阳性 (FP) 数量除以未购买物品的数量(FP + TN)。

plot方法将构建一个包含ROC 曲线的图表。为了可视化标签,我们添加了annotate = TRUE输入:

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

以下图像显示了 ROC 曲线:

评估推荐

以下两个准确度指标如下:

  • 精确度:这是被推荐并购买的物品的百分比。它是 FP 数量除以总正数(TP + FP)。

  • 召回率:这是被推荐购买的物品的百分比。它是 TP 数量除以总购买数量(TP + FN)。它也等于真阳性率。

如果推荐的购买物品比例很小,通常精度会降低。另一方面,如果推荐的购买物品比例更高,召回率会增加:

plot(results, "prec/rec", annotate = TRUE, main = "Precision-recall")

以下图像显示了精确度-召回率曲线:

评估推荐

此图表反映了精确度和召回率之间的权衡。即使曲线不是完美的单调递增,趋势也是预期的。

在本节中,我们看到了如何评估一个模型。在下一节中,我们将看到如何比较两个或更多模型。

识别最合适的模型

上一章向您展示了如何评估一个模型。性能指标对于比较不同的模型和/或参数很有用。在相同的数据上应用不同的技术,我们可以比较性能指标以选择最合适的推荐器。由于有不同的评估指标,没有客观的方法来做这件事。

起始点是我们在上一节中定义的 k 折评估框架。它存储在eval_sets中。

比较模型

为了比较不同的模型,我们首先需要定义它们。每个模型存储在一个列表中,包含其名称和参数。列表的组成部分如下:

  • name: 这是模型名称。

  • param: 这是一个包含其参数的列表。如果所有参数都保留为默认值,则可以是 NULL。

例如,这就是我们如何通过将k参数设置为20来定义基于物品的协同过滤:

list(name = "IBCF", param = list(k = 20))

为了评估不同的模型,我们可以定义一个包含它们的列表。我们可以构建以下过滤:

  • 基于物品的协同过滤,使用余弦作为距离函数

  • 基于物品的协同过滤,使用皮尔逊相关系数作为距离函数

  • 基于用户的协同过滤,使用余弦作为距离函数

  • 基于用户的协同过滤,使用皮尔逊相关系数作为距离函数

  • 随机推荐以获得基准

前面的点在以下代码中定义:

models_to_evaluate <- list(
  IBCF_cos = list(name = "IBCF", param = list(method = "cosine")),IBCF_cor = list(name = "IBCF", param = list(method = "pearson")),UBCF_cos = list(name = "UBCF", param = list(method = "cosine")),UBCF_cor = list(name = "UBCF", param = list(method = "pearson")),random = list(name = "RANDOM", param=NULL)
)

为了正确评估模型,我们需要测试它们,并改变物品的数量。例如,我们可能希望为每个用户推荐多达 100 部电影。由于 100 已经是一个很大的推荐数量,我们不需要包括更高的值:

n_recommendations <- c(1, 5, seq(10, 100, 10))

我们已经准备好运行和评估模型。像上一章一样,函数是evaluate。唯一的区别是现在输入方法是一个模型列表:

list_results <- evaluate(x = eval_sets, method = models_to_evaluate, n = n_recommendations)
class(list_results)
## evaluationResultList

list_results对象是一个evaluationResultList对象,它可以被视为一个列表。让我们看看它的第一个元素:

class(list_results[[1]])
## evaluationResults

list_results的第一个元素是一个evaluationResults对象,这个对象与使用单个模型的evaluate输出的对象相同。我们可以检查所有元素是否都相同:

sapply(list_results, class) == "evaluationResults"
## TRUE TRUE TRUE TRUE TRUE

list_results的每个元素都是一个evaluationResults对象。我们可以使用avg提取相关的平均混淆矩阵:

avg_matrices <- lapply(list_results, avg)

我们可以使用avg_matrices来探索性能评估。例如,让我们看看使用余弦距离的 IBCF:

head(avg_matrices$IBCF_cos[, 5:8])
精确率 召回率 TPR FPR
1 0.3589 0.004883 0.004883 0.002546
5 0.3371 0.02211 0.02211 0.01318
10 0.3262 0.0436 0.0436 0.02692
20 0.3175 0.08552 0.08552 0.0548
30 0.3145 0.1296 0.1296 0.08277
40 0.3161 0.1773 0.1773 0.1103

我们有了上一章的所有指标。在下一节中,我们将探索这些指标以确定表现最佳的模型。

识别最合适的模型

我们可以通过构建显示其 ROC 曲线的图表来比较模型。就像上一节一样,我们可以使用plotannotate参数指定了哪些曲线将包含标签。例如,第一和第二曲线通过定义annotate = c(1, 2)进行标记。在我们的情况下,我们将只标记第一条曲线:

plot(list_results, annotate = 1, legend = "topleft") title("ROC curve")

识别最合适的模型

一个好的性能指标是曲线下的面积(AUC),即 ROC 曲线下的面积。即使没有计算它,我们也可以注意到最高的 AUC 是使用余弦距离的 UBCF,所以它是表现最好的技术。

就像我们在上一节所做的那样,我们可以构建精确率-召回率图:

plot(list_results, "prec/rec", annotate = 1, legend = "bottomright") title("Precision-recall")

以下图像显示了精确率-召回率:

识别最合适的模型

使用余弦距离的 UBCF 仍然是最佳模型。根据我们想要实现的目标,我们可以设置一个合适的推荐项目数量。

优化数值参数

推荐模型通常包含一些数值参数。例如,IBCF 考虑了最近的 k 个项目。我们如何优化k

与分类参数类似,我们可以测试一个数值参数的不同值。在这种情况下,我们还需要定义我们想要测试的值。

到目前为止,我们将k保留为其默认值:30。现在,我们可以探索更多值,范围在540之间:

vector_k <- c(5, 10, 20, 30, 40)

使用lapply,我们可以定义一个要评估的模型列表。距离度量是余弦:

models_to_evaluate <- lapply(vector_k, function(k){
  list(name = "IBCF", param = list(method = "cosine", k = k))})names(models_to_evaluate) <- paste0("IBCF_k_", vector_k)
Using the same commands as we did earlier, let's build and evaluate the models:
n_recommendations <- c(1, 5, seq(10, 100, 10))
list_results <- evaluate(x = eval_sets, method = models_to_evaluate, n = n_recommendations)

通过构建 ROC 曲线的图表,我们应该能够识别表现最佳的k

plot(list_results, annotate = 1,      legend = "topleft") title("ROC curve")

优化数值参数

具有最大 AUC 的k是 10。另一个好的候选值是 5,但它永远不会有一个高的 TPR。这意味着,即使我们设置一个非常高的 n 值,算法也无法推荐大量用户喜欢的项目。k = 5的 IBCF 只推荐少量与购买相似的项目。因此,它不能用来推荐许多项目。

让我们看看精确率-召回率图:

plot(list_results, "prec/rec", annotate = 1, legend = "bottomright")
title("Precision-recall")

以下图像显示了精确率-召回率:

优化数值参数

要实现最高的召回率,我们需要将k设置为10。如果我们更关注精确率,我们将k设置为5

本节使用不同的方法评估了四种技术。然后,它优化了其中之一的一个数值参数。根据我们想要实现的目标,参数的选择可能会有所不同。

摘要

本章向您展示了如何评估不同模型的表现,以便选择最准确的一个。评估性能的方法有很多,可能会导致不同的选择。根据业务目标,评估指标也不同。这是如何将业务和数据结合以实现最终结果的例子。

下一章将解释一个完整的使用案例,其中我们将准备数据,构建不同的模型,并对它们进行测试。

第五章. 案例研究 – 构建您自己的推荐引擎

前两章展示了如何使用 R 语言构建、测试和优化推荐系统。尽管这些章节充满了示例,但它们基于 R 包提供的数据集。数据使用 redyal 结构化,并已准备好处理。然而,在现实生活中,数据准备是一个重要、耗时且困难的步骤。

之前示例的另一个局限性是它们仅基于评分。在大多数情况下,还有其他数据源,如项目描述和用户资料。一个很好的解决方案是结合所有相关信息。

本章将展示一个实际示例,我们将从原始数据开始构建和优化推荐系统。本章将涵盖以下主题:

  • 准备数据以构建推荐引擎

  • 通过可视化技术探索数据

  • 选择和构建推荐模型

  • 通过设置其参数来优化推荐模型的性能

最后,我们将构建一个生成推荐的引擎。

准备数据

从原始数据开始,本节将向您展示如何为推荐模型准备输入。

数据描述

数据是关于一周内访问网站的微软用户。对于每个用户,数据显示了用户访问的区域。为了简化,从现在起我们将使用“项目”一词来指代网站区域。

有 5,000 个用户,它们由 10,001 到 15,000 之间的连续数字表示。项目由 1,000 到 1,297 之间的数字表示,即使它们小于 298。

数据集是一个未结构化的文本文件。每个记录包含 2 到 6 个字段。第一个字段是一个字母,定义了记录包含的内容。主要有三种类型的记录,如下所示:

  • 属性 (A):这是网站区域的描述

  • 案例 (C):这是每个用户的案例,包含其 ID

  • 投票 (V):这是案例的投票行

每个案例记录后面跟着一个或多个投票,每个用户只有一个案例。

我们的目标是为每个用户推荐他们尚未探索的网站区域。

导入数据

本节将向您展示如何导入数据。首先,让我们加载我们将要使用的包:

library("data.table")
library("ggplot2")
library("recommenderlab")
library("countrycode")

以下代码将在以下要点中解释:

  • data.table:这个包用于处理数据

  • ggplot2:这个包用于制作图表

  • recommenderlab:这个包用于构建推荐引擎

  • countrycode:这个包包含国家名称

然后,让我们将表格加载到内存中。如果文本文件已经在我们的工作目录中,只需定义其名称即可。否则,我们需要定义其完整路径:

file_in <- "anonymous-msweb.test.txt"

行包含不同数量的列,这意味着数据是无结构的。然而,最多有六列,因此我们可以使用 read.csv 将文件加载到表格中。包含少于六个字段的行将只有空值:

table_in <- read.csv(file_in, header = FALSE)
head(table_in)
V1 V2 V3 V4 V5 V6
I 4 www.microsoft.com created by getlog.pl
T 1 VRoot 0 0 VRoot
N 0 0
N 1 1
T 2 Hide1 0 0 Hide
N 0 0

前两列包含用户 ID 及其购买记录。我们可以简单地删除其他列:

table_users <- table_in[, 1:2]

为了更轻松地处理数据,我们可以将其转换为数据表,使用以下命令:

table_users <- data.table(table_users)

列如下:

  • 分类: 这是一个指定列内容的字母。包含用户或项目 ID 的列分别属于 C 和 V 分类。

  • : 这是一个指定用户或项目 ID 的数字。

我们可以分配列名并选择包含用户或项目的行:

setnames(table_users, 1:2, c("category", "value"))
table_users <- table_users[category %in% c("C", "V")]
head(table_users)

分类
C 10001
V 1038
V 1026
V 1034
C 10002
V 1008

table_users 对象包含结构化数据,这是我们定义评分矩阵的起点。

定义评分矩阵

我们的目标是定义一个表格,其中每一行代表一个项目,每一列代表一次购买。对于每个用户,table_users 包含其 ID 和购买记录分别在不同的行中。在每个块或行中,第一列包含用户 ID,其他列包含项目 ID。

您可以使用以下步骤来定义一个评分矩阵:

  1. 标记案例。

  2. 定义一个长格式表格。

  3. 定义一个宽格式表格。

  4. 定义评分矩阵。

为了重塑表格,第一步是定义一个名为 chunk_user 的字段,为每个用户包含一个递增的数字。当用户行是块的第一行时,category == "C" 条件为真。使用 cumsum,每当有新用户的行时,我们就在索引 1 上递增:

table_users[, chunk_user := cumsum(category == "C")]
head(table_users)

分类 chunk_user
C 10001 1
V 1038 1
V 1026 1
V 1034 1
C 10002 2
V 1008 2

下一步是定义一个表格,其中行对应购买。我们需要一个包含用户 ID 的列和一个包含项目 ID 的列。新表格称为 table_long,因为它处于 格式:

table_long <- table_users[, list(user = value[1], item = value[-1]), by = "chunk_user"]
head(table_long)

chunk_user user item
1 10001 1038
1 10001 1026
1 10001 1034
2 10002 1008
2 10002 1056
2 10002 1032

现在,我们可以定义一个表格,其中每一行代表一个用户,每一列代表一个项目。如果项目已被购买,则值为 1,否则为 0。我们可以使用 reshape 函数来构建表格。其输入如下:

  • data:这是long格式的表。

  • direction:这显示了我们是将数据从长格式转换为宽格式还是其他方式。

  • idvar:这是标识组的变量,在这种情况下,是用户。

  • timevar:这是标识同一组内记录的变量。在这种情况下,它是项目。

  • v.names:这是值的名称。在这种情况下,它是始终等于一的评分。缺失的用户-项目组合将是 NA 值。

在将列value设置为1后,我们可以使用reshape构建table_wide

table_long[, value := 1]
table_wide <- reshape(data = table_long,direction = "wide",idvar = "user",timevar = "item",v.names = "value")
head(table_wide[, 1:5, with = FALSE])

chunk_user user value.1038 value.1026 value.1034
1 10001 1 1 1
2 10002 NA NA NA
3 10003 1 1 NA
4 10004 NA NA NA
5 10005 1 1 1
6 10006 NA NA 1

为了构建评分矩阵,我们需要只保留包含评分的列。此外,用户名将是矩阵的行名,因此我们需要将它们存储在vector_users向量中:

vector_users <- table_wide[, user]
table_wide[, user := NULL]
table_wide[, chunk_user := NULL]

为了使列名与项目名相等,我们需要从value前缀中获取。为此,我们可以使用substring函数:

setnames(x = table_wide,old = names(table_wide),new = substring(names(table_wide), 7))

我们需要在recommenderlab对象中存储评分矩阵。为此,我们首先需要将table_wide转换为矩阵。此外,我们还需要将行名设置为用户名:

matrix_wide <- as.matrix(table_wide)rownames(matrix_wide) <- vector_users
head(matrix_wide[, 1:6])

| user | 1038 | 1026 | 1034 | 1008 | 1056 | 1032 |
| --- | --- | --- | --- | --- |
| 10001 | 1 | 1 | 1 | NA | NA | NA |
| 10002 | NA | NA | NA | 1 | 1 | 1 |
| 10003 | 1 | 1 | NA | NA | NA | NA |
| 10004 | NA | NA | NA | NA | NA | NA |
| 10005 | 1 | 1 | 1 | 1 | NA | NA |
| 10006 | NA | NA | 1 | NA | NA | NA |

最后一步是使用asmatrix_wide强制转换为二进制评分矩阵,如下所示:

matrix_wide[is.na(matrix_wide)] <- 0
ratings_matrix <- as(matrix_wide, "binaryRatingMatrix")
ratings_matrix
## 5000 x 236 rating matrix of class binaryRatingMatrix with 15191 ratings.

让我们使用image查看矩阵:

image(ratings_matrix[1:50, 1:50], main = "Binary rating matrix")

下图显示了二进制评分矩阵:

定义评分矩阵

如预期的那样,矩阵是稀疏的。我们还可以可视化购买某个物品的用户数量的分布:

n_users <- colCounts(ratings_matrix)
qplot(n_users) + stat_bin(binwidth = 100) + ggtitle("Distribution of the number of users")

下图显示了用户数量的分布:

定义评分矩阵

有一些异常值,即被许多用户购买的物品。让我们排除它们来可视化分布:

qplot(n_users[n_users < 100]) + stat_bin(binwidth = 10) + ggtitle("Distribution of the number of users")

下图显示了用户数量的分布:

定义评分矩阵

有许多物品只被少数用户购买,我们不会推荐它们。由于它们增加了计算时间,我们可以通过定义最小购买数量来删除它们,例如,5

ratings_matrix <- ratings_matrix[, colCounts(ratings_matrix) >= 5]
ratings_matrix
## 5000 x 166 rating matrix of class 'binaryRatingMatrix' with 15043 ratings.

现在,我们有 166 个项目,与最初的 236 个相比。至于用户,我们希望向每个人推荐项目。然而,可能有一些用户只购买了我们已经删除的项目。让我们检查一下:

sum(rowCounts(ratings_matrix) == 0)
## _15_

有 15 个用户没有购买任何东西。这些购买应该被删除。此外,只购买了几件商品的用户的处理比较困难。因此,我们只保留至少购买过五件商品的用户:

ratings_matrix <- ratings_matrix[rowCounts(ratings_matrix) >= 5, ]
ratings_matrix
## 959 x 166 rating matrix of class 'binaryRatingMatrix' with 6816 ratings

提取项目属性

table_in原始数据中包含一些以A开头的记录,并显示有关项目的一些信息。为了提取这些记录,我们可以将table_in转换为数据表,并提取第一列包含A的行:

table_in <- data.table(table_in)
table_items <- table_in[V1 == "A"]
head(table_items)

V1 V2 V3 V4 V5
A 1277 1 NetShow for PowerPoint /stream
A 1253 1 MS Word Development /worddev
A 1109 1 TechNet (World Wide Web Edition) /technet
A 1038 1 SiteBuilder Network Membership /sbnmember
A 1205 1 Hardware Supprt /hardwaresupport
A 1076 1 NT Workstation Support /ntwkssupport

相关的列是:

  • V2: 项目 ID

  • V4: 项目描述

  • V5: 网页 URL

为了使表格更清晰,我们可以提取并重命名它们。此外,我们可以按项目 ID 对表格进行排序:

table_items <- table_items[, c(2, 4, 5), with = FALSE]
setnames(table_items, 1:3, c("id", "description", "url"))
table_items <- table_items[order(id)]
head(table_items)

id description url
1000 regwiz /regwiz
1001 Support desktop /support
1002 End user produced view /athome
1003 Knowledge base /kb
1004 Microsoft.com search /search
1005 Norway /norge

我们需要识别一个或多个描述项目的特征。如果我们看一下表格,我们可以识别出两种网页类别:

  • 微软产品

  • 地理位置信息

我们可以识别包含地理位置信息的记录,并将剩余的视为产品。为此,我们可以开始定义字段category,目前,对于所有记录,该字段等于product

table_items[, category := "product"]

国家代码包为我们提供了包含大多数国家名称的countrycode_data对象。我们可以定义一个包含国家和地理位置名称的name_countries向量。然后,我们可以将所有描述在name_countries中的记录分类为region

name_countries <- c(countrycode_data$country.name, "Taiwan", "UK", "Russia", "Venezuela", "Slovenija", "Caribbean", "Netherlands (Holland)", "Europe", "Central America", "MS North Africa")
table_items[description %in% name_countries, category := "region"]

有其他记录包含单词region。我们可以通过使用grepl的正则表达式来识别它们:

table_items[grepl("Region", description), category := "region"]
head(table_items)

V2 description url category
1000 regwiz /regwiz product
1001 Support Desktop /support product
1002 End User Produced View /athome product
1003 Knowledge Base /kb product
1004 Microsoft.com Search /search product
1005 Norway /norge region

让我们看一下结果,并找出每个类别中我们有多少个项目:

table_items[, list(n_items = .N), by = category]

category n_items
product 248
region 46

大约 80%的网页是产品,其余 20%是地区。

我们现在已准备好构建推荐模型。

构建模型

本节将向您展示如何使用商品描述和用户购买构建推荐模型。该模型结合了基于商品的协同过滤和一些关于商品的信息。我们将使用具有特征组合的单体混合系统包含商品描述。推荐器将在两个不同的阶段从两个数据源中学习。

按照第三章中描述的方法,第三章,推荐系统,让我们将数据分为训练集和测试集:

which_train <- sample(x = c(TRUE, FALSE),size = nrow(ratings_matrix),replace = TRUE,prob = c(0.8, 0.2))recc_data_train <- ratings_matrix[which_train, ]
recc_data_test <- ratings_matrix[!which_train, ]

现在,我们可以使用Recommender构建一个 IBCF 模型。由于评分矩阵是二进制的,我们将距离方法设置为Jaccard。更多详情,请参阅第三章中关于第三章,推荐系统二进制数据上的协同过滤部分。其余参数保持默认值:

recc_model <- Recommender(data = recc_data_train,method = "IBCF",parameter = list(method = "Jaccard"))

IBCF 的引擎基于一个关于商品的相似度矩阵。距离是从购买中计算出来的。相同用户购买的物品越多,它们就越相似。

我们可以从槽模型中的sim元素中提取矩阵。让我们看看它:

class(recc_model@model$sim)
## dgCMatrix
dim(recc_model@model$sim)
## _166_ and _166_

该矩阵属于dgCMatrix类,并且是方阵。我们可以使用image可视化它:

image(recc_model@model$sim)

下面的图像是前面代码的输出:

构建模型

我们无法识别任何明显的模式,这是因为商品没有排序。让我们看看值的范围:

range(recc_model@model$sim)
## _0_ and _1_

所有距离都在 0 到 1 之间。

我们的目标是将距离矩阵与商品描述结合,通过以下步骤:

  1. 基于购买定义一个相似度矩阵。

  2. 基于商品描述定义一个相似度矩阵。

  3. 结合两个矩阵。

recc_model开始,我们可以定义购买相似度矩阵。我们所需做的只是将dgCMatrix对象转换为matrix

dist_ratings <- as(recc_model@model$sim, "matrix")

为了根据商品描述构建矩阵,我们可以使用dist函数。鉴于它仅基于类别列,距离将如下所示:

  • 1,如果两个商品属于同一类别

  • 0,如果两个商品属于不同类别

我们需要构建一个相似度矩阵,我们有一个距离矩阵。由于距离在 0 到 1 之间,我们可以直接使用1 - dist()。所有操作都在数据表中执行:

dist_category <- table_items[, 1 - dist(category == "product")]class(dist_category)
## dist

dist_category原始数据是一个dist对象,可以使用as()函数轻松转换为矩阵:

dist_category <- as(dist_category, "matrix")

让我们比较dist_categorydist_ratings的维度:

dim(dist_category)
## _294_ and _294_
dim(dist_ratings)
## _166_ and _166_

dist_category表有更多的行和列,原因是它包含所有商品,而dist_ratings只包含那些已经被购买的商品。

为了将dist_categorydist_ratings结合,我们需要有相同的项。此外,它们需要按相同的方式排序。我们可以使用以下步骤使用项目名称匹配它们:

  1. 确保两个矩阵的行和列名中都包含项目名称。

  2. dist_ratings中提取行和列名。

  3. 根据dist_ratings的名称子集和排序dist_category

dist_ratings表格已经包含了行和列名。我们需要从table_items开始将它们添加到dist_category中:

rownames(dist_category) <- table_items[, id]
colnames(dist_category) <- table_items[, id]

现在,我们只需要从dist_ratings中提取名称并子集dist_category

vector_items <- rownames(dist_ratings)
dist_category <- dist_category[vector_items, vector_items]

让我们检查两个矩阵是否匹配:

identical(dim(dist_category), dim(dist_ratings))
## TRUE
identical(rownames(dist_category), rownames(dist_ratings))
## TRUE
identical(colnames(dist_category), colnames(dist_ratings))
## TRUE

一切都是相同的,所以它们匹配。让我们看看dist_category

image(dist_category)

下面的图像是前面代码的输出:

构建模型

矩阵只包含 0 和 1,它基于两个类别,因此有明显的模式。此外,我们可以注意到矩阵是对称的。

我们需要合并两个表,我们可以通过加权平均来实现。由于dist_category只考虑两种项目类别,因此最好不要给它太多的相关性。例如,我们可以将其权重设置为 25%:

weight_category <- 0.25
dist_tot <- dist_category * weight_category + dist_ratings * (1 - weight_category)

让我们看看使用imagedist_tot矩阵:

image(dist_tot)

下面的图像是前面代码的输出:

构建模型

我们可以看到一些代表非常相似项目的白色点。此外,我们还可以在背景中看到dist_category的模式。

现在,我们可以在recc_model中包含新的矩阵。为此,将dist_tot转换为dgCMatrix并插入到recc_model中:

recc_model@model$sim <- as(dist_tot, "dgCMatrix")
recc_model@model$sim <- as(dist_tot, "dgCMatrix")

如第三章中所示,推荐系统,我们可以使用predict()来推荐项目:

n_recommended <- 10
recc_predicted <- predict(object = recc_model, newdata = recc_data_test, n = n_recommended)

recc_predicteditemLabels槽包含项目名称,即它们的代码:

head(recc_predicted@itemLabels)

1038, 1026, 1034, 1008, 1056 and 1032

为了显示项目描述,我们可以使用table_items。我们只需要确保项目按与itemLabels相同的方式排序。为此,我们将准备一个包含项目信息的数据框。我们还将确保它按项目标签的相同方式排序,以下步骤:

  1. 定义一个包含有序项目标签列的数据框。

    table_labels <- data.frame(id = recc_predicted@itemLabels)
    
    
  2. table_labelstable_items之间进行左连接。注意参数sort = FALSE不允许我们重新排序表格:

    table_labels <- merge(table_labels, table_items,
     by = "id", all.x = TRUE, all.y = FALSE,
     sort = FALSE)
    
    
  3. 将描述从因子转换为字符:

    descriptions <- as(table_labels$description, "character")
    
    

让我们看看table_labels

head(table_labels)
id description url category
1038 SiteBuilder Network Membership /sbnmember product
1026 Internet Site Construction for Developers /sitebuilder product
1034 Internet Explorer /ie product
1008 Free Downloads /msdownload product
1056 sports /sports product
1032 Games /games product

如预期的那样,表格包含了项目的描述。现在,我们能够提取推荐。例如,我们可以为第一个用户做这件事:

recc_user_1 <- recc_predicted@items[[1]]
items_user_1 <- descriptions[recc_user_1]
head(items_user_1)

Windows 操作系统系列、支持桌面、知识库、Microsoft.com 搜索、产品、Windows 95。

现在,我们可以定义一个包含对所有用户推荐的表格。每一列对应一个用户,每一行对应一个推荐的项目。将 n_recommended 设置为 10 后,表格应该有 10 行。为此,我们可以使用 sapply()。对于 recc_predicted@items 的每个元素,我们识别相关的项目描述。

然而,每个用户被推荐的项目数量是一个介于 1 到 10 之间的数字,对每个用户来说都不相同。为了定义一个有 10 行的结构化表格,我们需要每个用户有相同数量的元素。因此,我们将缺失的推荐替换为空字符串。我们可以通过使用 rep() 来复制空字符串来获得它:

recc_matrix <- sapply(recc_predicted@items, function(x){
 recommended <- descriptions[x]
 c(recommended, rep("", n_recommended - length(recommended)))
})
dim(recc_matrix)
## _10_ and _191_

让我们看看前三个用户的推荐:

head(recc_matrix[, 1:3])

Windows 操作系统系列 产品 开发者工作坊
支持桌面 MS Word 站点构建网络会员
知识库 isapi isapi
Microsoft.com 搜索 regwiz Microsoft.com 搜索
产品 Windows 操作系统系列 Windows 操作系统系列
Windows 95 Microsoft.com 搜索 网站构建者画廊

我们可以注意到,有些项目被推荐给了三个用户:产品和支持桌面。因此,我们怀疑有些项目更有可能被推荐。

就像我们在第三章中做的那样,推荐系统,我们可以探索输出。对于每个项目,我们可以计算它被推荐了多少次:

table_recomm_per_item <- table(recc_matrix)
recomm_per_item <- as(table_recomm_per_item, "numeric")

为了可视化结果,我们使用 cut()bin_recomm_per_item 进行操作:

bin_recomm_per_item <- cut(recomm_per_item,breaks = c(0, 10, 20, 100,max(recomm_per_item)))

使用 qplot,我们可以可视化 recomm_per_item 的分布:

qplot(bin_recomm_per_item) + ggtitle("Recommendations per item")

以下图像显示了每个项目的推荐:

构建模型

大多数项目被推荐了 10 次或更少,而其中一些项目被推荐了超过 100 次。分布具有长尾。

我们还可以通过排序 recomm_per_item 来识别最受欢迎的项目:

recomm_per_item_sorted <- sort(table_recomm_per_item,decreasing = TRUE) recomm_per_item_top <- head(recomm_per_item_sorted, n = 4)
table_top <- data.frame(
 name = names(recomm_per_item_top), n_recomm = recomm_per_item_top)
table_top

名称 n_recomm
Internet Explorer 126
Windows 操作系统系列 120
知识库 118
产品 115

在本节中,我们构建并探索了一个混合推荐模型。下一步是评估它并优化其参数。

评估和优化模型

本节将向您展示如何评估我们推荐系统的性能。从评估开始,我们可以尝试一些参数配置,并选择表现最好的一个。有关更多详细信息,请参阅第四章,评估推荐系统

以下是对模型进行评估和优化的步骤:

  • 构建一个给定参数配置评估模型的函数

  • 使用该函数测试不同的参数配置,并选择最佳配置

让我们详细地走一遍这些步骤。

构建一个用于评估模型的函数

本节将向您展示如何定义一个函数:

  1. 使用k-折设置交叉验证。

  2. 构建一个混合 IBCF。

  3. 向测试集中的用户推荐项目。

  4. 评估推荐。

我们函数的输入如下:

  • 数据:这是包含项目描述的评分矩阵表

  • k-折参数:这是折数,测试集中保留的项目数

  • 模型参数:这是最近邻的数量,基于描述的距离权重,推荐的项目数量

让我们定义函数参数。您可以在每个参数名称旁边的注释中找到每个参数的描述:

evaluateModel <- function (
 # data inputs
 ratings_matrix, # rating matrix
 table_items, # item description table
 # K-fold parameters
 n_fold = 10, # number of folds
 items_to_keep = 4, # number of items to keep in the test set
 # model parameters
 number_neighbors = 30, # number of nearest neighbors
 weight_description = 0.2, # weight to the item description-based distance
 items_to_recommend = 10 # number of items to recommend
){
 # build and evaluate the model
}

现在,我们可以一步一步地遍历函数体。对于更详细的解释,请参阅上一节和第四章,评估推荐系统

  1. 使用evaluationScheme()函数设置k-折。参数k和给定分别根据输入n_folditems_to_keep设置。set.seed(1)命令确保示例可重复,也就是说,如果重复执行,随机成分将是相同的:

    set.seed(1)
    eval_sets <- evaluationScheme(data = ratings_matrix,
     method = "cross-validation",
     k = n_fold,
     given = items_to_keep)
    
    
  2. 使用Recommender()构建一个定义距离函数为Jaccardk参数为number_neighbors输入的 IBCF:

    recc_model <- Recommender(data = getData(eval_sets, "train"),
     method = "IBCF",
     parameter = list(method = "Jaccard",
     k = number_neighbors))
    
    
  3. recc_model模型中提取基于评分的距离矩阵:

    dist_ratings <- as(recc_model@model$sim, "matrix")
    vector_items <- rownames(dist_ratings)
    
    
  4. table_items输入开始,定义基于描述的距离矩阵:

    dist_category <- table_items[, 1 - as.matrix(dist(category == "product"))]
    rownames(dist_category) <- table_items[, id]
    colnames(dist_category) <- table_items[, id]
    dist_category <- dist_category[vector_items, vector_items]
    
    
  5. 定义结合dist_ratingsdist_category的距离矩阵。组合是加权平均,权重由weight_description输入定义:

    dist_tot <- dist_category * weight_description +
     dist_ratings * (1 - weight_description)
    recc_model@model$sim <- as(dist_tot, "dgCMatrix")
    
    
  6. 预测具有已知购买的测试集用户。由于我们使用的是只有 0 和 1 评分的表格,我们可以指定使用type = "topNList"参数预测前n个推荐项。定义推荐项目数量的参数n来自items_to_recommend输入:

    eval_prediction <- predict(object = recc_model, newdata = getData(eval_sets, "known"), n = items_to_recommend, type = "topNList")
    
    
  7. 使用calcPredictionAccuracy()评估模型性能。指定byUser = FALSE,我们得到一个包含平均指标(如精确率和召回率)的表格:

    eval_accuracy <- calcPredictionAccuracy(
     x = eval_prediction,
     data = getData(eval_sets, "unknown"),
     byUser = FALSE,
     given = items_to_recommend)
    
    
  8. 函数输出是eval_accuracy表:

    return(eval_accuracy)
    
    
  9. 现在,我们可以测试我们的函数:

    model_evaluation <- evaluateModel(ratings_matrix = ratings_matrix, table_items = table_items)
    model_evaluation
    
    
    index value
    TP 2
    FP 8
    FN 1
    TN 145
    precision 19%
    recall 64%
    TPR 64%
    FPR 5%

您可以在第四章中找到索引的详细描述,评估推荐系统

在本节中,我们定义了一个使用给定设置的函数来评估我们的模型。这个函数将帮助我们进行参数优化。

优化模型参数

从我们的 evaluateModel() 函数开始,我们可以优化模型参数。在本节中,我们将优化以下参数:

  • number_neighbors:这是 IBCF 的最近邻数量

  • weight_description:这是分配给基于描述的距离的权重

尽管我们可以优化其他参数,但为了简化,我们将它们保留为默认值。

我们的推荐模型结合了 IBCF 和项目描述。因此,首先优化 IBCF 是一个好习惯,即number_neighbors参数。

首先,我们必须决定我们想要测试哪些值。我们考虑 k,即最多为商品的一半,即大约80。另一方面,我们排除小于 4 的值,因为算法将过于不稳定。设置粒度为2,我们可以生成一个包含要测试的值的向量:

nn_to_test <- seq(4, 80, by = 2)

现在,我们可以根据 number_neighbors 来衡量性能。由于我们只优化 IBCF 部分,我们将 weight_description = 0。使用 lapply,我们可以构建一个包含每个 nn_to_test 值的性能的元素列表:

list_performance <- lapply(
 X = nn_to_test,
 FUN = function(nn){
 evaluateModel(ratings_matrix = ratings_matrix, table_items = table_items, number_neighbors = nn, weight_description = 0)
 })

让我们看看列表的第一个元素:

list_performance[[1]]

name value
TP 1.663
FP 8.337
FN 1.683
TN 144.3
精确度 0.1663
召回率 0.5935
TPR 0.5935
FPR 0.05449

第一个元素包含所有性能指标。为了评估我们的模型,我们可以使用精确度和召回率。参见第四章,评估推荐系统获取更多信息。

我们可以使用 sapply 提取精确度(或召回率)的向量:

sapply(list_performance, "[[", "precision")^t

0.1663, 0.1769, 0.1769, 0.175, 0.174, 0.1808, 0.176, 0.1779, 0.1788, 0.1788, 0.1808, 0.1817, 0.1817, 0.1837, 0.1846, 0.1837, 0.1827, 0.1817, 0.1827, 0.1827, 0.1817, 0.1808, 0.1817, 0.1808, 0.1808, 0.1827, 0.1827, 0.1837, 0.1827, 0.1808, 0.1798, 0.1798, 0.1798, 0.1798, 0.1798, 0.1798, 0.1788, 0.1788 and 0.1788

为了分析输出,我们可以定义一个表格,其列包括 nn_to_test、精确度和召回率:

table_performance <- data.table(
 nn = nn_to_test, precision = sapply(list_performance, "[[", "precision"), recall = sapply(list_performance, "[[", "recall")
)

此外,我们可以定义一个性能指数,我们将对其进行优化。性能指数可以是精确度和召回率之间的加权平均值。权重取决于用例,因此我们可以将其保留为 50%:

weight_precision <- 0.5
table_performance[
 performance := precision * weight_precision + recall * (1 - weight_precision)]
head(table_performance)

nn 精确度 召回率 性能
4 0.1663 0.5935 0.3799
6 0.1769 0.621 0.399
8 0.1769 0.5973 0.3871
10 0.175 0.5943 0.3846
12 0.174 0.5909 0.3825
14 0.1808 0.6046 0.3927

精确度是推荐商品中已购买的比例,召回率是已购买商品中被推荐的比例。

table_performance 表格包含所有评估指标。从它开始,我们可以构建图表,帮助我们确定最优的 nn

在构建图表之前,让我们定义一个 convertIntoPercent() 函数,我们将在 ggplot2 函数中使用它:

convertIntoPercent <- function(x){
 paste0(round(x * 100), "%")
}

我们已经准备好构建图表。第一个图表是基于 nn 的精确度。我们可以使用以下函数构建它:

  • qplot:这个函数用于构建散点图。

  • geom_smooth:这添加了一条平滑线。

  • scale_y_continuous:这改变了y轴的刻度。在我们的例子中,我们只想显示百分比。

以下命令包括前面的点:

 qplot(table_performance[, nn], table_performance[, precision]) + geom_smooth() + scale_y_continuous(labels = convertIntoPercent)

以下图像是前面代码的输出:

优化模型参数

平滑的线条增长到全局最大值,大约在nn = 35,然后缓慢下降。这个指标表示成功推荐的百分比,因此当与广告相关的成本较高时很有用。

让我们看看召回率,使用相同的命令:

 qplot(table_performance[, nn], table_performance[, recall]) + geom_smooth() + scale_y_continuous(labels = convertIntoPercent)

以下图像是前面屏幕截图的输出:

优化模型参数

最大召回率大约在nn = 40。这个指标表示我们推荐的购买百分比,因此如果我们想确保预测大多数购买很有用。

性能同时考虑了精确度和召回率。让我们看看它:

 qplot(table_performance[, nn], table_performance[, performance]) + geom_smooth() + scale_y_continuous(labels = convertIntoPercent)

优化模型参数

最佳性能在 30 到 45 之间。我们可以使用which.max识别最佳的nn

row_best <- which.max(table_performance$performance)
number_neighbors_opt <- table_performance[row_best, nn]
number_neighbors_opt
## _34_

最佳值是34。我们优化了 IBCF 参数,下一步是确定项目描述组件的权重。首先,让我们定义要尝试的权重。可能的权重范围在 0 到 1 之间,我们只需要设置粒度,例如,0.05

wd_to_try <- seq(0, 1, by = 0.05)

使用lapply,我们可以根据权重测试推荐器:

list_performance <- lapply(
 X = wd_to_try, FUN = function(wd){
 evaluateModel(ratings_matrix = ratings_matrix, table_items = table_items, number_neighbors = number_neighbors_opt, weight_description = wd) })

就像我们之前做的那样,我们可以构建一个包含精确度、召回率和性能的表格:

table_performance <- data.table(
 wd = wd_to_try, precision = sapply(list_performance, "[[", "precision"), recall = sapply(list_performance, "[[", "recall")
)
table_performance[
 performance := precision * weight_precision + recall * (1 - weight_precision)]

现在,我们可以通过图表可视化基于权重的性能:

 qplot(table_performance[, wd], table_performance[, performance]) + geom_smooth() + scale_y_continuous(labels = convertIntoPercent)

以下图像是前面命令的输出:

优化模型参数

性能在每个点上都是相同的,除了极端值。因此,平滑线没有用。

我们得到了考虑评分和描述的最佳性能。极端的0.00对应于纯 IBCF,它的表现略逊于混合模型。极端的1.00模型仅基于项目描述,这就是为什么它的表现如此糟糕。

性能变化不大的原因是项目描述仅基于二进制特征。如果我们添加其他特征,我们将看到更大的影响。

这一节向您展示了如何基于两个参数优化我们的推荐算法。下一步可以是基于剩余的 IBCF 参数优化并改进项目描述。

摘要

本章向您展示了如何在现实生活场景中应用这些技术。我们从原始的非结构化数据开始,构建了一个评分矩阵,这是协同过滤的输入。此外,我们还提取了项目描述,这提高了我们模型的表现。通过性能评估,我们优化了模型参数。如果适当细化,这种方法也可以应用于现实生活场景。

本书是一条路径,首先展示了机器学习的基础知识,然后是实际应用。阅读完本书后,您将能够应对现实生活中的挑战,确定最合适的推荐解决方案。感谢您一直跟随到这里。

如果您有任何疑问,请随时联系我们。

附录 A. 参考文献

想了解更多关于推荐系统和机器学习的知识,请参考以下参考资料:

  • Dietmar JannachMarkus ZankerAlexander FelfernigGerhard Friedrich合著的推荐系统:入门

  • Francesco RicciLior RokachBracha ShapiraPaul B. Kantor合著的推荐系统手册

  • Gareth JamesDaniela WittenTrevor HastieRobert Tibshirani合著的统计学习基础与应用:R 语言版

  • en.wikipedia.org/wiki/Precision_and_recall

  • 安德鲁·吴www.coursera.org/提供的在线课程机器学习

posted @ 2025-09-03 10:23  绝不原创的飞龙  阅读(9)  评论(0)    收藏  举报