blog.csdn.net/Richard_Mor…
Elephas的网址:github.com/maxpumperla…
分布式深层神经网络的Spark ML模型管线
该笔记本描述了如何使用Spark ML为分布式版本的Keras深度学习模型构建机器学习流水线。作为数据集,我们使用来自Kaggle的Otto产品分类挑战。我们选择这个数据的原因是它很小,结构非常好。这样,我们可以更多地关注技术组件,而不是进行复杂的处理。此外,具有较慢硬件或没有完整的Spark群集的用户应该能够在本地运行此示例,并且仍然会了解有关分布式模式的许多内容。
通常,模型训练不需要分配计算,而是建立数据流水线,即摄入,转换等。在训练中,深层神经网络往往在一台机器上的一个或多个GPU上做得相当好。大多数情况下,使用梯度下降方法,您将一个接一个地处理。即使如此,使用像Spark这样的框架也可能有益于将您的模型与您的周边基础架构相集成。除此之外,Spark ML管道提供的便利性非常有价值(在语法上非常接近你所知道的scikit-learn)。
TL; DR:我们将展示如何使用分布式深层神经网络和Spark ML管道来解决分类问题,这个例子基本上是这里发现的分布式版本。
使用这个笔记本
当我们要使用elephas时,您将需要访问正在运行的Spark上下文才能运行此笔记本。如果您还没有,请按照本文提供的说明在本地安装Spark 。确保还导出SPARK_HOME到您的路径并启动您的ipython / jupyter笔记本如下:
IPYTHON_OPTS="notebook" ${SPARK_HOME}/bin/pyspark --driver-memory 4G elephas/examples/Spark_ML_Pipeline.ipynb
1
1
要测试您的环境,请尝试打印Spark上下文(提供sc),即执行以下单元格。
from __future__ import print_function
print(sc)
12
12
<pyspark.context.SparkContext object at 0x1132d61d0>
奥托产品分类数据
培训和测试数据在这里可用。继续下载数据。检查它,您将看到提供的csv文件包含一个id列,93个整数特征列。train.csv有一个额外的标签栏,test.csv缺少。挑战是准确预测测试标签。对于本笔记本的其余部分,我们将假设存储数据data_path,您应根据需要修改下面的数据。
data_path = "./" # <-- Make sure to adapt this to where your csv files are.
1
1
加载数据比较简单,但是我们要照顾几件事情。首先,虽然你可以洗牌RDD,但通常不是很有效率。但是由于数据train.csv按类别排序,所以我们必须洗牌才能使模型运行良好。这是shuffle_csv下面的功能。接下来,我们用明文读入load_data_rdd,以逗号分割,并将要素转换为浮点型向量。另外请注意,最后一列train.csv表示具有Class_前缀的类别。
定义数据帧
Spark有一些核心的数据结构,其中包括data frame,这是一个分布式版本的命名列数据结构,现在很多都是来自R或熊猫。我们需要一个所谓的SQLContext和可选的列到名称映射来创建从头开始的数据框架。
from pyspark.sql import SQLContext
from pyspark.mllib.linalg import Vectors
import numpy as np
import random
sql_context = SQLContext(sc)
def shuffle_csv(csv_file):
lines = open(csv_file).readlines()
random.shuffle(lines)
open(csv_file, 'w').writelines(lines)
def load_data_frame(csv_file, shuffle=True, train=True):
if shuffle:
shuffle_csv(csv_file)
data = sc.textFile(data_path + csv_file) # This is an RDD, which will later be transformed to a data frame
data = data.filter(lambda x:x.split(',')[0] != 'id').map(lambda line: line.split(','))
if train:
data = data.map(
lambda line: (Vectors.dense(np.asarray(line[1:-1]).astype(np.float32)),
str(line[-1])) )
else:
# Test data gets dummy labels. We need the same structure as in Train data
data = data.map( lambda line: (Vectors.dense(np.asarray(line[1:]).astype(np.float32)),"Class_1") )
return sqlContext.createDataFrame(data, ['features', 'category'])
1234五67891011121314151617181920212223242526
1234五67891011121314151617181920212223242526
我们加载训练和测试数据,并使用方便的show方法打印几行数据。
train_df = load_data_frame("train.csv")
test_df = load_data_frame("test.csv", shuffle=False, train=False) # No need to shuffle test data
print("Train data frame:")
train_df.show(10)
print("Test data frame (note the dummy category):")
test_df.show(10)
1234五678
1234五678
Train data frame:
+--------------------+--------+
| features|category|
+--------------------+--------+
|[0.0,0.0,0.0,0.0,...| Class_8|
|[0.0,0.0,0.0,0.0,...| Class_8|
|[0.0,0.0,0.0,0.0,...| Class_2|
|[0.0,1.0,0.0,1.0,...| Class_6|
|[0.0,0.0,0.0,0.0,...| Class_9|
|[0.0,0.0,0.0,0.0,...| Class_2|
|[0.0,0.0,0.0,0.0,...| Class_2|
|[0.0,0.0,0.0,0.0,...| Class_3|
|[0.0,0.0,4.0,0.0,...| Class_8|
|[0.0,0.0,0.0,0.0,...| Class_7|
+--------------------+--------+
only showing top 10 rows
Test data frame (note the dummy category):
+--------------------+--------+
| features|category|
+--------------------+--------+
|[1.0,0.0,0.0,1.0,...| Class_1|
|[0.0,1.0,13.0,1.0...| Class_1|
|[0.0,0.0,1.0,1.0,...| Class_1|
|[0.0,0.0,0.0,0.0,...| Class_1|
|[2.0,0.0,5.0,1.0,...| Class_1|
|[0.0,0.0,0.0,0.0,...| Class_1|
|[0.0,0.0,0.0,0.0,...| Class_1|
|[0.0,0.0,0.0,1.0,...| Class_1|
|[0.0,0.0,0.0,0.0,...| Class_1|
|[0.0,0.0,0.0,0.0,...| Class_1|
+--------------------+--------+
only showing top 10 rows
预处理:定义变压器
到目前为止,我们基本上只读原始数据。幸运的是,Spark ML有很多预处理功能可用,所以我们唯一要做的就是定义数据帧的转换。
要继续,我们将首先将类别字符串转换为双精度值。这是由一个所谓的StringIndexer。请注意,我们已经在这里进行了实际的转型,但这只是为了演示的目的。我们真正需要的是太多的定义,string_indexer以便稍后再进行管理。
from pyspark.ml.feature import StringIndexer
string_indexer = StringIndexer(inputCol="category", outputCol="index_category")
fitted_indexer = string_indexer.fit(train_df)
indexed_df = fitted_indexer.transform(train_df)
1234五
1234五
接下来,将功能规范化,这是一个很好的做法StandardScaler。
from pyspark.ml.feature import StandardScaler
scaler = StandardScaler(inputCol="features", outputCol="scaled_features", withStd=True, withMean=True)
fitted_scaler = scaler.fit(indexed_df)
scaled_df = fitted_scaler.transform(indexed_df)
1234五
1234五
print("The result of indexing and scaling. Each transformation adds new columns to the data frame:")
scaled_df.show(10)
12
12
The result of indexing and scaling. Each transformation adds new columns to the data frame:
+--------------------+--------+--------------+--------------------+
| features|category|index_category| scaled_features|
+--------------------+--------+--------------+--------------------+
|[0.0,0.0,0.0,0.0,...| Class_8| 2.0|[-0.2535060296260...|
|[0.0,0.0,0.0,0.0,...| Class_8| 2.0|[-0.2535060296260...|
|[0.0,0.0,0.0,0.0,...| Class_2| 0.0|[-0.2535060296260...|
|[0.0,1.0,0.0,1.0,...| Class_6| 1.0|[-0.2535060296260...|
|[0.0,0.0,0.0,0.0,...| Class_9| 4.0|[-0.2535060296260...|
|[0.0,0.0,0.0,0.0,...| Class_2| 0.0|[-0.2535060296260...|
|[0.0,0.0,0.0,0.0,...| Class_2| 0.0|[-0.2535060296260...|
|[0.0,0.0,0.0,0.0,...| Class_3| 3.0|[-0.2535060296260...|
|[0.0,0.0,4.0,0.0,...| Class_8| 2.0|[-0.2535060296260...|
|[0.0,0.0,0.0,0.0,...| Class_7| 5.0|[-0.2535060296260...|
+--------------------+--------+--------------+--------------------+
only showing top 10 rows
Keras深度学习模式
现在我们有一个具有处理特征和标签的数据框架,我们定义一个深层神经网络,我们可以使用它来解决分类问题。你有机会来这里,因为你知道一两件关于深入学习的东西。如果是这样,下面的模型看起来很简单。我们通过选择一组三个连续的密集层来建立一个keras模型,其中包含退出和ReLU激活。对于这个问题肯定有更好的架构,但是我们只是想在这里展示一般的流程。
from keras.models import Sequential
from keras.layers.core import Dense, Dropout, Activation
from keras.utils import np_utils, generic_utils
nb_classes = train_df.select("category").distinct().count()
input_dim = len(train_df.select("features").first()[0])
model = Sequential()
model.add(Dense(512, input_shape=(input_dim,)))
model.add(Activation('relu'))
model.add(Dropout(0.5))
model.add(Dense(512))
model.add(Activation('relu'))
model.add(Dropout(0.5))
model.add(Dense(512))
model.add(Activation('relu'))
model.add(Dropout(0.5))
model.add(Dense(nb_classes))
model.add(Activation('softmax'))
model.compile(loss='categorical_crossentropy', optimizer='adam')
1234五6789101112131415161718192021
1234五6789101112131415161718192021
分布式Elephas模型
为了将上述Keras提升model到Spark,我们定义了一个Estimator。一个Estimator是,仍然有被训练模型的星火的化身。它本质上只有一个(必需)的方法,即fit。一旦我们调用fit了数据框架,我们就回到了一个Model,这是一个训练有素的模型,transform用来预测标签的方法。
我们通过初始化ElephasEstimator和设置一些属性来实现。到目前为止,我们的输入数据框架将有很多列,我们必须通过列名告诉模型在哪里查找功能和标签。然后我们提供Keras模型和Elephas优化器的序列化版本。我们不能直接插入keras模型Estimator,因为Spark将不得不将其序列化为与工作人员的沟通,所以最好自己提供序列化。事实上,虽然pyspark知道如何序列化model,但它是非常低效的,如果模型变得太大,可能会破裂。Spark ML对参数特别挑剔(正确地),或多或少地禁止您提供后者的非原子类型和数组。大多数剩余的参数是可选的,而且是自我解释的。加,许多人,你知道如果你以前曾经运行过克拉斯模型。我们只是将他们包括在内,以显示全套培训配置。
from elephas.ml_model import ElephasEstimator
from elephas import optimizers as elephas_optimizers
# Define elephas optimizer (which tells the model how to aggregate updates on the Spark master)
adadelta = elephas_optimizers.Adadelta()
# Initialize SparkML Estimator and set all relevant properties
estimator = ElephasEstimator()
estimator.setFeaturesCol("scaled_features") # These two come directly from pyspark,
estimator.setLabelCol("index_category") # hence the camel case. Sorry :)
estimator.set_keras_model_config(model.to_yaml()) # Provide serialized Keras model
estimator.set_optimizer_config(adadelta.get_config()) # Provide serialized Elephas optimizer
estimator.set_categorical_labels(True)
estimator.set_nb_classes(nb_classes)
estimator.set_num_workers(1) # We just use one worker here. Feel free to adapt it.
estimator.set_nb_epoch(20)
estimator.set_batch_size(128)
estimator.set_verbosity(1)
estimator.set_validation_split(0.15)
1234五678910111213141516171819
1234五678910111213141516171819
ElephasEstimator_415398ab22cb1699f794
SparkML管道
现在的简单部分:定义管道真的像列出流水线阶段一样简单。我们可以提供Transformers和Estimators真正的任何配置,但是这里我们只需要先前定义的三个组件。请注意,string_indexer并scaler和互换,而estimator有些明明已经来到最后的管道。
from pyspark.ml import Pipeline
pipeline = Pipeline(stages=[string_indexer, scaler, estimator])
123
123
安装和评估管道
现在的最后一步是适应管道的培训数据和评估。我们对训练数据进行评估,即转换,因为只有在这种情况下,我们有标签来检查模型的准确性。如果你喜欢,你也可以改造test_df。
from pyspark.mllib.evaluation import MulticlassMetrics
fitted_pipeline = pipeline.fit(train_df) # Fit model to data
prediction = fitted_pipeline.transform(train_df) # Evaluate on train data.
# prediction = fitted_pipeline.transform(test_df) # <-- The same code evaluates test data.
pnl = prediction.select("index_category", "prediction")
pnl.show(100)
prediction_and_label = pnl.map(lambda row: (row.index_category, row.prediction))
metrics = MulticlassMetrics(prediction_and_label)
print(metrics.precision())
1234五6789101112
1234五6789101112
61878/61878 [==============================] - 0s
+--------------+----------+
|index_category|prediction|
+--------------+----------+
| 2.0| 2.0|
| 2.0| 2.0|
| 0.0| 0.0|
| 1.0| 1.0|
| 4.0| 4.0|
| 0.0| 0.0|
| 0.0| 0.0|
| 3.0| 3.0|
| 2.0| 2.0|
| 5.0| 0.0|
| 0.0| 0.0|
| 4.0| 4.0|
| 0.0| 0.0|
| 4.0| 1.0|
| 2.0| 2.0|
| 1.0| 1.0|
| 0.0| 0.0|
| 6.0| 0.0|
| 2.0| 2.0|
| 1.0| 1.0|
| 2.0| 2.0|
| 8.0| 8.0|
| 1.0| 1.0|
| 5.0| 0.0|
| 0.0| 0.0|
| 0.0| 3.0|
| 0.0| 0.0|
| 1.0| 1.0|
| 4.0| 4.0|
| 2.0| 2.0|
| 0.0| 3.0|
| 3.0| 3.0|
| 0.0| 0.0|
| 3.0| 0.0|
| 1.0| 5.0|
| 3.0| 3.0|
| 2.0| 2.0|
| 1.0| 1.0|
| 0.0| 0.0|
| 2.0| 2.0|
| 2.0| 2.0|
| 1.0| 1.0|
| 6.0| 6.0|
| 1.0| 1.0|
| 0.0| 3.0|
| 7.0| 0.0|
| 0.0| 0.0|
| 0.0| 0.0|
| 1.0| 1.0|
| 1.0| 1.0|
| 6.0| 6.0|
| 0.0| 0.0|
| 0.0| 3.0|
| 2.0| 2.0|
| 0.0| 0.0|
| 2.0| 2.0|
| 0.0| 0.0|
| 4.0| 4.0|
| 0.0| 0.0|
| 6.0| 6.0|
| 2.0| 5.0|
| 0.0| 3.0|
| 3.0| 0.0|
| 0.0| 0.0|
| 3.0| 3.0|
| 4.0| 4.0|
| 0.0| 3.0|
| 0.0| 0.0|
| 0.0| 0.0|
| 4.0| 4.0|
| 3.0| 0.0|
| 2.0| 2.0|
| 1.0| 1.0|
| 7.0| 7.0|
| 0.0| 0.0|
| 0.0| 0.0|
| 0.0| 3.0|
| 1.0| 1.0|
| 1.0| 1.0|
| 5.0| 4.0|
| 1.0| 1.0|
| 1.0| 1.0|
| 4.0| 4.0|
| 3.0| 3.0|
| 0.0| 0.0|
| 2.0| 2.0|
| 4.0| 4.0|
| 7.0| 7.0|
| 2.0| 2.0|
| 0.0| 0.0|
| 1.0| 1.0|
| 0.0| 0.0|
| 4.0| 4.0|
| 1.0| 1.0|
| 0.0| 0.0|
| 0.0| 0.0|
| 0.0| 0.0|
| 0.0| 3.0|
| 0.0| 3.0|
| 0.0| 0.0|
+--------------+----------+
only showing top 100 rows
0.764132648114
结论
当然,需要一些时间掌握Keras和Spark的原理和语法,这取决于您来自哪里。然而,我们也希望您得出结论,一旦您超越了定义模型和预处理数据的艰巨阶段,构建和使用SparkML流水线的业务是非常优雅和有用的。
如果您喜欢您所看到的,请考虑进一步改善对Ceras或Spark的影响。你对这款笔记本电脑有什么建设性的意见吗?有什么要我澄清吗?无论如何,请随时与我联系。