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 環境へインストールするには以下のようにします。
- http://nd4j.org/getstarted.html#open のリンクから ND4J_Win64_OpenBLAS-v0.2.14.zip をダウンロード・解凍し、環境変数 PATH へ設定
また、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
環境変数 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
を設定すれば誤差をログ出力できます。
Evaluation
の eval
メソッドへ正解のラベルデータとニューラルネットの処理結果を渡すと正解率などを算出してくれ、accuracy
メソッドで正解率を取得できます。 (stats
メソッドを使えば結果を文字列で取得する事もできます)
org.nd4j.linalg.dataset.api.DataSet
は splitTestAndTrain
メソッドで学習用と評価用にデータを分割 ※、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.Builder
でLossFunctions.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 ・・・
(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 ・・・
(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 ・・・
(b) 隠れ層を追加 (入力層 - 隠れ層 - 出力層)
次に、隠れ層を追加してみます。
DenseLayer
を追加して nIn
と nOut
を調整します。
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 ・・・
(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 ・・・