Pyspark简介
Spark是一个用于大数据处理的开源框架。它最初是用scala编写的,后来由于对使用大数据进行机器学习的需求不断增加,因此发布了相同的python API。因此,Pyspark是一个用于Spark的Python API。它整合了Spark的力量和Python的简单性,用于数据分析。Pyspark可以有效地与spark组件一起工作,如spark SQL、Mllib和流,让我们利用大数据和机器学习的真正潜力。
在这篇文章中,我们将为企鹅数据建立一个分类管道。 我们将讨论如何处理缺失的数据,以及在Pyspark的管道模块的帮助下对数据进行扩展和转换。
启动一个Spark会话
Spark会话是每个底层spark功能的入口。它可以让我们创建和使用RDDs、Dataframes和Datasets。因此,要使用Spark,必须启动一个Spark会话。在Python中,我们可以通过使用下面提到的构建器模式来做到这一点。
from pyspark.sql import SparkSession
spark = SparkSession
.builder
.appName('classification with pyspark')
.config("spark.some.config.option", "some-value")
.getOrCreate()
读取数据
现在已经创建了一个spark会话,我们现在可以通过下面的代码片断来读取我们的数据。
dt = spark.read.csv('D:Data Setspenguins_size.csv', header=True)
dt.show(5)
+-------+---------+----------------+---------------+-----------------+-----------+------+
|species| island|culmen_length_mm|culmen_depth_mm|flipper_length_mm|body_mass_g| sex|
+-------+---------+----------------+---------------+-----------------+-----------+------+
| Adelie|Torgersen| 39.1| 18.7| 181| 3750| MALE|
| Adelie|Torgersen| 39.5| 17.4| 186| 3800|FEMALE|
| Adelie|Torgersen| 40.3| 18| 195| 3250|FEMALE|
| Adelie|Torgersen| NA| NA| NA| NA| NA|
| Adelie|Torgersen| 36.7| 19.3| 193| 3450|FEMALE|
+-------+---------+----------------+---------------+-----------------+-----------+------+
only showing top 5 rows
打印模式以了解我们的数据集中的数据类型。
output: root
|-- species: string (nullable = true)
|-- island: string (nullable = true)
|-- culmen_length_mm: string (nullable = true)
|-- culmen_depth_mm: string (nullable = true)
|-- flipper_length_mm: string (nullable = true)
|-- body_mass_g: string (nullable = true)
|-- sex: string (nullable = true)
正如你所看到的,所有的列都是字符串类型的,我们不能对字符串数据进行操作。因此,我们将把culmen长度、深度和flipper长度转换为浮点数,把身体质量转换为整数。
from pyspark.sql.types import IntegerType, FloatType
df = dt.withColumn("culmen_depth_mm",dt.culmen_depth_mm.cast(FloatType()))
.withColumn("culmen_length_mm",dt.culmen_length_mm.cast(FloatType()))
.withColumn("flipper_length_mm",dt.flipper_length_mm.cast('float'))
.withColumn("body_mass_g",dt.body_mass_g.cast('int'))
你可以用上面提到的两种方法进行转换。
让我们来看看我们的数据是否得到了转换。
output:root
|-- species: string (nullable = true)
|-- island: string (nullable = true)
|-- culmen_length_mm: float (nullable = true)
|-- culmen_depth_mm: float (nullable = true)
|-- flipper_length_mm: float (nullable = true)
|-- body_mass_g: integer (nullable = true)
|-- sex: string (nullable = true)
处理缺失值
在我们的数据集中有缺失值,让我们看看哪一列有多少缺失值。
from pyspark.sql.functions import col,isnan, when, count
df.select([count(when(isnan(c) | col(c).isNull() | col(c).contains('NA'), c)).alias(c) for c in df.columns]).show()
性别列有缺失值,但它们是字符串格式的,所以我们使用contains()和IsNull()。
找出缺失值的行
df.where(col('sex').contains('NA')).show()
现在,我们将创建一个没有任何缺失值的数据集。
df_new = df.where(df.sex != 'NA')
df_new.show(10)
+-------+---------+----------------+---------------+-----------------+-----------+------+
|species| island|culmen_length_mm|culmen_depth_mm|flipper_length_mm|body_mass_g| sex|
+-------+---------+----------------+---------------+-----------------+-----------+------+
| Adelie|Torgersen| 39.1| 18.7| 181.0| 3750| MALE|
| Adelie|Torgersen| 39.5| 17.4| 186.0| 3800|FEMALE|
| Adelie|Torgersen| 40.3| 18.0| 195.0| 3250|FEMALE|
| Adelie|Torgersen| 36.7| 19.3| 193.0| 3450|FEMALE|
| Adelie|Torgersen| 39.3| 20.6| 190.0| 3650| MALE|
| Adelie|Torgersen| 38.9| 17.8| 181.0| 3625|FEMALE|
| Adelie|Torgersen| 39.2| 19.6| 195.0| 4675| MALE|
| Adelie|Torgersen| 41.1| 17.6| 182.0| 3200|FEMALE|
| Adelie|Torgersen| 38.6| 21.2| 191.0| 3800| MALE|
| Adelie|Torgersen| 34.6| 21.1| 198.0| 4400| MALE|
+-------+---------+----------------+---------------+-----------------+-----------+------+
only showing top 10 rows
对分类变量进行编码
机器学习算法无法处理非数字数据,因此在将数据输入算法之前,需要将其转化为数字数据。
因此,首先,我们将根据数据类型将列名分开,这使我们更容易处理它们。
from collections import defaultdict
data_types = defaultdict(list)
for entry in df.schema.fields:
data_types[str(entry.dataType)].append(entry.name)
print(data_types)
Output: defaultdict(list,
{'StringType': ['species', 'island', 'sex'],
'FloatType': ['culmen_length_mm',
'culmen_depth_mm',
'flipper_length_mm'],
'IntegerType': ['body_mass_g']})
cat_cols = [var for var in data_types["StringType"]]
接下来,我们将导入Stringindexer,它是相当于Pyspark和OneHotEncoder的Scikit Learn Labelencoder。我们将使用管道方法来方便地将数据从分类类型转化为数字类型。OneHot编码将为每一行创建一个稀疏的向量。有关不同编码方法的详细知识,请访问这里。
from pyspark.ml.feature import StringIndexer, OneHotEncoder
stage_string_index = [StringIndexer(inputCol=col, outputCol=col+' string_indexed') for col in cat_cols]
stage_onehot_enc = [OneHotEncoder(inputCol=col+' string_indexed', outputCol=col+' onehot_enc') for col in cat_cols]
from pyspark.ml import Pipeline
ppl = Pipeline(stages= stage_string_index + stage_onehot_enc)
df_trans = ppl.fit(df_new).transform(df_new)
df_trans.show(10)
+-------+---------+----------------+---------------+-----------------+-----------+------+----------------------+---------------------+------------------+------------------+-----------------+--------------+
|species| island|culmen_length_mm|culmen_depth_mm|flipper_length_mm|body_mass_g| sex|species string_indexed|island string_indexed|sex string_indexed|species onehot_enc|island onehot_enc|sex onehot_enc|
+-------+---------+----------------+---------------+-----------------+-----------+------+----------------------+---------------------+------------------+------------------+-----------------+--------------+
| Adelie|Torgersen| 39.1| 18.7| 181.0| 3750| MALE| 0.0| 2.0| 0.0| (2,[0],[1.0])| (2,[],[])| (2,[0],[1.0])|
| Adelie|Torgersen| 39.5| 17.4| 186.0| 3800|FEMALE| 0.0| 2.0| 1.0| (2,[0],[1.0])| (2,[],[])| (2,[1],[1.0])|
| Adelie|Torgersen| 40.3| 18.0| 195.0| 3250|FEMALE| 0.0| 2.0| 1.0| (2,[0],[1.0])| (2,[],[])| (2,[1],[1.0])|
| Adelie|Torgersen| 36.7| 19.3| 193.0| 3450|FEMALE| 0.0| 2.0| 1.0| (2,[0],[1.0])| (2,[],[])| (2,[1],[1.0])|
| Adelie|Torgersen| 39.3| 20.6| 190.0| 3650| MALE| 0.0| 2.0| 0.0| (2,[0],[1.0])| (2,[],[])| (2,[0],[1.0])|
| Adelie|Torgersen| 38.9| 17.8| 181.0| 3625|FEMALE| 0.0| 2.0| 1.0| (2,[0],[1.0])| (2,[],[])| (2,[1],[1.0])|
| Adelie|Torgersen| 39.2| 19.6| 195.0| 4675| MALE| 0.0| 2.0| 0.0| (2,[0],[1.0])| (2,[],[])| (2,[0],[1.0])|
| Adelie|Torgersen| 41.1| 17.6| 182.0| 3200|FEMALE| 0.0| 2.0| 1.0| (2,[0],[1.0])| (2,[],[])| (2,[1],[1.0])|
| Adelie|Torgersen| 38.6| 21.2| 191.0| 3800| MALE| 0.0| 2.0| 0.0| (2,[0],[1.0])| (2,[],[])| (2,[0],[1.0])|
| Adelie|Torgersen| 34.6| 21.1| 198.0| 4400| MALE| 0.0| 2.0| 0.0| (2,[0],[1.0])| (2,[],[])| (2,[0],[1.0])|
+-------+---------+----------------+---------------+-----------------+-----------+------+----------------------+---------------------+------------------+------------------+-----------------+--------------+
only showing top 10 rows
在上面的代码片段中,我们定义了一个流水线,它将stage_string_indexer和stage_onehot_enc相继进行。第一阶段的输出列被用来作为第二阶段的输入。
缩放参数
如果你观察数据,与其他参数相比,体质量特征太大。而有些算法容易出现未缩放的参数,所以对数据进行缩放是一个好的做法。我们将再次使用Pipeline来缩放参数。为此,我们将需要VectorAssembler和StandardScaler方法。
*VectorAssembler从给定的列列表中创建一个单一的特征向量。
from pyspark.ml.feature import StandardScaler, VectorAssembler
assembler = [VectorAssembler(inputCols=[col], outputCol=col+'_vec') for col in ['culmen_length_mm','culmen_depth_mm','flipper_length_mm','body_mass_g']]
scale = [StandardScaler(inputCol=col+'_vec', outputCol=col+'_scaled') for col in ['culmen_length_mm','culmen_depth_mm','flipper_length_mm','body_mass_g']]
pipe = Pipeline(stages = assembler + scale)
df_scale = pipe.fit(df_trans).transform(df_trans)
df_scale.toPandas().iloc[:,-4:]
分类建模
在本节中,我们将使用pyspark定义一个管道,来处理我们打算进行的分类建模。在这篇文章中,我们将使用一个随机森林分类器。所以,让我们进入编码部分。
train_set, test_set =df_scale.randomSplit([0.75,0.25])
pyspark.ml.classification import RandomForestClassifier
features = VectorAssembler(inputCols=[ 'island onehot_enc', 'sex onehot_enc',
'culmen_length_mm_scaled','culmen_depth_mm_scaled','flipper_length_mm_scaled',
'body_mass_g_scaled'], outputCol='features')
model_rf = RandomForestClassifier(featuresCol='features', labelCol='species string_indexed')
pipe_lr = Pipeline(stages = [features, model_rf])
在上面的代码片段中,我们定义了一个向量集合器,并将选定的列作为我们数据集的输入。接下来,我们定义了我们的随机森林分类器,其标签列种string_indexed来自我们已经缩放的数据集。
接下来,我们将为我们的管道定义参数网格。这对于超参数的调整至关重要。
from pyspark.ml.tuning import CrossValidator, ParamGridBuilder
from pyspark.ml.evaluation import MulticlassClassificationEvaluator
evaluator = MulticlassClassificationEvaluator(predictionCol='prediction', labelCol='species string_indexed')
parameters = ParamGridBuilder()
.addGrid(model_rf.bootstrap, [True,False])
.addGrid(model_rf.maxDepth, [5,10,20,30])
.build()
cv = CrossValidator(estimator=pipe_lr,
estimatorParamMaps=parameters,
evaluator= evaluator)
在上面的代码中,我们首先定义了我们的评估器,它是一个多类评估器,我们又像以前一样指定了标签列。然后我们用不同的模型参数定义了我们的参数网格。最后用默认的交叉验证数3、评估器、参数网格和评估器建立交叉验证器。
cvModel = cv.fit(train_set)
让我们找出最佳模型
cvModel.bestModel.stages[-1]
output: RandomForestClassificationModel: uid=RandomForestClassifier_17983cbf4844, numTrees=20, numClasses=3, numFeatures=8
训练精度
predict = cvModel.transform(test_set)
f1 = evaluator.evaluate(predict, {evaluator.metricName:'f1'})
accuracy = evaluator.evaluate(predict, {evaluator.metricName:'accuracy'})
print(f'F1 score:{f1}')
print(f'Accuracy score:{accuracy}')
output: F1 score:0.978494623655914
Accuracy score:0.978494623655914
为了得到预测值,我们只需要对我们的cvModel调用transform()。
为了使代码更加简洁实用,你可以从一开始就描述整个过程,也就是在一个管道中对分类变量进行编码到分类。你所需要注意的是你的输入和输出列。
总结
当涉及到机器学习的规模时,Pyspark是一个无价的资产。而能够写出整洁且容易调试的代码,总是令人向往的。在这篇文章中,我们使用Pyspark库设计了一个分类管道。以下是文章的一些主要收获。
- 我们学会了用Pyspark加载和读取数据集
- 用StingIndexer和OneHotEncoder对分类变量进行编码
- 我们使用VectorAssembler和StandardScaler对数据进行缩放
- 最后建立一个分类管道和参数网格,用于超参数调整。
所以,这就是关于用Pyspark建立一个机器学习管道的全部内容。
我希望,你喜欢这篇文章。