R-机器学习示例-全-

R 机器学习示例(全)

原文:annas-archive.org/md5/099c261ea8cfb1acbd5544b92a953b97

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

数据科学和机器学习是目前技术界的热门词汇之一。从零售商店到《财富》500 强公司,每个人都努力使机器学习为他们提供数据驱动的洞察力,以促进业务增长。凭借强大的数据处理功能、机器学习包和活跃的开发者社区,R 赋予用户构建复杂的机器学习系统以解决现实世界数据问题的能力。

本书带您踏上一场数据驱动的旅程,从 R 和机器学习的基础知识开始,逐步构建概念,以解决实际问题的项目。

本书涵盖内容

第一章, 《使用 R 和机器学习入门》使您熟悉本书内容,并帮助您重新熟悉 R 及其基础知识。本章还为您提供了机器学习的简要介绍。

第二章, 《让我们帮助机器学习》通过解释构成其基础的概念,深入探讨机器学习。您还将了解到各种学习算法,以及一些现实世界的例子。

第三章, 《使用市场篮子分析预测客户购物趋势》以我们的第一个项目开始,即电子商务产品推荐、预测和模式分析,使用各种机器学习技术。本章特别涉及市场篮子分析和关联规则挖掘,以检测客户购物模式和趋势,并使用这些技术进行产品预测和建议。这些技术在零售公司如塔吉特(Target)、梅西百货(Macy's)、Flipkart 和亚马逊(Amazon)等电子商务商店中广泛用于产品推荐。

第四章, 《构建产品推荐系统》涵盖了我们的第一个项目——电子商务产品推荐、预测和模式分析的第二部分。本章特别涉及通过使用用户协同过滤等算法和技术分析不同用户对电子商务产品的评论和评分,以设计一个生产就绪的推荐系统。

第五章, 信用风险检测与预测 – 描述性分析,从我们的第二个项目开始,该项目将机器学习应用于一个复杂的金融场景,其中我们处理信用风险检测和预测。本章特别介绍了主要目标,查看一个包含 1,000 人申请银行贷款的金融信用数据集。我们将使用机器学习技术来检测可能存在信用风险的人,以及如果他们从银行贷款可能无法偿还的人,并预测未来可能的情况。本章还将详细讨论我们的数据集,处理数据时的主要挑战,数据集的主要特征,以及对数据的探索性和描述性分析。它将以解决这个问题的最佳机器学习技术作为结论。

第六章, 信用风险检测与预测 – 预测分析,从我们在上一章关于描述性分析中留下的地方开始,探讨使用预测分析。在这里,我们特别处理使用几个机器学习算法来检测和预测哪些客户可能是潜在的信用风险,并且如果他们从银行贷款可能不太可能偿还。这最终将帮助银行做出基于数据的决策,决定是否批准贷款。我们将涵盖几个监督学习算法,并比较它们的性能。这里还将涵盖评估各种机器学习算法效率和准确性的不同指标。

第七章, 社交媒体分析 – 分析 Twitter 数据,介绍了社交媒体分析的世界。我们从社交媒体的世界和通过 Twitter 的 API 收集数据的过程开始介绍。本章将向您介绍从推文中挖掘有用信息的过程,包括使用真实世界示例可视化 Twitter 数据,推文的聚类和主题建模,当前挑战和复杂性,以及解决这些问题的策略。我们通过示例展示了如何使用 Twitter 数据计算一些强大的度量。

第八章, Twitter 数据情感分析,基于 Twitter API 的知识,用于分析推文中的情感。该项目展示了多种机器学习算法,用于根据推断出的情感对推文进行分类。本章还将以比较的方式展示这些结果,并帮助您理解这些算法的工作原理和结果差异。

您需要为本书准备的内容

本软件适用于本书的所有章节:

  • Windows / Mac OS X / Linux

  • R 3.2.0(或更高版本)

  • RStudio 桌面版 0.99(或更高版本)

对于硬件,没有具体要求,因为 R 可以在任何装有 Mac、Linux 或 Windows 的 PC 上运行,但为了顺畅运行一些迭代算法,建议至少有 4GB 的物理内存。

本书面向的对象

如果您对使用最先进的技术从数据中挖掘有用信息,并使用数据驱动决策感兴趣,这是一本必读指南。虽然不需要数据科学方面的先前经验,但基本了解 R 是非常理想的。对机器学习的先验知识将有所帮助,但不是必需的。

约定

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

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

任何命令行输入或输出都应如下编写:

# comparing cluster labels with actual iris  species labels.
table(iris$Species, clusters$cluster)

新术语重要词汇将以粗体显示。屏幕上显示的词汇,例如在菜单或对话框中,将以如下方式显示:“从 Twitter 上的关注谁的相关推荐到 Netflix 上的你可能喜欢的其他电影,再到 LinkedIn 上的你可能感兴趣的工作,推荐引擎无处不在,而不仅仅是电子商务平台。”

注意

警告或重要注意事项将如下所示。

小贴士

小贴士和技巧将如下所示。

读者反馈

我们欢迎读者的反馈。请告诉我们您对本书的看法——您喜欢或不喜欢的地方。读者反馈对我们来说非常重要,因为它帮助我们开发出您真正能从中获得最大价值的标题。

要发送一般性反馈,请简单地发送电子邮件至<feedback@packtpub.com>,并在邮件主题中提及本书的标题。

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

客户支持

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

下载示例代码

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

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

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

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

  3. 点击代码下载与错误清单

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

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

  6. 从下拉菜单中选择您购买本书的来源。

  7. 点击代码下载

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

  • WinRAR / 7-Zip for Windows

  • Zipeg / iZip / UnRarX for Mac

  • 7-Zip / PeaZip for Linux

下载本书的颜色图像

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

错误清单

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

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

盗版

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

请通过发送链接到<copyright@packtpub.com>与我们联系,以提供疑似盗版材料的链接。

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

问题

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

第一章. R 和机器学习入门

这本入门章节将帮助你开始学习 R 的基础,包括各种构造、有用的数据结构、循环和向量化。如果你已经是 R 高手,你可以快速浏览这些部分,然后直接进入下一部分,该部分将讨论机器学习作为一个领域实际上代表什么以及它涵盖的主要领域。我们还将讨论每个领域使用的不同机器学习技术和算法。最后,我们将通过查看 R 中最受欢迎的机器学习包来结束,其中一些将在后续章节中使用。

如果你是一位数据或机器学习爱好者,你肯定已经听说,哈佛商业评论将数据科学家的工作称为 21 世纪的“最性感的工作”。

注意

参考:hbr.org/2012/10/data-scientist-the-sexiest-job-of-the-21st-century/

目前市场上对数据科学家的需求巨大,主要是因为他们的主要工作是收集从非结构化和结构化数据中获取关键见解和信息,以帮助他们的企业和组织战略性地增长。

一些朋友可能会想知道机器学习或 R 与这些有什么关系!好吧,要成为一名成功的数据科学家,你工具箱中需要的主要工具之一是能够执行复杂统计计算、处理各种类型的数据以及构建模型的强大语言,而 R 正是这种完美的语言!机器学习构成了你成为数据分析师或数据科学家所需技能的基础,这包括使用各种技术构建模型以从数据中获得见解。

本书将为你提供一些必要的工具,帮助你熟练掌握 R 和机器学习,不仅通过观察概念,还通过将概念应用于现实世界的例子。足够说了;现在让我们开始我们的 R 机器学习之旅吧!

在本章中,我们将涵盖以下方面:

  • 深入了解 R 的基础

  • 理解 R 中的数据结构

  • 使用函数

  • 控制代码流程

  • 进一步使用 R

  • 理解机器学习基础知识

  • 熟悉 R 中流行的机器学习包

深入了解 R 的基础

假设您至少熟悉 R 的基础知识或者之前使用过 R。因此,我们不会过多地讨论下载和安装。网上有大量资源提供了关于这方面的很多信息。我建议您使用 RStudio,这是一个集成开发环境IDE),它比基本的 R图形用户界面GUI)要好得多。您可以访问www.rstudio.com/获取更多关于它的信息。

注意

关于 R 项目的详细信息,您可以访问www.r-project.org/以了解该语言的概述。除此之外,R 还拥有大量的优秀包可供使用,您可以在cran.r-project.org/查看与 R 及其包相关的所有信息,该网站包含所有存档。

您必须已经熟悉 R 交互式解释器,通常被称为读取-评估-打印循环(REPL)。这个解释器就像任何命令行界面一样,要求输入,并以>字符开始,这表示 R 正在等待您的输入。如果您的输入跨越多行,例如在编写函数时,您将在后续的每一行看到一个+提示,这意味着您还没有完成整个表达式的输入,R 正在要求您提供剩余的表达式。

R 也可以读取和执行包含命令和函数的完整文件,这些命令和函数保存在以.R扩展名保存的文件中。通常,任何大型应用程序都由几个.R文件组成。每个文件在应用程序中都有自己的角色,通常被称为模块。在接下来的几节中,我们将探讨 R 的一些主要功能和能力。

使用 R 作为科学计算器

R 中最基本的构造包括变量和算术运算符,可以用来执行像计算器一样的简单数学运算,甚至复杂的统计计算。

> 5 + 6
[1] 11
> 3 * 2
[1] 6
> 1 / 0
[1] Inf

1.

你也可以像任何其他编程语言一样给变量赋值并在其上操作。

> num <- 6
> num ^ 2
[1] 36
> num
[1] 6     # a variable changes value only on re-assignment
> num <- num ^ 2 * 5 + 10 / 3
> num
[1] 183.3333

向量操作

R 中最基本的数据结构是向量。基本上,R 中的任何东西都是一个向量,即使是一个单独的数字,就像我们在前面的例子中看到的那样!向量基本上是一个序列或一组值。我们可以使用:运算符或c函数来创建向量,该函数将值连接起来以创建一个向量。

> x <- 1:5
> x
[1] 1 2 3 4 5
> y <- c(6, 7, 8 ,9, 10)
> y
[1]  6  7  8  9 10
> z <- x + y
> z
[1]  7  9 11 13 15

+ operator. This is known as vectorization and we will be discussing more about this later on. Some more operations on vectors are shown next:
> c(1,3,5,7,9) * 2
[1]  2  6 10 14 18
> c(1,3,5,7,9) * c(2, 4)
[1]  2 12 10 28 18 # here the second vector gets recycled

输出:

向量操作

> factorial(1:5)
[1]   1   2   6  24 120
> exp(2:10)   # exponential function
[1]     7.389056    20.085537    54.598150   148.413159   403.428793  1096.633158
[7]  2980.957987  8103.083928 22026.465795
> cos(c(0, pi/4))   # cosine function
[1] 1.0000000 0.7071068
> sqrt(c(1, 4, 9, 16))
[1] 1 2 3 4
> sum(1:10)
[1] 55

在第二次操作中,我们尝试将一个较小的向量与一个较大的向量相乘,但我们仍然得到了一个结果!如果您仔细观察,R 也抛出了一个警告。在这种情况下发生的情况是,由于两个向量的大小不相等,这个较小的向量c(2, 4)被循环或重复,变成了c(2, 4, 2, 4, 2),然后它与第一个向量c(1, 3, 5, 7 ,9)相乘,得到了最终的向量结果c(2, 12, 10, 28, 18)。这里提到的其他函数是 R 基础库中的标准函数,以及几个其他函数。

小贴士

下载示例代码

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

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

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

  • 将鼠标指针悬停在顶部的支持标签上

  • 点击代码下载与勘误

  • 搜索框中输入书籍名称

  • 选择您想要下载代码文件的书籍

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

  • 点击代码下载

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

  • WinRAR / 7-Zip for Windows

  • Zipeg / iZip / UnRarX for Mac

  • 7-Zip / PeaZip for Linux

特殊值

由于您将在数据分析和机器学习中处理大量混乱和脏数据,因此记住 R 中的一些特殊值很重要,这样您就不会在它们出现时感到太惊讶。

> 1 / 0
[1] Inf
> 0 / 0
[1] NaN
> Inf / NaN
[1] NaN
> Inf / Inf
[1] NaN
> log(Inf)
[1] Inf
> Inf + NA
[1] NA

TRUE and FALSE are logical data type values, similar to other programming languages.
> vec <- c(0, Inf, NaN, NA)
> is.finite(vec)
[1]  TRUE FALSE FALSE FALSE
> is.nan(vec)
[1] FALSE FALSE  TRUE FALSE
> is.na(vec)
[1] FALSE FALSE  TRUE  TRUE
> is.infinite(vec)
[1] FALSE  TRUE FALSE FALSE

这些函数的名称相当直观。它们清楚地表明哪些值是有限的,哪些值是有限的,并分别检查NaNNA值。其中一些函数在清理脏数据时非常有用。

R 中的数据结构

在这里,我们将探讨 R 中存在的一些最有用的数据结构,并使用一些虚构的例子来更好地掌握它们的语法和结构。我们将在这里涵盖的主要数据结构包括:

  • 向量

  • 数组和矩阵

  • 列表

  • 数据框

这些数据结构在 R 内部以及各种 R 包和函数中广泛使用,包括我们将在后续章节中使用的机器学习函数和算法。因此,了解如何使用这些数据结构来高效地处理数据是至关重要的。

第二章:向量

seq family of functions to initialize vectors in different ways.
> c(2.5:4.5, 6, 7, c(8, 9, 10), c(12:15))
 [1]  2.5  3.5  4.5  6.0  7.0  8.0  9.0 10.0 12.0 13.0 14.0 15.0
> vector("numeric", 5)
[1] 0 0 0 0 0
> vector("logical", 5)
[1] FALSE FALSE FALSE FALSE FALSE
> logical(5)
[1] FALSE FALSE FALSE FALSE FALSE
> # seq is a function which creates sequences
> seq.int(1,10)
 [1]  1  2  3  4  5  6  7  8  9 10
> seq.int(1,10,2)
[1] 1 3 5 7 9
> seq_len(10)
 [1]  1  2  3  4  5  6  7  8  9 10

向量的索引和命名

我们可以在向量上执行的最重要操作之一涉及子集和索引向量以访问特定元素,这在只想在特定数据点上运行代码时非常有用。以下示例展示了我们可以如何索引和子集向量:

> vec <- c("R", "Python", "Julia", "Haskell", "Java", "Scala")
> vec[1]
[1] "R"
> vec[2:4]
[1] "Python"  "Julia"   "Haskell"
> vec[c(1, 3, 5)]
[1] "R"     "Julia" "Java" 
> nums <- c(5, 8, 10, NA, 3, 11)
> nums
[1]  5  8 10 NA  3 11
> which.min(nums)   # index of the minimum element
[1] 5
> which.max(nums)   # index of the maximum element
[1] 6
> nums[which.min(nums)]  # the actual minimum element
[1] 3
> nums[which.max(nums)]  # the actual maximum element
[1] 11

现在,我们来看看如何命名向量。这基本上是 R 中的一个巧妙功能,您可以为向量中的每个元素添加标签,使其更易于阅读或解释。这可以通过两种方式完成,以下示例展示了这些方法:

> c(first=1, second=2, third=3, fourth=4, fifth=5)

输出:

索引和命名向量

> positions <- c(1, 2, 3, 4, 5)
> names(positions) 
NULL
> names(positions) <- c("first", "second", "third", "fourth", "fifth")
> positions

输出:

索引和命名向量

> names(positions)
[1] "first"  "second" "third"  "fourth" "fifth"
> positions[c("second", "fourth")]

输出:

索引和命名向量

因此,您可以看到,有时注释和命名向量变得非常有用,我们也可以使用元素名称而不是值来子集和切片向量。

数组和矩阵

向量是一维数据结构,这意味着它们只有一个维度,我们可以使用length属性来获取它们的元素数量。请记住,在其它编程语言中,数组可能也有类似的意义,但在 R 中,它们的意义略有不同。基本上,R 中的数组是包含多维数据的结构。矩阵是具有两个维度的通用数组的特例,由rowscolumns属性表示。让我们在下面的代码片段中查看一些示例。

创建数组和矩阵

首先,我们将创建一个三维数组。现在在屏幕上表示两个维度很容易,但要增加一个维度,R 有一些特殊的方法来转换数据。以下示例展示了 R 如何在每个维度中填充数据(列优先)以及一个 4x3x3 数组的最终输出:

> three.dim.array <- array(
+     1:32,    # input data
+     dim = c(4, 3, 3),   # dimensions
+     dimnames = list(    # names of dimensions
+         c("row1", "row2", "row3", "row4"),
+         c("col1", "col2", "col3"),
+         c("first.set", "second.set", "third.set")
+     )
+ )
> three.dim.array

输出:

创建数组和矩阵

就像我之前提到的,矩阵只是数组的特例。我们可以使用matrix函数创建矩阵,以下示例将详细介绍。请注意,我们使用byrow参数在矩阵中以行方式填充数据,而不是 R 中任何数组或矩阵的默认列方式填充。ncolnrow参数分别代表列数和行数。

> mat <- matrix(
+     1:24,   # data
+     nrow = 6,  # num of rows
+     ncol = 4,  # num of columns
+     byrow = TRUE,  # fill the elements row-wise
+ )
> mat

输出:

创建数组和矩阵

名称和维度

就像我们命名向量和访问元素名称一样,在下面的代码片段中我们将执行类似的操作。您已经在前面的示例中看到了dimnames参数的使用。让我们看看以下更多的示例:

> dimnames(three.dim.array)

输出:

名称和维度

> rownames(three.dim.array)
[1] "row1" "row2" "row3" "row4"
> colnames(three.dim.array)
[1] "col1" "col2" "col3"
> dimnames(mat)
NULL
> rownames(mat)
NULL
> rownames(mat) <- c("r1", "r2", "r3", "r4", "r5", "r6")
> colnames(mat) <- c("c1", "c2", "c3", "c4")
> dimnames(mat)

输出:

名称和维度

> mat

输出:

名称和维度

要访问与数组和矩阵相关的维度细节,有一些特殊函数。以下示例展示了相同的内容:

> dim(three.dim.array)
[1] 4 3 3
> nrow(three.dim.array)
[1] 4
> ncol(three.dim.array)
[1] 3
> length(three.dim.array)  # product of dimensions
[1] 36
> dim(mat)
[1] 6 4
> nrow(mat)
[1] 6
> ncol(mat)
[1] 4
> length(mat)
[1] 24

矩阵运算

许多机器学习和优化算法都处理矩阵作为它们的输入数据。在下一节中,我们将探讨矩阵上最常见的运算的一些例子。

我们首先初始化两个矩阵,然后探讨使用诸如c函数(返回一个向量)、rbind函数(按行组合矩阵)和cbind函数(按列组合矩阵)等函数组合这两个矩阵的方法。

> mat1 <- matrix(
+     1:15,
+     nrow = 5,
+     ncol = 3,
+     byrow = TRUE,
+     dimnames = list(
+         c("M1.r1", "M1.r2", "M1.r3", "M1.r4", "M1.r5")
+         ,c("M1.c1", "M1.c2", "M1.c3")
+     )
+ )
> mat1

输出:

矩阵运算

> mat2 <- matrix(
+     16:30,
+     nrow = 5,
+     ncol = 3,
+     byrow = TRUE,
+     dimnames = list(
+         c("M2.r1", "M2.r2", "M2.r3", "M2.r4", "M2.r5"),
+         c("M2.c1", "M2.c2", "M2.c3")
+     )
+ )
> mat2

输出:

矩阵运算

> rbind(mat1, mat2)

输出:

矩阵运算

> cbind(mat1, mat2)

输出:

矩阵运算

> c(mat1, mat2)

输出:

矩阵运算

现在我们来看看在矩阵上可以执行的一些重要的算术运算。大多数运算从下面的语法中可以很容易地理解:

> mat1 + mat2   # matrix addition

输出:

矩阵运算

> mat1 * mat2  # element-wise multiplication

输出:

矩阵运算

> tmat2 <- t(mat2)  # transpose
> tmat2

输出:

矩阵运算

> mat1 %*% tmat2   # matrix inner product

输出:

矩阵运算

> m <- matrix(c(5, -3, 2, 4, 12, -1, 9, 14, 7), nrow = 3, ncol = 3)
> m

输出:

矩阵运算

> inv.m <- solve(m)  # matrix inverse
> inv.m

输出:

矩阵运算

> round(m %*% inv.m) # matrix * matrix_inverse = identity matrix

输出:

矩阵运算

之前提到的算术运算只是众多可以应用于矩阵的函数和运算符中的一部分。这在诸如线性优化等领域非常有用。

列表

列表是向量的特例,其中向量的每个元素可以是不同类型的数据结构,甚至可以是简单的数据类型。在某些方面,它与 Python 编程语言中的列表相似,如果您之前使用过它,那么列表表示可以具有不同类型的元素,并且每个元素在列表中都有一个特定的索引。在 R 中,列表的每个元素可以像单个元素一样简单,也可以像整个矩阵、函数或字符串向量一样复杂。

创建和索引列表

在下面的例子中,我们将开始查看一些创建和初始化列表的常见方法。此外,我们还将探讨如何访问这些列表元素以进行进一步的计算。请记住,列表中的每个元素可以是一个简单的原始数据类型,甚至可以是复杂的数据结构或函数。

> list.sample <- list(
+     1:5,
+     c("first", "second", "third"),
+     c(TRUE, FALSE, TRUE, TRUE),
+     cos,
+     matrix(1:9, nrow = 3, ncol = 3)
+ )
> list.sample

输出:

创建和索引列表

> list.with.names <- list(
+     even.nums = seq.int(2,10,2),
+     odd.nums  = seq.int(1,10,2),
+     languages = c("R", "Python", "Julia", "Java"),
+     cosine.func = cos
+ )
> list.with.names

输出:

创建和索引列表

> list.with.names$cosine.func
function (x)  .Primitive("cos")
> list.with.names$cosine.func(pi)
[1] -1
>
> list.sample[[4]]
function (x)  .Primitive("cos")
> list.sample[[4]](pi)
[1] -1
>
> list.with.names$odd.nums
[1] 1 3 5 7 9
> list.sample[[1]]
[1] 1 2 3 4 5
> list.sample[[3]]
[1]  TRUE FALSE  TRUE  TRUE

您可以从前面的例子中看到,访问列表中的任何元素并用于进一步计算(如cos函数)是多么容易。

合并和转换列表

在下面的例子中,我们将看看如何将几个列表组合成一个单一的列表:

> l1 <- list(
+     nums = 1:5,
+     chars = c("a", "b", "c", "d", "e"),
+     cosine = cos
+ )
> l2 <- list(
+     languages = c("R", "Python", "Java"),
+     months = c("Jan", "Feb", "Mar", "Apr"),
+     sine = sin
+ )
> # combining the lists now
> l3 <- c(l1, l2)
> l3

输出:

合并和转换列表

将列表转换为向量以及相反的操作非常简单。以下例子展示了我们可以实现这一点的常见方法:

> l1 <- 1:5
> class(l1)
[1] "integer"
> list.l1 <- as.list(l1)
> class(list.l1)
[1] "list"
> list.l1

输出:

合并和转换列表

> unlist(list.l1)
[1] 1 2 3 4 5

数据框

数据框是特殊的数据结构,通常用于存储数据表或以电子表格形式的数据,其中每一列表示一个特定的属性或字段,而每一行包含这些列的特定值。这种数据结构在处理通常具有许多字段和属性的集合时非常有用。

创建数据框

我们可以使用 data.frame 函数轻松地创建数据框。我们将通过一些流行的超级英雄的示例来展示这一点。

> df <- data.frame(
+     real.name = c("Bruce Wayne", "Clark Kent", "Slade Wilson", "Tony Stark", "Steve Rogers"),
+     superhero.name = c("Batman", "Superman", "Deathstroke", "Iron Man", "Capt. America"),
+     franchise = c("DC", "DC", "DC", "Marvel", "Marvel"),
+     team = c("JLA", "JLA", "Suicide Squad", "Avengers", "Avengers"),
+     origin.year = c(1939, 1938, 1980, 1963, 1941)
+ )
> df

输出:

创建数据框

> class(df)
[1] "data.frame"
> str(df)

输出:

创建数据框

> rownames(df)
[1] "1" "2" "3" "4" "5"
> colnames(df)

输出:

创建数据框

> dim(df)
[1] 5 5

str 函数详细说明了数据框的结构,其中我们可以看到数据框中每个列中数据的详细信息。R 基础中有很多现成的数据集可以直接加载并开始使用。下面展示的是其中之一。mtcars 数据集包含有关各种汽车的信息,这些信息是从 1974 年的《Motor Trend U.S. Magazine》中提取的。

> head(mtcars)   # one of the datasets readily available in R

输出:

创建数据框

操作数据框

我们可以在数据框上执行许多操作,例如合并、组合、切片和转置数据框。在接下来的示例中,我们将查看一些重要的数据框操作。

使用单索引和如 subset 函数之类的函数,在数据框中索引和子集特定数据非常容易。

> df[2:4,]

输出:

操作数据框

> df[2:4, 1:2]

输出:

操作数据框

> subset(df, team=="JLA", c(real.name, superhero.name, franchise))

输出:

操作数据框

> subset(df, team %in% c("Avengers","Suicide Squad"), c(real.name, superhero.name, franchise))

输出:

操作数据框

我们现在将查看一些更复杂的操作,例如合并和合并数据框。

> df1 <- data.frame(
+     id = c('emp001', 'emp003', 'emp007'),
+     name = c('Harvey Dent', 'Dick Grayson', 'James Bond'),
+     alias = c('TwoFace', 'Nightwing', 'Agent 007')
+ )
> 
> df2 <- data.frame(
+     id = c('emp001', 'emp003', 'emp007'),
+     location = c('Gotham City', 'Gotham City', 'London'),
+     speciality = c('Split Persona', 'Expert Acrobat', 'Gadget Master')
+ )
> df1

输出:

操作数据框

> df2

输出:

操作数据框

> rbind(df1, df2)   # not possible since column names don't match
Error in match.names(clabs, names(xi)) : 
 names do not match previous names
> cbind(df1, df2)

输出:

操作数据框

> merge(df1, df2, by="id")

输出:

操作数据框

从前面的操作中可以看出,rbindcbind 的工作方式与我们之前看到的数组矩阵相同。然而,merge 允许你以与在关系数据库中连接各种表相同的方式合并数据框。

使用函数

接下来,我们将探讨函数,这是一种技术或方法,可以轻松地构建和模块化你的代码,特别是执行特定任务的代码行,这样你就可以在需要时执行它们,而无需再次编写。在 R 中,函数基本上被视为另一种数据类型,你可以分配函数,根据需要操作它们,并将它们作为参数传递给其他函数。我们将在下一节中探讨所有这些内容。

内置函数

R 包含了几个在 R-base 包中可用的函数,并且随着您安装更多的包,您将获得更多的功能,这些功能以函数的形式提供。在以下示例中,我们将查看一些内置函数:

> sqrt(5)
[1] 2.236068
> sqrt(c(1,2,3,4,5,6,7,8,9,10))
[1] 1.000000 1.414214 1.732051 2.000000 2.236068 2.449490 2.645751     [8] 2.828427 3.000000 3.162278
> # aggregating functions
> mean(c(1,2,3,4,5,6,7,8,9,10))
[1] 5.5
> median(c(1,2,3,4,5,6,7,8,9,10))
[1] 5.5

您可以从前面的示例中看到,例如 meanmediansqrt 这样的函数是内置的,并且可以在您启动 R 时随时使用,无需加载任何其他包或显式定义函数。

用户定义的函数

真正的力量在于能够根据您想要在数据上执行的不同操作和计算来定义自己的函数,并让 R 以您期望的方式执行这些函数。以下是一些示例:

square <- function(data){
 return (data²)
}
> square(5)
[1] 25
> square(c(1,2,3,4,5))
[1]  1  4  9 16 25
point <- function(xval, yval){
 return (c(x=xval,y=yval))
}
> p1 <- point(5,6)
> p2 <- point(2,3)
> 
> p1
x y 
5 6 
> p2
x y 
2 3

square which computes the square of a single number or even a vector of numbers using the same code. Functions such as point are useful to represent specific entities which represent points in the two-dimensional co-ordinate space. Now we will be looking at how to use the preceding functions together.

将函数作为参数传递

当您定义任何函数时,如果您打算在函数内部使用它们来执行一些复杂的计算,您也可以将其作为参数传递给函数。这减少了代码的复杂性和冗余。以下示例使用之前定义的 square 函数计算两点之间的欧几里得距离,该函数作为参数传递:

> # defining the function
euclidean.distance <- function(point1, point2, square.func){
 distance <- sqrt(
 as.integer(
 square.func(point1['x'] - point2['x'])
 ) +
 as.integer(
 square.func(point1['y'] - point2['y'])
 )
 )
 return (c(distance=distance))
}
> # executing the function, passing square as argument
> euclidean.distance(point1 = p1, point2 = p2, square.func = square)
distance 
4.242641 
> euclidean.distance(point1 = p2, point2 = p1, square.func = square)
distance 
4.242641 
> euclidean.distance(point1 = point(10, 3), point2 = point(-4, 8), square.func = square)
distance
14.86607

因此,您可以看到,使用函数,您可以定义一个特定的函数一次,并且根据需要执行多次。

控制代码流

本节涵盖了与控制代码执行相关的领域。使用特定的构造,如 if-elseswitch,您可以有条件地执行代码。例如,forwhilerepeat 以及 help 在执行相同代码多次时也称为循环。我们将在下一节中探讨所有这些构造。

使用 if、if-else 和 ifelse

有几种构造可以帮助我们在代码执行时进行条件判断。这在我们不希望按顺序依次执行一系列语句,而只想在满足或未满足特定条件时执行代码时特别有用。以下示例说明了相同的内容:

> num = 5
> if (num == 5){
+     cat('The number was 5')
+ }
The number was 5
> 
> num = 7
> 
> if (num == 5){
+     cat('The number was 5')
+ } else{
+     cat('The number was not 5')
+ }
The number was not 5
>
> if (num == 5){
+     cat('The number was 5')
+ } else if (num == 7){
+     cat('The number was 7')
+ } else{
+     cat('No match found')
+ }
The number was 7
> ifelse(num == 5, "Number was 5", "Number was not 5")
[1] "Number was not 5"

使用 switch

switch 函数在您必须将表达式或参数与多个条件匹配并且只有在存在特定匹配时才执行时特别有用。当使用 if-else 构造时,这会变得非常混乱,但使用 switch 函数则更加优雅,正如我们接下来将要看到的:

> switch(
+ "first",
+ first = "1st",
+ second = "2nd",
+ third = "3rd",
+ "No position"
+ )
[1] "1st"
> 
> switch(
+ "third",
+ first = "1st",
+ second = "2nd",
+ third = "3rd",
+ "No position"
+ )
[1] "3rd"
> # when no match, default statement executes
> switch(
+ "fifth",
+ first = "1st",
+ second = "2nd",
+ third = "3rd",
+ "No position"
+ )
[1] "No position"

循环

循环是在需要时重复执行代码段的一种极好方式。然而,对于处理大型数据集,向量化构造比循环更优化,但我们将在本章的后面看到这一点。现在,您应该记住,R 中有三种类型的循环,即 forwhilerepeat。我们将在以下示例中查看所有这些:

> # for loop
> for (i in 1:10){
+     cat(paste(i," "))
+ }
1  2  3  4  5  6  7  8  9  10 
> 
> sum = 0
> for (i in 1:10){
+     sum <- sum + i
+ }
> sum
[1] 55
> 
> # while loop
> count <- 1
> while (count <= 10){
+     cat(paste(count, " "))
+     count <- count + 1
+ }
1  2  3  4  5  6  7  8  9  10 
> 
> # repeat infinite loop 
> count = 1
> repeat{
+     cat(paste(count, " "))
+     if (count >= 10){
+         break  # break off from the infinite loop
+     }
+     count <- count + 1
+ }
1  2  3  4  5  6  7  8  9  10 

高级构造

当我们谈论在不使用循环的情况下对向量进行操作时,我们之前听到了 向量化 这个术语。虽然循环是遍历向量并执行计算的好方法,但当处理所谓的 大数据 时,它并不非常高效。在这种情况下,R 提供了一些高级构造,我们将在本节中查看这些构造。我们将涵盖以下函数:

  • lapply:遍历列表并对每个元素评估一个函数

  • sapplylapply 的简化版本

  • apply:在数组的边界或边缘上评估一个函数

  • tapply:在向量的子集上评估一个函数

  • mapplylapply 的多元版本

lapply 和 sapply

如我们之前提到的,lapply 函数接受一个列表和一个函数作为输入,并在列表的每个元素上评估该函数。如果输入列表不是一个列表,则在返回输出之前,使用 as.list 函数将其转换为列表。它比普通循环快得多,因为实际的循环是通过内部使用 C 语言代码来完成的。我们将在下面的代码片段中查看其实现和示例:

> # lapply function definition
> lapply
function (X, FUN, ...) 
{
 FUN <- match.fun(FUN)
 if (!is.vector(X) || is.object(X)) 
 X <- as.list(X)
 .Internal(lapply(X, FUN))
}
<bytecode: 0x00000000003e4f68>
<environment: namespace:base>
> # example
> nums <- list(l1=c(1,2,3,4,5,6,7,8,9,10), l2=1000:1020)
> lapply(nums, mean)

输出:

lapply 和 sapply

接下来是 sapply,它与 lapply 类似,但它在可能的情况下尝试简化结果。例如,如果最终结果中每个元素长度都是 1,则返回一个向量;如果结果中每个元素的长度相同但超过 1,则返回一个矩阵;如果它无法简化结果,则我们得到与 lapply 相同的结果。我们将在以下示例中展示同样的内容:

> data <- list(l1=1:10, l2=runif(10), l3=rnorm(10,2))
> data

输出:

lapply 和 sapply

> 
> lapply(data, mean)

输出:

lapply 和 sapply

> sapply(data, mean)

输出:

lapply 和 sapply

apply

apply 函数用于在数组的边缘或边界上评估一个函数;例如,在数组的行或列上应用聚合函数。rowSumsrowMeanscolSumscolMeans 函数也内部使用 apply,但它们在操作大型数组时更加优化和有用。我们将在以下示例中看到所有这些先前的构造:

> mat <- matrix(rnorm(20), nrow=5, ncol=4)
> mat

输出:

apply

> # row sums
> apply(mat, 1, sum)
[1]  0.79786959  0.53900665 -2.36486927 -1.28221227  0.06701519
> rowSums(mat)
[1]  0.79786959  0.53900665 -2.36486927 -1.28221227  0.06701519
> # row means
> apply(mat, 1, mean)
[1]  0.1994674  0.1347517 -0.5912173 -0.3205531  0.0167538
> rowMeans(mat)
[1]  0.1994674  0.1347517 -0.5912173 -0.3205531  0.0167538
>
> # col sums
> apply(mat, 2, sum)
[1] -0.6341087  0.3321890 -2.1345245  0.1932540
> colSums(mat)
[1] -0.6341087  0.3321890 -2.1345245  0.1932540
> apply(mat, 2, mean)
[1] -0.12682173  0.06643781 -0.42690489  0.03865079
> colMeans(mat)
[1] -0.12682173  0.06643781 -0.42690489  0.03865079
>
> # row quantiles
> apply(mat, 1, quantile, probs=c(0.25, 0.5, 0.75))

输出:

apply

因此,您可以看到如何在完全不使用循环的情况下轻松地应用各种统计函数。

tapply

函数 tapply 用于在向量的子集上评估一个函数。如果您熟悉使用关系数据库,这类似于在 SQL 中应用 GROUP BY 构造。我们将在以下示例中展示同样的内容:

> data <- c(1:10, rnorm(10,2), runif(10))
> data

输出:

tapply

> groups <- gl(3,10)
> groups
 [1] 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3
Levels: 1 2 3
> tapply(data, groups, mean)

输出:

tapply

> tapply(data, groups, mean, simplify = FALSE)

输出:

tapply

> tapply(data, groups, range)

输出:

tapply

mapply

mapply 函数是 lapply 的多元版本,用于在参数集上并行评估函数。一个简单的例子是,如果我们必须使用 rep 函数构建一个向量列表,我们必须多次编写它。然而,使用 mapply 我们可以以更优雅的方式实现相同的功能,如下所示:

> list(rep(1,4), rep(2,3), rep(3,2), rep(4,1))

输出:

mapply

> mapply(rep, 1:4, 4:1)

输出:

mapply

使用 R 的下一步

在我们深入机器学习之前,暂停片刻,深呼吸,思考一下你迄今为止学到了什么,这将很有用。这个关于 R 的快速而详细的复习将有助于你在接下来的章节中。然而,还有两件事我们必须快速了解。它们是如何在 R 中获取帮助以及如何在 R 中处理各种包。

获取帮助

到目前为止,你必须已经意识到 R 中有成千上万的函数和结构,不可能记住每个函数实际上做什么,你也不必这么做!R 提供了许多直观的方式来获取有关任何函数、包或数据结构的帮助。首先,你可以在 R 命令提示符中运行 help.start() 函数,这将启动一个手册浏览器。在这里,你可以获取有关 R 的详细信息,包括手册、参考和其他材料。以下命令显示了 help.start() 的内容,如命令后面的截图所示,你可以使用它来进一步导航并获得更多帮助:

> help.start()

如果没有发生任何事情,你应该自己打开 http://127.0.0.1:31850/doc/html/index.html

获取帮助

要获取 R 中任何特定函数或结构的帮助,如果你知道函数的名称,你可以使用帮助函数或与函数名称结合的 ? 操作符来获取帮助。例如,如果你想获取有关 apply 函数的帮助,只需键入 help("apply")?apply 以获取有关 apply 函数的详细信息。这种在 R 中获取帮助的简单机制提高了你的生产力,并使使用 R 变得愉快。通常,你可能不会完全记得你打算使用的函数的确切名称,但你可能对其名称有一个模糊的概念。R 也为此目的提供了一个帮助功能,你可以使用 help.search 函数或与函数名称结合的 ?? 操作符。例如,你可以使用 ??apply 来获取有关 apply 函数的更多信息。

处理包

CRAN(综合 R 存档网络)上,有数千个包含各种功能的包可用,这是一个托管所有这些包的仓库。要从 CRAN 下载任何包,你只需要运行install.packages函数,将包名作为参数传递,例如install.packages("caret")。一旦包下载并安装,你可以使用library函数将其加载到当前的 R 会话中。要加载caret包,只需键入library(caret),它应该可以立即用于使用。require函数具有类似的功能来加载特定的包,并且在类似的方式中特别用于函数内部,通过键入require(caret)来加载caret包。requirelibrary之间的唯一区别是,如果找不到特定的包,library将显示错误,而require将不会显示任何错误继续执行代码。然而,如果代码中有对该包的依赖调用,则你的代码肯定会抛出错误。

机器学习基础

既然你已经刷新了你对 R 的记忆,我们将讨论机器学习的基本概念,它是如何被使用的,以及机器学习内部的主要领域。本节旨在提供一个机器学习的概述,这将有助于为下一章的深入探讨铺平道路。

机器学习——它究竟意味着什么?

机器学习并没有一个明确的教科书定义,因为它是一个涵盖并借鉴了计算机科学中其他几个领域概念和技术的领域。它也被作为一门大学课程进行教授,并且最近获得了更多的关注,机器学习和数据科学以在线教育视频、课程和培训的形式被广泛采用。机器学习基本上是计算机科学、统计学和数学领域的元素交叉,它使用人工智能、模式检测、优化和学习理论的概念来开发算法和技术,这些算法和技术可以从数据中学习并做出预测,而无需进行明确的编程。

这里的学习指的是根据我们提供给计算机或机器的数据和算法,使它们变得智能的能力,以便它们可以从提供的数据中开始检测模式和洞察力。这种学习确保机器可以在不每次都进行显式编程的情况下,从输入的数据中检测模式。最初的数据或观察结果被输入到机器中,机器学习算法处理这些数据以生成一些输出,这些输出可以是预测、假设,甚至是某些数值结果。基于这个输出,可以对我们机器学习算法进行反馈机制以改进我们的结果。整个系统形成一个机器学习模型,可以直接应用于全新的数据或观察结果,从中获取结果,而无需再次编写任何单独的算法来处理这些数据。

机器学习——它在世界上是如何被使用的?

你可能想知道,究竟如何将某些算法或代码应用于现实世界。实际上,它们被广泛应用于不同领域的各种用例。以下是一些例子:

  • 零售:机器学习在零售和电子商务领域被广泛使用,每个商店都希望超越其竞争对手。

    • 定价分析:机器学习算法用于比较不同商店中商品的价格,以便商店以最具竞争力的价格销售商品。

    • 市场篮子分析:用于分析客户购物趋势,并推荐购买的产品,我们将在第三章,使用市场篮子分析预测客户购物趋势中详细讨论。

    • 推荐引擎:用于分析客户购买、评分和满意度,并向各种用户推荐产品。我们将在第四章,构建产品推荐系统中构建一些我们自己的推荐系统。

  • 广告:广告行业高度依赖机器学习来推广并向消费者展示正确的广告,以实现最大化的转化率。

    • 网站分析:分析网站流量

    • 客户流失分析:预测客户流失率

    • 广告点击率预测:用于预测广告对消费者的影响效果,以便他们点击广告购买相关产品

  • 医疗保健:机器学习算法在医疗保健领域被广泛用于更有效地治疗患者。

    • 疾病检测与预测:用于根据患者的病历检测和预测疾病发生的可能性。

    • 研究复杂结构,如人脑和 DNA,以更好地理解人体功能,从而实现更有效的治疗。

  • 检测和过滤垃圾邮件和消息。

  • 预测选举结果。

  • 欺诈检测和预测。我们将在第五章和第六章中尝试解决最关键的欺诈检测问题之一,即信用风险检测和预测 – 描述性分析信用风险检测和预测 – 预测分析

  • 在消息应用中的文本预测。

  • 自动驾驶汽车、飞机和其他车辆。

  • 天气、交通和犯罪活动预测和预报。

  • 情感和情绪分析,我们将在第八章中介绍,Twitter 数据情感分析

上述例子只是触及了机器学习真正能做什么的皮毛,到现在我相信你已经对机器学习被广泛应用的各个领域有了很好的了解。

机器学习算法的类型

如我们之前所讨论的,要让机器学习,你需要机器学习算法。机器学习算法是一类特殊的算法,它们在数据上工作并从中获取洞察。其想法是结合数据和算法构建一个模型,然后可以使用这个模型来处理新数据并得出可操作的见解。

每个机器学习算法都取决于它可以处理的数据类型和我们要解决的问题类型。你可能想学习几个算法,然后尝试将它们应用到你所面临的所有问题上。请记住,没有一种通用的机器学习算法可以适用于所有问题。机器学习算法的主要输入是数据,这些数据由特征组成,其中每个特征都可以描述为数据集的属性,例如如果我们处理的是与人类相关的数据,那么你的身高、体重等都可以是特征。机器学习算法可以分为两大类,即监督学习和无监督学习算法。

监督式机器学习算法

监督学习算法是机器学习算法家族的一个子集,主要用于预测建模。预测模型基本上是由机器学习算法和训练数据中的特征或属性构建的模型,这样我们就可以使用从输入数据中获得的其它值来预测一个值。监督学习算法试图模拟目标预测输出和输入特征之间的关系和依赖性,以便我们可以根据从先前数据集中学习到的关系预测新数据的输出值。主要的监督学习算法类型包括:

  • 分类算法:这些算法从具有特征和类别标签的训练数据中构建预测模型。这些预测模型反过来使用从训练数据中学习到的特征对新数据(之前未见过的数据)进行预测,以预测其类别标签。输出类别是离散的。分类算法的类型包括决策树、随机森林、支持向量机等,还有很多。我们将在第二章、让我们帮助机器学习、第六章、信用风险检测与预测 - 预测分析和第八章、推特数据情感分析中使用这些算法中的几个。

  • 回归算法:这些算法用于根据从数据中获得的某些输入特征预测输出值。为此,算法基于训练数据的特征和输出值构建一个模型,并使用此模型来预测新数据的值。在这种情况下,输出值是连续的,而不是离散的。回归算法的类型包括线性回归、多元回归、回归树和 lasso 回归等,还有很多。我们在第二章、让我们帮助机器学习中探讨了其中的一些。

无监督机器学习算法

无监督学习算法是主要用于模式检测和描述性建模的机器学习算法家族。描述性模型基本上是由无监督机器学习算法和与监督学习过程类似的输入数据特征构建的模型。然而,这里没有基于算法尝试建模关系的输出类别或标签。这些算法试图使用输入数据上的技术来挖掘规则、检测模式、总结和分组数据点,这些数据点有助于推导出有意义的见解并更好地向用户描述数据。由于我们没有特定的关系映射,我们只是试图从我们试图分析的数据中获得有用的见解和描述,因此这里没有特定的训练或测试数据概念。无监督学习算法的主要类型包括:

  • 聚类算法:这些算法的主要目标是仅使用从输入数据中提取的特征,而不使用任何其他外部信息,将输入数据点聚类或分组到不同的类别或类别中。与分类不同,聚类中的输出标签事先是未知的。构建聚类模型有不同的方法,例如使用均值、类中心、层次结构等。一些流行的聚类算法包括 k-means、k-medoids 和层次聚类。我们将在第二章、“让我们帮助机器学习”和第七章、“社交媒体分析 – 分析 Twitter 数据”中探讨一些聚类算法。

  • 关联规则学习算法:这些算法用于从数据集中挖掘和提取规则和模式。这些规则解释了不同变量和属性之间的关系,并描述了数据中出现的频繁项集和模式。这些规则反过来又帮助从任何企业或组织的庞大数据存储库中发现有用的见解。流行的算法包括 Apriori 和 FP Growth。我们将在第二章、“让我们帮助机器学习”和第三章、“使用市场篮子分析预测客户购物趋势”中使用一些这些算法。

R 中流行的机器学习包

在简要概述了机器学习基础和算法类型之后,你一定对如何使用 R 将这些算法应用于解决现实世界问题感到好奇。结果是,R 中有许多专门用于解决机器学习问题的包。这些包包含经过优化的算法,可以直接用于解决问题。我们将列出几个 R 中流行的机器学习包,以便你了解你可能需要的工具,并在后续章节中使用这些包时更加熟悉。根据使用和功能,以下 R 包在解决机器学习问题方面相当受欢迎:

  • caret: 这个包(简称分类和回归训练)包含用于构建预测模型的几个机器学习算法

  • randomForest: 这个包处理随机森林算法的分类和回归实现

  • rpart: 这个包专注于递归分割和决策树

  • glmnet: 这个包的主要重点是 lasso 和弹性网络正则化回归模型

  • e1071: 它涉及傅里叶变换、聚类、支持向量机以及许多其他监督和非监督算法

  • party: 它涉及递归分割

  • arules: 这个包用于关联规则学习算法

  • recommenderlab: 这是一个用于构建推荐引擎的库

  • nnet: 这个包使使用神经网络进行预测建模成为可能

  • h2o: 这是目前数据科学中最受欢迎的包之一,提供了包括梯度提升和深度学习在内的快速和可扩展的算法

除了上述库之外,还有许多其他与 R 中的机器学习相关的包。重要的是根据手头的数据和问题选择正确的算法和模型。

摘要

在本章中,我们简要地讨论了我们将要进入的机器学习和 R 的世界之旅。我们讨论了 R 的基础知识,并建立了 R 中使用的核心构造和数据结构的基础。然后,我们通过查看一些概念和算法以及它们在解决现实世界问题中的应用,深入了解了机器学习的世界。最后,我们快速浏览了一些 R 中最受欢迎的机器学习包,以便我们熟悉一些机器学习工具箱中的实用工具!

在下一章中,我们将深入探讨机器学习概念和算法,这将帮助我们让机器学习到一些东西!

第二章。让我们帮助机器学习

当你第一次听到机器学习这个词时,它听起来更像是一部科幻电影中的时髦词汇,而不是科技行业最新的趋势。和普通人谈论这个话题,他们的反应要么是对这个概念的一般好奇,要么是谨慎和恐惧,担心智能机器以某种终结者-天网的方式接管我们的世界。

我们生活在数字时代,时刻都被各种各样的信息包围。正如我们将在本章和接下来的章节中看到的,机器学习是一种喜欢数据的东西。事实上,这个领域最近的热潮和兴趣不仅是由计算技术的改进所推动,还由于每秒产生的数据量的指数级增长。最新的数据显示,每天大约有 2.5 亿亿字节的数据(即 2.5 后面跟着 18 个零)!

注意

有趣的事实:每分钟有超过 300 小时的视频数据上传到 YouTube

来源:www-01.ibm.com/software/data/bigdata/what-is-big-data.html

深吸一口气,环顾四周。你周围的一切都在不断生成各种类型的数据;你的手机、你的汽车、交通信号灯、GPS、恒温器、气象系统、社交网络,等等!到处都是数据,我们可以用它做各种有趣的事情,并帮助系统学习。好吧,虽然听起来很吸引人,但让我们开始我们的机器学习之旅。通过本章,我们将涵盖:

  • 理解机器学习

  • 机器学习中的算法及其应用

  • 算法系列:监督学习和无监督学习

理解机器学习

我们不是被教导计算机系统必须被编程来完成某些任务吗?它们可能在做事情时快上百万倍,但它们必须被编程。我们必须编写每一个步骤,然后这些系统才能工作并完成任务。那么,机器学习的概念本身不是一个非常矛盾的概念吗?

简单来说,机器学习是指一种教会系统学习执行某些任务的方法,比如学习一个函数。虽然听起来很简单,但它有点令人困惑,难以理解。之所以令人困惑,是因为我们对系统(特别是计算机系统)工作方式的看法和我们学习的方式这两个概念几乎不相交。它甚至更难以理解,因为学习虽然是人类的天赋能力,但很难用言语表达,更不用说教给系统了。

那么,什么是机器学习?在我们甚至尝试回答这个问题之前,我们需要理解在哲学层面上,它不仅仅是一种编程方式。机器学习包含了很多东西。

描述机器学习的方式有很多。继续从我们在上一章中提出的高级定义,让我们看看 1997 年汤姆·米切尔给出的定义:

"如果计算机程序就某个任务 T 和某些性能指标 P 而言,从经验 E 中学习,那么其性能 P,如 P 所测量的,随着经验 E 的提高而提高。"

注意

关于汤姆·米切尔教授的快速笔记

1951 年出生,他是美国计算机科学家,卡内基梅隆大学(CMU)的教授。他也是 CMU 机器学习系的主任。他在机器学习、人工智能和认知神经科学领域做出了贡献,因此广为人知。他是多个机构的成员,如人工智能协会。

现在让我们通过一个例子来理解这个简洁而有力的定义。假设我们想要构建一个预测天气的系统。对于当前示例,系统的任务(T)将是预测某个地方的天气。为了执行这个任务,它需要依赖于过去的天气信息。我们将它称为经验 E。它的性能(P)是通过它在任何给定一天预测天气的好坏来衡量的。因此,我们可以概括地说,如果一个系统通过利用过去的信息(或经验 E)在预测天气(或任务 T)方面变得更好(或提高其性能 P),那么它就成功地学会了如何预测天气(或任务 T)。

如前例所示,这个定义不仅帮助我们从工程角度理解机器学习,还为我们提供了量化这些术语的工具。这个定义帮助我们认识到,学习特定任务涉及以经验形式理解和处理数据。它还提到,如果一个计算机程序通过经验 E 学习,那么它的性能 P 会随着经验 E 的提高而提高,这与我们学习的方式非常相似。

机器学习算法

到目前为止,我们已经对机器学习有了抽象的理解。我们理解了机器学习的定义,即当计算机程序利用经验 E 中的数据来执行任务 T 时,如果其性能 P 随着它而提高,那么任务 T 可以被学习。我们还看到了机器学习与传统的编程范式不同的地方,因为我们不是编写每一个步骤,而是让程序形成对问题空间的理解,并帮助我们解决问题。看到这样的程序在我们面前工作,确实令人惊讶。

在我们学习机器学习概念的过程中,我们一直把这个神奇的计算机程序当作一个神秘的黑色盒子,它为我们学习和解决问题。现在是我们揭开它的神秘面纱,揭开盖子,看到这些神奇算法的全貌的时候了。

我们将从机器学习中一些最常用和广泛使用的算法开始,探讨它们的复杂性、用法,并在必要时涉及一些数学知识。通过本章,你将了解不同算法家族。这个列表绝对不是详尽的,尽管算法将被相当详细地解释,但每个算法的深入理论理解超出了本书的范围。有大量的材料以书籍、在线课程、博客等形式轻松可用。

感知器

这就像是机器学习宇宙中的 Hello World 算法。它可能是最容易理解和使用的一类,但绝对不比其他算法弱。

1958 年由弗兰克·罗森布拉特(Frank Rosenblatt)发表,感知器算法因其保证在可分数据集中找到分隔符而受到广泛关注。

感知器是一个函数(或更确切地说是一个简化的神经元)它接受一个实数向量作为输入,并生成一个实数作为输出。

从数学上讲,感知器可以表示为:

感知器

在这里,w1,…,wn 是权重,b 是称为偏置的常数,x1,…,xn 是输入,y 是函数 f 的输出,该函数被称为激活函数。

算法如下:

  1. 将权重向量 w 和偏置 b 初始化为小的随机数。

  2. 根据函数 f 和向量 x 计算输出向量 y

  3. 更新权重向量 w 和偏置 b 以抵消错误。

  4. 重复步骤 2 和 3,直到没有错误或错误低于某个阈值。

该算法试图通过使用称为训练数据集的标记数据集(训练数据集对应于前一小节中机器学习定义中的经验 E)来找到一个分隔符,将输入分为两类。算法首先随机分配权重向量 w 和偏置 b 的权重。然后根据函数 f 处理输入,并给出一个向量 y。然后,将生成的输出与训练数据集中的正确输出值进行比较,并相应地更新 wb。为了理解权重更新过程,让我们考虑一个点,比如 p1,其正确输出值为 +1。现在,假设如果感知器错误地将 p1 分类为 -1,它将更新权重 w 和偏置 b,以通过一个小量(移动受到学习率的限制,以防止突然跳跃)在 p1 的方向上移动感知器,以便正确分类它。当找到正确的分隔符或分类输入的错误低于某个用户定义的阈值时,算法停止。

现在,让我们通过一个小例子来看看算法的实际应用。

为了使算法工作,我们需要一个线性可分的数据集。让我们假设数据是由以下函数生成的:

感知器

根据前面的方程,正确的分隔符将给出如下:

感知器

使用 R 中的均匀分布数据生成输入向量x的方法如下:

#30 random numbers between -1 and 1 which are uniformly distributed
x1 <- runif(30,-1,1) 
x2 <- runif(30,-1,1)
#form the input vector x
x <- cbind(x1,x2)

现在我们有了数据,我们需要一个函数来将其分类为两个类别之一。

#helper function to calculate distance from hyperplane
calculate_distance = function(x,w,b) {
 sum(x*w) + b
}

#linear classifier
linear_classifier = function(x,w,b) {
distances =apply(x, 1, calculate_distance, w, b)
return(ifelse(distances < 0, -1, +1))
}

辅助函数calculate_distance计算每个点到分隔符的距离,而linear_classifier类将每个点分类为属于类-1或类+1

随后,感知器算法使用先前的分类器函数,通过训练数据集找到正确的分隔符。

#function to calculate 2nd norm
second_norm = function(x) {sqrt(sum(x * x))}

#perceptron training algorithm
perceptron = function(x, y, learning_rate=1) {

w = vector(length = ncol(x)) # initialize w
b = 0 # Initialize b
k = 0 # count iterations

#constant with value greater than distance of furthest point
R = max(apply(x, 1, second_norm)) 

incorrect = TRUE # flag to identify classifier

#initialize plot
plot(x,cex=0.2)

#loop till correct classifier is not found
while (incorrect ) {

 incorrect =FALSE 

 #classify with current weights
 yc <- linear_classifier(x,w,b)
 #Loop over each point in the input x
 for (i in 1:nrow(x)) {
 #update weights if point not classified correctly
 if (y[i] != yc[i]) {
 w <- w + learning_rate * y[i]*x[i,]
 b <- b + learning_rate * y[i]*R²
 k <- k+1

 #currect classifier's components
 # update plot after ever 5 iterations
 if(k%%5 == 0){
 intercept <- - b / w[[2]]
 slope <- - w[[1]] / w[[2]]
 #plot the classifier hyper plane
 abline(intercept,slope,col="red")
 #wait for user input
 cat ("Iteration # ",k,"\n")
 cat ("Press [enter] to continue")
 line <- readline()
 }
 incorrect =TRUE
 }
 }
}

s = second_norm(w)
#scale the classifier with unit vector
return(list(w=w/s,b=b/s,updates=k))
}

现在是时候训练感知器了!

#train the perceptron
p <- perceptron(x,Y)

图表将如下所示:

感知器

感知器正在寻找正确的分类器。正确的分类器用绿色显示

前面的图显示了感知器的训练状态。每个错误的分类器都用红线表示。如图所示,感知器在找到标记为绿色的正确分类器后结束。

如下所示,可以查看最终分隔符的放大视图:

#classify based on calculated 
y <- linear_classifier(x,p$w,p$b)

plot(x,cex=0.2)

#zoom into points near the separator and color code them
#marking data points as + which have y=1 and – for others
points(subset(x,Y==1),col="black",pch="+",cex=2)
points(subset(x,Y==-1),col="red",pch="-",cex=2)

# compute intercept on y axis of separator
# from w and b
intercept <- - p$b / p$w[[2]]

# compute slope of separator from w
slope <- - p$w[[1]] /p$ w[[2]]

# draw separating boundary
abline(intercept,slope,col="green")

图表如下所示:

感知器

感知器找到的正确分类器函数

算法系列

机器学习领域中存在大量的算法,每年都有新的算法被设计出来。在这个领域正在进行着大量的研究,因此算法列表不断增长。事实上,这些算法被使用得越多,对其改进的发现也越多。机器学习是一个工业和学术界紧密合作的空间。

但是,正如蜘蛛侠被告知的那样,“能力越大,责任越大”,读者也应该理解手头的责任。在如此多的算法可用的情况下,了解它们是什么以及它们适合哪里是必要的。一开始可能会感到压倒性和困惑,但这时将它们分类到系列中会很有帮助。

机器学习算法可以根据多种方式进行分类。最常见的方法是将它们分为监督学习算法和无监督学习算法。

监督学习算法

监督学习是指那些在预定义的数据集上训练的算法,这个数据集被称为训练数据集。通常,训练数据集是一个包含输入元素和期望输出元素或信号的二元组。一般来说,输入元素是一个向量。监督学习算法使用训练数据集来产生期望的函数。然后,产生的函数(或更确切地说,推断出的函数)被用来正确地映射新数据,更准确地说是测试数据。

一个学习良好的算法能够以合理的方式正确确定未见数据的结果。这引入了泛化和过拟合的概念。

简而言之,泛化是指算法根据(有限的)训练数据泛化所需函数,以正确处理未见数据的概念。过拟合是泛化的相反概念,其中算法推断一个函数,使其正好映射到训练数据集(包括噪声)。当算法学到的函数与新的/未见的数据进行比较时,这可能导致巨大的误差。

泛化和过拟合都围绕着输入数据中的随机误差或噪声。虽然泛化试图最小化噪声的影响,但过拟合则相反,通过拟合噪声来实现。

使用监督方法要解决的问题可以分为以下步骤:

  1. 准备训练数据:数据准备是所有机器学习算法最重要的步骤。由于监督学习使用标记的输入数据集(包含给定输入的对应输出的数据集),这一步骤变得更加重要。这些数据通常由人类专家或测量标记。

  2. 准备模型:模型是输入数据集和学到的模式的表示。模型表示受输入特征和学习算法本身等因素的影响。推断函数的准确性也取决于这种表示是如何形成的。

  3. 选择算法:根据要解决的问题和输入信息,然后选择一个算法来学习和解决问题。

  4. 检查和微调:这是一个迭代步骤,其中算法在输入数据集上运行,并对参数进行微调以达到期望的输出水平。然后,算法在测试数据集上测试以评估其性能和测量误差。

在监督学习中,有两个主要的子类别:

  1. 基于回归的机器学习:帮助回答诸如有多少?或有多少?等定量问题的学习算法。输出通常是连续值。更正式地说,这些算法根据训练数据和形成的模型预测未见/新数据的输出值。在这种情况下,输出值是连续的。线性回归、多元回归、回归树等是一些监督回归算法。

  2. 基于分类的机器学习:帮助回答客观问题或是与否预测的学习算法。例如,像这个组件是否损坏?或这个肿瘤是否会导致癌症?等问题的预测。更正式地说,这些算法根据训练数据和形成的模型预测未见或新数据的类别标签。支持向量机SVM)、决策树、随机森林等是一些常用的监督分类算法。

让我们详细看看一些监督学习算法。

线性回归

如前所述,回归帮助我们回答定量问题。回归的根源在统计学领域。研究人员使用线性关系来预测给定输入值X的输出值Y。这种线性关系称为线性回归或回归线。

数学上,线性回归表示为:

线性回归

其中,b[0]是截距,即直线与y轴相交的点。

b[1]是直线的斜率,即yx变化的改变量。

上述方程与表示直线的方式非常相似,因此得名线性回归。

现在,我们如何决定为我们的输入数据拟合哪条线,以便它能很好地预测未知数据?嗯,为此我们需要一个误差度量。可以有各种误差度量;最常用的是最小二乘法

在我们定义最小二乘法之前,我们首先需要理解残差这个术语。残差简单地是 Y 与拟合值的偏差。数学上:

线性回归

其中,ŷ[i]y的偏差值。

最小二乘法表明,当残差的平方和最小时,模型与数据的最佳拟合发生。

数学上:

线性回归

我们使用微积分来最小化残差平方和,并找到相应的系数。

现在我们已经理解了线性回归,让我们用一个现实世界的例子来看看它是如何应用的。

假设我们有一些与学校儿童身高和体重相关的数据。你内心的数据科学家突然开始思考这些儿童的体重和身高之间是否存在任何关系。正式来说,一个孩子的体重能否根据其给定的身高进行预测?

为了适应线性回归,第一步是理解数据并查看两个变量(体重身高)之间是否存在相关性。由于在这种情况下我们只处理两个维度,使用散点图可视化数据将帮助我们快速理解它。这还将使我们能够确定变量之间是否存在某种线性关系。

让我们先准备我们的数据,并在散点图上可视化它,同时显示相关系数。

#Height and weight vectors for 19 children

height <- c(69.1,56.4,65.3,62.8,63,57.3,59.8,62.5,62.5,59.0,51.3,64,56.4,66.5,72.2,65.0,67.0,57.6,66.6)

weight <- c(113,84,99,103,102,83,85,113,84,99,51,90,77,112,150,128,133,85,112)

plot(height,weight)
cor(height,weight)

输出:

[1] 0.8848454

散点图看起来是这样的:

线性回归

显示体重和身高维度数据点的图

上述散点图证明了我们对体重和身高之间具有线性关系的直觉是正确的。这可以通过相关函数进一步确认,它给出了0.88的值。

是时候为我们的数据集准备模型了!我们使用内置的lm或线性模型实用工具来找到系数b[0]b[1]

#Fitting the linear model

model <- lm(weight ~ height) # weight = slope*weight + intercept

#get the intercept(b0) and the slope(b1) values
model

输出看起来是这样的:

线性回归

你可以通过以下命令进一步实验,以找出lm实用工具计算出的更多细节。我们鼓励你继续尝试这些命令。

#check all attributes calculated by lm
attributes(model)

#getting only the intercept
model$coefficients[1] #or model$coefficients[[1]]

#getting only the slope
model$coefficients[2] #or model$coefficients[[2]]

#checking the residuals
residuals(model)

#predicting the weight for a given height, say 60 inches
model$coefficients[[2]]*50 + model$coefficients[[1]]

#detailed information about the model
summary(model)

作为最后一部分,让我们在我们的散点图上可视化回归线。

#plot data points
 plot(height,weight)

#draw the regression line
abline(model)

散点图看起来如下所示:

线性回归

带有计算回归线的散点图

因此,我们看到了如何通过几行代码识别两个变量之间的关系并做出预测。但我们还没有完成。在决定是否使用线性回归之前,读者必须理解一些注意事项。

如果且仅当给定输入的输出值可以预测时,线性回归才能使用:

  • 散点图形成线性模式

  • 它们之间的相关性是中等到强的(超过0.5-0.5

如果只满足上述两个条件中的一个,可能会导致错误的预测或完全无效的模型。例如,如果我们只检查相关性并发现它很强,而跳过了查看散点图的步骤,那么这可能会导致无效的预测,因为你可能试图拟合一条直线,而数据本身是遵循曲线形状的(注意,曲线数据集也可以有很高的相关性值,因此这个错误)。

重要的是要记住相关性不等于因果关系。简单来说,两个变量之间的相关性并不一定意味着一个导致另一个。可能存在一种情况,由于一个被称为共同变量的第三变量,因果关系是间接相关的。用来描述这个问题的最常见例子是鞋码与阅读能力之间的关系。从调查数据(如果有的话!)可以推断出,较大的鞋码与较高的阅读能力相关,但这显然并不意味着大脚会导致良好的阅读技能。可能更有趣的是注意到,年幼的孩子脚小,还没有被教过阅读。在这种情况下,这两个变量更准确地与年龄相关。

你现在应该对之前我们使用的体重与身高示例得出类似的结论。嗯,之前的例子也存在着类似的谬误,但它作为一个易于使用的场景。请随意四处看看,并建模那些不受此影响的案例。

线性回归在金融领域得到应用,用于投资风险的量化等。它也在经济学领域被广泛用于趋势线分析等。

除了线性回归之外,逻辑回归逐步回归多元自适应回归样条MARS)和其他一些监督回归学习算法。

K-最近邻(KNN)

从实现和理解的角度来看,K-Nearest Neighbors 或 KNN 算法是最简单的算法之一。它们是另一种监督学习算法,帮助我们分类数据。

KNN 可以用引用 “同类相吸”——柏拉图 来简单描述,即相似的事物很可能具有相似的性质。KNN 利用这一概念根据其与邻居的相似性来标记数据点。

形式上,KNN 可以描述为通过将未标记(或未见)的数据点分配给最相似标记数据点(或训练样本)的类别来对它们进行分类的过程。

KNN 是一种监督学习算法。因此,它从一个被分类为不同类别的训练数据集开始。然后,算法选择测试数据集中的每个数据点,并根据选择的相似度度量,确定其 k 个最近邻(其中 k 是预先指定的)。然后,数据点被分配给 k 个最近邻中的大多数的类别。

KNN 的秘诀在于相似度度量。我们有各种相似度度量可供选择。选择哪种度量取决于问题的复杂性、数据的类型等因素。欧几里得距离就是这样一种广泛使用的度量,它是两点之间最短的直接路线。数学上,它表示为:

K-Nearest Neighbors (KNN)

曼哈顿距离、余弦距离和闵可夫斯基距离是其他一些可以用于寻找最近邻的距离度量。

KNN 算法的下一个参数是 K-Nearest Neighbors 中的 kk 的值决定了 KNN 模型对测试数据的泛化程度。训练数据过拟合和欠拟合之间的平衡取决于 k 的值。经过稍微思考,很容易理解较大的 k 将最小化由噪声数据引起的方差的影响,但同时也将削弱数据中的小但重要的模式。这个问题被称为 偏差-方差权衡

k 的最佳值,尽管难以确定,但位于 k=1k=训练样本总数 的极端之间。一种常见的做法是将 k 的值设置为训练实例的平方根,通常在 3 到 10 之间。尽管这是一种常见的做法,但 k 的值取决于要学习概念复杂性和训练样本数量。

追求 KNN 算法的下一步是准备数据。用于准备输入向量的特征应该处于相似的尺度。这一步骤的合理性在于距离公式依赖于特征的测量方式。例如,如果某些特征与其他特征相比具有较大的值范围,那么距离测量将主要由这些测量值决定。将特征缩放到相似尺度的方法称为归一化。与距离测量一样,有各种归一化方法可用。其中一种方法是 min-max 归一化,数学上表示为:

K-Nearest Neighbors (KNN)

在我们开始示例以了解 KNN 之前,让我们概述执行 KNN 要执行的步骤:

  1. 收集数据并探索数据:我们需要收集与要学习概念相关的数据。我们还需要探索数据,以了解各种特征,了解其值范围,并确定类标签。

  2. 归一化数据:如前所述,KNN 对距离测量的依赖性使得我们非常重要的是要对数据进行归一化,以消除计算中的任何不一致性或偏差。

  3. 创建训练和测试数据集:由于学习一个概念并准备一个可以推广到未见数据可接受水平的模型非常重要,我们需要准备训练和测试数据集。尽管测试数据集已标记,但它用于确定模型的准确性和泛化所学习概念的能力。通常的做法是将输入样本分为三分之二和三分之一,分别用于训练和测试数据集。同样重要的是,这两个数据集应该是所有类标签和数据点的良好混合,也就是说,这两个数据集应该是完整数据的有代表性的子集。

  4. 训练模型:现在我们已经准备好了所有东西,我们可以使用训练数据集、测试数据集、标签和k的值来训练我们的模型,并为测试数据集中的数据点标记标签。

  5. 评估模型:最后一步是评估学习到的模式。在这个步骤中,我们确定算法相对于已知标签预测测试数据集类标签的效果如何。通常为此准备一个混淆矩阵。

现在,让我们看看 KNN 的实际应用。当前的问题是根据某些特征对不同的花卉种类进行分类。对于这个特定的例子,我们将使用 Iris 数据集。这个数据集是 R 默认安装的一部分。

收集和探索数据

第一步是收集和探索数据。让我们首先收集数据。

要检查您的系统是否有所需的数据库,只需输入名称:

iris
#this should print the contents of data set onto the console.

如果您没有可用的数据集,不用担心!您可以按照以下方式下载:

#skip these steps if you already have iris on your system
iris <- read.csv(url("http://archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data"), header = FALSE)

#assign proper headers
names(iris) <- c("Sepal.Length", "Sepal.Width", "Petal.Length", "Petal.Width", "Species")

现在我们有了数据,是时候探索和理解它了。为了探索数据集及其属性,我们使用以下命令:

#to view top few rows of data
head(iris)

输出:

收集和探索数据

#to view data types, sample values, categorical values, etc
str(iris)

输出:

收集和探索数据

#detailed view of the data set
summary(iris)

输出:

收集和探索数据

summary命令帮助我们更好地理解数据。它清楚地显示了不同的属性以及minmaxmedian和其他此类统计数据。这些有助于我们在接下来的步骤中,可能需要缩放或归一化数据或特征。

在第一步中,我们通常会对输入数据进行标记。由于我们当前的数据集已经标记,因此对于这个例子,我们可以跳过这一步。让我们通过视觉方式查看物种的分布情况。我们再次求助著名的散点图,但这次我们使用一个名为ggvis的包。

你可以安装ggvis如下:

install.packages("ggvis")

为了可视化所有 3 种物种的花瓣宽度和长度,我们使用以下代码片段:

#load the package
library(ggvis)

#plot the species
iris %>% ggvis(~Petal.Length, ~Petal.Width, fill = ~factor(Species)) %>% layer_points()

注意

%>%, to pass input data to ggvis and again uses the pipe operator to pass on the output to layer_points for final plotting. The ~ operator signifies to ggvis that Petal.Length is a variable in the input dataset (iris). Read more about ggvis at http://ggvis.rstudio.com/ggvis-basics.html.

收集和探索数据

前面的图表清楚地表明,对于Iris-setosa花,花瓣宽度和长度之间存在高度相关性,而对于其他两种物种则稍微低一些。

注意

尝试可视化萼片宽度和萼片长度,看看你是否能发现任何相关性。

数据归一化

下一步是将数据进行归一化,以便所有特征都在相同的尺度上。如数据探索步骤所示,所有属性的值或多或少都在一个可比较的范围内。但是,为了这个例子,让我们编写一个最小-最大归一化函数:

#normalization function

min_max_normalizer <- function(x)
{
num <- x - min(x) 
denom <- max(x) - min(x)
return (num/denom)
}

记住,归一化不会改变数据,它只是缩放它。因此,即使我们的数据不需要归一化,这样做也不会造成任何伤害。

注意

注意

在接下来的步骤中,我们将使用未归一化的数据以使输出更清晰。

#normalizing iris data set
normalized_iris <- as.data.frame(lapply(iris[1:4], min_max_normalizer))

#viewing normalized data
summary(normalized_iris)

以下是对归一化 DataFrame 的总结:

数据归一化

创建训练集和测试集

现在我们已经将数据归一化,我们可以将其分为训练集和测试集。我们将遵循通常的三分之二对三分之一的数据分割规则。如前所述,这两个数据集应该代表整个数据,因此我们需要选择合适的样本。我们将利用 R 的sample()函数来准备我们的样本。

#checking the data constituency
table(iris$Species)

输出:

创建训练集和测试集

#set seed for randomization
set.seed(1234)

# setting the training-test split to 67% and 33% respectively
random_samples <- sample(2, nrow(iris), replace=TRUE, prob=c(0.67, 0.33))

# training data set
iris.training <- iris[
random_samples ==1, 1:4] 

#training labels
iris.trainLabels <- iris[
random_samples ==1, 5]

# test data set
iris.test <- iris[
random_samples ==2, 1:4]

#testing labels
iris.testLabels <- iris[
random_samples ==2, 5]

从数据中学习/训练模型

一旦我们在训练集和测试集中准备好了数据,我们就可以进行下一步,使用 KNN 从数据中学习。R 中的 KNN 实现位于类库中。KNN 函数需要以下输入:

  • train:包含训练数据的 DataFrame。

  • test:包含测试数据的 DataFrame。

  • class:包含类别标签的向量。也称为因子向量。

  • k:k-最近邻算法中 k 的值。

对于当前情况,让我们假设k的值为3。奇数通常在打破平局时表现良好。KNN 的执行如下:

#setting library
library(class)

#executing knn for k=3
iris_model <- knn(train = iris.training, test = iris.test, cl = iris.trainLabels, k=3)

#summary of the model learnt
iris_model

输出:

从数据中学习/训练模型

快速浏览输出显示,除了 virginica 中的一个 versicolor 标签之外,所有内容都是正确的。尽管这个标签很容易找到,但还有更好的方法来评估模型。

评估模型

这将我们带到了最后一步,即评估模型。我们通过准备一个混淆矩阵或交叉表来完成这项工作,这有助于我们了解预测标签与测试数据的已知标签之间的关系。R 提供了一个名为CrossTable()的实用函数,该函数位于gmodels库中。让我们看看输出:

#setting library
library(gmodels)

#Preparing cross table
CrossTable(x = iris.testLabels, y = iris_model, prop.chisq=FALSE)

输出:

评估模型

从前面的输出中,我们可以得出结论,该模型将 virginica 的一个实例标记为 versicolor,而所有其他测试数据点都被正确标记。这也帮助我们推断出选择k=3确实是足够的。我们敦促读者尝试用不同的k值运行相同的示例,并观察结果的变化。

KNN 是一个简单而强大的算法,它不对底层数据分布做出假设,因此可以在特征和类别之间的关系复杂或难以理解的情况下使用。

从另一方面来看,KNN 是一个资源密集型的算法,因为它需要大量的内存来处理数据。对距离度量和缺失数据的依赖需要额外的处理,这也是该算法的另一个开销。

尽管有其局限性,KNN 在许多实际应用中都有使用,例如文本挖掘、预测心脏病发作、预测癌症等等。KNN 还应用于金融和农业领域。

决策树、随机森林和支持向量机是一些最流行和广泛使用的监督分类算法。

无监督学习算法

无监督学习是指算法自己学习概念(们)。既然我们已经熟悉了监督学习的概念,让我们利用我们的知识来理解无监督学习。

与需要标记输入训练数据集的监督学习算法不同,无监督学习算法的任务是在没有任何标记训练数据集的情况下,在数据中寻找关系和模式。这些算法处理输入数据以挖掘规则、检测模式、总结和分组数据点,这有助于得出有意义的见解,并向用户描述数据。在无监督学习算法的情况下,不存在训练和测试数据集的概念。相反,如前所述,输入数据被分析并用于推导模式和关系。

与监督学习类似,无监督学习算法也可以分为两大类:

  • 基于关联规则的机器学习:这些算法挖掘输入数据以识别模式和规则。这些规则解释数据集中变量之间的有趣关系,以描述频繁项集和数据中出现的模式。这些规则反过来又帮助从任何企业或组织的庞大数据存储库中发现有用的见解。流行的算法包括 Apriori 和 FP-Growth。

  • 基于聚类的机器学习:与基于监督学习的分类算法类似,这些算法的主要目标是仅使用从输入数据中提取的特征,将输入数据点聚类或分组到不同的类别或类别中,而不需要其他外部信息。与分类不同,聚类中事先并不知道输出标签。一些流行的聚类算法包括 k-means、k-medoids 和层次聚类。

让我们看看一些无监督学习算法。

Apriori 算法

这个震惊世界的算法由 Agarwal 和 Srikant 于 1993 年提出。该算法旨在处理事务数据,其中每个事务是一组物品或项集。简而言之,该算法识别出数据集中至少有 C 个事务的子集的项集。

正式地,设 为一个物品集合,D 为一个事务集合,其中每个事务 T 的一个子集。数学上:

Apriori 算法

然后,一个关联规则是形式为 X → Y 的蕴涵,其中事务 T 包含 的一个子集 X,并且:

Apriori 算法

如果在事务集合 D 中,包含 Xc% 事务也包含 Y,则蕴涵 X → Y 在事务集合 D 中成立,具有置信度因子 c。如果 s% 的事务在 D 中包含 X U Y,则关联规则 X → Y 被说成具有支持度因子 s。因此,给定一个事务集合 D,识别关联规则的任务意味着生成所有具有大于用户定义的阈值(称为 minsup,即最小支持度阈值)和 minconf(即最小置信度阈值)的置信度和支持度的规则。

广义上,该算法分为两个步骤。第一步是识别出现次数超过预定义阈值的项集。这样的项集被称为频繁项集。第二步是从识别出的频繁项集中生成满足最小置信度和支持度约束的关联规则。

可以使用以下伪代码更好地解释这两个相同的步骤:

Apriori 算法

现在我们来看看算法的实际应用。所考虑的数据集是 UCI 机器学习仓库的Adult数据集。该数据集包含人口普查数据,具有性别、年龄、婚姻状况、国籍和职业等属性,以及工作类别、收入等经济属性。我们将使用这个数据集来识别人口普查信息与个人收入之间是否存在关联规则。

Apriori 算法存在于arules库中,所考虑的数据集命名为Adult。它也包含在默认的 R 安装中。

# setting the apriori library
library(arules)

# loading data
data("Adult");

是时候探索我们的数据集并查看一些样本记录了:

# summary of data set
summary(Adult);

# Sample 5 records
inspect(Adult[0:5]);

我们了解到数据集包含一些 48k 笔交易,有 115 列。我们还获得了关于项目集大小分布的信息。inspect函数让我们窥视样本交易以及每列所持有的值。

现在,让我们建立一些关系:

# executing apriori with support=50% confidence =80%
rules <- apriori(Adult, parameter=list(support=0.5, confidence=0.8,target="rules"));

# view a summary
summary(rules);

#view top 3 rules
as(head(sort(rules, by = c("confidence", "support")), n=3), "data.frame")

Apriori 算法使用Adult数据集作为输入,以识别交易数据中的规则和模式。通过查看摘要,我们可以看到算法成功识别了满足支持度和置信度约束的84条规则,分别为50%80%。现在我们已经识别了规则,让我们看看它们是什么:

Apriori 算法

规则的形式为X→ Y,其中Xlhs或左侧,Yrhs或右侧。前面的图像还显示了相应的置信度和支持度值。从输出中我们可以推断出,如果人们全职工作,那么他们面临资本损失的机会几乎为零(置信度因子 95.8%)。另一条规则帮助我们推断,为私营雇主工作的人面临资本损失的机会也几乎为零。这样的规则可以用于制定社会福利、经济改革等政策或方案。

除了 Apriori 之外,还有其他关联规则挖掘算法,如 FP Growth、ECLAT 等,这些算法多年来被用于各种应用。

K-Means

在无监督聚类算法的世界中,最简单且最广泛使用的算法是 K-Means。正如我们最近所看到的,无监督学习算法在没有任何先前的标签或训练的情况下处理输入数据,以找出模式和关系。特别是聚类算法帮助我们聚类或划分数据点。

根据定义,聚类是指将对象分组到一组中,使得该组内的元素彼此之间比其他组中的元素更相似。K-Means 以无监督的方式执行同样的操作。

从数学上讲,给定一个包含 n 个观察值的集合 {x1,x2,…,xn},其中每个观察值是一个 d 维向量,算法试图通过最小化一个目标函数将这些 n 个观察值划分为 k (≤ n) 个集合。

与其他算法一样,可以有不同目标函数。为了简单起见,我们将使用最广泛使用的函数,称为簇内平方和WCSS 函数

K-Means

这里μ[i]是分区S[i]中点的均值。

该算法遵循一个简单的两步迭代过程,其中第一步称为分配步骤,随后是更新步骤。

  • 通过设置k个分区的均值来初始化:m1,m2…mk

  • 直到均值不再变化或变化低于某个阈值:

    1. 分配步骤:将每个观测值分配给一个使簇内平方和最小的分区,即分配给一个均值最接近观测值的分区。

    2. 更新步骤:对于i1 到 k之间,根据该分区中的所有观测值更新每个均值mi

该算法可以使用不同的初始化方法。最常见的是 Forgy 和随机分区方法。我鼓励你阅读更多关于这些内容。此外,除了输入数据集,算法还需要k的值,即要形成的聚类数量。最佳值可能取决于各种因素,通常根据用例来决定。

让我们看看算法的实际应用。

我们将再次使用 Iris 花卉数据集,我们之前已经用它来为 KNN 算法。对于 KNN,我们已经有物种标签,然后尝试在测试数据集中学习并分类数据点到正确的类别。

使用 K-Means,我们同样旨在实现相同的数据分区,但没有任何标记的训练数据集(或监督)。

# prepare a copy of iris data set
kmean_iris <- iris

#Erase/ Nullify species labels
kmean_iris$Species <- NULL

#apply k-means with k=3
(clusters <- kmeans(kmean_iris, 3))

现在我们已经得到了k-means的输出,让我们看看它如何将各种物种进行分区。记住,k-means没有分区标签,只是将数据点分组。

# comparing cluster labels with actual iris  species labels.
table(iris$Species, clusters$cluster)

输出

K-Means

输出显示,setosa 物种与聚类标签 2 匹配,versicolor 与标签 3 匹配,等等。从视觉上看,很容易看出数据点是如何分组的:

# plot the clustered points along sepal length and width
plot(kmean_iris[c("Sepal.Length", "Sepal.Width")], col=clusters$cluster,pch = c(15,16,17)[as.numeric(clusters$cluster)])

points(clusters$centers[,c("Sepal.Length", "Sepal.Width")], col=1:3, pch=8, cex=2)

K-Means

K-Means 在计算机图形学中的颜色量化等领域得到广泛应用;它与其他算法结合使用,用于自然语言处理、计算机视觉等。

k-means有不同变体(R 本身提供了三种不同的变体)。除了 k-means,其他无监督聚类算法还包括 k-medoids、层次聚类等。

摘要

通过本章,我们正式定义了机器学习的概念。我们讨论了机器学习算法实际上是如何学习一个概念的。我们还触及了各种其他概念,如泛化、过拟合、训练、测试、频繁项集等。我们还了解了机器学习算法的家族。我们探讨了不同的机器学习算法,以了解其背后的魔法以及它们的应用领域。

拥有这些知识,我们准备好解决一些现实世界的问题并拯救世界。

接下来的几章将基于本章的概念来解决具体问题和用例。准备好迎接一些行动吧!

第三章. 使用市场篮子分析预测客户购物趋势

在上一章第二章之后,“让我们帮助机器学习”,你现在知道如何让机器从观察和数据点中学习,以便它们可以发现有趣的模式、趋势并进行预测。在本章中,我们将处理零售商、商店和电子商务市场今天面临的一个复杂问题。随着现代技术和创新的兴起,购物已经变成了一种相对愉快和享受的体验,我们可以在家中舒适地享受这种体验,甚至无需冒险去实体店,只需使用提供购物功能的网络或专用应用程序。由于有大量的零售商、商店、市场和服务商,竞争相当激烈,为了吸引顾客,他们必须使用他们能从消费者那里收集到的关于他们个人特征和购物模式的所有数据,并使用机器学习技术尝试使购物体验尽可能个性化,基于每个顾客。

你可能想知道机器学习如何帮助为每个用户个性化购物体验!答案在于两个方面:数据和算法。通过结合这两者,零售商能够找出消费者购买的最热门商品,客户的喜好和厌恶,销售上升和下降的峰值时间,人们倾向于购买的产品趋势组合,以及其他零售商提供的相同产品的产品评论和价格。零售商有自己的数据科学团队,他们汇总这些数据并应用各种机器学习算法,这些算法用于分析趋势产品并构建推荐引擎,预测客户最有可能购买的产品,并根据他们的兴趣和购物历史向客户提供建议。

在本章中,我们将重点关注基于产品的推荐,其中算法专注于客户购物交易数据,我们观察到客户购买的产品组合的常见模式,以检测和预测客户最有可能购买的产品以及他们过去购买的产品。本章我们将重点关注以下技术:

  • 产品条件矩阵评估

  • 频繁项集生成

  • 关联规则挖掘

然而,使用关联规则和模式挖掘进行趋势分析有其局限性。它们不能根据客户的兴趣、他们购买和评价的产品等属性为每位客户提供更加个性化的购物体验。我们将在下一章中探讨这一点,我们将专注于用户基于协同过滤等算法,这些算法在构建推荐引擎时既考虑基于产品的特征,也考虑基于用户的特点。

最有趣的是,所有零售商和电子商务平台,例如 Staples、Macy's、Target、Amazon、Flipkart、Alibaba、Rakuten、Lazada 以及许多其他公司,都有自己的数据科学团队,这些团队解决包括我们之前讨论过的问题在内的各种问题。他们利用从客户购物交易、产品库存、配送、服务等级协议(SLAs)、评论、广告、点击率、跳出率、定价数据以及许多其他来源生成的大量数据。他们处理这些数据,并将其输入到基于机器学习的算法引擎中,以生成数据驱动的洞察力,从而提高业务的销售和利润。现在这无疑是市场上目前非常热门的一个领域。现在让我们进一步探讨一些帮助他们在做出如此伟大的数据驱动决策时使用的机器学习技术和算法!

检测和预测趋势

在本节中,我们将讨论我们所说的“趋势”究竟是什么,以及零售商如何检测和预测这些趋势。基本上,在零售环境中,趋势可以被定义为在一定时期内发生的一种特定模式或行为。这可能涉及在非常短的时间内售罄的产品或产品组合,甚至相反的情况。一个简单的例子就是一款热销智能手机在甚至上架到任何电子商务平台之前就被预订并售罄,或者像经典啤酒和尿布组合这样的产品组合,这在客户的购物篮或购物车中经常被找到!

我们如何甚至开始分析购物车或开始检测和预测购物趋势呢?就像我之前提到的,我们可以通过结合正确的数据和算法来实现这一点。假设我们领导着一个大型零售连锁店。首先,我们必须跟踪我们商店和网站上发生的每一笔交易。我们需要收集与购买的商品、缺货、一起购买的商品组合以及客户交易相关的数据点,作为开始。

一旦我们有了这些数据,我们就可以开始处理、归一化和汇总这些数据到机器可读的格式,这些格式可以轻松操作并输入到机器学习算法中,以基于购物趋势进行产品推荐。我们可以通过使用我们在第一章,“使用 R 和机器学习入门”中学到的合适的数据结构和构造来实现这一点。有几个机器学习算法可以帮助我们分析购物交易数据,并根据购物趋势推荐产品。这些算法主要遵循的范式通常被称为市场篮子分析。有趣的是,这些算法使用统计和机器学习概念,如概率、支持度、置信度、提升度、模式检测等,来确定哪些商品经常一起购买,这有助于我们分析购物交易、检测和预测趋势。这最终帮助我们为客户做出产品推荐,如果我们经营着一家零售连锁店,还可以明智地做出商业决策!请注意,在这两个算法中,我们将使用的数据仅基于纯购物交易数据。

在我们开始构建算法来分析购物车和交易之前,让我们首先看看市场篮子分析实际上意味着什么以及与之相关的概念。这将在我们稍后使用这些概念中的某些来实现机器学习算法以解决现实世界问题时派上用场。

市场篮子分析

市场篮子分析包括一些建模技术,这些技术通常由零售商和电子商务市场用来分析购物车和交易,以找出顾客购买最多的是什么,他们购买的商品类型,特定商品销售最旺盛的旺季是什么,等等。在本章中,我们将关注基于商品的交易模式,以检测和预测人们购买的商品以及他们最有可能购买的商品。让我们首先看看市场篮子分析的形式定义,然后我们将探讨与之相关的核心概念、指标和技术。最后,我们将总结如何实际使用这些结果来做出数据驱动的决策。

市场篮子分析实际上意味着什么?

市场篮子分析通常包括基于这样一个简单原则的几种建模技术:在购物时,如果你购买一组特定的商品(在机器学习术语中称为项集),你很可能还会购买与该项集相关的其他特定商品。我们分析人类的购物模式,并应用统计技术来生成频繁项集。这些项集包含人们最有可能一起购买的商品组合,基于过去的购物历史。

一个简单的项集例子就是人们在市场上经常同时购买啤酒和尿布。这个项集可以表示为 { beer, diapers }。频繁项集是指比通常发生频率更高的项集,它由一个称为支持度的度量来指定,我们稍后会讨论这个度量。因此,从先前的例子中,你可以得出结论,如果我买了啤酒,我也很可能会买 diapers,并推荐这个产品给我。我们还可以通过分析购物购买来在这些项集之上建立项关联规则。一个关联规则的例子可以用项集表示,记作 { beer, diapers } -> { milk },这表示如果我同时购买啤酒和尿布,我很可能还会购买牛奶!

核心概念和定义

现在你已经知道了市场篮子分析实际上做什么,让我们来看看在算法和技术中广泛使用的定义和概念。

事务数据集指的是记录客户每日/每周购物交易的数据库或数据集,包括客户一起购买的各种商品。我们将举一个例子,这个例子我们将在本章的算法中再次使用。考虑以下数据集,你也可以从本章的 shopping_transaction_log.csv 文件中获取这个数据集。数据如下图所示:

核心概念和定义

前面的数据集中的每个单元格也被定义为项。项也用符号 In 表示,其中 n 表示第 n 个项号,例子在正式定义和构建算法伪代码或手动进行一些计算时用花括号括起来。例如,单元格组合 (1, A) 表示项 I1,其值表示为 { beer }

项集被定义为在任意购物交易中一起购买的项的集合或组。因此,这些项根据交易被认为是共同出现的。我们将项集表示为 ISn,其中 n 表示第 n 个项集号。项集值将被括在花括号中。前面数据集中的每一行表示一个特定的交易,项的组合形成项集。项集 IS1 表示为 { beer, diapers, bread }

关联规则或简称规则是具有左侧LHS)和右侧RHS)的陈述,表明如果我们购买左侧的物品,我们很可能也会对购买右侧的物品感兴趣。这表明项集相互关联。它们表示为 ISx → ISy,这意味着如果我的购物车中有项集 x,我也会对购买与之一起的项集 y 感兴趣。一个例子规则可以是 { beer } → { diapers },这表明如果我的购物车中有啤酒,我也有可能购买尿布!我们现在将看到一些指标,这些指标决定了如何衡量频繁项集和关联规则的力量。

项集的频率基本上是指特定项集在所有事务列表中出现的次数。请注意,项集可以是事务中较大项集的子集,仍然可以计数,因为子集表示包含特定物品集合的项集与其他产品一起被购买。我们可以用 f(ISn) 来表示它,其中 ISn 是特定的项集,函数 f( ) 给出该项集在整个基于事务的数据集中的频率。以我们之前的示例数据集为例,f(IS{beer, diapers}) 是六,这表明 IS{beer, diapers} 在我们的数据集中的所有事务数据中总共被购买了六次。

项集的支持度定义为我们的事务数据集中包含该特定项集的事务的分数。基本上,这意味着该项集被购买次数除以数据集中的事务总数。它可以表示为 核心概念和定义,其中 S( ) 表示项集 ISn 的支持度。以我们之前的例子为例,S(IS{beer, diapers})核心概念和定义,这给出了 66.67% 的支持度。关联规则的支持度类似,可以表示为 核心概念和定义,其中我们使用交集运算符来查看事务数据集中两个项集同时出现的频率。我们之前定义的规则 S(IS{beer} → IS{diapers}) 的支持度再次是 核心概念和定义66.67%,因为啤酒和尿布组合的项集总共出现了六次,正如我们之前看到的。在评估关联规则或频繁项集的结果时,支持度越高,越好。支持度更多地是关于衡量规则检测过去交易中已经发生的事情的质量。

关联规则的置信度定义为对于包含规则左侧项集的新事务,该事务也包含规则右侧项集的概率或可能性。一个规则的置信度可以表示为核心概念和定义,其中C( )表示规则的置信度。请注意,由于支持率的计算涉及将项集频率除以分母中的事务总数,前面方程的右侧最终简化为获取分子和分母中项集的频率。因此,我们得到核心概念和定义作为获取置信度的简化公式。我们之前规则的置信度C(IS{beer} → IS{diapers})核心概念和定义100%,这意味着如果我的购物篮里有啤酒,购买尿布的概率是百分之百!这相当高,如果你回到数据集,你可以看到这是真的,因为对于涉及啤酒的每一笔交易,我们都可以看到与之相关的尿布。因此,你可以看到,进行预测和推荐并不是火箭科学,只是基于数据应用简单的数学和统计方法。记住,置信度更多地是关于检测基于过去交易数据预测未来可能发生的事情的规则质量。

关联规则的提升度定义为左侧(LHS)和右侧(RHS)两个项集组合的支持率之和与每个项集支持率乘积的比值。一个规则的提升度可以表示为核心概念和定义,其中L( )表示规则的提升度。对于我们的示例规则L(IS{beer} → IS{diapers})核心概念和定义评估结果为核心概念和定义,得到值为1.125,这是一个相当不错的值!一般来说,规则的提升度是衡量规则质量的一个指标。如果提升度> 1,则表示左侧项集的存在导致客户购买右侧项集的概率增加。这是确定项集关联和哪些商品影响人们购买其他商品的一种非常重要的方式,因为如果提升度的值为= 1,则表示左侧和右侧的项集是独立的,购买一个项集不会影响客户购买另一个项集。如果提升度< 1,则表示如果客户在左侧有一个项集,那么购买右侧项集的概率相对较低。

用于分析的技术

如果你被上一节中所有的数学信息压倒了,那就放松一下,深呼吸!你不需要记住所有东西,因为大多数时候,算法会为你计算一切!你需要擅长的是正确使用这些技术和算法,并解释结果以筛选出必要和有用的信息。前面提到的概念将在你开始实施和应用技术时有所帮助,我们将在本节中简要描述这些技术。我们将主要讨论三种技术,我们将在本章中探讨这些技术。

产品应急矩阵的评估是最简单的起点,它更多的是一种全局趋势捕捉机制,并在应急矩阵中显示一起购买的最顶级产品。我们稍后将使用的 R 包arules有一个名为crossTable的不错函数,它有助于将成对项目的联合发生情况交叉制表到应急矩阵中。我们将使用这个矩阵来预测客户最有可能与矩阵中的某些其他产品一起购买的产品。

频繁项集生成从产品应急矩阵的停止点开始,因为它有一个严重的限制,即在任何时间点都无法处理成对的产品。因此,为了进入可以包含任意数量产品的项集并从中检测模式,我们将使用机器学习构建自己的频繁项集生成器!使用这个工具,我们将能够获取具有特定支持值的频繁项集,这些项集表示可能一起购买的项目集合,从而为客户推荐产品的基础。

最后,我们将使用神奇的 Apriori 算法实现关联规则挖掘,该算法将频繁项集作为其规则生成过程的一部分。你已经在第二章中看到了这个演示,让我们帮助机器学习。然而,这次我们将使用其全部功能来查看产品项集之间的关联规则,使用我们之前讨论的指标来评估规则的质量,并使用这些规则对购物交易中的产品进行趋势预测和推荐。

基于数据的决策

你现在已经知道了市场篮子分析是什么,用于它的技术有哪些,以及它们能给我们带来什么结果。记住,市场篮子分析的结果是一组在交易中频繁共同出现的商品或产品。这可能是因为强大的支持度、置信度和提升度增强了其关联性,顾客倾向于购买它们,或者也可能是零售商将商品放置在商店或网站中一起或并排。然而,务必记住,强大的关联性并不总是偶然发生的,这正是零售商一直在试图通过我们之前讨论的技术来找出以提升销售额。

以下是一些基于市场篮子分析结果,零售商通常倾向于采取的一些关键数据驱动决策:

  • 应将包含如尿布和啤酒这样的产品对的频繁项集通常放置在商店中并排,这将使顾客容易访问,并且他们倾向于购买更多。

  • 应将包含大量不同商品或产品计数的频繁项集放置在特定类别或主题中,例如特殊的杂货组合或婴儿产品。对整个项集提供的折扣会吸引更多顾客。

  • 来自频繁项集或关联矩阵的具有长列表的商品或产品项集的关联规则可以显示为产品建议和推荐,当顾客浏览购物或电子商务网站时,在特定于项集的产品页面上。应注意,这些规则的提升度至少应大于 1,就像我们之前讨论的那样。

  • 可以基于市场篮子分析的结果构建推荐系统、定向广告和营销等。

如果在正确的时间和地点做出这些决策,将极大地帮助零售商提升销售额和获得良好的利润。

现在我们已经对市场篮子分析实际上做什么以及它是如何工作的有了坚实的理解,我们将从构建第一个技术的一个简单算法开始,即使用超市中购买的热门产品的产品关联矩阵进行产品推荐,然后继续构建更复杂的分析器和推荐器,利用 R 语言的强大机器学习功能。

评估产品关联矩阵

我们在这里将做几件事情。首先,我们将通过使用基于产品对购买频率的产品关联矩阵,分析一个属于超市的小型玩具数据集。然后我们将使用另一个数据集,通过支持度、提升度等其他指标构建关联矩阵。

我们第一个矩阵的数据包括超市销售的六种最受欢迎的产品,以及每种产品单独销售和与其他产品组合销售的次数。我们以csv文件的形式拥有这些数据,如图所示:

评估产品列联表

为了分析这些数据,我们首先需要了解它描述了什么。基本上,每个单元格的值表示该产品组合被售出的次数。因此,单元格组合 (1, A) 表示产品组合 (牛奶, 牛奶),这基本上是牛奶被购买的次数。另一个例子是单元格组合 (4, C),它与单元格组合 (3, D) 相似,表示购买黄油时面包被购买的次数。这通常也被称为列联表,在我们的案例中,它是一个产品列联表,因为它处理产品数据。让我们遵循我们的标准机器学习流程,获取数据,分析它,在我们的算法上运行它,并获取预期的结果。

获取数据

top_supermarket_transactions.csv file in the same directory from which you run the following code snippet, which is also available in the file named ch3_product contingency matrix.R along with this book.
> # reading in the dataset
> data <- read.csv("supermarket_transactions.csv")
> 
> # assigning row names to be same as column names
> # to build the contingency matrix
> row.names(data) <- data[[1]]
> data <- subset(data, select = c(-1))
>
> ## viewing the contingency matrix
> cat("Products Transactions Contingency Matrix")
Products Transactions Contingency Matrix
> data

输出:

获取数据

分析并可视化数据

在这里,我们将对数据集进行一些探索性分析,看看数据告诉我们什么样的故事。为此,我们首先将查看以下代码片段中与购买牛奶和面包相关的交易:

> ## Analyzing and visualizing the data
> # Frequency of products bought with milk
> data['milk', ]
 milk bread butter beer wine diapers
milk 10000  8758   5241  300  215     753
> 
> # Sorting to get top products bought with milk
> sort(data['milk', ], decreasing = TRUE)
 milk bread butter diapers beer wine
milk 10000  8758   5241     753  300  215
> 
> # Frequency of products bought with bread
> data['bread', ]
 milk bread butter beer wine diapers
bread 8758  9562   8865  427  322     353
> 
> # Sorting to get top products bought with bread
> sort(data['bread', ], decreasing = TRUE)
 bread butter milk beer diapers wine
bread  9562   8865 8758  427     353  322

因此,你可以看到,仅仅通过排序数据列,我们就能看到与面包或牛奶一起购买的顶级产品。在从矩阵中推荐顶级产品时,如果该产品已经在购物车中,我们将从推荐列表中删除该产品,因为,如果我买了面包,向我推荐面包就没有意义了。现在,我们将使用 mosaic 图可视化整个数据集。请注意,购买频率非常高的产品组合将具有高频率值,并在 mosaic 图中用显著区域表示。

> # Visualizing the data
> mosaicplot(as.matrix(data), 
+            color=TRUE, 
+            title(main="Products Contingency Mosaic Plot"),
+            las=2
+            )

代码生成了以下 mosaic 图,我们使用颜色参数应用渐变,并使用las参数指定轴标签与轴成直角,以制作更干净的图形。

分析并可视化数据

从前面的图中你可以注意到,现在很容易看出哪些产品与其他产品一起被大量购买。忽略相同的产品行和列值,我们可以轻松推断出像啤酒和尿布这样的产品组合被频繁购买!

注意

关于我们啤酒-尿布组合的背景故事实际上是在沃尔玛分析客户交易数据时发现的,当时他们发现,在周五,年轻的美国父亲们倾向于一起购买啤酒和尿布。他们会与朋友们庆祝周末,但作为父亲,他们也需要履行照顾孩子需求的基本职责。实际上,沃尔玛将啤酒和尿布并排放置在商店里,其销量显著上升!这正是分析学和机器学习的力量,它使我们能够发现未知和意外的模式。

全球推荐

现在,我们将根据客户购物车中选择的商品推荐产品。请注意,我们将其称为全球推荐,因为这些产品推荐既不是基于关联规则或频繁项集,这些是我们将在之后探讨的。它们纯粹是基于产品对购买计数的全球列联表。以下代码片段使我们能够从矩阵中为每个项目推荐前两个建议的产品:

## Global Recommendations
cat("Recommendations based on global products contingency matrix")
items <- names(data)
for (item in items){
 cat(paste("Top 2 recommended items to buy with", item, "are: "))
 item.data <- subset(data[item,], select=names(data)[!names(data) %in% item])
 cat(names(item.data[order(item.data, decreasing = TRUE)][c(1,2)]))
 cat("\n")
}

这给我们以下输出:

Top 2 recommended items to buy with milk are: bread butter
Top 2 recommended items to buy with bread are: butter milk
Top 2 recommended items to buy with butter are: bread milk
Top 2 recommended items to buy with beer are: wine diapers
Top 2 recommended items to buy with wine are: beer butter
Top 2 recommended items to buy with diapers are: beer milk

因此,你可以看到,基于从列联表中的产品对购买情况,我们得到了人们倾向于购买的前两个产品,这些产品是基于矩阵中捕捉到的全球趋势。现在我们将探讨更多基于其他指标生成更高级列联表的方法。

高级列联表

到目前为止,我们只是使用了基于产品购买频率的产品列联表。现在我们将探讨使用支持度和提升度等指标创建更多列联表,这些指标我们在之前已经讨论过,因为它们是客户在购物时可能一起购买的商品的更好指标。为此,我们将使用综合 R 档案网络CRAN)仓库中可用的arules包。如果你没有安装,可以使用install.packages('arules')命令下载。一旦安装完成,我们将查看一个基于标准杂货的交易日志数据库,并使用我们在前几章中用于处理任何数据集或问题的标准机器学习方法来构建列联表。

首先,我们将从加载所需的包和数据到我们的工作空间开始,并查看交易数据的样貌:

> # loading the required package
> library(arules)
> 
> # getting and loading the data
> data(Groceries)
> 
> # inspecting the first 3 transactions 
> inspect(Groceries[1:3])
 items 
1 {citrus fruit,semi-finished bread,margarine,ready soups}
2 {tropical fruit,yogurt,coffee} 
3 {whole milk}

每个先前的交易都是一个购买的产品集合,正如我们在前几节中讨论的那样。现在我们将构建几个基于不同矩阵的列联表,并查看客户可能感兴趣一起购买的前五对产品。以下代码片段显示了基于计数的商品列联表:

> # count based product contingency matrix 
> ct <- crossTable(Groceries, measure="count", sort=TRUE)
> ct[1:5, 1:5]

输出:

高级列联表

这里我们看到的是一个与我们之前工作过的类似的矩阵。现在我们将创建一个基于支持的产品条件矩阵:

> # support based product contingency matrix 
> ct <- crossTable(Groceries, measure="support", sort=TRUE)
> ct[1:5, 1:5]

输出结果:

高级条件矩阵

最后,我们来看另一个基于之前讨论的度量提升的矩阵。如果你还记得,提升值越高,如果大于 1,客户同时购买两种产品的可能性就越强。

> # lift based product contingency matrix 
> ct <- crossTable(Groceries, measure="lift", sort=TRUE)
> ct[1:5, 1:5]

输出结果:

高级条件矩阵

从前面的矩阵中,你可以得到这样的见解,比如人们倾向于一起购买酸奶和全脂牛奶,或者苏打水和全脂牛奶实际上并不搭配,因为它的提升值小于1。这些类型的见解有助于在商店和购物网站上规划产品摆放,以实现更好的销售和推荐。

然而,这个模型的一些主要问题如下:

  • 产品数量众多会导致矩阵变得非常大,难以处理,因为它需要更多的时间和空间来处理。

  • 只能检测频繁项集中的项目对以进行推荐。从这个模型中可以找到超过两个项目的组合,但这需要与集合理论相关的额外逻辑。

  • 面临推荐引擎中通常所说的冷启动问题,当新产品推出时,由于我们的历史数据没有与之相关的任何信息,我们无法预测推荐或它在市场上的销售情况。

频繁项集生成

现在,我们将探讨一种更好的技术来寻找模式和检测频繁购买的产品。为此,我们将使用频繁项集生成技术。我们将从头实现这个算法,因为尽管当我们解决任何机器学习或优化问题时,我们通常使用现成的机器学习算法,这些算法经过优化,可在各种 R 包中使用,但本书的主要目标之一是确保我们了解机器学习算法背后的具体运作。因此,我们将看到如何使用数学、统计学和逻辑原理构建一些这些算法。

开始

我们将使用的数据集是shopping_transaction_log.csv,我们在本章开头用它来解释市场篮子分析的概念。本节我们将使用的代码可在ch3_frequent项集generation.R文件中找到。我们首先将遍历所有函数,然后定义主函数,该函数利用所有辅助函数定义频繁项集生成的流程。

我们将首先加载一些库依赖项和实用函数:

## load library dependencies 
library(dplyr)  # manipulating data frames
library(gridExtra)  # output clean formatted tables

## Utility function: Appends vectors to a list
list.append <- function (mylist, ...){
 mylist <- c(mylist, list(...))
 return(mylist)
}

数据检索和转换

接下来,我们将定义获取数据和将其转换为包含产品和购买频率的数据框所需格式的函数。我们还有一个函数,如果我们想删除购买频率低于某个阈值的商品,可以修剪这个数据框。

## Step 1: Function to read the dataset into memory from file
get_transaction_dataset <- function(filename){
 df <- read.csv(filename, header = FALSE)
 dataset <- list()
 for (index in seq(nrow(df))){
 transaction.set <- as.vector(unlist(df[index,]))
 transaction.set <- transaction.set[transaction.set != ""]
 dataset <- list.append(dataset, transaction.set)
 }
 return(dataset)
} 

## Step 2: Function to convert dataset into a data frame
get_item_freq_table <- function(dataset){
 item.freq.table <- unlist(dataset) %>% table %>% data.frame
 return (item.freq.table)
}

## Step 3: Function to prune items based on minimum frequency
##         as specified by the user.
##         Here min freq <- item.min.freq
prune_item_freq_table <- function(item.freq.table, item.min.freq){
 pruned.item.table <- item.freq.table[item.freq.table$Freq >= 
 item.min.freq,]
 return (pruned.item.table)
}

构建项集关联矩阵

现在,我们将实现三个函数来帮助我们构建项集关联矩阵。我们首先构建第一个函数,该函数根据每个项集中项的数量,从我们的交易数据集中物品列表中返回不同的唯一项集组合。这有助于我们获取特定数量的项集。

## Step 4: Function to get possible itemset combinations where 
##         each itemset has n number of items where n is specified ##         by the user. Here n <- num.items 
get_associated_itemset_combinations <- function(pruned.item.table, 
 num.items){
 itemset.associations <- c()
 itemset.association.matrix <- combn(pruned.item.table$., 
 num.items)
 for (index in seq(ncol(itemset.association.matrix))){
 itemset.associations <- c(itemset.associations,
 paste(itemset.association.matrix[,index],
 collapse = ", ")
 )
 }
 itemset.associations <- unique(itemset.associations)
 return (itemset.associations)
}

以下函数构建一个频率列联表,显示数据集中每个交易中每个项集的发生情况。这构成了获取构建我们的频繁项集所需数据的基础。项集关联矩阵在高级别上显示了在先前的函数中按交易生成的不同唯一项集的发生情况。

## Step 5: Function to build an itemset association matrix where ##         we see a contingency table showing itemset association 
##         occurrence in each transaction of the dataset
build_itemset_association_matrix <- function(dataset, 
 itemset.association.labels,
 itemset.combination.nums){ 
 itemset.transaction.labels <- sapply(dataset, paste, 
 collapse=", ")
 itemset.associations <- lapply(itemset.association.labels, 
 function(itemset){
 unlist(strsplit(itemset, ", ", 
 fixed = TRUE)
 )
 }
 )
 # building the itemset association matrix
 association.vector <- c()
 for (itemset.association in itemset.associations){
 association.vector <- c(association.vector,
 unlist(
 lapply(dataset, 
 function(dataitem, 
 num.items=itemset.combination.nums){ 
 m <- match(dataitem, itemset.association)
 m <- length(m[!is.na(m)])
 if (m == num.items){
 1
 }else{
 NA
 }
 }
 )
 )
 )
 }

 itemset.association.matrix <- matrix(association.vector, 
 nrow = length(dataset))
 itemset.association.labels <- sapply(itemset.association.labels, 
 function(item) {
 paste0('{', paste(item, 
 collapse = ', '), '}')
 }
 ) 

 itemset.transaction.labels <- sapply(dataset, 
 function(itemset){
 paste0('{', paste(itemset, 
 collapse = ', '), '}')
 }
 )
 colnames(itemset.association.matrix) <- itemset.association.labels
 rownames(itemset.association.matrix) <- itemset.transaction.labels

 return (itemset.association.matrix)
}

一旦我们有了项集关联矩阵,我们就在以下函数中使用它,将这些单个项集发生次数加起来,以获取整个数据集中每个项集的总发生次数:

## Step 6: Function to generate total occurrences of each itemset 
##         in the transactional dataset based on data from the 
##         association matrix
get_frequent_itemset_details <- function(itemset.association.matrix){
 frequent.itemsets.table <- apply(itemset.association.matrix, 
 2, sum, na.rm=TRUE)
 return (frequent.itemsets.table)
}

创建频繁项集生成工作流程

最后,我们将定义一个函数,该函数将利用所有前面的函数来创建生成频繁项集的工作流程。这里我们将使用的主要参数包括data.file.path,它包含数据集的位置,itemset.combination.nums,它表示每个项集中应包含的项的数量,item.min.freq,它表示每个项的最小购买计数阈值,以及minsup,它告诉我们生成频繁项集的最小支持度。

## Step 7: Function containing entire workflow to generate 
##         frequent itemsets
frequent.itemsets.generator <- function(data.file.path, 
 itemset.combination.nums=2, 
 item.min.freq=2, minsup=0.2){
 # get the dataset
 dataset <- get_transaction_dataset(data.file.path)

 # convert data into item frequency table
 item.freq.table <- get_item_freq_table(dataset)
 pruned.item.table <- prune_item_freq_table(item.freq.table, 
 item.min.freq)

 # get itemset associations
 itemset.association.labels <- get_associated_itemset_combinations(pruned.item.table,
 itemset.combination.nums)
 itemset.association.matrix <- build_itemset_association_matrix(dataset, 
 itemset.association.labels, 
 itemset.combination.nums)

 # generate frequent itemsets
 frequent.itemsets.table <- get_frequent_itemset_details(itemset.association.matrix)
 frequent.itemsets.table <- sort(frequent.itemsets.table[frequent.itemsets.table > 0], 
 decreasing = TRUE)

 frequent.itemsets.names <- names(frequent.itemsets.table)
 frequent.itemsets.frequencies <- as.vector(frequent.itemsets.table)
 frequent.itemsets.support <- round((frequent.itemsets.frequencies * 100) / length(dataset), 
 digits=2)

 frequent.itemsets <- data.frame(Itemset=frequent.itemsets.names,
 Frequency=frequent.itemsets.frequencies,
 Support=frequent.itemsets.support)
 # apply minimum support cutoff to get frequent itemsets
 minsup.percentage <- minsup * 100
 frequent.itemsets <- subset(frequent.itemsets, 
 frequent.itemsets['Support'] >= minsup.percentage)
 frequent.itemsets.support <- sapply(frequent.itemsets.support,
 function(value){
 paste0(value,'%')
 }
 )

 # printing to console
 cat("\nItem Association Matrix\n")
 print(itemset.association.matrix)
 cat("\n\n")
 cat("\nValid Frequent Itemsets with Frequency and Support\n")
 print(frequent.itemsets)

 # displaying frequent itemsets as a pretty table
 if (names(dev.cur()) != "null device"){
 dev.off()
 }
 grid.table(frequent.itemsets)
}

检测购物趋势

现在是测试我们的算法的时候了!我们首先将生成所有频繁项集,其中包含两个物品,每个物品在整体数据集中至少购买过三次,并且最小支持度至少为 20%。为此,你必须在 R 控制台中运行以下函数。请记住,首先在内存中加载所有前面的函数。

> frequent.itemsets.generator(
 data.file.path='shopping_transaction_log.csv', 
 itemset.combination.nums=2, item.min.freq=3, minsup=0.2)

我们得到以下项集列联矩阵,它用于生成频繁项集。左侧的行表示交易,每一列代表一个项集。

检测购物趋势

最终的频繁项集将以漂亮的表格形式在控制台和绘图部分显示,如下所示:

检测购物趋势

因此,你可以清楚地看到,项集 {beer, diapers} 是我们最频繁的项集,支持率约为 67%,在我们的数据集中总共发生了六次,关联矩阵显示了它发生的确切交易。因此,这个功能检测到人们购买啤酒和尿布或尿布和牛奶的频率更高,因此当他们在购物时,我们可以向他们推荐相同的产品。我们还将查看包含三个项目的频繁项集:

> frequent.itemsets.generator(
 data.file.path='shopping_transaction_log.csv',
 itemset.combination.nums=3, item.min.freq=1, minsup=0.2)

这给我们以下表格,显示了频繁项集及其必要的统计数据:

检测购物趋势

因此,我们看到我们得到了两个支持率大于 20%的频繁项集。当然,请记住,这是一个小数据集,你拥有的包含购买交易的数据集越大,你将得到的模式越多,支持率也越强。

我们已经成功构建了一个生成频繁项集的算法!您可以使用这个相同的算法在新数据集上生成越来越多的频繁项集,然后我们可以在看到他们从任何频繁项集中购买一个或多个商品时立即开始推荐产品。一个简单的例子是,如果我们看到人们购买啤酒,我们可以向他们推荐尿布和牛奶,因为我们的算法在之前的频繁项集中检测到了这种购物趋势。

关联规则挖掘

我们现在将实现市场篮子分析的最终技术,以找出项集之间的关联规则,以检测和预测产品购买模式,这些模式可用于产品推荐和建议。我们将特别使用来自 arules 包的 Apriori 算法,该算法首先使用我们之前讨论的生成频繁项集的实现。一旦它有了频繁项集,算法就会根据支持度、置信度和提升度等参数生成必要的规则。我们还将展示如何使用 arulesViz 包可视化和交互这些规则。此实现的代码位于 ch3_association 规则 mining.R 文件中,您可以直接加载并按照书籍进行操作。

加载依赖和数据

我们将首先加载必要的包和数据依赖。请注意,我们将使用之前在处理高级列联矩阵的章节中讨论的 Groceries 数据集。

> ## loading package dependencies
> library(arules) # apriori algorithm
> library(arulesViz)  # visualize association rules
> 
> ## loading dataset
> data(Groceries)

探索性分析

我们将在这里对我们的数据集进行一些基本的探索性分析,以了解我们正在处理的数据类型以及哪些产品在客户中最受欢迎。

> ## exploring the data
> inspect(Groceries[1:3])
 items 
1 {citrus fruit,semi-finished bread,margarine,ready soups}
2 {tropical fruit,yogurt,coffee} 
3 {whole milk} 
> # viewing the top ten purchased products 
> sort(itemFrequency(Groceries, type="absolute"), 
+                    decreasing = TRUE)[1:10]

输出:

探索性分析

> # visualizing the top ten purchased products
> itemFrequencyPlot(Groceries,topN=10,type="absolute")

vegetables the most!

探索性分析

检测和预测购物趋势

我们现在将使用之前提到的 Apriori 算法生成关联规则,以检测购物趋势,以便我们可以预测顾客未来可能会购买什么,甚至向他们推荐。我们将从生成关联规则的正常工作流程开始:

> # normal workflow
> metric.params <- list(supp=0.001, conf=0.5)
> rules <- apriori(Groceries, parameter = metric.params)
> inspect(rules[1:5])

输出:

检测和预测购物趋势

解释这些规则的方式是观察左侧的物品和右侧的物品,并得出结论:如果顾客购物车中有左侧的物品,那么他购买右侧物品的可能性会增加。这种可能性可以使用剩余列中存在的指标进行量化。我们已经在市场篮子分析的概念中讨论了这些指标的重要性。从之前的规则中,我们可以得出结论,如果顾客购买蜂蜜,他有 73.3%的置信度会购买全脂牛奶。从之前的规则中,我们看到蜂蜜、可可、布丁和烹饪巧克力等物品都需要牛奶作为基本成分,这或许可以解释为什么人们倾向于将这些产品一起购买,并且我们可以向顾客推荐这些产品。请随意调整提升度、支持和置信度的参数,以从数据集中提取更多规则,以获得更多模式!

通常,Apriori 算法生成的规则中会有重复的关联规则,在检查规则集之前需要将其删除。您可以使用以下实用函数对生成的规则执行相同的操作:

# pruning duplicate rules
prune.dup.rules <- function(rules){
 rule.subset.matrix <- is.subset(rules, rules)
 rule.subset.matrix[lower.tri(rule.subset.matrix, diag=T)] <- NA
 dup.rules <- colSums(rule.subset.matrix, na.rm=T) >= 1
 pruned.rules <- rules[!dup.rules]
 return(pruned.rules)
}

也可以通过特定的指标对规则进行排序,以查看最佳质量的规则。我们将使用之前按最佳置信度值排序的指标参数值来查看最佳规则。

# sorting rules based on metrics
rules <- sort(rules, by="confidence", decreasing=TRUE)
rules <- prune.dup.rules(rules)
inspect(rules[1:5])

输出:

检测和预测购物趋势

在之前的规则中,我们看到如{ rice, sugar }这样的项目集有很强的倾向与{ whole milk }一起购买。置信度值相当高(而且应该是这样的,因为我们已经对它们进行了排序!),为 100%,提升度也大于 1,表明项目集之间存在正相关。请注意,在大数据集中,支持值可能不是很高,这是完全正常的,因为我们正在搜索整个交易数据集中的一些特定模式,这些模式可能连 1%的总交易量都覆盖不了,因为交易类型繁多。然而,对于我们来说,检测这些模式以做出关于预测可能一起销售的产品并推荐给顾客的明智决策至关重要。接下来,我们将查看另一个按提升度排序的最佳质量规则的示例:

> rules<-sort(rules, by="lift", decreasing=TRUE)
> rules <- prune.dup.rules(rules)
> inspect(rules[1:5])

输出:

检测和预测购物趋势

我们看到这些规则具有非常高的提升度和良好的置信度,这使得它们成为顾客最倾向于一起购买的物品!

我们现在将探讨检测特定购物模式,这是我们之前讨论过的。一种实现方式是针对特定商品,并生成包含这些商品的关联规则。第一种方式是预测如果顾客在关联规则的右侧购买了某个商品,他们购物车中可能包含哪些商品。我们通过明确指定商品,如下所示,并分析交易数据集来完成这项工作:

> # finding itemsets which lead to buying of an item on RHS
> metric.params <- list(supp=0.001,conf=0.5, minlen=2)
> rules<-apriori(data=Groceries, parameter=metric.params, 
+                appearance = list(default="lhs",rhs="soda"),
+                control = list(verbose=F))
> rules <- prune.dup.rules(rules)
> rules<-sort(rules, decreasing=TRUE, by="confidence")
> inspect(rules[1:5])

输出:

检测和预测购物趋势

有趣的是,人们倾向于一起购买饮料,例如咖啡、水、啤酒以及其他杂项饮料,以及从之前的规则中提到的汽水。因此,你可以看到,使用这些规则预测用户何时可能购买汽水并采取相应行动是非常容易的。

我们还可以通过在关联规则的左侧明确设置特定的商品集值,使用以下技术来预测如果用户已经在他们的购物车中放入了一些特定的商品,他们将购买哪些商品:

# finding items which are bought when we have an itemset on LHS
metric.params <- list(supp=0.001, conf = 0.3, minlen=2)
rules<-apriori(data=Groceries, parameter=metric.params, 
 appearance = list(default="rhs",
 lhs=c("yogurt", "sugar")),
 control=list(verbose=F))
#rules <- prune.dup.rules(rules)
rules<-sort(rules, decreasing=TRUE,by="confidence")
inspect(rules[1:5])

输出:

检测和预测购物趋势

你可以从之前的规则中清楚地看到,如果人们在购物车中同时或单独购买了酸奶和糖,他们倾向于购买牛奶。因此,通过针对特定的商品集,你可以向客户提供基于特定产品的推荐。

可视化关联规则

有一个优秀的包arulesViz,它提供了一种交互式的方式来可视化关联规则并与它们交互。以下是对之前关联规则的一个示例可视化:

> ## visualizing rules
> plot(rules, method="graph", interactive=TRUE, shading=TRUE)

arulesViz生成的这个可视化是完全交互式的,你可以玩转顶点和边,根据你的意愿放置商品集,以从各种规则中找到更多和更多的趋势和模式。

这就结束了我们对市场篮子分析中使用的检测和预测购物交易日志趋势的主要技术的讨论,并据此采取行动。

摘要

在本章中,我们覆盖了大量的内容!我们从一个关于如何在零售领域检测和预测趋势的讨论开始。然后我们深入探讨了市场篮子分析的真实含义以及算法背后的核心概念、数学公式,以及用于评估从算法中获得的结果的关键指标,特别是支持度、置信度和提升度。我们还讨论了用于分析的最流行技术,包括列联表评估、频繁项集生成和关联规则挖掘。接下来,我们讨论了如何使用市场篮子分析进行数据驱动决策。最后,我们实现了自己的算法,并使用了一些流行的 R 库,如arules,将这些技术应用于一些真实世界的交易数据,以检测、预测和可视化趋势。请注意,这些机器学习技术只讨论基于购买和交易日志的产品推荐,纯粹基于购买和交易日志。这里缺少了人性化的元素,因为我们没有考虑到基于用户购买或评分的喜好和厌恶。

在下一章中,我们将解决这些问题的其中一些,并构建用于推荐产品的稳健推荐引擎,这些推荐将考虑到产品以及用户兴趣。

第四章。构建产品推荐系统

数字世界让一切都可以一键获取。随着一切转向线上,在线购物或电子商务已经成为一大趋势。从杂货到电子产品,甚至汽车,一切都可以在亚马逊、Flipkart 和 eBay 等全球平台找到。这个不断扩大的数字市场正是数据科学展示其魔力的理想场所。

电子商务的在线革命不仅赋予了顾客权力,还让他们因选择过多而感到不知所措。选择不仅限于产品或类别,还包括不同的电子商务平台。在这个高度竞争的市场中,作为一家电子商务公司确实非常困难。脱颖而出是一个挑战,而数据再次发挥了救星的作用。

正如我们在第三章中看到的,“使用市场篮子分析预测客户购物趋势”,购买模式可以提供关于购物行为的很多见解。我们利用此类数据来找到关联规则,不仅帮助客户快速找到合适的产品,还帮助零售商增加收入(参见第三章,“使用市场篮子分析预测客户购物趋势”)。对于关联规则,其粒度位于交易层面。它们使用交易作为中心实体,因此不提供特定于用户的见解。

在本章中,我们也将继续在电子商务领域进行我们的项目工作。在这里,我们将解决个性化的问题。我们将使用机器学习算法提供特定于用户的推荐。

通过本章,我们将了解:

  • 推荐系统及其类型

  • 推荐系统的问题

  • 协同过滤器

  • 基于矩阵分解从头开始构建推荐系统

  • 利用高度优化的 R 包构建一个生产就绪的推荐引擎并评估其推荐

在本章的整个过程中,我们将交替使用术语推荐引擎推荐系统

理解推荐系统

每个人都是独一无二的,我们做事的方式定义了我们独特的个性。我们吃饭、走路、说话,甚至购物都有非常独特的方式。由于本章的重点是电子商务,我们将主要关注我们的购物行为。我们将利用每位客户的独特行为来提供个性化的购物体验。

为了完成提供个性化购物体验的任务,我们需要一个系统来理解和模拟我们的客户。推荐引擎是学习客户偏好、选择等信息的系统,以推荐更接近用户可能自己购买的新产品,从而提供个性化体验。这些系统提供的选项有很大的可能性会被客户购买。

让我们尝试正式定义一个推荐系统。

推荐系统(或推荐引擎)是一类信息过滤系统,它们分析输入数据以预测用户的偏好,就像他们为自己做的那样。

与信息过滤系统不同,后者会删除或过滤信息,推荐引擎会添加或重新排列流向用户的信息,使其与当前上下文更相关。

推荐引擎并不是一个新概念。在互联网出现之前,它们就已经存在了。它们以我们的朋友和家人的形式存在,他们曾经向我们推荐购买的东西,因为他们理解我们的选择。这些就是,并且仍然是某种离线推荐引擎。网络上充满了在线推荐引擎。从 Twitter 上的关注推荐到 Netflix 上的你可能喜欢的其他电影,再到 LinkedIn 上的你可能感兴趣的工作,推荐引擎无处不在,而不仅仅是电子商务平台。

现在我们已经了解了推荐引擎是什么,让我们来看看它们的不同类型:

  • 基于用户的推荐引擎:正如其名所示,这些系统以用户为中心实体。通过分析用户的活跃度、偏好或行为来预测他们可能喜欢的东西,这取决于他们与其他类似用户的相似性。由于这些推荐引擎广泛使用协同过滤器,因此它们通常被称为基于用户的协同过滤器。

  • 基于内容的推荐引擎:正如其名所示,这些引擎以内容或项目为中心实体。这些项目被分析以提取特征;同时,也建立用户档案以将用户偏好映射到项目类型。然后,这些引擎使用这些信息来预测与用户过去喜欢的项目相似的项目。这类推荐引擎也被称为基于项目的协同过滤器,其根源在于信息检索理论。

  • 混合推荐引擎:这些系统结合了两种方法的优点,以改进预测结果。两种纯类型可以同时使用,然后合并它们的结果;它们可以通过向基于内容的系统添加协同过滤功能或甚至将两种方法统一到一个单一模型中来实现。已经进行了多项研究来证明混合方法比简单方法更好。混合推荐引擎在解决一般推荐引擎面临的难题方面也更为出色。

在我们深入探讨这些算法的复杂性之前,让我们看看影响推荐系统的问题。

推荐系统的问题

推荐引擎主要受以下两个问题的影响:

  • 稀疏性问题:推荐引擎根据用户偏好(或根据应用的不同,对不同项目的评分)来预测或推荐产品。通常评分是在某个选定的尺度上给出的,但用户可能选择不对他/她未购买或查看的项目进行评分。对于这种情况,评分是空白或零。因此,评分矩阵 R 的元素形式如下:

    推荐系统的问题

    对于任何现实世界应用,例如电子商务平台,由于平台上可用的用户和项目数量庞大,这样的评分矩阵的大小是巨大的。尽管在这样一个平台上收集了大量的用户相关信息,但评分矩阵本身可能仍然相当稀疏,即矩阵可能有大量空白(或零)元素。这个问题通常被称为稀疏性问题。稀疏性问题使得推荐引擎的预测无效,因为算法无法正确推断出由于空白或缺失评分而产生的相关性。在最坏的情况下,算法可能会将两个用户视为不相关,而实际上他们有高度相似的选择偏好。稀疏性问题通常影响协同过滤算法。

  • 冷启动问题:稀疏问题的特殊情况是冷启动问题。如前所述,当评分矩阵包含稀疏元素(或评分)时,推荐引擎无法返回有效的推荐。冷启动问题在两种特定情况下发生。首先,假设一个新用户被添加到系统中。在这种情况下,代表该用户的行将包含零(大多数情况下)。由于缺乏与用户偏好相关的信息,向此类用户推荐项目几乎是不可能的。第二种情况是当新项目被添加到系统中。由于新添加的项目不会有任何用户评分,因此推荐此类项目对推荐系统来说将是困难的。因此,这两个场景代表了所谓的冷启动问题。与稀疏问题非常相似,冷启动问题也困扰着协同过滤器。

协同过滤器

推荐系统和协同过滤器有着悠久的历史。从早期使用特定分类和硬编码结果的原始推荐引擎,到当前各种电子商务平台上的复杂推荐引擎,推荐引擎一直使用协同过滤器。它们不仅易于理解,而且同样易于实现。在我们深入了解实现细节之前,让我们利用这个机会更多地了解协同过滤器。

注意

趣味事实

推荐引擎无疑已经过时了任何已知的电子商务平台!1979 年开发的 Grundy 是一个虚拟图书管理员。它是一个向用户推荐书籍的系统。它根据某些预定义的刻板印象来模拟用户,并为每个此类类别推荐已知列表中的书籍。

核心概念和定义

协同过滤器(以下简称CF)和一般的推荐引擎使用某些术语和定义来正式定义和解决该问题。

推荐引擎的问题领域围绕着用户和他们感兴趣的项目。一个用户是任何与系统互动并在项目(例如购买或查看它)上执行某些操作的人。同样,一个评分定义了用户对考虑中的项目的偏好。通常,这个三元组表示为(用户,项目,评分)元组。由于评分量化了用户的偏好,评分可以根据应用的不同以不同的方式定义。应用将评分定义为从0-5等整数刻度,而其他可能定义实值刻度。某些应用可能使用喜欢/不喜欢购买/未购买等值的二进制刻度。因此,每个应用都使用评分尺度来满足其用户的偏好。

现在我们已经知道了涉及的关键角色,下一步是数学上表示这些核心概念。一个(用户, 项目, 评分)元组通常以稀疏矩阵的形式表示,称为评分矩阵。每一行代表一个用户,而列表示项目。这个评分矩阵的每个元素都指代用户对项目的评分或偏好。由于并非所有项目都会被每个用户评分,因此这些未评分的项目将包含空值或空白值。使用 0-5 评分尺度(未评分/缺失评分用?表示)的评分矩阵如下所示,显示了三个用户对不同笔记本电脑型号的偏好:

核心概念和定义

一个示例评分矩阵

推荐引擎被赋予执行两个主要操作的任务:预测推荐。预测操作针对给定的用户和项目,以确定用户对所考虑项目的可能偏好。对于评分矩阵(如前面所示),预测就像识别缺失值(在先前的例子中用?表示)。

推荐操作在预测完成后进行。给定一个用户,推荐操作会根据用户的偏好生成一个包含前N个项目的列表。

注意

注意,在推荐引擎的上下文中,用于预测和推荐任务的用户被称为活跃用户

协同过滤算法

协同过滤算法是一组流行的算法,在众多应用中被广泛使用。众所周知,协同过滤算法利用类似用户的行为来预测和为活跃用户推荐项目。这些算法基于一个简单的假设,即类似用户会表现出类似的行为。更正式地说,算法假设系统中其他用户的偏好或评分可以用来为活跃用户提供合理的预测。

基于邻居的协同过滤,也称为用户-用户协同过滤kNN 协同过滤,是协同过滤算法家族中最早和最广泛使用的算法之一。kNN 协同过滤算法基于用户具有类似偏好的用户之间类似行为的核心假设。该算法利用相似度度量(在第二章中讨论,让我们帮助机器学习),来预测和为活跃用户推荐项目。该算法遵循两步方法,首先计算预测,然后进行推荐。接下来讨论该算法的三个主要组成部分。

预测

kNN CF 的第一步是利用评分矩阵(通常表示为 R)来计算预测。由于我们关注的是用户-用户 CF,因此需要考虑活跃用户(考虑中的用户)的邻域,表示为 u

U 为系统中所有可用用户的集合,N 表示所需的邻域 预测。然后算法使用一个相似度度量,例如 s,来计算 u 的邻居。一旦确定了 Nu 的邻域),就会汇总相邻用户的评分来计算 u 对当前项目的偏好。最常用的汇总偏好的方法是使用 N 个相邻用户的 加权平均

从数学上讲,活跃用户 u 对项目 i 的预测偏好,表示为 p[ui],如下所示:

预测

其中:

  • 预测 是活跃用户 u 的平均评分

  • 预测 是活跃用户 u 和相邻用户 预测 之间的相似度度量

在前面的公式中,我们从相邻用户的平均评分中减去活跃用户评分的均值 预测,以消除用户评分的偏差(一些用户给出极高或极低的评分,因此他们可能会影响整体预测评分)。一个有偏差的推荐引擎可能会阻碍更好的用户-产品匹配,偏向于受欢迎的产品或反对不太受欢迎的产品。我们可以通过使用标准差来规范化用户的评分,以控制评分在均值周围的分布,从而进一步提高预测的准确性。为了简化问题,我们将使用之前提到的公式。以下图像展示了活跃用户的最近邻:

预测

最近邻(K=3

现在出现的问题是,为什么只使用加权平均来预测评分,以及最优的邻居数量(N)是多少。使用加权平均的原因是,它是有助于生成一致结果的一种度量方法。多年来,不同的系统使用了各种方法,例如 多元回归(BellCore 系统用于视频推荐)、未加权平均(Ringo 用于音乐推荐)等等,但在实践中加权平均表现相当好。

注意

更多信息,请参阅 W. Hill, L. Stead, M. Rosenstein, 和 G. Furnas 的论文,在虚拟使用社区中推荐和评估选择,发表于 ACM CHI '95,第 194–201 页,ACM Press/Addison-Wesley 出版公司,1995 年。

接下来是关于最优邻居数量的第二个问题,这非常依赖于具体的应用。我们在第二章中看到,“让我们帮助机器学习”,邻居的数量如何改变算法的结果(参见K-最近邻 (KNN)),同样,N的值也会影响推荐引擎的结果。一般来说,限制邻居用户数量有助于通过移除与活跃用户相关性低的用户来减少噪声。但再次强调,N的值依赖于具体应用,并且需要数据科学家进行适当的尽职调查。

推荐

一旦对活跃用户进行了预测,可以通过按预测排名对项目进行排序来生成推荐列表。这个推荐列表可以通过应用某些最小阈值和其他用户特定特征(如对颜色、尺寸、价格敏感度等)进一步微调。因此,这一步生成了一份可能的项目列表,用户更有可能根据个人偏好购买这些项目。我们将在下一节“构建推荐引擎”中详细介绍这一点。

相似度

相似度度量是我们基于协同过滤的推荐引擎算法的一个重要组成部分。有各种相似度度量可供使用。其中最常见的是余弦相似度度量。这种方法将每个用户表示为一个n维度的评分向量,相似度通过计算两个此类用户向量之间的余弦距离来衡量。

从数学上讲,余弦相似度表示为:

相似度

其中,相似度相似度是每个评分向量的L2欧几里得范数。

皮尔逊 相关系数斯皮尔曼秩相关系数是两种广泛使用的统计相似度度量。

现在我们已经了解了协同过滤和一般概念的基础,我们准备着手处理实现细节。让我们从构建推荐系统,一块一块地开始!

构建推荐引擎

如前所述,协同过滤是一种简单而非常有效的预测和推荐项目给用户的方法。如果我们仔细观察,算法是针对输入数据工作的,这实际上只是不同产品用户评分的矩阵表示。

从数学角度来说,矩阵分解是一种操纵矩阵并从矩阵表示的数据中识别潜在或隐藏特征的技术。基于同样的概念,让我们使用矩阵分解作为预测用户尚未评分的项目评分的基础。

矩阵分解

矩阵分解指的是识别两个或多个矩阵,当这些矩阵相乘时,我们得到原始矩阵。如前所述,矩阵分解可以用来发现两种不同实体之间的潜在特征。随着我们为电子商务平台准备推荐引擎,我们将理解和使用矩阵分解的概念。

由于我们当前项目的目标是个性化购物体验并为电子商务平台推荐产品评分,我们的输入数据包含网站上各种产品的用户评分。我们处理输入数据,将其转换为矩阵表示,以便使用矩阵分解进行分析。输入数据看起来像这样:

矩阵分解

用户评分矩阵

如您所见,输入数据是一个矩阵,其中每一行代表特定用户对不同项目(在列中表示)的评分。对于当前情况,表示项目的列是不同的手机,如 iPhone 4、iPhone 5s、Nexus 5 等。每一行包含由八个不同用户给出的这些手机的评分。评分范围从 1 到 5,1 为最低,5 为最高。评分为 0 表示未评分的项目或缺失的评分。

我们推荐引擎的任务将是预测输入矩阵中缺失的正确评分。然后我们可以使用预测的评分来推荐用户最希望得到的物品。

在这里的前提是,如果两个用户喜欢类似的产品或物品特征,他们会对该产品或物品进行相似的评分。由于我们当前的数据与不同手机的用户评分相关,人们可能会根据硬件配置、价格、操作系统等因素对手机进行评分。因此,矩阵分解试图识别这些潜在特征,以预测特定用户和特定产品的评分。

在尝试识别这些潜在特征时,我们基于一个基本假设进行操作,即这些特征的数目少于考虑中的项目总数。这个假设是有意义的,因为如果是这样的话,那么每个用户都会有一个与他自己/她自己(以及类似地对于产品)相关的特定特征。这反过来会使推荐变得毫无意义,因为没有任何用户会对其他用户评分的项目感兴趣(这通常不是情况)。

现在让我们深入了解矩阵分解和我们的推荐引擎的数学细节。

由于我们处理的是不同产品的用户评分,让我们假设U是一个表示用户偏好的矩阵,同样地,一个矩阵P表示我们对其有评分的产品。然后评分矩阵R将被定义为矩阵分解

假设这个过程帮助我们识别出 K 个潜在特征,我们的目标是找到两个矩阵 XY,使得它们的乘积(矩阵乘法)近似于 R

矩阵分解

其中,X 是与用户相关的矩阵,它表示用户和潜在特征之间的关联。另一方面,Y 是与产品相关的矩阵,它表示产品和潜在特征之间的关联。

预测用户 u[i] 对产品 p[j] 的评分 矩阵分解 的任务是通过计算对应于 p[j] 的向量(即用户向量 Y)和 u[i] 的向量(即产品向量 X)的点积来完成的。

矩阵分解

现在,为了找到矩阵 XY,我们利用一种称为 梯度下降 的技术。简单来说,梯度下降试图找到一个函数的局部最小值;它是一种优化技术。在当前上下文中,我们使用梯度下降来迭代地最小化预测评分和实际评分之间的差异。首先,我们随机初始化矩阵 XY,然后计算它们的乘积与实际评分矩阵 R 的差异。

预测值和实际值之间的差异被称为 误差。对于我们的问题,我们将考虑 平方误差,其计算方式如下:

矩阵分解

在这里,r[ij] 是用户 i 对产品 j 的实际评分,而 矩阵分解 是相同评分的预测值。

为了最小化误差,我们需要找到正确的方向或梯度来改变我们的值。为了获得变量 xy 的梯度,我们分别对它们进行微分:

矩阵分解

因此,找到 x[ik]y[kj] 的方程可以表示为:

矩阵分解

其中,α 是表示 下降率 或接近最小值的速率的常数(也称为学习率)。α 的值定义了我们在两个方向上采取的步长的大小。较大的值可能导致振荡,因为我们可能会每次都超过最小值。通常的做法是选择非常小的 α 值,大约为 10^(-4)矩阵分解矩阵分解 是梯度下降每次迭代后 x[ik]y[kj] 的更新值。

如第二章中所述,在《让我们帮助机器学习》中,机器学习算法可能会出现过拟合问题。为了避免过拟合,除了控制矩阵XY中的极端或大值外,我们引入了正则化的概念。正式来说,正则化是指引入额外信息以防止过拟合的过程。正则化会惩罚具有极端值的模型。

为了防止我们的模型过拟合,我们引入了正则化常数β。引入β后,方程更新如下:

矩阵分解

此外,

矩阵分解矩阵分解

由于我们已经有评分矩阵R,并使用它来确定我们的预测值与实际值之间的距离,矩阵分解变成了一个监督学习问题。对于这个监督问题,正如我们在第二章中看到的,《让我们帮助机器学习》,我们使用一些行作为我们的训练样本。设S为我们的训练集,其元素为形式为(u[i], p[j], r[ij])的元组。因此,我们的任务是使训练集S中每个元组(u[i], p[j], r[ij])的误差(e[ij])最小化。

总体误差(例如E)可以计算如下:

矩阵分解

实现

既然我们已经研究了矩阵分解的数学原理,让我们将算法转换为代码,并为之前讨论的移动电话评分数据集准备推荐引擎。

矩阵分解部分所示,输入数据集是一个矩阵,其中每一行代表用户对列中提到的产品的评分。评分范围从 1 到 5,0 代表缺失值。

为了将我们的算法转换为可工作的代码,我们需要完成以下任务:

  • 加载输入数据并将其转换为评分矩阵表示

  • 准备基于矩阵分解的推荐模型

  • 预测并向用户推荐产品

  • 解释和评估模型

加载并将输入数据转换为矩阵表示很简单。如前所述,R 为我们提供了易于使用的实用函数来完成这项任务。

# load raw ratings from csv
raw_ratings <- read.csv(<file_name>)

# convert columnar data to sparse ratings matrix
ratings_matrix <- data.matrix(raw_ratings)

现在我们已经将数据加载到R矩阵中,我们继续准备用户潜在特征矩阵X和项目潜在特征矩阵Y。我们使用runif函数从均匀分布中初始化这两个矩阵。

# number of rows in ratings
rows <- nrow(ratings_matrix)

# number of columns in ratings matrix
columns <- ncol(ratings_matrix)

# latent features
K <- 2

# User-Feature Matrix
X <- matrix(runif(rows*K), nrow=rows, byrow=TRUE)

# Item-Feature Matrix
Y <- matrix(runif(columns*K), nrow=columns, byrow=TRUE)

主要组成部分是矩阵分解函数本身。让我们将任务分为两部分,计算梯度以及随后计算总体误差。

梯度计算的涉及评分矩阵 R 和两个因子矩阵 XY,以及常数 αβ。由于我们处理的是矩阵操作(特别是乘法),我们在开始任何进一步的计算之前将 Y 转置。以下代码行将之前讨论的算法转换为 R 语法。所有变量都遵循与算法相似的命名约定,以便于理解。

for (i in seq(nrow(ratings_matrix))){

 for (j in seq(length(ratings_matrix[i, ]))){

 if (ratings_matrix[i, j] > 0){

 # error 
 eij = ratings_matrix[i, j] - as.numeric(X[i, ] %*% Y[, j])

 # gradient calculation 

 for (k in seq(K)){
 X[i, k] = X[i, k] + alpha * (2 * eij * Y[k, j]/
 - beta * X[i, k])

 Y[k, j] = Y[k, j] + alpha * (2 * eij * X[i, k]/
 - beta * Y[k, j])
 }
 }
 }
 }

算法的下一部分是计算整体误差;我们再次使用相似的变量名以保持一致性:

# Overall Squared Error Calculation

e = 0

for (i in seq(nrow(ratings_matrix))){

 for (j in seq(length(ratings_matrix[i, ]))){

 if (ratings_matrix[i, j] > 0){

 e = e + (ratings_matrix[i, j] - /
 as.numeric(X[i, ] %*% Y[, j]))²

 for (k in seq(K)){
 e = e + (beta/2) * (X[i, k]² + Y[k, j]²)
 }
 }
 }
}

作为最后一部分,我们多次迭代这些计算以减轻冷启动和稀疏性的风险。我们将控制多次启动的变量称为epoch。一旦整体误差下降到某个阈值以下,我们也会停止计算。

此外,由于我们从均匀分布初始化了 XY,预测值将是实数。在返回预测矩阵之前,我们将最终输出四舍五入。

注意,这是一个非常简单的实现,为了便于理解,省略了很多复杂性。因此,预测矩阵中可能包含大于 5 的值。对于当前场景,可以安全地假设超过 5 的最大刻度值等同于 5(同样适用于小于 0 的值)。我们鼓励读者调整代码以处理此类情况。

α 设置为 0.0002β 设置为 0.02K(即潜在特征)设置为 2,并将 epoch 设置为 1000,让我们看看我们的代码的一个样本运行,整体误差阈值设置为 0.001

# load raw ratings from csv
raw_ratings <- read.csv("product_ratings.csv")

# convert columnar data to sparse ratings matrix
ratings_matrix <- data.matrix(raw_ratings)

# number of rows in ratings
rows <- nrow(ratings_matrix)

# number of columns in ratings matrix
columns <- ncol(ratings_matrix)

# latent features
K <- 2

# User-Feature Matrix
X <- matrix(runif(rows*K), nrow=rows, byrow=TRUE)

# Item-Feature Matrix
Y <- matrix(runif(columns*K), nrow=columns, byrow=TRUE)

# iterations
epoch <- 10000

# rate of descent
alpha <- 0.0002

# regularization constant
beta <- 0.02

pred.matrix <- mf_based_ucf(ratings_matrix, X, Y, K, epoch = epoch)

# setting column names
colnames(pred.matrix)<-c("iPhone.4","iPhone.5s","Nexus.5","Moto.X","Moto.G","Nexus.6",/"One.Plus.One")

上述代码行利用前面解释过的函数来准备推荐模型。预测评分或输出矩阵看起来如下:

实现

预测评分矩阵

结果解释

让我们快速进行视觉检查,看看我们的预测做得有多好或有多差。以用户 1 和用户 3 作为我们的训练样本。从输入数据集中,我们可以清楚地看到用户 1 对 iPhone 给出了高评分,而用户 3 对基于 Android 的手机也做了同样的操作。以下并排比较显示了我们的算法预测的值与实际值非常接近:

结果解释

用户 1 的评分

让我们查看以下截图中的用户 3 的评分:

结果解释

用户 3 的评分

现在我们有了更新值的评分矩阵,我们准备向用户推荐产品。只显示用户尚未评分的产品是常识。正确的推荐集也将使卖家能够推销那些有较高概率被用户购买的产品。

常规做法是返回每个用户未评分产品列表中排名前N的项目列表。考虑的用户通常被称为活跃用户。让我们以用户 6 作为我们的活跃用户。这位用户只按以下顺序对 Nexus 6、One Plus One、Nexus 5 和 iPhone4 进行了评分,即 Nexus 6 评分很高,而 iPhone4 评分最低。使用我们的算法为这样的客户提供Top 2推荐手机的结果将是 Moto X 和 Moto G(确实非常正确,你明白为什么吗?)。

因此,我们构建了一个足够智能的推荐引擎,能够为安卓爱好者推荐合适的手机,并拯救了世界免于又一次灾难!

数据拯救!

使用矩阵分解的简单推荐引擎实现让我们领略了此类系统实际运作的方式。接下来,让我们通过使用推荐引擎来进入一些实际操作。

适用于生产的推荐引擎

到目前为止,我们已经详细了解了推荐引擎,甚至从头开始开发了一个(使用矩阵分解)。通过所有这些,我们可以清楚地看到此类系统的应用范围是多么广泛。

电子商务网站(或者更确切地说,任何流行的技术平台)今天都提供了大量的内容。不仅如此,用户数量也非常庞大。在这种场景下,当成千上万的用户在全球范围内同时浏览/购买商品时,为他们提供推荐本身就是一项任务。更复杂的是,良好的用户体验(例如响应时间)可以在两个竞争对手之间产生巨大差异。这些都是处理数百万客户日复一日生产的实时系统实例。

注意

有趣的事实

Amazon.com 是电子商务领域最大的名字之一,拥有 2.44 亿活跃客户。想象一下,为了向如此庞大的客户群提供推荐,需要处理多少数据!

来源:www.amazon.com/b?ie=UTF8&node=8445211011

为了在这些平台上提供无缝的使用能力,我们需要高度优化的库和硬件。对于一个推荐引擎来说,要同时处理每秒成千上万的用户,R 语言有一个强大且可靠的框架,称为recommenderlab

Recommenderlab 是一个广泛使用的 R 扩展,旨在为推荐引擎提供一个稳健的基础。该库的重点是提供高效的数据处理、标准算法的可用性和评估能力。在本节中,我们将使用 recommenderlab 处理一个相当大的数据集,为用户推荐项目。我们还将使用 recommenderlab 的评估函数来查看我们的推荐系统是好是坏。这些功能将帮助我们构建一个生产就绪的推荐系统,类似于(或者至少更接近)许多在线应用程序,如亚马逊或 Netflix 所使用的系统。

本节使用的数据集包含 100 个物品的评分,由 5000 个用户进行评分。数据已被匿名化,产品名称已被产品 ID 替换。使用的评分范围是 0 到 5,其中 1 是最差的,5 是最佳的,0 表示未评分的项目或缺失的评分。

要使用 recommenderlab 为生产就绪的系统构建推荐引擎,需要执行以下步骤:

  1. 提取、转换和分析数据。

  2. 准备推荐模型并生成推荐。

  3. 评估推荐模型。

我们将在以下子节中查看所有这些步骤。

提取、转换和分析

就像任何数据密集型(尤其是机器学习)应用程序一样,首要步骤是获取数据,理解/探索它,然后将其转换为适合当前应用程序的算法所需的格式。对于使用 recommenderlab 包的推荐引擎,我们首先从上一节中描述的 csv 文件加载数据,然后使用各种 R 函数对其进行探索。

# Load recommenderlab library
library("recommenderlab")

# Read dataset from csv file
raw_data <- read.csv("product_ratings_data.csv")

# Create rating matrix from data 
ratings_matrix<- as(raw_data, "realRatingMatrix")

#view transformed data
image(ratings_matrix[1:6,1:10])

代码的前一节加载了 recommenderlab 包,然后使用标准实用工具函数读取 product_ratings_data.csv 文件。为了探索以及进一步的步骤,我们需要将数据转换为用户-项目评分矩阵格式(如 核心概念和定义 节中所述)。

as(<data>,<type>) 实用工具将 csv 转换为所需的评分矩阵格式。

csv 文件包含以下截图所示格式的数据。每一行包含一个用户对特定产品的评分。列标题是自解释的。

提取、转换和分析

产品评分数据

realRatingMatrix 转换将数据转换为以下图像所示的矩阵。用户表示为行,而列表示产品。评分使用渐变尺度表示,其中白色表示缺失/未评分的评分,而黑色表示 5 分/最佳评分。

提取、转换和分析

我们数据评分矩阵表示

现在我们已经将数据放入我们的环境中,让我们探索一些其特征,看看我们是否能解码一些关键模式。

首先,我们从主数据集中提取一个代表性样本(参见图 产品评分数据)并对其进行分析,以了解:

  • 我们用户群体的平均评分分数

  • 用户群体中项目评分的分布/扩散

  • 每个用户评分的项目数量

以下代码行帮助我们探索数据集样本并分析之前提到的点:

# Extract a sample from ratings matrix
sample_ratings <-sample(ratings_matrix,1000)

# Get the mean product ratings as given by first user
rowMeans(sample_ratings[1,])

# Get distribution of item ratings
hist(getRatings(sample_ratings), breaks=100,/
 xlab = "Product Ratings",main = " Histogram of Product Ratings")

# Get distribution of normalized item ratings
hist(getRatings(normalize(sample_ratings)),breaks=100,/
 xlab = "Normalized Product Ratings",main = /
 " Histogram of Normalized Product Ratings")

# Number of items rated per user
hist(rowCounts(sample_ratings),breaks=50,/
 xlab = "Number of Products",main =/
 " Histogram of Product Count Distribution")

我们从数据集中提取了 1,000 个用户的样本用于探索目的。用户评分的平均值,即用户评分样本的第一行给出的2.055,告诉我们这个用户要么没有看到/评分很多产品,要么通常评分很低。为了更好地了解用户如何评分产品,我们生成了一个项目评分分布的直方图。这个分布峰值在中间,即3。直方图如下所示:

提取、转换和分析

评分分布的直方图

直方图显示评分围绕平均值正常分布,对于评分非常高或非常低的产品计数较低。

最后,我们检查用户评分产品数量的分布。我们准备了一个直方图来显示这种分布:

提取、转换和分析

评分数量的直方图

前面的直方图显示有许多用户评分了70个或更多的产品,同样也有许多用户评分了所有100个产品。

探索步骤帮助我们了解我们的数据是什么样的。我们还对用户通常如何评分产品以及有多少产品被评分有了了解。

模型准备和预测

我们的数据已经在我们转换成评分矩阵格式的 R 环境中。在本节中,我们感兴趣的是基于用户协同过滤准备推荐引擎。我们将使用与之前章节中描述的类似术语。Recommenderlab 提供了直观的实用工具来学习和准备构建推荐引擎的模型。

我们基于仅 1,000 个用户的样本来准备我们的模型。这样,我们可以使用这个模型来预测评分矩阵中其余用户的缺失评分。以下代码行利用前 1,000 行来学习模型:

# Create 'User Based collaborative filtering' model 
ubcf_recommender <- Recommender(ratings_matrix[1:1000],"UBCF")

代码中的"UBCF"代表基于用户的协同过滤。Recommenderlab 还提供了其他算法,例如IBCF基于项目的协同过滤PCA主成分分析,以及其他算法。

在准备模型之后,我们使用它来预测系统中第 1,010 个和第 1,011 个用户的评分。Recommenderlab 还要求我们提及要推荐给用户的物品数量(当然按照偏好顺序)。对于当前情况,我们提到推荐 5 个物品。

# Predict list of product which can be recommended to given users
recommendations <- predict(ubcf_recommender,/
 ratings_matrix[1010:1011], n=5)

# show recommendation in form of the list
as(recommendations, "list")

前面的代码生成了两个列表,每个列表对应一个用户。这些列表中的每个元素都是推荐的产品。模型预测,对于用户 1,010,应该推荐产品 prod_93 作为最推荐的产品,其次是 prod_79,依此类推。

# output generated by the model
[[1]]
[1] "prod_93" "prod_79" "prod_80" "prod_83" "prod_89"

[[2]]
[1] "prod_80" "prod_85" "prod_87" "prod_75" "prod_79"

Recommenderlab 是一个健壮的平台,经过优化以处理大型数据集。我们只需几行代码就能加载数据,学习模型,甚至在几乎没有任何时间的情况下向用户推荐产品。与使用矩阵分解开发的简单推荐引擎相比(与 recommenderlab 相比,代码行数很多),除了明显的性能差异之外,这也有很大的不同。

模型评估

我们已经成功准备了一个模型,并使用它来预测和向系统中的用户推荐产品。但我们对我们模型的准确性了解多少?为了评估准备好的模型,recommenderlab 提供了方便易用的工具。由于我们需要评估我们的模型,我们需要将其分为训练数据和测试数据集。此外,recommenderlab 要求我们说明用于测试的物品数量(它使用其余部分来计算误差)。

对于当前案例,我们将使用 500 个用户来准备一个评估模型。该模型将基于 90-10 的训练-测试数据集分割,其中 15 个项目用于测试集。

# Evaluation scheme
eval_scheme <- evaluationScheme(ratings_matrix[1:500],/
 method="split",train=0.9,given=15)

# View the evaluation scheme
eval_scheme

# Training model
training_recommender <- Recommender(getData(eval_scheme,/
 "train"), "UBCF")

# Preditions on the test dataset
test_rating <- predict(training_recommender,/
 getData(eval_scheme, "known"), type="ratings")

#Error 
error <- calcPredictionAccuracy(test_rating,/
 getData(eval_scheme, "unknown"))

error

我们使用基于 UBCF 算法的评估方案来训练我们的模型。从训练数据集中准备好的模型用于预测给定项目的评分。我们最终使用 calcPredictionAccuracy 方法来计算测试集中已知和未知成分预测评分之间的误差。对于我们的案例,我们得到以下输出:

模型评估

生成的输出提到了 RMSE(均方根误差)、MSE(均方误差)和 MAE(平均绝对误差)的值。特别是对于 RMSE,其值与正确值相差 1.162(请注意,由于采样、迭代等各种因素,值可能略有偏差)。当将不同 CF 算法的输出进行比较时,这种评估将更有意义。

为了评估 UBCF,我们使用 IBCF 作为比较。以下几行代码帮助我们准备一个基于 IBCF 的模型并测试评分,然后可以使用 calcPredictionAccuracy 工具进行比较:

# Training model using IBCF
training_recommender_2 <- Recommender(getData(eval_scheme,/
 "train"), "IBCF")

# Preditions on the test dataset
test_rating_2 <- predict(training_recommender_2,/
 getData(eval_scheme, "known"),/
 type="ratings")

error_compare <- rbind(calcPredictionAccuracy(test_rating,/
 getData(eval_scheme, "unknown")),/
 calcPredictionAccuracy(test_rating_2,/
 getData(eval_scheme, "unknown")))

rownames(error_compare) <- c("User Based CF","Item Based CF")

比较输出显示,UBCF 在 RMSE、MSE 和 MAE 的值方面优于 IBCF。

模型评估

同样,我们可以使用 recommenderlab 中可用的其他算法来测试/评估我们的模型。我们鼓励用户尝试更多,看看哪个算法在预测评分中的误差最小。

摘要

在本章中,我们继续追求在电子商务领域应用机器学习以提升销售和整体用户体验。上一章讨论了基于交易日志的推荐;在本章中,我们考虑了人为因素,并探讨了基于用户行为的推荐引擎。

我们首先理解了推荐系统及其分类为基于用户、基于内容和混合推荐系统。我们简要提到了与推荐引擎相关的一般性问题。然后我们深入探讨了协同过滤的具体细节,并讨论了预测和相似度度量的数学原理。在弄清楚基础知识后,我们从头开始构建自己的推荐引擎。我们使用矩阵分解,通过一个小型虚拟数据集逐步构建推荐引擎。然后我们转向使用 R 语言中流行的库 recommenderlab 来构建一个生产就绪的推荐引擎。我们使用基于用户的 CF 作为核心算法,在一个包含 5,000 个用户对 100 个产品进行评分的大数据集上构建推荐模型。我们通过使用 recommenderlab 的实用方法来评估我们的推荐模型来结束我们的讨论。

接下来的几章将从电子商务领域转向金融领域,并利用机器学习来处理一些更有趣的应用场景。

第五章. 信用风险检测与预测 – 描述性分析

在最后两章中,你看到了一些围绕零售和电子商务领域的问题。你现在知道如何从购物模式中检测和预测购物趋势,以及如何构建推荐系统。如果你还记得第一章,使用 R 和机器学习入门,机器学习的应用是多样化的,我们可以将相同的概念和技术应用于解决现实世界中广泛的各种问题。在这里,我们将解决一个全新的问题,但请记住你所学到的知识,因为你在之前学到的几个概念很快就会派上用场!

在接下来的几章中,我们将探讨与金融领域相关的新问题。我们将研究一家特定德国银行的客户,根据之前收集的一些数据,这些客户可能是银行的信用风险。我们将对数据进行描述性和探索性分析,以突出数据集中不同的潜在特征,并查看它们与信用风险的关系。在下一步,我们将使用机器学习算法和数据特征来构建预测模型,以检测和预测可能成为潜在信用风险的客户。你可能还记得,为了进行这项分析并保持不变,我们需要的是数据和算法。

你可能会惊讶地发现,风险分析是金融机构,包括银行、投资公司、保险公司和经纪公司等,最关注的领域之一。这些机构中的每一个通常都有专门的团队来解决围绕风险分析的问题。经常分析的风险例子包括信用风险、销售风险、与欺诈相关的风险等等。

在本章中,我们将重点关注以下主题:

  • 信用风险数据集的描述性分析

  • 信用风险问题的领域知识

  • 数据集特征的详细分析

  • 数据的探索性分析

  • 各种数据特征的可视化

  • 用于确定特征重要性的统计测试

总是记住,在解决任何机器学习问题之前,领域知识是必不可少的,否则我们可能会盲目地应用随机算法和技术,这可能导致结果不正确。

分析类型

在我们开始解决下一个挑战之前,了解不同类型的分析将非常有用,这些分析大致涵盖了数据科学领域。我们使用各种数据挖掘和机器学习技术来解决不同的数据问题。然而,根据技术的机制和最终结果,我们可以将分析大致分为四种不同类型,以下将进行解释:

  • 描述性分析:这是我们分析一些数据时使用的。我们从查看数据的各种属性开始,提取有意义的特征,并使用统计和可视化来了解已经发生的事情。描述性分析的主要目的是获得我们处理的数据类型的大致概念,并总结过去发生的事情。几乎超过 80%的商业分析都是描述性的。

  • 诊断分析:这有时与描述性分析结合在一起。这里的主要目标是深入数据以寻找特定的模式并回答诸如为什么发生这种情况等问题。通常,它涉及根本原因分析,以找到事情发生的原因以及在其发生过程中涉及的主要因素。有时回归建模等技术有助于实现这一点。

  • 预测分析:这是任何分析流程中的最后一步。一旦你构建了具有良好数据流且稳定的预测模型,你就可以构建利用这些模型并开始制定可能采取的行动以改善业务的系统。请记住,预测模型只能预测未来可能发生的事情,因为所有模型本质上都是概率性的,没有任何事情是 100%确定的。

  • 规范性分析:如果你处于构建了具有良好数据流且一致的预测模型,能够预测未来可能发生的事情的阶段,那么这是任何分析流程中的最后一步。然后你可以构建利用这些模型并开始制定可能采取的行动以改善业务的系统。请记住,你需要具有良好数据的运行预测模型和出色的反馈机制才能实现这一点。

大多数组织进行大量的描述性分析以及一定程度的预测分析。然而,由于不断变化的商业条件和数据流以及与之相关的问题,实施规范性分析非常困难,其中最常见的问题是数据清洗问题。在本章中,我们将先讨论描述性分析,然后再在下一章中转向预测分析,以解决与信用风险分析相关的问题。

我们接下来的挑战

在过去的几章中,我们已经处理了一些电子商务领域机器学习的有趣应用。对于接下来的两章,我们的主要挑战将是在金融领域。我们将使用数据分析与机器学习技术来分析一家德国银行的财务数据。这些数据将包含大量关于该银行客户的信息。我们将分两个阶段分析这些数据,包括描述性和预测性分析。

  • 描述性:在这里,我们将仔细研究数据和其各种属性。我们将进行描述性分析和可视化,以了解我们正在处理什么样的特征以及它们可能与信用风险有何关联。我们在这里处理的数据已经标记,我们将能够看到有多少客户是信用风险,有多少不是。我们还将仔细研究数据中的每个特征,了解其重要性,这将在下一步中很有用。

  • 预测性:在这里,我们将更多地关注用于预测建模的机器学习算法,利用我们在上一步中已经获取的数据来构建预测模型。我们将使用各种机器学习算法,并在预测客户是否可能成为潜在的信用风险时测试模型的准确性。我们将使用标记数据来训练模型,然后在几个数据实例上测试模型,将我们的预测结果与实际结果进行比较,以查看我们的模型表现如何。

预测信用风险的重要性对金融机构来说非常有用,例如经常处理客户贷款申请的银行。他们必须根据他们关于客户的信息来决定批准或拒绝贷款。如果他们有一个强大的机器学习系统已经建立,可以分析客户数据并指出哪些客户可能是信用风险,那么他们可以通过不批准这些客户的贷款来防止他们的业务遭受损失。

什么是信用风险?

我们从本章开始就一直在使用这个术语信用风险,你们中的许多人可能想知道这究竟是什么意思,即使你在阅读了上一节之后可能已经猜到了。在这里,我们将清楚地解释这个术语,以便你们在后续章节分析数据时,对数据和其特征的理解不会有任何问题。

信用风险的标准定义是因借款人未能按时偿还债务而产生的违约风险。这种风险由贷款人承担,因为贷款人将遭受本金及其利息的损失。

在我们的案例中,我们将处理一家作为金融组织向申请贷款的客户发放贷款的银行。因此,可能无法按时偿还贷款的客户将成为银行的信用风险。通过分析客户数据并在其上应用机器学习算法,银行将能够提前预测哪些客户可能是潜在的信用风险。这将有助于风险缓解,并通过不向可能成为银行信用风险的客户发放贷款来最小化损失。

获取数据

我们数据分析流程的第一步是获取数据集。我们实际上已经清理了数据,并为数据属性提供了有意义的名称,您可以通过打开german_credit_dataset.csv文件来检查这一点。您还可以从以下 URL 获取实际数据集,该数据集来自慕尼黑大学统计学系:www.statistik.lmu.de/service/datenarchiv/kredit/kredit_e.html

您可以下载数据,然后在同一目录下启动 R 并运行以下命令,以了解以下章节中我们将要处理的数据:

> # load in the data and attach the data frame
> credit.df <- read.csv("german_credit_dataset.csv", header = TRUE, sep = ",") 
> # class should be data.frame
> class(credit.df)
[1] "data.frame"
> 
> # get a quick peek at the data
> head(credit.df)

下图显示了数据的头六行。每一列表示数据集的一个属性。我们将在稍后更详细地关注每个属性。

获取数据

要获取有关数据集及其属性的详细信息,您可以使用以下代码片段:

> # get dataset detailed info
> str(credit.df)

上述代码将使您能够快速查看您正在处理的数据点的总数,这包括记录数、属性数以及每个属性的详细信息,例如属性名称、类型和一些属性值的样本,正如您可以在以下屏幕截图中所见。利用这一点,我们可以对不同的属性及其数据类型有一个很好的了解,以便我们知道对它们应用哪些转换,以及在描述性分析期间使用哪些统计方法。

获取数据

从前面的输出中,您可以看到我们的数据集共有 1000 条记录,其中每条记录处理与一位银行客户相关的数据点。每条记录都有各种数据点或属性来描述数据,我们为每条记录有 21 个属性。每个属性的数据类型和样本值也在之前的图像中显示。

注意

请注意,默认情况下,R 根据其值将int数据类型分配给变量,但我们在数据预处理阶段将根据它们的实际语义更改其中一些。

数据预处理

在本节中,我们将重点关注数据预处理,包括数据清理、转换以及如果需要的话进行归一化。基本上,我们在开始对数据进行任何分析之前执行操作以使数据准备就绪。

处理缺失值

当您处理的数据将包含缺失值时,这些缺失值通常在 R 中表示为NA。有几种方法可以检测它们,我们将在下面展示几种方法。请注意,您可以通过多种方式完成此操作。

> # check if data frame contains NA values
> sum(is.na(credit.df))
[1] 0
> 
> # check if total records reduced after removing rows with NA 
> # values
> sum(complete.cases(credit.df))
[1] 1000

is.na 函数非常有用,因为它有助于找出数据集中是否有任何元素具有 NA 值。还有另一种方法可以做到这一点,即使用 complete.cases 函数,它本质上返回一个逻辑向量,表示行是否完整以及是否有任何 NA 值。你可以检查与原始数据集相比总记录数是否减少,这样你就会知道数据集中有一些缺失值。幸运的是,在我们的案例中,我们没有缺失值。然而,在未来如果你处理缺失值,有各种方法可以处理。其中一些包括使用 complete.cases 等函数删除具有缺失值的行,或者用最频繁的值或平均值等填充它们。这也被称为缺失值插补,它取决于变量的属性和你要处理的领域。因此,我们不会在这个领域过多关注。

数据类型转换

我们之前提到,默认情况下,数据集的所有属性都已声明为 int,这是 R 中的数值类型,但在这个案例中并非如此,我们必须根据变量语义和值来更改它。如果你已经上过基础的统计学课程,你可能知道我们通常处理两种类型的变量:

  • 数值变量:这些变量的值具有某种数学意义。这意味着你可以对它们执行数学运算,例如加法、减法等。一些例子可以是人的年龄、体重等。

  • 分类变量:这些变量的值没有任何数学意义,你不能对它们执行任何数学运算。这个变量中的每个值都属于一个特定的类别或类别。一些例子可以是人的性别、工作等。

由于我们数据集中的所有变量默认都已转换为数值类型,我们只需要将分类变量从数值数据类型转换为因子,这是在 R 中表示分类变量的好方法。

我们数据集中的数值变量包括 credit.duration.monthscredit.amount 和年龄,我们不需要进行任何转换。然而,剩余的 18 个变量都是分类变量,我们将使用以下实用函数来转换它们的数据类型:

# data transformation
to.factors <- function(df, variables){
 for (variable in variables){
 df[[variable]] <- as.factor(df[[variable]])
 }
 return(df)
}

此函数将用于我们的现有数据框 credit.df,如下所示,以转换变量数据类型:

> # select variables for data transformation
> categorical.vars <- c('credit.rating', 'account.balance', 
+                       'previous.credit.payment.status',
+                       'credit.purpose', 'savings', 
+                       'employment.duration', 'installment.rate',
+                       'marital.status', 'guarantor', 
+                       'residence.duration', 'current.assets',
+                       'other.credits', 'apartment.type', 
+                       'bank.credits', 'occupation', 
+                       'dependents', 'telephone', 
+                       'foreign.worker')
> 
> # transform data types
> credit.df <- to.factors(df = credit.df, 
+                         variables=categorical.vars)
> 
> # verify transformation in data frame details
> str(credit.df)

现在,我们可以看到以下输出中转换后的数据类型所选分类变量的属性细节。你会注意到,在数据集的 21 个变量/属性中,有 18 个已经成功转换为分类变量。

数据类型转换

这标志着数据预处理步骤的结束,我们将现在深入分析我们的数据集。

注意

请注意,我们的一些数据集属性/特征有很多类别或类别,我们将在分析阶段进行更多的数据转换和特征工程,以防止我们的预测模型过度拟合,我们将在后面讨论这一点。

数据分析和转换

现在我们已经处理了我们的数据,它已准备好进行分析。我们将进行描述性和探索性分析,如前所述。我们将分析不同的数据集属性,并讨论它们的显著性、语义以及与信用风险属性的关系。我们将使用统计函数、列联表和可视化来描述所有这些。

除了这个,我们还将对我们数据集中的某些特征进行数据转换,特别是分类变量。我们将这样做是为了合并具有相似语义的类别,并通过与相似类别合并来删除比例非常小的类别。这样做的一些原因包括防止我们将在第六章中构建的预测模型过度拟合,即“信用风险检测与预测 – 预测分析”,将语义相似的类别联系起来,以及因为像逻辑回归这样的建模技术并不擅长处理具有大量类别的分类变量。我们首先将分析数据集中的每个特征/变量,然后根据需要执行任何转换。

构建分析工具

在我们开始分析之前,我们将开发一些实用函数,我们将使用这些函数来分析数据集特征。请注意,所有实用函数都定义在一个单独的.R文件中,称为descriptive_analytics_utils.R。您可以通过使用命令source('descriptive_analytics_utils.R')将所有函数加载到内存中或任何其他 R 脚本文件中,然后开始使用它们。我们现在将讨论这些实用函数。

现在,我们将讨论我们使用过的各种包。我们使用了一些包,如pastecsgmodels来获取特征的摘要统计信息以及构建列联表。gridExtraggplot2包被用于网格布局和构建可视化。如果您尚未安装它们,可以使用install.packages命令进行安装。接下来,按照以下代码片段加载这些包:

# load dependencies
library(gridExtra) # grid layouts
library(pastecs) # details summary stats
library(ggplot2) # visualizations
library(gmodels) # build contingency tables

stat.desc and summary functions for getting detailed and condensed summary statistics about the variable. The conventions for independent variables and dependent variables are denoted by indep.var and dep.var in the code segments that follow and in other functions later on.
# summary statistics
get.numeric.variable.stats <- function(indep.var, detailed=FALSE){
 options(scipen=100)
 options(digits=2)
 if (detailed){
 var.stats <- stat.desc(indep.var)
 }else{
 var.stats <- summary(indep.var)
 }

 df <- data.frame(round(as.numeric(var.stats),2))
 colnames(df) <- deparse(substitute(indep.var))
 rownames(df) <- names(var.stats)

 if (names(dev.cur()) != "null device"){
 dev.off()
 }
 grid.table(t(df))
}

接下来,我们将构建一些用于可视化数值变量的函数。我们将通过使用直方图/密度图箱线图来描述属性分布来完成这项工作。

# visualizations
# histograms\density
visualize.distribution <- function(indep.var){
 pl1 <- qplot(indep.var, geom="histogram", 
 fill=I('gray'), binwidth=5,
 col=I('black'))+ theme_bw()
 pl2 <- qplot(age, geom="density",
 fill=I('gray'), binwidth=5, 
 col=I('black'))+ theme_bw()

 grid.arrange(pl1,pl2, ncol=2)
}

# box plots
visualize.boxplot <- function(indep.var, dep.var){
 pl1 <- qplot(factor(0),indep.var, geom="boxplot", 
 xlab = deparse(substitute(indep.var)), 
 ylab="values") + theme_bw()
 pl2 <- qplot(dep.var,indep.var,geom="boxplot",
 xlab = deparse(substitute(dep.var)),
 ylab = deparse(substitute(indep.var))) + theme_bw()

 grid.arrange(pl1,pl2, ncol=2)
}

我们使用了ggplot2包中的qplot函数来构建我们将看到的可视化。现在,我们将把注意力转向分类变量。我们将从一个函数开始,该函数用于获取任何分类变量的汇总统计信息。

# summary statistics
get.categorical.variable.stats <- function(indep.var){

 feature.name = deparse(substitute(indep.var))
 df1 <- data.frame(table(indep.var))
 colnames(df1) <- c(feature.name, "Frequency")
 df2 <- data.frame(prop.table(table(indep.var)))
 colnames(df2) <- c(feature.name, "Proportion")

 df <- merge(
 df1, df2, by = feature.name
 )
 ndf <- df[order(-df$Frequency),]
 if (names(dev.cur()) != "null device"){
 dev.off()
 }
 grid.table(ndf)
}

前面的函数将总结分类变量,并讨论其中有多少类别或类别以及一些其他细节,如频率和比例。如果你还记得,我们之前提到我们还将展示分类变量与类/依赖变量credit.risk的关系。以下函数将帮助我们以列联表的形式实现相同的目标:

# generate contingency table
get.contingency.table <- function(dep.var, indep.var, 
 stat.tests=F){
 if(stat.tests == F){
 CrossTable(dep.var, indep.var, digits=1, 
 prop.r=F, prop.t=F, prop.chisq=F)
 }else{
 CrossTable(dep.var, indep.var, digits=1, 
 prop.r=F, prop.t=F, prop.chisq=F,
 chisq=T, fisher=T)
 }
}

我们还将构建一些用于展示可视化的函数。我们将使用以下函数通过条形图可视化分类变量的分布:

# visualizations
# barcharts
visualize.barchart <- function(indep.var){
 qplot(indep.var, geom="bar", 
 fill=I('gray'), col=I('black'),
 xlab = deparse(substitute(indep.var))) + theme_bw()
}

我们将使用 mosaic 图来展示之前提到的列联表的可视化,使用以下函数:

# mosaic plots
visualize.contingency.table <- function(dep.var, indep.var){
 if (names(dev.cur()) != "null device"){
 dev.off()
 }
 mosaicplot(dep.var ~ indep.var, color=T, 
 main = "Contingency table plot")
}

现在我们已经构建了所有必要的工具,接下来我们将开始分析数据,并在下一节进行详细说明。

分析数据集

在本节中,我们将分析数据集的每个特征,并在必要时以汇总统计、关系、统计检验和可视化的形式展示我们的分析。我们将用表格表示对每个变量进行的必要分析。需要记住的一个重要点是,代码中用dep.var表示的依赖特征始终是credit.rating,因为这是依赖于其他特征的变量;这些特征是独立变量,在表格和图中通常用indep.var表示。

我们将对一些重要特征进行详细分析和转换,这些特征具有很大的意义,特别是具有大量类别的数据特征,这样我们可以清楚地理解数据分布以及它们在数据转换时的变化。对于剩余的特征,我们不会过多关注汇总统计,而更强调通过转换和它们与依赖变量credit.rating的关系来进行特征工程。

现在,我们将附加数据框,以便我们可以轻松访问单个特征。你可以使用以下代码片段来完成:

> # access dataset features directly
> attach(credit.df)

现在,我们将从依赖变量credit.risk开始分析,也称为数据集中的类变量,我们将在下一章尝试预测它。

以下代码片段帮助我们获取该特征的所需汇总统计信息:

> # credit.rating stats
> get.categorical.variable.stats(credit.rating)
> # credit.rating visualizations
> visualize.barchart(credit.rating)

以下可视化告诉我们credit.rating有两个类别,10,并提供了必要的统计数据。基本上,信用评分为1的客户是可信赖的,而评分为0的客户是不可信赖的。我们还从条形图中观察到,在银行中,有信用评级的客户比例相对于其他客户来说显著较高。

分析数据集

接下来,我们将分析account.balance特征。基本上,这个属性表示客户当前账户的余额。

我们将首先使用以下代码片段获取摘要统计信息并绘制条形图。为了更好地理解,我们将一起包含输出结果。

> # account.balance stats and bar chart
> get.categorical.variable.stats(account.balance)
> visualize.barchart(account.balance)

从以下可视化中,你可以看到account.balance有四个不同的类别,并且每个类别都有一些特定的语义,我们将在下面讨论。

分析数据集

从前面的输出中,你可以看到account.balance有四个不同的类别,并且每个类别都有一些语义,如下定义。货币 DM 表示德国的旧货币名称——德国马克。

这四个类别表示以下主要语义或至少持有一年以上的支票账户:

  • 1: 没有运行中的银行账户

  • 2: 没有余额或借记

  • 3: 余额小于200 DM

  • 4: 余额>=200 DM

货币 DM 表示德国马克,德国的旧货币名称。在这里,我们将进行一些特征工程,并将类别 3 和 4 合并在一起,以表示账户中有正余额的客户。我们这样做是因为类别 3 的比例相对于其他类别来说很小,我们不希望在不必要的情况下为每个特征保留太多的类别,除非它们是关键的。我们将通过以下代码片段实现这一点。

首先,我们将加载进行此操作所需的包。如果你还没有安装该包,请使用命令install.packages("car")进行安装。

> #load dependencies
> library(car)

现在,我们将重新编码必要的类别,如下所示:

> # recode classes and update data frame
> new.account.balance <- recode(account.balance,
+                           "1=1;2=2;3=3;4=3")
> credit.df$account.balance <- new.account.balance

现在,我们将使用之前讨论的列联表来查看new.account.balancecredit.rating之间的关系,并使用以下代码片段通过 mosaic plot 进行可视化。我们还将进行一些统计测试,我将在稍后简要解释。

> # contingency table and mosaic plot 
> get.contingency.table(credit.rating, new.account.balance, 
 stat.tests=T)
> visualize.contingency.table(credit.rating, new.account.balance)

在以下图中,你现在可以看到account.balance的各个类别与credit.rating在表格和图中的分布情况。一个有趣的现象是,90%的账户有资金的客户不是潜在的信用风险,这听起来是合理的。

分析数据集

我们在这里还执行了两个统计测试:卡方测试和费舍尔测试,这两个测试都是列联表中广泛用于假设检验的相关测试。详细讨论这些测试中涉及的统计计算超出了本章的范围。我将用一种易于理解的方式来说明。通常,我们从一个零假设开始,即之前描绘的两个变量之间不存在关联或关系,以及一个备择假设,即两个变量之间存在关联或关系的可能性。如果从测试中获得 p 值小于或等于0.05,那么我们才能拒绝零假设,接受备择假设。在这种情况下,您可以清楚地看到,这两个测试都给出了< 0.05的 p 值,这肯定有利于备择假设,即credit.ratingaccount.balance之间存在某种关联。这类测试在构建统计模型时非常有用。您可以在互联网或任何统计学书籍中查找前面的测试,以深入了解 p 值的意义及其工作原理。

注意

请注意,从现在开始,我们将只展示每个特征最重要的分析结果。然而,您始终可以使用我们之前解释过的函数尝试获取各种分析技术的相关信息。对于列联表,使用get.contingency.table()函数。可以通过在get.contingency.table()函数中将stat.tests参数设置为TRUE来执行统计测试。您还可以使用visualize.contingency.table()函数来查看错综图。

现在我们将查看credit.duration.months,这表示信用期限(按月)。这是一个数值变量,分析将与其他分类变量略有不同。

> # credit.duration.months analysis
> get.numeric.variable.stats(credit.duration.months)

我们可以从以下图中看到相同的内容:

分析数据集

我们看到的值是按月计算的,我们得到这个特征的典型汇总统计量,包括平均值、中位数和四分位数。我们现在将使用histograms/density图和boxplots来可视化这个特征的值分布。

> # histogram\density plot
> visualize.distribution(credit.duration.months)

我们现在以箱线图的形式可视化相同的内容,包括显示与credit.rating相关的下一个。

> # box plot
> visualize.boxplot(credit.duration.months, credit.rating)

有趣的是,从下面的图中我们可以看到,信用评级差的客户的平均信用期限高于信用评级好的客户。如果我们假设许多信用期限长的客户违约了,这似乎是合理的。

分析数据集

接下来,我们将查看下一个变量previous.credit.payment.status,这表示客户在支付其先前信用时的状态。这是一个分类变量,我们得到如下的统计数据:

> # previous.credit.payment.status stats and bar chart
> get.categorical.variable.stats(previous.credit.payment.status)
> visualize.barchart(previous.credit.payment.status)

这给我们以下表格和条形图,展示了数据分布:

分析数据集

这些类别表示以下主要语义:

  • 0: 犹豫不决的支付

  • 1: 运行账户有问题

  • 2: 没有剩余的先前信用

  • 3: 这家银行的当前信用额度没有问题

  • 4: 在这家银行还清了之前的信用

我们将对这个功能应用以下变换,因此新的语义将是:

  • 1: 支付存在一些问题

  • 2: 所有信用已支付

  • 3: 没有问题,并且只在银行支付了信用

我们将在以下代码片段中执行变换:

> # recode classes and update data frame
> new.previous.credit.payment.status <- 
 recode(previous.credit.payment.status,
+                                           "0=1;1=1;2=2;3=3;4=3")
> credit.df$previous.credit.payment.status <- 
 new.previous.credit.payment.status

变换后特征的列联表如下获得:

> # contingency table
> get.contingency.table(credit.rating,
 new.previous.credit.payment.status)

从以下表格中我们可以观察到,拥有良好信用评级的最大人数已经没有问题地支付了之前的信用,而没有良好信用评级的人则支付上存在一些问题,这是有道理的!

分析数据集

我们接下来要分析的功能是 credit.purpose,它表示信用金额的目的。这也是一个分类变量,我们得到其摘要统计信息,并绘制以下条形图来显示其各种类别的频率:

> # credit.purpose stats and bar chart
> get.categorical.variable.stats(credit.purpose)
> visualize.barchart(credit.purpose)

这给我们以下表格和条形图,展示了数据分布:

分析数据集

我们观察到,仅针对这个功能就有惊人的 11 个类别。除此之外,我们还观察到,与前 5 个类别相比,有几个类别的比例极低,而且类别标签 7甚至没有出现在数据集中!这正是我们需要通过将一些这些类别分组在一起进行特征工程的原因,就像我们之前做的那样。

这些类别表示以下主要语义:

  • 0: 其他

  • 1: 新车

  • 2: 二手车

  • 3: 家具物品

  • 4: 收音机或电视

  • 5: 家用电器

  • 6: 维修

  • 7: 教育

  • 8: 假期

  • 9: 再培训

  • 10: 商业

我们将通过结合一些现有的类和变换后的新语义来转换这个功能,变换后的语义如下:

  • 1: 新车

  • 2: 二手车

  • 3: 与家庭相关的物品

  • 4: 其他

我们将通过以下代码片段来完成这项工作:

> # recode classes and update data frame
> new.credit.purpose <- recode(credit.purpose,"0=4;1=1;2=2;3=3;
+                                              4=3;5=3;6=3;7=4;
+                                              8=4;9=4;10=4")
> credit.df$credit.purpose <- new.credit.purpose

通过以下代码片段获得变换后特征的列联表:

> # contingency table
> get.contingency.table(credit.rating, new.credit.purpose)

根据以下表格,我们看到拥有家庭相关物品或其他物品信用目的的客户在不良信用评级类别中的比例似乎最高:

分析数据集

我们接下来要分析的功能是 credit.amount,它基本上表示客户从银行请求的信用金额。这是一个数值变量,我们使用以下代码来获取摘要统计信息:

> # credit.amount analysis
> get.numeric.variable.stats(credit.amount)

分析数据集

我们可以看到一些正常统计量,例如平均信贷金额为 3270 DM,中位数约为 3270 DM。现在我们将使用直方图和密度图来可视化前面数据的分布,如下所示:

> # histogram\density plot
> visualize.distribution(credit.amount)

这将为我们提供credit.amount的直方图和密度图,您可以在以下图中看到它是一个右偏分布:

分析数据集

接下来,我们将使用以下代码片段使用箱线图来可视化数据,以查看数据分布及其与 credit.rating 的关系:

> # box plot
> visualize.boxplot(credit.amount, credit.rating)

这生成了以下箱线图,您可以在箱线图中清楚地看到分布的右偏,由箱线中的许多点表示。我们还发现了一个有趣的见解,即对于要求更高信贷金额的客户,其信贷评分的中位数是差的,这在许多客户可能未能支付所需全部还款以偿还信贷金额的情况下似乎很合理。

分析数据集

现在您已经了解了如何对分类和数值变量进行描述性分析,从现在开始,我们将不会展示每个特征的所有不同分析技术的输出。如果您有兴趣深入了解数据,请随意使用我们之前使用的函数对剩余变量进行实验,以获取汇总统计量和可视化结果!

下一个特征是储蓄,它是一个具有以下五个类别语义的分类变量:

  • 1: 没有储蓄

  • 2: < 100 DM

  • 3: 在 [100, 499] DM 之间

  • 4: 在 [500, 999] DM 之间

  • 5: >= 1000 DM

该特征表示客户拥有的平均储蓄/股票金额。我们将将其转换为以下四个类别标签:

  • 1: 没有储蓄

  • 2: < 100 DM

  • 3: 在 [100, 999] DM 之间

  • 4: >= 1000 DM

我们将使用以下代码片段:

> # feature: savings - recode classes and update data frame
> new.savings <- recode(savings,"1=1;2=2;3=3;
+                                4=3;5=4")
> credit.df$savings <- new.savings

现在我们将使用以下代码分析储蓄与 credit.rating 之间的关系,以生成列联表:

> # contingency table
> get.contingency.table(credit.rating, new.savings)

这生成了以下列联表。观察表值后,很明显,在具有不良信贷评分的客户中,没有储蓄的人的比例最高,这并不令人惊讶!这个数字对于具有良好信贷评分的客户来说也很高,因为与不良信贷评分的总记录数相比,良好信贷评分的总记录数也较高。然而,我们还可以看到,具有 > 1000 DM 和良好信贷评分的人的比例相对于在储蓄账户中同时具有不良信贷评分和 > 1000 DM 的人的比例相当高。

分析数据集

现在,我们将查看名为 employment.duration 的特征,它是一个表示客户至今为止被雇佣时间的分类变量。该特征的五个类别语义如下:

  • 1: 失业

  • 2: < 1

  • 3: 在 [1, 4] 年之间

  • 4: 在 [4, 7] 年之间

  • 5: >= 7

我们将将其转换为以下四个类别:

  • 1: 未就业或< 1

  • 2: 在 [1,4] 年之间

  • 3: 在 [4,7] 年之间

  • 4: >= 7

我们将使用以下代码:

> # feature: employment.duration - recode classes and update data frame
> new.employment.duration <- recode(employment.duration,
+                                   "1=1;2=1;3=2;4=3;5=4")
> credit.df$employment.duration <- new.employment.duration

现在我们使用列联表分析其关系,如下所示:

> # contingency table
> get.contingency.table(credit.rating, new.employment.duration)

从以下表中我们可以观察到,没有或显著低就业年数和不良信用评级的客户比例远高于具有良好信用评级的类似客户。在employment.duration特征中,值 1 表示失业或就业时间少于1年的人。在 300 人中有 93 人具有不良信用评级,这给出了 31%的比例,与具有良好信用评级的客户相同指标相比,这一比例要高得多,后者为 700 客户中的 141 人,或 20%。

分析数据集

我们现在继续分析下一个特征,名为installment.rate,这是一个具有以下语义的分类变量:

  • 1: >=35%

  • 2: 在 [25, 35]% 之间

  • 3: 在 [20, 25]% 之间

  • 4: 四个类别的< 20%

对于这个属性的原始元数据中信息不多,因此存在一些歧义,但我们假设它表示客户工资中用于支付信用贷款月度分期付款的百分比。我们在这里不会进行任何转换,所以我们将直接进入关系分析。

> # feature: installment.rate - contingency table and statistical tests
> get.contingency.table(credit.rating, installment.rate, 
+                      stat.tests=TRUE)

> 0.05, thus ruling the null hypothesis in favor of the alternative. This tells us that these two variables do not have a significant association between them and this feature might not be one to consider when we make feature sets for our predictive models. We will look at feature selection in more detail in the next chapter.

分析数据集

我们接下来要分析的是marital.status变量,它表示客户的婚姻状况,是一个分类变量。它有四个类别,具有以下语义:

  • 1: 男性离婚

  • 2: 男性单身

  • 3: 男性已婚/丧偶

  • 4: 女性

我们将根据以下语义将其转换为三个类别:

  • 1: 男性离婚/单身

  • 2: 男性已婚/丧偶

  • 3: 女性

我们将使用以下代码:

> # feature: marital.status - recode classes and update data frame
> new.marital.status <- recode(marital.status, "1=1;2=1;3=2;4=3")
> credit.df$marital.status <- new.marital.status

我们现在通过以下代码片段构建一个列联表来观察marital.statuscredit.rating之间的关系:

> # contingency table
> get.contingency.table(credit.rating, new.marital.status)

从以下表中,我们注意到,对于信用评级良好的客户,单身男性与已婚男性的比例为1:2,而对于信用评级较差的客户,这一比例接近1:1。这意味着也许已婚男性更倾向于按时偿还信用债务?这可能是一个可能性,但请记住,相关性并不总是意味着因果关系。

分析数据集

统计测试的 p 值给出了0.01的值,这表明特征之间可能存在某种关联。

下一个特征是担保人,这表示客户是否有任何其他债务人或担保人。这是一个有三个类别的分类变量,具有以下语义:

  • 1: 无

  • 2: 共同借款人

  • 3: 担保人

我们将它们转换成以下语义的两个变量:

  • 1: 否

  • 2: 是

对于转换,我们使用以下代码片段:

> # feature: guarantor - recode classes and update data frame
> new.guarantor <- recode(guarantor, "1=1;2=2;3=2")
> credit.df$guarantor <- new.guarantor

对此结果进行统计分析得到 p 值为 1,这个值远大于 0.05,因此拒绝零假设,暗示担保人与 credit.rating 之间可能没有关联。

提示

您也可以使用直接函数而不是每次都调用 get.contingency.table(…) 函数来运行统计分析。对于费舍尔精确检验,调用 fisher.test(credit.rating, guarantor),对于皮尔逊卡方检验,调用 chisq.test(credit,rating, guarantor)。您可以将担保人替换为任何其他独立变量以执行这些测试。

下一个特征是 residence.duration,表示客户在其当前地址居住的时间。

这是一个分类变量,以下是对四个类别的语义描述:

  • 1: < 1

  • 2: 在 [1,4] 年之间

  • 3: 在 [4,7] 年之间

  • 4: >= 7

我们将不对这些数据进行转换,而是直接进行统计分析,以查看该特征是否与 credit,rating 有任何关联。根据之前的提示,使用 fisher.testchisq.test 函数都给出了 p 值为 0.9,这个值显著大于 0.05,因此它们之间没有显著关系。我们将在这里展示这两个统计测试的输出,以便您了解它们所描述的内容。

> # perform statistical tests for residence.duration
> fisher.test(credit.rating, residence.duration)
> chisq.test(credit.rating, residence.duration)

您可以从以下输出中看到,我们从之前提到的两个测试中都得到了相同的 p 值:

分析数据集

我们现在将重点转向 current.assets,这是一个分类变量,以下是对四个类别的语义描述:

  • 1: 没有资产

  • 2: 汽车/其他

  • 3: 人寿保险/储蓄合同

  • 4: 房地产/土地所有权

我们将不对这些数据进行转换,而是直接运行相同的统计分析,以检查它是否与 credit.rating 有任何关联。我们得到的 p 值为 3 x 10-5,这个值显然小于 0.05,因此我们可以得出结论,备择假设成立,即变量之间存在某种关联。

我们将要分析的下一个变量是 age。这是一个数值变量,我们将如下获取其摘要统计信息:

> # age analysis
> get.numeric.variable.stats(age)

输出

分析数据集

我们可以观察到,客户平均年龄为 35.5 岁,中位数为 33 岁。为了查看特征分布,我们将使用以下代码片段使用直方图和密度图进行可视化:

> # histogram\density plot
> visualize.distribution(age)

从以下图表中我们可以观察到,分布是右偏分布,大多数客户年龄在 25 至 45 岁之间:

分析数据集

我们现在将通过箱线图可视化来观察 agecredit.rating 之间的关系,如下所示:

> # box plot
> visualize.boxplot(age, credit.rating)

从以下图表中可以看出,右偏斜在箱线图中通过我们在极端端点看到的点簇明显可辨。从右边的图表中我们可以得出的有趣观察是,信用评级差的客户的平均年龄低于信用评级好的客户。

分析数据集

这种关联的一个原因可能是,一些年轻人由于尚未稳定就业而未能偿还他们从银行借取的信用贷款。但,再次强调,这只是一个假设,除非我们查看每个客户的完整背景,否则我们无法验证。

接下来,我们将查看特征 other.credits,它具有以下三个类别的语义:

  • 1: 在其他银行

  • 2: 在商店

  • 3: 没有更多信用

这个特征表示客户是否在其他地方有任何其他待处理的信用。我们将将其转换为两个类别,具有以下语义:

  • 1: 是

  • 2: 否

我们将使用以下代码片段:

> # feature: other.credits - recode classes and update data frame
> new.other.credits <- recode(other.credits, "1=1;2=1;3=2")
> credit.df$other.credits <- new.other.credits

对新转换的特征进行统计分析,我们得到一个 p 值为 0.0005,这 < 0.05,因此支持备择假设而非零假设,表明这个特征与 credit.rating 之间存在某种关联,假设没有其他因素的影响。

下一个特征 apartment.type 是一个具有以下语义的三个类别的类别变量:

  • 1: 免费公寓

  • 2: 租赁公寓

  • 3: 拥有并居住的公寓

这个特征基本上表示客户居住的公寓类型。我们不会对这个变量进行任何转换,并将直接进行统计分析。这两个测试都给出了 p 值 < 0.05,这表明 apartment.typecredit.rating 之间存在某种关联,假设没有其他因素影响。

现在我们将查看特征 bank.credits,这是一个具有以下语义的类别变量,对于四个类别:

  • 1: 一个

  • 2: 两个/三个

  • 3: 四个/五个

  • 4: 六个或更多

这个特征表示客户从该银行(包括当前的一个)借取的信用贷款总数。我们将将其转换为一个二元特征,具有以下两个类别的语义:

  • 1: 一个

  • 2: 超过一个

我们将使用以下代码:

> # feature: bank.credits - recode classes and update data frame
> new.bank.credits <- recode(bank.credits, "1=1;2=2;3=2;4=2")
> credit.df$bank.credits <- new.bank.credits

对这个转换后的特征进行统计分析,我们得到一个 p 值为 0.2,这比 0.05 大得多,因此我们知道零假设仍然有效,即 bank.creditscredit.rating 之间没有显著的关联。有趣的是,如果你使用 bank.credits 的未转换版本进行统计分析,你会得到一个更高的 p 值为 0.4,这表明没有显著的关联。

下一个特征是occupation,显然表示客户的当前工作。这是一个分类变量,其四个类别具有以下语义:

  • 1: 失业且无永久居留权

  • 2: 无技能且拥有永久居留权

  • 3: 技能工人/次要公务员

  • 4: 执行/自雇/高级公务员

我们不会对这个特征应用任何转换,因为每个类别在特征上都有其独特的特点。因此,我们将直接进行统计分析,这两个测试得到的 p 值为0.6,这肯定大于0.05,因此零假设成立,即两个特征之间没有显著的关系。

我们现在将查看下一个特征dependents,这是一个具有以下语义的两个类别标签的分类变量:

  • 1: 零到两个

  • 2: 三或更多

这个特征表示作为客户依赖者的总人数。我们不会对这个特征应用任何转换,因为它已经是一个二进制变量。对这个特征进行统计分析得到的 p 值为1,这告诉我们这个特征与credit.rating没有显著的关系。

接下来是特征telephone,这是一个具有两个类别的二进制分类变量,以下语义表示客户是否有电话:

  • 1: 否

  • 2: 是

由于这是一个二进制变量,我们在这里不需要进行任何进一步的转换。因此,我们继续进行统计分析,得到的 p 值为0.3,这大于0.05,因此零假设被拒绝,表明电话与credit.rating之间没有显著的关联。

数据集中的最后一个特征是foreign.worker,这是一个具有两个类别的二进制分类变量,以下语义表示客户是否是外国工人:

  • 1: 是

  • 2: 否

我们没有进行任何转换,因为这个变量已经是二进制变量,具有两个不同的类别,所以我们继续进行统计分析。这两个测试都给出了小于 0.05 的 p 值,这可能表明这个变量与credit.rating有显著的关系。

通过这一点,我们完成了对数据集的数据分析阶段。

保存转换后的数据集

我们已经对多个分类变量进行了大量的特征工程,使用数据转换,由于我们将在转换后的特征集上构建预测模型,我们需要将这个数据集单独存储到磁盘上。我们使用以下代码片段来完成同样的任务:

> ## Save the transformed dataset
> write.csv(file='credit_dataset_final.csv', x = credit.df, 
+           row.names = F)

下次开始构建预测模型时,我们可以直接将上述文件加载到 R 中,我们将在下一章中介绍这一点。

下一步

我们已经分析了我们的数据集,进行了必要的特征工程和统计测试,构建了可视化,并获得了关于信用风险分析和银行在分析客户时考虑哪些特征的大量领域知识。我们详细分析数据集中每个特征的原因是为了让你了解银行在分析客户信用评级时考虑的每个特征。这是为了让你对领域知识有良好的理解,并帮助你熟悉未来对任何数据集进行探索性和描述性分析的技术。那么接下来是什么?现在真正有趣的部分来了;从这些数据中构建特征集,并将它们输入到预测模型中,以预测哪些客户可能是潜在的信用风险,哪些不是。如前所述,这有两个步骤:数据和算法。实际上,我们将更进一步,说有特征集和算法将帮助我们实现主要目标。

特征集

数据集基本上是一个包含多个观测记录的文件,其中每个元组或记录代表一组完整的观测,而列则是该观测中特定的属性或特征,它们讨论了特定的特性。在预测分析中,通常数据集中有一个属性或特征,其类别或类别需要被预测。这个变量在我们的数据集中是credit.rating,也称为因变量。所有其他依赖这些特征的变量都是自变量。这些特征的组合形成了一个特征向量,也常被称为特征集。确定我们应该考虑哪些特征集用于预测模型有多种方法,并且你将会看到,对于任何数据集,都没有一个固定的特征集。它根据特征工程、我们构建的预测模型类型以及基于统计测试的特征的重要性而不断变化。

特征集中的每个属性被称为特征或属性,在统计学中它们也被称为独立变量或解释变量。特征可以是各种类型,正如我们在数据集中所看到的。我们可以有具有多个类别的分类特征,具有两个类别的二元特征,有序特征,它们基本上是分类特征,但具有内在的某种顺序(例如,低、中、高),以及可能是整数或实数值的数值特征。特征在构建预测模型中非常重要,而且往往数据科学家会花费大量时间构建完美的特征集,以极大地提高预测模型的准确性。这就是为什么除了了解机器学习算法之外,领域知识也非常重要。

机器学习算法

一旦我们准备好了特征集,我们就可以开始使用预测模型来使用它们,并开始根据客户特征预测客户的信用评级。要记住的一个重要的事情是,这是一个迭代过程,我们必须根据从预测模型获得的输出和反馈来不断修改我们的特征集,以进一步提高它们。在本节中简要解释了与我们场景相关的几种方法,它们属于监督机器学习算法的类别。

  • 线性分类算法:这些算法通过线性函数进行分类,通过执行特征集和与之相关的某些权重的点积为每个类别分配分数。预测的类别是得分最高的类别。特征集的最优权重以各种方式确定,并且根据所选算法而有所不同。一些算法的例子包括逻辑回归、支持向量机和感知器。

  • 决策树:在这里,我们使用决策树作为预测模型,将数据点中的各种观察结果映射到我们要预测的记录的观察类别。决策树就像一个流程图结构,其中每个内部非叶节点表示对特征的检查,每个分支代表该检查的结果,每个终端叶节点包含一个我们最终预测的类别标签。

  • 集成学习方法:这包括使用多个机器学习算法来获得更好的预测模型。一个例子是随机森林分类算法,它在模型训练阶段使用决策树的集成,并在每个阶段从决策树的集成中获取多数输出决策作为其输出。这倾向于减少过度拟合,这在使用决策树时经常发生。

  • 提升算法:这也是监督学习算法家族中的一种集成学习技术。它由训练多个弱分类模型并从它们中学习的过程组成,然后再将它们添加到一个比它们更强的最终分类器中。在添加分类器时遵循一种加权的做法,这基于它们的准确性。未来的弱分类器更多地关注先前被错误分类的记录。

  • 神经网络:这些算法受到生物神经网络的启发,生物神经网络由相互连接的神经元系统组成,它们相互交换信息。在预测建模中,我们处理人工神经网络,它由相互连接的节点组组成。每个节点包含一个函数,通常是一个数学函数(即,Sigmoid 函数),并且与它相关联的权重是自适应的,根据输入到节点的数据不断变化,并在多次迭代(也称为时期)中不断检查从输出获得的错误。

在下一章构建预测模型时,我们将介绍这些算法中的几个。

摘要

恭喜你坚持看到本章的结尾!到目前为止,你已经通过本章学习到了一些重要内容。你现在对金融领域最重要的一个领域有了概念,那就是信用风险分析。除此之外,你还获得了关于银行如何分析客户以确定其信用评级以及他们考虑的哪些属性和特征的显著领域知识。对数据集的描述性和探索性分析也让你了解了当你只有一个问题要解决和一个数据集时,如何从头开始工作!你现在知道如何进行特征工程,使用ggplot2构建精美的出版物质量可视化,以及进行统计测试以检查特征关联。最后,我们通过讨论特征集来结束我们的讨论,并对几个监督机器学习算法进行了简要介绍,这些算法将帮助我们下一步预测信用风险。最有趣的部分还在后面,所以请继续关注!

第六章:信用风险检测与预测 - 预测分析

在上一章中,我们在金融领域覆盖了很多内容,我们接受了检测和预测可能成为潜在信用风险的银行客户的挑战。现在,我们对关于信用风险分析的主要目标有了很好的了解。此外,从数据集及其特征的描述性分析中获得的大量知识将对预测分析有用,正如我们之前提到的。

在本章中,我们将穿越预测分析的世界,这是机器学习和数据科学的核心。预测分析包括几个方面,如分类算法、回归算法、领域知识和业务逻辑,它们结合在一起构建预测模型并从数据中提取有用见解。我们在上一章的末尾讨论了各种机器学习算法,这些算法将适用于解决我们的目标,当我们使用给定的数据集和这些算法构建预测模型时,我们将在本章中探索其中的一些。

对预测分析的一个有趣的观点是,它为希望在未来加强其业务和利润的组织提供了很多希望。随着大数据的出现,现在大多数组织拥有的数据比他们能分析的数据还要多!虽然这是一个大挑战,但更严峻的挑战是从这些数据中选择正确的数据点并构建能够正确预测未来结果的预测模型。然而,这种方法有几个注意事项,因为每个模型基本上是基于公式、假设和概率的数学函数。此外,在现实世界中,条件和场景不断变化和演变,因此我们必须记住,今天构建的预测模型明天可能完全过时。

许多怀疑者认为,由于环境的不断变化,计算机模仿人类预测结果非常困难,甚至人类也无法预测,因此所有统计方法仅在理想假设和条件下才有价值。虽然这在某种程度上是正确的,但有了正确的数据、正确的思维方式和应用正确的算法和技术,我们可以构建稳健的预测模型,这些模型肯定可以尝试解决传统或蛮力方法无法解决的问题。

预测建模是一个困难的任务,尽管可能存在许多挑战,结果可能总是难以获得,但我们必须带着一点盐来接受这些挑战,并记住著名统计学家乔治·E·P·博克斯的名言:“本质上所有模型都是错误的,但其中一些是有用的!”这一点在我们之前讨论的内容中得到了充分的证实。始终记住,预测模型永远不会达到 100%完美,但如果它是基于正确的原则构建的,它将非常有用!

在本章中,我们将重点关注以下主题:

  • 预测分析

  • 如何预测信用风险

  • 预测建模中的重要概念

  • 获取数据

  • 数据预处理

  • 特征选择

  • 使用逻辑回归建模

  • 使用支持向量机建模

  • 使用决策树建模

  • 使用随机森林建模

  • 使用神经网络建模

  • 模型比较和选择

预测分析

在上一章中,我们已经就预测分析进行了相当多的讨论,以向您提供一个关于其含义的概述。在本节中,我们将更详细地讨论它。预测分析可以定义为机器学习领域的一个子集,它包括基于数据科学、统计学和数学公式的广泛监督学习算法,这些算法使我们能够使用这些算法和已经收集的数据来构建预测模型。这些模型使我们能够根据过去的观察预测未来可能发生的事情。结合领域知识、专业知识和商业逻辑,分析师可以使用这些预测进行数据驱动决策,这是预测分析最终的结果。

我们在这里讨论的数据是过去已经观察到的数据,并且在一段时间内为了分析而收集的数据。这些数据通常被称为历史数据或训练数据,它们被输入到模型中。然而,在预测建模方法中,我们大多数时候并不直接输入原始数据,而是使用从数据中提取的特征,这些特征经过适当的转换。数据特征与监督学习算法结合形成一个预测模型。在当前获得的这些数据可以输入到这个模型中,以预测正在观察的结果,并测试模型在各个准确度指标方面的性能。在机器学习领域,这种数据被称为测试数据。

我们在本章中执行预测分析将遵循的分析管道是一个标准流程,以下步骤简要解释:

  1. 获取数据:在这里,我们获取构建预测模型所需的数据集。我们将对数据集进行一些基本的描述性分析,这已经在上一章中介绍过。一旦我们有了数据,我们就会进入下一步。

  2. 数据预处理:在这个步骤中,我们执行数据转换,例如更改数据类型、特征缩放和归一化,如果需要,以准备数据供模型训练。通常这个步骤是在数据集准备步骤之后执行的。然而,在这种情况下,最终结果是一样的,因此我们可以按任何顺序执行这些步骤。

  3. 数据集准备:在这个步骤中,我们使用一些比例,如 70:30 或 60:40,将数据实例从数据中分离成训练集和测试集。我们通常使用训练集来训练模型,然后使用测试集检查其性能和预测能力。通常数据按 60:20:20 的比例划分,我们除了其他两个数据集外,还有一个验证集。然而,在本章中,我们只保留两个数据集。

  4. 特征选择:这个过程是迭代的,如果需要,甚至在后续阶段也会发生。在这个步骤中的主要目标是选择一组属性或特征,从训练数据集中选择,使预测模型能够给出最佳预测,最小化错误率并最大化准确性。

  5. 预测建模:这是主要步骤,我们选择最适合解决该问题的机器学习算法,并使用算法构建预测模型,通过向其提供从训练数据集中的数据中提取的特征来构建预测模型。这个阶段的输出是一个预测模型,可以用于对未来数据实例的预测。

  6. 模型评估:在这个阶段,我们使用测试数据集从预测模型中获得预测结果,并使用各种技术和指标来衡量模型的性能。

  7. 模型调优:我们微调模型的各个参数,并在必要时再次进行特征选择。然后我们重新构建模型并重新评估它,直到我们对结果满意。

  8. 模型部署:一旦预测模型给出令人满意的表现,我们就可以通过在任何应用程序中使用 Web 服务来部署这个模型,以提供实时或近实时的预测。这个步骤更多地关注围绕部署模型进行的软件和应用开发,因此我们不会涉及这个步骤,因为它超出了范围。然而,关于围绕预测模型构建 Web 服务以实现“预测即服务”的教程有很多。

最后三个步骤是迭代的,如果需要,可以执行多次

尽管乍一看这个过程可能看起来相当复杂,但实际上它是一个非常简单且直接的过程,一旦理解,就可以用于构建任何类型的预测模型。需要记住的一个重要事情是,预测建模是一个迭代的过程,我们可能需要通过从模型预测中获得反馈并评估它们来多次分析数据和构建模型。因此,即使你的模型在第一次尝试时表现不佳,你也绝不能气馁,因为正如我们之前提到的,模型永远不可能完美,构建一个好的预测模型既是艺术也是科学!

在下一节中,我们将关注如何应用预测分析来解决我们的预测问题,以及在本章中我们将探索的机器学习算法类型。

如何预测信用风险

如果你还记得上一章的主要目标,我们当时处理的是来自德国银行的客户数据。我们将快速回顾我们的主要问题场景以刷新你的记忆。这些银行客户是潜在的候选人,他们向银行申请信用贷款,条件是他们必须每月支付一定的利息来偿还贷款金额。在理想的世界里,信用贷款会被自由发放,人们会毫无问题地偿还它们。不幸的是,我们并不生活在一个乌托邦的世界,因此将会有一些客户会违约,无法偿还贷款金额,这会给银行造成巨大的损失。因此,信用风险分析是银行关注的几个关键领域之一,他们分析与客户及其信用历史相关的详细信息。

现在回到主要问题,对于预测信用风险,我们需要分析客户相关的数据集,使用机器学习算法围绕它构建预测模型,并预测客户是否可能拖欠信用贷款,并可能被标记为潜在的信用风险。我们将遵循的过程是我们在上一节中讨论的。你已经从上一章中了解了数据及其相关的特征。我们将探索几个预测模型,了解模型工作背后的概念,然后构建这些模型以预测信用风险。一旦我们开始预测结果,我们将比较这些不同模型的性能,然后讨论业务影响以及如何从模型预测结果中得出见解。请注意,预测不是预测分析生命周期中的输出,我们从这些预测中得出的宝贵见解才是最终目标。像金融机构这样的企业只能从使用领域知识将预测结果和机器学习算法的原始数字转换为数据驱动决策中获得价值,这些决策在正确的时间执行时有助于业务增长。

对于这个场景,如果你对数据集记得很清楚,特征credit.rating是响应或类别变量,它表示客户的信用评级。我们将根据其他特征(独立变量)预测这个值,以预测其他客户的信用评级。在建模时,我们将使用属于监督学习算法家族的机器学习算法。这些算法用于预测,可以分为两大类:分类和回归。然而,它们有一些区别,我们现在将讨论。在回归的情况下,要预测的变量的值是连续值,例如根据不同的特征(如房间数量、房屋面积等)预测房屋价格。回归主要处理基于输入特征估计和预测响应值。在分类的情况下,要预测的变量的值具有离散且独特的标签,例如预测我们银行的客户信用评级,其中信用评级可以是好的,用1表示,或者坏,用0表示。分类主要处理对数据集中的每个数据元进行分类和识别组成员资格。逻辑回归等算法是回归模型的特殊情况,用于分类,其中算法将变量属于某个类别标签的概率估计为其他特征的函数。在本章中,我们将使用以下机器学习算法构建预测模型:

  • 逻辑回归

  • 支持向量机

  • 决策树

  • 随机森林

  • 神经网络

我们选择这些算法是为了展示现有的各种监督机器学习算法的多样性,这样你不仅可以了解这些模型背后的概念,还可以学习如何使用它们构建模型,并使用各种技术比较模型性能。在我们开始分析之前,我们将简要回顾本书中提到的预测建模的一些基本概念,并详细讨论其中的一些,以便你对幕后发生的事情有一个良好的了解。

预测建模中的重要概念

当我们讨论机器学习流程时,已经探讨了几个概念。在本节中,我们将探讨预测建模中常用的典型术语,并详细讨论模型构建和评估概念。

准备数据

数据准备步骤,如前所述,涉及准备用于特征选择和构建预测模型所需的数据集。在这个背景下,我们经常使用以下术语:

  • 数据集:它们通常是数据点的集合或观察结果。大多数数据集通常对应于某种形式的结构化数据,涉及二维数据结构,如数据矩阵或数据表(在 R 中通常使用数据框表示)包含各种值。例如,我们来自第五章的german_credit_dataset.csv文件,信用风险检测与预测 – 描述性分析

  • 数据观察:它们是数据集中的行,其中每行包含一组观察结果与一组属性。这些行也常被称为元组。对于我们数据集,包含有关客户信息的每行都是一个很好的例子。

  • 数据特征:它们是数据集中的列,描述数据集中的每一行。这些特征通常被称为属性或变量。例如credit.ratingaccount.balance等特征构成了我们的信用风险数据集的特征。

  • 数据转换:它指的是根据描述性分析中的观察结果,根据需要转换各种数据特征。数据类型转换、缺失值插补、缩放和归一化是最常用的技术。此外,对于分类变量,如果你的算法无法检测变量的不同级别,你需要将其转换为几个虚拟变量;这个过程被称为独热编码。

  • 训练数据:它指的是仅用于训练预测模型的数据。机器学习算法从这个数据集中提取元组,并试图从各种观察实例中找出模式和进行学习。

  • 测试数据:它指的是输入到预测模型中以获取预测结果的数据,然后我们使用该数据集中已经存在的类别标签来检查模型的准确性。我们从不使用测试数据来训练模型,因为这会偏置模型并给出不正确的评估。

构建预测模型

我们使用机器学习算法和数据特征来构建实际的预测模型,并最终在输入新的数据元组时开始给出预测。与构建预测模型相关的概念如下:

  • 模型训练:它与构建预测模型类似,其中我们使用监督机器学习算法,并将训练数据特征输入其中来构建预测模型。

  • 预测模型:它基于某种机器学习算法,本质上是一个数学模型,具有一些假设、公式和参数值。

  • 模型选择:这是一个过程,其主要目标是从多个预测模型的迭代中选择一个预测模型。选择最佳模型的标准可能因我们想要选择的指标而异,例如最大化准确性、最小化错误率或获得最大的 AUC(我们将在后面讨论)。交叉验证是运行此迭代过程的好方法。

  • 超参数优化:这基本上是尝试选择算法在模型中使用的一组超参数,使得模型的预测准确性最优。这通常通过网格搜索算法来完成。

  • 交叉验证:这是一种模型验证技术,用于估计模型以通用方式的表现。它主要用于迭代过程,其中最终目标是优化模型并确保模型不会过度拟合数据,以便模型能够很好地泛化新数据并做出良好的预测。通常,会进行多轮交叉验证。每一轮交叉验证都涉及将数据分为训练集和测试集;使用训练数据来训练模型,然后使用测试集评估其性能。最终,我们得到一个模型,它是所有模型中最好的。

评估预测模型

预测建模中最重要的部分是测试创建的模型是否真正有用。这是通过在测试数据上评估模型并使用各种指标来衡量模型性能来完成的。我们将在下面讨论一些流行的模型评估技术。为了清楚地解释这些概念,我们将考虑一个与我们的数据相关的例子。让我们假设我们有 100 名客户,其中 40 名客户的信用评级不良,类别标签为 0,剩余的 60 名客户的信用评级良好,类别标签为 1。现在假设我们的模型将 40 个不良实例中的 22 个预测为不良,其余的 18 个预测为良好。模型还将 60 名良好客户中的 40 个预测为良好,其余的 20 个预测为不良。现在我们将看到我们将如何使用不同的技术来评估模型性能:

  • 预测值:它们通常是离散值,属于特定的类别或类别,通常被称为类别标签。在我们的案例中,这是一个二元分类问题,我们处理两个类别,其中标签 1 表示信用评级良好的客户,0 表示信用评级不良。

  • 混淆矩阵:这是一种很好的方式来查看模型是如何预测不同类别的。它是一个通常有两行两列的列联表,用于我们这样的二元分类问题。它报告了每个类别中预测实例的数量与实际类别值。对于我们的先前列举的例子,混淆矩阵将是一个 2x2 的矩阵,其中两行将表示预测的类别标签,两列将表示实际的类别标签。总共有坏(0)类别标签的预测实例数,实际上具有坏标签,被称为真负TN),而剩余的错误预测为好的坏实例被称为假正FP)。相应地,总共有好(1)类别标签的预测实例数,实际上被标记为好的,被称为真正TP),而剩余的错误预测为坏的好的实例被称为假负FN)。

    我们将在以下图中展示这一点,并讨论从混淆矩阵中导出的一些重要指标,这些指标也将在同一图中展示:

    评估预测模型

在先前的图中,2x2 矩阵中突出显示的值是我们模型正确预测的值。白色中的值是模型错误预测的。因此,我们可以很容易地推断以下指标:TN 是 22,FP18TP40FN20。总N40,总P60,在我们的示例数据集中总和为 100 名客户。

特异性也称为真阴性率,可以用公式评估预测模型表示,它给出了总真阴性数被正确预测的比例,这些真阴性数是所有实际为负的实例总数。在我们的案例中,特异性为55%

灵敏度,也称为真阳性率召回率,其公式为评估预测模型,表示在所有实际为正的实例中,正确预测的总真阳性数所占的比例。我们的例子中灵敏度为67%

精确率,也称为阳性预测值,其公式为评估预测模型,表示在所有正预测中实际正实例的数量。我们的例子中精确率为69%

负预测值的公式为评估预测模型,表示在所有负预测中实际负实例的数量。我们的例子中 NPV 为52%

假阳性率,也称为误报率,基本上是特异性的倒数;其公式为评估预测模型,表示在所有负例中错误正预测的数量。我们的例子中 FPR 为45%

假阴性率,也称为漏报率,基本上是敏感度的倒数;其公式为评估预测模型,表示在所有正例中错误负预测的数量。我们的例子中 FNR 为33%

准确率基本上是衡量模型在做出预测时准确性的指标,其公式为评估预测模型。我们的预测准确率为62%

F1分数是衡量模型准确性的另一个指标。它通过计算值的调和平均值来考虑精确率和召回率,公式表示为评估预测模型。我们的模型 f1 分数为68%

接收者操作特征ROC)曲线基本上是一个图表,用于可视化模型性能,当我们改变其阈值时。ROC 图由 FPR 和 TPR 作为x轴和y轴分别定义,每个预测样本都可以拟合为 ROC 空间中的一个点。完美的图表将涉及所有数据点的 TPR 为 1 和 FPR 为 0。一个平均模型或基线模型将是一条从(0, 0)(1, 1)的对角线,表示两个值都是0.5。如果我们的模型 ROC 曲线在基线对角线之上,则表明其性能优于基线。以下图解展示了典型的 ROC 曲线在一般情况下的样子:

评估预测模型

曲线下面积AUC)基本上是从模型评估中获得的 ROC 曲线下的面积。AUC 是一个值,表示模型将随机选择的正实例排名高于随机选择的负实例的概率。因此,AUC 越高,越好。请查看文件performance_plot_utils.R(与章节代码包共享),其中包含一些实用函数,用于绘制和描述我们稍后评估模型时将使用的这些值。

这应该为你提供了关于预测建模中重要术语和概念足够的背景知识,现在我们将开始对数据进行预测分析!

获取数据

在第五章中,信用风险检测与预测 – 描述性分析,我们已经分析了德国银行的信用数据集并进行了几个转换。在本章中,我们将处理这个转换后的数据集。我们已经保存了转换后的数据集,你可以通过打开credit_dataset_final.csv文件来查看。我们将像往常一样在 R 中进行所有分析。要加载数据到内存中,请运行以下代码片段:

> # load the dataset into data frame
> credit.df <- read.csv("credit_dataset_final.csv", header = TRUE, sep = ",")

这将数据集加载到一个数据框中,现在可以通过credit.df变量轻松访问。接下来,我们将关注数据转换和归一化。

数据预处理

在数据预处理步骤中,我们将主要关注两个方面:数据类型转换和数据归一化。最后,我们将数据分割成训练集和测试集以进行预测建模。你可以通过打开data_preparation.R文件来访问这一部分的代码。我们将使用一些实用函数,这些函数在下面的代码片段中提到。请记住,通过在 R 控制台中运行它们来将它们加载到内存中:

## data type transformations - factoring
to.factors <- function(df, variables){
 for (variable in variables){
 df[[variable]] <- as.factor(df[[variable]])
 }
 return(df)
}

## normalizing - scaling
scale.features <- function(df, variables){
 for (variable in variables){
 df[[variable]] <- scale(df[[variable]], center=T, scale=T)
 }
 return(df)
}

前面的函数在数据框上操作以转换数据。对于数据类型转换,我们主要对分类变量进行分解,即将分类特征的类型从数值转换为因子。有几个数值变量,包括credit.amountagecredit.duration.months,它们都有各种值。如果你还记得上一章中的分布,它们都是偏斜分布。这有多重不利影响,如引起共线性、梯度受到影响以及模型收敛时间更长。因此,我们将使用 z 分数标准化,其中,例如,一个名为 E 的特征的值,可以用以下公式计算:数据预处理,其中数据预处理代表特征 E 的整体均值,数据预处理代表特征 E 的标准差。我们使用以下代码片段来对我们的数据进行这些转换:

> # normalize variables
> numeric.vars <- c("credit.duration.months", "age", 
 "credit.amount")
> credit.df <- scale.features(credit.df, numeric.vars)
> # factor variables
> categorical.vars <- c('credit.rating', 'account.balance', 
+                       'previous.credit.payment.status',
+                       'credit.purpose', 'savings', 
+                       'employment.duration', 'installment.rate',
+                       'marital.status', 'guarantor', 
+                       'residence.duration', 'current.assets',
+                       'other.credits', 'apartment.type', 
+                       'bank.credits', 'occupation', 
+                       'dependents', 'telephone', 
+                       'foreign.worker')
> credit.df <- to.factors(df=credit.df, 
 variables=categorical.vars)

预处理完成后,我们将数据集分成训练集和测试集,比例为 60:40,其中训练集将包含 600 个元组,测试集将包含 400 个元组。它们将以以下随机方式选择:

> # split data into training and test datasets in 60:40 ratio
> indexes <- sample(1:nrow(credit.df), size=0.6*nrow(credit.df))
> train.data <- credit.df[indexes,]
> test.data <- credit.df[-indexes,]

现在我们已经准备好了数据集,我们将在下一节中探讨特征的重要性和选择。

特征选择

特征选择的过程涉及通过使用它们训练预测模型并根据其重要性对变量或特征进行排名,然后试图找出哪些变量是该模型中最相关的特征。虽然每个模型通常都有自己的重要特征集,但在分类中,我们将在这里使用随机森林模型来尝试确定哪些变量可能在基于分类的预测中普遍重要。

我们进行特征选择有几个原因,包括:

  • 在不损失太多信息的情况下移除冗余或不相关的特征

  • 通过使用过多的特征防止模型过拟合

  • 减少由过多特征引起的模型方差

  • 减少模型的训练时间和收敛时间

  • 构建简单且易于理解的模型

我们将使用递归特征消除算法进行特征选择,并使用预测模型进行评估算法。在这个过程中,我们将反复构建几个具有不同特征的机器学习模型。在每次迭代中,我们都会消除无关或冗余的特征,并检查获得最大准确率和最小误差的特征子集。由于这是一个迭代过程,遵循流行的贪婪爬山算法的原则,通常不可能进行穷举搜索并得到全局最优解,而且根据起始点不同,我们可能会陷入局部最优解,得到的特征子集可能与不同运行中获得的特征子集不同。然而,如果我们多次使用交叉验证运行它,通常大多数特征在获得的子集中将是恒定的。我们将使用随机森林算法,我们将在稍后详细解释。现在,只需记住它是一个集成学习算法,在其训练过程的每个阶段都使用多个决策树。这倾向于减少方差和过拟合,同时由于我们在算法的每个阶段引入了一些随机性,模型偏差略有增加。

本节代码位于feature_selection.R文件中。我们首先加载必要的库。如果你还没有安装它们,按照我们在前几章的做法进行安装:

> library(caret)  # feature selection algorithm
> library(randomForest) # random forest algorithm

the R console to load into memory for using it later:
run.feature.selection <- function(num.iters=20, feature.vars, class.var){
 set.seed(10)
 variable.sizes <- 1:10
 control <- rfeControl(functions = rfFuncs, method = "cv", 
 verbose = FALSE, returnResamp = "all", 
 number = num.iters)
 results.rfe <- rfe(x = feature.vars, y = class.var, 
 sizes = variable.sizes, 
 rfeControl = control)
 return(results.rfe)
}

默认情况下,前面的代码使用交叉验证,将数据分为训练集和测试集。对于每次迭代,都会进行递归特征消除,并在测试集上对模型进行训练和测试,以检查准确率和误差。数据分区在每次迭代中都会随机变化,以防止模型过拟合,并最终给出一个通用的估计,即模型在一般情况下的表现。如果你观察,我们的函数默认运行 20 次迭代。记住,在我们的情况下,我们总是在训练数据上训练,该数据由函数内部分区进行交叉验证。变量feature.vars表示在训练数据集中可以使用train.data[,-1]子集访问的所有独立特征变量,要访问表示要预测的类变量的class.var,我们使用train.data[,1]进行子集操作。

注意

我们完全不接触测试数据,因为我们只打算用它来进行预测和模型评估。因此,我们不想通过使用这些数据来影响模型,因为这会导致评估结果不准确。

我们现在使用定义好的函数在训练数据上运行算法,以下代码展示了这一过程。运行可能需要一些时间,所以如果你看到 R 在返回结果时花费了一些时间,请耐心等待:

rfe.results <- run.feature.selection(feature.vars=train.data[,-1], 
 class.var=train.data[,1])
# view results
rfe.results

查看结果后,我们得到以下输出:

特征选择

从输出中,你可以看到它从 20 个特征中找到了最重要的 10 个特征,并且默认返回了前五个特征。你可以进一步玩转这个结果变量,并使用 R 控制台中的varImp(rfe.results)命令查看所有变量的重要性。由于训练和测试数据分区是随机进行的,如果你记得,所以如果你看到与截图不同的值,请不要慌张。然而,根据我们的观察,前五个特征通常会保持一致。现在,我们将开始构建预测模型,使用不同的机器学习算法进行我们分析管道的下一阶段。然而,请记住,由于训练和测试集是随机选择的,你的集合可能给出的结果与我们在这里进行实验时描述的不同。

使用逻辑回归建模

逻辑回归是一种回归模型,其中因变量或类别变量不是连续的,而是分类的,就像在我们的案例中,信用评级是具有两个类别的因变量。原则上,逻辑回归通常被视为广义线性模型家族的一个特例。该模型通过估计概率来尝试找出类别变量与其他独立特征变量之间的关系。它使用逻辑或 Sigmoid 函数来估计这些概率。逻辑回归不直接预测类别,而是预测结果的概率。对于我们的模型,由于我们处理的是一个二元分类问题,我们将处理二项逻辑回归。

首先,我们将如下加载库依赖项,并分别分离测试特征和类别变量:

library(caret) # model training and evaluation
library(ROCR) # model evaluation
source("performance_plot_utils.R") # plotting metric results
## separate feature and class variables
test.feature.vars <- test.data[,-1]
test.class.var <- test.data[,1]

现在我们将使用所有独立变量训练初始模型如下:

> formula.init <- "credit.rating ~ ."
> formula.init <- as.formula(formula.init)
> lr.model <- glm(formula=formula.init, data=train.data, family="binomial")

我们可以使用summary(lr.model)命令查看模型细节,该命令显示基于其显著性值的各个变量及其重要性。我们在以下快照中展示了这些细节的一部分:

使用逻辑回归建模

你可以看到模型自动对分类变量执行了一元编码,这基本上是在该变量中为每个类别都有一个变量。旁边带有星号的变量具有 p 值< 0.05(我们在上一章中讨论过),因此是显著的。

接下来,我们在测试数据上执行预测,并如下评估结果:

> lr.predictions <- predict(lr.model, test.data, type="response")
> lr.predictions <- round(lr.predictions)
> confusionMatrix(data=lr.predictions, reference=test.class.var, positive='1')

运行此代码后,我们得到一个混淆矩阵以及相关的指标,我们之前已经讨论过,如下所示。看到我们实现了71.75%的整体准确率,这相当不错,考虑到这个数据集大多数客户都有良好的信用评级。它对不良信用评级的预测相当准确,这从48%特异性中可以看出。灵敏度83%,相当不错,NPV58%PPV76%

使用逻辑回归建模

现在,我们将尝试使用一些选定的特征构建另一个模型,并看看它的表现如何。如果你还记得,我们在特征选择部分获得了一些对分类很重要的通用特征。我们仍然会为逻辑回归运行特征选择,以使用以下代码片段查看特征的重要性:

formula <- "credit.rating ~ ."
formula <- as.formula(formula)
control <- trainControl(method="repeatedcv", number=10, repeats=2)
model <- train(formula, data=train.data, method="glm", 
 trControl=control)
importance <- varImp(model, scale=FALSE)
plot(importance)

我们从以下图中选择了前五个变量来构建下一个模型。正如你所见,阅读这个图相当简单。重要性越大,变量就越重要。你可以自由地添加更多变量,并使用它们构建不同的模型!

使用逻辑回归建模

接下来,我们使用与之前类似的方法构建模型,并使用以下代码片段在测试数据上测试模型性能:

> formula.new <- "credit.rating ~ account.balance + credit.purpose 
 + previous.credit.payment.status + savings 
 + credit.duration.months"
> formula.new <- as.formula(formula.new)
> lr.model.new <- glm(formula=formula.new, data=train.data, family="binomial")
> lr.predictions.new <- predict(lr.model.new, test.data, type="response") 
> lr.predictions.new <- round(lr.predictions.new)
> confusionMatrix(data=lr.predictions.new, reference=test.class.var, positive='1')

我们得到了以下混淆矩阵。然而,如果你查看模型评估结果,如以下输出所示,你会发现现在准确率略有提高,达到了72.25%灵敏度大幅上升到94%,这是非常好的,但遗憾的是,这是以特异性下降为代价的,特异性下降到27%,你可以清楚地看到,更多的不良信用评级被预测为好,这在测试数据中的 130 个不良信用评级客户中有 95 个!NPV上升到69%,因为由于灵敏度更高,较少的正信用评级被错误地分类为假阴性。

使用逻辑回归建模

现在的问题是我们要选择哪个模型进行预测。这不仅仅取决于准确率,还取决于问题的领域和业务需求。如果我们预测一个客户的不良信用评级(0)为1),这意味着我们将批准该客户的信用贷款,而该客户最终可能不会偿还,这将给银行造成损失。然而,如果我们预测一个客户的良好信用评级(1)为0),这意味着我们将拒绝他的贷款,在这种情况下,银行既不会盈利也不会遭受任何损失。这比错误地将不良信用评级预测为好要安全得多。

因此,我们选择我们的第一个模型作为最佳模型,现在我们将使用以下代码片段查看一些指标评估图:

> lr.model.best <- lr.model
> lr.prediction.values <- predict(lr.model.best, test.feature.vars, type="response")
> predictions <- prediction(lr.prediction.values, test.class.var)
> par(mfrow=c(1,2))
> plot.roc.curve(predictions, title.text="LR ROC Curve")
> plot.pr.curve(predictions, title.text="LR Precision/Recall Curve")

我们从前面的代码中得到了以下图:

使用逻辑回归建模

您可以从前面的图中看到,AUC0.74,这对于一个开始来说相当不错。我们现在将使用类似的过程构建下一个预测模型,使用支持向量机,并看看它的表现如何。

使用支持向量机建模

支持向量机属于用于分类和回归的监督机器学习算法家族。考虑到我们的二分类问题,与逻辑回归不同,支持向量机算法将围绕训练数据构建模型,使得属于不同类别的训练数据点通过一个清晰的间隙分开,这个间隙被优化到最大分离距离。位于边缘的样本通常被称为支持向量。分隔两个类别的边缘中间部分称为最优分离超平面。

位于错误边缘的数据点被降低权重以减少其影响,这被称为软边缘,与我们之前讨论的硬边缘分离相比。支持向量机分类器可以是简单的线性分类器,其中数据点可以线性分离。然而,如果我们处理的是由多个特征组成的数据,而这些特征无法直接进行线性分离,那么我们就使用多个核来实现这一点,这些核形成了非线性支持向量机分类器。您将能够通过以下来自 R 中svm库官方文档的图来可视化支持向量机分类器实际的样子:

使用支持向量机建模

图片来源:cran.r-project.org/web/packages/e1071/vignettes/svmdoc.pdf

从图中,你可以清楚地看到我们可以放置多个超平面来分隔数据点。然而,选择分隔超平面的标准是两个类别的分隔距离最大,支持向量是两个类别的代表性样本,如图中边缘所示。回顾非线性分类器的问题,SVM 除了用于线性分类的常规线性核之外,还有几个核可以用来实现这一点。这些包括多项式、径向基 函数RBF)以及几个其他核。这些非线性核函数背后的主要原理是,即使在线性特征空间中无法进行线性分隔,它们也能使分隔在更高维度的变换特征空间中发生,在那里我们可以使用超平面来分隔类别。需要记住的一个重要事情是维度的诅咒;由于我们可能最终要处理更高维度的特征空间,模型的泛化误差增加,模型的预测能力降低。如果我们有足够的数据,它仍然表现合理。在我们的模型中,我们将使用 RBF 核,也称为径向基函数,为此有两个重要的参数是成本和 gamma。

我们将首先加载必要的依赖项并准备测试数据特征:

library(e1071) # svm model
library(caret) # model training\optimizations
library(kernlab) # svm model for hyperparameters
library(ROCR) # model evaluation
source("performance_plot_utils.R") # plot model metrics
## separate feature and class variables
test.feature.vars <- test.data[,-1]
test.class.var <- test.data[,1]

一旦完成这些,我们将使用训练数据和 RBF 核在所有训练集特征上构建 SVM 模型:

> formula.init <- "credit.rating ~ ."
> formula.init <- as.formula(formula.init)
> svm.model <- svm(formula=formula.init, data=train.data, 
+                  kernel="radial", cost=100, gamma=1)
> summary(svm.model)

模型的属性如下所示,来自summary函数:

使用支持向量机建模

现在我们使用测试数据对这个模型进行预测并评估结果如下:

> svm.predictions <- predict(svm.model, test.feature.vars)
> confusionMatrix(data=svm.predictions, reference=test.class.var, positive="1")

这给我们带来了如下混淆矩阵,就像我们在逻辑回归中看到的那样,模型性能的细节如下。我们观察到准确率67.5%,灵敏度100%,特异性0%,这意味着这是一个非常激进的模型,它只是预测每个客户评价为好。这个模型显然存在主要类别分类问题,我们需要改进这一点。

使用支持向量机建模

为了构建更好的模型,我们需要进行一些特征选择。我们已经在特征选择部分获得了前五个最佳特征。尽管如此,我们仍然会运行一个专门为 SVM 设计的特征选择算法来查看特征重要性,如下所示:

> formula.init <- "credit.rating ~ ."
> formula.init <- as.formula(formula.init)
> control <- trainControl(method="repeatedcv", number=10, repeats=2)
> model <- train(formula.init, data=train.data, method="svmRadial", 
+                trControl=control)
> importance <- varImp(model, scale=FALSE)
> plot(importance, cex.lab=0.5)

这给我们一个图,我们看到前五个重要变量与我们的前五个最佳特征相似,但这个算法将年龄的重要性排名高于credit.amount,所以你可以通过构建具有不同特征的几个模型来测试这一点,看看哪一个给出最好的结果。对我们来说,从随机森林中选择的特征给出了更好的结果。变量重要性图如下所示:

使用支持向量机建模

现在我们基于给我们最佳结果的五个特征构建一个新的 SVM 模型,并使用以下代码片段在测试数据上评估其性能:

> formula.new <- "credit.rating ~ account.balance + 
 credit.duration.months + savings + 
 previous.credit.payment.status + credit.amount"
> formula.new <- as.formula(formula.new)
> svm.model.new <- svm(formula=formula.new, data=train.data, 
+                  kernel="radial", cost=100, gamma=1)
> svm.predictions.new <- predict(svm.model.new, test.feature.vars)
> confusionMatrix(data=svm.predictions.new, 
 reference=test.class.var, positive="1")

1% to 66.5%. However, the most interesting part is that now our model is able to predict more bad ratings from bad, which can be seen from the confusion matrix. The specificity is now 38% compared to 0% earlier and, correspondingly, the sensitivity has gone down to 80% from 100%, which is still good because now this model is actually useful and profitable! You can see from this that feature selection can indeed be extremely powerful. The confusion matrix for the preceding observations is depicted in the following snapshot:

使用支持向量机建模

我们肯定会选择这个模型,并继续使用网格搜索算法进行模型优化,如下所示,以优化成本和 gamma 参数:

cost.weights <- c(0.1, 10, 100)
gamma.weights <- c(0.01, 0.25, 0.5, 1)
tuning.results <- tune(svm, formula.new,
 data = train.data, kernel="Radial", 
 ranges=list(cost=cost.weights, gamma=gamma.weights))
print(tuning.results)

输出结果:

使用支持向量机建模

网格搜索图可以如下查看:

> plot(tuning.results, cex.main=0.6, cex.lab=0.8,xaxs="i", yaxs="i")

输出结果:

使用支持向量机建模

最暗的区域显示了给出最佳性能的参数值。我们现在选择最佳模型并再次评估,如下所示:

> svm.model.best = tuning.results$best.model
> svm.predictions.best <- predict(svm.model.best,
 test.feature.vars)
> confusionMatrix(data=svm.predictions.best, 
 reference=test.class.var, positive="1")

观察从以下输出中获得的混淆矩阵结果(我们此后只描述我们跟踪的指标),我们看到整体准确率增加到71%,灵敏度86%,特异性41%,与之前的模型结果相比,这是非常好的:

使用支持向量机建模

你可以看到超参数优化在预测建模中的强大作用!我们还会绘制一些评估曲线,如下所示:

> svm.predictions.best <- predict(svm.model.best, test.feature.vars, decision.values = T)
> svm.prediction.values <- attributes(svm.predictions.best)$decision.values
> predictions <- prediction(svm.prediction.values, test.class.var)
> par(mfrow=c(1,2))
> plot.roc.curve(predictions, title.text="SVM ROC Curve")
> plot.pr.curve(predictions, title.text="SVM Precision/Recall Curve")

我们可以看到预测是如何在评估空间中绘制的,并且我们看到从以下 ROC 图中,AUC 在这种情况下为 0.69:

使用支持向量机建模

现在,假设我们想要根据这个 ROC 图优化模型,目标是最大化 AUC。我们现在将尝试这样做,但首先我们需要将分类变量的值编码为一些字母,因为 R 在表示只有数字的因子变量的列名时会引起一些问题。所以基本上,如果credit.rating的值为01,则它被转换为X0X1;最终我们的类别仍然是不同的,没有任何变化。我们首先使用以下代码片段转换我们的数据:

> transformed.train <- train.data
> transformed.test <- test.data
> for (variable in categorical.vars){
+   new.train.var <- make.names(train.data[[variable]])
+   transformed.train[[variable]] <- new.train.var
+   new.test.var <- make.names(test.data[[variable]])
+   transformed.test[[variable]] <- new.test.var
+ }
> transformed.train <- to.factors(df=transformed.train, variables=categorical.vars)
> transformed.test <- to.factors(df=transformed.test, variables=categorical.vars)
> transformed.test.feature.vars <- transformed.test[,-1]
> transformed.test.class.var <- transformed.test[,1]

现在我们再次使用网格搜索构建一个 AUC 优化的模型,如下所示:

> grid <- expand.grid(C=c(1,10,100), sigma=c(0.01, 0.05, 0.1, 0.5, 
 1))
> ctr <- trainControl(method='cv', number=10, classProbs=TRUE,
 summaryFunction=twoClassSummary)
> svm.roc.model <- train(formula.init, transformed.train,
+                        method='svmRadial', trControl=ctr, 
+                        tuneGrid=grid, metric="ROC")

我们下一步是对测试数据进行预测并评估混淆矩阵:

> predictions <- predict(svm.roc.model, 
 transformed.test.feature.vars)
> confusionMatrix(predictions, transformed.test.class.var, 
 positive = "X1")

这给我们以下结果:

使用支持向量机建模

我们现在看到准确率进一步提高到72%,而特异性略有下降到40%,但灵敏度增加到87%,这是好的。我们再次绘制曲线,如下所示:

> svm.predictions <- predict(svm.roc.model, transformed.test.feature.vars, type="prob")
> svm.prediction.values <- svm.predictions[,2]
> predictions <- prediction(svm.prediction.values, test.class.var)
> par(mfrow=c(1,2))
> plot.roc.curve(predictions, title.text="SVM ROC Curve")
> plot.pr.curve(predictions, title.text="SVM Precision/Recall Curve")

这给我们以下图表,与我们的早期迭代中做的一样:

使用支持向量机建模

看到 AUC 确实从之前的 0.69 增加到现在的 0.74,这非常令人满意,这意味着基于 AUC 的优化算法确实有效,因为它在所有我们跟踪的方面都给出了比之前模型更好的性能。接下来,我们将探讨如何使用决策树构建预测模型。

使用决策树进行建模

决策树是再次属于监督机器学习算法家族的算法。它们也用于分类和回归,通常称为CART,代表分类和回归树。这些在决策支持系统、商业智能和运筹学中应用广泛。

决策树主要用于做出最有用的决策,以实现某些目标并基于这些决策设计策略。在核心上,决策树只是一个包含几个节点和条件边的流程图。每个非叶节点代表对某个特征的测试条件,每条边代表测试的结果。每个叶节点代表一个类标签,其中对最终结果进行预测。从根节点到所有叶节点的路径给出了所有的分类规则。决策树易于表示、构建和理解。然而,缺点是它们非常容易过拟合,并且这些模型通常泛化能力不佳。我们将遵循之前类似的分析流程,基于决策树构建一些模型。

我们首先加载必要的依赖项和测试数据特征:

> library(rpart)# tree models 
> library(caret) # feature selection
> library(rpart.plot) # plot dtree
> library(ROCR) # model evaluation
> library(e1071) # tuning model
> source("performance_plot_utils.R") # plotting curves
> ## separate feature and class variables
> test.feature.vars <- test.data[,-1]
> test.class.var <- test.data[,1]

现在我们将使用以下所有特征构建一个初始模型:

> formula.init <- "credit.rating ~ ."
> formula.init <- as.formula(formula.init)
> dt.model <- rpart(formula=formula.init, 
 method="class",data=train.data,control = 
 rpart.control(minsplit=20, cp=0.05))

我们使用以下代码在测试数据上预测和评估模型:

> dt.predictions <- predict(dt.model, test.feature.vars, 
 type="class")
> confusionMatrix(data=dt.predictions, reference=test.class.var, 
 positive="1")

从以下输出中,我们看到模型的准确率大约为68%,灵敏度92%,这是非常好的,但特异性仅为18%,这是我们应努力改进的:

使用决策树进行建模

我们现在将尝试特征选择来改进模型。我们使用以下代码来训练模型并按其重要性对特征进行排序:

> formula.init <- "credit.rating ~ ."
> formula.init <- as.formula(formula.init)
> control <- trainControl(method="repeatedcv", number=10, repeats=2)
> model <- train(formula.init, data=train.data, method="rpart", 
+                trControl=control)
> importance <- varImp(model, scale=FALSE)
> plot(importance)

这给我们以下图表,显示了不同特征的重要性:

使用决策树进行建模

如果你仔细观察,决策树在模型构建中并没有使用所有特征,并且前五个特征与我们之前在讨论特征选择时获得的是相同的。我们现在将使用以下特征构建一个模型:

> formula.new <- "credit.rating ~ account.balance + savings +
 credit.amount + 
 credit.duration.months + 
 previous.credit.payment.status"
> formula.new <- as.formula(formula.new)
> dt.model.new <- rpart(formula=formula.new, method="class",data=train.data, 
+                   control = rpart.control(minsplit=20, cp=0.05),
+                   parms = list(prior = c(0.7, 0.3)))

我们现在对测试数据进行预测并评估,如下所示:

> dt.predictions.new <- predict(dt.model.new, test.feature.vars, 
 type="class")
> confusionMatrix(data=dt.predictions.new, 
 reference=test.class.var, positive="1")

这给我们以下混淆矩阵和其他指标:

使用决策树进行建模

你现在可以看到,整体模型的准确率略有下降,达到了62%。然而,我们在不良信用评分预测方面取得了进步,我们预测在 130 个客户中有 100 个不良信用评分客户,这非常出色!因此,特异性上升到77%,而灵敏度下降到55%,但我们仍然将大量良好信用评分的客户分类为良好。尽管这个模型有些激进,但这是一个合理的模型,因为我们虽然拒绝了更多可能违约的客户的信用贷款,但我们同时也确保了合理数量的良好客户能够获得他们的信用贷款。

我们获得这些结果的原因是因为我们使用了一个名为先验的参数来构建模型,如果你检查前面的建模部分。这个先验参数基本上使我们能够对类别变量中的不同类别应用权重。如果你记得,在我们的数据集中有700个信用评分良好的人和300个信用评分不良的人,这是一个高度倾斜的数据集,所以在训练模型时,我们可以使用先验来指定这个变量中每个类别的相对重要性,从而调整每个类别的误分类的重要性。在我们的模型中,我们给予不良信用评分客户更多的重视。

你可以通过使用参数prior = c(0.7, 0.3)来反转先验概率,并给予良好信用评分客户更多的重视,这将给出以下混淆矩阵:

使用决策树建模

你现在可以清楚地看到,由于我们给予了良好信用评分更多的重视,灵敏度上升到92%,而特异性下降到18%。你可以看到,这为你提供了很大的灵活性,取决于你想要实现的目标。

要查看模型,我们可以使用以下代码片段:

> dt.model.best <- dt.model.new
> print(dt.model.best)

输出

使用决策树建模

为了可视化前面的树,你可以使用以下代码:

> par(mfrow=c(1,1))
> prp(dt.model.best, type=1, extra=3, varlen=0, faclen=0)

这给我们以下树形图,我们可以看到,使用先验概率,现在在五个特征中只使用了account.balance这个特征,并且忽略了其他所有特征。你可以尝试通过使用e1071包中的tune.rpart函数进行超参数调整来进一步优化模型:

使用决策树建模

我们通过绘制以下指标评估曲线来完成我们的分析:

> dt.predictions.best <- predict(dt.model.best, test.feature.vars, 
 type="prob")
> dt.prediction.values <- dt.predictions.best[,2]
> predictions <- prediction(dt.prediction.values, test.class.var)
> par(mfrow=c(1,2))
> plot.roc.curve(predictions, title.text="DT ROC Curve")
> plot.pr.curve(predictions, title.text="DT Precision/Recall 
 Curve")

AUC大约为0.66,这并不是最好的,但肯定比以下图中用红线表示的基线要好:

使用决策树建模

根据我们的业务需求,这个模型相当公平。我们将在本章后面讨论模型比较。现在我们将使用随机森林来构建我们的下一组预测模型。

使用随机森林建模

随机森林,也称为随机决策森林,是一种来自集成学习算法家族的机器学习算法。它用于回归和分类任务。随机森林实际上就是决策树集合或集成,因此得名。

算法的工作原理可以简要描述如下。在任何时刻,决策树集成中的每一棵树都是从一个自助样本中构建的,这基本上是带有替换的采样。这种采样是在训练数据集上进行的。在构建决策树的过程中,之前在所有特征中选择为最佳分割的分割不再进行。现在,每次分割时总是从特征的一个随机子集中选择最佳分割。这种随机性引入到模型中略微增加了模型的偏差,但大大减少了模型的方差,这防止了模型的过拟合,这在决策树的情况下是一个严重的问题。总的来说,这产生了性能更好的泛化模型。现在我们将开始我们的分析流程,通过加载必要的依赖项:

> library(randomForest) #rf model 
> library(caret) # feature selection
> library(e1071) # model tuning
> library(ROCR) # model evaluation
> source("performance_plot_utils.R") # plot curves
> ## separate feature and class variables
> test.feature.vars <- test.data[,-1]
> test.class.var <- test.data[,1]

接下来,我们将使用所有特征构建初始训练模型,如下所示:

> formula.init <- "credit.rating ~ ."
> formula.init <- as.formula(formula.init)
> rf.model <- randomForest(formula.init, data = train.data, 
 importance=T, proximity=T)

您可以使用以下代码查看模型详情:

> print(rf.model)

输出

使用随机森林建模

这给我们提供了关于袋外误差OOBE)的信息,大约为23%,以及基于训练数据的混淆矩阵,以及它在每个分割中使用的变量数量。

接下来,我们将使用此模型在测试数据上进行预测,并评估它们:

> rf.predictions <- predict(rf.model, test.feature.vars, 
 type="class")
> confusionMatrix(data=rf.predictions, reference=test.class.var, 
 positive="1")

new model:
formula.new <- "credit.rating ~ account.balance + savings +
 credit.amount + 
 credit.duration.months + 
 previous.credit.payment.status"
formula.new <- as.formula(formula.new)
rf.model.new <- randomForest(formula.new, data = train.data, 
 importance=T, proximity=T)

我们现在使用此模型在测试数据上进行预测,并如下评估其性能:

> rf.predictions.new <- predict(rf.model.new, test.feature.vars, 
 type="class")
> confusionMatrix(data=rf.predictions.new,   reference=test.class.var, positive="1")

这给我们以下混淆矩阵作为输出,以及其他关键性能指标:

使用随机森林建模

我们得到了略微降低的准确率71%,这是显而易见的,因为我们已经消除了许多特征,但现在特异性已增加到42%,这表明它能够更准确地分类更多的坏实例。灵敏度略有下降至84%。现在我们将使用网格搜索来对此模型进行超参数调整,如下所示,以查看我们是否可以进一步提高性能。这里感兴趣的参数包括ntree,表示树的数量,nodesize,表示终端节点的最小大小,以及mtry,表示每次分割时随机采样的变量数量。

nodesize.vals <- c(2, 3, 4, 5)
ntree.vals <- c(200, 500, 1000, 2000)
tuning.results <- tune.randomForest(formula.new, 
 data = train.data,
 mtry=3, 
 nodesize=nodesize.vals,
 ntree=ntree.vals)
print(tuning.results)

输出

使用随机森林建模

我们现在从先前的网格搜索中得到最佳模型,对测试数据进行预测,并使用以下代码片段评估其性能:

> rf.model.best <- tuning.results$best.model
> rf.predictions.best <- predict(rf.model.best, test.feature.vars, 
 type="class")
> confusionMatrix(data=rf.predictions.best,
 reference=test.class.var, positive="1")

从以下输出中,我们可以得出几个观察结果。性能提高非常微不足道,因为整体准确率保持在71%特异性保持在42%灵敏度略有提高,从84%增加到85%

使用随机森林建模

我们现在为这个模型绘制一些性能曲线,如下所示:

> rf.predictions.best <- predict(rf.model.best, test.feature.vars, type="prob")
> rf.prediction.values <- rf.predictions.best[,2]
> predictions <- prediction(rf.prediction.values, test.class.var)
> par(mfrow=c(1,2))
> plot.roc.curve(predictions, title.text="RF ROC Curve")
> plot.pr.curve(predictions, title.text="RF Precision/Recall Curve")

我们观察到总AUC约为0.7,在以下图中比红色基线AUC0.5要好得多:

使用随机森林建模

我们将要探索的最后一种算法是神经网络,我们将在下一节中使用它们来构建模型。

使用神经网络建模

神经网络,或者更具体地说,在这种情况下,人工神经网络,是一系列基于生物神经网络工作原理的机器学习模型,就像我们的神经系统一样。神经网络已经存在很长时间了,但最近,人们对于使用深度学习和人工智能构建高度智能系统的兴趣激增。深度学习利用深度神经网络,这些网络在输入层和输出层之间有大量的隐藏层。一个典型的神经网络可以用以下图示来表示:

使用神经网络建模

从图中,你可以推断出这个神经网络是一个由各种节点相互连接的网络,也称为神经元。每个节点代表一个神经元,它实际上就是一个数学函数。我们不可能详细说明如何从数学上表示一个节点,但在这里我们会给出要点。这些数学函数接收一个或多个带有权重的输入,这些输入在先前的图中表示为边,然后对这些输入进行一些计算以给出输出。在这些节点中使用的各种流行函数包括阶跃函数和 sigmoid 函数,这些函数你已经在逻辑回归算法中看到过使用。一旦输入被函数加权并转换,这些函数的激活就会发送到后续的节点,直到达到输出层。节点集合形成一层,就像在先前的图中,我们有三个层。

因此,神经网络依赖于多个神经元或节点以及它们之间的互连模式,学习过程用于在每次迭代(通常称为一个 epoch)中更新连接的权重,以及节点的激活函数,这些激活函数将带有权重的节点输入转换为输出激活,该激活通过层传递,直到我们得到输出预测。我们将从以下方式开始加载必要的依赖项:

> library(caret) # nn models
> library(ROCR) # evaluate models
> source("performance_plot_utils.R") # plot curves
> # data transformation
> test.feature.vars <- test.data[,-1]
> test.class.var <- test.data[,1]

现在,我们不得不进行一些特征值编码,类似于我们在为 SVM 进行 AUC 优化时所做的那样。为了刷新你的记忆,你可以运行以下代码片段:

> transformed.train <- train.data
> transformed.test <- test.data
> for (variable in categorical.vars){
+   new.train.var <- make.names(train.data[[variable]])
+   transformed.train[[variable]] <- new.train.var
+   new.test.var <- make.names(test.data[[variable]])
+   transformed.test[[variable]] <- new.test.var
+ }
> transformed.train <- to.factors(df=transformed.train, variables=categorical.vars)
> transformed.test <- to.factors(df=transformed.test, variables=categorical.vars)
> transformed.test.feature.vars <- transformed.test[,-1]
> transformed.test.class.var <- transformed.test[,1]

一旦我们准备好数据,我们将使用所有特征构建我们的初始神经网络模型,如下所示:

> formula.init <- "credit.rating ~ ."
> formula.init <- as.formula(formula.init)
> nn.model <- train(formula.init, data = transformed.train, method="nnet")

nnet package if you do not have it installed, so just select the option when it asks you and it will install it automatically and build the model. If it fails, you can install it separately and run the code again. Remember, it is an iterative process so the model building might take some time. Once the model converges, you can view the model details using the print(nn.model) command which will show several iteration results with different size and decay options, and you will see that it does hyperparameter tuning internally itself to try and get the best model!

我们现在在测试数据上执行预测并评估模型性能如下:

> nn.predictions <- predict(nn.model, 
 transformed.test.feature.vars, type="raw")
> confusionMatrix(data=nn.predictions, 
 reference=transformed.test.class.var, 
 positive="X1")

您可以从以下输出中观察到,我们的模型具有72%准确率,这相当不错。它很好地预测了负面评价为负面,这从特异性48%)中可以看出,而且通常灵敏度84%时表现良好。

使用神经网络建模

我们现在将使用以下代码片段来绘制基于神经网络的模型的重要性特征:

> formula.init <- "credit.rating ~ ."
> formula.init <- as.formula(formula.init)
> control <- trainControl(method="repeatedcv", number=10, repeats=2)
> model <- train(formula.init, data=transformed.train, method="nnet", 
 trControl=control)
> importance <- varImp(model, scale=FALSE)
> plot(importance)

这为我们提供了以下根据重要性排序的绘图排名变量:

使用神经网络建模

我们从先前的图中选择了最重要的特征,并按以下方式构建我们的下一个模型:

> formula.new <- "credit.rating ~ account.balance + credit.purpose + savings + current.assets +
foreign.worker + previous.credit.payment.status"
> formula.new <- as.formula(formula.new)
> nn.model.new <- train(formula.new, data=transformed.train, method="nnet")

我们现在在测试数据上执行预测并评估模型性能:

> nn.predictions.new <- predict(nn.model.new, 
 transformed.test.feature.vars, 
 type="raw")
> confusionMatrix(data=nn.predictions.new, 
 reference=transformed.test.class.var, 
 positive="X1")

这为我们提供了以下混淆矩阵,其中包含我们感兴趣的各个指标。从以下输出中我们可以观察到,准确率略有提高至73%,而灵敏度现在提高至87%,但以特异性为代价,它已降至43%

使用神经网络建模

您可以检查它内部进行的超参数调整,如下所示:

> plot(nn.model.new, cex.lab=0.5)

以下图显示了具有不同隐藏层节点数和权重衰减的各种模型的准确率:

使用神经网络建模

根据银行要求最小化损失的要求,我们选择最佳模型作为最初构建的初始神经网络模型,因为它具有与新模型相似的准确率,并且其特异性要高得多,这非常重要。我们现在绘制最佳模型的一些性能曲线如下:

> nn.model.best <- nn.model
> nn.predictions.best <- predict(nn.model.best, transformed.test.feature.vars, type="prob")
> nn.prediction.values <- nn.predictions.best[,2]
> predictions <- prediction(nn.prediction.values, test.class.var)
> par(mfrow=c(1,2))
> plot.roc.curve(predictions, title.text="NN ROC Curve")
> plot.pr.curve(predictions, title.text="NN Precision/Recall Curve")

从以下图中我们可以观察到,AUC0.74,这相当不错,并且比用红色表示的基线表现要好得多:

使用神经网络建模

这标志着我们的预测建模会话的结束,我们将通过模型选择和比较来总结。

模型比较和选择

我们已经探索了各种机器学习技术,并构建了几个模型来预测客户的信用评级,因此现在的问题是我们应该选择哪个模型以及模型之间是如何相互比较的。我们的测试数据有 130 个客户的不良信用评级0)和 270 个客户的良好信用评级1)。

如果您还记得,我们之前讨论过在建模后使用领域知识和业务需求来解释结果并做出决策。目前,我们的决定是选择最佳模型以最大化德国银行的利润并最小化损失。让我们考虑以下条件:

  • 如果我们错误地将信用评级差的客户预测为好,银行将损失其贷出的全部信用金额,因为他将违约支付,因此损失为 100%,可以用-1 表示,以方便计算。

  • 如果我们正确地将信用评级差的客户预测为坏,我们就正确地拒绝了他信用贷款,因此既没有损失也没有利润。

  • 如果我们正确地将信用评级好的客户预测为好,我们就正确地给他提供了信用贷款。假设银行对贷出的金额有利息,让我们假设利润是客户每月支付的利息的 30%。因此,利润表示为 30%或+0.3,以方便计算。

  • 如果我们错误地将信用评级好的客户预测为坏,我们就错误地拒绝了他信用贷款,但在此情况下既没有利润也没有损失。

考虑到这些条件,我们将为各种模型制作一个比较表,包括我们之前为每个机器学习算法的最佳模型计算的一些指标。记住,考虑到所有模型性能指标和业务需求,没有一种模型在所有模型中都是最佳的。每个模型都有其自身的良好性能点,这在以下分析中是显而易见的:

模型比较和选择

前表中突出显示的单元格显示了该特定指标的最佳性能。正如我们之前提到的,没有最佳模型,我们已经列出了针对每个指标表现最好的模型。考虑到总整体收益,决策树似乎是最优模型。然而,这是假设每个客户请求的信用贷款金额是恒定的。记住,如果每个客户请求的贷款金额不同,那么这种总收益的概念就无法比较,因为在这种情况下,一笔贷款的利润可能不同于另一笔,而损失也可能在不同贷款中不同。这种分析有些复杂,超出了本章的范围,但我们将简要说明如何计算。如果你还记得,有一个credit.amount特征,它指定了客户请求的信用金额。由于我们已经在训练数据中有了客户编号,我们可以将评级客户及其请求的金额进行汇总,并对那些产生损失和利润的客户进行汇总,然后我们将得到每种方法的银行总收益!

摘要

在本章中,我们探讨了监督学习领域中的几个重要方面。如果你从我们旅程的开始就跟随了这一章,并且勇敢地走到了最后,给自己鼓掌吧!你现在已经知道了构成预测分析的内容以及与之相关的一些重要概念。此外,我们还看到了预测模型是如何在实际中工作的,以及完整的预测分析流程。这将使你能够在未来构建自己的预测模型,并从模型预测中开始获得有价值的见解。我们还看到了如何实际使用模型进行预测,以及如何评估这些预测以测试模型性能,以便我们可以进一步优化模型,并根据指标和业务需求选择最佳模型。在我们得出结论并开始你自己的预测分析之旅之前,我想提到的是,你应该始终记住奥卡姆剃刀原理,它指出在相互竞争的假设中,应该选择假设最少的那一个,这也可以被解释为有时,最简单的解决方案是最好的一个。不要盲目地使用最新的包和技术来构建预测模型,因为首先你需要理解你正在解决的问题,然后从最简单的实现开始,这通常会带来比大多数复杂解决方案更好的结果。

第七章:社交媒体分析 – 分析 Twitter 数据

“连接”是描述 21 世纪生活的词汇。尽管有许多因素促成了这个术语,但有一个方面发挥了关键作用。那就是网络。网络使距离变得无关紧要,模糊了社会经济界限,它本身就是一个世界,我们都是其中的一部分。特别是网络或互联网在这个数据驱动革命中是一个核心实体。正如我们在前面的章节中看到的,对于大多数现代问题,网络/互联网(以下将互换使用)是数据来源。无论是电子商务平台还是金融领域,互联网每秒都为我们提供大量数据。在这个虚拟世界中,还有另一个数据海洋,它以非常个人化的方式触及我们的生活。社交网络,或社交媒体,是信息巨无霸,也是本章的主题。

在上一章中,我们讨论了金融领域,在那里我们分析了并预测了某家银行客户的信用风险。我们现在转换方向,进入社交媒体领域,看看机器学习和 R 如何使我们能够从这个数据海洋中揭示洞察力。

在本章中,我们将涵盖以下主题:

  • 社交网络的数据挖掘具体方法

  • 不同数据可视化的重要性和用途

  • 如何连接和收集 Twitter 数据的概述

  • 利用 Twitter 数据揭示惊人的洞察力

  • 看看社交网络如何对数据挖掘过程提出新的挑战

社交网络(Twitter)

我们每天都使用社交网络。有无数社交网络迎合各种意识形态和哲学,但 Facebook 和 Twitter(除少数几个外)已经成为社交网络本身的同义词。这两个社交网络之所以受欢迎,不仅因为它们的独特性和服务质量,还因为它们使我们能够以非常直观的方式互动。正如我们在电子商务网站中使用的推荐引擎(见第四章)所看到的,“建立产品推荐系统”,社交网络在 Facebook、Twitter 甚至互联网出现之前就已经存在。

社交网络对科学家和数学家都产生了兴趣。这是一个跨学科的话题,它跨越但不限于社会学、心理学、生物学、经济学、传播学和信息科学。已经发展出各种理论来分析社交网络及其对人类生活的影响,这些影响以影响经济、人口统计、健康、语言、读写能力、犯罪等因素的形式出现。

早在 19 世纪末进行的研究构成了我们今天所说的社会网络的基础。正如其词本身所表明的,社会网络是节点或实体之间的一种连接/网络,这些节点或实体由人类和影响社会生活的元素所代表。更正式地说,它是一个描绘关系和互动的网络。因此,看到各种图理论和算法被用来理解社会网络并不令人惊讶。在 19 世纪和 20 世纪,这些理论仅限于理论模型和艰苦的社会实验,而 21 世纪的技术为这些理论的测试、微调和建模打开了大门,以帮助理解社会互动的动态。尽管通过某些社会网络(称为社会实验)测试这些理论引起了争议,但这些话题超出了本书的范围。我们将限制自己在算法/数据科学领域,并将争议留给专家讨论。

备注

米尔格拉姆实验,或称为小世界实验,是在 20 世纪 60 年代末进行的,旨在考察美国人的平均路径长度。作为该实验的一部分,随机挑选的人被选为邮件链的起点。这些随机挑选的人被要求将邮件发送给下一个人,以便邮件更接近其目的地(波士顿某地),依此类推。这个著名实验记录的平均跳数是六步。都市传说表明,“六度分隔”这个短语起源于这个实验,尽管米尔格拉姆博士本人从未使用过这个术语!他进行了许多更多的实验;去搜索并惊叹吧。

来源:

www.simplypsychology.org/milgram.html

在我们深入具体内容之前,让我们尝试理解选择 Twitter 作为本章节和下一章节分析点的理由。让我们从了解 Twitter 是什么以及为什么它对终端用户和数据科学家都如此受欢迎开始。

如我们所知,Twitter 是一个社交网络/微博服务,它允许用户发送和接收最多 140 个字符的推文。但使 Twitter 如此受欢迎的是它满足基本的人类本能的方式。我们人类是好奇的生物,有着不断被听到的需求。对我们来说,有一个地方或某个人可以表达我们的观点是很重要的。我们喜欢分享我们的经历、成就、失败和想法。在某种程度上,我们也想知道我们的同龄人在做什么,名人忙于什么,或者新闻上有什么。Twitter 正是解决了这些问题。

在 Twitter 出现之前,就已经存在多个社交网络,Twitter 并没有取代其他服务。在我们看来,是 Twitter 组织信息和用户的方式吸引了人们的注意。其独特的关注关系模型满足了我们对好奇心的渴望,而其简短、免费、高速的通信平台使用户能够发声并被全球听到。通过允许用户关注感兴趣的人或实体,它使我们能够跟上他们的最新动态,而无需其他用户反过来关注我们。关注模型使 Twitter 的关系更倾向于兴趣图谱,而不是像 Facebook 这样的社交网络中通常发现的友谊模式。

Twitter 因其信息(以及谣言)的超级快速传播而闻名并被全球使用。在某些以前无法想象的情况下,它被创新地使用,例如在地震或台风等自然灾害时期寻找人们。它被用来传播信息,范围之广,深度之深,以至于达到了病毒般的规模。不对称的关系和高速度的信息交换有助于使 Twitter 成为一个如此动态的实体。如果我们仔细分析和研究这个社交网络的数据和动态,我们可以揭示许多见解。因此,它是本章的主题。

注意

有趣的链接:

www.technologyreview.com/s/419368/how-twitter-helps-in-a-disaster/

www.citylab.com/tech/2015/04/how-twitter-maps-can-be-useful-during-disasters/391436/

www.researchgate.net/publication/282150020_A_Systematic_Literature_Review_of_Twitter_Research_from_a_Socio-Political_Revolution_Perspective?channel=doi&linkId=56050b3f08ae5e8e3f3125cb&showFulltext=true

www.tandfonline.com/doi/abs/10.1080/1369118X.2012.696123

www.psmag.com/nature-and-technology/how-to-use-social-media-usefully

让我们用#RMachineLearningByExample!来对推文应用一些数据科学!

数据挖掘 @社交网络

我们已经通过这本书的章节走过了很长的路,理解了各种概念,并学习了一些令人惊叹的算法。我们甚至参与了在我们的日常生活中有应用的项目。简而言之,我们已经在没有明确使用术语的情况下进行了数据挖掘。现在,让我们抓住这个机会,正式定义数据挖掘。

在传统意义上,采矿指的是从地球中提取有用的矿物(如煤矿开采)。将这一概念置于信息时代的大背景下,采矿则指的是从大量数据中提取有用的信息。因此,如果我们仔细观察,知识挖掘从数据中发现知识(KDD)似乎比“数据挖掘”这个术语更能准确地表达。正如许多关键词一样,简洁明了往往能吸引人的注意。因此,你可能会在很多地方看到“从数据中发现知识”和“数据挖掘”这两个术语被交替使用,这是完全正确的。数据挖掘的过程,类似于采矿,包括以下步骤:

  1. 数据清洗以去除噪声和不需要的数据

  2. 数据转换以将数据转换为适合分析的相关形式

  3. 数据/模式评估以揭示有趣的洞察

  4. 数据展示以可视化有用的知识形式

注意

数据挖掘并不是使用搜索引擎获取信息,比如关于蛇的信息。相反,它涉及到揭示隐藏的洞察,比如蛇是唯一一种在除南极洲以外的每个大陆都能找到的生物!

如果我们花点时间理解前面的步骤,我们就可以看到我们在所有项目中使用了完全相同的过程。请记住,我们只是将我们在章节中一直遵循的过程进行了形式化和展示,并没有遗漏或修改之前章节中完成的任何步骤。

采矿社交网络数据

现在我们已经正式定义了数据挖掘,并看到了将数据转换为知识所涉及的步骤,让我们专注于社交网络的数据。虽然数据挖掘方法与数据来源无关,但有一些需要注意的事项,这可能导致更好的处理和改进的结果。

就像采矿任何其他类型的数据一样,领域知识对于采矿社交网络数据来说绝对是一个加分项。尽管社交网络分析是一个跨学科的主题(如前所述),但它主要涉及分析与用户或实体及其互动相关的数据。

在前面的章节中,我们看到了来自电子商务平台、银行以及与花卉特征相关的各种数据。我们所看到的数据具有不同的属性和特征。但如果我们仔细观察,这些数据都是某种测量或事件捕获的结果。

进入社交网络的领域,游戏场域略有不同,如果不是完全不同。与我们所看到的不同,社交媒体平台的数据极其动态。当我们说动态时,我们指的是数据点的实际内容,而不是其结构。数据点本身可能(也可能不)是结构化的,但内容本身不是。

让我们具体谈谈包含在推文中的数据。一个样本推文可能看起来像这样:

采矿社交网络数据

图片来源:twitter.com/POTUS/status/680464195993911296

如我们所知,推文是一个 140 个字符的消息。由于消息是由用户(通常)生成的,实际消息的长度、语言可能不同,或者可能包含图片、链接、视频等。因此,推文是一个包含用户名(@POTUS)、用户名(奥巴马总统)、消息(来自奥巴马家族...)以及与推文时间(2015 年 12 月 26 日)、点赞数和转发数相关的结构化数据点。推文还可能包含嵌入在消息中的标签、超链接、图片和视频。正如我们将在接下来的章节中看到的,推文除了前面讨论的属性外,还包含大量的元数据(关于数据的数据)。同样,其他社交网络的数据也包含比肉眼所见多得多的信息。

单条推文就能产生如此多的信息,再加上全球范围内每秒有数百万用户疯狂地发推,这产生了大量具有有趣模式的数据,等待被发现。

在其真正意义上,Twitter 的数据(以及社交网络的一般数据)很好地代表了大数据的 3V(体积、种类和速度)。

注意

2013 年 8 月 3 日,日本播出电影《天空之城》期间,每秒产生了 143,199 条推文,这是记录下的一个记录。平均每秒的推文数量通常约为 5700;这个记录是它的 25 倍!更多关于这个记录的信息可以在 Twitter 博客上阅读:blog.twitter.com/2013/new-tweets-per-second-record-and-how

因此,从社交网络中挖掘数据涉及理解数据点的结构,社交网络(如 Twitter 用于快速交换信息,而 LinkedIn 用于专业网络)的潜在哲学或用途,生成数据的速度和数量,以及数据科学家的大脑。

在本章的结尾,我们还将探讨社交网络对传统挖掘方法提出的挑战。

数据和可视化

当数据量每分钟以指数级增长时,数据挖掘活动的结果必须能够使决策者快速识别行动点。结果应该是无噪声/多余信息的,同时足够清晰和完整,以便可以使用。

将信息以最便捷和可用的形式呈现给目标受众(可能是不懂技术的受众),以便他们轻松消费,这是数据挖掘过程中的一个重要方面。到目前为止,在这本书中,我们已经分析了数据,并利用了折线图、条形图、直方图和散点图来揭示和展示洞察。在我们使用本章中的这些以及一些更多的可视化/图表之前,让我们先尝试理解它们的重要性,并明智地使用它们。

在处理数据挖掘作业时,我们通常会如此专注于数据、其复杂性、算法等,以至于我们往往会忽视我们需要使结果易于消费而不是难以阅读的数字和术语表格的部分。除了确保最终报告/文档包含正确和经过验证的数字外,我们还需要确保这些数字以易于最终用户使用的方式呈现。为了使信息/知识易于消费,我们借助不同的可视化。

由于这不是一本关于可视化的书,所以我们有选择性地跳过了通常的折线图、条形图、饼图、直方图和其他细节。在我们接下来使用这些可视化之前,让我们先了解一些非传统但广为人知/使用的可视化。

词云

社交网络以不同的形式和格式生成数据。这些平台上的数据可能被创建、共享、修改、引用或以各种不同的方式使用。为了表示复杂的关系,社交网络数据最广泛使用的可视化之一是标签云词云。例如,这些平台上的文本、图像、视频和博客等对象通常会被频繁标记。因此,标签云/词云代表了用户生成标签的统计数据。这些标签可能代表单词使用的相对频率或它们在多个对象中的存在。使用不同的字体大小和颜色来区分单词/标签,以表示选择的统计数据(通常是频率)。

词云

展示一组推文中常用词汇的词云

树状图

为了表示高维数据,通常不可能同时可视化所有维度。树状图就是这样一种可视化类型,它将所有维度划分为子集,并以分层的方式呈现。具体来说,树状图将维度划分为一组嵌套的矩形。树状图最常引用的例子之一是新闻地图,它可视化由谷歌新闻聚合的新闻,并以不同颜色显示不同的类别;颜色渐变表示文章的出现(在时间尺度上),而矩形的大小表示新闻条目的流行度。

树状图

展示由谷歌新闻聚合的新闻的树状图

图片来源:newsmap.jp/

像素导向地图

可视化不仅使结果更容易理解,而且非常实用。大多数时候,分析过程的结果是多维的。要在二维屏幕/纸张上图形化地表示这些数据是一个挑战。这就是像素导向可视化出现的地方。对于一个 n 维数据集,像素导向可视化将每个 n 维数据点映射到 n 个不同的子窗口中的单个像素。因此,每个数据点被分散在 n 个窗口中,每个窗口对应一个维度。这些帮助我们在一个可视化中映射大量数据。像素导向可视化看起来是这样的:

像素导向地图

样本像素导向地图

图片来源:bib.dbvis.de/uploadedFiles/163.pdf

其他可视化

除了已经提到的可视化之外,还有许多其他有趣的可视化,这些可视化在不同的用例中非常有用。例如,箱线图等可视化对于理解数据分布和异常检测非常有用。同样,还有 Chernoff 面孔、散点图、网络图等可视化,它们各有其优点和用例。

请注意,可视化本身就是一个研究领域,本节只是试图触及冰山一角。我们鼓励读者阅读章节“参考文献”部分中分享的书籍/在线内容,以了解更多相关信息。

开始使用 Twitter API

Twitter 对于使用 Twitter 发推文的 tweple(人们)和数据科学家来说都同样令人愉悦。API 和文档都得到了很好的更新,易于使用。让我们从 API 开始吧。

概述

Twitter 拥有最简单但最强大的 API 集合之一,这是任何社交网络都有的。这些 API 已被 Twitter 本身和数据科学家用来理解 Twitter 世界的动态。Twitter API 使用四个不同的对象,即:

  • 推文: 推文是定义 Twitter 本身的中心实体。正如前文所述,推文包含的信息(元数据)远不止推文的内容/信息。

  • 用户: 任何可以发推文、关注或执行 Twitter 任何操作的任何人或事物都是用户。Twitter 在用户定义上具有独特性,不一定是人类。@MarsCuriosity就是这样一种非人类流行的 Twitter 账号,拥有超过 200 万粉丝!

  • 实体: 这些是从推文对象本身提取的结构化信息片段。这可能包括有关 URL、标签、用户提及等信息。这些对象使处理更快,无需解析推文文本。

  • 地点:一条推文也可能附有位置信息。这些信息可能用于各种目的,例如显示“您附近的趋势话题”或定向营销。

Twitter API 中的前述对象已在网站dev.twitter.com/上进行了详细解释。我们敦促读者阅读以更好地理解对象和 API。

Twitter 在所有主要编程语言/平台上都有可用的库。我们将使用 TwitteR,即 Twitter 为 R 提供的库。

小贴士

Twitter 最佳实践

Twitter 在其开发者网站dev.twitter.com/上明确指定了一套最佳实践和一系列的“可以做”和“不可以做”的事项,其中讨论了安全性/身份验证、隐私等。由于 Twitter 支持庞大的客户群并具有高可用性,它还跟踪其 API 的使用情况,以保持其系统健康。对 API 查询次数有明确的速率限制。请阅读最佳实践,并成为一个#gooddeveloper

注册应用程序

现在我们对 Twitter 及其 API 对象有了足够的背景知识,让我们动手实践。开始使用 API 的第一步是通知 Twitter 关于您的应用程序。Twitter 使用标准的开放认证OAuth)协议来授权第三方应用程序。OAuth 使用应用程序的消费者密钥、消费者密钥、访问令牌和访问令牌密钥,允许它使用连接服务的 API 和数据。

以下快速步骤将为我们设置游戏做好准备:

  1. 前往 Twitter 的应用管理控制台apps.twitter.com/,使用您的凭据登录或如果您还没有账户,则创建一个账户。

  2. 点击创建新应用并填写应用的名称、网站等详细信息。在我们的用途中,我们将命名我们的应用为TwitterAnalysis_rmre。对于回调 URL,请使用http://127.0.0.1:1410指向您的本地系统。您也可以选择其他端口号。

  3. 点击创建您的 Twitter 应用程序以完成流程。您的应用程序管理控制台将类似于以下截图:注册应用程序

    Twitter 应用程序页面

恭喜,您的应用程序已创建并注册到 Twitter。但在我们能够使用它之前,还有一件事情要做。我们需要创建访问令牌,为此我们需要执行以下步骤。

  1. 前往 Twitter 应用详情页面上的密钥和访问令牌链接。

  2. 滚动并点击创建我的访问令牌以为您个人资料生成访问令牌。

  3. 在完成前述步骤后,密钥和访问令牌页面将类似于以下截图:注册应用程序

    应用密钥和访问令牌

我们将使用与下一章相同的同一个应用。请记住消费者密钥、消费者秘密、访问令牌和访问秘密;我们将在我们的应用中需要这些。

注意

为 OAuth 生成的密钥和秘密是敏感信息。它们使您的应用能够访问 Twitter 的数据。请像保管您的密码(甚至更安全)一样保管它们。#安全第一

连接/认证

现在我们已经在 Twitter 端准备好了所有东西,让我们在 R 端也设置一下。在我们开始处理 Twitter 的数据之前,第一步将是使用我们刚刚创建的应用通过 R 进行连接和认证。

我们将利用 Jeff Gentry 的 R 的 TwitteR 库。这个库或客户端允许我们通过 R 使用 Twitter 的 Web API。我们将使用setup_twitter_oauth()方法使用我们的应用凭证(密钥和访问令牌)连接到 Twitter。请将以下代码中的XXXX替换为您在之前步骤中生成的访问密钥/令牌:

> # load library
> library(twitteR)
> # set credentials
> consumerSecret = "XXXXXXXXXXXXX"
> consumerKey = "XXXXXXXXXXXXXXXXXXXXXXXXXx"

No to it:

连接/认证

这将打开您的浏览器,并要求您使用 Twitter 凭证登录并授权此应用,如下面的截图所示:

连接/认证

授权应用以获取数据

一旦授权,浏览器将重定向到我们在 Twitter 上创建应用时提到的回调 URL。您也可以为用户使用一个更具信息量的 URL。

连接/认证

恭喜,您现在已连接到推文的海洋。

提取样本推文

现在我们已通过 R 连接到 Twitter,是时候提取一些最新的推文并分析我们得到的结果了。为了提取推文,我们将使用 Twitter 账号 001(Twitter 的创始人及第一位用户)Jack Dorsey 的账号@jack。以下代码片段将从他那里提取最新的 300 条推文:

> twitterUser <- getUser("jack")
> # extract jack's tweets
> tweets <- userTimeline(twitterUser, n = 300)
> tweets

由于 Twitter 内容丰富,输出包含文本、不可打印的字符和 URL。我们将在稍后查看推文的元数据,但在那之前,提取的信息看起来如下:

提取样本推文

样本推文

要查看可用于分析和操作每条推文的属性和函数,请使用以下getClass方法:

> # get tweet attributes
> tweets[[1]]$getClass()
>
> # get retweets count
> tweets[[1]]$retweetCount
>
> # get favourite count
> tweets[[1]]$favoriteCount

将生成以下输出:

提取样本推文

Twitter 数据挖掘

现在我们已经测试了我们的工具、库和与 Twitter API 的连接,是时候开始寻找 Twitter 领域的隐藏宝藏了。让我们戴上数据挖掘者的帽子,开始挖掘吧!

在本节中,我们将处理从搜索关键词(或 Twitter 词汇中的标签)和用户时间线收集的 Twitter 数据。使用这些数据,我们将通过使用 TwitteR 和其他 R 包的不同函数和实用工具来揭示一些有趣的见解。

注意

请注意,我们的过程将隐式遵循数据挖掘中概述的步骤。为了简洁起见,我们可能不会明确提及每个步骤。我们正在挖掘一些“镀金”的见解;请放心,没有任何步骤被遗漏!

每年,我们都带着新的热情去实现伟大的成就,并改进我们的不足。我们大多数人会以新年决心的形式给自己许下承诺。让我们来看看 2016 年用户是如何处理他们的决心的!

注意

注意:Twitter 数据变化非常快,你的结果/图表可能与本章中描述的不同。

我们将使用相同的应用程序及其凭证来连接并获取 Twitter 数据。以下代码与我们在上一节中提取样本推文的方式完全相同:

library(twitteR)
library(ggplot2)
library(stringr)
library(tm)
library(wordcloud)

consumerSecret = "XXXXXXXXX"
consumerKey = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"

setup_twitter_oauth(consumer_key = consumerKey,consumer_secret = consumerSecret)

除了连接到 Twitter,我们还加载了所需的包,如ggplotstringrtmwordcloud。随着我们的进展,我们将看到这些包在哪里以及如何有用。

一旦连接到我们的数据源,我们就可以开始收集所需的数据。由于我们计划了解用户及其新年决心,我们将提取#ResolutionsFor2016标签的数据。我们也可以使用任何标签,例如#NewYearResolutions#2016Resolutions或标签的组合来获取相关的推文。以下代码不仅提取推文,还将推文/状态对象的列表转换为 R 数据框。我们还把每条推文转换为 UTF-8 格式,以处理不同语言的文本。

注意

惊人事实:Twitter 有 48 种不同的语言,并且还在不断增加!

# trending tweets
trendingTweets = searchTwitter("#ResolutionsFor2016",n=1000)
trendingTweets.df = twListToDF(trendingTweets)
trendingTweets.df$text <- sapply(trendingTweets.df$text,function(x) iconv(x,to='UTF-8'))

正如我们在上一节中看到的,一条推文包含的信息远不止文本本身。众多属性之一是状态源。状态源表示发布推文的设备。它可能是一部手机、平板电脑等等。在我们应用主要转换和清理推文对象之前,我们首先对状态源进行快速转换,将其转换为有意义的格式:

trendingTweets.df$tweetSource = sapply(trendingTweets.df$statusSource,function(sourceSystem) enodeSource(sourceSystem))

上述代码将statusSource从类似<a href=\"http://twitter.com/download/android\" rel=\"nofollow\">Twitter for Android</a>的值转换为简单的“Android”,并将其分配给一个名为tweetSource的新属性。

一旦我们有了数据,数据挖掘过程中的下一步就是清理数据。我们使用文本挖掘包tm来进行转换和清理。特别是Corpus函数帮助我们将推文/状态对象作为文档集合来处理。然后我们使用来自同一包的tm_map实用工具来应用/映射转换,例如将所有文本转换为小写,删除标点符号、数字和停用词。停用词是一系列最常用的词,如 a、an、the 等,在分析文本时可以安全地删除,而不会失去意义。

# transformations
tweetCorpus <- Corpus(VectorSource(trendingTweets.df$text))
tweetCorpus <- tm_map(tweetCorpus, tolower)
tweetCorpus <- tm_map(tweetCorpus, removePunctuation)
tweetCorpus <- tm_map(tweetCorpus, removeNumbers)

# remove URLs
removeURL <- function(x) gsub("http[[:alnum:]]*", "", x)
tweetCorpus <- tm_map(tweetCorpus, removeURL) 

# remove stop words
twtrStopWords <- c(stopwords("english"),'resolution','resolutions','resolutionsfor','resolutionsfor2016','2016','new','year','years','newyearresolution')
tweetCorpus <- tm_map(tweetCorpus, removeWords, twtrStopWords)

tweetCorpus <- tm_map(tweetCorpus, PlainTextDocument)

在我们进行下一步分析数据以寻找隐藏的模式/见解之前,最后的转换是一个术语-文档矩阵。正如其名所示,术语-文档矩阵是一种矩阵表示,其中术语作为行,而列则代表文档。矩阵中的每个条目表示一个术语在给定文档中的出现次数。更正式地说,术语-文档矩阵是一种描述文档集中术语频率的矩阵表示。这种表示在自然语言处理应用中非常有用。它是一种优化的数据结构,能够实现快速搜索、主题建模等。以下是一个简单示例,说明了如何使用这种数据结构,其中我们有两个文本文档,TD1TD2

Twitter 数据挖掘

样本术语-文档矩阵

tm 包为我们提供了一个易于使用的实用工具,称为术语-文档矩阵(TermDocumentMatrix也是可用的),我们使用它将我们的Corpus对象转换为所需的形式:

# Term Document Matrix
> twtrTermDocMatrix <- TermDocumentMatrix(tweetCorpus, control = list(minWordLength = 1))

常用词汇和关联

因此准备好的术语-文档矩阵包含每个推文(在清理和转换之后)中的词汇作为行,而列则代表推文本身。

作为快速检查,让我们看看在我们的数据集中哪些词汇使用得最频繁。将阈值设置为30次或更多。我们使用 apply 实用工具迭代术语-文档矩阵中的每个术语并计算其出现次数。该函数帮助我们过滤掉出现 30 次或更多的术语。

# Terms occuring in more than 30 times
> which(apply(twtrTermDocMatrix,1,sum)>=30)

结果将如以下截图所示:

常用词汇和关联

在推文中出现 30 次或更多的术语

如前一个截图所示,诸如健康、启发和积极等词汇出现在 30 次或更多出现的词汇列表中。说到年度目标,我们大家有很多共同之处,不是吗?

前面的操作是一个快速检查,看看我们是否真的有可以帮助我们了解新年愿望的推文。现在让我们采取正式的方法,并识别数据集中的频繁术语。我们还将尝试以创新且易于理解的方式呈现信息。为了获取数据集中最频繁的术语,我们再次使用tm包中的findFreqTerms函数。此函数为我们提供了一个比之前使用的快速修复更高级的抽象。findFreqTerms还允许我们设置术语频率的最小和最大阈值。在我们的情况下,我们只提到下限并查看结果:

# print the frequent terms from termdocmatrix
> (frequentTerms<-findFreqTerms(twtrTermDocMatrix,lowfreq = 10))

结果看起来像以下截图:

常用词汇和关联

我们得到大约 107 个术语,最小出现次数为 10。如果你仔细看,我们看到的至少 30 次频率的术语也出现在这个列表中,这是理所当然的。

现在我们确定确实有一些术语/单词的频率超过 10 次,让我们创建一个数据框,并按照我们之前决定的方式绘制术语与其频率的关系图。我们使用rowSums函数计算每个术语/单词的总出现次数。然后我们选择出现次数超过 10 次的术语子集,并使用ggplot进行绘图:

# calculate frequency of each term
term.freq <- rowSums(as.matrix(twtrTermDocMatrix))

# picking only a subset
subsetterm.freq <- subset(term.freq, term.freq >= 10)

# create data frame from subset of terms
frequentTermsSubsetDF <- data.frame(term = names(subsetterm.freq), freq = subsetterm.freq)

# create data frame with all terms
frequentTermsDF <- data.frame(term = names(term.freq), freq = term.freq)

# sort by subset DataFrame frequency
frequentTermsSubsetDF <- frequentTermsSubsetDF[with(frequentTermsSubsetDF, order(-frequentTermsSubsetDF$freq)), ]

# sort by complete DataFrame frequency
frequentTermsDF <- frequentTermsDF[with(frequentTermsDF, order(-frequentTermsDF$freq)), ]

# words by frequency from subset data frame
ggplot(frequentTermsSubsetDF, aes(x = reorder(term,freq), y = freq)) + geom_bar(stat = "identity") +xlab("Terms") + ylab("Frequency") + coord_flip()

以下代码块生成了以下频率图:

频繁单词及其关联

分析前面的图表后,我们可以迅速得到一些有趣的点:

  • 妈妈当选总统亿万富翁这些词出现在前十位。这个组合很奇怪,但很有趣。关于这一点,我们稍后再详细讨论。

  • 健康在列表中排名很高,但并未进入前十。因此,看起来健康是势在必行,但并不是特别突出。健身饮食也是如此。

  • 列表中的大多数单词在本质上都是积极的。例如,快乐希望积极改变等单词都指向了乐观的情绪,在迎接新年决心时!

尽管前面的图表以很好的布局方式为我们提供了很多关于单词及其频率的信息,但它仍然没有展示出完整的画面。记住,我们在生成这个图表之前,故意从数据集中提取了一个子集?我们这样做是有目的的,否则图表会变得过长,频率较低的单词会使得整个图表显得杂乱。这个图表遗漏的另一个点是频率之间的相对差异。

如果我们的目标是看到频率之间的相对差异,我们需要一种不同的可视化方式。这时,词云就派上用场了。使用wordcloud库,我们可以轻松地从数据框中生成词云,只需一行代码:

# wordcloud
> wordcloud(words=frequentTermsDF$term, freq=frequentTermsDF$freq,random.order=FALSE)

使用完整数据集生成的词云看起来大致如下:

频繁单词及其关联

前面的词云按照频率递减的顺序显示单词。每个单词的大小强调其频率。你可以尝试使用wordcloud函数生成一些有趣的视觉或艺术作品!

前面的图表中出现了很多单词,但看到亿万富翁这个词出现在前十位,难道不是很有趣吗?这背后的原因是什么?是机器人发出的垃圾邮件,还是某个名人爆红的推文,或者是完全不同的事情?让我们查看这个列表中的顶级推文,看看它是否包含亿万富翁这个词:

# top retweets
> head(subset(trendingTweets.df$text, grepl("trillionaire",trendingTweets.df$text) ),n=1)

以下截图是您会得到的结果:

频繁单词及其关联

结果证明我们的猜测是正确的。这是一条名人发布的、迅速走红的新年决心推文。在 Twitter 上快速搜索,我们发现这条推文:

频繁单词及其关联

图片来源:twitter.com/mishacollins?lang=en

进一步搜索发现,Misha Collins 是电视剧《超自然力量》中的著名演员。我们还可以看到,上述决议被转发惊人的 5k 次!值得注意的是,点赞数达到 14k,超过了转发数。我们能推断出推友们更喜欢点赞/心形符号而不是转发吗?我们还可以看到,诸如 mom、learn、trillionaire、elected 和 President 等词汇无疑都是最常见的词汇。间接地,我们也可以推断出《超自然力量》在 Twitter 上拥有庞大的粉丝群,而 Castiel(Misha 在电视剧中的角色)是该剧中一个受欢迎的角色。他决定学习钩针,这有点令人惊讶吗?

从超自然的事物转移到健身辩论。健身对我们大多数人来说都很重要。像锻炼或去健身房这样的活动在年初的头几个月/几周会激增。让我们看看 Twitter 上的朋友们有多注重健康!

由于许多词汇如健康、饮食、健身、健身房等都与健康的生活方式相关,让我们尝试找到与“健身”一词本身相关的词汇。findAssocs是一个方便的函数,它可以帮助我们从词-文档矩阵中找到与给定词汇至少有指定程度相关性的词汇。我们将使用该函数的输出结果,使用ggplot准备一个词-关联(相关性)图。这个过程与准备前面的频率图类似:

# Associatons
(fitness.associations <- findAssocs(twtrTermDocMatrix,"fitness",0.25))

fitnessTerm.freq <- rowSums(as.matrix(fitness.associations$fitness))

fitnessDF <- data.frame(term=names(fitnessTerm.freq),freq=fitnessTerm.freq)

fitnessDF <- fitnessDF[with(fitnessDF, order(-fitnessDF$freq)), ]
ggplot(fitnessDF,aes(x=reorder(term,freq),y=freq))
+geom_bar(stat = "identity") +xlab("Terms")
+ ylab("Associations")
+ coord_flip()

与“健康”一词最密切相关的词汇如下:

频繁词汇及其关联

同样的数据以图形形式更易于阅读,如下所示:

频繁词汇及其关联

如前图所示,诸如减肥锻炼getfit等术语证明了我们的观点,即推友们对健康的关注程度与我们一样。值得注意的是,列表中出现了“yogavideos”这个术语。看起来在 2016 年,瑜伽似乎正在赶上其他保持健康的技术。列表中还有冥想

流行设备

到目前为止,我们已经处理了推文的可见组件,如文本、转发次数等,并且能够提取许多有趣的见解。让我们拿出我们的精确工具,更深入地挖掘我们的数据。

如上几节所提到的几次,一条推文所包含的信息远比表面所见的多。其中一条信息就是关于推文的来源。Twitter 诞生于短信时代,其许多特征,如 140 个字符的字数限制,都让人联想到那个时代。了解人们如何使用 Twitter,即经常用来访问和发布推文的设备,将会很有趣。尽管世界已经远离了短信时代,但手机无处不在。为了获取这些信息,我们将利用我们的数据框trendingTweets.df中的属性tweetSource。我们是从tweet对象中已经存在的statusSource属性创建了这个附加属性(参见本节开头快速回顾)。

为了清晰起见,我们将使用基于转发次数的trendingTweets.df数据框的子集。我们再次使用ggplot来可视化我们的结果。

# Source by retweet count
trendingTweetsSubset.df <- subset(trendingTweets.df, trendingTweets.df$retweetCount >= 5000 )

ggplot(trendingTweetsSubset.df, aes(x =tweetSource, y =retweetCount/100)) + geom_bar(stat = "identity") +xlab("Source") + ylab("Retweet Count")

下面的图表是您的结果:

流行设备

毫无疑问,iPhone 是最受欢迎的设备,其次是 Android 和网页。有趣的是,人们使用网页/网站转发推文的次数比 iPad 还要多!Windows Phone 显然在这里有一些严重的问题需要解决。我们也可以推断 iPhone 是 tweeples 的首选设备吗?或者 iPhone 为 Twitter 提供了比其他设备更好的体验?或者我们甚至可以更进一步,说 iPhone 上的 Twitter 比任何其他设备都有一个更容易访问的“转发”按钮。这样的推断还有很多,但所有这些都蕴含着大量的知识/潜力,可以被管理层、用户体验团队等用来改进和改变事物。

层次聚类

我们在之前的章节中已经看到了聚类和分类(参见第二章,让我们帮助机器学习),并揭示了关于手头数据的某些有趣事实。对于我们的当前用例,尽管我们的推文都与 2016 年的决心有关,但我们永远无法确定 tweeples 会做出什么样的决心。这使得层次聚类成为一个非常合适的用例。与需要预先设置集群数量的 k-means 或其他聚类算法不同,层次聚类算法在计算时不依赖于它。

在我们将层次聚类应用于我们的数据之前,让我们抓住这个机会来理解层次聚类。层次聚类,就像任何其他聚类算法一样,帮助我们将相似的项目分组在一起。这个算法的一般细节可以解释如下:

  • 初始化:这是第一步,其中每个元素被分配到它自己的集群中。对于一个包含n个元素的集合,算法创建了n个不同的集群,每个集群中有一个元素。在这一步决定了一个距离/相似度度量。

  • 合并:在此步骤中,根据选择的距离/相似性度量,识别最近的簇对并将它们合并成一个簇。这一步骤的结果是比迄今为止的总簇数少一个簇。

  • 计算/重新计算:我们计算/重新计算在合并步骤中形成的新簇与现有簇之间的距离/相似性。

合并计算步骤会重复进行,直到我们只剩下一个包含所有n个项目的单个簇。正如其名所示,此算法生成一个层次结构,叶子表示基于相似性/距离结合的个体元素簇,随着我们向树根靠近。输出树通常被称为树状图

合并步骤是此算法存在变体的地方。有几种方法可以识别最近的簇。从简单的方法,如单链,它考虑两个簇中任何两个元素之间的最短距离作为距离度量,到复杂的方法,如 Ward 的方法,它使用方差来找到最紧凑的簇,有几种方法可以根据用例采用。

回到 Twitter 世界,让我们使用层次聚类来查看哪些术语/推文是最接近的。对于我们的当前用例,我们将使用单一方法作为合并标准。您可以尝试不同的算法并观察差异。

为了执行层次聚类,我们首先处理我们的数据集以去除稀疏术语,以便于清晰。为此,removeSparseTerms函数帮助我们删除具有低于指定限制的稀疏性的数据行。然后我们使用hclust实用程序来形成簇。此实用程序输出的结果可以直接绘制。让我们为此编写一些代码:

# remove sparse terms
twtrTermDocMatrix2 <- removeSparseTerms(twtrTermDocMatrix, sparse = 0.98)

tweet_matrix <- as.matrix(twtrTermDocMatrix2)

# cluster terms
distMatrix <- dist(scale(tweet_matrix))

fit <- hclust(distMatrix,method="single")
plot(fit)

输出的树状图非常简单易懂:

层次聚类

如果您观察右侧第二个簇,它包含术语万亿富翁当选妈妈打电话等等。将这些术语映射回 Mischa Collins 的顶转发推文,所有这些术语都在那条推文中被提及,并且我们的算法正确地将它们聚类在一起。聪明,不是吗?作为一个小练习,观察其他簇并看看这些术语在包含它们的推文中是如何出现的。在这里的一个重要观察是,树状图正确地将所有频繁术语映射到单个根下,这再次证实了所有这些术语都指向我们 2016 年决议的中心主题!

主题建模

到目前为止,我们的分析主要关于来自世界各地的用户有关新年决心的推文。我们已经分析了与我们选择的主题相关的推文。忽略垃圾邮件和其他噪声推文,我们的数据大致符合一个单一的主题。这个主题本身构成了一组单词(如健康、亿万富翁、健身、饮食、妈妈等),这些单词广泛描述了不同的决心。为了拓宽我们的分析范围并发现更多见解,让我们来谈谈主题建模的概念。

主题建模是一个发现未标记文本语料库中模式的过程,它代表了语料库的精髓。一个主题本身可以描述为一组共同出现的单词,用来描述大量文本。

在一次关于主题建模的会议期间提到的另一个定义:

主题建模

图片来源:twitter.com/footnotesrising/status/264823621799780353

主题建模的目的是自动识别语料库的潜在主题,因此对于需要基于主题进行信息检索的应用程序(但在没有已知关键词的情况下)是有用的。例如,通过使用“一国与另一国的关系”这样的主题而不是搜索关键词然后跟随链接,从报纸档案中了解两个国家之间的关系,这不是很令人惊讶吗?请注意,通过跟随链接来发现信息同样强大,但它还有很多不足之处。

执行主题建模的一种方式是通过潜在狄利克雷分配LDA);它是功能最强大且应用最广泛的模型之一。

LDA 由 David M Blie 在 2003 年的论文《概率主题模型导论》中提出。正如他的论文所说,LDA 可以被定义为一个生成模型,它允许通过未观察到的组来解释一组观察结果,这些组解释了为什么数据的一些部分是相似的。LDA 基于这样的假设,即文档表现出多个主题。

LDA 是一个概率模型,其数学相当复杂,超出了本书的范围。以非数学的方式,LDA 可以被解释为一个模型/过程,它有助于识别导致一组文档生成的主题。

注意

对于进一步阅读,请参阅 Blei 的论文。

www.cs.princeton.edu/~blei/papers/Blei2011.pdf

一篇用简单语言解释一切的博客:

tedunderwood.com/2012/04/07/topic-modeling-made-just-simple-enough/

对于我们的目的/用例,我们可以假设 LDA 是一个模型/过程,它帮助我们从一个未标记文本的语料库中识别潜在(隐藏/潜在)主题。幸运的是,R 将大部分数学细节以名为topicmodels的库的形式抽象出来。

为了进行主题建模,我们将使用一组新的推文。国际空间站(ISS)有多个 Twitter 账号,其中之一是@ISS_Research,它特别针对来自 ISS 的研究相关推文。让我们通过分析其时间线上的推文来探索@ISS_Research最近在忙些什么。我们将分析这些推文,以识别 ISS 研究背后的主题。为此,我们将使用与之前相同的过程提取推文并进行转换/清理。以下代码片段就是这样做的:

# set user handle
atISS <- getUser("ISS_Research")

# extract iss_research tweets
tweets <- userTimeline(atISS, n = 1000)

tweets.df=twListToDF(tweets)

tweets.df$text <- sapply(tweets.df$text,function(x) iconv(x,to='UTF-8'))

#Document Term Matrix
twtrDTM <- DocumentTermMatrix(twtrCorpus, control = list(minWordLength = 1))

document-term matrix, unlike last time where we prepared a *term-document matrix*.

一旦我们有了所需格式的推文,topicmodels包中的LDA实用程序帮助我们揭示隐藏的主题/模式。LDA 实用程序需要输入主题数量以及文档-术语矩阵。我们现在将尝试八个主题。以下代码使用LDA为八个主题中的每一个提取六个术语:

#topic modeling

# find 8 topics
ldaTopics <- LDA(twtrDTM, k = 8) 

#first 6 terms of every topic
ldaTerms <- terms(ldaTopics, 6) 

# concatenate terms
(ldaTerms <- apply(ldaTerms, MARGIN = 2, paste, collapse = ", "))

使用 LDA 生成的主题列表如下:

主题建模

一个视觉表示将更容易理解。我们可以利用qplot快速在面积图上按时间绘制主题,如下所示:

# first topic identified for every tweet
firstTopic <- topics(ldaTopics, 1)

topics <- data.frame(date=as.Date(tweets.df$created), firstTopic)

qplot(date, ..count.., data=topics, geom="density",fill=ldaTerms[firstTopic], position="stack")+scale_fill_grey()

生成的图表看起来如下截图所示:

主题建模

让我们现在分析输出结果。LDA 生成的每个主题的术语列表似乎给我们提供了对 ISS 上正在进行的工作/研究的一些很好的洞察。诸如火星、微重力、花朵、Cygnus 等术语告诉我们主要的研究领域或至少科学家/宇航员在 ISS 上讨论的主题。诸如 stationcdrkelly 和 astrotimpeake 之类的术语看起来更像是 Twitter 账号。

注意

一个快速练习是使用当前的@ISS_Research时间线数据,挖掘如stationcdrkelly这样的处理,以发现更多信息。谁知道呢,这可能会变成一个很好的宇航员名单来关注!

qplot输出为我们的普通主题列表添加了时间维度。分析时间维度上的主题有助于我们了解特定研究主题何时被讨论,或者何时宣布了令人惊叹的事情。列表中的第二个主题,或者图例顶部的第四个主题包含单词 flower。由于科学家最近在太空中成功培育了一些橙色花朵,上面的图表帮助我们得出结论,新闻最早在 1 月 15 日左右在 Twitter 上发布。快速查看 Twitter/新闻网站确认,新闻是在 2016 年 1 月 18 日通过推文发布的……非常接近!

提示

彩色面积图

尝试从qplot中移除scale_fill_grey()选项,以获得一些比纯灰色更容易阅读的美丽图表。

因此,我们最终学习了使用 LDA 在 ISS 数据上进行的主题建模,并发现了科学家和宇航员在太空中所做的一些令人惊叹的事情。

社会网络数据挖掘的挑战

在我们结束这一章之前,让我们看看社交网络对数据挖掘过程提出的不同挑战。以下是一些论点、问题和挑战:

  • 毫无疑问,社交网络生成数据在各个方面都归类为大数据。它具有所有体积、速度和多样性,足以压倒任何系统。然而,有趣的是,如此庞大的数据源所面临的挑战是足够细粒度数据的可用性。如果我们放大我们的数据集,并尝试基于每个用户使用数据,我们会发现没有足够的数据来完成一些最常见的工作,比如做出推荐!

  • 如 Twitter 这样的社交网络每秒处理数百万用户创建和分享的大量数据。为了确保他们的系统始终运行,他们会对通过 API 获取的数据量设置限制(安全性也是这些限制背后的一个主要原因)。这些限制使数据科学工作陷入困境,因为很难获得足够的数据样本来正确/完整地代表总体。样本不足可能会导致错误的模式或完全错过模式。

  • 社会网络分析中的预处理和结果评估也是一个挑战。在预处理数据时,我们会移除噪声内容。由于数据以各种形状和大小涌入,确定噪声内容比简单地移除停用词更具挑战性。由于大多数情况下没有可用的基准事实,以及由于此处和其它方面的限制,评估结果也是一个挑战,很难有信心确定结果的可靠性。

上文提出的论点/挑战要求数据科学家设计出创新和创造性的方法,这也是他们的工作有趣且极具回报性的原因。

参考文献

一些关于可视化的知名书籍如下:

关于这个主题的一些知名博客如下:

摘要

社交网络分析是数据科学领域的一个热门话题。正如我们在本章中看到的,这些平台不仅为我们提供了连接的方式,而且也为我们提供了一个独特的机会来研究全球范围内的人类动态。通过本章,我们学习了一些有趣的技术。我们首先从理解社交网络环境中的数据挖掘开始,接着讨论了可视化的重要性。我们专注于 Twitter,并了解了不同的对象和 API 来操作它们。我们使用了 R 的各种包,如TwitteRTM,来连接、收集和操作我们的分析数据。我们使用 Twitter 的数据来了解频率分布。最后,我们展示了社交网络词汇和关联、推特用户常用的流行设备、层次聚类甚至触及了主题建模所提出的挑战。我们使用了ggplot2wordcloud来可视化我们的结果,以及数据挖掘过程。在总结本章时,我们确信你现在可以欣赏到这些平台背后的惊人动态以及 R 分析这些动态的能力。我们还没有结束对@Twitter的分析,请继续关注你的#sentiments

第八章。Twitter 数据的情感分析

*"他塑造了公众舆论...使得制定法律和决策成为可能或不可能。"
--亚伯拉罕·林肯

人们的想法不仅对政治家和名人很重要,对我们大多数社会人也是如此。这种了解自己观点的需求影响了人们很长时间,并被前面的著名引言恰当地总结。意见的困扰不仅影响我们的观点,还影响我们使用产品和服务的方式。正如在学习市场篮子分析和推荐引擎时讨论的那样(参见第三章,使用市场篮子分析预测客户购物趋势和第四章,构建产品推荐系统分别),我们的行为可以通过观察具有类似特征(如价格敏感度、颜色偏好、品牌忠诚度等)的一组人的行为来近似或预测。我们在前面的章节中也讨论了,长期以来,我们在做出下一个重大购买决策之前,会向我们的朋友和亲戚征求他们的意见。虽然这些意见对我们个人来说很重要,但我们可以从这样的信息中得出更多有价值的见解。

仅仅说万维网的到来只是加速和扩大了我们的社交圈,这还不足以表达其影响。无需重复,值得一提的是,网络为分析人类行为开启了新的途径。

在上一章中,社交网络是讨论的主题。我们不仅使用社交网络作为工具来获取洞察,还讨论了这些平台满足我们天生的好奇心,想知道别人在想什么或做什么。社交网络为我们提供了一个平台,我们可以表达自己的观点并被人听到。其中“被人听到”这一方面定义和操作起来有些棘手。例如,我们对这些平台上某人或某事的观点和反馈(假设它们是真实的)肯定会直接或间接地被我们圈子中的人听到,但它们可能或可能不会被它们旨在影响的人或组织听到。尽管如此,这样的观点或反馈确实会影响与之相关的人及其随后的行为。这种观点的影响以及我们对人们想法的一般好奇心,加上更多这样的用例,正是本章的动机。

在本章中,我们将:

  • 了解情感分析及其关键概念

  • 探讨情感分析的应用和挑战

  • 理解执行意见挖掘的不同方法

  • 在 Twitter 数据上应用情感分析的概念

理解情感分析

互联网公司及其首席执行官作为全球经济体中最有利可图的实体之一,这充分说明了世界是如何被技术和互联网所驱动的,以及是如何被塑造的。与其他任何媒介不同,互联网已经无处不在,渗透到我们生活的方方面面。我们使用和依赖互联网以及基于互联网的解决方案来获取建议和推荐,这并不奇怪,除了用于许多其他目的之外。

正如我们在前几章所看到的,互联网与电子商务和金融机构等领域的联系非常深远。但我们对在线世界的使用和信任并不仅限于此。无论是预订你邻里的新餐厅的桌子,还是决定今晚要看哪部电影,我们都会在做出最终决定之前,从互联网上获取他人的意见或分享的内容。正如我们稍后将看到的,这样的决策辅助工具不仅限于商业平台,还适用于许多其他领域。

意见挖掘或情感分析(正如它被广泛且可互换地称呼)是使用自然语言处理、文本分析和计算语言学自动识别文本中主观性的过程。情感分析旨在使用这些技术识别说话者的正面、负面或中性意见、情感或态度。情感分析(以下简称情感挖掘)在从商业到服务领域的全球范围内都有应用。

情感分析的关键概念

现在,我们将探讨与情感分析相关的关键术语和概念。这些术语和概念将帮助我们使接下来的讨论更加规范化。

主观性

意见或情感是个人对观点和信念的表达。此外,主观性(或主观文本)表达了我们对于产品、人物、政府等实体的情感。例如,一个主观句子可能是“我喜欢使用 Twitter”,这表明一个人对某个特定社交网络的喜爱,而一个客观句子则是“Twitter 是一个社交网络”,第二个例子只是陈述了一个事实。情感分析围绕主观文本或主观性分类展开。同样重要的是要理解并非所有主观文本都表达情感。例如,“我刚创建了 Twitter 账户”。

情感极性

一旦我们有一段主观性(并表达某种情感)的文本,接下来的任务就是将其分类为正面或负面情感类别之一(有时也考虑中性)。这项任务还可能涉及将文本的情感放置在连续(或离散)的极性尺度上,从而定义积极程度(或情感极性)。情感极性分类可能根据上下文处理不同的类别。例如,在电影评分系统中,情感极性可能被定义为喜欢与不喜欢,或在辩论中观点可能被分类为支持与反对。

意见总结

从一段文本中提取观点或情感是情感分析过程中的一个重要任务。这通常随后是对情感的总结。为了从与同一主题(例如,某部电影的评论)相关的不同文本中得出见解或结论,将情感汇总(或总结)成可消费的形式以得出结论(电影是票房大作还是失败之作)是很重要的。这可能涉及到使用可视化来推断整体情感。

特征提取

如我们在各章节中看到的,特征识别和提取是使机器学习算法成功或失败的关键。它是在数据本身之后最重要的因素。让我们看看在解决情感分析问题中使用的某些特征集:

  • TF-IDF:信息检索大量使用词频-逆文档频率tf-idf)来实现快速的信息检索和分析。在 tf-idf 的上下文中,一段文本被表示为一个包含单词作为其组成部分的特征向量。最近的研究也表明,在情感分析的上下文中,与单词的频率相比,单词的存在可以提高性能和准确性。

    注意

    来源

    Bo Pang, Lillian Lee, 和 Shivakumar Vaithyanathan. Thumbs up? Sentiment classification using machine learning techniques. In Proceedings of the Conference on Empirical Methods in Natural Language Processing (EMNLP), pages 79–86, 2002.

    TF-IDF 表示为:特征提取

    其中,

    tf(t,d) 是文档 d 中术语 t 的词频。

    idf(t,D) 是文档集 D 中术语 t 的逆文档频率。

    例如,我们有以下两个文档及其术语和相应频率的截图:

    特征提取

    在其最简单的形式中,术语 TwitterTF-IDF 可以表示为:

    特征提取

    可以使用不同的权重方案来计算 tfidf;前面的例子使用以 10 为底的对数来计算 idf

  • n-Grams:计算语言学和概率将文本语料库视为连续的术语序列,这些术语可以是音素、字母、单词等。基于 n-gram 的建模技术源于信息理论,其中下一个字符或单词的可能性基于前n个术语。根据n的值,特征向量或模型被称为单语(对于n=1)、双语(对于n=2)、三元语(对于n=3)等等。n-grams 对于处理词汇表外的单词和近似匹配特别有用。例如,考虑一个单词序列,一个像A chapter on sentiment analysis这样的句子会有诸如a chapterchapter onon sentimentsentiment analysis等双语。

    注意

    Google 关于使用 n-grams 的有趣工作:googleresearch.blogspot.in/2006/08/all-our-n-gram-are-belong-to-you.html

  • 词性POS):理解和利用语言的基本结构进行分析具有明显的优势。词性是用于创建句子、段落和文档的语言规则。在其最简单的形式中,形容词通常是主观性的良好指标(尽管并非总是如此)。许多方法在分类主观文本时利用了形容词的极性。使用包含形容词的短语已被证明可以进一步提高性能。对使用其他词性(如动词和名词)的研究,以及与形容词一起使用,也显示出积极的结果。

    注意

    参考文献

    Peter Turney. Thumbs up or thumbs down? Semantic orientation applied to unsupervised classification of reviews. In Proceedings of the Association for Computational Linguistics (ACL), pages 417–424, 2002.

    以下示例显示了在样本句子中标记的词性(形容词、名词等),例如,We saw the yellow dog

    特征提取

    来源:www.nltk.org/

  • 否定:在情感分析的情况下,否定起着重要的作用。例如,像I like orangesI don't like oranges这样的句子,它们之间的区别仅在于单词don't,但否定词翻转了句子的极性到相反的类别(分别是正极和负极)。否定可以作为次要特征集使用,其中原始特征向量按原样生成,但后来根据否定词翻转极性。还有其他变体,与不考虑否定影响的方法相比,它们在结果上有所改进。

  • 主题特定特征:主题在设置上下文中起着重要作用。由于情感分析涉及说话者的观点,因此主观性受到所讨论主题的影响。在分析主题与文本语料库情感之间的关系方面进行了大量研究。

注意

参考文献

Tony Mullen 和 Nigel Collier. 使用支持向量机和多种信息源进行情感分析。在自然语言处理实证方法会议(EMNLP)论文集中,第 412–418 页,2004 年 7 月。海报论文。

方法

现在我们已经对情感分析领域的核心概念有了基本的了解,让我们来看看解决这个问题的不同方法。

情感分析主要在以下两个抽象级别上进行:

  • 文档级别:在这个抽象级别,任务是分析给定的文档,以确定其整体情感是积极、消极(或在某些情况下是中性的)。基本假设是整个文档表达了对单个实体的意见。例如,给定一个产品评论,系统会分析它以确定评论是积极的还是消极的。

  • 句子级别:句子级别分析是情感分析的一种更细粒度的形式。这种粒度级别抵消了这样一个事实,即文档中的所有句子并不都是主观的,因此更好地利用主观性分类来确定每句话的情感。

与其他机器学习技术类似,情感分析也可以使用监督和无监督方法来解决:

  • 监督方法:情感分析的研究已经进行了很长时间。虽然早期研究受限于标记数据集的可用性,并且进行了相当浅的分析,但现代监督学习方法在情感分析方面取得了显著进展,无论是在利用这些技术的系统数量上,还是在由于标记数据集的可用性而导致的系统整体性能上。例如,WordNet、SentiWordNet、SenticNet、新闻稿、Epinions 等数据集极大地帮助研究人员通过提供包含极性词汇、分类文档、用户意见等数据集来改进监督算法。朴素贝叶斯支持向量机SVM)等算法,如第六章(part0046_split_000.html#1BRPS1-973e731d75c2419489ee73e3a0cf4be8 "第六章. 信用风险检测和预测 – 预测分析")中讨论的,以及基于最大熵的分类算法,是监督学习方法的经典例子。

  • 无监督方法(Unsupervised Approach): 无监督情感分析算法通常从构建或学习情感词典开始,然后确定文本输入的极性。词典生成是通过诸如语言启发式、自举等技术完成的。Turney 在他的 2002 年论文中详细描述了一种著名的方法,其中他使用一些基于词性(POS)的固定句法模式进行无监督情感分析。

    注意:

    参考:

    语言启发式(Linguistic heuristics): Vasileios Hatzivassiloglou 和 Kathleen McKeown. 预测形容词的语义方向。在联合 ACL/EACL 会议论文集中,第 174-181 页,1997 年。

    自举(Bootstrapping): Ellen Riloff 和 Janyce Wiebe. 学习主观表达式的提取模式。在自然语言处理实证方法会议(EMNLP)论文集中,2003 年。

    Turney: Peter Turney. 点赞还是踩?语义方向在无监督分类评论中的应用。在计算语言学协会(ACL)会议论文集中,第 417-424 页,2002 年。

应用:

正如我们一直在讨论的,我们对在线意见的依赖是一种惊喜。在购买产品、下载软件、选择应用程序或选择餐厅之前,我们有意或无意地检查这些意见或受其影响。情感分析或意见挖掘在许多领域都有应用;它们可以概括为以下广泛类别:

  • 在线和离线商业(Online and Offline Commerce): 顾客的偏好可以在一瞬间决定品牌的命运。要使产品成为热销商品,包括定价、包装和营销在内的所有方面都必须完美无缺。顾客会对与产品相关的所有方面形成看法,从而影响其销售。这不仅适用于在线商业,顾客在购买前会在多个网站或博客上查看产品评论,而且口碑和其他类似因素也会影响离线商业中的顾客意见。因此,情感分析成为品牌或公司跟踪和分析以保持领先地位的重要因素。对社交媒体内容,如推文、Facebook 帖子、博客等的分析为品牌提供了洞察顾客如何看待其产品的见解。在某些情况下,品牌会推出特定的营销活动来设定关于产品的普遍情绪或炒作。

  • 治理(Governance): 在大多数活动都有在线对应物的世界中,政府也不例外。全球各国政府都开展了利用情感分析在政策制定和安全(通过分析和监控任何敌意或负面通信的增加)方面的问题的项目。分析人员还使用情感分析来确定或预测选举的结果。例如,eRuleMaking 等工具将情感分析作为关键组成部分。

除了上述两个类别之外,意见挖掘在推荐引擎和通用预测系统等领域的应用中充当一种增强技术。例如,意见挖掘可以与推荐引擎结合使用,排除那些意见或情感低于某些阈值的产品的推荐列表。情感分析也可能在预测即将上映的电影是否会成为票房炸弹方面找到创新的应用,这基于与明星阵容、制作公司、电影主题等相关联的情感。

挑战

理解他人的观点和/或情感是一个固有的困难任务。能够以算法方式处理这样的问题同样困难。以下是在执行情感分析时面临的一些挑战:

  • 理解和建模自然语言结构:情感分析本质上是一个自然语言处理(NLP)问题,尽管是受限的。尽管情感分析是一种受限的自然语言处理形式,涉及对积极、消极或中性的分类,但它仍然面临诸如指称消解、词义消歧和否定处理等问题。近年来,自然语言处理以及情感分析方面的进步在一定程度上帮助解决了这些问题,但在我们能够完美地模拟自然语言的规则之前,还有很长的路要走。

  • 讽刺:情感可以通过相当微妙的方式表达。这不仅仅是负面情感;积极的情感也可以在讽刺的句子中巧妙地隐藏。由于理解讽刺是一种只有少数人能够掌握的技巧,因此很难对讽刺进行建模并正确识别情感。例如,评论“这样一个简单易用的产品,你只需要阅读手册中的 300 页”,虽然只包含积极的词汇,但带有一种不易建模的负面味道。

  • 评审和评审质量:每个人的观点都不尽相同。我们中的一些人可能会非常强烈地表达自己的观点,而其他人可能不会。另一个问题是,无论是否了解某个主题,每个人都有自己的观点。这导致了评审和评审质量的问题,可能会影响整体分析。例如,一个普通读者可能不是最合适的人选来评审一本新书。同样,让一位新作者的书被评论家评审可能也不太合适。这两种极端情况可能会导致结果有偏见或得出错误的见解。

  • 意见数据规模和偏差:网络上有大量的博客和网站为用户提供了一个平台,让他们可以就地球上以及更远的地方的任何可能的事情发表和分享意见。然而,在细粒度层面上,意见数据仍然是一个问题。正如我们在上一章讨论的那样,与特定上下文(比如一个品牌或一个人)相关的数据量非常有限,这影响了整体分析。此外,由于偏见、错误的事实或谣言,可用的数据有时会偏向(或反对)某些实体。

对推文的情感分析

既然我们已经掌握了情感分析领域的核心术语和概念,让我们将我们的理论付诸实践。我们已经看到了情感分析的一些主要应用领域以及一般面临的挑战。在本节中,我们将进行情感分析,分为以下类别:

  • 极性分析:这将涉及使用标记的正面和负面词汇列表对情感极性进行评分和汇总。

  • 基于分类的分析:在这种方法中,我们将利用 R 丰富的库来执行基于可供公众使用的标记推文的分类。我们还将讨论它们的性能和准确性。

R 有一个非常强大的库,名为TwitteR,用于从 Twitter 中提取和操作信息。正如我们在上一章所看到的,在我们能够使用TwitteR或任何其他用于情感分析的库之前,我们首先需要使用 Twitter 的应用程序管理控制台创建一个应用程序。对于本章,我们将重用上一章中的应用程序(请妥善保管您的应用程序密钥和密码)。此外,在接下来的章节中,我们将以前几章中的代码为基础,以更结构化的格式使用我们的代码,以便重用并遵循#bestCodingPractices

在我们开始分析之前,让我们首先重构我们现有的代码并编写一些辅助函数,这些函数将在以后派上用场。正如我们所知,可以使用搜索词或从用户的时序中提取 Twitter 的数据。以下两个辅助函数帮助我们以可重用的方式完成相同的任务:

#extract search tweets
extractTweets <- function(searchTerm,tweetCount){
 # search term tweets
 tweets = searchTwitter(searchTerm,n=tweetCount)
 tweets.df = twListToDF(tweets)
 tweets.df$text <- sapply(tweets.df$text,function(x) iconv(x,to='UTF-8'))

 return(tweets.df)
}

#extract timeline tweets
extractTimelineTweets <- function(username,tweetCount){
 # timeline tweets
 twitterUser <- getUser(username)
 tweets = userTimeline(twitterUser,n=tweetCount)
 tweets.df = twListToDF(tweets)
 tweets.df$text <- sapply(tweets.df$text,function(x) iconv(x,to='UTF-8'))

 return(tweets.df)
}

函数extractTweets接受search词和要提取的推文数量作为输入,并返回一个包含转换为 UTF8 编码的文本的数据框。同样,函数extractTimelineTweets接受用户名和推文数量作为输入,并返回一个包含转换为 UTF8 编码的文本的数据框。因此,前两个函数将帮助我们多次提取推文(基于不同的search词或用户),而无需反复重写相同的代码行。

继续同一主题,我们将编写另一个辅助函数来清理和转换我们的数据集。正如我们在上一章中看到的,R 的 tm 库为我们提供了各种实用函数,可以快速清理和转换文本语料库。在这个函数中,我们将使用 tm_map 来转换我们的推文:

# clean and transform tweets
transformTweets <- function(tweetDF){
 tweetCorpus <- Corpus(VectorSource(tweetDF$text))
 tweetCorpus <- tm_map(tweetCorpus, tolower)
 tweetCorpus <- tm_map(tweetCorpus, removePunctuation)
 tweetCorpus <- tm_map(tweetCorpus, removeNumbers)

 # remove URLs
 removeURL <- function(x) gsub("http://[[:alnum:]]*", "", x)
 tweetCorpus <- tm_map(tweetCorpus, removeURL) 

 # remove stop words
 twtrStopWords <- c(stopwords("english"),'rt','http','https')
 tweetCorpus <- tm_map(tweetCorpus, removeWords, twtrStopWords)

 tweetCorpus <- tm_map(tweetCorpus, PlainTextDocument)

 #convert back to dataframe
 tweetDataframe <- data.frame(text=unlist(sapply(tweetCorpus, 
 ``, "content")), stringsAsFactors=F)

 #split each doc into words
 splitText <- function(x) {
 word.list = str_split(x, '\\s+')
 words = unlist(word.list)
 }

 # attach list of words to the data frame
 tweetDataframe$wordList = sapply(
 tweetDataframe$text,
 function(text) splitText(text))

 return (tweetDataframe)
}

除了常见的转换,例如去除停用词、转换为小写、去除标点符号等,函数 transformTweets 在单词级别对每条推文进行分词,并将每条推文中的单词列表附加到对象上。此外,该函数返回一个数据框中的转换后的推文,以便进行进一步的操作。

极性分析

如在 关键概念 部分所述,极性是对考虑中的文本片段的正、负或中性分类。类标签可能根据上下文(喜欢与不喜欢或有利与不利)而变化。极性也可能附有一个程度,将分析文本放置在极性(或离散)的连续(或离散)尺度上(例如从 -55)。这种极性程度有助于我们分析文本中积极(或消极)的程度(或程度)。这在比较研究中特别有用,因为我们有机会参考某些基准来查看分析文本。

在本节中,我们将分析推文并根据每条推文中确定的极性词对它们进行评分。简单的易于编码的算法概述如下步骤:

  1. 根据选定的搜索词或推特用户名提取推文。

  2. 清理和转换推文,使其适合分析,将推文分词成单词列表。

  3. 加载用于极性词识别的正面和负面词列表。

  4. 对于每条推文,计算与前一步骤 3 中获得的正面和负面词列表匹配的正面和负面词的数量。

  5. 根据前一步中正负匹配的差异,为每条推文分配一个极性分数。

前面的步骤可以用以下图示表示:

![极性分析

一旦数据集中的每条推文都被评分,我们可以汇总这些评分来了解与搜索词或推特用户名相关的整体情感分布。正值定义了积极情感;更大的数字表示更积极的程度,对于消极情感也是如此。中立立场由分数为 0 表示。例如,这辆车速度惊人,非常漂亮 的积极程度高于 这是一辆好车,尽管两者都是积极的句子。

让我们使用这个算法通过搜索词和 Twitter 用户名来分析情感。如前所述,情感挖掘不仅对品牌至关重要,对政府也是如此。每个实体都希望了解其目标受众对其及其倡议的看法,政府也不例外。最近,印度政府有效地利用了 Twitter 和其他社交媒体平台来接触其受众,并让他们了解其倡议和政策。其中一个这样的倡议是最近推出的“印度制造”倡议。考虑这样一个场景,一个人被要求分析此类倡议的有效性和公众意见。为了分析随时间动态变化的公众意见,Twitter 是一个不错的选择。因此,为了分析“印度制造”倡议的情感,让我们分析一些推文。

如前所述,我们首先连接到 Twitter,提取与搜索词印度制造相关的推文。这之后是预处理步骤,其中我们删除停用词、URL 等,将推文转换为可用的格式。我们还对每个推文进行分词,将其分解成构成词列表,用于后续步骤。一旦我们的数据集准备就绪,并以可消费的格式存在,我们就加载预编译的正面和负面词列表。该列表可在www.cs.uic.edu/~liub/FBS/sentiment-analysis.html找到。

我们首先编写一个可重用的analyzeTrendSentiments函数,该函数接受搜索词和要提取的推文数量作为输入。它利用extractTweetstransformTweets函数来完成工作:

analyzeTrendSentiments <- function(search,tweetCount){ 

 #extract tweets
 tweetsDF <- extractTweets(search,tweetCount)

 # transformations
 transformedTweetsDF <- transformTweets(tweetsDF)

 #score the words 
 transformedTweetsDF$sentiScore = sapply(transformedTweetsDF$wordList,function(wordList) scoreTweet(wordList))

 transformedTweetsDF$search <- search

 return(transformedTweetsDF) 
}

然后,我们使用analyzeTrendSentiments函数获取一个包含使用预编译的极性词列表评分的推文的 DataFrame。我们同时使用twitteRggplot2stringrtm库:

library(twitteR)
library(stringr)
library(tm)
library(ggplot2)

consumerSecret = "XXXXXXXXXX"
consumerKey = "XXXXXXXXXXXXXXXXXXXXXXXXX"

setup_twitter_oauth(consumer_key = consumerKey,consumer_secret = consumerSecret)

# list of positive/negative words from opinion lexicon
pos.words = scan(file= 'positive-words.txt', what='character', comment.char=';')

neg.words = scan(file= 'negative-words.txt', what='character', comment.char=';')

#extract 1500 tweets on the given topic
makeInIndiaSentiments <- analyzeTrendSentiments("makeinindia",1500)

#plot the aggregated scores on a histogram
qplot(makeInIndiaSentiments $sentiScore)

在上一章中,我们学习了使用不同的可视化来掌握分析中隐藏的洞察力。继续同样的思考过程,我们生成一个聚合得分的直方图。可视化看起来是这样的:

极性分析

直方图易于解释。它在 x 轴上显示了推文在极性尺度上的分布,在 y 轴上显示了推文的频率。结果显示为正态分布,整体倾向于正方向。这似乎表明该倡议得到了其受众的积极反响。

在分析本身深入一些,让我们分析相同搜索词的情感,看看意见是如何随时间变化的。

注意

用于此分析的所有推文都是在该倡议启动当天以及之后一天提取的。由于推特动态性的特点,您可能会观察到结果有所不同。您可能会在本章的其他示例中观察到结果差异。我们敦促您发挥创意,在处理本章的示例时尝试其他热门话题。

输出看起来像这样:

极性分析

前两个直方图显示了两天内观点的变化。如果你当时在关注新闻,在这个倡议的一个事件中突然发生了火灾,整个舞台被烧毁。顶部的图表是基于火灾发生后发布的推文,而标记为makeinindia_yday的图表则指的是前一天发布的推文。尽管情感的变化并不剧烈,但很明显,变化更多地偏向于正面(一些推文的得分甚至达到了 6+)。这会不会是因为推文作者开始赞扬紧急救援队伍和警察的快速行动,防止了伤亡?嗯,看起来推特不仅仅是人们随意发泄的地方!

注意

世界领袖

推特吸引了名人和政治家的狂热。作为一个快速练习,尝试分析来自世界领袖的推特账号,如@potus@pmoindia@number10gov的推文,看看我们的领导人在推特上传达了什么样的观点。如果他们的时间线是中性的,请不要感到惊讶……哦,外交啊!

极性分析

基于分类的算法

分类问题需要根据每个类别的定义特征对输入数据进行标记,以便将其归类到所需的类别中(有关详细信息,请参阅第二章,让我们帮助机器学习)。在情感分析的情况下,类别是正面和负面(或在某些情况下是中性)。在前几章中,我们已经学习了不同的分类算法,并看到了它们如何被用于各个领域来解决分类和分类问题,而情感分析又是这些算法高度有用的另一个领域。

在本节中,我们将使用 SVM 和提升等分类算法进行观点挖掘。我们还将简要介绍集成方法,看看它们如何帮助提高性能。请注意,对于本节,我们将仅集中讨论正面和负面极性,但这种方法足够通用,可以轻松扩展以包括中性极性。

标记数据集

由于这是一个监督学习方法,我们需要标记数据来训练和测试算法的性能。为了本章的目的,我们将利用来自www.sentiment140.com/的标记数据集。它包含标记为 0、2 和 4 的推文,分别代表负面、中性和正面情感。除了情感标签之外,还有各种属性,如tweet IDdatesearch queryusernametweet text。在我们的案例中,我们只考虑推文文本及其相应的标签。

标记数据集

注意

另一个标记推文的来源是github.com/guyz/twitter-sentiment-dataset。这个来源使用 Python 脚本下载大约 5000 条标记推文,同时考虑到 Twitter API 指南。

在我们深入算法的特定细节之前,让我们先查看标记数据集,并执行收集和将我们的数据转换为所需形式的初始步骤。我们将使用caretRTextTools等库来完成这些步骤。

如前所述,数据集包含标记为 0、2 和 4 的极性,分别代表负面、中性和正面。我们将使用 R 加载csv文件,并快速转换标签为正面和负面。一旦极性被转换为可理解的名字,我们将过滤掉包含中性情感的数据行。此外,我们只保留极性和推文文本的列,并删除其余部分。

# load labeled dataset
labeledDSFilePath = "labeled_tweets.csv"
labeledDataset = read.csv(labeledDSFilePath, header = FALSE)

# transform polarity labels
labeledDataset$V1 = sapply(labeledDataset$V1, 
 function(x) 
 if(x==4) 
 x <- "positive" 
 else if(x==0) 
 x<-"negative" 
 else x<- "none")

#select required columns only
requiredColumns <- c("V1","V6")

# extract only positive/negative labeled tweets 
tweets<-as.matrix(labeledDataset[labeledDataset$V1 
 %in% c("positive","negative")
 ,requiredColumns])

tweets对象现在作为一个矩阵可用,其中每一行代表一条推文,列则分别指代极性和推文文本。在我们将这个矩阵转换为分类算法所需的格式之前,我们需要将我们的数据分为训练集和测试集(参见第二章,让我们帮助机器学习,了解更多相关信息)。由于训练集和测试集都应该包含足够好的所有类别的样本分布,以便进行训练和测试,我们使用caret包中提供的createDataPartition函数。对于我们的用例,我们将数据分为 70/30 的训练集和测试集:

indexes <- createDataPartition(tweets[,1], p=0.7, list = FALSE)

train.data <- tweets[indexes,]
test.data <- tweets[-indexes,]

我们快速检查了原始数据集中正负类别以及训练集和测试集的数据分布情况,结果将在下面的屏幕截图中展示:

标记数据集

如我们所见,createDataPartition在训练集和测试集之间保持了相似的极性分布。

接下来是文档词矩阵转换。正如我们在第七章, 社交媒体分析 – 分析 Twitter 数据中看到的,文档词矩阵将给定的数据集转换为表示文档的行和表示术语(单词/句子)的列。与上一章不同,我们使用了tm库的DocumentTermMatrix函数进行转换,并使用tm_map应用了各种转换,对于当前的使用案例,我们将使用RTextTools库中的create_matrix函数。这个函数是tm对应函数的抽象。我们还将使用tfidf作为特征为每个术语分配权重。create_matrix方法还帮助我们处理将句子拆分为单词、去除停用词和数字,以及词干提取。以下是操作步骤:

train.dtMatrix <- create_matrix(train.data[,2], 
 language="english" , 
 removeStopwords=TRUE, 
 removeNumbers=TRUE,
 stemWords=TRUE,
 weighting = tm::weightTfIdf)

test.dtMatrix <- create_matrix(test.data[,2], 
 language="english" , 
 removeStopwords=TRUE, 
 removeNumbers=TRUE,
 stemWords=TRUE,
 weighting = tm::weightTfIdf,
 originalMatrix=train.dtMatrix)

test.data.size <- nrow(test.data)

注意

RTextTools v1.4.2中的create_matrix方法有一个小错误,使用originalMatrix选项时阻止了权重分配。以下这个小技巧可以用来修复问题,直到库更新:

>  trace("create_matrix",edit=T) 

滚动到第 42 行,并将缩写更新为缩写。

查看以下链接以获取更多详细信息以及处理此问题的其他方法:

github.com/timjurka/RTextTools/issues/4

stackoverflow.com/questions/16630627/recreate-same-document-term-matrix-with-new-data

现在我们已经有了训练集和测试集,都是以DocumentTermMatrix格式,我们可以继续进行分类算法,让我们的机器学习并构建情感分类器!

支持向量机

支持向量机,或通常所说的SVM,是分类中最灵活的监督学习算法之一。SVM 以这种方式构建模型,即不同类别的数据点被一个清晰的间隙分开,这个间隙被优化到最大可能的分离距离。边缘上的样本被称为支持向量,它们被一个超平面分开(详见第六章, 信用风险检测与预测 – 预测分析了解更多细节)。

sentiment classifier using the default values and then prints a confusion matrix, along with other statistics for evaluation, as shown in the following code snippet:
svm.model <- svm(train.dtMatrix, as.factor(train.data[,1]))

## view inital model details
summary(svm.model)

## predict and evaluate results
svm.predictions <- predict(svm.model, test.dtMatrix)

true.labels <- as.factor(test.data[,1])

confusionMatrix(data=svm.predictions, reference=true.labels, positive="positive")

如下生成的混淆矩阵显示,分类器的准确率仅为50%,这和掷硬币一样糟糕,完全没有对负面情绪进行预测!看起来分类器无法从训练数据集中推断或学习到很多东西。

支持向量机

为了构建性能更好的模型,我们现在将深入底层并调整一些参数。e1071中的svm实现提供了一个名为tune的出色实用工具,通过在给定的参数范围内进行网格搜索来获得超参数的优化值:

## hyperparameter optimizations

# run grid search
cost.weights <- c(0.1, 10, 100)
gamma.weights <- c(0.01, 0.25, 0.5, 1)
tuning.results <- tune(svm, train.dtMatrix, as.factor(train.data[,1]), kernel="radial", 
 ranges=list(cost=cost.weights, gamma=gamma.weights))

# view optimization results
print(tuning.results)

# plot results
plot(tuning.results, cex.main=0.6, cex.lab=0.8,xaxs="i", yaxs="i")

注意

radial bias kernel (or rbf for short) for hyperparameter optimization. The motivation for using rbf was due to its better performance with respect to *specificity* and *sensitivity* even though the overall accuracy was comparable to *linear* kernels. We urge our readers to try out linear kernels and observe the difference in the overall results. Please note that, for text classification, linear kernels usually perform better than other kernels, not only in terms of accuracy but in performance as well

参数调整导致超参数costgamma的优化值分别为100.01;以下图表证实了这一点(最暗的区域对应最佳值)。

支持向量机

以下代码片段使用最佳模型进行预测并准备混淆矩阵,如下所示:

# get best model and evaluate predictions
svm.model.best = tuning.results$best.model

svm.predictions.best <- predict(svm.model.best, test.dtMatrix)

confusionMatrix(data=svm.predictions.best, reference=true.labels, positive="positive")

以下混淆矩阵显示了从改进后的模型中得出的预测。从仅仅 50%的准确率到舒适的 80%以上是一个很大的飞跃。让我们检查此模型的 ROC 曲线,以确认准确率确实足够好:

支持向量机

为了准备 ROC 曲线,我们将重用我们的实用脚本performance_plot_utils.R,来自第六章,信用风险检测和预测 – 预测分析,并将优化模型的预测传递给它:

# plot best model evaluation metric curves
svm.predictions.best <- predict(svm.model.best, test.dtMatrix, decision.values = T)

svm.prediction.values <- attributes(svm.predictions.best)
$decision.values

predictions <- prediction(svm.prediction.values, true.labels)

par(mfrow=c(1,2))
plot.roc.curve(predictions, title.text="SVM ROC Curve")
plot.pr.curve(predictions, title.text="SVM Precision/Recall Curve")

ROC 曲线也证实了一个学习良好的模型,其 AUC 为 0.89。因此,我们可以使用此模型将推文分类为正面或负面类别。我们鼓励读者尝试基于 ROC 的优化,并观察模型是否还有进一步的改进。

集成学习方法

简而言之,监督式机器学习算法是关于学习底层函数或模式,这些函数或模式帮助我们根据历史数据准确预测(在一定的范围内)。在本书的整个过程中,我们遇到了许多这样的算法,尽管 R 使得编码和测试这些算法变得容易,但值得一提的是,学习一个高度准确的功能或模式并不是一件容易的事情。构建高度复杂的模型会导致过拟合和欠拟合等问题。在所有这些混乱中,值得注意的是,学习简单的规则和函数总是很容易的。

例如,为了将一封电子邮件分类为垃圾邮件或非垃圾邮件,机器学习算法需要学习多个规则,例如:

  • 包含类似“现在购买”等文本的电子邮件是垃圾邮件

  • 包含五个以上超链接的电子邮件是垃圾邮件

  • 地址簿中的联系人发送的电子邮件不是垃圾邮件

以及许多类似的规则。给定一个训练数据集,比如说T个标记的电子邮件,机器学习算法(特别是分类算法)将生成一个分类器C,这是一个底层函数或模式的假设。然后我们使用这个分类器C来预测新电子邮件的标签。

另一方面,分类器集成被定义为输出以某种方式组合以对新示例进行分类的分类器集合。在基于机器学习的集成领域的主要发现是,集成比它们所组成的单个分类器表现得更好。

集成优于其组成部分的必要且充分条件是它们应该是准确多样的。如果一个分类器的预测比随机猜测更好,则称其为准确(参见弱学习者)。而如果两个分类器在相同的数据点上做出不同的错误,则称它们为多样

我们可以将弱学习者定义为预测和决策至少比随机猜测更好的学习者。弱学习者也被称为基学习器或元学习器。

以下框图可视化了集成分类器的概念:

集成方法

如前述框图所示,训练数据集被分成 n 个数据集(这种数据集的分割或生成取决于集成方法),弱学习器(相同的或不同的弱学习器,同样取决于集成方法)在这些数据集上建立模型。然后根据加权或无权投票将这些模型组合起来,以准备一个最终模型,该模型用于分类。集成为何有效力的数学证明相当复杂,超出了本书的范围。

提升法

构建集成分类器(或回归器)的方法有很多,提升法就是其中之一。提升法作为罗伯特·沙皮雷在 1990 年发表的开拓性论文《弱学习能力的力量》中的答案出现,这篇论文的标题是《弱学习能力的力量》,他在其中优雅地描述了提升集成,同时回答了凯尔斯和瓦利亚恩在 1989 年发表的论文中提出的问题,该论文讨论了能够创建单个强学习者的多个弱学习者。

注意

《弱学习能力的力量》www.cs.princeton.edu/~schapire/papers/strengthofweak.pdf

凯尔斯和瓦利亚恩:关于学习布尔学习和有限自动机的密码学限制:dl.acm.org/citation.cfm?id=73049

提升法的原始算法由弗里德和沙皮雷修订,并命名为AdaBoost自适应****提升法。这个算法是实际可实现的,并且经验上提高了泛化性能。该算法可以如下数学表示:

提升法

来源:www.cs.princeton.edu/picasso/mats/schapire02boosting_schapire.pdf

这里:

  • X 是训练集

  • Y 是标签集

  • Dt 是在第 t 次迭代中对训练示例 i 的权重分布

  • h[t] 是在第 t 次迭代中获得的分类器

  • α 是强度参数或 h[t] 的权重

  • H 是最终的或组合分类器。

简而言之,提升(Boosting)通常从最初对所有训练样本分配相同的权重开始。然后,它在假设空间中迭代,以学习加权样本上的假设 h[t]。在每个这样的假设学习之后,权重以这种方式进行调整,即正确分类的样本的权重减少。这种权重的更新有助于弱学习者在后续迭代中更多地关注错误分类的数据点。最后,将每个学习的假设通过加权投票来得到最终的模型,H

现在我们已经对集成方法和提升有了概述,让我们使用 R 中的 RTextTools 库提供的提升实现来对推文进行正面或负面分类。

我们将重用为基于 SVM 的分类创建的训练-测试文档项矩阵 train.dtMatrixtest.dtMatrix,以及容器对象 train.containertest.container

为了构建基于提升集成的分类器,RTextTools 提供了一个易于使用的实用函数 train_model。它内部使用 LogitBoosting 来构建分类器。我们为构建我们的提升集成使用 500 次迭代。

boosting.model <- train_model(train.container, "BOOSTING"
 , maxitboost=500)
boosting.classify <- classify_model(test.container, boosting.model)

然后,我们准备一个混淆矩阵来查看我们的分类器在测试数据集上的表现。

predicted.labels <- boosting.classify[,1]
true.labels <- as.factor(test.data[,1])

confusionMatrix(data = predicted.labels, 
 reference = true.labels, 
 positive = "positive")

下面的混淆矩阵显示,我们的基于提升的分类器以 78.5%的准确率工作,考虑到我们没有进行任何性能调整,这是一个相当不错的成绩。将其与 SVM 的初始迭代进行比较,我们得到的准确率仅为 50%多。

提升

如前所述,集成方法(特别是提升)提高了泛化性能,也就是说,它们有助于在不过度拟合训练数据的情况下实现接近 0 的训练错误。为了理解和评估我们的提升分类器在这些参数上,我们将使用一种称为 交叉验证 的模型评估技术。

交叉验证

交叉验证是一种模型评估技术,用于评估模型的泛化性能。它也被称为 旋转估计。与残差方法相比,交叉验证是验证模型泛化性能的更好指标,因为对于传统的验证技术,训练集和测试集的错误(如 均方根误差/RMSE)并不能正确地表示模型的表现。交叉验证可以通过以下方式执行:

  • 保留法:最简单的交叉验证技术。数据被分为训练集和测试集。模型在训练集上拟合,然后使用测试集(模型之前未见过)来计算平均绝对测试误差。这个累积误差用于评估模型。这种技术由于依赖于训练-测试划分的方式,因此具有高方差。

  • K 折交叉验证方法:这是对保留法的一种改进。数据集被分为k个子集,然后使用k个子集中的一个作为测试集,其余的k-1个子集作为训练集,重复使用保留法k次。由于每个数据点至少有一次进入测试集,而有k-1次进入训练集,这种方法具有较低的方差。缺点是由于迭代次数较多,需要更多的计算时间。K 折交叉验证的一种极端形式是留一法交叉验证,其中除了一个数据点外,所有数据点都用于训练。这个过程重复N(数据集大小)次。

我们可以使用cross_validate函数轻松地在我们的提升分类器上执行 K 折交叉验证。通常使用 10 折交叉验证:

# Cross validation
N=10
set.seed(42)
cross_validate(train.container,N,"BOOSTING"
 , maxitboost=500)

结果表明,分类器已经很好地泛化,整体平均准确率为 97.8%。

提升法是构建基于弱学习者的集成分类器的一种方法。例如,袋装法、贝叶斯最优分类器、桶装法和堆叠法等都是一些具有各自优缺点的变体。

注意

构建集成

RTextTools是一个健壮的库,它提供了train_modelsclassify_models等函数,通过结合各种基学习器来准备集成。它还提供了生成分析的工具,以非常详细的方式评估此类集成的性能。请参阅journal.r-project.org/archive/2013-1/collingwood-jurka-boydstun-etal.pdf的详细说明。

摘要

Twitter 是数据科学的一个宝库,其中有趣的模式和见解遍布其中。其用户生成内容的持续流动,加上基于兴趣的独特关系,为近距离理解人类动态提供了机会。情感分析就是这样一个领域,Twitter 提供了理解我们如何表达和分享对产品、品牌、人物等观点的合适成分。

在本章中,我们探讨了情感分析的基础、关键术语以及应用领域。我们还探讨了在执行情感分析时遇到的各项挑战。我们研究了各种常用的特征提取方法,如 tf-idf、Ngrams、POS、否定等,用于执行情感分析(或一般文本分析)。我们基于上一章的代码库,对可重用的实用函数进行了简化和结构化。我们使用 Twitter 搜索词进行了极性分析,并看到了公众对某些活动的看法如何被轻松追踪和分析。然后,我们转向监督学习算法进行分类,其中我们使用了 SVM 和 Boosting,利用caretRTextToolsROCRe1071等库构建情感分类器。在结束最后一章之前,我们还简要介绍了高度研究和广泛使用的集成方法领域,以及基于交叉验证的模型评估。

还有许多其他算法和分析技术可以应用于从 Twitter 和其他互联网来源中提取更多有趣的见解。在本章(以及本书)中,我们仅仅尝试触及这个巨大冰山的一角!数据科学不仅仅是将算法应用于解决问题或得出见解。它需要创造性思维和大量的尽职调查,除了领域理解、特征工程和收集数据来尝试解决尚未知的问题之外。

总结一下,思考一下唐纳德·拉姆斯菲尔德的这句话:

"已知已知的事物。这些是我们知道我们知道的。已知未知的事物。也就是说,有些事情我们知道我们不知道。但还有未知未知的事物。有些事情我们不知道我们不知道。"

数据科学是一个学习已知事物和探索未知未知事物的旅程,而机器学习是帮助实现这一目标的强大工具。#KeepMining

posted @ 2025-09-03 10:23  绝不原创的飞龙  阅读(1)  评论(0)    收藏  举报