使用深度学习、Groovy 和 GraalVM 对鸢尾花进行分类
作者:_Paul King_
发布时间:2022-06-25 上午 10:52(上次更新时间:2022-06-27 上午 11:16)
Iris 项目位于groovy-data-science 存储库中,专门用于此示例。它包含许多 Groovy 脚本和一个 Jupyter/BeakerX 笔记本,重点介绍了比较和对比各种库和各种分类算法的示例。
涵盖的技术/库 | |
---|---|
数据操作 |
|
分类 |
|
可视化 |
|
涵盖的主要方面/算法 |
|
涵盖的其他方面/算法 |
如果您对任何其他技术感兴趣,请随时浏览这些其他示例和 Jupyter/BeakerX 笔记本。
在本篇博客中,我们只关注深度学习示例。我们将研究使用 Encog、Eclipse DeepLearning4J 和 Deep Netts(使用标准 Java 和使用 GraalVM 的本机映像)的解决方案,但首先进行简要介绍。
深度学习
深度学习属于机器学习和人工智能的分支。它涉及人工神经网络的多个层(因此称为“深度”)。配置此类网络的方法有很多,具体细节超出了本篇博客文章的范围,但我们可以提供一些基本细节。我们将有四个输入节点,对应于我们四个特征的测量值。我们将有三个输出节点,对应于每个可能的_类_(_物种_)。我们还将在它们之间有一个或多个附加层。
此网络中的每个节点在某种程度上都模仿人脑中的神经元。同样,我们将简化细节。每个节点都有多个输入,这些输入被赋予特定的权重,以及一个激活函数,该函数将确定我们的节点是否“触发”。训练模型是一个确定最佳权重应该是什么的过程。
将任何节点的输入转换为输出所涉及的数学运算并不难。我们可以自己编写它(如此处所示,使用矩阵和Apache Commons Math进行数字识别示例),但幸运的是我们不必这样做。我们将要使用的库为我们完成了大部分工作。它们通常提供一个流畅的 API,让我们可以以某种声明的方式指定网络中的层。
在探索我们的示例之前,我们应该预先警告大家,虽然我们确实对运行示例进行了计时,但没有尝试严格确保不同技术之间的示例完全相同。不同的技术支持略有不同的方式来设置它们各自的网络层。对参数进行了调整,以便在运行时验证中通常最多出现一两个错误。此外,可以设置随机或预定义的种子作为运行的初始参数。当使用随机种子时,每次运行的错误都会略有不同。如果我们想在技术之间进行更严格的时间比较,我们需要对示例进行一些额外的对齐并使用像JMH这样的框架。尽管如此,它应该可以非常粗略地指导各种技术的速度。
Encog
Encog是一个纯 Java 机器学习框架,创建于 2008 年。它还为 .Net 用户提供了一个 C# 端口。Encog 是一个简单的框架,支持许多其他框架中没有的高级算法,但它不像其他更新的框架那样被广泛使用。
使用 Encog 进行鸢尾花分类示例的完整源代码在此处,但关键部分是
def model = new EncogModel(data).tap {
selectMethod(data, TYPE_FEEDFORWARD)
report = new ConsoleStatusReportable()
data.normalize()
holdBackValidation(0.3, true, 1001) // test with 30%
selectTrainingType(data)
}
def bestMethod = model.crossvalidate(5, true) // 5-fold cross-validation
println "Training error: " + pretty(calculateRegressionError(bestMethod, model.trainingDataset))
println "Validation error: " + pretty(calculateRegressionError(bestMethod, model.validationDataset))
当我们运行示例时,我们会看到
paulk@pop-os:/extra/projects/iris_encog$ 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 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 real 0m3.073s user 0m9.973s sys 0m0.367s
我们不会解释所有统计数据,但它基本上表示我们有一个非常好的模型,预测误差很低。如果您在本博客前面的笔记本图像中看到绿色和紫色点,您会发现有些点很难一直正确预测。混淆矩阵显示,该模型在验证数据集上错误地预测了一种花。
这个库的一个非常好的方面是它是一个 jar 依赖项!
Eclipse DeepLearning4j
Eclipse DeepLearning4j是一套在 JVM 上运行深度学习的工具。它支持扩展到Apache Spark,以及在多个级别上与 Python 集成。它还提供与 GPU 和 C/++ 库的集成,以实现本机集成。
使用 DeepLearning4J 进行鸢尾花分类示例的完整源代码在此处,主要部分如下所示
MultiLayerConfiguration conf = new NeuralNetConfiguration.Builder()
.seed(seed)
.activation(Activation.TANH) // global activation
.weightInit(WeightInit.XAVIER)
.updater(new Sgd(0.1))
.l2(1e-4)
.list()
.layer(new DenseLayer.Builder().nIn(numInputs).nOut(3).build())
.layer(new DenseLayer.Builder().nIn(3).nOut(3).build())
.layer(new OutputLayer.Builder(LossFunctions.LossFunction.NEGATIVELOGLIKELIHOOD)
.activation(Activation.SOFTMAX) // override activation with softmax for this layer
.nIn(3).nOut(numOutputs).build())
.build()
def model = new MultiLayerNetwork(conf)
model.init()
model.listeners = new ScoreIterationListener(100)
1000.times { model.fit(train) }
def eval = new Evaluation(3)
def output = model.output(test.features)
eval.eval(test.labels, output)
println eval.stats()
当我们运行此示例时,我们会看到
paulk@pop-os:/extra/projects/iris_encog$ 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
统计数据再次告诉我们模型很好。我们的测试数据集的混淆矩阵中有一个错误。DeepLearning4J 确实具有一系列令人印象深刻的技术,可用于在某些情况下提高性能。对于此示例,我启用了 AVX(高级向量扩展)支持,但没有尝试使用 CUDA/GPU 支持,也没有使用任何 Apache Spark 集成。GPU 选项可能会加快应用程序的速度,但考虑到数据集的大小和训练网络所需的计算量,它可能不会加快多少速度。对于这个小例子,安装管道以访问本机 C++ 实现等的开销超过了收益。这些功能通常会在更大的数据集或大量的计算中发挥作用;例如密集的视频处理。
令人印象深刻的扩展选项的缺点是增加了复杂性。与我们在此博客中查看的其他技术相比,代码稍微复杂一些,这是基于 API 中的某些假设,即使我们没有在这里使用 Spark 集成,如果我们想使用它,也需要这些假设。好消息是,一旦工作完成,如果我们确实想使用 Spark,那将相对简单。
复杂性增加的另一个原因是类路径中所需的 jar 文件数量。我选择了使用 nd4j-native-platform
依赖项的简单选项,并添加了 org.nd4j:nd4j-native:1.0.0-M2:linux-x86_64-avx2
依赖项以获得 AVX 支持。这让我轻松了一些,但引入了超过 170 个 jar,其中包括许多不需要的平台。如果其他平台的用户也想尝试该示例,拥有所有这些 jar 会很棒,但对于某些在某些平台上因命令行过长而中断的工具来说,这可能会有些麻烦。如果它成为一个真正的问题,我当然可以做更多的工作来缩减这些依赖项列表。
Deep Netts
Deep Netts 是一家提供与深度学习相关的一系列产品和服务的公司。在这里,我们使用的是免费的开源Deep Netts 社区版纯 Java 深度学习库。它为 Java 视觉识别 API(JSR381)提供支持。JSR381 的专家组在今年早些时候发布了他们的最终规范,因此我们希望很快能看到更多符合规范的实现。
我们使用 Deep Netts 进行 Iris 分类示例的完整源代码在这里,重要部分如下
var splits = dataSet.split(0.7d, 0.3d) // 70/30% split
var train = splits[0]
var test = splits[1]
var neuralNet = FeedForwardNetwork.builder()
.addInputLayer(numInputs)
.addFullyConnectedLayer(5, ActivationType.TANH)
.addOutputLayer(numOutputs, ActivationType.SOFTMAX)
.lossFunction(LossType.CROSS_ENTROPY)
.randomSeed(456)
.build()
neuralNet.trainer.with {
maxError = 0.04f
learningRate = 0.01f
momentum = 0.9f
optimizer = OptimizerType.MOMENTUM
}
neuralNet.train(train)
new ClassifierEvaluator().with {
println "CLASSIFIER EVALUATION METRICS\n${evaluate(neuralNet, test)}"
println "CONFUSION MATRIX\n$confusionMatrix"
}
当我们运行此命令时,我们会看到
paulk@pop-os:/extra/projects/iris_encog$ 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
这比 DeepLearning4J 快,与 Encog 相似。考虑到我们的小数据集,这是意料之中的,并不代表更大问题的性能。
另一个优点是依赖项列表。它不像我们看到的 Encog 那样是单个 jar 文件,但相差不远。有一个 Encog jar 文件、位于单独 jar 文件中的 JSR381 VisRec API 以及一些日志记录 jar 文件。
使用 GraalVM 的 Deep Netts
如果性能对我们很重要,我们可能要考虑的另一项技术是GraalVM。GraalVM 是一个高性能 JDK 发行版,旨在加快以 Java 和其他 JVM 语言编写的应用程序的执行速度。我们将研究创建 Iris Deep Netts 应用程序的本机版本。我们使用了 GraalVM 22.1.0 Java 17 CE 和 Groovy 4.0.3。我们将只介绍基本步骤,但还有其他地方可以提供其他设置信息和故障排除帮助,例如这里、这里和这里。
Groovy 具有两种性质。它的动态特性支持在运行时通过元编程添加方法,并通过丢失方法拦截和其他技巧与方法调度处理交互。其中一些技巧大量使用了反射和动态类加载,这会导致 GraalVM 出现问题,因为 GraalVM 试图在编译时确定尽可能多的信息。Groovy 的静态特性具有一组更有限的元编程功能,但允许生成更接近 Java 的字节码。幸运的是,我们的示例不依赖于任何动态 Groovy 技巧。我们将使用静态模式编译它
paulk@pop-os:/extra/projects/iris_encog$ groovyc -cp "build/lib/*" --compile-static Iris.groovy
接下来我们构建本机应用程序
paulk@pop-os:/extra/projects/iris_encog$ 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
我们告诉 GraalVM 在运行时初始化 GrapeIvy
(避免在类路径中需要 Ivy jar 文件,因为 Groovy 只会在我们使用 @Grab
语句时才延迟加载这些类)。我们对 RandomWeights
类也做了同样的处理,以避免它被锁定在编译时固定的随机种子中。
现在我们准备运行我们的应用程序
paulk@pop-os:/extra/projects/iris_encog$ 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
我们可以在这里看到速度有了显着提高。这很好,但我们应该注意到,使用 GraalVM 通常需要进行一些棘手的调查,尤其是对于默认情况下具有动态特性的 Groovy 而言。Groovy 的一些特性在使用其静态特性时将不可用,并且某些库可能会出现问题。例如,Deep Netts 将 log4j2 作为其依赖项之一。在撰写本文时,将 log4j2 与 GraalVM 一起使用仍然存在问题。我们排除了 log4j-core
依赖项,并使用由 logback-classic
支持的 log4j-to-slf4j
来避开此问题。
结论
我们已经看到了一些使用 Groovy 执行深度学习分类的不同库。每个都有自己的优点和缺点。当然可以选择满足那些希望以极快的启动速度运行的用户,以及可以选择扩展到云中大型计算集群的用户。