如何基于spark做深度学习:从ML到keras、Elephas

来源:互联网 发布:狸窝照片制作软件 编辑:程序博客网 时间:2024/05/29 16:10

http://blog.csdn.net/Richard_More/article/details/53215142

Elephas的网址:https://github.com/maxpumperla/elephas

分布式深层神经网络的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_functionprint(sc)
  • 1
  • 2
  • 1
  • 2
<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 SQLContextfrom pyspark.mllib.linalg import Vectorsimport numpy as npimport randomsql_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'])
  • 1
  • 2
  • 3
  • 4
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 1
  • 2
  • 3
  • 4
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

我们加载训练和测试数据,并使用方便的show方法打印几行数据

train_df = load_data_frame("train.csv")test_df = load_data_frame("test.csv", shuffle=False, train=False) # No need to shuffle test dataprint("Train data frame:")train_df.show(10)print("Test data frame (note the dummy category):")test_df.show(10)
  • 1
  • 2
  • 3
  • 4
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 6
  • 7
  • 8
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 rowsTest 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 StringIndexerstring_indexer = StringIndexer(inputCol="category", outputCol="index_category")fitted_indexer = string_indexer.fit(train_df)indexed_df = fitted_indexer.transform(train_df)
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

接下来,将功能规范化,这是一个很好的做法StandardScaler

from pyspark.ml.feature import StandardScalerscaler = StandardScaler(inputCol="features", outputCol="scaled_features", withStd=True, withMean=True)fitted_scaler = scaler.fit(indexed_df)scaled_df = fitted_scaler.transform(indexed_df)
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4
print("The result of indexing and scaling. Each transformation adds new columns to the data frame:")scaled_df.show(10)
  • 1
  • 2
  • 1
  • 2
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 Sequentialfrom keras.layers.core import Dense, Dropout, Activationfrom keras.utils import np_utils, generic_utilsnb_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')
  • 1
  • 2
  • 3
  • 4
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 1
  • 2
  • 3
  • 4
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

分布式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 ElephasEstimatorfrom 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 propertiesestimator = 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 modelestimator.set_optimizer_config(adadelta.get_config())   # Provide serialized Elephas optimizerestimator.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)
  • 1
  • 2
  • 3
  • 4
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 1
  • 2
  • 3
  • 4
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
ElephasEstimator_415398ab22cb1699f794

SparkML管道

现在的简单部分:定义管道真的像列出流水线阶段一样简单。我们可以提供TransformersEstimators真正的任何配置,但是这里我们只需要先前定义的三个组件。请注意,string_indexerscaler和互换,而estimator有些明明已经来到最后的管道。

from pyspark.ml import Pipelinepipeline = Pipeline(stages=[string_indexer, scaler, estimator])
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

安装和评估管道

现在的最后一步是适应管道的培训数据和评估。我们对训练数据进行评估,即转换,因为只有在这种情况下,我们有标签来检查模型的准确性。如果你喜欢,你也可以改造test_df

from pyspark.mllib.evaluation import MulticlassMetricsfitted_pipeline = pipeline.fit(train_df) # Fit model to dataprediction = 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())
  • 1
  • 2
  • 3
  • 4
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 1
  • 2
  • 3
  • 4
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
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 rows0.764132648114

结论

当然,需要一些时间掌握Keras和Spark的原理和语法,这取决于您来自哪里。然而,我们也希望您得出结论,一旦您超越了定义模型和预处理数据的艰巨阶段,构建和使用SparkML流水线的业务是非常优雅和有用的。

如果您喜欢您所看到的,请考虑进一步改善对Ceras或Spark的影响。你对这款笔记本电脑有什么建设性的意见吗?有什么要我澄清吗?无论如何,请随时与我联系。

阅读全文
0 0
原创粉丝点击