| --- |
| layout: post |
| status: PUBLISHED |
| published: true |
| title: Classifying Iris Flowers with Deep Learning, Groovy and GraalVM |
| id: 4ef9c51f-49ed-4c12-8296-e9037096b8cf |
| date: '2022-06-25 10:52:59 -0400' |
| categories: groovy |
| tags: |
| - graalvm |
| - groovy |
| - machinelearning |
| - datascience |
| - deeplearning |
| - ai |
| - neuralnets |
| permalink: groovy/entry/classifying-iris-flowers-with-deep |
| --- |
| <p><img src="https://blogs.apache.org/groovy/mediaresource/80b1adc8-7234-4046-a279-e272b0a0edd4" alt="iris_description.png" style="width: 25%;" align="right">A classic data science <a href="https://en.wikipedia.org/wiki/Iris_flower_data_set" target="_blank">dataset</a> captures flower characteristics of Iris flowers. It captures the <i>width </i>and <i>length </i>of the <i>sepals </i>and <i>petals </i>for three <i>species </i>(<a href="https://en.wikipedia.org/wiki/Iris_setosa" target="_blank">Setosa</a>, <a href="https://en.wikipedia.org/wiki/Iris_versicolor" target="_blank">Versicolor</a>, and <a href="https://en.wikipedia.org/wiki/Iris_virginica" target="_blank">Virginica</a>).</p> |
| <p>The <a href="https://github.com/paulk-asert/groovy-data-science/tree/master/subprojects/Iris" target="_blank">Iris project</a> in the <a href="https://github.com/paulk-asert/groovy-data-science" target="_blank">groovy-data-science repo</a> is dedicated to this example. It includes a number of Groovy scripts and a Jupyter/BeakerX notebook highlighting this example comparing and contrasting various libraries and various classification algorithms.</p></p> |
| <table style="margin-top: 0px; margin-bottom: 16px; display: block; width: max-content; max-width: 70%; overflow: auto; color: rgb(36, 41, 47); font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 16px; background-color: rgb(255, 255, 255);"> |
| <thead> |
| <tr style="background-color: var(--color-canvas-default); border-top: 1px solid var(--color-border-muted);"> |
| <th style="padding: 6px 13px; border: 1px solid var(--color-border-default);"></th> |
| <th style="padding: 6px 13px; border: 1px solid var(--color-border-default);">Technologies/libraries covered</th> |
| </tr> |
| </thead> |
| <tbody> |
| <tr style="border-top: 1px solid var(--color-border-muted);"> |
| <td style="padding: 6px 13px; border: 1px solid var(--color-border-default);"><i>Data manipulation</i></td> |
| <td style="padding: 6px 13px; border: 1px solid var(--color-border-default);"><a href="https://www.cs.waikato.ac.nz/ml/weka/" rel="nofollow" style="transition: color 80ms cubic-bezier(0.33, 1, 0.68, 1) 0s, background-color 0s ease 0s, box-shadow 0s ease 0s, border-color 0s ease 0s;">Weka</a>, <a href="https://tablesaw.tech/" rel="nofollow" style="transition: color 80ms cubic-bezier(0.33, 1, 0.68, 1) 0s, background-color 0s ease 0s, box-shadow 0s ease 0s, border-color 0s ease 0s;">Tablesaw</a>, <a href="https://github.com/jeffheaton/encog-java-core" style="transition: color 80ms cubic-bezier(0.33, 1, 0.68, 1) 0s, background-color 0s ease 0s, box-shadow 0s ease 0s, border-color 0s ease 0s;">Encog</a>, <a href="https://github.com/EdwardRaff/JSAT" style="transition: color 80ms cubic-bezier(0.33, 1, 0.68, 1) 0s, background-color 0s ease 0s, box-shadow 0s ease 0s, border-color 0s ease 0s;">JSAT</a>, <a href="https://github.com/deeplearning4j/DataVec" style="transition: color 80ms cubic-bezier(0.33, 1, 0.68, 1) 0s, background-color 0s ease 0s, box-shadow 0s ease 0s, border-color 0s ease 0s;">Datavec</a>, <a href="https://tribuo.org/" rel="nofollow" style="transition: color 80ms cubic-bezier(0.33, 1, 0.68, 1) 0s, background-color 0s ease 0s, box-shadow 0s ease 0s, border-color 0s ease 0s;">Tribuo</a></td> |
| </tr> |
| <tr style="border-top: 1px solid var(--color-border-muted);"> |
| <td style="padding: 6px 13px; border: 1px solid var(--color-border-default);"><i>Classification</i></td> |
| <td style="padding: 6px 13px; border: 1px solid var(--color-border-default);"><a href="https://www.cs.waikato.ac.nz/ml/weka/" rel="nofollow" style="transition: color 80ms cubic-bezier(0.33, 1, 0.68, 1) 0s, background-color 0s ease 0s, box-shadow 0s ease 0s, border-color 0s ease 0s;">Weka</a>, <a href="http://haifengl.github.io/" rel="nofollow" style="transition: color 80ms cubic-bezier(0.33, 1, 0.68, 1) 0s, background-color 0s ease 0s, box-shadow 0s ease 0s, border-color 0s ease 0s;">Smile</a>, <a href="https://github.com/jeffheaton/encog-java-core" style="transition: color 80ms cubic-bezier(0.33, 1, 0.68, 1) 0s, background-color 0s ease 0s, box-shadow 0s ease 0s, border-color 0s ease 0s;">Encog</a>, <a href="https://tribuo.org/" rel="nofollow" style="transition: color 80ms cubic-bezier(0.33, 1, 0.68, 1) 0s, background-color 0s ease 0s, box-shadow 0s ease 0s, border-color 0s ease 0s;">Tribuo</a>, <a href="https://github.com/EdwardRaff/JSAT" style="transition: color 80ms cubic-bezier(0.33, 1, 0.68, 1) 0s, background-color 0s ease 0s, box-shadow 0s ease 0s, border-color 0s ease 0s;">JSAT</a>, <a href="https://deeplearning4j.org/" rel="nofollow" style="transition: color 80ms cubic-bezier(0.33, 1, 0.68, 1) 0s, background-color 0s ease 0s, box-shadow 0s ease 0s, border-color 0s ease 0s;">Deep Learning4J</a>, <a href="https://www.deepnetts.com/blog/deep-netts-community-edition" rel="nofollow" style="transition: color 80ms cubic-bezier(0.33, 1, 0.68, 1) 0s, background-color 0s ease 0s, box-shadow 0s ease 0s, border-color 0s ease 0s;">Deep Netts</a></td> |
| </tr> |
| <tr style="border-top: 1px solid var(--color-border-muted);"> |
| <td style="padding: 6px 13px; border: 1px solid var(--color-border-default);"><i>Visualization</i></td> |
| <td style="padding: 6px 13px; border: 1px solid var(--color-border-default);"><a href="https://knowm.org/open-source/xchart/" rel="nofollow" style="transition: color 80ms cubic-bezier(0.33, 1, 0.68, 1) 0s, background-color 0s ease 0s, box-shadow 0s ease 0s, border-color 0s ease 0s;">XChart</a>, <a href="https://jtablesaw.github.io/tablesaw/userguide/Introduction_to_Plotting.html" title="Tablesaw Plot.ly support" rel="nofollow" style="transition: color 80ms cubic-bezier(0.33, 1, 0.68, 1) 0s, background-color 0s ease 0s, box-shadow 0s ease 0s, border-color 0s ease 0s;">Tablesaw Plot.ly</a>, <a href="https://openjfx.io/" rel="nofollow" style="transition: color 80ms cubic-bezier(0.33, 1, 0.68, 1) 0s, background-color 0s ease 0s, box-shadow 0s ease 0s, border-color 0s ease 0s;">JavaFX</a></td> |
| </tr> |
| <tr style="border-top: 1px solid var(--color-border-muted);"> |
| <td style="padding: 6px 13px; border: 1px solid var(--color-border-default);"><i>Main aspects/algorithms covered</i></td> |
| <td style="padding: 6px 13px; border: 1px solid var(--color-border-default);">Reading csv, dataframes, visualization, exploration, <a href="https://en.wikipedia.org/wiki/Naive_Bayes_classifier" target="_blank">naive bayes</a>, <a href="https://en.wikipedia.org/wiki/Logistic_regression" target="_blank">logistic regression</a>, <a href="https://en.wikipedia.org/wiki/K-nearest_neighbors_algorithm#k-NN_regression" target="_blank">knn regression</a>, <a href="https://en.wikipedia.org/wiki/Multinomial_logistic_regression" target="_blank">softmax regression</a>, <a href="https://en.wikipedia.org/wiki/Decision_tree_learning" target="_blank">decision trees</a>, <a href="https://en.wikipedia.org/wiki/Support-vector_machine" target="_blank">support vector machine</a></td> |
| </tr> |
| <tr style="background-color: var(--color-canvas-default); border-top: 1px solid var(--color-border-muted);"> |
| <td style="padding: 6px 13px; border: 1px solid var(--color-border-default);"><i>Other aspects/algorithms covered</i></td> |
| <td style="padding: 6px 13px; border: 1px solid var(--color-border-default);"><a href="https://en.wikipedia.org/wiki/Artificial_neural_network" target="_blank">neural networks</a>, <a href="https://en.wikipedia.org/wiki/Multilayer_perceptron" target="_blank">multilayer perceptron</a>, <a href="https://en.wikipedia.org/wiki/Principal_component_analysis" target="_blank">PCA</a></td> |
| </tr> |
| </tbody> |
| </table> |
| <p>Feel free to browse these other examples and the Jupyter/BeakerX notebook if you are interested in any of these additional techniques.</p> |
| <p><a href="https://blogs.apache.org/groovy/mediaresource/f389f220-38f0-473b-ab52-72032694d435"><img style="width: 50%;" src="https://blogs.apache.org/groovy/mediaresource/f389f220-38f0-473b-ab52-72032694d435" alt="iris_jupyter.png"></a></p> |
| <p>For this blog, let's just look at the Deep Learning examples. We'll look at solutions using Encog, Eclipse DeepLearning4J and Deep Netts (with standard Java and as a native image using GraalVM) but first a brief introduction.</p> |
| <h3>Deep Learning</h3> |
| <p>Deep learning falls under the branches of <a href="https://en.wikipedia.org/wiki/Machine_learning" target="_blank">machine learning</a> and <a href="https://en.wikipedia.org/wiki/Artificial_intelligence" target="_blank">artificial intelligence</a>. It involves multiple layers (hence the "deep") of an <a href="https://en.wikipedia.org/wiki/Artificial_neural_network" target="_blank">artificial neural network</a>. There are lots of ways to configure such networks and the details are beyond the scope of this blog post, but we can give some basic details. We will have four input nodes corresponding to the measurements of our four characteristics. We will have three output nodes corresponding to each possible <i>class </i>(<i>species</i>). We will also have one or more additional layers in between.</p> |
| <p><a href="https://blogs.apache.org/groovy/mediaresource/2206eccb-0c50-4091-a030-e3057517d810"><img src="https://blogs.apache.org/groovy/mediaresource/2206eccb-0c50-4091-a030-e3057517d810" style="width: 50%;" alt="deep_network.png"></a></p> |
| <p>Each node in this network mimics to some degree a neuron in the human brain. Again, we'll simplify the details. Each node has multiple inputs, which are given a particular weight, as well as an activation function which will determine whether our node "fires". Training the model is a process which works out what the best weights should be.</p> |
| <p><a href="https://blogs.apache.org/groovy/mediaresource/b5d32431-a273-481d-b0b5-169b0665b385"><img src="https://blogs.apache.org/groovy/mediaresource/b5d32431-a273-481d-b0b5-169b0665b385" style="width: 50%;" alt="deep_node.png"></a></p> |
| <p>The math involved for converting inputs to output for any node isn't too hard. We could write it ourselves (as shown <a href="https://github.com/paulk-asert/groovy-data-science/blob/master/subprojects/Mnist/src/main/groovy/MnistTrainer.groovy" target="_blank">here</a> using matrices and <a href="https://commons.apache.org/proper/commons-math/" target="_blank">Apache Commons Math</a> for a digit recognition example) but luckily we don't have to. The libraries we are going to use do much of the work for us. They typically provide a fluent API which let's us specify, in a somewhat declarative way, the layers in our network.</p> |
| <p>Just before exploring our examples, we should pre-warn folks that while we do time running the examples, no attempt was made to rigorously ensure that the examples were identical across the different technologies. The different technologies support slightly different ways to set up their respective network layers. The parameters were tweaked so that when run there was typically at most one or two errors in the validation. Also, the initial parameters for the runs can be set with random or pre-defined seeds. When random ones are used, each run will have slightly different errors. We'd need to do some additional alignment of examples and use a framework like <a href="https://github.com/openjdk/jmh" target="_blank">JMH</a> if we wanted to get a more rigorous time comparison between the technologies. Never-the-less, it should give a very rough guide as to the speed to the various technologies.</p> |
| <h3>Encog</h3> |
| <p><a href="https://www.heatonresearch.com/encog/" target="_blank">Encog</a> is a pure Java machine learning framework that was created in 2008. There is also a C# port for .Net users. Encog is a simple framework that supports a number of advanced algorithms not found elsewhere but isn't as widely used as other more recent frameworks.</p> |
| <p>The complete source code for our Iris classification example using Encog is <a href="https://github.com/paulk-asert/groovy-data-science/blob/master/subprojects/Iris/src/main/groovy/NNFF_Encog.groovy" target="_blank" style="background-color: rgb(255, 255, 255);">here</a>, but the critical piece is:</p> |
| <pre style="background-color:#2b2b2b;color:#a9b7c6;font-family:'JetBrains Mono',monospace;font-size:9.8pt;">def model = <span style="color:#cc7832;">new </span>EncogModel(data).tap <span style="font-weight:bold;">{<br></span><span style="font-weight:bold;"> </span>selectMethod(data, <span style="color:#9876aa;font-style:italic;">TYPE_FEEDFORWARD</span>)<br> <span style="color:#9876aa;">report </span>= <span style="color:#cc7832;">new </span>ConsoleStatusReportable()<br> data.normalize()<br> holdBackValidation(<span style="color:#6897bb;">0.3</span>, <span style="color:#cc7832;">true</span>, <span style="color:#6897bb;">1001</span>) <span style="color:#808080;">// test with 30%<br></span><span style="color:#808080;"> </span>selectTrainingType(data)<br><span style="font-weight:bold;">}<br></span><span style="font-weight:bold;"><br></span><span style="color:#cc7832;">def </span>bestMethod = model.crossvalidate(<span style="color:#6897bb;">5</span>, <span style="color:#cc7832;">true</span>) <span style="color:#808080;">// 5-fold cross-validation<br></span><span style="color:#808080;"><br></span>println <span style="color:#6a8759;">"Training error: " </span>+ pretty(<span style="font-size: 9.8pt; color: rgb(152, 118, 170); font-style: italic;">calculateRegressionError</span><span style="font-size: 9.8pt;">(bestMethod, model.</span><span style="font-size: 9.8pt; color: rgb(152, 118, 170);">trainingDataset</span><span style="font-size: 9.8pt;">)</span>)
|
| println <span style="color:#6a8759;">"Validation error: " </span>+ pretty(<span style="font-size: 9.8pt; color: rgb(152, 118, 170); font-style: italic;">calculateRegressionError</span><span style="font-size: 9.8pt;">(bestMethod, model.</span><span style="font-size: 9.8pt; color: rgb(152, 118, 170);">validationDataset</span><span style="font-size: 9.8pt;">))</span></pre> |
| <p>When we run the example, we see:</p> |
| <pre style="background-color:#2b2b2b;color:#a9b7c6;font-family:'JetBrains Mono',monospace;font-size:9.8pt;"><span style="color: rgb(78, 154, 6); font-family: "JetBrains Mono", monospace; font-size: 9.8pt;"><b>paulk@pop-os</b></span><font color="#a9b7c6" face="JetBrains Mono, monospace"><span style="font-size: 9.8pt;">:</span></font><span style="color: rgb(52, 101, 164); font-family: "JetBrains Mono", monospace; font-size: 9.8pt;"><b>/extra/projects/iris_encog</b></span><font color="#a9b7c6" face="JetBrains Mono, monospace"><span style="font-size: 9.8pt;">$ time groovy -cp "build/lib/*" IrisEncog.groovy
|
| 1/5 : Fold #1
|
| 1/5 : Fold #1/5: Iteration #1, Training Error: 1.43550735, Validation Error: 0.73302237
|
| 1/5 : Fold #1/5: Iteration #2, Training Error: 0.78845427, Validation Error: 0.73302237
|
| ...
|
| 5/5 : Fold #5/5: Iteration #163, Training Error: 0.00086231, Validation Error: 0.00427126
|
| 5/5 : Cross-validated score:0.10345818553910753
|
| </span><span style="font-size: 13.0667px;">Training error: 0.0009
|
| Validation error: 0.0991
|
| Prediction errors:
|
| predicted: Iris-virginica, actual: Iris-versicolor, normalized input: -0.0556, -0.4167, 0.3898, 0.2500
|
| Confusion matrix: Iris-setosa Iris-versicolor Iris-virginica
|
| Iris-setosa 19 0 0
|
| Iris-versicolor 0 15 1
|
| Iris-virginica 0 0 10
|
| </span><span style="font-size: 9.8pt;">
|
| real 0m3.073s
|
| user 0m9.973s
|
| sys 0m0.367s</span></font></pre> |
| <p>We won't explain all of the stats, but it basically says we have a pretty good model with low errors in prediction. If you see the <span style="color: rgb(57, 123, 33); font-weight: bold;">green</span> and <span style="color: rgb(49, 24, 115); font-weight: bold;">purple</span> points in the notebook image earlier in this blog, you'll see there are some points which are going to be hard to predict correctly all the time. The confusion matrix shows that the model predicted one flower incorrectly on the validation dataset.</p> |
| <p>One very nice aspect of this library is that it is a single jar dependency!</p> |
| <h3>Eclipse DeepLearning4j</h3> |
| <p><a href="https://deeplearning4j.konduit.ai/" target="_blank">Eclipse DeepLearning4j</a> is a suite of tools for running deep learning on the JVM. It has support for scaling up to <a href="https://spark.apache.org/" target="_blank">Apache Spark</a> as well as some integration with python at a number of levels. It also provides integration to GPUs and C/++ libraries for native integration.</p> |
| <p>The complete source code for our Iris classification example using DeepLearning4J is <a href="https://github.com/paulk-asert/groovy-data-science/blob/master/subprojects/Iris/src/main/groovy/NNFF_Dl4j.groovy" target="_blank" style="white-space: pre; background-color: rgb(255, 255, 255);">here</a><span style="white-space: pre;">, with the main part shown below:</span></p> |
| <pre style="background-color:#2b2b2b;color:#a9b7c6;font-family:'JetBrains Mono',monospace;font-size:9.8pt;">MultiLayerConfiguration conf = <span style="color:#cc7832;">new </span>NeuralNetConfiguration.Builder()<br> .seed(seed)<br> .activation(Activation.<span style="color:#9876aa;font-style:italic;">TANH</span>) <span style="color:#808080;">// global activation<br></span><span style="color:#808080;"> </span>.weightInit(WeightInit.<span style="color:#9876aa;font-style:italic;">XAVIER</span>)<br> .updater(<span style="color:#cc7832;">new </span>Sgd(<span style="color:#6897bb;">0.1</span>))<br> .l2(<span style="color:#6897bb;">1e-4</span>)<br> .list()<br> .layer(<span style="color:#cc7832;">new </span>DenseLayer.Builder().nIn(numInputs).nOut(<span style="color:#6897bb;">3</span>).build())<br> .layer(<span style="color:#cc7832;">new </span>DenseLayer.Builder().nIn(<span style="color:#6897bb;">3</span>).nOut(<span style="color:#6897bb;">3</span>).build())<br> .layer(<span style="color:#cc7832;">new </span>OutputLayer.Builder(LossFunctions.LossFunction.<span style="color:#9876aa;font-style:italic;">NEGATIVELOGLIKELIHOOD</span>)<br> .activation(Activation.<span style="color:#9876aa;font-style:italic;">SOFTMAX</span>) <span style="color:#808080;">// override activation with softmax for this layer<br></span><span style="color:#808080;"> </span>.nIn(<span style="color:#6897bb;">3</span>).nOut(numOutputs).build())<br> .build()<br><br><span style="color:#cc7832;">def </span>model = <span style="color:#cc7832;">new </span>MultiLayerNetwork(conf)<br>model.init()<br><br>model.<span style="color:#9876aa;">listeners </span>= <span style="color:#cc7832;">new </span>ScoreIterationListener(<span style="color:#6897bb;">100</span>)<br><br><span style="color:#6897bb;">1000</span>.times <span style="font-weight:bold;">{ </span>model.fit(train) <span style="font-weight:bold;">}<br></span><span style="font-weight:bold;"><br></span><span style="color:#cc7832;">def </span>eval = <span style="color:#cc7832;">new </span>Evaluation(<span style="color:#6897bb;">3</span>)<br><span style="color:#cc7832;">def </span>output = model.output(test.<span style="color:#9876aa;">features</span>)<br>eval.eval(test.<span style="color:#9876aa;">labels</span>, output)<br>println eval.stats()<br></pre> |
| <p>When we run this example, we see:</p> |
| <pre style="background-color:#2b2b2b;color:#a9b7c6;font-family:'JetBrains Mono',monospace;font-size:9.8pt;"><span style="color:#4E9A06"><b>paulk@pop-os</b></span>:<span style="color:#3465A4"><b>/extra/projects/iris_dl4j</b></span>$ time groovy -cp "build/lib/*" IrisDl4j.groovy
|
| [main] INFO org.nd4j.linalg.factory.Nd4jBackend - Loaded [CpuBackend] backend
|
| [main] INFO org.nd4j.nativeblas.NativeOpsHolder - Number of threads used for linear algebra: 4
|
| [main] INFO org.nd4j.nativeblas.Nd4jBlas - Number of threads used for OpenMP BLAS: 4
|
| [main] INFO org.nd4j.linalg.api.ops.executioner.DefaultOpExecutioner - Backend used: [CPU]; OS: [Linux]
|
| ...
|
| [main] INFO org.deeplearning4j.optimize.listeners.ScoreIterationListener - Score at iteration 0 is 0.9707752535968273
|
| [main] INFO org.deeplearning4j.optimize.listeners.ScoreIterationListener - Score at iteration 100 is 0.3494968712782093
|
| ...
|
| [main] INFO org.deeplearning4j.optimize.listeners.ScoreIterationListener - Score at iteration 900 is 0.03135504326480282
|
|
|
| ========================Evaluation Metrics========================
|
| # of classes: 3
|
| Accuracy: 0.9778
|
| Precision: 0.9778
|
| Recall: 0.9744
|
| F1 Score: 0.9752
|
| Precision, recall & F1: macro-averaged (equally weighted avg. of 3 classes)
|
|
|
|
|
| =========================Confusion Matrix=========================
|
| 0 1 2
|
| ----------
|
| 18 0 0 | 0 = 0
|
| 0 14 0 | 1 = 1
|
| 0 1 12 | 2 = 2
|
|
|
| Confusion matrix format: Actual (rowClass) predicted as (columnClass) N times
|
| ==================================================================
|
|
|
| real 0m5.856s
|
| user 0m25.638s
|
| sys 0m1.752s
|
| </pre> |
| <p>Again the stats tell us that the model is good. One error in the confusion matrix for our testing dataset.<br>DeepLearning4J does have an impressive range of technologies that can be used to enhance performance in certain scenarios.<br /> |
| For this example, I enabled AVX (Advanced Vector Extensions) support but didn't try using the CUDA/GPU support nor make use of any Apache Spark integration. The GPU option might have sped up the application but given the size of the dataset and the amount of calculations needed to train our network, it probably wouldn't have sped up much. For this little example, the overheads of putting the plumbing in place to access native C++ implementations and so forth, outweighed the gains. Those features generally would come into their own for much larger datasets or massive amounts of calculations; tasks like intensive video processing spring to mind.</p> |
| <p>The downside of the impressive scaling options is the added complexity. The code was slightly more complex than the other technologies we look at in this blog based around certain assumptions in the API which would be needed if we wanted to make use of Spark integration even though we didn't here. The good news is that once the work is done, if we did want to use Spark, that would now be relatively straight forward.</p> |
| <p>The other increase in complexity is the number of jar files needed in the classpath. I went with the easy option of using the <span style="color: rgb(66, 66, 66);font-family:'JetBrains Mono',monospace;">nd4j-native-platform</span> dependency plus added the <span style="color: rgb(66, 66, 66);font-family:'JetBrains Mono',monospace;">org.nd4j:nd4j-native:1.0.0-M2:linux-x86_64-avx2</span> dependency for AVX support. This made my life easy but brought in over 170 jars including many for unneeded platforms. Having all those jars is great if users of other platforms want to also try the example but it can be a little troublesome with certain tooling that breaks with long command lines on certain platforms. I could certainly do some more work to shrink those dependency lists if it became a real problem.</p> |
| <p>[For the interested reader, the groovy-data-science repo has other DeepLearning4J examples. The <a href="https://www.cs.waikato.ac.nz/ml/weka/" target="_blank">Weka</a> library can wrap DeepLearning4J as shown for this Iris example <a href="https://github.com/paulk-asert/groovy-data-science/blob/master/subprojects/Iris/src/main/groovy/MLP_Weka.groovy" target="_blank">here</a>. There are also two variants of the digit recognition example we alluded to earlier using <a href="https://github.com/paulk-asert/groovy-data-science/blob/master/subprojects/Mnist/src/main/groovy/OneLayerMLP.groovy" target="_blank">one</a> and <a href="https://github.com/paulk-asert/groovy-data-science/blob/master/subprojects/Mnist/src/main/groovy/TwoLayerMLP.groovy" target="_blank">two</a> layer neural networks.]</p> |
| <h3>Deep Netts</h3> |
| <p><a href="https://www.deepnetts.com/" target="_blank">Deep Netts</a> is a company offering a range of products and services related to deep learning. Here we are using the free open-source <a href="https://www.deepnetts.com/blog/deep-netts-community-edition" target="_blank">Deep Netts community edition</a> pure java deep learning library. It provides support for the Java Visual Recognition API (<a href="https://jcp.org/en/jsr/detail?id=381" target="_blank" style="">JSR381</a>). The expert group from JSR381 released their final spec earlier this year, so hopefully we'll see more compliant implementations soon.</p> |
| <p>The complete source code for our Iris classification example using Deep Netts is <a href="https://github.com/paulk-asert/groovy-data-science/blob/master/subprojects/Iris/src/main/groovy/NNFF_DeepNetts.groovy" target="_blank">here</a> and the important part is below:</p> |
| <pre style="background-color:#2b2b2b;color:#a9b7c6;font-family:'JetBrains Mono',monospace;font-size:9.8pt;"><span style="color:#cc7832;">var </span>splits = dataSet.split(<span style="color:#6897bb;">0.7d</span>, <span style="color:#6897bb;">0.3d</span>) <span style="color:#808080;">// 70/30% split<br></span><span style="color:#cc7832;">var </span>train = splits[<span style="color:#6897bb;">0</span>]<br><span style="color:#cc7832;">var </span>test = splits[<span style="color:#6897bb;">1</span>]<br><br><span style="color:#cc7832;">var </span>neuralNet = FeedForwardNetwork.<span style="color:#9876aa;font-style:italic;">builder</span>()<br> .addInputLayer(numInputs)<br> .addFullyConnectedLayer(<span style="color:#6897bb;">5</span>, ActivationType.<span style="color:#9876aa;font-style:italic;">TANH</span>)<br> .addOutputLayer(numOutputs, ActivationType.<span style="color:#9876aa;font-style:italic;">SOFTMAX</span>)<br> .lossFunction(LossType.<span style="color:#9876aa;font-style:italic;">CROSS_ENTROPY</span>)<br> .randomSeed(<span style="color:#6897bb;">456</span>)<br> .build()<br><br>neuralNet.<span style="color:#9876aa;">trainer</span>.with <span style="font-weight:bold;">{<br></span><span style="font-weight:bold;"> </span><span style="color:#9876aa;">maxError </span>= <span style="color:#6897bb;">0.04f<br></span><span style="color:#6897bb;"> </span><span style="color:#9876aa;">learningRate </span>= <span style="color:#6897bb;">0.01f<br></span><span style="color:#6897bb;"> </span><span style="color:#9876aa;">momentum </span>= <span style="color:#6897bb;">0.9f<br></span><span style="color:#6897bb;"> </span><span style="color:#9876aa;">optimizer </span>= OptimizerType.<span style="color:#9876aa;font-style:italic;">MOMENTUM<br></span><span style="font-weight:bold;">}<br></span><span style="font-weight:bold;"><br></span>neuralNet.train(train)<br><br><span style="color:#cc7832;">new </span>ClassifierEvaluator().with <span style="font-weight:bold;">{<br></span><span style="font-weight:bold;"> </span>println <span style="color:#6a8759;">"CLASSIFIER EVALUATION METRICS</span><span style="color:#cc7832;">\n</span>$<span style="font-weight:bold;">{</span>evaluate(neuralNet, test)<span style="font-weight:bold;">}</span><span style="color:#6a8759;">"<br></span><span style="color:#6a8759;"> </span>println <span style="color:#6a8759;">"CONFUSION MATRIX</span><span style="color:#cc7832;">\n</span>$<span style="color:#9876aa;">confusionMatrix</span><span style="color:#6a8759;">"<br></span><span style="font-weight:bold;">}<br></span></pre> |
| <p>When we run this command we see:</p> |
| <pre style="background-color:#2b2b2b;color:#a9b7c6;font-family:'JetBrains Mono',monospace;font-size:9.8pt;"><span style="color:#4E9A06"><b>paulk@pop-os</b></span>:<span style="color:#3465A4"><b>/extra/projects/iris_graalvm</b></span>$ time groovy -cp "build/lib/*" Iris.groovy
|
| 16:49:27.089 [main] INFO deepnetts.core.DeepNetts - ------------------------------------------------------------------------
|
| 16:49:27.091 [main] INFO deepnetts.core.DeepNetts - TRAINING NEURAL NETWORK
|
| 16:49:27.091 [main] INFO deepnetts.core.DeepNetts - ------------------------------------------------------------------------
|
| 16:49:27.100 [main] INFO deepnetts.core.DeepNetts - Epoch:1, Time:6ms, TrainError:0.8584314, TrainErrorChange:0.8584314, TrainAccuracy: 0.5252525
|
| 16:49:27.103 [main] INFO deepnetts.core.DeepNetts - Epoch:2, Time:3ms, TrainError:0.52278274, TrainErrorChange:-0.33564866, TrainAccuracy: 0.52820516
|
| ...
|
| 16:49:27.911 [main] INFO deepnetts.core.DeepNetts - Epoch:3031, Time:0ms, TrainError:0.029988592, TrainErrorChange:-0.015680967, TrainAccuracy: 1.0
|
| TRAINING COMPLETED
|
| 16:49:27.911 [main] INFO deepnetts.core.DeepNetts - Total Training Time: 820ms
|
| 16:49:27.911 [main] INFO deepnetts.core.DeepNetts - ------------------------------------------------------------------------
|
| CLASSIFIER EVALUATION METRICS
|
| Accuracy: 0.95681506 (How often is classifier correct in total)
|
| Precision: 0.974359 (How often is classifier correct when it gives positive prediction)
|
| F1Score: 0.974359 (Harmonic average (balance) of precision and recall)
|
| Recall: 0.974359 (When it is actually positive class, how often does it give positive prediction)
|
|
|
| CONFUSION MATRIX
|
| none Iris-setosaIris-versicolor Iris-virginica
|
| none 0 0 0 0
|
| Iris-setosa 0 14 0 0
|
| Iris-versicolor 0 0 18 1
|
| Iris-virginica 0 0 0 12
|
|
|
|
|
| real 0m3.160s
|
| user 0m10.156s
|
| sys 0m0.483s
|
| </pre> |
| <p>This is faster than DeepLearning4j and similar to Encog. This is to be expected given our small data set and isn't indicative of performance for larger problems.</p> |
| <p> |
| Another plus is the dependency list. It isn't quite the single jar situation as we saw with Encog but not far off.<br /> |
| There is the Encog jar, the JSR381 VisRec API which is in a separate jar, and a handful of logging jars.</p> |
| <h3>Deep Netts with GraalVM</h3> |
| <p>Another technology we might want to consider if performance is important to us is <a href="https://www.graalvm.org/" target="_blank">GraalVM</a>. GraalVM is a high-performance JDK distribution designed to speed up the execution of applications written in Java and other JVM languages. We'll look at creating a native version of our Iris Deep Netts application. We used GraalVM 22.1.0 Java 17 CE and Groovy 4.0.3. We'll cover just the basic steps but there are other places for additional setup info and troubleshooting help like <a href="https://e.printstacktrace.blog/graalvm-and-groovy-how-to-start/" target="_blank">here</a>, <a href="https://github.com/wololock/gttp" target="_blank">here</a> and <a href="https://simply-how.com/fix-graalvm-native-image-compilation-issues" target="_blank">here</a>.</p> |
| <p>Groovy has two natures. It's dynamic nature supports adding methods at runtime through metaprogramming and interacting with method dispatch processing through missing method interception and other tricks. Some of these tricks make heavy use of reflection and dynamic class loading and cause problems for GraalVM which is trying to determine as much information as it can at compile time. Groovy's static nature has a more limited set of metaprogramming capabilities but allows bytecode much closer to Java to be produced. Luckily, we aren't relying on any dynamic Groovy tricks for our example. We'll compile it up using static mode:</p> |
| <pre style="background-color:#2b2b2b;color:#a9b7c6;font-family:'JetBrains Mono',monospace;font-size:9.8pt;"><br><span style="color:#4E9A06"><b>paulk@pop-os</b></span>:<span style="color:#3465A4"><b>/extra/projects/iris_graalvm</b></span>$ groovyc -cp "build/lib/*" --compile-static Iris.groovy
|
| <br></pre> |
| <p>Next we build our native application:</p> |
| <pre style="background-color:#2b2b2b;color:#a9b7c6;font-family:'JetBrains Mono',monospace;font-size:9.8pt;"><br><span style="color:#4E9A06"><b>paulk@pop-os</b></span>:<span style="color:#3465A4"><b>/extra/projects/iris_graalvm</b></span>$ native-image --report-unsupported-elements-at-runtime \
|
| --initialize-at-run-time=groovy.grape.GrapeIvy,deepnetts.net.weights.RandomWeights \
|
| --initialize-at-build-time --no-fallback -H:ConfigurationFileDirectories=conf/ -cp ".:build/lib/*" Iris
|
| <br></pre> |
| <p>We told GraalVM to initialize <span style="color: rgb(66, 66, 66);font-family:'JetBrains Mono',monospace;">GrapeIvy</span><br /> |
| at runtime (to avoid needing Ivy jars in the classpath since Groovy will lazily load those classes only if<br /> |
| we use <span style="color: rgb(66, 66, 66);font-family:'JetBrains Mono',monospace;">@Grab</span> statements).<br /> |
| We also did the same for the <span style="color: rgb(66, 66, 66);font-family:'JetBrains Mono',monospace;">RandomWeights</span> class to avoid it being locked into a random seed fixed at compile time.</p> |
| <p>Now we are ready to run our application:</p> |
| <pre style="background-color:#2b2b2b;color:#a9b7c6;font-family:'JetBrains Mono',monospace;font-size:9.8pt;"><br><span style="color:#4E9A06"><b>paulk@pop-os</b></span>:<span style="color:#3465A4"><b>/extra/projects/iris_graalvm</b></span>$ time ./iris
|
| ...
|
| CLASSIFIER EVALUATION METRICS
|
| Accuracy: 0.93460923 (How often is classifier correct in total)
|
| Precision: 0.96491224 (How often is classifier correct when it gives positive prediction)
|
| F1Score: 0.96491224 (Harmonic average (balance) of precision and recall)
|
| Recall: 0.96491224 (When it is actually positive class, how often does it give positive prediction)
|
|
|
| CONFUSION MATRIX
|
| none Iris-setosaIris-versicolor Iris-virginica
|
| none 0 0 0 0
|
| Iris-setosa 0 21 0 0
|
| Iris-versicolor 0 0 20 2
|
| Iris-virginica 0 0 0 17
|
|
|
|
|
| real 0m0.131s
|
| user 0m0.096s
|
| sys 0m0.029s
|
| <br></pre> |
| <p>We can see here that the speed has dramatically increased. This is great, but we should note, that using GraalVM often involves some tricky investigation especially for Groovy which by default has its dynamic nature. There are a few features of Groovy which won't be available when using Groovy's static nature and some libraries might be problematical. As an example, Deep Netts has log4j2 as one of its dependencies. At the time of writing, there are still issues using log4j2 with GraalVM. We excluded the <span style="color: rgb(66, 66, 66);font-family:'JetBrains Mono',monospace;">log4j-core</span> dependency and used <span style="color: rgb(66, 66, 66);font-family:'JetBrains Mono',monospace;">log4j-to-slf4j</span> backed by <span style="color: rgb(66, 66, 66);font-family:'JetBrains Mono',monospace;">logback-classic</span> to sidestep this problem.</p> |
| <p>[<b>Update</b>: I put the Deep Netts GraalVM <i>iris </i>application with some more detailed instructions into its own <a href="https://github.com/paulk-asert/groovy-data-science/tree/master/subprojects/IrisGraalVM" target="_blank">subproject</a>.]</p> |
| <h3>Conclusion</h3> |
| <p>We have seen a few different libraries for performing deep learning classification using Groovy.<br /> |
| Each has its own strengths and weaknesses.<br /> |
| There are certainly options to cater for folks wanting blinding fast startup speeds through to options which scale to massive computing farms in the cloud.</p> |