Spark 2.x 机器学习秘籍(三)
原文:
zh.annas-archive.org/md5/3C1ECF91245FC64E4B95E8DC509841AB译者:飞龙
第四章:实现强大机器学习系统的常见配方
在本章中,我们将涵盖:
-
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 2.0 进行二元分类模型评估
-
使用 Spark 2.0 进行多标签分类模型评估
-
使用 Spark 2.0 进行多类别分类模型评估
-
使用 Scala Breeze 库在 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, "type of correlation"): -
Pearson(默认)
-
Spearman
-
分层抽样 - RDD API:
-
使用替换 RDD
-
无需替换 - 需要额外的传递
-
假设检验:
-
向量 -
Statistics.chiSqTest( vector ) -
矩阵 -
Statistics.chiSqTest( dense matrix ) -
Kolmogorov-Smirnov(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.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}
- 将输出级别设置为
ERROR以减少 Spark 的日志输出:
Logger.getLogger("org").setLevel(Level.ERROR)
Logger.getLogger("akka").setLevel(Level.ERROR)
- 使用构建器模式初始化 Spark 会话,从而为 Spark 集群提供入口点:
val spark = SparkSession
.builder
.master("local[*]")
.appName("My Pipeline")
.config("spark.sql.warehouse.dir", ".")
.getOrCreate()
- 让我们创建一个包含几个随机文本文档的训练集 DataFrame:
val trainset = spark.createDataFrame(Seq(
(1L, 1, "spark rocks"),
(2L, 0, "flink is the best"),
(3L, 1, "Spark rules"),
(4L, 0, "mapreduce forever"),
(5L, 0, "Kafka is great")
)).toDF("id", "label", "words")
- 创建一个标记化器来解析文本文档为单独的词项:
val tokenizer = new Tokenizer()
.setInputCol("words")
.setOutputCol("tokens")
- 创建一个 HashingTF 来将词项转换为特征向量:
val hashingTF = new HashingTF()
.setNumFeatures(1000)
.setInputCol(tokenizer.getOutputCol)
.setOutputCol("features")
- 创建一个逻辑回归类来生成一个模型,以预测一个新的文本文档属于哪个组:
val lr = new LogisticRegression()
.setMaxIter(15)
.setRegParam(0.01)
- 接下来,我们构建一个包含三个阶段的数据流水线:
val pipeline = new Pipeline()
.setStages(Array(tokenizer, hashingTF, lr))
- 现在,我们训练模型,以便稍后进行预测:
val model = pipeline.fit(trainset)
- 让我们创建一个测试数据集来验证我们训练好的模型:
val testSet = spark.createDataFrame(Seq(
(10L, 1, "use spark please"),
(11L, 2, "Kafka")
)).toDF("id", "label", "words")
- 最后,我们使用训练好的模型转换测试集,生成预测:
model.transform(testSet).select("probability", "prediction").show(false)
- 通过停止 Spark 会话来关闭程序:
spark.stop()
它是如何工作的...
在本节中,我们研究了如何使用 Spark 构建一个简单的机器学习流水线。我们首先创建了一个由两组文本文档组成的 DataFrame,然后设置了一个流水线。
首先,我们创建了一个分词器,将文本文档解析为术语,然后创建了 HashingTF 来将术语转换为特征。然后,我们创建了一个逻辑回归对象,以预测新文本文档属于哪个组。
其次,我们通过向其传递参数数组来构建管道,指定三个执行阶段。您会注意到每个后续阶段都将结果提供为指定的列,同时使用前一阶段的输出列作为输入。
最后,我们通过在管道对象上调用fit()并定义一组测试数据来训练模型以进行验证。接下来,我们使用模型转换测试集,确定测试集中的文本文档属于定义的两个组中的哪一个。
还有更多...
Spark ML 中的管道受到了 Python 中 scikit-learn 的启发,这里提供了参考:
ML 管道使得在 Spark 中组合多个算法以实现生产任务变得容易。在现实情况下,很少会看到由单个算法组成的用例。通常,一些合作的 ML 算法一起工作以实现复杂的用例。例如,在基于 LDA 的系统(例如新闻简报)或人类情感检测中,核心系统之前和之后有许多步骤,需要作为单个管道来实现任何有意义且值得投入生产的系统。请参阅以下链接,了解需要使用管道来实现强大系统的真实用例:
www.thinkmind.org/index.php?view=article&articleid=achi_2013_15_50_20241
另见
更多多元统计摘要的文档:
-
管道文档可在
spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.ml.Pipeline找到 -
在加载和保存
.load()、.save()方法时有用的管道模型:spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.ml.PipelineModel -
管道阶段信息可在
spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.ml.PipelineStage找到 -
HashingTF,一种将序列映射到文本分析中的词频的老技巧,可在
spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.mllib.feature.HashingTF找到
使用 Spark 对数据进行归一化
在这个示例中,我们演示了在将数据导入 ML 算法之前对数据进行归一化(缩放)。有很多 ML 算法,比如支持向量机(SVM),它们使用缩放后的输入向量而不是原始值效果更好。
如何做...
-
转到 UCI 机器学习库并下载
archive.ics.uci.edu/ml/machine-learning-databases/wine/wine.data文件。 -
在 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 模式并显示其中包含的数据:
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.SparkSession
import org.apache.log4j.{ Level, Logger}
- 将输出级别设置为
ERROR以减少 Spark 的日志输出:
Logger.getLogger("org").setLevel(Level.ERROR)
Logger.getLogger("akka").setLevel(Level.ERROR)
- 使用构建器模式初始化 Spark 会话,从而为 Spark 集群提供入口点:
val spark = SparkSession
.builder
.master("local[*]")
.appName("Data Splitting")
.getOrCreate()
- 我们首先通过 Spark 会话的
csv()方法加载数据文件,以解析和加载数据到数据集中:
val data = spark.read.csv("../data/sparkml2/chapter4/newsCorpora.csv")
- 现在,我们计算 CSV 加载程序解析并加载到内存中的项目数。我们稍后需要这个值来调和数据拆分。
val rowCount = data.count()
println("rowCount=" + rowCount)
- 接下来,我们利用数据集的
randomSplit方法将数据分成两个桶,每个桶分配 80%和 20%的数据:
val splitData = data.randomSplit(Array(0.8, 0.2))
randomSplit方法返回一个包含两组数据的数组,第一组数据占 80%的训练集,下一组占 20%的测试集:
val trainingSet = splitData(0)
val testSet = splitData(1)
- 让我们为训练集和测试集生成计数:
val trainingSetCount = trainingSet.count()
val testSetCount = testSet.count()
- 现在我们对值进行调和,并注意到原始行数为
415606,训练集和测试集的最终总和等于415606:
println("trainingSetCount=" + trainingSetCount)
println("testSetCount=" + testSetCount)
println("setRowCount=" + (trainingSetCount+testSetCount))
rowCount=415606
trainingSetCount=332265
testSetCount=83341
setRowCount=415606
- 我们通过停止 Spark 会话来关闭程序:
spark.stop()
它是如何工作的...
我们首先加载数据文件newsCorpora.csv,然后通过附加到数据集对象的randomSplit()方法来拆分数据集。
还有更多...
为了验证结果,我们必须建立一个 Delphi 技术,其中测试数据对模型是完全未知的。有关详细信息,请参阅 Kaggle 竞赛www.kaggle.com/competitions。
强大的 ML 系统需要三种类型的数据集:
-
训练数据集:用于将模型拟合到样本
-
验证数据集:用于估计由训练集拟合的模型的增量或预测误差
-
测试数据集:用于在选择最终模型后评估模型的泛化误差
另请参阅
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 及更高版本中数据处理的未来方向。在第三章,Spark 的三大数据武士-机器学习的完美组合中,我们涵盖了三个详细的数据集示例,本章中我们涵盖了一些使用这些新 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就可以添加行为:
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函数对团队数据集进行横向遍历,生成一个新的城市名称数据集:
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 优化器
-
数据集利用了钨的非堆内存管理
-
在接下来的两年中,仍将有许多系统保持在 Spark 2.0 之前,因此出于实际原因,您仍必须学习和掌握 RDD 和 DataFrame。
另请参阅
数据集的文档可在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 会话,从而为 Spark 集群提供入口点:
val spark = SparkSession
.builder
.master("local[*]")
.appName("DatasetvsRDD")
.config("spark.sql.warehouse.dir", ".")
.getOrCreate()
- 在下面的代码块中,我们让 Spark 从文本文件中创建数据集对象。
文本文件包含非常简单的数据(每行包含逗号分隔的 ID 和名称):
import spark.implicits._
val ds = spark.read.textFile("../data/sparkml2/chapter4/beatles.txt").map(line => {
val tokens = line.split(",")
Beatle(tokens(0).toLong, tokens(1))
}).as[Beatle]
我们读取文件并解析文件中的数据。数据集对象由 Spark 创建。我们在控制台中确认类型,然后显示数据:
println("Dataset Type: " + ds.getClass)
ds.show()
从控制台输出:
Dataset Type: class org.apache.spark.sql.Dataset
- 现在我们使用与前一步非常相似的方式创建了一个包含相同数据文件的 RDD:
val rdd = spark.sparkContext.textFile("../data/sparkml2/chapter4/beatles.txt").map(line => {
val tokens = line.split(",")
Beatle(tokens(0).toLong, tokens(1))
})
然后我们确认它是一个 RDD,并在控制台中显示数据:
println("RDD Type: " + rdd.getClass)
rdd.collect().foreach(println)
请注意,方法非常相似但不同。
从控制台输出:
RDD Type: class org.apache.spark.rdd.MapPartitionsRDD
Beatle(1,John)
Beatle(2,Paul)
Beatle(3,George)
Beatle(4,Ringo)
- DataFrame 是 Spark 社区常用的另一种数据结构。我们展示了使用相同的方法基于相同的数据文件创建 DataFrame 的类似方式:
val df = spark.read.text("../data/sparkml2/chapter4/beatles.txt").map(
row => { // Dataset[Row]
val tokens = row.getString(0).split(",")
Beatle(tokens(0).toLong, tokens(1))
}).toDF("bid", "bname")
然后我们确认它是一个 DataFrame。
println("DataFrame Type: " + df.getClass)
df.show()
请注意DataFrame = Dataset[Row],因此类型是 Dataset。
从控制台输出:
DataFrame Type: class org.apache.spark.sql.Dataset
- 我们通过停止 Spark 会话来关闭程序:
spark.stop()
工作原理...
我们使用相同的文本文件使用类似的方法创建了一个 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 来运行数据的二元分类的逻辑回归。这里重点是 LabeledPoint,回归算法在第五章和第六章中有更深入的介绍,Spark 2.0 中的回归和分类的实用机器学习-第 I 部分和Spark 2.0 中的回归和分类的实用机器学习-第 II 部分。
如何做...
-
在 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()
- 我们使用
SparseVector和DenseVector创建 LabeledPoint。在以下代码块中,前四个 LabeledPoints 是由DenseVector创建的,最后两个 LabeledPoints 是由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()函数将集群类型设置为local。提供了一个注释,显示如何在特定端口上运行本地集群。
如果两者都存在,-D选项值将被代码中设置的集群主参数覆盖。
在SparkSession对象中,我们通常使用master()函数,而在 Spark 2.0 之前,在SparkConf对象中,使用setMaster()函数。
以下是连接到不同模式的集群的三种示例方式:
- 在
local模式下运行:
master("local")
- 在集群模式下运行:
master("spark://yourmasterhostIP:port")
- 传递主值:
-Dspark.master=local
- 我们读取一个 CSV 文件,并使用以下代码将 CSV 文件解析为 Spark:
val df = spark.read
.option("header","True")
.csv("../data/sparkml2/chapter4/mySampleCSV.csv")
- 我们在控制台中显示 DataFrame:
df.show()
-
您将在控制台中看到以下内容:
-
然后通过停止 Spark 会话来关闭程序:
spark.stop()
它是如何工作的...
在这个例子中,我们展示了如何使用本地和远程选项连接到 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 2.0 文档中选择了一些我们喜欢和有趣的 API:
Def version: String
此应用程序运行的 Spark 版本:
- Def sql(sqlText: String): DataFrame
使用 Spark 执行 SQL 查询,将结果作为DataFrame返回- 首选 Spark 2.0
- Val sqlContext:SQLContext
SQLContext的包装版本,用于向后兼容。
- 懒惰的 val conf:RuntimeConfig
Spark 的运行时配置界面。
- 懒惰的 val catalog:Catalog
用户可以通过它创建、删除、更改或查询底层数据库、表、函数等的接口。
- Def newSession(): SparkSession
使用隔离的 SQL 配置和临时表启动一个新会话;注册的函数是隔离的,但共享底层的SparkContext和缓存数据。
- **Def udf: UDFRegistration
一组用于注册用户定义函数(UDF)的方法。
我们可以通过 Spark 会话直接创建 DataFrame 和 Dataset。这是有效的,但在 Spark 2.0.0 中被标记为实验性的。
如果您要进行任何与 SQL 相关的工作,现在 SparkSession 是访问 Spark SQL 的入口点。SparkSession 是您必须创建的第一个对象,以创建 Spark SQL 应用程序。
另请参阅
SparkSession API 文档的文档可在spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.sql.SparkSession上找到。
在 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()函数来设置集群主位置。正如您所看到的,我们是在local模式下运行代码。
如果两者都存在,那么-D选项值将被代码中设置的集群主参数覆盖)。
以下是连接到不同模式下集群的三种示例方式:
- 在本地模式下运行:
setMaster("local")
- 在集群模式下运行:
setMaster("spark://yourmasterhostIP:port")
- 通过以下方式传递主值:
-Dspark.master=local
- 我们使用上述 SparkContext 来读取 CSV 文件并使用以下代码将 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 之前,通过SparkContext获取对 Spark 集群的访问。
对子系统的访问,如 SQL,是特定名称上下文(例如,SQLContext**)。
Spark 2.0 通过创建一个统一的访问点(即SparkSession)来改变我们访问集群的方式。
另请参阅
SparkContext 的文档可在spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.SparkContext上找到。
在 Spark 2.0 中通过 SparkSession 对象访问 SparkContext
在这个示例中,我们演示了如何使用 SparkSession 对象在 Spark 2.0 中获取 SparkContext。这个示例将演示从 RDD 到数据集的创建、使用和来回转换。这是重要的原因是,即使我们更喜欢数据集,我们仍然必须能够使用和增强大部分使用 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.SparkSession
import scala.util.Random
- 将输出级别设置为
ERROR以减少 Spark 的日志输出:
Logger.getLogger("org").setLevel(Level.ERROR)
- 使用构建模式初始化 Spark 会话,从而为 Spark 集群提供入口点:
val session = SparkSession
.builder
.master("local[*]")
.appName("SessionContextRDD")
.config("spark.sql.warehouse.dir", ".")
.getOrCreate()
- 我们首先展示了如何使用
sparkContext创建 RDD。以下代码示例在 Spark 1.x 中非常常见:
import session.implicits._
// SparkContext
val context = session.sparkContext
我们获取SparkContext对象:
println("SparkContext")
val rdd1 = context.makeRDD(Random.shuffle(1 to 10).toList)
rdd1.collect().foreach(println)
println("-" * 45)
val rdd2 = context.parallelize(Random.shuffle(20 to 30).toList)
rdd2.collect().foreach(println)
println("\n End of SparkContext> " + ("-" * 45))
我们首先从makeRDD方法创建了rdd1并在控制台中显示了 RDD:
SparkContext
4
6
1
10
5
2
7
3
9
8
然后我们使用parallelize方法生成了rdd2,并在控制台中显示了 RDD 中的数据。
从控制台输出:
25
28
30
29
20
22
27
23
24
26
21
End of SparkContext
- 现在我们展示了使用
session对象创建数据集的方法:
val dataset1 = session.range(40, 50)
dataset1.show()
val dataset2 = session.createDataset(Random.shuffle(60 to 70).toList)
dataset2.show()
我们使用不同的方法生成了dataset1和dataset2。
从控制台输出:
对于 dataset1:
对于 dataset2:
- 我们展示了如何从数据集中检索基础 RDD:
// retrieve underlying RDD from Dataset
val rdd3 = dataset2.rdd
rdd3.collect().foreach(println)
从控制台输出:
61
68
62
67
70
64
69
65
60
66
63
- 以下代码块显示了将 RDD 转换为数据集对象的方法:
// convert rdd to Dataset
val rdd4 = context.makeRDD(Random.shuffle(80 to 90).toList)
val dataset3 = session.createDataset(rdd4)
dataset3.show()
从控制台输出:
- 通过停止 Spark 会话来关闭程序:
session.stop()
它是如何工作的...
我们使用 SparkContext 创建了 RDD;这在 Spark 1.x 中被广泛使用。我们还演示了在 Spark 2.0 中使用 Session 对象创建数据集的方法。在生产中,来回转换是必要的,以处理 Spark 2.0 之前的代码。
这个示例的技术信息是,虽然 DataSet 是未来数据处理的首选方法,但我们始终可以使用 API 来回转换为 RDD,反之亦然。
还有更多...
有关数据类型的更多信息,请访问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 导出的支持目前仅限于:
-
线性回归
-
逻辑回归
-
岭回归
-
套索
-
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 提供了RegressionMetrics工具,它具有基本的统计功能,如均方误差(MSE),R-Squared 等。
这个示例的目标是了解 Spark 开箱即用提供的评估指标。最好集中在第 8 步,因为我们在第五章中更详细地介绍了回归,Spark 2.0 中的实用机器学习-回归和分类第一部分和第六章,Spark 2.0 中的实用机器学习-回归和分类第二部分以及整本书中。
如何做...
-
在 IntelliJ 或您选择的 IDE 中启动一个新项目。确保包含必要的 JAR 文件。
-
设置程序将驻留的包位置:
package spark.ml.cookbook.chapter4
- 导入必要的包以便 SparkContext 可以访问集群:
import org.apache.spark.mllib.evaluation.RegressionMetrics
import org.apache.spark.mllib.linalg.Vectors
import org.apache.spark.mllib.regression.LabeledPoint
import org.apache.spark.mllib.tree.DecisionTree
import org.apache.spark.sql.SparkSession
- 创建 Spark 的配置和 SparkContext:
val spark = SparkSession
.builder
.master("local[*]")
.appName("myRegressionMetrics")
.config("spark.sql.warehouse.dir", ".")
.getOrCreate()
- 我们利用威斯康星州乳腺癌数据集作为回归模型的示例数据集。
威斯康星州乳腺癌数据集是从威斯康星大学医院的 William H Wolberg 博士那里获得的。数据集是定期获得的,因为 Wolberg 博士报告了他的临床病例。
有关数据集的更多详细信息可以在第九章中找到,优化-使用梯度下降进行下坡。
val rawData = spark.sparkContext.textFile("../data/sparkml2/chapter4/breast-cancer-wisconsin.data")
val data = rawData.map(_.trim)
.filter(text => !(text.isEmpty || text.indexOf("?") > -1))
.map { line =>
val values = line.split(',').map(_.toDouble)
val slicedValues = values.slice(1, values.size)
val featureVector = Vectors.dense(slicedValues.init)
val label = values.last / 2 -1
LabeledPoint(label, featureVector)
}
我们将数据加载到 Spark 中,并过滤数据中的缺失值。
- 我们将数据集按 70:30 的比例分割成两个数据集,一个用于训练模型,另一个用于测试模型:
val splits = data.randomSplit(Array(0.7, 0.3))
val (trainingData, testData) = (splits(0), splits(1))
- 我们设置参数并使用
DecisionTree模型,在训练数据集之后,我们使用测试数据集进行预测:
val categoricalFeaturesInfo = Map[Int, Int]()
val impurity = "variance" val maxDepth = 5
val maxBins = 32
val model = DecisionTree.trainRegressor(trainingData, categoricalFeaturesInfo, impurity,
maxDepth, maxBins)
val predictionsAndLabels = testData.map(example =>
(model.predict(example.features), example.label)
)
- 我们实例化
RegressionMetrics对象并开始评估:
val metrics = new RegressionMetrics(predictionsAndLabels)
- 我们在控制台中打印出统计值:
// Squared error
println(s"MSE = ${metrics.meanSquaredError}")
println(s"RMSE = ${metrics.rootMeanSquaredError}")
// R-squared
println(s"R-squared = ${metrics.r2}")
// Mean absolute error
println(s"MAE = ${metrics.meanAbsoluteError}")
// Explained variance
println(s"Explained variance = ${metrics.explainedVariance}")
从控制台输出:
MSE = 0.06071332254584681
RMSE = 0.2464007356844675
R-squared = 0.7444017305996473
MAE = 0.0691747572815534
Explained variance = 0.22591111058744653
- 然后我们通过停止 Spark 会话来关闭程序:
spark.stop()
它是如何工作的...
在这个示例中,我们探讨了生成回归度量标准来帮助我们评估回归模型。我们开始加载一个乳腺癌数据文件,然后将其按 70/30 的比例分割,以创建训练和测试数据集。接下来,我们训练了一个DecisionTree回归模型,并利用它对我们的测试集进行了预测。最后,我们拿到了预测结果,并生成了回归度量标准,这给了我们平方误差,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.apache.org/docs/latest/api/scala/index.html#org.apache.spark.mllib.evaluation.RegressionMetrics上找到。
使用 Spark 2.0 进行二元分类模型评估
在这个示例中,我们演示了在 Spark 2.0 中使用BinaryClassificationMetrics工具以及其应用于评估具有二元结果的模型(例如逻辑回归)。
这里的目的不是展示回归本身,而是演示如何使用常见的度量标准(如接收器操作特征(ROC),ROC 曲线下面积,阈值等)来评估它。
我们建议您专注于第 8 步,因为我们在第五章中更详细地介绍了回归,使用 Spark 2.0 进行回归和分类的实际机器学习-第 I 部分和第六章中更详细地介绍了回归和分类的实际机器学习-第 II 部分*。
如何做...
-
在 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 个特征,其中六个是连续的,八个是分类的。在这个数据集中,连续特征被离散化为分位数,并且每个分位数由一个二进制特征表示。我们修改了数据以适应代码的目的。数据集特征的详细信息可以在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)
- 我们在控制台中按
Threashold打印出精度:
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
- 我们在控制台中打印出
Area Under Precision Recall Curve:
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.apache.org/docs/latest/api/scala/index.html#org.apache.spark.mllib.evaluation.BinaryClassificationMetrics。
使用 Spark 2.0 进行多类分类模型评估
在这个示例中,我们探讨了MulticlassMetrics,它允许您评估将输出分类到两个以上标签的模型(例如,红色、蓝色、绿色、紫色、不知道)。它突出了混淆矩阵(confusionMatrix)和模型准确性的使用。
如何做...
-
在 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.MulticlassMetrics
import org.apache.spark.mllib.regression.LabeledPoint
import org.apache.spark.mllib.util.MLUtils
- 创建 Spark 的配置和 SparkContext:
val spark = SparkSession
.builder
.master("local[*]")
.appName("myMulticlass")
.config("spark.sql.warehouse.dir", ".")
.getOrCreate()
- 我们下载了最初来自 UCI 的数据集,并对其进行修改以适应代码的需要:
// Load training data in LIBSVM format
//https://www.csie.ntu.edu.tw/~cjlin/libsvmtools/datasets/multiclass.html
val data = MLUtils.loadLibSVMFile(spark.sparkContext, "../data/sparkml2/chapter4/myMulticlassIrisData.txt")
数据集是一个修改后的数据集。原始的鸢尾花植物数据集有四个特征。我们修改了数据以适应代码的目的。数据集特征的详细信息可以在 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(3)
.run(training)
- 我们在测试数据集上计算原始分数:
val predictionAndLabels = test.map { case LabeledPoint(label, features) =>
val prediction = model.predict(features)
(prediction, label)
}
- 我们从预测中创建了
MulticlassMetrics对象,并开始对指标进行评估:
val metrics = new MulticlassMetrics(predictionAndLabels)
- 我们在控制台中打印出混淆矩阵:
println("Confusion matrix:")
println(metrics.confusionMatrix)
从控制台输出:
Confusion matrix:
18.0 0.0 0.0
0.0 15.0 8.0
0.0 0.0 22.0
- 我们在控制台中打印出总体统计信息:
val accuracy = metrics.accuracy
println("Summary Statistics")
println(s"Accuracy = $accuracy")
从控制台输出:
Summary Statistics
Accuracy = 0.873015873015873
- 我们在控制台中按标签值打印出精度:
val labels = metrics.labels
labels.foreach { l =>
println(s"Precision($l) = " + metrics.precision(l))
}
从控制台输出:
Precision(0.0) = 1.0
Precision(1.0) = 1.0
Precision(2.0) = 0.7333333333333333
- 我们在控制台中按标签打印出召回率:
labels.foreach { l =>
println(s"Recall($l) = " + metrics.recall(l))
}
从控制台输出:
Recall(0.0) = 1.0
Recall(1.0) = 0.6521739130434783
Recall(2.0) = 1.0
- 我们在控制台中按标签打印出假阳性率:
labels.foreach { l =>
println(s"FPR($l) = " + metrics.falsePositiveRate(l))
}
从控制台输出:
FPR(0.0) = 0.0
FPR(1.0) = 0.0
FPR(2.0) = 0.1951219512195122
- 我们在控制台中按标签打印出 F-度量:
labels.foreach { l =>
println(s"F1-Score($l) = " + metrics.fMeasure(l))
}
从控制台输出:
F1-Score(0.0) = 1.0
F1-Score(1.0) = 0.7894736842105263
F1-Score(2.0) = 0.846153846153846
- 我们在控制台中打印出加权统计值:
println(s"Weighted precision: ${metrics.weightedPrecision}")
println(s"Weighted recall: ${metrics.weightedRecall}")
println(s"Weighted F1 score: ${metrics.weightedFMeasure}")
println(s"Weighted false positive rate: ${metrics.weightedFalsePositiveRate}")
从控制台输出:
Weighted precision: 0.9068783068783068
Weighted recall: 0.873015873015873
Weighted F1 score: 0.8694171325750273
Weighted false positive rate: 0.06813782423538521
- 然后通过停止 Spark 会话来关闭程序:
spark.stop()
它是如何工作的...
在这个教程中,我们探索了为多类别模型生成评估指标。首先,我们将 Iris 数据加载到内存中,并将其按比例 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
每个参数的详细解释超出了范围,但以下链接提供了多标签指标的简短处理:
en.wikipedia.org/wiki/Multi-label_classification
另请参阅
多标签分类指标的文档:
在 Spark 2.0 中使用 Scala Breeze 库进行图形处理
在这个示例中,我们将使用 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。 -
在 Windows 机器上的
C:\spark-2.0.0-bin-hadoop2.7\examples\jars目录中放置 JAR 文件: -
在 macOS 中,请将 JAR 文件放在正确的路径下。对于我们的设置示例,路径为
/Users/USERNAME/spark/spark-2.0.0-bin-hadoop2.7/examples/jars/。 -
以下是显示 JAR 文件的示例截图:
-
在 IntelliJ 或您选择的 IDE 中启动一个新项目。确保包含必要的 JAR 文件。
-
设置程序所在的包位置:
package spark.ml.cookbook.chapter4
- 导入必要的包以便 Spark 会话可以访问集群和
log4j.Logger以减少 Spark 产生的输出量:
import org.apache.log4j.{Level, Logger}
import org.apache.spark.sql.SparkSession
import breeze.plot._
import scala.util.Random
- 将输出级别设置为
ERROR以减少 Spark 的日志输出:
Logger.getLogger("org").setLevel(Level.ERROR)
- 通过使用构建器模式指定配置来初始化 Spark 会话,从而为 Spark 集群提供入口点:
val spark = SparkSession
.builder
.master("local[*]")
.appName("myBreezeChart")
.config("spark.sql.warehouse.dir", ".")
.getOrCreate()
- 现在我们创建图形对象,并设置图形的参数:
import spark.implicits._
val fig = Figure()
val chart = fig.subplot(0)
chart.title = "My Breeze-Viz Chart" chart.xlim(21,100)
chart.ylim(0,100000)
-
我们从随机数创建一个数据集,并显示数据集。
-
数据集将在以后使用。
val ages = spark.createDataset(Random.shuffle(21 to 100).toList.take(45)).as[Int]
ages.show(false)
从控制台输出:
-
我们收集数据集,并设置x和y轴。
-
对于照片部分,我们将数据类型转换为 double,并将值派生为
y2。 -
我们使用 Breeze 库的 scatter 方法将数据放入图表中,并使用 Breeze 的 plot 方法绘制对角线:
val x = ages.collect()
val y = Random.shuffle(20000 to 100000).toList.take(45)
val x2 = ages.collect().map(xx => xx.toDouble)
val y2 = x2.map(xx => (1000 * xx) + (xx * 2))
chart += scatter(x, y, _ => 0.5)
chart += plot(x2, y2)
chart.xlabel = "Age" chart.ylabel = "Income" fig.refresh()
-
我们为x轴和y轴设置标签,并刷新图形对象。
-
以下是生成的 Breeze 图表:
- 通过停止 Spark 会话来关闭程序:
spark.stop()
它是如何工作的...
在这个示例中,我们从随机数中创建了一个 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 2.0 中进行回归和分类的实用机器学习-第一部分
在本章中,我们将涵盖以下内容:
-
将线性回归线拟合到数据的传统方法
-
Spark 2.0 中的广义线性回归
-
Spark 2.0 中具有 Lasso 和 L-BFGS 的线性回归 API
-
Spark 2.0 中具有 Lasso 和自动优化选择的线性回归 API
-
Spark 2.0 中具有岭回归和自动优化选择的线性回归 API
-
Apache Spark 2.0 中的等保回归
-
Apache Spark 2.0 中的多层感知器分类器
-
Apache Spark 2.0 中的一对多分类器(One-vs-All)
-
Apache Spark 2.0 中的生存回归-参数 AFT 模型
介绍
本章与下一章一起,涵盖了 Spark 2.0 ML 和 MLlib 库中可用的回归和分类的基本技术。Spark 2.0 通过将基于 RDD 的回归(见下一章)移动到维护模式来突显新的方向,同时强调线性回归和广义回归。
在高层次上,新的 API 设计更倾向于对弹性网的参数化,以产生岭回归与 Lasso 回归以及两者之间的一切,而不是命名 API(例如,LassoWithSGD)。新的 API 方法是一个更清晰的设计,并迫使您学习弹性网及其在特征工程中的作用,这在数据科学中仍然是一门艺术。我们提供充分的例子、变化和注释,以指导您应对这些技术中的复杂性。
以下图表描述了本章中回归和分类覆盖范围(第一部分):
首先,您将学习如何使用代数方程通过 Scala 代码和 RDD 从头开始实现线性回归,以便了解数学和为什么我们需要迭代优化方法来估计大规模回归系统的解决方案。其次,我们探讨广义线性模型(GLM)及其各种统计分布家族和链接函数,同时强调当前实现中仅限于 4,096 个参数的限制。第三,我们解决线性回归模型(LRM)以及如何使用弹性网参数化来混合和匹配 L1 和 L2 惩罚函数,以实现逻辑回归、岭回归、Lasso 等。我们还探讨了求解器(即优化器)方法以及如何设置它以使用 L-BFGS 优化、自动优化选择等。
在探索 GLM 和线性回归配方之后,我们继续提供更多外来的回归/分类方法的配方,例如等保回归、多层感知器(即神经元网络的形式)、一对多和生存回归,以展示 Spark 2.0 处理线性技术无法解决的情况的能力和完整性。随着 21 世纪初金融世界风险的增加和基因组的新进展,Spark 2.0 还将四种重要方法(等保回归、多层感知器、一对多和生存回归或参数 AFT)整合到一个易于使用的机器学习库中。规模化的参数 AFT 方法应该特别受到金融、数据科学家或精算专业人士的关注。
尽管一些方法,比如LinearRegression() API,从理论上来说自 1.3x+版本就已经可用,但重要的是要注意,Spark 2.0 将它们全部整合到一个易于使用和可维护的 API 中(即向后兼容),以 glmnet R 的方式移动基于 RDD 的回归 API 到维护模式。L-BFGS 优化器和正规方程占据主导地位,而 SGD 在 RDD-based APIs 中可用以实现向后兼容。
弹性网络是首选方法,不仅可以绝对处理 L1(Lasso 回归)和 L2(岭回归)的正则化方法,还可以提供类似拨盘的机制,使用户能够微调惩罚函数(参数收缩与选择)。虽然我们在 1.4.2 中使用了弹性网功能,但 Spark 2.0 将所有内容整合在一起,无需处理每个单独的 API 进行参数调整(根据最新数据动态选择模型时很重要)。当我们开始深入研究这些配方时,我们强烈鼓励用户探索各种参数设置setElasticNetParam()和setSolver()配置,以掌握这些强大的 API。重要的是不要混淆惩罚函数setElasticNetParam(value: Double)(L1,L2,OLs,弹性网:线性混合 L1/L2),这些是正则化或模型惩罚方案与与成本函数优化技术相关的优化(正常,L-BFGS,自动等)技术。
需要注意的是,基于 RDD 的回归仍然非常重要,因为有很多当前的 ML 实现系统严重依赖于以前的 API 体系及其 SGD 优化器。请参阅下一章,了解基于 RDD 的回归的完整处理和教学笔记。
用传统的方法将线性回归线拟合到数据
在这个配方中,我们使用 RDD 和封闭形式公式从头开始编写一个简单的线性方程。我们之所以将这个作为第一个配方,是为了演示您可以始终通过 RDD 实现任何给定的统计学习算法,以实现使用 Apache Spark 的计算规模。
如何做...
-
在 IntelliJ 或您选择的 IDE 中启动一个新项目。确保包含必要的 JAR 文件。
-
设置程序所在的包位置:
package spark.ml.cookbook.chapter5
- 导入必要的包,以便
SparkSession获得对集群的访问,以及log4j.Logger以减少 Spark 产生的输出量:
import org.apache.spark.sql.SparkSession
import scala.math._
import org.apache.log4j.Logger
import org.apache.log4j.Level
- 使用构建模式初始化
SparkSession,指定配置,从而使 Spark 集群的入口点可用:
val spark = SparkSession
.builder
.master("local[4]")
.appName("myRegress01_20")
.config("spark.sql.warehouse.dir", ".")
.getOrCreate()
- 将输出级别设置为
ERROR以减少 Spark 的输出:
Logger.getLogger("org").setLevel(Level.ERROR)
Logger.getLogger("akka").setLevel(Level.ERROR)
- 我们创建两个数组,表示因变量(即
y)和自变量(即x):
val x = Array(1.0,5.0,8.0,10.0,15.0,21.0,27.0,30.0,38.0,45.0,50.0,64.0)
val y = Array(5.0,1.0,4.0,11.0,25.0,18.0,33.0,20.0,30.0,43.0,55.0,57.0)
- 我们使用
sc.parallelize(x)将两个数组转换为 RDD:
val xRDD = sc.parallelize(x)
val yRDD = sc.parallelize(y)
- 在这一步中,我们演示了 RDD 的
zip()方法,它从两个 RDD 中创建因变量/自变量元组*(y,x)*。我们介绍这个函数,因为您经常需要学习如何在机器学习算法中使用成对工作:
val zipedRDD = xRDD.zip(yRDD)
- 为了确保我们理解
zip()功能,让我们来看一下输出,但一定要包括collect()或其他形式的操作,以确保数据按顺序呈现。如果我们不使用操作方法,RDD 的输出将是随机的:
- 这是一个重要的步骤,演示了如何迭代、访问和计算每个成员。为了计算回归线,我们需要计算和、乘积和平均值(即sum(x)、sum(y)和sum (x * y))。
map(_._1).sum()函数是一种机制,RDD 对被迭代,但只考虑第一个元素:
val xSum = zipedRDD.map(_._1).sum()
val ySum = zipedRDD.map(_._2).sum()
val xySum= zipedRDD.map(c => c._1 * c._2).sum()
- 在这一步中,我们继续计算每个 RDD 对成员的平均值以及它们的乘积。这些单独的计算(即mean(x)、mean(y)和mean(xy)*),以及平均平方,将用于计算回归线的斜率和截距。虽然我们可以从前面的统计数据中手动计算平均值,但我们应该确保熟悉 RDD 内在可用的方法:
val n= zipedRDD.count()
val xMean = zipedRDD.map(_._1).mean()
val yMean = zipedRDD.map(_._2).mean()
val xyMean = zipedRDD.map(c => c._1 * c._2).mean()
- 这是最后一步,我们计算
x和y的平方的平均值:
val xSquaredMean = zipedRDD.map(_._1).map(x => x * x).mean()
val ySquaredMean = zipedRDD.map(_._2).map(y => y * y).mean()
- 我们打印统计信息以供参考:
println("xMean yMean xyMean", xMean, yMean, xyMean)
xMean yMean xyMean ,26.16,25.16,989.08
- 我们计算公式的
分子和分母:
val numerator = xMean * yMean - xyMean
val denominator = xMean * xMean - xSquaredMean
- 我们最终计算回归线的斜率:
val slope = numerator / denominator
println("slope %f5".format(slope))
slope 0.9153145
- 现在我们计算截距并打印。如果你不想要截距(截距设置为
0),那么斜率的公式需要稍作修改。你可以在其他来源(如互联网)中寻找更多细节并找到所需的方程:
val b_intercept = yMean - (slope*xMean)
println("Intercept", b_intercept)
Intercept,1.21
- 使用斜率和截距,我们将回归线方程写成如下形式:
Y = 1.21 + .9153145 * X
它是如何工作的...
我们声明了两个 Scala 数组,将它们并行化为两个分开的x()和y()的 RDD,然后使用 RDD API 中的zip()方法产生了一个成对的(即,压缩的)RDD。它产生了一个 RDD,其中每个成员都是一个*(x,y)*对。然后我们继续计算均值,总和等,并应用上述封闭形式的公式来找到回归线的截距和斜率。
在 Spark 2.0 中,另一种选择是直接使用 GLM API。值得一提的是,GLM 支持的封闭正态形式方案的最大参数数量限制为 4,096。
我们使用了封闭形式的公式来证明与一组数字(*Y1,X1),...,(Yn,Xn)*相关联的回归线简单地是最小化平方误差和的线。在简单的回归方程中,该线如下:
-
回归线的斜率
-
回归线的偏移
-
回归线的方程
回归线简单地是最小化平方误差和的最佳拟合线。对于一组点(因变量,自变量),有许多直线可以穿过这些点并捕捉到一般的线性关系,但只有其中一条线是最小化所有拟合误差的线。
例如,我们呈现了线Y = 1.21 + .9153145 * X。下图显示了这样一条线,我们使用封闭形式的公式计算了斜率和偏移。线性模型由线性方程表示,代表了我们使用封闭形式公式得到的给定数据的最佳线性模型(斜率=.915345,截距=1.21):
在前面的图中绘制的数据点如下:
(Y, X)
(5.0, 1.0)
(8.0, 4.0)
(10.0, 11.0)
(15.0, 25.0)
(21.0, 18.0)
(27.0, 33.0)
(30.0, 20.0)
(38.0, 30.0)
(45.0, 43.0)
(50.0, 55.0)
(64.0, 57.0)
还有更多...
值得注意的是,并非所有的回归形式都有封闭形式的公式,或者在大型数据集上变得非常低效(即,不切实际)-这就是我们使用 SGD 或 L-BFGS 等优化技术的原因。
从之前的教程中,你应该确保缓存与机器学习算法相关的任何 RDD 或数据结构,以避免由于 Spark 优化和维护血统(即,延迟实例化)的方式而导致的延迟实例化。
另请参阅
我们推荐一本来自斯坦福大学的书,可以从以下网站免费下载。无论你是新手还是高级从业者,这都是一本经典必读的书:
《统计学习的要素,数据挖掘,推断和预测,第二版》,Hastie,Tibshirani 和 Friedman(2009)。Springer-Verlag (web.stanford.edu/~hastie/ElemStatLearn/)。
Spark 2.0 中的广义线性回归
本教程涵盖了 Spark 2.0 中的广义回归模型(GLM)实现。Spark 2.0 中的GeneralizedLinearRegression与 R 中的glmnet实现之间存在很大的相似性。这个 API 是一个受欢迎的补充,允许你选择和设置分布族(例如,高斯)和链接函数(例如,反对数)的 API。
如何做...
-
我们使用了 UCI 机器库存储中的房屋数据集。
-
从以下网址下载整个数据集:
数据集由 14 列组成,前 13 列是自变量(即特征),试图解释美国波士顿自住房的中位价格(即最后一列)。
我们已经选择并清理了前八列作为特征。我们使用前 200 行来训练和预测房价的中位数:
-
- CRIM:按城镇人均犯罪率
-
ZN:超过 25,000 平方英尺的住宅用地比例
-
INDUS:每个城镇的非零售业务面积比例
-
CHAS:查尔斯河虚拟变量(如果地块与河流相接则为 1;否则为 0)
-
NOX:一氧化氮浓度(每千万份之一)
-
RM:每个住宅的平均房间数
-
AGE:1940 年前建成的自住单位比例
- 请使用
housing8.csv文件,并确保将其移动到以下目录:
../data/sparkml2/chapter5/housing8.csv
-
在 IntelliJ 或您选择的 IDE 中开始一个新项目。确保包含必要的 JAR 文件。
-
设置程序所在的包位置:
package spark.ml.cookbook.chapter5.
- 导入
SparkSession所需的必要包,以便访问集群和log4j.Logger以减少 Spark 产生的输出量:
import org.apache.spark.ml.feature.LabeledPoint
import org.apache.spark.ml.linalg.Vectors
import org.apache.spark.ml.regression.GeneralizedLinearRegression
import org.apache.spark.sql.SparkSession
import org.apache.log4j.{Level, Logger}
- 将输出级别设置为
ERROR以减少 Spark 的日志输出:
Logger.getLogger("org").setLevel(Level.ERROR)
Logger.getLogger("akka").setLevel(Level.ERROR)
- 初始化
SparkSession指定配置以访问 Spark 集群:
val spark = SparkSession
.builder
.master("local[*]")
.appName("GLR")
.config("spark.sql.warehouse.dir", ".")
.getOrCreate()
- 我们需要导入数据转换例程的隐式:
import spark.implicits._
- 接下来,我们将住房数据加载到数据集中:
val data = spark.read.textFile( "../data/sparkml2/ /chapter5/housing8.csv" ).as[ String ]
- 让我们解析住房数据并将其转换为标签点:
val regressionData = data.map { line =>
val columns = line.split(',')
LabeledPoint(columns(13).toDouble , Vectors.dense(columns(0).toDouble,columns(1).toDouble, columns(2).toDouble, columns(3).toDouble,columns(4).toDouble,
columns(5).toDouble,columns(6).toDouble, columns(7).toDouble))
}
- 现在使用以下代码显示加载的数据:
regressionData.show(false)
输出如下所示:
- 接下来,我们为生成一个新模型配置了一个广义线性回归算法:
val glr = new GeneralizedLinearRegression()
.setMaxIter(1000)
.setRegParam(0.03) //the value ranges from 0.0 to 1.0\. Experimentation required to identify the right value.
.setFamily("gaussian")
.setLink( "identity" )
请随意尝试不同的参数以获得更好的拟合效果。
- 我们将模型拟合到住房数据:
val glrModel = glr.fit(regressionData)
- 然后,我们检索摘要数据以判断模型的准确性:
val summary = glrModel.summary
- 最后,我们打印出摘要统计信息:
val summary = glrModel.summary
summary.residuals().show()
println("Residual Degree Of Freedom: " + summary.residualDegreeOfFreedom)
println("Residual Degree Of Freedom Null: " + summary.residualDegreeOfFreedomNull)
println("AIC: " + summary.aic)
println("Dispersion: " + summary.dispersion)
println("Null Deviance: " + summary.nullDeviance)
println("Deviance: " +summary.deviance)
println("p-values: " + summary.pValues.mkString(","))
println("t-values: " + summary.tValues.mkString(","))
println("Coefficient Standard Error: " + summary.coefficientStandardErrors.mkString(","))
}
- 通过停止
SparkSession来关闭程序:
spark.stop()
工作原理...
在这个示例中,我们展示了广义线性回归算法的运行情况。我们首先将 CSV 文件加载和解析为数据集。接下来,我们创建了一个广义线性回归算法,并通过将数据集传递给fit()方法来生成一个新模型。完成拟合操作后,我们从模型中检索摘要统计信息,并显示计算出的值以调整准确性。
在这个例子中,我们探索了使用高斯分布和身份拟合数据,但还有许多其他配置可以用来解决特定的回归拟合问题,这些将在下一节中解释。
还有更多...
Spark 2.0 中的 GLM 是一个通用的回归模型,可以支持许多配置。我们对 Spark 2.0.0 初始版本提供的众多系列印象深刻。
重要的是要注意,截至 Spark 2.0.2:
-
目前回归的最大参数数量限制为 4,096 个。
-
目前唯一支持的优化(即求解器)是迭代重新加权最小二乘法(IRLS),这也是默认求解器。
-
当您将求解器设置为auto时,它默认为 IRLS。
-
setRegParam()设置 L2 正则化的正则化参数。根据 Spark 2.0 文档,正则化项为0.5 * regParam * L2norm(coefficients)² - 请确保您理解其影响。
如果您不确定如何处理分布拟合,我们强烈推荐我们最喜欢的书之一,用 R 拟合统计分布手册,在建模芝加哥期货交易所小麦等农产品时为我们提供了很好的帮助,它具有反向波动率笑曲线(与股票非常不同)。
配置和可用选项如下:
一定要尝试不同的族和链接函数,以确保您对基础分布的假设是正确的。
另请参阅
GeneralizedLinearRegression()的文档可在以下链接找到:
GeneralizedLinearRegression中的一些重要 API 调用:
-
def **setFamily**(value: String): GeneralizedLinearRegression.this.type -
def **setLink**(value: String): GeneralizedLinearRegression.this.type -
def **setMaxIter**(value: Int): GeneralizedLinearRegression.this.type -
def **setRegParam**(value: Double): GeneralizedLinearRegression.this.type -
def **setSolver**(value: String): GeneralizedLinearRegression.this.type -
def **setFitIntercept**(value: Boolean): GeneralizedLinearRegression.this.type
求解器目前是 IRLS;快速参考可在以下链接找到:
en.wikipedia.org/wiki/Iteratively_reweighted_least_squares
要完全理解 Spark 2.0+中 GLM 和线性回归的新方法,请务必参考并了解 R 中 CRAN glmnet 的实现:
Spark 2.0 中带有 Lasso 和 L-BFGS 的线性回归 API
在这个示例中,我们将演示如何使用 Spark 2.0 的LinearRegression() API 来展示一个统一/参数化的 API,以全面的方式处理线性回归,能够在不会出现 RDD 命名 API 的向后兼容问题的情况下进行扩展。我们展示如何使用setSolver()将优化方法设置为一阶内存高效的 L-BFGS,它可以轻松处理大量参数(即在稀疏配置中)。
在这个示例中,.setSolver()设置为lbgfs,这使得 L-BFGS(详见基于 RDD 的回归)成为选择的优化方法。.setElasticNetParam()未设置,因此默认值0仍然有效,这使得这是一个 Lasso 回归。
如何做...
-
我们使用 UCI 机器库存储中的住房数据集。
-
从以下链接下载整个数据集:
数据集由 14 列组成,前 13 列是独立变量(即特征),试图解释美国波士顿自住房的中位价格(即最后一列)。
我们选择并清理了前八列作为特征。我们使用前 200 行来训练和预测中位价格:
-
- CRIM:按城镇划分的人均犯罪率
-
ZN:用于 25,000 平方英尺以上地块的住宅用地比例
-
INDUS:每个城镇的非零售业务土地比例
-
CHAS:查尔斯河虚拟变量(如果地块与河流相接,则为
1;否则为0) -
NOX:一氧化氮浓度(每千万分之一)
-
RM:每个住宅的平均房间数
-
AGE:1940 年前建成的自住单位比例
- 请使用
housing8.csv文件,并确保将其移动到以下目录:
../data/sparkml2/chapter5/housing8.csv
-
在 IntelliJ 或您选择的 IDE 中启动一个新项目。确保包含必要的 JAR 文件。
-
设置程序所在的包位置:
package spark.ml.cookbook.chapter5.
- 导入必要的包,以便
SparkSession访问集群和log4j.Logger减少 Spark 产生的输出量:
import org.apache.spark.ml.regression.LinearRegression
import org.apache.spark.ml.feature.LabeledPoint
import org.apache.spark.sql.SparkSession
import org.apache.spark.ml.linalg.Vectors
import org.apache.log4j.{Level, Logger}
- 将输出级别设置为
ERROR以减少 Spark 的日志输出:
Logger.getLogger("org").setLevel(Level.ERROR)
Logger.getLogger("akka").setLevel(Level.ERROR)
- 初始化
SparkSession,指定配置以访问 Spark 集群:
val spark = SparkSession
.builder
.master("local[*]")
.appName("myRegress02")
.config("spark.sql.warehouse.dir", ".")
.getOrCreate()
- 我们需要导入数据转换例程的隐式:
import spark.implicits._
- 接下来,我们将房屋数据加载到数据集中:
val data = spark.read.text(
"../data/sparkml2/chapter5/housing8.csv"
).as[
String
]
- 让我们解析房屋数据并将其转换为标签点:
val RegressionDataSet = data.map { line =>
val columns = line.split(',')
LabeledPoint(columns(13).toDouble , Vectors.dense(columns(0).toDouble,columns(1).toDouble, columns(2).toDouble, columns(3).toDouble,columns(4).toDouble,
columns(5).toDouble,columns(6).toDouble, columns(7).toDouble
))
}
- 现在显示加载的数据:
RegressionDataSet.show(false)
输出如下所示:
- 接下来,我们配置线性回归算法以生成模型:
val numIterations = 10
val lr = new LinearRegression()
.setMaxIter(numIterations)
.setSolver("l-bfgs")
- 现在我们将模型拟合到房屋数据中:
val myModel = lr.fit(RegressionDataSet)
- 接下来,我们检索摘要数据以调和模型的准确性:
val summary = myModel.summary
- 最后,我们打印出摘要统计信息:
println ( "training Mean Squared Error = " + summary. meanSquaredError )
println("training Root Mean Squared Error = " + summary.rootMeanSquaredError) }
training Mean Squared Error = 13.608987362865541
training Root Mean Squared Error = 3.689036102136375
- 通过停止
SparkSession来关闭程序:
spark.stop()
它是如何工作的...
在这个示例中,我们再次使用房屋数据来演示 Spark 2.0 的LinearRegression()API,使用 L-BFGS 优化选项。我们读取文件,解析数据,并选择回归的特定列。我们通过接受默认参数来保持示例简短,但在运行.fit()方法之前,将迭代次数(用于收敛到解决方案)和优化方法设置为lbfgs。然后,我们继续输出一些快速指标(即 MSE 和 RMSE)仅用于演示。我们展示了如何使用 RDD 自己实现/计算这些指标。使用 Spark 2.0 的本机功能/指标和基于 RDD 的回归示例,我们展示了 Spark 现在可以直接完成这些指标,这证明了我们从 Spark 1.0.1 走过了多远!
对于少量列使用牛顿优化技术(例如lbfgs)是一种过度,稍后在本书中进行演示,以便读者能够在实际环境中的大型数据集上使用这些示例(例如,从第一章中提到的典型癌症/基因组数据)。
还有更多...
弹性网(由 DB Tsai 和其他人贡献)和由 Alpine Labs 推广的技术从 Spark 1.4 和 1.5 开始引起了我们的关注,现在已成为 Spark 2.0 中的事实标准技术。
为了水平设置,弹性网是 L1 和 L2 惩罚的线性组合。它可以在概念上被建模为一个可以决定在惩罚中包含多少 L1 和多少 L2 的旋钮(收缩与选择)。
我们想强调的是,现在我们可以通过参数设置来选择回归类型,而不是命名的 API。这是与基于 RDD 的 API(即现在处于维护模式)的重要分歧,我们稍后在本章中进行演示。
以下表格提供了一个快速设置参数的备忘单,以在 Lasso、Ridge、OLS 和弹性网之间进行选择。
请参阅以下表格setElasticNetParam(value: Double):
| 回归类型 | 惩罚 | 参数 |
|---|---|---|
| Lasso | L1 | 0 |
| Ridge | L2 | 1 |
| 弹性网 | L1 + L2 | 0.0 < alpha < 1.0 |
| OLS | 普通最小二乘法 | 无 |
通过以下简要处理来了解正则化是如何通过弹性网参数(对应于 Alpha)来控制的:
另请参阅
-
LinearRegression()的文档:spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.ml.regression.LinearRegression -
一定要查看实际的源代码,因为它扩展了* Regressor*本身:
github.com/apache/spark/blob/v2.0.2/mllib/src/main/scala/org/apache/spark/ml/regression/LinearRegression.scala -
LinearRegression中一些重要的 API 调用: -
def setElasticNetParam(value: Double): LinearRegression.this.type -
def **setRegParam**(value: Double): LinearRegression.this.type -
def **setSolver**(value: String): LinearRegression.this.type -
def **setMaxIter**(value: Int): LinearRegression.this.type -
def **setFitIntercept**(value: Boolean): LinearRegression.this.type
Spark ML 的一个重要方面是其简单而异常强大的 API 集,它允许开发人员在现有集群上轻松扩展到数十亿行,而几乎不需要额外的工作。您会惊讶于 Lasso 可以用于发现相关特征集的规模,而 L-BFGS 优化(不需要直接的海森矩阵存在)可以轻松处理大量特征。Spark 2.0 源代码中 LBFGS 的updater实现的细节超出了本书的范围。
由于其复杂性,我们将在后续章节中介绍与这些 ML 算法相关的优化。
Spark 2.0 中具有 Lasso 和'auto'优化选择的线性回归 API
在这个配方中,我们通过显式选择 LASSO 回归setElasticNetParam(0.0)来构建上一个配方LinearRegression,同时让 Spark 2.0 使用setSolver('auto')自行选择优化。*我们再次提醒,基于 RDD 的回归 API 现在处于维护模式,这是未来的首选方法。
如何做...
-
我们使用 UCI 机器库存储的住房数据集。
-
从以下网址下载整个数据集:
数据集由 14 列组成,前 13 列是独立变量(即特征),试图解释美国波士顿自有住房的中位价格(即最后一列)。
我们已经选择并清理了前八列作为特征。我们使用前 200 行来训练和预测中位数价格:
-
- CRIM: 按城镇划分的人均犯罪率
-
ZN: 用于超过 25,000 平方英尺的住宅用地比例
-
INDUS: 每个城镇的非零售业务面积比例
-
CHAS: 查尔斯河虚拟变量(如果地块边界河流,则为
1;否则为0) -
NOX: 一氧化氮浓度(每 1000 万份之一)
-
RM: 每个住宅的平均房间数
-
AGE: 1940 年前建造的自有住房的比例
- 请使用
housing8.csv文件,并确保将其移动到以下目录:
../data/sparkml2/chapter5/housing8.csv
-
在 IntelliJ 或您选择的 IDE 中开始一个新项目。确保包含必要的 JAR 文件。
-
设置程序所在的包位置:
package spark.ml.cookbook.chapter5.
- 导入必要的包,以便
SparkSession访问集群和Log4j.Logger减少 Spark 产生的输出量:
import org.apache.spark.ml.regression.LinearRegression
import org.apache.spark.ml.feature.LabeledPoint
import org.apache.spark.sql.SparkSession
import org.apache.spark.ml.linalg.Vectors
import org.apache.log4j.{Level, Logger}
- 将输出级别设置为
ERROR以减少 Spark 的日志输出:
Logger.getLogger("org").setLevel(Level.ERROR)
Logger.getLogger("akka").setLevel(Level.ERROR)
- 初始化一个
SparkSession,指定配置以访问 Spark 集群:
val spark = SparkSession
.builder
.master("local[*]")
.appName("myRegress03")
.config("spark.sql.warehouse.dir", ".")
.getOrCreate()
- 我们需要导入数据转换例程的隐式:
import spark.implicits._
- 接下来,我们将住房数据加载到数据集中:
val data = spark.read.text( "../data/sparkml2/chapter5/housing8.csv" ).as[ String ]
- 让我们解析住房数据并将其转换为标签点:
val RegressionDataSet = data.map { line =>
val columns = line.split(',')
LabeledPoint(columns(13).toDouble , Vectors.dense(columns(0).toDouble,columns(1).toDouble, columns(2).toDouble, columns(3).toDouble,columns(4).toDouble,
columns(5).toDouble,columns(6).toDouble, columns(7).toDouble
))
}
- 现在显示加载的数据:
- 接下来,我们配置一个线性回归算法来生成模型:
val lr = new LinearRegression()
.setMaxIter(1000)
.setElasticNetParam(0.0)
.setRegParam(0.01)
.setSolver( "auto" )
- 现在我们将模型拟合到住房数据中:
val myModel = lr.fit(RegressionDataSet)
- 接下来,我们检索摘要数据以调和模型的准确性:
val summary = myModel.summary
- 最后,我们打印摘要统计信息:
println ( "training Mean Squared Error = " + summary. meanSquaredError )
println("training Root Mean Squared Error = " + summary.rootMeanSquaredError) }
training Mean Squared Error = 13.609079490110766
training Root Mean Squared Error = 3.6890485887435482
- 我们通过停止
SparkSession来关闭程序:
spark.stop()
它是如何工作的...
我们读取住房数据并加载选定的列,并使用它们来预测住房单位的价格。我们使用以下代码片段选择回归为 LASSO,并让 Spark 自行选择优化:
val lr = new LinearRegression()
.setMaxIter(1000)
.setElasticNetParam(0.0)
.setRegParam(0.01)
.setSolver( "auto" )
我们将setMaxIter()更改为1000以进行演示。默认设置为100。
还有更多...
虽然 Spark 对 L-BFGS 有很好的实现,请参阅以下链接,快速了解 BFGS 及其内部工作原理,因为它与这个示例相关:
-
BFGS 的简单处理:
en.wikipedia.org/wiki/Broyden-Fletcher-Goldfarb-Shanno_algorithm -
来自机器学习研究杂志,也是一个很好的有限内存 BGFS 处理的数学编程视角:
www.jmlr.org/papers/volume14/hennig13a/hennig13a.pdf
还可以查看基于 RDD 的回归示例,以了解有关 LBGFS 的更多详细信息。如果您需要了解 BFGS 技术的实现细节,请参考以下链接。
这个 C 语言实现帮助我们在代码级别开发对一阶优化的扎实理解:www.chokkan.org/software/liblbfgs/
-
cctbx还提供了很好的实现细节,如果您需要了解更多:
cctbx.sourceforge.net -
哈佛大学关于 R 中 L-BFGS 的很好的处理:
cran.r-project.org/web/packages/lbfgs/vignettes/Vignette.pdf
另请参阅
-
LinearRegression()的文档:spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.ml.regression.LinearRegression -
BFGS 和 L-BFGS 的文档:
-
en.wikipedia.org/wiki/Broyden-Fletcher-Goldfarb-Shanno_algorithm
Spark 2.0 中具有岭回归和“自动”优化选择的线性回归 API
在这个示例中,我们使用LinearRegression接口实现岭回归。我们使用弹性网参数来设置适当的值以进行完整的 L2 惩罚,从而相应地选择岭回归。
如何做到...
-
我们使用 UCI 机器库存储的住房数据集。
-
从以下网址下载整个数据集:
数据集由 14 列组成,前 13 列是独立变量(即特征),试图解释美国波士顿自住房的中位价格(即最后一列)。
我们选择并清理了前八列作为特征。我们使用前 200 行来训练和预测中位价格:
-
- CRIM:按城镇计算的人均犯罪率
-
ZN:用于超过 25,000 平方英尺的地块的住宅用地比例
-
INDUS:每个城镇非零售业务英亩的比例
-
CHAS:查尔斯河虚拟变量(如果地区与河流相接则为 1;否则为 0)
-
NOX:一氧化氮浓度(每千万分之一)
-
RM:每个住宅的平均房间数
-
AGE:1940 年前建成的自住单位比例
- 请使用
housing8.csv文件,并确保将其移动到以下目录:
../data/sparkml2/chapter5/housing8.csv
-
在 IntelliJ 或您选择的 IDE 中启动一个新项目。确保包含必要的 JAR 文件。
-
设置程序将驻留的包位置:
package spark.ml.cookbook.chapter5.
- 导入必要的包以便
SparkSession访问集群和Log4j.Logger减少 Spark 产生的输出量:
import org.apache.spark.ml.feature.LabeledPoint
import org.apache.spark.ml.linalg.Vectors
import org.apache.spark.ml.regression.LinearRegression
import org.apache.spark.sql.SparkSession
import org.apache.log4j.{Level, Logger}
- 将输出级别设置为
ERROR以减少 Spark 的日志输出:
Logger.getLogger("org").setLevel(Level.ERROR)
Logger.getLogger("akka").setLevel(Level.ERROR)
- 初始化
SparkSession,指定配置以访问 Spark 集群:
val spark = SparkSession
.builder
.master("local[*]")
.appName("myRegress04")
.config("spark.sql.warehouse.dir", ".")
.getOrCreate()
- 我们需要导入数据转换例程的隐式:
import spark.implicits._
- 接下来,我们将房屋数据加载到数据集中:
val data = spark.read.text( "../data/sparkml2/chapter5/housing8.csv" ).as[ String ]
- 让我们解析房屋数据并将其转换为标签点:
val RegressionDataSet = data.map { line =>
val columns = line.split(',')
LabeledPoint(columns(13).toDouble , Vectors.dense(columns(0).toDouble,columns(1).toDouble, columns(2).toDouble, columns(3).toDouble,columns(4).toDouble,
columns(5).toDouble,columns(6).toDouble, columns(7).toDouble
))
}
- 现在显示加载的数据:
- 接下来,我们配置线性回归算法以生成模型:
val lr = new LinearRegression()
.setMaxIter(1000)
.setElasticNetParam(1.0)
.setRegParam(0.01)
.setSolver( "auto" )
- 现在,我们将模型拟合到房屋数据:
val myModel = lr.fit(RegressionDataSet)
- 接下来,我们检索摘要数据以调和模型的准确性:
val summary = myModel.summary
- 最后,我们打印出摘要统计信息:
println ( "training Mean Squared Error = " + summary. meanSquaredError )
println("training Root Mean Squared Error = " + summary.rootMeanSquaredError) }
training Mean Squared Error = 13.61187856748311
training Root Mean Squared Error = 3.6894279458315906
- 通过停止
SparkSession来关闭程序:
spark.stop()
它是如何工作的...
我们通过读取房屋数据并加载适当的列来加载数据。然后,我们继续设置将强制LinearRegression()执行岭回归的参数,同时保持优化为'auto'。以下代码显示了如何使用线性回归 API 来设置所需的回归类型为岭回归:
val lr = new LinearRegression()
.setMaxIter(1000)
.setElasticNetParam(1.0)
.setRegParam(0.01)
.setSolver( "auto" )
然后我们使用.fit()将模型拟合到数据。最后,我们使用.summary提取模型摘要并打印模型的 MSE 和 RMSE。
还有更多...
为了确保我们清楚岭回归和 Lasso 回归之间的区别,我们必须首先强调参数收缩(即,我们使用平方根函数压缩权重,但从不将其设置为零)和特征工程或参数选择之间的区别(即,我们将参数收缩到0,从而导致一些参数从模型中完全消失):
-
弹性网络 - 斯坦福大学:
web.stanford.edu/~hastie/TALKS/enet_talk.pdf
另请参阅
线性回归文档:spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.ml.regression.LinearRegression
Apache Spark 2.0 中的等渗回归
在这个示例中,我们演示了 Spark 2.0 中的IsotonicRegression()函数。当数据中期望有顺序并且我们想要将递增的有序线(即,表现为阶梯函数)拟合到一系列观察中时,使用等渗或单调回归。术语等渗回归(IR)和单调回归(MR)在文献中是同义的,可以互换使用。
简而言之,我们尝试使用IsotonicRegression()配方提供比朴素贝叶斯和 SVM 的一些缺点更好的拟合。虽然它们都是强大的分类器,但朴素贝叶斯缺乏 P(C | X)的良好估计,支持向量机(SVM)最多只提供代理(可以使用超平面距离),在某些情况下并不是准确的估计器。
如何做...
- 转到网站下载文件并将文件保存到以下代码块中提到的数据路径。我们使用著名的鸢尾花数据并将一步线拟合到观察中。我们使用库中以
LIBSVM格式的鸢尾花数据来演示 IR。
我们选择的文件名是iris.scale.txt www.csie.ntu.edu.tw/~cjlin/libsvmtools/datasets/multiclass/iris.scale。
-
在 IntelliJ 或您选择的 IDE 中启动新项目。确保包含必要的 JAR 文件。
-
设置程序将驻留的包位置:
package spark.ml.cookbook.chapter5
- 导入必要的包,以便
SparkSession可以访问集群和Log4j.Logger以减少 Spark 产生的输出量:
import org.apache.spark.sql.SparkSession
import org.apache.spark.ml.regression.IsotonicRegression
- 将输出级别设置为
ERROR以减少 Spark 的日志输出:
Logger.getLogger("org").setLevel(Level.ERROR)
Logger.getLogger("akka").setLevel(Level.ERROR)
- 使用构建模式初始化
SparkSession,从而使 Spark 集群的入口点可用:
val spark = SparkSession
.builder
.master("local[4]")
.appName("myIsoTonicRegress")
.config("spark.sql.warehouse.dir", ".")
.getOrCreate()
- 然后我们读入数据文件,打印出数据模式,并在控制台中显示数据:
val data = spark.read.format("libsvm")
.load("../data/sparkml2/chapter5/iris.scale.txt")
data.printSchema()
data.show(false)
我们得到以下控制台输出:
- 然后,我们将数据集分割为训练集和测试集,比例为0.7:0.3:
val Array(training, test) = data.randomSplit(Array(0.7, 0.3), seed = System.currentTimeMillis())
- 接下来,我们创建
IsotonicRegression对象并将其拟合到训练数据中:
val itr = new IsotonicRegression()
val itrModel = itr.fit(training)
- 现在我们在控制台中打印出模型边界和预测:
println(s"Boundaries in increasing order: ${itrModel.boundaries}")
println(s"Predictions associated with the boundaries: ${itrModel.predictions}")
我们得到以下控制台输出:
Boundaries in increasing order: [-1.0,-0.666667,-0.666667,-0.5,-0.5,-0.388889,-0.388889,-0.333333,-0.333333,-0.222222,-0.222222,-0.166667,-0.166667,0.111111,0.111111,0.333333,0.333333,0.5,0.555555,1.0]
Predictions associated with the boundaries: [1.0,1.0,1.1176470588235294,1.1176470588235294,1.1666666666666663,1.1666666666666663,1.3333333333333333,1.3333333333333333,1.9,1.9,2.0,2.0,2.3571428571428577,2.3571428571428577,2.5333333333333314,2.5333333333333314,2.7777777777777786,2.7777777777777786,3.0,3.0]
- 我们让模型转换测试数据并显示结果:
itrModel.transform(test).show()
我们得到以下控制台输出:
- 我们通过停止
SparkSession来关闭程序:
spark.stop()
它是如何工作的...
在这个例子中,我们探索了等距回归模型的特性。我们首先以libsvm格式将数据集文件读入 Spark。然后我们分割数据(70/30)并进行下一步。接下来,我们通过调用.show()函数在控制台中显示 DataFrame。然后,我们创建了IsotonicRegression()对象,并通过调用fit(data)函数让模型自行运行。在这个示例中,我们保持简单,没有改变任何默认参数,但读者应该进行实验,并使用 JChart 包来绘制线条,看看增长和阶梯线条结果的影响。
最后,我们在控制台中显示了模型边界和预测,并使用模型转换测试数据集,并在控制台中显示包含预测字段的结果 DataFrame。所有 Spark ML 算法对超参数值都很敏感。虽然设置这些参数没有硬性规定,但在投入生产之前需要进行大量的科学方法实验。
我们在前几章中介绍了 Spark 提供的许多模型评估设施,并在整本书中讨论了评估指标,而没有重复。Spark 提供以下模型评估方法。开发人员必须根据正在评估的算法类型(例如,离散、连续、二进制、多类等)选择特定的评估指标设施。
我们将单独使用配方来覆盖评估指标,但请参阅以下链接,了解 Spark 的模型评估覆盖范围:spark.apache.org/docs/latest/mllib-evaluation-metrics.html。
还有更多...
在撰写本文时,Spark 2.0 实现具有以下限制:
- 仅支持单特征(即单变量)算法:
def setFeaturesCol(value: String): IsotonicRegression.this.type
-
目前的实现设置为并行池相邻违反者算法(PAVA):
-
截至 Spark 2.1.0,它是单变量单调实现
-
查看 CRAN 实现,如 Spark 2.0:
cran.r-project.org/web/packages/isotone/vignettes/isotone.pdf -
参见 UCLA 论文(PAVA):
gifi.stat.ucla.edu/janspubs/2009/reports/deleeuw_hornik_mair_R_09.pdf -
参见威斯康星大学:
www.biostat.wisc.edu/sites/default/files/tr_116.pdf -
等距回归的文档:
-
spark.apache.org/docs/latest/ml-classification-regression.html#isotonic-regression -
spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.ml.regression.IsotonicRegression
另请参阅
有关保序回归的更多信息,请参见:
en.wikipedia.org/wiki/Isotonic_regression
保序回归线最终成为一个阶梯函数,而不是线性回归,即一条直线。以下图(来源:维基百科)提供了一个很好的参考:
Apache Spark 2.0 中的多层感知器分类器
在这个示例中,我们探索了 Spark 2.0 的多层感知器分类器(MLPC),这是前馈神经网络的另一个名称。我们使用鸢尾花数据集来预测描述输入的特征向量的二元结果。要记住的关键点是,即使名称听起来有点复杂,MLP 本质上只是用于无法通过简单的线性线或超平面分离的数据的非线性分类器。
如何做...
-
转到
LIBSVM数据:分类(多类)存储库,并从以下 URL 下载文件:www.csie.ntu.edu.tw/~cjlin/libsvmtools/datasets/multiclass/iris.scale -
在 IntelliJ 或您选择的 IDE 中启动一个新项目。确保包含必要的 JAR 文件。
-
设置程序所在的包位置:
package spark.ml.cookbook.chapter5
- 导入
SparkSession所需的包,以访问集群,并导入Log4j.Logger以减少 Spark 产生的输出量:
import org.apache.spark.ml.classification
.MultilayerPerceptronClassifier
import org.apache.spark.ml.evaluation.
MulticlassClassificationEvaluator
import org.apache.spark.sql.SparkSession
import org.apache.log4j.{ Level, Logger}
- 将输出级别设置为
ERROR,以减少 Spark 的日志输出:
Logger.getLogger("org").setLevel(Level.ERROR)
Logger.getLogger("akka").setLevel(Level.ERROR)
- 初始化
SparkSession,指定配置以访问 Spark 集群:
val spark = SparkSession
.builder
.master("local[*]")
.appName("MLP")
.getOrCreate()
- 首先,我们将
libsvm格式的数据文件加载到内存中:
val data = spark.read.format( "libsvm" )
.load("../data/sparkml2/chapter5/iris.scale.txt")
- 现在显示加载的数据:
从控制台,这是输出:
data.show(false)
- 接下来,我们利用数据集的
randomSplit方法将数据分成两个桶,每个桶分配 80%和 20%的数据:
val splitData = data.randomSplit(Array( 0.8 , 0.2 ), seed = System.currentTimeMillis())
randomSplit方法返回一个包含两组数据的数组,其中训练集占 80%,测试集占 20%:
val train = splitData(0)
val test = splitData(1)
- 接下来,我们配置多层感知器分类器,输入层为四个节点,隐藏层为五个节点,输出为四个节点:
val layers = ArrayInt
val mlp = new MultilayerPerceptronClassifier()
.setLayers(layers)
.setBlockSize(110)
.setSeed(System.currentTimeMillis())
.setMaxIter(145)
-
- Blocksize:用于将输入数据堆叠在矩阵中以加快计算速度的块大小。这更多是一个效率参数,推荐的大小在
10和1000之间。该参数涉及将数据推入分区以提高效率的总量。
- Blocksize:用于将输入数据堆叠在矩阵中以加快计算速度的块大小。这更多是一个效率参数,推荐的大小在
-
MaxIter:运行模型的最大迭代次数。
-
Seed:如果未设置权重,则设置权重初始化的种子。
在 GitHub 上 Spark 源代码的以下两行显示了代码中的默认设置:
setDefault(maxIter->100, tol -> 1e-6, blockSize ->128, solver -> MultilayerPerceptronClassifier.LBFGS, stepSize ->0.03)
要更好地理解参数和种子,请查看 MLP 源代码github.com/apache/spark/blob/master/mllib/src/main/scala/org/apache/spark/ml/classification/MultilayerPerceptronClassifier.scala。
- 我们通过调用 fit 方法生成模型:
val mlpModel = mlp.fit(train)
- 接下来,我们利用训练好的模型对测试数据进行转换,并显示预测结果:
val result = mlpModel.transform(test)
result.show(false)
结果将像以下内容一样显示在控制台上:
- 最后,我们从结果中提取预测和标签,并将它们传递给多类分类评估器以生成准确度值:
val predictions = result.select("prediction", "label")
val eval = new MulticlassClassificationEvaluator().setMetricName("accuracy")
println("Accuracy: " + eval.evaluate(predictions))
Accuracy: 0.967741935483871
- 通过停止
SparkSession来关闭程序:
spark.stop()
它是如何工作的...
在这个示例中,我们演示了多层感知器分类器的用法。我们首先加载了经典的鸢尾花数据集,格式为libsvm。接下来,我们将数据集分成 80%的训练集数据和 20%的测试集数据。在定义阶段,我们配置了一个输入层有四个节点,一个隐藏层有五个节点,一个输出层有四个节点的多层感知器分类器。我们通过调用fit()方法生成了一个训练模型,然后利用训练模型进行预测。
最后,我们获取了预测和标签,并将它们传递给多类分类评估器,计算准确度值。
在没有太多实验和拟合的情况下,对预测与实际情况进行简单的目测似乎非常令人印象深刻,并且证明了为什么神经网络(与上世纪 90 年代的版本大不相同)重新受到青睐。它们在捕捉非线性表面方面做得很好。以下是一些非线性表面的例子(来源:Mac App Store 上的 Graphing Calculator 4)。
以下图显示了一个样本非线性情况的二维描述:
以下图显示了一个样本非线性情况的三维描述。
一般来说,神经网络首先由以下代码示例定义:
val layers = ArrayInt
val mlp = new MultilayerPerceptronClassifier()
.setLayers(layers)
.setBlockSize(110)
.setSeed(System.currentTimeMillis())
.setMaxIter(145)
这定义了网络的物理配置。在这种情况下,我们有一个4 x 5 x 4 MLP,意思是四个输入层,五个隐藏层和四个输出层。通过使用setBlockSize(110)方法将BlockSize设置为 110,但默认值为 128。重要的是要有一个良好的随机函数来初始化权重,在这种情况下是当前系统时间setSeed(System.*currentTimeMillis*()。setMaxIter(145)是setSolver()方法使用的最大迭代次数,默认为l-bfgs求解器。
还有更多...
多层感知器(MLP)或前馈网络(FFN)通常是人们在毕业于受限玻尔兹曼机(RBM)和循环神经网络(RRN)之前首先了解的神经网络类型,这些类型在深度学习中很常见。虽然 MLP 在技术上可以被配置/称为深度网络,但必须进行一些调查并了解为什么它被认为是通往深度学习网络的第一步(仅此而已)。
在 Spark 2.0 的实现中,Sigmoid 函数(非线性激活)用于深度可堆叠网络配置(超过三层),将输出映射到Softmax函数,以创建一个能够捕捉数据极端非线性行为的非平凡映射表面。
Spark 使用 Sigmoid 函数通过易于使用的 API 在可堆叠的配置中实现非线性映射。以下图显示了 Sigmoid 函数及其在 Mac 上的图形计算器软件上的图形。
另请参阅
-
Spark 2.0 MLP 的文档:
spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.ml.classification.MultilayerPerceptronClassifier -
有关 MLP 的快速介绍,请参阅以下内容:
-
请参阅 Spark MLP 源代码:
github.com/apache/spark/blob/master/mllib/src/main/scala/org/apache/spark/ml/classification/MultilayerPerceptronClassifier.scala
理解深度信念网络(绝对最低限度)及其与简单 MLP 的对比所需的经典论文:
-
堆叠自动编码器:
papers.nips.cc/paper/3048-greedy-layer-wise-training-of-deep-networks.pdf -
稀疏表示:
www.cs.nyu.edu/~ranzato/publications/ranzato-nips06.pdf
MultilayerPerceptronClassifier中的一些重要的 API 调用:**
BlockSize默认设置为 128 - 只有在您完全掌握 MLP 时才应开始调整此参数:
-
def **setLayers**(value: Array[Int]): MultilayerPerceptronClassifier.this.type -
def **setFeaturesCol**(value: String): MultilayerPerceptronClassifier -
def **setLabelCol**(value: String): MultilayerPerceptronClassifier -
def **setSeed**(value: Long): MultilayerPerceptronClassifier.this.type -
def **setBlockSize**(value: Int): MultilayerPerceptronClassifier.this.type -
def **setSolver**(value: String): MultilayerPerceptronClassifier.this.type
Apache Spark 2.0 中的 One-vs-Rest 分类器(One-vs-All)
在这个示例中,我们演示了 Apache Spark 2.0 中的 One-vs-Rest。我们尝试通过OneVsRest()分类器使二元逻辑回归适用于多类/多标签分类问题。该示例是一个两步方法,首先我们配置一个LogisticRegression()对象,然后在OneVsRest()分类器中使用它来解决使用逻辑回归的多类分类问题。
如何做...
-
转到
LIBSVM数据:分类(多类)存储库,并下载文件:www.csie.ntu.edu.tw/~cjlin/libsvmtools/datasets/multiclass/iris.scale -
在 IntelliJ 或您选择的 IDE 中启动一个新项目。确保包含必要的 JAR 文件。
-
设置程序所在的包位置:
package spark.ml.cookbook.chapter5
- 导入必要的包,以便
SparkSession可以访问集群,Log4j.Logger可以减少 Spark 产生的输出量:
import org.apache.spark.sql.SparkSession
import org.apache.spark.ml.classification
.{LogisticRegression, OneVsRest}
import org.apache.spark.ml.evaluation
.MulticlassClassificationEvaluator
import org.apache.log4j.{ Level, Logger}
- 将输出级别设置为
ERROR,以减少 Spark 的日志输出:
Logger.getLogger("org").setLevel(Level.ERROR)
Logger.getLogger("akka").setLevel(Level.ERROR)
- 初始化一个
SparkSession,指定配置,构建一个 Spark 集群的入口点:
val spark = SparkSession
.builder
.master("local[*]")
.appName("One-vs-Rest")
.getOrCreate()
- 我们首先将
libsvm格式的数据文件加载到内存中:
val data = spark.read.format("libsvm")
.load("../data/sparkml2/chapter5/iris.scale.txt")
- 现在显示加载的数据:
data.show(false)
- 接下来,我们利用数据集的
randomSplit方法,将数据集按 80%的训练数据和 20%的测试数据进行分割:
val Array (train, test) = data.randomSplit(Array( 0.8 , 0.2 ), seed = System.currentTimeMillis())
- 让我们配置一个逻辑回归算法,用作 One-vs-Rest 算法的分类器:
val lrc = new LogisticRegression()
.setMaxIter(15)
.setTol(1E-3)
.setFitIntercept(true)
- 接下来,我们创建一个 one versus rest 对象,将我们新创建的逻辑回归对象作为参数传递:
val ovr = new OneVsRest().setClassifier(lrc)
- 通过在我们的 one-vs-rest 对象上调用 fit 方法生成模型:
val ovrModel = ovr.fit(train)
-
现在,我们将使用训练好的模型为测试数据生成预测并显示结果:
-
最后,我们将预测传递给多类分类评估器,生成准确度值:
val eval = new MulticlassClassificationEvaluator()
.setMetricName("accuracy")
val accuracy = eval.evaluate(predictions)
println("Accuracy: " + eval.evaluate(predictions))
Accuracy: 0.9583333333333334
- 通过停止
SparkSession来关闭程序:
spark.stop()
工作原理...
在这个示例中,我们演示了 One-vs-Rest 分类器的用法。我们首先加载了经典的 Iris 数据集,格式为libsvm。接下来,我们将数据集按 80%的比例分割为训练数据集和 20%的测试数据集。我们提醒用户注意,我们如何使用系统时间来进行分割的随机性如下:
data.randomSplit(Array( 0.8 , 0.2 ), seed = System.currentTimeMillis())
该算法最好可以描述为一个三步过程:
- 我们首先配置回归对象,而无需手头上有基本的逻辑模型,以便将其输入到我们的分类器中:
LogisticRegression()
.setMaxIter(15)
.setTol(1E-3)
.setFitIntercept(true)
- 在下一步中,我们将配置好的回归模型输入到我们的分类器中,并调用
fit()函数来完成相应的工作:
val ovr = new OneVsRest().setClassifier(lrc)
- 我们生成了一个训练模型,并通过该模型转换了测试数据。最后,我们将预测传递给多类分类评估器,生成一个准确度值。
还有更多...
这种算法的典型用法是将关于一个人的不同新闻项目标记和打包到各种类别中(例如友好与敌对、温和与欢欣等)。在医疗账单中的另一个用途可能是将患者诊断分类为用于自动结算和收入循环最大化的不同医疗编码。
一对多:如下图所示,这通过二元逻辑回归解决了一个n标签分类问题:
另请参阅
Spark 2.0 关于OneVsRest()的文档可以在以下找到:
spark.apache.org/docs/latest/api/scala/index.html#org.apache.spark.ml.classification.OneVsRest
另一种可视化方法是,评估给定二元分类器是否可以将 n 类输入分解为N个逻辑回归,然后选择最能描述数据的那个。以下是使用 Python 中 Scikit Learn 库的此分类器的众多示例:
但我们建议您在 GitHub 上对实际的 Scala 源代码进行快速扫描(仅不到 400 行):
生存回归 - 参数 AFT 模型在 Apache Spark 2.0 中
在这个教程中,我们探索了 Spark 2.0 对生存回归的实现,这不是典型的比例危险模型,而是加速失效时间(AFT)模型。这是一个重要的区别,应该在运行这个教程时牢记,否则结果将毫无意义。
生存回归分析关注的是事件发生时间的模型,这在医学、保险和任何时候对主题的生存能力感兴趣的情况下都很常见。我的一位合著者碰巧是一位受过全面训练的医生(除了是计算机科学家),所以我们使用了该领域一本备受尊敬的书中的真实数据集 HMO-HIM+研究,以便获得合理的输出。
目前,我们正在使用这种技术来进行干旱建模,以预测农产品在长期时间范围内的价格影响和预测。
如何做...
- 前往 UCLA 网站下载文件:
stats.idre.ucla.edu/stat/r/examples/asa/hmohiv.csv
我们使用的数据集是 David W Hosmer 和 Stanley Lemeshow(1999)的书《应用生存分析:事件发生时间数据的回归建模》中的实际数据。数据来自 HMO-HIM+研究,数据包含以下字段:
-
在 IntelliJ 或您选择的 IDE 中启动一个新项目。确保包含必要的 JAR 文件。
-
设置程序所在的包位置:
package spark.ml.cookbook.chapter5
- 导入
SparkSession所需的包,以便访问集群,以及Log4j.Logger以减少 Spark 产生的输出量:
import org.apache.log4j.{Level, Logger}
import org.apache.spark.ml.linalg.Vectors
import org.apache.spark.ml.regression.AFTSurvivalRegression
import org.apache.spark.sql.SparkSession
- 将输出级别设置为
ERROR,以减少 Spark 的日志输出:
Logger.getLogger("org").setLevel(Level.ERROR)
Logger.getLogger("akka").setLevel(Level.ERROR)
- 使用构建器模式初始化
SparkSession,指定配置,从而为 Spark 集群提供入口点:
val spark = SparkSession
.builder
.master("local[4]")
.appName("myAFTSurvivalRegression")
.config("spark.sql.warehouse.dir", ".")
.getOrCreate()
- 然后我们读取
csv文件,跳过第一行(标题)。
注意:有多种方法可以将csv文件读入 Spark DataFrame:
val file = spark.sparkContext.textFile("../data/sparkml2/chapter5/hmohiv.csv")
val headerAndData = file.map(line => line.split(",").map(_.trim))
val header = headerAndData.first
val rawData = headerAndData.filter(_(0) != header(0))
- 我们将字段从字符串转换为双精度。我们只对 ID、时间、年龄和审查字段感兴趣。然后这四个字段形成一个 DataFrame:
val df = spark.createDataFrame(rawData
.map { line =>
val id = line(0).toDouble
val time =line(1).toDouble
val age = line(2).toDouble
val censor = line(4).toDouble
(id, censor,Vectors.dense(time,age))
}).toDF("label", "censor", "features")
新的features字段是由time和age字段组成的向量。
- 接下来,我们在控制台中显示了 DataFrame:
df.show()
从控制台,这是输出:
- 现在我们将创建
AFTSurvivalRegression对象,并设置参数。
对于这个特定的配方,分位数概率设置为 0.3 和 0.6。这些值描述了分位数的边界,它们是概率的数值向量,其值范围在0.0到1.0 [0.0,1.0]之间。例如,使用(0.25, 0.5, 0.75)作为分位数概率向量是一个常见的主题。
分位数列名设置为quantiles。
在下面的代码中,我们创建了AFTSurvivalRegression()对象,并设置了列名和分位数概率向量。
来自 Spark 在 GitHub 上的源代码的以下代码显示了默认值:
@Since("1.6.0")
def getQuantileProbabilities: Array[Double] = $(quantileProbabilities)
setDefault(quantileProbabilities -> Array(0.01, 0.05, 0.1, 0.25, 0.5, 0.75, 0.9, 0.95, 0.99))
要了解参数化和种子,可以在 GitHub 上参考Spark Source Code for Survival Regression github.com/apache/spark/blob/master/mllib/src/main/scala/org/apache/spark/ml/regression/AFTSurvivalRegression.scala。
val aft = new AFTSurvivalRegression()
.setQuantileProbabilities(Array(0.3, 0.6))
.setQuantilesCol("quantiles")
- 我们让模型运行:
val aftmodel = aft.fit(df)
- 我们将模型数据打印到控制台:
println(s"Coefficients: ${aftmodel.coefficients} ")
println(s"Intercept: ${aftmodel.intercept}" )
println(s"Scale: ${aftmodel.scale}")
控制台中将看到以下输出:
Coefficients: [6.601321816135838E-4,-0.02053601452465816]
Intercept: 4.887746420937845
Scale: 0.572288831706005
- 我们使用前面的模型转换了数据集,并在控制台中显示了结果:
aftmodel.transform(df).show(false)
控制台中将看到以下输出:
- 我们通过停止
SparkSession来关闭程序:
spark.stop()
工作原理...
我们探索了加速失效时间(AFT)模型的特征。我们首先使用sparkContext.textFile()将数据集文件读入 Spark。有多种方法可以读取csv格式文件。我们只选择了一个显示更详细步骤的方法。
接下来,我们过滤了头行,并将感兴趣的字段从字符串转换为双精度,然后将双精度数据集转换为具有新features字段的新 DataFrame。
然后,我们创建了AFTSurvivalRegression对象并设置了分位数参数,并通过调用fit(data)函数让模型自行运行。
最后,我们显示了模型摘要,并使用模型转换了数据集,并显示了包括预测和分位数字段的结果 DataFrame。
还有更多...
Spark 实现的生存回归(AFTSurvivalRegression):
-
**模型:**加速失效时间(AFT)。
-
**参数化:**使用威布尔分布。
-
**优化:**Spark 选择 AFT 是因为它更容易并行化,并将问题视为具有 L-BFGS 作为优化方法的凸优化问题。
-
**R/SparkR 用户:**在没有拦截器的情况下拟合
AFTSurvivalRegressionModel到具有常数非零列的数据集时,Spark MLlib 会对常数非零列输出零系数。这种行为与 Rsurvival::survreg不同。(来自 Spark 2.0.2 文档)
您应该将结果视为发生感兴趣事件的时间,比如疾病的发生、赢得或失去、按揭违约时间、婚姻、离婚、毕业后找到工作等。这些模型的独特之处在于时间事件是一个持续时间,并不一定有解释变量(也就是说,它只是一段时间,以天、月或年为单位)。
您可能使用生存模型而不是简单回归(即诱人)的原因如下:
-
需要将结果变量建模为时间事件
-
审查-并非所有数据都是已知的或使用的(在使用来自过去几个世纪的长期商品数据时很常见)
-
非正态分布的结果-通常是时间的情况
-
这可能是多变量分析的情况,也可能不是。
尽管在此处概述了生存回归的两种方法,但在撰写本文时,Spark 2.0 仅支持 AFT 模型,而不支持最广为人知的比例风险模型:
-
比例风险模型(PH):
-
比例性假设随时间而定
-
在考虑时间内通过协方差乘以常数
-
示例:Cox 比例风险模型
-
*hx(y) = h0(y)g(X)
-
加速时间故障(ATF)- Spark 2.0 实施:
-
可以假设或违反比例性假设
-
通过协方差乘以常数值以获得回归系数值可能是:
-
加速
-
减速
-
允许回归展开的阶段:
-
疾病的阶段
-
生存能力的阶段
-
Yx * g(X) = Y0
Sx(y) = S0(yg(X))
其中,
Y:生存时间,
X:协变量向量,
hx(y):危险函数,
Sx(y):给定X的Y的生存函数,
Yx:给定X的Y
-
参数建模 - 时间变量的基础分布:
-
指数
-
Weibull - Spark 2.0 实施
-
对数逻辑
-
正态
-
伽马
-
另请参阅 - 在 R 中非常受欢迎 - 我们使用了这两个软件包:
-
Library(survival):标准生存分析
-
Library(eha):用于 AFT 建模
SurvivalRegression的文档可在以下网址找到:
HMOHIV数据集的原始格式可在以下网址找到 - 以访客身份连接:
ftp://ftp.wiley.com/public/sci_tech_med/survival
可以在以下网址找到 Proportional 与 AFT(Spark 2.0)风险模型的深入完整比较:
ecommons.usask.ca/bitstream/handle/10388/etd-03302009-140638/JiezhiQiThesis.pdf
具有图表的端到端真实世界医学研究: