Technology Topics by Brains

ブレインズテクノロジーの研究開発機関「未来工場」で働くエンジニアが、先端オープン技術、機械学習×データ分析(異常検知、予兆検知)に関する取組みをご紹介します。

SparkのDeep Learning Pipelinesを使ってみた

Impulse開発チームの塚田です。

今回は、DatabricksのDeep Learning Pipelinesを、spark-shell上で触ってみました。 内容はほぼ下記を実行したものなので、英語余裕で読めるぜ!って方はこちらを見てください。

Deep Learning Pipelines — Databricks Documentation

Deep Learning Pipelines

Deep Learning PipelinesはSparkでDeep Learningをスケーラブルに実行するためのhigh-level APIを提供するパッケージです。 TensorFlowやTensorFlowをバックエンドとしたKerasをサポートしています。 現在は画像データに特化した下記を実行することができます。

  • Sparkで画像を操作する
    • 画像をDataFrameとして読み込むAPI
  • 転移学習
    • 学習済みモデルを特徴抽出器として使用
  • Deep Learningのモデルを使ったtrasform
    • 下記を使用してTransformerを構成
      • 学習済みモデル(Inception v3)
      • TensorFlow Graph
      • Keras
  • モデルをSQL functionとしてデプロイする
  • 分散ハイパーパラメータチューニング (comming soon...)

今回は下記を使って、猫と犬の判別をしてみたいと思います。

  • Sparkで画像を操作する
  • 転移学習

実行環境

ハードウェアの概要
機種名 MacBook Pro
機種ID MacBookPro13,2
プロセッサ名 Intel Core i5
プロセッサ速度 2.9 GHz
プロセッサの個数 1
コアの総数 2
二次キャッシュ(コア単位) 256 KB
三次キャッシュ 4 MB
メモリ 16 GB

準備

使用するのは猫と犬の画像です。 下記から取得しました。(Kaggleへの登録が必要です)

https://www.kaggle.com/c/dogs-vs-cats-redux-kernels-edition

Dataからtrain.zipをダウンロードしてください。 解凍後、適当な枚数を選りすぐり下記のようにします。 (猫、犬ともに30枚ずつで実行しました)

- /images/
  - cat/
    - cat.0.jpg
    - ...
    - cat.29.jpg
  - dog/
    - dog.0.jpg
    - ...
    - dog.29.jpg

Pythonパッケージのインストール

下記のインストールが必要です

pip install tensorflow keras h5py

spark-shell実行

pythonAPIしかないので、pysparkを実行します。 packagesにspark-deep-learningを指定します。

pyspark --packages databricks:spark-deep-learning:0.1.0-spark2.1-s_2.11 --driver-memory 2G --executor-memory 2G

画像ファイルから入力データを作成

学習に使う画像データはSparkのDataFrameとして取り込みます。 ディレクトリを指定すると直下にある画像ファイルを読み込みます。

# データの読み込み
from sparkdl import readImages
from pyspark.sql.functions import lit

cat_df = readImages("/images/cat").withColumn("label", lit(1))
dog_df = readImages("/images/dog").withColumn("label", lit(0))
cat_train, cat_test = cat_df.randomSplit([0.6, 0.4], 0)
dog_train, dog_test = dog_df.randomSplit([0.6, 0.4], 0)
train_df = cat_train.unionAll(dog_train)
test_df = cat_test.unionAll(dog_test)

中身を見てみると、画像のサイズ等の情報と中身のバイナリが入ってそうですね。

>>> train_df.show(100, False)
+---------------------------+---------------------------+-----+
|filePath                   |image                      |label|
+---------------------------+---------------------------+-----+
|file:/images/cat/cat.19.jpg|[RGB,223,320,3,[B@5db7511c]|1    |
|file:/images/cat/cat.2.jpg |[RGB,396,312,3,[B@3562ebce]|1    |
|file:/images/cat/cat.23.jpg|[RGB,256,334,3,[B@5d07ef8a]|1    |
|file:/images/cat/cat.25.jpg|[RGB,500,345,3,[B@55547685]|1    |
|file:/images/cat/cat.26.jpg|[RGB,374,500,3,[B@320cf282]|1    |
|file:/images/cat/cat.27.jpg|[RGB,479,370,3,[B@10925396]|1    |
|file:/images/cat/cat.28.jpg|[RGB,270,286,3,[B@720b85b1]|1    |
|file:/images/cat/cat.29.jpg|[RGB,375,499,3,[B@41d352b] |1    |
|file:/images/cat/cat.3.jpg |[RGB,414,500,3,[B@51d05fc8]|1    |
|file:/images/cat/cat.6.jpg |[RGB,303,400,3,[B@53dca887]|1    |
|file:/images/cat/cat.8.jpg |[RGB,345,461,3,[B@7dd49940]|1    |
|file:/images/cat/cat.9.jpg |[RGB,425,320,3,[B@100d5fbc]|1    |
|file:/images/cat/cat.0.jpg |[RGB,374,500,3,[B@43707c6a]|1    |
|file:/images/cat/cat.10.jpg|[RGB,499,489,3,[B@75de6c13]|1    |
|file:/images/cat/cat.11.jpg|[RGB,410,431,3,[B@2c99f571]|1    |
|file:/images/cat/cat.13.jpg|[RGB,315,499,3,[B@5693afe1]|1    |
|file:/images/cat/cat.15.jpg|[RGB,353,405,3,[B@16401a75]|1    |
|file:/images/cat/cat.16.jpg|[RGB,258,448,3,[B@10f8525a]|1    |
|file:/images/dog/dog.19.jpg|[RGB,225,299,3,[B@73a91f49]|0    |
|file:/images/dog/dog.2.jpg |[RGB,199,187,3,[B@388e5a7a]|0    |
|file:/images/dog/dog.23.jpg|[RGB,403,499,3,[B@2e0b6cac]|0    |
|file:/images/dog/dog.25.jpg|[RGB,375,499,3,[B@7cb391b5]|0    |
|file:/images/dog/dog.26.jpg|[RGB,224,300,3,[B@47db9a3d]|0    |
|file:/images/dog/dog.27.jpg|[RGB,375,499,3,[B@d4c4536] |0    |
|file:/images/dog/dog.28.jpg|[RGB,432,287,3,[B@78d7363f]|0    |
|file:/images/dog/dog.29.jpg|[RGB,376,500,3,[B@18093ea9]|0    |
|file:/images/dog/dog.3.jpg |[RGB,375,499,3,[B@39855e7] |0    |
|file:/images/dog/dog.6.jpg |[RGB,488,499,3,[B@6c12c557]|0    |
|file:/images/dog/dog.8.jpg |[RGB,500,469,3,[B@2db57024]|0    |
|file:/images/dog/dog.9.jpg |[RGB,500,368,3,[B@7c98ff4d]|0    |
|file:/images/dog/dog.0.jpg |[RGB,375,499,3,[B@3d6eba54]|0    |
|file:/images/dog/dog.10.jpg|[RGB,292,269,3,[B@472cf8d3]|0    |
|file:/images/dog/dog.11.jpg|[RGB,101,135,3,[B@419e9442]|0    |
|file:/images/dog/dog.13.jpg|[RGB,428,362,3,[B@7ac779ab]|0    |
|file:/images/dog/dog.15.jpg|[RGB,374,500,3,[B@3233c0bd]|0    |
|file:/images/dog/dog.16.jpg|[RGB,380,500,3,[B@12f95c48]|0    |
+---------------------------+---------------------------+-----+

転移学習

Deep Learning Pipelinesにより学習済みモデルを使用して学習を行います。 現状は下記のモデルがサポートされています。

  • InceptionV3

InceptionV3の出力を特徴量とし、決定木により猫と犬を分類します。 DeepImageFeaturizerインスタンスを作ってPipelineを作るだけですね。

# 学習
from pyspark.ml.classification import DecisionTreeClassifier
from pyspark.ml import Pipeline
from sparkdl import DeepImageFeaturizer 

featurizer = DeepImageFeaturizer(inputCol="image", outputCol="features", modelName="InceptionV3")
dt = DecisionTreeClassifier(labelCol="label", featuresCol="features")
p = Pipeline(stages=[featurizer, dt])

p_model = p.fit(train_df)

出来上がったモデルを使ってtransformします。

# テスト
from pyspark.ml.evaluation import MulticlassClassificationEvaluator

tested_df = p_model.transform(test_df)
evaluator = MulticlassClassificationEvaluator(metricName="accuracy")
print("Test set accuracy = " + str(evaluator.evaluate(tested_df.select("prediction", "label"))))
Test set accuracy = 0.75

何も考えずに作った割には合ってる気もしますが、もうちょっと中身について見ていきます。

DataFlameの中身を見てみると、DeepImageFeaturizerの出力がfeaturesカラムに入っています。

>>> tested_df.show()
+--------------------+--------------------+-----+--------------------+-------------+-----------+----------+
|            filePath|               image|label|            features|rawPrediction|probability|prediction|
+--------------------+--------------------+-----+--------------------+-------------+-----------+----------+
|file:/images/cat/...|[RGB,374,500,3,[B...|    1|[0.0,0.8633406162...|   [0.0,17.0]|  [0.0,1.0]|       1.0|
|file:/images/cat/...|[RGB,374,500,3,[B...|    1|[0.0,0.0,0.0,0.0,...|   [0.0,17.0]|  [0.0,1.0]|       1.0|
|file:/images/cat/...|[RGB,499,431,3,[B...|    1|[0.0,0.0,0.229576...|   [0.0,17.0]|  [0.0,1.0]|       1.0|
|file:/images/cat/...|[RGB,345,500,3,[B...|    1|[0.0,0.0516236834...|   [0.0,17.0]|  [0.0,1.0]|       1.0|
|file:/images/cat/...|[RGB,374,500,3,[B...|    1|[0.0,0.0,0.0,0.0,...|   [0.0,17.0]|  [0.0,1.0]|       1.0|
|file:/images/cat/...|[RGB,375,499,3,[B...|    1|[0.12656563520431...|   [0.0,17.0]|  [0.0,1.0]|       1.0|
|file:/images/cat/...|[RGB,144,175,3,[B...|    1|[0.0,0.0,0.195755...|   [18.0,0.0]|  [1.0,0.0]|       0.0|
|file:/images/cat/...|[RGB,499,495,3,[B...|    1|[0.0,0.0,0.0,0.0,...|   [18.0,0.0]|  [1.0,0.0]|       0.0|
|file:/images/cat/...|[RGB,280,300,3,[B...|    1|[0.79150134325027...|   [0.0,17.0]|  [0.0,1.0]|       1.0|
|file:/images/cat/...|[RGB,224,300,3,[B...|    1|[1.65047085285186...|   [0.0,17.0]|  [0.0,1.0]|       1.0|
|file:/images/cat/...|[RGB,267,320,3,[B...|    1|[0.86000078916549...|   [18.0,0.0]|  [1.0,0.0]|       0.0|
|file:/images/cat/...|[RGB,375,499,3,[B...|    1|[0.0,0.0,0.0,0.14...|   [0.0,17.0]|  [0.0,1.0]|       1.0|
|file:/images/dog/...|[RGB,348,215,3,[B...|    0|[0.0,0.1367801278...|   [18.0,0.0]|  [1.0,0.0]|       0.0|
|file:/images/dog/...|[RGB,332,500,3,[B...|    0|[0.21633519232273...|   [18.0,0.0]|  [1.0,0.0]|       0.0|
|file:/images/dog/...|[RGB,499,415,3,[B...|    0|[0.0,0.0,0.884287...|   [18.0,0.0]|  [1.0,0.0]|       0.0|
|file:/images/dog/...|[RGB,371,499,3,[B...|    0|[0.93737035989761...|   [18.0,0.0]|  [1.0,0.0]|       0.0|
|file:/images/dog/...|[RGB,500,274,3,[B...|    0|[0.56643348932266...|    [0.0,1.0]|  [0.0,1.0]|       1.0|
|file:/images/dog/...|[RGB,287,300,3,[B...|    0|[0.10650488734245...|   [18.0,0.0]|  [1.0,0.0]|       0.0|
|file:/images/dog/...|[RGB,376,499,3,[B...|    0|[0.11714683473110...|   [18.0,0.0]|  [1.0,0.0]|       0.0|
|file:/images/dog/...|[RGB,264,299,3,[B...|    0|[0.0,0.0,0.0,0.0,...|    [0.0,1.0]|  [0.0,1.0]|       1.0|
|file:/images/dog/...|[RGB,499,327,3,[B...|    0|[0.21827600896358...|   [0.0,17.0]|  [0.0,1.0]|       1.0|
|file:/images/dog/...|[RGB,161,98,3,[B@...|    0|[0.04027611017227...|   [18.0,0.0]|  [1.0,0.0]|       0.0|
|file:/images/dog/...|[RGB,386,500,3,[B...|    0|[0.0,0.0,1.286264...|   [18.0,0.0]|  [1.0,0.0]|       0.0|
|file:/images/dog/...|[RGB,335,272,3,[B...|    0|[0.0,1.5273251533...|   [18.0,0.0]|  [1.0,0.0]|       0.0|
+--------------------+--------------------+-----+--------------------+-------------+-----------+----------+

featuresの中身は配列になっています。 長さは131072です。

>>> len(tested_df.head()['features'])
131072

決定木は下記の用になっています。

>>> print(p_model.stages[1].toDebugString)
DecisionTreeClassificationModel (uid=DecisionTreeClassifier_46609281014c140920a8) of depth 2 with 5 nodes
  If (feature 38387 <= 0.6593263149261475)
   If (feature 26 <= 0.5600484609603882)
    Predict: 0.0
   Else (feature 26 > 0.5600484609603882)
    Predict: 1.0
  Else (feature 38387 > 0.6593263149261475)
   Predict: 1.0

featuresの数の割に木が短いです。 単純に学習データを多くすれば精度を上げられそうです。 (今回はとりあえず動かすだけなので大分サンプル数を絞っています)

また、indexからfeaturesのラベルを逆引きする手段がDeepImageFeaturizerになさそうなので、26と38387が何の値なのかさっぱりわかりません。 indexとラベルのマッピングが取れる手段が欲しいですね。

おわりに

Deep Learning Pipelinesを使うとDeep Learningのことを大して知らなくても実行できます。

まだ画像しか扱えませんし使える学習済みモデルも少ないですが、その辺の選択肢が増えて来ると夢が広がりますよね。

参考