本文适合有Java基础的人阅读HelloGitHub的《讲解开源项目》系列。本期是亚马逊工程师:KeerthanVasist的DJL(一个完全用Java构建的深度学习平台)系列的第四期。一、前言长期以来,Java一直是企业非常流行的编程语言。得益于其丰富的生态和维护良好的包和框架,Java拥有庞大的开发者社区。尽管深度学习应用在不断演进和落地,但对于Java开发者来说,框架和类库却很紧缺。当今最流行的深度学习模型是用Python编译和训练的。对于Java开发者来说,想要进入深度学习的世界,需要在学习深度学习的复杂知识的同时,重新学习并接受一门新的编程语言。这使得大多数Java开发人员难以学习和过渡到深度学习开发。为了降低Java开发者学习深度学习的成本,AWS构建了DeepJavaLibrary(DJL),这是一个为Java开发者定制的开源深度学习框架。它为Java开发者提供了一个连接主流深度学习框架的桥梁。在这篇文章中,我们将尝试使用DJL构建一个深度学习模型,并在MNIST手写数字识别任务上对其进行训练。2、什么是深度学习?在正式开始之前,我们先了解一下机器学习和深度学习的基本概念。机器学习是利用统计知识,将数据输入计算机进行训练,完成特定目标任务的过程。这种归纳学习方法允许计算机学习特征并执行一系列复杂任务,例如识别照片中的对象。由于需要编写复杂的逻辑和测量标准,这些任务在传统计算科学领域很难实现。深度学习是机器学习的一个分支,专注于人工神经网络的发展。人工神经网络是通过研究人脑如何学习和实现目标而衍生出的一组计算逻辑。它通过模拟人脑的一部分和神经之间的信息传递过程来实现各种复杂的任务。深度学习中的“深度”来自于我们在人工神经网络中编织构建了很多层,进一步对数据信息进行更深层次的传递。深度学习技术有着广泛的应用,目前被应用于目标检测、动作识别、机器翻译、语义分析等各种现实应用中。3.训练MNIST手写数字识别3.1项目配置可以使用如下gradle配置引入依赖。在这种情况下,我们使用DJL的api包(核心DJL组件)和basicdataset包(DJL数据集)来构建神经网络和数据集。本例中,我们使用MXNet作为深度学习引擎,因此我们将引入两个包,mxnet-engine和mxnet-native-auto。本案例也可以在PyTorch引擎下运行,只需要更换相应的软件包即可。plugins{id'java'}repositories{jcenter()}dependencies{implementationplatform("ai.djl:bom:0.8.0")implementation"ai.djl:api"implementation"ai.djl:basicdataset"//MXNetruntimeOnly"ai.djl.mxnet:mxnet-engine"runtimeOnly"ai.djl.mxnet:mxnet-native-auto"}3.2NDArray和NDManagerNDArray是DJL存储数据结构和数学运算的基本结构。NDArray表示固定长度的多维数组。NDArray的使用类似于Python中的numpy.ndarray。NDManager是NDArray的老大。它负责管理NDArray的生成和回收过程,可以帮助我们更好的优化Java内存。每个NDArray将由一个NDManager创建,并且当NDManager关闭时它们将一起关闭。NDManager和NDArray都是用Java的AutoClosable构建的,它可以确保在运行结束时及时回收。想了解更多它们的用法和实践,可以参考我们之前的文章:DJL的Java玩转多维数组,就像NumPyModel在DJL中,训练和推理都是从Model类构建的。我们这里主要讲训练过程中的构造方法。接下来我们为模型创建一个新目标。因为Model也继承了AutoClosable结构,所以我们用一个try块来实现:try(Modelmodel=Model.newInstance()){...//主要训练代码...}准备数据MNIST(修改国研StandardsandTechnology)数据库包含大量手写数字图形,常用于训练图像处理系统。DJL已经将MNIST数据集收集到basicdataset数据集中,每个MNIST图的大小为28x28。如果你有自己的数据集,也可以通过DJL数据集导入教程将数据集导入到你的训练任务中。数据集导入教程:http://docs.djl.ai/docs/development/how_to_use_dataset.html#how-to-create-your-own-datasetintbatchSize=32;//batchsizeMnisttrainingDataset=Mnist.builder().optUsage(Usage.TRAIN)//训练集.setSampling(batchSize,true).build();MnistvalidationDataset=Mnist.builder().optUsage(Usage.TEST)//验证集.setSampling(batchSize,true).build();此代码分别制作训练集和验证集。我们还随机排列数据集以进行更好的训练。除了这些配置之外,你还可以对图像进行进一步的处理,比如设置图像的大小,对图像进行归一化等。制作模型(buildablock)当你的数据集准备好后,我们就可以搭建一个神经网络了。在DJL中,神经网络由Blocks(代码块)组成。块是具有各种神经网络属性的结构。它们可以表示一个操作,一个神经网络的一部分,甚至是一个完整的神经网络。然后可以顺序或并行执行块。同时,Block本身也可以有参数和子块。这种嵌套结构可以帮助我们构建一个复杂但可维护的神经网络。在训练过程中,每个块附带的参数都会实时更新,包括它们的子块。这种递归更新过程确保整个神经网络得到充分训练。我们在构建这些Blocks的时候,最简单的方式就是一个一个嵌套。通过直接使用DJL-readyBlock类型,我们可以快速创建各种类型的神经网络。根据几种基本的神经网络工作模式,我们提供了Block的几种变体。构造SequentialBlock来处理每个子块的顺序执行。它将前一个子块的输出作为下一个块的输入,一直执行到最后。与之对应的是ParallelBlock,用于将一个输入并行输入到各个子块中,将输出结果按照特定的组合方程进行组合。最后说说LambdaBlock,这是一个帮助用户进行快速操作的区块。它没有任何参数,因此在训练期间不会更新任何部分。让我们尝试创建一个基本的多层感知器(MLP)神经网络。多层感知器是一种简单的前馈神经网络,仅包含几个完全连接的层(LinearBlock)。那么要构建这个网络,我们可以直接使用SequentialBlock。ininput=28*28;//输入层大小intoutput=10;//输出层大小int[]hidden=newint[]{128,64};//隐藏层大小SequentialBlocksequentialBlock=newSequentialBlock();sequentialBlock.add(Blocks.batchFlattenBlock(input));for(inthiddenSize:hidden){//全连接层sequentialBlock.add(Linear.builder().setUnits(hiddenSize).build());//激活函数sequentialBlock.add(activation);}sequentialBlock.add(Linear.builder().setUnits(output).build());当然DJL也提供了一个MLPBlock可以直接使用:Blockblock=newMlp(Mnist.IMAGE_HEIGHT*Mnist.IMAGE_WIDTH,Mnist.NUM_CLASSES,newint[]{128,64});训练在我们准备好数据集和神经网络之后,我们就可以开始训练模型了。在深度学习中,一般通过以下几个步骤来完成一个训练过程:初始化:我们会对每个Block的参数进行初始化,初始化每个参数的函数由Initializer集合决定。前向传播:这一步将输入数据在神经网络中逐层传递,然后产生输出数据。计算损失:我们会根据一个特定的损失函数Loss来计算输出与标注结果的偏差。Backpropagation:在这一步中,可以使用lossbackderivative来计算每个参数的梯度。更新权重:我们会根据选择的优化器(Optimizer)更新Block上每个参数的值。DJL使用Trainer结构来简化整个过程。开发者只需要创建一个Trainer并指定对应的Initializer、Loss和Optimizer即可。这些参数由TrainingConfig设置。我们来看看具体的参数设置:TrainingListener:这是训练过程中设置的监听器。可以实时反馈每个阶段的训练结果。这些结果可以用来记录训练过程或者帮助调试神经网络训练过程中的问题。用户也可以自定义自己的TrainingListener来监听训练过程。DefaultTrainingConfigconfig=newDefaultTrainingConfig(Loss.softmaxCrossEntropyLoss()).addEvaluator(newAccuracy()).addTrainingListeners(TrainingListener.Defaults.logging());try(Trainertrainer=model.newTrainer(config)){//Trainingcode}当trainer生成后,我们就可以定义输入的shape了。然后就可以调用fit函数进行训练了。fit函数会对输入数据进行多个epoch的训练,最后将结果存储在本地目录中。/**MNIST包含28x28灰度图,导入到28*28NDArray中。*第一维是batchsize,这里我们将batchsize设置为1进行初始化。*/ShapeinputShape=newShape(1,Mnist.IMAGE_HEIGHT*Mnist.IMAGE_WIDTH);intnumEpoch=5;StringoutputDir="/build/model";//用输入初始化trainertrainer.initialize(inputShape);TrainingUtils.fit(trainer,numEpoch,trainingSet,validateSet,outputDir,"mlp");这就是训练过程的全过程!用DJL训练还容易吗?然后看训练结果每一步的输出。如果您使用我们的默认侦听器,输出类似于以下内容:[INFO]-Downloadinglibmxnet.dylib...[INFO]-Trainingon:cpu().[INFO]-LoadMXNetEngineVersion1.7.0in0.131ms.Training:100%|█████████████████████████████████████████|精度:0.93,Softmax交叉熵损失:0.24,speed:1235.20items/secValidating:100%|██████████████████████████████████████████|[INFO]-Epoch1finished.[INFO]-Train:Accuracy:0.93,SoftmaxCrossEntropyLoss:0.24[INFO]-Validate:Accuracy:0.95,SoftmaxCrossEntropyLoss:0.14Training:100%|█████████████████████████████████████████|准确度:0.97,Softmax交叉熵损失:0.10,速度:2851.06项/秒验证:100%|█████████████████████████████████████████|[信息]-Epoch2finished.NG[1m41s][INFO]-Train:Accuracy:0.97,SoftmaxCrossEntropyLoss:0.10[INFO]-Validate:Accuracy:0.97,SoftmaxCrossEntropyLoss:0.09[INFO]-trainP50:12.756ms,P90:21.044ms[INFO]-forwardP50:0.375ms,P90:0.607ms[INFO]-training-metricsP50:0.021ms,P90:0.034ms[INFO]-backwardP50:0.608ms,P90:0.973ms[INFO]-stepP50:0.543ms,P90:0.869ms[INFO]-epochP50:35.989s,P90:35.989s当训练结果完成后,我们就可以使用刚才的模型来推理识别手写数字了。如果刚才的内容不是很清楚,可以参考下面两个链接直接尝试训练。手写数据集训练:https://docs.djl.ai/examples/docs/train_mnist_mlp.html手写数据集推理:https://docs.djl.ai/jupyter/tutorial/03_image_classification_with_your_model.html四、最后在本文中,我们介绍了深度学习的基本概念,以及如何优雅地使用DJL来构建和训练深度学习模型。DJL还提供更多样化的数据集和神经网络。如果你有兴趣学习深度学习,你可以看看我们的Java深度学习书。Java深度学习书籍:https://zh.d2l.ai/DeepJavaLibrary(DJL)是一个基于Java的深度学习框架,同时支持训练和推理。DJL博采众长,建立在多个深度学习框架(TenserFlow、PyTorch、MXNet等)之上,同时具备多个框架的优良特性。您可以轻松地使用DJL来训练然后部署您的模型。它还具有强大的模型库支持:只需一行代码即可轻松读取各种预训练模型。现在DJL的模型库同时支持来自GluonCV、HuggingFace、TorchHub和Keras的多达70个模型。项目地址:https://github.com/awslabs/djl/
