原文:
zh.annas-archive.org/md5/622C4EC47FAAB3CE38900F0C3C942E11
译者:飞龙
第六章:用于大数据分析的 Spark
随着 Hadoop 及其相关技术在各自生态系统中的使用日益突出,Hadoop 操作模型的一些明显和显著的缺陷变得明显。特别是对 MapReduce 范式的根深蒂固的依赖以及与 MapReduce 相关的其他方面,使得 Hadoop 生态系统的真正功能性使用仅对深度投资于相关技术的主要公司可能。
在 2011 年的加州大学伯克利分校电气工程和计算机科学(EECS)年度研究研讨会上,Ian Stoica 教授在一次演讲中宣布了该大学一个新的研究小组的愿景(amplab.cs.berkeley.edu/about/
)。它奠定了一个将深刻改变大数据格局的关键单位的基础。AMPLab 于 2011 年 2 月成立,旨在通过整合算法、机器和人员提供可扩展和统一的解决方案,以满足未来的需求,而无需进行任何重大的重新设计工作。
从 AMPLab 计划发展出来的最著名和最广泛使用的项目是 Spark,可以说是 Hadoop 生态系统的一个更优秀的替代方案,或者更准确地说是扩展。
在本章中,我们将介绍 Spark 的一些显著特点,并以一个真实世界的教程结束,介绍如何使用 Spark。我们将涵盖的主题包括:
-
Spark 的出现
-
Spark 中的理论概念
-
Spark 的核心组件
-
Spark 架构
-
Spark 解决方案
-
Spark 教程
Spark 的出现
当 Spark 的第一个版本于 2014 年发布时,自 2009 年以来,Hadoop 在商业领域已经经历了数年的增长。尽管 Hadoop 解决了高效分析大规模数据集的主要障碍,使用广泛可访问的分布式计算方法,但仍存在阻碍其更广泛接受的缺陷。
Hadoop 的限制
Hadoop 的一些常见限制如下:
-
I/O 绑定操作:由于依赖本地磁盘存储来保存和检索数据,Hadoop 中执行的任何操作都会产生 I/O 开销。在涉及数千个数据块跨越数百台服务器的大型数据集的情况下,问题变得更加严重。公平地说,通过 HDFS 协调并发 I/O 操作的能力构成了 Hadoop 世界中分布式计算的基础。然而,有效地利用这种能力并在不同的用例和数据集中调整 Hadoop 集群需要极大且可能是不成比例的专业知识水平。因此,工作负载的 I/O 绑定特性成为使用 Hadoop 处理极大数据集的阻碍因素。例如,需要数百次迭代操作的机器学习用例意味着系统会在每次迭代中产生 I/O 开销。
-
MapReduce 编程(MR)模型:正如本书前面部分所讨论的,Hadoop 中的所有操作都需要用 MapReduce 编程模型来表达问题,即用户必须以每对键值独立计算的方式来表达问题。在 Hadoop 中,编写高效的 MapReduce 程序,特别是对于那些对 Java 或 Hadoop(或两者)都不熟悉的人来说,是非常困难的。
-
非 MR 使用案例:由于依赖 MapReduce,其他更常见和更简单的概念,如过滤器、连接等,也必须以 MapReduce 程序的形式表达。因此,跨主键在两个文件之间进行连接必须采用键值对方法。这意味着简单和复杂的操作都很难在没有重大编程工作的情况下实现。
-
编程 API:在 Hadoop 中将 Java 作为中心编程语言的使用意味着,为了能够正确地管理和使用 Hadoop,开发人员必须对 Java 和相关主题(如 JVM 调优、垃圾收集等)有很强的了解。这也意味着,其他流行语言(如 R、Python 和 Scala)的开发人员几乎没有办法重用或至少在他们最擅长的语言中实现他们的解决方案。
-
总的来说,尽管 Hadoop 世界曾经领导了大数据革命,但它未能使大数据技术在广泛范围内得到民主化使用。
AMPLab 团队早早意识到了这些缺点,并着手创建 Spark 来解决这些问题,并希望开发一种新的、更优越的替代方案。
克服 Hadoop 的局限性
现在我们将看一下前一节讨论的一些限制,并了解 Spark 如何通过这些方面来解决这些问题,从而提供了 Hadoop 生态系统的一个更优越的替代方案。
首先要牢记的一个关键区别是,Spark 不需要 Hadoop 才能运行。事实上,Spark 访问数据的底层后端可以是诸如 HBase、Hive 和 Cassandra 以及 HDFS 等技术。
这意味着希望利用独立的 Spark 系统的组织可以在没有已有的 Hadoop 基础设施的情况下这样做。
Spark 的解决方案如下:
-
I/O 绑定操作:与 Hadoop 不同,Spark 可以存储和访问存储在内存中的数据,即 RAM - 正如前面讨论的,这比从磁盘读取数据快 1000 多倍。随着 SSD 驱动器的出现,成为当今企业系统的标准,差异已经显著减少。最近的 NVMe 驱动器可以提供每秒 3-5GB(千兆字节)的带宽。然而,RAM 的读取速度平均约为 25-30GB 每秒,仍然比从较新的存储技术中读取快 5-10 倍。因此,能够将数据存储在 RAM 中,可以使 Spark 操作读取数据的时间提高 5 倍或更多。这是对依赖于磁盘读取所有操作的 Hadoop 操作模型的显著改进。特别是,涉及迭代操作的任务,如机器学习,受益于 Spark 能够存储和从内存中读取数据的功能。
-
MapReduce 编程(MR)模型:虽然 MapReduce 是用户可以从 Hadoop 平台中受益的主要编程模型,但 Spark 并没有相同的要求。这对于更复杂的用例特别有帮助,比如涉及无法轻松并行化的计算的定量分析,比如机器学习算法。通过将编程模型与平台解耦,Spark 允许用户编写和执行用各种语言编写的代码,而不强制任何特定的编程模型作为先决条件。
-
非 MR 用例:Spark SQL、Spark Streaming 和 Spark 生态系统的其他组件提供了丰富的功能,允许用户执行常见任务,如 SQL 连接、聚合和相关的类似数据库的操作,而无需利用其他外部解决方案。Spark SQL 查询通常针对存储在 Hive 中的数据(JSON 是另一个选项)执行,并且该功能也可用于其他 Spark API,如 R 和 Python。
-
编程 API:Spark 中最常用的 API 是 Python、Scala 和 Java。对于 R 程序员,还有一个名为
SparkR
的单独包,允许直接从 R 访问 Spark 数据。这是 Hadoop 和 Spark 之间的一个主要区别,通过在这些语言中公开 API,Spark 立即对更大的开发者社区可用。在数据科学和分析中,Python 和 R 是最突出的选择语言,因此,任何 Python 或 R 程序员都可以利用 Spark,相对于 Hadoop,学习曲线更简单。此外,Spark 还包括一个用于临时分析的交互式 shell。
Spark 中的理论概念
以下是 Spark 中的核心概念:
-
弹性分布式数据集
-
有向无环图
-
SparkContext
-
Spark DataFrames
-
操作和转换
-
Spark 部署选项
弹性分布式数据集
弹性分布式数据集,更常被称为RDD,是 Spark 中使用的主要数据结构。RDD 本质上是一个记录的集合,以分布式的方式存储在 Spark 集群中。RDD 是不可变的,也就是说,一旦创建就无法更改。存储在节点上的 RDD 可以并行访问,因此本身支持并行操作。
用户无需编写单独的代码来获得并行化的好处,只需运行特定的命令即可获得与 Spark 平台本身相关的操作和转换的好处。由于 RDD 也可以存储在内存中,作为额外的好处,并行操作可以直接在内存中对数据进行操作,而不会产生昂贵的 I/O 访问惩罚。
有向无环图
在计算机科学和数学术语中,有向无环图表示一对节点(也称为顶点)之间用边(或线)连接的图,这些边是单向的。也就是说,给定节点 A 和节点 B,边可以连接 A 到 B 或 B 到 A,但不能同时连接。换句话说,任何一对节点之间没有循环关系。
Spark 利用 DAG 的概念来构建内部工作流程,以划分 Spark 作业中不同阶段的处理。从概念上讲,这类似于创建一份虚拟流程图,展示了获得特定输出所需的一系列步骤。例如,如果所需的输出涉及在文档中生成单词计数,中间步骤 map-shuffle-reduce 可以表示为一系列导致最终结果的操作。通过维护这样的map,Spark 能够跟踪操作中涉及的依赖关系。更具体地说,RDD 是节点,而稍后在本节中讨论的转换是 DAG 的边缘。
SparkContext
SparkContext 是所有 Spark 操作的入口点,也是应用程序连接到 Spark 集群资源的方式。它初始化了一个 Spark 实例,然后可以用于创建 RDD,对 RDD 执行操作和转换,提取数据和其他 Spark 功能。SparkContext 还初始化了进程的各种属性,如应用程序名称、核心数、内存使用参数和其他特性。这些属性集中在 SparkConf 对象中,作为参数传递给 SparkContext。
SparkSession
是用户启动与 Spark 连接的新抽象。它是 Spark 2.0.0 之前SparkContext
提供的功能的超集。然而,实践者仍然可以互换使用SparkSession
和SparkContext
来表示同一个实体;即与Spark
交互的主要方式。SparkSession
本质上结合了SparkContext
和HiveContext
的功能。
Spark DataFrames
在 Spark 中,DataFrame 是组织成行和列的原始数据。这在概念上类似于 CSV 文件或 SQL 表。使用 R、Python 和其他 Spark API,用户可以使用常见的 Spark 命令与 DataFrame 交互,用于过滤、聚合和更一般的数据操作。DataFrame 中包含的数据实际上位于 Spark 集群的多个节点上。然而,通过在DataFrame中表示它们,它们看起来像是一个统一的数据单元,而不暴露底层操作的复杂性。
请注意,DataFrame 和 Dataset 不是 Spark 中常用的术语。Dataset 指的是存储在 Spark 集群中的实际数据。DataFrame 是 Dataset 的表格表示。
从 Spark 2.0 开始,DataFrame 和 Dataset API 被合并,DataFrame 现在本质上代表了一组行的 Dataset。也就是说,DataFrame 仍然是想要利用 Python 和 R 与 Spark 数据交互的用户的主要抽象。
操作和转换
Spark 操作有两种类型:
-
转换
-
操作
转换指定一般的数据操作,如过滤数据、连接数据、执行聚合、抽样数据等。当执行代码中包含转换操作的行时,转换不会返回任何结果。相反,命令在执行时会向 Spark 的内部 DAG 添加相应的操作请求。常见的转换示例包括:map
、filter
、groupBy
、union
、coalesce
等等。
操作,另一方面,返回结果。换句话说,它们执行用户可能在相应的 RDD 上指定的一系列转换(如果有的话),并产生输出。换句话说,操作触发 DAG 中步骤的执行。常见的操作包括:reduce
、collect
、take
、aggregate
、foreach
等等。
请注意,RDD 是不可变的。它们不能被改变;转换和操作总是会产生新的 RDD,但永远不会修改现有的 RDD。
Spark 部署选项
Spark 可以以各种模式部署。最重要的是:
-
独立模式:作为一个独立的集群,不依赖于任何外部集群管理器
-
Amazon EC2:在亚马逊网络服务的 EC2 实例上,可以从 S3 访问数据
-
Apache YARN:Hadoop ResourceManager
其他选项包括Apache Mesos和Kubernetes。
更多详细信息可以在 Spark 文档网站找到,spark.apache.org/docs/latest/index.html
。
Spark API
Spark 平台可以通过 Python、Scala、R 和 Java 中可用的 Spark API 轻松访问。它们一起使得在 Spark 中处理数据变得简单且广泛可访问。在 Spark 项目初始阶段,它只支持 Scala/Java 作为主要 API。然而,由于 Spark 的一个主要目标是为多样化的开发者提供一个简单的接口,Scala API 之后又跟着 Python 和 R API。
在 Python 中,PySpark 包已经成为 Python 开发者社区编写 Spark 应用程序的广泛标准。在 R 中,用户通过 SparkR 包与 Spark 进行交互。这对于可能也对在 Spark 生态系统中存储的数据进行操作的 R 开发者来说是有用的。这两种语言在数据科学社区中非常普遍,因此,引入 Python 和 R API 为分析用例上的大数据分析在 Spark 上的民主化奠定了基础。
Spark 的核心组件
以下组件在 Spark 中非常重要:
-
Spark Core
-
Spark SQL
-
Spark Streaming
-
GraphX
-
MLlib
Spark Core
Spark Core 在 Spark 中提供了基本功能,如使用 RDD、执行操作和转换,以及更多的管理任务,如存储、高可用性和其他主题。
Spark SQL
Spark SQL 为用户提供了使用标准 SQL 命令查询存储在 Apache Hive 中的数据的能力。这通过提供开发人员通过 Spark SQL 接口使用常见的 SQL 术语与数据集交互,增加了额外的可访问性。托管底层数据的平台不仅限于 Apache Hive,还可以包括 JSON、Parquet 等。
Spark Streaming
Spark 的流处理组件允许用户与流数据进行交互,如与网络相关的内容等。它还包括高可用性等企业特性。Spark 可以从各种中间件和数据流服务中读取数据,如 Apache Kafka、Apache Flume 和云服务供应商如亚马逊网络服务。
GraphX
Spark 的 GraphX 组件支持基于图的操作,类似于支持专门数据结构的图数据库技术。这使得使用、访问和表示数据的相互连接点变得容易,如社交网络。除了分析,Spark GraphX 平台还支持图算法,这些算法对于需要在规模上表示关系的业务用例非常有用。例如,信用卡公司使用类似于 Spark 的 GraphX 组件的基于图的数据库来构建检测具有相似特征的用户的推荐引擎。这些特征可能包括购买习惯、位置、人口统计学和其他定性和定量因素。在这些情况下使用图系统允许公司构建网络,其中节点代表个体,边代表关系度量,以找到它们之间的共同特征。
MLlib
MLlib 是 Spark 生态系统的旗舰组件之一。它提供了一个可扩展的、高性能的接口,用于在 Spark 中执行资源密集型的机器学习任务。此外,MLlib 可以原生连接到 HDFS、HBase 和其他在 Spark 中支持的底层存储系统。由于这种多功能性,用户不需要依赖预先存在的 Hadoop 环境来开始使用内置到 MLlib 中的算法。MLlib 中支持的一些算法包括:
-
分类:逻辑回归
-
回归:广义线性回归、生存回归等
-
决策树、随机森林和梯度提升树
-
推荐:交替最小二乘法
-
聚类:K 均值、高斯混合和其他
-
主题建模:潜在狄利克雷分配
-
Apriori:频繁项集、关联规则
ML 工作流程实用程序包括:
-
特征转换:标准化、归一化等
-
ML Pipeline 构建
-
模型评估和超参数调整
-
ML 持久性:保存和加载模型和管道
Spark 的架构
Spark 由 3 个主要的架构组件组成:
-
SparkSession/SparkContext
-
集群管理器
-
工作节点(托管执行器进程)
SparkSession/SparkContext,或者更一般地说,Spark Driver,是所有 Spark 应用程序的入口点,如前所述。SparkContext 将用于创建 RDD 并对 RDD 执行操作。SparkDriver 发送指令到工作节点以安排任务。
集群管理器在概念上类似于 Hadoop 中的资源管理器,事实上,支持的解决方案之一是 YARN。其他集群管理器包括 Mesos。Spark 也可以在独立模式下运行,在这种情况下不需要 YARN/Mesos。集群管理器协调工作节点之间的通信,管理节点(如启动、停止等),并执行其他管理任务。
工作节点是托管 Spark 应用程序的服务器。每个应用程序都有自己独特的执行器进程,即执行实际操作和转换任务的进程。通过分配专用的执行器进程,Spark 确保任何特定应用程序中的问题不会影响其他应用程序。工作节点由执行器、JVM 和 Spark 应用程序所需的 Python/R/其他应用程序进程组成。请注意,在 Hadoop 的情况下,工作节点和数据节点是一样的:
Spark 解决方案
Spark 直接可从spark.apache.org作为开源解决方案获得。Databricks是 Spark 商业解决方案的领先提供商。对于熟悉 Python、R、Java 或 Scala 编程的人来说,由于高效的接口(如 PySpark API),开始使用 Spark 所需的时间很短。
基于云的 Spark 平台,如 Databricks Community Edition,提供了一种简单易行的方式来使用 Spark,而不需要安装和配置 Spark。因此,希望使用 Spark 进行编程和相关任务的用户可以更快地开始,而不需要花时间在管理任务上。
Spark 实践
在本节中,我们将在 Databricks 的 Community Edition 上创建一个帐户,并完成一个实际操作的练习,引导读者了解操作、转换和 RDD 概念的基础知识。
注册 Databricks Community Edition
以下步骤概述了注册Databricks Community Edition的过程:
- 点击立即开始按钮并输入您的信息:
- 确认您已阅读并同意弹出菜单中的条款(向下滚动到底部找到同意按钮):
- 检查您的电子邮件,确认来自 Databricks 的确认电子邮件,并点击链接确认您的账户:
- 点击链接确认您的账户后,您将被带到一个登录界面,在那里您可以使用注册账户时使用的电子邮件地址和密码登录:
- 登录后,点击集群设置一个 Spark 集群,如下图所示:
- 输入
Packt_Exercise
作为集群名称,然后点击页面顶部的创建集群按钮:
- 这将启动启动一个 Spark 集群的过程,我们将在其中使用 iPython 笔记本执行我们的 Spark 命令。iPython Notebook 是一个常用的 IDE 的名称,它是一个用于编写和测试 Python 代码的基于 Web 的开发应用程序。笔记本还可以通过内核支持其他语言,但在本练习中,我们将专注于 Python 内核。
一段时间后,状态将从待定变为运行:
几分钟后状态变为运行:
- 点击工作区(在左侧栏)并选择选项,用户 | (
您的用户 ID
),然后点击您的电子邮件地址旁边的下拉箭头。选择创建 | 笔记本:
- 在弹出屏幕中,输入
Packt_Exercise
作为笔记本的名称,然后点击创建按钮:
- 单击创建按钮后,您将直接进入笔记本,如下面的屏幕截图所示。这是 Spark 笔记本,您将能够执行接下来几个部分中给出的其余代码。应在笔记本的单元格中输入代码,如所示。输入代码后,按Shift + Enter执行相应的单元格:
-
在接下来的几个练习中,您可以将文本复制粘贴到笔记本的单元格中。或者,您还可以导入笔记本并直接在工作区中加载它。如果这样做,您将不需要输入命令(尽管输入命令将提供更多的实践熟悉度)。
-
复制粘贴命令的另一种方法:您可以通过单击以下屏幕截图中显示的导入来导入笔记本:
- 在弹出菜单中输入以下URL(选择URL作为从选项导入):
- 然后笔记本将显示在您的电子邮件 ID 下。单击笔记本的名称加载它:
Spark 练习-亲身体验 Spark(Databricks)
本笔记本是基于 Databricks 进行的教程(databricks.com/
)。该教程将使用 Databricks 的 Spark 社区版进行,可在databricks.com/try-databricks
注册。Databricks 是 Spark 的商业和企业支持版本的领先提供商。
在本教程中,我们将介绍一些在 Spark 中使用的基本命令。鼓励用户尝试更广泛的 Spark 教程和笔记本,这些教程和笔记本可以在网络上找到更详细的示例。
Spark 的 Python API 文档可以在spark.apache.org/docs/latest/api/python/pyspark.html#pyspark.sql
找到。
本书的数据已导入 Databricks 的 Spark 平台。有关导入数据的更多信息,请转到导入数据 - Databricks (docs.databricks.com/user-guide/importing-data.html
)。
# COMMAND ----------
# The SparkContext/SparkSession is the entry point for all Spark operations
# sc = the SparkContext = the execution environment of Spark, only 1 per JVM
# Note that SparkSession is now the entry point (from Spark v2.0)
# This tutorial uses SparkContext (was used prior to Spark 2.0)
from pyspark import SparkContext
# sc = SparkContext(appName = "some_application_name") # You'd normally run this, but in this case, it has already been created in the Databricks' environment
# COMMAND ----------
quote = "To be, or not to be, that is the question: Whether 'tis nobler in the mind to suffer The slings and arrows of outrageous fortune, Or to take Arms against a Sea of troubles, And by opposing end them: to die, to sleep No more; and by a sleep, to say we end the heart-ache, and the thousand natural shocks that Flesh is heir to? 'Tis a consummation devoutly to be wished. To die, to sleep, To sleep, perchance to Dream; aye, there's the rub, for in that sleep of death, what dreams may come, when we have shuffled off this mortal coil, must give us pause."
# COMMAND ----------
sparkdata = sc.parallelize(quote.split(' '))
# COMMAND ----------
print "sparkdata = ", sparkdata
print "sparkdata.collect = ", sparkdata.collect
print "sparkdata.collect() = ", sparkdata.collect()[1:10]
# COMMAND ----------
# A simple transformation - map
def mapword(word):
return (word,1)
print sparkdata.map(mapword) # Nothing has happened here
print sparkdata.map(mapword).collect()[1:10] # collect causes the DAG to execute
# COMMAND ----------
# Another Transformation
def charsmorethan2(tuple1):
if len(tuple1[0])>2:
return tuple1
pass
rdd3 = sparkdata.map(mapword).filter(lambda x: charsmorethan2(x))
# Multiple Transformations in 1 statement, nothing is happening yet
rdd3.collect()[1:10]
# The DAG gets executed. Note that since we didn't remove punctuation marks ... 'be,', etc are also included
# COMMAND ----------
# With Tables, a general example
cms = sc.parallelize([[1,"Dr. A",12.50,"Yale"],[2,"Dr. B",5.10,"Duke"],[3,"Dr. C",200.34,"Mt. Sinai"],[4,"Dr. D",5.67,"Duke"],[1,"Dr. E",52.50,"Yale"]])
# COMMAND ----------
def findPayment(data):
return data[2]
print "Payments = ", cms.map(findPayment).collect()
print "Mean = ", cms.map(findPayment).mean() # Mean is an action
# COMMAND ----------
# Creating a DataFrame (familiar to Python programmers)
cms_df = sqlContext.createDataFrame(cms, ["ID","Name","Payment","Hosp"])
print cms_df.show()
print cms_df.groupby('Hosp').agg(func.avg('Payment'), func.max('Payment'),func.min('Payment'))
print cms_df.groupby('Hosp').agg(func.avg('Payment'), func.max('Payment'),func.min('Payment')).collect()
print
print "Converting to a Pandas DataFrame"
print "--------------------------------"
pd_df = cms_df.groupby('Hosp').agg(func.avg('Payment'), func.max('Payment'),func.min('Payment')).toPandas()
print type(pd_df)
print
print pd_df
# COMMAND ----------
wordsList = ['to','be','or','not','to','be']
wordsRDD = sc.parallelize(wordsList, 3) # Splits into 2 groups
# Print out the type of wordsRDD
print type(wordsRDD)
# COMMAND ----------
# Glom coallesces all elements within each partition into a list
print wordsRDD.glom().take(2) # Take is an action, here we are 'take'-ing the first 2 elements of the wordsRDD
print wordsRDD.glom().collect() # Collect
# COMMAND ----------
# An example with changing the case of words
# One way of completing the function
def makeUpperCase(word):
return word.upper()
print makeUpperCase('cat')
# COMMAND ----------
upperRDD = wordsRDD.map(makeUpperCase)
print upperRDD.collect()
# COMMAND ----------
upperLambdaRDD = wordsRDD.map(lambda word: word.upper())
print upperLambdaRDD.collect()
# COMMAND ----------
# Pair RDDs
wordPairs = wordsRDD.map(lambda word: (word, 1))
print wordPairs.collect()
# COMMAND ----------
# #### Part 2: Counting with pair RDDs
# There are multiple ways of performing group-by operations in Spark
# One such method is groupByKey()
#
# ** Using groupByKey() **
#
# This method creates a key-value pair whereby each key (in this case word) is assigned a value of 1 for our wordcount operation. It then combines all keys into a single list. This can be quite memory intensive, especially if the dataset is large.
# COMMAND ----------
# Using groupByKey
wordsGrouped = wordPairs.groupByKey()
for key, value in wordsGrouped.collect():
print '{0}: {1}'.format(key, list(value))
# COMMAND ----------
# Summation of the key values (to get the word count)
wordCountsGrouped = wordsGrouped.map(lambda (k,v): (k, sum(v)))
print wordCountsGrouped.collect()
# COMMAND ----------
# ** (2c) Counting using reduceByKey **
#
# reduceByKey creates a new pair RDD. It then iteratively applies a function first to each key (i.e., within the key values) and then across all the keys, i.e., in other words it applies the given function iteratively.
# COMMAND ----------
wordCounts = wordPairs.reduceByKey(lambda a,b: a+b)
print wordCounts.collect()
# COMMAND ----------
# %md
# ** Combining all of the above into a single statement **
# COMMAND ----------
wordCountsCollected = (wordsRDD
.map(lambda word: (word, 1))
.reduceByKey(lambda a,b: a+b)
.collect())
print wordCountsCollected
# COMMAND ----------
# %md
#
# This tutorial has provided a basic overview of Spark and introduced the Databricks community edition where users can upload and execute their own Spark notebooks. There are various in-depth tutorials on the web and also at Databricks on Spark and users are encouraged to peruse them if interested in learning further about Spark.
总结
在本章中,我们了解了 Spark 的一些核心特性,这是当今大数据领域中最突出的技术之一。自 2014 年发布以来,Spark 已迅速成熟,当时它作为一个大数据解决方案发布,缓解了 Hadoop 的许多缺点,如 I/O 争用等。
如今,Spark 有几个组件,包括专门用于流式分析和机器学习的组件,并且正在积极开发中。Databricks 是 Spark 商业支持版本的领先提供商,还托管了一个非常方便的基于云的 Spark 环境,用户可以免费访问有限资源。这大大降低了用户的准入门槛,因为用户无需安装完整的 Spark 环境来学习和使用该平台。
在下一章中,我们将开始讨论机器学习。直到这一部分,大部分文本都集中在大规模数据的管理上。有效利用数据并从数据中获得洞察力始终是最终目标。为了做到这一点,我们需要采用今天已经变得司空见惯的先进算法技术。下一章将讨论机器学习的基本原则,之后我们将在随后的章节中更深入地探讨这一主题领域。
第七章:机器学习概念简介
机器学习已经成为我们日常生活中司空见惯的话题。该领域的发展如此戏剧性,以至于今天,甚至手机都集成了先进的机器学习和人工智能相关设施,能够根据人类指令做出响应和采取行动。
曾经只限于大学课堂的一个学科,如今已经发展成为一个完全成熟的行业,以一种我们几年前无法想象的方式渗透到我们的日常生活中。
本章的目的是向读者介绍机器学习的基础知识,并以简单明了的术语解释概念,帮助读者熟悉该学科的核心思想。我们将从机器学习的高层概述开始,解释不同的类别以及如何加以区分。我们将解释机器学习中一些显著的概念,如数据预处理、特征工程和变量重要性。下一章将更详细地介绍单个算法和理论机器学习。
我们将通过使用 R 来执行机器学习操作的真实数据集来结束本章。
我们将在本章中涵盖以下主题:
-
什么是机器学习?
-
流行的出现
-
机器学习、统计学和人工智能(AI)
-
机器学习的类别
-
机器学习的核心概念
-
机器学习教程
什么是机器学习?
机器学习并不是一个新的学科;它作为一个正式学科已经存在了 70 多年,但是以不同的名称存在:统计学,更普遍的是数学,然后是人工智能(AI),今天是机器学习。虽然统计学和人工智能等其他相关学科同样普遍,但是机器学习已经开辟了一个独立的领域,成为一个独立的学科。
简而言之,机器学习涉及基于历史数据预测未来事件。我们在日常生活中看到它的体现,无论我们是否知情,我们都会每天应用机器学习的原则。
当我们随意评论一部电影是否会在票房上成功,使用我们对主演的人气的理解时,我们正在应用机器学习,尽管是下意识地。我们对主演角色的理解是在多年观看他们出演的电影中形成的。当我们对未来出演同一人的电影的成功做出评估时,我们是在利用历史信息进行评估。
另一个例子是,如果我们有关于温度、湿度和降水(雨)的数据,比如 12 个月的数据,我们能否利用这些信息来预测今天是否会下雨,给定温度和湿度的信息?
这类似于统计学中常见的回归问题。但是,机器学习涉及对练习应用更高级别的严谨性,以便基于不仅仅是理论计算,而且是使用迭代方法进行数百次甚至数千次验证计算后得出结论。
需要在这里指出和澄清的是,术语“机器学习”指的是通常在计算设备上执行的旨在预测结果的算法或程序。这些算法构建数学模型,然后可以用来进行预测。人们普遍错误地认为机器学习实际上是指一个“学习”的“机器”。正如刚才解释的那样,实际含义要逊色得多。
机器学习的演变
机器学习的时间线,如维基百科上所述(en.wikipedia.org/wiki/Timeline_of_machine_learning
),提供了该领域演变的简明而富有洞察力的概述。其根源可以追溯到 18 世纪中期,当时托马斯·贝叶斯在伦敦皇家学会上发表了他关于逆概率的论文。逆概率,今天更常被称为概率分布,涉及确定给定一组先前事件的系统状态的问题。例如,如果一个盒子里有牛奶巧克力和白巧克力,你随机拿出几个,得到两块牛奶巧克力和三块白巧克力,我们能推断盒子里有多少块巧克力吗?
换句话说,我们能根据我们可以假设一个正式理论的一些数据点推断出未知的情况吗?贝叶斯的工作被皮埃尔-西蒙·拉普拉斯进一步发展成为贝叶斯定理,收录在他的著作《概率分析理论》中。
在 20 世纪初,安德烈·马尔可夫对普希金的诗《叶甫盖尼·奥涅金》的分析,以确定俄罗斯文学中辅音和元音的押韵,导致了一种称为马尔可夫链的技术的发展,该技术今天用于对涉及随机事件的复杂情况进行建模。谷歌的 PageRank 算法实现了马尔可夫链的一种形式。
机器学习的第一个正式应用,或者更普遍地说,人工智能的最终出现作为一门学科,应归功于艾伦·图灵。他开发了图灵测试——一种确定机器是否足够智能以模仿人类行为的方法。图灵在他的论文《计算机器械与智能》中提出了这一点,论文开头是这样的:
我建议考虑这个问题,“机器能思考吗?”这应该从定义“机器”和“思考”的含义开始。这些定义可能被构造得尽可能反映这些词的正常用法,但这种态度是危险的。如果通过检查它们通常的用法来找到“机器”和“思考”的含义,很难逃脱这样的结论,即问题“机器能思考吗?”的含义和答案应该在统计调查中寻找,比如盖洛普民意调查。但这是荒谬的。我不打算尝试这样的定义,而是用另一个问题来代替它,这个问题与它密切相关,并用相对明确的词语表达。
图灵在论文的后面写道:
“原始问题‘机器能思考吗?’我认为太毫无意义,不值得讨论。尽管如此,我相信在本世纪末,词语的使用和一般受过教育的观点将发生如此大的变化,以至于人们将能够谈论机器思考而不期望遭到反驳。我进一步相信,隐藏这些信念是没有任何有益的目的。”
图灵在人工智能领域的工作之后,机器学习和人工智能出现了一系列重要事件。1951 年,马文·米斯基开发了第一个神经网络,阿瑟·塞缪尔在 1952 年开始了第一个下棋的机器学习程序的工作,罗森布拉特在 1957 年发明了感知器,这是神经网络的基本单元。杰出人物如利奥·布雷曼、杰罗姆·弗里德曼、弗拉迪米尔·瓦普尼克和阿列克谢·切尔沃年基斯、杰夫·辛顿和杨立昆通过 20 世纪 90 年代末做出了重大贡献,使机器学习成为当今独立的研究领域。我们对他们的工作和贡献深表感激,这使得机器学习在当今的研究领域中脱颖而出。
1997 年,IBM 的深蓝击败了卡斯帕罗夫,这立刻成为了全球轰动的事件。一台机器能够击败世界顶级国际象棋冠军并非寻常的成就。这一事件为机器学习赢得了一些急需的可信度,使其成为图灵所设想的智能机器的有力竞争者。
机器学习成功的因素
鉴于机器学习作为一个学科已经存在了几十年,人们不禁要问:为什么它没有比今天更早地变得如此受欢迎?事实上,诸如神经网络之类的复杂机器学习算法的理论在 20 世纪 90 年代晚期就已经广为人知,而在理论领域,基础也早已奠定。
机器学习成功的几个因素:
-
互联网:网络在民主化信息和以前所未有的方式连接人们方面发挥了关键作用。它使信息交换变得简单,这是通过印刷媒体传播信息的现有方法所无法实现的。网络不仅转变和革新了信息传播,还开辟了新的机会。正如前面提到的,谷歌的 PageRank 是将统计模型应用于开发高度成功的网络企业的最早大规模和高度可见的成功之一。
-
社交媒体:虽然网络提供了一个交流平台,但缺乏与现实世界中人们互动的灵活性。有一个明显的但低调的、可以说是未被开发的差距。诸如 IRC 和 Usenet 之类的工具是社交网络网站的前身,比如 Myspace,这是最早用于创建个人网络的基于网络的平台之一。到了 21 世纪初至中期,Facebook 成为社交网络的领导者。这些平台提供了一个独特的机会,利用互联网以个人层面收集数据。每个用户留下了一串信息,可以使用自然语言处理和其他技术进行收集和分析。
-
计算硬件:用于计算机的硬件以指数速度发展。机器学习算法本质上是计算和资源密集型的,也就是说,它们需要强大的 CPU、快速的磁盘和根据数据大小的高内存。在固态硬盘(SSD)上存储数据的新方法是从以前的旋转硬盘存储方式中跨越的一大步。更快的访问意味着数据可以以更快的速度传递给 CPU,并减少了传统计算中一直存在的 I/O 瓶颈。更快的 CPU 意味着可以及时执行机器学习算法所需的数百甚至数千次迭代。最后,需求导致了计算资源价格的降低,使更多人能够负担得起原本价格昂贵的计算硬件。算法是存在的,但资源最终能够以合理的时间和成本来执行它们。
-
编程语言和软件包:R 和 Python 开发者等社区抓住了机会,个人开始发布暴露他们的工作给更广泛的程序员社区的软件包。特别是提供机器学习算法的软件包立即受到欢迎,并激发了其他从业者发布他们的个人代码库,使得 R 等平台成为一个真正的全球协作努力。如今,R 中有超过 10,000 个软件包,而 2010 年只有 2000 个。
机器学习、统计学和人工智能
机器学习是一个有各种同义词的术语——这些名称是企业的营销活动的结果,或者只是可以互换使用的术语。尽管有人可能会争辩它们有不同的含义,但它们最终都指的是机器学习作为一门学科,它利用历史信息来预测未来事件。
机器学习常听到的术语包括预测分析、预测分析、预测建模等等。因此,除非发布材料的实体解释了他们对术语的解释,更具体地说明了它的不同之处,否则可以安全地假设他们是在指机器学习。这往往会让新手感到困惑,主要是由于技术术语的误用和滥用。
另一方面,统计学是一个已经有 200 多年历史的独立学科领域。这个词源自新拉丁语statisticum collegium(英语中的国务院)和意大利语statista,意思是政治家或政治家。您可以访问en.wikipedia.org/wiki/History_of_statistics#Etymology
了解更多关于这个主题的详情。机器学习实现了各种统计模型,由于涉及到的计算严谨,它与经典统计学的分支有所不同。
人工智能也与机器学习密切相关,但它是一个更广泛的主题。它可以被宽泛地定义为在存在不确定性的情况下,能够以(通常)负责任和社会意识的方式做出具体决策,以达到目标终极目标的系统(软件/硬件)。换句话说,人工智能旨在通过系统地处理既包括已知又包括未知(潜在)因素的情况来产生行动。
人工智能唤起了智能机器人在科幻电影中的形象,就像它提醒我们智能系统,比如 IBM Watson,可以解析复杂问题并处理模糊陈述以找到具体答案一样。
机器学习与一些相同特征-使用训练数据逐步开发模型,并使用测试数据测量准确性。然而,人工智能已经存在了很多年,并且是一个家喻户晓的术语。美国的卡内基梅隆大学等机构一直在制定人工智能的关键原则和指导方针。
关于人工智能与机器学习的在线资源/文章似乎没有提供任何关于它们之间区别的定论。然而,大学的人工智能课程大纲使这些区别变得非常明显。您可以在cs.brown.edu/courses/csci1410/lectures.html
了解更多关于人工智能的信息。
人工智能涉及涉及的广泛研究领域:
-
受限优化:在给定情况下,达到最佳结果,考虑一组约束或限制
-
博弈论:例如,零和游戏,均衡等-根据决策如何影响未来决策和影响期望的最终目标来做出权衡决策
-
不确定性/贝叶斯定理:在先验信息的情况下,发生这种情况的可能性是多少,考虑到已经发生了其他事情
-
规划:制定行动计划=一组路径(图),以应对情况/达到最终目标
-
机器学习:通过使用旨在处理不确定性并模仿人类推理的算法来实现(实现)前述目标。通常用于人工智能的机器学习算法包括:
-
神经网络/深度学习(发现隐藏因素)
-
自然语言处理(NLP)(使用语气,语言学等理解上下文)
-
视觉对象识别
-
概率模型(例如,贝叶斯分类器)
-
马尔可夫决策过程(例如,随机事件的决策,例如赌博)
-
各种其他机器学习算法(聚类、支持向量机)
-
社会学:研究机器学习决策如何影响社会,并采取补救措施纠正问题
机器学习的分类
1959 年,Arthur Samuel 在 IBM 工作时创造了机器学习这个术语。机器学习的一个流行定义归功于 Arthur,据信他称机器学习为一门计算机科学领域,使计算机能够在没有明确编程的情况下学习。
1998 年,Tom Mitchell 对机器学习增加了更具体的定义,并称其为一种研究算法的学科,这些算法通过经验 E 在某个任务 T 上提高其性能 P。
一个简单的解释可以帮助说明这个概念。现在,我们大多数人都熟悉电子邮件中的垃圾邮件概念。大多数电子邮件账户也包含一个名为垃圾邮件、垃圾或类似术语的单独文件夹。对文件夹的粗略检查通常会显示出许多邮件,其中许多可能是未经请求的并包含无意义的信息。
将电子邮件分类为垃圾邮件并将其移动到文件夹中的简单任务也涉及机器学习的应用。Andrew Ng 在他的流行机器学习 MOOC 课程中优雅地强调了这一点。
在 Mitchell 的术语中,垃圾邮件分类过程涉及:
-
任务 T:将电子邮件分类为垃圾邮件/非垃圾邮件
-
性能 P:准确识别为垃圾邮件的数量
-
经验 E:模型提供了被标记为垃圾邮件/非垃圾邮件的电子邮件,并利用这些信息来确定新邮件是否为垃圾邮件
广义上讲,机器学习有两种明显的类型:
-
监督式机器学习
-
无监督式机器学习
我们将在这里依次讨论它们。
监督式和无监督式机器学习
让我们先从监督式机器学习开始。
监督式机器学习
监督式机器学习指的是涉及使用标记数据预测结果的机器学习练习。标记数据简单地指的是我们用来进行预测的数据集(以及我们将要预测的结果)具有明确的值(不管是什么)。例如,将电子邮件分类为垃圾邮件或非垃圾邮件、预测温度和从图像中识别人脸都是监督式机器学习的例子。
车辆里程、数字识别和其他例子
给定一个包含有关每加仑英里数、汽缸数等各种汽车信息的数据集,如果我们只有其他值可用,我们能预测每加仑英里数的值吗?
在这种情况下,我们的结果是mpg
,我们使用cyl
(汽缸数)、hp
(马力)、gear
(齿轮数)等其他变量来构建一个模型,然后应用于一个数据集,其中mpg
的值标记为MISSING
。模型读取数据的前五行中这些列的信息,并根据这些信息预测其他行中mpg
的值,如下图所示:
之所以被认为是监督式的是因为在构建我们的机器学习模型的过程中,我们向模型提供了关于结果的信息。其他例子包括:
-
识别字母和数字:在这种情况下,模型的输入是图像,比如字母和数字的图像,结果是图像上显示的字母数字值。构建模型后,可以用于识别和预测图像中显示的数字。这是一个简单的例子,但非常强大。想象一下,如果你拿到了 10 万张带有门牌号码的房屋图片。手动识别门牌号码的方式是逐个查看每张图片并写下号码。机器学习模型使我们能够完全自动化整个操作。你可以简单地运行模型来识别图像,以极短的时间内获得结果,而不必手动查看每个图像。
-
自动驾驶汽车:算法的输入是图像,图像中的对象已被识别,例如人、街道标志、汽车、树木、商店和其他元素。一旦展示了足够数量的图像,并且给出了一个未标记的图像,也就是对象尚未被识别的图像,算法就能够识别它们。公平地说,这是对一个非常复杂的主题的高度简化的解释,但总体原则是相同的。
用于数字识别的 MNIST 数据集:
无监督机器学习
无监督机器学习涉及没有标记结果的数据集。以预测汽车的每加仑英里数为例,在无监督练习中,我们的数据集将如下所示:
如果所有的结果都缺失,那么就不可能知道这些值可能是什么。请记住,机器学习的主要前提是利用历史信息对结果未知的数据集进行预测。但是,如果历史信息本身没有任何确定的结果,那么就不可能建立模型。在不知道任何其他信息的情况下,表中的 mpg 值可能全部为 0 或全部为 100;我们无法判断,因为没有任何数据点可以帮助我们得出这个值。
这就是无监督机器学习的应用。在这种类型的机器学习中,我们并不试图预测结果。相反,我们试图确定哪些项目彼此最相似。
这种练习的一个常见名称是聚类,也就是说,我们试图找到彼此最相似的记录的簇或组。我们可以在哪些地方使用这些信息,无监督学习的一些例子是什么?
网络上有各种新闻聚合器 - 这些网站本身不发布信息,而是从其他新闻来源收集信息。谷歌新闻就是这样的一个聚合器。比如,如果我们要搜索卡西尼号对土星拍摄的最新图像的信息,我们可以在谷歌新闻上简单搜索这个短语news.google.com/news/?gl=US&ned=us&hl=en
。这里有一个示例:
请注意,在新闻文章底部有一个“查看全部”的链接。点击该链接将带您到包含所有其他相关新闻文章的页面。当然,谷歌并没有手动将文章分类为特定的搜索词。事实上,谷歌事先并不知道用户会搜索什么。搜索词本来也可能是“太空中土星环的图片”。
那么,谷歌是如何知道哪些文章属于特定的搜索词的呢?答案在于聚类或无监督学习原则的应用。无监督学习检查特定数据集的属性,以确定哪些文章彼此最相似。为了做到这一点,算法甚至不需要知道上下文背景。
假设你拿到了两套没有封面的书,一套是关于园艺的书,另一套是关于计算机编程的书。尽管你可能不知道书的标题,但很容易区分计算机书和园艺书。一套书会有大量与计算机相关的术语,而另一套会有大量与植物相关的术语。仅凭书中的图片就能区分出两种不同的书类别,即使是一个不了解计算机或园艺的读者也不难。
无监督机器学习的其他示例包括检测恶性和非恶性肿瘤以及基因测序。
细分监督机器学习
监督机器学习可以进一步细分为以下练习之一:
-
分类
-
回归
这些概念非常简单。
分类涉及具有离散结果的机器学习任务 - 分类结果。所有名词都是分类变量,例如水果、树木、颜色和真/假。
分类练习中的结果变量也被称为离散或分类变量。
一些例子包括:
-
根据大小、重量和形状确定水果
-
给定一组数字图像的数字(如前一章所示)
-
识别街道上的物体
-
识别红心、黑桃、红桃和梅花的扑克牌
-
根据学生的成绩确定学生的班级排名
-
最后一个可能看起来不明显,但是排名,即 1(st)、2(nd)、3^(rd)表示一个固定的类别。一个学生可以排名,比如 1^(st)或 2^(nd),但不能有 1.5 的排名!
下面显示了一些非典型的分类示例的图像:
![]() | ![]() |
---|
回归,另一方面,涉及计算数值结果。您可以执行数值运算的任何结果,例如加法、减法、乘法和除法,都将构成回归问题。
回归的例子包括:
-
预测每日温度
-
计算股价
-
预测住宅物业和其他物业的销售价格
下面显示了一些非典型的回归示例的图像。在这两种情况下,我们处理的是连续的定量数值数据。因此,回归的结果变量也被称为定量或连续变量。
![]() | ![]() |
---|
请注意,分类或回归的概念并不适用于无监督学习。由于无监督学习中没有标签,因此在严格意义上不存在离散的分类或回归。也就是说,由于无监督学习将数据分类为簇,簇中的对象通常被认为属于同一类(与同一簇中的其他对象相同)。这类似于分类,只是在事后创建,而在对象被分类到各个簇之前并不存在类。
机器学习中的常见术语
在机器学习中,您经常会听到特征、预测变量和因变量这些术语。它们都是一样的。它们都指的是用于预测结果的变量。在我们之前关于汽车的例子中,变量cyl(汽缸)、hp(马力)、wt(重量)和gear(齿轮)是预测变量,而mpg(每加仑英里数)是结果。
简单来说,以电子表格为例,列的名称本质上被称为特征、预测变量和因变量。例如,如果我们获得了一个收费站收费的数据集,并被要求根据一天的时间和其他因素来预测收费金额,一个假设的例子可能如下:
在这个电子表格中,列date、time、agency、type、prepaid和rate是特征或预测变量,而列amount是我们的结果或因变量(我们正在预测的内容)。
金额的值取决于其他变量的值(因此被称为自变量)。
简单的方程也反映了明显的区别,例如,在一个方程中,y = a + b + c,左手边(LHS)是因变量/结果变量,a、b和c是特征/预测变量。
总之:
机器学习的核心概念
机器学习中有许多重要的概念;我们将介绍一些更常见的主题。机器学习涉及一个多步骤的过程,从数据获取、数据挖掘,最终到构建预测模型。
模型构建过程的关键方面包括:
-
数据预处理:预处理和特征选择(例如,居中和缩放,类别不平衡和变量重要性)
-
训练,测试分割和交叉验证:
-
创建训练集(比如说,数据的 80%)
-
创建测试集(数据的大约 20%)
-
执行交叉验证
-
创建模型,获取预测:
-
你应该尝试哪些算法?
-
你试图优化哪些准确性指标?
-
你应该使用哪些调整参数?
机器学习中的数据管理步骤
预处理,或者更一般地处理数据,是大多数机器学习练习的一个重要部分。你开始使用的数据集很少会与你构建机器学习模型的确切格式一致;在大多数情况下,它都需要进行相当多的清理。事实上,数据清理通常是整个过程中最耗时的部分。在本节中,我们将简要介绍一些你在实践中可能遇到的顶级数据处理步骤。
预处理和特征选择技术
数据预处理,顾名思义,涉及筛选数据,使其适用于机器学习练习。有各种各样的预处理方法,这里列举了一些比较常见的方法。
请注意,数据预处理应该作为交叉验证步骤的一部分进行,也就是说,预处理不应该在事先进行,而应该在模型构建过程中进行。稍后将对此进行更详细的解释。
居中和缩放
对数值列应用中心和缩放函数通常是为了标准化数据并消除数字的数量或差异的影响。你可能在大学课程中遇到过这种情况,学生会按照标准化的方式或曲线进行评分。
例如,假设一张考试试卷异常困难,10 名学生中有一半的学生得分低于 60 分 - 这是课程的及格率。教授可以选择 a)决定让 50%的学生重新上课,或者 b)标准化分数以找出学生相对于彼此的表现。
假设班级分数是:
45,66,66,55,55,52,61,64,65,49
以 60 分为及格分数,这意味着得分为 45、55、55、52 和 49 的学生将无法成功完成课程。
然而,这可能并不是他们相对优点的真正准确的表示。教授可以选择使用一种称为标准化的中心和缩放方法,它包括:
-
找到所有分数的平均值
-
从分数中减去平均值
-
将结果除以所有分数的标准差
下面是操作的示例。
分数的平均值是 57.8。因此,从每个数字中减去 57.8 会产生第二行中显示的数字。但是,我们还没有完成。我们需要将这些数字除以分数的标准差,以获得最终的标准化值:
除以SD(标准差)表明,在所有测试成绩范围内,只有两名学生的成绩低于一个标准差。因此,根据原始数字,不是五名学生未能成功完成课程,而是只有两名学生。
尽管这是一个真正简单的操作,但不难看出,它在平滑数据的大波动方面非常有效。
在 R 中,可以使用 scale 命令非常容易地进行居中和缩放,如下所示:
> scores <- c(45,66,66,55,55,52,61,64,65,68)
> scale(scores)
[,1]
[1,] -1.9412062
[2,] 0.8319455
[3,] 0.8319455
[4,] -0.6206578
[5,] -0.6206578
[6,] -1.0168223
[7,] 0.1716713
[8,] 0.5678358
[9,] 0.6998907
[10,] 1.0960552
attr(,"scaled:center")
[1] 59.7
attr(,"scaled:scale")
[1] 7.572611
接近零方差函数
nearZeroVar
函数在R package, caret
中可用于识别具有很少或没有方差的变量。考虑一个具有仅三个不同值的 10,000 个数字集。这样的变量可能对算法几乎没有价值。为了使用nearZeroVar
函数,首先在 RStudio 中安装 R 软件包 caret(我们在第三章中设置了The Analytics Toolkit)。使用nearZeroVar
的效果的确切代码如下所示:
> library(caret)
Loading required package: lattice
Loading required package: ggplot2
Need help getting started? Try the cookbook for R: http://www.cookbook-r.com/Graphs/
> repeated <- c(rep(100,9999),10) # 9999 values are 100 and the last value is 10
>random<- sample(100,10000,T) # 10,000 random values from 1 - 100
>data<- data.frame(random = random, repeated = repeated)
>nearZeroVar(data)
[1] 2
> names(data)[nearZeroVar(data)]
[1] "repeated"
正如示例所示,该函数能够正确检测到符合标准的变量。
去除相关变量
相关变量可能会产生过分强调变量贡献的结果。在回归练习中,这会增加 R² 的值,并且不准确地代表模型的实际性能。尽管许多类别的机器学习算法对相关变量的影响具有抵抗力,但它值得一提,因为这是该学科中的一个常见主题。
删除这样的变量的前提是冗余变量不会为模型增加增量值。例如,如果数据集包含英寸和米的身高,这些变量的相关性几乎完全为 1,使用其中一个与使用另一个一样好。使用去除相关变量的方法进行实际练习,可以极大地帮助简化模型,特别是涉及我们无法直观判断的变量。
以下示例说明了去除相关变量的过程。数据集Pima Indians Diabetes包含有关 Pima 印第安人饮食的重要统计数据,以及名为diabetes
的结果变量。
在接下来的章节示例中,我们将经常提到这个数据集。数据集中不同列的含义的高级概述如下:
pregnant Number of times pregnant
glucose Plasma glucose concentration (glucose tolerance test)
pressure Diastolic blood pressure (mm Hg)
triceps Triceps skin fold thickness (mm)
insulin 2-Hour serum insulin (mu U/ml)
mass Body mass index (weight in kg/(height in m)\²)
pedigree Diabetes pedigree function
age Age (years)
diabetes Class variable (test for diabetes)
我们有兴趣查找除糖尿病(我们的结果变量)以外的任何相关变量。如果有的话,删除冗余变量可能会有用。
在 RStudio 中安装mlbench
和corrplot
软件包,并执行以下命令:
install.packages("mlbench")
install.packages("corrplot")
library(corrplot)
library(mlbench)
data (PimaIndiansDiabetes)
diab <- PimaIndiansDiabetes # To produce a correlogram
corrplot(cor(diab[,-ncol(diab)]), method="color", type="upper") # To get the actual numbers
corrplot(cor(diab[,-ncol(diab)]), method="number", type="upper")
该命令将使用corrplot
软件包从www.sthda.com/english/wiki/visualize-correlation-matrix-using-correlogram
生成一个图表,如下所示:
![]() | >![]() |
---|
阴影越深,相关性越高。在这种情况下,它显示年龄和怀孕之间有相对较高的相关性。我们可以使用method="number"
找到确切的值。您也可以在www.sthda.com/english/wiki/visualize-correlation-matrix-using-correlogram
上查看图表。
我们还可以使用以下函数直接查找相关变量,而无需绘制相关图:
correlated_columns<- findCorrelation(cor(diab[,-ncol(diab)]), cutoff = 0.5)
correlated_columns
其他常见的数据转换
还有其他几种数据转换方法适用于不同的情况。这些转换的摘要可以在caret
软件包的文档网站的Pre-Processing下找到topepo.github.io/caret/pre-processing.html
。
在 caret 的预处理函数中提供的选项可以在其帮助部分中找到,通过在 RStudio 中运行命令?preProcess
。其代码如下:
Method
a character vector specifying the type of processing.
Possible values are "BoxCox", "YeoJohnson", "expoTrans", "center", "scale", "range", "knnImpute", "bagImpute", "medianImpute", "pca", "ica", "spatialSign", "corr", "zv", "nzv", and "conditionalX" (see Details below)
数据抽样
您可能会遇到具有高度不平衡结果类别的数据集。例如,如果您正在处理一个罕见疾病的数据集,您的结果变量是真或假,由于发生的罕见性,您可能会发现标记为假的观察数量(即,该人没有罕见疾病)远远高于标记为真的观察数量(即,该人患有罕见疾病)。
机器学习算法试图最大化性能,在许多情况下可能是预测的准确性。比如,在 1000 条记录的样本中,只有 10 条被标记为真,其余的990
条观察结果都是假的。
如果有人随机将所有观察结果都标记为假,准确率将是:
(990/1000) * 100 = 99%
但是,这项练习的目标是找到患有罕见疾病的个体。我们已经很清楚,由于疾病的性质,大多数个体不会属于这一类别。
数据抽样本质上是最大化机器学习指标,如特异性、敏感性、精确度、召回率和 kappa的过程。这些将在后面讨论,但在本节的目的上,我们将展示一些方法,通过这些方法,您可以对数据进行抽样,以产生一个更均衡的数据集。
R 软件包caret
包括几个有用的函数,用于从不平衡的数据集中创建一个平衡的类别分布。
在这些情况下,我们需要重新对数据进行重新抽样,以获得更好的类别分布,以建立一个更有效的模型。
一些常见的方法包括:
-
上采样:增加具有较少实例的类别
-
下采样:减少具有更多实例的类别
-
创建合成示例(例如,SMOTE(合成少数过采样技术))
-
随机过采样(例如,(ROSE) 随机过采样示例)
我们将使用与先前示例相同的数据创建一个模拟数据集,其中 95%的行将被标记为负:
library(mlbench)
library(caret)
diab<- PimaIndiansDiabetes
diabsim<- diab
diabrows<- nrow(diabsim)
negrows<- floor(.95 * diabrows)
posrows<- (diabrows - negrows)
negrows
[1] 729
posrows
[1] 39
diabsim$diabetes[1:729] <- as.factor("neg")
diabsim$diabetes[-c(1:729)] <- as.factor("pos")
table(diabsim$diabetes)
neg. pos
729 39
# We observe that in this simulated dataset, we have 729 occurrences of positive outcome and 39 occurrences of negative outcome
# Method 1: Upsampling, i.e., increasing the number of observations marked as 'pos' (i.e., positive)
upsampled_simdata<- upSample(diabsim[,-ncol(diabsim)], diabsim$diabetes)
table(upsampled_simdata$Class)
negpos
729 729
# NOTE THAT THE OUTCOME IS CALLED AS 'Class' and not 'diabetes'
# This is because of the use of the variable separately
# We can always rename the column to revert to the original name
# Method 2: Downsampling, i.e., reducing the number of observations marked as 'pos' (i.e., positive)
downsampled_simdata<- downSample(diabsim[,-ncol(diabsim)], diabsim$diabetes)
table(downsampled_simdata$Class)
neg pos
39 39
- SMOTE(合成少数类过采样技术)是第三种方法,它不是简单的上/下采样,而是从少数类的最近邻中创建合成记录。在我们的模拟数据集中,很明显
neg
是少数类,也就是发生次数最少的类别。
SMOTE 函数的帮助文件简洁地解释了这个概念:
不平衡的分类问题给许多学习算法带来了问题。这些问题的特点是每个问题类别的案例比例不均衡。
SMOTE(Chawla 等人,2002)是一个用于解决这个问题的著名算法。该方法的一般思想是使用少数类的最近邻人工生成新的示例。此外,多数类的示例也被下采样,从而导致更平衡的数据集:
# Method 3: SMOTE
# The function SMOTE is available in the R Package DMwR
# In order to use it, we first need to install DmWR as follows
install.packages ("DMwR")
# Once the package has been installed, we will create a synthetic
# Dataset in which we will increase the number of 'neg' records
# Let us check once again the distribution of neg/pos in the dataset
table(diabsim$diabetes)
negpos
729 39
# Using SMOTE we can create synthetic cases of 'pos' as follows
diabsyn<- SMOTE(diabetes ~ ., diabsim, perc.over = 500, perc.under = 150)
# perc.over = 500 means, increase the occurrence of the minority
# class by 500%, i.e., 39 + 5*39 = 39 + 195 = 234
# perc.under = 150 means, that for each new record generated for the
# Minority class, we will generate 1.5 cases of the majority class
# In this case, we created 195 new records (500% of 39) and hence
# we will generate 150% of 195 records = 195 * 150% = 195 * 1.5
# = 292.5, or 292 (rounded down) new records
# We can verify this by running the table command against the newly
# Created synthetic dataset, diabsyn
table(diabsyn$diabetes)
negpos
292 234
- ROSE(随机过采样示例),本节的最后一种方法,通过 R 中的 ROSE 软件包提供。与 SMOTE 类似,它是一种生成合成样本的方法。ROSE 的帮助文件说明了该函数的高级用法如下:
通过随机过采样示例生成合成数据,通过扩大少数和多数类示例的特征空间来创建合成数据样本。在操作上,新的示例是从两个类的条件核密度估计中抽取的,如 Menardi 和 Torelli(2013)中所述。
install.packages("ROSE")
library(ROSE)
# Loaded ROSE 0.0-3
set.seed(1)
diabsyn2 <- ROSE(diabetes ~ ., data=diabsim)
table(diabsyn2$data$diabetes)
# negpos
# 395 373
数据插补
有时,您的数据可能存在缺失值。这可能是由于数据收集过程中的错误、真正缺失的数据或其他原因,导致信息不可用。缺失数据的现实世界例子可以在调查中找到,调查对象没有回答调查中的特定问题。
您可能有一个包含 1000 条记录和 20 列的数据集,其中某一列有 100 个缺失值。您可以选择完全丢弃这一列,但这也意味着丢弃了 90%的信息。您仍然有其他 19 列具有完整数据。另一个选择是简单地排除该列,但这意味着您无法利用该列中可用的数据所带来的好处。
存在多种数据插补方法,即填充缺失数据的过程。我们不知道确切的值是什么,但通过查看表中的其他条目,我们可能能够对值进行系统的评估。
一些常见的数据插补方法包括:
-
均值、中位数、众数插补:使用列的均值、中位数或众数值替换缺失值。然而,这样做会增加被插补的变量之间的相关性,这对多变量分析可能不是理想的。
-
K 最近邻插补:kNN 插补是使用机器学习方法(最近邻)来填补缺失值的过程。它通过找到与具有缺失值的记录最相似的 k 条记录,并使用欧几里德距离相对于 k 条记录计算加权平均值来工作。
-
使用回归模型进行插补:回归方法使用 R 中的标准回归方法来预测缺失变量的值。然而,正如维基百科上关于基于回归的插补的相应部分所指出的那样
en.wikipedia.org/wiki/Imputation_(statistics)#Regression
,问题在于(回归插补)估计的插补数据没有包括误差项。因此,估计值完全符合回归线,没有任何残差方差。这导致关系被过度识别,并表明插补值的精度比实际情况更高。 -
热卡插补:使用数据集本身的观察值填充缺失值的另一种技术。这种方法虽然非常普遍,但有一个局限性,即通过为大范围的缺失值分配一个单一值,可能会在观察中增加显著的偏差,并产生误导性的结果。
这里提供了一个简短的示例,演示了如何使用 kNN 插补进行插补。我们通过在PimaIndiansDiabetes
数据集中将大量值更改为 NA 来模拟缺失数据。
我们利用以下因素进行处理:
-
我们使用均值来填充 NA 值。
-
我们使用 kNN 插补来填补缺失值。然后比较这两种方法的表现:
library(DMwR)
library(caret)
diab<- PimaIndiansDiabetes
# In the dataset, the column mass represents the body mass index
# Of the individuals represented in the corresponding row
# mass: Body mass index (weight in kg/(height in m)\²)
# Creating a backup of the diabetes dataframe
diabmiss_orig<- diab
# Creating a separate dataframe which we will modify
diabmiss<- diabmiss_orig
# Saving the original values for body mass
actual <- diabmiss_orig$mass
# Change 91 values of mass to NA in the dataset
diabmiss$mass[10:100] <- NA
# Number of missing values in mass
sum(is.na(diabmiss$mass))
# 91
# View the missing values
diabmiss[5:15,]
我们得到以下输出:
# Test with using the mean, we will set all the missing values
# To the mean value for the column
diabmiss$mass[is.na(diabmiss$mass)] <- mean(diabmiss$mass,na.rm = TRUE)
# Check the values that have been imputed
data.frame(actual=actual[10:100], impute_with_mean=diabmiss$mass[10:100])
前面代码的输出如下:
# Check the Root-Mean-Squared-Error for the entire column
# Root Mean Squared Error provides an estimate for the
# Difference between the actual and the predicted values
# On 'average'
diabmissdf<- data.frame(actual=actual, impute_with_mean=diabmiss$mass)
rmse1 <- RMSE(diabmissdf$impute_with_mean,actual)
rmse1
# [1] 3.417476
# We will re-run the exercise using knnImputation (from package DMwR)
# Change the value of the records back to NA
diabmiss<- diabmiss_orig
diabmiss$mass[10:100] <- NA
# Perform knnImputation
diabknn<- knnImputation(diabmiss,k=25)
# Check the RMSE value for the knnImputation method
rmse2 <- RMSE(diabknn$mass,actual)
rmse2
# [1] 3.093827
# Improvement using the knnImputation methods in percentage terms
100 * (rmse1-rmse2)/rmse1
[1] 22.20689
虽然这可能不代表一个显著的变化,但仍然比使用简单的方法(如使用均值或常数值)要好。
R 中有几个数据插补的包。其中一些著名的包如下:
- Amelia II:时间序列数据中的缺失信息
- 使用 R 包进行热卡补充: HotDeckImputation 和 hot.deck
cran.r-project.org/web/packages/HotDeckImputation/
cran.r-project.org/web/packages/hot.deck/
- 多变量填充(通过链式方程)
cran.r-project.org/web/packages/mice/index.html
- 在 R 包中使用贝叶斯框架进行值的填充: mi
cran.r-project.org/web/packages/mi/index.html
变量的重要性
在模型构建过程中,数据集可能有数十个变量。并非所有变量都可能对预测模型有价值。将数据集减少到包括变量子集并允许机器学习程序员花更多时间来调整选择的变量和模型构建过程是很常见的。减少数据集中变量数量也有技术上的理由。在非常大的、即高维数据集上执行机器学习建模可能非常计算密集,即可能需要大量的时间、CPU 和 RAM 来执行数值运算。这不仅使得应用某些算法变得不切实际,还会导致不必要的延迟。因此,变量的系统选择有助于分析时间和算法分析的计算要求。
变量选择也被称为特征选择/属性选择。随机森林和套索回归等算法实现了变量选择作为其算法操作的一部分。但是,变量选择也可以作为一个单独的练习来完成。
R 包caret
为变量选择提供了一个非常简单易用和直观的接口。由于我们还没有讨论建模过程,我们将学习如何找到重要的变量,并在下一章深入探讨这个主题。
我们将使用一个常见的、众所周知的算法,称为RandomForest
,用于构建决策树。该算法将在下一章中更详细地描述,但在这里使用它的目的仅仅是为了展示如何进行变量选择。这个例子说明了一般过程是什么样的。
我们将重复使用我们一直在处理的数据集,即来自mlbench
包的PimaIndiansDiabetes
数据。我们还没有讨论模型训练过程,但在这里使用它是为了得出变量重要性的值。在这种情况下,结果变量是糖尿病,其他变量被用作自变量。换句话说,我们能否使用可用的数据来预测一个人是否患有糖尿病:
diab<- PimaIndiansDiabetes
# We will use the createDataPartition function from caret to split
# The data. The function produces a set of indices using which we
# will create the corresponding training and test sets
training_index<- createDataPartition(diab$diabetes, p = 0.80, list = FALSE, times = 1)
# Creating the training set
diab_train<- diab[training_index,]
# Create the test set
diab_test<- diab[-training_index,]
# Create the trainControl parameters for the model
diab_control<- trainControl("repeatedcv", number = 3, repeats = 2, classProbs = TRUE, summaryFunction = twoClassSummary)
# Build the model
rf_model<- train(diabetes ~ ., data = diab_train, method = "rf", preProc = c("center", "scale"), tuneLength = 5, trControl = diab_control, metric = "ROC")
# Find the Variable Importance
varImp(rf_model)
rf variable importance
Overall
glucose 100.000
mass 52.669
age 39.230
pedigree 24.885
pressure 12.619
pregnant 6.919
insulin 2.294
triceps 0.000
# This indicates that glucose levels, body mass index and age are the top 3 predictors of diabetes.
# caret also includes several useful plot functions. We can visualize the variable importance using the command:
plot(varImp(rf_model))
上述代码的输出如下所示。它表明葡萄糖、体重指数和年龄是对创建模型(预测糖尿病)做出最大贡献的变量。
训练、测试拆分和交叉验证概念
在机器学习中,训练、测试拆分和交叉验证集是一个基本概念。这是一个纯统计方法与机器学习方法有实质区别的领域之一。在统计建模任务中,一个人可能进行回归、参数/非参数测试,并应用其他方法,而在机器学习中,算法方法被补充了对产生的结果的迭代评估和随后的模型改进的元素。
将数据拆分为训练集和测试集
每个机器学习建模练习都始于数据清洗的过程,正如前面讨论的那样。下一步是将数据分割成训练集和测试集。通常是通过随机选择数据中的行来完成的,这些行将被用来创建模型。然后未被选择的行将被用来测试最终模型。
通常的分割在 70-80%之间(训练数据与测试数据)。在 80-20 的分割中,80%的数据将被用来创建模型。剩下的 20%将被用来测试最终模型。
我们在前面的部分中应用了这个方法,但我们可以再次查看代码。createDataPartition
函数被用来分割数据,参数为p = 0.80
。training_index
变量保存了我们将使用的训练索引(dataset
,diab
):
training_index<- createDataPartition(diab$diabetes, p = 0.80, list = FALSE, times = 1)
length(training_index) # Number of items that we will select for the train set [1] 615
nrow(diab) # The total number of rows in the dataset [1] 768
# Creating the training set, this is the data we will use to build our model
diab_train<- diab[training_index,]
# Create the test set, this is the data against which we will test the performance of our model
diab_test<- diab[-training_index,]
我们不一定要使用createDataPartition
函数,而是可以使用简单的 R 命令创建一个随机样本,如下所示:
# Create a set of random indices representing 80% of the data
training_index2 <- sample(nrow(diab),floor(0.80*nrow(diab)))
# Check the size of the indices just created
length(training_index2) [1] 614
# Create the training set
diab_train2 <- diab[training_index2,]
# Create the test set
diab_test2 <- diab[-training_index2]
交叉验证参数
交叉验证将训练-测试分割的概念推向了下一个阶段。机器学习练习的目标本质上是找到哪组模型参数能提供最佳的性能。模型参数指的是函数(模型)所需的参数。例如,对于决策树模型,参数可能包括模型应该构建的深度级别、分割数量等。如果有n个不同的参数,每个参数有k个不同的值,那么总参数数量将是k^n。通常我们会为每个参数选择一组固定的组合,可能最终会有 100-1000+个组合。我们将测试模型的性能(例如,正确预测结果的准确度)。
对于一个简单的训练-测试分割,比如说,如果我们选择了 500 个参数组合,我们只需要对训练数据集运行它们,并确定哪一个显示出最佳性能。
通过交叉验证,我们进一步将训练集分成更小的子集,比如说通常使用三折或五折。如果有三折,也就是说,我们将训练集分成三个子集,我们将一折放在一边,比如说第 2 折,然后使用第 1 折和第 3 折构建一个模型。然后测试它对第 2 折的准确性。这个步骤会重复多次,每次迭代都代表了一组独特的折,训练-测试过程和准确度测量。最终,我们会选择表现最佳的参数组合。
标准的方法可以总结如下:
-
创建一个 80-20 的训练-测试分割
-
使用不同的模型参数组合执行你的模型
-
选择表现最佳的模型参数并创建最终模型
-
在测试集上应用最终模型以查看结果
交叉验证方法要求我们进一步将训练数据集分成更小的子集。这些子集通常被称为折,总称为k 折,其中k代表分割的数量:
-
创建一个 80-20 的训练-测试分割
-
将训练集分成 k 折,比如说三折
-
将第 1 折放在一边,使用第 2 折和第 3 折构建模型
-
在第 1 折上测试你的模型表现(例如,准确结果的百分比)
-
将第 2 折放在一边,使用第 1 折和第 3 折构建模型
-
在第 2 折上测试你的模型表现
-
将第 3 折放在一边,使用第 1 折和第 2 折构建模型
-
在第 3 折上测试你的模型表现
-
取所有三折模型的平均性能
-
对每组模型参数重复步骤 1
-
选择表现最佳的模型参数并创建最终模型
-
在测试集上应用最终模型以查看结果
这张图片说明了使用不带交叉验证的方法和带交叉验证的方法之间的差异。交叉验证方法可以说更加健壮,并且涉及对模型的严格评估。也就是说,尝试最初创建一个不带交叉验证的模型通常是有用的,以了解可能期望的性能。例如,如果使用 2-3 个训练-测试分割构建的模型显示出 30%的准确性,那么很可能任何其他方法,包括交叉验证,都不会使其达到 90%。换句话说,标准方法有助于了解可能期望的性能。由于交叉验证可能非常耗费计算资源和时间,因此在性能的初步分析中获得初始反馈是有帮助的。
R 中的 caret 包提供了一个非常用户友好的方法来使用交叉验证构建模型。请记住,数据预处理必须通过或作为交叉验证过程的一个组成部分。因此,假设我们需要对数据集进行中心化和缩放,并进行五折交叉验证,我们只需要在 caret 的trainControl
函数中定义我们想要使用的抽样类型。
Caret 关于trainControl
的网页提供了函数的详细概述,并附有示例,网址为topepo.github.io/caret/model-training-and-tuning.html#basic-parameter-tuning
。
我们在之前的练习中使用了这种方法,在PimaIndiansDiabetes
数据集上使用RandomForest
构建了一个模型。这里再次展示出来,以表明这种技术的使用情况:
# Create the trainControl parameters for the model
# The parameters indicate that a 3-Fold CV would be created
# and that the process would be repeated 2 times (repeats)
# The class probabilities in each run will be stored
# And we'll use the twoClassSummary* function to measure the model
# Performance
diab_control<- trainControl("repeatedcv", number = 3, repeats = 2, classProbs = TRUE, summaryFunction = twoClassSummary)
# Build the model
# We used the train function of caret to build the model
# As part of the training process, we specified a tunelength** of 5
# This parameter lets caret select a set of default model parameters
# trControl = diab_control indicates that the model will be built
# Using the cross-validation method specified in diab_control
# Finally preProc = c("center", "scale") indicate that the data
# Would be centered and scaled at each pass of the model iteration
rf_model<- train(diabetes ~ ., data = diab_train, method = "rf", preProc = c("center", "scale"), tuneLength = 5, trControl = diab_control, metric = "ROC")
您可以从cran.r-project.org/web/packages/caret/vignettes/caret.pdf
获取有关summaryFunction
的更详细解释。
summaryFunction
参数用于传递一个函数,该函数接受观察值和预测值,并估计某种性能指标。该包中已经包含了两个这样的函数:defaultSummary
和twoClassSummary
。后者将计算特定于两类问题的度量,例如 ROC 曲线下面积、灵敏度和特异性。由于 ROC 曲线是基于预测类别概率的(这些概率不会自动计算),因此需要另一个选项。classProbs = TRUE
选项用于包括这些计算。
以下是来自caret
的train
函数的帮助文件中关于tuneLength
的解释。
tuneLength
是一个整数,表示调整参数网格中的粒度。默认情况下,该参数是由train
生成的每个调整参数的级别数。如果trainControl
选项中有search = random
,则这是由随机搜索生成的调整参数组合的最大数量。
请注意,如果给出了这个参数,必须要有名称。
创建模型
创建模型后的最后一步是使用模型对测试数据集进行预测。通常使用 R 中的predict
函数来完成,第一个参数是创建的模型,第二个参数是您想要获取预测结果的数据集。
以PimaIndiansDiabetes
数据集为例,在模型构建完成后,我们可以按以下方式在测试数据集上进行预测:
# Install the R Package e1071, if you haven't already
# By running install.packages("e1071")
# Use the predict function and the rf_model that was previously built
# To get the predictions on the test dataset
# Note that we are not including the column diabetes in the test
# dataset by using diab_test[,-ncol(diab_test)]
predictions<- predict(rf_model, diab_test[,-ncol(diab_test)])
# First few records predicted
head(predictions)
[1] negnegpospospospos
Levels: negpos
# The confusion matrix allows us to see the number of true positives
# False positives, True negatives and False negatives
cf<- confusionMatrix(predictions, diab_test$diabetes)
cf
# Confusion Matrix and Statistics
#
# Reference
# Prediction negpos
# neg 89 21
# pos 11 32
#
# Accuracy : 0.7908
# 95% CI : (0.7178, 0.8523)
# No Information Rate : 0.6536
# P-Value [Acc> NIR] : 0.0001499
#
# Kappa : 0.5167
# Mcnemar's Test P-Value : 0.1116118
#
# Sensitivity : 0.8900
# Specificity : 0.6038
# PosPredValue : 0.8091
# NegPredValue : 0.7442
# Prevalence : 0.6536
# Detection Rate : 0.5817
# Detection Prevalence : 0.7190
# Balanced Accuracy : 0.7469
#
# 'Positive' Class :neg
让我们看看混淆矩阵告诉我们什么:
# This indicates that of the records that were marked negative (neg)
# We predicted 89 of them as negative and 11 as positive (i.e., they
# were negative but we incorrectly classified them as a positive
# We correctly identified 32 positives but incorrectly classified
# 21 positives as negative
#
# Reference
# Prediction neg pos
# neg 89 21
# pos 11 32
# The overall accuracy was 79%
# This can be improved (significantly) by using more
# Accuracy : 0.7908
# We can plot the model using plot(rf_model) as follows
plot(rf_model)
绘图如下:
# And finally we can also visualize our confusion matrix using the
# inbuilt fourfoldplot function in R
fourfoldplot(cf$table)
我们得到的绘图如下:
根据*fourfoldplot
*的文档[来源:stat.ethz.ch/R-manual/R-devel/library/graphics/html/fourfoldplot.html
],二元行和列变量之间的关联(与 1 不同的几率比)由对角线相对方向的单元格大小差异的倾向来指示;颜色用于显示这个方向。几率比的置信环允许对无关联的零假设进行视觉检验;如果相邻象限的环重叠,那么观察计数与零假设一致。
利用模型中的多核处理
在这里使用 PimaIndianDiabetes2 数据集重复上一节的练习。该数据集包含一些缺失值。因此,我们将首先填补缺失值,然后运行机器学习示例。
该练习已经以一些额外的细微差别重复进行,比如使用多核/并行处理以使交叉验证运行更快。
要利用多核处理,使用以下代码安装doMC
包:
Install.packages("doMC") # Install package for multicore processing
Install.packages("nnet") # Install package for neural networks in R
现在我们将按照这里的代码运行程序:
# Load the library doMC
library(doMC)
# Register all cores
registerDoMC(cores = 8)
# Set seed to create a reproducible example
set.seed(100)
# Load the PimaIndiansDiabetes2 dataset
data("PimaIndiansDiabetes2",package = 'mlbench')
diab<- PimaIndiansDiabetes2
# This dataset, unlike PimaIndiansDiabetes has 652 missing values!
> sum(is.na(diab)) [1] 652
# We will use knnImputation to fill in the missing values
diab<- knnImputation(diab)
# Create the train-test set split
training_index<- createDataPartition(diab$diabetes, p = .8, list = FALSE, times = 1)
# Create the training and test dataset
diab_train<- diab[training_index,]
diab_test<- diab[-training_index,]
# We will use 10-Fold Cross Validations
diab_control<- trainControl("repeatedcv", number = 10, repeats = 3, search = "random", classProbs = TRUE)
# Create the model using methodnnet (a Neural Network package in R)
# Note that we have changed the metric here to "Accuracy" instead of # ROC
nn_model<- train(diabetes ~ ., data = diab_train, method = "nnet", preProc = c("center", "scale"), trControl = diab_control, tuneLength = 10, metric = "Accuracy")
predictions<- predict(nn_model, diab_test[,-ncol(diab_test)])
cf<- confusionMatrix(predictions, diab_test$diabetes)
cf
# >cf
# Confusion Matrix and Statistics
#
# Reference
# Prediction negpos
# neg 89 19
# pos 11 34
#
# Accuracy : 0.8039
# 95% CI : (0.7321, 0.8636)
# No Information Rate : 0.6536
# P-Value [Acc> NIR] : 3.3e-05
#
即使有 650 多个缺失值,我们的模型也能够达到 80%以上的准确率。
它肯定可以得到改进,但作为基准,它展示了机器学习模型可以期望的性能类型。
在二元结果变量的情况下,随机猜测的准确率为 50%。80%的准确率显然比我们只使用猜测所能达到的准确率要高得多:
plot(nn_model)
得到的图如下:
fourfoldplot(cf$table)
结果如下图所示:
总结
在本章中,我们了解了机器学习的基本原理,不同类型,如监督和无监督,以及数据预处理、数据填补、管理不平衡类别和其他主题。
我们还了解了今天可以互换使用的一些关键术语之间的区别,特别是 AI 和机器学习这两个术语。我们了解到人工智能涉及到各种各样的主题,如博弈论、社会学、受限优化和机器学习;相对于机器学习,AI 的范围要广得多。
机器学习促进了人工智能;也就是说,机器学习算法被用来创建人工智能系统,但它们的范围不同。回归问题(在给定一组点的情况下找到最佳拟合线)可以被视为机器学习算法,但在概念上,它不太可能被视为 AI 算法(尽管从技术上讲它可能是)。
在下一章中,我们将研究机器学习中的其他概念,如偏差、方差和正则化。我们还将了解一些重要的算法,并学习如何使用 R 中的机器学习包应用它们。
第八章:深入学习机器学习
之前关于机器学习的章节提供了该主题的初步概述,包括该主题领域的不同类别和核心概念。本章将更深入地探讨机器学习的理论方面,比如算法的限制以及不同算法的工作原理。
机器学习是一个广阔而复杂的主题,因此本章侧重于不同主题的广度,而不是深度。这些概念是以高层次介绍的,读者可以参考其他来源进一步了解这些主题。
我们将首先讨论机器学习中的一些基本理论,比如梯度下降和 VC 维度。接下来,我们将研究偏差和方差,这两个在任何建模过程中最重要的因素,以及偏差-方差权衡的概念。
接下来我们将讨论各种机器学习算法,它们的优势和应用领域。
最后,我们将通过使用真实世界的数据集来执行机器学习操作,来总结本章。
本章将涵盖以下主题:
-
偏差、方差和正则化属性
-
梯度下降和 VC 维度理论
-
机器学习算法
-
教程:使用 R 进行机器学习
偏差、方差和正则化属性
偏差、方差以及与之密切相关的正则化在机器学习领域中占据着非常特殊和基础的位置。
当机器学习模型过于“简单”时,就会出现偏差,导致结果与实际值一直偏离。
方差发生在模型过于“复杂”时,导致在测试数据集上结果非常准确,但在未见过/新的数据集上表现不佳。
一旦用户熟悉了创建机器学习模型的过程,似乎这个过程相当简单-获取数据,创建训练集和测试集,创建模型,在测试数据集上应用模型,练习完成。创建模型很容易;创建一个好模型则是一个更具挑战性的话题。但是如何测试模型的质量呢?也许更重要的是,如何构建一个“好”的模型呢?
答案在于一个叫做正则化的术语。这可能是一个花哨的词,但它的意思只是在创建模型的过程中,通过对训练数据集上过于出色的表现进行惩罚,对表现不佳的模型进行放松,从而受益。
要理解正则化,了解过拟合和欠拟合的概念会有所帮助。为此,让我们看一个简单但熟悉的例子,即绘制最佳拟合线。对于那些使用过 Microsoft Excel 的人,你可能已经注意到了绘制最佳拟合线的选项-实质上,给定一组点,你可以绘制一条代表数据并逼近点所代表的函数的线。
以下表格显示了几个属性的价格与面积的关系。为了确定房价与房屋大小之间的关系,我们可以绘制最佳拟合线或趋势线,如下所示:
Sq. ft. | Price ($) |
---|---|
862 | 170,982 |
1235 | 227,932 |
932 | 183,280 |
1624 | 237,945 |
1757 | 275,921 |
1630 | 274,713 |
1236 | 201,428 |
1002 | 193,128 |
1118 | 187,073 |
1339 | 202,422 |
1753 | 283,989 |
1239 | 228,170 |
1364 | 230,662 |
995 | 169,369 |
1000 | 157,305 |
如果我们使用线性趋势线绘制最佳拟合线,图表会看起来像这样:
Excel 提供了一个有用的附加功能,允许用户绘制趋势线的延伸,这可以提供未知变量的估计或预测。在这种情况下,延伸趋势线将告诉我们,根据函数,1800-2000 平方英尺范围内的房屋价格可能是多少。
描述数据的线性函数如下:
y=126.13x + 54,466.81
下图中的延伸趋势线显示价格很可能在 275,000 美元和 300,000 美元之间:
然而,有人可能会认为这条线不是最好的近似,可能可以增加 R2 的值,这种情况下是 0.87。一般来说,R² 越高,描述数据的模型就越好。有各种不同类型的R²值,但在本节中,我们假设R²越高,模型就越好。
在下一节中,我们将绘制一个具有更高 R² 的新趋势线,但使用多项式函数。这个函数具有更高的 R²(0.91 比 0.87),在视觉上看起来更接近平均点。
在这种情况下,函数是一个 6 次多项式:
y = -0.00x⁶ + 0.00x⁵ - 0.00x⁴ + 2.50x³ - 2,313.40x² + 1,125,401.77x - 224,923,813.17
但是,即使该线具有更高的 R²,如果我们延伸趋势线,试图找出 1800-2000 平方英尺范围内房屋的价格可能是什么,我们得到以下结果。
1800-2000 平方英尺范围内的房屋价格从大约 280,000 美元到负 2 百万美元。换句话说,购买 1800 平方英尺房屋的人预计要花费 280,000 美元,而购买 2000 平方英尺房屋的人根据这个函数应该获得 200 万美元!当然,这是不准确的,但我们刚刚见证的是所谓的过拟合。下图说明了这一现象。
在光谱的另一端是欠拟合。当构建的模型不能描述数据时就会发生这种情况。在下图中,函数 y = 0.25x - 200 就是一个例子:
简而言之,这一部分可以简化如下:
-
一个过于拟合数据的函数,可以近似训练数据集中的几乎所有点的函数被认为是过拟合。
-
一个完全不适合数据的函数,或者换句话说远离训练数据集中实际点的函数被认为是欠拟合。
-
机器学习是在过拟合和欠拟合数据之间取得平衡的过程。这可能是一个不容易的练习,这就是为什么即使构建模型可能是微不足道的,构建一个相当不错的模型却是一个更加困难的挑战。
-
欠拟合是指你的函数“根本不思考”-它具有很高的偏差。
-
过拟合是指你的函数“想得太多”-它具有很高的方差。
-
欠拟合和过拟合的另一个例子将在接下来的例子中给出。
假设我们的任务是确定一堆水果是橙子还是苹果,并已经知道它们在水果篮(左侧或右侧)、大小和重量的位置:
![]() | ![]() |
---|---|
篮子 1(训练数据集) | 篮子 2(测试数据集) |
过拟合的一个例子可能是,根据训练数据集,关于篮子 1,我们可能得出结论说篮子右侧只有橙子,左侧全是苹果。
欠拟合的一个例子可能是我得出结论说篮子里只有橙子。
模型 1:在第一种情况下-过拟合-我实际上是在记忆位置。
模型 2:在第二种情况下 - 对于欠拟合 - 我根本记不清任何东西。
现在,给定第二个篮子 - 位置为苹果和橙子互换的测试数据集 - 如果我使用模型 1,我会错误地得出右手边的所有水果都是橙子,左手边的都是苹果的结论(因为我记住了训练数据)。
如果我使用模型 2,我会再次错误地得出所有水果都是橙子的结论。
然而,有办法管理欠拟合和过拟合之间的平衡 - 或者换句话说,高偏差和高方差之间的平衡。
用于偏差-方差权衡的常用方法之一称为正则化。这指的是对模型进行惩罚(例如,回归中的模型系数),以产生一个能够在一系列数据点上很好泛化的输出。
下一页的表格说明了偏差和方差的一些关键概念,并说明了当模型存在高偏差或高方差时的补救措施选项:
在建模过程中,高偏差通常表现为训练集误差和测试集误差保持一致地高。对于高方差(过拟合),训练集误差迅速减少,但测试集误差保持不变。
梯度下降和 VC 维度理论
梯度下降和 VC 维度是机器学习中的两个基本理论。一般来说,梯度下降提供了一种结构化方法来找到函数的最优系数。函数的假设空间可能很大,通过梯度下降,算法试图找到成本函数(例如,误差的平方和)最低的最小值。
VC 维度提供了系统中可以分类的最大点数的上限。它本质上是函数丰富性的度量,并以结构化方式提供了对假设限制的评估。函数或假设可以准确分类的点数称为假设的 VC 维度。例如,线性边界可以准确分类 2 或 3 个点,但不能是 4 个。因此,这个二维空间的 VC 维度将是 3。
VC 维度,就像计算学习理论中的许多其他主题一样,既复杂又有趣。这是一个较少人知晓(和讨论)的话题,但它试图回答关于学习限制的问题,因此具有深远的影响。
流行的机器学习算法
有各种不同类别的机器学习算法。因此,由于算法可以同时属于多个“类别”或类别,概念上很难明确说明算法专属于单一类别。在本节中,我们将简要讨论一些最常用和知名的算法。
这些包括:
-
回归模型
-
关联规则
-
决策树
-
随机森林
-
Boosting 算法
-
支持向量机
-
K 均值
-
神经网络
请注意,在这些示例中,我们展示了使用整个数据集的 R 函数的基本用法。在实践中,我们会将数据分成训练集和测试集,一旦建立了令人满意的模型,就会将其应用于测试数据集以评估模型的性能。
回归模型
回归模型从统计学中常用的线性、逻辑和多元回归算法到 Ridge 和 Lasso 回归,后者对系数进行惩罚以提高模型性能。
在我们之前的例子中,我们看到了在创建趋势线时应用线性回归的情况。多元线性回归指的是创建模型的过程需要多个自变量。
例如:
总广告成本 = x*印刷广告,将是一个简单的线性回归;而
总广告成本 = X + 印刷广告 + 广播广告 + 电视广告,由于存在多个独立变量(印刷、广播和电视),将是多元线性回归。
逻辑回归是另一种常用的统计回归建模技术,用于预测离散分类值的结果,主要用于结果变量是二分的情况(例如,0 或 1,是或否等)。然而,也可以有超过 2 个离散的结果(例如,州 NY、NJ、CT),这种类型的逻辑回归称为多项式逻辑回归。
岭回归和 Lasso 回归在线性回归的其他方面之外还包括一个正则化项(λ)。正则化项,岭回归,会减少β系数(因此“惩罚”系数)。在 Lasso 中,正则化项倾向于将一些系数减少到 0,从而消除变量对最终模型的影响:
# Load mlbench and create a regression model of glucose (outcome/dependent variable) with pressure, triceps and insulin as the independent variables.
> library("mlbench")
>lm_model<- lm(glucose ~ pressure + triceps + insulin, data=PimaIndiansDiabetes[1:100,])
> plot(lm_model)
关联规则
关联规则挖掘,或apriori,试图找到数据集中变量之间的关系。关联规则经常用于各种实际的现实用例。给定一组变量,apriori 可以指示交易数据集中固有的模式。我们的一个教程将基于实现用于 apriori 的 R Shiny 应用程序,因此在本节中将更加重视这一点。
例如,假设一个超市连锁店正在决定货架上商品的摆放顺序。针对包含销售交易的数据库运行的 apriori 算法将识别出最常一起购买的商品。这使得超市能够确定哪些商品,当放置在彼此紧邻的战略位置时,可以产生最多的销售额。这通常也被称为市场篮子分析。
反映这一点的一个简单例子可能是:
# The LHS (left-hand side) leads to the RHS (right-hand side) in the relationships shown below.
# For instance, {Milk, Bread} --> {Butter} indicates that someone purchasing milk and bread is also likely to purchase butter.
{Milk, Bread} --> {Butter}
{Butter, Egg} --> {Baking Tray}
{Baking Tray, Butter} --> {Sugar}
...
在所有这些情况下,左侧购买某物导致了表达式右侧提到的物品的购买。
还可以从不一定包含交易的数据库中导出关联规则,而是使用滑动窗口沿着时间属性浏览事件,比如 WINEPI 算法。
Apriori 中有 3 个主要的度量。为了说明它们,让我们使用一个包含 4 个独立交易中购买的商品的样本数据集:
交易 | 商品 1 | 商品 2 | 商品 3 |
---|---|---|---|
1 | 牛奶 | 面包 | 黄油 |
2 | 牛奶 | 鸡蛋 | 黄油 |
3 | 面包 | 鸡蛋 | 奶酪 |
4 | 黄油 | 面包 | 鸡蛋 |
置信度
置信度指的是当左侧有效时,apriori 表达式的右侧有多少次有效。例如,给定一个表达式:
{Milk} à {Bread}
我们想知道牛奶也购买时面包经常购买吗。
在这种情况下:
-
交易 1:牛奶和面包都存在
-
交易 2:有牛奶,但没有面包
-
交易 3 和 4:牛奶不存在
因此,根据我们所看到的,有 2 个交易中有牛奶,其中有 1 个交易中有面包。因此,规则{牛奶} à {面包}的置信度为 1/2 = 50%
再举个例子:
{Bread} à {Butter}
我们想知道,购买面包时,黄油也经常购买吗?:
-
交易 1:面包和黄油都存在
-
交易 2:没有面包(黄油是存在的,但我们的参考点是面包,因此这不算)
-
交易 3:有面包但没有黄油
-
交易 4:面包和黄油都存在
因此,在这种情况下,我们有 3 个交易中有面包,3 个交易中有面包和黄油。因此,在这种情况下,规则{面包} à {黄油}的“置信度”是2/3 = 66.7。
支持
支持是指规则满足的次数与数据集中的总交易次数的比率。
例如:
{牛奶} --> {面包},在 4 次交易中发生了 1 次(在交易 1)。因此,这条规则的支持率为¼ = 0.25(或 25%)。
{面包} --> {黄油},在 4 次交易中发生了 2 次(在交易 1 和 4)。因此,这条规则的支持率为½ = 0.50(或 50%)。
提升
提升可以说是 3 个度量中最重要的一个;它衡量了规则相对于表达式的两侧的支持度;换句话说,它衡量了规则相对于 LHS 和 RHS 的随机发生的强度。它正式定义为:
*提升=支持(规则)/(支持(LHS)支持(RHS))
低提升值(例如,小于或等于 1)表示 LHS 和 RHS 的发生是相互独立的,而较高的提升度量表示共同发生是显著的。
在我们之前的例子中,
{面包} --> {黄油}的提升为:
支持({面包} --> {黄油})
支持{面包} * 支持{黄油}
= 0.50/((3/4)*(3/4))= 0.50/(0.75 * 0.75)= 0.89。
这表明尽管规则的置信度很高,但规则本身相对于可能高于 1 的其他规则并不重要。
一个提升高于 1 的规则示例是:
{项目 1:面包} --> {项目 3:奶酪}
这有一个提升:
支持{项目 1:面包 --> 项目 3:奶酪}/(支持{项目 1:奶酪} * 支持{项目 3:奶酪})
=(1/4)/((1/4)*(1/4)= 4。
决策树
决策树是一种预测建模技术,它生成推断出某种结果的可能性的规则,这些规则是基于先前结果的可能性推导出来的。一般来说,决策树通常类似于流程图,具有一系列节点和叶子,表示父子关系。不链接到其他节点的节点称为叶子。
决策树属于一类算法,通常被称为CART(分类和回归树)。如果感兴趣的结果是一个分类变量,它属于分类练习,而如果结果是一个数字,它被称为回归树。
一个例子将有助于使这个概念更清晰。看一下图表:
图表显示了一个假设的场景:如果学校关闭/不关闭。蓝色的矩形框代表节点。第一个矩形(学校关闭)代表根节点,而内部矩形代表内部节点。带有倾斜边缘的矩形框(绿色和斜体字母)代表“叶子”(或终端节点)。
决策树易于理解,是少数不是“黑匣子”的算法之一。用于创建神经网络的算法通常被认为是黑匣子,因为由于模型的复杂性,很难(甚至不可能)直观地确定最终结果达成的确切路径。
在 R 中,有各种创建决策树的工具。在 R 中创建它们的常用库是rpart
。我们将重新访问我们的PimaIndiansDiabetes
数据集,看看如何使用该包创建决策树。
我们想创建一个模型来确定葡萄糖、胰岛素、(体重)质量和年龄与糖尿病的关系。请注意,在数据集中,糖尿病是一个具有是/否响应的分类变量。
为了可视化决策树,我们将使用rpart.plot
包。相同的代码如下所示:
install.packages("rpart")
install.packages("rpart.plot")
library(rpart)
library(rpart.plot)
rpart_model<- rpart (diabetes ~ glucose + insulin + mass + age, data = PimaIndiansDiabetes)
>rpart_model
n= 768
node), split, n, loss, yval, (yprob)
* denotes terminal node
1) root 768 268 neg (0.6510417 0.3489583)
2) glucose< 127.5 485 94neg (0.8061856 0.1938144) *
3) glucose>=127.5 283 109 pos (0.3851590 0.6148410)
6) mass< 29.95 76 24neg (0.6842105 0.3157895)
12) glucose< 145.5 41 6 neg (0.8536585 0.1463415) *
13) glucose>=145.5 35 17pos (0.4857143 0.5142857)
26) insulin< 14.5 21 8 neg (0.6190476 0.3809524) *
27) insulin>=14.5 14 4 pos (0.2857143 0.7142857) *
7) mass>=29.95 207 57pos (0.2753623 0.7246377)
14) glucose< 157.5 115 45pos (0.3913043 0.6086957)
28) age< 30.5 50 23neg (0.5400000 0.4600000)
56) insulin>=199 14 3 neg (0.7857143 0.2142857) *
57) insulin< 199 36 16pos (0.4444444 0.5555556)
114) age>=27.5 10 3 neg (0.7000000 0.3000000) *
115) age< 27.5 26 9 pos (0.3461538 0.6538462) *
29) age>=30.5 65 18pos (0.2769231 0.7230769) *
15) glucose>=157.5 92 12pos (0.1304348 0.8695652) *
>rpart.plot(rpart_model, extra=102, nn=TRUE)
# The plot shown below illustrates the decision tree that the model, rpart_model represents.
从顶部开始阅读,图表显示数据集中有 500 个糖尿病=neg
的情况(共 768 条记录)。
> sum(PimaIndiansDiabetes$diabetes=="neg")
[1] 500
在数据集中总共 768 条记录中,血糖<128 的记录有 485 条被标记为负面。其中,模型正确预测了 391 个案例为负面(节点编号 2,从底部向左的第一个节点)。
对于血糖读数>128 的记录,有 283 条记录标记为阳性(节点编号 3,即最顶部/根节点的下方节点)。模型正确分类了这些案例中的 174 个。
另一个更近期的提供直观决策树和全面视觉信息的包是FFTrees(Fast and Frugal Decision Trees)。以下示例仅供信息目的:
install.packages("FFTrees")
library(caret)
library(mlbench)
library(FFTrees)
set.seed(123)
data("PimaIndiansDiabetes")
diab<- PimaIndiansDiabetes
diab$diabetes<- 1 * (diab$diabetes=="pos")
train_ind<- createDataPartition(diab$diabetes,p=0.8,list=FALSE,times=1)
training_diab<- diab[train_ind,]
test_diab<- diab[-train_ind,]
diabetes.fft<- FFTrees(diabetes ~.,data = training_diab,data.test = test_diab)
plot(diabetes.fft)
# The plot below illustrates the decision tree representing diabetes.fft using the FFTrees package.
决策树通过递归地分割数据,直到达到停止条件,比如达到一定深度或案例数量低于指定值。每次分割都是基于将导致“更纯净子集”的变量进行的。
原则上,我们可以从给定的变量集合中生成无数棵树,这使得它成为一个特别困难和棘手的问题。存在许多算法可以提供一种有效的方法来分裂和创建决策树。其中一种方法是 Hunt’s Algorithm。
有关该算法的更多详细信息可以在以下链接找到:www-users.cs.umn.edu/~kumar/dmbook/ch4.pdf
。
随机森林扩展
随机森林是我们刚讨论的决策树模型的扩展。在实践中,决策树易于理解、易于解释、使用现有算法快速创建,并且直观。然而,决策树对数据的细微变化敏感,只允许沿着一个轴进行分割(线性分割),并且可能导致过拟合。为了减轻决策树的一些缺点,同时仍然获得其优雅之处,诸如随机森林的算法会创建多个决策树,并对随机特征进行抽样以建立一个聚合模型。
随机森林基于自助聚合或bagging的原则。Bootstrap 是一个统计术语,表示带有替换的随机抽样。对给定的记录进行自助采样意味着随机抽取一定数量的记录,并可能在样本中多次包含相同的记录。然后,用户会在样本上测量他们感兴趣的指标,然后重复这个过程。通过这种方式,从随机样本中多次计算的指标值的分布预计将代表总体的分布,以及整个数据集。
Bagging 的一个例子是一组 3 个数字,比如(1,2,3,4):
(1,2,3),(1,1,3),(1,3,3),(2,2,1),等等。
Bootstrap Aggregating,或bagging,意味着利用多个自助采样进行投票,同时在每个个体样本(n 条记录的集合)上构建模型,最后对结果进行聚合。
随机森林还实现了简单 bagging 之外的另一层操作。它还会在每次分裂时随机选择要包括在模型构建过程中的变量。例如,如果我们使用PimaIndiansDiabetes
数据集创建一个随机森林模型,其中包括变量 pregnant, glucose, pressure, triceps, insulin, mass, pedigree, age, 和 diabetes,在每个自助采样(n 条记录的抽样)中,我们会选择一个随机特征子集来构建模型–例如,glucose, pressure, 和 insulin; insulin, age, 和 pedigree; triceps, mass, 和 insulin; 等等。
在 R 中,用于 RandomForest 的常用包被称为 RandomForest。我们可以通过该包直接使用,也可以通过 caret 使用。两种方法如下所示:
- 使用 Random Forest 包:
> rf_model1 <- randomForest(diabetes ~ ., data=PimaIndiansDiabetes) > rf_model1 Call: randomForest(formula = diabetes ~ ., data = PimaIndiansDiabetes)
Type of random forest: classification Number of trees: 500 No. of variables tried at each split: 2 OOB estimate of error rate: 23.44% Confusion matrix: negposclass.error neg430 70 0.1400000 pos 110 158 0.4104478
- 使用 caret 的
method="rf"
函数进行随机森林:
> library(caret)
> library(doMC)
# THE NEXT STEP IS VERY CRITICAL - YOU DO 'NOT' NEED TO USE MULTICORE
# NOTE THAT THIS WILL USE ALL THE CORES ON THE MACHINE THAT YOU ARE
# USING TO RUN THE EXERCISE
# REMOVE THE # MARK FROM THE FRONT OF registerDoMC BEFORE RUNNING
# THE COMMAND
># registerDoMC(cores = 8) # CHANGE NUMBER OF CORES TO MATCH THE NUMBER OF CORES ON YOUR MACHINE
>rf_model<- train(diabetes ~ ., data=PimaIndiansDiabetes, method="rf")
>rf_model
Random Forest
768 samples
8 predictor
2 classes: 'neg', 'pos'
No pre-processing
Resampling: Bootstrapped (25 reps)
Summary of sample sizes: 768, 768, 768, 768, 768, 768, ...
Resampling results across tuning parameters:
mtry Accuracy Kappa
2 0.7555341 0.4451835
5 0.7556464 0.4523084
8 0.7500721 0.4404318
Accuracy was used to select the optimal model using the largest value.
The final value used for the model was mtry = 5\.
>getTrainPerf(rf_model)
TrainAccuracyTrainKappa method
1 0.7583831 0.4524728rf
还可以在原始随机森林模型的每棵树中看到分裂和其他相关信息(未使用 caret)。这可以使用getTree
函数来完成:
>getTree(rf_model1,1,labelVar = TRUE)
left daughter right daughter split var split point status prediction
1 2 3 mass 27.8500 1 <NA>
2 4 5 age 28.5000 1 <NA>
3 6 7 glucose 155.0000 1 <NA>
4 8 9 age 27.5000 1 <NA>
5 10 11 mass 9.6500 1 <NA>
6 12 13 pregnant 7.5000 1 <NA>
7 14 15 insulin 80.0000 1 <NA>
8 0 0 <NA> 0.0000 -1 neg
9 16 17 pressure 68.0000 1 <NA>
10 0 0 <NA> 0.0000 -1 pos
11 18 19 insulin 131.0000 1 <NA>
12 20 21 insulin 87.5000 1 <NA>
[...]
提升算法
提升是一种使用权重和一组弱学习器(如决策树)来提高模型性能的技术。提升根据模型误分类和未来学习器(在提升机器学习过程中创建)关注误分类示例来为数据分配权重。正确分类的示例将被重新分配新的权重,通常低于未正确分类的示例。权重可以基于成本函数,例如使用数据子集的多数投票。
简单而非技术性地说,提升使用一系列弱学习器,每个学习器都从先前学习器的错误中“学习”。
与装袋相比,提升通常更受欢迎,因为它根据模型性能分配权重,而不是像装袋那样对所有数据点分配相等的权重。这在概念上类似于加权平均与没有加权标准的平均函数之间的区别。
R 中有几个用于提升算法的软件包,其中一些常用的如下:
-
Adaboost
-
GBM(随机梯度提升)
-
XGBoost
其中,XGBoost 是一个广泛流行的机器学习软件包,在竞争激烈的机器学习平台(如 Kaggle)上被非常成功地使用。XGBoost 有一种非常优雅和计算效率高的方式来创建集成模型。由于它既准确又极快,用户经常用 XGBoost 来处理计算密集型的机器学习挑战。您可以在www.kaggle.com
了解更多关于 Kaggle 的信息。
# Creating an XGBoost model in R
library(caret)
library(xgboost)
set.seed(123)
train_ind<- sample(nrow(PimaIndiansDiabetes),as.integer(nrow(PimaIndiansDiabetes)*.80))
training_diab<- PimaIndiansDiabetes[train_ind,]
test_diab<- PimaIndiansDiabetes[-train_ind,]
diab_train<- sparse.model.matrix(~.-1, data=training_diab[,-ncol(training_diab)])
diab_train_dmatrix<- xgb.DMatrix(data = diab_train, label=training_diab$diabetes=="pos")
diab_test<- sparse.model.matrix(~.-1, data=test_diab[,-ncol(test_diab)])
diab_test_dmatrix<- xgb.DMatrix(data = diab_test, label=test_diab$diabetes=="pos")
param_diab<- list(objective = "binary:logistic",
eval_metric = "error",
booster = "gbtree",
max_depth = 5,
eta = 0.1)
xgb_model<- xgb.train(data = diab_train_dmatrix,
param_diab, nrounds = 1000,
watchlist = list(train = diab_train_dmatrix, test = diab_test_dmatrix),
print_every_n = 10)
predicted <- predict(xgb_model, diab_test_dmatrix)
predicted <- predicted > 0.5
actual <- test_diab$diabetes == "pos"
confusionMatrix(actual,predicted)
# RESULT
Confusion Matrix and Statistics
Reference
Prediction FALSE TRUE
FALSE 80 17
TRUE 21 36
Accuracy : 0.7532
95% CI : (0.6774, 0.8191)
No Information Rate : 0.6558
P-Value [Acc> NIR] : 0.005956
Kappa : 0.463
Mcnemar's Test P-Value : 0.626496
Sensitivity : 0.7921
Specificity : 0.6792
PosPredValue : 0.8247
NegPredValue : 0.6316
Prevalence : 0.6558
Detection Rate : 0.5195
Detection Prevalence : 0.6299
Balanced Accuracy : 0.7357
'Positive' Class : FALSE
支持向量机
支持向量机,通常称为SVMs,是另一类用于使用称为超平面的概念将数据分类为一类或另一类的机器学习算法。超平面用于在点之间划定线性边界。
例如,在 x-y 轴上给定一组黑白点,我们可以找到多条分隔它们的线。在这种情况下,线代表了划分每个点所属类别的函数。在下图中,线 H1 和 H2 都准确地分隔了点。在这种情况下,我们如何确定 H1 和 H2 中哪一个是最佳线呢?:
直观地说,最靠近点的线 - 例如,垂直线 H1 - 可能不是分隔点的最佳线。由于该线离点太近,因此对给定数据集上的点太具体,如果新点稍微偏离线的右侧或左侧,可能会被错误分类。换句话说,该线对数据的小变化过于敏感(这可能是由于随机/确定性噪声,如数据中的缺陷)。
另一方面,线 H2 成功地分隔了数据,同时保持了离线最近的点的最大可能距离。数据中的轻微缺陷不太可能影响点的分类,就像线 H1 可能做的那样。这本质上描述了下图中所示的最大间隔分离原则。
靠近线的点,也称为超平面,被称为“支持向量”(因此得名)。在图像中,位于虚线上的点因此是支持向量。
然而,在现实世界中,并非所有点都可能是“线性可分”的。支持向量机利用了一种称为“核技巧”的概念。实质上,可能不是线性可分的点可以被投影或映射到更高维度的表面上。例如,给定一个在 2D x-y 空间上的一组点,如果它们不是线性可分的,那么可能可以将它们投影到 3 维空间上,如下图所示。红色的点在 2D 线上是不可分的,但是当映射到 3 维表面时,它们可以被如下图所示的超平面分开:
R 中有几个包可以让用户利用 SVM,比如kernlab
、e1071
、klaR
等。在这里,我们展示了来自e1071
包的 SVM 的使用,如下所示:
library(mlbench)
library(caret)
library(e1071)
set.seed(123)
data("PimaIndiansDiabetes")
diab<- PimaIndiansDiabetes
train_ind<- createDataPartition(diab$diabetes,p=0.8,list=FALSE,times=1)
training_diab<- diab[train_ind,]
test_diab<- diab[-train_ind,]
svm_model<- svm(diabetes ~ ., data=training_diab)
plot(svm_model,training_diab, glucose ~ mass)
# The plot below illustrates the areas that are classified 'positive' and 'negative'
# Creating and evaluating the Confusion Matrix for the SVM model
svm_predicted<- predict(svm_model,test_diab[,-ncol(test_diab)])
confusionMatrix(svm_predicted,test_diab$diabetes)
Confusion Matrix and Statistics
Reference
Prediction negpos
neg 93 26
pos7 27
Accuracy : 0.7843
95% CI : (0.7106, 0.8466)
No Information Rate : 0.6536
P-Value [Acc> NIR] : 0.0003018
Kappa : 0.4799
Mcnemar's Test P-Value : 0.0017280
Sensitivity : 0.9300
Specificity : 0.5094
PosPredValue : 0.7815
NegPredValue : 0.7941
Prevalence : 0.6536
Detection Rate : 0.6078
Detection Prevalence : 0.7778
Balanced Accuracy : 0.7197
'Positive' Class :neg
K-Means 机器学习技术
K-Means 是最流行的无监督机器学习技术之一,用于创建聚类,从而对数据进行分类。
一个直观的例子可以如下提出:
假设一所大学开设了一门新的美国历史和亚洲历史课程。该大学保持 15:1 的学生-教师比例,因此每 15 名学生配备 1 名教师。它进行了一项调查,其中包含每位学生对学习美国历史或亚洲历史的偏好分配的 10 分数值分数。
我们可以使用 R 中内置的 K-Means 算法创建 2 个簇,可能通过每个簇中的点的数量来估计可能报名每门课程的学生人数。相同的代码如下所示:
library(data.table)
library(ggplot2)
library()
historyData<- fread("~/Desktop/history.csv")
ggplot(historyData,aes(american_history,asian_history)) + geom_point() + geom_jitter()
historyCluster<- kmeans(historyData,2) # Create 2 clusters
historyData[,cluster:=as.factor(historyCluster$cluster)]
ggplot(historyData, aes(american_history,asian_history,color=cluster)) + geom_point() + geom_jitter()
# The image below shows the output of the ggplot command. Note that the effect of geom_jitter can be seen in the image below (the points are nudged so that overlapping points can be easily visible)
以下图像可以直观地估计每门课程可能报名的学生人数(从而确定可能需要多少老师):
K-Means 算法有几种变体,但标准和最常用的是 Lloyd 算法。算法步骤如下:
给定一组 n 个点(比如在 x-y 轴上),为了找到 k 个簇:
-
从数据集中随机选择 k 个点,代表 k 个簇的中点(比如初始质心)。
-
从所选的 k 个点到其他点的距离(代表 k 个簇)被测量,并分配给距离最近的簇。
-
簇中心被重新计算为簇中点的平均值。
-
再次计算质心与所有其他点之间的距离,如步骤 2 中所示,并计算新的质心,如步骤 3 中所示。以此类推,重复步骤 2 和 3,直到没有新数据被重新分配。
存在各种用于聚类的距离和相似度度量,如欧氏距离(直线距离),余弦相似度(向量之间的夹角的余弦),汉明距离(通常用于分类变量),马哈拉诺比斯距离(以 P.C.马哈拉诺比斯命名;这测量了一个点与分布的均值之间的距离),等等。
尽管不能总是明确地确定最佳的簇数,但有各种方法试图找到一个估计。一般来说,可以通过簇内点之间的接近程度(簇内方差,如平方和-WSS)和簇之间的距离来衡量簇(因此簇之间的较大距离会使簇更容易区分)。用于确定最佳数量的一种方法称为肘部法。以下图表说明了这个概念:
图表显示了 WSS(我们试图最小化的簇内平方和)与簇的数量之间的关系。显然,将簇的数量从 1 增加到 2 会大幅降低 WSS 值。当增加更多的簇时,WSS 的值会迅速下降,直到第 4 或第 5 个簇,之后再增加簇不会显著改善 WSS。通过视觉评估,机器学习从业者可以得出结论,可以创建的理想簇的数量在 3-5 之间,根据图像。
请注意,低 WSS 分数不足以确定最佳簇的数量。必须通过检查指标的改善来完成。当每个点成为独立簇时,WSS 最终会减少到 0。
与神经网络相关的算法
与神经网络相关的算法已经存在了很多十年。第一个计算模型是由沃伦·麦卡洛克和沃尔特·皮茨于 1943 年在《数学生物物理学公报》上描述的。
您可以在pdfs.semanticscholar.org/5272/8a99829792c3272043842455f3a110e841b1.pdf
和en.wikipedia.org/wiki/Artificial_neural_network
了解更多相关概念。
物理世界中的各种人造物体,如飞机,都从自然中汲取了灵感。神经网络本质上是对神经元之间数据交换现象的一种表征,这种现象发生在人类神经系统中的轴突和树突(也称为树突)之间。就像数据在一个神经元和多个其他神经元之间传递以做出复杂的决策一样,人工神经网络以类似的方式创建了一个接收其他神经元输入的神经元网络。
在高层次上,人工神经网络由 4 个主要组件组成:
-
输入层
-
隐藏层
-
输出层
-
节点和权重
这在下图中表示出来:
图中的每个节点根据前一层的输入产生输出。输出是使用激活函数产生的。有各种类型的激活函数,产生的输出取决于所使用的函数类型。例如二进制步进(0 或 1)、双曲正切(-1 到+1 之间)、Sigmoid 等。
下图说明了这个概念:
值 x1 和 x2 是输入,w1 和 w2 代表权重,节点代表对输入和它们的权重进行评估并由激活函数产生特定输出的点。因此,输出 f 可以表示为:
这里,f 代表激活函数,b 代表偏置项。偏置项独立于权重和输入值,并允许用户移动输出以实现更好的模型性能。
具有多个隐藏层(通常为 2 个或更多)的神经网络计算量很大,在最近的日子里,具有多个隐藏层的神经网络,也被称为深度神经网络或更一般地称为深度学习,已经变得非常流行。
行业中的许多发展都是由机器学习和人工智能推动的,这些发展直接是多层神经网络的实现结果。
在 R 中,nnet
包提供了一个可直接使用的神经网络接口。尽管在实践中,神经网络通常需要复杂的硬件、GPU 卡等,但为了说明的目的,我们已经利用nnet
包在PimaIndiansDiabetes
数据集上运行了早期的分类练习。在这个例子中,我们将利用 caret 来执行nnet
模型:
library(mlbench)
library(caret)
set.seed(123)
data("PimaIndiansDiabetes")
diab<- PimaIndiansDiabetes
train_ind<- createDataPartition(diab$diabetes,p=0.8,list=FALSE,times=1)
training_diab<- diab[train_ind,]
test_diab<- diab[-train_ind,]
nnet_grid<- expand.grid(.decay = c(0.5,0.1), .size = c(3,5,7))
nnet_model<- train(diabetes ~ ., data = training_diab, method = "nnet", metric = "Accuracy", maxit = 500, tuneGrid = nnet_grid)
# Generating predictions using the neural network model
nnet_predicted <- predict(nnet_model, test_diab)
> plot (nnet_model)
# Confusion Matrix for the Neural Network model
confusionMatrix(nnet_predicted,test_diab$diabetes)
Confusion Matrix and Statistics
Reference
Prediction negpos
neg 86 22
pos 14 31
Accuracy : 0.7647
95% CI : (0.6894, 0.8294)
No Information Rate : 0.6536
P-Value [Acc> NIR] : 0.001988
Kappa : 0.4613
Mcnemar's Test P-Value : 0.243345
Sensitivity : 0.8600
Specificity : 0.5849
PosPredValue : 0.7963
NegPredValue : 0.6889
Prevalence : 0.6536
Detection Rate : 0.5621
Detection Prevalence : 0.7059
Balanced Accuracy : 0.7225
'Positive' Class :neg
教程 - 使用 CMS 数据进行关联规则挖掘
本教程将实现一个用于访问在 R 中使用 Apriori 包创建的规则的接口。
我们将从 CMS OpenPayments 网站下载数据。该网站提供有关公司向医生和医院支付的数据:
该网站提供了多种下载数据的方式。用户可以选择感兴趣的数据集并手动下载。在我们的情况下,我们将使用其中一种面向所有用户的基于 Web 的 API 来下载数据。
下载数据
数据集可以通过 Unix 终端(在虚拟机中)下载,也可以直接从浏览器访问该网站进行下载。如果您在虚拟机中下载数据集,请在终端窗口中运行以下命令:
time wget -O cms2016_2.csv 'https://openpaymentsdata.cms.gov/resource/vq63-hu5i.csv?$query=select Physician_First_Name as firstName,Physician_Last_Name as lastName,Recipient_City as city,Recipient_State as state,Submitting_Applicable_Manufacturer_or_Applicable_GPO_Name as company,Total_Amount_of_Payment_USDollars as payment,Nature_of_Payment_or_Transfer_of_Value as paymentNature,Product_Category_or_Therapeutic_Area_1 as category,Name_of_Drug_or_Biological_or_Device_or_Medical_Supply_1 as product where covered_recipient_type like "Covered Recipient Physician" and Recipient_State like "NY" limit 1200000'
或者,如果您从浏览器下载数据,请在浏览器窗口中输入以下 URL 并点击Enter:
如下图所示:
编写 Apriori 的 R 代码
如前所述,Apriori 算法允许用户查找数据集中固有的关系或模式。为此,我们将在 R/RStudio 中使用 arules 包。该代码将读取下载的数据集(在示例中称为cms2016_2.csv
)并运行 Apriori 算法以查找关联规则。
在 RStudio 中创建一个新的 R 文件,并输入以下代码。确保您更改了下载的 csv 文件的位置,以便将其存储在适当的目录中:
library(data.table)
library(arules)
cms<- fread("~/cms2016_2.csv") # CHANGE THIS TO YOUR LOCATION OF THE DATA
cols <- c("category","city","company","firstName","lastName","paymentNature","product")
cms[ ,(cols) := lapply(.SD, toupper), .SDcols = cols]
cms[,payment:=as.numeric(payment)]
quantile_values<- quantile(cms$payment,seq(0,1,.25))
interval_values<- findInterval(cms$payment,quantile_values,rightmost.closed=TRUE)
cms[,quantileVal:=factor(interval_values, labels=c("0-25","25-50","50-75","75-100"))]
rules_cols<- c("category","city","company","paymentNature","product","quantileVal")
cms[ ,(rules_cols) := lapply(.SD, factor), .SDcols = rules_cols]
cms_factor<- cms[,.(category,city,company,paymentNature,product,quantileVal)]
rhsVal<- paste0("quantileVal","=",c("0-25","25-50","50-75","75-100"))
cms_rules<- apriori(cms_factor,parameter=list(supp=0.001,conf=0.25,target="rules",minlen=3))
cms_rules_dt<- data.table(as(cms_rules,"data.frame"))
cms_rules_dt[, c("LHS", "RHS") := tstrsplit(rules, "=>", fixed=TRUE)]
num_cols<- c("support","confidence","lift")
cms_rules_dt[,(num_cols) := lapply(.SD, function(x){round(x,2)}), .SDcols = num_cols]
saveRDS(cms_rules_dt,"cms_rules_dt.rds")
saveRDS(cms_factor,"cms_factor_dt.rds")
Shiny(R 代码)
在 RStudio 中,选择文件|新建文件|Shiny Web 应用程序:
在app.R
中输入以下代码:
# Packt: Big Data Analytics
# Chapter 8 Tutorial
library(shiny)
library(shinydashboard)
library(data.table)
library(DT)
library(shinyjs)
cms_factor_dt<- readRDS("~/r/rulespackt/cms_factor_dt.rds")
cms_rules_dt<- readRDS("~/r/rulespackt/cms_rules_dt.rds")
# Define UI for application that draws a histogram
ui<- dashboardPage (skin="green",
dashboardHeader(title = "Apriori Algorithm"),
dashboardSidebar(
useShinyjs(),
sidebarMenu(
uiOutput("company"),
uiOutput("searchlhs"),
uiOutput("searchrhs"),
uiOutput("support2"),
uiOutput("confidence"),
uiOutput("lift"),
downloadButton('downloadMatchingRules', "Download Rules")
)
),dashboardBody(
tags$head(
tags$link(rel = "stylesheet", type = "text/css", href = "packt2.css"),
tags$link(rel = "stylesheet", type = "text/css", href = "//fonts.googleapis.com/css?family=Fanwood+Text"),
tags$link(rel = "stylesheet", type = "text/css", href = "//fonts.googleapis.com/css?family=Varela"),
tags$link(rel = "stylesheet", type = "text/css", href = "fonts.css"),
tags$style(type="text/css", "select { max-width: 200px; }"),
tags$style(type="text/css", "textarea { max-width: 185px; }"),
tags$style(type="text/css", ".jslider { max-width: 200px; }"),
tags$style(type='text/css', ".well { max-width: 250px; padding: 10px; font-size: 8px}"),
tags$style(type='text/css', ".span4 { max-width: 250px; }")
),
fluidRow(
dataTableOutput("result")
)
),
title = "Aprior Algorithm"
)
# Define server logic required to draw a histogram
server <- function(input, output, session) {
PLACEHOLDERLIST2 <- list(
placeholder = 'Select All',
onInitialize = I('function() { this.setValue(""); }')
)
output$company<- renderUI({
datasetList<- c("Select All",as.character(unique(sort(cms_factor_dt$company))))
selectizeInput("company", "Select Company" ,
datasetList, multiple = FALSE,options = PLACEHOLDERLIST2,selected="Select All")
})
output$searchlhs<- renderUI({
textInput("searchlhs", "Search LHS", placeholder = "Search")
})
output$searchrhs<- renderUI({
textInput("searchrhs", "Search RHS", placeholder = "Search")
})
output$support2 <- renderUI({
sliderInput("support2", label = 'Support',min=0,max=0.04,value=0.01,step=0.005)
})
output$confidence<- renderUI({
sliderInput("confidence", label = 'Confidence',min=0,max=1,value=0.5)
})
output$lift<- renderUI({
sliderInput("lift", label = 'Lift',min=0,max=10,value=0.8)
})
dataInput<- reactive({
print(input$support2)
print(input$company)
print(identical(input$company,""))
temp <- cms_rules_dt[support > input$support2 & confidence >input$confidence& lift >input$lift]
if(!identical(input$searchlhs,"")){
searchTerm<- paste0("*",input$searchlhs,"*")
temp <- temp[LHS %like% searchTerm]
}
if(!identical(input$searchrhs,"")){
searchTerm<- paste0("*",input$searchrhs,"*")
temp <- temp[RHS %like% searchTerm]
}
if(!identical(input$company,"Select All")){
# print("HERE")
temp <- temp[grepl(input$company,rules)]
}
temp[,.(LHS,RHS,support,confidence,lift)]
})
output$downloadMatchingRules<- downloadHandler(
filename = "Rules.csv",
content = function(file) {
write.csv(dataInput(), file, row.names=FALSE)
}
)
output$result<- renderDataTable({
z = dataInput()
if (nrow(z) == 0) {
z <- data.table("LHS" = '', "RHS"='', "Support"='', "Confidence"='', "Lift" = '')
}
setnames(z, c("LHS", "RHS", "Support", "Confidence", "Lift"))
datatable(z,options = list(scrollX = TRUE))
})
} shinyApp(ui = ui, server = server)
以下图像显示了代码被复制并保存在名为app.R
的文件中。
为应用程序使用自定义 CSS 和字体
对于我们的应用程序,我们将使用自定义的 CSS 文件。我们还将使用自定义字体,以使应用程序具有良好的外观和感觉。
您可以从本书的软件存储库中下载自定义的 CSS 文件。
CSS、字体和其他相关文件应存储在名为www
的文件夹中,该文件夹位于您创建 R Shiny 应用程序的目录中:
运行应用程序
如果一切顺利,您现在应该能够通过点击页面顶部的“运行应用程序”选项来运行应用程序,如下图所示:
单击“运行”按钮后,用户将看到一个类似下面所示的弹出窗口。请注意,浏览器中应启用弹出窗口才能正常运行。
该应用程序具有多个控件,例如:
-
搜索 LHS/RHS:在规则的左侧或右侧输入您想要过滤的任何测试。
-
支持:指示数据集中规则的普遍性。
-
置信度:规则中有多少是精确匹配的。
-
提升:定义规则重要性的变量。大于 1 的数字被认为是显著的。
只要它们以与 R 脚本部分中概述的方式类似的方式进行处理,您就可以将此应用于任何其他规则文件。
总结
机器学习从业者常常认为创建模型很容易,但创建一个好模型要困难得多。事实上,不仅创建一个“好”模型很重要,更重要的是知道如何识别“好”模型,这是成功与不那么成功的机器学习努力之间的区别。
在本章中,我们深入了解了机器学习中一些更深层次的理论概念。偏差、方差、正则化和其他常见概念都在需要时用例子解释了。通过附带的 R 代码,我们还学习了一些常见的机器学习算法,如随机森林、支持向量机等。最后,我们通过教程学习了如何针对 CMS OpenPayments 数据创建一个详尽的基于 Web 的关联规则挖掘应用程序。
在下一章中,我们将阅读一些在企业中用于大数据和机器学习的技术。我们还将讨论云计算的优点以及它们如何影响企业软件和硬件堆栈的选择。