Deeplearning4J で iris を分類

Deeplearning4J (DL4J) を使って 「ConvNetJS で iris を分類」 と同様に iris を分類してみました。

今回は Groovy を使って実行します。

ソースは http://github.com/fits/try_samples/tree/master/blog/20160412/

準備

iris データセット

iris のデータセットorg.deeplearning4j.datasets.DataSets.iris() メソッドで取得できるため、ConvNetJS の時のようにダウンロードする必要はありません。

OpenBlas のインストール(Windows

Deeplearning4J が利用する ND4J を効果的に使うには OpenBLAS などのネイティブの BLAS ライブラリが必要です。

MNIST データセットのパース」 でも少し書きましたが、OpenBLAS を Windows 環境へインストールするには以下のようにします。

また、JniLoader が netlib-native_system-win-x86_64.dll を %TEMP% ディレクトリへダウンロードするのを防止するには、この dll も環境変数 PATH へ設定した場所へ配置しておきます。 (dll は Maven の Central Repository からダウンロードできます)

今回は、ND4J_Win64_OpenBLAS-v0.2.14.zip の解凍先へ netlib-native_system-win-x86_64.dll も配置しました。

  • C:\ND4J_Win64_OpenBLAS-v0.2.14
    • libblas3.dll
    • libgcc_s_seh-1.dll
    • libgfortran-3.dll
    • liblapack3.dll
    • libopenblas.dll
    • libquadmath-0.dll
    • netlib-native_system-win-x86_64.dll
環境変数 PATH 設定例
> set PATH=C:\ND4J_Win64_OpenBLAS-v0.2.14;%PATH%

共通処理の作成

Deeplearning4J には学習モデルを JSON 化するような処理は用意されていないようです。

ただ、MultiLayerNetwork 自体が Serializable なので、今回は Javaシリアライズ機能を使って保存等を行いました。

1. 学習モデルのセーブ・ロード処理

ObjectInputStream・ObjectOutputStream を使って MultiLayerNetwork を保存・復元する処理です。

ModelManager.groovy
@Grab('org.deeplearning4j:deeplearning4j-core:0.4-rc3.8')
@Grab('org.nd4j:nd4j-x86:0.4-rc3.8')
import org.deeplearning4j.nn.conf.MultiLayerConfiguration
import org.deeplearning4j.nn.multilayer.MultiLayerNetwork

abstract class ModelManager extends Script {

    // MultiLayerNetwork の復元
    def loadModel(String fileName) {
        new File(fileName).withObjectInputStream(this.class.classLoader) {
            it.readObject() as MultiLayerNetwork
        }
    }

    // MultiLayerNetwork の生成と保存
    def saveModel(String fileName, MultiLayerConfiguration conf) {
        // MultiLayerNetwork の生成と初期構築
        def model = new MultiLayerNetwork(conf)
        model.init()

        new File(fileName).withObjectOutputStream {
            it.writeObject model
        }
    }
}

2. 学習・評価処理

以前 ConvNetJS で実装したものと同じような出力結果となるように実装してみました。

今回使用した MultiLayerNetworkメソッドは以下の通りです。

メソッド 備考
fit 学習の実施(誤差の算出、重みの調整等)
score 誤差の取得(引数次第で誤差の算出も実施)
output ニューラルネットの処理結果を取得

output は正解率の算出に使うだけなので、第 2引数を false にして評価モード(TrainingMode.TEST)で実行します。

今回は、学習時と評価時の処理を共通化するため score メソッドで誤差を算出しましたが、setListeners メソッドScoreIterationListener を設定すれば誤差をログ出力できます。

Evaluationeval メソッドへ正解のラベルデータとニューラルネットの処理結果を渡すと正解率などを算出してくれ、accuracy メソッドで正解率を取得できます。 (stats メソッドを使えば結果を文字列で取得する事もできます)

org.nd4j.linalg.dataset.api.DataSetsplitTestAndTrain メソッドで学習用と評価用にデータを分割 ※、batchBy メソッドでミニバッチへ分割できます。

 ※ getTrain で学習用、getTest で評価用のデータセットを取得できます
iris_train.groovy
@Grab('org.deeplearning4j:deeplearning4j-core:0.4-rc3.8')
@Grab('org.nd4j:nd4j-x86:0.4-rc3.8')
import org.deeplearning4j.datasets.DataSets
import org.deeplearning4j.eval.Evaluation
import org.nd4j.linalg.dataset.SplitTestAndTrain

import groovy.transform.BaseScript

@BaseScript ModelManager baseScript

def epoch = args[0] as int
def trainRate = 0.7
def batchSize = 1

def modelFile = args[1]

// 誤差・正解率の算出
class SimpleEvaluator {
    private def model
    private def ev = new Evaluation(3) // iris の品種の数 3 を設定
    private def lossList = []

    SimpleEvaluator(model) {
        this.model = model
    }

    def eval(d) {
        // 誤差の算出とリストへの追加
        lossList << model.score(d)
        // 正解率などの算出
        ev.eval(d.labels, model.output(d.featureMatrix, false))
    }
    // 誤差(平均値)
    def loss() {
        lossList.sum() / lossList.size()
    }
    // 正解率
    def accuracy() {
        ev.accuracy()
    }
}

// 学習モデル (MultiLayerNetwork) のロード
def model = loadModel(modelFile)
// iris データセットの取得
def data = DataSets.iris()

(0..<epoch).each {
    def ev = [
        train: new SimpleEvaluator(model),
        test: new SimpleEvaluator(model)
    ]

    data.shuffle()

    // 学習用とテスト用にデータセットを分割
    def testAndTrain = data.splitTestAndTrain(trainRate)

    // 学習用データセットをミニバッチへ分割
    testAndTrain.train.batchBy(batchSize).each {
        ev.train.eval(it)
        // 学習
        model.fit(it)
    }

    // テスト用データセットを評価
    ev.test.eval(testAndTrain.test)

    // 結果の出力
    println([
        ev.train.loss(), ev.train.accuracy(),
        ev.test.loss(), ev.test.accuracy()
    ].join(','))
}

また、デフォルトではログを標準出力するので、今回は設定ファイルを用意して無効化しました。

logback.xml
<configuration>
  <root level="OFF"></root>
</configuration>

(a) 単純な構成 (入力層 - 出力層)

入力層と出力層だけの単純なニューラルネットを試します。

iris データセットは、4つの変数を使って 3つの品種に分類する事になりますので、OutputLayer.Builder へ以下のように設定します。

  • nIn へ変数の数 4 を設定
  • nOut へ分類の数 3 を設定

ソフトマックスと 3種類以上の交差エントロピー(Cross Entropy)を実施するには、以下のように設定すれば良いみたいです。

  • activation へ softmax を設定
  • OutputLayer.BuilderLossFunctions.LossFunction.MCXENT (Multiclass Cross Entropy) を設定

また、list には設定するレイヤーの数 (以下では 1) を設定します ※。

 ※ ただし、最新のソースで list(int) は Deprecated となっており、
    代わりにレイヤー数を指定しなくても済む list() を使うようです

    また、list(int) は list() を呼ぶだけの実装へ変わっていました
create_iris_hnn1.groovy
@Grab('org.deeplearning4j:deeplearning4j-core:0.4-rc3.8')
@Grab('org.nd4j:nd4j-x86:0.4-rc3.8')
import org.deeplearning4j.nn.conf.NeuralNetConfiguration
import org.deeplearning4j.nn.conf.Updater
import org.deeplearning4j.nn.conf.layers.OutputLayer
import org.nd4j.linalg.lossfunctions.LossFunctions

import groovy.transform.BaseScript

@BaseScript ModelManager baseScript

def learningRate = args[0] as double
def updateMethod = Updater.valueOf(args[1].toUpperCase())
def destFile = args[2]

def conf = new NeuralNetConfiguration.Builder()
    .iterations(1) // 最適化の繰り返し回数(デフォルト設定は 5)
    .updater(updateMethod)
    .learningRate(learningRate)
    .list(1)
    .layer(0, new OutputLayer.Builder(LossFunctions.LossFunction.MCXENT)
        .nIn(4)
        .nOut(3)
        .activation('softmax')
        .build()
    )
    .build()

// 保存
saveModel(destFile, conf)

学習・評価

更新方法 (updateMethod) だけを変えたモデルを作って学習・評価を実施してみました。

(a-1) learningRate = 0.01, updateMethod = adam, epoch = 50

実行例
> groovy create_iris_hnn1.groovy 0.01 adam models/a-1_adam.ser
・・・
> groovy iris_train.groovy 50 models/a-1_adam.ser > results/a-1.csv
・・・

f:id:fits:20160412211058p:plain

(a-2) learningRate = 0.01, updateMethod = adadelta, epoch = 50

実行例
> groovy create_iris_hnn1.groovy 0.01 adadelta models/a-2_adadelta.ser
・・・
> groovy iris_train.groovy 50 models/a-2_adadelta.ser > results/a-2.csv
・・・

f:id:fits:20160412211111p:plain

(a-3) learningRate = 0.01, updateMethod = sgd, epoch = 50

実行例
> groovy create_iris_hnn1.groovy 0.01 sgd models/a-3_sgd.ser
・・・
> groovy iris_train.groovy 50 models/a-3_sgd.ser > results/a-3.csv
・・・

f:id:fits:20160412211124p:plain

(b) 隠れ層を追加 (入力層 - 隠れ層 - 出力層)

次に、隠れ層を追加してみます。

DenseLayer を追加して nInnOut を調整します。

create_iris_hnn2.groovy
@Grab('org.deeplearning4j:deeplearning4j-core:0.4-rc3.8')
@Grab('org.nd4j:nd4j-x86:0.4-rc3.8')
import org.deeplearning4j.nn.conf.NeuralNetConfiguration
import org.deeplearning4j.nn.conf.Updater
import org.deeplearning4j.nn.conf.layers.DenseLayer
import org.deeplearning4j.nn.conf.layers.OutputLayer
import org.nd4j.linalg.lossfunctions.LossFunctions

import groovy.transform.BaseScript

@BaseScript ModelManager baseScript

def learningRate = args[0] as double
def updateMethod = Updater.valueOf(args[1].toUpperCase())

// 隠れ層のニューロン数
def fcNeuNum = args[2] as int
// 隠れ層の活性化関数
def fcAct = args[3]

def destFile = args[4]

def conf = new NeuralNetConfiguration.Builder()
    .iterations(1)
    .updater(updateMethod)
    .learningRate(learningRate)
    .list(2)
    // 隠れ層
    .layer(0, new DenseLayer.Builder()
        .nIn(4)
        .nOut(fcNeuNum)
        .activation(fcAct)
        .build()
    )
    .layer(1, new OutputLayer.Builder(LossFunctions.LossFunction.MCXENT)
        .nIn(fcNeuNum)
        .nOut(3)
        .activation('softmax')
        .build()
    )
    .build()

// 保存
saveModel(destFile, conf)

学習・評価

隠れ層の活性化関数 (fcAct) だけを変えたモデルを作って学習・評価を実施してみました。

(b-1) fcNeuNum = 8, fcAct = relu, learningRate = 0.01, updateMethod = adam, epoch = 50

実行例
> groovy create_iris_hnn2.groovy 0.01 adam 8 relu models/b-1_adam_relu.ser
・・・
> groovy iris_train.groovy 50 models/b-1_adam_relu.ser > results/b-1.csv
・・・

f:id:fits:20160412211144p:plain

(b-2) fcNeuNum = 8, fcAct = sigmoid, learningRate = 0.01, updateMethod = adam, epoch = 50

実行例
> groovy create_iris_hnn2.groovy 0.01 adam 8 sigmoid models/b-2_adam_sigmoid.ser
・・・
> groovy iris_train.groovy 50 models/b-2_adam_sigmoid.ser > results/b-2.csv
・・・

f:id:fits:20160412211155p:plain