Deeplearning4J on Spark | Hadoop Advent Calendar 2016 #20

hadoop-logo

こんにちは、小澤です。 この記事はHadoop Advent Calendar 20日目のものとなります。

前回はZeppelinについて書かせていただきました。
今回はJavaで実装されたDeep LearningのライブラリであるDeeplearning4JをSparkから利用してみたいと思います。

Deep Learningとは

Deep Learningについてはその名前を聞いたことがあるという方も多いかもしれません。 現在の人工知能やAIのブームを代表する技術であるため、「どんなものか詳しくは知らないけどすごいことができる」と認識してる方も多かと思います。
そのため、まずはDeep Learningとは何者なのかについて簡単に解説したいと思います。
なお、Deep Learningは活用事例だけでなく研究領域においても現在活発に新しいものが実現されており非常に流れの速い領域です。 そのため、ここで書かせていただく内容がすぐに古い情報になってしまう可能性があることをご了承ください。

ニューラルネットワーク

Deep Learningはニューラルネットワークという手法を発展させたものになります。
このニューラルネットワークは、人間の脳の仕組みを...という解説をよく見かけますが、機械学習におけるニューラルネットワーク(区別するために人工ニューラルネットワークと呼ぶこともあります)を理解する上で、脳のニューラルネットワークの仕組みを知っていなければ全く理解できないというわけではないのでそちらの話に関しては割愛します。
そのため、これ以降ニューラルネットワーク(ニューラルネット、NN)は全てこの人工ニューラルネットワークを指します。

ニューラルネットではニューロンと呼ばれるものを複数組み合わせて実現されています。 これ自体は特に難しいことを行っているわけではなく、各入力とウエイトを足し合わせたものを活性化関数というものに入れてやることで出力を求めるものになります。

下の図は1つのニューロンを表すのものになります。 もっとも簡単な構造の単純パーセプトロンと呼ばれるものは、この1つのニューロンのみで成り立っており、活性化関数も入力が一定の値以上であれば1、そうでなければ0を返すというものでした。 perceptron

これを拡張すると、複数個をニューロンを複数段積み重ねることができます。
下の図は3層の構造となっています。 この真ん中の層のように入出力以外の間の層を挟むことで、内部的に様々な表現が可能になっています。

nn1

あとは、データを利用して入出力の組み合わせを学習し適切なウエイトの値を求めれば利用可能となります。
学習のさせ方はバックプロパゲーション(誤差逆伝播)という方法によって行なわれます。 詳細は割愛しますが、現在のwの値での出力と正解ラベルを比較し、適切な値に近づくよう中間層と出力の間のwの値を更新し、 次にその値を利用して中間層と入力層との間のwを更新するというやり方となります。
これを何度も繰り返すうちに学習データの入出力のペアに近い値が予測できるようになっていきます。

nn2

Deep Learningへの発展

このような形式を取れるということは、理論上は入出力の間の中間層をいくらでも増やすことができます。
しかも中間の層を増やすということは、その分だけ内部で様々な表現が可能になりそうです。 実際、画像認識では入力に近い層から

  • 線などのエッジ検出
  • エッジの組み合わせでの目や鼻の検出
  • さらにそれらの組み合わせで顔を検出
  • といったように層が深くなるほどより複雑な組み合わせを表現するような学習を行っています。

    しかし、層を深くしていくと学習がうまくいかないという問題がありました。 学習の性質上、出力に近い層から順に更新していくのですが、この時に層が深くなると入力に近い浅い層までうまく情報を伝えることができなかったのです。
    それによって、ニューラルネットの研究は下火となってしまいました。 しかし、その間も研究は続けられ、近年のハードウェアの性能向上もとのなってこの問題をうまく回避する方法が従来の機械学習手法よりもいい結果を出せるようになったことで注目されるようになったのがDeep Learningというわけです。

    nn3

    Deeplearning4Jとは

    Deep Learningを行うライブラリの1つです。 Deep Learningのためのライブラリは数多く存在し、今もなお増え続けています。 それぞれに特徴があるのですが、Deeplearning4Jはその中でも珍しいJavaで実装されたライブラリとなります。
    そのため、大量のデータを扱い際に、HadoopやSparkなどでの実行が行いやすいものになっています。

    どのようなものかの説明はこれくらいにして、exampleを例に実際にDeeplearning4Jを使う方法を見ていましょう。
    Githubにある公式のdl4j-examplesを見ます。
    dl4j-examples/src/main/java/org/deeplearning4j/examples/feedforward/mnist/MLPMnistSingleLayerExample.javaを例にしたいと思います。

    このexampleを見ていただくとわかる通り、mainメソッドで学習処理を実行するjavaのプログラムとなっています。
    やっていることはMNISTという手書きの数字(0から9のいずれか)の画像からどの数値が描かれているかを予測するものになっています。 キーポイントは

    MultiLayerConfiguration conf = new NeuralNetConfiguration.Builder()
      .seed(rngSeed)
      .optimizationAlgo(OptimizationAlgorithm.STOCHASTIC_GRADIENT_DESCENT)
      .iterations(1)
      .learningRate(0.006)
      .updater(Updater.NESTEROVS).momentum(0.9)
      .regularization(true).l2(1e-4)
      .list()
      .layer(0, new DenseLayer.Builder()
        .nOut(1000)
        .activation("relu")
        .weightInit(WeightInit.XAVIER)
        .build())
      .layer(1, new OutputLayer.Builder(LossFunction.NEGATIVELOGLIKELIHOOD)
        .nIn(1000)
        .nOut(outputNum)
        .activation("softmax")
        .weightInit(WeightInit.XAVIER)
        .build())
      .pretrain(false)
      .backprop(true)
      .build();
    

    これは何をしているかというとネットワークの構造を定義しています。 このようにメソッドチェーンを使って全体の定義をしているわけです。 Deep Learningはこのように設定すべき項目が非常に多くなりますし、それぞれが何を意味しているかにも知識が必要なります。
    ある程度知識があれば何を設定しているかわかりやすいメソッド名になっているのですが、このコードの全てを解説するには、それら一つ一つの要素も含めての解説が必要になるため、いくつかの部分のみを簡単に解説します。

    optimizationAlgo

    これは学習に際して、現在の出力結果と正解ラベルとの間にどの程度間違いがあり、それをどのように修正していくかを決めるアルゴリズムとして何を使うかを設定しています。
    ここではSGD(確率的勾配降下法)というものを利用しています。

    layer

    ここでニューラルネットの各層の設定を行っています。
    第一引数で『0』を渡しているのが入力層です。
    中間層の数は1000、活性化関数にはReLUというものを利用しています。
    『1』となっているのが、出力層です。
    こちらは中間層が1000なので入力は1000、出力は分類対象となるクラス数を渡しています。 活性関数はsoftmaxとなっており、これは多値分類を行う際に利用するものになります。

    このサンプルはmavenでfat jarを作るのと、実行するための設定が既にされているため、実行はmvn:execで行うことができます。

    mvn exec:java -Dexec.mainClass=org.deeplearning4j.examples.feedforward.mnist.MLPMnistSingleLayerExample
    

    実行すると

    o.d.e.f.m.MLPMnistSingleLayerExample - Build model....
    o.n.n.NativeOps - Number of threads used for NativeOps: 4
    Unable to guess runtime. Please set OMP_NUM_THREADS or equivalent manually.
    o.n.n.Nd4jBlas - Number of threads used for BLAS: 4
    o.d.n.c.MultiLayerConfiguration - Warning: new network default sets pretrain to false.
    o.d.n.c.MultiLayerConfiguration - Warning: new network default sets backprop to true.
    o.d.e.f.m.MLPMnistSingleLayerExample - Train model....
    o.d.o.l.ScoreIterationListener - Score at iteration 0 is 2.3464891972546997
    o.d.o.l.ScoreIterationListener - Score at iteration 1 is 2.3305461459398917
    o.d.o.l.ScoreIterationListener - Score at iteration 2 is 2.3167860535029745
    o.d.o.l.ScoreIterationListener - Score at iteration 3 is 2.2717926509834436
    o.d.o.l.ScoreIterationListener - Score at iteration 4 is 2.2650938026073915
    o.d.o.l.ScoreIterationListener - Score at iteration 5 is 2.2236700069569535
    ...
    ...
    o.d.o.l.ScoreIterationListener - Score at iteration 7033 is 0.08376503645330606
    o.d.o.l.ScoreIterationListener - Score at iteration 7034 is 0.05295153433788113
    o.d.e.f.m.MLPMnistSingleLayerExample - Evaluate model....
    o.d.e.f.m.MLPMnistSingleLayerExample - 
    Examples labeled as 0 classified by model as 0: 965 times
    Examples labeled as 0 classified by model as 2: 1 times
    Examples labeled as 0 classified by model as 3: 1 times
    Examples labeled as 0 classified by model as 5: 4 times
    ...
    ...
    ==========================Scores========================================
     Accuracy:        0.9729
     Precision:       0.9729
     Recall:          0.9726
     F1 Score:        0.9727
    ========================================================================
    o.d.e.f.m.MLPMnistSingleLayerExample - ****************Example finished********************
    

    のように出力されます。 最後の『Scores』の『Accuracy』を見ると、テストデータに対する正解率がおよそ97%であることがわかります。

    Deeplearning4J on Sparkを動かしてみる

    では、次にこれをSparkで動かしてみたいと思います。 Spark上での実行もサンプルコードがdl4j-spark-examples/dl4j-spark/src/main/java/org/deeplearning4j/mlp/MnistMLPExample.javaにあるのでそれを動かしてみます。

    こちらもSparkで実行するための設定がいくつか含まれていますが、基本的な部分は変わりません。
    最初のSparkの設定は従来と同じです。 その次にローカルで動かすのと違う部分として、Spark上で扱えるようにデータをRDDに変換しています。

    SparkConf sparkConf = new SparkConf();
    if (useSparkLocal) {
      sparkConf.setMaster("local[*]");
    }
    sparkConf.setAppName("DL4J Spark MLP Example");
    JavaSparkContext sc = new JavaSparkContext(sparkConf);
    
    DataSetIterator iterTrain = new MnistDataSetIterator(batchSizePerWorker, true, 12345);
    DataSetIterator iterTest = new MnistDataSetIterator(batchSizePerWorker, true, 12345);
    List<DataSet> trainDataList = new ArrayList<>();
    List<DataSet> testDataList = new ArrayList<>();
    while (iterTrain.hasNext()) {
      trainDataList.add(iterTrain.next());
    }
    
    while (iterTest.hasNext()) {
      testDataList.add(iterTest.next());
    }
    
    JavaRDD<DataSet> trainData = sc.parallelize(trainDataList);
    JavaRDD<DataSet> testData = sc.parallelize(testDataList);
    

    ネットワークの設定はローカルと同じです。 このサンプルでは中間層を2層にしていますが、それ以外に変わった点はないかと思います。

    次にSpark上での動作について設定を行っています。 1ノードあたりのデータ件数(batchサイズ)や分散させた各ノードでのwの値の全体での平均をとる頻度などを設定します。

    TrainingMaster tm = new ParameterAveragingTrainingMaster.Builder(batchSizePerWorker)
      .averagingFrequency(5)
      .workerPrefetchNumBatches(2)
      .batchSizePerWorker(batchSizePerWorker)
      .build();
    

    最後に各種設定を渡して学習の処理を実行します。

    SparkDl4jMultiLayer sparkNet = new SparkDl4jMultiLayer(sc, conf, tm);
    for (int i = 0; i < numEpochs; i++) {
      sparkNet.fit(trainData);
      log.info("Completed Epoch {}", i);
    }
    

    実行方法はローカルでの設定と同じです。デフォルトではlocal[*]で動くようになっているので、クラスタ上で動かしたい場合は-useSparkLocalオプションを設定してください。 今回はローカルで動かすので、オプションは指定していません。
    実行を行うと、学習を行っている過程がログとして出力され、最後にローカルで動かしたとき同様テストデータに対する予測の評価結果が出力されます。

    ==========================Scores========================================
     Accuracy:        0.998
     Precision:       0.998
     Recall:          0.9981
     F1 Score:        0.998
    ========================================================================
    

    中間層の数を増やしているため、結果もよくなっていることがわかります。

    終わりに

    今回はSparkで動くDeep LearningのライブラリであるDeeplearning4Jについて書かせていだきました。
    Deep Learning界隈のライブラリは数多く出ており、Tensorflowのようなそれ自体が分散処理なものもありまが、多くの場合Pythonで記述することになります。Rやlua, C++で記述できるものもありますが、javaの実装はあまり多くなく、すでにSpark環境が整っているなどの場合などで有用かと思います。

    明日はSQL on HadoopをDataFrameのように扱うIbisというものについて書かせていただく予定です。
    ぜひ、お楽しみに!