PySpark 大规模数据分析精要(二)

原文:annas-archive.org/md5/b3bdd515fb6b21fd6dc5f2c481c3d18f

译者:飞龙

协议:CC BY-NC-SA 4.0

第六章:特征工程 – 提取、转换和选择

在上一章中,您了解了 Apache Spark 的原生、可扩展的机器学习库MLlib,并获得了其主要架构组件的概述,包括转换器、估算器和管道。

本章将带领您进入可扩展机器学习旅程的第一阶段——特征工程。特征工程涉及从预处理和清理后的数据中提取机器学习特征的过程,以便为机器学习做准备。您将学习特征提取特征转换特征缩放特征选择等概念,并通过 Spark MLlib 中的算法和一些代码示例实现这些技术。在本章结束时,您将掌握实施可扩展特征工程管道的必要技术,将预处理的数据转换为适合并准备好用于机器学习模型训练过程的格式。

特别地,在本章中,您将学习以下内容:

  • 机器学习过程

  • 特征提取

  • 特征转换

  • 特征选择

  • 特征库作为中央特征存储库

  • Delta 作为离线特征存储库

技术要求

在本章中,我们将使用 Databricks 社区版来运行代码。可以在community.cloud.databricks.com找到。

机器学习过程

一个典型的数据分析和数据科学过程包括收集原始数据、清理数据、整合数据和集成数据。之后,我们将统计学和机器学习技术应用于预处理后的数据,以生成机器学习模型,最后以数据产品的形式总结并向业务相关方传达过程结果。机器学习过程的高层次概述见下图:

https://github.com/OpenDocCN/freelearn-ds-pt3-zh/raw/master/docs/ess-pyspk-scl-da/img/B16736_06_01.jpg

图 6.1 – 数据分析和数据科学过程

从前面的图表可以看出,实际的机器学习过程只是整个数据分析过程的一小部分。数据团队花费大量时间进行数据策划和预处理,而其中只有一部分时间用于构建实际的机器学习模型。

实际的机器学习过程包括多个阶段,这些阶段使你能够执行诸如数据探索、特征提取、模型训练、模型评估以及应用模型到实际商业场景等步骤,如下图所示:

https://github.com/OpenDocCN/freelearn-ds-pt3-zh/raw/master/docs/ess-pyspk-scl-da/img/B16736_06_02.jpg

图 6.2 – 机器学习过程

在本章中,你将了解机器学习过程中的特征工程阶段。接下来的章节将介绍一些在Spark MLlib库中可用的突出算法和工具,这些算法和工具涉及特征提取特征转换特征缩放特征选择特征工程步骤。

特征提取

机器学习模型等同于数学中的函数或计算机编程中的方法。机器学习模型接受一个或多个参数或变量作为输入,并生成一个输出,称为预测。在机器学习术语中,这些输入参数或变量称为特征。特征是机器学习算法或模型中输入数据集的一列。特征是一个可度量的数据点,如个人的姓名、性别或年龄,或者是与时间相关的数据、天气数据,或其他对分析有用的数据。

机器学习算法利用线性代数这一数学领域,并使用矩阵和向量等数学结构在内部以及算法代码层面上表示数据。即使在经过数据工程处理之后,实际世界中的数据也很少以矩阵和向量的形式出现。因此,特征工程过程会应用于预处理数据,以将其转换为适合机器学习算法的格式。

特征提取过程专门处理将文本、图像、地理空间或时间序列数据转换为特征向量的问题。Apache Spark MLlib 提供了多种特征提取方法,如TF-IDFWord2VecCountVectorizerFeatureHasher

让我们以一组单词为例,使用CountVectorizer算法将其转换为特征向量。在本书的早期章节中,我们查看了一个在线零售商的样本数据集,并对这些数据集应用了数据工程过程,以获得一个干净且整合的、适合分析的数据集。

那么,让我们从在第五章《可扩展机器学习与 PySpark》末尾产生的预处理和清理后的数据集开始,该数据集名为retail_ml.delta。这个预处理的数据集,作为机器学习过程的输入,通常被称为训练数据集

  1. 作为第一步,让我们将数据从数据湖以 Delta 格式加载到 Spark DataFrame 中。如下方代码片段所示:

    preproc_data = spark.read.format("delta").load("dbfs:/FileStore/shared_uploads/delta/retail_ml.delta")
    preproc_data.show()
    

    在前面的代码块中,我们将存储在数据湖中的 Delta 格式数据加载到 Spark DataFrame 中,然后使用show()命令显示数据。

  2. 显示函数的结果如下图所示:https://github.com/OpenDocCN/freelearn-ds-pt3-zh/raw/master/docs/ess-pyspk-scl-da/img/B16736_06_03.jpg

    图 6.3 – 预处理数据

    在前面的图示中,我们得到了经过数据工程和数据整理步骤后的预处理数据。请注意,数据集中有11列,数据类型各异,包含字符串、双精度数和时间戳。在当前格式下,它们不适合作为机器学习算法的输入;因此,我们需要通过特征工程过程将其转换为适合的格式。

  3. 让我们从description列开始,该列为文本类型,并对其应用CountVectorizer特征提取算法,以便将其转换为特征向量,如下方代码块所示:

    from pyspark.sql.functions import split, trim
    from pyspark.ml.feature import CountVectorizer
    cv_df = preproc_data.withColumn("desc_array", split(trim("description"), " ")).where("description is NOT NULL")
    cv = CountVectorizer(inputCol="desc_array", 
                         outputCol="description_vec", 
                         vocabSize=2, minDF=2.0)
    cv_model = cv.fit(cv_df)
    train_df = model.transform(cv_df)
    train_df.display()
    

    在前面的代码块中,发生了以下操作:

    1. 我们从pyspark.ml.feature库中导入CountVectorizer

    2. CountVectorizer接受一个Array对象作为输入,因此我们使用split()函数将 description 列分割成一个单词的Array对象。

    3. 然后,我们使用之前定义的估算器在输入数据集上初始化一个新的CountVectorizer fit()方法。结果是一个经过训练的模型transform()方法应用于输入 DataFrame,从而生成一个新的 DataFrame,并为 description 列添加一个新的特征向量列。

    通过使用 Spark MLlib 中的CountVectorizer特征提取器,我们能够从文本类型的列中提取特征向量。

  4. Spark MLlib 中还可以使用其他特征提取器,如Word2Vec,如下方代码片段所示:

    from pyspark.ml.feature import Word2Vec
    w2v_df = preproc_data.withColumn("desc_array", split(trim("description"), "\t")).where("description is NOT NULL")
    word2vec = Word2Vec(vectorSize=2, minCount=0, 
                        inputCol="desc_array", 
                        outputCol="desc_vec")
    w2v_model = word2vec.fit(w2v_df)
    train_df = w2v_model.transform(w2v_df)
    

    在前面的代码块中,Word2Vec估算器的使用方式与之前提到的CountVectorizer类似。在这里,我们用它从基于文本的数据列中提取特征向量。虽然CountVectorizerWord2Vec都帮助将一个单词语料库转换成特征向量,但它们在每个算法的内部实现上有所不同。它们各自有不同的用途,取决于问题情境和输入数据集,并且在不同的情况下可能会产生不同的结果。

请注意,讨论这些算法的细微差别或提出何时使用特定特征提取算法的建议超出了本书的范围。

现在您已经学习了几种特征提取技术,在下一节中,让我们探讨几种特征转换的 Spark MLlib 算法。

特征转换

特征转换是仔细审查训练数据中存在的各种变量类型,如分类变量和连续变量,并确定最佳转换类型以实现最佳模型性能的过程。本节将描述如何转换机器学习数据集中发现的几种常见变量类型的示例代码,例如文本和数值变量。

转换分类变量

分类变量是具有有限和有限范围的离散值的数据片段。它们通常是基于文本的性质,但也可以是数字的。例如,国家代码和年份的月份。我们在前一节中提到了关于如何从文本变量中提取特征的几种技术。在本节中,我们将探讨几种其他算法来转换分类变量。

将文本标记化为单独的术语

Tokenizer类可以用来将文本分解为其组成的术语,如下面的代码示例所示:

from pyspark.ml.feature import Tokenizer
tokenizer = Tokenizer(inputCol="description", 
                      outputCol="desc_terms")
tokenized_df = tokenizer.transform(preproc_data)
tokenized_df.show()

在前面的代码块中,我们通过传入inputColoutputCol参数来初始化Tokenizer类,从而生成一个转换器。然后,我们转换训练数据集,得到一个 Spark DataFrame,其中包含一个新列,其中包含每个句子中被转换为小写的单词数组。这在下表中显示:

https://github.com/OpenDocCN/freelearn-ds-pt3-zh/raw/master/docs/ess-pyspk-scl-da/img/B16736_06_04.jpg

图 6.4 – 使用 Tokenizer 标记化文本

在上表中,您可以从标记化的单词中看到有一些不需要的词,我们需要将它们去掉,因为它们没有添加任何价值。

使用StopWordsRemover删除常见词

每种语言都包含常见且频繁出现的单词,如介词、冠词、连词和感叹词。这些词在机器学习过程中不具备任何意义,并且最好在训练机器学习算法之前将其删除。在 Spark 中,可以使用StopWordsRemover类来实现这一过程,如下面的代码片段所示:

from pyspark.ml.feature import StopWordsRemover
stops_remover = StopWordsRemover(inputCol="desc_terms", 
                                 outputCol="desc_nostops")
stops_df = stops_remover.transform(tokenized_df)
stops_df.select("desc_terms", "desc_nostops").show()

在前面的代码块中,我们通过传入inputColoutputCol参数来初始化StopWordsRemover类,从而生成一个转换器。然后,我们转换训练数据集,得到一个 Spark DataFrame,其中包含一个新列,该列具有一个数组,其中包含已删除停用词的单个单词。

一旦我们有了一个删除了停用词的字符串数组,就可以使用诸如Word2VecCountVectorizer等特征提取技术来构建特征向量。

编码离散的分类变量

现在我们有其他类型的字符串类型列,例如需要转换为数值形式以供机器学习算法使用的国家代码。你不能简单地为这些离散的分类变量分配任意的数值,因为这样可能会引入一种数据中本不存在的模式。

让我们考虑一个例子,其中我们按字母顺序单调递增地为分类变量分配值。然而,这可能会为这些变量引入一种排名,而这种排名在最初并不存在。这将扭曲我们的机器学习模型,并且并不理想。为了解决这个问题,我们可以使用 Spark MLlib 中的多种算法来编码这些分类变量。

使用 StringIndexer 编码字符串变量

在我们的训练数据集中,我们有字符串类型或分类变量,它们具有离散值,如country_code。这些变量可以使用StringIndexer分配标签索引,如下方代码示例所示:

from pyspark.ml.feature import StringIndexer
string_indexer = StringIndexer(inputCol="country_code", 
                               outputCol="country_indexed", 
                               handleInvalid="skip" )
indexed_df = string_indexer.fit(stops_df).transform(stops_df)
indexed_df.select("country_code", "country_indexed").show()

在前面的代码片段中,我们初始化了StringIndexer类,并设置了输入和输出列的名称。然后,我们将handleInvalid设置为skip,以跳过NULL和无效值。这样就得到了一个可以应用于训练数据框的估算器,进而得到了一个变换器。该变换器可以应用于训练数据集,从而生成一个新的 Spark 数据框,并且在其中新增了一个列,包含输入分类变量的标签索引。

使用 OneHotEncoder 将分类变量转换为向量

一旦我们将分类变量编码为标签索引,它们最终可以使用OneHotEncoder类转换为二进制向量,如下方代码片段所示:

from pyspark.ml.feature import OneHotEncoder
ohe = OneHotEncoder(inputCol="country_indexed", 
                    outputCol="country_ohe")
ohe_df = ohe.fit(indexed_df).transform(indexed_df)
ohe_df.select("country_code", "country_ohe").show()

在前面的代码片段中,我们初始化了OneHotEncoder类,并设置了输入和输出列的名称。这样就得到了一个估算器,可以应用于训练数据框,进而生成一个变换器。该变换器可以应用于训练数据集,从而生成一个新的 Spark 数据框,并在其中新增一个列,包含表示原始分类变量的特征向量。

变换连续变量

连续变量以测量或观测值的形式表示数据。通常,它们是数值型的,几乎可以具有无限的范围。在这里,数据是连续的,而非离散的,一些例子包括年龄、数量和单价。它们看起来很直接,并可以直接输入机器学习算法。然而,它们仍然需要进行特征工程,因为连续变量可能有太多值,机器学习算法无法处理。处理连续变量的方法有很多种,比如分箱、归一化、应用自定义业务逻辑等,应该根据所解决的问题和业务领域选择适当的方法。

一种特征工程连续变量的技术是二值化,其中将连续的数值转换为基于用户定义阈值的二进制值,如以下代码示例所示:

from pyspark.ml.feature import Binarizer
binarizer = Binarizer(threshold=10, inputCol="unit_price", 
                      outputCol="binarized_price")
binarized_df = binarizer.transform(ohe_df)
binarized_df.select("quantity", "binarized_price").show()

在前面的代码块中,我们通过输入和输出列参数初始化 Binarizer 类,生成一个转换器。然后可以将此转换器应用于训练数据框,从而得到一个新的数据框,并附加一个表示连续变量的二进制值的新列。

转换日期和时间变量

日期或时间戳类型的列本身对机器学习模型的训练过程并没有太大价值。然而,日期的组成部分,如月份、年份或星期几,可能会有某些模式。因此,选择日期时间列的某一部分并将其转换为适当的特征是非常有用的。

在以下代码示例中,我们从日期时间列中提取月份值并将其转换为特征,将其视为类别变量:

from pyspark.sql.functions import month
month_df = binarized_df.withColumn("invoice_month", 
                                   month("invoice_time"))
month_indexer = StringIndexer(inputCol="invoice_month", 
                              outputCol="month_indexed", 
                              handleInvalid="skip" )
month_df = month_indexer.fit(month_df).transform(month_df)
month_df.select("invoice_month", "month_indexed").show()

在前面的代码块中,我们首先使用 month() 函数从时间戳列中提取月份,并将其附加到数据框中。然后,我们将新列通过 StringIndexer 估算器进行转换,将月份的数字列转换为标签索引。

将单个特征组合成特征向量

大多数机器学习算法接受一个单一的特征向量作为输入。因此,将提取并转换的单个特征合并为一个特征向量是很有用的。可以使用 Spark MLlib 的 VectorAssembler 转换器来实现,如以下代码示例所示:

from pyspark.ml.feature import VectorAssembler
vec_assembler = VectorAssembler(
    inputCols=["desc_vec", "country_ohe", 
               "binarized_price", "month_indexed", 
               "quantity_indexed"],
    outputCol="features")
features_df = vec_assembler.transform(month_df)
features_df.select("features").show()

在前面的代码块中,我们通过输入和输出参数初始化 VectorAssembler 类,生成一个转换器对象。我们利用该转换器将单个特征合并为一个特征向量。这样,新的向量类型列就会附加到训练数据框中。

特征缩放

训练数据集通常会有不同计量单位的列。例如,一列可能使用公制计量单位,而另一列可能使用英制单位。某些列可能具有较大的数值范围,例如,一列表示美元金额,另一列表示数量。这些差异可能导致机器学习模型不当地对某些值赋予更多权重,从而产生不良影响,可能会引入偏差或模型失真。为了解决这个问题,可以使用一种称为特征缩放的技术。Spark MLlib 内置了一些特征缩放算法,如NormalizerStandardScalerRobustScalerMinMaxScalerMaxAbsScaler

在下面的代码示例中,我们将使用StandardScaler来演示如何在 Apache Spark 中应用特征缩放。StandardScaler转换特征向量,并将每个向量规范化为具有标准差单位:

from pyspark.ml.feature import StandardScaler
std_scaler = StandardScaler(inputCol="features", 
                            outputCol="scaled_features")
scaled_df = std_scaler.fit(features_df).transform(features_df)
scaled_df.select("scaled_features").show()

在前面的代码块中,StandardScaler类被初始化为输入和输出列的参数。然后,StandardScaler估算器应用于训练数据集,生成一个StandardScaler模型转换对象。接着,可以将该对象应用到训练 DataFrame,从而生成一个新 DataFrame,并添加一个包含规范化特征的新列。

到目前为止,在本节中,你已经学习了如何从数据集的列中提取机器学习特征。此外,你还学习了将基于文本的列转换为特征向量的特征提取技术。我们还探讨了将分类、连续以及基于日期和时间的变量转换的特征转换技术。介绍了将多个单独的特征组合成单个特征向量的技术,最后,你还学习了一种特征缩放技术来规范化特征。

在接下来的部分,你将学习减少特征数量的技术,这被称为特征选择

特征选择

特征选择是一种技术,它通过减少机器学习过程中的特征数量,同时利用较少的数据,并提高训练模型的准确性。特征选择是一个过程,可以自动或手动选择那些对你所关注的预测变量贡献最大的特征。特征选择是机器学习中的一个重要方面,因为不相关或半相关的特征可能会严重影响模型的准确性。

Apache Spark MLlib 提供了一些特征选择器,包括VectorSlicerChiSqSelectorUnivariateFeatureSelectorVarianceThresholdSelector。让我们通过以下代码示例,探索如何在 Apache Spark 中实现特征选择,利用ChiSqSelector根据我们试图预测的标签列选择最优特征:

from pyspark.ml.feature import ChiSqSelector
chisq_selector=ChiSqSelector(numTopFeatures=1, 
                             featuresCol="scaled_features", 
                             outputCol="selected_features", 
                             labelCol="cust_age")
result_df = chisq_selector.fit(scaled_df).transform(scaled_df)
result_df.select("selected_features").show()

在前面的代码块中,我们使用输入列和输出列初始化ChiSqSelector。我们还指定了标签列,因为ChiSqSelector选择最适合预测标签列的最优特征。然后,将ChiSqSelector估算器应用于训练数据集,生成一个ChiSqSelector模型转换器对象。接下来,可以将该对象应用于训练数据框,以生成一个新的数据框列,其中包含新选择的特征。

类似地,我们也可以利用VectorSlicer从给定的特征向量中选择一部分特征,如下列代码片段所示:

from pyspark.ml.feature import VectorSlicer
vec_slicer = VectorSlicer(inputCol="scaled_features", 
                          outputCol="selected_features", 
                          indices=[1])
result_df = vec_slicer.transform(scaled_df)
result_df.select("scaled_features", 
                 "selected_features").display()

前面的代码块也执行了特征选择。然而,与ChiSqSelector不同,VectorSlicer并没有针对给定的变量优化特征选择。相反,VectorSlicer接受一个带有指定索引的向量列。这将生成一个新的向量列,其值通过指定的索引进行选择。每个特征选择器都有自己进行特征选择的方法,应该根据给定的场景和待解决的问题选择合适的特征选择器。

到目前为止,你已经学习了如何从基于文本的变量中提取特征,以及如何对分类和连续类型的变量进行特征转换。此外,你还探索了特征切片和特征选择的技术。你已经掌握了将预处理后的原始数据转换为特征向量的技术,这些特征向量可以直接输入到机器学习算法中,用于构建机器学习模型。

然而,对于每一个机器学习问题都执行特征工程似乎既冗余又耗时。那么,能不能直接使用一些已经构建好的特征来为新模型服务呢?答案是肯定的,你应该在新的机器学习问题中重用之前构建的特征。你也应该能够利用你其他团队成员的特征。这可以通过一个集中式特征存储来实现。我们将在接下来的部分进一步探讨这个话题。

特征存储作为中央特征库

在任何机器学习问题上花费的大部分时间都用于数据清洗和数据整理,以确保我们建立模型的基础是干净且有意义的数据。特征工程是机器学习过程中的另一个关键步骤,数据科学家们花费大量时间策划机器学习特征,这是一个复杂且耗时的过程。为每个新的机器学习问题再次创建特征似乎是违反直觉的。

通常,特征工程是在已经存在的历史数据上进行的,新特征在不同的机器学习问题中是可以完全重用的。事实上,数据科学家花费大量时间寻找问题所需的正确特征。因此,拥有一个集中的特征库,可搜索并具有用于识别特征的元数据将是非常有益的。这个集中的可搜索特征库通常被称为特征存储。典型的特征存储架构如下图所示:

https://github.com/OpenDocCN/freelearn-ds-pt3-zh/raw/master/docs/ess-pyspk-scl-da/img/B16736_06_05.jpg

图 6.5 – 特征存储架构

特征不仅在机器学习过程的模型训练阶段中有用,而且在模型推断过程中也是必需的。推断,也称为模型评分,是将已构建的模型与新的未见特征一起输入,以便在新数据上生成预测的过程。根据推断过程是批处理模式还是流式实时模式,特征可以被广泛分类为离线特征和在线特征。

使用离线特征存储进行批量推断

离线特征,顾名思义,是使用批处理作业离线生成的。它们的消耗也是离线进行的,可以通过模型训练过程或批量机器学习管道中的模型推断来进行,即使用定期安排的批处理方式。这些特征可能需要耗费时间来创建,通常使用大数据框架(如 Apache Spark)创建,或通过从数据库或数据仓库运行定期查询来创建。

用于生成离线特征的存储机制被称为离线特征存储。历史数据存储、关系型数据库、数据仓库系统和数据湖都是离线特征存储的良好选择。离线特征存储应具有强类型、模式强制执行机制,并具有存储元数据以及实际特征的能力。任何数据库或数据仓库都适用于离线特征存储;然而,在下一节中,我们将探讨 Delta Lake 作为离线特征存储。

Delta Lake 作为离线特征存储

第三章《数据清洗与集成》中,我们将数据湖确立为长期存储历史数据的可扩展且相对便宜的选择。我们讨论了数据可靠性和基于云的数据湖的一些挑战,并且你了解了 Delta Lake 是如何设计来克服这些挑战的。作为云数据湖的抽象层,Delta Lake 的优势不仅限于数据工程工作负载,也扩展到了数据科学工作负载,我们将在本节中深入探讨这些优势。

Delta Lake 是基于云的数据湖中理想的离线特征存储候选,因为它具备数据可靠性特性和 Delta Lake 提供的独特时间旅行功能。我们将在接下来的章节中讨论这些内容。

Delta 表的结构和元数据

Delta Lake 支持具有明确定义列数据类型的结构化数据。这使得 Delta 表具有强类型,确保可以将各种数据类型的特征存储在 Delta 表中。相比之下,实际存储发生在相对便宜且可以无限扩展的基于云的数据湖中。这使得 Delta Lake 成为云中理想的离线特征存储候选。

Delta Lake 的模式强制执行与演变

Delta Lake 完全支持模式强制执行,这意味着插入到 Delta Lake 特征存储中的特征数据的完整性得到了良好的维护。这将确保只有正确的、具有适当数据类型的数据用于机器学习模型的构建过程,从而确保模型的性能。Delta Lake 对模式演变的支持也意味着可以轻松地将新特征添加到基于 Delta Lake 的特征存储中。

支持同时处理批处理和流处理工作负载

由于 Delta Lake 完全支持统一的批处理和流处理工作负载,数据科学家除了批处理管道之外,还可以构建近实时的流式特征工程管道。这将有助于使用最新特征训练机器学习模型,并且也能够近实时地生成预测。这将通过仅利用 Apache Spark 的统一分析引擎,帮助消除高延迟推断需求的操作开销。

Delta Lake 时间旅行

数据科学家经常通过微小的数据变动来改善模型准确度,通常会为此目的维护相同物理数据的多个版本。利用 Delta Lake 的时间旅行功能,单个 Delta 表可以轻松支持数据的多个版本,从而消除数据科学家维护多个物理数据版本的开销。

与机器学习操作工具的集成

Delta Lake 还支持与流行的机器学习操作和工作流管理工具 MLflow 集成。我们将在第九章《机器学习生命周期管理》中探讨 MLOps 和 MLflow。

这里展示了一个利用 Delta Lake 作为离线特征存储的代码示例:

spark.sql("CREATE DATABASE feature_store")
(result_df
   .write
   .format("delta")
   .mode("append")
   .option("location", "/FileStore/shared_uploads/delta/retail_features.delta")
   .saveAsTable("feature_store.retail_features"))

首先,我们创建一个名为 feature_store 的数据库。然后,我们将上一节中的 特征选择 步骤的结果保存为 Delta 表。

通过这种方式,可以使用简单的 SQL 命令搜索特征,并且还可以通过共享的 Hive 元存储库将其共享并用于其他机器学习应用场景。Delta Lake 还支持常见的元数据,例如列名和数据类型,其他元数据,如用户备注和评论,也可以添加,以便为特征存储中的特征提供更多上下文信息。

提示

大多数大数据平台,包括 Databricks,都支持内置的 Hive 元存储库来存储表的元数据。此外,这些平台还提供了安全机制,如数据库、表格,有时甚至包括行级和列级访问控制机制。通过这种方式,特征存储可以得到保护,特征可以在数据团队之间进行选择性共享。

通过这种方式,使用 Delta Lake 可以作为云数据湖上的离线特征存储。一旦特征被存储在 Delta 表中,它们可以通过 Spark 的所有 API 访问,包括 DataFrame 和 SQL API。

注意

Spark MLlib 中的所有函数和方法都经过设计,可以原生地进行扩展。因此,使用 Spark MLlib 执行的任何机器学习操作天生具有可扩展性,并且可以运行具有并行和分布式任务的 Spark 作业。

用于实时推理的在线特征存储

用于在线机器学习推理的特征被称为在线特征。通常,这些特征具有超低延迟的要求,通常从毫秒到秒级。在线特征的一些使用场景包括在最终用户应用中的实时预测。

让我们考虑一个客户浏览电商网站应用的例子。客户将一个产品加入购物车,基于客户的邮政编码,网页应用需要在几秒钟内提供一个预计的送货时间。这里涉及的机器学习模型需要一些特征来估算交货时间,比如仓库位置、产品库存、历史交货时间,甚至可能还需要天气和季节性条件,但最重要的是,它需要客户的邮政编码。大多数特征可能已经在离线特征存储中预先计算并可用。然而,鉴于该用例的低延迟要求,特征存储必须能够以最低的延迟提供这些特征。数据湖、数据库或数据仓库并不是该用例的理想选择,需要一个超低延迟的在线特征存储。

从前面的示例中,我们可以得出结论,实时在线推理需要一些关键组件:

  • 一个超低延迟的、最好是内存中的特征存储。

  • 一个事件处理、低延迟流处理引擎

  • 用于与终端用户的网页和移动应用集成的 RESTful API

以下图示展示了一个实时推理管道的例子:

https://github.com/OpenDocCN/freelearn-ds-pt3-zh/raw/master/docs/ess-pyspk-scl-da/img/B16736_06_06.jpg

图 6.6 – 实时机器学习推理管道

在前面的图示中,数据从网页或移动应用实时到达消息队列,如 Apache Kafka。一个低延迟的事件处理引擎,如 Apache Flink,处理传入的特征并将其存储到 NoSQL 数据库(如 Apache Cassandra)或内存数据库(如 Redis)中,以便用于在线特征存储。机器学习推理引擎从在线特征存储中提取特征,生成预测,并通过 REST API 将预测推送回网页或移动应用。

注意

无论是 Apache Spark 还是基于云的数据湖中的 Delta Lake,都不适合用作在线特征存储。Spark 的结构化流处理被设计用来处理高吞吐量,而非低延迟处理。结构化流处理的微批处理不适合在事件到达源头时进行处理。通常,基于云的数据湖是为了可扩展性而设计的,并且具有延迟规范,因此 Delta Lake 无法满足在线特征存储对超低延迟的要求。

总结

在本章中,你了解了特征工程的概念,以及它为何是整个机器学习过程中的重要部分。此外,你还了解了为何需要创建特征并训练机器学习模型。

你探索了各种特征工程技术,如特征提取,以及它们如何将基于文本的数据转换为特征。介绍了在处理类别型和连续变量时有用的特征转换技术,并展示了如何将它们转换为特征的示例。你还探索了有助于归一化特征的特征缩放技术,以防止某些特征对训练模型产生过度偏倚。

最后,你了解了通过特征选择技术选择合适特征的方法,以优化预测标签的模型性能。本章所学的技能将帮助你使用 Apache Spark 实现可扩展且高效的特征工程管道,并利用 Delta Lake 作为特征的中央共享存储库。

在接下来的章节中,你将学习属于监督学习范畴的各种机器学习训练算法。此外,你将实现代码示例,利用本章生成的特征,在 Apache Spark MLlib 中训练实际的机器学习模型。

第七章:监督机器学习

在前两章中,你已经了解了机器学习过程、各个阶段以及该过程的第一步——特征工程。掌握了机器学习过程的基本知识,并拥有一组可用的机器学习特征后,你已准备好进入机器学习过程的核心部分——模型训练

在本章中,你将接触到监督学习类别的机器学习算法,了解参数化非参数化算法,掌握使用机器学习解决回归分类问题所需的知识。最后,你将使用 Spark 机器学习库实现一些回归算法,如线性回归决策树,以及一些分类算法,如逻辑回归朴素贝叶斯支持向量机。还将介绍树集成方法,这可以提高决策树的性能和准确性。本章还将展示回归和分类的几个现实世界应用,帮助你理解机器学习如何在日常场景中得到应用。

本章将涵盖以下主要主题:

  • 监督学习简介

  • 回归

  • 分类

  • 树集成

  • 现实世界中的监督学习应用

到本章结束时,你应已掌握足够的知识和技能,能够使用 Spark MLlib 构建你自己的回归和分类模型并进行大规模训练。

技术要求

在本章中,我们将使用 Databricks Community Edition 来运行我们的代码(community.cloud.databricks.com)。

监督学习简介

机器学习问题可以看作是一个过程,通过数学或统计函数从一组已知变量中推导出一个未知变量。不同之处在于,机器学习算法从给定的数据集中学习映射函数。

监督学习是一类机器学习算法,其中模型在一个数据集上进行训练,每一组输入的结果已经是已知的。这被称为监督学习,因为在此过程中,算法像教师一样引导训练,直到达到期望的模型性能水平。监督学习需要已经标注的数据。监督学习算法可以进一步分为参数化算法和非参数化算法。我们将在接下来的章节中详细讨论这些内容。

参数化机器学习

一种通过用一组固定参数总结数据来简化学习过程的机器学习算法称为参数化学习算法。它通过假设学习函数具有已知的形式,并从给定的数据集中学习线性函数的系数来实现这一点。学习函数的假定形式通常是线性函数或描述直线的代数方程。因此,参数化学习函数也被称为线性机器学习算法。

参数化学习算法的一个重要特性是,线性学习函数所需的参数数量与输入的训练数据集无关。这大大简化了学习过程,使得训练相对更快。这里的一个缺点是,给定数据集的潜在学习函数不一定是直线,因此可能会过度简化所学模型。然而,大多数实际的机器学习算法是参数化学习算法,例如线性回归、逻辑回归和朴素贝叶斯。

非参数化机器学习

非参数化学习算法不对学习函数的形式做任何假设。这些算法通过学习映射函数最大限度地利用训练数据集,同时保持对未见数据的适应能力。这意味着非参数化学习算法可以学习更广泛的学习函数。这些算法的优势在于它们灵活,并且能生成更优性能的模型,而劣势是通常需要更多的数据来学习,训练时间较慢,并且有时可能导致模型过拟合。一些非参数化学习算法的例子包括 K 最近邻、决策树和支持向量机。

监督学习算法有两个主要应用,即回归和分类。我们将在接下来的章节中探讨这些内容。

回归

回归是一种监督学习技术,它帮助我们学习一个称为标签的连续输出参数与一组输入参数(称为特征)之间的关系。回归生成的机器学习模型根据特征向量预测一个连续的标签。回归的概念可以通过以下图示来最好地解释:

https://github.com/OpenDocCN/freelearn-ds-pt3-zh/raw/master/docs/ess-pyspk-scl-da/img/B16736_07_01.jpg

图 7.1 – 线性回归

在前面的图示中,散点图表示分布在二维空间中的数据点。线性回归算法是一种参数化学习算法,它假设学习函数将呈线性形式。因此,它学习表示直线的系数,这条直线大致拟合散点图中的数据点。

Spark MLlib 提供了几种著名回归算法的分布式和可扩展实现,例如线性回归、决策树、随机森林和梯度提升树。在接下来的章节中,我们将使用 Spark MLlib 实现这些回归算法中的几个。

线性回归

在前几章中,我们清理、整合并策划了一个包含客户在线零售销售交易的数据集,并在同一整合数据集中捕获了他们的 demographic 信息。在 第六章特征工程 – 提取、转换和选择 中,我们还将预处理的数据转换成了一个适合机器学习训练的特征向量,并将其存储在 Delta Lake 中,作为我们的离线 特征存储。让我们利用这个特征工程数据集来训练一个回归算法,利用其他特征作为参数预测客户的年龄,如下方代码块所示:

from pyspark.ml.regression import LinearRegression
retail_features = spark.read.table("retail_features")
train_df = retail_features.selectExpr("cust_age as label", "selected_features as features")
lr = LinearRegression(maxIter=10, regParam=0.3, 
                      elasticNetParam=0.8)
lr_model = lr.fit(train_df)
print("Coefficients: %s" % str(lr_model.coefficients))
print("Intercept: %s" % str(lr_model.intercept))
summary = lr_model.summary
print("RMSE: %f" % summary.rootMeanSquaredError)
print("r2: %f" % summary.r2)

在前面的代码块中,我们做了以下操作:

  1. 首先,我们从 Spark MLlib 导入了 LinearRegression 算法。

  2. 零售特征是从 Delta 表中加载的,并被加载到 Spark DataFrame 中。

  3. 我们只需要特征向量和标签列来训练一个 LinearRegression 模型,因此我们仅在训练 DataFrame 中选择了这两列。

  4. 然后,我们通过指定该算法所需的超参数来初始化一个 LinearRegression 变换器。

  5. 然后,我们在训练数据集上调用了 fit 方法来启动训练过程,这会在后台启动一个 Spark 任务,分布式地执行训练任务。

  6. 一旦模型成功训练完成,我们打印了模型训练摘要,包括学习到的线性学习函数的系数和截距。

  7. 我们还显示了模型的准确性指标,例如 RMSE 和 R 平方值。通常,理想的情况是得到一个尽可能低的 RMSE 的模型。

因此,利用 Spark MLlib 的线性回归分布式实现,您可以在大型数据集上以分布式方式训练回归模型,而无需处理任何底层的分布式计算复杂性。然后,模型可以应用于新数据集,以生成预测。Spark MLlib 模型还可以使用内置方法持久化到磁盘或数据湖中,然后稍后重新使用。

现在,我们已经使用参数学习算法训练了一个简单的线性回归模型,让我们来看看如何使用非参数学习算法来解决同样的回归问题。

使用决策树进行回归

决策树是解决回归和分类机器学习问题的流行非参数学习算法。决策树之所以流行,是因为它们易于使用,能够处理各种分类和连续特征,同时也易于解释和说明。

Spark MLlib 的决策树实现通过按行划分数据来实现分布式训练。由于非参数学习算法通常需要大量数据,Spark 的决策树实现能够扩展到非常大规模的数据集,甚至是数百万或数十亿行。

让我们训练一个决策树模型,通过使用其他在线零售交易特征作为输入,预测客户的年龄,如下方代码块所示:

from pyspark.ml.evaluation import RegressionEvaluator
from pyspark.ml.regression import DecisionTreeRegressor
retail_features = spark.read.table("retail_features").selectExpr("cust_age as label", 
           "selected_features as features")
(train_df, test_df) = retail_features.randomSplit([0.8, 0.2])
dtree = DecisionTreeRegressor(featuresCol="features")
model = dtree.fit(train_df)
predictions = model.transform(test_df)
evaluator = RegressionEvaluator(
    labelCol="label", predictionCol="prediction", 
    metricName="rmse")
rmse = evaluator.evaluate(predictions)
print("RMSE for test data = %g" % rmse)
print(model.toDebugString)

在前面的代码片段中,我们完成了以下操作:

  1. 首先,我们导入了DecisionTreeRegressor Spark ML 库,并引入了一个工具方法来帮助评估训练模型的准确性。

  2. 我们将特征向量数据集从 Delta Lake 加载到 Spark DataFrame,并只选择特征和标签列。

  3. 为了能够在训练过程后评估我们模型的准确性,我们需要一个不会用于训练的数据集。因此,我们将数据集分为训练集和测试集两部分。我们使用 80%的数据进行模型训练,同时保留 20%用于模型评估。

  4. 然后,我们用所需的超参数初始化了DecisionTreeRegressor类,从而得到了一个Transformer对象。

  5. 我们将DecisionTreeRegressor变换器拟合到我们的训练数据集,从而得到一个决策树模型估算器。

  6. 我们将模型的Estimator对象应用于测试数据集,以生成实际预测结果。

  7. 随后,这个预测数据框(DataFrame)与RegressionEvaluator工具方法一起使用,用于推导模型的 RMSE,该值可用于评估训练模型的准确性。

通过使用 Spark MLlib 内置的决策树回归算法,我们可以在非常大量的数据上,以分布式的方式快速高效地训练回归模型。需要注意的一点是,这两个回归模型的 RMSE 值大致相同。这些模型可以通过模型调优技术进一步调优,以提高其准确性。你将会在第九章机器学习生命周期管理中学到更多关于模型调优的内容。

分类

分类是另一种监督学习技术,其任务是将给定的数据集分类到不同的类别中。机器学习分类器从输入参数(称为特征)中学习映射函数,输出一个离散的输出参数(称为标签)。在这里,学习函数尝试预测标签是否属于几个已知类别中的一个。下图展示了分类的概念:

https://github.com/OpenDocCN/freelearn-ds-pt3-zh/raw/master/docs/ess-pyspk-scl-da/img/B16736_07_02.jpg

图 7.2 – 逻辑回归

在前面的图示中,逻辑回归算法正在学习一个映射函数,将二维空间中的数据点分成两个不同的类别。学习算法学习Sigmoid 函数的系数,该函数将一组输入参数分类为两个可能类别之一。这种分类方法可以分为两个不同的类别,这就是二分类二项分类

逻辑回归

逻辑回归是一种流行的分类算法,可以从标记数据中学习模型,以预测输出变量的类别。Spark MLlib 实现的逻辑回归支持二项分类和多项分类问题。

二项分类

二项分类或二分类是指学习算法需要判断输出变量是否属于两个可能结果之一。基于前面章节的示例,我们将使用逻辑回归训练一个模型,尝试根据在线零售交易中的其他特征预测顾客的性别。让我们看看如何使用 Spark MLlib 来实现这一点,代码示例如下:

from pyspark.ml import Pipeline
from pyspark.ml.feature import StringIndexer
from pyspark.ml.classification import LogisticRegression
train_df = spark.read.table("retail_features").selectExpr("gender", "selected_features as features")
string_indexer = StringIndexer(inputCol="gender", 
                               outputCol="label", 
                               handleInvalid="skip" )
lr = LogisticRegression(maxIter=10, regParam=0.9, 
                        elasticNetParam=0.6)
pipeline = Pipeline(stages=[string_indexer, lr])
model = pipeline.fit(train_df)
lr_model = model.stages[1]
print("Coefficients: " + str(lr_model.coefficientMatrix))
print("Intercepts: " + str(lr_model.interceptVector))
summary.roc.show()
print("areaUnderROC: " + str(summary.areaUnderROC))

在前面的代码块中,我们做了以下操作:

  1. 我们的训练数据集中,性别是一个字符串数据类型,因此首先需要将其转换为数字格式。为此,我们使用了 StringIndexer 将其转换为一个数字标签列。

  2. 然后,我们通过指定该算法所需的超参数,初始化了 LogisticRegression 类。

  3. 接着,我们将 StringIndexerLogisticRegression 阶段串联在一起,形成一个管道。

  4. 然后,我们在训练数据集上调用 fit 方法,开始使用管道中的 Transformer 对象进行训练过程。

  5. 一旦模型成功训练,我们打印了模型的系数和截距,并且展示了接收器操作特征曲线(ROC)以及 ROC 曲线下的面积(AUC)指标,以衡量训练模型的准确性。

这样,我们已经展示了如何利用 Spark 机器学习库中的逻辑回归算法,以可扩展的方式实现二分类。

多项分类

多项分类中,学习算法需要预测多个可能的结果。让我们从前一节的示例出发,扩展一个模型,使用逻辑回归来预测一个客户的来源国家,基于来自在线零售交易的其他特征,如下面的代码片段所示:

train_df = spark.read.table("retail_features").selectExpr("country_indexed as label", "selected_features as features")
mlr = LogisticRegression(maxIter=10, regParam=0.5, 
                         elasticNetParam=0.3, 
                         family="multinomial")
mlr_model = mlr.fit(train_df)
print("Coefficients: " + str(mlr_model.coefficientMatrix))
print("Intercepts: " + str(mlr_model.interceptVector))
print("areaUnderROC: " + str(summary.areaUnderROC))
summary.roc.show()

前面的代码片段几乎与二分类示例相同,唯一的区别是标签列具有多个可能值,并且我们为 LogisticRegression 类指定了 multinomial 的 family 参数。一旦模型训练完成,可以通过显示模型的接收者操作特征(ROC)和 ROC 曲线下的面积来衡量模型的准确性。

使用决策树进行分类

Spark MLlib 提供了一个 DecisionTreeClassifier 类来解决分类问题。在下面的代码中,我们将使用决策树实现二分类:

retail_df = spark.read.table("retail_features").selectExpr("gender", "selected_features as features")
(train_df, test_df) = retail_df.randomSplit([0.8, 0.2])
string_indexer = StringIndexer(inputCol="gender", 
                               outputCol="label", 
                               handleInvalid="skip" )
dtree = DecisionTreeClassifier(labelCol="label", 
                               featuresCol="features")
pipeline = Pipeline(stages=[string_indexer, dtree])
model = pipeline.fit(train_df)
predictions = model.transform(test_df)
evaluator = MulticlassClassificationEvaluator(
    labelCol="label", predictionCol="prediction", 
    metricName="accuracy")
accuracy = evaluator.evaluate(predictions)
print("Accuracy = %g " % (accuracy))
dtree_model = model.stages[1]
#print(dtree_model.toDebugString)

在前面的代码块中,我们做了以下操作:

  1. 首先,我们将数据集分为两个集合,用于训练和测试。这使我们能够在训练完成后评估模型的准确性。

  2. 然后,我们使用了 StringIndexer 将性别字符串列转换为数值标签列。

  3. 之后,我们初始化了一个带有所需超参数的 DecisionTreeClassifier 类。

  4. 然后,我们将 StringIndexerDecisionTreeClassifier 阶段组合成一个管道。

  5. 之后,我们在训练数据集上调用了 fit 方法开始模型训练过程,并将模型的 Estimator 对象应用于测试数据集以计算预测值。

  6. 最后,我们使用这个 DataFrame,并结合 MulticlassClassificationEvaluator 工具方法,推导出训练模型的准确性。

通过这种方式,我们展示了如何使用 Spark 机器学习库的决策树来解决大规模的分类问题。

朴素贝叶斯

朴素贝叶斯是基于贝叶斯定理的概率分类算法家族,它假设输入学习算法的特征之间是独立的。贝叶斯定理可以用来预测一个事件发生的概率,前提是另一个事件已经发生。朴素贝叶斯算法计算给定输入特征集的所有可能输出标签类别的概率,然后选择具有最大概率的输出。朴素贝叶斯可以用于二项分类问题以及多项分类问题。让我们来看一下如何使用 Spark MLlib 实现朴素贝叶斯,如下面的代码示例所示:

from pyspark.ml.classification import NaiveBayes
from pyspark.ml.evaluation import MulticlassClassificationEvaluator
retail_df = spark.read.table("retail_features").selectExpr("gender", "selected_features as features")
(train_df, test_df) = retail_df.randomSplit([0.8, 0.2])
string_indexer = StringIndexer(inputCol="gender", 
                               outputCol="label", 
                               handleInvalid="skip" )
nb = NaiveBayes(smoothing=0.9, modelType="gaussian")
pipeline = Pipeline(stages=[string_indexer, nb])
model = pipeline.fit(train_df)
predictions = model.transform(test_df)
evaluator = MulticlassClassificationEvaluator(
    labelCol="label",
    predictionCol="prediction",
    metricName="accuracy")
accuracy = evaluator.evaluate(predictions)
print("Model accuracy = %f" % accuracy)

在前面的代码块中,我们做了以下操作:

  1. 首先,我们将数据集分别分为训练集和测试集。

  2. 然后,我们使用了 StringIndexer 将性别字符串列转换为数值标签列。

  3. 之后,我们初始化了一个带有所需超参数的 NaiveBayes 类。

  4. 然后我们将StringIndexerNaiveBayes阶段合并到一个管道中。

  5. 然后,我们在训练数据集上调用了fit方法,以启动模型训练过程,将模型的Estimator对象应用到测试数据集上进行预测计算。

  6. 然后,将这个 DataFrame 与MulticlassClassificationEvaluator工具方法一起使用,以推导训练模型的准确性。

    注意

    多项式和伯努利朴素贝叶斯模型需要非负特征。因此,建议只选择具有正值的特征,或者使用其他能够处理非负值特征的分类算法。

支持向量机

支持向量机 (SVM) 是一类分类算法,它以数据点作为输入,输出最佳分隔给定数据点的超平面,将数据点分为两个不同的类别,并在二维平面上表示。因此,SVM 仅支持二分类问题。让我们使用 Spark MLlib 的 SVM 实现来实现二分类,如以下代码块所示:

from pyspark.ml.classification import LinearSVC
train_df = spark.read.table("retail_features").selectExpr("gender", "selected_features as features")
string_indexer = StringIndexer(inputCol="gender",
                               outputCol="label", 
                               handleInvalid="skip" )
svm = LinearSVC(maxIter=10, regParam=0.1)
pipeline = Pipeline(stages=[string_indexer, svm])
model = pipeline.fit(train_df)
svm_model = model.stages[1]
# Print the coefficients and intercept for linear SVC
print("Coefficients: " + str(svm_model.coefficients))
print("Intercept: " + str(svm_model.intercept))

在前面的代码块中,我们做了以下操作:

  1. 首先,我们使用了StringIndexer将性别列转换为数值标签列。

  2. 然后,我们通过指定此算法所需的超参数来初始化LinearSVC类。

  3. 然后,我们将StringIndexerLinearSVC阶段合并到一个管道中。

  4. 然后,我们在训练数据集上调用了fit方法,以开始使用管道的Transformer对象进行训练过程。

  5. 一旦模型成功训练,我们打印了模型的系数和截距。

到目前为止,你已经了解了最流行的有监督学习算法,用于解决回归和分类问题,并通过实际代码示例看到它们在 Spark MLlib 中的实现。在接下来的章节中,你将了解树集成的概念,以及如何使用树集成方法将多个决策树模型结合起来,以获得最佳模型。

树集成

非参数化学习算法,如决策树,不对学习函数的形式做任何假设,而是尝试将模型拟合到手头的数据。然而,决策树可能会出现过拟合训练数据的风险。树集成方法是利用决策树优势同时最小化过拟合风险的好方法。树集成方法将多个决策树结合起来,从而生成表现更好的预测模型。一些常见的树集成方法包括随机森林和梯度提升树。我们将探讨如何使用这些集成方法通过 Spark MLlib 构建回归和分类模型。

使用随机森林进行回归

随机森林构建多个决策树并将它们合并,从而生成一个更准确的模型并减少过拟合的风险。随机森林可以用于训练回归模型,如以下代码示例所示:

from pyspark.ml.regression import RandomForestRegressor
from pyspark.ml.evaluation import RegressionEvaluator
retail_features = spark.read.table("retail_features").selectExpr("cust_age as label", "selected_features as features")
(train_df, test_df) = retail_features.randomSplit([0.8, 0.2])
rf = RandomForestRegressor(labelCol="label", 
                           featuresCol="features", 
                           numTrees=5)
rf_model = rf.fit(train_df)
predictions = rf_model.transform(test_df)
evaluator = RegressionEvaluator(
    labelCol="label", predictionCol="prediction", 
    metricName="rmse")
rmse = evaluator.evaluate(predictions)
print("RMSE for test data = %g" % rmse)
print(rf_model.toDebugString)

在前面的代码片段中,我们做了以下操作:

  1. 首先,我们将数据集拆分为两个子集,分别用于训练和测试。

  2. 然后,我们初始化RandomForestRegressor类,并设置多个树进行训练。我们将其设置为5

  3. 接下来,我们将RandomForestRegressor转换器应用于训练数据集,以获得一个随机森林模型。

  4. 然后,我们将模型的Estimator对象应用于测试数据集,以生成实际的预测结果。

  5. 然后,这个 DataFrame 被用在RegressionEvaluator工具方法中,以得出RMSE值。

  6. 最后,我们使用模型对象的toDebugString属性打印训练好的随机森林。

使用随机森林进行分类

就像决策树一样,随机森林也支持训练多类分类模型,如下面的代码块所示:

retail_df = spark.read.table("retail_features").selectExpr("gender", "selected_features as features")
(train_df, test_df) = retail_df.randomSplit([0.8, 0.2])
string_indexer = StringIndexer(inputCol="gender", 
                               outputCol="label", 
                               handleInvalid="skip" )
rf = RandomForestClassifier(labelCol="label", 
                            featuresCol="features", 
                            numTrees=5)
pipeline = Pipeline(stages=[string_indexer, rf])
model = pipeline.fit(train_df)
predictions = model.transform(test_df)
evaluator = MulticlassClassificationEvaluator(
    labelCol="label", predictionCol="prediction", 
    metricName="accuracy")
accuracy = evaluator.evaluate(predictions)
print("Accuracy = %g " % (accuracy))
rf_model = model.stages[1]
print(rf_model.toDebugString)

在前面的代码片段中,我们做了以下操作:

  1. 首先,我们将数据集拆分为两个子集,分别用于训练和测试。这将使我们能够在训练完成后评估模型的准确性。

  2. 我们利用StringIndexer将性别字符串列转换为数值标签列。

  3. 然后,我们初始化一个RandomForestClassifier类,设置所需的超参数,并指定训练的决策树数量为5

  4. 然后,我们将StringIndexerRandomForestClassifier阶段合并到一个管道中。

  5. 然后,我们在训练数据集上调用fit方法,开始模型训练过程,并将模型的Estimator对象应用于测试数据集,以计算预测结果。

  6. 然后,这个 DataFrame 被用在MulticlassClassificationEvaluator工具方法中,以得出训练模型的准确性。

  7. 随机森林模型也可以使用toDebugString属性进行打印,模型对象上可以访问该属性。

这样,机器学习分类就可以通过 Spark 机器学习库在大规模上实现。

使用梯度提升树进行回归

梯度提升树GBTs)是另一种基于决策树的集成方法,它也能提高训练模型的稳定性和准确性,同时最小化过拟合的风险。GBTs 通过梯度提升的过程,迭代训练多个决策树,同时最小化损失函数。让我们通过下面的代码示例,探讨如何在 Spark 中使用 GBTs 训练回归模型:

from pyspark.ml.regression import GBTRegressor
from pyspark.ml.evaluation import RegressionEvaluator
retail_features = spark.read.table("retail_features").selectExpr("cust_age as label", "selected_features as features")
(train_df, test_df) = retail_features.randomSplit([0.8, 0.2])
gbt = GBTRegressor(labelCol="label",featuresCol="features",
                   maxIter=5)
gbt_model = gbt.fit(train_df)
predictions = gbt_model.transform(test_df)
evaluator = RegressionEvaluator(
    labelCol="label", predictionCol="prediction", 
    metricName="rmse")
rmse = evaluator.evaluate(predictions)
print("RMSE for test data = %g" % rmse)
print(gbt_model.toDebugString)

在前面的代码片段中,我们做了以下操作:

  1. 首先,我们将数据集拆分为两个子集,分别用于训练和测试。

  2. 然后,我们初始化GBTRegressor类,并将最大迭代次数设置为5

  3. 接下来,我们将RandomForestRegressor转换器应用于训练数据集。这导致了一个随机森林模型估算器。之后,我们将要训练的树的数量设置为5

  4. 然后,我们将模型的Estimator对象应用于测试数据集,以生成实际的预测结果。

  5. 然后,我们使用RegressionEvaluator工具方法对这个 DataFrame 进行处理,以得出RMSE值。

  6. 训练后的随机森林也可以通过模型对象的toDebugString属性打印出来。

通过这种方式,可以使用 Spark MLlib 中的 GBT 算法在大规模上实现回归。

使用 GBT 进行分类

GBT 也可以用来训练分类模型,如以下代码示例所示:

retail_df = spark.read.table("retail_features").selectExpr("gender", "selected_features as features")
(train_df, test_df) = retail_df.randomSplit([0.8, 0.2])
string_indexer = StringIndexer(inputCol="gender", 
                               outputCol="label", 
                               handleInvalid="skip" )
gbt = GBTClassifier(labelCol="label", 
                    featuresCol="features",
                    maxIter=5)
pipeline = Pipeline(stages=[string_indexer, gbt])
model = pipeline.fit(train_df)
predictions = model.transform(test_df)
evaluator = MulticlassClassificationEvaluator(
    labelCol="label", predictionCol="prediction", 
    metricName="accuracy")
accuracy = evaluator.evaluate(predictions)
print("Accuracy = %g " % (accuracy))
gbt_model = model.stages[1]
print(gbt_model.toDebugString)

在前面的代码片段中,我们做了以下操作:

  1. 首先,我们使用StringIndexer将性别字符串列转换为数字标签列。

  2. 接着,我们初始化了GBTClassifier类,并将要训练的决策树数量设置为5

  3. 然后,我们将StringIndexerRandomForestClassifier阶段合并到一个管道中。

  4. 之后,我们在训练数据集上调用fit方法,开始模型训练过程,并将模型的Estimator对象应用到测试数据集上以计算预测值。

  5. 然后,我们使用MulticlassClassificationEvaluator工具方法对这个 DataFrame 进行处理,以得出训练模型的准确度。

到目前为止,您已经探索了如何使用树集成方法将多个决策树结合起来,生成更好、更准确的机器学习模型,以解决回归和分类问题。在接下来的部分中,您将了解一些可以应用于日常场景的机器学习分类和回归模型的真实世界应用。

真实世界的监督学习应用

过去,数据科学和机器学习仅用于学术研究。然而,在过去的十年里,这一领域已在实际商业应用中找到了用途,帮助企业寻找竞争优势、提升整体业务表现并实现盈利。在本节中,我们将探讨一些机器学习的真实世界应用。

回归应用

本节将介绍机器学习回归模型的一些应用及其如何帮助改善业务表现。

客户生命周期价值估算

在任何零售或消费品行业中,客户流失率是一个重要因素,因此必须将营销预算定向到那些具有盈利潜力的客户。在非订阅型业务中,通常 20%的客户群体贡献了 80%的收入。可以利用机器学习模型来模拟并预测每个客户的生命周期价值客户生命周期价值CLV)模型有助于预测一个客户的预期生命周期,这是衡量我们预计客户还能盈利多久的一个指标。CLV 模型还可以预测单个客户在其预期生命周期内可能产生的收入。因此,回归模型可以用来估算 CLV,并帮助将营销预算集中用于吸引和留住那些在预期生命周期内具有盈利潜力的客户。

货运提前期估算

零售商、物流公司、餐饮服务聚合商或任何需要将产品交付给客户的企业,都需要能够预测将产品送达客户所需的时间。回归模型可以用来考虑诸如起始和目的地邮政编码、这两个地点之间过往的运输表现、库存可用性,以及季节性、天气状况甚至当地交通等因素,从而建立模型,估算产品到达客户所需的时间。这有助于企业进行库存优化、供应链规划,甚至提升整体客户满意度。

动态定价优化

动态定价优化,也称为动态定价,是根据当前产品需求或市场状况为产品或服务设定价格的过程。这在多个行业中都是一种常见做法,包括交通、旅游和酒店业、电子商务、娱乐业以及数字聚合平台等。企业可以利用数字经济中产生的大量数据,通过实时调整价格来优化定价。虽然动态定价是一个优化问题,回归模型可以用来预测特定时刻的价格、当前需求、市场状况以及竞争对手的定价。

分类应用

本节将讨论一些分类模型如何应用于解决业务场景的示例。

金融欺诈检测

金融欺诈和身份盗窃是金融行业面临的最大挑战之一。金融机构历来使用统计模型和基于规则的引擎来检测金融欺诈;然而,欺诈分子已通过使用新型欺诈手段绕过传统的欺诈检测机制。分类模型可以使用一些简单的算法,例如朴素贝叶斯,或者一些更为复杂的方法,例如决策树集成方法来构建。这些模型可以帮助企业应对新兴的欺诈模式,并将金融交易标记为欺诈交易。

邮件垃圾信息检测

这是任何使用电子邮件的人都曾经历过的常见场景:即收到不需要的、推销性质的或有时甚至是令人反感的电子邮件内容。电子邮件提供商正在使用分类模型来将电子邮件分类,并将垃圾邮件标记出来,从而将其排除在用户的收件箱之外。

工业机械设备故障预测

油气和建筑等重工业公司已经在其重型工业设备上安装或开始安装物联网设备,这些设备不断向后台服务器发送遥测和诊断数据。经过训练的分类模型可以帮助预测机器故障,帮助行业防止停机,标记出问题的辅助零部件供应商,甚至通过防止大规模的机械召回来节省巨额成本。

物体检测

分类模型一直是高端相机的组成部分,这些相机内置了物体跟踪和自动对焦功能。现代手机应用程序也利用分类模型来将照片中的主体与背景分离,以及识别和标记照片中的人物。

总结

在本章中,我们介绍了一类叫做监督学习算法的机器学习算法,它可以从已标记的现有数据中学习。你探讨了参数化和非参数化学习算法的概念及其优缺点。还介绍了监督学习算法的两个主要应用场景——回归和分类。通过模型训练示例以及来自 Spark MLlib 的代码,我们探索了几种常见的回归和分类模型。同时,介绍了树集成方法,这些方法通过结合多个模型并防止过拟合,提升了决策树模型的稳定性、准确性和性能。

最后,你探索了本章中介绍的各种机器学习模型在现实世界商业中的应用。我们解释了如何利用监督学习来解决商业用例,并提供了工作代码示例,帮助你使用 Spark MLlib 在大规模上训练模型并高效解决商业问题。

在下一章中,我们将探索无监督机器学习算法,了解它们与监督学习模型的区别,以及它们在解决现实世界商业问题中的应用。我们还将提供工作代码示例来展示这一点。

第八章:无监督机器学习

在前两章中,你已经了解了监督学习类的机器学习算法、它们的实际应用,以及如何使用 Spark MLlib 在大规模环境中实现它们。在本章中,你将接触到无监督学习类别的机器学习,学习有关参数化和非参数化的无监督算法。为了帮助你理解 无监督学习 在解决实际问题中的应用,我们将介绍一些 聚类关联 算法的实际应用。你将获得使用无监督机器学习进行聚类和关联问题的基本知识和理解。我们还将讨论在 Spark ML 中实现一些聚类算法的细节,如 K-means 聚类层次聚类潜在狄利克雷分配,以及一种叫做 交替最小二乘法 的关联算法。

在本章中,我们将讨论以下主要内容:

  • 无监督学习简介

  • 使用机器学习进行聚类

  • 使用机器学习建立关联

  • 无监督学习的实际应用

到本章结束时,你应该已经获得了足够的知识和实践经验,了解聚类和关联类型的无监督机器学习算法、它们的实际应用,并能够使用 Spark MLlib 在大规模环境中实现这些类型的算法。

技术要求

在本章中,我们将使用 Databricks 社区版来运行我们的代码(community.cloud.databricks.com)。

无监督机器学习简介

无监督学习是一种机器学习技术,在这种技术中,学习算法在训练数据中没有已知标签值的指导。无监督学习在根据数据中固有的模式、相似性或差异将未知数据点分组时非常有用,而无需任何先验数据知识。

在监督学习中,模型是在已知数据上进行训练的,然后使用新数据(未见过的数据)从模型中推断结论。另一方面,在无监督学习中,模型训练过程本身就是最终目标,在模型训练过程中会发现隐藏在训练数据中的模式。与监督学习相比,无监督学习更难,因为在没有任何外部评估的情况下,很难确定无监督学习算法的结果是否有意义,特别是当无法访问任何正确标记的数据时。

无监督学习的一个优势是,它有助于解释非常大的数据集,在这些数据集上标注现有数据并不现实。无监督学习还可用于预测数据集中类别的数量,或在应用监督学习算法之前对数据进行分组和聚类。它在解决分类问题时也非常有用,因为无监督学习可以很好地处理未标记的数据,且无需任何人工干预。无监督学习可以分为两种主要的学习技术,分别是聚类和关联。接下来的部分将介绍这两种方法。

使用机器学习进行聚类

在机器学习中,聚类是指在不需要任何外部指导的情况下识别未分类数据中的模式或结构。聚类算法会解析给定的数据,以识别数据集中具有匹配模式的簇或数据组。聚类算法的结果是数据簇,这些簇可以定义为在某种方式上相似的对象集合。下图展示了聚类是如何工作的:

https://github.com/OpenDocCN/freelearn-ds-pt3-zh/raw/master/docs/ess-pyspk-scl-da/img/B16736_08_01.jpg

图 8.1 – 聚类

在前面的图示中,一个未分类的数据集通过聚类算法进行处理,结果是根据数据点与另一个数据点在二维欧几里得空间中的接近程度,将数据分类成较小的簇或数据组。

因此,聚类算法基于二维平面上数据之间的欧几里得距离进行数据分组。聚类算法会考虑训练数据集中数据点之间的欧几里得距离,在同一簇内,数据点之间的距离应当较小,而簇外,数据点之间的距离应当较大。接下来的部分将介绍 Spark MLlib 中几种可用的聚类技术。

K-means 聚类

K-means 是最流行的聚类算法,也是最简单的无监督学习算法之一。K-means 聚类算法在给定数据集上以迭代的方式工作,将其分为 k 组。k 的值越大,聚类的大小越小,反之亦然。因此,使用 K-means,用户可以控制在给定数据集内识别的聚类数量。

在 K-means 聚类中,每个簇都是通过为每个簇创建一个中心来定义的。这些质心尽可能远离彼此。然后,K-means 将每个数据点与给定数据集中的最近质心进行关联,从而形成第一个簇。K-means 随后会迭代地重新计算质心在数据集中的位置,使其尽可能接近已识别簇的中心。当质心不再需要移动时,过程停止。

以下代码块演示了如何使用 Spark MLlib 实现 K-means 聚类:

from pyspark.ml.clustering import KMeans
from pyspark.ml.evaluation import ClusteringEvaluator
retail_features = spark.read.table("retail_features")
train_df = retail_features.selectExpr("selected_features as features")
kmeans = KMeans(k=3, featuresCol='features')
kmeans_model = kmeans.fit(train_df)
predictions = kmeans_model.transform(train_df)
evaluator = ClusteringEvaluator()
silhouette = evaluator.evaluate(predictions)
print("Silhouette measure using squared Euclidean distance = " + str(silhouette))
cluster_centers = kmeans_model.clusterCenters()
print(cluster_centers)

在前面的代码片段中,我们进行了以下操作:

  1. 首先,我们使用 import 导入了与聚类和聚类评估相关的适当 MLlib 包。

  2. 然后,我们将之前在特征工程过程中获得的现有特征向量导入到 Spark DataFrame 中,并以 Delta 格式将其存储在数据湖中。

  3. 接下来,我们初始化了一个新的 KMeans 对象,通过传入所需的聚类数和特征向量的列名来进行设置。

  4. 我们在训练 DataFrame 上调用了 fit() 方法,启动了学习过程。结果生成了一个模型对象。

  5. 通过调用模型对象的 transform() 方法,生成了原始训练数据集上的预测结果。

  6. 接下来,我们调用了 Spark MLlib 的 ClusteringEvaluator() 辅助函数,该函数用于评估聚类算法,并将其应用于我们在前一步生成的预测 DataFrame。这会得到一个被称为 silhouette 的值,它是衡量聚类一致性的指标,计算方式基于数据点之间的欧氏距离度量。silhouette 值越接近 1,表示簇内的数据点越聚集,簇外的数据点则相距较远。silhouette 值越接近 1,表示学习到的模型性能越好。

  7. 最后,我们打印了每个分类簇的质心。

这样,只需几行代码,就可以通过 Spark 实现的 K-means 聚类算法轻松地将未分类数据进行聚类。

使用二分 K-means 的层次聚类

层次聚类是一种聚类技术,其中所有数据点从一个单一的聚类开始。然后它们通过递归地向下分割到更小的聚类中,从而构成一个层次结构。Spark ML 通过二分 K 均值算法实现这种分裂式的层次聚类。以下示例说明了如何使用 Spark MLlib 实现二分 K 均值聚类:

from pyspark.ml.clustering import BisectingKMeans
from pyspark.ml.evaluation import ClusteringEvaluator
retail_features = spark.read.table("retail_features")
train_df = retail_features.selectExpr("selected_features as features")
bkmeans = BisectingKMeans(k=3, featuresCol='features')
bkmeans_model = kmeans.fit(train_df)
predictions = bkmeans_model.transform(train_df)
evaluator = ClusteringEvaluator()
silhouette = evaluator.evaluate(predictions)
print("Silhouette measure using squared euclidean distance = " + str(silhouette))
cluster_centers = kmeans_model.clusterCenters()
print(cluster_centers)

在之前的代码片段中,我们执行了以下操作:

  1. 首先,我们通过传入所需聚类的数量和特征列的列名来初始化一个新的BisectingKMeans对象。

  2. 我们在训练数据集上调用了fit()方法以开始学习过程。作为结果,生成了一个模型对象。

  3. 接下来,我们通过在模型对象上调用transform()方法,对原始训练数据集生成了预测结果。

  4. 然后,我们调用了 Spark MLlib 的ClusteringEvaluator()辅助函数,该函数对于评估聚类算法非常有用,并将其应用于我们在前一步生成的预测数据框。这将得到silhouette值,它是衡量聚类内一致性的指标,基于数据点之间的欧几里得距离计算。

  5. 最后,我们打印了每个聚类的中心点。

现在我们已经学习了一种聚类技术,让我们在下一节中了解另一种学习技术。

使用潜在狄利克雷分配进行主题建模

主题建模是一种学习技术,通过它你可以对文档进行分类。主题建模与主题分类不同,因为主题分类是一种有监督学习技术,其中学习模型尝试根据一些先前标注的数据对未见过的文档进行分类。而主题建模则像聚类算法对数值数据进行分组一样,在没有任何外部指导的情况下对包含文本或自然语言的文档进行分类。因此,主题建模是一个无监督学习问题。

潜在狄利克雷分配LDA)是一种流行的主题建模技术。LDA 的目标是根据文档中发现的关键词将给定文档与特定主题关联起来。在这里,主题是未知的,隐藏在文档中,这就是 LDA 中的潜在部分。LDA 的工作方式是,假设文档中的每个单词都属于一个不同的主题,并为每个单词分配一个概率值。一旦估算出每个单词属于特定主题的概率,LDA 就会通过设置阈值并选择所有符合或超过该阈值的单词来挑选属于某个主题的所有单词。LDA 还认为每个文档只是一个词袋,并不重视单个单词在语法中的角色。此外,像文章、连词和感叹词等停用词需要在应用 LDA 之前被去除,因为这些词并不携带任何主题信息。

以下代码示例说明了如何使用 Spark MLlib 实现 LDA:

from pyspark.ml.clustering import LDA
train_df = spark.read.table("retail_features").selectExpr("selected_features as features")
lda = LDA(k=3, maxIter=1)
lda_model = lda.fit(train_df)
topics = lda_model.describeTopics(3)
topics.show()
transformed = lda_model.transform(dataset)
transformed.show()

在前面的代码片段中,我们做了以下操作:

  1. 首先,我们导入了与 LDA 相关的适当 MLlib 包。

  2. 接下来,我们将特征工程过程中生成的现有特征向量导入到 Spark DataFrame 中,并以 Delta 格式将其存储在数据湖中。

  3. 之后,我们通过传入聚类的数量和最大迭代次数来初始化一个新的LDA对象。

  4. 接下来,我们在训练 DataFrame 上调用了fit()方法,开始了学习过程。最终生成了一个模型对象。

  5. 使用 LDA 算法建模的主题可以通过在模型对象上使用describeTopics()方法来显示。

正如我们所见,通过使用 Apache Spark 实现的 LDA 算法,主题建模可以在大规模上实现。

高斯混合模型

K-means 聚类的一个缺点是它会将每个数据点与一个特定的聚类关联起来。这样,就无法获得数据点属于某一特定聚类的概率。高斯混合模型GSM)尝试解决 K-means 聚类的硬聚类问题。

GSM 是一个概率模型,用于表示数据点总体样本中的一个子集。GSM 表示多个高斯分布的数据点的混合,其中数据点从其中一个K高斯分布中抽取,并具有属于这些分布之一的概率得分。

以下代码示例描述了使用 Spark ML 实现 GSM 的实现细节:

from pyspark.ml.clustering import GaussianMixture
train_df = spark.read.table("retail_features").selectExpr("selected_features as features")
gmm = GaussianMixture(k=3, featuresCol='features')
gmm_model = gmm.fit(train_df)
gmm_model.gaussiansDF.display()

在前面的代码块中,我们在从pyspark.ml.clustering包中导入适当的库后,初始化了一个新的GaussianMixture对象。然后,我们传入了一些超参数,包括聚类的数量和包含特征向量的列的名称。接着,我们使用fit()方法训练了模型,并通过模型的gaussianDF属性显示了训练后的结果。

到目前为止,您已经看到了不同类型的聚类和主题建模技术及其在使用 Spark MLlib 时的实现。在接下来的部分中,您将了解另一种叫做关联规则的无监督学习算法。

使用机器学习构建关联规则

if-then-else语句有助于展示实体之间关系的概率。关联规则技术广泛应用于推荐系统、市场篮子分析和亲和力分析等问题。

使用交替最小二乘法进行协同过滤

在机器学习中,协同过滤更常用于 推荐系统。推荐系统是一种通过考虑用户偏好来过滤信息的技术。根据用户的偏好并考虑他们的过去行为,推荐系统可以预测用户可能喜欢的项。协同过滤通过利用历史用户行为数据和他们的偏好,构建用户-项关联矩阵来执行信息过滤。Spark ML 使用 交替最小二乘法 算法实现协同过滤技术。

以下代码示例演示了 Spark MLlib 中交替最小二乘算法的实现:

from pyspark.ml.evaluation import RegressionEvaluator
from pyspark.ml.recommendation import ALS
from pyspark.sql import Row
ratings_df = (spark.read.table("retail_features").selectExpr(
    "CAST(invoice_num AS INT) as user_id",
    "CAST(stock_code AS INT) as item_id",
    "CAST(quantity AS INT) as rating")
    .where("user_id is NOT NULL AND item_id is NOT NULL"))
df.display()
(train_df, test_df) = ratings_df.randomSplit([0.7, 0.3])
als = ALS(maxIter=3, regParam=0.03, userCol="user_id", 
          itemCol="item_id", ratingCol="rating", 
          coldStartStrategy="drop")
als_model = als.fit(train_df)
predictions = model.transform(test_df)
evaluator = RegressionEvaluator(metricName="rmse", 
                                labelCol="rating", 
                                predictionCol="prediction")
rmse = evaluator.evaluate(predictions)
print("Root-mean-square error = " + str(rmse))
user_recs = als_model.recommendForAllUsers(5)
user_recs.show()
item_recs = als_model.recommendForAllItems(5)
item_recs.show()

在前面的代码块中,我们做了以下操作:

  1. 首先,我们使用存储在 Delta Lake 中的特征数据集生成了一个 Spark DataFrame 作为评分数据集。ALS 算法所需的几个列,如 user_iditem_idratings,并未采用所需的整数格式。因此,我们使用了 CAST Spark SQL 方法将其转换为所需的数据格式。

  2. 接下来,我们使用所需的参数初始化了一个 ALS 对象,并通过 randomSplit() 方法将训练数据集随机分成了两部分。

  3. 然后,我们通过在训练数据集上调用 fit() 方法启动了学习过程。

  4. 接下来,我们使用 Spark MLlib 提供的评估器评估了准确度指标的 RMSE

  5. 最后,我们通过内置的 recommendForAllUsers()recommendForAllItems() 方法分别收集了每个用户的前 5 个推荐项和每个项的前 5 个用户推荐。

通过这种方式,你可以利用交替最小二乘法构建推荐系统,用于诸如 视频点播 平台的电影推荐、产品推荐或电子商务应用中的 市场篮分析 等用例。Spark MLlib 使你能够只用几行代码就实现这一规模。

除了聚类和关联规则外,Spark MLlib 还允许你实现 降维 算法,如 奇异值分解SVD)和 主成分分析PCA)。降维是减少考虑的随机变量数量的过程。尽管它是一种无监督学习方法,但降维在特征提取和选择中非常有用。关于这一主题的详细讨论超出了本书的范围,Spark MLlib 仅为 RDD API 提供了降维算法的实现。有关降维的更多详细信息,可以在 Apache Spark 的公共文档中找到,网址为 spark.apache.org/docs/latest/mllib-dimensionality-reduction.html

在下一节中,我们将深入探讨一些当前各大企业正在使用的无监督学习算法的实际应用。

无监督学习的实际应用

无监督学习算法如今正被用来解决一些现实世界中的商业挑战。在本节中,我们将探讨几个这样的挑战。

聚类应用

本节介绍了一些聚类算法在实际商业中的应用。

客户细分

零售营销团队以及面向消费者的企业组织,始终致力于优化其营销支出。尤其是营销团队,他们关注一个特定的指标——每次获取成本CPA)。CPA 表示组织需要花费多少才能获得一个客户,最优的 CPA 意味着更好的营销投资回报。优化 CPA 的最佳方式是通过客户细分,因为这能提高营销活动的效果。传统的客户细分会考虑标准的客户特征,如人口统计、地理位置和社会信息,以及历史交易数据,从而定义标准的客户群体。然而,传统的客户细分方法费时费力,且容易出错。与此同时,可以利用机器学习算法发现数据源之间隐藏的模式和关联。近年来,客户接触点的数量大幅增加,手动识别所有这些接触点之间的模式变得不实际且不直观。然而,机器学习算法能够轻松地分析数百万条记录,提取出营销团队可以迅速利用的见解,从而满足客户的需求,在客户需要的时候、他们想要的地方。因此,通过利用聚类算法,营销人员可以通过更加精准的客户细分,提高营销活动的有效性。

零售产品组合优化

拥有实体店面的零售商面临有限的店铺空间。因此,他们需要确保通过放置那些最有可能销售的产品来实现店铺空间的最佳利用。一个经典的商品组合优化案例是美国中西部地区的一个五金零售商,在寒冷的冬季进货草坪割草机,当时雪季几乎在整个冬季都会持续。在这个例子中,店铺空间没有得到最佳利用,因为草坪割草机在雪季几乎没有销售机会。更好的选择应该是空间加热器、雪铲或其他冬季用品。为了解决这个问题,零售商通常会雇佣分析师,结合历史交易数据、季节性变化和当前趋势,提出适合季节和商店位置的商品组合推荐。然而,如果我们把这个问题规模扩大到一个拥有成千上万仓库和数万个零售店的大型零售商呢?在这种规模下,手动规划商品的最佳组合变得不切实际且非常耗时,极大地降低了实现价值的速度。商品组合优化可以看作一个聚类问题,聚类算法可以应用于帮助规划如何对这些群体进行排序。在这里,必须考虑更多的数据点,包括历史消费者购买模式、季节性变化、社交媒体上的趋势、搜索引擎的搜索模式等。这不仅有助于更好的商品组合优化,还能提高收入,减少产品浪费,并加快企业的市场响应速度。

客户流失分析

由于客户偏好的不断变化和市场竞争的激烈,企业越来越难以获取新客户。因此,企业需要留住现有客户。客户流失率是企业高层希望最小化的一个重要指标。机器学习分类算法可以用于预测某个客户是否会流失。然而,了解影响流失的因素会很有帮助,这样企业可以调整或改进运营以提高客户满意度。聚类算法不仅可以用来识别哪些客户群体可能会流失,还可以通过识别影响流失的一组因素来进一步分析。企业可以根据这些流失因素采取行动,引入新产品或改进产品以提高客户满意度,进而减少客户流失。

保险欺诈检测

保险公司传统上使用人工检查以及规则引擎来标记保险索赔是否为欺诈。然而,随着数据量的增加,传统方法可能会错过相当大一部分的索赔,因为人工检查既费时又容易出错,且欺诈者不断创新并策划新的欺诈方式。机器学习的聚类算法可以用来将新的索赔与现有的欺诈群体进行分组,而分类算法可以用来判断这些索赔是否为欺诈。通过利用机器学习和聚类算法,保险公司可以不断地检测和防止保险欺诈。

关联规则和协同过滤应用

关联规则和协同过滤是用于构建推荐系统的技术。本节将探讨推荐系统的一些实际应用案例,以便于实际商业应用。

推荐系统

推荐系统被电子零售商用来进行市场购物篮分析,在该分析中,系统根据用户的偏好以及购物车中已存在的商品向用户推荐产品。推荐系统还可以用于基于位置或临近度的推荐,例如,当客户靠近特定商店时,系统可以显示广告或优惠券。推荐系统还广泛应用于营销领域,营销人员可以通过推荐系统获取有关可能购买某商品的用户的信息,从而提高营销活动的效果。

推荐系统在在线音乐和视频服务提供商中也得到了广泛应用,用于用户内容个性化。在这里,推荐系统根据用户的偏好和历史使用模式,向用户推荐新的音乐或视频。

总结

本章介绍了无监督学习算法,以及如何对未标记的数据进行分类并识别数据实体之间的关联。介绍了无监督学习算法的两个主要领域,即聚类和关联规则。你了解了最流行的聚类算法和协同过滤算法,并通过 Spark MLlib 中的代码示例展示了 K-means、二分 K-means、LDA 和 GSM 等聚类算法的工作原理。你还看到了使用 Spark MLlib 中替代最小二乘法算法构建推荐引擎的代码示例。最后,展示了一些无监督学习算法在现实商业中的应用。我们探讨了关于无监督学习算法的若干概念、技术和代码示例,以便你能够使用 Spark MLlib 在大规模上训练模型。

到目前为止,在本章和前一章中,你只探讨了机器学习过程中的数据清洗、特征工程和模型训练部分。在下一章中,你将接触到机器学习生命周期管理,在这里你将探索一些概念,如模型性能调优、跟踪机器学习实验、将机器学习模型存储在中央仓库中,以及在将它们投入生产应用之前进行机器学习模型的运营化。最后,还将介绍并探索一个开源的端到端机器学习生命周期管理工具——MLflow。

第九章:机器学习生命周期管理

在前面的章节中,我们探讨了使用Apache Spark进行可扩展机器学习的基础知识。介绍了处理监督学习无监督学习的算法,并展示了如何使用Apache Spark MLlib实现这些算法。在现实世界中,仅训练一个模型是不够的。相反,必须通过调整模型参数,使用相同的数据集构建多个版本的同一模型,以获得最佳的模型。此外,同一个模型可能并不适用于所有应用,因此需要训练多个模型。因此,有必要跟踪各种实验、它们的参数、指标以及它们训练所用的数据版本。而且,模型通常会出现漂移,意味着它们的预测能力会因为环境变化而降低,因此需要进行监控并在必要时重新训练。

本章将介绍实验跟踪、模型调优、模型生产化以及使用离线和在线技术进行模型推理的概念。本章将通过一个端到端的开源机器学习生命周期管理工具MLflow来展示这些概念。最后,我们将探讨机器学习的持续部署概念,以自动化整个机器学习ML)生命周期管理过程。

本章我们将覆盖以下主要内容:

  • 机器学习生命周期简介

  • 使用MLflow跟踪实验

  • 使用MLflow 模型注册表跟踪模型版本

  • 模型服务和推理

  • 机器学习的持续部署

技术要求

本章我们将使用 Databricks Community Edition 来运行代码(community.cloud.databricks.com)。

我们还将使用 Databricks Community Edition 提供的托管版 MLflow。安装独立版 MLflow 的说明可以在这里找到:mlflow.org/docs/latest/quickstart.html

机器学习生命周期简介

机器学习生命周期是数据科学项目所遵循的一个持续过程。它包含四个主要阶段,分别是数据收集与准备、模型训练、模型评估,最后是模型推理和监控。机器学习过程是一个持续的过程,生命周期在优化数据和不断提高模型性能之间反复迭代;或者说,防止模型性能随着时间的推移而下降:

https://github.com/OpenDocCN/freelearn-ds-pt3-zh/raw/master/docs/ess-pyspk-scl-da/img/B16736_09_01.jpg

图 9.1 – 机器学习生命周期

前面的图表展示了机器学习生命周期管理的持续过程,从数据准备到模型开发,然后从训练到模型部署和监控。当模型性能因训练数据的变化、模型代码的变化或模型参数的变化而下降时,循环过程将重新开始。

前几章介绍了数据收集与准备、清洗和整合的过程,以及在大规模上训练各种机器学习模型的技术。本章将介绍机器学习生命周期的其余阶段,包括模型评估、模型推理和监控。

机器学习生命周期管理的这一循环过程帮助你不断优化数据集和模型,并保持模型的性能。你在机器学习生命周期中的迭代速度决定了你将模型投入实际应用的速度,从而决定了你的数据科学项目对企业的价值以及与数据科学项目相关的成本。因此,使用机器学习生命周期管理工具来简化整个机器学习过程、从数据科学项目中获取最大价值,并确保业务用户能够从中获得实际收益是至关重要的。目前存在多种机器学习生命周期管理工具可以处理这一任务。PacydermKubeflowMLflowMetaflow是一些开源工具,而AWS SagemakerGoogle Cloud AutoMLMicrosoft Azure ML都是具有完整机器学习生命周期管理支持的云原生服务。本章将探索MLflow作为机器学习生命周期管理工具,接下来的章节将更详细地探讨MLflow

MLflow 介绍

在传统软件开发中,生命周期代码是按照给定的功能规格编写的。然而,在机器学习中,目标是优化特定的指标,直到达到所需的准确度。优化某一特定指标的过程不是一次性的,而是一个持续的实验和改进过程。此外,在机器学习中,结果的质量不仅仅依赖于代码的质量,还与其他参数有关,比如用于机器学习训练过程的数据以及提供给训练算法的参数。传统机器学习使用的编程堆栈在不同的数据科学家之间差异很大。最后,在一个环境中创建的机器学习模型通常会被部署到不同的环境中,因此确保模型的可移植性和可复现性也非常重要。因此,整个机器学习生命周期是非常迭代的,涉及多个参数、数据集和各种编程库。

MLflow 是一款易于使用、模块化的端到端机器学习生命周期管理开源软件,旨在解决上述机器学习生命周期中的挑战:

https://github.com/OpenDocCN/freelearn-ds-pt3-zh/raw/master/docs/ess-pyspk-scl-da/img/B16736_09_02.jpg

图 9.2 – 使用 MLflow 的机器学习生命周期

MLflow 包含以下四个组件,用于解决机器学习生命周期中的挑战,如实验跟踪管理、实验可复现性、模型库和模型部署:

  • MLflow 跟踪

  • MLflow 项目

  • MLflow 模型

  • 模型注册表

下面的章节将进一步探讨这些组件。你将学习这些组件如何帮助解决机器学习生命周期管理中的挑战,并查看一些代码示例。

使用 MLflow 跟踪实验

在现实中,构建单一模型是远远不够的。一个典型的模型构建过程需要多次迭代,有时需要改变模型参数,有时需要调整训练数据集,直到达到理想的模型准确度。有时,适合某个特定用例的模型在另一个场景中可能并不适用。这意味着一个典型的数据科学过程涉及对多个模型进行实验,以解决单一的业务问题,并记录所有数据集、模型参数和模型指标,以供未来参考。传统上,实验跟踪是通过使用简单的工具,如电子表格来完成的,但这会拖慢生产速度,而且是一个繁琐的过程,容易出错。

MLflow 跟踪组件通过其 API 和 UI 来解决这个问题,用于记录机器学习实验,包括模型参数、模型代码、指标、模型训练过程的输出,以及与实验相关的任何任意文档。让我们学习如何安装 MLflow 并使用跟踪服务器跟踪实验:

%pip install mlflow

上述命令将在你的 Databricks 笔记本中安装 MLflow 并重启 Python 会话。

注意

前一个命令只会在您的本地 Python 会话中安装 MLflow 客户端库。我们将使用 Databricks 社区版提供的托管 MLflow 作为跟踪服务器组件。有关在 Databricks 之外配置和运行 MLflow Tracking 服务器的说明可以在这里找到:mlflow.org/docs/latest/tracking.html#how-runs-and-artifacts-are-recorded

在以下代码示例中,我们正在构建一个简单的回归模型,该模型在之前的章节中使用过,并使用 MLflow Tracking 来跟踪实验:

import mlflow
import mlflow.spark
from pyspark.ml.evaluation import RegressionEvaluator
from pyspark.ml.regression import LinearRegression
from pyspark.ml.tuning import ParamGridBuilder, CrossValidator

在之前的代码示例中,我们执行了以下操作:

  • 首先,我们导入相关的库。在这里,我们导入了各种 MLflow 客户端库,还通过mlflow.spark导入了特定于 Spark 的 MLflow 组件。

  • 在这个代码示例中,我们使用pyspark.ml库构建了多个回归模型来执行这些操作以衡量我们模型的准确性。

现在,我们需要初始化 MLflow Tracking,以便开始跟踪我们的实验,如下面的代码块所示:

mlflow.set_tracking_uri("databricks")
retail_features = spark.read.table("retail_features")
retail_df = retail_features.selectExpr("cust_age as label", "selected_features as features")
train_df, test_df = retail_df.randomSplit([0.9, 0.1])

现在,我们必须初始化训练数据集,如下面的代码块所示:

  • 首先,从我们在之前章节中创建的特征 Delta 表中创建一个retail_features DataFrame。我们必须从包含我们从丰富的零售交易数据中提取的所有特征的 DataFrame 中选择用于模型训练的标签和特征列。

  • 现在,我们必须使用randomSplit()函数将包含标签和特征列的 DataFrame 随机拆分为两个单独的 DataFrame。训练 DataFrame 用于模型训练目的,而测试 DataFrame 则保留用于在训练模型后检查模型准确性。

在这一点上,我们可以开始实验过程,如下面的代码块所示:

evaluator = RegressionEvaluator(labelCol="label", 
                                metricName="rmse")
mlflow.set_tracking_uri("databricks")
mlflow.set_experiment("/Users/snudurupati@outlook.com/linregexp")
experiment = mlflow.get_experiment_by_name("/Users/snudurupati@outlook.com/linregexp")

在前面的代码块中,我们设置了我们的初始算法参数和 MLflow Tracking 服务器参数:

  • 我们实例化了RegressionEvaluator对象,该对象传递标签列并使用 RMSE 作为准确性指标。这在交叉验证过程中计算标签列上的rmse时非常有用。

  • 现在我们准备开始我们的实验,通过使用mlflow.set_tracking_uri("databricks")函数为 MLflow Tracking 服务器配置我们的实验。

  • MLflow Tracking 服务器可以跟踪来自多个用户、多个会话的多个实验。因此,您必须为您的实验提供一个唯一的名称,我们可以使用mlflow.set_experiment("/Users/user_name/exp_name")来实现。指定给实验名称的路径需要是一种持久存储形式,比如本地磁盘或数据湖位置。在这种情况下,我们使用了Databricks 文件系统DBFS)。

在之前的代码块中,我们将 URI 指定为databricks,因为我们打算使用 Databricks 社区版自带的 MLflow Tracking 服务器。如果你在本地运行 MLflow,可以将 URI 指定为./mlruns,或者如果你自己设置了远程跟踪服务器,可以提供远程服务器的 URI。如何设置自己的跟踪服务器的说明请参见mlflow.org/docs/latest/tracking.html#mlflow-tracking-servers

lr = LinearRegression(maxIter=10)
paramGrid = (ParamGridBuilder()
    .addGrid(lr.regParam, [0.1, 0.01]) 
    .addGrid(lr.fitIntercept, [False, True])
    .addGrid(lr.elasticNetParam, [0.0, 0.5, 1.0])
    .build())
csv = CrossValidator(estimator=lr,
                          estimatorParamMaps=param_grid,
                          evaluator=RegressionEvaluator(),
                          numFolds=2)

现在我们已经初始化了所有实验所需的对象,并通过 MLflow 配置了实验跟踪,我们可以开始训练过程:

  • 首先,我们必须实例化LinearRegression模型对象,并将maxIter参数设置为10

  • 然后,我们必须为交叉验证过程设置参数网格,并为模型参数指定一系列值。

  • 最后,我们必须通过初始化CrossValidator对象来配置训练-验证拆分过程。我们可以通过将实际模型对象、参数网格对象和评估对象作为参数传递来完成此操作。

关于CrossValidator对象如何工作的更多细节将在接下来的章节中提供。现在,我们准备好开始模型训练实验过程,如下方代码块所示:

with mlflow.start_run() as run_id:
  lr_model = csv.fit(train_df)
  test_metric = evaluator.evaluate(lr_model.transform(test_df))
  mlflow.log_metric(evaluator.getMetricName(), 
                    test_metric) 
  mlflow.spark.log_model(spark_model=lr_model.bestModel, 
                         artifact_path='best-model') 

在之前的代码示例中,我们调用了 MLflow Tracking 服务器以开始跟踪我们的实验:

  • 由于我们使用的是交叉验证技术,并且已经定义了一个带有值范围的参数网格,fit()过程将构建多个模型,而不是单一模型,并根据指定的准确度指标记录最佳模型。由于我们希望记录在此过程中构建的所有模型,因此我们必须使用mlflow.start_run()方法调用 MLflow Tracking 服务。

  • 在交叉验证过程中生成的度量值将使用mlflow.log_metric()函数记录到 MLflow Tracking 服务器。在 Databricks 管理的 MLflow 环境中,使用CrossValidator时,模型参数会自动记录。然而,也可以使用mlflow.log_parameter()函数显式记录模型参数,并且任何任意的工件,如图表或图像,可以使用mlflow.log_artifact()函数记录。

  • CrossValidator会遍历多个模型,并根据指定的准确度指标生成最佳模型。在此过程中生成的最佳模型作为bestModel对象可用。可以使用mlflow.spark.log_model(spark_model=lr_model.bestModel, artifact_path='best-model')命令记录该模型。这里,artifact_path表示模型在 MLflow Tracking 服务器中存储的路径。

以下截图显示了我们刚刚执行的 ML 实验的 MLflow Tracking 界面:

https://github.com/OpenDocCN/freelearn-ds-pt3-zh/raw/master/docs/ess-pyspk-scl-da/img/B16736_09_03.jpg

图 9.3 – MLflow 模型跟踪界面

在前面的截图中,我们可以看到,MLflow 记录了我们在 交叉验证 过程中为每个模型构建的准确度指标和模型参数,作为一个单独的实验。然而,只有所有运行中表现最好的模型被记录下来。可以通过点击其中一项运行来访问单个运行的详细信息,如以下截图所示:

https://github.com/OpenDocCN/freelearn-ds-pt3-zh/raw/master/docs/ess-pyspk-scl-da/img/B16736_09_4.jpg

图 9.4 – 从 MLflow Tracking UI 运行的各个模型

在前面的截图中,所有的模型参数和指标都已记录在 MLflow Tracking UI 中,并附带其他元数据,如模型的运行日期和时间、用户名、此特定模型运行的源代码版本、模型运行的持续时间以及模型的状态。只要数据的相同版本可用,这些信息在未来重现实验或在不同环境中重现相同实验时非常有用。

提示

如果数据存储在 Delta 表中,可以使用 MLflow Tracking 跟踪实验中使用的数据版本,因为 Delta 提供了内置的数据版本控制。可以通过将 Delta 表的版本作为任意工件记录到 MLflow 来实现这一点。

mlflow. 命令在前面的代码片段中指定了我们使用的是 MLflow 模型的 Spark 版本。MLflow 支持其他类型的模型,如以下章节所述。

MLflow 模型

MLflow 模型是一种通用的、可移植的模型格式,支持多种模型类型,从简单的 Python pickle 对象到 scikit-learn、TensorFlow、PyTorch、MLeap 和其他模型格式,包括 Parquet 格式的 Spark 模型。MLflow 模型提供了可以使用各种常见机器学习工具生成的抽象模型,并可部署到各种机器学习环境中。MLflow 模型以 MLflow 格式提供来自流行机器学习框架的模型。MLflow 模型具有标准的目录结构,其中包含配置文件和序列化的模型工件。它还包含所有模型评估依赖项,以 conda 环境的形式通过 conda.yaml 文件提供模型的可移植性和可重现性。

https://github.com/OpenDocCN/freelearn-ds-pt3-zh/raw/master/docs/ess-pyspk-scl-da/img/B16736_09_5.jpg

图 9.5 – MLflow 模型

上面的截图展示了一个典型的 MLflow 模型结构。这个标准模型格式创建了一个可以被任何下游工具理解的模型风味。模型风味用于部署目的,帮助我们理解来自任何机器学习库的模型,而无需将每个工具与每个库集成。MLflow 支持许多它内建的部署工具支持的主要模型风味,例如描述如何将模型作为 Python 函数运行的简单 Python 函数风味。例如,在前面的代码示例中,我们将模型存储为一个 Spark 模型,然后可以将其加载为 Spark 对象,或者作为一个简单的 Python 函数,在任何 Python 应用程序中使用,即使这些应用程序根本不理解 Spark。

ML 模型调优

模型调优是模型构建过程中一个重要的环节,通过编程识别最佳模型参数,以实现最佳模型性能。通过反复调整模型参数来选择模型的过程称为 超参数调优。典型的超参数调优过程包括将可用数据分割成多个训练集和测试集。然后,对于每个训练数据集,一个测试对会遍历一组模型参数,称为参数网格,并从所有训练过的模型中选择性能最佳的模型。

Spark ML 提供了一个 ParamGridBuilder 工具来帮助构建参数网格,以及 CrossValidatorTrainValidationSplit 类来处理模型选择。CrossValidator 通过将数据集分割成一组折叠,将这些折叠作为独立的训练集和测试集对进行模型选择,每个折叠仅使用一次作为测试集。使用 ParamGridBuilderCrossValidator 进行模型调优和选择的示例已在前一节的代码示例中展示。TrainValidationSplit 是另一种流行的超参数调优技术。有关其在 Apache Spark 中的实现细节,可以参阅 Spark 的文档页面 spark.apache.org/docs/latest/ml-tuning.html#train-validation-split

现在,我们已经学习了如何使用 Apache Spark 进行模型选择调优,以及如何使用 MLflow 跟踪实验,机器学习生命周期管理的下一步是将模型及其版本存储在中央模型仓库中,以便后续使用。在接下来的章节中,我们将使用 MLflow 模型注册表 探索这一过程。

使用 MLflow 模型注册表跟踪模型版本

虽然 MLflow 跟踪服务器可以让您跟踪所有机器学习实验的属性,MLflow 模型注册中心提供了一个集中式的模型存储库,让您跟踪模型生命周期的各个方面。MLflow 模型注册中心包括一个用户界面和 API,用于跟踪模型的版本、血统、阶段过渡、注释以及任何开发者评论。MLflow 模型注册中心还包含用于 CI/CD 集成的 Webhooks 和用于在线模型服务的模型服务器。

MLflow 模型注册中心为我们提供了一种跟踪和组织在开发、测试和生产过程中由企业生成和使用的众多机器学习模型的方法。模型注册中心通过利用访问控制列表提供了一种安全的共享模型方式,并提供与模型治理和审批工作流集成的方法。模型注册中心还允许我们通过其 API 监控 ML 部署及其性能。

提示

模型注册中心的访问控制和设置已注册模型权限的功能仅在 Databricks 完整版中可用。它们在 Databricks 社区版或开源版本的 MLflow 中不可用。

一旦模型被记录到模型注册中心,您可以通过 UI 或 API 添加、修改、更新、过渡或删除模型,如下列代码示例所示:

import mlflow
from mlflow.tracking.client import MlflowClient
client = MlflowClient()
model_name = "linear-regression-model"
artifact_path = "best_model"
model_uri = "runs:/{run_id}/{artifact_path}".format (run_id=run_id, artifact_path=artifact_path)
registered_model = mlflow.register_model(model_uri=model_uri, name=model_name, )
client.update_model_version(
  name=registered_model.name,
  version=registered_model.version,
  description="This predicts the age of a customer using transaction history."
)
client.transition_model_version_stage(
  name=registered_model.name,
  version=registered_model.version,
  stage='Staging',
)
model_version = client.get_model_version(
  name=registered_model.name,
  version=registered_model.version,
)
model_uri = "models:/{model_name}/staging".format(model_name=model_name)
spark_model = mlflow.spark.load_model(model_uri)

在之前的代码片段中,我们执行了以下操作:

  1. 首先,我们导入了相关的 MLflow 库和 MlflowClient,它是一个通过 Python 访问 MLflow 跟踪服务器和模型注册中心工件的 MLflow 接口。客户端接口通过 MlflowClient() 方法调用。

  2. 然后,我们通过提供模型、名称、模型工件位置和来自已跟踪实验的 run_id 属性来构建 model_uri。这些信息可以通过 MLflow 跟踪服务器访问,无论是通过 API 还是 UI。

  3. 由于我们已经从跟踪服务器重建了模型 URI,我们通过 register_model() 函数将模型注册到模型注册中心。如果该模型尚未存在于模型注册中心,则会注册一个版本为1的新模型。

  4. 一旦模型注册完成,我们就可以通过 update_model_version() 方法添加或更新模型描述。

  5. 在模型的生命周期中,它通常会不断演变,并需要过渡其阶段,比如从暂存阶段到生产阶段或归档阶段。可以使用 transition_model_version_stage() 方法来实现这一点。

  6. 可以使用 get_model_version() 方法列出已注册模型的版本和阶段。

  7. 最后,我们使用 load_model() 方法从模型注册中心加载特定版本/阶段的模型,在构建模型 URI 时使用了 models 关键字、模型名称及其版本或阶段名称。

模型注册表还提供了其他实用功能,用于列出和搜索个别已注册模型的不同版本,以及存档和删除已注册的模型。相关方法的参考资料可以在 www.mlflow.org/docs/latest/model-registry.html#model-registry-workflows 中找到。

重要提示

我们将在本书中使用的 Databricks Community Edition 并不包含模型注册表,因此你需要使用 Databricks 的完整版来执行前面的代码示例。或者,你可以部署自己的 MLflow 远程跟踪服务器,并在 Databricks 环境之外使用数据库支持的后端存储,具体方法可以参见:mlflow.org/docs/latest/tracking.html#mlflow-tracking-servers

现在你已经学会了如何使用 MLflow 模型注册表存储、版本管理和检索模型,机器学习生命周期的下一步是将训练、评估和调优后的模型应用到实际业务中,通过它们来进行推理。我们将在下一节中讨论这一内容。

模型服务与推理

模型服务与推理是整个机器学习生命周期中最重要的步骤。这是将已构建的模型部署到业务应用程序中的阶段,以便我们能够从中得出推理。模型服务与推理可以通过两种方式进行:在离线模式下使用批处理,或者在在线模式下实时推理。

离线模型推理

离线模型推理是指使用批处理生成来自机器学习模型的预测。批处理推理作业按预定时间表定期运行,每次都在一组新的数据上生成预测。这些预测会存储在数据库中或数据湖中,并以离线或异步的方式供业务应用程序使用。批处理推理的一个例子是由营销团队使用的数据驱动客户细分,或者零售商预测客户的生命周期价值。这些用例并不需要实时预测,批处理推理可以满足需求。

对于 Apache Spark,批处理推理可以利用 Spark 的可扩展性,在非常大的数据集上大规模地进行预测,以下代码示例演示了这一点:

model_uri = "runs:/{run_id}/{artifact_path}".format(run_id=active_run.info.run_id, artifact_path="best-model")
spark_udf = mlflow.pyfunc.spark_udf(spark, model_uri)
predictions_df = retail_df.withColumn("predictions", spark_udf(struct("features")))
predictions_df.write.format("delta").save("/tmp/retail_predictions")

在之前的代码块中,我们使用模型的名称和跟踪服务器中的工件位置重新创建了模型的 URI。然后,我们使用来自跟踪服务器中 Spark 模型的 MLflow Python 函数模型风格创建了一个 Spark 用户定义函数UDF)。这个 Spark UDF 可以与 Spark DataFrame 一起使用,以批量方式进行大规模推理。这个代码片段可以作为定时任务定期执行,预测结果可以保存到数据湖中的某个位置。

Apache Spark 框架可以帮助我们实现完整的机器学习生命周期,从模型训练到模型推理,使用一个统一的数据处理框架。Spark 还可以通过 Spark Structured Streaming 将批量推理扩展到接近实时的推理。然而,在超低延迟实时模型服务方面,Apache Spark 并不适用。我们将在下一节进一步探讨这个问题。

在线模型推理

在线推理是指在实时生成机器学习预测的过程,可以通过将推理代码嵌入到业务应用程序中,或者使用超低延迟的 API 来实现。与批量推理不同,后者是在大数据集上批量生成的,实时在线推理通常是针对每次一个观察生成的。在线推理可以通过在毫秒到秒之间生成预测,而不是小时到天,帮助机器学习的全新应用。

以一个移动游戏应用为例,假设你想根据玩家的等级或他们所玩的游戏类型向他们展示个性化的促销。在线推理可以迅速从移动应用收集用户行为数据,并在应用内或通过低延迟 API 服务器生成预测推荐。例如,这可以帮助企业为客户实时生成个性化的体验。尽管 Apache Spark 本身不适合在线推理,MLflow 模型注册中心包含一个可以激活的模型服务组件,使用以下命令进行启动:

mlflow models serve -m "models:/ linear-regression-model/Production"

在之前的命令中,我们调用了 MLflow 内置的模型服务来为我们之前在模型注册中心注册的模型提供服务。这个模型服务提供了一个 RESTful API,外部应用程序可以通过 HTTP 与模型服务进行通信,发送一个称为负载的单一观察,并一次性接收一个预测结果。

注意

MLflow 的模型服务功能在 Databricks Community Edition 中不可用,因此如果你使用的是该版本,之前的命令将无法执行。此功能可以在开源的 MLflow 或 Databricks 的完整版本中使用。

截至目前,MLflow 的模型服务功能仍处于预览阶段,并且存在一些限制,如目标吞吐量为每秒 20 个查询,以及每次请求的有效载荷大小限制为 16MB。因此,这个选项仅建议用于低吞吐量、非生产环境的应用。不过,MLflow 确实提供与其他模型服务平台的集成,例如AWS SagemakerAzure MLGoogle Cloud AI。有关这些集成的详细信息,请参阅各自云服务提供商的文档。

机器学习的持续交付

与传统软件代码不同,ML 代码是动态的,并且不断受到模型代码本身、底层训练数据或模型参数变化的影响。因此,ML 模型的性能需要持续监控,且模型需要定期重新训练和重新部署,以保持所期望的模型性能水平。这个过程如果手动进行,可能会很费时且容易出错。但机器学习的持续交付CD4ML)可以帮助简化并自动化这个过程。

CD4ML 来源于持续集成持续交付CI/CD)的 软件工程原则,这些原则旨在推动自动化、质量和纪律,帮助创建一个可靠且可重复的流程,使得软件能够顺利地投入生产。CD4ML 在此基础上对 CI/CD 流程进行了扩展和调整,应用于机器学习(ML)领域,其中数据团队生成与 ML 过程相关的工件,如代码数据和模型,并以安全和小步长的方式逐步推进,这些工件可以在任何时候可靠地重复和发布。

CD4ML 流程包括数据科学家构建模型及其参数、源代码和所需的训练数据。下一步是模型评估和调优。达到可接受的模型精度后,模型需要进行生产化,并对其进行测试。最后一步是部署和监控模型。如果模型需要调整,CD4ML 管道应该触发 ML 过程,从头开始重新进行。这确保了持续交付的实现,并将最新的代码变更和模型推送到生产环境。MLflow 提供了大部分实施此 CD4ML 过程所需的组件,如下所示:

  1. 首先,MLflow Tracking 服务器可以帮助你跟踪模型训练过程,包括所有训练工件、数据版本以及已被版本控制并标记为测试或生产使用的模型,通过模型注册表进行管理。

  2. 然后,已注册的模型可以用自定义标签进行注释,帮助编码多种信息。这包括指示模型的部署方式,无论是批处理模式还是在线模式,或是模型部署所在的区域。

  3. 数据科学家、测试工程师和 ML 工程师可以在模型中添加评论,指定测试失败、模型不准确或生产部署失败的备注,以帮助跨职能团队之间的讨论。

  4. 可以通过模型注册中的 Webhook 推送通知,帮助通过外部 CI/CD 工具触发各种操作和自动化测试。例如,模型创建、版本更改、添加新评论等事件都可以触发相应的操作。

  5. 最后,MLflow Projects 有助于将整个模型开发工作流打包成一个可重用、可参数化的模块。

通过利用 MLflow 组件,例如 MLflow Tracking 服务器、模型注册、MLflow Projects 及其 Webhook 功能,再结合 Jenkins 等过程自动化服务器,可以编排整个 CD4ML 流水线。

注意

MLflow Model Registry 的 Webhook 功能仅在 Databricks 的完整版中可用,社区版或开源 MLflow 中无法使用。有关 MLflow Projects 及其使用方法的更多信息,请访问:www.mlflow.org/docs/latest/projects.html

通过这种方式,MLflow 通过其模型追踪和模型注册流程,可以帮助简化整个 CD4ML 流程,否则这一过程将非常复杂,涉及许多手动步骤,缺乏可重复性,并且容易导致跨职能团队之间的错误和混乱。

总结

本章介绍了端到端的 ML 生命周期及其中涉及的各个步骤。MLflow 是一个完整的端到端 ML 生命周期管理工具。介绍了 MLflow Tracking 组件,它对于流式处理 ML 实验过程非常有用,帮助你追踪所有的属性,包括数据版本、ML 代码、模型参数和指标,以及任何其他任意的工件。介绍了 MLflow Model 作为一种基于标准的模型格式,提供了模型的可移植性和可重复性。还介绍了 MLflow Model Registry,它是一个集中式的模型库,支持新创建的模型的整个生命周期,从暂存到生产再到归档。还介绍了模型服务机制,如使用批处理和在线处理。最后,介绍了 ML 的持续交付,它用于简化整个 ML 生命周期,并通过 Model Registry 功能自动化模型生命周期过程,如模型阶段转换、添加评论和注释的方式,以及通过外部编排工具使用 Webhook 来帮助自动化模型生命周期过程。

到目前为止,你已经掌握了在大规模进行数据工程和数据科学的实用技能。在下一章,我们将重点介绍如何基于标准 Python 扩展单机 ML 库的技术。

第十章:使用 PySpark 扩展单节点机器学习

第五章**,使用 PySpark 进行可扩展的机器学习中,你学习了如何利用Apache Spark的分布式计算框架进行大规模的机器学习ML)模型训练和评分。Spark 的本地 ML 库涵盖了数据科学家通常执行的标准任务;然而,还有许多标准的单节点Python库提供了丰富的功能,这些库并不是为分布式工作方式设计的。本章讨论了如何将标准 Python 数据处理和 ML 库(如pandasscikit-learnXGBoost等)水平扩展到分布式环境。它还涵盖了典型数据科学任务的扩展,如探索性数据分析EDA)、模型训练模型推断,最后,还介绍了一种名为Koalas的可扩展 Python 库,它允许你使用熟悉且易于使用的 pandas 类似语法编写PySpark代码。

本章将涵盖以下主要主题:

  • 扩展 EDA

  • 扩展模型推断

  • 分布式超参数调优

  • 使用极易并行计算进行模型训练

  • 使用 Koalas 将 pandas 升级到 PySpark

本章中获得的一些技能包括大规模执行 EDA、大规模执行模型推断和评分、超参数调优,以及单节点模型的最佳模型选择。你还将学习如何水平扩展几乎所有的单节点 ML 模型,最后使用 Koalas,它让我们能够使用类似 pandas 的 API 编写可扩展的 PySpark 代码。

技术要求

扩展 EDA

EDA 是一种数据科学过程,涉及对给定数据集的分析,以了解其主要特征,有时通过可视化图表,有时通过数据聚合和切片。你已经在第十一章**,使用 PySpark 进行数据可视化中学习了一些可视化的 EDA 技术。在这一节中,我们将探索使用 pandas 进行非图形化的 EDA,并将其与使用 PySpark 和 Koalas 执行相同过程进行比较。

使用 pandas 进行 EDA

标准 Python 中的典型 EDA 涉及使用 pandas 进行数据处理,使用 matplotlib 进行数据可视化。我们以一个来自 scikit-learn 的示例数据集为例,执行一些基本的 EDA 步骤,如以下代码示例所示:

import pandas as pd
from sklearn.datasets import load_boston
boston_data = datasets.load_boston()
boston_pd = pd.DataFrame(boston_data.data, 
                         columns=boston_data.feature_names)
boston_pd.info()
boston_pd.head()
boston_pd.shape
boston_pd.isnull().sum()
boston_pd.describe()

在前面的代码示例中,我们执行了以下步骤:

  1. 我们导入 pandas 库并导入 scikit-learn 提供的示例数据集 load_boston

  2. 然后,我们使用 pd.DataFrame() 方法将 scikit-learn 数据集转换为 pandas DataFrame。

  3. 现在我们有了一个 pandas DataFrame,可以对其进行分析,首先使用 info() 方法,打印关于 pandas DataFrame 的信息,如列名及其数据类型。

  4. pandas DataFrame 上的 head() 函数打印出实际 DataFrame 的几行几列,并帮助我们从 DataFrame 中直观地检查一些示例数据。

  5. pandas DataFrame 上的 shape 属性打印出行和列的数量。

  6. isnull() 方法显示 DataFrame 中每一列的 NULL 值数量。

  7. 最后,describe() 方法打印出每一列的统计数据,如均值、中位数和标准差。

这段代码展示了使用 Python pandas 数据处理库执行的一些典型 EDA 步骤。现在,让我们看看如何使用 PySpark 执行类似的 EDA 步骤。

使用 PySpark 进行 EDA

PySpark 也有类似于 pandas DataFrame 的 DataFrame 构造,你可以使用 PySpark 执行 EDA,如以下代码示例所示:

boston_df = spark.createDataFrame(boston_pd)
boston_df.show()
print((boston_df.count(), len(boston_df.columns)))
boston_df.where(boston_df.AGE.isNull()).count()
boston_df.describe().display()

在前面的代码示例中,我们执行了以下步骤:

  1. 我们首先使用 createDataFrame() 函数将前一部分创建的 pandas DataFrame 转换为 Spark DataFrame。

  2. 然后,我们使用 show() 函数展示 Spark DataFrame 中的一小部分数据。虽然也有 head() 函数,但 show() 能以更好的格式和更易读的方式展示数据。

  3. Spark DataFrame 没有内置的函数来显示 Spark DataFrame 的形状。相反,我们使用 count() 函数计算行数,使用 len() 方法计算列数,以实现相同的功能。

  4. 同样,Spark DataFrame 也不支持类似 pandas 的 isnull() 函数来统计所有列中的 NULL 值。相反,我们使用 isNull()where() 的组合,逐列过滤掉 NULL 值并进行计数。

  5. Spark DataFrame 确实支持 describe() 函数,可以在分布式模式下计算每列的基本统计数据,通过后台运行一个 Spark 作业来实现。对于小型数据集这可能看起来不太有用,但对于描述非常大的数据集来说,它非常有用。

通过使用 Spark DataFrame 提供的内置函数和操作,你可以轻松地扩展你的 EDA。由于 Spark DataFrame 本身支持Spark SQL,你可以在使用 DataFrame API 进行 EDA 的同时,也通过 Spark SQL 执行可扩展的 EDA。

扩展模型推理

除了数据清洗、模型训练和调优外,整个 ML 过程的另一个重要方面是模型的生产化。尽管有大量的数据可供使用,但有时将数据下采样,并在较小的子集上训练模型是有用的。这可能是由于信噪比低等原因。在这种情况下,不需要扩展模型训练过程本身。然而,由于原始数据集的大小非常庞大,因此有必要扩展实际的模型推理过程,以跟上生成的大量原始数据。

Apache Spark 与MLflow一起,可以用来对使用标准非分布式 Python 库(如 scikit-learn)训练的模型进行评分。以下代码示例展示了一个使用 scikit-learn 训练的模型,随后使用 Spark 进行大规模生产化的示例:

import mlflow
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
X = boston_pd[features]
y = boston_pd['MEDV']
with mlflow.start_run() as run1:
  lr = LinearRegression()
  lr_model = lr.fit(X_train,y_train)
  mlflow.sklearn.log_model(lr_model, "model")

在上一个代码示例中,我们执行了以下步骤:

  1. 我们打算使用 scikit-learn 训练一个线性回归模型,该模型在给定一组特征的情况下预测波士顿房价数据集中的中位数房价。

  2. 首先,我们导入所有需要的 scikit-learn 模块,同时我们还导入了 MLflow,因为我们打算将训练好的模型记录到MLflow Tracking Server

  3. 然后,我们将特征列定义为变量X,标签列定义为y

  4. 然后,我们使用with mlflow.start_run()方法调用一个 MLflow 实验。

  5. 然后,我们使用LinearRegression类训练实际的线性回归模型,并在训练的 pandas DataFrame 上调用fit()方法。

  6. 然后,我们使用mlflow.sklearn.log_model()方法将结果模型记录到 MLflow 追踪服务器。sklearn限定符指定记录的模型是 scikit-learn 类型。

一旦我们将训练好的线性回归模型记录到 MLflow 追踪服务器,我们需要将其转换为 PySpark 的用户定义函数UDF),以便能够以分布式方式进行推理。实现这一目标所需的代码如下所示:

import mlflow.pyfunc
from pyspark.sql.functions import struct
model_uri = "runs:/" + run1.info.run_id + "/model"
pyfunc_udf = mlflow.pyfunc.spark_udf(spark, model_uri=model_uri)
predicted_df = boston_df.withColumn("prediction", pyfunc_udf(struct('CRIM','ZN','INDUS','CHAS','NOX','RM','AGE','DIS','RAD','TAX','PTRATIO', 'B', 'LSTAT')))
predicted_df.show()

在上一个代码示例中,我们执行了以下步骤:

  1. 我们从 mlflow 库中导入pyfunc方法,用于将 mlflow 模型转换为 PySpark UDF。

  2. 然后,我们通过run_id实验从 MLflow 构建model_uri

  3. 一旦我们拥有了model_uri,我们使用mlflow.pyfunc()方法将模型注册为 PySpark UDF。我们指定模型类型为spark,因为我们打算在 Spark DataFrame 中使用它。

  4. 现在,模型已经作为 PySpark 的 UDF 注册,我们可以用它对 Spark DataFrame 进行预测。我们通过使用该模型创建一个新的 Spark DataFrame 列,并将所有特征列作为输入。结果是一个包含每一行预测值的新列的数据框。

  5. 需要注意的是,当调用show操作时,它会启动一个 Spark 作业,并以分布式方式执行模型评分。

通过这种方式,结合使用 MLflow 的pyfunc方法和 Spark DataFrame 操作,使用像 scikit-learn 这样的标准单节点 Python 机器学习库构建的模型,也可以以分布式方式进行推理推导,从而实现大规模推理。此外,推理的 Spark 作业可以被配置为将预测结果写入持久化存储方法,如数据库、数据仓库或数据湖,且该作业本身可以定期运行。这也可以通过使用结构化流处理轻松扩展,以近实时方式通过流式 DataFrame 进行预测。

使用令人尴尬的并行计算进行模型训练

如你之前所学,Apache Spark 遵循数据并行处理分布式计算范式。在数据并行处理中,数据处理代码被移动到数据所在的地方。然而,在传统的计算模型中,如标准 Python 和单节点机器学习库所使用的,数据是在单台机器上处理的,并且期望数据存在于本地。为单节点计算设计的算法可以通过多进程和多线程技术利用本地 CPU 来实现某种程度的并行计算。然而,这些算法本身并不具备分布式能力,需要完全重写才能支持分布式计算。Spark ML 就是一个例子,传统的机器学习算法已被完全重新设计,以便在分布式计算环境中工作。然而,重新设计每个现有的算法将是非常耗时且不切实际的。此外,已经存在丰富的基于标准的 Python 机器学习和数据处理库,如果能够在分布式计算环境中利用这些库,将会非常有用。这就是令人尴尬的并行计算范式发挥作用的地方。

在分布式计算中,同一计算过程在不同机器上执行数据的不同部分,这些计算过程需要相互通信,以完成整体计算任务。然而,在令人尴尬的并行计算中,算法不需要各个进程之间的通信,它们可以完全独立地运行。在 Apache Spark 框架内,有两种方式可以利用令人尴尬的并行计算进行机器学习训练,接下来的部分将介绍这两种方式。

分布式超参数调优

机器学习过程中的一个关键步骤是模型调优,数据科学家通过调整模型的超参数来训练多个模型。这种技术通常被称为超参数调优。超参数调优的常见方法叫做网格搜索,它是一种寻找能够产生最佳性能模型的超参数组合的方法。网格搜索通过交叉验证选择最佳模型,交叉验证将数据分为训练集和测试集,并通过测试数据集评估训练模型的表现。在网格搜索中,由于多个模型是在相同数据集上训练的,它们可以独立地进行训练,这使得它成为一个适合显式并行计算的候选方法。

使用标准 scikit-learn 进行网格搜索的典型实现,通过以下代码示例进行了说明:

from sklearn.datasets import load_digits
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import GridSearchCV
digits_pd = load_digits()
X = digits_pd.data 
y = digits_pd.target
parameter_grid = {"max_depth": [2, None],
              "max_features": [1, 2, 5],
              "min_samples_split": [2, 3, 5],
              "min_samples_leaf": [1, 2, 5],
              "bootstrap": [True, False],
              "criterion": ["gini", "entropy"],
              "n_estimators": [5, 10, 15, 20]}
grid_search = GridSearchCV(RandomForestClassifier(), 
                           param_grid=parameter_grid)
grid_search.fit(X, y) 

在前面的代码示例中,我们执行了以下步骤:

  1. 首先,我们导入GridSearchCV模块、load_digits样本数据集,以及 scikit-learn 中与RandomForestClassifier相关的模块。

  2. 然后,我们从 scikit-learn 样本数据集中加载load_digits数据,并将特征映射到X变量,将标签列映射到y变量。

  3. 然后,我们通过指定RandomForestClassifier算法使用的各种超参数的值,如max_depthmax_features等,定义需要搜索的参数网格空间。

  4. 然后,我们通过调用GridSearchCV()方法来启动网格搜索交叉验证,并使用fit()方法执行实际的网格搜索。

通过使用 scikit-learn 的内置网格搜索和交叉验证器方法,你可以执行模型超参数调优,并从多个训练模型中识别出最佳模型。然而,这个过程是在单台机器上运行的,因此模型是一个接一个地训练,而不是并行训练。使用 Apache Spark 和名为spark_sklearn的第三方 Spark 包,你可以轻松实现网格搜索的显式并行实现,以下代码示例演示了这一点:

from sklearn import grid_search
from sklearn.datasets import load_digits
from sklearn.ensemble import RandomForestClassifier
from spark_sklearn import GridSearchCV
digits_pd = load_digits()
X = digits_pd.data 
y = digits_pd.target
parameter_grid = {"max_depth": [2, None],
              "max_features": [1, 2, 5],
              "min_samples_split": [2, 3, 5],
              "min_samples_leaf": [1, 2, 5],
              "bootstrap": [True, False],
              "criterion": ["gini", "entropy"],
              "n_estimators": [5, 10, 15, 20]}
grid_search = grid_search.GridSearchCV(RandomForestClassifier(), 
             param_grid=parameter_grid)
grid_search.fit(X, y)

使用grid_sklearn进行网格搜索的前面代码片段与使用标准 scikit-learn 进行网格搜索的代码几乎相同。然而,我们不再使用 scikit-learn 的网格搜索和交叉验证器,而是使用grid_sklearn包中的网格搜索和交叉验证器。这有助于以分布式方式运行网格搜索,在不同的机器上对相同数据集进行不同超参数组合的模型训练。这显著加快了模型调优过程,使你能够从比仅使用单台机器时更大的训练模型池中选择模型。通过这种方式,利用 Apache Spark 上的显式并行计算概念,你可以在仍然使用 Python 的标准单节点机器库的情况下,扩展模型调优任务。

在接下来的部分中,我们将看到如何使用 Apache Spark 的 pandas UDF 来扩展实际的模型训练,而不仅仅是模型调优部分。

使用 pandas UDF 扩展任意 Python 代码

一般来说,UDF 允许你在 Spark 的执行器上执行任意代码。因此,UDF 可以用于扩展任意 Python 代码,包括特征工程和模型训练,适用于数据科学工作流。它们还可以用于使用标准 Python 扩展数据工程任务。然而,UDF 每次只能执行一行代码,并且在 JVM 与运行在 Spark 执行器上的 Python 进程之间会产生序列化反序列化的开销。这个限制使得 UDF 在将任意 Python 代码扩展到 Spark 执行器时不太具备吸引力。

使用groupby操作符,将 UDF 应用于每个组,最终将每个组生成的单独 DataFrame 合并成一个新的 Spark DataFrame 并返回。标量以及分组的 pandas UDF 示例可以在 Apache Spark 的公开文档中找到:

spark.apache.org/docs/latest/api/python/reference/api/pyspark.sql.functions.pandas_udf.html

到目前为止,你已经看到如何使用 Apache Spark 支持的不同技术来扩展 EDA 过程、模型调优过程,或者扩展任意 Python 函数。在接下来的部分中,我们将探索一个建立在 Apache Spark 之上的库,它让我们可以使用类似 pandas 的 API 来编写 PySpark 代码。

使用 Koalas 将 pandas 升级到 PySpark

pandas 是标准 Python 中数据处理的事实标准,Spark 则成为了分布式数据处理的事实标准。pandas API 是与 Python 相关的,并利用 Python 独特的特性编写可读且优雅的代码。然而,Spark 是基于 JVM 的,即使是 PySpark 也深受 Java 语言的影响,包括命名约定和函数名称。因此,pandas 用户转向 PySpark 并不容易或直观,且涉及到相当的学习曲线。此外,PySpark 以分布式方式执行代码,用户需要理解如何在将 PySpark 代码与标准单节点 Python 代码混合时,分布式代码的工作原理。这对普通的 pandas 用户来说,是使用 PySpark 的一大障碍。为了解决这个问题,Apache Spark 开发者社区基于 PySpark 推出了另一个开源库,叫做 Koalas。

Koalas 项目是基于 Apache Spark 实现的 pandas API。Koalas 帮助数据科学家能够立即使用 Spark,而不需要完全学习新的 API 集。此外,Koalas 帮助开发人员在不需要在两个框架之间切换的情况下,维护一个同时适用于 pandas 和 Spark 的单一代码库。Koalas 随pip一起打包发布。

让我们看几个代码示例,了解 Koalas 如何提供类似 pandas 的 API 来与 Spark 一起使用:

import koalas as ks
boston_data = load_boston()
boston_pd = ks.DataFrame(boston_data.data, columns=boston_data.feature_names)
features = boston_data.feature_names
boston_pd['MEDV'] = boston_data.target
boston_pd.info()
boston_pd.head()
boston_pd.isnull().sum()
boston_pd.describe()

在前面的代码片段中,我们执行了本章早些时候所进行的相同的基本 EDA 步骤。唯一的不同是,这里我们没有直接从 scikit-learn 数据集创建 pandas DataFrame,而是导入 Koalas 库后创建了一个 Koalas DataFrame。你可以看到,代码和之前写的 pandas 代码完全相同,然而,在幕后,Koalas 会将这段代码转换为 PySpark 代码,并在集群上以分布式方式执行。Koalas 还支持使用 DataFrame.plot() 方法进行可视化,就像 pandas 一样。通过这种方式,你可以利用 Koalas 扩展任何现有的基于 pandas 的机器学习代码,比如特征工程或自定义机器学习代码,而无需先用 PySpark 重写代码。

Koalas 是一个活跃的开源项目,得到了良好的社区支持。然而,Koalas 仍处于初期阶段,并且有一些局限性。目前,只有大约 70% 的 pandas API 在 Koalas 中可用,这意味着一些 pandas 代码可能无法直接使用 Koalas 实现。Koalas 和 pandas 之间存在一些实现差异,并且在 Koalas 中实现某些 pandas API 并不合适。处理缺失的 Koalas 功能的常见方法是将 Koalas DataFrame 转换为 pandas 或 PySpark DataFrame,然后使用 pandas 或 PySpark 代码来解决问题。Koalas DataFrame 可以通过 DataFrame.to_pandas()DataFrame.to_spark() 函数分别轻松转换为 pandas 和 PySpark DataFrame。然而,需注意的是,Koalas 在幕后使用的是 Spark DataFrame,Koalas DataFrame 可能过大,无法在单台机器上转换为 pandas DataFrame,从而导致内存溢出错误。

摘要

在本章中,你学习了一些技术来水平扩展基于 Python 的标准机器学习库,如 scikit-learn、XGBoost 等。首先,介绍了使用 PySpark DataFrame API 扩展 EDA(探索性数据分析)的方法,并通过代码示例进行了展示。接着,介绍了结合使用 MLflow pyfunc 功能和 Spark DataFrame 来分布式处理机器学习模型的推断和评分技术。还介绍了使用 Apache Spark 进行令人尴尬的并行计算技术扩展机器学习模型的方法。此外,还介绍了使用名为 spark_sklearn 的第三方包对标准 Python 机器学习库训练的模型进行分布式调优的方法。然后,介绍了 pandas UDF(用户定义函数),它可以以向量化的方式扩展任意 Python 代码,用于在 PySpark 中创建高性能、低开销的 Python 用户定义函数。最后,Koalas 被介绍为一种让 pandas 开发者无需首先学习 PySpark API,就能使用类似 pandas 的 API,同时仍能利用 Apache Spark 在大规模数据处理中的强大性能和效率的方法。

第三部分:数据分析

一旦我们在数据湖中拥有清洗和集成的数据,并且已经在大规模上训练和构建了机器学习模型,最后一步就是以有意义的方式将可操作的见解传递给业务决策者,帮助他们做出商业决策。本节涵盖了数据分析中的**商业智能(BI)**和 SQL 分析部分。首先介绍了使用笔记本的各种数据可视化技术。接着,介绍了如何使用 Spark SQL 进行大规模的业务分析,并展示了将 BI 和 SQL 分析工具连接到 Apache Spark 集群的技术。最后,本节介绍了数据湖仓范式,它弥合了数据仓库和数据湖之间的差距,提供了一个单一的、统一的、可扩展的存储,满足数据工程、数据科学和商业分析等所有数据分析需求。

本节包括以下章节:

  • 第十一章*,使用 PySpark 进行数据可视化*

  • 第十二章*,Spark SQL 入门*

  • 第十三章*,将外部工具与 Spark SQL 集成*

  • 第十四章*,数据湖仓*

第十一章:使用 PySpark 进行数据可视化

到目前为止,从 第一章*,* 分布式计算基础,到 第九章机器学习生命周期管理,你已经学习了如何获取、整合和清洗数据,以及如何使数据适合用于分析。你还学会了如何使用清洗后的数据,通过数据科学和机器学习来解决实际的业务应用。本章将向你介绍如何利用数据可视化从数据中提取意义的基本知识。

在本章中,我们将涵盖以下主要主题:

  • 数据可视化的重要性

  • 使用 PySpark 进行数据可视化的技巧

  • PySpark 转换为 pandas 时的注意事项

数据可视化是通过使用图表、图形和地图等视觉元素,将数据图形化呈现的过程。数据可视化帮助你以视觉化的方式理解数据中的模式。在大数据的世界中,面对海量的数据,使用数据可视化来从中提取意义并以简单易懂的方式呈现给业务用户变得尤为重要;这有助于他们做出基于数据的决策。

技术要求

在本章中,我们将使用 Databricks Community Edition 来运行代码。

数据可视化的重要性

数据可视化是将数据转化为图形、图表或地图等形式的过程。这使得人类大脑更容易理解复杂的信息。通常,数据可视化是商业分析的最后阶段,也是任何数据科学过程的第一步。虽然有些专业人员专门从事数据可视化,但任何数据专业人员都需要能够理解和制作数据可视化。数据可视化帮助以易于理解的方式向业务用户传达隐藏在数据中的复杂模式。每个企业都需要信息以实现最佳性能,而数据可视化通过以视觉方式展示数据集之间的关系,并揭示可操作的见解,帮助企业做出更简单的数据驱动决策。随着大数据的到来,结构化和非结构化数据爆炸性增长,若没有可视化工具的帮助,很难理解这些数据。数据可视化通过揭示关键信息,加速决策过程,帮助业务用户迅速采取行动。数据可视化还帮助讲故事的过程,通过向正确的受众传递正确信息来传达信息。

数据可视化可以是一个简单的图表,代表当前业务状态的某一方面,也可以是一个复杂的销售报告,或是一个展示组织整体表现的仪表盘。数据可视化工具是解锁数据可视化潜力的关键。我们将在接下来的部分探讨不同类型的数据可视化工具。

数据可视化工具类型

数据可视化工具为我们提供了一个更简单的方式来创建数据可视化。它们通过提供图形用户界面、数据库连接和有时的数据处理工具,在单一统一的界面中,方便数据分析师和数据科学家创建数据可视化。有不同类型的数据可视化工具,每种工具都有略微不同的用途。在这一部分,我们将深入探讨这些工具。

商业智能工具

商业智能 (BI) 工具通常是企业级工具,帮助组织追踪和直观呈现其关键绩效指标 (KPIs)。BI 工具通常包括用于创建复杂逻辑数据模型的功能,并包含数据清洗和集成功能。BI 工具还包括连接到各种数据源的连接器,并内置了拖放功能的数据可视化,帮助业务用户快速创建数据可视化、运营和绩效仪表盘以及计分卡,以追踪某一部门或整个组织的表现。BI 工具的主要用户是参与制定战术和战略决策的业务分析师和高管。

BI 工具传统上使用数据仓库作为数据源,但现代 BI 工具支持 RDMS、NoSQL 数据库和数据湖作为数据源。一些著名的 BI 工具包括 TableauLookerMicrosoft Power BISAP Business ObjectsMicroStrategyIBM CognosQlikview 等。BI 工具可以连接到 Apache Spark,并通过 ODBC 连接消费存储在 Spark SQL 表中的数据。这些概念将在 第十三章与 Spark SQL 集成外部工具 中详细探讨。一类没有任何数据处理能力,但具备所有必要的数据可视化和数据连接组件的数据可视化工具,如 Redash,也可以通过 ODBC 连接到 Apache Spark。

可观察性工具

可观察性是一个持续监控和理解高度分布式系统中发生的事情的过程。可观察性帮助我们理解哪些地方变慢或出现故障,以及哪些方面需要修复以提高性能。然而,由于现代云环境是动态的,并且不断扩展和复杂化,大多数问题既不被知晓,也没有被监控。可观察性通过使你能够持续监控和暴露可能出现的问题,解决了现代云环境中常见的问题,这些问题具有动态性并且规模不断扩大。可观察性工具帮助企业持续监控系统和应用程序,并使企业能够获得关于系统行为的可操作见解,提前预测停机或问题的发生。数据可视化是可观察性工具的重要组成部分;一些流行的示例包括 Grafana 和 Kibana。

数据团队通常不负责监控和维护数据处理系统的健康状况——这通常由 DevOps 工程师等专业人员来处理。Apache Spark 默认没有与任何可观察性工具的直接集成,但它可以与流行的可观察性平台如 PrometheusGrafana 集成。Apache Spark 与可观察性工具的集成超出了本书的范围,因此我们在此不做讨论。

Notebooks

Notebooks 是交互式计算工具,用于执行代码、可视化结果并分享见解。Notebooks 是数据科学过程中不可或缺的工具,并且在整个数据分析开发生命周期中变得越来越重要,正如你在本书中所见。Notebooks 也是优秀的数据可视化工具,它们帮助你将 Python 或 SQL 代码转化为易于理解的交互式数据可视化。一些 notebooks,如 Databricks、Jupyter 和 Zeppelin notebooks,甚至可以作为独立的仪表盘使用。本章剩余部分将重点介绍如何在使用 PySpark 时将 notebooks 用作数据可视化工具。

使用 PySpark 可视化数据的技术

Apache Spark 是一个统一的数据处理引擎,默认并不带有图形用户界面。正如前面部分所讨论的,经过 Apache Spark 处理的数据可以存储在数据仓库中,并使用 BI 工具进行可视化,或者使用笔记本进行本地可视化。在本节中,我们将重点介绍如何利用笔记本以交互方式使用 PySpark 处理和可视化数据。正如我们在本书中所做的那样,我们将使用 Databricks Community Edition 提供的笔记本,尽管 JupyterZeppelin 笔记本也可以使用。

PySpark 本地数据可视化

没有任何数据可视化库可以原生地与 PySpark DataFrame 一起使用。然而,基于云的 Spark 分发版的笔记本实现,如 Databricks 和 Qubole,支持使用内置的 display() 函数原生可视化 Spark DataFrame。让我们看看如何在 Databricks Community Edition 中使用 display() 函数可视化 PySpark DataFrame。

我们将使用我们在 第六章 结束时制作的已清洗、集成和整理的数据集,特征工程 – 提取、转换和选择,如下所示的代码片段:

retail_df = spark.read.table("feature_store.retail_features")
viz_df = retail_df.select("invoice_num", "description", 
                          "invoice_date", "invoice_month", 
                          "country_code", "quantity", 
                          "unit_price", "occupation", 
                          "gender")
viz_df.display()

在前面的代码片段中,我们将一个表格读入了 Spark DataFrame,并选择了我们打算可视化的列。接着,我们在 Spark DataFrame 上调用了 display() 方法。结果是在笔记本中显示的一个网格,如下图所示:

https://github.com/OpenDocCN/freelearn-ds-pt3-zh/raw/master/docs/ess-pyspk-scl-da/img/B16736_11_01.jpg

图 11.1 – 网格小部件

上一截图显示了在 Databricks 笔记本中调用 display() 函数在 Spark DataFrame 上的结果。通过这种方式,任何 Spark DataFrame 都可以在 Databricks 笔记本中以表格格式进行可视化。该表格网格支持对任意列进行排序。Databricks 笔记本还支持图表和图形,这些可以在笔记本内使用。

提示

Databricks 的 display() 方法支持 Spark 所有编程 API,包括 Python、Scala、R 和 SQL。此外,display() 方法还可以渲染 Python pandas DataFrame。

我们可以使用相同的网格显示,并通过点击图表图标并从列表中选择所需的图表,将其转换为图表,如下图所示:

https://github.com/OpenDocCN/freelearn-ds-pt3-zh/raw/master/docs/ess-pyspk-scl-da/img/B16736_11_02.jpg

图 11.2 – 图表选项

正如我们所看到的,图表菜单有多个图表选项,柱状图排在列表的首位。如果选择柱状图,图表选项可以用来配置图表的关键字段、值字段和系列分组选项。类似地,我们也可以使用折线图或饼图,如下所示:

https://github.com/OpenDocCN/freelearn-ds-pt3-zh/raw/master/docs/ess-pyspk-scl-da/img/B16736_11_03.jpg

图 11.3 – 饼图

这里,display() 函数可以用于在笔记本中显示各种图表,并帮助配置各种图形选项。Databricks 笔记本还支持一个基础的地图小部件,可以在世界地图上可视化指标,如以下截图所示:

https://github.com/OpenDocCN/freelearn-ds-pt3-zh/raw/master/docs/ess-pyspk-scl-da/img/B16736_11_04.jpg

图 11.4 – 世界地图

上述截图展示了世界地图上的指标。由于我们的数据集只包含少数几个欧洲国家,法国和英国在地图小部件中已经被高亮显示。

注意

对于这个小部件,值应为 ISO 3166-1 alpha-3 格式的国家代码(例如 “GBR”)或美国州的缩写(例如 “TX”)。

除了基本的条形图和图表外,Databricks 笔记本还支持科学可视化,如散点图直方图分位数图Q-Q 图,如下图所示:

https://github.com/OpenDocCN/freelearn-ds-pt3-zh/raw/master/docs/ess-pyspk-scl-da/img/B16736_11_05.jpg

图 11.5 – 分位数图

如前面图中所示,分位数图帮助判断两个数据集是否具有共同的分布。Databricks 笔记本可以通过图表菜单访问分位数图,图表属性如键、值和系列分组可以通过图表选项菜单进行设置。

我们可以使用以下代码使 Databricks 笔记本显示图像:

image_df = spark.read.format("image").load("/FileStore/FileStore/shared_uploads/images")

上述代码片段使用 Apache Spark 内置的图像数据源从持久存储(如数据湖)中的目录加载图像:

https://github.com/OpenDocCN/freelearn-ds-pt3-zh/raw/master/docs/ess-pyspk-scl-da/img/B16736_11_06.jpg

图 11.6 – 图像数据

这张图像通过 Databricks 的 display() 函数在笔记本中渲染显示,因为它能够显示图像预览。

Databricks 笔记本还能够渲染机器学习特定的可视化内容,例如使用 display() 函数可视化我们训练的决策树模型,如下图所示:

https://github.com/OpenDocCN/freelearn-ds-pt3-zh/raw/master/docs/ess-pyspk-scl-da/img/B16736_11_07.jpg

图 11.7 – 决策树模型

上述截图展示了我们使用 Spark ML 构建的决策树模型,并在 Databricks 笔记本中渲染显示。

提示

更多关于使用 Databricks 笔记本渲染机器学习特定可视化的信息,请参阅 Databricks 的公开文档:docs.databricks.com/notebooks/visualizations/index.html#machine-learning-visualizations

使用 JavaScript 和 HTML 的交互式可视化

Databricks 笔记本还支持 displayHTML() 函数。你可以将任意 HTML 代码传递给 displayHTML(),并将其渲染在笔记本中,如以下代码片段所示:

displayHTML("<a href ='/files/image.jpg'>Arbitrary Hyperlink</a>")

上述代码片段在笔记本中显示了一个任意的 HTML 超链接。其他 HTML 元素,如段落、标题、图片等,也可以与 displayHTML() 函数一起使用。

提示

可以使用超链接、图像和表格等 HTML 块来使你的笔记本更具描述性和交互性,这些可以帮助讲述过程中的交互性。

同样地,可以使用displayHTML()函数来渲染 SVG 图形,如下代码块所示:

displayHTML("""<svg width="400" height="400">
  <ellipse cx="300" cy="300" rx="100" ry="60" style="fill:orange">
    <animate attributeType="CSS" attributeName="opacity" from="1" to="0" dur="5s" repeatCount="indefinite" />
  </ellipse>
</svg>""")

上述代码渲染了一个橙色的动画椭圆,可以淡入淡出。还可以渲染更复杂的 SVG 图形,并且可以传递来自 Spark DataFrame 的数据。同样,流行的基于 HTML 和 JavaScript 的可视化库也可以与 Databricks 笔记本一起使用,如下所示:

https://github.com/OpenDocCN/freelearn-ds-pt3-zh/raw/master/docs/ess-pyspk-scl-da/img/B16736_11_08.jpg

图 11.8 – 使用 D3.js 的词云

在这里,我们从我们在前几章节中处理数据时创建的retail_sales Delta 表中取出了description列,然后从商品描述列中提取了各个单词。接着,我们利用 HTML、CSS 和 JavaScript 使用词云可视化渲染了这些单词。之后,我们使用流行的 D3.js JavaScript 库基于数据操作 HTML 文档。这个可视化的代码可以在github.com/PacktPublishing/Essential-PySpark-for-Scalable-Data-Analytics/blob/main/Chapter11/databricks-charts-graphs.py找到。

到目前为止,您已经看到了一些通过 Databricks 笔记本界面可以直接与 Spark DataFrame 一起使用的基本和统计图表。然而,有时您可能需要一些在笔记本中不可用的额外图表和图形,或者您可能需要更多对图表的控制。在这些情况下,可以使用诸如matplotlibplotlyseabornaltairbokeh等流行的 Python 可视化库与 PySpark 一起使用。我们将在下一节中探讨一些这些可视化库。

使用 PySpark 进行 Python 数据可视化

正如您在前一节中学到的那样,PySpark 本身并不具备任何可视化能力,但您可以选择使用 Databricks 笔记本功能来在 Spark DataFrame 中可视化数据。在无法使用 Databricks 笔记本的情况下,您可以选择使用流行的基于 Python 的可视化库,在任何您熟悉的笔记本界面中进行数据可视化。在本节中,我们将探讨一些著名的 Python 可视化库以及如何在 Databricks 笔记本中使用它们进行数据可视化。

使用 Matplotlib 创建二维图

使用像pip这样的包管理器在PyPI仓库中获取。以下代码示例展示了如何在 PySpark 中使用 Matplotlib:

import pandas as pd
import matplotlib.pyplot as plt
retail_df = spark.read.table("feature_store.retail_features")
viz_df = retail_df.select("invoice_num", "description", 
                          "invoice_date", "invoice_month", 
                          "country_code", "quantity", 
                          "unit_price", "occupation", 
                          "gender")
pdf = viz_df.toPandas()
pdf['quantity'] = pd.to_numeric(pdf['quantity'], 
                                errors='coerce')
pdf.plot(kind='bar', x='invoice_month', y='quantity',
         color='orange')

在前面的代码片段中,我们执行了以下操作:

  1. 首先,我们导入了pandasmatplotlib库,假设它们已经在笔记本中安装好了。

  2. 然后,我们使用在前几章的数据处理步骤中创建的在线零售数据集生成了一个包含所需列的 Spark DataFrame。

  3. 由于基于 Python 的可视化库不能直接使用 Spark DataFrame,我们将 Spark DataFrame 转换为 pandas DataFrame。

  4. 接着,我们将数量列转换为数值数据类型,以便进行绘制。

  5. 之后,我们使用 Matplotlib 库的plot()方法在 pandas DataFrame 上定义了一个图表,指定生成的图表类型为柱状图,并传入了 x 轴和 y 轴的列名。

  6. 某些笔记本环境可能需要显式调用 display() 函数才能显示图表。

通过这种方式,如果我们将 Spark DataFrame 转换为 pandas DataFrame,就可以使用 Matplotlib。生成的图表如下所示:

https://github.com/OpenDocCN/freelearn-ds-pt3-zh/raw/master/docs/ess-pyspk-scl-da/img/B16736_11_09.jpg

图 11.9 – Matplotlib 可视化

上一个图表显示了在某一特定时间段内已售出的商品数量。

使用 Seaborn 进行科学可视化

pip。以下代码示例展示了如何在 PySpark DataFrame 中使用 Seaborn:

import matplotlib.pyplot as plt
import seaborn as sns
data = retail_df.select("unit_price").toPandas()["unit_price"]
plt.figure(figsize=(10, 3))
sns.boxplot(data)

在上一个代码片段中,我们执行了以下操作:

  1. 首先,我们导入了 matplotlibseaborn 库。

  2. 接下来,我们将包含名为 unit_price 的单列的 Spark DataFrame 使用 toPandas() PySpark 函数转换为 pandas DataFrame。

  3. 接着,我们使用 plot.figure() Matplotlib 方法定义了图表的尺寸。

  4. 最后,我们通过调用 seaborn.boxplot() 方法并传入包含单列的 pandas DataFrame 绘制了箱线图。生成的图表如下所示:

https://github.com/OpenDocCN/freelearn-ds-pt3-zh/raw/master/docs/ess-pyspk-scl-da/img/B16736_11_10.jpg

图 11.10 – Seaborn 箱线图可视化

上一个截图显示了如何使用 unit_price 列的最小值、第一个四分位数、中位数、第三个四分位数和最大值将其分布为箱线图。

使用 Plotly 进行交互式可视化

Plotly 是一个基于 JavaScript 的可视化库,使得 Python 用户能够创建交互式的网页可视化,并可以在笔记本中展示或保存为独立的 HTML 文件。Plotly 已预装在 Databricks 中,可以按以下方式使用:

import plotly.express as plot
df = viz_df.toPandas()
fig = plot.scatter(df, x="fin_wt", y="quantity",
                   size="unit_price", color="occupation",
                   hover_name="country_code", log_x=True, 
                   size_max=60)
fig.show()

在上一个代码片段中,我们执行了以下操作:

  1. 首先,我们导入了 matplotlibseaborn 库。

  2. 接下来,我们将包含所需列的 Spark DataFrame 转换为 pandas DataFrame。

  3. 然后,我们使用 plot.scatter() 方法定义了 Plotly 图表的参数。该方法配置了一个具有三维坐标的散点图。

  4. 最后,我们使用 fig.show() 方法渲染了图表。生成的图表如下所示:

https://github.com/OpenDocCN/freelearn-ds-pt3-zh/raw/master/docs/ess-pyspk-scl-da/img/B16736_11_11.jpg

图 11.11 – Plotly 气泡图可视化

上面的截图展示了一个气泡图,显示了三个指标在三个维度上的分布。该图是互动式的,当您将鼠标悬停在图表的不同部分时,会显示相关信息。

使用 Altair 的声明性可视化

Altair 是一个用于 Python 的声明性统计可视化库。Altair 基于一个名为Vega的开源声明性语法引擎。Altair 还提供了一种简洁的可视化语法,使用户能够快速构建各种各样的可视化图表。可以使用以下命令安装它:

%pip install altair 

上面的命令将 Altair 安装到笔记本的本地 Python 内核中,并重新启动它。一旦 Altair 成功安装后,可以像通常那样使用 Python 的import语句来调用它,代码示例如下:

import altair as alt
import pandas as pd
source = (viz_df.selectExpr("gender as Gender", "trim(occupation) as Occupation").where("trim(occupation) in ('Farming-fishing', 'Handlers-cleaners', 'Prof-specialty', 'Sales', 'Tech-support') and cust_age > 49").toPandas())

在前面的代码片段中,我们导入了 Altair 和 pandas 库。然后,我们从 Spark 表中选择所需的列,并将其转换为 pandas DataFrame。一旦数据在 Python 中以 pandas DataFrame 的形式存在,就可以使用 Altair 来创建图表,如下所示:

https://github.com/OpenDocCN/freelearn-ds-pt3-zh/raw/master/docs/ess-pyspk-scl-da/img/B16736_11_12.jpg

图 11.12 – Altair 等型图可视化

上面的图展示了一个等型图可视化,显示了不同国家按性别分布的职业情况。其他开源库,如bokehpygalleather,也可以用于可视化 PySpark DataFrame。Bokeh 是另一个流行的 Python 数据可视化库,提供高性能的互动图表和图形。Bokeh 基于 JavaScript 和 HTML,与 Matplotlib 不同,它允许用户创建自定义可视化图表。关于在 Databricks 笔记本中使用 Bokeh 的信息,可以在 Databricks 的公共文档中找到,网址为docs.databricks.com/notebooks/visualizations/bokeh.html#bokeh

到目前为止,您已经学习了如何通过将 PySpark DataFrame 转换为 pandas DataFrame,使用一些在 Python 中与 Spark DataFrame 兼容的流行可视化方法。然而,在将 PySpark DataFrame 转换为 pandas DataFrame 时,您需要考虑一些性能问题和限制。我们将在下一部分讨论这些问题。

PySpark 到 pandas 转换的注意事项

本节将介绍pandas,演示 pandas 与 PySpark 的差异,并介绍在 PySpark 与 pandas 之间转换数据集时需要注意的事项。

pandas 简介

pandas 是 Python 中最广泛使用的开源数据分析库之一。它包含了一系列用于处理、操作、清洗、整理和转换数据的实用工具。与 Python 的列表、字典和循环相比,pandas 更加易于使用。从某种意义上讲,pandas 类似于其他统计数据分析工具,如 R 或 SPSS,这使得它在数据科学和机器学习爱好者中非常受欢迎。

pandas 的主要抽象是SeriesDataFrames,前者本质上是一个一维数组,后者是一个二维数组。pandas 和 PySpark 之间的基本区别之一在于,pandas 将其数据集表示为一维和二维的NumPy数组,而 PySpark 的 DataFrames 是基于 Spark SQL 的RowColumn对象的集合。虽然 pandas DataFrames 只能通过 pandas DSL 进行操作,但 PySpark DataFrames 可以使用 Spark 的 DataFrame DSL 和 SQL 进行操作。由于这个差异,熟悉使用 pandas 的开发者可能会发现 PySpark 不同,并且在使用该平台时可能会遇到学习曲线。Apache Spark 社区意识到这一难题,启动了一个新的开源项目——Koalas。Koalas 在 Spark DataFrames 上实现了类似 pandas 的 API,以尝试克服 pandas 和 PySpark 之间的差异。关于如何使用 Koalas 的更多信息将在第十章使用 PySpark 扩展单节点机器学习中介绍。

注意

NumPy 是一个用于科学计算的 Python 包,它提供了多维数组和一组用于快速操作数组的例程。有关 NumPy 的更多信息,请访问:numpy.org/doc/stable/user/whatisnumpy.html

另一个基本区别是在大数据和处理大规模数据的上下文中,pandas 是为了处理单台机器上的数据而设计的,而 PySpark 从设计上就是分布式的,可以以大规模并行的方式在多台机器上处理数据。这突显了 pandas 与 PySpark 相比的一个重要限制,以及开发者在从 pandas 转换到 PySpark 时需要考虑的一些关键因素。我们将在接下来的章节中探讨这些内容。

从 PySpark 转换到 pandas

PySpark API 提供了一个方便的实用函数 DataFrame.toPandas(),可以将 PySpark DataFrame 转换为 pandas DataFrame。该函数在本章中有多次演示。如果你回顾一下我们在第一章,“分布式计算入门”中的讨论,尤其是关于 Spark 集群架构 部分,Spark 集群由 Driver 进程和一组在工作节点上运行的执行进程组成,Driver 负责编译用户代码,将其传递给工作节点,管理并与工作节点通信,并在需要时从工作节点聚合和收集数据。而 Spark 工作节点则负责所有数据处理任务。然而,pandas 并非基于分布式计算范式,它仅在单一计算机上运行。因此,当你在 Spark 集群上执行 pandas 代码时,它会在 Driver 或 Master 节点上执行,如下图所示:

https://github.com/OpenDocCN/freelearn-ds-pt3-zh/raw/master/docs/ess-pyspk-scl-da/img/B16736_11_13.jpg

图 11.13 – PySpark 架构

如我们所见,当在 Spark DataFrame 上调用 Python 和 toPandas() 函数时,它会从所有 Spark 工作节点收集行数据,然后在 Driver 内部的 Python 内核上创建一个 pandas DataFrame。

这个过程面临的第一个问题是 toPandas() 函数实际上会将所有数据从工作节点收集并带回 Driver。如果收集的数据集过大,这可能会导致 Driver 内存不足。另一个问题是,默认情况下,Spark DataFrame 的 Row 对象会作为 listtuples 收集到 Driver 上,然后再转换为 pandas DataFrame。这通常会消耗大量内存,有时甚至会导致收集到的数据占用的内存是 Spark DataFrame 本身的两倍。

为了减轻 PySpark 转换为 pandas 时的内存问题,可以使用 Apache Arrow。Apache Arrow 是一种内存中的列式数据格式,类似于 Spark 内部数据集的表示方式,且在 JVM 和 Python 进程之间传输数据时非常高效。默认情况下,Spark 没有启用 Apache Arrow,需要通过将 spark.sql.execution.arrow.enabled Spark 配置设置为 true 来启用它。

注意

PyArrow,Apache Arrow 的 Python 绑定,已在 Databricks Runtime 中预装。然而,你可能需要安装适用于你的 Spark 集群和 Python 版本的 PyArrow 版本。

Apache Arrow 有助于缓解使用 toPandas() 时可能出现的一些内存问题。尽管进行了优化,但转换操作仍然会导致 Spark DataFrame 中的所有记录被收集到 Driver 中,因此你应该仅在原始数据的一个小子集上执行转换。因此,通过利用 PyArrow 格式并小心地对数据集进行抽样,你仍然可以在笔记本环境中使用所有与标准 Python 兼容的开源可视化库来可视化你的 PySpark DataFrame。

总结

在本章中,你了解了使用数据可视化以简单的方式传达复杂数据集中的含义的重要性,以及如何轻松地将数据模式呈现给业务用户。介绍了多种使用 Spark 进行数据可视化的策略。你还学习了如何在 Databricks 笔记本中原生使用 PySpark 进行数据可视化。我们还探讨了使用普通 Python 可视化库来可视化 Spark DataFrame 数据的技巧。介绍了一些知名的开源可视化库,如 Matplotlib、Seaborn、Plotly 和 Altair,并提供了它们的实际使用示例和代码示例。最后,你了解了在 PySpark 中使用普通 Python 可视化时的陷阱,PySpark 转换的需求,以及克服这些问题的一些策略。

下一章将讨论如何将各种 BI 和 SQL 分析工具连接到 Spark,这将帮助你执行临时数据分析并构建复杂的操作和性能仪表板。

第十二章:Spark SQL 入门

在上一章中,你了解了数据可视化作为数据分析的强大工具。你还学习了可以用于可视化 pandas DataFrame 数据的各种 Python 可视化库。另一个同样重要、普遍且必不可少的技能是结构化查询语言SQL)。SQL 自数据分析领域诞生以来一直存在,即使在大数据、数据科学和机器学习ML)兴起的今天,SQL 仍然被证明是不可或缺的。

本章将向你介绍 SQL 的基础知识,并探讨如何通过 Spark SQL 在分布式计算环境中应用 SQL。你将了解构成 Spark SQL 的各种组件,包括存储、元数据存储和实际的查询执行引擎。我们将比较Hadoop Hive和 Spark SQL 之间的差异,最后介绍一些提高 Spark SQL 查询性能的技巧。

在本章中,我们将涵盖以下主要内容:

  • SQL 简介

  • Spark SQL 简介

  • Spark SQL 语言参考

  • 优化 Spark SQL 性能

本章涉及的内容包括 SQL 作为数据切片和切割语言的实用性、Spark SQL 的各个组件,以及它们如何结合起来在 Apache Spark 上创建一个强大的分布式 SQL 引擎。你将查阅 Spark SQL 语言参考,以帮助你的数据分析需求,并学习一些优化 Spark SQL 查询性能的技巧。

技术要求

本章所需的内容如下:

SQL 简介

SQL 是一种声明式语言,用于存储、操作和查询存储在关系型数据库中的数据,也称为关系数据库管理系统RDBMSes)。关系型数据库中的数据以表格形式存储,表格包含行和列。在现实世界中,实体之间存在关系,关系型数据库试图将这些现实世界中的关系模拟为表格之间的关系。因此,在关系型数据库中,单个表格包含与特定实体相关的数据,这些表格可能存在关联。

SQL 是一种声明式编程语言,帮助你指定想要从给定表中检索的行和列,并指定约束以过滤掉任何数据。RDBMS 包含一个查询优化器,它将 SQL 声明转换为查询计划,并在数据库引擎上执行。查询计划最终被转换为数据库引擎的执行计划,用于读取表中的行和列到内存中,并根据提供的约束进行过滤。

SQL 语言包括定义模式的子集——称为数据定义语言DDL)——以及修改和查询数据的子集——称为数据操作语言DML),如以下章节所述。

DDL

CREATEALTERDROPTRUNCATE等。以下 SQL 查询表示一个 DDL SQL 语句:

CREATE TABLE db_name.schema_name.table_name (
    column1 datatype,
    column2 datatype,
    column3 datatype,
   ....
);

上一个 SQL 语句表示在特定数据库中创建新表的典型命令,并定义了几个列及其数据类型。数据库是数据和日志文件的集合,而模式是数据库中的逻辑分组。

DML

SELECTUPDATEINSERTDELETEMERGE等。以下是一个 DML 查询示例:

SELECT column1, SUM(column2) AS agg_value
FROM db_name.schema_name.table_name 
WHERE column3 between value1 AND value2
GROUP BY column1
ORDER BY SUM(column2)
);

上一个查询结果通过对column1的每个不同值进行聚合,在基于column3上指定的约束过滤行后,最终根据聚合值对结果进行排序。

注意

尽管 SQL 通常遵循美国国家标准协会ANSI)设定的某些标准,但每个 RDBMS 供应商对 SQL 标准的实现略有不同,您应参考特定 RDBMS 的文档以获取正确的语法。

上一个 SQL 语句表示标准的 DDL 和 DML 查询;然而,每个 RDBMS 在 SQL 标准的实现上可能会有细微的差异。同样,Apache Spark 也有自己对 ANSI SQL 2000 标准的实现。

连接与子查询

关系型数据库中的表包含相关数据,通常需要在不同表之间进行连接,以生成有意义的分析。因此,SQL 支持诸如连接和子查询等操作,使用户能够跨表合并数据,如以下 SQL 语句所示:

SELECT a.column1, b.cloumn2, b.column3
FROM table1 AS a JOIN  table2 AS b
ON a.column1 = b.column2

在之前的 SQL 查询中,我们使用一个公共键列连接了两个表,并在JOIN操作后从这两个表中生成了列。类似地,子查询是查询中的查询,可以出现在SELECTWHEREFROM子句中,它允许你从多个表中合并数据。接下来的章节将探讨在 Spark SQL 中这些 SQL 查询的具体实现。

基于行存储与列存储

数据库以两种方式之一物理存储数据,要么以行存储方式,要么以列存储方式。每种方式都有其优缺点,取决于使用场景。在行存储中,所有的值一起存储,而在列存储中,单个列的所有值会连续存储在物理存储介质上,如下图所示:

https://github.com/OpenDocCN/freelearn-ds-pt3-zh/raw/master/docs/ess-pyspk-scl-da/img/B16736_12_1.jpg

图 12.1 – 行存储与列存储

如前面截图所示,在行存储中,整行及其所有列值会被一起存储在物理存储介质上。这使得查找单个行并快速高效地从存储中检索其所有列变得更容易。而列存储则将单个列的所有值连续存储在物理存储介质上,这使得检索单个列既快速又高效。

行存储在事务系统中更为流行,在这些系统中,快速检索单个事务记录或行更为重要。另一方面,分析系统通常处理的是行的聚合,只需要每个查询检索少量的列。因此,在设计分析系统时,选择列存储更加高效。列存储还提供了更好的数据压缩比,从而在存储大量历史数据时,能够更有效地利用可用存储空间。包括 数据仓库数据湖 在内的分析存储系统更倾向于使用列存储而非行存储。流行的大数据文件格式如 Parquet优化行列存储ORC)也采用列存储。

SQL 的易用性和普遍性促使许多非关系型数据处理框架的创建者,如 Hadoop 和 Apache Spark,采纳 SQL 的子集或变体来创建 Hadoop Hive 和 Spark SQL。我们将在接下来的章节中详细探讨 Spark SQL。

Spark SQL 简介

Spark SQL 为 Apache Spark 提供了原生的 SQL 支持,并统一了查询存储在 Spark DataFrames 和外部数据源中的数据的过程。Spark SQL 将 DataFrames 和关系表统一,使得开发者可以轻松地将 SQL 命令与外部数据查询结合,进行复杂的分析。随着 Apache Spark 1.3 的发布,Spark SQL 驱动的 Spark DataFrames 成为表达数据处理代码的事实标准抽象方式,而 弹性分布式数据集RDDs)仍然是 Spark 的核心抽象方法,如下图所示:

https://github.com/OpenDocCN/freelearn-ds-pt3-zh/raw/master/docs/ess-pyspk-scl-da/img/B16736_12_2.jpg

图 12.2 – Spark SQL 架构

如前面的图所示,你可以看到现在大多数 Spark 组件都利用了 Spark SQL 和 DataFrames。Spark SQL 提供了关于数据结构和正在执行的计算的更多信息,Spark SQL 引擎利用这些额外的信息对查询进行进一步的优化。使用 Spark SQL,Spark 的所有组件——包括结构化流DataFramesSpark MLGraphFrames——以及所有的编程应用程序编程接口APIs)——包括ScalaJavaPythonRSQL——都使用相同的执行引擎来表达计算。这种统一性使你可以轻松地在不同的 API 之间切换,并让你根据当前任务选择合适的 API。某些数据处理操作,如连接多个表格,在 SQL 中表达起来更容易,开发者可以轻松地将SQLScalaJavaPython代码混合使用。

Spark SQL 还引入了一个强大的新优化框架,名为Catalyst,它可以自动将任何数据处理代码,不论是使用 Spark DataFrames 还是使用 Spark SQL 表达的,转换为更高效的执行方式。我们将在接下来的章节中深入探讨Catalyst优化器。

Catalyst 优化器

SQL 查询优化器在关系型数据库管理系统(RDBMS)中是一个过程,它确定给定 SQL 查询处理存储在数据库中的数据的最有效方式。SQL 优化器尝试生成给定 SQL 查询的最优执行方式。优化器通常会生成多个查询执行计划,并从中选择最优的一个。它通常会考虑因素如中央处理单元CPU)、输入/输出I/O)以及查询的表格的任何可用统计信息,以选择最优的查询执行计划。优化器根据选择的查询执行计划,决定以任何顺序重新排序、合并和处理查询,以获得最优结果。

Spark SQL 引擎也配备了一个名为Catalyst的查询优化器。Catalyst基于函数式编程的概念,像 Spark 的其他代码库一样,利用 Scala 编程语言的特性来构建一个强大且可扩展的查询优化器。Spark 的 Catalyst 优化器通过一系列步骤为给定的 Spark SQL 查询生成一个最优执行计划,如下图所示:

https://github.com/OpenDocCN/freelearn-ds-pt3-zh/raw/master/docs/ess-pyspk-scl-da/img/B16736_12_03.jpg

图 12.3 – Spark 的 Catalyst 优化器

如前图所示,Catalyst 优化器首先在解析引用后生成逻辑计划,然后基于标准的规则优化技术优化逻辑计划。接着,使用优化后的逻辑计划生成一组物理执行计划,并选择最佳的物理计划,最终使用最佳的物理计划生成Java 虚拟机JVM)字节码。这个过程使得 Spark SQL 能够将用户查询转化为最佳的数据处理代码,而开发者无需了解 Spark 分布式数据处理范式的微观细节。此外,Spark SQL DataFrame API 在 Java、Scala、Python 和 R 编程语言中的实现都经过相同的 Catalyst 优化器。因此,无论使用哪种编程语言编写的 Spark SQL 或任何数据处理代码,性能都是相当的。

提示

在某些情况下,PySpark DataFrame 的代码性能可能无法与 Scala 或 Java 代码相比。一个例子是当在 PySpark DataFrame 操作中使用非矢量化的用户定义函数UDFs)时。Catalyst 无法查看 Python 中的 UDF,因此无法优化代码。因此,应该将其替换为 Spark SQL 的内置函数或在 Scala 或 Java 中定义的 UDF。

一位经验丰富的数据工程师,如果对 RDD API 有深入了解,可能写出比 Catalyst 优化器更优化的代码;然而,通过让 Catalyst 处理代码生成的复杂性,开发者可以将宝贵的时间集中在实际的数据处理任务上,从而提高效率。在深入了解 Spark SQL 引擎的内部工作原理后,理解 Spark SQL 可以处理的数据源类型会非常有用。

Spark SQL 数据源

由于 Spark DataFrame API 和 SQL API 都基于由 Catalyst 优化器提供支持的相同 Spark SQL 引擎,它们也支持相同的数据源。这里展示了一些突出的 Spark SQL 数据源。

文件数据源

Spark SQL 原生支持基于文件的数据源,例如 Parquet、ORC、Delta 等,以下是一个 SQL 查询示例:

SELECT * FROM delta.'/FileStore/shared_uploads/delta/retail_features.delta' LIMIT 10;

在前面的 SQL 语句中,数据直接从 delta. 前缀查询。同样的 SQL 语法也可以用于数据湖中的 Parquet 文件位置。

其他文件类型,如JavaScript 对象表示法JSON)和逗号分隔值CSV),需要先在元数据存储中注册表或视图,因为这些文件不自描述,缺乏固有的模式信息。以下是使用 CSV 文件与 Spark SQL 的示例 SQL 查询:

CREATE OR REPLACE TEMPORARY VIEW csv_able
USING csv
OPTIONS (
  header "true",
  inferSchema "true",
  path "/FileStore/ConsolidatedCities.csv"
);
SELECT * FROM csv_able LIMIT 5;

在前面的 SQL 语句中,我们首先使用数据湖中的 CSV 文件通过 CSV 数据源创建一个临时视图。我们还使用 OPTIONS 来指定 CSV 文件包含标题行,并从文件本身推断出模式。

注意

元存储是一个 RDBMS 数据库,Spark SQL 将数据库、表、列和分区等元数据信息持久化存储在其中。

如果表需要在集群重启后保持持久化,并且将来会被重用,您也可以创建一个永久表而不是临时视图。

JDBC 数据源

现有的 RDBMS 数据库也可以通过 Java 数据库连接JDBC)与元存储进行注册,并作为 Spark SQL 的数据源。以下代码块展示了一个示例:

CREATE TEMPORARY VIEW jdbcTable
USING org.apache.spark.sql.jdbc
OPTIONS (
  url "jdbc:mysql://localhost:3306/pysparkdb",
  dbtable "authors",
  user 'username',
  password 'password'
);
SELECT * FROM resultTable; 

在前面的代码块中,我们使用 jdbc 数据源创建了一个临时视图,并指定了数据库连接选项,如数据库 统一资源定位符URL)、表名、用户名、密码等。

Hive 数据源

Apache Hive 是 Hadoop 生态系统中的一个数据仓库,可以使用 SQL 读取、写入和管理存储在 Hadoop 文件系统或数据湖中的数据集。Spark SQL 可以与 Apache Hive 一起使用,包括 Hive 元存储、Hive 序列化/反序列化器SerDes)以及 Hive UDF。Spark SQL 支持大多数 Hive 特性,如 Hive 查询语言、Hive 表达式、用户定义的聚合函数、窗口函数、连接、并集、子查询等。然而,像 Hive 原子性、一致性、隔离性、持久性ACID)表更新、Hive I/O 格式以及某些 Hive 特定优化等特性是不支持的。支持和不支持的特性完整列表可以在 Databricks 的公共文档中找到,链接如下:docs.databricks.com/spark/latest/spark-sql/compatibility/hive.html

现在,您已经了解了 Spark SQL 组件,如 Catalyst 优化器及其数据源,我们可以深入探讨 Spark SQL 特定的语法和函数。

Spark SQL 语言参考

作为 Hadoop 生态系统的一部分,Spark 一直以来都与 Hive 兼容。尽管 Hive 查询语言与 ANSI SQL 标准有很大差异,Spark 3.0 的 Spark SQL 可以通过配置 spark.sql.ansi.enabled 来实现 ANSI SQL 兼容。启用此配置后,Spark SQL 将使用 ANSI SQL 兼容的方言,而不是 Hive 方言。

即使启用了 ANSI SQL 兼容,Spark SQL 可能仍无法完全符合 ANSI SQL 方言,本节将探讨一些 Spark SQL 的突出 DDL 和 DML 语法。

Spark SQL DDL

使用 Spark SQL 创建数据库和表的语法如下所示:

CREATE DATABASE IF NOT EXISTS feature_store;
CREATE TABLE IF NOT EXISTS feature_store.retail_features
USING DELTA
LOCATION '/FileStore/shared_uploads/delta/retail_features.delta';

在前面的代码块中,我们执行了以下操作:

  • 首先,如果数据库不存在,我们使用 CREATE DATABASE 命令创建一个数据库。通过此命令,还可以指定一些选项,如持久存储中的物理仓库位置和其他数据库属性。

  • 然后,我们使用 delta 作为数据源创建一个表,并指定数据的位置。这里,指定位置的数据已经存在,因此无需指定任何模式信息,如列名及其数据类型。然而,为了创建一个空的表结构,仍然需要指定列及其数据类型。

要更改现有表的某些属性,例如重命名表、修改或删除列,或修改表分区信息,可以使用 ALTER 命令,如下所示的代码示例所示:

ALTER TABLE feature_store.retail_features RENAME TO feature_store.etailer_features;
ALTER TABLE feature_store.etailer_features ADD COLUMN (new_col String);

在前面的代码示例中,我们在第一个 SQL 语句中重命名了表。第二个 SQL 语句修改了表并添加了一个新的 String 类型的列。在 Spark SQL 中,仅支持更改列注释和添加新列。以下代码示例展示了 Spark SQL 语法,用于完全删除或删除对象:

TRUNCATE TABLE feature_store.etailer_features;
DROP TABLE feature_store.etailer_features;
DROP DATABASE feature_store;

在前面的代码示例中,TRUNCATE 命令删除了表中的所有行,并保持表结构和模式不变。DROP TABLE 命令删除了表及其模式,而 DROP DATABASE 命令则删除整个数据库。

Spark DML

数据操作涉及从表中添加、修改和删除数据。以下代码语句展示了一些示例:

INSERT INTO feature_store.retail_features
SELECT * FROM delta.'/FileStore/shared_uploads/delta/retail_features.delta';

前面的 SQL 语句通过另一个 SQL 查询的结果将数据插入到现有表中。类似地,INSERT OVERWRITE 命令可用于覆盖现有数据,然后将新数据加载到表中。以下 SQL 语句可用于选择性地删除表中的数据:

DELETE FROM feature_store.retail_features WHERE country_code = 'FR';

前面的 SQL 语句根据筛选条件从表中删除选择性的数据。虽然 SELECT 语句不是必须的,但它们在数据分析中至关重要。以下 SQL 语句展示了使用 SELECT 语句进行 Spark SQL 数据分析:

SELECT
  year AS emp_year
  max(m.last_name),
  max(m.first_name),
  avg(s.salary) AS avg_salary
FROM
  author_salary s
  JOIN mysql_authors m ON m.uid = s.id
GROUP BY year
ORDER BY s.salary DESC

前面的 SQL 语句根据公共键对两个表进行内连接,并按年份计算每位员工的平均薪资。此查询的结果提供了员工薪资随年份变化的见解,并且可以轻松安排定期刷新。

通过这种方式,使用 Apache Spark 强大的分布式 SQL 引擎及其富有表现力的 Spark SQL 语言,您可以在无需学习任何新编程语言的情况下,以快速高效的方式执行复杂的数据分析。有关支持的数据类型、函数库和 SQL 语法的完整参考指南,可以在 Apache Spark 的公共文档中找到:spark.apache.org/docs/latest/sql-ref-syntax.html

尽管 Spark SQL 的Catalyst优化器承担了大部分优化工作,但了解一些进一步调整 Spark SQL 性能的技巧还是很有帮助的,以下部分将介绍几个显著的技巧。

优化 Spark SQL 性能

在上一节中,你了解了 Catalyst 优化器是如何通过将代码运行经过一系列优化步骤,直到得出最佳执行计划,从而优化用户代码的。为了充分利用 Catalyst 优化器,推荐使用利用 Spark SQL 引擎的 Spark 代码——即 Spark SQL 和 DataFrame API——并尽量避免使用基于 RDD 的 Spark 代码。Catalyst 优化器无法识别 UDF(用户定义函数),因此用户可能会编写出次优的代码,从而导致性能下降。因此,推荐使用内置函数,而不是 UDF,或者在 Scala 和 Java 中定义函数,然后在 SQL 和 Python API 中使用这些函数。

尽管 Spark SQL 支持基于文件的格式,如 CSV 和 JSON,但推荐使用序列化数据格式,如 Parquet、AVRO 和 ORC。半结构化格式(如 CSV 或 JSON)会带来性能开销,首先是在模式推断阶段,因为它们无法将模式直接提供给 Spark SQL 引擎。其次,它们不支持诸如谓词下推(Predicate Pushdown)之类的数据过滤功能,因此必须将整个文件加载到内存中,才能在源头过滤掉数据。作为天生的无压缩文件格式,CSV 和 JSON 相比于 Parquet 等二进制压缩格式,也消耗更多的内存。甚至传统的关系型数据库比使用半结构化数据格式更为推荐,因为它们支持谓词下推,并且可以将一些数据处理任务委托给数据库。

对于诸如机器学习(ML)等迭代工作负载,数据集被多次访问的情况下,将数据集缓存到内存中非常有用,这样后续对表或 DataFrame 的扫描就会在内存中进行,从而大大提高查询性能。

Spark 提供了各种 BROADCASTMERGESHUFFLE_HASH 等。然而,Spark SQL 引擎有时可能无法预测某个查询的策略。可以通过将提示传递给 Spark SQL 查询来缓解这个问题,如下代码块所示:

SELECT /*+ BROADCAST(m) */
  year AS emp_year
  max(m.last_name),
  max(m.first_name),
  avg(s.salary) AS avg_salary
FROM
  author_salary s
  JOIN mysql_authors m ON m.uid = s.id
GROUP BY year
ORDER BY s.salary DESC;

在前面的代码块中,我们传入了一个SELECT子句。这指定了较小的表会被广播到所有的工作节点,这应该能够提高连接操作的性能,从而提升整体查询性能。同样,COALESCEREPARTITION提示也可以传递给 Spark SQL 查询;这些提示减少了输出文件的数量,从而提升了性能。

注意

SQL 提示、查询提示或优化器提示是标准 SQL 语句的补充,用于提示 SQL 执行引擎选择开发者认为最优的特定物理执行计划。SQL 提示在所有关系型数据库管理系统(RDBMS)引擎中通常都得到支持,现在 Spark SQL 也支持某些类型的查询,如前所述。

虽然 Catalyst 优化器在生成最佳物理查询执行计划方面表现出色,但仍然可能会因为表上的陈旧统计数据而受到影响。从 Spark 3.0 开始,spark.sql.adaptive.enabled 配置项得以引入。这些只是 Spark SQL 性能调优技术中的一部分,每项技术的详细描述可以在 Apache Spark 官方文档中找到,链接如下:spark.apache.org/docs/latest/sql-performance-tuning.html

摘要

本章介绍了 SQL 作为一种声明式语言,它因易用性和表达能力而被普遍接受为结构化数据分析的语言。你了解了 SQL 的基本构造,包括 SQL 的 DDL 和 DML 方言。你还介绍了 Spark SQL 引擎,这是一个统一的分布式查询引擎,支持 Spark SQL 和 DataFrame API。一般来说,介绍了 SQL 优化器,Spark 自己的查询优化器 Catalyst 也做了介绍,并说明了它如何将 Spark SQL 查询转换为 Java JVM 字节码。还介绍了 Spark SQL 语言的参考资料,以及最重要的 DDL 和 DML 语句,并提供了示例。最后,我们讨论了一些性能优化技术,帮助你在数据分析过程中充分发挥 Spark SQL 的优势。在下一章,我们将进一步拓展 Spark SQL 知识,探讨外部数据分析工具,如 商业智能BI)工具和 SQL 分析工具,如何利用 Apache Spark 的分布式 SQL 引擎,以快速高效地处理和可视化大量数据。

第十三章:将外部工具与 Spark SQL 集成

商业智能BI)指的是使组织能够做出明智的、数据驱动的决策的能力。BI 结合了数据处理能力、数据可视化、业务分析和一套最佳实践,帮助组织在战略和战术决策中优化、精炼和简化其业务流程。组织通常依赖专业的软件工具,称为 BI 工具,以满足其 BI 需求。BI 工具将战略与技术结合,收集、分析和解释来自不同来源的数据,并提供有关企业过去和现在状态的业务分析。

BI 工具传统上依赖数据仓库作为数据源和数据处理引擎。然而,随着大数据和实时数据的出现,BI 工具已经扩展到使用数据湖和其他新的数据存储和处理技术作为数据源。在本章中,您将探索如何通过 Spark Thrift Java 数据库连接/开放数据库连接JDBC/ODBC)服务器,将 Spark SQL 作为分布式结构化查询语言SQL)引擎,用于 BI 和 SQL 分析工具。将介绍 Spark SQL 与 SQL 分析和 BI 工具的连接要求,以及详细的配置和设置步骤。最后,本章还将介绍从任意 Python 应用程序连接到 Spark SQL 的选项。

本章将涵盖以下主要内容:

  • Apache Spark 作为分布式 SQL 引擎

  • Spark 与 SQL 分析工具的连接

  • Spark 与 BI 工具的连接

  • 使用pyodbc将 Python 应用程序连接到 Spark SQL

本章所获得的技能包括理解 Spark Thrift JDBC/ODBC 服务器、如何通过 JDBC 和 Spark Thrift 服务器将 SQL 编辑器和 BI 工具连接到 Spark SQL,以及使用 Pyodbc 将基于 Python 的业务应用程序与 Spark SQL 引擎连接。

技术要求

本章所需内容如下:

Apache Spark 作为一个分布式 SQL 引擎

SQL 的一个常见应用是与 BI 和 SQL 分析工具的结合。这些基于 SQL 的工具通过 JDBC 或 ODBC 连接以及内置的传统 RDBMS JDBC/ODBC 连接来连接到 关系型数据库管理系统 (RDBMS)。在前面的章节中,你已经看到,Spark SQL 可以通过笔记本使用,并与 PySpark、Scala、Java 或 R 应用程序混合使用。然而,Apache Spark 也可以作为一个强大且快速的分布式 SQL 引擎,通过 JDBC/ODBC 连接或命令行使用。

注意

JDBC 是一个基于 SQL 的 应用程序编程接口 (API),Java 应用程序通过它连接到关系型数据库管理系统 (RDBMS)。类似地,ODBC 是由 Microsoft 创建的一个 SQL 基础的 API,用于为基于 Windows 的应用程序提供 RDBMS 访问。JDBC/ODBC 驱动程序是一个客户端软件组件,由 RDBMS 厂商自己开发或由第三方开发,可与外部工具一起使用,通过 JDBC/ODBC 标准连接到 RDBMS。

在接下来的章节中,我们将探讨如何利用 Apache Spark 的 JDBC/ODBC 服务功能。

Hive Thrift JDBC/ODBC 服务器介绍

虽然 JDBC/ODBC 驱动程序赋予 BI 或 SQL 分析工具等客户端软件连接数据库服务器的能力,但数据库服务器也需要一些服务器端组件才能利用 JDBC/ODBC 标准。大多数 RDBMS 都内置了这些 JDBC/ODBC 服务功能,Apache Spark 也可以通过 Thrift JDBC/ODBC 服务器启用此服务器端功能。

HiveServer2 是一个服务器端接口,旨在使 Hadoop Hive 客户端能够执行针对 Apache Hive 的 Hive 查询。HiveServer2 已经开发出来,旨在通过 JDBC 和 ODBC 等开放的 API 提供多客户端并发能力。HiveServer2 本身基于 Apache Thrift,这是一种用于在多种编程语言中创建服务的二进制通信协议。Spark Thrift Server 是 Apache Spark 对 HiveServer2 的实现,允许 JDBC/ODBC 客户端在 Apache Spark 上执行 Spark SQL 查询。

Spark Thrift Server 随 Apache Spark 发行版一起捆绑,且大多数 Apache Spark 厂商默认在他们的 Spark 集群上启用此服务。以 Databricks 为例,可以通过 集群 页面访问此服务,如下图所示:

https://github.com/OpenDocCN/freelearn-ds-pt3-zh/raw/master/docs/ess-pyspk-scl-da/img/B16736_13_01.jpg

](https://github.com/OpenDocCN/freelearn-ds-pt3-zh/raw/master/docs/ess-pyspk-scl-da/img/B16736_13_01.jpg)

图 13.1 – Databricks JDBC/ODBC 接口

你可以通过导航到 Databricks Web 界面内的Clusters页面,然后点击高级选项,再点击JDBC/ODBC选项卡,来访问前一屏幕截图中显示的 Databricks JDBC/ODBC 接口。Databricks JDBC/ODBC 接口提供了外部工具连接 Databricks Spark 集群所需的主机名、端口、协议、HTTP 路径和实际的 JDBC URL。在接下来的部分中,我们将探讨如何使用 Spark Thrift Server 功能,使外部基于 SQL 的客户端能够利用 Apache Spark 作为分布式 SQL 引擎。

Spark 与 SQL 分析工具的连接

SQL 分析工具,如其名称所示,是专为快速和简便 SQL 分析而设计的工具。它们允许您连接到一个或多个关系数据库管理系统(RDBMS),浏览各种数据库、模式、表和列。它们甚至帮助您可视化分析表及其结构。它们还具有设计用于快速 SQL 分析的界面,具有多个窗口,让您可以在一个窗口浏览表和列,在另一个窗口中编写 SQL 查询,并在另一个窗口中查看结果。其中一种这样的 SQL 分析工具,称为SQL Workbench/J,如下屏幕截图所示:

https://github.com/OpenDocCN/freelearn-ds-pt3-zh/raw/master/docs/ess-pyspk-scl-da/img/B16736_13_02.jpg

图 13.2 – SQL Workbench/J 界面

前面的屏幕截图展示了SQL Workbench/J的界面,它代表了一个典型的 SQL 编辑器界面,左侧窗格有数据库、模式、表和列浏览器。顶部窗格有一个用于编写实际 SQL 查询的文本界面,底部窗格显示已执行 SQL 查询的结果,并有其他选项卡显示任何错误消息等等。顶部还有一个菜单和工具栏,用于建立数据库连接、在各个数据库之间切换、执行 SQL 查询、浏览数据库、保存 SQL 查询、浏览查询历史记录等等。这种类型的 SQL 分析界面非常直观,非常适合快速 SQL 分析,以及构建基于 SQL 的数据处理作业,因为可以轻松浏览数据库、表和列。可以轻松地将表和列名拖放到查询组合器窗口中,快速查看和分析结果。

此外,还有一些更复杂的 SQL 分析工具,可以让你在同一个界面内可视化查询结果。一些开源工具如RedashMetabaseApache Superset,以及一些云原生工具如Google Data StudioAmazon QuickSight等。

提示

Redash 最近被 Databricks 收购,并可在 Databricks 付费版本中使用;截至目前,它在 Databricks 社区版中不可用。

现在,您已经了解了 SQL 分析工具的外观和工作原理,接下来让我们来看一下将 SQL 分析工具(如 SQL Workbench/J)连接到 Databricks Community Edition 所需的步骤。

SQL Workbench/J 是一个免费的、独立于 RDBMS 的 SQL 分析工具,基于 Java,可以与任何您选择的操作系统一起使用。有关下载和运行 SQL Workbench/J 的说明,请参阅此处:www.sql-workbench.eu/downloads.html

一旦您在本地机器上成功设置并运行 SQL Workbench/J,接下来的步骤将帮助您将其与 Databricks Community Edition 连接:

  1. databricks.com/spark/jdbc-drivers-download 下载 Databricks JDBC 驱动程序,并将其存储在已知位置。

  2. 启动 SQL Workbench/J 并打开 文件 菜单。然后,点击 连接窗口,以进入以下屏幕:https://github.com/OpenDocCN/freelearn-ds-pt3-zh/raw/master/docs/ess-pyspk-scl-da/img/B16736_13_03.jpg

    ](https://github.com/OpenDocCN/freelearn-ds-pt3-zh/raw/master/docs/ess-pyspk-scl-da/img/B16736_13_03.jpg)

    图 13.3 – SQL Workbench/J 连接窗口

  3. 在前面的窗口中,点击 管理驱动程序,以进入以下屏幕:https://github.com/OpenDocCN/freelearn-ds-pt3-zh/raw/master/docs/ess-pyspk-scl-da/img/B16736_13_04.jpg

    ](https://github.com/OpenDocCN/freelearn-ds-pt3-zh/raw/master/docs/ess-pyspk-scl-da/img/B16736_13_04.jpg)

    图 13.4 – 管理驱动程序屏幕

  4. 如前所述的 管理驱动程序 窗口截图所示,点击文件夹图标,导航到您存储已下载 Databricks 驱动程序的文件夹并打开它,然后点击 OK 按钮。

  5. 现在,导航到您的 Databricks UIDPWD 部分,并将其粘贴到 URL 字段中,位于 SQL Workbench/J 连接窗口,如下所示截图所示:https://github.com/OpenDocCN/freelearn-ds-pt3-zh/raw/master/docs/ess-pyspk-scl-da/img/B16736_13_06.jpg

    ](https://github.com/OpenDocCN/freelearn-ds-pt3-zh/raw/master/docs/ess-pyspk-scl-da/img/B16736_13_06.jpg)

    图 13.6 – SQL Workbench/J 连接参数

  6. 在输入来自 Databricks 集群 页面所需的 JDBC 参数后,在 SQL Workbench/J 连接窗口中的 用户名密码 字段中输入您的 Databricks 用户名和密码。然后,点击 测试 按钮以测试与 Databricks 集群的连接。如果所有连接参数已正确提供,您应该会看到一个 连接成功 的消息弹出。

    提示

    如果您看到任何连接失败或 Host Not Found 类型的错误,请确保 Databricks 集群已启动并正在运行。

通过遵循之前的步骤,您可以成功地将 SQL 分析工具(如 SQL Workbench/J)连接到 Databricks 集群,并远程运行 Spark SQL 查询。也可以连接到运行在其他供应商集群上的其他 Spark 集群——只需确保直接从供应商处获取适当的 HiveServer2 驱动程序。现代 BI 工具也认识到连接大数据技术和数据湖的重要性,在接下来的部分中,我们将探讨如何通过 JDBC 连接将 BI 工具与 Apache Spark 连接。

Spark 连接到 BI 工具

在大数据和人工智能AI)时代,Hadoop 和 Spark 将数据仓库现代化为分布式仓库,能够处理高达拍字节PB)的数据。因此,BI 工具也已发展为利用基于 Hadoop 和 Spark 的分析存储作为其数据源,并通过 JDBC/ODBC 连接到这些存储。包括 Tableau、Looker、Sisense、MicroStrategy、Domo 等在内的 BI 工具都支持与 Apache Hive 和 Spark SQL 的连接,并内置了相应的驱动程序。在本节中,我们将探讨如何通过 JDBC 连接将 BI 工具,如 Tableau Online,连接到 Databricks Community Edition。

Tableau Online 是一个完全托管在云中的 BI 平台,允许你进行数据分析、发布报告和仪表盘,并创建交互式可视化,所有操作都可以通过网页浏览器完成。以下步骤描述了将 Tableau Online 与 Databricks Community Edition 连接的过程:

  1. 如果你已经拥有一个现有的 Tableau Online 账户,请登录。如果没有,你可以在此处请求免费试用:www.tableau.com/products/online/request-trial

  2. 登录后,点击右上角的新建按钮,如以下截图所示:https://github.com/OpenDocCN/freelearn-ds-pt3-zh/raw/master/docs/ess-pyspk-scl-da/img/B16736_13_07.jpg

    图 13.7 – Tableau Online 新工作簿

  3. 新创建的工作簿会提示你连接到数据。点击连接器标签,并从可用数据源列表中选择Databricks,如以下截图所示:https://github.com/OpenDocCN/freelearn-ds-pt3-zh/raw/master/docs/ess-pyspk-scl-da/img/B16736_13_08.jpg

    图 13.8 – Tableau Online 数据源

  4. 然后,提供 Databricks 集群的详细信息,如服务器主机名HTTP 路径身份验证用户名密码,如以下截图所示,并点击登录按钮。这些详细信息可以在 Databricks 集群页面找到:https://github.com/OpenDocCN/freelearn-ds-pt3-zh/raw/master/docs/ess-pyspk-scl-da/img/B16736_13_09.jpg

    图 13.9 – Tableau Online Databricks 连接

  5. 一旦连接成功,你的新工作簿将在数据源标签页中打开,你可以浏览现有的数据库和表格,如以下截图所示:https://github.com/OpenDocCN/freelearn-ds-pt3-zh/raw/master/docs/ess-pyspk-scl-da/img/B16736_13_10.jpg

    图 13.10 – Tableau Online 数据源

  6. 数据源标签页还允许你拖放表格并定义表之间的关系和连接,如以下截图所示:https://github.com/OpenDocCN/freelearn-ds-pt3-zh/raw/master/docs/ess-pyspk-scl-da/img/B16736_13_11.jpg

    图 13.11 – Tableau Online:定义表连接

  7. Tableau Online 数据源还允许你与基础数据源创建两种类型的连接——一种是实时连接,它会在每次请求时查询基础数据源,另一种是创建提取,它从数据源中提取数据并将其存储在 Tableau 中。对于像 Apache Spark 这样的庞大数据源,建议创建实时连接,因为查询的数据量可能远大于平常。

  8. 还可以通过点击更新现在按钮在数据源标签中浏览示例数据,如下图所示:https://github.com/OpenDocCN/freelearn-ds-pt3-zh/raw/master/docs/ess-pyspk-scl-da/img/B16736_13_12.jpg

    图 13.12 – Tableau Online 数据源预览

  9. 一旦数据源连接建立,你可以通过点击Sheet1或创建新的附加工作表来继续可视化数据并创建报告和仪表板。

  10. 一旦进入工作表,你可以通过将数据窗格中的列拖放到空白工作表上来开始可视化数据。

  11. Tableau 会根据选择的字段自动选择合适的可视化方式。可视化方式也可以通过可视化下拉选择器进行更改,数据过滤器可以通过过滤器框定义。在字段中可以定义度量的聚合,还可以根据需要将列定义为维度属性度量。顶部菜单还提供了排序和透视数据的其他设置,并有其他格式化选项。Tableau Online 还提供了高级分析选项,如定义四分位数、中位数等。通过这种方式,利用 Tableau Online 内置的 Databricks 连接器,数据可以在 Apache Spark 的强大和高效的支持下进行大规模分析,同时还可以享受像 Tableau Online 这样显著 BI 工具的图形用户界面GUI)的易用性,截图如下所示:

https://github.com/OpenDocCN/freelearn-ds-pt3-zh/raw/master/docs/ess-pyspk-scl-da/img/B16736_13_13.jpg

图 13.13 – Tableau Online 工作表与可视化

Tableau Online是许多流行的 BI 工具之一,支持开箱即用的原生 Databricks 连接。现代 BI 工具还提供了 Spark SQL 连接选项,用于连接 Databricks 之外的 Apache Spark 发行版。

连接 Apache Spark 不仅限于 SQL 分析和 BI 工具。由于 JDBC 协议基于 Java,并且是为了被基于 Java 的应用程序使用而设计的,因此任何使用Java 虚拟机JVM)基础编程语言(如JavaScala)构建的应用程序也可以使用 JDBC 连接选项连接 Apache Spark。

那么,像 Python 这样流行的编程语言构建的应用程序怎么办?这些类型的 Python 应用程序可以通过Pyodbc连接到 Apache Spark,我们将在接下来的部分中探讨这一点。

将 Python 应用程序连接到 Spark SQL,使用 Pyodbc

Pyodbc是一个开源的 Python 模块,用于通过 ODBC 连接将 Python 应用程序连接到数据源。Pyodbc 可以与任何本地 Python 应用程序一起使用,通过 ODBC 驱动程序连接到 Apache Spark,并访问使用 Apache Spark SQL 定义的数据库和表格。在本节中,我们将探讨如何通过以下步骤使用Pyodbc将运行在本地机器上的 Python 连接到 Databricks 集群:

  1. 从这里下载并安装 Databricks 提供的 Simba ODBC 驱动程序到本地机器:databricks.com/spark/odbc-drivers-download

  2. 使用pip在本地机器的 Python 中安装 Pyodbc,如下所示的命令:

    sudo pip install pyodbc
    
  3. 使用您选择的文本编辑器创建一个新的 Python 文件,并将以下代码粘贴到其中:

    import pyodbc
    odbc_conn = pyodbc.connect("Driver /Library/simba/spark/lib/libsparkodbc_sbu.dylib;" +
                          "HOST=community.cloud.databricks.com;" +
                          "PORT=443;" +
                          "Schema=default;" +
                          "SparkServerType=3;" +
                          "AuthMech=3;" +
                          "UID=username;" +
                          "PWD=password;" +
                          "ThriftTransport=2;" +
                          "SSL=1;" +
                          "HTTPPath= sql/protocolv1/o/4211598440416462/0727-201300-wily320",
                          autocommit=True)
    cursor = odbc_conn.cursor()
    cursor.execute(f"SELECT * FROM retail_features LIMIT 5")
    for row in cursor.fetchall():
      print(row)
    
  4. 前述代码配置的驱动路径可能会根据您的操作系统有所不同,具体如下:

    macOS: /Library/simba/spark/lib/libsparkodbc_sbu.dylib
    Linux 64-bit: /opt/simba/spark/lib/64/libsparkodbc_sb64.so
    Linux 32-bit: /opt/simba/spark/lib/32/libsparkodbc_sb32.so
    
  5. 主机名、端口和 HTTP 路径的值可以从 Databricks 集群的JDBC/ODBC标签页获取。

  6. 一旦您添加了所有代码并输入了适当的配置值,保存 Python 代码文件并命名为pyodbc-databricks.py

  7. 现在,您可以使用以下命令在 Python 解释器中执行代码:

    python pyodbd-databricks.py
    
  8. 一旦代码成功运行,您在 SQL 查询中指定的表格的前五行将显示在控制台上。

    注意

    配置 Windows 机器上的 ODBC 驱动程序以供 Pyodbc 使用的说明,请参考 Databricks 公开文档页面:docs.databricks.com/dev-tools/pyodbc.html#windows

通过这种方式,使用Pyodbc,您可以将 Apache Spark SQL 集成到任何运行在您本地机器或云端、数据中心等远程机器上的 Python 应用程序中,同时仍能利用 Apache Spark 自带的快速强大的分布式 SQL 引擎。

摘要

在本章中,你已经探索了如何利用 Apache Spark 的 Thrift 服务器启用 JDBC/ODBC 连接,并将 Apache Spark 用作分布式 SQL 引擎。你学习了 HiveServer2 服务如何允许外部工具使用 JDBC/ODBC 标准连接到 Apache Hive,以及 Spark Thrift 服务器如何扩展 HiveServer2,从而在 Apache Spark 集群上实现类似的功能。本章还介绍了连接 SQL 分析工具(如 SQL Workbench/J)所需的步骤,并详细说明了如何将 Tableau Online 等 BI 工具与 Spark 集群连接。最后,还介绍了如何使用 Pyodbc 将任意 Python 应用程序(无论是本地运行在你的机器上,还是远程部署在云端或数据中心)连接到 Spark 集群的步骤。在本书的下一章也是最后一章中,我们将探索 Lakehouse 模式,这一模式可以帮助组织通过一个统一的分布式和持久化存储层,结合数据仓库和数据湖的最佳特性,轻松应对数据分析的三种工作负载——数据工程、数据科学和 SQL 分析。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值