Spark2 数据处理和实时分析(三)
原文:
zh.annas-archive.org/md5/16D84784AD68D8BF20A18AC23C62DD82译者:飞龙
第十二章:实施稳健机器学习系统的常见配方
本章我们将涵盖:
-
Spark 的基本统计 API,助你构建自己的算法
-
现实生活中的机器学习应用的 ML 管道
-
使用 Spark 进行数据标准化
-
数据分割以进行训练和测试
-
使用新的 Dataset API 进行常见操作
-
在 Spark 2.0 中从文本文件创建和使用 RDD、DataFrame 与 Dataset
-
Spark ML 中的 LabeledPoint 数据结构
-
在 Spark 2.0+中访问 Spark 集群
-
在 Spark 2.0 之前访问 Spark 集群
-
在 Spark 2.0 中通过 SparkSession 对象访问 SparkContext
-
Spark 2.0 中的新模型导出和 PMML 标记
-
使用 Spark 2.0 进行回归模型评估
-
二元分类模型评估...
引言
在各行各业中,无论是经营一家小企业还是开发和维护关键任务应用程序,都有一系列常见的任务需要在执行功能过程中几乎每个工作流程中包含。即使在构建强大的机器学习系统时也是如此。在 Spark 机器学习中,这些任务包括从数据分割以进行模型开发(训练、测试、验证)到标准化输入特征向量数据,再到通过 Spark API 创建 ML 管道。本章提供了一系列配方,旨在帮助读者思考实施端到端机器学习系统实际需要的内容。
本章试图展示在任何稳健的 Spark 机器学习系统实现中存在的多种常见任务。为了避免在本书中每个配方中重复提及这些常见任务,我们将这些常见任务作为本章中的简短配方提取出来,读者在阅读其他章节时可根据需要加以利用。这些配方既可以独立使用,也可以作为大型系统中的管道子任务。请注意,这些常见配方在后续章节中关于机器学习算法的更大背景下得到强调,同时为了完整性,本章也包含了它们作为独立配方。
Spark 的基本统计 API,助你构建自己的算法
在本配方中,我们涵盖了 Spark 的多变量统计摘要(即Statistics.colStats),如相关性、分层抽样、假设检验、随机数据生成、核密度估计器等,这些可以在处理极大数据集时利用 RDD 的并行性和弹性。
如何操作...
-
在 IntelliJ 或您选择的 IDE 中启动新项目。确保包含必要的 JAR 文件。
-
设置程序将驻留的包位置:
package spark.ml.cookbook.chapter4
- 为 Spark 会话导入必要的包以访问集群,并使用
log4j.Logger减少 Spark 产生的输出量:
import org.apache.spark.mllib.linalg.Vectors
import org.apache.spark.mllib.stat.Statistics
import org.apache.spark.sql.SparkSession
import org.apache.log4j.Logger
import org.apache.log4j.Level
- 将输出级别设置为
ERROR以减少 Spark 的日志输出:
Logger.getLogger("org").setLevel(Level.ERROR)
Logger.getLogger("akka").setLevel(Level.ERROR)
- 通过构建器模式初始化 Spark 会话并指定配置,从而为 Spark 集群提供入口点:
val spark = SparkSession
.builder
.master("local[*]")
.appName("Summary Statistics")
.config("spark.sql.warehouse.dir", ".")
.getOrCreate()
- 让我们检索底层 SparkContext 的 Spark 会话,以便在生成 RDD 时使用:
val sc = spark.sparkContext
- 现在我们使用手工数据创建一个 RDD,以说明摘要统计的使用:
val rdd = sc.parallelize(
Seq(
Vectors.dense(0, 1, 0),
Vectors.dense(1.0, 10.0, 100.0),
Vectors.dense(3.0, 30.0, 300.0),
Vectors.dense(5.0, 50.0, 500.0),
Vectors.dense(7.0, 70.0, 700.0),
Vectors.dense(9.0, 90.0, 900.0),
Vectors.dense(11.0, 110.0, 1100.0)
)
)
- 我们通过调用
colStats()方法并传递 RDD 作为参数来使用 Spark 的统计对象:
val summary = Statistics.colStats(rdd)
colStats() 方法将返回一个 MultivariateStatisticalSummary,其中包含计算出的摘要统计信息:
println("mean:" + summary.mean)
println("variance:" +summary.variance)
println("none zero" + summary.numNonzeros)
println("min:" + summary.min)
println("max:" + summary.max)
println("count:" + summary.count)
mean:[5.142857142857142,51.57142857142857,514.2857142857142]
variance:[16.80952380952381,1663.952380952381,168095.2380952381]
none zero[6.0,7.0,6.0]
min:[0.0,1.0,0.0]
max:[11.0,110.0,1100.0]
count:7
- 我们通过停止 Spark 会话来关闭程序:
spark.stop()
工作原理...
我们创建了一个从密集向量数据生成的 RDD,然后使用统计对象在其上生成摘要统计信息。一旦调用 colStats() 方法返回,我们便获取了诸如均值、方差、最小值、最大值等摘要统计信息。
还有更多...
在大数据集上,统计 API 的高效性怎么强调都不为过。这些 API 将为您提供实现任何统计学习算法的基本元素。基于我们的研究和经验,我们鼓励您在实现自己的算法之前,先阅读源代码,确保 Spark 中没有实现相应功能。
虽然我们在此仅演示了基本的统计摘要,但 Spark 自带了以下功能:
-
相关性:
Statistics.corr(seriesX, seriesY, "相关性类型"):-
皮尔逊(默认)
-
斯皮尔曼
-
-
分层抽样 - RDD API:
-
有替换的 RDD
-
无替换 - 需要额外遍历
-
-
假设检验:
-
向量 -
Statistics.chiSqTest( 向量 ) -
矩阵 -
Statistics.chiSqTest( 密集矩阵 )
-
-
柯尔莫哥洛夫-斯米尔诺夫(KS)等同性检验 - 单侧或双侧:
Statistics.kolmogorovSmirnovTest(RDD, "norm", 0, 1)
-
随机数据生成器 -
normalRDD():-
正态 - 可以指定参数
-
众多选项加上
map()以生成任何分布
-
-
核密度估计器 -
KernelDensity().estimate( data )
统计学中拟合优度概念的快速参考可在en.wikipedia.org/wiki/Goodness_of_fit链接找到。
另请参阅
更多多元统计摘要的文档:
ML 管道,适用于实际机器学习应用
这是涵盖 Spark 2.0 中 ML 管道的两个配方中的第一个。有关 ML 管道的更高级处理,包括 API 调用和参数提取等详细信息,请参阅本书后面的章节。
在本例中,我们尝试构建一个单一管道,该管道能够对文本进行分词,使用 HashingTF(一种老技巧)映射词频,运行回归以拟合模型,并预测新词属于哪个组(例如,新闻过滤、手势分类等)。
如何操作...
-
在 IntelliJ 或您选择的 IDE 中启动一个新项目。确保包含了必要的 JAR 文件。
-
设置程序将驻留的包位置:
package spark.ml.cookbook.chapter4
- 为 Spark 会话导入必要的包以访问集群,并使用
log4j.Logger来减少 Spark 产生的输出量:
import org.apache.spark.ml.Pipelineimport org.apache.spark.ml.classification.LogisticRegressionimport org.apache.spark.ml.feature.{HashingTF, Tokenizer}import org.apache.spark.sql.SparkSessionimport org.apache.log4j.{Level, Logger}
- 将输出级别设置为
ERROR以减少 Spark 的日志输出:
Logger.getLogger("org").setLevel(Level.ERROR)Logger.getLogger("akka" ...
工作原理...
本节中,我们探讨了使用 Spark 构建简单机器学习管道的过程。我们首先创建了一个包含两组文本文档的 DataFrame,随后设置了管道。
首先,我们创建了一个分词器来将文本文档解析为词项,随后创建了 HashingTF 来将这些词项转换为特征。接着,我们创建了一个逻辑回归对象来预测新文本文档属于哪个组。
其次,我们通过传递一个参数数组来构建管道,指定了三个执行阶段。您会注意到,每个后续阶段都使用前一阶段的输出列作为输入,并提供一个指定的结果列。
最后,我们通过在管道对象上调用fit()并定义一组测试数据进行验证来训练模型。接下来,我们使用模型转换测试集,确定测试集中的文本文档属于定义的两个组中的哪一个。
还有更多...
Spark ML 中的管道灵感来源于 Python 中的 scikit-learn,此处为完整性引用:
机器学习管道使得在 Spark 中结合多个用于实现生产任务的算法变得容易。在实际应用中,通常不会只使用单一算法。往往需要多个协作的机器学习算法共同工作以实现复杂的用例。例如,在基于 LDA 的系统(如新闻简报)或人类情感检测中,在核心系统前后都有多个步骤需要实现为一个单一管道,以产生有意义且适用于生产的系统。请参阅以下链接了解一个实际应用案例,该案例需要...
另请参见
更多多元统计摘要的文档:
-
管道文档可在
spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.ml.Pipeline找到 -
当我们加载和保存
.load(),.save()方法时,有用的流水线模型:此处 -
流水线阶段信息可在此处找到。
-
HashingTF,文本分析中将序列映射到其词频的一个不错老技巧,可在此处找到。
使用 Spark 进行数据归一化
在本食谱中,我们展示了在将数据导入 ML 算法之前进行归一化(缩放)。有许多 ML 算法(如支持向量机(SVM))在缩放输入向量上比在原始值上工作得更好。
如何操作...
-
前往 UCI 机器学习库并下载此文件。
-
在 IntelliJ 或您选择的 IDE 中启动一个新项目。确保包含必要的 JAR 文件。
-
设置程序将驻留的包位置:
package spark.ml.cookbook.chapter4
- 导入必要的包,以便 Spark 会话能够访问集群并使用
log4j.Logger减少 Spark 产生的输出量:
import org.apache.spark.sql.SparkSession
import org.apache.spark.ml.linalg.{Vector, Vectors}
import org.apache.spark.ml.feature.MinMaxScaler
- 定义一个方法,将葡萄酒数据解析为元组:
def parseWine(str: String): (Int, Vector) = {
val columns = str.split(",")
(columns(0).toInt, Vectors.dense(columns(1).toFloat, columns(2).toFloat, columns(3).toFloat))
}
- 将输出级别设置为
ERROR以减少 Spark 的日志输出:
Logger.getLogger("org").setLevel(Level.ERROR)
Logger.getLogger("akka").setLevel(Level.ERROR)
- 使用构建器模式初始化 Spark 会话,指定配置,从而为 Spark 集群提供入口点:
val spark = SparkSession
.builder
.master("local[*]")
.appName("My Normalize")
.getOrCreate()
- 导入
spark.implicits,从而仅通过import添加行为:
import spark.implicits._
- 让我们将葡萄酒数据加载到内存中,只取前四列,并将后三列转换为一个新的特征向量:
val data = Spark.read.text("../data/sparkml2/chapter4/wine.data").as[String].map(parseWine)
- 接下来,我们生成一个包含两列的 DataFrame:
val df = data.toDF("id", "feature")
- 现在,我们将打印出 DataFrame 模式并展示 DataFrame 中包含的数据:
df.printSchema()
df.show(false)
- 最后,我们生成缩放模型并将特征转换为介于负一和正一之间的公共范围,展示结果:
val scale = new MinMaxScaler()
.setInputCol("feature")
.setOutputCol("scaled")
.setMax(1)
.setMin(-1)
scale.fit(df).transform(df).select("scaled").show(false)
- 通过停止 Spark 会话来关闭程序:
spark.stop()
工作原理...
在本例中,我们探讨了特征缩放,这是大多数机器学习算法(如分类器)中的关键步骤。我们首先加载葡萄酒数据文件,提取标识符,并使用接下来的三列创建特征向量。
接着,我们创建了一个MinMaxScaler对象,配置了一个最小和最大范围,以便将我们的值进行缩放。通过在缩放器类上调用fit()方法来执行缩放模型,然后使用该模型对 DataFrame 中的值进行缩放。
最后,我们展示了结果的 DataFrame,并注意到特征向量值的范围在负 1 到正 1 之间。
还有更多...
通过检查线性代数入门中的单位向量概念,可以更好地理解归一化和缩放的根源。以下是一些关于单位向量的常见参考链接:
-
你可以在
en.wikipedia.org/wiki/Unit_vector参考单位向量
对于输入敏感的算法,如 SVM,建议在特征的缩放值(例如,范围从 0 到 1)上训练算法,而不是原始向量表示的绝对值。
另请参阅
MinMaxScaler的文档可在spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.ml.feature.MinMaxScaler获取。
我们想强调MinMaxScaler是一个广泛的 API,它扩展了Estimator(来自 ML 管道的概念),正确使用时可以实现编码效率和高精度结果。
分割数据用于训练和测试
在本食谱中,你将学习使用 Spark 的 API 将可用的输入数据分割成不同的数据集,这些数据集可用于训练和验证阶段。通常使用 80/20 的分割比例,但也可以根据你的偏好考虑其他数据分割方式。
如何操作...
-
前往 UCI 机器学习库并下载
archive.ics.uci.edu/ml/machine-learning-databases/00359/NewsAggregatorDataset.zip文件。 -
在 IntelliJ 或你选择的 IDE 中启动一个新项目。确保包含必要的 JAR 文件。
-
设置程序将驻留的包位置:
package spark.ml.cookbook.chapter4
- 为 Spark 会话导入必要的包以访问集群,并导入
log4j.Logger以减少 Spark 产生的输出量:
import org.apache.spark.sql.SparkSessionimport org.apache.log4j.{ Level, Logger}
- 将输出级别设置为
ERROR以减少 Spark 的日志输出:
Logger.getLogger("org").setLevel(Level.ERROR)Logger.getLogger("akka" ...
工作原理...
我们首先加载newsCorpora.csv数据文件,然后通过数据集对象上的randomSplit()方法,我们分割了数据集。
还有更多...
为了验证结果,我们必须设置一个德尔菲技术,其中测试数据对模型来说是完全未知的。详情请参见 Kaggle 竞赛,网址为www.kaggle.com/competitions。
一个健壮的机器学习系统需要三种类型的数据集。
-
训练数据集:用于拟合模型以进行采样。
-
验证数据集:用于估计拟合模型(由训练集训练)的预测误差或增量。
-
测试数据集:一旦选定最终模型,用于评估模型的泛化误差。
另请参见
关于randomSplit()的文档可在spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.api.java.JavaRDD@randomSplit(weights:Array%5BDouble%5D):Array%5Borg.apache.spark.api.java.JavaRDD%5BT%5D%5D找到。
randomSplit()是在 RDD 内部调用的方法。尽管 RDD 方法调用的数量可能令人不知所措,但掌握这个 Spark 概念和 API 是必须的。
API 签名如下:
def randomSplit(weights: Array[Double]): Array[JavaRDD[T]]
使用提供的权重随机分割此 RDD。
使用新 Dataset API 的常见操作
在本菜谱中,我们介绍了 Dataset API,这是 Spark 2.0 及以后版本进行数据整理的前进方式。本章中,我们涵盖了一些处理这些新 API 集合所需的常见、重复操作。此外,我们还展示了由 Spark SQL Catalyst 优化器生成的查询计划。
如何操作...
-
在 IntelliJ 或您选择的 IDE 中启动一个新项目。确保包含必要的 JAR 文件。
-
我们将使用一个名为
cars.json的 JSON 数据文件,该文件是为这个示例创建的:
name,city
Bears,Chicago
Packers,Green Bay
Lions,Detroit
Vikings,Minnesota
- 设置程序将驻留的包位置。
package spark.ml.cookbook.chapter4
- 为 Spark 会话导入必要的包以访问集群,并导入
log4j.Logger以减少 Spark 产生的输出量。
import org.apache.spark.ml.Pipeline
import org.apache.spark.ml.classification.LogisticRegression
import org.apache.spark.ml.feature.{HashingTF, Tokenizer}
import org.apache.spark.sql.SparkSession
import org.apache.log4j.{Level, Logger}
- 定义一个 Scala
case class来建模数据:
case class Team(name: String, city: String)
- 将输出级别设置为
ERROR以减少 Spark 的日志输出。
Logger.getLogger("org").setLevel(Level.ERROR)
Logger.getLogger("akka").setLevel(Level.ERROR)
- 使用构建器模式初始化 Spark 会话并指定配置,从而为 Spark 集群提供入口点。
val spark = SparkSession
.builder
.master("local[*]")
.appName("My Dataset")
.config("spark.sql.warehouse.dir", ".")
.getOrCreate()
- 导入
spark.implicits,从而通过仅一个导入操作添加行为。
import spark.implicits._
- 让我们从一个 Scala 列表创建数据集并打印结果:
val champs = spark.createDataset(List(Team("Broncos", "Denver"), Team("Patriots", "New England")))
champs.show(false)
- 接下来,我们将加载一个 CSV 文件到内存中,并将其转换为类型为
Team的数据集。
val teams = spark.read
.option("Header", "true")
.csv("../data/sparkml2/chapter4/teams.csv")
.as[Team]
teams.show(false)
- 现在我们通过使用
map函数遍历 teams 数据集,生成一个新的城市名称数据集。
val cities = teams.map(t => t.city)
cities.show(false)
- 显示检索城市名称的执行计划:
cities.explain()
== Physical Plan ==
*SerializeFromObject [staticinvoke(class org.apache.spark.unsafe.types.UTF8String, StringType, fromString, input[0, java.lang.String, true], true) AS value#26]
+- *MapElements <function1>, obj#25: java.lang.String
+- *DeserializeToObject newInstance(class Team), obj#24: Team
+- *Scan csv [name#9,city#10] Format: CSV, InputPaths: file:teams.csv, PartitionFilters: [], PushedFilters: [], ReadSchema: struct<name:string,city:string>
- 最后,我们将
teams数据集保存到 JSON 文件中。
teams.write
.mode(SaveMode.Overwrite)
.json("../data/sparkml2/chapter4/teams.json"){"name":"Bears","city":"Chicago"}
{"name":"Packers","city":"Green Bay"}
{"name":"Lions","city":"Detroit"}
{"name":"Vikings","city":"Minnesota"}
- 我们通过停止 Spark 会话来关闭程序。
spark.stop()
工作原理...
首先,我们从 Scala 列表创建了一个数据集,并显示输出以验证数据集的创建是否符合预期。其次,我们将一个 逗号分隔值(CSV)文件加载到内存中,将其转换为类型为 Team 的数据集。第三,我们对数据集执行了 map() 函数,以构建一个团队城市名称列表,并打印出用于生成数据集的执行计划。最后,我们将之前加载的 teams 数据集持久化为 JSON 格式的文件,以备将来使用。
还有更多...
请注意关于数据集的一些有趣点:
-
数据集采用 惰性 评估
-
数据集利用了 Spark SQL Catalyst 优化器
-
数据集利用了 tungsten 的堆外内存管理
-
未来两年内,许多系统仍将停留在 Spark 2.0 之前,因此出于实际考虑,您必须学习并掌握 RDD 和 DataFrame。
参见
Dataset 的文档可在 spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.sql.Dataset 获取。
Spark 2.0 中从文本文件创建和使用 RDD 与 DataFrame 与 Dataset
在本示例中,我们通过一段简短的代码探索了从文本文件创建 RDD、DataFrame 和 Dataset 的细微差别以及它们之间的关系:
Dataset: spark.read.textFile()
RDD: spark.sparkContext.textFile()
DataFrame: spark.read.text()
假设 spark 是会话名称
如何操作...
-
在 IntelliJ 或您选择的 IDE 中启动一个新项目。确保包含了必要的 JAR 文件。
-
设置程序将驻留的包位置:
package spark.ml.cookbook.chapter4
- 为 Spark 会话导入必要的包以访问集群,并导入
log4j.Logger以减少 Spark 产生的输出量:
import org.apache.log4j.{Level, Logger}import org.apache.spark.sql.SparkSession
- 我们还定义了一个
case class来承载所使用的数据:
case class Beatle(id: Long, name: String)
- 将输出级别设置为
ERROR以减少 Spark 的日志输出:
Logger.getLogger("org").setLevel(Level.ERROR)
- 通过构建器模式初始化 Spark 会话并指定配置,从而...
工作原理...
我们使用相同文本文件的类似方法创建了 RDD、DataFrame 和 Dataset 对象,并使用 getClass 方法确认了类型:
Dataset: spark.read.textFile
RDD: spark.sparkContext.textFile
DataFrame: spark.read.text
请注意,它们非常相似,有时会令人困惑。Spark 2.0 已将 DataFrame 转变为 Dataset[Row] 的别名,使其真正成为一个数据集。我们展示了前面的方法,以便用户选择示例来创建自己的数据类型风格。
还有更多...
数据类型的文档可在 spark.apache.org/docs/latest/sql-programming-guide.html 获取。
如果您不确定手头有什么样的数据结构(有时差异并不明显),请使用 getClass 方法进行验证。
Spark 2.0 已将 DataFrame 转变为 Dataset[Row] 的别名。虽然 RDD 和 Dataram 在不久的将来仍然完全可行,但最好学习和使用数据集编写新项目。
参见
RDD 和 Dataset 的文档可在以下网站获取:
-
spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.rdd.RDD -
spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.sql.Dataset
Spark ML 中的 LabeledPoint 数据结构
LabeledPoint是一种自早期以来就存在的数据结构,用于打包特征向量及其标签,以便用于无监督学习算法。我们展示了一个简短的示例,使用 LabeledPoint、Seq数据结构和 DataFrame 来运行二元分类数据的逻辑回归。
如何操作...
-
在 IntelliJ 或您选择的 IDE 中启动一个新项目。确保包含必要的 JAR 文件。
-
设置程序将驻留的包位置:
package spark.ml.cookbook.chapter4
- 导入必要的包以使 SparkContext 能够访问集群:
import org.apache.spark.ml.feature.LabeledPoint
import org.apache.spark.ml.linalg.Vectors
import org.apache.spark.ml.classification.LogisticRegression
import org.apache.spark.sql._
- 创建 Spark 配置和 SparkContext,以便我们能够访问集群:
val spark = SparkSession
.builder
.master("local[*]")
.appName("myLabeledPoint")
.config("spark.sql.warehouse.dir", ".")
.getOrCreate()
- 我们创建 LabeledPoint,使用
SparseVector和DenseVector。在以下代码块中,前四个 LabeledPoint 通过DenseVector创建,最后两个 LabeledPoint 通过SparseVector创建:
val myLabeledPoints = spark.createDataFrame(Seq(
LabeledPoint(1.0, Vectors.dense(0.0, 1.1, 0.1)),
LabeledPoint(0.0, Vectors.dense(2.0, 1.0, -1.0)),
LabeledPoint(0.0, Vectors.dense(2.0, 1.3, 1.0)),
LabeledPoint(1.0, Vectors.dense(0.0, 1.2, -0.5)),
LabeledPoint(0.0, Vectors.sparse(3, Array(0,2), Array(1.0,3.0))),
LabeledPoint(1.0, Vectors.sparse(3, Array(1,2), Array(1.2,-0.4)))
))
DataFrame 对象从前述 LabeledPoint 创建。
-
我们验证原始数据计数和处理数据计数。
-
您可以对创建的 DataFrame 调用
show()函数:
myLabeledPoints.show()
-
您将在控制台看到以下内容:
-
我们基于刚创建的数据结构创建了一个简单的 LogisticRegression 模型:
val lr = new LogisticRegression()
lr.setMaxIter(5)
.setRegParam(0.01)
val model = lr.fit(myLabeledPoints)
println("Model was fit using parameters: " + model.parent.extractParamMap())
在控制台中,将显示以下model参数:
Model was fit using parameters: {
logreg_6aebbb683272-elasticNetParam: 0.0,
logreg_6aebbb683272-featuresCol: features,
logreg_6aebbb683272-fitIntercept: true,
logreg_6aebbb683272-labelCol: label,
logreg_6aebbb683272-maxIter: 5,
logreg_6aebbb683272-predictionCol: prediction,
logreg_6aebbb683272-probabilityCol: probability,
logreg_6aebbb683272-rawPredictionCol: rawPrediction,
logreg_6aebbb683272-regParam: 0.01,
logreg_6aebbb683272-standardization: true,
logreg_6aebbb683272-threshold: 0.5,
logreg_6aebbb683272-tol: 1.0E-6
}
- 随后通过停止 Spark 会话来关闭程序:
spark.stop()
工作原理...
我们使用 LabeledPoint 数据结构来建模特征并驱动逻辑回归模型的训练。首先定义了一组 LabeledPoints,用于创建 DataFrame 以进行进一步处理。接着,我们创建了一个逻辑回归对象,并将 LabeledPoint DataFrame 作为参数传递给它,以便训练我们的模型。Spark ML API 设计得与 LabeledPoint 格式兼容良好,且几乎无需干预。
还有更多内容...
LabeledPoint 是一种常用结构,用于将数据打包为Vector + Label,适用于监督机器学习算法。LabeledPoint 的典型布局如下所示:
Seq(
LabeledPoint (Label, Vector(data, data, data))
......
LabeledPoint (Label, Vector(data, data, data))
)
请注意,不仅密集向量,稀疏向量也可以与 LabeledPoint 配合使用,这在测试和开发期间,如果驱动程序中驻留了大型且稀疏的数据集,将会在效率上产生巨大差异。
另请参阅
-
LabeledPoint API 文档可在此处查阅:
spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.ml.feature.LabeledPoint -
DenseVector API 文档可在
spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.ml.linalg.DenseVector获取 -
SparseVector API 文档可在
spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.ml.linalg.SparseVector获取
获取 Spark 2.0 中 Spark 集群的访问权限
在本示例中,我们演示了如何通过名为 SparkSession 的单点访问来获取 Spark 集群的访问权限。Spark 2.0 将多个上下文(如 SQLContext、HiveContext)抽象为一个入口点 SparkSession,允许您以统一的方式访问所有 Spark 子系统。
操作方法...
-
在 IntelliJ 或您选择的 IDE 中启动一个新项目。确保包含必要的 JAR 文件。
-
设置程序所在包的位置:
package spark.ml.cookbook.chapter4
-
导入必要的包以通过 SparkContext 访问集群。
-
Spark 2.x 中,
SparkSession更常用。
import org.apache.spark.sql.SparkSession
- 创建 Spark 配置和
SparkSession,以便我们能够访问集群:
val spark = SparkSession.builder.master("local[*]") // if use cluster master("spark://master:7077").appName("myAccesSparkCluster20").config("spark.sql.warehouse.dir", ".").getOrCreate()
上述代码利用 master() 函数来设置集群类型...
工作原理...
在本例中,我们展示了如何使用本地和远程选项连接到 Spark 集群。首先,我们创建一个 SparkSession 对象,通过 master() 函数指定集群是本地还是远程,从而授予我们访问 Spark 集群的权限。您还可以通过在启动客户端程序时传递 JVM 参数来指定主节点位置。此外,您可以配置应用程序名称和工作数据目录。接下来,调用 getOrCreate() 方法创建新的 SparkSession 或为您提供现有会话的引用。最后,我们执行一个小型示例程序以验证 SparkSession 对象创建的有效性。
还有更多...
Spark 会话拥有众多可设置和使用的参数及 API,但建议查阅 Spark 文档,因为其中一些方法/参数被标记为实验性或留空 - 对于非实验性状态(我们上次检查时至少有 15 个)。
另一个需要注意的变化是使用 spark.sql.warehouse.dir 来指定表的位置。Spark 2.0 使用 spark.sql.warehouse.dir 设置仓库位置以存储表,而不是 hive.metastore.warehouse.dir。spark.sql.warehouse.dir 的默认值为 System.getProperty("user.dir")。
更多详情请参阅 spark-defaults.conf。
同样值得注意的是以下几点:
- 我们最喜欢的 Spark 中一些有趣且有用的 API...
另请参阅
SparkSession API 文档可在此处获取。
在 Spark 2.0 之前访问 Spark 集群
这是一份Spark 2.0 之前的指南,但对于希望快速比较和对比集群访问方式,以便将 Spark 2.0 之前程序迁移至 Spark 2.0 新范式的开发者来说,将大有裨益。
操作方法...
-
在 IntelliJ 或您选择的 IDE 中新建项目。确保包含必要的 JAR 文件。
-
设置程序所在包的位置:
package spark.ml.cookbook.chapter4
- 为 SparkContext 导入必要包以访问集群:
import org.apache.spark.{SparkConf, SparkContext}
- 创建 Spark 配置及 SparkContext 以便访问集群:
val conf = new SparkConf()
.setAppName("MyAccessSparkClusterPre20")
.setMaster("local[4]") // if cluster setMaster("spark://MasterHostIP:7077")
.set("spark.sql.warehouse.dir", ".")
val sc = new SparkContext(conf)
上述代码利用setMaster()函数设置集群主节点位置。如您所见,我们正运行于本地模式。
若两者同时存在,代码中集群主参数设置将覆盖-D选项值。
以下是三种不同模式下连接集群的示例方法:
- 运行于本地模式:
setMaster("local")
- 运行于集群模式:
setMaster("spark://yourmasterhostIP:port")
- 传递主节点值:
-Dspark.master=local
- 我们使用上述 SparkContext 读取 CSV 文件,并将其解析为 Spark 中的数据,代码如下:
val file = sc.textFile("../data/sparkml2/chapter4/mySampleCSV.csv")
val headerAndData = file.map(line => line.split(",").map(_.trim))
val header = headerAndData.first
val data = headerAndData.filter(_(0) != header(0))
val maps = data.map(splits => header.zip(splits).toMap)
- 我们将示例结果打印至控制台:
val result = maps.take(4)
result.foreach(println)
-
控制台将显示如下内容:
-
随后,我们通过停止 SparkContext 来关闭程序:
sc.stop()
工作原理...
本例展示如何在 Spark 2.0 之前通过本地和远程模式连接至 Spark 集群。首先,我们创建一个SparkConf对象并配置所有必需参数。我们将指定主节点位置、应用名称及工作数据目录。接着,我们创建 SparkContext,将SparkConf作为参数传递以访问 Spark 集群。此外,启动客户端程序时,您可通过传递 JVM 参数指定主节点位置。最后,我们执行一个小型示例程序以验证 SparkContext 运行正常。
更多内容...
Spark 2.0 之前,访问 Spark 集群通过SparkContext实现。
对子系统(如 SQL)的访问需通过特定名称上下文(例如,SQLContext**)。
Spark 2.0 通过创建单一统一访问点(即SparkSession)改变了我们访问集群的方式。
相关内容
SparkContext 文档可在此查阅。
Spark 2.0 中通过 SparkSession 对象访问 SparkContext
在本教程中,我们展示了如何在 Spark 2.0 中通过 SparkSession 对象获取 SparkContext。本教程将演示 RDD 到 Dataset 的创建、使用以及来回转换。这样做的重要性在于,尽管我们倾向于使用 Dataset,但我们仍需能够使用和增强主要利用 RDD 的遗留(预 Spark 2.0)代码。
如何操作...
-
在 IntelliJ 或您选择的 IDE 中启动一个新项目。确保包含必要的 JAR 文件。
-
设置程序所在包的位置:
package spark.ml.cookbook.chapter4
- 为 Spark 会话导入必要的包以访问集群,并使用
log4j.Logger减少 Spark 产生的输出量:
import org.apache.log4j.{Level, Logger}import org.apache.spark.sql.SparkSessionimport scala.util.Random
- 将输出级别设置为
ERROR以减少 Spark 的日志输出:
Logger.getLogger("org").setLevel(Level.ERROR)
- 通过构建器模式初始化 Spark 会话并指定配置,从而为 Spark 集群提供入口点:
val session = SparkSession ...
工作原理...
我们使用 SparkContext 创建了 RDD;这在 Spark 1.x 中广泛使用。我们还展示了在 Spark 2.0 中使用 Session 对象创建 Dataset 的方法。为了处理生产环境中的预 Spark 2.0 代码,这种来回转换是必要的。
本教程的技术要点是,尽管 Dataset 是未来数据处理的首选方法,我们始终可以使用 API 在 RDD 和 Dataset 之间来回转换。
还有更多...
关于数据类型的更多信息,请访问spark.apache.org/docs/latest/sql-programming-guide.html。
另请参阅
SparkContext 和 SparkSession 的文档可在以下网站找到:
-
spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.SparkContext -
spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.sql.SparkSession
Spark 2.0 中的新模型导出和 PMML 标记
在本教程中,我们探讨了 Spark 2.0 中的模型导出功能,以使用预测模型标记语言(PMML)。这种基于 XML 的标准语言允许您在其他系统上导出和运行模型(存在一些限制)。更多信息,请参阅*还有更多...*部分。
如何操作...
-
在 IntelliJ 或您选择的 IDE 中启动一个新项目。确保包含必要的 JAR 文件。
-
设置程序所在包的位置:
package spark.ml.cookbook.chapter4
- 为 SparkContext 导入必要的包以访问集群:
import org.apache.spark.mllib.linalg.Vectors
import org.apache.spark.sql.SparkSession
import org.apache.spark.mllib.clustering.KMeans
- 创建 Spark 的配置和 SparkContext:
val spark = SparkSession
.builder
.master("local[*]") // if use cluster master("spark://master:7077")
.appName("myPMMLExport")
.config("spark.sql.warehouse.dir", ".")
.getOrCreate()
- 我们从文本文件中读取数据;数据文件包含一个用于 KMeans 模型的示例数据集:
val data = spark.sparkContext.textFile("../data/sparkml2/chapter4/my_kmeans_data_sample.txt")
val parsedData = data.map(s => Vectors.dense(s.split(' ').map(_.toDouble))).cache()
- 我们设置了 KMeans 模型的参数,并使用前面提到的数据集和参数来训练模型:
val numClusters = 2
val numIterations = 10
val model = KMeans.train(parsedData, numClusters, numIterations)
- 我们有效地从刚刚创建的数据结构中创建了一个简单的 KMeans 模型(通过将集群数量设置为 2)。
println("MyKMeans PMML Model:\n" + model.toPMML)
在控制台中,将显示以下模型:
MyKMeans PMML Model:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<PMML version="4.2" >
<Header description="k-means clustering">
<Application name="Apache Spark MLlib" version="2.0.0"/>
<Timestamp>2016-11-06T13:34:57</Timestamp>
</Header>
<DataDictionary numberOfFields="3">
<DataField name="field_0" optype="continuous" dataType="double"/>
<DataField name="field_1" optype="continuous" dataType="double"/>
<DataField name="field_2" optype="continuous" dataType="double"/>
</DataDictionary>
<ClusteringModel modelName="k-means" functionName="clustering" modelClass="centerBased" numberOfClusters="2">
<MiningSchema>
<MiningField name="field_0" usageType="active"/>
<MiningField name="field_1" usageType="active"/>
<MiningField name="field_2" usageType="active"/>
</MiningSchema>
<ComparisonMeasure kind="distance">
<squaredEuclidean/>
</ComparisonMeasure>
<ClusteringField field="field_0" compareFunction="absDiff"/>
<ClusteringField field="field_1" compareFunction="absDiff"/>
<ClusteringField field="field_2" compareFunction="absDiff"/>
<Cluster name="cluster_0">
<Array n="3" type="real">9.06 9.179999999999998 9.12</Array>
</Cluster>
<Cluster name="cluster_1">
<Array n="3" type="real">0.11666666666666665 0.11666666666666665 0.13333333333333333</Array>
</Cluster>
</ClusteringModel>
</PMML>
- 然后我们将 PMML 导出到数据目录中的 XML 文件:
model.toPMML("../data/sparkml2/chapter4/myKMeansSamplePMML.xml")
- 然后我们通过停止 Spark 会话来关闭程序:
spark.stop()
它是如何工作的...
在花费时间训练模型后,下一步将是持久化模型以供将来使用。在本教程中,我们首先训练了一个 KMeans 模型,以生成后续步骤中用于持久化的模型信息。一旦我们有了训练好的模型,我们就调用模型的toPMML()方法将其转换为 PMML 格式以便存储。该方法的调用会生成一个 XML 文档,然后该 XML 文档文本可以轻松地持久化到文件中。
还有更多...
PMML 是由数据挖掘组(DMG)开发的标准。该标准通过允许您在一个系统上构建,然后部署到生产中的另一个系统,实现了跨平台的互操作性。PMML 标准已经获得了动力,并已被大多数供应商采用。其核心是基于一个 XML 文档,包含以下内容:
-
包含一般信息的头部
-
字典描述了第三组件(模型)使用的字段级定义。
-
模型结构和参数
截至本文撰写时,Spark 2.0 机器学习库对 PMML 导出的支持目前仅限于:
-
线性回归
-
逻辑回归
-
岭回归
-
Lasso
-
SVM
-
KMeans
您可以将模型导出到 Spark 支持的以下文件类型:
- 本地文件系统:
Model_a.toPMML("/xyz/model-name.xml")
- 分布式文件系统:
Model_a.toPMML(SparkContext, "/xyz/model-name")
- 输出流——充当管道:
Model_a.toPMML(System.out)
另请参见
关于PMMLExportable API 的文档可以在spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.mllib.pmml.PMMLExportable找到。
使用 Spark 2.0 进行回归模型评估
在本教程中,我们探讨了如何评估回归模型(本例中为回归决策树)。Spark 提供了回归度量工具,该工具具有基本的统计功能,如均方误差(MSE)、R 平方等,开箱即用。
本教程的目标是理解 Spark 原生提供的评估指标。
如何操作...
-
在 IntelliJ 或您选择的 IDE 中启动一个新项目。确保包含了必要的 JAR 文件。
-
设置程序将驻留的包位置:
package spark.ml.cookbook.chapter4
- 导入必要的 SparkContext 包以访问集群:
import org.apache.spark.mllib.evaluation.RegressionMetricsimport org.apache.spark.mllib.linalg.Vectorsimport org.apache.spark.mllib.regression.LabeledPointimport org.apache.spark.mllib.tree.DecisionTreeimport org.apache.spark.sql.SparkSession
- 创建 Spark 的配置和 SparkContext:
val spark = SparkSession.builder.master("local[*]").appName("myRegressionMetrics").config("spark.sql.warehouse.dir", ".").getOrCreate()
- 我们利用了...
它是如何工作的...
在本教程中,我们探讨了生成回归度量以帮助评估我们的回归模型。我们首先加载了一个乳腺癌数据文件,然后以 70/30 的比例将其分割,创建了训练和测试数据集。接下来,我们训练了一个决策树回归模型,并利用它对测试集进行预测。最后,我们获取了这些预测结果,并生成了回归度量,这些度量为我们提供了平方误差、R 平方、平均绝对误差和解释方差。
还有更多...
我们可以使用RegressionMetrics()来生成以下统计量:
-
均方误差(MSE)
-
均方根误差(RMSE)
-
R 平方
-
平均绝对误差(MAE)
-
解释方差
关于回归验证的文档可在en.wikipedia.org/wiki/Regression_validation找到。
R 平方/决定系数可在en.wikipedia.org/wiki/Coefficient_of_determination找到。
另请参阅
-
威斯康星乳腺癌数据集可在 ftp://ftp.cs.wisc.edu/math-prog/cpo-dataset/machine-learn/cancer/cancer1/datacum 下载
使用 Spark 2.0 进行二分类模型评估
在本教程中,我们展示了在 Spark 2.0 中使用BinaryClassificationMetrics工具及其应用于评估具有二元结果(例如,逻辑回归)的模型。
这里的重点不是展示回归本身,而是演示如何使用常见的度量标准(如接收者操作特征(ROC)、ROC 曲线下的面积、阈值等)来评估它。
如何操作...
-
在 IntelliJ 或您选择的 IDE 中启动一个新项目。确保包含了必要的 JAR 文件。
-
设置程序将驻留的包位置:
package spark.ml.cookbook.chapter4
- 为 SparkContext 导入必要的包以访问集群:
import org.apache.spark.sql.SparkSession
import org.apache.spark.mllib.classification.LogisticRegressionWithLBFGS
import org.apache.spark.mllib.evaluation.BinaryClassificationMetrics
import org.apache.spark.mllib.regression.LabeledPoint
import org.apache.spark.mllib.util.MLUtils
- 创建 Spark 配置和 SparkContext:
val spark = SparkSession
.builder
.master("local[*]")
.appName("myBinaryClassification")
.config("spark.sql.warehouse.dir", ".")
.getOrCreate()
- 我们从 UCI 下载原始数据集,并对其进行修改以适应代码需求:
// Load training data in LIBSVM format
//https://www.csie.ntu.edu.tw/~cjlin/libsvmtools/datasets/binary.html
val data = MLUtils.loadLibSVMFile(spark.sparkContext, "../data/sparkml2/chapter4/myBinaryClassificationData.txt")
该数据集是一个修改过的数据集。原始的成人数据集有 14 个特征,其中 6 个是连续的,8 个是分类的。在这个数据集中,连续特征被离散化为分位数,每个分位数由一个二进制特征表示。我们修改了数据以适应代码的目的。数据集特征的详细信息可在archive.ics.uci.edu/ml/index.php UCI 网站上找到。
- 我们将数据集以 60:40 的随机比例分割为训练和测试部分,然后获取模型:
val Array(training, test) = data.randomSplit(Array(0.6, 0.4), seed = 11L)
training.cache()
// Run training algorithm to build the model
val model = new LogisticRegressionWithLBFGS()
.setNumClasses(2)
.run(training)
- 我们使用训练数据集创建的模型来进行预测:
val predictionAndLabels = test.map { case LabeledPoint(label, features) =>
val prediction = model.predict(features)
(prediction, label)
}
- 我们从预测结果创建了
BinaryClassificationMetrics对象,并开始对度量进行评估:
val metrics = new BinaryClassificationMetrics(predictionAndLabels)
- 我们在控制台输出了按
阈值的精确度:
val precision = metrics.precisionByThreshold
precision.foreach { case (t, p) =>
println(s"Threshold: $t, Precision: $p")
}
从控制台输出中:
Threshold: 2.9751613212299755E-210, Precision: 0.5405405405405406
Threshold: 1.0, Precision: 0.4838709677419355
Threshold: 1.5283665404870175E-268, Precision: 0.5263157894736842
Threshold: 4.889258814400478E-95, Precision: 0.5
- 我们在控制台输出了
recallByThreshold:
val recall = metrics.recallByThreshold
recall.foreach { case (t, r) =>
println(s"Threshold: $t, Recall: $r")
}
从控制台输出中:
Threshold: 1.0779893231660571E-300, Recall: 0.6363636363636364
Threshold: 6.830452412352692E-181, Recall: 0.5151515151515151
Threshold: 0.0, Recall: 1.0
Threshold: 1.1547199216963482E-194, Recall: 0.5757575757575758
- 我们在控制台输出了
fmeasureByThreshold:
val f1Score = metrics.fMeasureByThreshold
f1Score.foreach { case (t, f) =>
println(s"Threshold: $t, F-score: $f, Beta = 1")
}
从控制台输出中:
Threshold: 1.0, F-score: 0.46874999999999994, Beta = 1
Threshold: 4.889258814400478E-95, F-score: 0.49230769230769234, Beta = 1
Threshold: 2.2097791212639423E-117, F-score: 0.48484848484848486, Beta = 1
val beta = 0.5
val fScore = metrics.fMeasureByThreshold(beta)
f1Score.foreach { case (t, f) =>
println(s"Threshold: $t, F-score: $f, Beta = 0.5")
}
从控制台输出中:
Threshold: 2.9751613212299755E-210, F-score: 0.5714285714285714, Beta = 0.5
Threshold: 1.0, F-score: 0.46874999999999994, Beta = 0.5
Threshold: 1.5283665404870175E-268, F-score: 0.5633802816901409, Beta = 0.5
Threshold: 4.889258814400478E-95, F-score: 0.49230769230769234, Beta = 0.5
- 我们在控制台输出了
Precision Recall 曲线下的面积:
val auPRC = metrics.areaUnderPR
println("Area under precision-recall curve = " + auPRC)
从控制台输出中:
Area under precision-recall curve = 0.5768388996048239
- 我们在控制台输出了 ROC 曲线下的面积:
val thresholds = precision.map(_._1)
val roc = metrics.roc
val auROC = metrics.areaUnderROC
println("Area under ROC = " + auROC)
从控制台输出中:
Area under ROC = 0.6983957219251337
- 然后我们通过停止 Spark 会话来关闭程序:
spark.stop()
工作原理...
在本示例中,我们研究了二分类度量的评估。首先,我们加载了libsvm格式的数据,并按 60:40 的比例分割,生成了训练集和测试集。接着,我们训练了一个逻辑回归模型,并从测试集中生成预测。
在我们得到预测结果后,我们创建了一个二分类度量对象。最后,我们获取了真阳性率、阳性预测值、接收者操作特征曲线、接收者操作特征曲线下的面积、精确召回曲线下的面积和 F 度量,以评估模型的适应性。
还有更多...
Spark 提供了以下度量以方便评估:
-
TPR - 真阳性率
-
PPV - 阳性预测值
-
F - F 度量
-
ROC - 接收者操作特征曲线
-
AUROC - 接收者操作特征曲线下的面积
-
AUORC - 精确召回曲线下的面积
以下链接提供了度量的良好入门材料:
另请参见
原始数据集信息的文档可在以下链接获得:
二分类度量的文档可在此处获得。
使用 Spark 2.0 进行多类别分类模型评估
在本示例中,我们探讨了MulticlassMetrics,它允许你评估一个将输出分类为两个以上标签(例如,红色、蓝色、绿色、紫色、未知)的模型。它突出了混淆矩阵(confusionMatrix)和模型准确性的使用。
如何操作...
-
在 IntelliJ 或您选择的 IDE 中启动一个新项目。确保包含必要的 JAR 文件。
-
设置程序将驻留的包位置:
package spark.ml.cookbook.chapter4
- 为 SparkContext 导入必要的包以访问集群:
import org.apache.spark.sql.SparkSessionimport org.apache.spark.mllib.classification.LogisticRegressionWithLBFGSimport org.apache.spark.mllib.evaluation.MulticlassMetricsimport org.apache.spark.mllib.regression.LabeledPointimport org.apache.spark.mllib.util.MLUtils
- 创建 Spark 的配置和 SparkContext:
val spark = SparkSession.builder.master("local[*]").appName("myMulticlass").config("spark.sql.warehouse.dir", ".").getOrCreate() ...
工作原理...
在本节中,我们探讨了为多分类模型生成评估指标。首先,我们将鸢尾花数据加载到内存中,并按 60:40 的比例分割。其次,我们使用三个分类训练了一个逻辑回归模型。第三,我们使用测试数据集进行预测,并利用MultiClassMetric生成评估测量。最后,我们评估了诸如模型准确性、加权精度、加权召回率、加权 F1 分数、加权假阳性率等指标。
还有更多...
尽管本书的范围不允许对混淆矩阵进行全面处理,但提供了一个简短的解释和一个链接作为快速参考。
混淆矩阵只是一个错误矩阵的华丽名称。它主要用于无监督学习中以可视化性能。它是一种布局,捕捉实际与预测结果,使用两维中相同的标签集:
混淆矩阵
要快速了解无监督和监督统计学习系统中的混淆矩阵,请参见en.wikipedia.org/wiki/Confusion_matrix。
另请参见
原始数据集信息的文档可在以下网站获得:
多类分类度量文档可在此处获得:
使用 Spark 2.0 进行多标签分类模型评估
在本节中,我们探讨了 Spark 2.0 中的多标签分类MultilabelMetrics,这不应与前一节中涉及多类分类MulticlassMetrics的内容混淆。探索此节的关键是专注于评估指标,如汉明损失、准确性、F1 度量等,以及它们所衡量的内容。
如何操作...
-
在 IntelliJ 或您选择的 IDE 中启动一个新项目。确保包含必要的 JAR 文件。
-
设置程序将驻留的包位置:
package spark.ml.cookbook.chapter4
- 为 SparkContext 导入必要的包以访问集群:
import org.apache.spark.sql.SparkSession
import org.apache.spark.mllib.evaluation.MultilabelMetrics
import org.apache.spark.rdd.RDD
- 创建 Spark 的配置和 SparkContext:
val spark = SparkSession
.builder
.master("local[*]")
.appName("myMultilabel")
.config("spark.sql.warehouse.dir", ".")
.getOrCreate()
- 我们为评估模型创建数据集:
val data: RDD[(Array[Double], Array[Double])] = spark.sparkContext.parallelize(
Seq((Array(0.0, 1.0), Array(0.1, 2.0)),
(Array(0.0, 2.0), Array(0.1, 1.0)),
(Array.empty[Double], Array(0.0)),
(Array(2.0), Array(2.0)),
(Array(2.0, 0.0), Array(2.0, 0.0)),
(Array(0.0, 1.0, 2.0), Array(0.0, 1.0)),
(Array(1.0), Array(1.0, 2.0))), 2)
- 我们从预测结果创建
MultilabelMetrics对象,并开始对指标进行评估:
val metrics = new MultilabelMetrics(data)
- 我们在控制台打印出总体统计摘要:
println(s"Recall = ${metrics.recall}")
println(s"Precision = ${metrics.precision}")
println(s"F1 measure = ${metrics.f1Measure}")
println(s"Accuracy = ${metrics.accuracy}")
从控制台输出中:
Recall = 0.5
Precision = 0.5238095238095238
F1 measure = 0.4952380952380952
Accuracy = 0.4523809523809524
- 我们在控制台打印出各个标签的值:
metrics.labels.foreach(label =>
println(s"Class $label precision = ${metrics.precision(label)}"))
metrics.labels.foreach(label => println(s"Class $label recall = ${metrics.recall(label)}"))
metrics.labels.foreach(label => println(s"Class $label F1-score = ${metrics.f1Measure(label)}"))
从控制台输出中:
Class 0.0 precision = 0.5
Class 1.0 precision = 0.6666666666666666
Class 2.0 precision = 0.5
Class 0.0 recall = 0.6666666666666666
Class 1.0 recall = 0.6666666666666666
Class 2.0 recall = 0.5
Class 0.0 F1-score = 0.5714285714285715
Class 1.0 F1-score = 0.6666666666666666
Class 2.0 F1-score = 0.5
- 我们在控制台打印出微观统计值:
println(s"Micro recall = ${metrics.microRecall}")
println(s"Micro precision = ${metrics.microPrecision}")
println(s"Micro F1 measure = ${metrics.microF1Measure}")
From the console output:
Micro recall = 0.5
Micro precision = 0.5454545454545454
Micro F1 measure = 0.5217391304347826
- 我们在控制台从度量中打印出汉明损失和子集准确性:
println(s"Hamming loss = ${metrics.hammingLoss}")
println(s"Subset accuracy = ${metrics.subsetAccuracy}")
From the console output:
Hamming loss = 0.39285714285714285
Subset accuracy = 0.2857142857142857
- 然后我们通过停止 Spark 会话来关闭程序。
spark.stop()
工作原理...
在本节中,我们探讨了为多标签分类模型生成评估指标的过程。我们首先手动创建了一个用于模型评估的数据集。接着,我们将数据集作为参数传递给MultilabelMetrics,并生成了评估指标。最后,我们打印出了各种指标,如微观召回率、微观精确度、微观 F1 度量、汉明损失、子集准确性等。
还有更多...
注意,多标签分类和多类别分类听起来相似,但实际上是两种不同的概念。
所有多标签的MultilabelMetrics()方法试图实现的是将多个输入(x)映射到一个二进制向量(y),而不是典型分类系统中的数值。
与多标签分类相关的重要度量包括(参见前面的代码):
-
准确性
-
汉明损失
-
精确度
-
召回率
-
F1 分数
每个参数的完整解释超出了范围,但以下链接提供了对多标签度量的简要说明:
另请参见
多标签分类度量文档:
使用 Scala Breeze 库在 Spark 2.0 中进行图形绘制
在本节中,我们将使用 Scala Breeze 线性代数库(的一部分)中的scatter()和plot()函数来绘制二维数据的散点图。一旦在 Spark 集群上计算出结果,要么可以在驱动程序中使用可操作数据进行绘图,要么可以在后端生成 JPEG 或 GIF 图像,并推送以提高效率和速度(这在基于 GPU 的分析数据库如 MapD 中很流行)
如何操作...
-
首先,我们需要下载必要的 ScalaNLP 库。从 Maven 仓库下载 JAR 文件,地址为
repo1.maven.org/maven2/org/scalanlp/breeze-viz_2.11/0.12/breeze-viz_2.11-0.12.jar。 -
将 JAR 文件放置在 Windows 机器上的
C:\spark-2.0.0-bin-hadoop2.7\examples\jars目录中: -
在 macOS 上,请将 JAR 文件放置在其正确的路径中。对于我们的设置示例,路径是
/Users/USERNAME/spark/spark-2.0.0-bin-hadoop2.7/examples/jars/。 -
以下是显示 JAR 文件的示例截图:
-
在 IntelliJ 或你选择的 IDE 中启动一个新项目。确保包含必要的 JAR 文件。
-
设置程序将驻留的包位置:
package spark.ml.cookbook.chapter4
- 导入...
工作原理...
在本教程中,我们通过随机数在 Spark 中创建了一个数据集。随后,我们创建了一个 Breeze 图形并设置了基本参数。我们从创建的数据集中导出了x, y数据。
我们使用 Breeze 的scatter()和plot()函数,通过 Breeze 库进行图形绘制。
还有更多...
在前一章中,我们展示了可以使用 Breeze 作为 JFreeChart 等更复杂、功能更强大的图表库的替代方案。ScalaNLP 项目倾向于利用 Scala 的特性,如隐式转换,使得编码相对容易。
Breeze 图形 JAR 文件可在central.maven.org/maven2/org/scalanlp/breeze-viz_2.11/0.12/breeze-viz_2.11-0.12.jar下载。
更多关于 Breeze 图形的信息可在github.com/scalanlp/breeze/wiki/Quickstart找到。
API 文档(请注意,API 文档可能不是最新的)可在www.scalanlp.org/api/breeze/#package找到。
注意,一旦进入根包,你需要点击 Breeze 以...
参见
关于 Breeze 的更多信息,请参阅 GitHub 上的原始资料github.com/scalanlp/breeze。
注意,一旦进入根包,你需要点击 Breeze 以查看详细信息。
关于 Breeze API 文档的更多信息,请下载repo1.maven.org/maven2/org/scalanlp/breeze-viz_2.11/0.12/breeze-viz_2.11-0.12-javadoc.jar JAR。
第十三章:使用 Spark 实现可扩展的推荐引擎
在本章中,我们将涵盖:
-
为 Spark 2.0 中的可扩展推荐引擎准备所需数据
-
探索 Spark 2.0 中推荐系统的电影数据细节
-
探索 Spark 2.0 中推荐系统的评分数据细节
-
构建可扩展的推荐引擎:使用 Spark 2.0 中的协同过滤
引言
在前几章中,我们使用简短的方法和极其简化的代码来演示 Spark 机器学习库的基本构建块和概念。在本章中,我们展示了一个更成熟的应用,它使用 Spark 的 API 和功能来解决特定的机器学习库领域。本章中的方法较少;然而,我们进入了一个更 ML 应用设置。
在本章中,我们将探讨推荐系统及其使用称为交替最小二乘(ALS)的潜在因子模型矩阵分解技术的实现。简而言之,当我们尝试将用户-物品评分的大矩阵分解为两个低秩、更瘦的矩阵时,我们通常会面临一个非常难以解决的非线性或非凸优化问题。我们非常擅长通过固定一个变量并部分解决另一个变量,然后来回切换(因此交替)来解决凸优化问题;我们可以使用已知的优化技术在并行中更好地解决这种分解(从而发现一组潜在因素)。
我们使用一个流行的数据集(电影数据集)来实现推荐引擎,但与其他章节不同,我们使用两个方法来探索数据,并展示如何引入图形元素,如流行的 JFreeChart 库到您的 Spark 机器学习工具包。
下图展示了本章中概念和方法的流程,以演示一个 ALS 推荐应用:
推荐引擎已经存在很长时间,并在 20 世纪 90 年代早期的电子商务系统中使用,从硬编码的产品关联到基于用户画像的内容推荐。现代系统使用协同过滤(CF)来解决早期系统的不足,并解决在现代商业系统(如亚马逊、Netflix、eBay、新闻等)中竞争所需的规模和延迟(例如,最大 100 毫秒及以下)。
现代系统采用基于历史互动和记录(页面浏览、购买、评分等)的 CF。这些系统解决了两个主要问题,即规模化和稀疏性(即我们没有所有电影或歌曲的所有评分)。大多数系统采用基于交替最小二乘法与加权 Lambda 正则化的变体,这些可以在大多数主要平台上并行化(例如,Spark)。尽管如此,为商业目的实施的实用系统会采用多种增强措施来处理偏差(即并非所有电影和用户都是平等的)和时间问题(即用户的选择会变化,物品库存也会变化),这些问题在当今生态系统中普遍存在。在开发智能且领先的电子商务系统时,构建一个有竞争力的推荐器并非纯粹主义方法,而是一种实用方法,它采用多种技术,至少利用所有三种技术(协同过滤、基于内容的过滤和相似性)来构建亲和矩阵/热图。
鼓励读者查阅有关推荐系统冷启动问题的白皮书和资料。
为了设定背景,下图提供了一个构建推荐系统可用方法的高级分类。我们简要讨论了每种系统的优缺点,但重点是 Spark 中可用的矩阵分解(潜在因子模型)。
尽管单值分解(SVD)和交替最小二乘法(ALS)都可用,但由于 SVD 在处理缺失数据等方面的不足,我们专注于使用 MovieLens 数据的 ALS 实现。
以下部分将解释当前使用的推荐引擎技术。
内容过滤
内容过滤是推荐引擎的原始技术之一,它依赖用户档案来提供推荐。这种方法主要依赖于用户(类型、人口统计、收入、地理位置、邮政编码)和库存(产品、电影或歌曲的特性)的预先设定档案来推断属性,然后可以进行过滤和处理。主要问题在于,预先获取的知识往往不完整且成本高昂。这项技术已有十多年历史,至今仍在使用。
协同过滤
协同过滤是现代推荐系统的核心,它依赖于生态系统中的用户互动而非档案来提供推荐。
该技术依赖于用户过去的行为和产品评分,并不假设任何预先存在的知识。简而言之,用户对库存项目进行评分,并假设客户口味在一段时间内将保持相对稳定,这可以用来提供推荐。话虽如此,一个智能系统将根据任何可用上下文(例如,用户是来自中国的女性)来增强和重新排序推荐。
这类技术的主要问题是冷启动,但其不受领域限制、更高的准确性和易于扩展的优势,使其在大数据时代成为赢家。
邻域方法
这项技术主要以加权局部邻域的形式实现。其核心是一种相似性技术,严重依赖于对物品和用户的假设。尽管该技术易于理解和实施,但算法在可扩展性和准确性方面存在缺陷。
潜在因子模型技术
该技术试图通过推断一组次级潜在因素来解释用户对库存项目(例如,亚马逊上的产品)的评分,这些潜在因素是从评分中推断出来的。其优势在于,你无需事先了解这些因素(类似于 PCA 技术),而是直接从评分本身推断出来。我们采用矩阵分解技术来推导这些潜在因素,这些技术因其极高的可扩展性、预测准确性和灵活性(允许偏差和用户及库存的时间特性)而广受欢迎。
-
奇异值分解(SVD):SVD 自 Spark 早期就已可用,但我们建议不要将其作为核心技术,因为其在处理现实数据稀疏性(例如,用户通常不会对所有内容进行评分)、过拟合和排序(我们真的需要生成最底部的 1000 条推荐吗?)方面存在问题。
-
随机梯度下降(SGD):SGD 易于实现,且由于其逐个电影和逐个用户/物品向量的处理方式(选择一部电影并针对该用户微调其配置文件,而非批量处理),运行速度更快。我们可以根据需要使用 Spark 中的矩阵设施和 SGD 来实现这一点。
-
交替最小二乘法(ALS):在开始这段旅程之前,请先了解 ALS。Spark 中的 ALS 从一开始就能利用并行化。与普遍认为 Spark 使用半因子分解相反,Spark 实际上在内部实现了完整的矩阵分解。我们鼓励读者参考源代码自行验证。Spark 提供了针对显式(有评分)和隐式(需要间接推断,例如,播放曲目的时长而非评分)的 API。我们在食谱中通过引入数学和直觉来讨论偏差和时间问题,以阐明我们的观点。
为 Spark 2.0 中的可扩展推荐引擎设置所需数据
在本节中,我们探讨下载 MovieLens 公共数据集并初步探索数据。我们将使用基于 MovieLens 数据集中客户评级的显式数据。MovieLens 数据集包含来自 6,000 名用户的 4,000 部电影的 1,000,000 个评分。
你需要以下命令行工具之一来检索指定数据:curl(Mac 推荐)或wget(Windows 或 Linux 推荐)。
如何操作...
- 你可以通过以下任一命令开始下载数据集:
wget http://files.grouplens.org/datasets/movielens/ml-1m.zip
你也可以使用以下命令:
curl http://files.grouplens.org/datasets/movielens/ml-1m.zip -o ml-1m.zip
- 现在你需要解压 ZIP 文件:
unzip ml-1m.zip
creating: ml-1m/
inflating: ml-1m/movies.dat
inflating: ml-1m/ratings.dat
inflating: ml-1m/README
inflating: ml-1m/users.dat
该命令将创建一个名为ml-1m的目录,其中包含解压后的数据文件。
- 切换到
m1-1m目录:
cd m1-1m
- 现在我们通过验证
movies.dat中的数据格式开始数据探索的第一步:
head -5 movies.dat
1::Toy Story (1995)::Animation|Children's|Comedy
2::Jumanji (1995)::Adventure|Children's|Fantasy
3::Grumpier Old Men (1995)::Comedy|Romance
4::Waiting to Exhale (1995)::Comedy|Drama
5::Father of the Bride Part II (1995)::Comedy
- 现在我们来看看评分数据的格式:
head -5 ratings.dat
1::1193::5::978300760
1::661::3::978302109
1::914::3::978301968
1::3408::4::978300275
1::2355::5::978824291
工作原理...
MovieLens 数据集是原始 Netflix KDD 杯数据集的绝佳替代品。此数据集有多个版本,从小型(100 K 数据集)到大型(1 M 和 20 M 数据集)。对于那些希望调整源代码以添加自己的增强功能(例如,更改正则化技术)的用户,数据集的范围使其易于研究缩放效果并查看执行者每秒 Spark 利用率与数据从 100 K 到 20 M 的性能曲线。
下载 URL 为grouplens.org/datasets/movielens/。
还有更多...
仔细查看我们下载数据的位置,因为更多数据集可在files.grouplens.org/datasets/上使用。
下图展示了数据的规模和范围。本章我们使用小数据集,以便在资源有限的小型笔记本电脑上轻松运行。
来源:MovieLens
另请参阅
请阅读解压数据后所在目录中的 README 文件。README 文件包含有关数据文件格式和数据描述的信息。
还有一个 MovieLens 基因组标签集可供参考。
-
计算的标签-电影 1100 万
-
从 1,100 个标签池中得出的相关性评分
-
应用于 10,000 部电影
对于那些有兴趣探索原始 Netflix 数据集的人,请参阅academictorrents.com/details/9b13183dc4d60676b773c9e2cd6de5e5542cee9a的 URL。
在 Spark 2.0 中为推荐系统探索电影数据详情
在本教程中,我们将开始通过将数据解析到 Scala case类中并生成一个简单指标来探索电影数据文件。关键在于获取对数据的了解,以便在后续阶段,如果出现模糊的结果,我们将有一些见解来做出关于我们结果正确性的明智结论。
这是探索电影数据集的两个教程中的第一个。数据探索是统计分析和机器学习的重要第一步。
快速理解数据的最佳方法之一是生成其数据可视化,我们将使用 JFreeChart 来实现这一点。确保您对数据感到舒适并直接了解每个文件中的内容以及它试图讲述的故事非常重要。
在我们做任何其他事情之前,我们必须始终探索、理解和可视化数据。大多数 ML 和其他系统的性能和失误都可以追溯到对数据布局及其随时间变化的社会缺乏了解。如果我们在本教程的第 14 步中查看给出的图表,我们立即意识到电影在年份上的分布不均匀,而是具有高偏度的。虽然我们不会在这本书中探讨这个属性以进行优化和采样,但它强调了电影数据性质的重要观点。
如何实现它...
-
在 IntelliJ 或您选择的 IDE 中启动一个新项目。确保包含必要的 JAR 文件。
-
JFreeChart JAR 可以从
sourceforge.net/projects/jfreechart/files/网站下载。 -
请确保 JFreeChart 库及其依赖项(JCommon)位于本章的类路径上。
-
我们为 Scala 程序定义包信息:
package spark.ml.cookbook.chapter7
- 导入必要的包:
import java.text.DecimalFormat import org.apache.log4j.{Level, Logger} import org.apache.spark.sql.SparkSession import org.jfree.chart.{ChartFactory, ChartFrame, JFreeChart} import org.jfree.chart.axis.NumberAxis import org.jfree.chart.plot.PlotOrientation import org.jfree.data.xy.{XYSeries, ...
它是如何工作的...
当程序开始执行时,我们在驱动程序中初始化了一个 SparkContext,以启动处理数据任务。这意味着数据必须适合驱动程序的内存(用户的工作站),在这种情况下这不是服务器的要求。必须设计其他分治方法来处理极端数据集(部分检索和目的地组装)。
我们继续通过将数据文件加载并解析到具有电影数据类型的数据集中。然后,电影数据集按年份分组,产生一个按年份键入的电影地图,并附有相关电影的存储桶。
接下来,我们提取特定年份及其相关电影数量的计数,以生成我们的直方图。然后,我们收集数据,导致整个结果数据集合在驱动程序上具体化,并将其传递给 JFreeChart 以构建数据可视化。
还有更多...
由于 Spark SQL 的灵活性,你需要了解我们对它的使用。更多信息可访问spark.apache.org/docs/latest/sql-programming-guide.html#running-sql-queries-programmatically。
另请参阅
更多关于使用 JFreeChart 的信息,请参考 JFreeChart API 文档www.jfree.org/jfreechart/api.html。
你可以在www.tutorialspoint.com/jfreechart/链接找到关于 JFreeChart 的优质教程。
JFreeChart 本身的链接是www.jfree.org/index.html。
探索 Spark 2.0 中推荐系统的评分数据细节
在本食谱中,我们从用户/评分的角度探索数据,以了解我们数据文件的性质和属性。我们将开始通过将数据解析为 Scala case class 并生成可视化来探索评分数据文件以获取洞察。评分数据稍后将用于为我们的推荐引擎生成特征。我们再次强调,任何数据科学/机器学习实践的第一步都应该是数据的可视化和探索。
再次强调,快速理解数据的最佳方式是生成其数据可视化,我们将使用 JFreeChart 散点图来实现这一点。快速查看图表...
如何做到这一点...
-
在 IntelliJ 或你选择的 IDE 中启动一个新项目。确保包含必要的 JAR 文件。
-
我们为 Scala 程序定义包信息:
package spark.ml.cookbook.chapter7
- 导入必要的包:
import java.text.DecimalFormat
import org.apache.log4j.{Level, Logger}
import org.apache.spark.sql.SparkSession
import org.jfree.chart.{ChartFactory, ChartFrame, JFreeChart}
import org.jfree.chart.axis.NumberAxis
import org.jfree.chart.plot.PlotOrientation
import org.jfree.data.xy.{XYSeries, XYSeriesCollection}
- 我们现在定义一个 Scala
case class来模拟评分数据:
case class Rating(userId: Int, movieId: Int, rating: Float, timestamp: Long)
- 让我们定义一个在窗口中显示 JFreeChart 的函数:
def show(chart: JFreeChart) {
val frame = new ChartFrame("plot", chart)
frame.pack()
frame.setVisible(true)
}
- 在这一步中,我们定义了一个函数,用于将
ratings.dat文件中的一行数据解析为评分case class:
def parseRating(str: String): Rating = {
val columns = str.split("::")
assert(columns.size == 4)
Rating(columns(0).toInt, columns(1).toInt, columns(2).toFloat, columns(3).toLong)
}
- 我们准备好开始构建我们的
main函数,所以让我们从ratings.dat文件的位置开始:
val ratingsFile = "../data/sparkml2/chapter7/ratings.dat"
- 创建 Spark 的配置,SparkSession。在本例中,我们首次展示了如何在小笔记本上设置 Spark executor 内存(例如,2GB)。如果你想使用大型数据集(144MB 的数据集),你必须增加这个分配:
val spark = SparkSession
.*builder* .master("local[*]")
.appName("MovieRating App")
.config("spark.sql.warehouse.dir", ".")
.config("spark.executor.memory", "2g")
.getOrCreate()
- 日志消息的交错导致输出难以阅读;因此,将日志级别设置为
ERROR:
Logger.getLogger("org").setLevel(Level.ERROR)
- 从数据文件创建所有评分的数据集:
import spark.implicits._
val ratings = spark.read.textFile(ratingsFile).map(*parseRating*)
- 现在我们将评分数据集转换为内存表视图,我们可以在其中执行 Spark SQL 查询:
ratings.createOrReplaceTempView("ratings")
- 我们现在生成一个按用户分组的所有用户评分的列表,以及他们的总数:
val resultDF = spark.sql("select ratings.userId, count(*) as count from ratings group by ratings.userId")
resultDF.show(25, false);
从控制台输出:
- 展示每个用户的评分散点图。我们选择散点图以展示与前一节不同的数据查看方式。鼓励读者探索标准化技术(例如移除均值)或波动性变化机制(例如 GARCH),以探索此数据集的自回归条件异方差特性(这超出了本书的范围)。建议读者查阅任何高级时间序列书籍,以理解时间序列的时间变化。
val scatterPlotDataset = new XYSeriesCollection()
val xy = new XYSeries("")
resultDF.collect().foreach({r => xy.add( r.getAsInteger, r.getAsInteger) })
scatterPlotDataset.addSeries(xy)
val chart = ChartFactory.*createScatterPlot*(
"", "User", "Ratings Per User", scatterPlotDataset, PlotOrientation.*VERTICAL*, false, false, false)
val chartPlot = chart.getXYPlot()
val xAxis = chartPlot.getDomainAxis().asInstanceOf[NumberAxis]
xAxis.setNumberFormatOverride(new DecimalFormat("####"))
- 显示图表:
*show*(chart)
- 我们通过停止 Spark 会话来关闭程序:
spark.stop()
其工作原理...
我们首先将数据文件加载并解析为具有评分数据类型的数据集,最终将其转换为 DataFrame。然后,使用 DataFrame 执行 Spark SQL 查询,该查询按用户及其总数对所有评分进行分组。
全面理解 API 及其概念(延迟实例化、阶段划分、流水线和缓存)对每位 Spark 开发者至关重要。
最后,我们将数据集的结果传递给 JFreeChart 散点图组件以显示我们的图表。
还有更多...
Spark DataFrame 是一个分布式数据集合,按命名列组织。所有 DataFrame 操作也会自动在集群上并行化和分布。此外,DataFrames 像 RDD 一样是惰性评估的。
参见
DataFrame 的文档可在spark.apache.org/docs/latest/sql-programming-guide.html找到。
关于 JFreeChart 的优秀教程可在www.tutorialspoint.com/jfreechart/链接中找到。
JFreeChart 可从www.jfree.org/index.html网址下载。
使用 Spark 2.0 中的协同过滤构建可扩展的推荐引擎
本节中,我们将演示一个利用协同过滤技术的推荐系统。协同过滤的核心在于分析用户之间的关系以及库存(例如电影、书籍、新闻文章或歌曲)之间的依赖性,基于一组称为潜在因素的次要因素(例如女性/男性、快乐/悲伤、活跃/被动)来识别用户与物品之间的关系。关键在于,您无需预先了解这些潜在因素。
推荐将通过 ALS 算法生成,这是一种协同过滤技术。从高层次上看,协同过滤涉及基于收集的先前已知偏好以及许多其他用户的偏好,对用户可能感兴趣的内容进行预测。我们将使用 MovieLens 数据集中的评分数据,并将其转换为推荐算法的输入特征。
如何操作...
-
在 IntelliJ 或您选择的 IDE 中启动一个新项目。确保包含必要的 JAR 文件。
-
我们为 Scala 程序定义包信息:
package spark.ml.cookbook.chapter7
- 导入必要的包:
import org.apache.log4j.{Level, Logger} import org.apache.spark.sql.SparkSession import org.apache.spark.ml.recommendation.ALS
- 我们现在定义两个 Scala case 类,以模拟电影和评分数据:
case class Movie(movieId: Int, title: String, year: Int, genre: Seq[String]) case class FullRating(userId: Int, movieId: Int, rating: Float, timestamp: Long)
- 在此步骤中,我们定义函数,用于将
ratings.dat文件中的一行数据解析为评分case class,以及用于解析...
它是如何工作的...
由于程序的复杂性,我们首先提供概念性解释,然后逐步详细说明程序内容。
下图描绘了 ALS 的概念视图及其如何将用户/电影/评分矩阵分解为低阶的瘦长矩阵和潜在因子向量:f(用户)和 f(电影)。
另一种思考方式是,这些因子可用于将电影置于n维空间中,该空间将与给定用户的推荐相匹配。始终希望将机器学习视为在维度变量空间中的搜索查询。需要记住的是,潜在因子(学习的几何空间)并非预先定义,其数量可以从 10 到 100 或 1000 不等,具体取决于所搜索或分解的内容。因此,我们的推荐可以看作是在 n 维空间内放置概率质量。下图提供了一个可能的双因子模型(二维)的极其简化的视图,以阐明这一点:
尽管 ALS 的实现可能因系统而异,但其核心是一种迭代的全因子分解方法(在 Spark 中),带有加权正则化。Spark 的文档和教程提供了对该算法实际数学和性质的洞察。它将算法描述如下:
理解这个公式/算法的最佳方式是将其视为一个迭代装置,试图通过交替输入(即,固定一个输入,然后近似/优化另一个——如此往复)来发现潜在因子,同时试图最小化与加权 lambda 正则化惩罚相关的最小二乘误差(MSE)。下一节将提供更详细的解释。
程序流程如下:
-
示例首先从 MovieLens 数据集中加载评分和电影数据。加载的数据随后被转换为 Scala case 类以便进一步处理。接下来,将评分数据划分为训练集和测试集。训练集数据用于训练机器学习算法。训练是机器学习中用于构建模型以便提供所需结果的过程。测试数据将用于最终步骤中验证结果。
-
虚构用户,即用户 ID 零,通过配置一个未包含在原始数据集中的单一用户,帮助通过即时创建包含随机信息的数据显示结果,并最终将其附加到训练集中。通过将包含用户 ID、电影 ID 和评分的训练集数据传递给 ALS 算法来调用它,随后从 Spark 中产生一个矩阵分解模型。为测试数据集和用户 ID 零生成预测。
-
最终结果通过结合评分信息与电影数据展示,以便结果能被理解并在原始评分旁显示估计评分。最后一步是计算生成评分的均方根误差,该评分包含在测试数据集中。RMSE 将告诉我们训练模型的准确性。
还有更多...
尽管 ALS 本质上是一个带有正则化惩罚的简单线性代数运算,但人们常常难以掌握。ALS 的强大之处在于其能够并行化处理以及应对规模(例如 Spotify)。
ALS 用通俗语言来说涉及以下内容:
- 使用 ALS,你基本上想要将一个大型评分矩阵 X(拥有超过 1 亿用户并非夸张)和用户产品评分分解为两个低秩矩阵 A 和 B(参见任何入门线性代数书籍)。问题在于,这通常成为一个非常难以解决的非线性优化问题。为了解决这个问题,ALS 引入了一个简单方案(交替),其中你固定一个矩阵并部分...
另请参见
Spark 2.0 ML 文档以探索 ALS API:
-
spark.apache.org/docs/latest/mllib-collaborative-filtering.html -
spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.ml.recommendation.ALS -
spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.ml.recommendation.ALSModel
Spark 2.0 MLlib 文档可于spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.mllib.recommendation.ALS获取。
ALS 参数及其默认构造,以默认参数创建 ALS 实例如下:
{numBlocks: -1, rank: 10, iterations: 10, lambda: 0.
numBlocks: -1,
rank: 10,
iterations: 10,
lambda: 0.01,
implicitPrefs: false,
alpha: 1.0
处理训练中的隐式输入
有时实际观察(评分)数据不可得,此时需处理隐含反馈参数。这可能简单到记录用户在互动期间听取了哪个音轨,或是观看了多久的电影,亦或是上下文(预先索引)以及导致切换的原因(如 Netflix 电影在开头、中间或特定场景附近被放弃观看)。第三个示例中通过使用ALS.train()处理了显式反馈。
针对隐式数据,Spark ML 库提供了另一种方法ALS.trainImplicit(),该方法有四个超参数来控制算法。若你对测试此方法感兴趣(它与显式反馈非常相似...
第十四章:使用 Apache Spark 2.0 进行无监督聚类
在本章中,我们将涵盖:
-
在 Spark 2.0 中构建 KMeans 分类系统
-
在 Spark 2.0 中,二分 KMeans 作为新星登场
-
在 Spark 2.0 中使用高斯混合模型和期望最大化(EM)算法进行数据分类
-
在 Spark 2.0 中使用幂迭代聚类(PIC)对图的顶点进行分类
-
使用潜在狄利克雷分配(LDA)将文档和文本分类为主题
-
使用流式 KMeans 在接近实时的情况下对数据进行分类
引言
无监督机器学习是一种学习技术,我们试图直接或间接(通过潜在因素)从一组未标记的观察中得出推断。简而言之,我们试图在未对训练数据进行初始标记的情况下,从一组数据中发现隐藏的知识或结构。
尽管大多数机器学习库的实现在大数据集上应用时会崩溃(迭代、多次遍历、大量中间写入),但 Apache Spark 机器学习库通过提供为并行性和极大数据集设计的算法,并默认使用内存进行中间写入,从而取得了成功。
在最抽象的层面上,我们可以将无监督学习视为:
在 Spark 2.0 中构建 KMeans 分类系统
在本教程中,我们将使用 LIBSVM 文件加载一组特征(例如,x,y,z 坐标),然后使用KMeans()实例化一个对象。接着,我们将设置期望的簇数为三个,并使用kmeans.fit()执行算法。最后,我们将打印出我们找到的三个簇的中心。
值得注意的是,Spark并未实现 KMeans++,这与流行文献相反,而是实现了 KMeans ||(发音为 KMeans Parallel)。请参阅以下教程以及代码之后的部分,以获得对 Spark 中实现的算法的完整解释。
如何操作...
-
在 IntelliJ 或您选择的 IDE 中启动一个新项目。确保包含必要的 JAR 文件。
-
设置程序将驻留的包位置:
package spark.ml.cookbook.chapter8
- 为了获取集群访问权限并使用
Log4j.Logger减少 Spark 产生的输出量,需要导入必要的 Spark 上下文包:
import org.apache.log4j.{Level, Logger}import org.apache.spark.ml.clustering.KMeansimport org.apache.spark.sql.SparkSession
- 将输出级别设置为
ERROR以减少 Spark 的日志输出:
Logger.getLogger("org").setLevel(Level.ERROR)
- 创建 Spark 的 Session 对象:
val spark = SparkSession .builder.master("local[*]") .appName("myKMeansCluster") .config("spark.sql.warehouse.dir" ...
工作原理...
我们读取了一个 LIBSVM 文件,其中包含一组坐标(可以解释为三个数字的元组),然后创建了一个 KMean() 对象,但将默认簇数从 2(开箱即用)更改为 3,以便演示。我们使用 .fit() 创建模型,然后使用 model.summary.predictions.show() 显示哪个元组属于哪个簇。在最后一步中,我们打印了成本和三个簇的中心。从概念上讲,可以将其视为拥有一组 3D 坐标数据,然后使用 KMeans 算法将每个单独的坐标分配给三个簇之一。
KMeans 是一种无监督机器学习算法,其根源在于信号处理(矢量量化)和压缩(将相似的物品矢量分组以实现更高的压缩率)。一般来说,KMeans 算法试图将一系列观察值 {X[1,] X[2], .... , X[n]} 分组到一系列簇 {C[1,] C[2 .....] C[n]} 中,使用一种距离度量(局部优化),该度量以迭代方式进行优化。
目前使用的 KMeans 算法主要有三种类型。在一项简单的调查中,我们发现了 12 种 KMeans 算法的专门变体。值得注意的是,Spark 实现了一个名为 KMeans ||(KMeans 并行)的版本,而不是文献或视频中提到的 KMeans++ 或标准 KMeans。
下图简要描绘了 KMeans 算法:
来源:Spark 文档
KMeans(Lloyd 算法)
基本 KMeans 实现(Lloyd 算法)的步骤如下:
-
从观察结果中随机选择 K 个数据中心作为初始中心。
-
持续迭代直至满足收敛条件:
-
测量一个点到每个中心的距离
-
将每个数据点包含在与其最接近的中心对应的簇中
-
根据距离公式(作为不相似性的代理)计算新的簇中心
-
使用新的中心点更新算法
-
下图描绘了三代算法:
KMeans++(亚瑟算法)
对标准 KMeans 的下一个改进是 David Arthur 和 Sergei Vassilvitskii 于 2007 年提出的 KMeans++。亚瑟算法通过在种子过程(初始步骤)中更加挑剔来改进初始的 Lloyd 的 KMeans。
KMeans++并非随机选择初始中心(随机质心),而是随机选取第一个质心,然后逐个选取数据点并计算D(x)。接着,它随机选择另一个数据点,并使用比例概率分布D(x)2,重复最后两个步骤,直到选出所有K个数。初始播种完成后,我们最终运行 KMeans 或其变体,使用新播种的质心。KMeans++算法保证在*Omega= O(log k)*复杂度内找到解决方案。尽管初始播种步骤较多,但准确性提升显著。
KMeans||(发音为 KMeans Parallel)
KMeans || 经过优化,可并行运行,相较于 Lloyd 的原始算法,性能提升可达一到两个数量级。KMeans++的局限性在于它需要对数据集进行 K 次遍历,这在大规模或极端数据集上运行 KMeans 时会严重限制其性能和实用性。Spark 的 KMeans||并行实现运行更快,因为它通过采样 m 个点并在过程中进行过采样,减少了数据遍历次数(大幅减少)。
算法的核心及数学原理在下图中展示:
简而言之,KMeans ||(并行...)的亮点在于...
还有更多...
在 Spark 中还有一个流式 KMeans 实现,允许您实时对特征进行分类。
还有一个类帮助您生成 KMeans 的 RDD 数据。我们在应用程序开发过程中发现这非常有用:
def generateKMeansRDD(sc: SparkContext, numPoints: Int, k: Int, d: Int, r: Double, numPartitions: Int = 2): RDD[Array[Double]]
此调用使用 Spark 上下文创建 RDD,同时允许您指定点数、簇数、维度和分区数。
一个相关的实用 API 是:generateKMeansRDD()。关于generateKMeansRDD的文档可以在spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.mllib.util.KMeansDataGenerator$找到,用于生成供 KMeans 使用的测试数据 RDD。
另请参阅
我们需要两个对象来编写、测量和操作 Spark 中 KMeans ||算法的参数。这两个对象的详细信息可以在以下网站找到:
-
KMeans():spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.ml.clustering.KMeans -
KMeansModel():spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.ml.clustering.KMeansModel
Bisecting KMeans,Spark 2.0 中的新秀
在本节中,我们将下载玻璃数据集,并尝试使用 Bisecting KMeans 算法来识别和标记每种玻璃。Bisecting KMeans 是 K-Mean 算法的层次化版本,在 Spark 中通过BisectingKMeans()API 实现。虽然该算法在概念上类似于 KMeans,但在存在层次路径的情况下,它可以为某些用例提供显著的速度优势。
本节中使用的数据集是玻璃识别数据库。对玻璃类型分类的研究源于犯罪学研究。如果玻璃能被正确识别,它可能被视为证据。数据可在台湾大学(NTU)找到,已采用 LIBSVM 格式。
如何操作...
- 我们从以下链接下载了 LIBSVM 格式的预处理数据文件:
www.csie.ntu.edu.tw/~cjlin/libsvmtools/datasets/multiclass/glass.scale
该数据集包含 11 个特征和 214 行数据。
-
原始数据集及数据字典亦可在 UCI 网站上获取:
archive.ics.uci.edu/ml/datasets/Glass+Identification-
ID 号:1 至 214
-
RI: 折射率
-
Na: 钠(单位测量:相应氧化物中的重量百分比,属性 4-10 也是如此)
-
Mg: 镁
-
Al: 铝
-
Si: 硅
-
K: 钾
-
Ca: 钙
-
Ba: 钡
-
Fe: 铁
-
玻璃类型:我们将使用BisectingKMeans()来寻找我们的类别属性或簇:
-
building_windows_float_processed -
building_windows_non-_float_processed -
vehicle_windows_float_processed
工作原理...
在本节中,我们探讨了 Spark 2.0 中新引入的 Bisecting KMeans 模型。我们利用了玻璃数据集,并尝试使用BisectingKMeans()来指定玻璃类型,但将 k 值调整为 6,以便拥有足够的簇。按照惯例,我们使用 Spark 的 libsvm 加载机制将数据加载到数据集中。我们将数据集随机分为 80%和 20%,其中 80%用于训练模型,20%用于测试模型。
我们创建了BiSectingKmeans()对象,并使用fit(x)函数来构建模型。随后,我们使用transform(x)函数对测试数据集进行模型预测,并在控制台输出结果。我们还输出了计算簇的成本(误差平方和),并展示了簇中心。最后,我们打印了特征及其分配的簇编号,并停止操作。
层次聚类的方法包括:
-
分割型:自上而下的方法(Apache Spark 实现)
-
聚合型:自下而上的方法
还有更多...
关于 Bisecting KMeans 的更多信息,请访问:
我们使用聚类来探索数据,并对聚类结果的外观有所了解。二分 K 均值是层次分析与 K 均值聚类的一个有趣案例。
最佳的理解方式是将二分 K 均值视为递归层次的 K 均值。二分 K 均值算法通过类似 K 均值的相似度测量技术分割数据,但采用层次结构以提高准确性。它在...中尤为普遍...
参见
实现层次聚类有两种方法——Spark 采用递归自顶向下的方法,在其中选择一个簇,然后在算法向下移动层次时执行分割:
-
关于层次聚类方法的详细信息可在
en.wikipedia.org/wiki/Hierarchical_clustering找到 -
Spark 2.0 关于二分 K-均值的文档可在
spark.apache.org/docs/latest/ml-clustering.html#bisecting-k-means找到 -
一篇描述如何使用二分 K 均值对网络日志进行分类的论文可在
research.ijcaonline.org/volume116/number19/pxc3902799.pdf找到
在 Spark 中使用高斯混合和期望最大化(EM)进行数据分类
在本食谱中,我们将探讨 Spark 对期望最大化(EM)的实现GaussianMixture(),它计算给定一组特征输入的最大似然。它假设每个点可以从 K 个子分布(簇成员)中采样的高斯混合。
操作方法...
-
在 IntelliJ 或您选择的 IDE 中启动一个新项目。确保包含必要的 JAR 文件。
-
设置程序将驻留的包位置:
package spark.ml.cookbook.chapter8.
- 导入用于向量和矩阵操作的必要包:
import org.apache.log4j.{Level, Logger}
import org.apache.spark.mllib.clustering.GaussianMixture
import org.apache.spark.mllib.linalg.Vectors
import org.apache.spark.sql.SparkSession
- 创建 Spark 的会话对象:
val spark = SparkSession
.builder.master("local[*]")
.appName("myGaussianMixture")
.config("spark.sql.warehouse.dir", ".")
.getOrCreate()
- 让我们查看数据集并检查输入文件。模拟的 SOCR 膝痛质心位置数据代表了 1000 名受试者假设的膝痛位置的质心位置。数据包括质心的 X 和 Y 坐标。
此数据集可用于说明高斯混合和期望最大化。数据可在wiki.stat.ucla.edu/socr/index.php/SOCR_Data_KneePainData_041409获取
样本数据如下所示:
-
X:一个受试者和一个视图的质心位置的x坐标。
-
Y:一个受试者和一个视图的质心位置的y坐标。
X, Y
11 73
20 88
19 73
15 65
21 57
26 101
24 117
35 106
37 96
35 147
41 151
42 137
43 127
41 206
47 213
49 238
40 229
下图基于wiki.stat.ucla的 SOCR 数据集描绘了一个膝痛地图:
- 我们将数据文件放置在一个数据目录中(您可以将数据文件复制到您喜欢的任何位置)。
数据文件包含 8,666 条记录:
val dataFile ="../data/sparkml2/chapter8/socr_data.txt"
- 接着,我们将数据文件加载到 RDD 中:
val trainingData = spark.sparkContext.textFile(dataFile).map { line =>
Vectors.dense(line.trim.split(' ').map(_.toDouble))
}.cache()
- 现在,我们创建一个高斯混合模型并设置模型参数。我们将 K 值设为 4,因为数据是通过四个视角收集的:左前(LF)、左后(LB)、右前(RF)和右后(RB)。我们将收敛值设为默认值 0.01,最大迭代次数设为 100:
val myGM = new GaussianMixture()
.setK(4 ) // default value is 2, LF, LB, RF, RB
.setConvergenceTol(0.01) // using the default value
.setMaxIterations(100) // max 100 iteration
- 我们运行模型算法:
val model = myGM.run(trainingData)
- 训练后,我们打印出高斯混合模型的关键值:
println("Model ConvergenceTol: "+ myGM.getConvergenceTol)
println("Model k:"+myGM.getK)
println("maxIteration:"+myGM.getMaxIterations)
for (i <- 0 until model.k) {
println("weight=%f\nmu=%s\nsigma=\n%s\n" format
(model.weights(i), model.gaussians(i).mu, model.gaussians(i).sigma))
}
- 由于我们将 K 值设为 4,因此控制台记录器将打印出四组值:
- 我们还根据高斯混合模型预测打印出前 50 个聚类标签:
println("Cluster labels (first <= 50):")
val clusterLabels = model.predict(trainingData)
clusterLabels.take(50).foreach { x =>
*print*(" " + x)
}
- 控制台中的样本输出将显示以下内容:
Cluster labels (first <= 50):
1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
- 然后通过停止 Spark 上下文来关闭程序:
spark.stop()
工作原理...
在前一个示例中,我们观察到 KMeans 能够发现并基于迭代方法(如欧几里得距离等)将成员分配到一个且仅一个集群。可以将 KMeans 视为高斯混合模型中 EM 模型的专用版本,其中强制执行离散(硬)成员资格。
但存在重叠情况,这在医学或信号处理中很常见,如下图所示:
在这种情况下,我们需要一个能够表达每个子分布中成员资格的概率密度函数。采用期望最大化算法的高斯混合模型
新建 GaussianMixture()
这构建了一个默认实例。控制模型行为的默认参数如下:
采用期望最大化算法的高斯混合模型是一种软聚类形式,其中可以通过对数最大似然函数推断出成员资格。在此情况下,使用具有均值和协方差的概率密度函数来定义属于 K 个集群的成员资格或似然性。其灵活性在于,成员资格未量化,这允许基于概率(索引到多个子分布)的成员资格重叠。
下图是 EM 算法的一个快照:
以下是 EM 算法的步骤:
-
假设有N个高斯分布。
-
迭代直至达到收敛:
-
对于每个点 Z,其条件概率为从分布 Xi 中抽取,记作P(Z | Xi)
-
调整参数的均值和方差,使其适合分配给子分布的点
-
有关更数学化的解释,包括关于最大似然的详细工作,请参阅以下链接:www.ee.iisc.ernet.in/new/people/faculty/prasantg/downloads/GMM_Tutorial_Reynolds.pdf
还有更多...
下图提供了一个快速参考点,以突出硬聚类与软聚类之间的一些差异:
另请参阅
-
构造器 GaussianMixture 的文档可在
spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.mllib.clustering.GaussianMixture找到 -
构造器 GaussianMixtureModel 的文档可在
spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.mllib.clustering.GaussianMixtureModel找到
在 Spark 2.0 中使用幂迭代聚类(PIC)对图的顶点进行分类
这是一种基于顶点相似性(由边定义)对图的顶点进行分类的方法。它使用随 Spark 一起提供的 GraphX 库来实现算法。幂迭代聚类类似于其他特征向量/特征值分解算法,但没有矩阵分解的开销。当您有一个大型稀疏矩阵(例如,以稀疏矩阵表示的图)时,它很适用。
未来,GraphFrames 将成为 GraphX 库的替代/接口(databricks.com/blog/2016/03/03/introducing-graphframes.html)。
如何操作...
-
在 IntelliJ 或您选择的 IDE 中启动一个新项目。确保包含必要的 JAR 文件。
-
设置程序将驻留的包位置:
package spark.ml.cookbook.chapter8
- 为 Spark 上下文导入必要的包以访问集群,并导入
Log4j.Logger以减少 Spark 产生的输出量:
import org.apache.log4j.{Level, Logger}
import org.apache.spark.mllib.clustering.PowerIterationClustering
import org.apache.spark.sql.SparkSession
- 将日志级别设置为 ERROR,仅以减少输出:
Logger.getLogger("org").setLevel(Level.*ERROR*)
- 创建 Spark 配置和 SQL 上下文,以便我们可以访问集群并能够根据需要创建和使用 DataFrame:
// setup SparkSession to use for interactions with Sparkval spark = SparkSession
.builder.master("local[*]")
.appName("myPowerIterationClustering")
.config("spark.sql.warehouse.dir", ".")
.getOrCreate()
- 我们使用 Spark 的
sparkContext.parallelize()函数创建包含一系列数据集的训练数据集,并创建 Spark RDD:
val trainingData =spark.sparkContext.parallelize(*List*(
(0L, 1L, 1.0),
(0L, 2L, 1.0),
(0L, 3L, 1.0),
(1L, 2L, 1.0),
(1L, 3L, 1.0),
(2L, 3L, 1.0),
(3L, 4L, 0.1),
(4L, 5L, 1.0),
(4L, 15L, 1.0),
(5L, 6L, 1.0),
(6L, 7L, 1.0),
(7L, 8L, 1.0),
(8L, 9L, 1.0),
(9L, 10L, 1.0),
(10L,11L, 1.0),
(11L, 12L, 1.0),
(12L, 13L, 1.0),
(13L,14L, 1.0),
(14L,15L, 1.0)
))
- 我们创建一个
PowerIterationClustering对象并设置参数。我们将K值设置为3,最大迭代次数设置为15:
val pic = new PowerIterationClustering()
.setK(3)
.setMaxIterations(15)
- 然后让模型运行:
val model = pic.run(trainingData)
- 我们根据模型打印出训练数据的集群分配情况:
model.assignments.foreach { a =>
println(s"${a.id} -> ${a.cluster}")
}
- 控制台输出将显示以下信息:
- 我们还为每个聚类在集合中打印出模型分配数据:
val clusters = model.assignments.collect().groupBy(_.cluster).mapValues(_.map(_.id))
val assignments = clusters.toList.sortBy { case (k, v) => v.length }
val assignmentsStr = assignments
.map { case (k, v) =>
s"$k -> ${v.sorted.mkString("[", ",", "]")}" }.mkString(", ")
val sizesStr = assignments.map {
_._2.length
}.sorted.mkString("(", ",", ")")
println(s"Cluster assignments: $assignmentsStr\ncluster sizes: $sizesStr")
- 控制台输出将显示以下信息(总共,我们在前面的参数中设置了三个聚类):
Cluster assignments: 1 -> [12,14], 2 -> [4,6,8,10], 0 -> [0,1,2,3,5,7,9,11,13,15]
cluster sizes: (2,4,10)
- 然后我们通过停止 Spark 上下文来关闭程序:
spark.stop()
其工作原理...
我们创建了一个图的边和顶点列表,然后继续创建对象并设置参数:
new PowerIterationClustering().setK(3).setMaxIterations(15)
下一步是训练数据模型:
val model = pic.run(trainingData)
然后输出聚类以供检查。代码末尾附近的代码使用 Spark 转换运算符在集合中为每个聚类打印出模型分配数据。
PIC(幂迭代聚类)的核心是一种避免矩阵分解的特征值类算法,它通过生成一个特征值加上一个特征向量来满足Av = λv。由于 PIC 避免了矩阵 A 的分解,因此它适用于输入矩阵 A(描述图...
还有更多...
如需对主题(幂迭代)进行更详细的数学处理,请参阅卡内基梅隆大学提供的以下白皮书:www.cs.cmu.edu/~wcohen/postscript/icml2010-pic-final.pdf
另请参阅
-
构造函数
PowerIterationClustering()的文档可以在以下位置找到:spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.mllib.clustering.PowerIterationClustering -
构造函数
PowerIterationClusteringModel()的文档可以在以下位置找到:spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.mllib.clustering.PowerIterationClusteringModel
使用潜在狄利克雷分配(LDA)对文档和文本进行主题分类
在本食谱中,我们将探讨 Spark 2.0 中的潜在狄利克雷分配(LDA)算法。本食谱中使用的 LDA 与线性判别分析完全不同。潜在狄利克雷分配和线性判别分析都称为 LDA,但它们是截然不同的技术。在本食谱中,当我们使用 LDA 时,我们指的是潜在狄利克雷分配。关于文本分析的章节也与理解 LDA 相关。
LDA 常用于自然语言处理,试图将大量文档(例如安然欺诈案中的电子邮件)分类为有限数量的主题或主题,以便于理解。LDA 也是根据个人兴趣选择文章的良好候选方法(例如,当你翻页并花时间在特定主题上时),在给定的杂志文章或页面上。
如何操作...
-
在 IntelliJ 或您选择的 IDE 中启动一个新项目。确保包含必要的 JAR 文件。
-
设置程序将驻留的包位置:
package spark.ml.cookbook.chapter8
- 导入必要的包:
import org.apache.log4j.{Level, Logger}import org.apache.spark.sql.SparkSessionimport org.apache.spark.ml.clustering.LDA
- 我们设置必要的 Spark 会话以访问集群:
val spark = SparkSession .builder.master("local[*]") .appName("MyLDA") .config("spark.sql.warehouse.dir", ".") .getOrCreate()
- 我们有一个 LDA 样本数据集,位于以下相对路径(您也可以使用绝对路径)。该样本文件随任何 Spark 发行版提供,并且...
工作原理...
LDA 假设文档是具有 Dirichlet 先验分布的不同主题的混合体。文档中的单词被认为对特定主题有亲和力,这使得 LDA 能够对整体文档(构成并分配分布)进行分类,以最佳匹配主题。
主题模型是一种生成潜在模型,用于发现文档主体中出现的抽象主题(主题)(通常对于人类来说太大而无法处理)。这些模型是总结、搜索和浏览大量未标记文档及其内容的先驱。一般来说,我们试图找到一起出现的特征(单词、子图像等)的集群。
下图描绘了 LDA 的整体方案:
为了完整性,请务必参考此处引用的白皮书:ai.stanford.edu/~ang/papers/nips01-lda.pdf
LDA 算法的步骤如下:
-
初始化以下参数(控制集中度和平滑度):
-
Alpha 参数(高 alpha 值使得文档间更为相似,且包含相似的主题)
-
Beta 参数(高 beta 值意味着每个主题最可能包含大多数单词的混合)
-
-
随机初始化主题分配。
-
迭代:
-
对于每个文档。
-
对于文档中的每个单词。
-
为每个单词重新采样主题。
- 相对于所有其他单词及其当前分配(对于当前迭代)。
-
-
-
获取结果。
-
模型评估
在统计学中,Dirichlet 分布 Dir(alpha)是一族由正实数向量α参数化的连续多元概率分布。关于 LDA 的更深入探讨,请参阅原始论文:
机器学习杂志上的原论文链接:www.jmlr.org/papers/volume3/blei03a/blei03a.pdf
LDA 不对主题赋予任何语义,也不关心主题的名称。它只是一个生成模型,使用细粒度项(例如,关于猫、狗、鱼、汽车的单词)的分布来分配总体主题,该主题得分最高。它不知道、不关心,也不理解被称为狗或猫的主题。
我们通常需要通过 TF-IDF 对文档进行分词和向量化,然后才能输入到 LDA 算法中。
还有更多...
下图简要描绘了 LDA:
文档分析有两种方法。我们可以简单地使用矩阵分解将大型数据集矩阵分解为较小的矩阵(主题分配)乘以向量(主题本身):
另请参阅
-
LDA:构造函数的文档可在
spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.ml.clustering.LDA -
LDAModel:构造函数的文档可在
spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.ml.clustering.LDAModel
另请参阅,通过 Spark 的 Scala API,以下文档链接:
-
DistributedLDAModel
-
EMLDAOptimizer
-
LDAOptimizer
-
LocalLDAModel
-
OnlineLDAOptimizer
流式 KMeans 用于近实时分类数据
Spark 流式处理是一个强大的功能,它允许您在同一范式中结合近实时和批处理。流式 KMeans 接口位于 ML 聚类和 Spark 流式处理的交叉点,充分利用了 Spark 流式处理本身提供的核心功能(例如,容错、精确一次交付语义等)。
如何操作...
-
在 IntelliJ 或您选择的 IDE 中启动一个新项目。确保包含必要的 JAR 文件。
-
导入流式 KMeans 所需的包:
package spark.ml.cookbook.chapter14.
- 导入流式 KMeans 所需的包:
import org.apache.log4j.{Level, Logger}
import org.apache.spark.mllib.clustering.StreamingKMeans
import org.apache.spark.mllib.linalg.Vectors
import org.apache.spark.mllib.regression.LabeledPoint
import org.apache.spark.sql.SparkSession
import org.apache.spark.streaming.{Seconds, StreamingContext}
-
我们为流式 KMeans 程序设置了以下参数。训练目录将是发送训练数据文件的目录。KMeans 聚类模型利用训练数据运行算法和计算。
testDirectory将用于预测的测试数据。batchDuration是以秒为单位的批处理运行时间。在以下情况下,程序将每 10 秒检查一次是否有新的数据文件用于重新计算。 -
集群设置为
2,数据维度将为3:
val trainingDir = "../data/sparkml2/chapter8/trainingDir" val testDir = "../data/sparkml2/chapter8/testDir" val batchDuration = 10
val numClusters = 2
val numDimensions = 3
- 使用上述设置,示例训练数据将包含以下格式的数据(以[X[1], X[2], ...X[n]]格式,其中n是
numDimensions):
[0.0,0.0,0.0]
[0.1,0.1,0.1]
[0.2,0.2,0.2]
[9.0,9.0,9.0]
[9.1,9.1,9.1]
[9.2,9.2,9.2]
[0.1,0.0,0.0]
[0.2,0.1,0.1]
....
测试数据文件将包含以下格式的数据(以(y, [X1, X2, .. Xn])格式,其中n是numDimensions,y是标识符):
(7,[0.4,0.4,0.4])
(8,[0.1,0.1,0.1])
(9,[0.2,0.2,0.2])
(10,[1.1,1.0,1.0])
(11,[9.2,9.1,9.2])
(12,[9.3,9.2,9.3])
- 我们设置必要的 Spark 上下文以访问集群:
val spark = SparkSession
.builder.master("local[*]")
.appName("myStreamingKMeans")
.config("spark.sql.warehouse.dir", ".")
.getOrCreate()
- 定义流式上下文和微批处理窗口:
val ssc = new StreamingContext(spark.sparkContext, Seconds(batchDuration.toLong))
- 以下代码将通过解析上述两个目录中的数据文件创建
trainingData和testData RDDs:
val trainingData = ssc.textFileStream(trainingDir).map(Vectors.parse)
val testData = ssc.textFileStream(testDir).map(LabeledPoint.parse)
- 我们创建
StreamingKMeans模型并设置参数:
val model = new StreamingKMeans()
.setK(numClusters)
.setDecayFactor(1.0)
.setRandomCenters(numDimensions, 0.0)
- 程序将使用训练数据集训练模型,并使用测试数据集进行预测:
model.trainOn(trainingData)
model.predictOnValues(testData.map(lp => (lp.label, lp.features))).print()
- 我们启动流式上下文,程序将每 10 秒运行一次批处理,以检查是否有新的训练数据集可用,以及是否有新的测试数据集用于预测。如果收到终止信号(退出批处理运行),程序将退出。
ssc.start()
ssc.awaitTermination()
- 我们将
testKStreaming1.txt数据文件复制到上述testDir设置中,并在控制台日志中看到以下打印输出:
-
对于 Windows 机器,我们将
testKStreaming1.txt文件复制到了目录:C:\spark-2.0.0-bin-hadoop2.7\data\sparkml2\chapter8\testDir\。 -
我们还可以通过访问
http://localhost:4040/来检查 SparkUI 以获取更多信息。
作业面板将显示流式作业,如图所示:
如图所示,流式面板将显示上述流式 KMeans 矩阵,显示批处理作业每 10 秒运行一次:
您可以通过点击任何批处理,如图所示,获取有关流式批处理的更多详细信息:
工作原理...
在某些情况下,我们不能使用批处理方法来加载和捕获事件,然后对其做出反应。我们可以使用在内存或着陆数据库中捕获事件的创造性方法,然后快速将其转移到另一个系统进行处理,但大多数这些系统无法作为流式系统运行,并且通常构建成本非常高。
Spark 提供了一种近乎实时的(也称为主观实时)方式,可以接收来自 Twitter feeds、信号等的传入源,通过连接器(例如 Kafka 连接器)进行处理,并以 RDD 接口的形式呈现。
这些是构建和构造 Spark 中流式 KMeans 所需的元素:
- 使用流式上下文而不是...
还有更多...
流式 KMeans 是 KMeans 实现的一种特殊情况,其中数据可以近乎实时地到达,并根据需要被分类到集群(硬分类)中。关于 Voronoi 图的参考,请参见以下 URL:en.wikipedia.org/wiki/Voronoi_diagram
目前,Spark 机器学习库中除了流式 KMeans 外还有其他算法,如图所示:
另请参阅
-
流式 KMeans 文档可在
spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.mllib.clustering.StreamingKMeans找到。 -
流式 KMeans 模型文档可在
spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.mllib.stat.test.StreamingTest找到。 -
流式测试文档——对数据生成非常有用——可在
spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.mllib.clustering.StreamingKMeansModel找到。