12月からJava読書会BOF*1で読み始めた「Deep Learning Javaプログラミング」で、2.5.1節のパーセプトロン(単層ニューラルネットワーク)のサンプルコードをJavaFXでGUIを追加して作成してみました。
Deep Learning Javaプログラミング 深層学習の理論と実装 (impress top gear)
本書籍のサンプルコードは出版社(インプレス)の書籍ページからダウンロードできます。
http://book.impress.co.jp/books/1115101146
ただ、コンソールで実行するサンプルとなっていたので、データの分布が分かるようJavaFXでGUIを付けてみました。
実行した画面は次になります。
プログラミングメモ
散布図(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)の設置
NetBeans のJavaFX 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/