1. 概述
在本教程中,我们将了解如何利用Apache Spark MLlib开发机器学习产品,我们将使用Spark MLlib开发一个简单的机器学习产品来演示其核心概念。
2. 机器学习简介
机器学习是人工智能(AI)这一更广泛领域的一部分,机器学习是指研究统计模型,利用模式和推理来解决特定问题。这些模型利用从问题空间中提取的训练数据,针对特定问题进行“训练”。
2.1 机器学习类别
根据方法,我们可以将机器学习大致分为监督学习和无监督学习,当然还有其他类别,但我们主要讨论以下两类:
-
监督学习处理的是一组包含输入和期望输出的数据-例如,一个包含房产各种特征和预期租金收入的数据集。监督学习进一步分为两大类,即分类和回归:
- 分类算法与分类输出相关,例如某个属性是否被占用
- 回归算法与连续输出范围相关,例如属性的值
-
另一方面,无监督学习处理的是一组仅包含输入值的数据,它试图识别输入数据中的固有结构。例如,通过一组消费者的消费行为数据集来发现不同类型的消费者。
2.2 机器学习工作流程
机器学习是一个跨学科的研究领域,它需要商业领域、统计学、概率论、线性代数和编程方面的知识。由于这显然会让人不知所措,因此最好以有序的方式进行,我们通常称之为机器学习工作流程:
由此可见,每个机器学习项目都应该以清晰定义的问题陈述开始,接下来应该采取一系列与可能解答该问题的数据相关的步骤。
然后,我们通常会根据问题的本质选择一个模型,接下来进行一系列的模型训练和验证,这被称为模型微调。最后,我们会使用之前未见过的数据测试模型,如果结果令人满意,就将其部署到生产环境中。
3. 什么是Spark MLlib?
Spark MLlib是Spark Core上的一个模块,它以API的形式提供机器学习原语,机器学习通常需要处理大量数据以进行模型训练。
Spark的基础计算框架是一个巨大的优势,除此之外,MLlib还提供了大多数流行的机器学习和统计算法,这极大地简化了大规模机器学习项目的工作。
4. 使用MLlib进行机器学习
现在,我们对机器学习以及MLlib如何助力实现这一目标有了足够的了解,让我们开始使用Spark MLlib实现机器学习项目的基本示例。
回想一下我们之前关于机器学习工作流程的讨论,我们应该先陈述问题,然后再处理数据。幸运的是,我们将选择机器学习的“Hello World”-鸢尾花数据集。这是一个多变量标记数据集,包含不同种类鸢尾花的萼片和花瓣的长度和宽度。
这给出了我们的问题目标:我们能否根据鸢尾花的萼片和花瓣的长度和宽度来预测其种类?
4.1 设置依赖
首先,我们必须在Maven中定义以下依赖来提取相关库:
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-mllib_2.11</artifactId>
<version>2.4.3</version>
<scope>provided</scope>
</dependency>
我们需要初始化SparkContext才能使用Spark API:
SparkConf conf = new SparkConf()
.setAppName("Main")
.setMaster("local[2]");
JavaSparkContext sc = new JavaSparkContext(conf);
4.2 加载数据
首先,我们应该下载数据,它是CSV格式的文本文件。然后,我们必须将这些数据加载到Spark中:
String dataFile = "data\\iris.data";
JavaRDD<String> data = sc.textFile(dataFile);
Spark MLlib提供了多种数据类型,包括本地和分布式,用于表示输入数据及其对应的标签。最简单的数据类型是Vector:
JavaRDD<Vector> inputData = data
.map(line -> {
String[] parts = line.split(",");
double[] v = new double[parts.length - 1];
for (int i = 0; i < parts.length - 1; i++) {
v[i] = Double.parseDouble(parts[i]);
}
return Vectors.dense(v);
});
请注意,我们这里仅包含输入特征,主要用于执行统计分析。
训练示例通常由多个输入特征和一个标签组成,由LabeledPoint类表示:
Map<String, Integer> map = new HashMap<>();
map.put("Iris-setosa", 0);
map.put("Iris-versicolor", 1);
map.put("Iris-virginica", 2);
JavaRDD<LabeledPoint> labeledData = data
.map(line -> {
String[] parts = line.split(",");
double[] v = new double[parts.length - 1];
for (int i = 0; i < parts.length - 1; i++) {
v[i] = Double.parseDouble(parts[i]);
}
return new LabeledPoint(map.get(parts[parts.length - 1]), Vectors.dense(v));
});
数据集中的输出标签是文本,表示鸢尾花的种类,为了将其输入机器学习模型,我们必须将其转换为数值。
4.3 探索性数据分析
探索性数据分析涉及分析可用数据,现在,机器学习算法对数据质量非常敏感,因此更高质量的数据更有可能产生预期的结果。
典型的分析目标包括消除异常和检测模式,这甚至会融入特征工程的关键步骤,以便从现有数据中提取有用的特征。
在这个例子中,我们的数据集很小,而且结构良好。因此,我们无需进行大量的数据分析。但是,Spark MLlib配备了丰富的API,可以提供相当丰富的见解。
让我们从一些简单的统计分析开始:
MultivariateStatisticalSummary summary = Statistics.colStats(inputData.rdd());
System.out.println("Summary Mean:");
System.out.println(summary.mean());
System.out.println("Summary Variance:");
System.out.println(summary.variance());
System.out.println("Summary Non-zero:");
System.out.println(summary.numNonzeros());
在这里,我们观察特征的均值和方差,这有助于确定是否需要对特征进行归一化。让所有特征保持相似的尺度很有用,我们还注意到非0值,它们可能会对模型性能产生不利影响。
以下是我们输入数据的输出:
Summary Mean:
[5.843333333333332,3.0540000000000003,3.7586666666666666,1.1986666666666668]
Summary Variance:
[0.6856935123042509,0.18800402684563744,3.113179418344516,0.5824143176733783]
Summary Non-zero:
[150.0,150.0,150.0,150.0]
另一个需要分析的重要指标是输入数据中特征之间的相关性:
Matrix correlMatrix = Statistics.corr(inputData.rdd(), "pearson");
System.out.println("Correlation Matrix:");
System.out.println(correlMatrix.toString());
任何两个特征之间的高度相关性表明它们没有增加任何增量价值,其中一个可以删除。以下是特征之间的相关性:
Correlation Matrix:
1.0 -0.10936924995064387 0.8717541573048727 0.8179536333691672
-0.10936924995064387 1.0 -0.4205160964011671 -0.3565440896138163
0.8717541573048727 -0.4205160964011671 1.0 0.9627570970509661
0.8179536333691672 -0.3565440896138163 0.9627570970509661 1.0
4.4 拆分数据
如果我们回想一下我们对机器学习工作流程的讨论,它涉及模型训练和验证的几次迭代,然后进行最终测试。
为此,我们必须将训练数据拆分为训练集、验证集和测试集。为了简单起见,我们将跳过验证部分。因此,让我们将数据拆分为训练集和测试集:
JavaRDD<LabeledPoint>[] splits = parsedData.randomSplit(new double[] { 0.8, 0.2 }, 11L);
JavaRDD<LabeledPoint> trainingData = splits[0];
JavaRDD<LabeledPoint> testData = splits[1];
4.5 模型训练
所以,我们已经到了分析和准备数据集的阶段,剩下的就是把它输入模型。我们需要选择一个合适的算法来解决这个问题-回想一下我们之前提到的机器学习的不同类别。
不难理解,我们的问题属于监督分类问题。目前,有很多算法可用于此类问题。
其中最简单的是逻辑回归(不要让回归这个词混淆我们;毕竟,它是一种分类算法):
LogisticRegressionModel model = new LogisticRegressionWithLBFGS()
.setNumClasses(3)
.run(trainingData.rdd());
这里我们使用基于三类有限内存BFGS的分类器,该算法的具体细节超出了本教程的讨论范围,但它是最广泛使用的算法之一。
4.6 模型评估
请记住,模型训练涉及多次迭代,但为了简单起见,我们在这里只使用了一次迭代。现在我们已经训练好了模型,是时候在测试数据集上进行测试了:
JavaPairRDD<Object, Object> predictionAndLabels = testData
.mapToPair(p -> new Tuple2<>(model.predict(p.features()), p.label()));
MulticlassMetrics metrics = new MulticlassMetrics(predictionAndLabels.rdd());
double accuracy = metrics.accuracy();
System.out.println("Model Accuracy on Test Data: " + accuracy);
那么,我们如何衡量一个模型的有效性呢?我们可以使用多种指标,其中最简单的一个就是准确率,简而言之,准确率是预测正确的次数与预测总数的比率。以下是我们模型单次运行可以实现的目标:
Model Accuracy on Test Data: 0.9310344827586207
请注意,由于算法的随机性,每次运行的结果都会略有不同。
然而,在某些问题领域,准确率并不是一个非常有效的指标,其他更复杂的指标包括精确度和召回率(F1分数)、ROC曲线和混淆矩阵。
4.7 保存和加载模型
最后,我们通常需要将训练好的模型保存到文件系统,并加载到生产数据中进行预测,这在Spark中很简单:
model.save(sc, "model\\logistic-regression");
LogisticRegressionModel sameModel = LogisticRegressionModel
.load(sc, "model\\logistic-regression");
Vector newData = Vectors.dense(new double[]{1,1,1,1});
double prediction = sameModel.predict(newData);
System.out.println("Model Prediction on New Data = " + prediction);
因此,我们将模型保存到文件系统,然后再加载回来,加载后,该模型可以直接用于预测新数据的输出。以下是针对随机新数据的示例预测:
Model Prediction on New Data = 2.0
5. 超越原始示例
虽然我们之前的示例大致涵盖了机器学习项目的工作流程,但它遗漏了许多细微而重要的要点,虽然这里无法详细讨论,但我们可以略过一些重要的要点。
Spark MLlib通过其API在所有这些领域都提供广泛的支持。
5.1 模型选择
模型选择通常是一项复杂而关键的任务,训练模型是一个复杂的过程,最好使用一个我们更有信心能够产生预期结果的模型进行训练。
虽然问题的性质可以帮助我们确定要选择的机器学习算法类别,但这并非一项完全确定的工作。正如我们之前所见,在像分类这样的类别中,通常有许多不同的算法及其变体可供选择。
通常,最好的做法是在较小的数据集上快速构建原型,像Spark MLlib这样的库可以让快速构建原型的工作变得容易得多。
5.2 模型超参数调优
典型的模型由特征、参数和超参数组成。特征是我们作为输入数据输入到模型中的;模型参数是模型在训练过程中学习到的变量;根据模型的不同,还有一些额外的参数需要我们根据经验进行设置并迭代调整,这些参数被称为模型超参数。
例如,学习率是基于梯度下降算法的典型超参数。学习率控制着训练周期中参数调整的速度,必须合理设置学习率,才能使模型以合理的速度有效学习。
虽然我们可以根据经验开始确定此类超参数的初始值,但我们必须执行模型验证并手动迭代调整它们。
5.3 模型性能
统计模型在训练过程中容易出现过拟合和欠拟合,这两者都会导致模型性能不佳。欠拟合是指模型未能充分提取数据中的一般细节;另一方面,过拟合是指模型开始从数据中拾取噪声。
有几种方法可以避免欠拟合和过拟合的问题,这些方法通常结合使用。例如,为了解决过拟合问题,最常用的技术包括交叉验证和正则化。同样,为了改善欠拟合,我们可以增加模型的复杂度并增加训练时间。
Spark MLlib对大多数技术(例如正则化和交叉验证)提供了出色的支持,事实上,大多数算法都默认支持这些技术。
6. Spark MLlib比较
虽然Spark MLlib对于机器学习项目来说是一个非常强大的库,但它肯定不是唯一的选择。不同编程语言中有很多库,并且支持程度各不相同,我们将在这里介绍一些流行的库。
6.1 Tensorflow/Keras
Tensorflow是一个用于数据流和可微分编程的开源库,广泛应用于机器学习应用。与其高级抽象Keras一起,它成为了机器学习的首选工具。它们主要用Python和C++编写,并主要在Python中使用。与Spark MLlib不同,它不支持多语言。
6.2 Theano
Theano是另一个基于Python的开源库,用于操作和计算数学表达式,例如,常用于机器学习算法的矩阵表达式。与Spark MLlib不同,Theano也主要在Python中使用。不过,Keras可以与Theano后端一起使用。
6.3 CNTK
Microsoft Cognitive Toolkit(CNTK)是一个用C++编写的深度学习框架,它通过有向图描述计算步骤。它既可以在Python程序中使用,也可以在C++程序中使用,主要用于开发神经网络。CNTK提供了一个基于Keras的后端,提供了我们熟悉的直观抽象。
7. 总结
在本教程中,我们学习了机器学习的基础知识,包括不同的类别和工作流程;我们还学习了Spark MLlib作为可用的机器学习库的基础知识。
此外,我们基于现有数据集开发了一个简单的机器学习应用程序,我们在示例中实现了机器学习工作流程中一些最常见的步骤。
我们还学习了典型机器学习项目中的一些高级步骤,以及Spark MLlib如何帮助完成这些步骤。最后,我们还了解了一些可供使用的替代机器学习库。
Post Directory
