torutkのブログ

ソフトウェア・エンジニアのブログ

「Deep Learning Javaプログラミング」のパーセプトロンのサンプルプログラムにGUIを付けてみた #javareading

12月からJava読書会BOF*1で読み始めた「Deep Learning Javaプログラミング」で、2.5.1節のパーセプトロン(単層ニューラルネットワーク)のサンプルコードをJavaFXGUIを追加して作成してみました。

Deep Learning Javaプログラミング 深層学習の理論と実装 (impress top gear)

本書籍のサンプルコードは出版社(インプレス)の書籍ページからダウンロードできます。
http://book.impress.co.jp/books/1115101146

ただ、コンソールで実行するサンプルとなっていたので、データの分布が分かるようJavaFXGUIを付けてみました。

実行した画面は次になります。

プログラミングメモ

散布図(ScatterChart)

2次元のデータ分布なので、JavaFXの散布図(ScatterChart)を使いました。
id:torutk:20170113 にScene Builder上でScatterChartを貼ってその軸のプロパティを設定するメモを書いています。

デフォルトの散布図では、シンボル(オレンジ色の丸いもの)が大きくデータ数が多くなると重なって分布が見えにくくなってしまいました。そこで、CSSファイルを設けてシンボルの大きさを5pxから2pxに変更しています。

  • perceptron.css
.chart-symbol {
    -fx-background-radius: 2px;
    -fx-padding: 2px;
}

NetBeans上でCSSファイルを作成し、Scene Builder上で登録する方法は、ちょうど今月公開されたITProのJavaFX連載記事(著者は櫻庭さん)に書かれています。
JavaFXで見た目を設定する、JavaFXにおけるCSSとは | 日経 xTECH(クロステック)

散布図に表示させるデータは、個々のデータをXYChart.Dataで表現し、データの集まり(系列)をXYChart.Seriesで表現します。データ系列ごとにシンボル形状と色が変わります。

デフォルトでは、凡例が表示されるので、データ系列が1つのときは邪魔なのでScene Builder上からScatterChartのプロパティLegend Visibleを外しました。

デフォルトでは、NumberAxisのプロパティ Auto Ranging が有効となっています。データの範囲に合わせて表示するスケールを調整してくれるので、今回の用途では便利です。逆に、Lower Bound、Upper Boundに設定した軸の表示の最小・最大値はデータをセットする前(Auto Rangingが作用する前)の表示に反映されます。

ViewModel(Presentation Model)の設置

NetBeansJavaFX FXMLアプリケーションで生成したプロジェクトでは、Application派生クラス(mainメソッドを持つクラス)、FXMLファイル、FXMLから参照されるコントローラクラスの3つのファイルが生成されます。

小さなGUIアプリケーションの場合、ついコントローラクラスに表示するデータを持たせてしまいます。しかし、コントローラクラスがどんどん肥大化してしまいます。あとでリファクタリングしようとしても、びったり癒着してしまったものを引き剥がすのは大変です。
そこで、最初からコントローラとは別に表示するデータを持たせておくクラス(ViewModelあるいはPresentationModel)を作成しておき、コントローラはあくまでViewModelとFXMLとの接合(バインディングとかリスナー登録とかの作業)に専念しておきます。

今回作成したコントローラクラスは、データの保持をViewModelに分離したので次のようにシンプルなコードとなっています。

package perceptron;

import java.net.URL;
import java.util.ResourceBundle;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.chart.ScatterChart;
import javafx.scene.control.Label;

public class PerceptronViewController implements Initializable {
    
    @FXML
    private ScatterChart chart;
    @FXML
    private Label accuracyLabel;
    @FXML
    private Label precisionLabel;
    @FXML
    private Label recallLabel;
    
    private PerceptronViewModel model = new PerceptronViewModel();
    
    @FXML
    private void doLearning(ActionEvent event) {
        model.getTrainData().stream()
                            .forEach(chart.getData()::add);
        model.train();
        model.test();
        model.evaluate();
    }
    
    @Override
    public void initialize(URL url, ResourceBundle rb) {
        chart.setTitle("学習データの散布図");
        accuracyLabel.textProperty().bind(model.accuracyProperty());
        precisionLabel.textProperty().bind(model.precisionProperty());
        recallLabel.textProperty().bind(model.recallProperty());
    }    
    
}
正規分布乱数の生成

書籍のサンプルコードでは、正規分布の乱数を生成するロジックを独自にコーディングしていました。Java SEの標準API java.util.RandomクラスにはnextGaussian メソッドがあり、平均0.0、標準偏差1.0の正規分布乱数を取得できます。

今回生成するデータは、平均が-2.0および2.0で標準偏差1.0なので、nextGaussianの戻り値に平均の大きさ(-2.0および2.0)を加えたものになります。

書籍のサンプルコード

        GaussianDistribution g1 = new GaussianDistribution(-2.0, 1.0, rng);
        GaussianDistribution g2 = new GaussianDistribution(2.0, 1.0, rng);

        // data set in class 1
        for (int i = 0; i < train_N/2 - 1; i++) {
            train_X[i][0] = g1.random();
            train_X[i][1] = g2.random();
            train_T[i] = 1;
        }

nextGaussianを使った生成(for文の替わりにStream使用)

        IntStream.range(0, NUM_TRAIN / 2)
                .forEach(i -> {
                    trainData[i][0] = random.nextGaussian() - 2.0;
                    trainData[i][1] = random.nextGaussian() + 2.0;
                    trainLabels[i] = 1;
                });

本来は、Streamの終端にforEachではなく、toArrayを用いて配列にしたかったのですが、単純な1次元配列ではないので手が見えずあきらめました。

*1:次回は1月21日(土)に開催します。http://www.javareading.com/bof/