DeepLearning4Jの紹介

  • 6
    いいね
  • 0
    コメント

DeepLearning Advent Calendar 2016 最終日の投稿をさせていただきます。

Qiitaの初めての投稿なので少し自己紹介をした方が良いですね。

初めまして、ゴンザレズと申します。日本のSIerでほぼ8年間勤めています。最近機械学習を使ったサービスを開発し、一部にDeepLearning4Jを活用しています。

この投稿はDeepLearning4Jのプロジェクトの作り方とJava EEのアプリサーバーに展開する方法を紹介します。質問やコメントがありましたらDeepLearning4Jの日本語チャットルームに是非投稿してください。

DeepLearning4Jの特徴

DeepLearning4J(下記DL4J)はJavaの深層学習ライブラリです。Skymindというサンフランシスコのスタートアップが開発していますが、ライブラリ自体はTensorflowMXNetと同じApache 2のライセンスを利用している、オープンソースのプロジェクトです。

DL4Jは計算速度の為、独自のC++行列計算バックエンド(ND4J)を利用しています。GPU対応とCPU対応のそれぞれのバーションがあるので、実行時にクラスパスに含まれているものが利用されます。私はGPUがないMacBook Airで開発し、サーバーにアップする前にGPU版をビルドし直して、アップする事はよくします。

自分がDL4Jを選んだ理由はApache Sparkの統合性があったからです。他のフレームワークはなんとなく、Sparkで利用する事が可能ですが、DL4Jは直接Sparkに対応しています。

最後にSIerとしての一番のメリットは、サポートする会社があるからだと思います。特に金融機関系のシステムだと、何かが起こった時に、直接連絡できる会社があれば一安心です。

DL4Jのプロジェクト作成

もしScalaを利用されるなら、私のg8テンプレートからプロジェクトを作成する事もできます。最近のScalaのビルドツール、SBT(スブタ)は、g8テンプレートに対応していますので、下記のコマンドからプロジェクトを作成できます。

$ sbt new wmeddie/dl4j-scala.g8

このテンプレートはサンプルのデータと、訓練するクラス(com.example.Train)がありますので、参考になるかと思います。

Javaを利用したい場合はmavenやgradleを利用する事が必須です。

私もJavaのmavenテンプレートを開発中です。まだMaven Centralに公開していませんので、下記のコマンドでテンプレートを利用する事が出来ます。

最初に、MavenのArchetypeのプロジェクトをインストールし

$ git clone https://github.com/wmeddie/dl4j-trainer-archetype.git

$ cd dl4j-trainer-archetype

$ mvn install   

その後、Archetypeを利用する事が出来ます。

$ mvn archetype:generate -Dfilter=com.yumusoft:dl4j-trainer-archetype

すでにMavenを利用しているプロジェクトがありましたら、pom.xmlのファイルに下記の依存を<dependencies>に追加すればDL4Jが利用出来ます

<dependency>
  <groupId>org.nd4j</groupId>
  <artifactId>nd4j-native-platform</artifactId>
</dependency>
<dependency>
  <groupId>org.deeplearning4j</groupId>
  <artifactId>deeplearning4j-core</artifactId>
  <version>0.7.1</version>
</dependency>
<dependency>
  <groupId>org.deeplearning4j</groupId>
  <artifactId>deeplearning4j-ui_2.10</artifactId>
  <version>0.7.1</version>
</dependency>

GPUを利用する場合はnd4j-native-platformがインストールされているcudaのバーションによってnd4j-cuda-7.5-platformnd4j-cuda-8.0-platformに書き換える事が良いと思われます。GPUの計算能力が高くありますが、モデルによってCPUの方が早い場合がありますので両方試す事をお勧めします。

深層学習のモデルを訓練する

DL4Jの訓練方法は他のライブラリとほとんど変わりません。JavaはPythonと変わって引数の名前をつける事が出来ませんのでBuilderパターン利用しています。

基本的なネットワークはNeuralNetConfigurationを利用します。

MultiLayerConfiguration conf = new NeuralNetConfiguration.Builder()
  .seed(42)
  .iterations(1)
  .activation("relu")
  .weightInit(WeightInit.XAVIER)
  .learningRate(0.1)
  .regularization(true).l2(1e-4)
  .list(
    new DenseLayer.Builder().nIn(10).nOut(10).build(),
    new DenseLayer.Builder().nIn(10).nOut(5).build(),
    new OutputLayer.Builder(LossFunctions.LossFunction.NEGATIVELOGLIKELIHOOD)
      .activation("softmax")
      .nIn(5)
      .nOut(2)
      .build()
  )
  .build();

上記のBuilderパターンでは`activation`や`weightInit`はレイヤーの設定です。listを呼ぶ前に設定すると、全てのレイヤーがその値を利用されます。最後のレイヤー見たいにレイヤーに直接設定する事が出来ます。

MultiLayerNetwork model = new MultiLayerNetwork(conf);
model.init();

上記の設定より複雑なニューラルネットワークが必要の場合はNeuralNetConfigurationの変わりにComputationGraphが使います。例として下記のネットワークは二つの数字を足し算できる仕組みです:

ComputationGraphConfiguration configuration = new NeuralNetConfiguration.Builder()
  .weightInit(WeightInit.XAVIER)
  .learningRate(0.5)
  .updater(Updater.RMSPROP)
  .optimizationAlgo(OptimizationAlgorithm.STOCHASTIC_GRADIENT_DESCENT).iterations(nIterations)
  .seed(seed)
  .graphBuilder()
    .addInputs("additionIn", "sumOut")
    .setInputTypes(InputType.recurrent(FEATURE_VEC_SIZE), InputType.recurrent(FEATURE_VEC_SIZE))
    .addLayer("encoder", new GravesLSTM.Builder().nIn(FEATURE_VEC_SIZE).nOut(numHiddenNodes).activation("softsign").build(),"additionIn")
    .addVertex("lastTimeStep", new LastTimeStepVertex("additionIn"), "encoder")
    .addVertex("duplicateTimeStep", new DuplicateToTimeSeriesVertex("sumOut"), "lastTimeStep")
    .addLayer("decoder", new GravesLSTM.Builder().nIn(FEATURE_VEC_SIZE+numHiddenNodes).nOut(numHiddenNodes).activation("softsign").build(), "sumOut","duplicateTimeStep")
    .addLayer("output", new RnnOutputLayer.Builder().nIn(numHiddenNodes).nOut(FEATURE_VEC_SIZE).activation("softmax").lossFunction(LossFunctions.LossFunction.MCXENT).build(), "decoder")
    .setOutputs("output")
  .pretrain(false).backprop(true)
  .build();

ComputationGraph model = new ComputationGraph(configuration);
model();

上記のコードはDL4J-Examplesからとりました。DL4Jを利用する時にDL4J-Examplesはとても参考なります。

モデルがありましたらtrainというメソッドで訓練する事が出来ます。シンプルなデータの場合は直接行列(INDArray)をtrainに渡す事が出来ますが、元データはCSVファイルや画像のファイルである場合DL4Jのベクトル変換フレームワークが便利です。

CSVを読み込む場合は下記のコードはirisデータセットの基本的な使い方です。

CSVRecordReader recordReader = new CSVRecordReader(0, ",");
recordReader.initialize(new FileSplit(new File(name)));

int labelIndex = 4;  // 5カラム目はラベルです。
int numClasses = 3;  // ラベルに3種類があります。
int batchSize = 50;  // 一回読み込む件数。GPU/CPUによってこのパラメターを変更するべき場合はあります。

RecordReaderDataSetIterator iterator = new RecordReaderDataSetIterator(
  recordReader,
  batchSize,
  labelIndex,
  numClasses
);

RecordReaderDataSetIteratorがあれば、それを丸ごとにモデルのtrainメソッドに渡す事が出来ます。

for (int epoch = 0; epoch < 10; epoch++) {
  model.train(iterator);
  iterator.reset();
}

訓練監視

trainのメソッドは時間かかるのでモデルの訓練がうまくいっているかを見る事が大事です。DL4Jではモデルの監視がIterationListenerで行います。trainを呼ぶ前にsetListenersでいくつかのIterationListenerを設定する事が出来ます。一番簡単のIterationListenerScoreIterationListenerです。ScoreIterationListenerは基本的にエラーを出力されます。出力される数字が大きな変化や増加している場合は訓練がうまくいってない証拠です。GPUとCPUの速さを比較為にPerformanceListenerが便利です。最後に少し使い方が難しいがGUIもあります。使い方は下記の通りです。

UIServer uiServer = UIServer.getInstance();

StatsStorage statsStorage = new InMemoryStatsStorage();
uiServer.attach(statsStorage);

model.setListeners(Arrays.asList(new ScoreIterationListener(1), new StatsListener(statsStorage)));

trainを呼び始めたらブラウザーをhttp://localhost:9000をアクセスすると下記のような画面が出ます。

DL4J_UI_01.png

レイヤーごとのパラメターの動きまでビジュアルで見れるのでとても便利です。

モデルの評価にはDL4JはEvaluationというクラスを用意しています。基本的にevalのメソッドとstatsのメソッドを呼びながら評価の結果を確認出来ます。


Evaluation eval = new Evaluation(3);
while (testData.hasNext()) {
  DataSet ds = testData.next();
  INDArray output = model.output(ds.getFeatureMatrix());
  eval.eval(ds.getLabels(), output);
}

log.info(eval.stats());

最後に訓練したモデルを保存します。DL4Jの場合はModelSerializerが一番簡単です。使い方は下記となります。

ModelSerializer.writeModel(model, "model1.net", true);

最後の引数がtrueの場合はモデルの再トレーニングが出来ます。

訓練したモデルを本番にデプロイ

モデルを利用するのはBatchで一番簡単ですがウエブサービスとして利用する事も可能です。問題はモデルのoutputメソッドはatomicではありませんので複数スレッドで利用すると、変な値が予測されてしまう可能性があります。Java EEのEJB仕様では一つのスレッドで同時に実行されないので、それを活用し、正しく利用出来ます。

@Startup
@Stateless
public class Inferencer {
  private MultiLayerNetwork model;

  @PostConstruct
  public void init() {
    try {
      InputStream modelStream = ClassLoader.getSystemResourceAsStream("model1.net");
      model = ModelSerializer.restoreMultiLayerNetwork(modelStream);
    } catch (Exception e) {
      throw new RuntimeError(e.toString());
    }
  }

  public int predict(float a, float b, float c) {
      INDArray input = Nd4j.create(new float[] { a, b, c });
      return Nd4j.argMax(model.output(input)).getInt(0);
  }
}

モデルが大きい場合はコンテナーでEJBのプールサイズを調整する必要がありますが、この仕組みである程度利用出来ます。EJBが利用出来ない場合はパフォーマンスが下がりますがmodel.clone()してからoutputを呼ぶという対象方法があります。

GPUを利用する事になると予測をバッチして返す事がパフォーマンスのために必要になりますが、それをしてくれるクラスが現在開発中です。

まとめ

DL4Jを利用すると、一つの言語でモデルの開発、評価と本番にデプロイする事が出来ます。一つの言語を利用していますので生のデータをベクトル化する部分が共通出来るのでとても便利です。まだ利用してない人は是非試して見てください。