Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
本页将向您全面介绍如何运行DL4J示例,启动您自己的项目。建议您加入我们的QQ交流群。您可以在QQ上请求帮助、提出反馈,不过也请您在遇到问题时先参考本指南中已列出的疑难解答。如果您是初次接触深度学习,我们准备了一份初学者学习计划,包括课程、阅读材料和其他资源的链接。
由于项目的进展速度超出了我们翻译文档的速度,因此请同时参考英文文档。
每一个机器学习工作流至少由两部分组成。第一部份是加载你的数据并准备它用于学习。我们将此部分称为ETL(提取、转换、加载)过程。DataVec是我们为让构建数据管道更容易而构建的库。第二部分是实际的学习系统本身。这是DL4J的算法核心。
所有的深层学习都是基于向量和张量的,DL4J依赖于一个叫做ND4J的张量库。它为我们提供了处理n维数组(也叫做张量)的能力。由于其不同的后端,它甚至使我们能够同时使用CPU和GPU。
与其他机器学习或深度学习框架不同,DL4J将加载数据和训练算法的任务视为单独的过程。你不只是把模型指向在磁盘上保存的数据,而是使用DataVec加载数据。这为你提供了更大的灵活性,并保留了简单数据加载的便利性。
在算法开始学习之前,你必须准备好数据,即使你已经有了一个经过训练的模型。准备数据意味着加载数据并将其置于正确的形状和值范围(例如,归一化、零均值和单位方差)。从头开始构建这些过程是容易出错的,因此尽可能使用DataVec。
Deeplearning4j可以处理许多不同的数据类型,比如图像、CSV、ARFF、纯文本,并且通过Apache Camel集成,可以处理几乎任何其它可以想到的数据类型。
要使用DataVec,需要连同RecordReaderDataSetIterator一起使用一个RecordReader接口的实现。
一旦你有了一个DataSetIterator, 它是一个描述顺序访问数据的模式,你可以使用它得到适合训练神经网络模型的格式的数据。
当它们被馈送的数据被归一化时,神经网络工作得最好,数据被限制在1到1之间。这样做有几个原因。一个是使用梯度下降训练网络,并且它们的激活函数通常在-1和1之间的某个范围。即使使用不会很快饱和的激活函数,将你的值限制到这个范围以提高性能仍然是很好的实践。
在DL4J中归一化数据相当简单,取决于你想要怎样归一化你的数据,并为你的DataSetIterator设置相关的DataNormalization作为预处理器。
ImagePreProcessingScaler显然是图像数据的不错的选择。如果你在输入数据的所有维度上具有统一的范围,那么NormalizerMinMaxScaler是一个不错的选择,并且NormalizerStandardize是你在其他情况下通常使用的工具。
如果你需要其他类型的归一化,你也可以自由地实现DataNormalization接口。
如果你使用NormalizerStandardize,请注意这是一个取决于从数据中提取的统计信息的一个归一化器,所以你必须同模型一起保存这些统计信息,以便你可以在恢复模型时恢复它们。
顾名思义,DataSetIterator会返回DataSet对象。DataSet对象是数据的特征和标签的容器。但它们并不局限于一次只持有一个实例。数据集可以包含需要的多个实例。
它通过在几个INDArray实例中保存这些值:一个用于实例的特性,一个用于标签,以及另外两个用于屏蔽,如果你正在使用时间序列数据(参见使用RNN/Masking,了解更多信息)。
INDArray是ND4J中使用的n维数组或张量之一。在特征的情况下,它是实例大小数x特征数量的矩阵。即使只有一个实例,它也会有这种形状。
为什么它不同时包含所有的数据实例?
这是深入学习的另一个重要概念:迷你批处理。为了产生准确的结果,经常需要大量真实世界的训练数据。通常,这是比在可用内存中拟合的数据更多,所以有时将其存储在单个数据集中是不可能的。但是,即使有足够的数据存储,还有一个重要的原因不立即使用所有的数据。那就是使用小批量,你可以在一次训练中获得更多的更新。
那么,为什么要在数据集中有一个以上的例子呢?由于模型是使用梯度下降训练,它需要一个良好的梯度学习如何最小化误差。一次只使用一个示例将创建只考虑当前示例产生的错误的梯度。这会使学习行为不稳定,减慢学习,甚至可能导致不可用的结果。
一个小批量应该足够大,以提供真实世界(或者至少是你的数据)的代表性样本。这意味着它应该始终包含你想要预测的所有类,并且这些类的计数应该以与总体数据中的类分布大致相同。
DL4J为数据科学家和开发人员提供了工具,用于在一个高层使用的概念上构建一个深度神经网络,例如 layer。它使用一个构建器模式来声明性地构建神经网络,正如您在这个(简化的)示例中可以看到的:
如果你熟悉其他的深度学习框架,你会注意到这有点像Keras。
与其他框架不同,DL4J从更新器算法中分离优化算法。这允许灵活性,当你寻求一个优化器与更新器来为你的数据和问题最好的工作。
除了上面示例中看到的DenseLayer和OutputLayer之外,还有其他几种层类型,如GravesLSTM、卷积层、RBM、EmbeddingLayer等。使用这些层,你不仅可以定义简单的神经网络,还可以定义递归和卷积网络。
在你配置你的神经网络之后,你必须训练你的模型。最简单的情况是在模型上简单的调用.fit()方法,并用你的DataSetIterator作为配置参数。这将在你所有的数据上一次性训练你的模型。在整个数据集上传递一次叫做一次训练。DL4J 有多个不同的方法来多次传递数据。
最简单的方法,是重置你的DataSetIterator并在fit的调用上循环你想要的次数。这种方法可以训练你的模型一直到你认为你的训练有良好的拟合。
然而还有一种方法是使用EarlyStoppingTrainer。只要你喜欢,你可以配置这个训练器用于你想要的训练次数。它将在每个训练之后评估你的网络性能(或者你 所配置的任何阶段),并保存性能最好的版本供以后使用。
还要注意的是,DL4J不仅支持多层次网络的训练,而且还支持更灵活的计算图
当你训练你的模型时,你会想测试它的性能。对于该测试,你将需要一个专用的数据集,该数据集将不用于训练,而是仅用于评估模型。这些数据应该与你想用模型进行预测的真实世界数据有相同的分布。不能简单地将训练数据用于评估的原因是因为机器学习方法容易过拟合(擅长对训练集进行预测,但在较大的数据集上表现不佳)。
评价类用于评价。略有不同的方法适用于评估一个正常的前馈网络或循环网络。有关使用它的更多细节,请查看相应的示例。
建立神经网络来解决问题是一个经验过程。也就是说,它需要反复试验。因此,你必须尝试不同的设置和体系结构,以便找到性能良好的神经网络配置。
DL4J提供了一个监听器工具,帮助你直观地监视网络的性能。你可以为你的模型设置监听器,在每个小批量处理之后调用。DL4J最常用的监听器之一是ScoreIterationListener。检查更多的Listeners。
虽然ScoreIterationListener将简单的打印你的网络的当前错误分数,HistogramIterationListener将启动一个网页界面来提供你一组可以用来微调网络配置的不同信息。查看可视化,网络学习监控与调试来知道如何解释这些数据
查看神经网络模型故障排查获取如何改进结果的更多信息。
新手深度学习的路线图。
你从哪里开始取决于你已经知道了什么。
真正理解深度学习的先决条件是线性代数、微积分和统计学,以及编程和一些机器学习。应用它的先决条件是学习如何部署模型。
在DL4J的场景中,你应该很了解JAVA并且对类似IntelliJ IDE的工具和自动化构建工具Maven感到舒服。
下面你会找到一个资源列表。这些章节大致按它们有用的顺序组织起来。
Patrick Winston人工智能介绍 @MIT (面向那些对人工智能的调查感兴趣的人)
Andrej Karpathy斯坦福卷积神经网络课程 (面向那些对图片识别感兴趣的人)
深度学习所涉及的数学基本上是线性代数、微积分和概率,如果你在本科阶段学习了这些,你将能够理解深层学习论文中的大部分思想与符号。如果没有在大学里学习过,就不要害怕。有很多免费的资源(有一些在这个网站上)。
机器学习所涉及的线性代数; Patrick van der Smagt
如果你还不知道如何编程,你可以从Java开始,但你可能会发现其他语言更容易。Python和Ruby资源可以在更快的反馈循环中传达基本思想。“学习Python的硬方法”和“学习编程(Ruby)”是两个很好的开始。
一个Vim基础教程 (Vim 是一种用命令行访问的编辑器。)
如果你想不使用JAVA而直接使用深度学习,我们推荐Theano和各种可以顶替它的Python框架,包括Keras 和 Lasagne。
一旦你有了编程基础,就可以着手Java,这是世界上使用最广泛的编程语言。世界上大多数大型组织都使用巨大的Java代码库。(总是有JAVA的作品)(大数据栈—Hadoop, Spark, Kafka, Lucene, Solr, Cassandra, Flink –在很大程度上是为Java的计算环境JVM编写的。
带着那些,我们推荐你通过它的 例子来走近DL4J。
快速入门
你也可以下载一个 智能层免费版, 它是支持 Python, Java 和 Scala 机器学习和数据科学的工具 。 SKIL是一个可以在prem和云端工作的机器学习后端,可以装载你的软件,提供一个机器学习模型服务器 。
我们所知道的关于深度学习的大部分内容都包含在学术论文中。你可以在这里找到一些主要的研究小组。 虽然个别课程对他们所能教的东西有限制,但互联网却没有。大多数的数学和编程问题可以通过Google和搜索如 Stackoverflow 和 Math Stackexchange。
Eclipse基金会项目的IP/版权要求
本页说明向eclipse/deeplearning4j github代码库中的项目贡献代码所需的步骤:https://github.com/eclipse/deeplearning4j
贡献者(任何想将代码提交到存储库的人)在合并他们的代码之前需要做两件事:
签署Eclipse贡献者协议(一次)
签署提交(每次)
所有Eclipse基础项目都必须满足这两个要求,而不仅仅是DL4J和ND4J: https://projects.eclipse.org/
通过签署ECA,你实际上是在明确你提交的代码是你编写的,或者你有权为项目做出贡献。这是避免版权问题的必要法律保护。
通过签署你的提交,你可以明确该特定提交中的代码是你自己的。
你只需要签署一次Eclipse贡献者协议(ECA)。流程如下:
步骤1:注册Eclipse帐户
可以在这里完成 https://accounts.eclipse.org/user/register
注意:你必须使用与你的github帐户(要提交请求的GitHub帐户)相同的电子邮件进行注册。
步骤2:签署ECA
去 https://accounts.eclipse.org/user/eca 并按说明操作。
有几种方法可以签署提交。请注意,你可以使用这些选项中的任何一个。
选项1:在命令行提交时使用 -s
在这里签署提交很简单:
注意,-s(小写s)-大写S(即,-S)用于GPG签名(见下文)。
选项2:设置Bash别名(“或Windows cmd别名”)用于自动签署
例如,可以在bash中设置以下别名:
然后提交将使用以下操作完成:
对于Windows命令行,可以通过一些机制使用类似的选项(请参见此处)
一种简单的方法是创建包含以下内容的gcm.bat文件,并将其添加到系统路径中:
然后,你可以使用与上面相同的过程提交(即gcm“My commit”)
选项3: 使用GPG签署
要获取GPG签署详情,查看此链接
注意,这个选项可以与别名组合使用(见上文),如alias gcm=-git commit-S-m'-注意GPG签署的大写字母-S。
选项4: 使用带自动签署的IntelliJ提交
可用于执行git提交,包括通过签署提交。有关详细信息,请参阅本页。
在执行提交之后,你可以使用几种不同的方法进行签入。一种方法是使用git log--show signature-1来显示最后一次提交的签署(例如,使用-5来显示最后5次提交)
输出如下:
顶部提交是未签署的,而底部提交是已签署的(请注意存在已签名者)。
如果忘记签署上次提交,可以使用以下命令:
假设你的分支有3个新提交,所有提交都是未签署的:
一个简单的方法是压缩并签署这些提交。要在最后3次提交时执行此操作,请使用以下命令:(注意,可能需要先进行备份)
结果:
你可以使用如前所示显示git log -1 --show-signature
来确认提交已签署。
请注意,一旦将提交合并为master,你的提交将被粉碎,因此丢失提交历史并不重要。
如果你正在更新现有的PR,则可能需要强制使用 -f(如git push X -f
)。
从源代码构建所有DL4J库的说明。
注意:大多数用户应该使用Maven Central上的快速入门指南,而不是从源代码构建。
除非你有一个非常好的从源码构建的理由(例如开发新的特性——不包括自定义层、自定义激活函数、自定义丢失函数等——所有这些都可以在不直接修改DL4J的情况下添加),否则不应该从源码构建。从源码上构建可能相当复杂,在很多情况下都没有好处。
对于那些喜欢使用最新版本的Deeplearning4j或新建分支并构建自己的版本的开发人员和工程师,这些说明将指导你构建和安装Deeplearning4j。首选安装目的地是你本地机器的Maven仓库。如果不使用主分支,可以根据需要修改这些步骤(即:切换GIT分支并修改build-dl4j-stack.sh脚本)。
本地构建需要构建完整的Deeplearning4j栈,包括:
请注意,Deeplearning4j被设计用于大多数平台(Windows、OS X和Linux),并且还包括多种“风格”,取决于你选择使用的计算架构。这包括CPU(OpenBLAS,MKL,ATLAS)和GPU(CUDA)。DL4J堆栈还支持X86和PowerPC架构。
在尝试构建和安装DL4J栈之前,本地机器将需要设置一些基本的软件和环境变量。根据你的平台和操作系统的版本,在使它们工作时指令可能有所不同。该软件包括:
git
cmake (3.2或更高版本)
OpenMP
gcc (4.9 或更高版本)
maven (3.3 或更高版本)
架构专用软件包括:
CPU 选项:
Intel MKL
OpenBLAS
ATLAS
GPU 选项:
CUDA
IDE-指定要求:
IntelliJ Lombok plugin
DL4J 测试依赖:
dl4j-test-resources
Ubuntu假设你使用Ubuntu作为你的Linux偏好,并且作为非root用户运行,请按照以下步骤安装必备软件:
Homebrew是安装必备软件的公认方法。假设你在本地安装了Homebrew,请按照以下步骤安装必要的工具。
首先,在使用Homebrew之前,我们需要确保安装一个最新版本的XCODE(它被用作主编译器):
最后, 安装必备工具:
注意:你不能使用CLANG。你也不使能用GCC的新版本。如果你有一个更新版本的GCC,请用这个这个链接切换版本。
libnd4j 依赖 Unix 工具包来编译。 所以为了编译它你需要安装Msys2 。
按他们的说明安装了MyS2之后,你将不得不安装一些额外的开发包。启动msys2 shell并设置DEV环境:
这将在MyS2 shell中安装所需使用的依赖项。
你还需要设置PATH环境变量来包括C:\msys64\mingw64\bin
(或者你决定安装msys2的任何位置)。如果打开了IntelliJ(或其他IDE),则必须在此更改对通过它们启动的应用程序生效之前重新启动它。如果不这样做,你可能会看到一个“Can’t find dependent libraries”的错误。
一旦安装了必备工具,现在就可以为平台安装所需的架构。
在所有现有的CPU架构中,英特尔MKL是目前最快的。但是,在实际安装之前,它需要一些“开销”。
Ubuntu 假设你正在使用Ubuntu,你可以通过如下安装 OpenBLAS :
你还需要确保/opt/OpenBLAS/lib
(或OpenBLAS的任何其他主目录)在你的路径上。为了让OpenBLAS与Apache Spark一起工作,你还需要做如下操作:
CentOS 用root用户在终端(或ssh会话)输入如下:
之后,你 应该在终端上看到很多活动和安装。为了验证你有,例如,GCC,请输入此行:
去这里获得更多完整说明。
你可以在OS X用Homebrew安装OpenBLAS:
msys2 的
OpenBLAS包已经可用,你可以用pacman命令安装。
Ubuntu 在Ubuntu上有一个ATLAS的apt包可以用:
CentOS 你可以在 CentOS 上按如下按装ATLAS:
在OS X上安装ATLAS是一个有点复杂和漫长的过程。但是,下面的命令将适用于大多数机器:
在这里可以找到安装GPU架构(如CUDA)的详细说明。
CUDA后端在构建之前有一些附加的要求:
Visual Studio 2012 or 2013 (Please note请注意: CUDA 7.5及以下版本不支持Visual Studio 2015)
为了构建CUDA后端,你必须首先调用vcvars64.bat
来设置更多的环境变量。但首先,设置系统环境变量 SET_FULL_PATH
为true
,所以所有vcvars64.bat设置
的变量都传递到mingw shell。
在正常的cmd.exe命令提示符内,运行C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\bin\amd64\vcvars64.bat
在理面运行 c:\msys64\mingw64_shell.bat
切换到 libnd4j 目录
./buildnativeoperations.sh -c cuda
这将构建CUDA nd4j.dll.
如果你是通过一个IDE如IntelliJ构建Deeplearning4j,你将需要安装一些插件,以确保你的IDE使得代码高亮适当。你需要为Lombok安装一个插件:
IntelliJ Lombok 插件: https://plugins.jetbrains.com/plugin/6317-lombok-plugin
Eclipse Lombok 插件: Follow instructions at https://projectlombok.org/download.html
如果你想在ScalNet、Scala API或某些模块(如DL4J UI)上工作,则需要确保IDE安装了Scala支持,并且该支持可用。
Deeplearning4j使用一个单独的存储库,包含测试所需的所有资源。这是为了保持中央DL4J存储库轻量级,避免Git历史中的大blobs。为了运行测试,你需要从https://github.com/deeplearning4j/dl4j-test-resources(约10GB)安装测试资源。如果你不关心历史,只做一个浅克隆。
测试将只在testresources
和后端profile(例如,test-nd4j-native)被选择时运行。
[图片上传中...(image-13982b-1540534628348-8)]
运行测试需要一段时间。要运行一个单独的Maven模块的测试,可以添加一个模块约束,用-pl deeplearning4j-core(
详情见此处)。
在运行DL4J栈构建脚本之前,必须确保在运行构建之前定义了某些环境变量。下面将根据你的架构进行概述。
你将需要知道运行DL4J构建脚本的目录的确切路径(鼓励你使用干净的空目录)。否则,你的构建将失败。一旦确定了这条路径,就将/libnd4j
添加到该路径的末端,并将其导出到本地环境中。这看起来像:
你可以在构建时连接MKL或是在运行时连接一个二进制文件,初始化时连接另外的BLAS实现,例如OpenBLAS。要根据MKL进行构建,只需将包含libmkl_rt.so
(或Windows上的mkl_rt.dll
)的路径(例如/path/to/intel64/lib/
)添加到Linux上的LD_LIBRARY_PATH
环境变量(或Windows上的PATH)中,然后像以前一样构建。在Linux上,为了确保它使用OpenMP的正确版本,我们也可能需要设置这些环境变量:
当libnd4j无法重建时,我们可以在事实之后使用MKL库,并在运行时加载它们,而不是OpenBLAS,但是事情有点棘手。请另外按照下面的说明进行操作。
确保这些文件例如 /lib64/libopenblas.so.0
和 /lib64/libblas.so.3
都不可用 (或出现在Windows的PATH参数之后
), 或者他们将被LBND4J通过他们的绝对路径加载,在其他事情之前。
在/path/to/intel64/lib/
内部,创建libmkl_rt.so
(或Windows上的 mkl_rt.dll
)到libnd4j需要加载的名称的符号链接或副本,例如:
最后,将/path/to/intel64/lib/
添加到LD_LIBRARY_PATH
环境变量(或者在Windows上的PATH
路径),并像往常一样运行Java应用程序。
如果你愿意,你可以手动在DL4J栈中构建每一块。每个软件块的过程基本上是:
Git clone
Build
Install
整个过程如下面的命令所示,但libnd4j’s ./buildnativeoperations.sh
接受基于你正在构建的后端的参数。你需要按照他们给出的顺序来执行这些指令。如果你不这样做,你的执行就会出错误。下面的特定于GPU的指令已经被注释掉了,但是在构建GPU后端时,应该取代特定于CPU的命令。
一旦你将DL4J栈安装到本地maven存储库之后,现在可以将其引入在构建工具的依赖项中。按照Deeplearning4j的经典入门说明,在主 POM上用SNAPSHOT版本适当地替换当前使用的版本。
请注意,一些构建工具,如Gradle和SBT没有正确地引入平台特定的二进制文件。你可以按照这里的说明来设置你最喜欢的构建工具。
如果在本地构建时遇到问题,Deeplearning4j的Early Adopters Channel是专门用于帮助解决构建问题和其他源代码问题的通道。请在Gitter上获取帮助。
如何贡献Eclipse Deeplearning4j源代码。
在做出贡献之前,确保你知道所有的Eclipse DL4J库的结构。早在2018年初,所有的库都入住在Deeplearning4j monorepo。这些包括:
DeepLearning4J: 包含用于既在单个机器上,又在分布式上学习神经网络的所有代码。
ND4J: “Java的n维数组”。ND4J是建立DL4J的数学后端。所有的DL4J神经网络都是使用ND4J中的运算(矩阵乘法、向量运算等)来构建的。ND4J是DL4J实现在没有改变网络本身的情况化,即可以CPU又可以GPU训练网络的原因。 没有Nd4J,就不会有DL4J。
DataVec: DataVec处理管道侧的数据导入和转换。如果你 想将图像、视频、音频或简单CSV数据导入DL4J:你可能想要使用DataVec来实现。
Arbiter: Arbiter是一种用于神经网络超参数优化的软件包。超参数优化是指自动选择网络超参数(学习速率、层数等)以获得良好性能的过程。
我们也有一个额外的示例仓库在 dl4j-examples.
有多种方式为DeepLearning4J (和相关项目)做贡献,这取决于你的兴趣和经验。这有一些建议:
添加一个新的神经网络类型 (例如: 不同类型的 RNNs, 本地连接网络等)
添加一个新的训练特征
修复缺陷
DL4J 示例: 那儿有我们没有示例的程序或神经网络架构吗?
测试性能和识别瓶颈或可以改进的地方。
改进网站的文档(或写教程等)
改进java文档
这有很多不同的方法来寻找工作。这些包括:
回顾我们的路线图
与开发人员交谈,特别是我们的早期使用者栏目
回顾最近的论文和博客文章关于训练特征、神经网络架构和应用。
回顾网站和例子-有什么什么似乎缺少,不完整,或只是有用的(或酷)?
在你开始做之前,有一些事情你需要知道。特别是我们使用的工具:
Maven: 一个依赖性管理和构建工具,用于我们所有的项目。有关Maven的详细信息,请参见此。
Git: 我们使用的版本控制系统
Project Lombok: Lombok项目是一种代码生成/注释工具,其目的是减少Java中所需的“多余”代码(即标准重复代码)的数量。要使用源代码,你需要为IDE安装Lombok插件。
VisualVM:分析工具,最有效的识别性能问题和瓶颈。
IntelliJ IDEA: 这是我们的IDE选择,当然,你可能会使用替代品,如Eclipse和NetBeans,你可能会发现使用与开发人员相同的IDE更容易避免出现问题。但这取决于你。
要记住的事情:
代码应该符合Java 7 或以上
如果你添加一个新的方法或类:添加java文档
为一个明显的功能添加添加一个作者标签是受大家欢迎的。这也可以帮助特征贡献者,以防他们需要问原作者问题。如果多个作者出现在一个类中:提供谁做了什么的细节(“初始实现”,“添加一个特征”等)
在你的代码中提供有益的注释。这有助于保持所有代码的可维护性。
任何新的功能都应该包括单元测试(使用JUnit)来测试代码。这应该包括边缘情况。
如果添加新的层类型,则必须按照这些单元测试包含数值梯度检查。这些是必要的,以确认计算的梯度是正确的。
如果你正在添加重要的新功能,请考虑更新网站的相关部分,并提供一个示例。毕竟,没有人知道的功能(或者没有人知道如何使用)是没有用的。添加合适的文档是非常受到鼓励的,但不是严格要求的。
如果你对某些事不确定,请在Gitter 上咨询我们。
顶尖的JVM深度学习框架:Eclipse Deeplearning4j的事实与介绍
Eclipse DeepLearning4j是一个用Java和Scala开发的开源分布式深度学习项目,由总部位于旧金山的商业智能和企业软件公司Konduit的员工牵头。我们是一个由数据科学家、深度学习专家、Java系统工程师和半感知机器人组成的团队。
当你在训练一个分布式的深度学习网络时,有很多事情要做。我们已经尽了最大努力来解释它们,因此Eclipse Deeplearning4j可以作为在Hadoop和其他文件系统上工作的Java、Scala和Clojure程序员的DIY工具。
Deeplearning4j 已经在 Wired, GigaOM, Businessweek, Venturebeat, The Wall Street Journal, Fusion 与 Java Magazine 出现过。
如果你计划出版学术论文并希望引用 Deeplearning4j,请使用这种格式:
Eclipse Deeplearning4j 开发团队。 Deeplearning4j: 用于JVM的开源分布式学习, Apache 软件基金许可2.0. http://deeplearning4j.org
由 YourKit 分析支持。
Eclipse Deeplearning4J的硬件设置,包括GPU和CUDA。
你可以通过更改ND4J的POM.xml文件中的依赖项来为后端线性代数操作选择GPU或本地CPU。你的选择将影响应用程序中正在使用的ND4J和DL4J。
如果你的CUDA v9.2+已经安装并且有NVIDIA兼容的硬件,那么你的依赖声明将看起来像:
否则,你将需要使用ND4J的本地实现作为CPU后端:
如果你在多个操作系统/系统架构上开发项目,则可以在artifactId
的末尾添加-platform
,该artifactId
将下载大多数主要系统的二进制文件。
如果你有多个GPU但你的系统迫使你 只能用一个,你可以用 helper CudaEnvironment.getInstance().getConfiguration().allowMultiGPU(true);
作为你的 main()方法的第一行
。
查看我们的 CuDNN 页。
在NVIDIA 网站 上查看CUDA安装说明
为DL4J应用程序设置可用内存/RAM
ND4J使用堆外内存来存储NDArray,以便在与来自本地代码(如BLAS和CUDA库)的NDAArray一起工作时提供更好的性能。
“堆外”意味着内存被分配到JVM(Java虚拟机)之外,因此不受JVM垃圾收集(GC)的管理。在Java/JVM方面,我们只保存指向堆外内存的指针,这些指针可以通过JNI传递给底层C++代码,用于ND4J操作。
为了管理内存分配,我们使用两种方法:
JVM 垃圾回收 (GC) 和弱引用追踪
内存工作间 - 阅读 内存工作间指引 获取详情
尽管这两种方法之间存在差异,但想法是相同的:一旦Java端不再需要NDArray,与其关联的堆外内存应该被释放,以便以后再使用。GC和“内存工作空间”方法之间的区别在于何时以及如何释放内存。
对于JVM/GC内存:每当垃圾收集器收集INDArray时,它的堆外内存将被释放,假设它没有被其他地方使用。
对于内存工作间:每当INDArray离开工作区范围时——例如,当一个层完成前向传递/预测时——其内存可以在不释放和重新分配的情况下被重用。这导致如神经网络训练和推理这样周期性的工作有更好的性能。
对于DL4J/ND4J,有两种类型的内存限制需要注意和配置:堆上JVM内存限制和堆外内存限制(NDArray所在的位置)。这两个限制都是通过Java命令行参数来控制的:
-Xms
-这定义了JVM堆将在应用程序启动时使用多少内存。
-Xmx
- 这允许你指定JVM堆内存限制(最大值,在任何点)。如果需要的话,最多只分配给这个数量(在JVM的自由裁量权下)。
-Dorg.bytedeco.javacpp.maxbytes
- 这允许你指定堆外内存限制。
-Dorg.bytedeco.javacpp.maxphysicalbytes
- 这指定了整个进程的最大字节-通常设置为maxbytes+Xmx加上一些额外的字节,以防其它库还需要一些堆外内存。与设置maxbytes
的设置不同,maxphysicalbytes
是可选的。
示例: 配置 1GB 初始化堆内存, 2GB 最大堆内存, 8GB 堆外内存, 10GB 进程最大内存:
对于GPU系统,maxbytes和maxphysicalbytes设置当前也有效地定义了GPU的内存限制,因为堆外内存被(通过NDArray)映射到GPU—请阅读下面的GPU部分中的更多信息。
对于许多应用程序,你希望JVM堆中使用的RAM更少,堆外使用的RAM更多,因为所有NDArray都存储在那里。如果对JVM堆分配太多,就不会有足够的内存留给堆外内存。
如果你得到一个“RuntimeException: Can’t allocate [HOST] memory: xxx; threadId: yyy”,你已经用完了堆外内存。你应该最常使用一个工作间配置来处理你的NDArrays分配,特别的在例如训练或评估/推断循环-如果你没有这么做,NDArray及其堆外(和GPU)资源使用JVM GC回收,这可能引入严重的延迟和可能的内存不足情况。
如果不指定JVM堆限制,默认情况下,它将使用系统总RAM的1/4作为限制。
如果不指定堆外内存限制,默认情况下将使用JVM堆限制(Xmx)的2倍。即-XMX8G将意味着8GB可以被JVM堆使用,而16GB可以被堆外的ND4J使用。
在有限的内存环境中,使用把-Xmx值
和-Xms值都设为很大
通常是一个坏主意。这是因为这样做不会留下足够的堆外内存。好比一个16GB系统,其中你设置-Xms14G:16GB的14GB将分配给JVM,仅留下2GB用于堆外内存、OS和所有其他程序,这样并不合理。
当使用nd4j-native
后端时,ND4J支持使用内存映射文件代替RAM。一方面,它比RAM慢,但另一方面,它允许你以不可能的方式分配内存块。
以下是示例代码:
在这种情况下,将创建一个1GB临时文件并会被映射,NDArray X将在该空间中被创建。显然,当你需要的NDArrays不能适合放入你的RAM时,这个选项是可行的。
当使用GPU时,通常你的CPU使用的RAM往往会大于GPU使用的RAM。当GPU RAM小于CPU RAM时,需要监视堆外使用了多少RAM。你可以根据上面指定的JavaCPP选项来检查。
我们在GPU上分配你指定大小的堆外内存的。我们不会使用超过GPU的内存。也允许你指定大于GPU的堆空间(这是不鼓励的,但这是可能的)。如果这样做,当运行作业时,GPU将耗尽RAM。
我们也在CPU RAM上分配堆外内存。这是为了CPU与GPU的有效通信,以及CPU从NDArray访问数据,而无需每次调用时都从GPU获取数据。
如果JavaCPP或GPU抛出内存不足错误(OOM),或者即使由于GPU内存有限导致计算速度减慢,如果可能,你可能希望减小批处理大小或增加允许JavaCPP分配的堆外内存量。
尝试运行一个堆外内存等于你的GPU的RAM。同时,请记住使用Xmx
选项设置一个小的JVM堆空间。
注意,如果你的GPU的RAM小于2G,它可能无法用于深度学习。如果情况是这样,你应该考虑使用你的CPU。典型的深度学习工作负载应该至少有4GB的RAM。尽管这样也是很小。GPU上的8GB RAM被推荐用于深度学习工作负载。
可以使用具有CUDA后端的HOST-only的内存。这可以使用工作区来完成。
示例:
不建议直接使用只使用HOST-only数组,因为它们会极大地降低性能。但是,它们与INDArray.unsafeDuplication()
方法一起作为内存缓存对,可能是很有用的 。
为Deeplearning4j配置Maven构建工具。
你可以通过Maven使用DL4J,将以下内容添加到你的pom.xml
:
下面的指令适用于所有DL4J和ND4J子模块,例如 deeplearning4j-api
, deeplearning4j-scaleout
和ND4J后端。
DL4J依赖于ND4J用于硬件特定的实现和张量操作。通过将下面的代码粘贴到你的pom.xml文件
中来添加后端:
你还可以把 GPUs 替换为标准的CPU实现
为Deeplearning4j配置构建工具。
虽然我们鼓励Deeplearning4j、ND4J和DataVec用户使用Maven,但是为如何为其它工具配置构建文件编写文档是值得的,就像Ivy, Gradle 和 SBT,特别是对于Android项目,Google更喜欢Gradle而不是Maven。
下面的指令适用于所有DL4J和ND4J子模块,例如deeplearning4j-api、deeplearning4j-scaleout和ND4J后端。
你可以在Gradle上使用DL4J,通过添加如下代码到你的build.gradle的依赖块中
通过添加如下代码到你的 build.gradle 来添加一个后端:
你还可以把 GPUs 替换为标准的CPU实现。
你可以通过添加如下代码到build.sbt以便在SBT中使用DL4J:
通过添加如下代码到你的 build.sbt 来添加一个后端:
你还可以把 GPUs 替换为标准的CPU实现。
你可以通过添加如下代码到ivy.xml以便在ivy中使用DL4J:
通过添加如下代码到你的 ivy.xml 来添加一个后端:
你还可以把 GPUs 替换为标准的CPU实现。
Clojure程序员可能希望使用Leiningen或 Boot与Maven一起工作。这里有一个Leiningen教程。
注意:你仍然需要下载ND4J、DataVec和Deeplearning4j,或者双击Maven/Ivy/Gradle下载的相应JAR文件,以便在Eclipse安装中安装它们。
在 DL4J中使用NVIDIA cuDNN 库
DL4J支持CUDA,但可以进一步通过cuDNN加速。大多数2D 卷积神经网络层(如ConvolutionLayer、SubsamplingLayer等),以及LSTM和BatchNormalization层都支持cuDNN。
要让DL4J加载cuDNN,我们只需要添加一个依赖项deeplearning4j-cuda-9.2
, deeplearning4j-cuda-10.0
, 或 deeplearning4j-cuda-10.1
, 例如 :
或
或
cuDNN的实际库没有捆绑,因此请确保从NVIDIA下载并安装适合你的平台的包
注意cuDNN和CUDA有多个组合被支持。当前,DL4J的支持下面的组合:
CUDA 版本
cuDNN 版本
9.2
7.2
10.0
7.4
10.1
7.6
10.2
7.6
要安装,只需将库提取到本地库使用的系统路径中找到的目录即可。最简单的方法是将它放在默认目录中的CUDA之外的其他库中。 (/usr/local/cuda/lib64/
在 Linux上, /usr/local/cuda/lib/
在 Mac OS X, 并且 C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v9.2\bin\
, C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v10.0\bin\
, 或 C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v10.1\bin\
在 Windows上)。
或者,对于CUDA 9.2,cuDNN与用于CUDA的JavaCPP Presets 的“redist”包捆绑在一起。在同意许可之后,我们可以添加以下依赖项,来取代安装CUDA和cuDNN:
还要注意,默认情况下,DL4j将使用根据cuDNN可用的最快算法,但是内存使用可能溢出,导致奇怪的启动错误。当这种情况发生时,尝试通过使用通过网络配置设置的NO_WORKSPACE模式来减少内存使用, 替换默认的ConvolutionLayer.AlgoMode.PREFER_FASTEST
,例如:
使用每日版本访问最新的Eclipse Deeplearning4J功能。
ND4J后端配置
我们提供仓库的自动化日常构建,如ND4J, DataVec, DeepLearning4j, RL4J等等,所以所有最新的功能和最新的bug修复都是每天发布的。
快照像任何其他的Maven依赖一样工作。唯一的区别是,它们是从自定义存储库提供的,而不是从Maven Central提供的。
由于正在进行的开发,快照版应该被认为比发布版更不稳定:在正常开发过程中,原则上可以在任何时候引入破坏性的变化或错误。通常情况下,应在可能的情况下使用发布版本(不是快照),除非需要修复错误或需要新的功能。
步骤1: 若要在项目中使用快照,应将像这样将快照仓库信息添加到你的POM.xml文件中:
步骤2: 请确保指定快照版本。我们遵循一个简单的规则:如果最新的稳定发布版本是A.B.C
,快照版本将是A.B.(C+1)-SNAPSHOT
。当前快照版本为 1.0.0-SNAPSHOT
。有关pom.xml文件的存储库部分的更多细节,请参见Maven 文档
如果使用类似DL4J示例的属性,则更改:从版本:
改为版本:
使用快照的示例pom.xml
这里提供了示例pom.xml:使用快照的示例pom.xml。这是从DL4J独立示例项目中获取的,并且使用上面的步骤1和2进行了修改。原件(使用最新版本)可以在这里找到
-platform
(所有操作系统)和单操作系统(非平台)快照依赖项都已释放。由于快照的多平台构建特性,-platform
工件可能(虽然很少)暂时不同步,这可能会导致构建问题。
如果您只在一个平台上构建和部署,那么使用如下non-platform构件会比较安全
在Maven中使用快照依赖时可能有用的两个命令如下:
-U
- 例如, 在 mvn package -U
. 这个 -U
选项强制Maven去检查 (并且有必要的话,下载)最新的快照版。这在你需要确保你有绝对最新快照版时很有用。
-nsu
- 例如, 在 mvn package -nsu
. 这个 -nsu
选项停止Maven正在进行的快照版检查。但是,请注意,只有当你有一些快照依赖项已经下载到本地Maven缓存 (.m2 目录)中时,你的构建才会成功使用此选项。
另一种方法(1)是在<repositories>节点
设置 <updatePolicy>always</updatePolicy>
另一种方法(2)是在<repositories>节点
设置设置<updatePolicy>never</updatePolicy>
快照不能与Gradle一起工作。你必须使用Maven下载文件。之后,你可以尝试使用 mavenLocal()
使用本地Maven仓库。
最小文件像这样:
应该在理论上工作,但事实并非如此。这是因为Gradle的一个缺陷。带有快照的Gradle和Maven分类器似乎有问题。
值得注意的是,当使用Gradle上的ND4J本地后端(和SBT -但不是Maven)时,需要添加OpenBLAS作为依赖项。我们在-platform pom上为你做了这件事。参考-platform pom 这里来重复检查你的依赖关系。请注意,这些是版本属性。有关运行 nd4j-native所需的OpenBLAS和javacpp presets当前版本,请参阅POM的<properties>
节点。
ND4J/Deeplearning4j中的 CPU与AVX支持
AVX(高级向量扩展)是一组用于加速数值计算的CPU指令。更多细节见维基百科。
请注意,AVX仅适用于x86设备的nd4j-native(CPU)后端,而不适用于GPU和ARM/PPC设备。
AVX的重要性:性能。您希望使用系统支持的最高级别AVX编译的ND4J版本。
不同CPU的AVX支持-摘要:
大多数现代x86 CPU:支持AVX2
一些高端服务器CPU:AVX512可能被支持
旧CPU(2012年之前)和低功耗x86(Atom、Celeron):不支持AVX(通常)
注意,支持更高版本AVX的CPU还包括所有更早版本。这意味着可以在支持AVX512的系统上运行通用X86或AVX2二进制指令。但是,不可能在不支持这些指令的CPU上运行为更高版本(如AVX512)构建的二进制指令。
在版本1.0.0-beta6和更高版本中,如果未优化配置AVX,则可能会收到以下警告:
如前所述,为了获得最佳性能,您应该使用与CPU支持的AVX级别相匹配的ND4J版本。
对于nd4j/nd4j-platform依赖项,ND4J默认配置(仅包括nd4j-native 或 nd4j-native-platform依赖项而不包括maven分类器配置)是“generic x86”(无AVX)。
要配置AVX2和AVX512,需要为适当的架构指定一个分类器。
为X86体系结构提供了以下二进制文件(nd4j-native分类器):
Generic x86 (no AVX): linux-x86_64
, windows-x86_64
, macosx-x86_64
AVX2: linux-x86_64-avx2
, windows-x86_64-avx2
, macosx-x86_64-avx2
AVX512: linux-x86_64-avx512
示例:在Windows上配置AVX2(Maven pom.xml)
示例:在Linux上配置AVX512(Maven pom.xml)
请注意,您需要nd4j-native依赖项-有或没有分类器。
在上面的例子中,假设Maven属性 nd4j.version
设置为适当的ND4J版本, 1.0.0-beta6
SameDiff中使用的变量类型、它们的属性以及如何切换这些类型。
定义或传递每个SameDiff
实例的所有值(无论是权重、偏差、输入、激活或常规参数)都由SDVariable
类的对象处理。
观察到变量,我们通常指的不仅仅是单个值——就像在描述自动微分的各种在线示例中所做的那样——而是它们的整个多维数组。
SameDiff
中的所有变量都属于四种变量类型之一,构成枚举VariableType
。它们在这里:
VARIABLE
: 是网络的可训练参数,例如层的权重和偏差。当然,我们希望它们都被存储起来以供进一步使用——我们说,它们是持久化的——也在训练期间被更新。
CONSTANT
: 是那些参数,像变量一样,对于网络来说是持久化的,但是没有被训练;但是,它们可以由用户在外部更改。
PLACEHOLDER
: 存储要从外部提供的临时值,如输入和标签。 因此,由于新占位符的值是在每次迭代时提供的,所以它们不会被存储:换句话说,与VARIABLE
和CONSTANT
不同,PLACEHOLDER
不是持久化的。
ARRAY
: 也是临时值,表示SameDiff中操作的输出,例如向量的和、层的激活等等。它们在每次迭代时都要重新计算,因此,与PLACEHOLDER
一样,它们不是持久化的。
要推断特定变量的类型,可以使用getVariableType
方法,如下所示:
可以使用getArr
或getArr(true)
获取INDArray
形式的变量的当前值,如果希望程序在变量值未初始化时抛出异常,则使用后者。
每个变量中的数据也有其数据类型,包含在DataType
中。目前,在DataType
中有三种浮点类型:FLOAT、DOUBLE和HALF;四种整数类型:LONG、INT、SHORT和UBYTE;一种布尔类型BOOL
—所有这些类型都称为数值类型。此外,还有一个字符串类型称为UTF8
;还有两个helper数据类型COMPRESSED
和UNKNOWN
。16位浮点格式BFLOAT16
和无符号整数类型(UINT16
、UINT32
和UINT64
)将在1.0.0-beta5中提供。
要推断变量的数据类型,请使用
您可能需要跟踪变量的数据类型,因为有时它确实很重要,您在操作中使用哪些类型。例如,一个卷积积,比如这个
将要求其SDVariable
参数input
和weights
为浮点数据类型之一,否则将抛出异常。另外,正如我们下面将要讨论的,VARIABLE
类型的所有SDVariables
都应该是浮点类型的。
在我们讨论变量之间的差异之前,让我们先看看它们共享的属性
所有变量最终都来自SameDiff
的一个实例,作为其图。实际上,每个变量都有一个SameDiff
作为其字段之一。
所有操作的结果(输出)均为ARRAY
类型。
操作中涉及的所有SDVariable
都属于同一个SameDiff
。
所有变量都可能有名字,也可能没有名字——在后一种情况下,名字实际上是自动创建的。无论哪种方式,名称都必须是唯一的。我们将回到下面的命名。
现在让我们更仔细地看看每种类型的变量,以及它们之间的区别。
变量是网络的可训练参数。这就预先决定了他们在SameDiff
中的本性。如前所述,变量的值既需要为应用程序保留,也需要在训练期间更新。训练意味着,我们迭代地用梯度的一小部分来更新值,这只有当变量是浮点类型时才有意义(见上面的数据类型)。
变量可以使用SameDiff
实例中不同版本的var函数添加到SameDiff
中。例如,代码
将一个784x10个float
数字数组的变量,一个单层MNIST感知器中的权重,这是一个预先存在的SameDiff
实例samediff
。
但是,通过这种方式,变量中的值将被设置为零。您也可以使用预设INDArray
中的值创建变量。这样
将创建一个充满正态分布随机生成数的变量,方差为1/28
。当然,您可以使用任何其他数组创建方法,而不是nrand
或任何预设数组。此外,您还可以使用一些流行的初始化方案,例如:
现在,权重将使用Xavier方案随机初始化。有其他方法可以创建和填充变量:您可以在[我们的javadoc]的'known subclasses'部分中查找它们(https://deeplearning4j.org/api/latest/org/nd4j/weightinit/WeightInitScheme.html").
常量保存存储的值,但与变量不同的是,在训练期间保持不变。例如,这些可能是您希望在网络中拥有的一些超参数,可以从外部访问。或者它们可能是您希望保持不变的神经网络的预训练权重(请参阅下面更改变量类型中的更多内容)。常数可以是任何数据类型。
例如,int
和boolean
可以与float
和double
一起使用。
通常,常量是通过constant
方法添加到SameDiff
中的。常数可以从INDArray
创建,如下所示:
可以使用scalar
方法之一创建由单个标量值组成的常量:
同样,我们引用javadoc作为整个参考。
SameDiff
中最常见的占位符通常是输入和标签(如果适用)。可以创建任何数据类型的占位符,具体取决于在其中使用它们的操作。要向SameDiff
添加占位符,可以调用占位符方法之一,例如:
如MNIST中。在这里,我们指定你的占位符的名称,数据类型和形状-在这里,我们有28x28个灰度图片呈现为1d向量(因此784)来成批的长度,我们事先不知道(因此-1)。
ARRAY
类型的变量显示为SameDiff
中操作的输出。因此,数组类型变量的数据类型取决于它所生成的操作类型和它的参数的变量类型。数组不是持久化的——它们是一次性的值,将在下一步从头开始重新计算。但是,与占位符不同,梯度是为它们计算的,因为这些是更新VARIABLE
值所必需的。
创建数组类型变量的方法和创建操作的方法一样多,因此您最好关注我们的操作部分、javadoc和示例。
让我们在一个表中总结变量类型的主要属性:
标题
可训练
梯度
持久化
工作间
数据类型
从何处实例化
VARIABLE
Yes
Yes
Yes
Yes
Float only
Instance
CONSTANT
No
No
Yes
No
Any
Instance
PLACEHOLDER
No
No
No
No
Any
Instance
ARRAY
No
Yes
No
Yes
Any
Operations
我们还没有讨论“工作间”的含义—如果您不知道,不用担心,这是一个内部技术术语,基本上描述了内存是如何在内部管理的。
您也可以更改变量类型。目前,有三种选择:
有时-例如,如果执行迁移学习-您可能希望将变量转换为常量。这样做:
其中someVariable
是VARIABLE
类型的。变量someVariable
将不再被训练。
相反,如果常量是浮点数据类型,则可以转换为变量。所以,举个例子,如果你希望你的冻结权重能够再次训练
占位符也可以转换为常量-例如,如果需要冻结其中一个输入。对数据类型没有任何限制,但是,由于占位符值不是持久化的,在将它们转换为常量之前,应该设置它们的值。这可以按如下方式进行
现在不可能将常量转换回占位符,如果需要,我们可以考虑添加此功能。现在,如果您希望有效地冻结占位符,但能够再次使用它,请考虑为它提供常量值,而不是将其转换为常量。
SameDiff
获取变量回想一下SameDiff
实例中的每个变量都有其唯一的String
名。SameDiff
实际上是按变量名跟踪变量,并允许您使用getVariable(String name)
方法检索它们。
请考虑以下几行:
这里,在函数sub
中,我们实际上已经隐式地引入了一个变量(ARRAY
类型),它保存了减法的结果。通过在操作的参数中添加一个名称,我们保证了从其他地方检索变量的可能性:例如,如果以后需要推断标签和作为向量的预测之间的差异,可以只写:
如果您的整个SameDiff
实例在其他地方初始化,并且您仍然需要掌握它的一些变量(例如,多个输出),那么这将变得特别方便。
您可以获取和设置SDVariable
的名称,方法分别是getVarName
和setVarName
。重命名时,请注意变量的名称在其SameDiff
中保持唯一。
可以使用eval()
方法以INDArray
的形式检索任何变量的当前值。注意,对于非持久化变量,应该首先设置该值。对于具有梯度的变量,也可以使用getGradient
方法推断梯度的值。
在DL4J中,工作间是一种有效的内存分页模型。
ND4J提供了一个额外的内存管理模型:工作间。这允许你在没有用于堆外内存跟踪的JVM垃圾回收器的情况下,重用循环工作负载的内存。换句话说,在工作间循环结束时,所有的数组内存内容都会失效。工作间被集成到DL4J中进行训练和推理。
基本思想很简单:你可以在工作间(或空间)内执行你需要的操作,并且如果你要从其去除一个INDArray(即,将结果移出工作空间),只需调用INDArray.detach(),你将获得一个独立的INDArray副本。
对于DL4J用户来说,工作间提供了更好的性能,并且默认情况下从1.0.0-alpha开始启用。因此,对于大多数用户,不需要明确的工作间配置。
为了从工作间中受益,它们需要被启用。你可以使用以下方式配置工作间模式:
在你的神经网络中使用 .trainingWorkspaceMode(WorkspaceMode.SEPARATE)
或 .inferenceWorkspaceMode(WorkspaceMode.SINGLE)
。
SEPARATE工作间和SINGLE工作间之间的差异是性能和内存占用之间的折衷:
SEPARATE 稍微慢一点,但是使用更少的内存。
SINGLE 稍微快一点,但使用更多内存。
也就是说,可以使用不同的模式进行训练和推理(即,使用SEPARATE进行训练,并使用SINGLE进行推理,由于推理仅涉及前馈循环,而不涉及反向传播或更新器)。
通过启用工作间,在训练期间使用的所有内存将被重用和跟踪,而不会受到JVM GC干扰。唯一例外的是在内部使用前馈循环的工作间(如果启用的话)的output()
方法。随后,它将产生的INDArray
从工作间中分离出来,从而为您提供独立的INDArray
,该INDArray
将由JVM GC处理。
请注意:在1.0.0-alpha发行版之后,DL4J中的工作间被重构——SEPARATE/SINGLE模式已经被废弃,并且用户应该使用ENABLED来代替。
如果你的训练过程使用工作间,建议你禁用(或减少周期性GC调用的频率)。可以这样做:
把它放到 model.fit(...)
调用之前。
对于ParallelWrapper
,还添加了工作间模式配置选项。这样,每个训练线程将使用一个单独的工作间,连接到指定的设备。
我们提供异步预获取迭代器、AsyncDataSetIterator
和AsyncMultiDataSetIterator
,它们通常在内部使用。
这些迭代器可选地使用特殊的循环工作间模式来获得较小的内存占用。在这种情况下,工作空间的大小将由从底层迭代器出来的第一个DataSet
的内存需求决定,而缓冲区大小由用户定义。如果内存需求随时间变化(例如,如果使用可变长度的时间序列),则工作间将被调整。
警告:如果你使用的是自定义迭代器或RecordReader,请确保在第一次next()调用中没有初始化大型对象。在构造函数中这样做,以避免不希望的工作间增长。
警告:使用了AsyncDataSetIterator
,DataSets应
在调用next()
数据集之前被使用。你不应该以任何方式存储它们,也不应不调用detach()
。否则,在DataSet内用于INDArrays的内存最终将在AsyncDataSetIterator
内被覆盖。
如果由于某种原因,你不希望将迭代器包装到异步预获取器中(例如,用于调试目的),则提供特殊的包装器:AsyncShieldDataSetIterator
和AsyncShieldMultiDataSetIterator
。基本上,这些只是薄的包装,防止预获取。
通常,评估假设使用model.output()方法,该方法本质上返回与工作空间分离的INDArray
。在训练过程中定期评估的情况下,最好使用内置的方法进行评估。例如:
这段代码将在iteratorTest
上运行一个单独的周期,并且它将在不需要任何额外INDArray
分配的情况下更新两个IEvaluation
实现(或更少/更多,如果你要求)。
也有一些情况,比如,你缺少RAM,可能希望释放你无法控制的所有工作空间;例如,在评估或训练期间。
那么可以这样做:Nd4j.getWorkspaceManager().destroyAllWorkspacesForCurrentThread();
此方法将销毁在调用线程中创建的所有工作空间。如果你自己在一些外部线程中创建了工作间,那么可以在不再需要工作间之后,在该线程中使用相同的方法。
如果工作区使用不正确(例如,在自定义层或数据管道中的缺陷),你可能会看到一个错误消息,如:
DL4J的层API包含一个“层工作区管理器”的概念。
这个类的思想是,它允许我们在给定工作间的不同的可能配置的情况下,轻松且精确地控制给定数组的位置。例如,层外的激活可以在推理期间放置在一个工作间中,而在训练期间放置在另一个工作间中;这是出于性能原因。然而,使用层工作间管理器设计,层的实现者不需要为此而烦恼。
这在实践中意味着什么?通常很简单…
当返回 (activate(boolean training, LayerWorkspaceMgr workspaceMgr)
方法),确保返回的数组已在 ArrayType.ACTIVATIONS
(i.e., 使用 LayerWorkspaceMgr.create(ArrayType.ACTIVATIONS, …) 或类似)中定义
当返回激活梯度 (backpropGradient(INDArray epsilon, LayerWorkspaceMgr workspaceMgr)
),类似的返回一个在 ArrayType.ACTIVATION_GRAD 中定义的数组。
你还可以在适合的工作间使用一个在任何工作间定义的数组,例如:LayerWorkspaceMgr.leverageTo(ArrayType.ACTIVATIONS, myArray)
注意,如果你没有实现自定义层(而是只想对MultiLayerNetwork/ComputationGraph之外的层执行转发),那么可以使用LayerWorkspaceMgr.noWorkspaces()
。
如何在SameDiff图中添加微分函数和其他操作。
要开始使用SameDiff,请熟悉GitHub上ND4J API的autodiff模块。
不管好坏,SameDiff代码只组织在几个关键的地方。对于SameDiff的基本使用和测试,以下模块是关键。我们将更详细地讨论其中的一些。
functions
: 这个模块有基本的构建块来构建SameDiff变量和图。
execution
: 拥有与SameDiff图执行相关的所有内容。
gradcheck
: 用于检查SameDiff梯度的实用程序功能,其结构类似于DL4J中的相应工具。
loss
: SameDiff的损失函数
samediff
: 主要用于定义、设置和运行SameDiff操作和图形的SameDiff模块。
functions
模块中的微分函数请参阅GitHub上的functions
模块。
functions
模块的中心抽象是DifferentialFunction
,它几乎是SameDiff中所有内容的基础。在数学上,我们在SameDiff中所做的是建立一个有向无环图,它的节点是微分函数,我们可以计算梯度。在这方面,DifferentialFunction
构成了一个基本层次上的SameDiff图。
注意,每个DifferentialFunction
函数都有一个SameDiff实例。我们稍后再讨论SameDiff
和这段关系。另外,虽然只有很少的关键抽象,但它们实际上在任何地方都被使用,所以几乎不可能单独讨论SameDiff概念。最后,我们将讨论每个部分。
每个微分函数都有属性。在最简单的情况下,微分函数只有一个名字。根据所讨论的操作,通常会有更多的属性(考虑卷积中的步幅或内核大小)。当我们从其他项目(TensorFlow、ONNX等)导入计算图时,这些属性需要映射到我们内部使用的约定。attributeAdaptersForFunction
、mappingsForFunction
、propertiesForFunction
和resolvePropertiesFromSameDiffBeforExecution
方法是您希望在开始时查看的内容。
定义属性并正确映射后,分别为TensorFlow和ONNX import调用initFromTensorFlow和initFromOnnx。稍后,当我们讨论构建SameDiff操作时,将详细介绍这一点。
使用函数属性对输入列表执行微分函数,并生成一个或多个输出变量。您可以访问许多帮助函数来设置或访问这些变量:
args()
: 返回所有输入变量。
arg()
: 返回第一个输入变量(唯一一个用于一元操作)。
larg()
与 rarg()
: 返回二进制操作的第一个和第二个(读“left”和“right”)参数
outputVariables()
: 返回所有输出变量的列表。这取决于操作,可以动态计算。正如我们稍后将看到的,要获得具有单个输出的ops的结果,我们将调用.outputVariables()[0]。
处理输出变量是很棘手的,也是使用和扩展SameDiff的一个陷阱。例如,可能需要为微分函数实现calculateOutputShape
,但如果实现不正确,则可能导致难以调试的失败。(注意,SameDiff最终将调用libnd4j
中的操作执行,动态自定义操作要么推断输出形状,要么需要提供正确的输出形状。)
微分函数的自动微分是用一种方法实现的:doDiff。每个操作都必须提供doDiff的实现。如果您正在为libnd4j
op x实现SameDiff
操作,并且幸运地找到了x_bp
(如“反向传播”),那么您可以使用它,并且doDiff
实现基本上是自由的。
您还将看到内部使用的diff
实现并调用doDiff
。
重要的是,每个微分函数都可以通过调用f()
访问factory(DifferentialFunctionFactory
的实例)。更准确地说,这将返回微分函数具有的SameDiff实例的工厂:
这在许多地方都有使用,并允许您访问当前在SameDiff中注册的所有微分函数。把这家工厂看作是一个操作提供者。下面是一个将sum
暴露给DifferentialFunctionFactory
的示例:
我们故意省略了函数参数。注意,我们所做的只是重定向到ND4J中其他地方定义的Sum操作,然后返回第一个输出变量(SDVariable
类型,将在第二个中讨论)。现在不考虑实现细节,这允许您从任何可以访问微分函数工厂的地方调用f().sum(...)
。例如,当实现SameDiff op x
并且函数工厂中已经有x_bp
时,可以重写x
的doDiff
SameDiff
中图的构建与执行请参阅GitHub上的samediff
模块。
不足为奇,这就是魔法发生的地方。这个模块具有SameDiff操作的核心结构。首先,让我们看看构成SameDiff操作的变量。
SDVariable
(读取SameDiff变量)是DifferentialFunction
的扩展,它对SameDiff的作用就像INDArray
对老ND4J的作用一样,特别是SameDiff图对这些变量进行操作,每个单独的操作都会接收并输出一个SDVariable列表。SDVariable带有一个名称,配备一个SameDiff实例,具有形状信息,并且知道如何使用ND4J WeightInitScheme
初始化自身。您还可以找到一些助手来设置和获取这些属性。
SDVariable
可以做的少数事情之一就是DifferentialFunction
不能通过调用eval()
评估其结果并返回底层的INDArray
。这将在内部运行SameDiff并获取结果。类似的getter是getArr(),您可以在任何时候调用它来获取此变量的当前值。此功能广泛用于测试,以断言正确的结果。SDVariable
还可以通过gradient()
访问其当前梯度。初始化时不会有任何梯度,通常在稍后的点计算。
除了这些方法之外,SDVariable还提供了具体操作的方法(在这方面与DifferentialFunctionFactory
有点相似)。例如,定义add
如下:
允许对两个SameDiff变量调用c=a.add(b)
,其结果可由c.eval()
访问。
SameDiff
类是该模块的主要工作程序,它汇集了迄今为止讨论的大多数概念。有点不幸的是,反过来也是正确的,SameDiff
实例在某种程度上是所有其他SameDiff
模块抽象的一部分(这就是为什么您已经多次看到它)。一般来说,SameDiff
是自动微分的主要入口点,您可以使用它来定义一个符号图,该图对SDVariables
执行操作。一旦构建,SameDiff
图可以通过几种方式运行,例如exec()
和execandResult()
。
让自己相信调用SameDiff()
会创建很多很多东西!本质上,SameDiff将收集并允许您访问(就getter和setter而言)
图的所有微分函数及其所有属性,可以通过各种方式(如名称或id)访问。
所述功能的所有输入和输出信息。
所有函数属性以及如何映射它们、propertiesToResolve
和propertiesForFunction
都特别值得注意。
SameDiff
也是向SameDiff
模块公开新操作的地方。实际上,您可以为DifferentialFunctionFactory
实例f()
中的各个操作编写一个小包装器。下面是交叉积的一个例子:
在这一点上,查看并运行几个示例可能是一个好主意。SameDiff
测试是一个很好的来源。下面是一个如何将两个SameDiff
变量相乘的示例
这个例子取自SameDiffTests,它是一个主要的测试源,在这里您还可以找到一些完整的端到端的例子。
你发现测试的第二个地方是samediff 。无论何时向SameDiff添加新操作,都要为前向传播和梯度检查添加测试。
第三组相关测试存储在imports中,包含用于导入TensorFlow和ONNX图的测试。另外,这些导入测试的资源是在我们的TFOpsTests项目中生成的。
我们已经看到了DifferentialFunctionFactory和SameDiff如何获取ND4J操作,以便在不同级别将它们暴露给SameDiff。至于实际实现这些操作,您需要知道一些事情。在libnd4j中,您可以找到两类操作,这里将详细介绍这两类操作。我们将演示如何实现这两种操作类型。
所有的操作都是在这里进行的,而且大多数情况下,很明显要把操作放在哪里。要特别注意层,这是为深度学习层实现(如Conv2D)保留的。这些高级操作基于模块的概念,类似于pytorch中的模块或TensorFlow中的层。这些层操作实现还提供了更多涉及操作实现的源。
遗留(或XYZ)操作是具有特征“XYZ”签名的老一代ND4J操作。下面是如何在ND4J中通过包装libn4j中的cos遗留操作来实现cosine:cosine实现。说到SameDiff,遗留操作的好处是它们已经在ND4J中可用,但是需要通过SameDiff特定的功能来增强才能通过测试。因为余弦函数没有任何属性,所以这个实现很简单。使此操作SameDiff兼容的部分包括:
如果仔细观察,这只是事实的一部分,因为Cos
扩展了实现其他SameDiff功能的BaseTransformOp
。(注意,BaseTransformOp
是一个BaseOp
,它早期扩展了DifferentialFunction
。)例如,calculateOutputShape
就是在这里实现的。如果您想实现一个新的转换,也可以简单地从BaseTransformOp
继承。对于其他的操作类型,如reductions等,也可以使用操作基类,这意味着您只需要解决上面的三个要点。
在极少数情况下,您需要从头开始编写一个遗留操作,您需要从libn4j中找到相应的操作编号,可以在legacy-ops.h
中找到。
DynamicCustomOp
是libnd4j中的一种新操作,所有最近添加的操作都是这样实现的。ND4J中的这种操作类型直接扩展了DifferentialFunction
。
这里是从DynamicCustomOp
继承的BatchToSpace
操作的示例:
BatchToSpace由两个属性(block和crops)初始化。请注意,blocks和crops都是整数类型的,它们是如何通过调用addIArgument
添加到操作的整数参数中的。对于float参数和其他类型,请改用addTArgument
。
操作获取自己的名称和要导入的名称,
和doDiff
被实现
BatchToSpace操作在这里集成到DifferentialFunctionFactory
中,在这里暴露在SameDiff
中并在这里测试。
BatchToSpace当前唯一缺少的是属性映射。我们调用这个操作block和crops的属性,但是在ONNX或TensorFlow中,它们的调用和存储方式可能完全不同。要查找正确映射的差异,请参见Tensorflow的ops.proto和ONNX的onnxops.json。
让我们看看另一个正确执行属性映射的操作,即DynamicPartition
。这个操作只有一个属性,在SameDiff中称为numPartitions
。要映射和使用此属性,请执行以下操作:
实现一个名为addArgs
的小助手方法,该方法用于op的构造函数和导入助手中,下面一行我们将讨论。这是没有必要的,但鼓励这样做,并一致称之为addArgs
,为了清晰。
重写initFromTensorFlow
方法,该方法使用TFGraphMapper
实例为我们映射属性,并使用addArgs
添加参数。注意,由于ONNX在编写本文时不支持动态分区(因此没有onnxName
),因此也没有initFromOnnx
方法,其工作方式与initFromTensorFlow
几乎相同。
为了使TensorFlow导入可以工作,我们还需要重写mappingsForFunction。这个映射示例非常简单,它所做的只是将TensorFlow的属性名称 num_partitions
映射到我们的名称 numPartitions
。
请注意,虽然DynamicPartition
具有正确的属性映射,但它当前没有一个有效的doDiff
实现。
作为最后一个例子,我们展示了一个更有趣的属性映射设置,即Dilation2d。正如您在mappingsForFunction
中看到的,这个操作不仅要映射更多的属性,而且属性还带有属性值,如attributeAdaptersForFunction
中定义的。我们之所以选择显示此操作,是因为它具有属性映射,但既不向DifferentialFunctionFactory
公开,也不向SameDiff
公开。
因此,所示的三个DynamicCustomOp示例都有自己的缺陷,并代表了必须为SameDiff完成的工作的示例。总之,要添加新的SameDiff 操作,您需要:
在ND4J中创建扩展DifferentialFunction
的新操作。具体如何设置此实现取决于
操作生成 (遗留与动态定制)
操作类型 (变换、缩减等)
定义自己的操作名,以及TensorFlow和ONNX名称。
定义必要的SameDiff构造函数
使用addArgs
以可重用的方式添加操作参数。
首先在DifferentialFunctionFactory
中公开操作,然后将其包装在SameDiff
(或变量方法的SDVariable
)中。
实现doDiff
的自动微分。
重写mappingsForFunction
以映射TensorFlow和ONNX的属性
If necessary, also provide an attribute adapter by overriding attributeAdaptersForFunction
.如果需要,还可以通过重写attributeAdaptersForFunction
来提供属性适配器。
通过添加initFromTensorFlow
和initFromOnnx
(使用addArgs
),为TensorFlow和ONNX添加import一行。
测试,测试,测试
DL4J和ND4J中基准通用准则。
准则1: 在基准测试之前运行预热迭代
预热期是你开始计时进行更多迭代之前,在没有计时的情况下运行多个(例如 几百个)迭代。
为什么有预热期?任意ND4J/DL4J 执行的前几次迭代可能比后面的迭代要慢,有以下原因:
在初始化基准测试迭代中,JVM没有时间去进行代码的即时编译。一旦即时编译完成,对于所有后续操作,代码可能执行得更快。
在ND4J 和 DL4J (和其它库)都有某种程度的懒初始化:第一次操作可能触发一些一次性执行的代码。
DL4J 或 ND4J (当使用工作间) 可以用一些迭代来学习执行的内存要求。在这个学习过程中,性能会比在它完成之前低。
准则 2: 为所有基准测试运行多个迭代
你的基准测试不是运行在你电脑上的惟一的东西(更不用说如果你使用的是云硬件,可能有共享资源)。并且操作运行时间不是完全确定的。
为了让基准测试可信,多次迭代是很重要的- 并且理想地报告运行时的均值和标准差。没有这一点,就不可能比较操作的性能,因为性能差异可能只是由于随机变化造成的。
准则3: 特别注意你基准测试的是什么
当比较框架时,这尤其重要。在声明“X操作的性能为Y”或“A比B快”之前,请确保:
你只对你感兴趣的操作进行基准测试。
如果您的目标是检查操作的性能,请确保只有此操作正被计时。
您应该仔细检查是否无意中包括其他内容——例如,是否包括:JVM初始化时间?库初始化时间?结果数组分配时间?垃圾收集时间?数据加载时间?
理想情况下,这些应在你报告的任何时间/性能结果中排除。如果他们不能被排除在外,请在制定性能要求时务必注意这点。
你使用的是哪些本地库? 例如:BLAS的实现是什么(MKL, OpenBLAS, 等)?如果你正在使用CUD,你也使用了CuDNN吗? ND4J 和 DL4J 可使用这些库(MKL, CuDNN),当它们可用的时候。但通常默认是不可用的。如果它们没有被设为可用,性能会比较低,有时候相当低。
在比较库之间的结果时,这一点尤其重要:例如,如果比较两个库(一个使用OpenBLAS,另一个使用MLK),那么结果可能只是反映了正在使用的BLAS库的性能差异,而不是别的正在测试的库的性能。类似地,一个CuDNN和另一个没有CuDNN的库可能简单地反映了使用CuDNN的性能收益。
如何配置?
不论好坏,DL4J和ND4J允许很多配置。对于大多数用户来说,这种配置的默认值是足够的,但有时需要手动配置才能获得最佳性能。这在某些基准测试中尤其适用。例如,这些配置选项中的一些允许用户以更高的内存使用以获得更好的性能。需要注意的一些配置选项:(a)内存配置(b)工作区和垃圾收集(c)CuDNN(d)DL4J缓存模式(启用.cacheMode
(CacheMode.DEVICE
))
如果你不确定你是否只是在运行DL4J或ND4J代码时测量您想要测量的内容,那么可以使用分析器,例如VisualVM或YourKit Profilers。
你正在使用的是哪个版本? 在基准测试时,您应该使用最新版本的任何基准测试库。识别和报告一个6个月前修复的瓶颈是没有意义的。当您在版本间比较性能时,这将是一个例外。还请注意,DL4J和ND4J的快照版本也是可用的——它们可能包含性能改进(可以随时询问)
准则 4: 关注现实世界的用例——并运行一定范围的大小
例如,考虑一个基准测试,两个数字相加:
在 ND4J 中等价于:
当然,上面的ND4J基准测试要慢得多—需要方法调用、执行输入验证、必须调用本地代码(具有上下文切换开销)等等。一个必须问的问题:这个问题是用户实际使用ND4J还是等价的线性代数库?这是一个极端的例子,但一般的观点是有效的。
还要注意,数学运算的性能可是特定的大小和形状。例如,如果你对矩阵乘法的性能进行基准测试,那么矩阵维数可能非常重要。在一些内部基准测试中,我们发现不同的BLAS实现(MKL 对比 OpenBLAS)和不同的后端(CPU 对比 GPU)可以在不同的矩阵维度下表现非常不同。对于所有输入形状和大小,我们在内部测试的BLAS实现(OpenBLAS、MKL、CUDA)没有一个比其他BLAS实现本质上更快。
因此,无论何时运行基准测试,都必须以多种不同的输入形状/大小运行这些基准测试,以获得完整的性能图。
准则 5: 理解你的硬件
当比较不同的硬件时,重要的是要知道它擅长什么。例如,你可能会发现神经网络小批量大小为1的时候,在CPU上训练比在GPU上执行得更快—但是更大的小批量大小正好相反。类似地,小的网络层尺寸可能不能充分利用GPU的计算能力。
此外,可能需要特别编译一些深度学习发行版来提供对诸如AVX2之类的硬件特性的支持(注意,ND4J的最新版本封装有支持这些特性的CPU的二进制文件)。当运行基准时,这些特征的利用(或缺乏)会对性能造成相当大的差异。
准则 6: 让它可重现
当运行基准测试时,重要的是使基准测试可重现。为什么?好或坏的表现可能只在某些有限的情况下发生。
最后,记住(a)ND4J和DL4J在不断发展,(b)基准测试有时的确会识别性能瓶颈(毕竟我们-ND4J实际上包括数百个不同的操作)。如果你确定了性能瓶颈,我们很想知道它,这样我们可以修复它。每当发现一个潜在的瓶颈时,我们首先需要重现它—这样我们就可以研究它、理解它并最终修复它。
准则 7: 理解你的基准测试的局限
线性代数库包含数百个不同的操作。神经网络库包含几十种层类型。当基准测试时,了解这些基准测试的局限性是很重要的。对一个类型的操作或层进行基准测试不能告诉你关于其他类型层或操作的任何性能,除非它们共享已被识别为性能瓶颈的代码。
准则 8: 如果你不确定 - 咨询
DL4J/ND4J开发者可在Gitter上使用。你可以在那里问一些关于基准测试和性能的问题: https://gitter.im/deeplearning4j/deeplearning4j
如果你碰巧发现一个性能问题-让我们知道!
一个关于BLAS和数组顺序的标注
BLAS或基本线性代数子程序-是指用于线性代数运算的接口和方法集。一些例子包括“gemm”-通用矩阵乘法和“axpy”,它实现了Y = a*X+Y
.
ND4J可以使用多个BLAS实现,1.0.0-beta版本及以上已经默认为OpenBLAS。然而,如果安装了英特尔MKL(这里提供免费版本),ND4J将与其链接,以提高许多BLAS操作的性能。
注意ND4J在初始化的时候会打印在后台使用的BLAS信息,例如:
性能取决于可用的BLAS库—在内部测试中,我们发现OpenBLAS比MKL快30%或慢8倍——这取决于阵列大小和数组顺序。
考虑到数组顺序,这也是影响性能的重要因素。ND4J有可能行优先(‘c’)也有可能列优先 (‘f’) 顺序呈现数组。查看这个维基百科页面 获取更多详情。矩阵乘法操作的性能和更通用的ND4J操作-取决于输入和结果顺序。
对于矩阵乘法,这意味着存在8个数组顺序的可能组合(对于输入1、输入2和结果阵列中的每个都为c/f)。对于所有的情况,性能都是不一样的。
类似地,对于某些输入顺序的组合,诸如逐元素相加(即,z=x+y)的操作将比其他操作快得多,尤其是当x、y和z都是相同顺序时。简而言之,这是由于内存跨步造成的:当这些内存地址在存储器中彼此相邻时,读取一系列内存地址要比分散到很远的地方成本更低。
注意,在缺省情况下,ND4J期望结果数组(用于矩阵乘法)按照列优先(“f”)顺序定义,以便在后端保持一致,因为CuBLAS(即,NVIDIA的用于CUDA的BLAS库)要求结果为f顺序。因此,与用“f”顺序数组执行相同操作相比,用结果数组按c顺序执行矩阵乘法的一些方法性能更低。
最后,当说到CUDA:数组顺序/跨步比在CPU上运行更重要。例如某些顺序组合可以比其他组合快得多-当输入输出的维度是32 或64的偶数倍的时候要比不是32的偶数倍的时候快(有时候快得多)。
对于ND4J所说的大部分也适用于DL4J。
另外:
如果你正在使用nd4j-native (CPU) 后台,确保你正在使用 Intel MKL。在多数情况下这比默认的OpenBLAS要快。
如果你正在使用CUDA, 确保你正在使用 CuDNN (链接
注意ETL瓶颈。你可以在网络训练中添加PerformanceListener以查看ETL是否是瓶颈。
别忘了性能取决于小批量的大小。不要使用微批量为1用来基准测试-使用更真实的数字。
如果你需要多GPU 训练或推理 ,请使用ParallelWrapper 或ParallelInference。
别忘了CuDNN 是可配置的: 你可以指定 DL4J/CuDNN 来获取更好的性能 -以内存消耗为代价 - 在卷积层上使用 .cudnnAlgoMode(ConvolutionLayer.AlgoMode.PREFER_FASTEST)
配置
当使用 GPU的时候, 对于输入大小或层的大小 8 (或 32) 的倍数可能性能更好。
当使用 RNNs (并且手动创建INDArrays), 为特征和(RnnOutputLayer) 标签使用 ‘f’ 有序数组. 否则,使用“C”有序数组。这是为了更快的内存访问。
最后,这里列出了常见的基准测试错误:
没有使用ND4J/DL4J的最新版本(没有发现瓶颈,因为在前几个版本就已经修复)。考虑尝试快照以获得最新的性能改进。
没有注意正在使用什么本地库(MKL, OpenBLAS, CuDNN 等)
在基准测试开始之前没有提供预热期
只运行一个(或很少)的迭代,或没有报告均值,方差和迭代次数。
没有配置工作间,垃圾回收等。
只运行一种可能的情况-例如,在BLAS基准测试操作中对一单个数组维度/顺序集合进行基准测试。
运行异常小的输入-例如,在GPU上微批次大小为1(这可能是慢的,但不真实!)
不精确地测量,只测量你要测量的(例如,不考虑数组分配、初始化或垃圾收集时间)
没有让你的基准测试可重现(基准结测试论可归纳吗?基准测试有问题吗?我们能做些什么来修复它?)
跨硬件来比较结果,没有计算差异(例如,一个在带有AVX2的机器上测试,一个在没有AVX2的机器上测试)
没有咨询开发者(通过DL4J/ND4J 极客频道) 我们很高兴提供建议和对性能不正常时进行调查。
总训练时间通常是ETL时间加上计算时间 。也就是说,数据管道和矩阵操作一起决定了神经网络在数据集上训练的时间。
当一个熟悉Python的程序员试图将 Deeplearning4j基准测试与著名的Python框架的基准测试进行比较,他们通常在DL4J上用ETL+计算时间与Python框架的计算时间作为比较。那就是说,他们在用苹果与桔子作比较。下面我们将解释如何优化一些参数。
JVM有调整的旋钮,如果你知道如何调整它们,你可以使它成为一个非常快速的深度学习环境。在JVM上有几件事要牢记。你需要:
增加堆空间
让垃圾回收正确
让ETL异步进行
预保存数据集
用户必须自己重新配置JVM,包括设置堆空间。我们不能把预先配置好的给你,但是我们可以告诉你怎么做。这里是堆空间的两个最重要的旋钮。
Xms 设置最小的堆空间
Xmx 设置最大的推空间
你可以将这些设置在IntelliJ和Eclipse等IDE中,也可以通过客户端这样设置:
在 IntelliJ中,这是一个虚拟机参数, 不是一个程序参数。当你在IntelliJ中点击(绿色按钮)运行时,会设置运行时配置。IJ以你指定的配置启动Java VM。
设置XMX的理想量是多少?这取决于计算机上有多少RAM。一般来说,按照JVM需要完成的任务分配尽可能多的堆空间。假设你在一个16G RAM的笔记本电脑上,向JVM分配8G的RAM。更小RAM的笔记本应设为3G,所以
这似乎是违反直觉的,但你希望堆空间最小值和堆空间最大值是相同的,即Xms
应该等于Xmx
。如果它们不相等,JVM将根据需要逐步分配更多的内存,直到达到最大值,并且逐渐分配的过程会减慢速度。你希望在开始时预先分配它。所以
IntelliJ 将自动指定所涉及的 Java 主类 。
另一种方法是设置环境变量。在这里,你将更改隐藏的.bash_profile
文件,它将环境变量添加到BASH中。若要查看这些变量,请在命令行中输入env
。若要添加更多的堆空间,请在控制台中输入此命令:
我们需要增加堆空间,因为Deeplearning4j在后台加载数据,这意味着我们在内存中占用更多的RAM。通过为JVM提供更多的堆空间,我们可以在内存中缓存更多的数据。
垃圾回收器是在JVM上运行并清除Java应用程序不再使用的对象的程序。它是自动内存管理。在Java中创建一个新对象占用堆内存:默认情况下,一个新的Java对象占用8字节的内存。因此,创建的每个新的DatasetIterator
都需要额外8个字节。
你可能需要更改Java正在使用的垃圾回收算法。这可以通过命令行完成,例如:
更好的垃圾收集算法增加了吞吐量。关于这个问题的更详细的探索,请阅读这篇InfoQ 文章。
DL4J与垃圾回收器紧密相连。JavaCPP是JVM和C++之间的桥梁,它遵循你用Xmx设置的堆空间,并广泛使用堆外内存。堆外内存不会超过指定的堆空间。
JavaCPP,由Skymind工程师创建,依赖于垃圾回收器告诉它什么已经完成。我们依赖Java垃圾回收器告诉我们什么要被收集;Java垃圾回收器指向我们知道如何用JavaCPP去销毁它们的对象。这同样适用于我们如何使用GPU。
你使用的批次越大,内存占用的RAM越多。
在我们的dl4j-examples中,我们没有使用异步的
ETL,因为我们的目标是让它们更简单。但对于真实问题,你需要使用异步的ETL,我们将在示例中展示如何使用它。
数据存储在磁盘上,磁盘速度慢,这是默认的。因此,当将数据加载到硬盘上时,会遇到瓶颈。当优化吞吐量时,最慢的组件始终是瓶颈。例如,使用三GPU的机器和一个CPU的机器分布式spark作业将有CPU瓶颈。GPU必须等待CPU完成。
Deeplearning4j 的 DatasetIterator
类隐藏了从磁盘加载数据的复杂性。用于任何Datasetiterator的代码通常是一样的,调用看上去是一样的,但它们以不同的方式工作。
一个从磁盘加载
一个异步加载
一个从RAM加载预保存的数据
下面是对于MISIST如何统一调用DatasetIterator的方法:
你可以通过在后台使用一个异步加载器来优化。Java可以实现真正的多线程。它可以在后台加载数据,而其他线程则负责计算。因此,你正在运行计算的同时,将数据加载到GPU中。甚至当你从内存中获取新数据时,神经网络也在进行训练。
这是 相关代码,第三行比较特殊:
实际上有两种类型的异步数据集迭代器。“AsyncDataSetIterator
”是你在大多数时间使用的。它在 Javadoc 中被描述。
对于特殊情况,如应用于时间序列的循环神经网络,或用于计算图,你将使用一个 AsyncMultiDataSetIterator
, 在 Javadoc 中被描述。
在上面的代码中注意,prefetchSize
是另一个要设置的参数。正常的批大小可能是1000个示例,但是如果将prefetchSize
设为3,它将预获取3000个实例。
在Python中,程序员将它们的数据转换为泡菜或二进制数据对象。如果他们用一个小玩具数据集工作,他们将所有的泡菜装入RAM中。因此,它们有效地归避了处理较大数据集的主要任务。同时,当对DL4J进行基准测试时,它们不将所有数据加载到RAM上。所以他们实际上将Dl4j的训练计算+ETL的时间与Python框架的训练计算时间进行了比较。
但是Java有强大的工具来移动大数据,如果比较正确,比Python快得多。Deeplearning4j社区报告说,在优化ETL和计算时,与Python框架相比,Deeplearning4j速度提高了3700%。
Deeplearning4j使用DataVec作为ETL和向量化库。与其他深度学习工具不同,DataVec不强制数据集是一种特定格式。(例如,Caffe强制你使用hdf5)。
我们试着变得更灵活。这意味着你可以将DL4J指向原始照片,它会加载图像、运行转换并将其放入NDArray中动态生成数据集。
但是如果但如果你的训练管道每次都这样做,Deeplearning4j看起来会比其他框架慢10倍,因为你把时间花费在了创建数据集上。每次你调用“fit
”,你都会一遍又一遍地创建数据集。为了方便我们允许这样使用它,但是我们可以告诉你如何加快速度。有多种办法使它变快。
一种类似于Python框架的方式预先保存数据集的方法。(泡菜是预先格式化的数据)。当你预先保存数据集时,你将创建一个单独的类。
这里你是如何 预保存数据集 的例子
Recordreaderdatasetiterator
与 Datavec 交互并为DL4J输出数据。
这里是你如何 加载预保存数据 的例子。
第90行是你看到异步ETL的地方。在本例中,它包装了预先保存的迭代器,因此你可以利用这两种方法,通过异步将预先保存的数据加载到后台作为网络训练。
如果您在CPU上运行推理基准测试,请确保你正在和Intel的MKL库一起使用Deeplearning4j,该库可以通过单击安装包获得;即,Deeplearning4j不像Anaconda那样捆绑MKL,Anaconda是PyTorch等库使用的。
ND4J的主要功能和简要示例。
ND4J是JVM的科学计算库。它是用来在生产环境中使用的,而不是作为一个研究工具,这意味着常规的设计是为了以最低的RAM需求快速运行。主要特点是:
一个多功能的n维数组对象。
线性代数和信号处理函数。
多平台功能,包括GPU。
所有主要操作系统: win/linux/osx/android.
架构: x86, arm, ppc.
此快速入门遵循与Numpy快速入门相同的布局和方法。这将帮助熟悉Python和Numpy的人快速开始使用Nd4J。
您可以从任何JVM语言中使用Nd4J。(例如:Scala、Kotlin)。可以将Nd4J与任何构建工具一起使用。此快速入门中的示例代码使用以下内容:
Java (developer version) 1.7或更高版本(仅支持64位版本)
Apache Maven (自动构建和依赖关系管理器)
Git (分布式版本控制系统)
为了提高可读性,我们向您展示System.out.println(...)
的输出。但是我们还没有在示例代码中显示print语句。如果您有信心知道如何使用maven和git,请随时跳到基础部分。在本节的其余部分中,我们将构建一个小型的“hello ND4J”应用程序,以验证先决条件设置是否正确。
执行以下命令从github获取项目。
当一切设置正确时,您应该看到以下输出:
Nd4j的主要特点是具有多功能的n维阵列接口INDArray。为了提高性能,Nd4j使用堆外内存来存储数据。INDArray不同于标准Java数组。
INDArray x的一些关键属性和方法如下:
要创建INDArray,可以使用Nd4j类的静态工厂方法。
Nd4j.createFromArray
函数被重载,以便于从常规Java数组创建INDArrays。下面的示例使用Javadouble
数组。类似的create方法对于float、int和long重载。对于所有类型,Nd4j.createFromArray
函数都有高达4d的重载。
Nd4j可以使用函数zeros
和ones
创建用0和1初始化的数组。rand函数允许您创建用随机值初始化的数组。创建的INDArray的默认数据类型是float。有些重载允许您设置数据类型。
使用arange
函数创建一个均匀空间值数组:
linspace
函数允许您指定生成的点数:
INDArray支持Java的toString()
方法。当前的实现具有有限的精度和有限的元素数。输出类似于打印NumPy数组:
必须使用INDArray方法对数组执行操作。有就地(in-place)和复制重载、标量和元素级重载版本。就地(in-place)运算符返回对数组的引用,因此可以方便地将操作链接在一起。尽可能使用就地(in-place)运算符来提高性能。复制运算符有新的数组创建开销。
加法: arr.add(...), arr.addi(...) 减法: arr.sub(...), arr.subi(...) 乘法: arr.mul(...), arr.muli(...) 除法 : arr.div(...), arr.divi(...)
执行基本操作时,必须确保基础数据类型相同。
INDArray有实现缩减/累加操作的方法,如 sum
, min
, max
.
提供维度参数以在指定维度上应用操作:
Nd4j提供熟悉的数学函数,如sin、cos和exp,这些称为转换操作。结果作为INDArray返回。
您可以在Javadoc中查看转换操作的完整列表
我们已经在基本操作中看到了元素的乘法运算。其他矩阵运算有自己的方法:
索引、切片和迭代在Java中比Python更困难。 要从INDArray检索单个值,可以使用getDouble
、getFloat
或getInt
方法。INDArrays不能像Java数组那样被索引。可以使用toDoubleVector()
、toDoubleMatrix()
、toFloatVector()
和toFloatMatrix()
从INDArray获取Java数组。
对于多维数组,应该使用INDArray.get(NDArrayIndex...)
。下面的示例演示如何遍历二维数组的行和列。注意,对于2D数组,我们可以使用getColumn
和getRow
便利方法。
沿着每个轴的元素的数量是形状。形状可以用各种方法改变。
可以使用vstack
和hstack
方法将数组堆叠在一起。
使用INDArrays时,并不总是复制数据。这里有三种情况你应该注意。
简单的指派不会复制数据。Java通过引用传递对象。在方法调用上不生成任何副本。
一些函数将返回数组的视图。
要复制数组,请使用dup
方法。这将为您提供一个包含新数据的新数组。
arange, create, copy, empty, empty_like, eye, linspace, meshgrid, ones, ones_like, rand, readTxt, zeros, zeros_like
convertToDoubles, convertToFloats, convertToHalfs
concatenate, hstack, ravel, repeat, reshape, squeeze, swapaxes, tear, transpose, vstack
choice, cumsum, mmul, prod, put, putWhere, sum
covarianceMatrix, mean, std, var
“SameDiff”中有哪些操作以及如何使用它们
SameDiff
中的操作基本上是按照您预期的方式进行的。在我们的框架中,变量是SDVariable
类型的对象,对它们执行apply操作,从而生成新的变量。在对可用操作进行概述之前,让我们列出它们的一些公共属性。
任何变量类型的变量都可以在任何操作中使用,只要它们的数据类型与操作所需的数据类型匹配(同样,请参阅变量部分了解变量类型)。大多数情况下,操作将要求其SDVariable
具有浮点数据类型。
由操作创建的变量具有ARRAY
变量类型。
对于所有操作,您可以定义结果变量的字符串名称,尽管对于大多数操作,这不是必需的。名称作为每个操作中的第一个参数,如下所示:
可以使用SameDiff
方法getVariable(String name)
从外部访问命名变量。对于上面的代码,此方法将允许您推断output
和mmul操作的结果。注意我们 甚至没有显式地将此结果定义为单独的SDVariable
,但是相应的SDVariable
将在内部创建并添加到SameDiff的实例中,字符串名为“matrix_product”。事实上字符串名称是为操作生成的每个SDVariable
指定的:如果不显式指定名称,则会根据操作的名称自动将其指定给生成的SDVariable
。
当前可用的操作的数量,包括重载总计数百,它们按复杂度排序,从通过卷积层产生输出的简单加法和乘法到创建专用的循环神经网络模块,以及更多。操作的数量之多会使得将它们全部列在一页上变得很麻烦。因此,如果您已经在寻找特定的东西,那么最好检查我们的javadoc,它已经包含了每个操作的详细信息,或者只需浏览自动完成提示(如果您的IDE支持的话)。在这里,我们试图给你一个主意,你可能会找到什么样的行动,并在哪里寻找他们。
所有操作可以分为两个主要分支:SDVariable
方法和SameDiff
类方法。让我们仔细看看每一个:
SDVariable
操作我们已经在前面的例子中看到了SDVariable
操作,比如
其中x
和y
是SDVariable
的。
在SDVariable
方法中,您将发现:
执行线性代数的BLAS
类型操作:例如add
, neg
, mul
(用于缩放和元素相乘)和mmul
(矩阵相乘)、dot
、rdiv
等。;
比较操作,如gt
或lte
,用于将每个元素与固定的双精度值进行比较,以及与相同形状的另一个SDVariable
进行元素级比较;
基本缩减运算:如min
、sum
、prod
(数组中元素的乘积)、mean
、norm2
、argmax
(最大元素的索引)、squaredDifference
,可以沿着指定的维度进行;
计算给定尺寸的平均值和标准差的基本统计操作:平均值和标准差。
重组底层数组的操作:reshape
和permute
,以及shape
——一个将变量的形状作为整数数组传递的操作——维度大小;
SDVariable
操作很容易链接,生产线如下:
SameDiff
操作SameDiff
方法的操作是通过每一个SameDiff
中存在的6个辅助对象之一调用的,该操作将所有操作分割成6个不均匀分支:
math
- 一般数学运算;
random
- 创建不同的随机数生成器;
nn
- 通用神经网络工具;
cnn
- 卷积神经网络工具;
rnn
- 循环神经网络工具;
loss
- 损失函数;
为了使用特定的操作,您需要从SameDiff
实例调用这6个对象中的一个,然后调用操作本身,如下所示:
或
辅助对象之间的操作分布在结构上不具有更直观的方式来组织事物。所以,举个例子,如果你不确定是在math
还是nn
中寻找tanh
运算,不用担心:我们都有。
让我们简要描述一下您希望在每个分支中找到的操作类型:
math
- 基本数学运算数学模块主要由通用数学函数和统计方法组成。其中包括:
幂函数,例如 square
, cube
, sqrt
, pow
, reciprocal
等;
三角函数,例如。 sin
, atan
等;
指数/双曲函数,如 exp
, sinh
, log
, atanh
等;
各种各样的元素操作,比如取绝对值、舍入和剪裁,比如 abs
, sign
, ceil
, round
, clipByValue
, clipByNorm
等;
指定维度的缩减: min
, amax
, mean
, asum
, logEntropy
, 以及类似的;
distance (reduction) operations, such as距离缩减操作,例如 euclideanDistance
, manhattanDistance
, jaccardDistance
, cosineDistance
,
hammingDistance
, cosineSimilarity
, 沿指定维度,对于两个形状相同的 SDVariables
;
特定矩阵运算: matrixInverse
, matrixDeterminant
, diag
(创建对角矩阵), trace
, eye
(变维恒等式矩阵的建立), 和一些其它的;
更多统计操作: standardize
, moment
, normalizeMoments
, erf
和 erfc
(高斯误差函数及其互补);
计数和索引缩减:类似方法 conuntZero
(零元素数字), iamin
(具有最小绝对值的元素的索引), firstIndex
(满足指定Condition
函数的第一元素的索引);
表示底层数组属性的缩减。这些包括例如。 isNaN
(按元素检查), isMax
(沿指定维度保持形状), isNonDecreasing
(沿指定维度缩减);
元素逻辑操作: and
, or
, xor
, not
.
math
中的大多数运算都有非常简单的结构,并且是这样推断的:
操作可以是链式的,尽管与SDVariable
操作相比,其方式更为繁琐,例如:
观察到sum
操作中的(整数)参数1告诉我们必须沿着1维,即矩阵的列取最大绝对值。
random
- 创建随机值这些操作创建变量,其基础数组将按某些分布填充随机数
比如,伯努利,正态,二项式等等。这些值将在每次迭代时重置。例如,如果希望创建一个变量,将高斯噪声添加到MNIST数据集的条目中,可以执行以下操作:
随机变量的形状可能会有所不同。例如,假设你有不同长度的音频信号,你想给它们添加噪声。然后,您需要指定一个SDVariable
,例如,windowShape
和一个整数数据类型,然后继续这样做
nn
- 通用神经网络工具这里我们存储的神经网络方法不一定与卷积的方法相关。其中包括
创建密连的线性层和ReLU层(带或不带偏置),并单独添加偏置:linear
, reluLayer
, biasAdd
;
常用的激活函数,例如。 relu
, sigmoid
, tanh
, softmax
以及使用较少的版本,如
leakyRelu
, elu
, hardTanh
, 还有更多;
使用pad
方法填充的二维数组,支持多种填充类型,具有恒定和可变的填充宽度;
梯度爆炸/过拟合预防,如层dropout
、layerNorm
、batchNorm
。批量归一化;
有些方法是为内部使用而创建的,但可以公开使用。其中包括:
一些流行的激活函数的导数-这些主要是为了加速反向传播;
attention模块-基本上,我们将在下面讨论循环神经网络的构建模块。
虽然nn
中的激活相当简单,但其他操作变得更加复杂。例如,要创建线性层或ReLU层,可能需要多达三个预定义的SDVariable
对象,如下代码所示:
其中input
、weights
和bias
需要具有相互匹配的维度。
要创建具有softmax激活的密连层,可以按以下步骤进行:
cnn
- 卷积神经网络工具cnn
模块包含通常用于卷积神经网络的层和操作-可以从nn
模块中提取不同的激活。在cnn
的操作中,我们目前已经创建了:
线性卷积层,目前用于尺寸小于等于3的张量(不包括小批量):conv1d
, conv2d
,
conv3d
, depthWiseConv2d
, separableConv2D
/sconv2d
;
线性反卷积层,目前 deconv1d
, deconv2d
, deconv3d
;
池化,例如 maxPoooling2D
, avgPooling1D
;
特殊变形方法: batchToSpace
, spaceToDepth
, col2Im
和类似的
上采样,目前由upsampling2d
操作提出;
局部响应归一化:localResponseNormalization
,目前仅用于二维卷积层;
卷积和反卷积操作由许多静态参数指定,如核大小、膨胀、有无偏置等。为了简化创建过程,我们将所需的参数打包成易于构造和更改的配置对象。所需的激活可以从nn
模块借用。因此,例如,如果我们想要创建一个具有relu
激活的3x3卷积层,我们可以如下进行:
在第一行中,我们使用它的默认构造函数构造卷积配置。然后我们指定内核大小(这是必需的)和可选的填充大小,并保持其他默认设置(单位步幅、无伸缩、无偏置 、NCHW数据格式)。然后,我们使用此配置创建一个线性卷积,其中预定义了用于输入和权重的SDVariables
;weights
的形状将调整为input
和预先config
的形状。因此,在上面的例子中,如果input
有形状,比如说,[-1,nIn,height,width]
,那么weights
的形式就是[nIn,nOut,3,3]
(因为我们有3x3卷积核)。结果变量convoluton2d
的形状将由这些参数预先确定(在我们的情况下,它将是[-1,nOut,height,width]
)。最后,在最后一行中,我们应用relu激活。
rnn
- 循环神经网络这个模块可以说包含了框架中最复杂的方法。目前它允许您创建
简单循环单元,使用 sru
和 sruCell
方法;
LSTM 单元,使用 lstmCell
, lstmBlockCell
和 lstmLayer
;
Graves LSTM 单元,使用 gru
方法。
到目前为止,循环操作需要特殊的配置对象作为输入,在这些对象中,您需要打包将在一个单元中使用的所有变量。这可能会在以后的版本中发生更改。例如,要创建一个简单的循环单元,您需要这样进行:
这里,SRUConfiguration
构造函数中的参数是预先定义的变量。显然,它们的形状应该是匹配的,并且这些形状预先确定了output
的形状。
loss
- 损失函数在这个分支中,我们保留了共同的损失函数。大多数损失函数可以非常简单地创建,例如:
其中labels
和predictions
是SDVariable
的。在大多数loss
方法中,字符串名称是必需的参数,但它可能被设置为空-在这种情况下,将自动生成名称。您还可以通过添加另一个包含权重的SDVariable参数来创建加权损失函数,并指定小批量损失的减少方法(见下文)。因此,一个全面的logLoss
操作可能看起来像:
一些损失操作可能允许/需要进一步的参数,这取决于它们的类型:例如,计算损失的维度(如cosineLoss
),或一些实数参数。
至于缩减方法,在小批中,目前有4种可用。因此,首先计算每个小批量样例的损失值,然后乘以权重(如果指定),最后执行下列程序之一:
NONE
- 保持结果(权重)损失值不变;结果是具有小批量长度的INDArray
:sum_loss=sum(weights*loss_per_sample)
。
SUM
- 对值求和,生成标量结果。
MEAN_BY_WEIGHT
- 首先计算上述总和,然后除以所有权重之和,生成标量值:mean_loss = sum(weights * loss_per_sample) / sum(weights)
。 如果未指定权重,则将所有权重都设置为1.0,此缩减等于获得小批量的平均损失值。
MEAN_BY_NONZERO_WEIGHT_COUNT
- 将加权和除以非零权重的个数,生成标量:
mean_count_loss = sum(weights * loss_per_sample) / count(weights != 0)
。有用的,例如,当你只想计算有效样本子集的平均值时,将权重设置为0或1。当没有给出权重时,它只产生平均值,因此等价于MEAN_BY_WEIGHT
。
为了使SameDiff
操作正常工作,需要遵守几个主要规则。如果不这样做,可能会导致异常,甚至会导致工作代码产生不希望的结果。我们在本节提到的所有事情都描述了什么是你最好不要做的。
一个操作中的所有变量都必须属于SamdeDiff
的同一个实例(请参阅变量部分,了解如何将变量添加到SameDiff
实例)。换句话说,你最好不要
充其量,为一个操作或一系列操作的结果创建一个新变量。换句话说,最好不要重新定义现有变量,最好不要让操作返回结果。换言之,尽量避免这样的代码:
上述代码的正确工作版本(如果我们希望以一种不寻常的方式获得2xy+2y2)将是
要了解它为什么会这样工作,请参阅我们的图章节。
提供Eclipse Deeplearning4j里通用的功能与代码片段
DL4J(和相关项目)有很多功能。此篇的目标是总结这个功能,以便用户知道存在什么功能,以及在哪里可以找到更多信息。
内容
DenseLayer - (源码) - 简单/标准全连接层
EmbeddingLayer - (源码) - 以正整数索引作为输入,输出向量。只作为模型中的第一层使用。数学上等效于(当启用偏置)DenseLayer,使用OneHot输入,但更高效。
输出层:通常用作网络中的最后一层。这里会设置损失函数。
OutputLayer - (源码) - 在MLPs/CNNs中的标准分类/回归输出层。有一个内置的全连接的DenseLayer。 2d 输入/输出 (即, 每个示例中的行向量)。
LossLayer - (源码) - 没有参数的输出层 - 只有损失函数和激活函数。2d 输入/输出 (即, 每个示例中的行向量)。与Outputlayer 不同,它有nIn = nOut的限制。
RnnOutputLayer - (源码) - 循环神经网络的输出层。三维(时间序列)的输入和输出。内置有时间分布全连接层。
RnnLossLayer - (源码) - 无参版本的RnnOutputLayer。 三维(时间序列)的输入和输出。
CnnLossLayer - (源码) - 与CNNs一起使用,其中必须在输出的每个空间位置进行预测(例如:分割或去噪)。没有参数,四维输入/输出与形状[小批量,深度,高度,宽度]。当使用softmax时,这是在每个空间位置的深度应用。
Yolo2OutputLayer - (源码) - 用于目标检测的YOLO 2模型实现
CenterLossOutputLayer - (源码) - OutputLayer的一个版本,也试图最小化示例激活的类内距离,即,“如果示例x在Y类中,则确保嵌入(x)接近于所有示例y在Y中的平均值(嵌入(y))。
ConvolutionLayer / Convolution2D - (Source) - 标准的二维卷积神经网络层。输入和输出有4个维度形状分别为 [minibatch,depthIn,heightIn,widthIn] 和[minibatch,depthOut,heightOut,widthOut]。
Convolution1DLayer / Convolution1D - (Source) - 标准的一维卷积神经网络层。
Deconvolution2DLayer - (Source) - 也称为转置或分数阶卷积。可以认为是“反向”卷积层;输出通常大于输入,同时保持空间连接结构。
SeparableConvolution2DLayer - (Source) - 深度可分离卷积层。
SubsamplingLayer - (Source) -为CNNs实现的标准二维空间池化,最大、平均和p范数池可用。
Subsampling1DLayer - (Source)
Upsampling2D - (Source) - 通过重复行/列 来升级CNN激活 。
Upsampling1D - (Source) - 一维版本的上采样层
Cropping2D - (Source) - 二维卷积神经网络的裁剪层。
ZeroPaddingLayer - (Source) -非常简单的层,将指定数量的零填充添加到四维输入激活的边缘。
ZeroPadding1DLayer - (Source) - 一维版本的ZeroPaddingLayer
SpaceToDepth - (Source) - 给定块大小,这个操作采用四维数组,并把数据从空间维度移动到通道。
SpaceToBatch - (Source) - 根据指定的“块”,将张量从2个空间维度转换为批量维度。
LSTM - (Source) - 没有窥视孔连接的LSTM RNN。 支持 CuDNN。
GravesLSTM - (Source) - 具有窥视孔连接的LSTM RNN。不支持CuDNN(因此对于GPU,LSTM应该优先使用)。
GravesBidirectionalLSTM - (Source) - 具有窥视连接的双向LSTM实现。等效于双向(ADD,GravesLSTM)。由于增加了双向包装(以下),已被主干弃用。
Bidirectional - (Source) - 一个“包装”层-将任何标准的单向RNN转换成双向RNN(双倍数量的参数-前向/后向网络具有独立的参数)。前向/后向网络的激活可以是增加的、乘法的、平均的或级联的。
SimpleRnn - (Source) - 一个标准的“普通”RNN层。在长时间系列依赖的情况下,通常在实际中不生效。更推荐使用LSTM。
LastTimeStep - (Source) - 一个“包装器”层提取出它封装的(非双向)RNN层的最后一个时间步长。三维输入的形状[minibatch, size, timeSeriesLength],二维输出与形状[minibatch, size]。
AutoEncoder - (Source) - 标准降噪自动编码器层
GlobalPoolingLayer - (Source) - 实现基于时间的池化(对于RNs/时间序列-输入大小[minibatch,size,timeSeriesLength]、out[minibatch,size])和全局空间池化(对于CNN-输入大小[minibatch,.,h,w]、out[minibatch,.])。可用的池模式:和,平均,最大和p-范数。
ActivationLayer - (Source) - 将激活函数(仅)应用于输入激活。请注意,大多数DL4J层具有作为配置选项内置的激活函数。
DropoutLayer - (Source) - 实现丢弃的单独的层。注意大多数 DL4J层有一个内置的丢弃配置选项。
BatchNormalization - (Source) - 二维(前馈),三维(时间系列)或4维(卷积神经网络)激少的批量归一化。对于时间系列,参数是跨时间共享的;对于卷积神经网络,参数是跨空间位置(不是深度)共享的。
LocalResponseNormalization - (Source) - 卷积神经网络的本地响应归一化层。在现代的卷积神经网络架构中不经常用。
FrozenLayer - (Source) - 通常不会被用户直接使用-被作为迁移学习的一部份添加,用于冻结层在将来的训练中不再改变的参数。
图顶点: 与 ComputationGraph 一起使用。和层类似,顶点通常没有任何参数,并可以支持多个输入。
ElementWiseVertex - (Source) - 对输入进行元素操作-加法、减法、乘积、平均值、最大值
L2NormalizeVertex - (Source) - 通过对每个示例除以L2范数来归一化输入激活。即,out<-out/L2范数(out)
L2Vertex - (Source) - 为每个示例分别计算两个输入阵列之间的L2距离。对于每个输入值,输出是一个单一值。
MergeVertex - (Source) - 将输入激活沿维度1合并,以生成更大的输出数组。对于CNNs,它实现沿深度/通道维度的合并。
PreprocessorVertex - (Source) - 包括一个输入预处理器的简单的图顶点
ReshapeVertex - (Source) - 执行任意激活阵列整形。下一节中的预处理器通常是首选的。
ScaleVertex - (Source) - 实现输入的简单乘法缩放,即OUT =标量*输入。
ShiftVertex - (Source) - 在输入上实现简单的标量元素添加(即,out=输入+标量)。
StackVertex - (Source) - 用于按小批量的维度堆叠所有输入。类似于MergeVertex,但沿维度0(小批量)而不是维度1(输出/通道)
SubsetVertex - (Source) - 用于获得沿维度1的输入激活的连续子集。例如,可以使用两个SubsetVertex实例来将激活从输入数组分割为两个单独的激活。本质上与MergeVertex是相反的。
UnstackVertex - (Source) - 与SubsetVertex类似,但沿维度0(小批量)而不是维度1(输出/通道)。与StackVertex相反。
输入预处理器是一个简单的类/接口,它对一个层的输入进行操作。也就是说,预处理器连接到一个层上,并在输入到输出之前对输入执行一些操作。预处理器还处理反向传播——即,预处理操作一般是可求导的。
请注意,在许多情况下(例如XtoYPreProcessor类),用户不需要(也不应该)手动添加这些,而只能使用.setInputType(InputType.feedForward(10))来代替,这会根据需要推断和添加预处理器。
CnnToFeedForwardPreProcessor - (Source) - 对一个卷积层(ConvolutionLayer, SubsamplingLayer, etc) 到 DenseLayer/OutputLayer的转换做必要的激活修正处理。
CnnToRnnPreProcessor - (Source) - 对一个卷积神经网络层到循环神经网络层的转换做必要的激活修正处理。
ComposableInputPreProcessor - (Source) - 一个简单的类,允许多个预处理器链接在单个层上。
FeedForwardToCnnPreProcessor - (Source) - 对一个行向量到一个卷积网络层的转换做激活修正处理。注意这种转换或预处理仅在激活为真实的卷积神经网络激活,但已被扁平化为一个行向量。
FeedForwardToRnnPreProcessor - (Source) - 处理从(时间分布)前馈层到RNN层的转换。
RnnToCnnPreProcessor - (Source) - 处理从具有形状[minibatch,...,timeSeriesLength]格式的CNN激活序列到时间分布[numExam.*timeSeriesLength,numChannels,inputWidth,inputHeight]格式的转换。
RnnToFeedForwardPreProcessor - (Source) - 处理从时间序列激活(.[minibatch,size,timeSeriesLength])到时分布前馈(.[minibatch*tsLength,size])激活的转换。
迭代监听器:可以附加到模型,并在训练期间调用,在每次迭代之后(即,在每次参数更新之后)。训练监听器:迭代监听器的扩展。在训练的不同阶段调用许多附加方法。即在向前传递、梯度计算之后,在每次训练开始或结束。
没有(迭代/训练)在训练之外(即在输出或前馈方法中)被调用。
ScoreIterationListener - (Source, Javadoc) - 记录每隔n次训练迭代的损失函数评分。
PerformanceListener - (Source, Javadoc) -记录每N次训练迭代的性能(每秒钟多少示例,每秒钟多少微批次,ETL时间)并可以选择评分
EvaluativeListener - (Source, Javadoc) - 在一个测试集上评估每N次迭代或训练的网络性能。也有一个回调系统,来保存评估结果。
CheckpointListener - (Source, Javadoc) - 周期性的保存网络检查点-基于训练,迭代或时间(或这三个中的组合)
CollectScoresIterationListener - (Source, Javadoc) - 与ScoreIterationListener类似,但在一个本地的列表中保存评分(用于之后获取),而不是记录评分
TimeIterationListener - (Source, Javadoc) - 试图,基于当前的速度和指定的迭代次数估算训练完成之前的时间。
链接: 主要的评估页
DL4J具有用于评估网络性能的多个类,与测试集相对应。不同的评估类适合于不同类型的网络。
Evaluation - (Source) - 用于多类分类器的评估(假设标准one-hot标签,以及N类上的软最大概率分布用于预测)。计算一些度量-正确率,精确率,召回,F1,Fβ,马休斯相关系数,混淆矩阵。可选地计算前N正确率、自定义二分类决策阈值和成本数组(对于非二分类情况)。通常用于软最大 + 麦克森特/负对数似然网络。
EvaluationBinary - (Source) -评估类的多标签二分类版本。假设每个网络输出是独立的/独立的二分类,概率0到1与所有其他输出无关。通常用于sigmoid+二值交叉熵网络。
EvaluationCalibration - (Source) - 用于评价二分类或多类分类器的校准。产生可靠性图、残差图和概率直方图。使用EvaluationTools.exportevaluationCalibrationToHtmlFile 方法导出图表到HTML
ROC - (Source) - 仅用于单输出二分类器。即,具有nOut(1) + sigmoid, 或 nOut(2) + softmax。支持2种模式:阈值(近似)或精确(默认)。计算ROC曲线下面积,精确召回曲线下面积。使用EvaluationTools绘制ROC和P-R曲线到HTML。
ROCBinary - (Source) - 一个用于多标签二分类网络(即 sigmoid + 二值交叉熵)的ROC版本,它的每个网络的输出假被为一个独立的二分类变量。
ROCMultiClass - (Source) - 一个用于多类(非二分类)网络的ROC版本。 (即, softmax + 麦克森特/负对数似然 网络)。由于ROC度量仅定义为二分类,因此将多类输出视为一组“一对所有”的二分类问题。
RegressionEvaluation - (Source) - 一个用于回归模型的评估类(包括多输出回归模型)。报告每个输出的度量,例如均方误差(MSE)、平均绝对误差等。
可以使用ModelSerializer类,特别是writeModel、restoreMultiLayerNetwork和restoreComputationGraph方法来保存多层网络和计算图。
对于当前主干(但不是0.9.1) MultiLayerNetwork.save(File)方法 和 MultiLayerNetwork.load(File) 方法已被添加。这些在内部使用ModelSerializer。计算图也增加了类似的保存/加载方法。
示例: 加载与保存网络
网络可以在保存和加载之后进一步训练:但是,请确保加载“更新器”(即,更新器的历史状态,如momentum)。如果不需要进一步的训练,则更新器状态可以被忽略以节省磁盘空间和内存。
大多数归一化器(实现ND4J Normalizer接口)也可以使用addNormalizerToModel方法添加到模型中。
注意,DL4J中用于模型的格式是.zip:可以使用支持zip格式的程序打开/提取这些文件。
本节列出了DL4J支持的各种配置选项。
激活函数可以用两种方式之一定义:(a)通过向配置传递激活枚举值,例如,.activation(Activation.TANH)(b)通过传递IActivation实例,例如,.activation(new ActivationSigmoid())。
注意,DL4J支持自定义激活函数,它可以通过扩展BaseActivationFunction来定义。
支持的激活函数列表:
CUBE - (Source) - f(x) = x^3
HARDSIGMOID - (Source) - 标准sigmoid激活函数的分段线性化. f(x) = min(1, max(0, 0.2*x + 0.5))
HARDTANH - (Source) - 标准 tanh 激活函数的分段线性化.
IDENTITY - (Source) - 一个“无运算”激活函数: f(x) = x
LEAKYRELU - (Source) - 漏校正线性单元. f(x) = max(0, x) + alpha * min(0, x)
默认的 alpha=0.01
.
RELU - (Source) - 标准校正线性单元: f(x) = x
if x>0
或 f(x) = 0
SIGMOID - (Source) - 标准的 sigmoid 激活函数, f(x) = 1 / (1 + exp(-x))
SOFTMAX - (Source) - 标准的 softmax 激活函数
SOFTPLUS - (Source) - f(x) = log(1+e^x)
- 形状类似于RELU 激活函数的平滑版本
SOFTSIGN - (Source) - f(x) = x / (1+|x|)
- 形状类似于标准的 tanh 激活函数 (计算更快).
TANH - (Source) - 标准的 tanh (双曲正切) 激活函数
RECTIFIEDTANH - (Source) - f(x) = max(0, tanh(x))
SELU - (Source) - 比例指数线性单位,与自归一化神经网络一起使用
权值初始化指的是一个新网络的初始参数应该被设置的方法。
权重初始化通常使用WeightInit枚举来定义。
自定义权重初始化可以使用 .weightInit(WeightInit.DISTRIBUTION).dist(new NormalDistribution(0, 1))
例如. 对于主干 (非 0.9.1 版本) .weightInit(new NormalDistribution(0, 1))
也是可用的, 这相当于以前的方法。
可用的权重初始化。并不是所有的版本都在0.9.1版本中可用:
DISTRIBUTION: Sample weights from a provided distribution 从给定的分布获取权重样例 (specified 通过 dist
配置方法来指定)
ZERO: 生成权重为零
ONES: 所有权重设为1
SIGMOID_UNIFORM: sigmoid激活函数的一个XAVIER_UNIFORM版本。 U(-r,r) with r=4*sqrt(6/(fanIn + fanOut))
NORMAL: 均值为0,标准差为 1/sqrt(fanIn)的 正态/高斯分布。这是Klambauer等人提出的初始化,2017、“自归一化神经网络”论文。相当于 DL4J’的 XAVIER_FAN_IN 和 LECUN_NORMAL (即. Keras 的 “lecun_normal”)
LECUN_UNIFORM: U[-a,a] 与 a=3/sqrt(fanIn)保持统一
UNIFORM: U[-a,a] 与 a=1/sqrt(fanIn)保持统一。 Glorot和BeNIO 2010的“常用启发式”
XAVIER: As per Glorot and Bengio 2010: 均值 0, 方差为 2.0/(fanIn + fanOut)的高斯分布
XAVIER_UNIFORM: As per Glorot and Bengio 2010: 分布 U(-s,s) 与 s = sqrt(6/(fanIn + fanOut))保持统一
XAVIER_FAN_IN: 类似于Xavier, 除了 1/fanIn -> Caffe 原来用过这个.
RELU: He et al. (2015), “深入研究整流器”. 方差为2.0/nIn的正态分布
RELU_UNIFORM: He et al. (2015), “深入研究整流器”. 分布 U(-s,s) 与 s = sqrt(6/fanIn)保持统一
IDENTITY: 权重被设置为单位矩阵。注:只能与平方权重矩阵一起使用。
VAR_SCALING_NORMAL_FAN_IN: 均值0,方差为1.0/(fanIn)的高斯分布
VAR_SCALING_NORMAL_FAN_OUT: 均值0,方差为1.0/(fanOut)的高斯分布
VAR_SCALING_NORMAL_FAN_AVG: 均值0,方差为1.0/((fanIn + fanOut)/2)的高斯分布
VAR_SCALING_UNIFORM_FAN_IN: U[-a,a] 与 a=3.0/(fanIn)保持统一
VAR_SCALING_UNIFORM_FAN_OUT: U[-a,a] 与 a=3.0/(fanOut)保持统一
VAR_SCALING_UNIFORM_FAN_AVG:U[-a,a] 与 a=3.0/((fanIn + fanOut)/2)保持统一
DL4J中的“更新器”是一个需要原始梯度并将其修改为更新的类。然后将这些更新应用于网络参数。这篇CS231n 课程笔记对这些更新器有很好的解释。
DL4J支持的更新器:
Adam - (Source)
Nesterovs - (Source) - 牛顿动量更新器
NoOp - (Source) - 一个“无操作”更新程序。也就是说,梯度不会被这个更新器修改。数学等价于学习率为1的SGD更新器
Sgd - (Source) - 标准随机梯度下降更新器。此更新器仅适用学习速率。
支持学习速率的所有更新器也支持学习速率调度(牛顿动量更新器也支持动量调度)。学习速率调度可以根据迭代次数或已逝去的训练数来指定。Dropout(见下文)也可以利用这里列出的调度表。
配置用法,例如: .updater(new Adam(new ExponentialSchedule(ScheduleType.ITERATION, 0.1, 0.99 )))
你可以在你创建的调度对象上通过调用ISchedule.valueAt(int iteration, int epoch)
来制图/监视将在任意点使用的学习率。
可用的调度:
ExponentialSchedule - (Source) - 实现 value(i) = initialValue * gamma^i
InverseSchedule - (Source) - 实现 value(i) = initialValue * (1 + gamma * i)^(-power)
MapSchedule - (Source) - 基于用户提供的映射的学习率调度。注意所提供的映射必须有一个用于迭代/训练 0次 的值。有一个构建器类来方便的定义一个调度。
PolySchedule - (Source) - 实现 value(i) = initialValue * (1 + i/maxIter)^(-power)
SigmoidSchedule - (Source) - 实现 value(i) = initialValue * 1.0 / (1 + exp(-gamma * (iter - stepSize)))
StepSchedule - (Source) - 实现 value(i) = initialValue * gamma^( floor(iter/step) )
请注意,自定义调度可以通过实现ISchedule接口来创建。
L1和L2正则化可以容易地通过配置:.l1(0.1).l2(0.2)添加到网络中。注意, .regularization(true) 必须在0.9.1上启用(这个选项在0.9.1发布后被删除)。 L1和L2正则化仅适用于权重参数。也就是说,.l1 和 .l2 不会影响偏置参数-这些可以使用.l1Bias(0.1).l2Bias(0.2)实现被正则化。
所有的丢弃类型公在训练时应用。它们不在测试时应用。
Dropout - (Source) - 每个输入激活X被独立地设置为(0,与概率1-p)或(x/p与概率p)。
GaussianDropout - (Source) - 这是一个输入激活上的乘法高斯噪声(均值1)。每个输入激活X独立地设置为:x * y, y ~ N(1, stdev = sqrt((1-rate)/rate))
GaussianNoise - (Source) - 将加法,平均零高斯噪声应用于输入-即 x = x + N(0,stddev)
AlphaDropout - (Source) - AlphaDropout是一个丢弃技术,由Klaumbauer et al. 2017 - 自归一化神经网络提出。设计为自归一化神经网络(SELU 激活函数, NORMAL 权重初始化)。试图让丢弃后激活的均值和方差与AlphaDropout被应用之前相同。
注意(从当前主干开始,但不是0.9.1),丢弃参数也可以根据学习率调度部分中提到的任何调度类来指定。
根据丢弃,丢弃连接/权重噪声只适用于训练时间。
WeightNoise - (Source) - 在训练时把指定分布噪声应用于权重。支持加法和乘法模式。-当 加法时,噪声应当均值为0,当乘法时,噪声均值应当为1。
约束是在每次迭代结束时(在参数更新发生之后)放置在模型的参数上的确定性限制。它们可以被认为是正则化的一种类型。
MaxNormConstraint - (Source) - 将每个单元的输入权重的最大L2范数约束为小于或等于指定值。如果L2范数超过指定值,则权重将被缩减以满足约束。
MinMaxNormConstraint - (Source) -将每个单元的输入权重的最小和最大L2范数约束在指定值之间。如果需要的话,权重将被放大/缩小。
NonNegativeConstraint - (Source) - 约束所有参数为非负。负参数将被替换为0。
UnitNormConstraint - (Source) -将每个单元的输入权重的L2范数约束为1。
DataSetIterator是DL4J用于对小批量数据进行迭代的抽象,用于训练。DataSetIterator返回DataSet对象,这些对象是小批量,并支持最多1个输入和1个输出数组(INDArray)。 MultiDataSetIterator类似于DataSetIterator,但是返回MultiDataSet对象,该对象可以具有网络所需的多个输入和多个输出数组。
这些迭代器按需要下载它们的数据。它们返回的实际数据集不是可定制的。
MnistDataSetIterator - (Source) - 著名的MNIST数字数据集的DataSetIterator。默认情况下,返回行向量(1x784),其值被归一化为0至1范围。使用.setInputType(InputType.convolutionalFlat())来与CNN一起使用。
IrisDataSetIterator - (Source) -一个众所周知的鸢尾花数据集的迭代器。4个特征,3个输出类。
CifarDataSetIterator - (Source) - CIOFAR图像数据集的迭代器。10类,在DL4J中CNNs的4D特征/激活格式:[minibatch,channels,height,width] = [minibatch,3,32,32]。特征不是归一化的,而是在0到255的范围内。
LFWDataSetIterator - (Source)
TinyImageNetDataSetIterator (Source) - 标准IMANET数据集的子集;200个类,每个类500个图像
UciSequenceDataSetIterator (Source) - UCI 综合控制时间序列数据集
此子章节的迭代器与用户提供的数据一起使用。
RecordReaderDataSetIterator - (Source) - 采用DataVec记录读取器(如CsvRecordReader或ImageRecordReader)并处理到数据集的转换、批处理、屏蔽等的迭代器。DL4J中最常用的迭代器之一。只处理非序列数据,作为输入(即,RecordReader,非SequenceeRecordReader)。
RecordReaderMultiDataSetIterator - (Source) - RecordReaderDataSetIterator 的MultiDataSet版本, 支持多个读取器。具有用于创建更复杂的数据管道的构建器模式(例如,读取器输出到不同输入/输出阵列的不同子集、转换到一个热点等等)。处理序列和非序列数据作为输入。
SequenceRecordReaderDataSetIterator - (Source) - RecordReaderDataSetIterator 的sequence (SequenceRecordReader) 版本。 用户最好结合RecordReaderMultiDataSetIterator使用。
DoublesDataSetIterator - (Source)
FloatsDataSetIterator - (Source)
INDArrayDataSetIterator - (Source)
MultiDataSetIteratorAdapter - (Source) - 包装一个 DataSetIterator来转换为一个MultiDataSetIterator
SingletonMultiDataSetIterator - (Source) - 包装一个MultiDataSet 转换为一个 MultiDataSetIterator 并返回一个 MultiDataSet (即, 包装的MultiDataSet是不可分割的)
AsyncDataSetIterator - (Source) - 在适当的情况下由多层网络和计算图自动使用。实现数据集的异步预获取以提高性能。
AsyncMultiDataSetIterator - (Source) - 在适当的情况下由计算图自动使用。实现多数据集的异步预获取以提高性能。
AsyncShieldDataSetIterator - (Source) - 通常只用于调试。使用AsyncDataSetIterator来停止多层网络和计算图。
AsyncShieldMultiDataSetIterator - (Source) - AsyncShieldDataSetIterator 的 MultiDataSetIterator 版本。
EarlyTerminationDataSetIterator - (Source) - 包装另一个DataSetIterator,确保在重置之间仅返回指定(最大)数量的小批量(DataSet)对象。可以用来“剪短”一个迭代器,只返回前N个数据集。
EarlyTerminationMultiDataSetIterator - (Source) - EarlyTerminationDataSetIterator的MultiDataSetIterator版本
ExistingDataSetIterator - (Source) - 转换一个 Iterator<DataSet>
或 Iterable<DataSet>
为 一个 DataSetIterator。 不拆分基础数据集对象
FileDataSetIterator - (Source) - 一个迭代器,用于迭代以前用 DataSet.save(File)保存的DataSet文件。支持随机化、过滤、不同的输出批量大小与保存的数据集批量大小等。
FileMultiDataSetIterator - (Source) - FileDataSetIterator的MultiDataSet版本。
IteratorDataSetIterator - (Source) - 转换一个 Iterator<DataSet>
为一个 DataSetIterator. 与ExistingDataSetIterator不同,底层DataSet对象可以是拆分/组合的——即,对于输出,小批量大小可能与输入迭代器不同。
IteratorMultiDataSetIterator - (Source) - IteratorDataSetIterator 的Iterator<MultiDataSet>版本
MultiDataSetWrapperIterator - (Source) - 转换一个MultiDataSetIterator 为一个 DataSetIterator。 注意,如果特征和标签数组的数量等于1,才是可能的。
MultipleEpochsIterator - (Source) - 当训练时,将基础迭代器的多次训练视为单个训练。
WorkspaceShieldDataSetIterator - (Source) - 通常只用于调试,而通常不由用户使用。分离/迁移来自底层DataSetIterator的数据集。
ND4J提供了用于执行数据归一化的多个类。这些实现为数据集预处理器。归一化的基本模式:
创建你的 (非归一化) DataSetIterator 或 MultiDataSetIterator: DataSetIterator myTrainData = ...
创建你想使用的归一化器: NormalizerMinMaxScaler normalizer = new NormalizerMinMaxScaler();
拟合归一化器: normalizer.fit(myTrainData)
在迭代器上设置归一化器/预处理器 : myTrainData.setPreProcessor(normalizer);
最终结果:来自DataSetIterator的数据现在将被归一化。
通常你应该只在训练数据上拟合,并且与仅在训练数据上拟合的相同的/单一的归一化器一起执行 trainData.setPreProcessor(normalizer)
和 testData.setPreProcessor(normalizer)
注意,在适当的情况下(NormalizerStandard.,NormalizerMinMaxScaler),诸如平均值/标准偏差/最小值/最小值的统计数据,跨时间(对于时间序列)和跨图像x/y位置(但是对于图像数据不是深度/通道)共享。
数据归一化示例: 链接
可用的归一化器: DataSet / DataSetIterator
ImagePreProcessingScaler - (Source) - 应用最小最大缩放到图像激活。默认设置将0到255输入到0-1输出(但是是可配置的)。注意,与这里的其他归一化器不同,该归一化器不依赖于从数据收集的统计数据(均值/最小值/最大值 等),因此normalizer.fit(trainData)步骤是不必要的(是非操作性的)。
NormalizerStandardize - (Source) - 独立地将每个特征值(和可选的标签值)归一化为0平均值和1的标准差。
NormalizerMinMaxScaler - (Source) - 独立归一化每个特征值(以及可选的标签值),使其位于最小值和最大值之间(默认情况下在 0和1之间)
可用的归一化器: MultiDataSet / MultiDataSetIterator
ImageMultiPreProcessingScaler - (Source) - ImagePreProcessingScaler的MultiDataSet/MultiDataSetIterator版本
MultiNormalizerStandardize - (Source) - NormalizerStandardize的MultiDataSet/MultiDataSetIterator版本
MultiNormalizerMinMaxScaler - (Source) - NormalizerMinMaxScaler的 MultiDataSet/MultiDataSetIterator 版本
MultiNormalizerHybrid - (Source) - 一个 MultiDataSet归一化器,可以为 不同的 输入/特征 和输出/标签 数组组合不同的归一化类型(标准化,最小/最大化) 。
DL4j具有用于执行迁移学习的类/实用程序——即,采用现有网络,并修改一些层(可选地冻结其他层,以便它们的参数不改变)。例如,可以在ImageNet上训练图像分类器,然后应用于新的/不同的数据集。多层网络和计算图都可以与迁移学习一起使用——通常从模型动物园的预训练模型开始(参见下一节),虽然可以单独使用任何多层网络/计算图。
链接: 迁移学习示例
迁移学习的主要类别是TransferLearning。该类具有可用于添加/删除层、冻结层等的构建器模式。FineTuneConfiguration可用于指定非冻结层的学习速率和其他设置。
DL4J提供了一个“model zoo”——一组预训练模型,可以下载和使用(例如,用于图像分类),或者经常用于迁移学习。
DL4J 的 model zoo中可用的模型有:
AlexNet - (Source)
Darknet19 - (Source)
FaceNetNN4Small2 - (Source)
InceptionResNetV1 - (Source)
LeNet - (Source)
ResNet50 - (Source)
SimpleCNN - (Source)
TextGenerationLSTM - (Source)
TinyYOLO - (Source)
VGG16 - (Source)
VGG19 - (Source)
*注: Keras 已训练好的模型 (不是 DL4J 提供) 或许也可以导入, 使用 DL4J的 Keras 模型导入功能。
Eclipse DL4J库提供了很多功能,我们将这个速查表放在一起,以帮助用户组装神经网络并更快地使用张量。
用于多层网络和计算图的通用参数和层的配置代码。完整的API见MultiLayerNetwork和ComputationGraph。
序列网络
大多数网络配置可以使用多层网络类,如果它们是序列的和简单的。
复杂网络
具有复杂图和“分支”的网络需要使用计算图。
下面的代码片段创建一个基本的管道,从磁盘加载图像,应用随机变换,并将它们拟合到神经网络。它还设置了UI实例,以便你可以可视化进度,并使用早期停止来提前终止训练。你可以为许多不同的用例修改此管道。
DataVec附带了一个便利的转换进程类,允许更复杂的数据冲突和数据转换。它与2D和序列数据集都能很好地工作。
在创建更复杂的转换之前,我们建议先查看一下 DataVec examples。
MultiLayerNetwork和ComputationGraph都带有内置的eval()方法,允许你传递数据集迭代器并返回评估结果。
对于高级评估,下面的代码片段可以被适用于训练管道。这是当内置的neuralNetwork.eval()方法输出混乱的结果或你需要检查原始数据时需要使用。
评估神经网络性能的工具和类
当训练或部署神经网络时,了解模型的准确性是有用的。在DL4J中,评估类和评估类的变体可用于评估模型的性能。
评估类用于评估二分类和多类分类器(包括时间序列分类器)的性能。本节介绍了评估类的基本用法。
给定一个DataSetIterator形式的数据集,执行评估的最简单方法是使用MultiLayerNetwork和ComutationGraph上的内置评估方法:
然而,也可以对单个小批量进行评价。这里是一个例子,从我们的示例项目中数据实例/CSV示例中获得。
CSV的例子有3类花的CSV数据,建立了一个简单的前馈神经网络用于对基于4个测量值的花的分类。
第一行创建一个具有3个类的评估对象。第二行从模型中获取我们测试数据集的标签。第三行使用eval方法将来自testdata的标签数组与从模型生成的标签进行比较。第四行将评估数据记录到控制台。
输出
默认情况下,.stats() 方法显示混淆矩阵条目(每行一个)、准确度、精度、召回率和F1分数。此外,评估类还可以计算并返回以下值:
混淆矩阵
假阳性/阴性率
真阳性/阴性
类别计数
F-beta, G-measure, Matthews 关系数及更多, 查看 Evaluation JavaDoc
显示混淆矩阵。
显示
此外,可以直接访问混淆矩阵,使用CSV或HTML转换。
为了评估执行回归的网络,使用回归评估类。
带着评估类,一个DataSetIterator上的回归评估可以执行如下:
这里有一个单列的代码片段,在这种情况下,神经网络是根据测量值来预测自己的年龄。
打印评估的统计数据。
返回
列是均方误差、均方绝对误差、均方根误差、相对平方误差和R^2决定系数。
查看 回归评估JavaDoc
当执行多种类型的评估时(例如,在同一网络和数据集上执行评估和ROC),在数据集的一次传递中执行以下操作更有效:
时间序列评估与上述评估方法非常相似。DL4J中的评估对所有(非掩码的)时间步分别执行——例如,长度为10的时间序列将为评估对象贡献10个预测/标签。与时间序列的一个不同之处在于掩码数组是(可选的),这些掩码数组用于将一些时间步标记为丢失或不存在。请参阅使用RNNS掩码以获得更多关于掩码的细节。
对于大多数用户来说,仅仅使用 MultiLayerNetwork.evaluate(DataSetIterator)
或 MultiLayerNetwork.evaluateRegression(DataSetIterator)
和类似的方法就足够了。如果掩码数组存在,这些方法将正确地处理掩码。
EvaluationBinary用于评估具有二分类输出的网络——这些网络通常具有Sigmoid激活函数和XENT损失函数。为每个输出计算典型的分类度量,例如准确度、精度、召回率、F1得分等。
ROC(接收者操作特征)是另一种常用的评估分类器的评估指标。DL4J中存在三个ROC变体:
ROC -用“一对全部”的方法评估非二分类器
ROCBinary - 用于单二分类标签(作为单列概率,或两列的softmax概率分布)
ROCMultiClass - 用于多二分类标签
这些类具有通过calculateAUC()和calculateAUPRC()方法计算ROC曲线下面积(AUROC)和精确度-召回曲线下面积(AUPRC)的能力。此外,可以使用getRocCurve()
和getPrecisionRecallCurve()
获得ROC和精确度-召回曲线。
ROC和精确度-召回曲线可以导出到HTML以便查看,使用:“EvaluationTools.exportRocChartsToHtmlFile(ROC,File)”,该文件将导出具有ROC和精确度-召回曲线的HTML文件,可以在浏览器中查看。
注意,所有三种支持两种操作/计算模式。
阈值(近似AUROC/AUPRC计算,无内存问题)
精确(精确的AUROC/AUPRC计算,但是对于非常大的数据集(即具有数百万个示例的数据集)可能需要大量的内存
可以使用构造函数设置容器的数量。可以使用默认构造函数new ROC()
来精确设置,或者显式地使用new ROC(0)
。
参见ROCBinary JavaDoc用于评估二元分类器。
DL4J还具有评估校准类,它被设计用于分析分类器的校准。它提供了许多的工具用于如下目的:
每个类别的标签数量和预测的计数
可靠性图(或可靠性曲线)
残差图(直方图)
概率直方图,包括每个类的概率
使用评估校准的分类器评估方式与其它评估类相似。可以使用EvaluationTools.exportevaluationCalibrationToHtmlFile(EvaluationCalibration, File)
将各种绘图/直方图导出到HTML以便查看。
SparkDl4jMultiLayer 和 SparkComputationGraph 都有相似的评估方法:
多任务网络是经过训练以产生多个输出的网络。例如,可以对给定音频样本的网络进行训练,以预测说话者的语言和说话人的性别。这里简要描述了多任务配置。
适用于多任务网络的评估类
了解常见错误如NaNs和调整超参数。
神经网络很难调优。如果网络超参数选择不当,网络学习可能会慢,或者根本不学习。本页旨在提供在调优网络时应采取的一些基准步骤。
这些技巧中的许多已经在学术文献中讨论过。我们的目的是把它们合并在一个网站中,并尽可能清楚表达他们。
你的数据分布是什么?你正在适当地缩放它吗?作为一般规则:
对于连续值:你希望这些值在-1到1、0到1的范围内,或者以平均值0和标准偏差1正态分布。这不一定是准确的,但确保在训练期间你的输入大约在这个范围内会有所帮助。缩小大输入,扩大小输入。
对于离散类(以及对于输出的分类问题),通常使用one-hot表示。也就是说,如果你有3个类,那么对于3个类中的每一个,你的数据将分别重新表示为 [1,0,0]、[0,1,0]或[0,0,1]。
注意,对于训练数据和测试数据,使用完全相同的归一化方法是非常重要的。
DL4J支持几种不同类型的权重初始化,使用权重初始化参数。在你的配置中使用.weightInit(WeightInit)方法来设置。
你需要确保你的权重既不太大也不太小。Xavier权重初始化通常是一个很好的选择。对于具有整流线性(Relu)或leaky relu激活的网络,Relu权重初始化是明智的选择。
一个epoch被定义为数据集的完整传递。
太少的epoch没有给你的网络足够的时间来学习好的参数;太多,你可能会过度拟合训练数据。选择epoch数量的一种方法是使用早停。早停还可以帮助防止神经网络过拟合(即,可以帮助网络更好地对看不见的数据进行泛化)。
学习率不是最重要的超参数之一。如果这是太大或太小,你的网络可能会学习很差,非常缓慢,或根本没有。学习速率的典型值在0.1至1e-6的范围内,然而最佳学习速率通常是特定于数据(和网络架构)的。一些简单的建议是先尝试三种不同的学习速率——1e-1、1e-3和1e-6——在进一步调优之前大致了解应该做什么。理想情况下,他们同时运行具有不同学习速率的模型,以节省时间。
选择适当学习率的通常方法是使用DL4J的可视化界面来可视化训练过程。你需要同时注意时间上的损失,以及更新量与参数量的比率(大约1:1000的比例是一个好的开始)。有关调整学习速率的更多信息,请参见此链接。
与在单台机器上训练相同的网络相比,以分布式方式训练神经网络,可能需要不同的(经常更高的)学习速率。
你可以为你的神经网络选择一个学习速率策略。策略会随着时间的推移而改变学习率,获得更好的结果,因为学习速率可以“减速”,以找到更接近的局部极小收敛。常用的策略是调度。有关实践中使用的学率调度,请参阅LeNet 示例。
请注意,如果使用多个GPU,这将影响你的调度。例如,如果你有2个 GPU,那么你需要将你的调度中的迭代分成2份,因为你的训练过程的吞吐量将是两倍,并且学习速率调度只适用于本地GPU。
关于激活函数的选择,有两个方面需要注意。
首先,隐藏(非输出)层的激活函数。一般来说,“Relu”或“leakyrelu”激活是很好的选择。一些其他的激活函数(tanh、sigmoid等)更容易出现梯度消失问题,这使得深度神经网络的学习更加困难。然而,对于LSTM层,TANH激活函数仍然是常用的。
第二,关于输出层的激活函数:这通常是特定于应用的。对于分类问题,通常需要使用softmax激活函数,结合负对数似然度/MCXENT.(多类交叉熵)。softmax激活函数为你提供了分类的概率分布(即,输出总和为1)。对于回归问题,结合均方误差(MSE)损失函数,“identity”激活函数通常是一个很好的选择。
每个神经网络层的损失函数既可以用于预训练、学习更好的权重,也可以用于分类(在输出层)以获得一些结果。(在上面的例子中,分类发生在覆盖部分)。
你的网络的目的将决定你使用的损失函数。对于预训练,选择重建熵。对于分类,使用多类交叉熵。
正则化方法有助于避免训练过程中的过拟合。当网络很好地预测训练集,但是对网络从未见过的数据做出糟糕的预测时,就会发生过拟合。一种考虑过拟合的方法是网络记忆了训练数据(而不是学习其中的总体关系)。
正则化的常见类型包括:
L1和L2正则化惩罚了大的网络权重,并且避免了权重变得太大。L2正则化的一些级别在实践中是常用的。然而,请注意,如果l1或l2正则化系数太高,它们可能对网络造成过度惩罚,并阻止其学习。L2正则化的通常值是1E-3至1E-6。
丢弃,是一种常用的正则化方法,可以非常有效。最常用的丢弃率为0.5。
丢弃连接(概念上类似于丢弃,但使用得不太频繁)
限制网络大小的总数(即,限制每个层的层数和大小)
使用L1/L2/dropout正则化,分别使用regularization(TRUE)和L1(x)、L2(y)、.dropout(z)。注意,在dropout(z)中的z是保持激活的概率。
minibatch指的是在计算梯度和参数更新时一次使用的实例的数量。在实践中(除了最小的数据集之外),将数据设置成多个小批量是标准的做法。
理想的小批量大小将有所不同。例如,一个大小为10的小批量对于GPU来说常常太小,但是可以在CPU上工作。大小为1的小批量将允许网络进行训练,但不会获得并行性的好处。32可以是尝试的合理起点,其中16-128(有时小于或大于或取决于应用和网络类型)范围内的小批量是常见的。
在DL4J中,术语“更新器”指的是训练机制,如动量、RMSProp、adagrad等。与“vanilla”随机梯度下降相结合,使用这些方法中的一种可以导致更快的网络训练。可以使用.updater(Updater) 配置选项设置更新器。
给定梯度,优化算法是如何进行更新。最简单(也是最常用的)的方法是随机梯度下降(SGD),但是DL4J也为SGD提供了线性搜索、共轭梯度和LBFGS优化算法。与SGD相比,后者的算法更强大,但是由于线性搜索组件,每次参数更新的成本要高得多,并且在实践中没有使用太多。请注意,原则上可以将任何更新器与任何优化算法相结合。
在大多数情况下,一个好的默认选择是使用随机梯度下降优化算法结合动量/rmsprop/adagrad更新器之一,动量在实践中经常使用。注意,对于动量,更新器称为NESTEROVS,动量速率可以通过 .momentum(double) 选项设置。
在训练神经网络时,有时应用梯度归一化是有帮助的,以避免梯度太大(所谓的梯度爆炸问题,在循环神经网络中常见)或太小。这可以用.gradientNormalization(GradientNormalization) 和.gradientNormalizationThreshould(double) 方法来实现。对于梯度归一化的例子参见,GradientNormalization.java。这个例子的测试代码在这里。
在训练具有长时间序列的循环网络时,一般建议使用截断反向传播算法。在“标准”的反向传播(在DL4J中默认),每个参数更新的成本可以变为禁止的。有关详细信息,请参阅本页。
当使用深度信念网络时,请密切关注。受限波尔滋曼机(用于特征提取的DBN的组件)是随机的,并且将从相对于指定的可见或隐藏单元的不同概率分布中采样。
有关所有不同概率分布的列表,请参阅Geoff Hinton的定论《训练受限波尔滋曼机实用指南》。
当为执行压缩的自编码器创建隐藏层时,给它们比输入数据更少的神经元。如果隐藏层节点太接近输入节点的数量,则有可能有重建恒等函数的风险。太多隐层神经元增加了噪声和过拟合的可能性。对于784的输入层,你可以选择500的初始隐藏层和250的第二隐藏层。没有隐藏层应该少于输入层节点的四分之一。输出层将仅仅是与标签的数量相等。
较大的数据集需要更多的隐藏层。脸谱网的Deep Face使用了九个隐藏层,我们只能假设它是一个巨大的语料库。许多较小的数据集可能只需要三或四个隐藏层,其精度降低到超过该深度。通常情况下,较大的数据集包含更多的变化,需要更多的特征/神经元来获得准确的结果。典型的机器学习,当然,有一个隐藏层,而那些浅网被称为感知器。
大型数据集需要预先训练受限波尔滋曼机多次。只有通过多个预训练,算法才能够正确地在数据集的上下文中学习权重特征。也就是说,你可以并行地运行数据,或者通过集群来加速预训练。
问:为什么我的神经网络会抛出NaN值?
答:反向传播涉及非常小的梯度的乘法,因为在表示非常接近于零的实数值时精度有限,无法表示。这个问题的术语是算术下溢。如果你的神经网络正在抛出NaN值,那么解决方案是重新调整你的网络以避免非常小的梯度。这更可能是一个更深层次的神经网络问题。
你可以尝试使用double数据类型,但通常建议先重新调整网络。
遵循基本的调整技巧和监控结果是确保NAN不再出现的方法。
此页包含一些常见分布式训练任务的操作指南。请注意,有关构建数据管道的指南,请参见此处。
在阅读这些指南之前,请确保您已经阅读了此处的DL4J Spark训练入门指南。
训练前指南
如何使用Maven通过Spark submit构建用于训练的uber JAR
如何利用GPU进行Spark训练
如何在master上使用CPU,在workers上使用GPU
如何为Spark配置内存设置
如何为workers配置垃圾回收
如何与DL4J和ND4J一起使用Kryo序列化
如何使用YARN和GPU
如何配置Spark本地配置
训练时与训练后指南
如何配置编码阈值
如何进行分布式测试集评估
如何保存(和加载)Spark训练的神经网络
如何进行分布式推理
问题和故障排除指南
如何调试常见的Spark依赖问题(NoClassDefFoundException和类似)
如何修复“Error querying NTP server”错误
如何安全缓存RDD[INDArray]和RDD[DataSet]
在Ubuntu16.04上训练失败(Ubuntu Bug可能会影响DL4J Spark用户)
当向集群提交一个训练作业时,一个典型的工作流是构建一个提交给Spark submit的“uber jar”。uber jar是包含运行作业所需的所有依赖项(库、类文件等)的单个jar文件。请注意,Spark submit是一个带有Spark发行版的脚本,用户将其作业(以JAR文件的形式)提交到该发行版,以便开始执行其Spark作业。
本指南假设您已经设置了用于在Spark上训练网络的代码。
步骤1:决定所需的依赖项。
与DL4J和ND4J的单机训练有很多重叠。例如,对于单机训练和Spark训练 ,您应该包括标准的deeplearning4j依赖项集,例如:
deeplearning4j-core
deeplearning4j-spark
nd4j-native-platform (仅用于CPU训练)
此外,您还需要包括Deeplearning4j的Spark模块、dl4j-spark_2.10
或dl4j-spark_2.11
。本模块是开发和执行Deeplearning4j Spark作业所必需的。小心使用与集群匹配的spark版本-spark版本(Spark 1 vs.Spark 2)和Scala版本(2.10 vs.2.11)。如果这些不匹配,则您的作业可能在运行时失败。
依赖示例: Spark 2, Scala 2.11:
依赖示例, Spark 1, Scala 2.10:
请注意,如果添加Spark依赖项,如spark-core_2.11,则可以在pom.xml中设置scope为provided
scope(有关更多详细信息,请参阅Maven文档),因为Spark submit将添加Spark到类路径。在集群上执行时不需要添加此依赖项,但如果要在本地计算机上测试或调试基于Spark的作业,则可能需要添加此依赖项。
在使用CUDA GPU进行训练时,在添加CUDA依赖项时有两种可能的情况:
案例1:集群节点在主节点和工作节点上安装了CUDA工具包
当CUDA工具包和CuDNN在集群节点上可用时,我们可以使用更小的依赖:
如果构建uber-jar的操作系统与集群的操作系统相同:包含 nd4j-cuda-x.x
如果构建uber jar的操作系统与集群操作系统不同(即,在Windows上构建,在Linux集群上执行Spark):包含 nd4j-cuda-x.x-platform
在这两种情况下,包括x.x是CUDA版本的地方——例如,x.x=9.2是CUDA 9.2。
案例2:集群节点没有在主节点和工作节点上安装CUDA工具包
当群集节点上未安装CUDA/CuDNN时,我们可以执行以下操作:
首先,按照上面的“案例1”包含依赖项
然后包括集群操作系统的“redist” javacpp-presets,如下所述:DL4J CuDNN Docs
步骤2:配置pom.xml文件以构建uber jar
使用Spark submit时,您需要一个uber jar来提交以启动和运行您的作业。在步骤1中配置了相关的依赖项之后,我们需要配置pom.xml文件来正确构建uber-jar。
我们建议您使用maven shade插件来构建uber-jar。有其他工具/插件可以用于此目的,但这些工具/插件并不总是包含源jar中的所有相关文件,例如Java的ServiceLoader机制正常运行所需的文件。(ServiceLoader机制被ND4J和许多其他软件库使用)。
独立示例项目pom.xml文件中提供了适用于此目的的Maven shade配置:
步骤3: 构建 uber jar
最后,打开一个命令行窗口(Linux上的bash、Windows上的cmd等),只需运行mvn package-DskipTests
为您的项目构建uber-jar。注意,uber-jar应该出现在<project_root>/target/<project_name>-bin.jar
下。一定要使用大的…-bin.jar文件,因为这是一个带有所有依赖项的着色jar文件。
这是-你现在应该有一个uber jar,适合提交到Spark submit,用于spark上与cpu或NVIDA(CUDA)gpu的训练网络。
DL4J和ND4J使用NVIDA GPU支持GPU加速。DL4J Spark训练也可以使用GPU执行。
DL4J和ND4J的设计方式使得代码(神经网络配置,数据管道代码)是“后端独立的”。也就是说,您可以只编写一次代码,然后在CPU或GPU上执行它,只需包含适当的后端(nd4j-native用于CPU,nd4j-cuda-x.x用于GPU)。在Spark上执行与在单个节点上执行在这方面没有区别:您只需要包括适当的ND4J后端,并确保您的计算机(在本例中是主/工作节点)与CUDA库进行了适当的设置(有关在CUDA上运行而无需在每个节点上安装CUDA/cuDNN的信息,请参阅uber-jar指南)。
在GPU上运行时,有几个组件:(a)ND4J CUDA后端(nd4j-cuda-x.x 依赖项)(b)CUDA工具包(c)获得cuDNN支持的Deeplearning4j CUDA依赖项(deeplearning4j-cuda-x.x)(d)cuDNN库文件
(a)和(b)都必须可供ND4J/DL4J使用可用的CUDA GPU运行。(c) 和(d)是可选的,但建议获得最佳性能-NVIDIA的cuDNN库能够显著加快许多层的训练,例如卷积层(ConvolutionLayer, SubsamplingLayer, BatchNormalization等)和LSTM RNN层。
有关为Spark作业配置依赖项的信息,请参阅上面的uber-jar部分。要在单个节点上配置cuDNN,请参阅将Deeplearning4j与cuDNN一起使用
在某些情况下,只使用CPU运行master和使用GPU运行workers可能是有意义的。如果资源(即,可用GPU机器的数量)不受限制,那么使用同构集群可能更容易:即,设置集群,以便主机也使用GPU执行。
假设master/driver在CPU机器上执行,而workers在GPU机器上执行,那么您可以简单地包括两个后端(即nd4j-cuda-x.x和nd4j-native依赖项,如uber-jar部分所述)。
当类路径上存在多个后端时,默认情况下将首先尝试CUDA后端。如果无法加载,则将第二次加载CPU(nd4j-native)后端。因此,如果驱动程序没有GPU,它应该回到使用CPU。但是,可以通过在master/driver上设置BACKEND_PRIORITY_CPU
或BACKEND_PRIORITY_GPU
环境变量来更改此默认行为,如此处所述。设置环境变量的确切过程可能取决于集群管理器-Spark 独立 vs.YARN vs.Mesos。关于如何为driver/master设置Spark作业的环境变量,请参考每个相应的文档。
有关DL4J和ND4J的内存和内存配置工作原理的重要背景,请从阅读ND4J/DL4J的内存管理开始。
Spark上的内存管理类似于单节点训练的内存管理:
堆内存是使用标准Java Xms和Xmx内存配置设置配置的
堆外内存是使用javacpp系统属性配置的
然而,Spark上下文中的内存配置增加了一些额外的复杂性:1、通常,对于driver/master和workers序必须分别进行内存配置(有时使用不同的机制)。2、配置内存的方法可以依赖于集群资源管理器-Spark Standalone 和YARN 和Mesos,等等 。3、群集资源管理器默认内存设置通常不适用于严重依赖堆外内存的库(如DL4J/ND4J)
请参阅群集管理器的Spark文档:
你应该设置4个内容:1、堆内存上的worker(Xmx)-通常设置为Spark submit的参数(例如,--executor-memory 4g
用于 YARN)2、worker堆外内存(javacpp系统属性选项)(例如,--conf "spark.executor.extraJavaOptions=-Dorg.bytedeco.javacpp.maxbytes=8G"
)。3、堆内存上的驱动程序-通常设置为。4、驱动程序堆外内存
一些注释:
在YARN上,通常需要设置spark.yarn.driver.memoryOverhead
和spark.yarn.executor.memoryOverhead
属性。对于DL4J训练,默认设置太小。
在Spark standalone上,还可以通过修改每个节点上的conf/spark-env.s
文件来配置内存,如Spark配置文档中所述。例如,可以添加以下行来设置driver的8GB堆内存、driver的12 GB堆外内存、workers的12GB堆内存和18GB堆外内存:
SPARK_DRIVER_OPTS=-Dorg.bytedeco.javacpp.maxbytes=12G
SPARK_DRIVER_MEMORY=8G
SPARK_WORKER_OPTS=-Dorg.bytedeco.javacpp.maxbytes=18G
SPARK_WORKER_MEMORY=12G
总的来说,这可能看起来像(对于YARN,有4GB的堆内存,5GB的堆外内存,6GB的YARN堆外开销):
训练效果的一个决定因素是垃圾收集的频率。使用默认启用的工作间(另请参阅这里)时,减少垃圾收集的频率可能会有所帮助。对于简单的机器训练(在driver上),这很简单:
训练效果的一个决定因素是垃圾收集的频率。使用默认启用的工作区(另请参此处)时,减少垃圾收集的频率可能会有所帮助。对于简单的机器训练(以及对driver的训练),这很简单:
但是,在driver上设置此项不会更改workers上的设置。相反,可以为workers设置如下:
默认值(从1.0.0-beta3开始)是每5秒对workers执行一次定期垃圾收集。
DL4J和ND4J可以利用Kryo序列化,配置适当。请注意,由于INDArrays的堆外内存,与在其他上下文中使用Kryo相比,Kryo提供的性能优势要小一些。
要启用Kryo序列化,首先添加nd4j-kryo依赖项:
其中${dl4j-version}
是用于DL4J和ND4J的版本。
然后,在训练工作开始时,添加以下代码:
注意,当使用DL4J的SparkDl4jMultiLayer或SparkComputationGraph类时,如果Kryo配置不正确,将记录一个警告。
对于DL4J,CUDA GPU的唯一要求是使用适当的后端,在每个节点上安装适当的NVIDIA库,或者在uber-JAR中提供(有关更多详细信息,请参阅Spark操作指南)。对于最新版本的YARN,在某些情况下可能需要一些额外的配置-有关更多详细信息,请参阅YARN GPU文档。
早期版本的YARN(例如,2.7.x和类似版本)本地不支持GPU。对于这些版本,可以利用节点标签来确保将作业调度到仅GPU的节点上。有关更多详细信息,请参见Hadoop YARN文档。
请注意,还需要特定于YARN的内存配置(请参阅内存操作指南)。
配置Spark位置设置是一个可选的配置选项,可以提高训练性能。
小结:在Spark submit 配置中添加--conf spark.locality.wait=0
可能会稍微减少训练时间,方法是将network fit操作安排为更早开始。
DL4J的Spark实现使用阈值编码方案在节点之间发送参数更新。这种编码方案产生一个小的量化消息,这大大降低了网络通信更新的成本。有关此编码过程的详细信息,请参阅技术说明页。
这个阈值编码过程引入了一个“分布式训练特定”超参数-编码阈值。太大的阈值和太小的阈值都可能导致次优性能:
较大的阈值意味着不频繁的通信-太不频繁收敛可能会受到影响
较小的阈值意味着更频繁的通信,但在每个步骤中都会进行较小的更改
要使用的编码阈值由 ThresholdAlgorithm控制。ThresholdAlgorithm的具体实现决定了应该使用什么阈值。
DL4J的默认行为是使用 AdaptiveThresholdAlgorithm 算法,该算法尝试将稀疏率保持在一定范围内。
稀疏比定义为numValues(encodedUpdate)/numParameters-1.0表示完全密集(所有值都已传递),0.0表示完全稀疏(没有值传递)
较大的阈值意味着更多的稀疏值(更少的网络通信),较小的阈值意味着更少的稀疏值(更多的网络通信)
默认情况下,AdaptiveThresholdAlgorithm
尝试将稀疏度比率保持在0.01到0.0001之间。如果更新的稀疏性不在此范围内,则阈值将增加或减少,直到它在此范围内为止。
仍然需要设置初始阈值-我们发现
在实践中,我们已经看到了这种自适应阈值过程能够很好地工作。阈值算法的内置实现包括:
AdaptiveThresholdAlgorithm
FixedThresholdAlgorithm: 使用指定编码阈值的固定、非自适应阈值。
TargetSparsityThresholdAlgorithm: 一种自适应阈值算法,针对特定的稀疏性,增加或减少阈值以尝试匹配目标。
此外,DL4J还有一个ResidualPostProcessor接口,默认实现是ResidualClippingPostProcessor,它每5步将残余向量裁剪到当前阈值的最大5倍。这样做的动机是,更新的“残余”部分(即,那些未通信的部分)存储在残余向量中。如果更新远远大于阈值,我们可以有一个我们称之为“残余爆炸”的现象,也就是说,残余值可以继续增长到阈值的许多倍(因此需要采取许多步骤来传达梯度)。为了避免这种现象,采用了残余后处理器。
阈值算法(和初始阈值)和残余后处理器可以设置如下:
最后,DL4J的SharedTrainingMaster还有一个编码调试模式,通过在SharedTrainingMaster builder中设置.encodingDebugMode(true)
来启用。启用此选项后,每个工作节点进程都将记录当前阈值、稀疏性和有关编码的各种其他统计信息。这些统计数据可用于确定是否适当设置了阈值:例如,许多更新是阈值的数十倍或数百倍,可能表明阈值太低,应增加阈值;在频谱的另一端,非常稀疏的更新(少于10000个值中的一个值被传送)可能表示应该降低阈值。
DL4J支持神经网络的大多数标准评估指标。有关评估的基本信息,请参阅DL4J评估页。
DL4J支持的所有评估指标都可以使用Spark以分布式方式计算。
步骤1:准备数据
Spark上的DL4J评估数据与训练数据非常相似。也就是说,您可以使用:
RDD<DataSet>
或 JavaRDD<DataSet>
用于评估单输入/输出网络
RDD<MultiDataSet>
或 JavaRDD<MultiDataSet>
用于评估多输入/输出网络
RDD<String>
或 JavaRDD<String>
HDFS.其中每个字符串都是指向网络存储(如HDFS)上序列化DataSet/MultiDataSet(或其他基于小批量文件的格式)的路径。
步骤2:准备网络
创建你的网络很简单。首先,使用以下指南中的信息将网络(多层网络或计算图)加载到driver的内存中:如何保存(和加载)在Spark上训练的神经网络
然后,只需使用以下方法创建网络:
注意,您不需要配置TrainingMaster(即上面的第三个参数为空),因为评估不使用它。
步骤3:调用适当的评估方法
对于常见情况,可以调用SparkDl4jMultiLayer或SparkComputationGraph上的标准评估方法之一:
要同时执行多个评估(比按顺序执行效率更高),可以使用以下方法:
请注意,某些计算方法具有带额外参数的重载,包括:
int evalNumWorkers
- 评估工作节点数-即每个节点上用于评估的网络副本数(最多为每个工作节点的最大Spark线程数)。对于大型网络(或有限的群集内存),您可能希望减少这个值,以避免遇到内存问题。
int evalBatchSize
- 执行评估时要使用的小批量大小。这需要足够大以有效地使用硬件资源,但要足够小以不耗尽内存。32-128的值无疑是一个很好的起点;当有更多可用内存和较小的网络时增加;如果内存有问题,则减少。
DataSetLoader loader
和 MultiDataSetLoader loader
- 这些将在 RDD<String>
或 JavaRDD<String>
上评估时可用。它们是使用自定义用户定义函数将路径加载到DataSet或MultiDataSet
的接口。大多数用户不需要使用这些功能,但是提供的功能具有更大的灵活性。例如,如果保存的minibatch文件格式不是DataSet/MultiDataSet,而是其他(可能是自定义)格式,则将使用它们。
Finally, if you want to save the results of evaluation (of any type) you can save it to JSON format directly to remote storage such as HDFS as follows:最后,如果要保存评估结果(任何类型),可以直接将其保存为JSON格式,保存到远程存储,如HDFS,如下所示:
SparkUtils
的导入是 org.datavec.spark.transform.utils.SparkUtils
可以使用以下方法加载评估:
DL4J的Spark功能是围绕包装类的思想构建的,即SparkDl4jMultiLayer
和SparkComputationGraph
在内部使用标准的MultiLayerNetwork
和 ComputationGraph
类。您可以分别使用SparkDl4jMultiLayer.getNetwork()和SparkComputationGraph.getNetwork()访问内部MultiLayerNetwork/ComputationGraph类。
若要保存在主节点/driver的本地文件系统上,请获取如上所述的网络,然后仅使用ModelSerializer类或MultiLayerNetwork.save(File)/.load(File)
和 ComputationGraph.save(File)/.load(File)
方法
要保存到(或从其中加载)远程位置或分布式文件系统(如HDFS)中,可以使用输入和输出流。
例如,
读取是类似的流程:
DL4J的Spark实现支持分布式推理。也就是说,我们可以使用机器集群轻松地在输入的RDD上生成预测。这种分布式推理也可用于在单机上训练并用Spark加载的网络(有关如何用Spark加载保存网络的详细信息,请参阅保存/加载部分)。
注意:如果要执行评估(即计算准确率、F1、MSE等),请参考评估操作指南。
用于执行分布式推理的方法签名如下:
如果需要,也有接受输入掩码数组的重载
注意参数K
-这是一个泛型类型,表示用于标识每个示例的唯一“key”。键值不作为推理过程的一部分使用。这个键是必需的,因为Spark的RDD是无序的-没有这个键,我们就无法知道预测RDD中的哪个元素对应于输入RDD中的哪个元素。在执行推理时,batch size参数用于指定小批量大小。它不影响返回的值,而是用来平衡内存使用和计算效率:大批量的计算可能会更快,但需要更多的内存。在许多情况下,如果您不确定要使用什么,那么批处理大小为64是一个很好的尝试起点。
不幸的是,如果您的项目配置不正确,则在运行时群集上可能会发生依赖关系问题。这些问题可以在任何Spark作业中发生,而不仅仅是那些使用DL4J的作业,它们可能是由类路径上的其他依赖项或库引起的,而不是由DL4J依赖项引起的。
当发生依赖性问题时,它们通常会产生如下异常:
NoSuchMethodException
ClassNotFoundException
AbstractMethodError
例如,不匹配的Spark版本(尝试在Spark 2群集上使用Spark 1)可能看起来像:
另一类错误是UnsupportedClassVersionError
,例如java.lang.UnsupportedClassVersionError:XYZ:Unsupported major.minor version 52.0
-这可能是由于试图在仅使用java 7 JRE/JDK设置的集群上运行(例如)java 8代码所致。
如何调试依赖问题:
步骤1:收集依赖项信息
第一步(使用Maven时)是生成一个可以参考的依赖树。打开一个命令行窗口(例如,Linux上的bash,Windows上的cmd),导航到Maven项目的根目录并运行mvn dependency:tree这将为您提供一个依赖项列表(直接的和暂时的),这有助于准确理解类路径上的内容和原因。
还要注意,mvn dependency:tree -Dverbose
将提供额外的信息,并且在调试与不匹配的库版本相关的问题时非常有用。
步骤2:检查你的Spark版本
当遇到依赖性问题时,请检查以下内容。
首先:检查Spark版本如果集群运行的是Spark 2,那么应该使用以_spark_2
结尾的deeplearning4j-spark_2.10/2.11(和DataVec)版本
看穿
如果发现问题,应按如下方式更改项目依赖项:在Spark 2(Scala 2.11)群集上,使用:
而在Spark 1(Scala 2.11)集群上,应该使用:
步骤3:检查Scala版本
Apache Spark的发行版本同时支持Scala 2.10和scala2.11。
为了避免Scala版本出现问题,您需要做两件事:(a)确保您的项目类路径上没有Scala 2.10和scala2.11(或2.12)依赖项的混合。检查依赖关系树中以_2.10
或_2.11
结尾的条目:例如,org.apache.spark:spark-core_2.11:jar:1.6.3:compile
是一个使用Scala 2.11的spark 1(1.6.3)依赖关系(b)确保您的项目与集群使用的匹配。例如,如果集群使用Scala 2.11运行spark 2,那么所有Scala依赖项也应该使用2.11。请注意,Scala 2.11在Spark集群中更为常见。
如果发现不匹配的Scala版本,则需要通过更改pom.xml(或其他依赖项管理系统的类似配置文件)中的依赖项版本来对齐它们。许多库(包括Spark和DL4J)都发布了Scala 2.10和2.11版本的依赖项。
步骤4:检查不匹配的库版本
在Java生态系统中广泛使用的一些公共实用程序库在不同版本之间不兼容。例如,Spark可能依赖于库 x 版本 y,当库 x 版本 z位于类路径上时,Spark将无法运行。此外,这些库中的许多被分为多个模块(即多个独立的模块依赖项),在混合不同版本时无法正常工作。
一些常见的问题包括:
Jackson
Guava
DL4J和ND4J使用这些库的版本,应该避免与Spark的依赖冲突。但是,其他(第三方库)可能会引入这些依赖项的版本。
通常,异常会提示查找位置,即堆栈跟踪可能包含一个特定的类,该类可用于标识有问题的库。
步骤5:一旦确定,修复依赖冲突
要调试这些问题,请仔细检查依赖树(mvn dependency:tree -Dverbose
的输出)。如有必要,可以使用exclusions或将有问题的依赖项作为直接依赖项添加到问题中以强制其版本。为此,需要将所需版本的依赖项直接添加到项目中。通常,这足以解决问题。
请记住,在使用Spark submit时,Spark将向driver和工作节点类路径添加Spark及其依赖库的副本。这意味着,对于Spark添加的依赖项,不能简单地在项目中排除它们,Spark submit将在运行时添加它们,无论您是否在项目中排除它们。
另一个值得了解的设置是(实验性的)Spark配置选项spark.driver.userClassPathFirst
和 spark.executor.userClassPathFirst
(有关更多详细信息,请参阅Spark配置文档)。在某些情况下,这些选项可能会解决依赖性问题。
Spark在如何处理带有大型堆外组件的Java对象方面存在一些问题,例如DL4J中使用的DataSet和INDArray对象。本节解释与缓存/持久化这些对象相关的问题。
要知道的要点是:
由于Spark没有正确估计RDD中对象的大小,因此MEMORY_ONLY和MEMORY_AND_DISK
持久性可能会对堆外内存造成问题。这可能导致堆外内存问题。
当持久化一个 RDD<DataSet>
或 RDD<INDArray>
用于重用,使用 MEMORY_ONLY_SER 或 MEMORY_AND_DISK_SER
为什么MEMORY_ONLY_SER或MEMORY_AND_DISK_SER被推荐
Apache Spark提高性能的方法之一是允许用户在内存中缓存数据。这可以使用RDD.cache()
或RDD.persist(StorageLevel.MEMORY_ONLY())
以反序列化(即标准Java对象)的形式将内容存储在内存中。基本思想很简单:如果您持久化一个RDD,您可以从内存(或磁盘,取决于配置)中重用它,而不必重新计算它。然而,大型RDD可能不能完全装入内存。在这种情况下,RDD的某些部分必须重新计算或从磁盘加载,具体取决于使用的存储级别。此外,为了避免使用太多内存,Spark会在需要时丢弃RDD的部分(块)。
Spark中可用的主要存储级别如下所示。有关这些的说明,请参阅Spark编程指南。
MEMORY_ONLY
MEMORY_AND_DISK
MEMORY_ONLY_SER
MEMORY_AND_DISK_SER
DISK_ONLY
Spark的问题是如何处理内存。特别是,Spark将根据一个RDD(块)的估计大小丢弃该块的一部分。Spark估计块大小的方式取决于持久性级别。对于MEMORY_ONLY
和MEMORY_AND_DISK
持久化 ,这是通过遍历Java对象图来完成的,即查看对象中的字段并递归地估计这些对象的大小。然而,这个过程没有考虑到DL4J或ND4J使用的堆外内存。对于像DataSet和INDArray这样的对象(它们几乎完全存储在堆外),Spark大大低估了使用这个过程的对象的真实大小。此外,Spark在决定是否保留或丢弃块时只考虑堆上内存的使用量。由于DataSet和INDArray对象的堆上大小非常小,Spark会将它们中的太多对象保留在MEMORY_ONLY
和MEMORY_AND_DISK
上,从而导致堆外内存耗尽,导致内存不足问题。
但是,对于MEMORY_ONLY_SER
和MEMORY_AND_DISK_SER
Spark,它将块以序列化的形式存储在Java堆中。Spark可以准确估计以序列化形式存储的对象的大小(序列化对象没有堆外内存组件),因此Spark将在需要时丢弃块,从而避免任何内存不足问题。
DL4J的参数平均实现可以选择使用SparkDl4jMultiLayer.setCollectTrainingStats(true)来收集训练状态。启用时,需要internet访问才能连接到NTP(网络时间协议)服务器。
可能会出现NTPTimeSource: Error querying NTP server, attempt 1 of 10
。有时,这些失败是暂时的(稍后的重试将起作用),可以忽略。但是,如果Spark集群的配置使一个或多个工作进程无法访问internet(特别是NTP服务器),则所有重试都可能失败。
有两种解决方案:
不使用 sparkNet.setCollectTrainingStats(true)
-此功能是可选的(训练时不需要),默认情况下禁用
将系统设置为使用本地计算机时钟而不是NTP服务器作为时间源(但请注意,时间线信息可能因此非常不准确) 要使用系统时钟时间源,请在Spark submit中添加以下内容:
在Ubuntu 16.04机器上运行YARN集群的Spark时,很可能在完成一个作业之后,运行Hadoop/YARN的用户拥有的所有进程都会被终止。这与Ubuntu中的一个bug有关,该bug记录在https://bugs.launchpad.net/ubuntu/+source/procps/+bug/1610499上。 在 https://stackoverflow.com/questions/38419078/logouts-while-running-hadoop-under-ubuntu-16-04。 上有一个Stackoverflow讨论。
建议采取一些解决办法。
选项1
添加
到 /etc/systemd/logind.conf, 并重启。
选项 2
从Ubuntu 14.04复制/bin/kill二进制文件并使用它。
选项3
降级为Ubuntu 14.04
选项 4
在 sudo loginctl enable-linger hadoop_user_name
集群节点上运行
模型导入概述
Keras模型导入为导入最初用Keras配置和训练的神经网络模型提供了例程,Keras是一个流行的Python深度学习库。
一旦你的模型导入到DL4J,我们的整个生产栈是由你来处理的。我们支持导入所有的Keras模型类型、大多数层和几乎所有的实用功能。请在这里查看支持的Keras特性的完整列表。
要导入Keras模型,首先需要创建和序列化这样的模型。这里有一个你可以使用的简单例子。该模型是一个简单的MLP,它采用长度为100的小批量向量,具有两个密集层,并预测总共10个类别。在定义模型之后,我们将其序列化为HDF5格式。
如果将这个模型文件(simple_mlp.h5
)放到项目的资源文件夹的根目录中,则可以将Keras模型加载为DL4J 的 MultiLayerNetwork,如下所示
就是这样!KerasModelImport是模型导入的主要入口点,类负责在内部将Keras映射到DL4J概念。作为用户,你只需要提供你的模型文件,请参阅我们的入门指南,以了解将Keras模型加载到DL4J中的更多细节和选项。
现在可以使用导入的模型进行推断(这里使用简单的数据来简化)
以下是你如何在DL4J中为你导入的模型做训练:
在我们的DL4J示例中可以找到刚才所示的完整示例。
要在现有项目中使用Keras模型导入,只需要将下列依赖项添加到pom.xml中。
如果首先需要开始一个项目,请考虑克隆DL4J示例,并按照仓库中的说明来构建项目。
DL4J Keras 模型导入与后端无关。 不管你选择哪一个后端 (TensorFlow, Theano, CNTK), 你的模号可以导入DL4J。
我们支持为越来越多的应用程序导入,在这里查看当前所覆盖的模型的完整列表。这些应用包括
Deep convolutional and Wasserstein GANs
UNET
ResNet50
SqueezeNet
MobileNet
Inception
Xception
IncompatibleKerasConfigurationException
信息说明你正在尝试导入一个当前不被DL4J支持的Keras模型(要么因为模型导入不覆盖它,要么DL4J不实现该层或特征)。
一旦导入了模型,我们就推荐我们自己的“ModelSerializer
”类来进一步保存和重新加载模型。
你可以通过访问DL4J gitter频道进一步咨询。你可能会考虑通过Github来提交一个特性请求,这样这个缺失的功能可以放在DL4J开发路线图上,或者甚至向我们发送一个带有必要更改的pull请求!
Keras是用Python编写的一个流行的、用户友好的深度学习库。Keras的直观API使得Python轻松地定义和运行你的深度学习模型。Keras允许你选择它在哪个底层库上运行,但是为每个这样的后端提供了统一的API。目前,Keras支持Tensorflow、CNTK和Theano后端,但是Skymind也在为Keras开发ND4J后端。
一个公司的生产系统和它的数据科学家的实验设置之间经常有差距。Keras模型导入允许数据科学家用Python编写他们的模型,但是仍然与生产栈无缝集成。
Keras模型导入主要针对在Python中熟悉用Keras编写模型的用户。通过模型导入,你可以通过允许用户将模型导入DL4J生态圈以进行进一步的训练或评估,从而将Python模型带到生产中。
当项目的试验阶段完成并且需要将模型交付生产时,你应该使用这个模块。Skymind商业支持Keras在企业中的实现。
支持的损失函数
DL4J支持所有可用的Keras损失函数(除了logcosh),即:
mean_squared_error
mean_absolute_error
mean_absolute_percentage_error
mean_squared_logarithmic_error
squared_hinge
hinge
categorical_hinge
logcosh
categorical_crossentropy
sparse_categorical_crossentropy
binary_crossentropy
kullback_leibler_divergence
poisson
cosine_proximity
Keras的损失函数映射可在KerasLossUtils中找到。
支持的Keras激活。
我们支持所有的Keras激活函数,即:
softmax
elu
selu
softplus
softsign
relu
tanh
sigmoid
hard_sigmoid
linear
Keras到DL4J激活函数的映射定义在KerasActivationUtils中。
已支持的Keras约束。
导入functional模型。
假设你使用Keras开始定义一个简单的MLP:
在Keras,有几种保存模型的方法。你可以将整个模型(模型定义、权重和训练配置)存储为HDF5文件,仅存储模型配置(作为JSON或YAML文件)或仅存储权重(作为HDF5文件)。以下是你如何做每一件事:
如果你决定保存完整的模型,那么你将能够访问模型的训练配置,否则你将不访问。因此,如果你想在导入之后在DL4J中进一步训练模型,请记住这一点,并使用model.save(...)来持久化你的模型。
让我们从推荐的方法开始,将完整模型加载回DL4J(我们假设它在类路径上):
万一你没有编译你的Keras模型,它就不会有一个训练配置。在这种情况下,你需要显式地告诉模型导入忽略训练配置,方法是将enforceTrainingConfig标志设置为false,如下所示:
若要仅从JSON加载模型配置,请按如下使用KerasModelImport
如果另外你还想加载模型权重与配置,那么以下是你要做的:
在后面两种情况下,将不读取训练配置。
超参数优化中使用Arbiter的介绍。
机器学习有一个参数集合,必须在任何训练开始之前选择。这些参数就是所谓 的超参数。 一些超参数的例子如K邻近值算法的“K”和支持向量机中的正则化参数。 神经网络,比较特别,有很多的超参数。它们中的一些定义了神经网络的结构,像层的数量和它们的大小。其它一些定义了学习过程比如说学习率和正则化传统上,这些选择是根据现有的经验法则做出的,或者经过大量的试验和错误后做出的,这两者都不太理想。 无疑的这些参数选择会在学习取得的结果上有重要影响。 超参数优化尝试使用应用搜索策略的软件让该过程自动化。
Arbiter 阿比特是企业机器学习/深度学习工具的DL4J套件之一. 它专用于dl4j神经网络的创建和导入时的参数优化 . 它允许用户为超参数设置搜索空间,并运行网格搜索或随机搜索来在基于给定的评分指标上选择最好的配置。 什么时候用阿比特? 阿比特可以用作寻找表现好的模型,潜在的为你节约调优模型超参数的时间,但是牺牲了更大的计算时间。注意到阿比特不会完全自动化神经网络调优的过程,用户仍需要指定搜索空间。 这个搜索空间定义了每个超参数可用的值的范围(例如:学习率允许的最大值和最小值)。 如果这个搜索空间先择过于小,阿比特可能不能找到任何好的模型。添加以下到你的pom.xml来在你的工程中引用阿比特,${arbiter.version}是最新的发布版本。
阿比特还提供方便的用户界面来帮助把优化的结果可视化.使用阿比特的先决条件是用户应熟悉DL4j中的神经网络配置,多层神经网络配置和计算图配配置类.
使用 本节将概述使用阿比特所必需的重要组成。接下来的章节将深入细节。在最高级别,设置超参数优化涉及设置一个优化配置和通过IOptimizationRunner来运行它。 如下是一些演示在优化配置中建造者模式的代码:
如上所述,设置优化配置需要:
候选生成器: 为评估提出候选 (i.e., 超参数配置) . 候选基于一些策略创建. 目前支持随机搜索和网格搜索.有效的候选配置取决于与候选生成器相关连的超参数空间. 数据源: 数据源在后台使用,用于为生成的候选提供训练和测试数据。 模型保存器: 指定每个超参数优化器运行的结果将如何保存. 例如,是否保存到本地磁盘,数据库,HDFS系统,或只是简单的保存在内存. 评分函数: 一个单数字度量值,用于寻找最小化或最大化来决定最好的候选 . 如模型损失或分类精确度。终止条件: 决定超参数优化在什么时候停止. 如一定数量的候选已被评估,已过了一定量的计算时间。
然后将优化配置连同任务创建者一起传递给优化运行程序。
如果创建的候选是多层网络,这个设置如下:
作为可选项,如果创建的候选为计算图,这个设置如下:
目前为止运行器惟一可用的选项为LocalOptimizationRunner,用于在单台机器上执行学习(在当前JAVA虚拟机上).原则上,其它执行方法(如,在spark或云计算机器)可以被实现。
这里总结了建立超参数优化运行的步骤 :
指定超参数的搜索空间
为超参数搜索空间指定一个候选生成器
接下来的步骤可以按任意的顺序执行:
指定一个数据源
指定一个模型保存器
指定一个评分函数
指定终止条件
以下步骤必须按顺序执行:
使用如上的2-6来构建一个优化配置
和优化运行器一起运行
阿比特的ParameterSpace 类定义了一个给定的超参数可能使用可接受的值的范围。参数空间可以是一个简单的,如参数空间定义一个双精度值的连续范围(学习率) 或是复杂的,如多个嵌套参数空间,与多层空间(为MultilayerConfiguration定义搜索空间)情况类似。
多层空间和计算图空间是阿比特对于dl4j的多层配置和计算图配置的副本。它们用于在多层配置和计算图配置中为可用的超参数设置超参数空间。除此之外,这些用户也可以设置训练次数或是一个早停配置来指示在每个候选神经网络训练时什么候应该停止。如果一个早停配置和训练次数都被指定,早停会优先使用。
如果用户熟悉整数,连续和离散参数空间,和层空间和更新器空间,那么设置多层空间和计算图空间是非常容易直接的。惟一需要注意的警告是虽然设置权重约束是可能的,但是作为神经网络配置一部份的l1Bias 和 l2Bias 必须在多层空间中的每层/层空间基础上设置。通常所有可通过建造器使用的属性/超参数将采用固定值或该类型的参数空间。这意味着几乎多层配置的每一个层面都会被扫描用于对各种结构和初始化值进行彻底测试。
这里是一个多层空间的简单例子:
需要特别注意的是阿比特在多层空间中改变层数量的能力。下面是一个简单的示例,演示了如何为加权损失函数设置参数搜索空间:
上面创建的两到五层将是相同的(堆叠)。目前为止阿比特不支持创建独立层,最后,也可以创建固定数量的相同层,如下面的示例所示:
在这个例子中,网格搜索将创建三个独立的体系结构。它们将在每一个方面都相同,但是在非输出层中被选择的激活函数中是不同的。另外,要注意的是,在每个体系结构中创建的层是相同的(堆叠)。创建计算图形空间与多层空间非常相似。然而,目前只支持固定的图形结构。下面是一个简单的例子,演示了设置计算图空间:
多层空间,计算图空间和优化配置有toJso
n方法也有fromJson
方法。你可以存储JSON表示以供进一步使用。指定一个前面提到过的候选生成器,阿比特当前支持网格搜索和随机搜索。
设置一个随机搜索是很简单的,如下所示 :
MultiLayerSpace mls; … CandidateGenerator candidateGenerator = new RandomSearchGenerator(mls);
设置一个网格搜索同样简单。通过网格搜索,用户还可以指定离散化计数和模式。离散化计数决定连续参数有多少值被包含在其中。例如,一个在[0,1]区间的连续参数,在离散化计数为3时被转化为[0.0,0.5,1.0]该模式决定了生成候选的方式。候选可以按顺序创建或随机创建。按照顺序,第一超参数变化最快,因此最后一个超参数变化最慢。请注意,这两种模式将导致在相同的一组候选,只是顺序不同。
这里是一个网格搜索如何用一个离散化计数为4的按顺序被设置一个例子。
数据源接口定义了用于训练不同候选的数据来源。实现起来非常简单。注意到需要定义一个没有参数的构造器。取决于用户的需要,数据源可以通过配置实现,就像小批理的大小。一个使用MNIST数据集的数据源的简单实现在repo例子中可以使用,它将在后面的指引中涉及。需要注意的是很重要的是训练次数(早停配置也是一样)可以通过多层空间和计算图构建器被设置。
阿比特目前支持 内存保存到磁盘(文件模型保存器)来保存模型或在内存存储结果(内存结果保存器) 对于大模型来说内存结果保存器很明显是不推荐的。设置它们是很简单的。文件模型保存器构造器使用一个字符路径。它保存配置,参数和分数到:baseDir/0/, baseDir/1/, 等,它们的索引由OptimizationResult.getIndex()方法提供,内存结果保存器不需要参数。
指定一个评分函数这里有三个主要的类用于评分函数:EvaluationScoreFunction,ROCScoreFunction和 RegressionScoreFunction。
EvaluationScoreFunction使用一个dl4j评估指标,可用的指标是ACCURACY, F1, PRECISION, RECALL, GMEASURE, MCC。这里有一个使用accuracy的简单例子: ScoreFunction scoreFunction = new EvaluationScoreFunction(Evaluation.Metric.ACCURACY);
ROCScoreFunction 在测试数据集上计算AUC(曲线面积)或AUPRC(精度/召回曲线面积) 。支持不同的曲线面积类型(ROC, ROCBinary 和 ROCMultiClass)。这里是一个简单的使用AUC的例子:ScoreFunction sf = new ROCScoreFunction(ROCScoreFunction.ROCType.BINARY, ROCScoreFunction.Metric.AUC));
RegressionScoreFunction 用于回归和支持所有dl4j的回归指标(MSE, MAE, RMSE, RSE, PC, R2)这里是一个简单的例子:ScoreFunction sf = new RegressionScoreFunction(RegressionEvaluation.Metric.MSE);
阿比特目前仅支持两种终止条件-最大时间条件最大候选条件。最大时间条件是为超参数优化指定一个停止时间。最大候选条件为超参数优化停止前指定一个候选的最大数量。终止条件可以指定为一个列表。如果任何一个条件满足超参数优化将停止。这里是一个简单的例子,运行将会在15分中后停止或在训练了10个候选之后,取决于哪个条件先到。
DL4J的例子中包括一个在MNIST数据集上的BasicHyperparameterOptimizationExample例子。用户可以在这里浏览这个简单的例子。这个例子也通过设置阿比特的界面来实现。阿比特使用与DL4j界面相同的存储和持久化方法。更多的有关界面的文档可以在这里找到。界面可以通过http://localhost:9000/arbiter 访问。
请参阅斯坦福大学CS231N类的超参数优化的优秀章节。这些技术的总结如下:
在网格搜索和随机搜索中优先使用随机搜索。要比较随机和网格搜索方法,请参阅用于超参数优化的随机搜索(Bergstra和Bengio,2012)。
从粗略到精细运行搜索(以粗略的参数开始搜索一个或两个训练次数,挑最好的候选来做精细搜索,用更多的训练次数,迭代 )
为某些超参数(如学习率、L2等)使用对数均匀分布
注意接近参数搜索空间边界的值。
已支持的Keras优化器
支持的优化器 支持所有标准的Keras优化器,但是导入自定义TensorFlow优化器将不能工作:
SGD
RMSprop
Adagrad
Adadelta
Adam
Adamax
Nadam
TFOptimizer
如果参数被设置的值小于或等于0.5它将返回true,否则是false。
固定值是只定义单个固定值的参数空间。
getValue
最小值与最大值之间均匀分布的连续参数空间
参数 min 可生成的最小值
参数 max 可生成的最大值
用于未排序值集合
一些最小值和最大值
getMin
在指定的最小/最大(包含)之间创建一个具有均匀分布的整数参数空间
参数 min 的最小值, 包括最小值
参数 max 的最大值, 包括最大值
在另一个参数空间上实现标量数学运算的简单参数空间。这允许你做像 Y=2X,X是参数空间。例如,可以将一个层大小超参数用前一层的大小2x来设置 。
在另一个参数空间上实现成对数学运算的简单参数空间。这允许你做诸如z=x+y之类的事情,其中x和y是参数空间。
用于自编码器的层空间
用于批量规一化的层空间
双向层包装器。可以用同样的方式包装现有的层空间。
用于卷积层的层空间
用于稠密层空间的层超参数配置空间 (多层感知器层)
用于双向长短记忆网络层的层空间
用于长短记忆层的层空间
用于长短记忆层的层空间
使用隐藏层大小代替
numHidden
使用隐藏层大小代替
参数 numHidden
return
用于输出层的层超参数配置空间
循环神经网络输出层的层超参数配置空间
用于子采样层的层超参数配置空间
支持的Keras功能。
鲜为人知的事实:DL4J的创始人,Skymind,在我们的团队中拥有前五名的Keras贡献者中的两个,使其成为继Keras的创始人Francois Chollet之后对Keras的最大贡献者。 虽然并非DL4J中的每个概念在Keras中都有等效的概念,反之亦然,但是许多关键概念可以匹配。将Keras模型导入DL4J是在我们的deeplearning4j-modelimport 模块中完成的。下面是当前支持的特性的综合列表。
层
损失
激活函数
初始化器
正则化器
约束
度量
优化器
将模型映射到DL4J层是在模型导入的层子模块中完成的。该项目的结构随意地反映了Keras的结构。
❌ GRU
✅ LSTM
❌ ConvLSTM2D
✅ Add / add
✅ Multiply / multiply
✅ Subtract / subtract
✅ Average / average
✅ Maximum / maximum
✅ Concatenate / concatenate
❌ Dot / dot
✅ PReLU
✅ ELU
❌ TimeDistributed
✅ mean_squared_error
✅ mean_absolute_error
✅ mean_absolute_percentage_error
✅ mean_squared_logarithmic_error
✅ squared_hinge
✅ hinge
✅ categorical_hinge
❌ logcosh
✅ categorical_crossentropy
✅ sparse_categorical_crossentropy
✅ binary_crossentropy
✅ kullback_leibler_divergence
✅ poisson
✅ cosine_proximity
✅ softmax
✅ elu
✅ selu
✅ softplus
✅ softsign
✅ relu
✅ tanh
✅ sigmoid
✅ hard_sigmoid
✅ linear
✅ Zeros
✅ Ones
✅ Constant
✅ RandomNormal
✅ RandomUniform
✅ TruncatedNormal
✅ VarianceScaling
✅ Orthogonal
✅ Identity
✅ lecun_uniform
✅ lecun_normal
✅ glorot_normal
✅ glorot_uniform
✅ he_normal
✅ he_uniform
✅ l1
✅ l2
✅ l1_l2
✅ max_norm
✅ non_neg
✅ unit_norm
✅ min_max_norm
✅ SGD
✅ RMSprop
✅ Adagrad
✅ Adadelta
✅ Adam
✅ Adamax
✅ Nadam
❌ TFOptimizer
导入functional模型
假设你使用Keras的函数API开始定义一个简单的MLP:
在Keras,有几种保存模型的方法。你可以将整个模型(模型定义、权重和训练配置)存储为HDF5文件,仅存储模型配置(作为JSON或YAML文件)或仅存储权重(作为HDF5文件)。以下是你如何做每一件事:
如果你决定保存完整的模型,那么你将能够访问模型的训练配置,否则你将不访问。因此,如果你想在导入之后在DL4J中进一步训练模型,请记住这一点,并使用model.save(...)来持久化你的模型。
让我们从推荐的方法开始,将完整模型加载回DL4J(我们假设它在类路径上):
万一你没有编译你的Keras模型,它就不会有一个训练配置。在这种情况下,你需要显式地告诉模型导入忽略训练配置,方法是将enforceTrainingConfig标志设置为false,如下所示:
若要仅从JSON加载模型配置,请按如下使用KerasModelImport
如果另外你还想加载模型权重与配置,那么以下是你要做的:
在后面两种情况下,将不读取训练配置。
从Keras(函数API)模型或序列模型配置构建计算图。
KerasModel
(建议)(函数API)模型的构建器模式构造器。
参数 modelBuilder 构建器对象
抛出 IOException IO 异常
抛出 InvalidKerasConfigurationException 无效的 Keras 配置
抛出 UnsupportedKerasConfigurationException 不支持的 Keras 配置
getComputationGraphConfiguration
(不推荐)来自模型配置(JSON或YAML)、训练配置(JSON)、权重和“训练模式”布尔指示符的(函数 API)模型的构造器。当内置在训练模式时,某些不支持的配置(例如,未知的正则化器)将抛出异常。当强制TrainingConfig= false时,这些将生成警告,但将被忽略。
参数 modelJson 模型配置JSON 字符串
参数 modelYaml 模型配置 YAML 字符串
参数 enforceTrainingConfig 是否实施训练相关配置
抛出 IOException IO 异常
抛出 InvalidKerasConfigurationException 无效的 Keras 配置
抛出 UnsupportedKerasConfigurationException 不支持的 Keras 配置
getComputationGraph
从这个Keras模型配置构建计算图并导入权重。
返回 ComputationGraph
getComputationGraph
从这个Keras模型配置构建计算图并(可选的)导入权重。
参数 importWeights 是否导入权重
返回 ComputationGraph
数据向量的关键工具之一是转换。数据向量帮助用户将数据集从一个概要映射到另一个概要,并提供一个操作列表来转换类型,格式化数据,把一个2D数据集转换成系列数据。
一个转换过程需要一个概要来成功地转换数据。概要和转换过程类都附带一个帮助构建器类,对于组织代码和避免复杂的构建器来说是很有用的。当两者结合起来它们看起来像如下的样例代码。请注意inputDataSchema是如何传到Builder构造器的。没有它,你的转换过程将会编译失败。
现在有不同的执行器后台可以使用。使用上面的转换过程对象tp,这里是你如何用一个数据向量在本地执行它。
在概要发生变化的时候在一个转换过程中的每个操作代表一个步骤。有时候,转换的结果不是预期想要的。你可以按如下通过打印转换过程对象tp中每个步骤调试这它。
数据集和转换概要
现实中的不幸是数据是脏的。当为了深度学习而试图向量化一个数据集时,很少能找到没有错的文件。在使用神经网络训练神经网络之前,概要对于维护数据的意义是很重要的。
概要基本上用于程序设计变换。在正确执行转换过程之前,需要传递正在转换的数据的概要。一个用于商家记录的概要的例子看起来如下:
如果你有两个你想要合并的不同的数据集,数据向理提供一个Join连接类,它有不同的连接策略,例如 Inner内连和RightOuter右外连
一旦你已经定义了你的连接并且你已经加载了数据到数据向量,你必须使用一个Executor执行器来完成连接。
本页提供了在Spark上使用DL4J进行分布式训练所需的关键类的API参考。确保您已经阅读了深入DL4J Spark训练入门指南。
SharedTrainingMaster基于Strom 2015论文“使用有价值的GPU云计算的可扩展分布式DNN训练”: https://s3-us-west-2.amazonaws.com/amazon.jobs-public-documents/strom_interspeech2015.pdf,使用压缩量化梯度(更新)共享实现神经网络的分布式训练。DL4J实现进行了许多修改,例如可以选项,在没有多播网络支持的情况下使用基于容错和执行实现的参数服务器 。
fromJson
通过反序列化用{-link#toJson()}序列化的JSON字符串来创建SharedTrainingMaster实例
param jsonStr SharedTrainingMaster配置序列化为JSON
fromYaml
通过反序列化已用{-link#toYaml()序列化的YAML字符串来创建SharedTrainingMaster实例
param yamlStr SharedTrainingMaster 配置序列化为YAML
collectTrainingStats
使用默认值创建SharedTrainingMaster,而不是RDD示例数
param rddDataSetNumExamples 当从{code RDD}拟合时,每个数据集中有多少个示例?
repartitionData
此参数定义何时应用重新分区(如果应用)。
param repartition 重新分区设置
deprecated Use {- link #repartitioner(Repartitioner)}
repartitionStrategy
repartitionStrategy与{-link#repartitionData(Repartition)}(定义何时应执行重新分区)一起使用,它定义如何执行重新分区。有关详细信息,请参见{-link RepartitionStrategy}
param repartitionStrategy 要使用的重新分区策略
deprecated Use {- link #repartitioner(Repartitioner)}
storageLevel
设置{-code RDD}s的存储级别。 默认值:StorageLevel.MEMORY_ONLY SER()-即,以序列化形式存储在内存中,若要使用RDD持久化,请使用{-code null}注意,仅当使用{-code RDDTrainingApproach.Direct}时(这不是默认值),以及从{-code RDD}拟合时,此选项才有效。
注意:Spark的StorageLevel.MEMORY_ONLY()和StorageLevel.MEMORY_AND_DISK()在处理堆外数据(DL4J/ND4J广泛使用)时可能会出现问题。在决定是否/何时删除块以确保足够的可用内存时,Spark不考虑堆外内存;因此,对于大于(堆外)内存总量的数据集RDD,这可能会导致OOM问题。换句话说:Spark只计算DataSet和INDArray对象的堆上大小(这是可以忽略的),从而大大低估了真正的DataSet对象大小。因此,内存中保存的数据集比我们实际负担得起的还要多。
还要注意,不建议直接从{-code RDD}进行拟合-最好只一次导出准备好的数据并调用(例如}{-code SparkDl4jMultiLayer.fit(String savedDataDirectory)}。详情请参阅DL4J的Spark网站文档。
param storageLevel 用于数据集RDD的存储级别
rddTrainingApproach
在{-code RDD}或{-code RDD}上进行训练时使用的方法。默认值:{-link RDDTrainingApproach#Export},它首先将数据导出到临时目录。
虽然可以使用{-link#exportDirectory(String)}配置默认的集群临时目录,但也请注意,不建议直接从{-code RDD}进行拟合-最好只一次导出准备好的数据并调用(例如}{-code SparkDl4jMultiLayer.fit(String savedDataDirectory)}。详情请参阅DL4J的Spark网站文档。
param rddTrainingApproach {- code RDD}从{-code RDD}或{-code RDD}进行训练时要使用的训练方法
exportDirectory
当{-link#rddTrainingApproach(RDDTrainingApproach)}设置为{-link rddTrainingApproach#Export}(默认情况下)时,首先将数据导出到临时目录。
默认值:null。->使用{hadoop.tmp.dir}/dl4j/。在本例中,数据被导出到{hadoop.tmp.dir}/dl4j/SOME_UNIQUE_ID/ 如果指定目录,则将使用目录{exportDirectory}/SOME_UNIQUE_ID/。
param exportDirectory 导出数据的基本目录
rngSeed
随机数生成器种子,主要用于在RDD上强制执行可重复的拆分/重新分区 默认值:无种子集(即随机种子)
param rngSeed 随机数生成器种子
updatesThreshold
不推荐使用{link#thresholdAlgorithm(ThresholdAlgorithm)}和(例如){link AdaptiveThresholdAlgorithm}
thresholdAlgorithm
用于确定更新编码阈值的算法。较低的值可能会改善收敛性,但会增加网络通信量 值太低也可能影响网络收敛。如果观察到收敛问题,尝试将其增加或减少10倍,例如1e-4和1e-2。 有关技术细节,请参阅论文“使用有用的GPU云计算的可扩展分布式DNN训练” 另请参见{link ThresholdAlgorithm} 默认值:{- link AdaptiveThresholdAlgorithm} 带有默认参数
param thresholdAlgorithm 用于确定编码阈值的阈值算法
residualPostProcessor
残余后置处理器。有关详细信息,请参见{-link ResidualPostProcessor}。
默认值:{code new ResidualClippingPostProcessor(5.0,5)}-即{link ResidualClippingPostProcessor},每5次迭代,将残余值剪裁为当前阈值的+/- 5倍。
param residualPostProcessor 要使用的残余后置处理器
batchSizePerWorker
训练工作节点时使用的小批量大小。原则上,源数据(即{-code RDD}等)在每个{-code DataSet}中的示例数可以不同于我们在训练时要使用的示例数。i、 如果需要,我们可以拆分或合并数据集。
param batchSize 拟合每个工作节点时使用的小批量大小
workersPerNode
此方法允许配置每个群集节点的网络训练线程数。
默认值:-1,根据系统中存在的硬件(即,如果在启用了GPU的系统上进行训练,则为GPU的数量)定义自动选择的工作节点数量。 在GPU上进行训练时,每个GPU应使用1个工作节点(这是默认值)。对于CPU,通常首选每个节点1个工作线程,尽管在增加工作线程数时,多个CPU(即多个物理CPU)或具有大量核心计数的CPU可能具有更好的吞吐量(即每秒更多示例),但代价是消耗更多内存。请注意,如果增加CPU系统上的工作节点线程数,则应使用{- code OMP_NUM_THREADS}属性设置OpenMP线程数-有关详细信息,请参阅{- link org.nd4j.config.ND4JEnvironmentVars#OMP_NUM_THREADS}。例如,一台具有32个物理核心的机器可以使用4个工作节点线程,其中{- code OMP_NUM_THREADS=8}
param numWorkers 每个节点上的工作节点线程数。
debugLongerIterations
此方法允许您在给定时间内使用Thread.sleep()人为地延长迭代时间。
请注意:不要在生产环境中使用该选项。它只适合调试。
param timeMs
return
transport
可选方法:VoidParameterAveraging的传输实现TransportType.CUSTOM 方法用户通常不使用
param transport 要使用的传输
return
workerPrefetchNumBatches
训练时要在每个工作节点上异步预取的小批量数。默认值:2,通常适用于大多数情况。在某些情况下,增加这一点可能有助于ETL(数据加载)瓶颈的解决,但代价是更大的内存消耗。
param prefetchNumBatches 要预取的批数
repartitioner
要在拟合之前用于重新分区数据的重新分区程序。 DL4J执行MapPartitions操作以进行训练,因此如何对数据进行分区对性能影响很大—分区太少(或分区非常不平衡会导致群集利用率低,因为某些工作线程处于空闲状态)。较大数量的较小分区有助于避免所谓的“轮数终结”效应,即训练只能在最后一个/最慢的工作节点完成其分区后才能完成。
默认重分区程序是{-link DefaultRepartitioner},它平均重分区最多可达5000个分区,通常适用于大多数用途。在最坏的情况下,使用分区器时的“轮数终结”效果应限制为处理单个分区所需的最大时间。
param repartitioner 使用的重分器程序
workerTogglePeriodicGC
用于禁用对工作节点的定期垃圾收集调用。 相当于{- code Nd4j.getMemoryManager().togglePeriodicGc(workerTogglePeriodicGC);} 传递false以禁用工作节点上的定期GC,或传递true(相当于默认值,或不设置它)以保持启用状态。
param workerTogglePeriodicGC 工作节点定期垃圾收集设置
workerPeriodicGCFrequency
用于设置工作节点的定期垃圾收集频率。 相当于对每个工作进程调用{- code Nd4j.getMemoryManager().setAutoGcWindow(workerPeriodicGCFrequency);}
如果{- link #workerTogglePeriodicGC(boolean)} 设置为false,则没有任何效果。
param workerPeriodicGCFrequency 对工作节点使用的周期性GC频率
encodingDebugMode
启用阈值编码的调试模式。启用时,将计算阈值和残余值的各种统计信息,并记录在每个工作节点(在信息日志级别)。 此信息可用于检查编码阈值是否太大(例如,几乎所有更新都远小于阈值)或太大(大多数更新远大于阈值)。
默认情况下禁用encodingDebugMode。
重要提示:启用此选项会产生性能开销,除非实际需要调试信息,否则不应启用此选项。
param enabled 设置为true来启用
使用Spark训练计算图网络的主要类。也用于在这些网络上执行分布式评估和推理
getSparkContext
使用给定的上下文、网络和训练主机实例化计算图实例。
param sparkContext 要使用的spark上下文
param network 要使用的网络
param trainingMaster 训练所需,如果SparkComputationGraph仅用于评估或推理,则训练所需的值可能为空
getNetwork
返回训练的ComputationGraph
getTrainingMaster
返回此网络的TrainingMaster
setNetwork
param network 用于任何后续训练、推理和评估步骤的网络
getDefaultEvaluationWorkers
返回当前设置的默认评估工作节点/线程数。请注意,当在评估方法中显式提供工作节点数时,不使用默认值。 在许多情况下,我们可能希望它小于Spark线程的数量,以减少内存需求。例如,对于32个Spark线程和一个大型网络,我们不希望启动32个网络实例来执行评估。最好(为了满足内存需求,减少缓存抖动)使用4个工作线程。 如果未显式设置,则将使用{- link #DEFAULT_EVAL_WORKERS}
返回默认的工作节点(线程)数。
setDefaultEvaluationWorkers
设置默认的评估工作节点/线程数。请注意,当在评估方法中显式提供工作节点数,不使用默认值。
在许多情况下,我们可能希望它小于Spark线程的数量,以减少内存需求。例如,对于32个Spark线程和一个大型网络,我们不希望启动32个网络实例来执行评估。最好(为了满足内存需求,减少缓存抖动)使用4个工作节点。 如果未显式设置,则将使用{- link #DEFAULT_EVAL_WORKERS}
fit
用给定的数据集拟合计算图
param rdd 用于训练的数据
return 要训练的网络
fit
用给定的数据集拟合计算图
param rdd 用于训练的数据
return 要训练的网络
fit
使用序列化数据集对象的目录拟合SparkComputationGraph网络。这里的假设是该目录包含许多{-link DataSet}对象,每个对象都使用 {- link DataSet#save(OutputStream)}序列化
param path 包含序列化DataSet对象集的目录的路径
return 训练后的 MultiLayerNetwork
fit
弃用 {- link #fit(String)}
fitPaths
使用序列化DataSet对象的路径列表拟合网络。
param paths 路径列表
return 训练的网络
fitPathsMultiDataSet
用给定的数据集拟合计算图
param rdd 用于训练的数据
return 要训练的网络
fitMultiDataSet
已弃用{- link #fitMultiDataSet(String)}
getScore
从调用fit获取上一个(平均)小批量分数。这是每个工作进程中执行的最后一个小批量的所有执行器的平均得分。
calculateScore
通过对整个数据集求和或求平均值,计算所提供{code JavaRDD}中所有示例的得分。要单独计算每个示例的分数,请使用{-link#scoreExamples(JavaPairRDD,boolean)}或类似的方法之一。在每个工作节点中使用默认的小批量大小{- link SparkComputationGraph#DEFAULT_EVAL_SCORE_BATCH_SIZE}
param data 要评分的数据
param average 是否要加总或是平均分数
calculateScore
通过对整个数据集求和或求平均值,计算所提供{code JavaRDD}中所有示例的得分。要单独计算每个示例的分数,请使用{-link#scoreExamples(JavaPairRDD,boolean)}或类似的方法之一。
param data 要评分的数据
param average 是否要加总或是平均分数
param minibatchSize 评分时在每个小批量中使用的示例数。如果一个分区中的示例多于此数目,则将执行多个评分操作(通过一次性完成整个分区,避免使用过多内存)
calculateScoreMultiDataSet
通过对整个数据集求和或求平均值,计算所提供{code JavaRDD}中所有示例的得分。要单独计算每个示例的分数,在每个工作节点中使用默认的小批量大小{- link SparkComputationGraph#DEFAULT_EVAL_SCORE_BATCH_SIZE}
param data 要评分的数据
param average 是否要加总或是平均分数
calculateScoreMultiDataSet
通过对整个数据集求和或求平均值,计算所提供{code JavaRDD}中所有示例的得分。要单独计算每个示例的分数。
param data 要评分的数据
param average 是否要加总或是平均分数
param minibatchSize 评分时在每个小批量中使用的示例数。如果一个分区中的示例多于此数目,则将执行多个评分操作(通过一次性完成整个分区,避免使用过多内存)
scoreExamples
{- link #scoreExamples(JavaRDD, boolean)}的DataSet 版本
scoreExamples
{- link #scoreExamples(JavaPairRDD, boolean, int)}DataSet 版本
scoreExamplesMultiDataSet
{- link #scoreExamples(JavaPairRDD, boolean)}DataSet 版本
scoreExamplesMultiDataSet
使用指定的批处理大小对示例进行单独评分。与 {- link #calculateScore(JavaRDD, boolean)}不同,此方法分别返回每个示例的分数。如果特定示例需要评分,请使用 {- link #scoreExamples(JavaPairRDD, boolean)}或{- link #scoreExamples(JavaPairRDD, boolean, int)},每个示例都可以有一个键。
param data 要评分的数据
param includeRegularizationTerms 如果为true:在分数中包含l1/l2正则化项(如果有)
param batchSize 评分时要使用的批大小
return 包含每个示例分数的JavaDoubleRDD
查看ComputationGraph#scoreExamples(MultiDataSet, boolean)
evaluate
使用默认的批大小{- link #DEFAULT_EVAL_SCORE_BATCH_SIZE}对示例进行单独评分。与{- link #calculateScore(JavaRDD, boolean)}不同,此方法分别为每个示例返回一个分数 。
注意:提供的JavaPairRDD有一个与每个示例和返回的分数相关的键。
注意:传入的数据集对象中必须只有一个示例(否则:在要评分的键和数据集之间不能有1:1的关联)
param data 要评分的数据
param includeRegularizationTerms 如果为true:在分数中包含l1/l2正则化项(如果有
param Key 类型
return 包含每个示例分数的{- code JavaPairRDD}
查看 MultiLayerNetwork#scoreExamples(DataSet, boolean)
evaluate
在包含一组要用{-link MultiDataSetLoader}加载的MultiDataSet对象的目录上计算单个输出网络。使用默认的批大小{- link #DEFAULT_EVAL_SCORE_BATCH_SIZE}
param path 包含要加载的数据集的目录的路径/URI
return Evaluation
evaluateROCMDS
{- link #evaluate(JavaRDD)}的重载
使用Spark训练多层网络的主要类。也用于在这些网络上执行分布式评估和推理
getSparkContext
使用给定的上下文和网络实例化多层spark实例。这是预测构造函数
param sparkContext 要使用的spark上下文
param network 要使用的网络
getNetwork
return SparkDl4jMultiLayer底层的多层网络
getTrainingMaster
return 这个网络的TrainingMaster
setNetwork
设置这个SparkDl4jMultiLayer 实例下面的网络
param network 要设置的网络
getDefaultEvaluationWorkers
返回当前设置的默认评估工作节点/线程数。请注意,当在评估方法中显式提供工作节点数,不使用默认值。
在许多情况下,我们可能希望它小于Spark线程的数量,以减少内存需求。例如,对于32个Spark线程和一个大型网络,我们不希望启动32个网络实例来执行评估。最好(为了满足内存需求,减少缓存抖动)使用4个工作节点。 如果未显式设置,则将使用{- link #DEFAULT_EVAL_WORKERS}
返回默认的评估工作节点数。
setDefaultEvaluationWorkers
设置默认的评估工作节点/线程数。请注意,当在评估方法中显式提供工作节点数,不使用默认值。
在许多情况下,我们可能希望它小于Spark线程的数量,以减少内存需求。例如,对于32个Spark线程和一个大型网络,我们不希望启动32个网络实例来执行评估。最好(为了满足内存需求,减少缓存抖动)使用4个工作节点。 如果未显式设置,则将使用{- link#DEFAULT_EVAL_WORKERS}
setCollectTrainingStats
设置是否应为调试目的收集训练统计信息。默认情况下禁用统计信息收集
param collectTrainingStats 如果为 true: 收集训练统计数据。如果为 false: 不收集
getSparkTrainingStats
使用 {- link #setCollectTrainingStats(boolean)}启用统计信息收集后,获取训练统计信息
return 训练统计信息
predict
预测给定的特征矩阵
param features 给定的特征矩阵
return 预言
predict
预测给定向量
param point 要预测的向量
return 预测向量
fit
拟合 DataSet RDD。相当于fit(trainingData.toJavaRDD())
param trainingData 到fitDataSet的训练数据RDD
return 训练后的MultiLayerNetwork
fit
拟合 DataSet RDD
param trainingData 传递到fitDataSet的训练数据RDD
return 训练后的MultiLayerNetwork
fit
使用序列化数据集对象的目录拟合SparkDl4jMultiLayer网络。这里的假设是该目录包含许多{-link DataSet}对象,每个对象都使用{-link DataSet.save(OutputStream)}序列化
param path 包含序列化DataSet对象的目录的路径
return 训练后的多层网络
fit
已弃用 {- link #fit(String)}
fitPaths
使用序列化DataSet对象的路径列表拟合网络。
param paths 路径列表
return 已训练网络
fitLabeledPoint
使用Spark MLLib LabeledPoint实例拟合多层网络。这将把标记的点转换为内部的DL4J数据格式,并在此基础上训练模型
param rdd 传递到fitDataSet的rdd
return fitDataSet的多层网络
fitContinuousLabeledPoint
使用Spark MLLib LabeledPoint实例拟合多层网络这将把具有用于回归的连续标签的标签点转换为内部DL4J数据格式,并在此基础上训练模型
param rdd 包含标记点的javaRDD
return 多层网络
getScore
从调用fit获取上一个(平均)小批量分数。这是每个工作进程中执行的最后一个小批量的所有执行器的平均得分。
calculateScore
{-code RDD}而不是{-code JavaRDD}的{-link#calculateScore(JavaRDD,boolean)}重载
calculateScore
通过对整个数据集求和或求平均值,计算所提供{code JavaRDD}中所有示例的得分。要单独计算每个示例的分数,请使用 {- link #scoreExamples(JavaPairRDD, boolean)}或类似的方法之一。在每个工作节点中使用默认的小批量大小{- link SparkDl4jMultiLayer#DEFAULT_EVAL_SCORE_BATCH_SIZE}
param data 要评分的数据
param average 是否要加总或是平均分数
calculateScore
通过对整个数据集求和或求平均值,计算所提供{code JavaRDD}中所有示例的得分。要单独计算每个示例的分数,请使用 {- link #scoreExamples(JavaPairRDD, boolean)}或类似的方法之一。
param data 要评分的数据
param average 是否要加总或是平均分数
param minibatchSize 评分时在每个小批量中使用的示例数。如果一个分区中的示例多于此数目,则将执行多个评分操作(通过一次性完成整个分区,避免使用过多内存)
scoreExamples
{-code RDD}重载{- link #scoreExamples(JavaPairRDD, boolean)}
scoreExamples
使用指定的批处理大小 {- link #DEFAULT_EVAL_SCORE_BATCH_SIZE}对示例进行单独评分。与 {- link #calculateScore(JavaRDD, boolean)}不同,此方法分别返回每个示例的分数。如果特定示例需要评分,请使用 {- link #scoreExamples(JavaPairRDD, boolean)}或{- link #scoreExamples(JavaPairRDD, boolean, int)},每个示例都可以有一个键。
param data 要评分的数据
param includeRegularizationTerms 如果为true:在分数中包含l1/l2正则化项(如果有)
param batchSize 评分时要使用的批大小
return 包含每个示例分数的JavaDoubleRDD
查看 MultiLayerNetwork#scoreExamples(DataSet, boolean)
scoreExamples
{-code RDD}重载{- link #scoreExamples(JavaRDD, boolean, int)}
scoreExamples
使用指定的批处理大小 {- link #DEFAULT_EVAL_SCORE_BATCH_SIZE}对示例进行单独评分。与 {- link #calculateScore(JavaRDD, boolean)}不同,此方法分别返回每个示例的分数。如果特定示例需要评分,请使用 {- link #scoreExamples(JavaPairRDD, boolean)}或{- link #scoreExamples(JavaPairRDD, boolean, int)},每个示例都可以有一个键。
param data 要评分的数据
param includeRegularizationTerms 如果为true:在分数中包含l1/l2正则化项(如果有)
param batchSize 评分时要使用的批大小
return 包含每个示例分数的JavaDoubleRDD
查看 MultiLayerNetwork#scoreExamples(DataSet, boolean)
Spark上训练网络的实现。这是具有可配置平均周期的标准参数平均值。
removeHook
param saveUpdater 如果为true:在进行参数平均时保存(并平均)更新器状态
param numWorkers 群集的工作节点数(每个执行器的执行器线程数)
param rddDataSetNumExamples {-code RDD}中每个数据集对象中的示例数
param batchSizePerWorker 每个工作节点每次似合使用的示例数
param averagingFrequency 平均参数的频率(以小批量为单位)
param aggregationDepth 参数聚合中使用的聚合级别数
param prefetchNumBatches 要异步预取的批数(0:禁用)
param repartition 设置是否/何时对训练数据进行重新分区
param repartitionStrategy 要使用的重新分区策略。见 {- link RepartitionStrategy}
param collectTrainingStats 如果为true:收集用于调试/优化目的的训练统计信息
addHook
为主节点添加一个钩子,用于训练前和训练后
param trainingHook 要添加的训练钩子
fromJson
通过反序列化已用{- link #toJson()}序列化的JSON字符串,创建ParameterAveragingTrainingMaster实例
param jsonStr ParameterAveragingTrainingMaster配置序列化为JSON
fromYaml
通过反序列化已用{- link #toYaml()}序列化的YAML字符串,创建ParameterAveragingTrainingMaster实例
param yamlStr ParameterAveragingTrainingMaster配置序列化为YAML
trainingHooks
将训练钩子添加到主节点。训练主节点将为工作节点设置所需的钩子进行训练。这可以允许像参数服务器、异步更新以及收集统计数据之类的设置。
param trainingHooks 要添加的训练钩子
return
trainingHooks
将训练钩子添加到主节点。训练主节点将为工作节点设置所需的钩子进行训练。这可以允许像参数服务器、异步更新以及收集统计数据之类的设置。
param hooks 要添加的训练钩子
return
batchSizePerWorker
与{- link #Builder(Integer, int)}相同,但基于JavaSparkContext.defaultParallelism()自动设置工作节点数
param rddDataSetNumExamples {-code RDD}中每个数据集对象中的示例数
averagingFrequency
平均工作节点参数的频率。
注意:太高或太低可能有不同的原因。
过低(如1)会导致大量网络流量
过高(>>20左右)可能导致精度问题或网络收敛问题
param averagingFrequency 平均参数的频率(以“batchSizePerWorker”大小的小批量数为单位)
aggregationDepth
聚合树中用于参数同步的级别数。(默认值:2)注意:对于使用多个分区训练的大型模型,增加此数字将减少driver的负载,并有助于防止它成为瓶颈。
param aggregationDepth 平均参数更新时的RDD树聚合通道。
workerPrefetchNumBatches
在工作节点中设置要异步预取的小批量数。
默认值:0(无预取)
param prefetchNumBatches 要获取的小批量(batchSizePerWorker大小的数据集)的数量
saveUpdater
设置是否应保存更新器(即动量、adagrad等的历史状态)。注意:这可以使每个方向上的网络流量增加一倍(或更多),但可能会提高网络训练性能(对于某些更新器,如adagrad,可能更稳定)。
这在默认情况下是启用的。
param saveUpdater 如果为true:保留更新器状态(默认)。如果为false,则不保留(更新器将在平均后在每个工作节点中重新调整大小写)。
repartionData
设置是否/何时对训练数据进行重新分区。 默认值:总是重新分区(如果需要确保每个分区中的分区数和示例数正确)。
param repartition 重新分区的设置
repartitionStrategy
repartitionStrategy与{- link #repartionData(Repartition)}(定义何时应执行重新分区)一起使用,它定义了如何执行重新分区。有关详细信息,请参见{-link RepartitionStrategy}
param repartitionStrategy 要使用的重新分区策略
storageLevel
设置{-code RDD}s的存储级别。 默认值:StorageLevel.MEMORY_ONLY_SER()-即,存储在内存中,以序列化的形式使用非RDD持久化,请使用{-code null}
注意:Spark的StorageLevel.MEMORY_ONLY()和StorageLevel.MEMORY_AND_DISK()在处理堆外数据(DL4J/ND4J广泛使用)时可能会出现问题。在决定是否/何时删除块以确保足够的可用内存时,Spark不考虑堆外内存;因此,对于大于(堆外)内存总量的数据集RDD,这可能会导致OOM问题。换句话说:Spark只计算DataSet和INDArray对象的堆上大小(这是可以忽略的),从而大大低估了真正的DataSet对象大小。因此,内存中保存的数据集比我们实际负担得起的还要多。
param storageLevel 用于数据集RDD的存储级别
storageLevelStreams
设置从流中拟合数据时使用的存储级别RDD: PortableDataStreams (sc.binaryFiles 通过 {- link SparkDl4jMultiLayer#fit(String)} 和 {- link SparkComputationGraph#fit(String)}) 或字符路径 (通过 {- link SparkDl4jMultiLayer#fitPaths(JavaRDD)}, {- link SparkComputationGraph#fitPaths(JavaRDD)} 和 {- link SparkComputationGraph#fitPathsMultiDataSet(JavaRDD)}).
默认的存储级别是StorageLevel.MEMORY_ONLY(),这在大多数情况下应该是合适的。
param storageLevelStreams 存储级别
rddTrainingApproach
在{-code RDD}或{-code RDD}上进行训练时使用的方法。默认值:{link RDDTrainingApproach#Export},它首先将数据导出到临时目录
param rddTrainingApproach 从{-code RDD}或{-code RDD}进行训练时要使用的训练方法
exportDirectory
当{-link#rddTrainingApproach(RDDTrainingApproach)}设置为{- link RDDTrainingApproach#Export}(默认情况下)时,首先将数据导出到临时目录。
默认值:null。->使用{hadoop.tmp.dir}/dl4j/。在本例中,数据被导出到{hadoop.tmp.dir}/dl4j/SOME_UNIQUE_ID/ 如果指定目录,则将使用目录{exportDirectory}/SOME_UNIQUE_ID/。
param exportDirectory 导出数据的基本目录
rngSeed
随机数生成器种子,主要用于强制RDD上的可重复拆分默认值:无种子集(即随机种子)
param rngSeed 随机数生成器种子
return
collectTrainingStats
是否应启用训练统计信息收集(默认情况下禁用)。
查看 ParameterAveragingTrainingMaster#setCollectTrainingStats(boolean)
查看org.deeplearning4j.spark.stats.StatsUtils#exportStatsAsHTML(SparkTrainingStats, OutputStream)
param collectTrainingStats
收集数据集的统计信息。
有时候数据集太大或格式太抽象以致于在某些列或模式上无法分析和评估统计。
数据向量附带一些辅助工具来执行数据分析,以及最大值、平均值、最小值和其他有用的度量。
如果你已经把你的数据加载到Apache Spark,数据向量有一个特殊的AnalyzeSpark类可以生成直方图,收集统计数据,并返回数据质量信息。假设你你已经把数据加载到一个Spark RDD,把 JavaRDD
and Schema
传到这个类。
如果你在scala中使用数据向量并且你的数据已经被加载到一个常规的RDD
class,你可以通过调用 .toJavaRDD()
转换它,它返回一个JavaRDD。如果你需要把它还原,调用rdd()。
如下的代码演示了在Spark中使用 RDD javaRdd
和 概要mySchema 对2D数据集的分析。
注意到如果你有序列数据,这里同样有特别的方法:
AnalyzeLocal类工作起来和Spark的副本非常类似并且有相同的API。传一个可以允许迭代数据集的RecordReader来替代传RDD。
阅读不同格式的单独记录。
读取器从存储中的数据集迭代记录,并将数据加载到数据向量中。除了数据集中的单个条目之外,阅读器的用处包括:如果想要在语料库上训练文本生成器,或是以编程方式将两个条目组合在一起形成新的记录的时候该怎么办?读取器实现对于复杂的文件类型或分布式存储机制是有用的。
读取器返回记录记录中每一列的Writable类。这些类用于将每个记录转换为张量/NDArray 格式。
每个读取器实现都扩展了BaseRecordReader并提供了一个简单的API用于选取数据集中的下一条记录,行为类似于迭代器。
包括以下有用的方法:
next
: 返回一个批量的 Writable
。
nextRecord
: 返回单条记录,RecordMetaData
是可选的。
reset
: 重置基础迭代器。
hasNext
: 迭代器方法以确定是否有其他记录可用 。
你可以将自定义的RecordListener挂钩到记录读取器进行调试或可视化目的。在初始化类之后,立即将你的自定义侦听器传递给addListener基类方法。
DL4J语言处理概述
尽管没有设计成可以与诸如Stanford CoreNLP或NLTK之类的工具相提并论,但DL4J确实包括本文描述的一些核心文本处理工具。
DL4J的NLP依赖 ClearTK,一个开源的机器学习和Apache非结构化信息管理架构的自然语言处理框架,或UIMA。UIMA的使我们能够执行的语言识别,特定语言的分割,句子边界检测和实体检测(专有名词:个人、企业、地方和事物)。
处理自然语言有几个步骤。第一个是遍历你的语料库创建一个文件列表,它可以和个推文这么短,或和一篇报纸的文章这么长。这是由一个句子迭代器来执行的,它将是这样的:
SentenceIterator封装一个语料库或文本,组织它,比如说,每行一条推文。它负责将文本逐条输入到你的自然语言处理器中。SentenceIterator与类似命名的类不同,如DatasetIterator创建用于训练神经网络的数据集。相反,它通过分割语料库来创建字符串集合。
分词器进一步分割文本为单个词,或是作为n-grams。ClearTK包括了底层的分词器,例如词性和解析树,允许依赖和选区解析,就像递归神经张量网络(RNTN)所使用的一样。
分词器由TokenizerFactory 创建和包装。默认的词元是被空格分割的单词。分词进程还引入了一些机器学习来区分模棱两可的符号。哪些句子和缩写词如Mr. 和 vs。
Tokenizers(分词器)和SentenceIterator(句子迭代器)都与Preprocessors(预处理器)一起处理像Unicode这样的混乱文本中的异常,并统一地呈现这些文本,比如小写字符。
每一个文档都必须分割为词汇,用于文档或语料库的单词集合。这些单词存在词汇缓存中,它包含关于文档中计数的单词子集的统计信息。区分重要和不重要单词的线条是移动的,但是区分两组的基本思想是单词只出现一次(或少于五次)是很难学习的,并且它们的存在代表了无用的噪音。
词汇缓存了例如Word2vec和词袋方法的元数据,以极端不同的方式对待单词。Word2vec以数百个系数长的向量形式创建单词或神经单词嵌入的表示。这些系数帮助神经网络预测一个词在任何给定上下文中出现的可能性;例如,在另一个单词之后。这里是Word2vec,配置如下:
一旦你获得单词向量,你就可以把它们输入深度网络进行分类、预测、情感分析等。
简要介绍DL4J中的可用示例。
DL4J的Github仓库有很多示例可以涵盖它的功能。快速入门向你展示了如何设置Intellij并克隆仓库。本页提供这些例子中的一些概述。
大多数示例都使用DataVec,这是一个通过归一化,标准化,搜索和替换,列洗牌和向量化 预处理和清洗数据 的工具包。为神经网络读取原始数据并将其转换为DataSet对象通常是训练该网络的第一步。如果你不熟悉DataVec,这里有一个描述和一些有用的例子的链接。
本例采用同名花卉品种的标准Iris数据集,其相关测量值是萼片长度、萼片宽度、花瓣长度和花瓣宽度。它从相对较小的数据集构建一个Spark RDD,并对其进行分析。
加载数据到一个Spark RDD的示例。所有 DataVec 转换操作都使用Spark RDD。这里我们使用DataVec来过滤数据,应用时间转换和删除列。
这个例子显示了打印概要工具,这些工具对于可视化和确保转换的代码行为符合预期是有用的。
在传递传递数据集到一个神经网络之前,你可能需要连接数据集。你可以用DataVec实现,这个例子告诉你如何实现。
这是使用DataVec解析日志数据的示例。明显的使用场景是是网络安全和客户关系管理。
这个例子是从下面的视频中演示的,它展示了ParentPathLabelGenerator和ImagePreProcessing缩放器。
此示例演示了DataVec中可用的预处理特征。
DataMeta数据跟踪——即查看每个示例的数据来自何处——在跟踪导致错误和其他问题的格式错误的数据时非常有用。此示例演示RecordMetaData类中的功能。
为了构建一个神经网络,你将使用多层网络或计算图。这两种选项都使用Builder接口工作。下面描述了几个例子中的亮点。
MNIST是深度学习的入门。简单,直接,重心在于图像识别,神经网络做得很好的任务。
这是一个用于识别数字的单层感知器。请注意,这会从包含数据集的二进制包中提取图像,这是数据提取的一种特殊情况。
MNIST的两层感知器,展示对于一个给定的数据集有不止一个有用的网络。
通过前馈神经网络的数据流, 通过隐藏层从输入到输出的单向传递。
这些网络可用于各种各样的任务,这取决于它们被配置。除了对MNIST数据进行图像分类之外,这个目录还有演示回归、分类和异常检测的示例。
卷积神经网络主要用于图像识别,虽然它们也适用于声音和文本。
此示例可以使用LeNet或AlexNet运行。
在大量训练数据上训练网络需要时间。幸运的是,你可以保存一个经过训练的模型并加载模型以供以后的训练或推断。
演示了保存和加载用计算图构建的神经网络。
演示如何保存和加载用多层网络构建的神经网络。
我们的视频系列显示了包括保存和加载模型以及推理的代码。
你需要添加一个不可用的或是没有预构建的损失函数吗?查看这些示例。
你需要添加一个在DL4J中没有的特征的层吗?这个例子展示了如何开始。
我们也有自己的自然语言处理神经网络
用于词表示的全局向量对于检测词之间的关系是有用的。
单词的矢量化表示。这里有描述。
表示句子的一种方式是单词序列。
这里有描述
T-分布随机相邻嵌入(T-SNE)对于数据可视化是有用的。我们在NLP部分包括一个例子,因为词相似性可视化是一种常用的用法。
循环神经网络可用于处理时间序列数据或其他顺序馈送的数据,如视频。循环神经网络的示例文件夹有以下内容:
学习字符串的循环神经网络。
将莎士比亚的全部作品作为字符序列,并训练神经网络逐字创作“莎士比亚”。
这个例子训练一个神经网络做加法。
这个例子训练一个神经网络来执行各种数学运算。
一个公开的六类时间序列数据集,循环,向上等,例如,一个学习分类时间序列的RNN示例。
自动驾驶车辆如何区分行人、停车标志和绿灯?在一组视频上训练卷积和循环层的复杂神经网络。实况车载视频传递给训练好的网络,并且基于来自神经网络的目标检测的决策确定车辆动作。
这个例子类似,但简化了。它结合卷积、最大池、密连(前馈)和循环(LSTM)层来对视频中的帧进行分类。
该情感分析实例使用词向量和循环神经网络将情感分类为正或负。
DL4J支持使用Spark集群进行网络训练。这里是例子。
这是一个多层感知机对手写数字的MNIST数据集进行训练的例子。
在Spark上的一个LSTM循环网络
ND4J是张量处理库。它可以被认为是JVM版的Numpy。神经网络通过处理和更新多维数值数组来工作。在典型的神经网络应用程序中,您使用DataVec提取并转换数据为数字。使用的类是RecordReader。一旦需要将数据传递到神经网络,通常使用RecordReaderDataSetIterator。RecordReaderDataSetIterator返回DataSet对象。DataSet由输入特征和标签的NDArray组成。
学习算法和损失函数作为ND4J操作被执行。
这是一个带有创建和操作NDArrays示例的目录
深度学习算法已经学会了使用强化学习来玩《Space Invaders》和《Doom》。DL4J/RL4J强化学习的例子在这里可用:
java与maven快速入门
这是运行DL4J示例所需的一切,并开始自己的项目。
我们建议你加入我们的 Gitter Live Chat。Gitter是你可以请求帮助和提供反馈的地方,但是在问下面我们已经回答的问题之前,请务必使用这个指南。如果你是深度学习的新手,我们已经为初学者提供了路线图,链接到课程、阅读和其他资源。
DL4J是一种特定领域的语言,用于配置由多层构成的深度神经网络。一切都从MultiLayerConfiguration开始,这些MultiLayerConfiguration组织这些层和它们的超参数。
超参数是决定神经网络如何学习的变量。它们包括更新模型权重的次数、如何初始化这些权重、将哪个激活函数附加到节点、使用哪个优化算法以及模型应该学习多快。这就是一个配置的样子:
使用Deeplearning4j,可以通过调用NeuralNetConfiguration.Builder()上的layer方法来添加层,按照层的顺序(下面的零索引层是输入层)指定其位置、输入和输出节点的数量是nIn和nOut以及类型为:DenseLayer。
一旦你已经配置好你的网络,你可以用 model.fit
来训练你的网络
Java (developer version) 1.7 或更高版本 (仅支持64位版本)
Apache Maven (自动化构建和依赖管理器)
IntelliJ IDEA 或 Eclipse
你应该安装这些来使用这个快速入门指南。DL4J针对熟悉生产部署、IDE和自动化构建工具的专业Java开发人员。如果你已经有了这些经验,使用DL4J将是最简单的。如果您是Java新手或不熟悉这些工具,请阅读下面的详细信息以帮助安装和设置。否则,跳到DL4J示例。
如果你没有Java 1.7 或更高版版的Java,下载当前最新的Java Development Kit (JDK) 。用以下的命令来检查你是否有安装可兼容的Java版本。
确保你有一个64位版本的JAVA已被安装,如果你决定用32位版本来尝试,你会看到一个错误告诉你no jnind4j in java.library.path
确保JAVA_HOME 环境变量已被设置。
Maven是Java项目的依赖管理和自动构建工具。它很好地与IntelliJ等IDE一起工作,并允许你轻松安装DL4J项目库。按照他们的说明为您的系统安装或更新Maven最新版本。若要检查是否安装了最新版本的Maven,请输入以下内容:
如果你在Mac上工作,你可以简单的输入如下的命令行:
Maven在Java开发人员中被广泛使用,它与DL4J一起工作是非常必要的。如果你来自不同的背景,Maven对你来说是新的,请检查Apache的Maven概览和我们对非Java程序员的Maven的介绍,其中包括一些额外的疑难解答。其他构建工具,如常ivy和Dradle也可以工作,但我们对Maven支持最好。
集成开发环境(IDE)允许你使用我们的API并在几个步骤中配置神经网络。我们强烈建议使用IntelliJ,它与Maven通信来处理依赖关系。IntelliJ社区版是免费的。
还有其他流行的IDE,如Eclipse和NETBeans。但是,IntelliJ是首选,如果需要的话,使用它会让你在Gitter Live Chat 上更容易找到帮助。
安装Git的最新版本。如果你已经拥有Git,你可以使用Git本身更新到最新版本:
使用命令行输入以下内容:
通过向导选项继续。选择以jdk开头的SDK。(你也许需要点击一个加号来查看你的选项)然后点击 finish。等待IntelliJ下载完所有的依赖。你将看到右下角水平的进度条。
为了在你自己的工程中运行DL4J,我们强烈推荐使用Maven用于Java用户,或者为scala使用SBT工具。基本的依赖集及其版本如下所示。这包括:
deeplearning4j-core
, 包括神经网络的实现
nd4j-native-platform
, CPU版本的ND4J库为DL4J提供支持
datavec-api
- Datavec是我们用于向量化和加载数据的库
每个Maven工程都有一个POM文件。这里是运行示例时应该出现的POM文件。
在IntelliJ内部,你需要选择你要运行的第一个深度学习4J示例。我们建议MLPClassifierLinear,因为你几乎会立即看到网络将两组数据分类在我们的UI中。GITHUB上的文件可以在这里找到。
若要运行该示例,请右键单击它并选择下拉菜单中的绿色按钮。你会看到,在IntelliJ的底部窗口,一系列的分数。最右边的数字是网络分类的错误分数。如果你的网络正在学习,那么随着它处理的每个批次,这个数字会随着时间的推移而减少。最后,这个窗口会告诉你你的神经网络模型变得有多精确:
在另一个窗口中,将出现一个图表,显示多层感知器(MLP)如何对示例中的数据进行分类。它看起来像这样:
恭喜,你已用DL4J训练了你的第一个神经网络。
在Gitter上加入我们。我们有三个大的社区渠道。
DL4J Live Chat 是有关DL4J问题的主要渠道。大多数人出现在这儿。
Tuning Help 是提供给刚开始学习神经网络的人。初学者从这访问我们。
Early Adopters 是提供给检查和改进版本的人。警告:这个是提供给更多经验的爱好者。
阅读 神经网络介绍.
查看更详细的 全面设置指南.
浏览 DL4J 文档 .
Python 爱好者 : 如果您计划在Deeplearning4j上运行基准测试,并将其与著名的Python框架[x]进行比较,请阅读这些关于如何在JVM上优化堆空间、垃圾收集以及ETL的说明。通过参照它们,你会看到至少10倍的加速训练时间。
问: 我在Windows上使用64位的JAVA仍然得到 no jnind4j in java.library.path
错误
答: 你可能有不兼容的 DLLs 在你的 PATH中. 为了让 DL4J忽略这些, 你必须添加如下作为 VM 参数 (Run -> Edit Configurations -> VM Options in IntelliJ):
问: 我正在运行示例,并且基于Spark的示例存在问题,例如分布式培训或datavec转换选项。
答: 你可能丢失了Spark要求的一些依赖。查看这个Stack Overflow discussion来获得一个一个潜在的依赖问题讨论。windows用户可能需要来自Hadoop的winutils.exe。 从https://github.com/steveloughran/winutils and put it into the null/bin/winutils.exe下载winutils.exe (或创建一个hadoop文件夹并添加它到HADOOP_HOME)。
Windows用户可能看到如下信息:
如果是这个问题,请参阅本页。在这种情况下,替换为“Nd4jcpu”。
我们推荐使用Maven 和 Intellij。如果你更喜欢Eclipse并不喜欢Maven 这里有一篇很好的博客让你过度到Eclipse配置
现在,您已经了解了如何运行不同的示例,我们已经为你提供了一个模板,该模版具有一个基本的EMNIST训练器,具有早停和评估代码。
快速入门模版在此https://github.com/deeplearning4j/dl4j-quickstart.
使用模版:
克隆到你的本地机器 git clone https://github.com/deeplearning4j/dl4j-quickstart.git
导入 dl4j-quickstart
主文件夹到 IntelliJ.
开始编码!
Deeplearning4j是一个可以让你从一开始就可以选择一切的框架。我们不是Tensorflow(一个具有自动微分的低级数值计算库)或是Pytorch。
Deeplearning4j有几个子项目,使其易于构建端到端应用程序。
如果你想将模型部署到生产中,你可能会喜欢我们的从Keras导入的模型。
Deeplearning4j有几个子模块。这些范围从可视化UI到spark分布式训练。对于这些模块的概述,请查看Github上的Deeplearning4j示例。
如果你想要一个简单的桌面应用作为开始,你需要两件个东西:一个 nd4j backend 和 deeplearning4j-core
。更多代码请查看 simpler examples submodule.
如果你想要一个灵活的深度学习API,这有两种方式。你可以单独使用nd4j,查看我们的 nd4j 示例 或 计算图API.
如果你想要在 Spark进行分布式训练,你可以查看我们的 Spark page记住我们不会为你设置spark
如果你想要设置spark和GPU,这在很大程度上取决于你。Deeplearning4j简单的作为一个jar文件部署在一个存在的Spark集群上。
如果你想要Spark和GPU一起工作,我们推荐你看Spark with Mesos。
如果你想在移动端部署,你可以看我们的Android page。
我们为各种硬件架构部署优化后的代码。我们使用基于C++的循环,就像其他人一样。请查看C++ framework libnd4j。
Deeplearning4j有其它两个值得注意的组件:
Arbiter: 超参数优化和模型评估
DataVec: 用于机器学习数据管道的内置ETL工具
Deeplearning4j是构建真实应用程序的端到端平台,而不仅仅是具有自动微分的张量库。如果你想要一个带有autodiff的张量库,请参阅ND4J和samediff。samediff仍然在测试,但是如果你想做出贡献,请加入我们的live chat on Gitter。
最后,如果你正在测试DL4J,请考虑进入我们的在线聊天并获取提示。DL4J有所有的模块但可能不像Python框架那样工作。你必须为一些应用从源码来构建DL4J。
DL4J迁移学习API使用户能够:
修改现有模型的结构
调优现有模型的学习配置。
在训练期间保持指定层参数为常量(恒定不变),也称为“冻结”。
将某些层冻结在网络上,并且训练实际上与对输入的转换版本的训练相同,转换版本是冻结层边界处的中间输出。这是从输入数据中“特征提取”的过程,在本文档中称为“特征化”。
在大型的、预训练的网络上,对输入数据进行“特征化”的前向传递可能很耗时。DL4J还提供了一个具有以下功能的TransferLearningHelper类。
对输入数据集进行特征化保存以备将来使用
用特征化数据集拟合冻结层模型
给定一个特征化输入,从冻结层的模型输出。
当运行多个epoch时,用户将节省计算时间,因为对冻结层/顶点的昂贵传递将只需进行一次。
本例将使用VGG16对属于五种花卉的图像进行分类。数据集将自动从http://download.tensorflow.org/example_images/flower_photos.tgz下载
在0.9.0(0.8-1快照)下,DL4J有一个新的本地模型动物园。阅读有关deeplearning4j-zoo 模块的更多信息来了解如何使用预训练模型。这里,我们加载用ImageNet上训练的权重初始化的预训练VGG-16模型:
VGG16的最后一层对ImageNet.中的1000个类进行软最大回归。我们修改最后一层,给出五个类的预测,保持其他层的冻结。
在仅仅30次迭代(在这种情况下是曝光450幅图像)之后,该模型在测试数据集上达到了>75%的准确率。考虑到从头训练图像分类器的复杂性,这相当显著。
在这里,我们保持除了最后三个密连层之外的所有密连层冻结,并将新的密连层附加到其上。请注意,本文的主要目的是演示API的使用,其次可能提供更好结果。
假设我们已经从(B)中保存了模型,现在想要允许“block_5”层进行训练。
我们使用迁移学习助手API。注意,这冻结了传入的模型的层。 以下是如何在指定层“fc2”获得数据集的特征版本。
下面是如何与特征数据集拟合的方法。vgg16Transfer 是第三部分(a)中设置的一种模型。
迁移学习构建器返回一个DL4J模型的新实例。
请记住,这是第二个模型,使原件保持原状。对于大型的预训练网络,要考虑内存需求,并相应地调整JVM堆空间。
受过训练的模型助手从Keras导入模型而不强制执行训练配置。
因此,最后一层(如打印摘要时所见)是密连层,而不是具有损失函数的输出层。因此,为了修改输出层的nOut,我们删除了层顶点,保持了它的连接,并添加回具有相同名称、不同nOut、合适的损失函数等的新输出层。
在层/顶点处更改nOut将修改它的层/顶点的nIn。
当改变nOut时,用户可以指定层的权重初始化方案或分布,以及它的层的单独的权重初始化方案或分布。
当将模型写入磁盘时,不保存冻结层配置。
换句话说,在序列化和读回时具有冻结层的模型将不具有任何冻结层。为了持续训练以保持特定层常量,用户需要通过迁移学习助手或迁移学习API。在DL4J模型中有两种方法来“冻结”层。
On a copy: 使用迁学习API,它将返回一个包含相关冻结层的新模型
In place: 使用迁移学习帮助API将冻结层应用于给定模型。
调优配置将有选择地更新学习参数。
例如,如果指定了学习速率,则该学习速率将应用于模型中的所有未冻结/可训练层。然而,新添加的层可以通过在层构建器中指定它们自己的学习速率来覆盖此学习速率。
如何可视化、监控和调试神经网络学习。
注意:这里的信息属于DL4J版本0.7.0和更高版本。
DL4J在浏览器中提供了一个用户界面来可视化当前的网络状态和训练过程。这个界面通常用于帮助调整神经网络,即选择超参数(例如学习速率)以获得网络的良好性能。
第1步:向项目中添加DL4J UI依赖项。
请注意 _2.10
后缀:这是Scala版本(由于使用了Play框架,一个Scala库,用于后端)。如果你没有使用其他Scala库,_2.10
或 _2.11
都可以。
第2步:启用项目中的用户界面
这是比较直截了当的:
要访问UI,请打开浏览器,转到http://localhost:9000/train。可以使用org.deeplearning4j.ui.port系统属性设置端口:即,使用端口9001,在启动时将下列内容传递给JVM:-Dorg.deeplearning4j.ui.port=9001
然后,当你在网络上调用fit()方法时,信息将被收集并路由到UI。
示例: 在这里查看UI示例
完整的UI示例集在这里是可用的。
概述页面(3个可用页面之一)包含以下信息:
左上角:分数与迭代图-这是当前小批量的损失函数的值
右上角:模型与训练信息
左下角:所有网络权重与迭代的参数更新(逐层)的比率
右下角:激活、梯度和更新的标准差(时间)。
请注意,对于下面两个图表,这些图表被显示为值的对数(底数为10)。因此,更新值为-3:参数比率图对应于10的-3次方=0.001的比率。
参数更新的比率具体地是这些值的平均幅度的比率(即,log10(mean(abs(updates))/mean(abs(parameters)) )。
请参阅本页后面的部分,说明如何在实践中使用这些值。
模型页面包含神经网络层的图形,其作为选择机制运行。点击一个图层来显示它的信息。
在右边,在选择图层之后可以得到以下图表:
图层信息表
更新此层的参数比例,如概述页。这个比率的组成部分(参数和更新平均幅度)也可以通过标签来获得。
随着时间的推移,层激活(平均和平均+/- 2标准偏差)
每个参数类型的参数和更新的直方图
学习速率与时间(注意这将是平的,除非使用学习速调度)
注:参数标注如下:权重(W)和偏置(B)。对于循环神经网络,W是指连接层到下层的权重,RW指的是循环权重(即,时间步之间的权重)。
DL4J UI可以与Spark一起使用。然而,对于0.7.0版本,冲突依赖意味着在相同的JVM中运行UI和Spark可能是困难的。
有两种选择:
收集并保存相关的统计数据,以便在稍后的时间内可视化(离线)
在单独的服务器中运行UI,并使用远程UI功能将数据从Spark master上传到UI实例。
收集用于以后离线使用的统计信息
然后,你可以使用以下方式加载和显示保存的信息:
使用远程UI功能
首先,在JVM中用于UI(注意这是服务器):
这将需要deeplearning4j-ui_2.10
或deeplearning4j-ui_2.11依赖
。(注意,这不是客户端,这是你的服务器-下面的客户端使用:deeplearning4j-ui-model)
第一,客户端(spark和独立神经网络都使用简单的deeplearning4j-nn)第二,对于你的神经网络(注意,这个例子是用于spark的,但是计算图和多层网络都具有相同的用法,setListeners方法,这里找到的示例):
为了避免与Spark的依赖冲突,你应该使用deeplearning4j-ui-model
依赖来获得StatsListener,而不是完整的deeplearning4j-ui_2.10 UI
依赖。
Scala用户注意事项:
如果你在一个新的Scala版本上,你需要使用上面的方法。请参阅客户端的链接示例。
注意:你应该用运行用户界面实例的机器的IP地址替换UI_MACHINE_IP
。
这是一个很好的网页,由Andrej Karpathy编写的关于可视化神经网络训练。第一页是值得阅读和理解的。
调整神经网络往往是一门艺术而不是一门科学。然而,这里有一些有用的想法:
概述页面-模型得分与迭代图
分数与迭代应该(总体)随着时间推移而下降。
如果分数持续上升,你的学习率可能会太高。尽量减少,直到分数变得更稳定。
增加分数也可以指示其他网络问题,例如不正确的数据归一化。
如果分数是平坦的或下降非常缓慢(超过几百次迭代)(a)你的学习率可能太低,(b)你可能在优化上有困难。在后一种情况下,如果使用SGD更新器,则尝试不同的更新程序,如Nesterovs(动量)、RMSProp或Adagrad。
注意,未被混洗的数据(即,对于分类,每个小批次只包含一个类)可能导致非常粗糙的或异常的得分与迭代图
该线图中的一些噪声是被期待的(即,线将在一个小范围内上下)。但是,如果在运行之间分数相差很大,这可能有问题。
上面提到的问题(学习速率、归一化、数据混洗)可能有助于这一点。
将小批量大小设置为非常小的例子也可以导致有噪声的分数与迭代图,并且可能导致优化困难。
概述页面和模型页面-使用更新:参数比率图表
参数更新的平均大小的比值在概述和模型页上提供。
“平均级数”=当前时间步中参数或更新的绝对值的平均值
这个比率的最重要的用途是选择学习率。作为经验法则:这个比率应该在1:1000=0.001左右。在(log10)图上,这对应于一个值-3(即10的-3次方)=0.001)。
请注意,这只是一个粗略的指南,可能不适合所有的网络。然而,这通常是一个很好的起点。
如果比率与此显著不同(例如>-2(即,10的负-2次方=0.01)或<-4(即,10的-4次方=0.0001),则参数可能太不稳定而无法学习有用的特性,或者变化太慢而无法学习有用的特性。
要改变这个比率,调整你的学习速率(或者有时,参数初始化)。在一些网络中,你可能需要对不同的层设置不同的学习速率。
注意这个比例的异常大的尖峰:这可能意味着梯度爆炸。
模型页面:层激活(vs.时间)图
这个图表可以用于检测消失或爆炸的激活(由于恬当的权重初始化、太多的规则化、缺乏数据归一化或学习率太高)。
这个图表应该理想情况下会随时间稳定(通常是几百次迭代)。
一个良好的标准差的激活是在0.5至2的顺序。显著地超出这个范围可以指示出现了上面提到的问题之一。
模型页:层参数直方图
仅在最近迭代中显示层参数直方图。
经过一段时间后,对于权重,这些直方图应该有一个近似高斯(正态)分布。
对于偏置,这些直方图通常在0开始,并且通常以高斯近似结束。
对此的一个例外是LSTM循环神经网络层:默认情况下,一个(遗忘门 )的偏置被设置为1.0(默认情况下,尽管这是可配置的),以帮助在长时间段学习依赖性。这导致偏置图最初具有0附近的多个偏置,在1附近具有另一组偏置。
注意那些发散到+/-无限的参数:这可能是由于学习率太高或者正则化不足(尝试向网络中添加一些L2正则化)。
留意那些变得非常大的偏置。如果类的分布非常不平衡,则有时会出现在用于分类的输出层中。
模型页:层更新直方图
只显示最近一次迭代的层更新直方图。
注意,这些是更新,即应用学习速率、动量、正则化等之后的梯度。
与参数图一样,这些图应该具有近似高斯(正态)分布。
注意非常大的值:这可以指示网络中的爆炸梯度。
爆炸梯度是有问题的,因为它们可以“弄乱”网络的参数。
在这种情况下,它可以指示权重初始化、学习速率或输入/标签数据归一化问题。
在循环神经网络的情况下,添加一些梯度归一化或梯度裁剪可能有帮助。
模型页:参数学习率图表
如果你不使用学习率调度,图表将是平的。如果你使用的是学习率调度,则可以使用这个图表来跟踪随着时间推移的学习率的当前值(对于每个参数)。
我们依赖于TSNE,将单词特征向量的维数降到二维或三维空间。这里有一些使用TSNE与Word2Vec的代码:
DL4J UI可能发生的异常如下:
这个异常不是直接由于DL4J造成的,而是由于缺少application.conf文件,这个文件是Play框架(DL4J的UI所基于的库)需要的。这最初存在于deeplearning4j-play依赖项中:但是,如果uber-jar(即,具有依赖项的JAR文件)(例如,通过mvn包)被构建,uber-jar则可能无法被正确地复制。例如,使用maven-assembly-plugin已经对
一些用户造成了这个异常。
推荐的解决方案(对于Maven)是使用Maven Shade插件生成Uber jar,配置如下:
然后,用MVN包创建你的uber.jar,并通过cd 目标 和 java -cp dl4j-examples-0.9.1-bin.jar org.deeplearning4j.examples.userInterface.UIExample
。 注意生成的JAR文件的“-bin”后缀:这包括所有依赖项。
还要注意,这个Maven Shade方法是为DL4J的示例库配置的。
Spark上的DL4J: 介绍
DL4J支持使用Apache Spark在CPU或GPU机器集群上进行神经网络训练。DL4J还支持分布式评估以及使用Spark的分布式推理。
DL4J有两种分布式训练实现。
梯度共享,1.0.0-beta版本可用:基于Nikko Strom的论文,是一种异步SGD实现,在Spark+Aeron中实现量化和压缩更新
参数平均:一个同步SGD实现,带有一个完全用Spark实现的参数服务器。
用户被引导到取代参数平均实现的梯度共享实现。梯度共享实现导致更快的训练时间,并且实现为可伸缩和容错(从1.0.0-beta3开始)。为了完整起见,本页还将介绍参数平均方法。技术参考部分涵盖了实现的细节。 除了分布式训练之外,DL4J还允许用户进行分布式评估(包括同时进行多次评估)和分布式推理。请参阅Spark上的DL4J:如何指导 获取更多细节。
Spark并不总是训练神经网络最合适的工具。 你应该使用Spark训练的场景如下:
你有一组用于训练的机器集群(不仅仅是一台机器——这包括多GPU机器)
你需要多台机器来训练网络
你的网络大到足以证明有理由用分布式实现。
对于具有多个GPU或多个物理处理器的单台机器,用户应考虑使用DL4J的ParallelWrapper实现,如本示例所示。ParallelWrapper允许在具有多个核的单台机器上轻松进行网络的数据并行训练。与ParallelWrapper相比,Spark用于单机训练的开销更高。
类似地,如果你不需要Spark(更小的网络和/或数据集)-建议使用单机训练,这通常更容易设置。
对于一个足够大的网络:这里有一个粗略的指南。如果网络需要100ms或更长时间来执行一次迭代(每个小批次的拟合操作为100ms),则分布式训练应该工作良好,具有良好的可扩展性。在每次迭代为10ms时,我们可能期望性能与节点数量的次线性缩放。在每次迭代大约1ms或更低时,通信开销可能太大:集群上的训练可能不会比单台机器上的训练更快(或者甚至更慢)。为了并行性优于通信开销,用户应该考虑网络传输时间与计算时间的比率,并确保计算时间足够大,以掩盖分布式训练的额外开销。
为了在GPU上运行训练,请确保你在pom文件中指定了正确的后端(GPU的nd4j-cuda-x.x相对于CPU的nd4j-native 后端),并且已经用适当的CUDA库设置了机器。请参阅Spark上的DL4J:如何指导 获取更多细节。
使用梯度共享实现包括以下依赖性:
如果使用参数平均实现(同样,梯度共享实现应该是优选的),则包括:
注意,${scala.binary.version}是一个Maven属性,值为2.10或2.11,应该与你正在使用的Spark版本匹配。
以下是用户开始使用DL4J进行分布式训练时应该熟悉的关键类。
TrainingMaster: 指定如何在实践中进行分布式训练。实现包括梯度共享(SharedTrainingMaster)或参数平均(ParameterAveragingTrainingMaster)
SparkDl4jMultiLayer 与 SparkComputationGraph: 它们是DL4J中的MultiLayerNetwork和ComutationGraph类的包装器,支持与分布式训练相关的功能。为了训练,他们配置了一个TrainingMaster。
RDD<DataSet>
与 RDD<MultiDataSet>
: 具有DL4J的DataSet或MultiDataSet类的Spark RDD定义训练数据(或评估数据)的源。注意,推荐的最佳实践是只对数据进行一次预处理,并将其保存到诸如HDFS之类的网络存储中。有关更多细节,请参考Spark上的DL4J:如何构建数据管道。
训练工作流程通常如下:
准备带有几个组件的训练代码:a.神经网络配置b.数据管道c.SparkDl4jMultiLayer/SparkComputationGraph加上Trainingmaster
创建 uber-JAR 文件(请参阅Spark上的DL4J:如何指导 获取更多细节)
确定Spark提交的参数(内存、节点数量等)
带着必要参数提交uber-JAR 到 Spark
下面的代码片段概述了所需的一般设置。API参考概述了各个类的详细用法。用户可以向Spark Submit提交一个uber jar,以便使用正确的选项执行。请参阅Spark DL4J:如何指导 获取更多细节。
DL4J示例代码仓库包含一些可供用户使用作为参考的Spark示例。
Spark上的DL4J: 如何构建数据管道
本页提供了一些关于当在Spark使用DL4J时如何创建用于训练和评估的数据管道的指导。
本页面假设你对Spark(RDD、主节点、工作节点等 )和DL4J(网络、DataSet等)有一些了解。
与单台机器上的训练一样,数据管道的最后一步应该是生成DataSet(单个特征数组、单个标签数组)或MultiDataSet(一个或多个特征数组、一个或多个标签数组)。在DL4J 在 Spark的情况下,数据管道的最后一步是以下格式之一的数据:(a)RDD<DataSet>/JavaRDD<DataSet>
(b)RDD<MultiDataSet>/JavaRDD<MultiDataSet>
(c)网络存储上的串行化数据集/MultiDataSet(小批量)对象的目录,如HDFS、S3或Azure blob存储(d)其他格式的小批量目录
一旦数据是上述四种格式之一,就可以将其用于训练或评估。
注意:当在单个数据集上训练多个模型时,最佳做法是一次预处理数据,并将其保存到网络存储,如HDFS。然后,当训练网络时,可以调用SparkDl4jMultiLayer.fit(String path)
或 SparkComputationGraph.fit(String path)
,其中path是保存文件的目录。
Spark数据准备:操作指南
本指南展示了如何加载包含在一个或多个CSV文件中的数据,并产生一个“JavaDRD<DataSet>”,用于在Spark上导出、训练或评估。
这个过程相当简单。请注意,DataVecDataSetFunction
函数与非常常用于单机训练的RecordReaderDataSetIterator
非常类似。
例如,假设CSV具有以下格式——6个总列:5个特征,然后是一个用于分类的整数类索引,以及10个可能的分类
我们可以使用下面的代码加载这些数据进行分类:
然而,如果这个数据集是用于回归的,那么再有6个总列、3个特征列(文件行中的位置0、1和2)和3个标签列(位置3、4和5),我们可以使用与上面相同的过程加载它,但是将最后3行改为:
RecordReaderMultiDataSetIterator (RRMDSI) 是为单机训练数据管道创建MultiDataSet实例的最常用方法。可以将RRMDSI用于Spark数据管道,其中数据来自一个或多个RDD<List<Writable>>
(对于“标准”数据)或RDD<List<List<Writable>>
(对于序列数据)。
案例1: Single单 RDD<List<Writable>>
到 RDD<MultiDataSet>
考虑CSV分类任务的以下单节点(非Spark)数据管道。
相当于以下Spark数据管道:
对于序列数据 (List<List<Writable>>
) 你可以使用SparkSourceDummySeqReader 来代替.
案例2: Multiple多 RDD<List<Writable>>
或 RDD<List<List<Writable>>
到 RDD<MultiDataSet>
在这种情况下,过程基本相同。但是,在内部,使用连接。
如本页开头所述,预处理和导出数据一次(即,保存到诸如HDFS之类的网络存储和重用)而不是在每个训练作业中直接从RDD<DataSet>或RDD<MultiDataSet>拟合,被认为是最佳做法。
这有很多原因:
更好的性能(避免冗余加载/计算):当拟合来自同一数据集的多个模型时,一次预处理该数据并将其保存到磁盘要比每次训练运行再次预处理更快。
最小化内存和其他资源:通过从磁盘导出和拟合,我们只需要将当前使用的DataSets(加上一个小的异步预取缓冲区)保存在内存中,而不需要将许多未使用的DataSet对象保存在内存中。导出导致较低的总内存使用量,因此我们可以使用更大的网络、更大的小批量大小,或者为作业分配更少的资源。
避免重新计算:当RDD太大而不能放入内存时,可能需要在使用RDD之前重新计算RDD的某些部分(取决于缓存设置)。当这种情况发生时,Spark将多次重新计算数据管道的一部分,这会耗费时间和内存。预导出步骤完全避免了这种重新计算。
第1步:保存
一旦有了RDD<DataSet>,保存DataSet对象是非常简单的:
请记住,这是一个映射函数,因此在执行路径RDD之前,不会保存任何数据——即,你应该执行以下操作:
或
或
可以使用BatchAndExportMultiDataSetsFunction以相同的方式保存RDD<MultiDataSet>,它采用相同的参数。
第2步:加载和拟合
导出的数据可以以几种方式使用。首先,它可以直接用于拟合网络:
类似地,如果我们保存RDD<MultiDataSet>,则可以使用SparkComputationGraph.fitMultiDataSet(String path)。 或者,我们可以以几种不同的方式加载路径,这取决于我们是否或者如何保存它们:
然后我们可以使用诸如SparkDl4jMultiLayer.fitPaths(JavaRDD<String>)之类的方法在这些路径上执行训练。
另一种可能的工作流程是从单台机器上的数据管道开始,导出DataSet或MultiDataSet对象以便在集群上使用。显然,该工作流不像在集群上准备数据那样具有可伸缩性(你只使用一台机器来准备数据),但是在某些情况下,它可以是一个简单的选项,尤其是在你拥有现有数据管道的情况下。
本节假设你有一个用于单机训练的现有DataSetIterator或MultiDataSetIterator。有许多不同的方法可以创建一个,这超出了本指南的范围。
第1步: 保存 DataSets 或 MultiDataSets
可以使用以下代码将DataSet的内容保存到本地目录:
注意,对于Spark的目的,确切的文件名并不重要。保存MultiDataSet的过程几乎是相同的。 另外:可以使用FileDataSetIterator在单个机器上读取这些保存的DataSet对象(用于非Spark训练)。 另一种方法是使用输出流直接保存到集群,例如,保存到(例如)HDFS。这只有在运行代码的机器正确配置了所需的库和访问权限时才能实现。例如,将数据集直接保存到HDFS,你可以使用:
第2步:在集群上加载和训练
然后可以将保存的DataSet对象复制到集群或网络文件存储(例如,使用Hadoop集群上的Hadoop FS实用程序),并且如下使用:
或者可选地/等价地,我们可以使用RDD列出路径:
另一种方法是使用Hadoop MapFile和SequenceFiles,它们是有效的二进制存储格式。这可以用于将任何DataVec RecordReader或SequenceRecordReader(包括自定义记录读取器)的输出转换为可用于Spark的格式。MapFileRecordWriter和MapFileSequenceRecordWriter需要以下依赖项:
第1步:在本地创建一个MapFile
在下面的示例中,将使用CSVRecordReader,但是任何其他RecordReader都可以在其位置使用:
使用SequenceRecordReader结合MapFileSequenceRecordWriter的过程实际上是相同的。
还请注意,MapFileRecordWriter和MapFileSequenceRecordWriter都支持拆分,即创建多个较小的映射文件而不是创建单个(可能为多GB)的映射文件。当以这种方式保存数据以便与Spark一起使用时,建议使用拆分。
第2步: 复制到HDFS或其他网络文件存储
确切的过程超出了本指南的范围。但是,只要将目录(上面示例中的“/map/file/root/dir”)复制到HDFS上的位置就足够了。
第3步: 为训练读取和转换为RDD<DataSet>
我们可以使用以下方法加载数据进行训练:
本指南展示了如何加载训练RNN的CSV文件。假设数据集由多个CSV文件组成,其中:
每个CSV文件代表一个序列
CSV的每一行包含一个时间步的值(一个或多个列/值,所有文件的所有行中的相同数量的值)
每个CSV可以包含到其他CSV的不同数量的行(即,可变长度序列在这里是可以的)
标题行既不存在于任何文件中,也不存在于所有文件中。
可以使用以下过程创建数据管道:
本指南说明如何从本地或HDFS等网络文件系统存储的图像开始,创建用于图像分类的RDD<DataSet>
。
这里使用的方法(在1.0.0-beta3中添加)是首先将图像预处理成批文件—FileBatch对象。这种方法的动机很简单:原始图像文件通常使用高效的压缩(例如,JPEG),这比位图(int8或32位浮点)表示更有效。然而,在集群中,我们希望最小化磁盘读取,由于远程存储延迟导致的问题——一个文件读取/传输将比minibatchSize远程文件读取更快。
“TinyImageNet”示例也说明了如何做到这一点。 注意,该实现的一个限制是需要手动知道、提供或收集一组类(即,在进行分类时类/类别标签)。这与在单个机器上使用ImageRecordReader进行分类不同,后者可以自动推断类标签集。
首先,假设图像是在基于它们的类标签的子目录。例如,假设存在两个类“cat”和“dog”,则目录结构如下所示:
(注意,在这个示例中,文件名并不重要,但是,父目录名是类标签)
第1步(2的选项1):在本地进行预处理
本地预处理可以按如下完成:
SparkDataUtils 的完整导入是 org.deeplearning4j.spark.util.SparkDataUtils
.
在完成预处理之后,可以将目录复制到集群中用于训练(步骤2)。
第1步(2的选项2):使用Spark进行预处理
或者,如果原始图像在远程文件存储(例如HDFS)上,则可以使用以下方法:
第2步: 训练
图像分类的数据管道可以构造如下。这个代码取自TinyImageNet 示例:
就是这样。 注意:对于其他标签生成情况(比如从文件名而不是父目录中提供的标签),或者对于诸如语义分割之类的任务,你可以替换不同的PathLabelGenerator而不是默认的。例如,如果标签应该来自文件名,则可以使用PatternPathLabelGenerator。假设图像的格式为“cat_img1234.jpg”、“dog_2309.png”等。
注意,PathLabelGenerator返回一个Writable对象,所以对于像图像分割这样的任务,可以使用自定义PathLabelGenerator中的NDArrayWritable类返回INDArray。
DL4J Spark训练支持加载以自定义格式系列化的数据的能力。假设远程/网络存储中的每个文件都以某种可读格式表示单个小批量数据。
请注意,此方法通常不需要或不推荐给大多数用户,但作为高级用户或这些以自定义格式或不由DL4J本地支持的格式预先准备好的数据的用户提供附加选项。当文件以自定义格式表示单个记录/示例(而不是小批量)时,可以使用自定义RecordReader。
需要注意的接口是:
这两种方法都扩展了单方法Loader接口。 假设HDFS目录包含许多文件,每个文件都是某种定制格式的小批量文件。这些可以使用以下过程加载:
自定义加载器类看起来像:
DL4J中NLP神经词嵌入
内容
Word2Vec是一个处理文本的两层神经网络。它的输入是一个文本语料库,它的输出是一组向量:语料库中的单词的特征向量。Word2Vec不是一个深度神经网络,它将文本转换成一个深度网络可以理解的数值形式。DL4J实现了一个分布式的Word2Vec,用于Java和Scala,它在Spark的GPU上工作。
Word2Vec的应用扩展了自然界的句子解析。它也可以同样地应用于基因、代码、喜欢、播放列表、社交媒体图表和其他可以识别模式的语言或符号系列。
为什么?因为单词只是像上面提到的其他数据一样的离散状态,我们只是在寻找这些状态之间的转移概率:它们将同时发生的可能性。所以gene2vec,like2vec和follower2vec 都是可能的。记住这一点,下面的教程将帮助你理解如何为任意一组离散和共现状态创建神经嵌入。
Word2Vec的目的和实用性是将相似词的向量分组到向量空间中。也就是说,它在数学上检测相似性。Word2Vec创建向量,这些向量是单词特征(例如单个单词的上下文)的分布式数字表示。这样做没有人为干预。
给定足够的数据、用法和上下文,Word2Vec可以基于过去的出现对单词的意义做出高度准确的猜测。这些猜测可以用来建立一个单词与其他单词的关联(例如,“男人”是“男孩”,“女人”是“女孩”),或者是聚类文档,并按主题分类。这些聚类可以构成搜索的基础、情感分析和在科学研究、法律发现、电子商务和客户关系管理等多个领域的建议。
Word2Vec神经网络的输出是一个词汇表,其中每个项目都有一个附加到它的向量,它可以被送入深度学习网络或简单地查询以检测词之间的关系。
测量余弦相似度,90度角表示没有相似度,而总的相似度是1是0度角,完全重叠;即Sweden等于Sweden,而Norway到Sweden的余弦距离是0.760124,是任何其他国家中最高的。
这是一个使用Word2Vec生成的与“Sweden”相关的单词列表,按接近顺序排序:
斯堪的纳维亚的国家和几个富裕的北欧、日耳曼国家跻身前九位。
我们用来表示单词的向量称为神经词嵌入,表示是奇怪的。一件事描述了另一件事,尽管这两件事是根本不同的。正如Elvis Costello所说:“写作对于音乐就像跳舞对于建筑。”Word2Vec对单词“向量化”,通过这样做,它使得自然语言可以被计算机阅读——我们可以开始对单词执行强大的数学运算以检测它们的相似性。
因此,神经词嵌入用数字代表一个单词。这是一个简单但不太可能的翻译。
Word2Vec类似于一个自动编码器,将每个单词编码在一个向量中,而不是通过重建对输入单词进行训练,Word2Vec在语料库中将单词和与它们相邻的其他单词进行训练。
它以两种方式中的其中一种来实现,或者使用上下文来预测目标单词(一种称为连续词袋或CBOW的方法),或者使用单词来预测目标上下文,即skip-gram。我们使用后一种方法,因为它对大数据集产生更精确的结果。
当分配给单词的特征向量不能用于精确预测该单词的上下文时,向量的组成部分会被调整。语料库中的每个单词的上下文是老师,往回发送错误信号以调整特征向量。通过调整在向量中数值凑在一起的上下文,单词的向量被它们判断为相似的。
正如梵高的向日葵画是油画布上的二维混合物,代表了1880年代末巴黎三维空间中的植物物质,所以以向量排列的500个数字可以代表一个词或一组词。
这些数字将每个单词定位为500维向量空间中的一个点。超过三个维度的空间难以可视化。(Geoff Hinton教授人们想象13维空间,建议学生首先想象3维空间,然后对自己说:“13、13、13”:)
一组训练有素的单词向量将在那个空间中放置相似的单词。“橡树”、“榆树”和“桦树”可能会聚集在一个角落,而战争、冲突和争斗则聚集在另一个角落。
类似的事情和想法被证明是“接近的”。它们的相对意义已经转化为可测量的距离。质量变成数量,算法可以完成他们的工作。但相似性只是Word2Vec可以学习的许多关联的基础。例如,它可以衡量一种语言的单词之间的关系,并将它们映射到另一种语言。
这些向量是更全面的词汇几何的基础。如图所示,像罗马、巴黎、柏林和北京这样的首都城市相互靠近,在向量空间上它们各自具有与其国家相似的距离,即罗马-意大利=北京-中国。如果你只知道罗马是意大利的首都,并想知道中国的首都,那么等式罗马-意大利+中国将返回北京。这不是玩笑。
让我们看看Word2Vec可以产生的其他关联。
我们将用逻辑类比的符号代替加减等号,给出结果,其中:
是 “对于”的意思和::
“等同”的意思,例如“罗马对意大利就像北京对中国一样”=罗马:
意大利::
北京:
中国。在最后一点,当给出前三个元素时,我们将给出Word2vec模型建议的单词列表,而不是提供“答案”:
这个模型是在谷歌新闻vocab上进行训练的,你可以导入并玩一玩。考虑片刻,Word2Vec算法从来没有被教过一条英语语法规则。它对世界一无所知,与任何基于规则的符号逻辑或知识图无关。然而,比在多年的人力学习后大的大多数知识图的学习,它以更灵活和自动化的方式学习。它把Google新闻的文档看作一张白板,训练结束后,它可以计算对人类有意义的复杂类推。
你还可以查询Word2Vec模型进行其他关联。并不是每件事都必须有两个相互镜像的类推。(我们解释如下……)
地缘政治学:伊拉克-暴力=约旦
区分:人类-动物=伦理
总统-权力=总理
图书馆-图书=大厅
类推:股票市场≈温度计
通过构建一个单词与其他类似单词的邻近场景,这些单词不一定包含相同的字母,我们已经从硬标记,进入了更平滑和更普遍的意义的场景 。
这些是DL4J自然语言处理的组件:
SentenceIterator/DocumentIterator: 用于迭代一个数据集。 SentenceIterator 返回一个字符串 , DocumentIterator 与输入流一起工作。
Tokenizer/TokenizerFactory: 用于对文本进行分词。 在NLP术语中,句子被表示为一系列词。TokenizerFactory为一个句子创建一个分词器的实例。
VocabCache: 用于跟踪元数据,包括单词计数、文档出现、词集(本例中不是vocab,而是已经发生的令牌词)、vocab(词袋和单词向量查找表中包括的特性)
Inverted Index: 存储有关单词发生的元数据。可以用于理解数据集。自动创建具有Lucene实现(1)的Lucene索引。
Word2vec是指一系列相关算法,该实现采用负采样。
使用Maven在IntelliJ中创建一个新项目。如果你不知道怎么做,请看我们的快速入门页面。然后在项目的根目录的POM.xml文件中指定这些属性和依赖项(你可以检查Maven以获得最新版本,请使用这些版本…)。
现在在Java中创建并命名一个新类。之后,你将在.txt文件中获取原始语句,用迭代器遍历它们,并使它们接受某种预处理,例如将所有单词转换为小写。
如果你想加载一个文本文件,用我们的例子中提供的句子之外的句子,你这样做:
也就是说,去掉ClassPathResource
,并将你的.txt文件的绝对路径填入到LineSentenceIterator
中。
在bash中,通过在命令行中从同一目录中键入pwd,可以找到任何目录的绝对文件路径。对于该路径,你将添加文件名。
Word2Vec需要用词而不是完整的句子,所以下一步就是把数据分词。把文本分词是把它分解成原子单位,例如,每次你点击一个空白处时,创建一个新的分词。
那样它会给你每行一个词。
现在数据已准备就绪,你可以配置Word2Vec神经网络并输入分词。
此配置接受许多超参数。 一些需要一些解释:
batchSize 是你一次处理的单词数量。
minWordFrequency 是单词必须出现在语料库中的最小次数。 在这里,如果它出现少于5次,则不会学习。 单词必须出现在多个上下文中才能学习有关它们的有用特征。 在非常大的语料库中,提高最小值是合理的。
useAdaGrad - Adagrad为每个特征创建不同的梯度。 在这里,我们并不关心这一点。
layerSize 指定单词向量中的特征数。这等于特征空间中的维数。由500个特征表示的词成为500维空间中的点。
learningRate 是每个更新系数的步长,因为单词在特征空间中被重新定位。
minLearningRate 是学习率的底板。学习速率随着你训练的单词数量的减少而衰减。如果学习率下降太多,网络的学习就不再有效了。这保持系数移动。
iterate 告诉网络它正在训练的数据集的批次。
tokenizer 从当前批次中为它提供单词。
vec.fit() 告诉配置的网络开始训练。
这里是训练你以前训练过的单词向量的示例。
下一步是评估特征向量的质量。
vec.similarity("word1","word2")这行
将返回输入的两个词的余弦相似度。越接近1,网络就理解为越类似于那些词(参见上面的瑞典-挪威例子)。例如:
使用vec.wordsNearest("word1", numWordsNearest)
,打印到屏幕上的单词允许你查看网络是否聚集了语义上相似的单词。你可以用wordsNearest方法的第二个参数来设置你想要的最近单词的数量。例如:
我们依赖于TSNE来把单词特征向量和项目词的维数减少到两个或三维空间。TSNE的完整的DL4J/ND4J例子在这里。
你会想保存这个模型。在DL4J中保存模型的常规方法是通过序列化工具(Java序列化类似于Python的pickling,将一个对象转换成一系列字节)。
这将将向量保存到一个名为pathToSaveModel.txt
的文件中,该文件将出现在Word2Vec被训练的目录的根目录中。文件中的输出每行应该有一个单词,后面是一系列数字,它们一起表示它的向量。
为了继续使用向量,简单地像这样调用关于vec的方法:
Word2Vec的词算术的经典例子是 国王-皇后=男人-女人,它的逻辑扩展是 国王-皇后+女人=男人。
上面的例子将把10个最近的单词输出到向量 国王-皇后+女人 ,这应该包括“男人”。wordsNearest的第一个参数必须包括“正”单词国王 和女人,它们具有与之关联的+符号;第二个参数包括“负”单词皇后,它与负符号关联(这里正和负没有情感内涵);第三是你想看的最接近单词列表的长度。请记住将此添加到文件的顶部:import java.util.Arrays;
任何数量的组合都是可能的,但只有在语料库中出现足够频繁的查询词时,它们才会返回合理的结果。显然,返回相似词(或文档)的能力是搜索引擎和推荐引擎的基础。
你可以像这样把向量重新加载到内存中:
然后,您可以使用Word2Vec作为查找表:
如果单词不在词汇表中,Word2Vec返回零。
在S3托管的谷歌新闻语料库模型,我们用来测试我们的训练网的准确性。对于那些在大型语料库上训练当前硬件需要很长时间的用户,可以简单地下载它来探索Word2Vec模型,而不需要前奏。
如果你用C vectors或Gensimm进行训练,此行将导入模型。
记得添加 import java.io.File;
到你引入的包。
对于大型模型,你可能会遇到堆空间的问题。Google模型可能需要多达10G的RAM,而JVM只使用256MB的RAM启动,因此必须调整堆空间。你可以用一个bash_profile
文件(参见我们的故障排查部分),或者通过IntelliJ本身来做:
单词被一次读入到向量,并在一定范围内来回扫描。这些范围是n-gram,一个 n-gram是给定语言序列中n个项目的连续序列;它是unigram、bigram、trigram、4-gram或5-gram的第n个版本。skip-gram简单地从N-gram中删除项目。
Mikolov推广并在DL4J实现中使用的skip-gram被证明比其他模型(如连续词袋)更精确,这是因为生成的上下文更具有通用性。
然后将该n-gram输入到神经网络以学习给定词向量的重要性;即,重要性被定义为其实用性,作为作为某些更大含义或标签的指示器。
请注意:下面的代码可能过时了。有关更新的示例,请参阅Github上的我们的DL4J示例库。
既然你已经有了一个关于如何建立Word2Vec的基本思想,这里有一个例子,它是如何与DL4J的API一起使用的:
在按照快速入门的说明后,你可以在IntelliJ中打开这个示例并点击Run运行它。如果你在Word2Vec模型中查询一个不包含在训练语料库的单词,它将返回NULL。
问:我有很多这样的堆栈跟踪
答:看看你启动Word2Vec应用程序的目录里面。例如,这可以是一个IntelliJ项目主目录或在命令行键入Java的目录。它应该有一些目录看起来像:
你可以关闭你的Word2Vec应用程序并尝试删除这些目录。
问:不是所有来自我原始文本数据的单词都出现在我的Word2Vec对象中…
答: 试着在你的Word2Vec对象上通过.layerSize() 来提高图层大小,像这样
问:如何加载我的数据?为什么训练会永远持续下去?
答:如果你所有的句子都被作为一个句子被加载,Word2Vec训练可能需要很长的时间。这是因为Word2Vec是一个句子级别的算法,所以句子边界非常重要,因为共现统计是逐句收集的。(对于GloVe来说,句子边界并不重要,因为它关注于语料库范围的共现。对于许多语料库,平均句子长度为六个单词。这意味着在窗口大小为5的情况下,有30个(随机数)回合的skip-gram计算。如果你忘记指定句子的边界,你可能加载一个“10000个单词”长的句子。在这种情况下,Word2Vec将为整个10000个单词“句子”尝试全skip-gram循环。在DL4J的实现中,假定一行是一个句子。你需要插入你自己的句子迭代器和分词器。通过要求你指定你的句子如何结束,DL4J仍然是语言不可知论者。UimaSentenceIterator是这样做的一种方式。使用OpenNLP进行句子边界检测。
问:为什么把整个文档作为一个“句子”而不是分割成句子时,在性能上有如此不同?
答:如果平均句子包含6个单词,窗口大小为5,那么理论上最多10个skipgram回合的次数是0字。句子不够长,不能用文字表达完整的窗口。在这句话中所有单词的粗略最大数目为5个skipgram回合。但如果你的“句子”有1000k个单词的长度,这个句子中的每个单词就有10个skipgram回合,不包括前5个和最后5个。因此,你将不得不花费大量时间来构建模型+由于缺少句子边界,协同统计将会发生变化。
问:Word2Vec是如何使用内存的?
答:Word2Vec中的主要内存消耗是权重矩阵。数学是简单的:单词数x维度数x 2 x数据类型 内存占用。因此,如果使用浮点数和100维来构建100k字的Word2Vec模型,那么内存占用将是100kx100x2x4(浮点数大小)=80MB RAM,仅用于矩阵+用于字符串、变量、线程等的一些空间。如果加载预构建的模型,则在构建时间中使用大约1/2的RAM,因此它是40MB RAM。目前使用的最流行的模型是谷歌新闻模型。有3百万字,向量大小为300。这就使我们需要3.6G RAM仅加载模型。而且必须添加3M的字符串,这些字符串在Java中没有固定的大小。所以,通常是大约4-6GB用于加载模型,这取决于JVM版本/供应商,GC状态和月球的相位。
问:我做了你说的每一件事,结果还是不对头。
答:确保你正遇到不是正常性问题。一些任务,如wordsNearest(),默认使用标准化的权重,而其他的则需要非标准化的权重。注意这个区别。
谷歌学者保存了论文记录,这里引用了Word2Vec的DL4J实现。
来自比利时的数据科学家Kenny Helsens将Word2Vec的DL4J实现应用于NCBI的在线孟德尔人类继承(OMIM)数据库。然后,他寻找与alk(一种已知的非小细胞肺癌的致癌基因)最相似的单词,Word2vec返回:“nonsmall, carcinomas, carcinoma, mapdkd”。从那里,他建立了其他癌症表型和基因型之间的类比。这只是Word2Vec在大型语料库上可以学习的一个例子。发现重要疾病新方面的潜力才刚刚开始,在医学之外,机会也同样多样。
Andreas Klintberg在瑞典训练了Word2Vec的DL4J实现,并在媒体上写下了一个完整的指导。
Word2Vec在信息检索准备基于文本的数据和问答系统中特别有用,DL4J通过深度自动编码器来实现这些系统。
营销人员可能寻求建立产品间的关系来建立推荐引擎。调查者可能会分析一个社会图表,以显示单个群体的成员,或者他们可能必须定位或资助的其他关系。
Word2Vec是由Tomas Mikolov领导的谷歌研究团队介绍的一种计算单词向量表示的方法。谷歌托管了一个开源版本的Word2Vec,它是在Apache 2许可下发布的。在2014,Mikolov离开谷歌去了Facebook,并在2015年5月,谷歌被授予获得此专利,已发布的版本没有废除Apache许可证。
虽然所有语言中的单词都可以用Word2Vec转换为向量,并且这些向量通过DL4J学习,但是NLP预处理可以非常特定于语言,并且需要超出我们库的工具。斯坦福自然语言处理小组有许多基于Java的工具,用于语言的分词、词性标注和命名实体识别,例如普通话、阿拉伯语、法语、德语和西班牙语。对于日本人来说,像Kuromoji之类的NLP工具是有用的。其他的外语资源,包括文本语料库,都在这里。
加载和保存GloVe模型到Word2Vec可以这样做:
DL4J具有一个名为SequenceVectors的类,它是单词向量之上的抽象级别,并且允许你从任何序列中提取特征,包括社交媒体概要、事务、蛋白质等。 如果数据可以被描述为序列,它可以通过skip-gram和层次化的softmax与AbstractVectors类来学习。这与深度算法相兼容,也在DL4J中实现。
模型序列化/反序列化 被添加后的权重会更新。也就是说,你可以通过调用loadFullModel、向其中添加TokenizerFactory和SentenceIterator、以及调用还原的模型上的fit()
来使用200GB的新文本更新模型状态。
用于词汇构建的多个数据源的选项被添加。
训练和迭代可以单独指定,尽管它们通常都是“1”。
Word2Vec.Builder 有这个选项: hugeModelExpected
. 如果设为 true
, 在构建过程中,词汇将被周期性的截断。
minWordFrequency
有助于忽略语料库中的稀有词,可以排除任何数量的词来定制。
两个新的WordVectorsSerialiaztion 方法已被介绍: writeFullModel
和 loadFullModel
. 这些保存和加载一个完整的模型状态。
一个体面的工作站应该能够处理一个有几百万单词的词汇量。DL4J的Word2Vec实现可以在一台机器上对兆兆字节的数据进行建模。大致来说,计算公式 是:vectorSize * 4 * 3 * vocab.size()。
word2vec 解释: Deriving Mikolov et al.’s Negative-Sampling Word-Embedding Method; Yoav Goldberg and Omer Levy
ND4J综合编程指南。
NDArray本质上是n维数组:即矩形数字数组,具有一定维数。
一些你应该熟悉的概念:
NDArray的rank是维度数。二维数组的rank为2,三维数组的rank为3,依此类推。你可以创建具有任意rank的NDArrays。
NDArray的(shape)形状定义了每个维度的大小。假设我们有一个有3行5列的二维数组。这个
NDArray的形状是[3,5]
NDArray的长度定义了数组中元素的总数。长度始终等于构成形状的值的乘积。
NDArray的步幅定义为每个维度中相邻元素的间隔(在底层数据缓冲区中)。步幅是按维度定义的,因此一个rank 为 n的 NDArray有n个步幅值,每个维度一个。请注意,大多数情况下,你不需要了解(或关注)步幅-只需注意这是ND4J内部的运作方式。下一节有一个步幅的例子。
NDArray的数据类型指的是一个NDArray的数据类型(例如, float 或 double 精度)。注意在nd4j中是全局的设置,所以所有的NDArrays应该有相同的数据类型。设置数据类型会在这个文档的后面再讨论。
就索引而言这里有一些事情需要知道。首先,维度0是行,维度1是列:因此INDArray.size(0)是行的数量,INDArray.size(1)是列的数量,索引是0开始的:因此行有从0到INDArray.size(0)-1的索引,对于其他维度,依此类推。
在本文档中,我们将使用NDArray
术语来描述n维数组的一般概念;术语NDArray
具体指的是ND4J定义的Java接口。实际上,这两个术语可以互换使用。
接下来的几段描述了ND4J背后的一些架构。理解这一点并不是使用ND4J所必需的,但它可能有助于你了解幕后发生的事情。NDArrays作为一个单一的扁平的数字数组(或者更一般地说,作为单个连续的内存块)存储在内存中,因此与典型的Java多维数组(例如,浮点[]][2] [][][][]有很大的不同。
物理上,INDArray背后的数据是堆外存储的:也就是说,它存储在Java虚拟机(JVM)之外。这具有许多优点,包括性能、与高性能BLAS库的互操作性以及避免JVM在高性能计算中的一些缺点(例如,由于整数索引,Java数组限于2 ^ 31 - 1(21亿4000万)个元素)。
在编码方面,可以按C(行主要)或Fortran(列主要)顺序对NDArray进行编码。有关行与列主顺序的更多详细信息,请参阅维基百科。ND4J可以同时使用C和F顺序数组的组合。大多数用户只能使用默认的数组排序,但请注意,如果需要,可以对给定的数组使用特定的排序。
下图显示了简单的3x3(2d)NDArray是如何存储在内存中的,
在如下的数组,我们有:
Shape = [3,3]
(3 行 , 3 列)
Rank = 2
(2 维)
Length = 9
(3x3=9)
Stride
C顺序步幅:[3,1]: 在缓冲区中连续行中的值按3分割
,在缓冲区中连续列中的值按1分割
F顺序步幅:[1,3]: 在缓冲区中连续行中的值按1分割
,在缓冲区中连续列中的值按3分割
NDArrays中的一个关键概念是两个NDArrays实际上可以指向内存中相同的底层数据。通常,我们有一个数组引用另一个数组的某个子集,而这只发生在某些操作(如INDArray.get()
, INDArray.transpose()
, INDArray.getRow()
等)中。这是一个强大的概念,值得理解。
这主要有两个动机:
有相当大的性能优势,尤其是在避免复制数组方面
我们在NDArrays上如何执行操作方面获得了很大的权力。
考虑一个简单的操作,比如一个大型(10000x1000)矩阵上的矩阵转置。使用视图,我们可以在不执行任何复制的情况下(即,大O标记法中的O(1))在恒定时间内执行矩阵转置,从而避免复制所有数组元素的相当大的成本。当然,有时我们确实想制作一份副本-这时我们可以使用INDArray.dup()
来获取副本。例如,要获得转置矩阵的副本,请使用INDArray out = myMatrix.transpose().dup()
。在这个dup()调用之后,原始数组myMatrix和数组out之间将没有链接(因此,对其中一个的更改不会影响另一个)。
所以看看视图是如何强大的,考虑一个简单的任务:将1.0添加到更大数组的第一行,myarray。我们可以很容易地做到这一点,在一行中:
myArray.getRow(0).addi(1.0)
让我们来分析一下这里发生的事情。首先,getRow(0)
操作返回一个INDArray,是原始数据的视图。注意myarray和 myArray.getRow(0)
都指向内存中的相同区域:
然后,在addi(1.0)被执行后,我们有如下状态
如我们所见,对myArray.getRow(0)
返回的NDArray的更改将反映在原始数组myArray
中;同样,对myArray
的更改将反映在行向量中。
创建数组最常用的两种方法是:
Nd4j.zeros(int...)
Nd4j.ones(int...)
数组的形状被指定为整数。例如,要创建一个由3行5列组成的零填充数组,请使用 Nd4j.zeros(3,5)
。
这些通常可以与其他操作结合使用其他值创建数组。例如,要创建一个填充了10的数组:
INDArray tens = Nd4j.zeros(3,5).addi(10)
上面的初始化工作分为两个步骤:首先分配一个3x5数组,用零填充,然后向每个值加10。
Nd4j提供了一些创建
INDArrays的方法,其中内容是伪随机数。
要在0到1范围内生成统一的随机数,请使用Nd4j.rand(int nRows, int nCols)(对于二维数组)或Nd4j.rand(int[])
(对于3个或更多维度)。
同样,要生成平均零和标准偏差为1的高斯随机数,请使用
Nd4j.randn(int nRows, int nCols)
or Nd4j.randn(int[])。
对于可重复性(即设置nd4j的随机数生成器种子),可以使用Nd4j.getRandom().setSeed(long)
ND4J为从Java float和double组创建数组提供了方便的方法。
要从1D Java数组创建1D NDArray,请使用:
行向量: Nd4j.create(float[])
或 Nd4j.create(double[])
列向量: Nd4j.create(float[],new int[]{length,1})
或 Nd4j.create(double[],new int[]{length,1})
对于 2维数组, 使用 Nd4j.create(float[][])
或 Nd4j.create(double[][])
.
为了从具有3个或多个维度的Java原始数组(double [][][]等)创建NDArrays,一种方法是使用以下:
从其它数组创建数组的方法主要有三种:
使用INDArray.dup()创建现有
NDArray的精确副本
将数组创建为现有NDArray的子集
合并多个现有NDArrays以创建新的NDArrays
对于第二种情况,您可以使用getRow(), get()等。有关详细信息,请参见获取和设置NDArrays的部分。
合并NDArrays的两种方法是Nd4j.hstack(INDArray...)
和 Nd4j.vstack(INDArray...)
。
hstack(水平堆叠)将具有相同行数的多个矩阵作为参数,并将它们水平堆叠以生成新的数组。但是,输入NDArrays可以有不同数量的列。
示例:
输出:
vstack
(垂直堆叠) 是hstack的垂直等效值。输入数组的列数必须相同。
示例:
输出:
ND4J.concat
沿维度组合数组。
示例:
输出:
ND4J.pad
用于填充数组
示例:
输出:
另一种偶尔有用的方法是Nd4j.diag(INDArray in)
。此方法有两种用途,具体取决于中的参数:
如果in是向量,diag输出一个对角线等于数组in(其中N是in的长度)的NxN矩阵。
如果in是一个
NxN矩阵,diag将输出一个从in的对角线获取的向量。
要创建大小为N的单位矩阵(对角线上的值都为1,其它值都为零),可以使用Nd4j.eye(N)
。
要使用元素[a, a+1, a+2, ..., b]
创建行向量,可以使用linspace命令:
Nd4j.linspace(a, b, b-a+1)
Linspace可以与reshape操作结合使用以获得其他形状。例如,如果要一个包含5行和5列、值为1到25(包括1到25)的二维NDArray,可以使用以下内容:
Nd4j.linspace(1,25,25).reshape(5,5)
对于INDArray,可以使用要获取或设置的元素的索引来获取或设置值。对于维度为n数组(即具有n个维度的数组),需要n个索引。
注意:单独获取或设置值(例如,在for循环中一次一个值)在性能方面通常是一个坏主意。如果可能,尝试使用其他一次对大量元素进行操作的INDArray方法。
要从二维数组中获取值,可以使用:INDArray.getDouble(int row, int column)
对于任何维度的数组,可以使用INDArray.getDouble(int...)
。例如,要获取索引i、j、k处的值,请使用 INDArray.getDouble(i,j,k)
要设置值,请使用putScalar方法之一:
INDArray.putScalar(int[],double)
INDArray.putScalar(int[],float)
INDArray.putScalar(int[],int)
这里,int[]是索引,double/float/int是要放置在该索引上的值。
在某些情况下可能有用的一些附加功能是NDIndexIterator
类。NDIndexIterator
允许你按定义的顺序获取索引(具体来说,对于rank为3的数组,C顺序遍历顺序为[0,0,0]、[0,0,1]、[0,0,2]、…、[0,1,0]、…等)。
要迭代二维数组中的值,可以使用:
要从INDArray中获取单行,可以使用INDArray.getRow(int)
。这显然会返回一个行向量。这里需要注意的是,这一行是一个视图:对返回行的更改将影响原始数组。这有时非常有用(例如:myArr.getRow(3).addi(1.0)
将1.0添加到较大数组的第三行);如果需要行的副本,请使用getRow(int).dup()
。
同样,要获取多行,请使用INDArray.getRows(int...)
。这将返回一个行已堆叠的数组;但是请注意,这将是原始行的副本(而不是视图),由于NDArrays存储在内存中的方式,因此此处不可能有视图。
对于设置单行,可以使用 myArray.putRow(int rowIdx,INDArray row)
。这会将myarray的rowidxth行设置为包含在indarray行中的值。
Get:
更强大和通用的方法是使用INDArray.get(NDArrayIndex...)
。此功能允许你基于某些索引获取任意子数组。这也许最好用一些例子来解释:
要获取单行(和所有列),可以使用: myArray.get(NDArrayIndex.point(rowIdx), NDArrayIndex.all())
要获取行(行A(含)到行(不含)和所有列的范围,可以使用: myArray.get(NDArrayIndex.interval(a,b), NDArrayIndex.all())
要获取所有行和第二列,可以使用: myArray.get(NDArrayIndex.all(),NDArrayIndex.interval(0,2,nCols))
尽管上面的示例仅适用于二维数组,但NDArrayIndex方法扩展到3个或更多维度。对于三维,你将提供3个NDArrayIndex对象,而不是两个,如上所述。 请注意,NDArrayIndex.interval(...)
, .all()
与 .point(int)
方法始终返回底层数组的视图。因此,.get()返回的数组更改将反映在原始数组中。
Put:
同样的NDArrayIndex方法也用于将元素放入另一个数组:在本例中,你使用INDArray.put(INDArrayIndex[], INDArray toPut)
方法。显然,NDArray toput的大小必须与提供的索引所暗示的大小匹配。
还要注意myArray.put(NDArrayIndex[],INDArray other)
在功能上等同于执行myArray.get(INDArrayIndex...).assign(INDArray other)
。同样,这是因为.get(INDArrayIndex...)
返回底层数组的视图,而不是副本。
(注:与当前版本相比,0.4-rc3.8及更早版本的ND4J返回的沿维度张量结果略有不同)。
沿维张量是一种强大的技术,但一开始可能有点难以理解。沿维张量(以下简称为TAD)背后的思想是得到一个低阶子数组,它是原始数组的视图。
“沿维张量”方法采用两个参数:
要返回的张量的索引(在0到numTensors-1的范围内)
执行TAD操作的维度(1个或多个值)
The simplest case is a tensor along a single row or column of a 2d array. Consider the following diagram (where dimension 0 (rows) are indexed going down the page, and dimension 1 (columns) are indexed going across the page):
最简单的情况是沿二维数组的单个行或列的张量。考虑下面的关系图(其中维度0(行)在页面下方被索引,维度1(列)在页面上方被索引):
注意,在所有情况下,一个维的tensorAlongDimension调用的输出都是行向量。
为了理解我们为什么得到这个输出:考虑上图中的第一个案例。在那里,我们取第0(第一)个张量沿维数0(维数0为行);当我们沿维数0移动时,值(1,5,2)在一条线上,因此输出。同样地,tensorAlongDimension(1,1)
是第二个(index=1)沿维数1的张量;当我们沿维数1移动时,值(5,3,5)在一条线上。
TAD操作也可以沿多个维度执行。例如,通过指定两个维度来执行TAD操作,我们可以使用它从3D(或4D或5D…)数组中获取二维子数组。同样,通过指定3个维度,我们可以使用它从4d或更高维度获得3d。
有两件事我们需要知道的输出,为TAD操作是有用的。
首先,我们需要我们能得到的张量的数量,对于一组给定的维度。为了确定这一点,我们可以使用“沿维度的张量数量”方法,即INDArray.tensorssAlongDimension(int... dimensions)
。此方法只返回沿指定维度的张量数。在上面的例子中,我们有:
myArray.tensorssAlongDimension(0) = 3
myArray.tensorssAlongDimension(1) = 3
myArray.tensorssAlongDimension(0,1) = 1
myArray.tensorssAlongDimension(1,0) = 1
(在后两种情况下,请注意,沿维度的张量将提供与原始数组相同的数组输出,即,我们从二维数组获得二维输出)。
一般来说,张量的个数是由剩余尺寸的乘积给出的,张量的形是由原形状中指定维度的大小给出的。
以下是一些例子:
对于输入形状[a,b,c],tensorssAlongDimension(0)给出b*c张量,tensorAlongDimension(i,0)返回形状 [1,a]的张量。
对于输入形状[a,b,c],tensorssAlongDimension(1)给出一个a*c张量,tensorAlongDimension(i,1)返回形状[1,b]的张量。
对于输入形状[a,b,c],tensorsalongdimension(0,1)给出c张量,tensorAlongDimension(i,0,1)返回形状[a,b]的张量。
对于输入形状[a,b,c],tensorssAlongDimension(1,2)给出张量,tensorAlongDimension(i,1,2)返回形状[b,c]的张量。
对于输入形状[a,b,c,d], tensorssAlongDimension(1,2) 给出了一个a*d张量,tensorAlongDimension(i,1,2)返回了形状[b,c]的张量。
对于输入形状[a,b,c,d],tensorssAlongDimension(0,2,3)给出b张量,tensorAlongDimension(i,0,2,3)返回形状为[a,c,d]的张量。
[本节:即将到来。]
ND4J有操作的概念,你可能想用一个INDArray来做(或去做)很多事情。例如,ops用于应用tanh操作、添加标量或执行元素操作。
ND4J定义了五种类型的操作:
Scalar(标量)
Transform(转换)
Accumulation(累加)
Index Accumulation(索引累加)
Broadcast(广播)
以及两种执行方法:
直接在整个INDArray上,或
沿一个维度
在讨论这些操作的细节之前,让我们先考虑一下就地和复制操作之间的区别。
许多操作既有就地操作,也有复制操作。假设我们要添加两个数组。Nd4j为此定义了两种方法:INDArray.add(INDArray)
和INDArray.addi(INDArray)
。前者(add)是一个复制操作;后者是一个就地操作-i在addi中表示就地操作。此约定(…i表示就地,没有 i表示复制)适用于通过INDArray接口可访问的其他操作。
假设我们有两个INDArrays x和y,我们做INDArray z = x.add(y)
或 INDArray z = x.addi(y)操作
。这些操作的结果如下所示。
注意,使用x.add(y)
操作时,不会修改原始数组x
。相比之下,对于就地版本x.addi(y)
,数组x被修改。在添加操作的两个版本中,都会返回包含结果的INDArray。但是请注意,在addi
操作的情况下,结果数组实际上只是原始数组x
。
标量操作是一种元素操作,它也接受一个标量(即一个数字)。标量操作的例子有add、max、multiple、set和divide操作(有关完整列表,请参见上一链接)。
许多方法,例如INDArray.addi(Number)
和INDArray.divi(Number)
实际上在后台执行标量操作,因此,在可用时,使用这些方法更方便。
要更直接地执行标量操作,可以使用以下示例:
Nd4j.getExecutioner().execAndReturn(new ScalarAdd(myArray,1.0))
请注意,myarray是通过此操作修改的。如果这不是你想要的,请使用 myArray.dup()
。
与其余的操作不同,标量操作没有对沿维度执行它们的合理解释。
转换操作是诸如元素方向的对数、余弦、tanh、校正线性等操作。其他示例包括加法、减法和复制操作。转换操作通常以元素为导向的方式使用(例如,对每个元素使用tanh),但情况并非总是如此——例如,softmax通常沿维度执行。
要直接(在完整的NDArray上)执行元素相关的tanh操作,可以使用:
INDArray tanh = Nd4j.getExecutioner().execAndReturn(new Tanh(myArr))
与上面提到的标量操作一样,使用上面方法的转换操作是就地操作:即修改了NDArray myArr,并且返回的数组tanh实际上是与输入myarr相同的对象。同样,如果需要副本,可以使用myArr.dup()
。
Transforms类还定义了一些方便的方法,例如:INDArray tanh = Transforms.tanh(INDArray in,boolean copy)
;这相当于使用 Nd4j.getExecutioner()的
方法。
在执行累加时,在整个NDArray上执行累加与在特定维度(或维度)上执行累加之间有一个关键区别。在第一种情况下(对整个数组执行),只返回一个值。在第二种情况下(沿维度累加),将返回一个新的NDArray。
要获取数组中所有值的总和,请执行以下操作:
double sum = Nd4j.getExecutioner().execAndReturn(new Sum(myArray)).getFinalResult().doubleValue();
或同等(更方便)
double sum = myArray.sumNumber().doubleValue();
还可以沿维度执行累加操作。例如,要获取每列中所有值的总和(每列=沿维度0或“每行中的值”),可以使用:
INDArray sumOfColumns = Nd4j.getExecutioner().exec(new Sum(myArray),0);
或同等,
INDArray sumOfColumns = myArray.sum(0)
假设这是在3x3输入数组上执行的。从视觉上看,这个维度0操作的求和操作如下:
请注意,这里输入的形状为[3,3](3行,3列),输出的形状为[1,3](即,我们的输出是行向量)。如果我们改为沿着维度1进行操作,我们将得到一个形状为[3,1]且值为(12,13,11)的列向量。
沿维度的累加也概括为具有3个或更多维度的NDArrays。
索引累加操作与累加操作非常相似。区别在于它们返回的是一个整数索引,而不是一个双精度值。 索引累加操作的示例有IMax(argmax)、IMin(argmin)和IAMax(绝对值的argmax)。 要获取数组中最大值的索引,请执行以下操作:
int idx = Nd4j.getExecutioner().execAndReturn(new IAMax(myArray)).getFinalResult();
索引累加操作通常在沿维度执行时最有用。例如,要获取每列(每列=沿维度0)中最大值的索引,可以使用:
INDArray idxOfMaxInEachColumn = Nd4j.getExecutioner().exec(new IAMax(myArray),0);
假设这是在3x3输入数组上执行的。从视觉上看,沿维度0操作的argmax/IAMax操作如下:
与上述累加运算一样,输出的形状为[1,3]。同样,如果我们沿着维度1进行操作,我们将得到一个形状为[3,1],值为(1,0,2)的列向量。
ND4J还定义了广播和向量操作。 一些更有用的操作是向量操作,如addRowVector和muliColumnVector。 例如,考虑操作 x.addRowVector(y)
,其中x是矩阵,而y是行向量。在这种情况下,addRowVector
操作将行向量y添加到矩阵x的每一行,如下所示。
与其他操作一样,有就地和复制版本。这些操作还有列-列版本,例如addColumnVector
,它将列向量添加到原始INDArray的每一列。
[本节:即将到来]
Link: Boolean Indexing Unit Tests
工作间是ND4J的一个特性,通过更有效的内存分配和管理,可以提高性能。具体来说,工作空间是为周期性工作负载而设计的,例如训练神经网络,因为它们允许堆外内存重用(而不是在循环的每次迭代中持续分配和释放内存)。净效果是提高性能和减少内存使用。 有关工作间的详细信息,请参见以下链接:
有时,对于工作区,你可能会遇到一个异常,例如:
或
理解范围恐慌异常
简而言之:这些异常意味着在工作区中分配的INDArray被错误地使用(例如,缺陷或某些方法的错误实现)。这有两个原因:
INDArray已从定义的工作区“泄漏”出去。
INDArray在正确的工作区内使用,但来自上一次迭代。
在这两种情况下,INDArray指向的底层堆外内存都已失效,无法再使用。 导致工作区泄漏的事件序列示例:
工作间 W 被打开
INDArray X 分配在了工作间W
工作间 W 被关闭, 因此X的内存不再有效。
INDArray X在某些操作中使用,导致异常。
导致过期工作间指针的事件序列示例:
工作间 W被打开 (第1次迭代)
INDArray X 在工作间W中分配 (第1次迭代)
工作间 W 被关闭 (第1次迭代)
工作间 W被打开 (第2次迭代)
INDArray X (来自第1次迭代) 在其它操作中被使用,抛出异常。
范围恐慌异常的解决方法和修复方法
根据原因,有两种基本解决方案。 第一。如果已经实现了一些自定义代码(或者正在手动使用工作间),这通常表示代码中存在错误。通常,您有两种选项:
使用INDArray.detach()
方法从所有工作间分离INDArray。其结果是,返回的数组不再与工作区关联,可以在任何工作区内部或外部自由使用。
首先不要在工作间中分配数组。您可以使用:try(MemoryWorkspace scopedOut = Nd4j.getWorkspaceManager().scopeOutOfWorkspaces()){ <your code here> }
暂时“关闭”工作间。其结果是,try块中的任何新数组(例如,通过nd4j.create创建的)都不会与工作区关联,并且可以在工作区之外使用。
使用INDArray.leverage()
或leverageTo(String)
或migrate()
方法之一,将数组移动/复制到父工作间。有关更多详细信息,请参见这些方法的javadoc。
第二,如果你使用工作间为Deeplearning4j的一部分,并且没有实现任何自定义功能(即,你没有编写自己的层、数据管道等),那么(在遇到这种情况的时候),这很可能表示底层库中存在一个bug,通常应该通过GitHub报告问题。一个可能的解决方法是使用以下代码禁用工作区:
如果异常是由于数据管道中的问题造成的,则可以在AsyncShieldDataSetIterator
或 AsyncShieldMultiDataSetIterator中
尝试包装 DataSetIterator
或 MultiDataSetIterator
无论是哪种原因,如果你确定你的代码是正确的,最后的解决方案是尝试禁用范围恐慌。请注意,这是不推荐的,如果存在合法问题,可能会导致JVM崩溃。为了实现禁用范围恐慌,请在执行代码之前使用Nd4j.getExecutioner().setProfilingMode(OpExecutioner.ProfilingMode.DISABLED)
。
ND4J目前允许用浮点或双精度值来支持INDArrays。默认值为单精度(浮点)。要将nd4j全局用于数组的顺序设置为双精度,可以使用:
注意,这应该在使用ND4J操作或创建数组之前完成。 或者,可以在启动JVM时设置属性:
[本节:即将到来]
扁平化是给定数组的一些遍历顺序,将一个或多个INDArrays转换为单个平面数组(行向量)的过程。 ND4J为此提供了以下方法:
Nd4j还提供了带有默认顺序的重载toFlattened方法。参数order必须是“c”或“f”,并定义从数组中获取值的顺序:参数order为c导致使用数组索引对数组进行扁平化,其顺序为[0,0,0]、[0,0,1]等(对于三维数组),而参数order为f导致按[0,0,0]、[1,0,0]等顺序获取值。
[本节:即将到来]
[本节:即将到来]
[本节:即将到来]
ND4J提供了许多格式的INDArrays序列化。下面是一些二进制和文本序列化的示例:
nd4j-serde 目录为 Aeron, base64, camel-routes, gsom, jackson 和 kryo提供包。
本节以摘要形式列出了ND4J中最常用的操作。关于其中大部分的更多详细信息,可以在本页后面找到。 在本节中,假设arr、arr1等为INDArrays。
创建NDArrays:
创建一个零初始化数组:Nd4j.zeros(nRows, nCols)或
Nd4j.zeros(int...)
创建一个一初始化数组
: Nd4j.ones(nRows, nCols)
创建一个
NDArray的副本(复制):arr.dup()
从double[]创建行/列向量
: myRow = Nd4j.create(myDoubleArr)
, myCol = Nd4j.create(myDoubleArr,new int[]{10,1})
从double[][]创建二维
NDArray: Nd4j.create(double[][])
堆叠一组数组以形成较大的数组:Nd4j.hstack(INDArray...)
, Nd4j.vstack(INDArray...)
分别用于水平和垂直
均匀随机数NDArrays:Nd4j.rand(int,int)
, Nd4j.rand(int[])
等
Normal(0,1) 随机 NDArrays: Nd4j.randn(int,int)
, Nd4j.randn(int[])
确定INDArray的大小/尺寸:
以下方法由INDArray接口定义:
获取维度数: rank()
对于 2维 NDArrays 仅有: rows()
, columns()
第i维的大小: size(i)
获取所有维度的大小,以int[]返回: shape()
确定数组中元素的总数: arr.length()
也参见: isMatrix()
, isVector()
, isRowVector()
, isColumnVector()
获取和设置单个值:
获取第i行第j列的值: arr.getDouble(i,j)
从一个三维以上的数组获取一个值: arr.getDouble(int[])
在一个数组中设置单个值: arr.putScalar(int[],double)
标量操作:标量操作接受一个double/float/int值,并对每个值执行一个操作,就像元素操作一样,有就地操作和复制操作。
添加标量: arr1.add(myDouble)
减去一个标量: arr1.sub(myDouble)
乘以一个标量: arr.mul(myDouble)
除以标量: arr.div(myDouble)
反向减法 (scalar - arr1): arr1.rsub(myDouble)
反向除法 (scalar / arr1): arr1.rdiv(myDouble)
元素操作:注意:有复制(添加、mul等)和就地(addi、muli)操作。前者:arr1未修改。后者中:arr1被修改
加: arr1.add(arr2)
减: arr.sub(arr2)
乘: add1.mul(arr2)
除: arr1.div(arr2)
赋值(将arr1中的每个值设置为arr2中的值):arr1.assign(arr2)
缩减操作(SUM等);请注意,这些操作对整个数组进行操作。调用.doubleValue()
从返回的数字中获取一个双精度值。
所有元素的总和: arr.sumNumber()
所有元素的乘积: arr.prod()
L1和L2范数: arr.norm1()
与 arr.norm2()
各元素标准差: arr.stdNumber()
线性代数操作:
矩阵乘法: arr1.mmul(arr2)
矩阵转置: transpose()
求矩阵的对角线: Nd4j.diag(INDArray)
矩阵求逆: InvertMatrix.invert(INDArray,boolean)
获取更大的NDArray的一部分:注意:所有这些方法都返回
获取一行 (仅二维数组NDArrays): getRow(int)
获取多行作为一个矩阵(仅二维数组): getRows(int...)
设置一行 (仅二维数组NDArrays): putRow(int,INDArray)
获取前3行,所有列: Nd4j.create(0).get(NDArrayIndex.interval(0,3),NDArrayIndex.all());
元素转换 (Tanh, Sigmoid, Sin, Log etc):
使用 Transforms: Transforms.sin(INDArray)
, Transforms.log(INDArray)
, Transforms.sigmoid(INDArray)
等
直接地 (方法 1): Nd4j.getExecutioner().execAndReturn(new Tanh(INDArray))
直接地 (方法 2) Nd4j.getExecutioner().execAndReturn(Nd4j.getOpFactory().createTransform("tanh",INDArray))
Q: ND4J支持稀疏数组吗?
目前:不支持,未来计划支持。
Q: 是否可以动态增大或缩小INDArray上的大小?
在当前版本的ND4J中,这是不可能的。不过,我们将来可能会添加此功能。
有两种可能的解决办法:
分配一个新数组并进行复制(例如.put()操作)
最初,预先分配一个大于所需的NDArray,然后对该数组的视图进行操作。然后,由于你需要一个更大的数组,请在原始预分配的数组上获得更大的视图。
高维数据的t-SNE可视化。
T-分布式随机相邻嵌入(T-SNE)是由Delft技术大学的Laurens van der Maaten创建的数据可视化工具。
虽然它可以用于任何数据,但是t-SNE(发音为Tee-Snee)只对标记数据有意义,这说明输入是如何聚类的。下面,你可以看到使用t-SNE处理MNIST数据在DL4J中生成的图形类型。
仔细看,你可以看到数字聚集在它们相似的地方,旁边的点。 下面是T-SNE如何出现在DL4J中的代码。
在特定条件下终止训练。
在训练神经网络时,需要对使用的设置(超参数)作出许多决策,以便获得良好的性能。一旦这样的超参数是训练epochs的数目:也就是说,数据集(epochs)的完整传递应该有几次?如果我们使用太少的epochs,我们可能欠拟合(即,不能从训练数据中学习我们能学的所有);如果我们使用太多的epochs,我们可能过拟合(即,在训练数据中拟合“噪声”,而不是信号)。 早期停止尝试删除手动设置该值的需要。它也可以被认为是一种正则化方法(如L1/L2权重衰减和丢弃),因为它可以阻止网络过拟合。
早停的思想相对简单:
将数据分割成训练集和测试集
在每个epoch的末尾(或每N个epoch):
评估测试集上的网络性能
如果网络性能超过以前的最佳模型:在当前epoch保存网络的副本
作为最终模型,具有最佳测试集性能的模型
下面用图形显示:
最好的模型是在垂直虚线时保存的模型,即在测试集上具有最高准确率的模型。 使用DL4J的早停功能需要你提供一些配置选项:
得分计算器,如多层网络的DataSetLossCalculator(JavaDoc, Source Code)或计算图的DataSetLossCalculatorCG (JavaDoc, Source Code)。用于在每个epoch进行计算(例如:测试集上的损失函数值或测试集上的准确率)
我们想要计算分数函数的频率(默认值:每个epoch)
一个或多个终止条件,它告诉训练过程何时停止。停止条件有两类:
Epoch终止条件:每N个epoch评估
迭代终止条件: 每个小批量评估一次
一个模型保存器,它定义了如何保存模型。
例如,在epoch终止条件最大为30epoch、最大为20分钟的训练时间的情况下,计算每个epoch的得分,并将中间结果保存到磁盘:
你还可以实现自己的迭代和epoch终止条件。
上面描述的早停实现将仅用于单个设备。然而,EarlyStoppingParallelTrainer
提供与早期停止类似的功能,并允许你为多个CPU或GPU进行优化。EarlyStoppingParallelTrainer
将你的模型包装在ParallelWrapper
类中,并执行本地化的分布式训练。
请注意,EarlyStoppingParallelTrainer
并不支持作为其单个设备使用时所具有的所有功能。它不是UI兼容的,可能无法与复杂的迭代监听器一起工作。这是由于模型是在后台分发和复制的机置引起的。
模型导入入门。
以下是演示将Keras模型加载到DL4J中并验证工作网络的工作代码的视频教程。教员Tom Hanlon提供了一个简单的分类器概述,该分类器在Keras内置的Iris数据上具有Theano后端,并导出和加载到DL4J:
如果你看视频有问题,请点击这里查看YouTube上的视频。
Spark上的DL4J: 技术说明
本节将介绍DL4J的Apache Spark梯度共享训练实现的技术细节。关于参数平均实现的细节也有。注意,从1.0.0beta开始,参数平均实现已经被梯度共享实现所取代。本指南假定读者熟悉分布式训练中的关键概念,如数据并行和同步与异步SGD。
异步SGD实现
参数平均实现
容错
DL4J的异步SGD实现基于Nikko Strom的Strom2015神经网络训练论文,并做了一些修改。下一节将回顾Strom论文的主要特性,然后介绍DL4J实现以及它与论文的不同之处。
当在集群上训练神经网络时,工作机器需传递对其参数的改变。通过直接传递新的参数值(例如在参数平均中)或通过传递梯度/更新信息(如在梯度共享中)。
此方法的关键特征在于,与跨网络转达所有参数/更新相反,仅传送高于用户指定阈值的更新。换一种说法:我们从一个需要通信的更新向量(每个参数一个条目)开始。代替原样通信向量,我们仅以量化方式(它是稀疏二进制向量)通信大元素,而不是所有元素。这里的动机是减少所需的网络通信量——这种“稀疏、1位二进制编码”方法可以将通信更新所需的大小减少1000倍或更多——有关一些压缩统计信息,请参阅Strom的论文。
注意,低于阈值的更新不会被丢弃,而是累积在稍后要应用的“剩余”向量中。同样值得注意的是,缺少一个集中式参数服务器,它被对等通信所取代,如下图所示。
更新的向量, 上图中的δi,j 是:
稀疏:在每个向量δi,j中只传递一些梯度(其余的假设为0)-稀疏条目使用整数索引编码
量化为单个比特:稀疏更新向量的每个元素取值+τ 或 −τ。向量的所有元素的t值是相同的。因此只需要一个位来区分这两个选项。
整数索引(用于标识稀疏数组中的条目)可选地使用熵编码进行压缩,以进一步减小更新大小(作者以额外的计算为代价引用了进一步3倍的通信量缩减,然而这种好处可能比不上额外的开销)
异步SGD的主要问题之一是陈旧梯度问题。在Strom的方法中,不需要显式地处理陈旧梯度——在大多数情况下,更新在每个节点上应用非常快。论文报告网络传输减少了几个数量级。给定一个适当的计算密集型模型(如RNN或CNN),网络通信的急剧减少确保了模型在所有节点上相等,并且陈旧的梯度不是问题。
然而,这种方法无法规避下述的缺点:
Strom报告说在训练的早期阶段可能会出现收敛问题(在一个epoch的一小部分使用较少的计算节点似乎是有帮助的)
压缩和量化不是免费的:这些过程导致每个小批量额外的计算时间和每个执行器的少量内存开销
该过程引入两个要考虑的额外超参数:阈值的值、τ以及是否使用熵编码进行更新(然而很显然,参数平均和异步SGD都引入额外的超参数)
DL4J 实现与Strom的方法有以下不同 :
非点对点:该实现允许用户选择两种网络组织模式——普通模式和网格模式。当集群中的节点数小于32个节点时,使用普通模式,对于较大的集群则使用网格模式。详情请参阅“不同模式”一节。
两种编码方案:DL4J使用两种编码方案,根据哪种方案将提供较少的网络通信,在这两种方案之间动态切换。有关详细信息,请参阅关于编码的章节。
调整量化阈值:根据每次迭代后的更新分布,将量化阈值提高或降低。这是在每个节点上独立完成的,以确保更新确实稀疏。实际上,这是通过ThresholdAlgorithm接口及其实现实现的。
如前所述的残差裁剪,更新的“残差”部分(即那些未通信的部分)存储在残差向量中。如果更新远远大于阈值,则可能出现我们称之为“残差爆炸”的现象——即,残差值可以继续增长到阈值的许多倍(因此将采取许多步骤来传递梯度)。为了避免这种情况,DL4J具有一个ResidualPostProcessor接口,默认实现是ResidualClippingPostProcessor,每5步将残差向量裁剪到当前阈值的最大5倍。
通过ParallelWrapper实现的本地并行:这使得多CPU/GPU节点能够更快地共享信息
从描述中可以看出,ASGD的实现需要在每次迭代训练时进行更新传播。集群内的工作机之间的进一步通信是网格模式的要求。
为了使spark通信快速脱离,DL4J使用了Aeron。Aeron是一种高性能的消息传递系统,可以在UDP、无限带宽或共享内存上运行。Aeron被设计成最高吞吐量并且最低和最可预测的延迟的任何可能的消息系统。在Aeron之上构建我们自己的通信栈允许我们使用Spark集成参数服务器的自定义实现,同时控制和最小化线路的分配权。
普通模式VS网格模式
DL4J的梯度共享实现可以通过两种方式来配置,这取决于集群大小。 下面是一个描述普通模式如何组织的图片:
在普通模式下,量化编码更新由每个节点转达到主节点,然后主节点转达到其余节点。这确保了主节点始终具有模型的最新版本,这是容错所必需的。然而,主节点在这个实现中是一个潜在的瓶颈。为了扩展到更大的集群(超过大约32个节点——尽管这是网络和硬件指定的),使用网格模式,如下所述。
下面是描述网格模式如何组织的图像:
网络模式是一个非二进制树,spark主节点是它的根。默认情况下,每个节点最多可以有8个节点,树最多可以有5层深。在网格模式下,每个节点将编码的更新转达到连接到它的所有节点,并且每个节点聚合从连接到它的所有其他节点接收的更新。在网格模式下,由于直接接收的通信量减少,主节点不再是瓶颈。在编写本文档时,已经用单播和多播(1.0.0-beta3中提供)测试了实现。未来的支持计划为RDMA。
编码方案
使用以下两种方案中的一种来发送更新。
阈值编码:发送一个整数数组,每个整数组都引用参数的索引。对于正阈值发送正整数,对于负阈值发送负整数。
位图编码:每个参数更新用两个比特编码。这四种状态用于指示没有变化,a +ve阈值变化,a -ve阈值变化以及+ve和-ve之间循环的半阈值变化。
使用这两种编码方案可以适应更新密集的情况。因为每个节点都有自己的阈值,它的值也与每个传输进行通信。为了实现性能和GPU并行化,将编码更新推到优化的本机代码(C++)。稀疏阈值(整数索引)编码可以导致非常高的压缩率,而位图编码导致固定大小的16倍压缩比(即,对于原始更新向量,每个参数2比特,相对于32比特)。
参数平均实现是DL4J中第一个分布式训练实现,它已经被上一节描述的梯度共享实现所取代。为了完整起见,这里包括了关于参数平均实现的细节。 参数平均实现是一种完全在Spark中实现的同步SGD方法。DL4J的参数平均实现使用单个参数服务器,一个由Spark主节点服务的角色。 参数平均是概念上最简单的数据并行方法。它要求用户指定工作机彼此之间和与主节点同步的频率。通过参数平均,训练进行如下:
主机(Spark驱动程序)从初始网络配置和参数开始
基于TrainingMaster的配置,数据被分成多个子集。
对数据拆分进行迭代。对于训练数据的每个分割:a.将配置、参数(以及如果适用的话,用于momentum/rmsprop/adagrad的网络更新器状态)从主节点分配给每个工作机 b.使每个工作机拟合其分割的部分 c.平均参数(以及如果适用的话,更新器状态)并且返回平均结果到主节点。
训练完成后,主节点有一个已训练网络的拷贝。
在下面的图像中演示了步骤3a到3c。在这个图中,W表示神经网络的参数(权重、偏置)。订阅用于对参数的版本进行索引,并在必要时对每个工作机器进行索引。
该实现在后台使用Spark的树状聚集。可以对这个实现进行许多增强,这将导致更快的训练时间。即使有了这些增强,具有量化压缩更新的异步SGD方法预计将持续快得多。因此,强烈建议用户从参数平均实现切换到异步SGD梯度共享方法。
DL4J中分布式训练的Spark实现在1.0.0-beta3中具有容错性。参数平均实现始终是容错的;梯度共享实现是在(不包括)1.0.0-beta2之后完全容错的。
在深入了解实现的细节之前,让我们首先考虑当节点出现故障时会发生什么。由于Spark对通过Aeron发送的更新没有感知,RDD谱系回溯到初始参数和优化器状态。当Spark恢复一个节点来代替故障节点时,它将因此从初始状态恢复训练。换句话说,这个恢复的节点将与其他节点不同步,这将导致训练偏离。
DL4J的梯度共享利用Spark之外的其自身的内部心跳机制来检测节点何时出现故障,以及检测恢复的节点何时联机。为了确保训练持续而不偏离,需要恢复节点,使用与当前点处的其他节点相同的模型副本来恢复训练。为了确保更新不会被多次应用,每个更新都用唯一的ID进行标记。更新器/优化器(RMSProp、AdaGrad等)的状态以及迭代/周期号也要求 网络训练从节点故障之前的状态开始。
以下概述当节点在普通模式下出现故障并恢复时发生的情况:
还原的节点重新连接到主节点。
恢复的节点开始接收更新,然后发送对参数、更新器状态和当前epoch/迭代的请求。
主节点实现这些请求(由其本身或代理)
恢复的节点仅应用相关的更新(相对于参数向量)
继续训练新节点上的RDD数据,与其他节点适当同步并适当收敛
在节点开始接收更新之后,请求模型的副本可以确保不错过更新。更新由唯一的ID标记,并且不会两次错误应用更新。由于主节点不执行任何训练,因此它不保持更新器状态,当它接收到对更新器/优化器状态的请求时,它向其他节点之一发送请求——在接收到请求时,它向恢复的节点发送更新器。
当节点失败时,网格节点中唯一的附加步骤是重新映射失败节点的子节点。在这种情况下,失败节点的子节点被映射到主节点并且所有剩余的子节点都被映射到主节点的一个映射。
具体来说,如果节点2失败,则使用下面的树结构,将节点5映射到主节点,将节点6和7映射到节点5。
之所以决定重新映射到主节点而不是相邻节点,是因为假定主节点是最可靠的选项。由于同样的原因,向主节点请求模型副本等也是如此。需要注意的是,类似于Spark作业,使用DL4J的分布式神经网络训练不能承受主节点故障。因此,建议用户频繁地保持模型的状态。在这种情况下,如果主控程序失败,则可以从最新的保存状态重新启动训练。
容错限制:梯度共享实现的容错有两个主要限制。第一:少量数据(几个小批量)可以多次处理。这是因为失败的节点可能在失败之前处理分区的一部分(发送更新)。在实践中这不是一个问题:重复的小批量通常很少,而且我们通常要进行多个epoch的训练(因此在训练期间已经多次看到每个示例)。第二:主/驱动节点是单点故障。这实质上是Spark限制:DL4J可以(原则上)实现从失败的主节点恢复并继续训练的功能,但是Apache Spark不支持主节点的容错。
DL4J支持使用Spark和参数服务器进行快速分布式训练。
DL4J支持Apache Spark环境和Aeron中的分布式训练,用于Spark之外的高性能节点间通信。这个思想相对简单:单个工作节点在他们的数据集上计算梯度。
在将梯度应用于网络权重之前,它们被累积在中间存储机制(每台机器一个)中。聚合之后,超过某些可配置阈值的更新值作为稀疏二进制数组在网络上传播。低于阈值的值被存储并添加到将来的更新中,因此它们不会丢失,而只是在通信中延迟。
与发送整个密集更新或参数向量的天真方法相比,这种阈值化方法将网络通信需求减少了许多数量级,同时保持了高精度。
关于阈值化方法的更多细节,参见Strom, 2015 - 使用商业GPU云计算进行弹性分布式DNN训练。
下面是Nikko Strom提出的原始算法中添加的一些额外优点:
可变阈值:如果每次迭代的更新次数太少,则阈值将自动被一个可配置的步骤值减少。
密集位图编码:如果更新次数太高,则使用另一种编码方案,它为任何给定的更新消息提供在线发送的“最大字节数”保证。
周期性地,我们发送“抖动”消息,用明显更小的阈值编码,以共享不能超过当前阈值的延迟权重。
注意,使用Spark需要开销。为了确定Spark是否会帮助你,考虑使用Performance Listener并查看毫秒迭代时间。如果是<150毫秒,Spark可能不值得。
为了运行训练你只需要一个Spark 1.x/2.x集群和至少一个开放的UDP端口(入站/出站)。
如上所述,DL4J同时支持Spark 1.x和Spark 2.x集群。但是,这个特定的实现也需要Java 8 +来运行。如果你的群集运行Java 7,则你必须升级或使用我们的参数平均训练模式。
梯度共享严重依赖于UDP协议用于训练期间主节点和从节点之间的通信。如果你在诸如AWS或Azure之类的云环境中运行集群,则需要允许一个UDP端口用于入站/出站连接,并且必须在传递给SharedTrainingMaster构造函数的VoidConfiguration.unicastPort(int)bean中指定该端口。
另一个需要记住的选项:如果使用YARN(或任何其他处理Spark网络的资源管理器),则必须指定用于UDP通信的网络的网络掩码。这可以通过以下方式完成:VoidConfiguration.setNetworkMask(“10.1.1.0/24”)。
选择IP地址的最后一种选择是DL4J_VOID_IP 环境变量。在你正在运行的每个节点上设置该变量,并使用本地IP地址进行通信。
网络掩码是CIDR符号,只是告诉软件应该使用哪些网络接口进行通信的一种方式。例如,如果你的集群有3个具有以下IP地址的盒子:192.168.1.23、192.168.1.78、192.168.2.133,则它们的网络地址的公共部分是192.168.*,因此子网掩码是192.168.0/16。你还可以在维基百科:https://en.wikipedia.org/wiki/Subnetwork中找到子掩网码的详细解释。
当Spark集群运行在hadoop之上,或者任何其他不假定Spark IP地址已公布的环境时,我们将使用网络掩码。在这种情况下,应在VoidConfiguration bean中提供有效的网络掩码,并将使用它来选择Spark之外的通信接口。
下面是唯一需要的依赖项的模板:
例如:
以下是来自Github上的示例仓库的一个示例项目的片段
请注意:此配置假定在集群内的所有节点上都打开了UDP端口40123。
网络IO有自己的代价,该算法也做了一些IO。训练时间的额外开销可以计算为
更新编码时间+消息序列化时间+来自其他工作节点的更新应用程序。
原始迭代时间越长,共享带来的相对影响就越小,并且你将获得更好的假设可伸缩性。 这是一个简单的表单,可以在扩展性预期上帮助你:
迭代时间
编码时间
解码时间
更新时间
服务开销
550
50
5
50
20
节点数量
每个节点的工作节点
8
4
可伸缩率: 70.51%
通过设计,Spark允许你为任务配置执行器的数量和每个执行器的内核数量。假设你有一个18个节点的集群 ,在每个节点中有32个核心。
在这种情况下,你的--num-executors值是18,而建议的--executor-core值在2到32之间。这个选项将基本定义RDD将分成多少个分区。
另外,你可以手动设置每个节点上使用的DL4J工作节点的具体数量。这可以通过SharedTrainingMaster.Builder() workersPerNode(int)方法来完成。
如果你的节点使用GPU,那么通常最好将workersPerNode(int)设置为每盒GPU的数量,或者保持其默认值以进行自动调优。
较高的阈值将给你提供更多的稀疏更新,这将提高网络IO性能,但它可能会(也可能会)影响神经网络的学习性能。
较低的阈值将给予你更密集的更新,因此每个单独的更新消息将变得更大。这将降低网络IO性能。单独的“最佳阈值”是不可能预测的,因为它可能因不同的结构而变化,但是1e-3的默认值是一个好的开始值。
经验法则很简单:网络越快,性能就越好。1GBe网络应该被认为是绝对最小值,但是10GBe由于延迟较小而性能更好。
当然,性能取决于神经网络大小和计算量。较大的神经网络需要更大的带宽,但是每次迭代也需要更多的时间(因此可能为异步通信留下更多的时间)。
为了确保最大的兼容性(例如,对于不支持多播的云计算环境,如AWS和Azure),DL4J当前只使用UDP单播。
UDP广播传输应该更快,但是对于训练性能,差异不应该显著(除了可能非常小的工作负载)。
根据设计,每个工作节点每次迭代发送1条更新消息,并且无论UDP传输类型如何,都不会改变。由于UDP单播传输中的消息重传由主节点处理(通常利用率较低),并且由于消息传递是异步的。为了获得性能,我们只需要更新通信时间小于网络迭代时间-通常是这种情况。
在设备之间PCIe/NVLink P2P连通性盒子,可以预期最佳结果。然而,即使没有P2P,一切仍然可以正常工作。只是“稍慢”。:)
DL4J向量化和ETL库概述。
数据向量解决了有效机器或深度学习的最重要障碍之一:将数据转换成神经网络可以理解的格式。神经网络理解向量。向量化是数据科学家开始在数据上训练他们的算法之前必须解决的首要问题。数据向量应该适用于你99%的数据转换,如果你不确定它是否适用于你,请在gitter 上咨询。数据向量支持大多数数据格式,但是您也可以实现自己的自定义记录读取器。如果你的数据是以CSV(逗号分割值)格式存在文本文件中,必须转换为数值并攫取,或者您的数据是标记图像的目录结构,那么数据向量就是帮助您组织这些数据以便在Deeping4J中使用的工具。在使用数据向量之前请阅读这一整页,特别是下面的记录读取章节。
这个视频描述了图片数据到向量的转换
数据向量使用一个输入/输出格式系统(类似于MapReduce使用InputFormat来决定InputSplits(输入分割器)和RecordReaders(记录读取器)的某些方式,数据向量也提供了RecordReaders来系列化数据 )
设计为支持所有主要输入数据类型(文本, CSV, 音频, 图像和视频)
使用一个输出格式系统来指定一个实现- 中性型向量格式(ARFF, SVMLight,等.)
可被扩展且于特殊的输入格式(例如exotic图片格式 );你可以写你自己的定制的输入格式并让代码库的其余部分处理转换管道。
把向量化当作一等公民
内置转换工具用于转换和归一化数据
下面有一个简短的教程。
将基于CSV的UCI Iris数据集转换为svmLight开放矢量文本格式
从原始的二进制文件将MNIST数据集转换为svmLight开放矢量文本格式
转换原始文本为metronome向量格式
在一个文本向量格式{svmLight, metronome, arff}中将原始文本转换为基于TF-IDF(词频-逆文件频率) 的向量
在一个文本向量格式{svmLight, metronome, arff}中将原始文本转换为word2vec向量
用脚本转换语言将任意的CSV转换为向量
MNIST 转换为向量
文本转换为向量
TF-IDF(词频-逆文件频率)
词袋
word2vec
如果数据是数字的和适当的格式,那么CSVRecordReader可能是令人满意的。然而如果你的数据有非数字属性比如有代表布尔值(T/F)的字符或是用于标签的字符那么概要转换是有必要的。数据向量使用apache spark 来完成转换操作。请注意你不需要知道spark内部机置就可以成功地用数据向量完成转换。
一个简单的数据向量转换视频教程和一个如下可用的代码。
我们的例子包括一系列数据向量的例子。
如下代码展示了一个例子如何工作,原始图片,把它们转换为可以和dl4j和nd4j一起工作的格式。
RecordReader是数据向量中的一个类,帮助把面向字节的输入转换为面向记录的数据;元素的一个集合是以一个数字固定的并以一个惟一ID索引。把数据转换为记录是向量化的过程。记录本身是个向量,每个元素都是一个特征。ImageRecordReader 是 RecordReader的子类并且它内置自动摄取28×28像素图像。
因此,LFW图像被缩放到28像素×28像素。你可以通过更改传给ImageRecordReader的参数来更改维度以匹配自定义图像,只要你确保适应nIn超参数即可,nIn超参数等于图像高度x图像宽度的乘积。上面显示的其他参数包括true,它指示读取器将标签附加到记录中,标签是用于验证神经网络模型结果的一组监督值(例如,目标) 这里是所有来自数据向量预构建的记录读取器的扩展(在IntelliJ中,你可以通过在RecordReader右击,在下拉菜单中再点击 Go go 并选择 Implementations 来找到它们)
DataSetIterator 是Deeplearning4J 中 用于访问列表元素的类。
迭代器遍历数据列表,顺序访问每个元素项,通过指向其当前元素跟踪其进度,并修改自身以指向遍历中的每个新步骤的下一个元素。
DataSetIterator迭代输入数据集,每次迭代获取一个或多个新示例,并将这些示例加载到神经网络可以使用的数据集对象中。
需要注意的是 ImageRecordReader产生4维图像数据,匹配dl4j所需要的激活层。因此,每一个28x28 RGB图像用一个4维数组表示,例如维度[小批量,通首,高,宽]=[1,3,28,28]。注意到上面的构造器行也指明了标签的数量。注意到 ImageRecordReader 不会规一化图片数据,因此每个像素/通道值将会在0到255之间(一般应分别归一化-例如使用ND4J的 ImagePreProcessingScaler 和其它的规一化器)。RecordReaderDataSetIterator
可以作为你想指定的记录读取器的参数(图片,声音)和批量大小。 对于有监督学习,它还将采取标签索引和可应用于输入的可能标签的数量(对于LFW,标签的数量是5749)。
作为本地串行进程和一个MapReduce进程来运行,没有代码更改的扩展过程。
svmLight
libsvm
Metronome
ARFF
理解如何通用文本和文本转换为向量,并把它与例如核散列和TF-IDF库存技术结合使用。
打开IntelliJ并选择Import Project。然后选择dl4j-examples主要的目录(注意:以下的示例中阐述的是一个过期的仓库名为dl4j-0.4-examples。尽管如此,你将要下载和安装的仓库为 dl4j-examples)。
选择 ‘Import project from external model’ 并确保Maven被选择
在左边文件树中选择一个例子 ,右击文件来运行
这个图表简单地显示了选定层随着时间的推移的参数的学习速率。
这是使用gnuplot绘制的tsne-standard-coords.csv文件的图像。
DL4J中处理一般NLP任务的机制。
词汇缓存是DL4J中处理通用自然语言任务的机制,包括普通TF-IDF、单词向量和某些信息检索技术。词汇缓存的目标是成为文本向量化的一站式商店,其中封装了单词袋和单词向量等常用的技术。
词汇缓存通过倒排索引处理词、词统计频率、倒排文档频率和文档出现的存储。InMemoryLookupCache是参考实现。
为了在迭代文本和索引词时使用词汇缓存,你需要确定词是否应该包括在词汇缓存中。该标准通常是如果词出现在语料库中超过一定预先配置的频率。在该频率以下,单个词不是一个词汇缓存的单词,它只是一个词。
我们也跟踪词。为了跟踪词,请执行下列操作:
当你想添加一个词汇缓存的词,按如下做:
向索引添加单词来 设置索引。然后你把它声明为一个词汇缓存单词。(声明它是一个词汇缓存单词,将从索引中拉出单词。)
DL4J中用于语言处理的单词、文档和句子的迭代。
Sentence Iterator (句子迭代器)用于 Word2vec 和 词袋 。
它将一些文本以向量的形式输入到神经网络中,也涵盖了文本处理中的文档概念。
在自然语言处理中,文档或句子通常用来封装算法应该学习的上下文。
一些例子包括分析推文和成熟的新闻文章。句子迭代器的目的是把文本分成可处理的位。注意句子迭代器是输入不可知的。因此,一些文本(文档)可以来自文件系统、Twitter API或Hadoop。
根据如何处理输入,句子迭代器的输出将被传递给分词器,用于处理单个词,这些通常是单词,但也可以是ngram、skipgrams或其他单元。分词器是由一个Tokenizer Factory(分词器工厂)根据每句话创建的。分词器工厂是被传递到文本处理向量化器中的。
一些典型的例子如下:
假设文件中的每一行都是一个句子。
还可以将字符串列表作为如下语句:
这将假定每个字符串是一个句子(文档)。记住,这可能是一个推文或文章列表,两者都适用。
可以对文件进行如下迭代:
这将逐行解析文件,并在每行返回单个句子。
对于任何复杂的情况,我们推荐一个实际的机器学习级管道,UimaSentenceIterator 。
UimaSentenceIterator够进行分词、词性标注和语义化等。UimaSentenceIterator迭代一组文件并可以分割句子。你可以根据传入的AnalysisEngine来定制它的行为。
AnalysisEngine是UIMA文本处理管道概念。DL4J附带了所有这些常见任务的标准分析引擎,允许你自定义传入的文本以及如何定义语句。AnalysisEngines是OpenNLP管道的线程安全版本。我们还包括基于cleartk的用于处理常见任务的管道。
对于那些使用UIMA或者对UIMA感到好奇的人来说,在类型系统内,它使用cleartk类型系统用于分词、句子和其他注释。
下面是如何创建UimaSentenceItrator:
你也可以直接实例化:
对于熟悉Uima的人来说,这是广泛使用Uimafit创建分析引擎。还可以通过扩展SentenceIterator来创建自定义语句迭代器。
在DL4J中用于语言处理的Doc2Vec和任意文档。
为自定义层扩展DL4J功能。
有两个组件可添加自定义层:
添加层配置类: 扩展 org.deeplearning4j.nn.conf.layers.Layer
添加层实现类: 实现 org.deeplearning4j.nn.api.Layer
配置层(以上(1))类处理设置。这是你在构建多层网络或计算图时所使用的方法。你可以在这里添加自定义设置,并在你的图层中使用这些设置。
实现层(以上(2))类具有参数,并处理网络前向传播、反向传播等。它是从org.deeplearning4j.nn.conf.layers.Layer.instantiate(…)方法创建的。换句话说:instanceiate方法是我们从配置到实现的方式;MultiLayerNetwork或ComputationGraph在初始化的时候调用。
其中的一个例子是CustomLayer(配置类)和CustomLayerImpl(实现类)。这两类都对它们的方法有广泛的注释。
你将注意到,在DL4J中有两个DenseLayer 类 、两个GravesLSTM类等:原因在于一个用于配置,一个用于实现。我们没有遵循这个“同名”模式,希望避免混淆。
一旦添加了自定义层,就需要运行一些测试来确保它是正确的。
这些测试至少应包括以下内容:
测试以确保JSON配置(到/从JSON)正常工作,这对于你的自定义层与模型序列化(保存)和Spark训练都起作用的网络来说是必要的。
梯度检查,以确保执行是正确的。
我们提供了一个完整的自定义层示例。在我们的 示例仓库 中。
如何用DL4J计算图构造复杂网络。
本页描述了如何使用 DL4J的计算图功能来构建更复杂的网络。
内容
DL4J有两种类型的包括多个层的网络:
MultiLayerNetwork(多层网络),是神经网络层栈必不可少的(具有单个输入层和单个输出层)。
ComputationGraph (计算图), 它允许在网络架构中获得更大的自由度。
具体而言,计算图允许构建具有以下特征的网络:
多网络输入数组
多个网络输出(包括混合分类/回归架构)
使用有向无环图连接结构连接到其他层的层(而不是一个层栈)
一般说来,当构建具有单个输入层、单个输出层和 输入->a->b->c->输出 类型连接结构的网络时:多层网络通常是首选网络。然而,MultiLayerNetwork所能做的一切,ComputationGraph也可以做到——尽管配置可能稍微复杂一些。
可以使用计算图构建的一些架构的示例包括:
多任务学习架构
具有跳跃连接的循环神经网络
GoogLeNet,一种用于图像分类的复杂卷积网络
其基本思想是,在计算图中,核心构建块是图形顶点,而不是层。层(或者,更准确地说,是层顶点对象),只是图中的一种顶点。其他类型的顶点包括:
输入顶点
元素运算顶点
合并顶点
子集顶点
预处理器顶点
下面简要描述这些类型的图形顶点。
LayerVertex: 层顶点(具有神经网络层的图形顶点)用.addLayer(String,Layer,String...)方法
被添加。第一个参数是层的标签,最后的参数是该层的输入。如果需要手动添加InputPreProcessor(通常不需要,请参阅下一节),可以使用.addLayer(String、Layer、InputPreProcessor、String...)方法。
InputVertex: 输入顶点由你配置中的addInputs(String...)
方法指定。用作输入的字符串可以是任意的——它们是用户定义的标签,并且可以在以后的配置中引用。提供的字符串数量定义了输入的数量;输入的顺序还在fit方法(或DataSet/MultiDataSet对象)中定义了相应的INDArray的顺序。
ElementWiseVertex: 元素操作顶点执行例如从一个或多个其他顶点对激活进行元素式加法或减法。因此,用作ElementWiseVertex的输入的激活必须都具有相同的大小,并且元素顶点的输出大小与输入相同。
MergeVertex: 合并顶点 联接/合并输入激活。例如,如果一个合并顶点分别具有2个大小为5和10的输入,则输出大小将是5 + 10=15激活。对于卷积网络激活,示例沿着深度合并:因此假设来自一个层的激活具有4个特征,而另一个具有5个特征(都具有(4或5)x宽度x高度激活),那么输出将具有(4+5)x宽度x高度激活。
SubsetVertex: 子集顶点允许你只从另一个顶点获得激活的一部分。例如,为了从标签为“layer1”的另一个顶点获得前5个激活,可以使用.addVertex("subset1", new SubsetVertex(0,4), "layer1")
:这意味着“layer1”顶点中的第0至第4(包含)个激活将被用作子集顶点的输出。
PreProcessorVertex: 有时,你可能希望InputPreProcessor的功能不与层相关联。PreProcessorVertex顶点允许你这样做。
最后,也可以为你自定义的图顶点通过实现一个configuration 和 implementation 类实现自定义图顶点。
假设我们希望建立以下循环神经网络体系架构:
为了这个例子,假设我们的输入数据的大小是5。我们的配置如下:
考虑下面的架构:
这里,合并顶点从层L1和L2取出激活,并合并(连接)它们:因此,如果层L1和L2都具有4个输出激活(.nOut(4)),则合并顶点的输出大小是4+4=8个激活。
为了构建上述网络,我们使用以下配置:
在多任务学习中,使用神经网络进行多个独立的预测。例如,考虑同时用于分类和回归的简单网络。在这种情况下,我们有两个输出层,“out1”用于分类,和“out2”回归。
在这种情况下,网络配置是:
ComputationGraphConfiguration的一个特性是,你可以使用配置中的.setInputTypes(InputType...)
方法来指定网络的输入类型。
setInputType 有两个作用:
它将根据需要自动添加任何输入预处理器。输入预处理器对于处理例如全连接(密连)层和卷积层,或者循环和全连接层之间的交互是必需的。
它将自动计算一个层的输入数(.nin(x)配置)。因此,如果你使用的是setInputTypes(InputType...)功能,则无需手动指定配置中的.nIn(x)选项。这可以简化构建一些架构(例如具有完全连接层的卷积网络)。如果为层指定了.nIn(x),则当使用InputType功能时,网络将不覆盖此。
例如,如果你的网络有2个输入,一个是卷积输入,另一个是前馈输入,那么你将使用.setInputTypes(InputType.convolutional(depth,width,height), InputType.feedForward(feedForwardInputSize))。
有两种类型的数据可以与计算图一起使用。
DataSet类最初是为与MultiLayerNetwork一起使用而设计的,但是也可以与ComputationGraph一起使用,但前提是该计算图具有单个输入和输出数组。对于具有多个输入数组或多个输出数组的计算图架构,不能使用DataSet和DataSetIterator(相反,使用MultiDataSet/MultiDataSetIterator)。
DataSet对象基本上是一对容纳你的训练数据的INDArray 。在RNNs的情况下,它也可以包括掩蔽阵列(参见这个详细信息)。DataSetIterator本质上是DataSet对象上的迭代器。
MultiDataSet是DataSet的多输入和/或多输出版本。在神经网络的情况下,它还可以包括多个掩模阵列(对于每个输入/输出阵列)。作为一般规则,除非使用多个输入和/或多个输出,否则应使用DataSet/DataSetIterator。
当前有两种方式使用MultiDataSetIterator:
通过直接实现 MultiDataSetIterator 接口
与DataVec记录读取器结合使用RecordReaderMultiDataSetIterator
RecordReaderMultiDataSetIterator提供了多个加载数据的选项。特别地,RecordReaderMultiDataSetIterator提供以下功能:
可以同时使用多个DataVec记录读取器
记录读取器不必是相同的模式:例如,可以把CSV记录读取器与图像记录读取器一起使用。
可以出于不同的目的使用RecordReader中的列的子集——例如,CSV中的前10列可以是你的输入,而后5列可以是你的输出。
将单个列从类索引转换为one-hot表示是可能的。
如下是如何使用 RecordReaderMultiDataSetIterator 的示例。你可能也能找到这些有用的单元测试。
假设我们有一个包含5列的CSV文件,我们希望使用前3列作为输入,最后2列作为输出(用于回归)。我们可以构建一个MultiDataSetIterator来执行以下操作:
假设我们有两个单独的CSV文件,一个用于我们的输入,一个用于我们的输出。进一步假设我们正在构建一个多任务学习架构,其中有两个输出-一个用于分类。对于这个例子,假设数据如下:
输入文件:myInput.csv,我们希望使用所有列作为输入(没有修改)
输出文件: myOutput.csv.
网络输入1 - 回归: 列 0 到 3
网络输出 2 - 分类: 列 4是分类索引,有3个类。因此,列4只包含整数值[0,1,2],我们希望将这些索引转换为one-hot表示以进行分类。
在这种情况下,我们可以构建如下的迭代器:
循环神经网络在DL4J中的实现。
本文概述了在DL4J中如何使用循环神经网络的具体训练特征和实用性。本文假定对循环神经网络及其使用有一定了解,而不是对循环神经网络的介绍,并且假定你对它们的使用和术语有一些熟悉。
内容
DL4J 目前支持以下类型的循环神经网络
GravesLSTM (长短期记忆)
BidirectionalGravesLSTM(双向格拉夫长短期记忆)
BaseRecurrent
每种网络的Java文档都是可用的, GravesLSTM,BidirectionalGravesLSTM, BaseRecurrent
用于RNN的数据
暂时考虑一个标准的前馈网络(DL4J中的多层感知机或“密连层”)。这些网络期望输入和输出数据是二维的:即,具有“形状”的数据[numExamples,inputSize]。这意味着进入前馈网络的数据具有“numExamples”行/示例,其中每行由“inputSize”列组成。单个示例将具有形状[1,inputSize],但是在实践中,为了计算和优化效率,我们通常使用多个示例。类似地,标准前馈网络的输出数据也是二维的,具有形状[numExamples,outputSize]。
相反,RNN的数据是时间序列。因此,他们有3个维度:一个额外的时间维度。输入数据因此具有形状[numExamples,inputSize,timeSeriesLength],输出数据具有形状[numExamples,outputSize,timeSeriesLength]。这意味着,我们的INDArray中的数据被布置成如此 使得位置(i,j,k)的值是 小批量中第i个示例的第k个时间步骤的第j个值。该数据布局如下所示。
当使用类CSVSequenceRecordReader导入时间序列数据时,数据文件中的每一行表示一个时间步骤,用第一行(或头行后第一行(如果存在)中的最早观察到的时间序列 及csv的最后一行中的最新观察来表示。每个特征时间序列是CSV文件的单独列。例如,如果你在时间序列中有五个特征,每个特征具有120个观察值,以及大小为53的训练和测试集,那么将有106个输入csv文件(53个输入,53个标签)。53个输入CSV文件将分别有五列和120行。标签CSV文件将有一列(标签)和一行。
RnnOutputLayer (循环神经网络输出层)
循环神经网络输出层是用作具有许多循环神经网络系统(用于回归和分类任务)的最后层的一种层。循环神经网络输出层处理诸如评分计算、给定损失函数时的错误计算(预测与实际)等问题。在功能上,它与“标准”OutputLayer类(与前馈网络一起使用)非常相似;但是它同时输出(并且期望作为标签/目标)三维时间序列数据集。
循环神经网络输出层的配置遵循与其他层相同的设计:例如,将多层网络中的第三层设置为循环神经网络输出层以进行分类:
在实践中使用循环神经网络输出层可以在示例中看到,链接到本文末尾。
训练神经网络(包括RNNs)会在计算上非常苛刻。对于循环神经网络,当我们处理长序列时尤其如此,即具有许多时间步长的训练数据。
为了降低循环神经网络中每个参数更新的计算复杂度,提出了截断反向传播时间算法(BPTT)。总而言之,对于给定的计算能力,它允许我们更快地训练网络(通过执行更频繁的参数更新)。建议在输入序列长的时候使用截断的BPTT(通常超过几百个时间步长)。
考虑当训练具有长度为12个时间步长的时间序列的循环神经网络时会发生什么。这里,我们需要进行12步的正向传递,计算误差(基于预测的与实际的),并且进行12步的后向传递:
对于12个时间步长,在上面的图像中,这不是问题。然而,考虑到输入时间序列是10000个或更多的时间步长。在这种情况下,对于每个参数更新的每个正向和向后传递,通过时间的标准反向传播将需要10000个时间步骤。这计算要求当然是非常大的。
在实践中,截断的BPTT将前向和后向传播分成一组较小的前向/后向传播操作。这些向前/向后传播片断的长度是由用户设置的参数。例如,如果我们使用长度为4个时间步长的截断BPTT,学习看起来如下:
请注意,截断BPTT和标准BPTT的总体复杂度大致相同——在前向/反向传播中,它们时间步数量都相同。然而,使用这种方法,我们得到3个参数更新,而不是一个近似相同的工作量。然而,成本并不完全相同,每个参数更新都有少量的开销。
截断BPTT的缺点是在截断的BPTT中学习的依赖的长度可以短于完整的BPTT。这是很容易看到的:考虑上面的图像,TBPTT长度为4。假设在时间步骤10,网络需要存储来自时间步骤0的一些信息,以便做出准确的预测。在标准的BPTT中,这是可以的:梯度可以从时间10到时间0沿着展开的网络一路向后流动。在截断的BPTT中,这是有问题的:时间步10的梯度没有返回到足够远的地方,导致存储所需信息的所需参数更新。这种折衷通常是值得的,并且(只要适当地设置截断BPTT长度),截断BPTT在实践中工作得很好。
在DL4J中使用截断的BPTT非常简单:只需将下列代码添加到网络配置中(最后,在网络配置的最后.build()之前)
上面的代码片段将导致任意网络训练(即,对MultiLayerNetwork.fit() 方法的调用)使用长度为100步的片段的截断BPTT。
一些值得注意的事情:
默认情况下(如果不手动指定反向传播类型),DL4J将使用BackpropType.Standard(即,全BPTT)。
tBPTTLength配置参数设置截断的BPTT传递的长度。通常,这是在50到200个时间步长的某个地方,不过取决于应用程序和数据。
截断的BPTT长度通常是总时间序列长度(即,200对序列长度1000)的一部分,但是当使用TBPTT(例如,具有两个序列的小批—一个长度为100和另一个长度为1000——以及TBPTT长度为200 -将正确工作)时,在同一个小批次中时可变长度时间序列是可以的。
基于填充和掩码的思想DL4J支持RNN的一些相关的训练特征。填充和掩码允许我们支持训练情况,包括一对多、多对一,还支持可变长度时间序列(在同一小批量中)。
假设我们想训练一个具有不会在每一个时间步长发生输入或输出的循环神经网络。这个例子(对于一个例子)在下面的图像中显示。DL4J支持所有这些情况的网络训练:
没有掩码和填充,我们仅限于多对多的情况(上面,左边):即,(a)所有示例都具有相同的长度,(b)示例在所有时间步骤都有输入和输出。
填充背后的思想很简单。考虑在相同的小批量中两个长度分别为50和100时间步的时间序列。训练数据是矩形阵列;因此,我们填充(即,向其添加零)较短的时间序列(对于输入和输出),使得输入和输出都具有相同的长度(在本示例中:100个时间步骤)。
当然,如果这是我们全部所做的,它会在训练过程中产生问题。因此,除了填充,我们使用掩码机制。掩码背后的思想很简单:我们有两个额外的数组,记录输入或输出是否实际出现在给定时间步长和示例中,或者输入/输出是否只是填充。
回想一下,对于RNN,我们的小批量处理数据具有3维,分别具有输入和输出的形状[miniBatchSize、inputSize、timeSeriesLength]和[miniBatchSize、outputSize、timeSeriesLength]。然后填充数组是二维的,输入和输出都具有形状[miniBatchSize,timeSeriesLength],每个时间序列和示例的值是0 (‘absent’) or 1 (‘present’)。用于输入和输出的掩码数组被存储在单独的数组中。
对于单个示例,输入和输出掩码数组如下所示:
对于“不需要掩码”的情况,我们可以等效地使用所有1的掩码数组,这将给出与完全没有掩码数组相同的结果。还要注意,在学习RNN时可以使用零、一或两个掩码数组——例如,多对一的情况可以只针对输出使用掩码数组。
在实践中:这些填充数组通常在数据导入阶段创建(例如,由SequenceRecordReaderDatasetIterator(稍后讨论)创建),并且包含在DataSet对象中。如果DataSet包含掩码数组,多层网络拟合将在训练期间自动使用它们。如果它们不存在,则不使用掩码功能。
带有掩码的评估与评分
当进行评分和评估时(也就是说,当评估RNN分类器的精度)时,掩码数组也是重要的。例如,考虑多对一的情况:每个示例只有一个输出,并且任何评估都应该考虑这一点。
使用(输出)掩码数组的评估可以在评估时使用,通过把它传递到以下方法:
其中,标签是实际输出(3d时间序列),预测的是网络预测(3d时间序列,与标签的形状相同),并且outputMask是用于输出的2d掩码数组。注意,评估不需要输入掩码数组。
得分计算也将利用掩码数组,通过MultiLayerNetwork.score(DataSet) 方法。同样,如果DataSet包含输出掩码数组,那么在计算网络的得分(损失函数-均方误差、负对数似然等)时将自动使用它。
序列分类是掩码的一种常用方法。其思想是,尽管我们有一个序列(时间序列)作为输入,但我们只希望为整个序列提供一个单一的标签(而不是在序列中的每个时间步提供一个标签)。
然而,RNN通过设计输出序列,输入序列的长度相同。对于序列分类,掩码允许我们在最后的时间步用这个单一标签训练网络,我们本质上告诉网络除了最后的时间步之外实际上没有任何标签数据。
现在,假设我们已经训练了我们的网络,并且希望从时间序列输出数组获得最后的预测时间步。我们该怎么做呢?
为了得到最后一个时间步,有两个案例需要注意。首先,当只有一个示例时,实际上不需要使用掩码数组:我们只需要获得输出数组中的最后一个时间步:
假设分类(不过,回归的过程相同),上面的最后一行给出了最后一个时间步的概率,即序列分类的类概率。
稍微复杂一点的情况是,在一个小批(特征数组)中有多个示例,其中每个示例的长度不同。(如果所有长度相同:我们可以使用与上面相同的过程)。
在这个“可变长度”的情况下,我们需要分别为每个示例获取最后一个时间步。如果数据流管道中的每个示例都有时间序列长度,那么就变得简单了:我们只是迭代示例,用该示例的长度替换上面代码中的timeSeriesLength
。
如果我们没有直接的时间序列的长度,我们需要从掩码数组中提取它们。
如果我们有一个标签掩码数组(它是一个one-hot向量,像每个时间序列[0,0,01,1,0]):
或者,如果我们只有特征掩码:一个快速和粗爆的方法就是使用这个:
要理解这里正在发生的事情,请注意,最初我们有一个特征掩码,如[1,1,1,1,0],我们希望从中获得最后一个非零元素。我们映射[1,1,1,1,0] 到 [1,2,3,4,0],然后得到最大的元素(这是最后一个时间步长)。
在这两种情况下,我们都可以做到以下几点:
DL4J中的RNN层可以与其他层类型相结合。例如,可以在同一网络中组合DenseLayer(密连层)和LSTM(长短记录单元)层,或者组合用于视频的卷积(CNN)层和LSTM层。
当然,DenseLayer(密连层)和卷积层不处理时间序列数据——他们期望不同类型的输入。为了解决这个问题,我们需要使用层预处理器功能:例如,CnnToRnnPreProcessor和FeedForwardToRnnPreprocessor类。请参见这里所有预处理器。幸运的是,在大多数情况下,DL4J配置系统将根据需要自动添加这些预处理器。然而,可以手动添加预处理器(覆盖每个层自动添加的预处理器)。
例如,为了手动添在1层和2层之间添加预处理器,请将下列内容添加到网络配置中:.inputPreProcessor(2, new RnnToFeedForwardPreProcessor())
与其他类型的神经网络一样,可以使用MultiLayerNetwork.output()和MultiLayerNetwork.feedForward()
方法生成对RNNs的预测。这些方法在许多情况下是有用的;然而,这些方法的局限性在于,我们只能对时间序列从零开始每次生成预测。
例如,考虑我们希望在实时系统中生成预测的情况,其中这些预测基于大量的历史。在这种情况下,使用输出/前馈方法是不切实际的,因为它们在每次调用整个数据历史时进行完全的正向传递。如果我们希望在每个时间步长对单个时间步长进行预测,那么这些方法可能既(a)非常耗性能,又(b)浪费,因为它们一遍又一遍地进行相同的计算。
对于这些情况,多层网络提供了四种方法:
rnnTimeStep(INDArray)
rnnClearPreviousState()
rnnGetPreviousState(int layer)
rnnSetPreviousState(int layer, Map<String,INDArray> state)
rnnTimeStep()方法被设计成允许有效地执行前向传递(预测),一次执行一个或多个步骤。与输出/前馈方法不同,rnnTimeStep方法在被调用时跟踪RNN层的内部状态。重要的是要注意,rnnTimeStep和输出/feedForward方法的输出应该是相同的(对于每个时间步),无论我们是一次全部做出这些预测(输出/feedForward),还是每次生成一个或多个步骤(rnnTimeStep)。因此,唯一的区别应该是计算成本。
总之,MultiLayerNetwork.rnnTimeStep()方法做了两件事:
使用先前存储状态(如果有的话)生成输出/预测(前向传递)
更新存储的状态,存储最后一个时间步的激活(准备下次rnnTimeStep被调用时使用)
例如,假设我们想使用RNN来预测天气,提前一小时(基于前面100小时的天气作为输入)。如果我们要使用输出方法,在每一小时,我们需要输入整整100个小时的数据,以预测101个小时的天气。然后,为了预测102小时的天气,我们需要输入100小时(或101小时)的数据,103小时等等。
或者,我们可以使用rnnTimeStep方法。当然,如果我们要在进行第一次预测之前利用全部100个小时的历史,我们仍然需要进行全面的向前传递:
我们第一次调用rnnTimeStep时,两种方法之间唯一的实际区别是存储了上一个时间步的激活/状态——这用橙色表示。但是,下次我们使用rnnTimeStep方法时,这个存储状态将被用于做出下一个预测:
这里有许多重要的区别:
在第二个图片中(rnnTimeStep的第二次调用),输入数据由单个时间步组成,而不是由数据的完整历史组成
前向传播是一个单一的时间步(与数百个或更多)相比。
rnnTimeStep方法返回后,内部状态将自动更新。因此,可以以与时间102相同的方式进行时间103的预测。等等。
但是,如果希望开始对新的(完全独立的)时间序列进行预测,则必须(而且很重要)使用MultiLayerNetwork.rnnClearPreviousState()
方法手动清除存储的状态。这将重置网络中所有循环层的内部状态。
如果需要存储或设置用于预测的RNN的内部状态,则可以针对每一层分别使用rnnGetPreviousState和rnnSetPreviousState方法。例如,在序列化(网络保存/加载)期间,这可能是有用的,因为rnnTimeStep方法中的内部网络状态在缺省情况下没有被保存,并且必须单独保存和加载。注意,这些GET/SET状态方法返回并接受一个由激活类型作为键的映射。例如,在LSTM模型中,有必要存储输出激活和存储单元状态。
其他一些注意事项:
我们可以同时为多个独立的示例/预测使用rnnTimeStep方法。在上面的天气示例中,例如,我们希望使用相同的神经网络对多个地点进行预测。这与训练和前向传递/输出方法相同:多行(输入数据中的维度0)用于多个示例。
如果没有设置历史/存储状态(即,最初或调用rnnClearPreviousState之后),则使用默认初始化(零)。这是与训练过程相同的方法。
rnnTimeStep可以同时用于任意数量的时间步-不只是一个时间步。然而,重要的是要注意:
对于单个时间步预测:数据是二维的,形状是 [numExamples,nIn];在这种情况下,输出也是二维的,形状是[numExamples,nOut]。
对于多个时间步预测:数据是三维的,具有形状[numExamples,nIn,numTimeSteps];输出将具有形状[numExamples,nOut,numTimeSteps]。同样,最后的时间步激活和以前一样被存储。
在rnnTimeStep的调用之间不可能改变示例的数量(换句话说,如果rnnTimeStep的第一次使用是针对例如3个示例,则所有后续的调用必须具有3个示例)。在重置内部状态之后(使用rnnClearPreviousState()),任何数量的示例都可以用于rnnTimeStep的下一次调用。
rnnTimeStep方法不改变参数,只在训练完成后才使用网络。
rnnTimeStep方法与包含单个和堆叠/多个RNN层的网络以及结合其他层类型(例如卷积层或密连层)的网络一起工作。
RnnOutputLayer 层类型不具有任何内部状态,因为它没有任何循环连接。
RNN的数据导入是复杂的,因为我们有多种不同类型的数据可用于RNN:一对多、多对一、可变长度时间序列等。本节将描述当前实现的DL4J的数据导入机制。
这里描述的方法利用SequenceRecordReaderDataSetIterator类,以及来自DataVec的CSVSequenceRecordReader类。此方法目前允许你从文件中加载被(制表符、逗号等)分隔的数据,其中每个时间序列位于单独的文件中。该方法还支持:
可变长度时间序列输入
一对多和多对一数据加载(输入和标签在不同文件中)
用于分类的从索引到one-hot表示(即“2”到[0,0,1,0])的标签转换
跳过数据文件开始时的固定/指定行数(即注释或头行)
注意在所有情况下,数据文件中的每一行代表一个时间步。
(除了下面的例子,你可能会发现这些单元测试是有用处的。)
假设在我们的训练数据中有10个时间序列,由20个文件表示:每个时间序列有10个文件用于输入,而输出/标签有10个文件。现在,假设这20个文件都包含相同数量的时间步(即,相同的行数)。
为了使用SequenceRecordReaderDataSetIterator和CSVSequenceRecordReader方法,我们首先创建两个CSVSequenceRecordReader对象,一个用于输入,一个用于标签:
这个特定的构造函数需要跳过的行数(这里跳过的1行)和分隔符(这里使用逗号字符)。
第二,我们需要初始化这两个读取器,告诉他们从哪里获取数据。我们使用一个InputSplit对象来实现这一点。假设我们的时间序列被编号,文件名为“myInput_0.csv”、“myInput_1.csv”、“myLabels_0.csv”等等。一种方法是使用NumberedFileInputSplit:
在这个特定的方法中,“%d”被替换为相应的数字,并且使用数字0到9(包括 )。
最后,我们可以创建我们的SequenceRecordReaderdataSetIterator:
这个DataSetIterator可以被传送到MultiLayerNetwork.fit() 方法来训练网络。
miniBatchSize参数指定每个小批量中的实例数量(时间序列)。例如,对于总共10个文件,miniBatchSize为5将给我们两个数据集,每个数据集包含2个小批(DataSet对象),每个小批中包含5个时间序列。
注意:
对于分类问题: numPossibleLabels是你的数据集中类别的数量 。 使用 regression = false。
标签数据:每行一个值,作为一个分类索引
标签数据将自动转换为one-hot表示。
对于回归问题: numPossibleLabels不被使用(设为任何值) 并使用 regression = true。
输入和标签中的值可以是任意的(不像分类:可以有任意数量的输出)。
当regression = true 不进行标签的处理。
根据上一个示例,假设我们的输入数据和标签不是一个单独的文件,而是在同一个文件中。但是,每个时间序列仍然在一个单独的文件中。
从DL4J 0.4-rc3.8开始,这种方法对输出具有单列的限制(分类索引或单个实值回归输出)
在这种情况下,我们创建并初始化单个读取器。同样,我们跳过一个标题行,并将格式指定为逗号分隔,并假设我们的数据文件名为“myData_0.csv”, …, “myData_9.csv”:
miniBatchSize
和numPossibleLabels
与前面的示例相同。这里,labelIndex
指定标签在哪个列。例如,如果标签在第五列中,则使用labelIndex= 4(即,列被索引为0到numColumns-1)。
对于单一输出值的回归,我们使用:
同样,numPossibleLabels参数不用于回归。
根据前两个示例,假设对于每个示例,输入和标签具有相同的长度,但是这些长度在时间序列之间不同。
我们可以使用相同的方法(CSVSequenceRecordReader和SequenceRecordReaderDataSetIterator),不过使用不同的构造函数:
此处的参数与前面的示例相同,除了AlignmentMode.ALIGN_END。这种对齐模式输入告诉SequenceRecordReaderDataSetIterator需要两件事:
时间序列可以具有不同的长度。
为每个示例把输入和标签对齐,以使它们的最后值出现在同一时间步。
注意,如果特征和标签总是具有相同的长度(如示例3中的假设),那么两个对齐模式(AlignmentMode.ALIGN_END 和 AlignmentMode.ALIGN_START)将给出相同的输出。对齐模式选项将在下一节中解释。
还要注意:在数据数组中,可变长度时间序列总是在时间0处开始:如果要求,将在时间序列结束之后添加填充。
与上面的示例1和2不同,上面的variableLengthIter实例生成的DataSet对象还将包括输入和掩码数组,如本文前面所述。
例4:多对一和一对多数据
我们还可以使用示例3中的AlignmentMode功能来实现多对一RNN序列分类器。在这里,让我们假设:
输入和标签在单独的被分隔的文件中。
标签文件包含一个单行(时间步)(用于分类的类索引,或用于回归的一个或多个数字)。
示例之间的输入长度可以(可选地)不同。
实际上,与示例3相同的方法可以做到这一点:
对准模式相对简单。它们指定是否填充较短时间序列的开始或结束。下面的图表展示了它如何与掩码数组(如本文前面所讨论的)一起工作:
一对多个案例(类似于上面的最后一个案例,但只有一个输入)是通过使用AlignmentMode.ALIGN_START实现。
注意,在包含不同长度的时间序列的训练数据的情况下,标签和输入将针对每个示例单独对齐,然后按要求填充更短的时间序列:
为开箱即用应用程序预先构建的模型架构和权重。
DL4J具有可直接从DL4J访问和实例化的本地模型动物园。模型动物园还包括用于不同数据集的预训练权重,这些数据集是自动下载的,并使用校验总和机制检查完整性。
如果你想使用新的模型动物园,你需要添加它作为依赖项。Maven POM将添加以下内容:
一旦你成功地将动物园依赖项添加到项目中,就可以开始导入和使用模型。每一个模型扩展了ZooModel
抽象类,并使用了InstantiableModel
接口。这些类提供了帮助你初始化空的、新的网络或预先训练的网络的方法。
你可以使用.init()
方法立即从动物园实例化一个模型。例如,如果要实例化一个新的未经训练的AlexNet网络,可以使用以下代码:
如果希望调优参数或更改优化算法,可以获得对底层网络配置的引用:
一些模型具有可用的预训练权重,并且少量模型跨不同的数据集进行预训练。PretrainedType是概述不同权重类型的枚举器,包括IMAGENET、MNIST、CIFAR10和VGGFACE。
例如,你可以初始化VGG-16模型,使用ImageNet的权重,例如:
并用VGGFace训练的权值初始化另一个VGG16模型:
如果不确定模型是否包含预训练权重,可以使用返回布尔值的.pretrainedAvailable
()方法。简单地将一个PretrainedType
枚举传递给这个方法,如果权重可用,它将返回true。
注意,对于卷积模型,输入形状信息遵循NCHW约定。因此,如果模型的输入形状默认值是 new int[]{3, 224, 224},
则意味着该模型具有3个通道和高度/宽度为224。
模型动物园带有在深入学习社区著名的图像识别配置。动物园还包括一个用于文本生成的LSTM,以及一个用于一般图像识别的简单CNN。
这包括ImageNet模型,如VGG-16, ResNet-50, AlexNet, Inception-ResNet-v1, LeNet等。
如果你想在不同的用例中使用这些模型,动物园还有几个附加的特征。
除了将某些配置信息传递给动物园模型的构造函数之外,还可以使用.setInputShape()更改其输入形状。注意:这只适用于新的配置,并且不会影响预先训练的模型:
在Android应用中使用深度学习和神经网络
内容
一般来说,训练一个神经网络是一项最适合有多个GPU的强大计算机的任务。但如果你想在你简陋的安卓手机或平板电脑上做呢?好吧,这绝对是可能的。然而,考虑到一个普通的Android设备的规格,它很可能会相当慢。如果这对你来说不是问题,继续读下去。
在本教程中,我将向您展示如何使用 ,一个流行的基于Java的深度学习库,在Android设备上创建和训练神经网络。
为了获得最佳效果,您需要以下各项:
运行API级别21或更高的安卓设备或模拟器,内部存储空间大约为200 MB。我强烈建议您首先使用模拟器,因为您可以快速调整它,以防内存或存储空间不足。
Android Studio 2.2 或更新版本
这里可以找到在Android应用程序中使用DL4J的更深入的研究。本指南涵盖依赖项、内存管理、保存设备训练模型以及在应用程序中加载预先训练的模型。
要在项目中使用Deeplearning4J,请将以下实现依赖项添加到应用程序模块的build.gradle文件中:
如果选择将依赖项的快照版本与gradle一起使用,则需要在根目录中创建pom.xml文件,并从终端对其运行 mvn -U compile
。您还需要在build.gradle文件的repository{}
块中包含mavenLocal()
。下面提供了一个pom.xml文件示例。
Android Studio 3.0引入了新的Gradle,现在也应该定义annotationProcessors如果您正在使用它,请向Gradle依赖项添加以下代码:
如您所见,DL4J依赖于ND4J,Java的N维缩写,它是一个提供快速N维数组的库。ND4J在内部依赖于一个名为OpenBLAS的库,该库包含特定于平台的本地代码。因此,您必须加载与您的Android设备架构相匹配的OpenBLAS和ND4J版本。
DL4J和ND4J的依赖项有几个同名的文件。为了避免构建错误,请将以下排除参数添加到packagingOptions中。
编译后的代码将有超过65536个方法。要处理此情况,请在defaultConfig中添加以下选项:
现在,按Sync now更新项目。最后,确保APK不同时包含lib/armeabi和lib/armeabi-v7a子目录。如果是,请将所有文件移动到其中一个或另一个,因为某些Android设备将同时存在这两个文件。
训练神经网络是CPU密集型的,这就是为什么您不想在应用程序的UI线程中进行训练。我不太确定DL4J是否在默认情况下异步训练其网络。为了安全起见,我现在将使用AsyncTask类生成一个单独的线程。
因为createAndUseNetwork()方法还不存在,所以创建它。
DL4J有一个非常直观的API。现在让我们用它来创建一个简单的多层感知器与隐藏层。它将获取两个输入值,并输出一个输出值。要创建层,我们将使用DenseLayer和OutputLayer类。相应地,将以下代码添加到在上一步中创建的createAndUseNetwork()方法中:
现在我们的层已经准备好了,让我们创建一个NeuralNetConfiguration.Builder对象来配置我们的神经网络。
我们现在必须创建一个NeuralNetConfiguration.ListBuilder对象来实际连接我们的层并指定它们的顺序。
另外,通过添加以下代码启用反向传播:
此时,我们可以将神经网络生成并初始化为多层网络类的实例。
为了创建我们的训练数据,我们将使用ND4J提供的INDArray类
正如你可能已经猜到的,我们的神经网络将表现得像一个异或门。训练数据有四个样本,您必须在代码中提到它。
现在,为输入和预期输出创建两个INDArray对象,并用零初始化它们。
注意,输入数组中的列数等于输入层中的神经元数。类似地,输出数组中的列数等于输出层中的神经元数。
用训练数据填充这些数组很容易。只需使用putScalar()方法:
我们不会直接使用INDArray对象。相反,我们将把它们转换成一个DataSet。
此时,我们可以通过调用神经网络的fit()
方法并将数据集传递给它来开始训练。for
循环控制通过网络的数据集的迭代。在本例中,它被设置为1000次迭代。
就这些。你的神经网络已经可以使用了。
在本教程中,您看到了在Android Studio项目中使用Deeplearning4J库创建和训练神经网络是多么容易。不过,我想提醒你的是,在低功耗、电池供电的设备上训练神经网络可能并不总是一个好主意。
第二个例子DL4J Android应用程序包括一个用户界面可以在这里找到。这个例子使用Anderson的iris数据集在设备上训练一个神经网络,用于iris类型分类。该应用程序包括用户输入的测量值,并返回这些测量值属于三种iris类型(Iris serosa, Iris versicolor, 和 Iris virginica)之一的概率。
移动设备处理能力和电池寿命的限制使得训练健壮、多层网络不可行。作为在设备上训练网络的替代方法,应用程序使用的神经网络可以在桌面机上训练,通过ModelSerializer保存,然后作为预先训练的模型加载到应用程序中。第三个例子DL4J Android应用程序可以在这里找到,它加载一个预先训练的MNIST网络,并使用它对用户绘制的数字进行分类。
神经网络的存储与加载。
MultiLayerNetwork 与 ComputationGraph 都有保存和加载方法。
可以使用以下命令保存/加载MultiLayerNetwork:
类似地,可以使用以下命令保存/加载ComputationGraph:
在内部这些方法使用ModelSerializer
类,它是一个处理加载和保存模型的类。通过链接显示的示例中保存模型有两种方法。第一个例子保存了一个正常的多层网络,第二个例子保存了一个计算图。
下面是一个,其中包含使用ModelSerializer类保存计算图的代码,以及使用ModelSerializer保存使用MultiLayer配置构建的神经网络的示例。
如果你的模型使用概率(即,DropOut/DropConnect),那么单独保存它并在恢复模型之后应用它可能是有意义的;即:
这将保证会话/JVM之间相等的结果。
为DL4J设置和配置Android Studio。
内容
虽然神经网络通常在使用多个GPU的功能强大的计算机上运行,但Deeplearning4J与Android平台的兼容性使得在Android应用程序中使用DL4J神经网络成为可能。本教程将介绍为构建DL4J应用程序设置Android Studio的基础知识。下面概述了为减轻低功耗移动设备的限制而需要的依赖项、内存管理和编译排除的几种配置。如果你只想让一个DL4J应用在你的设备上运行,你可以跳到一个简单的演示应用程序,它训练一个神经网络Iris花分类,在这里可用。
Android Studio 2.2 或者更新的,可以在下载。
Android Studio 版本2.2及更高版本带有最新的嵌入式OpenJDK;但是,建议您自己安装JDK,因为这样您就可以独立于Android Studio进行更新。Android Studio 3.0及更高版本支持所有Java 7和Java 8语言特性的子集。Java JDKs可以从Oracle的网站下载。
在Android studio中,Android SDK管理器可用于安装Android构建工具24.0.1或更高版本、SDK平台24或更高版本以及Android支持库。
运行API级别21或更高级别的Android设备或模拟器。建议至少有200 MB的内部存储空间可用。
我们还建议您下载并安装IntelliJ IDEA、Maven和完整的dl4j-examples目录,以便在桌面上而不是android studio上构建、构建和训练神经网络。
为了在Android项目中使用Deeplearning4J,您需要将以下依赖项添加到应用程序模块的build.gradle文件中。根据应用程序中使用的神经网络类型,可能需要添加其他依赖项。
DL4J依赖于ND4J,ND4J是一个提供快速n维数组的库。ND4J反过来依赖于一个特定于平台的本地代码库JavaCPP,因此您必须加载一个与Android设备架构相匹配的ND4J版本。可以包括-x86和-arm类型以支持多种设备处理器类型。
上述依赖项包含多个名称相同的文件,必须使用packagingOptions的以下排除参数来处理这些文件。
将上述依赖项和排除项添加到build.gradle文件后,请尝试将gradle同步,以查看是否需要任何其他排除项。错误消息将标识应添加到排除列表中的文件路径。带有文件路径的错误消息示例是: > More than one file was found with OS independent path 'org/bytedeco/javacpp/ windows-x86_64/msvp120.dll”编译这些依赖项涉及大量文件,因此有必要在defaultConfig中将multiDexEnabled设置为true。
junit模块版本中的冲突通常会导致以下错误:> Conflict with dependency 'junit:junit' in project ':app'。应用程序(4.8.2)和测试应用程序(4.12)的解析版本不同。这可以通过强制所有junit模块使用相同的版本来抑制:
建议将Android SDK中的ProGuard升级到最新版本(5.1或更高版本)。请注意,升级构建工具或SDK的其他方面可能会导致ProGuard重置为SDK附带的版本。为了强制ProGuard使用Android Gradle默认版本以外的其他版本,可以在build.gradle
文件的buildscript中包含以下内容:
ProGuard优化并减少Android应用程序中的代码量,以便使if更小更快。不幸的是,ProGuard默认删除注释,包括javaCV使用的@Platform注释。要使ProGuard保留这些注释并保留本机方法,请将以下标志添加到progaurd-rules.pro文件中。
测试应用程序是检查是否有由不适当删除的代码引起任何错误的最好方法;但是,您也可以通过查看保存在/build/outputs/mapping/release/中的usage.txt输出文件来检查删除的内容。
要修复错误并强制ProGuard保留某些代码,请在ProGuard配置文件中添加-keep行。例如:
通过将android:largeHeap=“true”添加到清单文件中,增加分配给应用程序的内存也可能是有利的。分配更大的堆意味着您可以降低在内存密集型操作期间抛出OutOfMemoryError的风险。
从0.9.0版开始,ND4J提供了一个额外的内存管理模型:工作间。工作间允许您为循环工作负载重用内存,而无需使用JVM垃圾收集器进行堆外内存跟踪。D4L工作间允许在try/catch块之前预先分配内存,并在该块内重新使用内存。
如果训练过程使用工作区,建议在调用model.fit()之前禁用或减少定期GC调用的频率。
在构建运行神经网络的Android应用程序时,需要考虑性能限制。在设备上训练一个神经网络是可能的,但是应该只尝试使用层、节点和迭代次数有限的网络。第一个演示应用程序DL4JIrisClassifierDemo能够在15秒内在标准设备上进行训练。
当在设备上进行训练是一个合理的选择时,一旦初始训练完成,就可以将训练的模型保存在手机的外部存储器中,从而提高应用程序的性能。训练后的模型可以用作应用程序资源。这种方法对于训练用户输入数据的网络是有用的。下面的代码演示如何训练网络并将其保存在手机的外部资源中。
对于API23及更高版本,您将需要在清单中包含权限,并在activity中以编程方式请求读写权限。所需的清单权限为:
您需要在activity中实现AActivityCompat.OnRequestPermissionsResultCallback,然后检查权限状态。
要在设备上训练后保存网络,请在try catch块中使用OutputStream。
要从存储中加载经过训练的网络,可以使用restoreMultiLayerNetwork方法。
对于更大或更复杂的神经网络,如卷积神经网络或循环神经网络,在设备上进行训练是不现实的选择,因为在网络训练过程中,长时间的处理可能会产生OutOfMemoryError并导致不良的用户体验。作为替代方案,神经网络可以在桌面端训练,通过ModelSerializer保存,然后作为预先训练的模型加载到应用程序中。在Android应用程序中使用预先训练的模型可以通过以下步骤实现:
在桌面端训练模型并通过modelSerializer保存。
在应用程序的res目录中创建原始资源文件夹。
将model.zip文件复制到raw文件夹中。
使用try/catch块中的inputStream从资源访问它。
这里可以找到使用预训练模型的示例应用程序。
你可以使用这个找到一个完整的模型列表。
预先训练的模型对于迁移学习来说是完美的!你可以在阅读更多关于迁移学习的内容。
初始化方法通常有一个名为“workspaceMode
”的附加参数。对于大多数用户来说,你不需要使用它;但是,如果你有一台具有“健壮”规范的大型机器,则可以为具有数百万参数的模型传递WorkspaceMode.SINGLE
,如VGG-19。要了解更多关于工作间的内容,请参阅。
DL4J依赖项编译大量文件。ProGuard可以用来最小化APK文件的大小。ProGuard从打包的应用程序(包括代码库中的应用程序)中检测并删除未使用的类、字段、方法和属性。你可以在学习更多关于使用ProGuard的知识。要使用ProGuard启用代码收缩,请将minifyEnabled true添加到build.gradle文件中的相应生成类型。
The example below illustrates the use of a Workspace for memory allocation in the AsyncTask of and Android Application. More information concerning ND4J Workspaces can be found .
下面的示例演示了在Android应用程序的AsyncTask中使用工作间进行内存分配。有关ND4J工作间的更多信息可以在找到。
如何使用Eclipse Deeplearning4j在Android上创建IRIS分类器。
示例应用程序使用Anderson的Iris数据集在设备上训练一个小的神经网络,用于Iris花类型分类。要更深入地了解如何为DL4J优化android,请参阅此处的先决条件和配置文档。此应用程序有一个简单的用户界面,可以从用户处测量花瓣长度、花瓣宽度、萼片长度和萼片宽度,并返回测量值属于三种类型Iris(Iris serosa, Iris versicolor, 和 Iris virginica)之一的概率。一个数据集包括150个测量值(每种Iris类型50个)并且训练模型需要5-20秒,视设备而定。
内容
Deeplearning4J应用程序需要build.gradle文件中的几个依赖项。Deeplearning库又依赖于ND4J和OpenBLAS库,因此这些库也必须添加到依赖关系声明中。从Android Studio 3.0开始,还需要定义annotationProcessors,需要依赖于-x86或-arm处理器。
编译这些依赖项涉及大量文件,因此有必要在defaultConfig中将multiDexEnabled设置为true。
junit模块版本中的冲突通常会导致以下错误:> Conflict with dependency 'junit:junit' in project ':app'。应用程序(4.8.2)和测试应用程序(4.12)的解析版本不同。这可以通过强制所有junit模块使用相同的版本来抑制:
即使是像本例中这样简单的神经网络训练,也需要相当大的处理器功率,这在移动设备上是受限的。因此,必须使用一个后台线程来构建和训练神经网络,然后将输出返回给主线程以更新UI。在本例中,我们将使用AsyncTask,它接受来自UI的输入测量,并将它们作为double类型传递给doInBackground()方法。首先,让我们获取对UI布局中editTexts的引用,该UI布局接受onCreate方法内部的iris测量。然后onClickListener将执行我们的asyncTask,将用户输入的测量传递给它,并显示一个进度条,直到我们在onPostExecute()中再次隐藏它为止。
现在让我们编写AsyncTask<Params, Progress, Results>.。AsyncTask需要有Double类型的参数才能从UI接收十进制值测量值。结果类型设置为INDArray,它从doInBackground()方法返回并传递给onPostExecute()方法以更新UI。NDArrays由ND4J库提供,本质上是具有给定维数的n维数组。有关NDArrays的更多信息,请参见https://nd4j.org/userguide.
doInBackground()方法将处理训练数据的格式化、神经网络的构造、网络的训练以及通过训练模型对输入数据的分析。用户输入只有4个值,因此我们可以使用putScalar()方法将这些值直接添加到1x4 INDArray中。训练数据要大得多,必须通过迭代for循环从CSV列表转换为矩阵。
训练数据以两个数组的形式存储在应用程序中,一个数组用于名为irisData的Iris测量,其中包含150个Iris测量的列表,另一个数组用于名为labelData的Iris类型标签。它们将分别转换为150x4和150x3矩阵,以便它们可以转换为INDArray对象,神经网络将用于训练。
现在我们的数据已经准备好了,我们可以用一个隐藏层来构建一个简单的多层感知器。DenseLayer类用于创建网络的输入层和隐藏层,OutputLayer类用于输出层。输入INDArray中的列数必须等于输入层(nIn)中的神经元数。隐藏层输入中的神经元数必须等于输入层的输出数组(nOut)数。最后,outputLayer输入应该与hiddenLayer输出匹配。输出必须等于可能的分类数,即3。
下一步是使用nccBuilder构建神经网络。下面选择的训练参数是标准的。要了解有关优化网络训练的更多信息,请参阅deeplearning4j.org。
一旦神经网络的训练和用户测量的分类完成,doInBackground()方法将完成,onPostExecute()将访问主线程和UI,允许我们用分类结果更新UI。注意,概率报告的小数位数可以通过设置DecimalFormat模式来控制。
希望本教程说明了DL4J与Android的兼容性如何使它在移动设备上构建、训练和评估神经网络变得容易。我们使用一个简单的UI从测量中获取输入值,然后在AsyncTask中将它们作为参数传递。处理器密集的数据准备、网络层构建、模型训练和用户数据评估步骤都是在后台线程的doInBackground()方法中执行的,保持了设备的稳定和响应。完成后,我们将输出INDArray作为AsyncTask Results传递给onPostExecute(),更新UI以演示分类结果。移动设备处理能力和电池寿命的限制使得训练健壮的多层网络变得有些不可行。为了解决这个限制,我们接下来将看一个Android应用程序示例,该应用程序在初始模型训练之后将经过训练的模型保存在设备上以获得更快的性能。
此处提供了此示例的完整代码。
Eclipse DeepLearning4J上的新闻文章列表。
关于深度学习的突出和/或有趣的媒体故事的非详尽列表,带有突出的片段。
科学家在深度学习项目中看到了希望,作者:John Markoff;2012年11月23日 卢加诺大学瑞士人工智能实验室的科学家们设计的一个程序在德国交通标志数据库中识别图像的能力超过了竞争对手的软件系统和人类专家,从而赢得了一场模式识别比赛。 获奖程序在一组5万张图片中准确识别出99.46%的图像;32名人类参与者的最高得分为99.22%,人类的平均得分为98.84%。 今年夏天,谷歌(Google)技术研究员杰夫•迪恩(Jeff Dean)和斯坦福大学(Stanford)计算机科学家恩达•吴(Andrew Y.Ng)为一个由16000台计算机组成的集群编程,训练自己在一个拥有1400万张20000个不同物体图片的库中自动识别图像。虽然准确率很低,只有15.8%,但这个系统比最先进的前一个系统好70%。
Facebook向开源组积提供人工智能技术;Quentin Hardy;2015年1月16日 --Facebook的一个聪明的招聘游戏。他们碰巧保留了使用该操作系统软件生产的任何产品的专利权。
Deeplearning4j的开源和企业支持。
查找bug或请求新功能?在Github上报告所有Eclipse Deeplearning4j问题。
有关使用DL4J、ND4J、DataVec、Arbiter或任何库的帮助,请在Gitter上加入我们的社区聊天。
我们还监视StackOverflow以了解有关DL4J库和Java的任何一般用法问题。
Konduit是Eclipse DeepLearning4J的开发者,为数据科学和模型服务提供专业支持和软件。
与其他算法相比,何时选择深度学习。
深度学习在机器学习和其他优化算法中何处适用,以及它能解决什么样的问题?当您选择工具来解决任何数据问题时,首先要回答这些问题。
深度学习是机器学习的同义词,只是更大领域的高级子集。从技术上讲,深度学习是一组神经网络的总称,这些神经网络由三层或更多层组成,即至少一个隐藏层,以及输入和输出的可见层。
那么,什么是深度学习可做的和不可做的?深度学习可以识别模式并对其进行分类。换言之,它可以告诉你的机器它在看什么,听什么,或者作为一个数字流被输入。
在我们有机会反思之前,在我们头脑中出现的一个非常基本的功能,是一个计算机的巨大复杂性,只有经过大量计算才能完成的任务。面对一系列像素,没有一台计算机天生就知道房子、树和猫的区别。深度学习就是这个带有画笔的人。
要想让深度学习顺利进行,它需要大量的数据。所以首先,你应该拥有它。(在处理非结构化数据时,深度学习比机器学习具有明显的优势。它不需要你标记所有的东西来发现模式。)
接下来,你需要一个你想回答的问题。这些非结构化数据是由什么组成的?有需要标签的图片吗?需要名字的声音?与书写的文本相匹配的演讲?包含多个要分析的对象的视频?或者你想按情感或内容分组的文本?这些都是深度学习可以帮助解决的具体问题。如果这些匹配你的问题,然后你应该选择你的算法。
简而言之:你的问题是否需要在大量的输入中确定某个对象或现象?在你做决定之前,你需要把一些事件隔离开来吗?对这些问题回答“是”可能会引导你选择深入度学习。
通过测量图像之间的相似性,比如说,一个深度学习算法可以找到某个人脸上的所有图片,并将它们收集到一个la Facebook页面上。相反,通过测量这些差异,它也许能告诉你是否有一张不知名的脸出现,比如说,在晚上出现在你家门口。它将相似的东西分组,并突出不同的东西。
突出显示差异称为异常检测。由于深度学习最适合于文本、声音和图像等非结构化媒体,因此我们将使用视觉异常。
医生每天都在CT扫描中寻找肿瘤——有时他们会发现肿瘤,有时则不会。医院拥有大量标记为癌症的图像,在不知道肿瘤是否存在的地方,也有同样数量的图像。标记的数据可以作为训练集,尚未标记的图像可以用深度学习网络进行分类。
如果有足够多的癌症标记实例,一个深度学习网络可以开始识别更微妙的模式,而医生自己可能很难意识到这一点。
现在想象一下,深度学习能够以同样的精度分析视频、声音、文本和时间序列数据。它可以识别股票变动中的面孔、声音、类似文件和信号。它可能会在机场航站楼发现可疑的面孔,或者识别出欺诈电话的潜在音频伪造品。
有了时间序列数据和声音,它能够分析一个模式的开始,并填充其余的部分,不管其余部分是一个完整的词,还是市场上的下一个小插曲。它不关心模式是否完全存在于当下,比如一张脸,或者部分地在未来,就像一个被说的句子。
与以前的神经网络和其他机器学习算法相比,深度学习的一个主要优势是它能够从训练集中包含的有限特征集合中推断出新的特征。也就是说,它将搜索并找到与已知特征相关的其他特征。它将发现在噪音中听到信号的新方法。
深度学习在不被明确告知的情况下创建特征的能力意味着数据科学家可以通过依赖这些网络节省数月的工作。这也意味着数据科学家可以使用比机器学习工具更复杂的特征集。
虽然可以调试神经网络,但不能像使用随机森林决策树那样将决策映射回单个特征。
深度学习无法告诉你为什么它会得出某种结论。它的隐藏网络正在创建自己的特征,根据训练集中包含的手动特征进行推断。它创建的那些特征可能没有名称。
在机器学习中,这些特征是由工程师手工创建的,他们知道这些特征对算法的最终决策有多大的贡献。
深度学习缺乏特征自醒。如果您试图将事件分类为欺诈性或非欺诈性,并且必须记录并证明您是如何得出结论的,那么这一点很重要。如果你拒绝一个客户的订单,因为欺诈,你的老板问你为什么,她可能不喜欢回答“我不知道”
神经网络对文本处理是高性能的:当前部分数据对文本的依赖性很大,神经网络在自然语言处理中占主导地位。他们还提供了一套灵活的建模选项,同时最小化特征工程。主要的替代方案对训练来说同样或更复杂,例如。,条件随机场或生成模型,或对文本有严重限制,例如随机森林。
神经网络对图像处理是高性能的:现代的深度学习框架,例如卷积网络,将是迄今为止处理此类数据的最佳选择(可能是唯一可行的)。
神经网络比其他方法(如RFs或图形模型)更能自然地扩展到大数据。
神经网络可以利用未标记的数据:深度学习提供了多种方法,使我们能够更好地利用未标记的数据。其他方法,例如RFs,不能自然地使用未标记的数据。
神经网络可以用于多任务学习:训练一个神经网络同时预测多个目标是很简单的。这通常可以提高性能。在其他范例中,多任务学习是非常有限的(例如,线性模型),或者是困难的(例如,随机森林)。
神经网络学习可用于其他任务的数据的转换表示:神经网络显式学习可用于其他任务的原始数据(隐藏层)的转换。
关于Eclipse DeepLearning4J、深度学习和人工智能的常见问题。
DL4J有一个用于Java和Scala的通用n维数组类,它可以在Hadoop上进行扩展,利用GPU支持AWS上的扩展,包括一个用于机器学习libs的通用矢量化工具,而且大多数都依赖于ND4J:一个比Numpy快得多的矩阵库,并且大部分是用C++编写的。我们还构建了RL4J:使用Deep Q learning和A3C的Java强化学习。
像Deeplearning4j这样的人工智能工具可以应用于机器人过程自动化(RPA)、欺诈检测、网络入侵检测、推荐系统(CRM、adtech、客户流失预防)、回归和预测分析、人脸/图像识别、语音搜索、语音到文本(转录)和预防性硬件监控(异常检测)。
希望为Deeplearning4j做出贡献的开发人员可以从阅读我们的贡献者指南开始。
Deeplearning4j包括一个分布式的多线程深度学习框架和一个普通的单线程深度学习框架。训练在集群中进行,这意味着它可以快速处理大量数据。网络通过迭代reduce进行并行训练,并且它们与Java、Scala、Clojure和Kotlin具有同等的兼容性。Deeplearning4j作为开放堆栈中的模块化组件,使其成为第一个适用于微服务架构的深度学习框架。
Eclipse Deeplearning4j的每个版本都有新的变化。
内容
添加了模型服务器-使用JSON或(可选)二进制序列化对SameDiff和DL4J模型进行远程推理
服务器: 查看 JsonModelServer
客户端: 查看 JsonRemoteInference
增加了Scala 2.12支持,删除了Scala2.10支持。具有Scala依赖关系的模块现在随Scala 2.11和2.12版本一起发布。
Apache Spark 1.x 支持删除 (现在只支持 Spark 2.x ). Note注意: Spark 删除了版本后缀 用于升级: 1.0.0-beta4_spark2 -> 1.0.0-beta5
添加FastText 支持到 deeplearning4j-nlp
CUDA支持所有ND4J/SameDiff操作
在1.0.0-beta4中,有些操作仅限于CPU。现在,所有的操作都有完整的CUDA支持
在ND4J(和DL4J/SameDiff)中添加了对新数据类型的支持:BFLOAT16、UINT16、UINT32、UINT64
ND4J: 隐式广播支持添加到 INDArray (已经出现在SameDiff - 例如形状 [3,1]+[3,2]=[3,2]
)
CUDA 9.2, 10.0 与 10.1-Update2 仍然支持
注意: 对于 CUDA 10.1, CUDA 10.1 update 2是推荐的。 CUDA 10.1 与 10.1 Update 1仍将运行,但在某些系统上的多线程代码中可能会遇到罕见的内部cuBLAS问题
依赖项升级: Jackson (2.5.1 to 2.9.9/2.9.9.3), Commons Compress (1.16.1 to 1.18), Play Framework (2.4.8 to 2.7.3), Guava: (20.0 to 28.0-jre, 并遮挡以避免依赖关系冲突)
CUDA: 现在,除了设备(GPU)缓冲区之外,主机(RAM)缓冲区只在需要时分配(以前:主机缓冲区总是被分配的)
添加了FastText-推理和训练,包括OOV(词汇表外)支持(链接)
Scala 2.12 支持添加, Scala 2.10支持删除 (链接)
添加模型服务器(DL4J 与 SameDiff 模型, JSON 与二进制通信) - JsonModelServer, JsonRemoteInference, 链接,链接
添加了保存的模型格式验证实用程序 - DL4JModelValidator, DL4JKerasModelValidator (链接)
添加LabelLastTimeStepPreProcessor (链接)
BertIterator: 添加了将令牌前置到输出的选项(例如某些模型所期望的[cls])(链接)
在多层网络和计算图中添加跟踪级日志记录有助于调试某些问题(链接)
Upsampling3D: 添加 NDHWC 支持 (链接)
MergeVertex 现在支持广播 (链接)
LSTM 与 Dropout现在将依赖于内置实现,如果从cuDNN中遇到异常 (与 Subsampling/ConvolutionLayer一样 ) (链接)
更新的deeplearning4j-ui界面主题(Link)
修复了MergeVertex和CNN3D激活的问题 (Link)
修复Yolo2OutputLayer builder/configuration 方法名中排版错误 (Link)
改进计算图构建器输入类型验证(Link)
移除dl4j-spark-ml模块,直到它可以被适当的维护 (Link)
修复了BertWordPieceTokenizerFactory和错误字符编码的问题 (Link)
修复了从环境变量设置SharedTrainingMaster控制器地址时出现的问题 (Link)
修复了在某些情况下SameDiffOutputLayer初始化的问题 (Link)
修复了上采样层内存报告可能产生OOM异常的问题 (Link)
改进了RecordReaderDataSetIterator的UX/验证 (Link)
修正了EmbeddingSequenceLayer不检查掩码数组数据类型的问题 (Link)
使用非rank-2(shape[1,numParams])数组初始化网络时的验证改进 (Link)
修正了BertIterator的数据类型问题 (Link)
修正了Word2Vec模型向后兼容(beta3和早期的模型现在可以再次加载)Link
修复了某些Keras导入模型可能会失败的问题Could not read abnormally long HDF5 attribute
(Link)
为RnnOutputLayer特征/标签数组长度添加了验证 (Link)
修复了SameDiffOutputLayer不支持变量minibatch大小的问题 (Link)
修复了DL4J SameDiff 层掩码支持 (Link)
DL4J UI: 修复了一个罕见的UI线程问题 (Link)
修复了JSON格式更改时的Keras导入问题 (Link)
修复了Keras导入问题,其中更新程序学习速率调度可能被错误导入(Link)
对DL4J SameDiff层的修复和优化 (Link)
如果在工作区关闭期间发生第二个异常,多层网络/计算图现在将记录原始异常,而不是将其吞入(推理/拟合操作try/finally块) (Link)
更新依赖: Jackson (2.5.1 to 2.9.9/2.9.9.3), Commons Compress (1.16.1 to 1.18), Play Framework (2.4.8 to 2.7.3), Guava: (20.0 to 28.0-jre, 并遮挡以避免依赖关系冲突) (Link)
现在可以为DL4J UI配置日志框架(由于Play framework依赖项升级) (Link)
MnistDataFetcher产生的垃圾量减少(影响MNIST和EMNIST DataSetIterators) (Link)
DL4J AsyncDataSetIterator 和 AsyncMultiDataSetIterator移到了 ND4J, 使用 org.nd4j.linalg.dataset.Async(Multi)DataSetIterator
替换原来的
无法再加载从1.0.0-alpha及以前版本中保存的具有自定义图层的模型。解决方法:加载1.0.0-beta4,然后重新保存模型(链接)。没有自定义层的模型仍然可以加载回0.5.0
Apache Spark 1.x 支持被删除( 现在只有 Spark 2.x 被支持). 注意:Spark版本后缀已删除:对于升级,请按如下方式更改版本: 1.0.0-beta4_spark2 -> 1.0.0-beta5
Scala 2.10 被删除, Scala 2.12 被添加 (对于具有Scala依赖项的模块)
由于添加了同步,某些层(如LSTM)在不使用cuDNN时在CUDA的1.0.0-beta5上可能比1.0.0-beta4上运行得慢。此同步将在1.0.0-beta5之后的下一个版本中删除
CUDA 10.1:在运行cuda10.1 update 1(可能还有10.1)时,在某些系统上的多线程代码中可能会遇到罕见的内部cuBLAS问题。建议使用CUDA 10.1 update2。
添加新数据类型: BFLOAT16, UINT16, UINT32, UINT64 (Link)
添加了模型服务器(DL4J和SameDiff模型、JSON和二进制通信)- JsonModelServer, JsonRemoteInference, Link, Link
添加了对形状为零的空数组的支持,以与TensorFlow导入兼容 (Link)
CUDA:现在,除了设备(GPU)缓冲区外,主机(RAM)缓冲区只在需要时分配(以前:总是分配主机缓冲区)
改进的SameDiff训练API-添加了“在线”测试集评估、返回带有损失曲线的历史对象等 (Link)
添加了保存的模型格式验证实用程序 - Nd4jValidator, Nd4jCommonValidator (Link)
添加SameDiff.convertDataTypes 方法,用于变量dtype转换 (Link)
添加crop 与 resize 操作 (Link)
DL4J AsyncDataSetIterator 与 AsyncMultiDataSetIterator 迁移到 ND4J Link
添加 basic/MVP SameDiff UI listener (Link)
添加 SameDiff名称作用域 (Link)
SameDiff: 更新器 状态和训练配置现在写入FlatBuffers格式 (Link)
添加了可从Java调用的c++基准测试套件
Nd4j.getExecutioner().runLightBenchmarkSuit()
与 Nd4j.getExecutioner().runFullBenchmarkSuit()
(Link)
为评估实例的添加轴配置 (Evaluation, RegressionEvaluation, ROC, etc - getAxis 与 setAxis 方法) 来允许不同的数据格式 (NCHW vs. 用于CNNs的NHWC, 例如 ) (Link)
SameDiff: 添加了将常量转换为占位符的支持, 通过 SDVariable.convertToConstant() 方法 (Link)
SameDiff: 为 SameDiff添加了GradCheckUtil.checkActivationGradients方法来检查激活梯度(不仅仅是在现有的梯度检查方法中的参数梯度) (Link)
添加CheckNumerics 操作 (Link)
添加 FakeQuantWithMinMaxArgs 与 FakeQuantWithMinMaxVars 操作 (Link)
添加了带“keep dimensions”选项的INDArray缩减方法-例如,INDArray.mean(boloean, int... dimension)
(Link)
添加了INDArray.toString(NDArrayStrings options)、toStringFull()和toString方法重载,以便更轻松地控制数组打印(Link)
添加 HashCode 操作, INDArray.hashCode() (Link)
SameDiff: 为 loops/conditional 操作添加 whileLoop,ifCond方法 (Link)
deeplearning4j-nlp: 重命名 AggregatingSentencePreProcessor 为 sentencePreProcessor method (Link)
升级 Protobuf 版本 - 3.5.1 到 3.8.0 (Link)
为 libnd4j 本地操作切换c=style错误处理(Link)
更新到JavaCPP/JavaCV 1.5.1-1 (Link)
SameDiff: 现在只有在需要计算请求的变量时,才必须提供占位符(Link)
SameDiff: 修正了重复变量名验证的问题 (Link)
SameDiff: 修复了标量的SDVariable.getArr问题 (Link)
向DeviceLocalNDArray添加了延迟模式(需要时才复制到设备) (Link)
ND4J: 修复了以numpy.npy格式写入0d(标量)NDArrays的问题(Link)
修复了一些固定情况下Pad操作的问题 (Link)
SameDiff: 修复了某些使用ND4J默认数据类型的操作的DataType推理问题(Link)
INDArray.castTo(DataType) 当数组已经是正确的类型时,现在是no-op (Link)
SameDiff: 修复了训练混合精度网络的问题(Link)
修复了Evaluation类错误地报告二进制情况下macro-averaged精度的问题 (Link)
从 SameDiff TrainingConfig (不再需要)移除 trainableParams config/field (Link)
修复当[1,N] 与 [N,1]时INDArray.isMatrix() 不被认为是矩阵的问题 (Link)
修复INDArray.median(int... dimension) 的问题(Link)
修复当执行收集操作backprop 可能发生无指针异常(Link)
修复 LogSumExp操作Java/C++映射的问题 (Link)
在读取Numpy.npy文件时添加了头验证,以确保文件有效 (Link)
修复了在CUDA上读取Numpy.npy文件时可能出现的问题(Link)
修复了读取Numpy.npy布尔文件时出现的问题(Link)
TensorFlow导入的各种问题修复 (Link)
修复了少数Nd4j.create方法不创建与java原始类型对应的数组的问题 (Link)
一些Nd4j.create方法的形状验证改进 (Link)
清理了未维护的Nd4j.createSparse方法 (Link)
修复了带有CC 3.0的CUDA GPU的CUDA问题 (Link)
修复了c++代码中一些可能的整数溢出(Link)
修正了一些jvm可能由于SameDiff依赖关系(现在已删除)而警告“非法反射访问”的问题(Link)
SDVariable现在不再扩展DifferentialFunction(Link)
将数个操作calculateOutputShape的实例从Java迁移到C++ (Link)
修复了maxpool2d_bp在存在NaN值时引发异常的问题(Link)
修复了空形状(带零)连接的问题 (Link)
删除 INDArray.javaTensorAlongDimension (Link)
LayerNorm 操作现在支持axis参数, NCHW 格式数据 (Link)
libnd4j:由于cuBLAS的限制,cuBLAS hgemm(FP16 gemm)仅对计算能力大于等于5.3的设备调用(Link)
Nd4j.readNumpy 优化 (Link)
在c语言的ELU和lrelu-bp操作中添加了可配置的alpha参数++ (Link)
OldAddOp, OldSubOp, 等被删除 : 用 AddOp, SubOp, 等操作
Nd4j.trueScalar 与 trueVector 被删除 ; 使用 Nd4j.scalar 和 Nd4j.createFromArray 方法
INDArray.javaTensorAlongDimension 被删除 ; 用 INDArray.tensorAlongDimension 代替它
INDArray.lengthLong() 被删除 ; 使用 INDArray.length() 代替它
nd4j-native 在一些 OSX 系统上会出现这样的错误 Symbol not found: ___emutls_get_address
- 查看此链接
SBT1.3.0可能失败,Illegal character in path;SBT1.2.8可以。这是SBT问题,不是ND4J问题。有关详细信息,请参阅此链接
修复了HistoryProcessor的压缩问题 (Link)
Jackson版本的升级需要更改通用对象序列化的执行方式;存储在1.0.0-beta4或更早格式中的Arbiter JSON数据在1.0.0-beta5中可能不可读取。(链接)
主要亮点:对ND4J和DL4J的完全多数据类型支持。在过去的版本中,ND4J中的所有N维数组都被限制为单个数据类型(float或double),全局设置。现在,所有数据类型的数组都可以同时使用。支持以下数据类型:
DOUBLE: 双精度浮点,64位 (8 字节)
FLOAT: 单精度浮点,32位 (4 字节)
HALF: 半精度浮点,16位(2字节), "FP16"
LONG: 长有符号整数,64位(8字节)
INT: 有符号整数,32位(4字节)
SHORT: 有符号短整数,16位(2字节)
UBYTE: 无符号字节,8位(1字节),0到255
BYTE: 有符号字节,8位(1字节),-128至127
BOOL: 布尔类型,(0/1,真/假)。使用ubyte存储以便于操作并行化
UTF8: 字符串数组类型,UTF8格式
ND4J 行为变化:
从Java原生数组创建INDArray时,INDArray数据类型将由原生数组类型确定(除非指定了数据类型)
例如:Nd4j.createFromArray(double[])->double数据类型INDArray
类似地,Nd4j.scalar(1)、Nd4j.scalar(1L)、Nd4j.scalar(1.0)和Nd4j.scalar(1.0f)将分别生成INT、LONG、DOUBLE和FLOAT类型的标量INDArray
某些操作要求操作数具有匹配的数据类型
例如,如果x和y是不同的数据类型,则可能需要强制转换: x.add(y.castTo(x.dataType()))
有些操作有数据类型限制:例如,不支持UTF8数组上的sum,BOOL数组上的variance也不受支持。对于布尔数组(如sum)上的某些操作,首先强制转换为整数或浮点类型可能是有意义的。
DL4J 行为变化:
MultiLayerNetwork/ComputationGraph 不再以任何方式依赖于ND4J全局数据类型。
网络的数据类型(用于参数和激活的数据类型)可以在构建期间设置, 使用 NeuralNetConfigutation.Builder().dataType(DataType)
方法
网络可以从一种类型转换为另一种类型(双精度到浮点,浮点到半精度等)使用 MultiLayerNetwork/ComputationGraph.convertDataType(DataType)
方法
主要新方法:
Nd4j.create(), zeros(), ones(), linspace(), 等带有DataType参数的方法
INDArray.castTo(DataType) 方法 - 将INDArrays从一种数据类型转换为另一种数据类型
新 Nd4j.createFromArray(...) 方法
ND4J/DL4J: CUDA - 10.1 支持添加, CUDA 9.0 支持删除
1.0.0-beta4支持的CUDA版本: CUDA 9.2, 10.0, 10.1.
ND4J: Mac/OSX CUDA 支持被删除
不再提供Mac(OSX)CUDA二进制文件。Linux(x86_64,ppc64le)和Windows(x86_64)CUDA支持仍然存在。OSX CPU支持(x86_64)仍然可用。
DL4J/ND4J: MKL-DNN 支持被添加 在CPU/本机后端上运行时,默认情况下,DL4J(和ND4J conv2d等操作)现在支持MKL-DNN。MKL-DNN支持针对以下层类型实现:
ConvolutionLayer 与 Convolution1DLayer (与 Conv2D/Conv2DDerivative ND4J 操作)
SubsamplingLayer 与 Subsampling1DLayer (与 MaxPooling2D/AvgPooling2D/Pooling2DDerivative ND4J 操作 )
BatchNormalization 层 (与 BatchNorm ND4J op)
LocalResponseNormalization 层 (与 LocalResponseNormalization ND4J 操作 )
Convolution3D 层 (与 Conv3D/Conv3DDerivative ND4J 操作)
MKL-DNN对其他层类型(如LSTM)的支持将在将来的版本中添加。
MKL-DNN 可以使用以下代码全局禁用(ND4J和DL4J)Nd4jCpu.Environment.getInstance().setUseMKLDNN(false);
MKL-DNN 可以全局禁用特定操作通过设置 ND4J_MKL_FALLBACK
环境变量为要禁用MKL-DNN支持的操作的名称。例如:ND4J_MKL_FALLBACK=conv2d,conv2d_bp
ND4J: 内存管理更改提高了性能
以前的ND4J版本使用定期垃圾收集(GC)来释放未在内存工作区中分配的内存。(请注意,默认情况下,DL4J几乎对所有操作都使用工作区,因此在训练 DL4J网络时,经常会禁用定期GC)。但是,对垃圾收集的依赖导致性能开销随着JVM堆中对象的数量而增加。
在1.0.0-beta4中,周期性垃圾收集在默认情况下是禁用的;相反,只有在需要从工作区外分配的数组中回收内存时,才会调用GC。
要重新启用定期GC(根据beta3中的默认设置),并将GC频率设置为每5秒(5000ms),可以使用:
ND4J: 改进了Rank 0/1数组支持
在以前的ND4J版本中,在获取行/列、使用INDArray.get(NDArrayIndex…)获取子数组或从Java数组/标量创建数组时,标量和向量有时是rank 2而不是rank 0/1。现在,这些rank 0/1情况的行为应该更加一致。注意:为了保持getRow和getColumn的旧行为(即分别返回shape[1,x]和[x,1]的rank2数组),可以使用getRow(long,boolean)和getColumn(long,boolean)方法。
DL4J: Attention 层被添加
添加点积attention 层 : AttentionVertex, LearnedSelfAttentionLayer, RecurrentAttentionLayer 与 SelfAttentionLayer
NeuralNetConfiguration.Builder(Link)上的dataType(dataType)方法为新网络设置新模型的参数/激活数据类型
EmbeddingLayer 与 EmbeddingSequenceLayer builders 现在有 .weightInit(INDArray)
和 .weightInit(Word2Vec)
方法用于从预训练的词向量(链接)初始化参数
PerformanceListener 现在可以配置为报告垃圾收集信息(数量/持续时间)链接
评估类现在将检查预测输出中的NaN,并抛出异常,而不是将argMax(NaNs)视为具有值0(Link)
为ParallelInference添加了ModelAdapter,以方便使用和YOLO等用例(通过避免分离(不在工作区内)数组提高性能)
增加了GELU激活函数(链接)
添加BertIterator(BERT训练的MultiDataSetIterator-有监督和无监督)链接
添加ComputationGraph.output(List<String> layers, boolean train, INDArray[] features, INDArray[] featureMasks)
方法仅获取特定层/顶点集的激活(无多余计算)(链接)
增加了胶囊网络层(在下一版本发布之前没有GPU加速)- CapsuleLayer, CapsuleStrengthLayer 与 PrimaryCapsules (Link)
Layer/NeuralNetConfiguration builder现在也有getter/setter方法,以便更好地支持Kotlin (Link)
UI的大多数JavaScript依赖项和字体已经迁移到WebJars (Link)
CheckpointListener 现在有静态的availableCheckpoints(File), loadCheckpointMLN(File, int) 与 lostLastCheckpointMLN(File) 等方法 (Link)
MultiLayerNetwork/ComputationGraph 现在,在某些不兼容的RNN配置中验证并抛出异常,如通过时间的截断反向传播与LastTimeStepLayer/Vertex 相结合(Link)
添加 BERT WordPiece 分词器 (Link)
Deeplearning4j UI现在支持多用户/多会话 - 用 UIServer.getInstance(boolean multiSession, Function<String,StatsStorage>)
在多会话模式下启动UI (Link)
Layer/NeuralNetworkConfiguration builder 方法验证标准化和改进(Link)
WordVectorSerializer 现在支持读取和导出文本格式向量,通过 WordVectorSerializer.writeLookupTable 与 readLookupTable方法 (Link]
更新JavaCPP, JavaCPP presets, 与 JavaCV 到 1.5版本 (Link)
增加了EvaluationBinary报警失败率计算(Link)
ComputationGraph GraphBuilder有一个 appendLayer 方法可用于添加层连接到上次添加的层/顶点 (Link)
添加 Wasserstein损失函数 (Link)
Keras导入:改进了lambda层导入的错误/异常 (Link)
Apache Lucene/Solr从7.5.0升级到7.7.1 (Link)
KMeans聚类策略现在是可配置的 (Link)
DL4J Spark 训练:修复共享集群(多个同时训练作业)-现在随机生成的Aeron流ID(链接)
如果抛出内存不足异常(Link),cuDNN帮助程序将不再试图依靠内置层实现
批量归一化全局方差重新参数化,以避免在分布式训练期间出现下溢和零/负方差(Link)
修正了tensorAlongDimension可能导致边界情况的数组顺序不正确,从而导致LSTMs(Link)中出现异常的问题(Link)
修复了ComputationGraph.getParam(String)的边界情况问题,其中层名称包含下划线(Link)
Keras导入:为权重初始化添加别名(链接)
修复了在克隆网络配置时无法正确克隆dropout实例的问题(链接)
修复了具有单个输入的ElementwiseVertex的工作区问题(链接)
修复了用户界面中分离StatsStorage可能会尝试两次删除存储的问题,从而导致异常(链接)
修复了从保存的格式(链接)还原网络时DepthwiseConv2D权重可能形状错误的问题
修复了BaseDatasetIterator.next()在设置预处理器(链接)时不应用预处理器的问题
改进了CenterLossOutputLayer(链接)的默认配置
修复了UNet非预训练配置(Link)的问题
修复了Word2Vec VocabConstructor在某些情况下可能死锁的问题(链接)
SkipGram和CBOW(在Word2Vec中使用)被设置为本地操作以获得更好的性能(Link)
修复了在使用InMemoryStatsListener(链接)时保留对分离的StatsListener实例的引用可能导致内存问题的问题
优化:工作区已添加到SequenceVectors和Word2Vec(链接)
为RecordReaderDataSetIterator改进校验 (Link)
改进的WordVectors实现中的未知词处理(Link)
Yolo2OutputLayer: 添加了对错误标签形状的验证。(链接)
LastTimeStepLayer 当输入掩码全部为0时,现在将引发异常(没有数据-没有最后一个时间步)(链接)
修复了MultiLayerNetwork/ComputationGraph.setLearningRate方法在某些罕见情况下可能导致更新器状态无效的问题(Link)
修正了Conv1D 层在MultiLayerNetwork.summary()中计算输出长度的问题(Link)
异步迭代器现在用于EarlyStoppingTrained中,以提高数据加载性能(Link)
EmbeddingLayer 与 EmbeddingSequenceLayer在CUDA上的性能已经被提升 (Link)
修复L2NormalizeVertex equals/hashcode方法中的问题(链接)
修复了ConvolutionalListener中的工作区问题(Link)
修复了 EvaluationBinary falsePositiveRate 计算 (Link)
为MultiLayerNetwork.output(DataSetIterator)方法添加了验证和有用的异常(Link)
修复了在尚未调用init()的情况下ComputationGraph.summary()将引发NullPointerException的小问题 (Link)
修正了一个计算图问题,在训练过程中,输入到一个单层/顶点重复多次可能会失败 (Link)
改进KMeans实现的性能 (Link)
修复了“wrapper”层(如FrozenLayer)中RNNs的rnnGetPreviousState问题 (Link)
Keras导入:修复了导入某些Keras分词器时出现的单词顺序问题(链接)
Keras导入:修复了KerasTokenizer类中可能存在UnsupportedOperationException异常的问题(Link)
Keras导入:修复了一个导入问题,模型结合了嵌入、变形和卷积层 (Link)
Keras导入:修复了一些RNN模型的输入类型推理的导入问题 (Link)
修复在 LocallyConnected1D/2D 层中的一些填充问题 (Link)
删除了对处理工作区外(分离的)INDArrays内存管理的定期垃圾收集调用的依赖 (Link)
添加了INDArray.close()方法,允许用户立即手动释放堆外内存 (Link)
SameDiff: 添加了TensorFlowImportValidator工具以确定是否可以将TensorFlow图导入SameDiff。报告使用的操作以及SameDiff中是否支持这些操作 (Link)
添加了Nd4j.createFromNpzFile方法以加载Numpy npz文件 (Link)
Added basic ("technology preview") of SameDiff UI. Should be considered early WIP with breaking API changes expected in future releases. Supports plotting of SameDiff graphs as well as various metrics (line charts, histograms, etc)添加了SameDiff UI的基本(“技术预览”)。应被视为早期WIP,并在未来版本中打破预期的API更改。支持绘制SameDiff图以及各种度量(折线图、直方图等)
当前嵌入在DL4J UI中 - 调用 UIServer.getInstance()
然后访问 localhost:9000/samediff
。
添加 DotProductAttention 与 MultiHeadDotProductAttention 操作 (Link)
添加 Nd4j.exec(Op) 与 Nd4j.exec(CustomOp) 方便的方法 (Link)
ND4J/SameDiff -新操作被添加:
SameDiff: 缩减操作现在支持轴参数的“动态”(非常量)输入。 (Link)
ROCBinary 现在有.getROC(int outputNum) 方法 (Link)
SameDiff: 添加 SDVariable.convertToVariable() 与 convertToConstant() - 以改变 SDVariable 类型 (Link)
为空数组上的缩减添加了检查和有用的异常(Link)
SameDiff“op creator”方法(SameDiff.tanh()、SameDiff.conv2d(…)等)已移动到子类-通过SameDiff.math()/random()/nn()/cnn()/rnn()/loss()方法或SameDiff.math/random/nn/cnn/rnn/loss字段访问creator (Link)
Libnd4j (c++) 增加基准测试框架 (Link)
添加了OpExecutioner.inspectArray(INDArray)方法以获取用于分析/调试的摘要统计信息(链接)
为Kotlin添加了SDVariable方法重载(plus, minus, times, 等) (Link)
增加了dot, reshape, permute的SDVariable便利方法 (Link)
添加SameDiff SDIndex.point(long, boolean keepDim)方法(将输出数组中的点索引保持为大小1轴) (Link)
添加了SameDiff ProtoBufToFlatBufConversion命令行工具,用于将TensorFlow冻结模型(protobuf)转换为SameDiff FlatBuffers (Link)
改进了SameDiff操作的数据类型验证 (Link)
ND4J数据类型-重大更改,请参阅本节顶部的突出显示
nd4j-base64 模块 (在 beta3中弃用) 已经被移除 . Nd4jBase64 类已被移到 nd4j-api (Link)
在指定执行沿维度操作(例如,缩减)的参数时,现在在操作构造函数中指定了缩减轴,而不是在OpExecutioner调用中单独指定。 (Link)
移除旧的基于Java循环的BooleanIndexing方法。应该使用等效的本地操作。 (Link)
移除 Nd4j.ENFORCE_NUMERICAL_STABILITY, Nd4j.copyOnOps, 等 (Link)
SameDiff“op creator”方法(SameDiff.tanh()、SameDiff.conv2d(…)等)已移动到子类-通过SameDiff.math()/random()/nn()/cnn()/rnn()/loss()方法或SameDiff.math/random/nn/cnn/rnn/loss字段访问creator (Link)
Nd4j.emptyLike(INDArray) 已被移除。使用 Nd4j.like(INDArray) 代替 (Link)
org.nd4jutil.StringUtils 被移除; 建议使用Apache commons lang3 StringUtils 代替 (Link)
nd4j-instrumentation 模块由于缺乏使用/维护而被移除 (Link)
修复了带有InvertMatrix.invert()和[1,1]形状矩阵的错误 (Link)
修复了长度为1的状态数组的更新器实例的边界情况缺陷 (Link)
修复了带有空文档的FileDocumentIterator的边界情况缺陷 (Link)
修复了1d矩阵上Nd4j.vstack返回1d输出而不是二维叠加输出的问题 (Link)
修复了Numpy格式导出的问题 - Nd4j.toNpyByteArray(INDArray)
(Link)
当SameDiff在外部工作区中使用时的修复 (Link)
修复了一个问题,即空NDarray可能被报告为具有标量形状信息,长度为1 (Link)
优化: libnd4j (c++) 操作的索引将在需要和可能的情况下使用uint进行更快的偏移计算。 (Link)
修复了INDArray.repeat在某些视图数组上的问题 (Link)
在视图阵列上执行某些操作的性能得到改进 (Link)
改进了non-EWS降维操作的性能 (Link)
改进转换操作的性能 (Link)
优化: 空数组只创建一次并缓存(因为它们是不可变的) (Link)
改进“reduce 3”缩减操作的性能 (Link)
改进了在多线程环境中对CUDA上下文的处理 (Link)
修复了Evaluation.reset()无法正确清除字符串类标签的问题 (Link)
SameDiff: 改进的梯度计算性能/效率;现在不再为非浮点变量定义“梯度”,也不再为不需要计算损失或参数梯度的变量定义“梯度” (Link)
IEvaluation实例的行为现在不再依赖于全局(默认)数据类型设置 (Link)
INDArray.get(point(x), y) 或 .get(y, point(x)) 现在,当对rank 2数组执行时,返回rank 1数组(Link)
为更好的性能和可靠性改写ND4J索引(INDArray.get)实现(Link)
本地响应归一化反向传播操作的修复 (Link)
大多数CustomOperation操作(如SameDiff中使用的操作)在下一版本之前都是CPU。未能及时完成1.0.0-beta4版本的GPU支持。
一些使用Intel Skylake CPU的用户报告说,当OMP_NUM_THREADS设置为8或更高时,MKL-DNN卷积2d反向传播操作(DL4J卷积层反向传播,ND4J“conv2d_bp”操作)出现死锁。调查表明,这可能是MKL-DNN的问题,而不是DL4J/ND4J的问题。见7637号问题。解决方法:通过ND4J_MKL_FALLBACK(见前面)禁用用于conv2d_bp操作的MKL-DNN,或对Skylake cpu全局禁用MKL-DNN。
LineRecordReader (和子类型)现在可以选择定义字符集 (Link)
添加 TokenizerBagOfWordsTermSequenceIndexTransform (TFIDF 转换), GazeteerTransform (单词表示的二进制向量) 与 MultiNlpTransform 转换; 添加 BagOfWordsTransform 接口 (Link)
修复了 ImageLoader.scalingIfNeeded 的问题(Link)
Arbiter 现在支持遗传算法搜索 (Link)
修复了Arbiter中使用的提前停止将导致序列化异常的问题 (Link)
ND4J/Deeplearning4j: 增加了对CUDA 10.0的支持。放弃了对CUDA 8.0的支持。(1.0.0-beta3版本支持CUDA 9.0、9.2和10.0)
SameDiff现在支持从DataSetIterator和MultiDataSetIterator进行训练和评估。 评估类已经被迁移到ND4J.
DL4J Spark 训练(梯度共享)现在是完全容错的,并且对阈值自适应(可能更稳健的收敛)有改进。现在可以在master/worker上轻松地独立配置端口。
使用用户指定的工作区添加了ComputationGraph/MultiLayerNetwork rnnTimeStep重载。 Link
添加 Cnn3DLossLayer Link
ParallelInference:实例现在可以实时更新模型(无需重新初始化) Link
ParallelInferenc: 添加 ParallelInference 的原地模式 Link
增加了对不兼容损失/激活函数组合的验证(如softmax+nOut=1, 或 sigmoid+mcxent)。可以使用outputValidation(false)禁用验证 Link
Spark 训练: 改进了梯度共享阈值自适应算法;使自定义阈值设置成为可能,并且使默认值对初始阈值配置更加健壮,在某些情况下提高了收敛速度。 Link
Spark 训练: 为大消息实现分块消息传递以减少内存需求(以及缓冲区长度不足问题) Link
Spark 训练: 添加 MeshBuildMode 配置用于提高了大型集群的可扩展性 Link
Spark 网络数据管道: 为“小文件”(图像等)分布式训练用例添加了FileBatch、FileBatchRecordReader等 Link
为容错/调试目的添加了FailureTestingListener Link
将Apache Lucene/Solr升级到7.5.0版(从7.4.0版) Link
MultiLayerNetwork/ComputationGraph.clearLayerStates 方法修改为 public (以前是 protected) Link
AbstactLayer.layerConf()
方法现在是 public Link
ParallelWrapper模块现在不再有artifact id的Scala版本后缀; 新 artifact id 是 deeplearning4j-parallel-wrapper
Link
在Yolo2OutputLayer中改进了无效输入/标签的验证和错误消息 Link
Spark 训练: 添加 SharedTrainingMaster.Builder.workerTogglePeriodicGC 和 .workerPeriodicGCFrequency 在worker上轻松配置ND4J垃圾回收配置。在worker上将默认GC设置为5秒 Link
Spark 训练: 添加了阈值编码调试模式(在训练 期间记录每个worker的当前阈值和编码统计信息)。使用 SharedTrainingConfiguration.builder.encodingDebugMode(true)
启用。注意这个操作有计算开销。 Link
修复了L1/L2和更新器(Adam、Nesterov等)在用小批量划分梯度以获得平均梯度之前被应用的问题。保持旧的行为,使用NeuralNetConfiguration.Builder.legacyBatchScaledL2(true)
Link.
请注意,对于某些更新器(如Adam)来说,学习率可能需要降低,以解释与早期版本相比的这一变化。其他一些更新器(如SGD、NoOp等)应该不受影响。
请注意,保存在1.0.0-beta2或更早版本中的反序列化(加载)配置/网络将默认为旧行为,以实现向后兼容性。所有新网络(在1.0.0-beta3中创建)将默认为新行为。
修复了EarlyStoppingScoreCalculator不能正确处理“最大分数”的情况而不是最小化的问题。 Link
修复VGG16ImagePreProcessor通道偏移值的顺序(BGR与RGB) Link
基于权值噪声的变分自编码缺陷修复 Link
修复BaseDataSetIterator 不遵循 'maximum examples' 配置的问题 Link
优化: 工作区现在用于计算图/多层网络评估方法(避免在计算期间分配必须由垃圾收集器清理的堆外内存) Link
修复了一个问题,即混洗与MnistDataSetIterator的子集组合在一起时,在重置之间不会保持相同的子集 Link
修复StackVertex.getOutputType的问题 Link
修复CNN到/从RNN预处理器处理掩码数组的问题 Link
修复了模型动物园中VGG16非预训练配置的问题 Link
修复 了TransferLearning nOutReplace中一行中多个层被修改的问题 Link
修复了CuDNN工作区在标准fit调用之外执行反向传播的问题 Link
修复了在计算图中的输出层上过早清除丢弃掩码的问题 Link
RecordReaderMultiDataSetIterator 现在支持5D数组 (用于 3D CNNs) Link
修复了TBPTT结合掩码和不同数量输入输出数组的多输入输出计算图中的缺陷 Link
改进了批归一化层的输入验证/异常 Link
修复了TransferLearning GraphBuilder nOutReplace与子采样层结合时的错误 Link
SimpleRnnParamInitializer 现在正确地遵循偏差初始化配置 Link
修复SqueezeNet动物园模型非预处理配置 Link
修复Xception动物园模型非预训练配置 Link
修正了多输出计算图的一些评估签名问题 Link
改进的大型网络多层网络/计算图汇总格式Link
修正了一个问题,如果一个层中的所有参数的梯度都恰好为0.0,那么梯度归一化可能导致NaNs Link
修复了MultiLayerNetwork/ComputationGraph.setLearningRate可能引发SGD和NoOp更新器异常的问题 Link
修复了StackVertex加掩码在某些罕见情况下的问题 Link
修复了1.0.0-alpha之前格式的冻结层的JSON反序列化问题 Link
修复了GraphBuilder.removeVertex在某些有限情况下可能失败的问题 Link
修复 CacheableExtractableDataSetFetcher中的缺陷 Link
DL4J Spark 训练:修复了多GPU训练+评估的线程/设备关联性问题 Link
DL4J Spark 训练: 使所有Aeron线程后台程序线程在所有其他线程完成时阻止Aeron停止JVM关闭 Link
为批归一化层添加了cudnnAllowFallback配置(如果CuDNN意外失败,则回滚到内置实现) Link
修复了BatchNormalization层的问题,该问题阻止在每个worker上正确同步平均值/方差估计值以进行GradientSharing训练,从而导致收敛问题Link
在ArchiveUtils中添加了检测ZipSlip CVE尝试的检查 Link
DL4J Spark 训练与评估:方法现在使用Spark上下文中的Hadoop配置来确保运行时集配置在Spark函数中可用,这些函数直接从远程存储(HDFS等)读取 Link
为Nd4j.readTxt添加了数据验证-现在对无效输入抛出异常,而不是返回不正确的值 Link
修复了KNN实现中使用无效距离函数(返回小于0的“距离”)时可能出现死锁的问题 Link
在加载Keras导入模型时添加了同步,以避免用于加载的底层HDFS库中出现线程安全问题 Link
修复了具有大预取值的Async(Multi)DataSetIterator的罕见问题 Link
DL4J中的IEvaluation类已被弃用并移到ND4J,以便它们可用于SameDiff训练。功能和api不变
MultiLayerConfiguration/ComputationGraphConfiguration pretrain(boolean)
与 backprop(boolean)
已被弃用并不再使用。使用 fit 和 pretrain/pretrainLayer 方法来替代 。 Link
ParallelWrapper 模块现在不再有artifact id的Scala版本后缀; 新 artifact id 为 deeplearning4j-parallel-wrapper
应用用来替代 Link
deeplearning4j-nlp-korean 模块现在有Scala版本后缀,由于scala依赖项; 新artifact ID 是 deeplearning4j-nlp-korean_2.10
与 deeplearning4j-nlp-korean_2.11
Link
在一个物理节点上同时运行多个Spark训练作业(即,来自一个或多个Spark作业的多个jvm)可能会导致网络通信问题。解决方法是在VoidConfiguration中手动设置唯一的流ID。对不同的作业使用唯一(或随机)整数值。 Link
修复了由于Keras 2.2.3+的Keras JSON格式更改而导致的导入问题 Link
为时间序列预处理添加了Keras导入 Link
Elephas Link
修复了在嵌入层之后使用导入模型变形的问题 Link
增加了对keras掩码层的支持 Link
修复了某些层/预处理器(如Permute)的JSON反序列化问题 Link
修正了Keras导入Nadam配置的问题 Link
添加了SameDiff训练和评估:SameDiff实例现在可以直接使用DataSetIterator和MultiDataSetIterator进行训练,并使用IEvaluation实例(已从ND4J移动到DL4J)进行评估 Link
添加了GraphServer实现:带有Java API的SameDiff(和Tensorflow,通过TF导入)的c++推理服务器 Link
为某些操作(Conv2d等)添加了MKL-DNN支持 Link
将ND4J(和DataVec)升级到Arrow 0.11.0 Link,它还修复了 Link
Added Nd4j.where 操作方法 (和 numpy.where同样的语义) Link
Added Nd4j.stack 操作方法 (combine arrays 结合数组 +把数组的rank加到1 ) Link
SameDiff损失函数:清除加正向传播实现 Link
CudaGridExecutioner 现在警告异常堆栈跟踪可能会延迟,以避免在异步执行操作期间调试异常时出现混淆 Link
JavaCPP 与 JavaCPP-presets 已更新到1.4.3版本 Link
改进SDVariable 类的 Javadoc Link
android端修复: 删除RawIndexer的使用 Link
Libnd4j 本地操作修复:
SameDiff: 改进的多输出情况下的错误处理 Link
修复了INDArray.permute无法正确抛出无效长度情况异常的问题 Link
DataSet.merge小修改 -现在签名可以接受任何DataSet的子类型 Link
INDArray.transposei 操作不是原地操作 Link
修复 INDArray.mmul 与 MMulTranspose的问题 Link
为ND4J创建方法添加了额外的顺序 验证(create, rand, 等) Link
修复从堆字节缓冲区反序列化时ND4J二进制反序列化(BinarySerde) 的问题 Link
Fixed issue with Nd4j-common ClassPathResource path resolution in some IDEs修复了某些ide中Nd4j-common ClassPathResource路径解析的问题 Link
修复了rank 1数组上的INDArray.get(interval)将返回rank 2数组的问题 Link
INDArray.assign(INDArray) 不再允许指定不同的形状数组(标量/矢量情况除外) Link
NDarrayStrings (and INDArray.toString()) 现在格式化数字时总是使用US语言环境Link
修正了V100 GPU特有的GaussianDistribution问题 Link
修复了V100 GPU特有的位图压缩/编码问题 Link
Transforms.softmax 现在在不支持的形状上抛出错误,而不是简单地不应用操作 Link
VersionCheck 功能: 处理在早期版本的Android上没有SimpleFileVisitor的情况 Link
SameDiff 卷积层配置 (Conv2dConfig/Conv3dConfig/Pooling3dConfig 等)已将参数名对齐 Link
CUDA 8.0支持已被删除。CUDA 9.0、9.2和10.0支持在1.0.0-beta3中提供
nd4j-base64模块内容已被弃用;从现在起使用nd4j-api中的等效类 Link
nd4j jackson模块中的某些类已被弃用;从现在起使用nd4j-api中的等效类 Link
Android 可能需要手动排除掉模块 nd4j-base64(现在已弃用)。这是由于org.nd4j.serde.base64.Nd4jBase64
类在 nd4j-api 和 nd4j-base64 模块中都出现了。两个版本都有相同的内容。使用 exclude group: 'org.nd4j', module: 'nd4j-base64'
来排除。
为org.opencv.core.Mat和字符串文件名添加了NativeImageLoader方法重载 Link
修复JDBCRecordReader对空值的处理 Link
改进了针对无效输入的ObjectDetectionRecordReader的错误/验证(如果图像对象中心位于图像边界之外) Link
修复了使用在早期版本的Android上不可用的方法进行FileSplit的问题Link
修复了JDBCRecordReader处理实数值列结果类型的问题 Link
添加了CSVRecordReader/LineRecordReader在未初始化的情况下使用的验证和有用异常 Link
修正了一些与dropout层有关的问题 Link
添加了org.nd4j.linalg.primitives.Pair/Triple和Scala元组之间的转换 Link
ND4J/Deeplearning4j: 增加了对CUDA9.2的支持。放弃对CUDA 9.1的支持。(1.0.0-beta2版本支持CUDA 8.0、9.0和9.2)
Deeplearning4j 资源(数据集、预训练模型)存储目录现在可以通过 DL4JResources.setBaseDirectory
方法或 org.deeplearning4j.resources.directory
系统属性
ND4J: 现在所有索引都是用long而不是int来完成的,以使数组的维数和长度大于整数(大约 21亿)。
ND4J: 现在将使用Intel MKL-DNN作为默认/捆绑的BLAS实现(替换以前默认的 OpenBLAS)
Deeplearning4j: 添加了内存溢出(OOM)崩溃转储报告功能。在训练 /推理OOM时提供内存使用和配置转储(以帮助调试和调整内存配置)。
资源(数据集、预训练模型)存储目录现在可以通过 DL4JResources.setBaseDirectory
方法或 org.deeplearning4j.resources.directory
系统属性。请注意,还可以为下载设置不同的基本位置(用于DL4J资源的本地镜像) Link
添加了内存溢出(OOM)崩溃转储报告功能。如果训练/推理OOM,则提供内存使用和配置的转储。对于MultiLayerNetwork/ComputationGraph.memoryInfo方法,也提供了相同的信息(没有崩溃)。可以使用系统属性禁用(或输出目录设置) - Link
添加了复合的[Multi]DataSetPreProcessor,以便在单个迭代器中应用多个[Multi]DataSetPreProcessor Link
多输出网络的附加计算图评估方法: evaluate(DataSetIterator, Map<Integer,IEvaluation[]>)
与 evaluate(MultiDataSetIterator, Map<Integer,IEvaluation[]>)
Link
添加了JointMultiDataSetIterator-用于从多个DataSetIterator创建多个MultiDataSetIterator的实用迭代器 Link
GraphVertices现在可以直接使用可训练参数(不只是用可训练参数封闭层) Link
添加 MultiLayerNetwork/ComputationGraph getLearningRate 方法 Link
增加了周期性的“1周期”学习率调度等 - Link
Spark训练的RDD重新分区更具可配置性(adds Repartitioner 接口) Link
添加 ComputationGraph.getIterationCount() 与 .getEpochCount() 为了与 MultiLayerNetwork保持一致 Link
Spark 评估: 添加了允许指定评估worker数量(小于Spark线程数)的评估方法重载 Link
CnnSentenceDataSetIterator 现在有一个Format参数,支持RNNs和1D CNNs的输出数据 Link
添加 ComputationGraph/MultiLayerNetwork.pretrain((Multi)DataSetIterator, int epochs)
方法重载 Link
EmbeddingSequenceLayer现在除了支持 [minibatch,seqLength]
格式数据外还支持 [minibatch,1,seqLength]
格式序列数据 Link
CuDNN 批归一化实现现在将用于rank 2输入,而不仅仅是rank 4输入 Link
MultiLayerNetwork 与 ComputationGraph output/feedForward/fit 方法现在是线程安全的,通过同步来实现。注意,由于性能的原因,不建议并发使用(相反:使用并行推理);但是现在同步的方法应该避免由于并发修改而导致的模糊错误 Link
BarnesHutTSNE 现在在距离度量未定义的情况下抛出一个有用的异常(例如,所有零加上余弦相似性) Link
BatchNormalization 层现在正确地断言,如果需要,将设置nOut(而不是以后出现不友好的形状错误) Link
修正了OutputLayer不能正确初始化参数约束的问题 Link
修正了Nesterov更新器在执行CUDA时只使用CPU操作的性能问题 Link
删除了DL4J优化器的TerminationCondition-在实践中未使用,并且有小的开销 Link
修复了启用工作区时EvaluativeListener可能遇到工作区验证异常的问题 Link
修复了计算图未正确调用TrainingListener.onEpochStart/onEpochEnd的问题 Link
修复了TensorFlowCnnToFeedForwardPreProcessor工作区问题 Link
CuDNN批量归一化的性能优化 Link
性能优化:在安全的情况下,Dropout将会被原地应用,避免复制。 Link
增加了Dropout的CuDNN实现 Link
减少CuDNN的内存使用:CuDNN工作内存现在在网络中的层之间共享和重用 Link
CuDNN批处理归一化实现将失败,数据类型为FP16 Link
修复双向LSTM可能错误地使用导致异常的工作区问题 Link
修复早停,分数最大化(准确率,F1等)没有正确触发终止条件的问题 Link
修复了在ComputationGraph.computeGradientAndScore()中标签掩码计数器可能错误递增的问题 Link
ComputationGraph 在训练期间没有设置lastEtlTime 字段 Link
修复了启用工作区时自动编码器层的问题Link
修复了EmbeddingSequenceLayer使用掩码数组的问题 Link
Lombok现在在任何地方都提供了作用域,在使用DL4J时不在用户类路径上 Link
修复了WordVectorSerializer.readParagraphVectors(File)初始化标签源的问题Link
Spark 训练(梯度共享)现在可以正确地处理训练期间遇到的空分区边界情况 Link
对于Spark梯度共享训练,错误传播得更好/更一致 Link
修复了1D CNN层的问题,该层具有遮罩阵列且stride>1(遮罩未正确缩小) Link
DL4J批归一化实现仅在训练期间(CuDNN未受影响),在推理期间未正确添加epsilon值 Link
当最大非填充值小于0时,具有最大池和ConvolutionMode.SAME的CUDNN子采样层可能把填充值(0)作为边界值的最大值。 Link
Spark梯度共享训练现在可以将监听器正确地传递给worker Link
修复了用户界面和FileStatsStorage的罕见(非终端)并发修改问题 Link
CuDNN 卷积层现在支持dilation>2(以前:使用DL4J conv层实现作为回调) Link
Yolo2OutputLayer 现在实现 computeScoreForExamples() Link
SequenceRecordReeaderDataSetIterator 现在正确处理“没有标签”的情况Link
修复了BarnesHutTSNE可能遇到工作区验证异常的问题 Link
在某些情况下,EMNIST迭代器在重置后可能会产生不正确的数据Link
由于缺少CuDNN支持,Graves LSTM已被弃用,取而代之的是LSTM,但其精度与实际情况类似。改用LSTM类。
deeplearning4j-modelexport-solr: 现在使用Lucene/Solr版本7.4.0(以前是7.3.0) Link
CNN2d层的掩码数组必须是可广播的4d格式: [minibatch,depth or 1, height or 1, width or 1]
- 以前是二维的形状 [minibatch,height]
或 [minibatch,width]
。 这可以防止以后的情况(池化层)中的模糊性,并允许更复杂的掩码场景(例如在同一个小批量中为不同的图像大小进行掩码 )。 Link
一些旧的/不推荐使用的模型和层方法已被删除。(validateInput(), initParams())。因此,可能需要更新某些自定义层 Link
Windows用户无法加载SvhnLabelProvider(在HouseNumberDetection示例中使用)中使用的HDF5文件。Linux/Mac用户不受影响。windows用户的解决方法是添加sonatype快照依赖项org.bytedeco.javacpp-presets:hdf5-platform:jar:1.10.2-1.4.3-SNAPSHOT
Link
Keras 模型导入现在导入每个Keras应用程序
支持GlobalPooling3D图层导入
支持RepeatVector层导入
支持LocallyConnected1D和LocallyConnected2D层
现在可以通过注册自定义SameDiff层导入Keras Lambda层
现在支持所有Keras优化器
现在可以导入所有高级激活功能。
许多小错误已经被修复,包括对所有的BatchNormalization配置进行适当的权重设置,改进SeparableConvolution2D的形状,以及完全支持双向层。
ND4J: all indexing is now done with longs instead of ints to allow for arrays with dimensions and lengths greater than Integer.MAX_VALUE (approx. 2.1 billion)现在所有索引都是用long而不是int来完成的,以使数组的维数和长度大于整数(大约为 21亿)。
添加了使用Nd4j.writesNumpy(INDArray,File)编写Numpy.npy格式的功能,并使用Nd4j.convertToNumpy(INDArray)将INDArray转换为内存中严格的Numpy格式Link
ND4j-common ClassPathResource: 添加 ClassPathResource.copyDirectory(File) Link
SameDiff: 现有大量新操作和已存在操作的反向传播实现
添加 Nd4j.randomBernoulli/Binomial/Exponential 便捷方法 Link
添加了禁用/禁止ND4J初始化日志记录的方法 通过 org.nd4j.log.initialization
系统属性 Link
SameDiff 类 - 大多数op/constructor方法现在都有完整/有用的javadoc Link
现在可以全局禁用工作区,忽略工作区配置。这主要用于调试;使用 Nd4j.getWorkspaceManager().setDebugMode(DebugMode.DISABLED)
或 Nd4j.getWorkspaceManager().setDebugMode(DebugMode.SPILL_EVERYTHING);
来启用它。 Link [Link]
为环境变量处理添加了EnvironmentalAction API Link
SameDiff: 执行和单个操作的大量错误修复
修复了使用真标量的INDArray.toDoubleArray()的问题(rank 0数组)Link
修复了DataSet.sample()不适用于rank 3+功能的问题 Link
IActivation 现在实现对激活和梯度验证/强制执行相同的形状Link
修正了向量为1d的muliColumnVector问题 Link
ImagePreProcessingScaler现在支持通过NormalizerSerializerStrategy和ModelSerializer进行序列化 Link
DL4J Spark梯度共享分布式训练实现中阈值编码的性能优化 Link
SameDiff: DL4J Spark梯度共享分布式训练实现中阈值编码的性能优化 Link
DataSet.save() 与 MultiDataSet.save() 方法现在出现时保存示例元数据 Link
修正了当数据集不等分为没有余数的包时KFoldIterator的问题 Link
修复了当资源位于带有空格的路径上时版本检查功能无法加载资源的问题 Link
CUDA 9.1支持已被移除。提供CUDA 8.0、9.0和9.2支持
由于长索引的变化,在某些地方应该使用 long/long[]代替int/int[]。(如 INDArray.size(int), INDArray.shape())
简化DataSetIterator API: totalExamples(), cursor() 与 numExamples() - 这些在大多数DataSetIterator实现中都是不受支持的,并且在实践中没有用于训练。自定义迭代器也应该删除这些方法 Link
已删除长期不推荐使用的getFeatureMatrix()。改用DataSet.getFeatures()。 Link
已删除未使用且未正确测试/维护的实用程序类BigDecimalMath。如果需要,用户应该为这个功能找到一个候选库。
未正确维护的复数支持类(IComplexNumber,IComplexNDArray)已完全删除 Link
添加了AnalyzeLocal类以镜像AnalyzeSpark的功能(但没有Spark依赖项) Link
添加了JacksonLineSequenceRecordReader:RecordReader用于多示例JSON/XML,其中文件中的每一行都是一个独立的示例Link
添加 RecordConvert.toRecord(Schema, List<Object>)
Link
添加缺失的 FloatColumnCondition Link
为“CSV中的每一行是一个序列,序列是单值/单变量”添加了CSVLineSequenceRecordReader Link
为“单个CSV中的多个多值序列”数据添加了CSVMultiSequenceRecordReader Link
修复了Android上NativeImageLoader的问题 Link
已修复ExcelRecordReader的问题 Link
修复了 CSVRecordReader.next(int)
参数错误的问题,可能导致生成不必要的大列表 Link
添加了DataSource接口。与旧的DataProvider不同,这不需要JSON序列化(只需要一个无参数构造函数) Link
DataProvider已被弃用。改用DataSource。
DL4J的性能和内存优化
添加 ComputationGraph.output(DataSetIterator) 方法 Link
添加 SparkComputationGraph.feedForwardWithKey 具有特征掩码支持的重载 Link
添加了从配置获取每个层的输入/激活类型的支持:ComputationGraphConfiguration.getLayerActivationTypes(InputType...)
, ComputationGraphConfiguration.GraphBuilder.getLayerActivationTypes()
, NeuralNetConfiguration.ListBuilder.getLayerActivationTypes()
, MultiLayerConfiguration.getLayerActivationTypes(InputType)
methods Link
Evaluation.stats() 现在以更易于读取的矩阵格式而不是列表格式打印混淆矩阵 Link
添加ModelSerializer.addObjectToFile, .getObjectFromFile and .listObjectsInFile 用于将任意Java对象存储在与保存的网络相同的文件中 Link
添加了SpatialDropout支持(具有Keras导入支持) Link
添加MultiLayerNetwork/ComputationGraph.fit((Multi)DataSetIterator, int numEpochs)
重载 Link
增加了性能(硬件)监听器: SystemInfoPrintListener
与 SystemInfoFilePrintListener
Link
通过优化工作区的内部使用来优化性能和内存 Link
RecordReaderMultiDataSetIterator 将不再尝试将未使用的列转换为数值 Link
新增动物园模型:
(将要做 )
修正了RecordReaderMulitDataSetIterator,其中某些构造函数的输出可能不正确 Link
修复了计算图拓扑排序在所有平台上可能不一致的问题;有时可能中断在PC上训练并部署在Android上的计算图(具有多个有效拓扑排序) Link
修复了CuDNN批归一化的问题,使用 1-decay
替代 decay
Link
deeplearning4j-cuda 如果在将nd4j-native后端设置为更高优先级的类路径上出现异常,则不再引发异常 Link
为CifarDataSetIterator添加了RNG控件 Link
WordVectorSerializer完成后立即删除临时文件 Link
WorkspaceMode.SINGLE 与 SEPARATE 已经被弃用; 使用 WorkspaceMode.ENABLED 代替
内部层API更改:自定义层将需要更新为新的层API-请参阅内置层或自定义层示例
由于JSON格式更改,需要先注册1.0.0-beta JSON(ModelSerializer)之前格式的自定义层等,然后才能对其进行反序列化。保存在1.0.0-beta或更高版本中的内置层和模型不需要这样做. 使用 NeuralNetConfiguration.registerLegacyCustomClassesForJSON(Class)
来实现这个目的
IterationListener已被弃用,取而代之的是TrainingListener。对于现有自定义侦听器, 从 implements TrainingListener
切换到 extends BaseTrainingListener
Link
ExistingDataSetIterator 已弃用;使用 fit(DataSetIterator, int numEpochs)
method instead方法代替
ComputationGraph TrainingListener onEpochStart 与 onEpochEnd 未正确调用方法
DL4J Zoo Model FaceNetNN4Small2 未正确调用方法
早停计分计算器的值THAR应最大化(准确性,F1等)不正常工作(值被最小化,而不是最大化)。解决方法:重写ScoreCalculator.calculateScore(...)
并返回 1.0 - super.calculateScore(...)
.
并非所有的操作梯度都实现了自动微分
在1.0.0-beta版本中添加的绝大多数新操作尚未使用GPU。
ImageRecordReader 现在记录推理的标签类的数量(以减少用户在错误配置时丢失问题的风险) Link
为多个列添加了AnalyzeSpark.getUnique重载Link
增加了性能/计时模块 Link
通过缓冲区重用减少ImageRecordReader垃圾生成 Link
删除了DataVec中使用的反射库 Link
TransformProcessRecordReader批处理支持修复 Link
带过滤器操作的TransformProcessRecordReader修复 Link
修复了ImageRecordReader/ParentPathLabelGenerator错误过滤包含 .
character(s) 的目录Link
ShowImageTransform现在延迟初始化帧以避免出现空白窗口 Link
DataVec ClassPathResource 已被弃用; 使用 nd4j-common 版本替代 Link
为OCNN(一分类神经网络)添加LayerSpace
修复了可能导致在UI中不正确呈现第一个模型结果的时间戳问题 Link
在遇到终止条件时返回之前,执行现在等待最后一个模型完成Link
根据DL4J等:反射库的使用已完全从Arbiter中删除 Link
由于Android编译的问题,不再使用Eclipse集合库 Link
对已完成模型的改进清理以减少对训练的最大内存需求Link
ND4J:添加了SameDiff-Java自动微分库(alpha版本)和Tensorflow导入(技术预览)以及数百个新操作
ND4J: 增加了CUDA 9.0和9.1支持(使用cuDNN),放弃了对CUDA 7.5的支持,继续支持CUDA 8.0
ND4J: 本机二进制文件(Maven Central上的nd4j-native)现在支持AVX/AVX2/AVX-512(Windows/Linux)
DL4J: 大量新层和API改进
DL4J: Keras 2.0 导入支持
Layers (新的和增强的)
添加 Yolo2OutputLayer CNN 层用于目标检测 (Link). 同样查看 DataVec的 ObjectDetectionRecordReader
添加通过 hasBias(boolean)
实现对“无偏置”层的支持
config (DenseLayer, EmbeddingLayer, OutputLayer, RnnOutputLayer, CenterLossOutputLayer, ConvolutionLayer, Convolution1DLayer). EmbeddingLayer 现在默认为无偏置 (Link)
增加了对空洞卷积(也称为“膨胀”卷积)的支持- ConvolutionLayer, SubsamplingLayer, 和 1D 版本 。 (Link)
ElementWiseVertex 现在(另外) 支持 Average
和 Max
模式,除Add/Subtract/Product 外(Link)
添加 SeparableConvolution2D 层 (Link)
添加 Deconvolution2D 层 (也叫转置卷积,分步卷积层) (Link)
添加ReverseTimeSeriesVertex (Link)
添加 RnnLossLayer - 无参数版本的RnnOutputLayer,或相当于RNN的LossLayer (Link)
添加了CnnLossLayer-没有参数CNN输出层,用于分割、去噪等用例。 (Link)
Added Bidirectional layer wrapper (converts any uni-directional RNN to a bidirectional RNN) 添加了双向层包装器(将任何单向RNN转换为双向RNN)(Link)
添加SimpleRnn层(也叫 "香草" RNN 层) (Link)
添加了LastTimeStep包装层(包装一个RNN层以获取上一个时间步,如果存在则考虑掩码)(Link)
添加了MaskLayer实用程序层,当存在一个掩码数组时,该实用程序层仅在正向传递时将激活归零 (Link)
添加了alpha版本(还不稳定)SameDiff层对DL4J的支持(注意:向前传递,目前仅限于CPU)(Link)
添加Cropping2D 层 (Link)
添加了参数约束API(LayerConstraint接口), 和 MaxNormConstraint, MinMaxNormConstraint, NonNegativeConstraint, UnitNormConstraint 实现 (Link)
学习率调度的显著重构 (Link)
添加了ISchedule接口;添加了Exponential、Inverse、Map、Poly、Sigmoid和Step调度实现(Link)
Added support for both iteration-based and epoch-based schedules via ISchedule. Also added support for custom (user defined) schedules通过ISchedule增加了对基于迭代和基于轮数的计划的支持。还添加了对自定义(用户定义)计划的支持
在更新程序上配置学习率调度, 通过 .updater(IUpdater)
方法
添加了dropout API(IDropout-以前的dropout是可用的,但不是类);添加了Dropout、AlphaDropout(用于自归一化网络)、GaussianDropout(乘法)、GaussianNoise(加法)。添加了对自定义dropout类型的支持 (Link)
通过ISchedule接口增加了对辍学调度的支持 (Link)
增加了权重/参数噪声API(IWeightNoise接口);增加了DropConnect和WeightNoise(加法/乘法高斯噪声)实现(Link);DropConnect和dropout现在可以同时使用
添加层配置别名 .units(int)
相当于 .nOut(int)
(Link)
添加 ComputationGraphConfiguration GraphBuilder .layer(String, Layer, String...)
的别名 .addLayer(String, Layer, String...)
不再需要层索引 MultiLayerConfiguration ListBuilder (i.e., .list().layer(<layer>)
现在可以用于配置) (Link)
添加 MultiLayerNetwork.summary(InputType)
和 ComputationGraph.summary(InputType...)
方法(显示层和激活大小信息) (Link)
MultiLayerNetwork, ComputationGraph 分层可训练层现在可以跟踪轮数量 (Link)
添加deeplearning4j-ui-standalone 模块: uber-jar 方便用户界面服务器的启动 (使用: java -jar deeplearning4j-ui-standalone-1.0.0-alpha.jar -p 9124 -r true -f c:/UIStorage.bin
)
权重初始化:
添加 .weightInit(Distribution)
方便/重载(之前:必需.weightInit(WeightInit.DISTRIBUTION).dist(Distribution)
) (Link)
WeightInit.NORMAL (用于自归一化神经网络) (Link)
Ones, Identity 权重初始化 (Link)
添加新分布(LogNormalDistribution, TruncatedNormalDistribution, OrthogonalDistribution, ConstantDistribution) 可用于权重初始化 (Link)
RNNs: 增加了将循环网络权重的权重初始化分别指定为“输入”权重的功能(Link)
添加的层别名: Convolution2D (ConvolutionLayer), Pooling1D (Subsampling1DLayer), Pooling2D (SubsamplingLayer) (Link)
增加了Spark IteratorUtils-包装了一个用于Spark网络训练的RecordReaderMultiDataSetIterator (Link)
CuDNN支持层(卷积层等)现在警告用户如果使用没有CuDNN的CUDA (Link)
二进制交叉熵(LossBinaryXENT)现在实现了裁剪(默认情况下为1e-5到(1-1e-5))以避免数值下溢/NaNs (Link)
SequenceRecordReaderDataSetIterator 现在支持多标签回归(Link)
TransferLearning FineTuneConfiguration 现在有了设置训练/推理工作区模式的方法 (Link)
IterationListener iterationDone 方法现在同时报告当前迭代和epoch计数;删除了不必要的调用/被调用方法 (Link)
添加 MultiLayerNetwork.layerSize(int), ComputationGraph.layerSize(int)/layerSize(String) 轻松确定层的大小 (Link)
添加 MultiLayerNetwork.toComputationGraph() 方法 (Link)
添加 NetworkUtils 易于更改已初始化网络的学习速率的方便方法 (Link)
添加 MultiLayerNetwork.save(File)/.load(File) 和 ComputationGraph.save(File)/.load(File) 方便方法 (Link)
添加 CheckpointListener 在训练期间定期保存模型副本(每N个iter/epoch,每T个时间单位) (Link)
添加带掩码数组的ComputationGraph输出方法重载 (Link)
一种新的LossMultiLabel损失函数 (Link)
增加了用于早停的额外评分功能(ROC指标、全套评估/回归指标等) (Link)
为多层网络和计算图添加额外的ROC和ROCMultiClass评估重载 (Link)
阐明Evaluation.stats()输出以引用“预测”而不是“示例”(前者更适合于RNN) (Link)
EarlyStoppingConfiguration 现在支持 Supplier<ScoreCalculator>
用于非序列化分数计算器 (Link)
改进的通过错误方法加载模型时的ModelSerializer异常 (例如 尝试通过restoreMultiLayerNetwork加载计算图) (Link)
添加了SparkDataValidation实用程序方法来验证HDFS或本地上保存的DataSet和MultiDataSet (Link)
ModelSerializer: 添加 restoreMultiLayerNetworkAndNormalizer and restoreComputationGraphAndNormalizer 方法(Link)
ParallelInference 现在有了支持输入掩码数组的输出重载 (Link)
Lombok不再作为传递依赖项包含 (Link)
具有大量历史记录的J7FileStatsStorage的性能改进 (Link)
修复变分自动编码器层的用户界面层大小 (Link)
UI Play servers 切换到生产(PROD)模式(Link)
与以上相关:用户现在可以设置 play.crypto.secret
系统属性手动设置播放应用程序密钥;默认情况下是随机生成的 (Link).
SequenceRecordReaderDataSetIterator 会应用预处理器两次 (Link)
Evaluation 在Spark上使用时,无参构造函数可能导致NaN评估度量
CollectScoresIterationListener可能以无休止地重复 (Link)
Async(Multi)DataSetIterator 调用 reset() 在某些情况下,底层迭代器可能会导致问题(Link)
在某些情况下,L2正则化可能(错误地)应用于冻结层 (Link)
NearestNeighboursServer的日志修复 (Link)
BaseStatsListener内存优化 (Link)
用于从流加载Keras模型的ModelGuesser修复(以前可能会失败)(Link)
修复DuplicateTimeSeriesVertex中的错误条件 (Link)
修复某些有效计算图网络上的getMemoryReport异常 (Link)
RecordReaderDataSetIterator 当与预处理器一起使用时,在某些情况下可能会导致异常 (Link)
CnnToFeedForwardPreProcessor 只要输入数组长度与预期长度匹配,就可以悄悄地对无效输入变形 (Link)
ModelSerializer如果JVM崩溃,将不会删除临时文件;现在不再需要时立即删除 (Link)
RecordReaderMultiDataSetIterator 在某些情况下,当设置为“ALIGN_END”模式时,不能添加掩码数组 (Link)
ConvolutionIterationListener 以前在冻结所有卷积层时产生的IndexOutOfBoundsException(Link)
PrecisionRecallCurve.getPointAtRecall 当多个点具有相同的召回率时,可以以正确但次优的精度返回一个点(Link)
如果dropout存在于现有层上,在迁移学习上设置dropout(0),FineTuneConfiguration不会删除dropout。 (Link)
在某些罕见的情况下,Spark评估可能导致NullPointerException (Link)
ComputationGraph: 配置验证中不总是检测到断开连接的顶点(Link)
激活层并不总是继承全局激活函数配置 (Link)
PerformanceListener 现在可序列化 (Link)
ScoreIterationListener 与 PerformanceListener 现在报告模型迭代,而不是“自监听器创建以来的迭代” (Link)
合并ROC实例后,不能更新ROC类中的精度/回调曲线缓存值 (Link)
在评估大量实例之后,ROC合并可能会产生IllegalStateException(Link)
向EmbeddingLayer添加了对无效输入索引的检查(Link)
修复了从JSON加载遗留(0.9.0以前版本)模型配置时可能出现的NPE问题 (Link)
修复了EvaluationCalibration HTML导出图表呈现的问题 (Link)
修复了与Spark训练一起使用时使用J7FileStatsStorage可能不正确呈现UI/StatsStorage图表的问题(Link)
MnistDataSetIterator 不会总是可靠地检测并自动修复/重新下载损坏的下载数据 (Link)
修复线程中断的传播 (Link)
修复TSNE将数据发布到UI以进行可视化 (Link)
PerformanceListener现在对无效的频率参数抛出一个有用的异常(在构造函数中),而不是运行时ArithmeticException (Link)
RecordReader(Multi)DataSetIterator 现在,Writable值为非数值时抛出更有用的异常(Link)
UI: 修复了从uber JARs读取国际化数据.txt文件时非英语语言可能出现的字符编码问题(Link)
UI: 修复了加载I18N数据时试图错误解析非DL4J UI资源的问题 (Link)
各种线程修复 (Link)
Evaluation: 现在无参方法(f1(),precion()等)为二分类返回单个类值,而不是宏平均值;请澄清stats()方法和javadoc中的值 (Link)
早停训练:未正确调用TrainingListener opEpochStart/End(等)方法 (Link)
修复了不总是将dropout应用于RNN层输入的问题(Link)
ModelSerializer:改进了从无效/空/关闭流中读取数据时的验证/异常 (Link)
基于就地随机操作的网络权值初始化内存优化 (Link)
修正了VariationalAutoencoder builder解码器层大小验证(Link)
改进的Kmeans吞吐量link
将RPForest添加到邻近值算法 link
对于多层网络和计算图,缺省训练工作空间模式已从NONE切换到SEPARATE。 (Link)
行为变更: fit(DataSetIterator)
类似的方法不再进行分层预训练,然后进行反向传播。用于预训练,使用 pretrain(DataSetIterator)
和 pretrain(MultiDataSetIterator)
方法 (Link)
以前不推荐的更新器配置方法 (.learningRate(double)
, .momentum(double)
etc) 全被删除
配置学习率: 使用 .updater(new Adam(lr))
代替.updater(Updater.ADAM).learningRate(lr)
配置偏置学习率: 使用 .biasUpdater(IUpdater)
方法
配置学习率调度: 使用 use .updater(new Adam(ISchedule))
通过枚举更新器配置(i.e., .updater(Updater)
) 已弃用;使用 .updater(IUpdater)
.regularization(boolean)
已删除配置;功能现在总是等同于.regularization(true)
.useDropConnect(boolean)
移除;使用 .weightNoise(new DropConnect(double))
代替
.iterations(int)
方法已被删除(很少使用,用户感到困惑)
多个实用程序类 (在 org.deeplearning4j.util
) 已弃用和/或移至 nd4j-common. 在nd4j common中使用相同的类名org.nd4j.util
代替。
DL4J中的DataSetIterators已从deeplearning4j nn模块移动到新的deeplearning4j-datasets, deeplearning4j-datavec-iterators 和 deeplearning4j-utility-iterators。包/导入保持不变;deeplearning4j-core将它们作为可传递的依赖项引入,因此在大多数情况下不需要用户更改。 (Link)
以前已弃用 .activation(String)
已被移除;使用 .activation(Activation)
或 .activation(IActivation)
代替
层API更改:自定义层可能需要实现applyConstraints(int iteration, int epoch)
方法
参数初始值设定项API更改:自定义参数初始值设定项可能需要实现isWeightParam(String)
和 isBiasParam(String)
方法
RBM (受限制的玻尔兹曼机器)层已完全移除。考虑使用VariationalAutoencoder作为替换 (Link)
GravesBidirectionalLSTM 已被弃用;使用 new Bidirectional(Bidirectional.Mode.ADD, new GravesLSTM.Builder()....build()))
代替
以前不推荐的WordVectorSerializer方法现在已被删除 (Link)
删除 deeplearning4j-ui-remote-iterationlisteners 模块并废弃 RemoteConvolutionalIterationListener (Link)
与0.9.1(配置了工作区)相比,CUDA上某些网络类型的性能可能会降低。这将在下一个版本中解决
在对CUDA的FP16支持下发现了一些问题 (Link)
Keras 2支持,保持Keras 1的向后兼容性
Keras 2和1导入使用完全相同的API,由DL4J推断
Keras单元测试覆盖率增加了10倍,更多的真实集成测试
用于导入和检查层权重的单元测试
Leaky ReLU, ELU, SELU 支持模型导入
所有Keras层都可以用可选的偏置项导入
删除旧的deeplearning4j keras模块,删除旧的“模型”API
所有Keras初始化 (Lecun normal, Lecun uniform, ones, zeros, Orthogonal, VarianceScaling, Constant) 被支持
DL4J和Keras模型导入支持的1D卷积和池化
Keras模型导入支持的一维和二维空洞卷积层
支持1D 零填充层
DL4J和模型导入完全支持Keras约束模块
DL4J和Keras模型导入中的1D和2D层上采样(包括测试中的GAN示例)
Keras模型导入支持的大多数合并模式,支持Keras 2合并层API
DL4J与Keras模型导入支持可分离卷积二维层
DL4J和Keras模型导入支持二维反卷积
导入时完全支持Keras噪声层(Alpha dropout、Gaussian dropout和noise)
在Keras模型导入中支持SimpleRNN层
支持双向层wrapper keras模型导入
在DL4J中添加LastTimestepVertex以支持Keras RNN层的return_sequences=False。
DL4J支持循环权重初始化和Keras导入集成。
SpaceToBatch和BatchToSpace层在DL4J中提供更好的YOLO支持,加上端到端的YOLO Keras导入测试。
DL4J和Keras模型导入中的Cropping2D支持
在0.9.1中已弃用 Model
和 ModelConfiguration
已被永久删除。使用 KerasModelImport 代替, 它现在是Keras模型导入的唯一入口点。
Embedding layer: 在DL4J中,嵌入层的输出默认为2D,除非指定了预处理器。在Keras中,输出总是3D的,但是根据指定的参数可以解释为2D。这通常会导致在导入嵌入层时出现困难。许多情况已得到处理,问题已得到解决,但仍有不一致之处。
Batchnormalization layer: DL4J的批归一化层比Keras的版本要严格得多(一种好的方式)。例如,DL4J仅允许对4D卷积输入的空间维度进行归一化,而在Keras中,任何轴都可以用于归一化。根据维度顺序(NCHW对比NHWC)和Keras用户使用的特定配置,这可能会导致预期的(!) 以及意外的导入错误。
在DL4J中导入用于训练 目的的Keras模型的支持仍然非常有限(enforceTrainingConfig == true),将在下一版本中正确处理。
Keras Merge layers:Keras functional API似乎很好用,但是在Sequential模型中使用时会出现问题。
Reshape layers: 在导入时可能有点不可靠。DL4J很少需要在标准输入预处理器之外显式地重塑输入。在路缘石中,经常使用重塑层。在边缘情况下,映射这两种范式可能很困难。
新增数百项新操作
具有自动微分功能的新DifferentialFunctionapi(参见SameDiff一节) Link
新增tensorflow导入技术预览(支持1.4.0及以上版本)
增加了Apache Arrow序列化,支持新的tensor API Link
增加对AVX/AVX2和AVX-512指令集的支持,适用于Windows/Linux的nd4j-native Link
Nvidia CUDA 8/9.0/9.1 现在被支持
引入了工作区改进以确保安全:默认情况下启用SCOPE_PANIC profiling模式
INDArray serde的FlatBuffers支持
添加了对自动广播操作的支持
libnd4j, underlying c++ library, 功能增强,现在提供: NDArray 类, Graph 类, 并可以用作独立库或可执行文件。
卷积相关操作现在除了支持NCHW数据格式外,还支持NHWC。
累积操作现在可以选择保持缩小的维度。
并非所有的操作梯度都实现了自动微分
在1.0.0-alpha中添加的绝大多数新操作尚未使用GPU。
初始技术预览 Link
IF和WHILE原生支持控制流
用于ND4J的SameDiff自动微分引擎的Alpha发布。
有两种可用的执行模式:Java驱动的执行和序列化图的本机执行。
SameDiff图可以使用FlatBuffers序列化
从SameDiff操作生成并运行计算图。
图可以对输入数据运行正向传播,并计算反向传播的梯度。
已经支持许多高级层,如密集连层、卷积(1D-3D)反卷积、可分离卷积、池和上采样、批处理归一化、局部响应归一化、LSTM和GRU。
总共有大约350个SameDiff操作可用,包括许多用于构建复杂图的基本操作。
支持TensorFlow和ONNX图的基本导入以进行推理。
TFOpTests 是用于为TensorFlow导入创建测试资源的专用项目。
已知问题和限制
在1.0.0-alpha中添加的绝大多数新操作尚未使用GPU。
虽然许多在实践中广泛使用的基本操作和高级层都得到了支持,但是操作的覆盖范围仍然有限。目标是实现与TensorFlow的特征等价,并完全支持TF图的导入。
一些现有的操作没有实现反向传播。 (在SameDiff中叫作 doDiff
).
为单机执行添加了LocalTransformExecutor(没有Spark依赖项) (Link)
添加了ArrowRecordReader(用于读取ApacheArrow格式的数据) (Link)
添加了用于在RecordReader和RecordWriter之间转换的RecordMapper类(Link)
添加了BoxImageTransform-一种不改变纵横比而裁剪或填充的ImageTransform (Link)
添加 CSVVariableSlidingWindowRecordReader (Link)
ImageRecordReader: 支持标签的回归用例(以前:仅分类)(Link)
DataAnalysis/AnalyzeSpark 现在包括分位数(通过t-digest)(Link)
添加AndroidNativeImageLoader.asBitmap(), Java2DNativeImageLoader.asBufferedImage() (Link)
StringToTimeTransform 如果没有提供格式,将尝试猜测时间格式 (Link)
改进了Android上NativeImageLoader的性能 (Link)
添加 BytesWritable (Writable for byte[] data) (Link)
添加 TranformProcess.inferCategories 从RecordReader自动推断类别的方法 (Link)
Lombok不再作为传递依赖项包含 (Link)
MapFileRecordReader 和 MapFileSequenceRecordReader可以处理多部分映射文件的空分区/拆分 (Link)
Writables: 相等语义已更改:例如现在 DoubleWritable(1.0) 等于于 IntWritable(1) (Link)
NumberedFileInputSplit 现在支持前导零 (Link)
CSVSparkTransformServer 与 ImageSparkTransformServer 播放服务器改为生产模式(Link)
修复FloatMetaData的JSON子类型信息 (Link)
JacksonRecordReader, RegexSequenceRecordReadert序列化修复 (Link)
添加RecordReader.resetSupported() 方法 (Link)
SVMLightRecordReader 现在实现 nextRecord() 方法 (Link)
使用条件时自定义缩减的修复 (Link)
不再使用后端口java.util.functions;改为使用ND4J函数API (Link)
修正时间列的转换数据质量分析 (Link)
许多util类 (主要在 org.datavec.api.util
包) 已弃用或删除;在nd4j-common模块中使用等效名称的util类 (Link)
RecordReader.next(int) 方法现在返回 List<List<Writable>>
用于批量, 不是 List<Writable>
。同样查看 NDArrayRecordBatch
RecordWriter 和 SequenceRecordWriter APIs 已使用多个新方法更新
根据DL4J API更改: 更新器配置选项(learning rate, momentum, epsilon, rho等)已移到参数空间。 引入更新空间(AdamSpace、AdaGradSpace等) (Link)
根据DL4J API的变化:Dropout配置现在是通过 ParameterSpace<IDropout>
, DropoutSpace 引入 (Link)
RBM 层空间被删除 (Link)
ComputationGraphSpace: 添加 layer/vertex 带有预处理器重载的方法 (Link)
添加了直接使用DL4J层指定“固定”层的支持(而不是使用layerspace,即使对于没有超参数的层也是如此) (Link)
添加 LogUniformDistribution (Link)
分数函数的改进;增加了ROC分数函数 (Link)
增加学习率调度支持 (Link)
为 ParameterSpace<Double>
和 ParameterSpace<Integer>
添加数学操作 (Link)
改进了失败任务执行的日志记录 (Link)
修复UI JSON序列化 (Link)
将保存的模型文件重命名为model.bin (Link)
修复使用非线程安全候选/参数空间线程问题 (Link)
Lombok不再作为传递依赖项包含 (Link)
根据DL4J更新器API的更改:旧的更新器配置(learningRate、momentum等)方法已被删除。使用 .updater(IUpdater)
或 .updater(ParameterSpace<IUpdater>)
方法代替
为A3C增加对LSTM层的支持
修复A3C,使其使用新的 ActorCriticLoss
正确使用随机性
修复QLearning
失败的情况(非平面输入、不完整序列化、不正确的归一化)
用异步算法修HistoryProcessor
的逻辑和图像预处理失败
整理并更正统计数据的输出,还允许使用IterationListener
修复妨碍CUDA高效执行的问题
使用NeuralNet.getNeuralNetworks()
, Policy.getNeuralNet()
和方便的Policy
构造函数提供对更多内部结构的访问
Add MDPs for ALE (Arcade Learning Environment) and MALMO to support Atari games and Minecraft为ALE(街机学习环境)和MALMO添加mdp以支持Atari游戏和Minecraft
为Doom更新MDP以允许使用最新版本的VizDoom
First release of ScalNet Scala API, which closely resembles Keras' API.
可以用sbt和maven构建。
支持与DL4J的MultiLayerNetwork
相对应的Keras启发的 Sequential模型和与ComputationGraph
相对应的 Model
项目结构与DL4J模型导入模块和Keras紧密结合。
支持以下层: Convolution2D, Dense, EmbeddingLayer, AvgPooling2D, MaxPooling2D, GravesLSTM, LSTM, 双向层包装器, Flatten, Reshape。此外, DL4J OutputLayers 被支持。
Scala 2.12 支持
Deeplearning4J
修复了0.9.0中版本依赖项不正确的问题
添加 EmnistDataSetIterator Link
使用softmax对LossMCXENT / LossNegativeLogLikelihood的数值稳定性改进(应使用非常大的激活减少NaNs)
ND4J
为ND4J, DL4J, RL4J, Arbiter, DataVec添加运行时版本检查 Link
已知问题
Deeplearning4j:使用Evaluation类无参构造函数(即new Evaluation())可能导致accuracy/stats报告为0.0。其他评估类构造函数和ComputationGraph/MultiLayerNetwork.evaluate(DataSetIterator)方法按预期工作。
这也会影响Spark(分布式)评估:解决方法是sparkNet.evaluate(testData);
和 sparkNet.doEvaluation(testData, 64, new Evaluation(10))[0];
, 其中10是要使用的类数,64是要使用的评估小批量大小。
SequenceRecordReaderDataSetIterator对每个数据集应用两次预处理器(例如归一化)(可能的解决方法:使用RecordReaderMultiDataSetIterator + MultiDataSetWrapperIterator)
迁移学习:计算图可能错误地将l1/l2正则化(在FinetuneConfiguration中定义)应用于冻结层。解决方法:在FineTuneConfiguration上设置0.0 l1/l2,在新的/非冻结层上直接设置所需的l1/l2。请注意,使用迁移学习的多层网络似乎不受影响。
Deeplearning4J
添加了工作区功能(更快的训练性能+更少的内存) Link
ParallelInference被添加-使用内部批处理和队列请求的服务器推理包装器 Link
除了现有的参数平均模式之外,ParallelWrapper现在还可以与梯度共享一起工作 Link
VPTree 性能显著提高
增加了CacheMode网络配置选项-以牺牲额外的内存使用为代价提高了CNN和LSTM的性能 Link
添加LSTM层,用CUDNN支持链接(注意到现有的GraveLSTM实现不支持CUDNN)
带有预训练ImageNet、MNIST和VGG-Face权重的新型本地模型动物园 Link
卷积性能改进,包括激活缓存
现在支持自定义/用户定义的更新器 Link
评估改进
EvaluationBinary, ROCBinary 类被添加 : 二进制多类网络的评估(sigmoid + xent 输出层 ) Link
Evaluation和其他工具现在有G-Measure和Matthews相关系数支持;还有对Evaluation类度量的宏+微观平均支持 Link
ComputationGraph 与 SparkComputationGraph 增加评估便利方法 (evaluateROC, etc)
ROC和ROCMultiClass支持精确计算(以前:使用阈值计算) Link
ROC类现在支持precision recall曲线计算下的区域;在指定的阈值处获取precision/recall/confusion矩阵(通过PrecisionRecallCurve类)Link
RegressionEvaluation、ROCBinary等现在支持每个输出掩码(除了每个示例/每个时间步掩码)
Evaluation 和 EvaluationBinary: 现在支持自定义分类阈值或成本数组 Link
优化:更新器,偏置计算
ND4J
添加了工作区功能 Link
添加了原生并行排序
新操作被添加: SELU/SELUDerivative, TAD-based comparisons, percentile/median, Reverse, Tan/TanDerivative, SinH, CosH, Entropy, ShannonEntropy, LogEntropy, AbsoluteMin/AbsoluteMax/AbsoluteSum, Atan2
添加了新的距离函数: CosineDistance, HammingDistance, JaccardDistance
DataVec
Spark:将JavaRDD<List<Writable>>
和JavaRDD<List<List<Writable>>
数据保存和加载到Hadoop MapFile和SequenceFile格式的实用程序 Link
TransformProcess和Transforms现在支持NDArrayWritables和NDArrayWritable列
多个新转换类
Arbiter
Arbiter UI: Link
UI现在使用Play 框架,与DL4J UI(取代Dropwizard后端)集成。已修复依赖关系问题/冲突版本。
支持DL4J StatsStorage和StatsStorageRouter机制(FileStatsStorage、通过RemoveUIStatsStorageRouter实现的远程用户界面)
常规UI改进(附加信息、格式修复)
Deeplearning4j
更新器配置方法,如 .momentum(double) 和 .epsilon(double) 已弃用。替换: 使用 .updater(new Nesterovs(0.9))
和 .updater(Adam.builder().beta1(0.9).beta2(0.999).build())
等来配置
DataVec
CsvRecordReader构造函数:现在使用字符作为分隔符,而不是字符串(即,','而不是“,”)
Arbiter
Arbiter UI现在是一个单独的模块,带有Scala版本后缀: arbiter-ui_2.10
and arbiter-ui_2.11
添加迁移学习API Link
Spark 2.0支持(DL4J和DataVec;请参阅下面的迁移说明)
新 ComputationGraph 顶点
L2距离顶点
L2归一化顶点
现在大多数损失函数都支持每输出掩码(对于每输出掩码,使用与标签数组大小/形状相等的掩码数组;以前的掩码功能是针对RNN的每个示例)
L1和L2正则化现在可以针对偏置配置(通过l1Bias和l2Bias配置选项)
评估改进:
DL4J现在有一个IEvaluation类(Evaluation、RegressionEvaluation等都实现了。也允许对Spark进行自定义评估) Link
添加了多个类(一对所有) ROC: ROCMultiClass Link
对于MultiLayerNetwork和SparkDl4jMultiLayer:添加了evaluateRegression、evaluateROC、evaluateROCMultiClass便利方法
为ROC图表添加了HTML导出功能 Link
TSNE重新添加到新UI
训练用户界面:现在可以在没有internet连接的情况下使用(不再依赖外部托管的字体)
UI: “无数据”条件下错误处理的改进
Epsilon配置现在用于Adam和RMSProp更新器
修复双向LSTM+可变长度时间序列(使用掩码)
Spark+Kryo:现在测试序列化,如果配置错误则抛出异常(而不是记录可以忽略的错误)
MultiLayerNetwork 现在,如果未指定名称,则添加默认图层名
DataVec:
JSON/YAML支持数据分析、自定义转换等
对ImageRecordReader进行了重构,以减少垃圾收集负载(从而使大型训练集提高性能)
更快的质量分析。
Arbiter: 添加新的层类型以匹配DL4J
Word2Vec/ParagraphVectors分词与训练的性能改进。
段落向量的批量推理
Nd4j 改进
可用于ND4j的新本地操作: firstIndex, lastIndex, remainder, fmod, or, and, xor.
OpProfiler NAN_PANIC & INF_PANIC 现在还要检查BLAS调用的结果。
Nd4.getMemoryManager() 现在提供了调整GC行为的方法。
Spark引入了Word2Vec/ParagraphVectors参数服务器的Alpha版本。请注意:目前还不推荐用于生产。
CNN推理的性能改进
Spark版本控制方案:随着Spark 2支持的增加,Deeplearning4j和DataVec Spark模块的版本已经改变
对于 Spark 1: 用 <version>0.8.0_spark_1</version>
对于 Spark 2: 用 <version>0.8.0_spark_2</version>
另请注意:支持Spark 2的模块仅支持Scala 2.11。Spark 1模块同时支持Scala 2.10和2.11
UI/CUDA/Linux 问题: Link
JVM退出时的脏关闭可能是CUDA的后端引起的: Link
RBM实现问题 Link
Keras 1D卷积层和池化层还不能导入。将在即将发布的版本中得到支持。
无法导入Keras v2模型配置。将在即将发布的版本中得到支持。
增加变分自动编码器 Link
Keras模型导入的多个修复/改进
为CNN添加了P-norm池化(作为子采样层配置的一部分)
迭代计数持久化:在模型配置中正确存储/持久化 + Spark网络训练的学习率调度修正
LSTM:现在可以配置门激活函数(以前:硬编码为sigmoid)
UI:
增加中文翻译
修复UI+预训练层
添加了与Java 7兼容的stats集合兼容性 Link
前端处理NAN的改进
添加 UIServer.stop() 方法
修复分数与迭代移动平均线(带子采样)
用基于Spring Boot的应用程序解决了Jaxb/Jackson问题
RecordReaderDataSetIterator现在支持对标签使用NDArrayWritable(设置 regression==true;用于多标签分类+图像等)
激活函数(内置):现在使用激活枚举指定,而不是字符串(不推荐使用基于字符串的配置)
RBM 与 AutoEncoder 键修复:
确保在训练前更新和应用视觉偏置。
RBM HiddenUnit是该层的激活函数;因此,根据各自的HiddenUnit建立了反向传播的导数计算。
已修复CUDA后端的RNG性能问题
已修复macOS、powerpc和linux的OpenBLAS问题。
DataVec现在回到了Java 7。
为ND4J/DL4J修复了多个小错误
UI检修:新的训练UI有更多的信息,支持持久性(保存信息并稍后加载),日/韩/俄文支持。用Play框架替换了Dropwizard。 Link
使用Keras配置和训练的模型的导入
支持的模型: Sequential 模型
支持层 : Dense, Dropout, Activation, Convolution2D, MaxPooling2D, LSTM
为CNN添加更多“Same”填充(ConvolutionMode网络配置选项) Link
权重损失函数:损失函数现在支持每个输出权重数组(行向量)
为二进制分类器添加ROC和AUC Link
改进了关于无效配置或数据的错误消息;改进了对两者的验证
新增元数据功能:从数据导入到评估跟踪数据源(文件、行号等)。现在支持从该元数据加载示例/数据的子集。Link
删除了Jackson作为核心依赖项(遮挡);用户现在可以使用Jackson的任何版本
添加了LossLayer:仅应用损失函数的OutputLayer版本(与OutputLayer不同:它没有权重/偏置)
构建三元组嵌入模型所需的功能 (L2 vertex, LossLayer, Stack/Unstack vertices 等)
减少了DL4J和ND4J的“冷启动”初始化/启动时间
Pretrain 默认更改为false,backprop 默认更改为true。在设置网络配置时不再需要设置这些,除非需要更改默认值。
添加了TrainingListener接口(扩展了IterationListener)。提供网络训练时对更多信息/状态的访问 Link
在DL4J和ND4J上修复了许多缺陷
nd4j-native 和 nd4j-cuda 后端的性能改进
独立Word2Vec/ParagraphVectors检修:
性能改进
对PV-DM和PV-DBOW都可用ParaVec推理
添加了并行分词支持,以解决计算量大的分词器。
为在多线程执行环境中更好的重现性引入了本地RNG。
添加了其他RNG调用:Nd4j.choice(),和BernoulliDistribution 操作。
引入了非gpu存储,以将大的东西保存在主机内存中,比如Word2Vec模型。可通过WordVectorSerializer.loadStaticModel()获得
nd4j-native后端性能优化的两个新选项:setTADThreshold(int) & setElementThreshold(int)
升级基于0.6.0到0.7.0的代码库的显著变化:
UI: 新的UI包名称是deeplearning4j-ui_2.10或deeplearning4j-ui_2.11(以前是deeplearning4j UI)。Scala版本后缀是必需的,因为现在正在使用Play 框架(用Scala编写)。
已弃用直方图和流迭代监听器。它们仍然可以工作,但建议使用新的UI Link
DataVec ImageRecordReader:标签现在默认按字母顺序排序,然后根据文件迭代顺序为每个标签分配整数类索引(以前为0.6.0和更早版本)。如果需要,使用.setLabels(List)手动指定顺序。
CNNs: 配置验证现在不那么严格了。使用新的ConvolutionMode选项,0.6.0等同于“Strict”模式,但新的默认值是“Truncate”
有关更多详细信息,请参ConvolutionMode javadoc: Link
CNN和LSTM的Xavier权重初始化更改:Xavier现在可以更好地与原始Glorot论文和其他库对齐。Xavier权重初始化。相当于0.6.0的版本作为XAVIER_LEGACY提供
DataVec: 自定义的RecordReader和SequenceRecordReader类需要其他方法才能实现新的元数据功能。参考如何实现这些方法的现有记录读取器实现。
Word2Vec/ParagraphVectors:
少量新的builder方法:
allowParallelTokenization(boolean)
useHierarchicSoftmax(boolean)
行为更改:批大小:现在批大小还用作sg/cbow执行计算批数量的阈值
自定义层支持
支持自定义损失函数
支持压缩的INDArrays,在海量数据上节省内存
适用于布尔值索引的原生支持
对CUDA联合操作的初步支持
CPU和CUDA后端的显著性能改进
更好地支持使用CUDA和cuDNN以及多gpu集群的Spark环境
新的UI工具:FlowIterationListener和ConfluctionIterationListener,用于更好地洞察NN中的进程
用于性能跟踪的特殊IterationListener实现:PerformanceListener
为ParagraphVectors添加的推理实现,以及使用现有的Word2Vec模型的选项
Deeplearning4J api上的文件大小减小很多
nd4j-cuda-8.0
后端现在可用于cuda 8 RC
新增多个内置损失函数
自定义预处理器支持
提高Spark训练实施的性能
使用InputType功能改进网络配置验证
FP16对CUDA的支持
为多gpu提供更好的性能
包括可选的P2P内存访问支持
对时间序列和图像的归一化支持
对标签的归一化支持
移除Canova并转移到DataVec: Javadoc, Github Repo
大量的错误修复
Spark 改进
独立和Spark最初的多GPU支持是可行的
显著地重构了Spark API
添加了CuDNN包装器
ND4J的性能改进
引入 DataVec: 许多新的功能,用于转换,预处理,清理数据。(这代替了Canova)
用现有数据馈送神经网络的新DataSetIterators:现有ExistingDataSetIterator,Floats(Double)DataSetIterator, IteratorDataSetIterator
Word2Vec和ParagraphVectors的新学习算法:CBOW和PV-DM
新的本地操作以获得更好的性能: DropOut, DropOutInverted, CompareAndSet, ReplaceNaNs
默认情况下为多层网络和计算图启用影异步数据集预取
使用JVM GC和CUDA后端更好地处理内存,从而显著降低内存占用
ScalNet Scala API (WIP!)
与Keras共享的标准NN配置文件
CGANs
模型可解释性
如何使用Eclipse Deeplearning4j创建Android图像分类应用程序。
内容
此示例应用程序使用在28x28灰度0..255像素值的手写数字0..9的标准MNIST数据集上训练的神经网络。应用程序用户界面允许用户在设备屏幕上绘制一个数字,然后根据经过训练的网络进行测试。输出显示最可能的数值和概率分数。本教程将介绍如何在Android应用程序中使用经过训练的神经网络,如何处理用户生成的图像,以及如何将结果从后台线程输出到UI。关于构建DL4J Android应用程序的一般先决条件的更多信息可以在这里找到。
Deeplearning4J应用程序需要build.gradle文件中特定于应用程序的依赖项。Deeplearning库又依赖于ND4J和OpenBLAS库,因此这些库也必须添加到依赖关系声明中。从Android Studio 3.0开始,还需要定义annotationProcessors,因此,如果您在Android studio3.0或更高版本中工作,则应根据您的设备包括-x86或-arm处理器的依赖项。请注意,这两个应用程序都可以包含而不发生冲突,就像在示例应用程序中所做的那样。
编译这些依赖项涉及大量文件,因此有必要在defaultConfig中将multiDexEnabled设置为true。
junit模块版本中的冲突通常会导致以下错误:> Conflict with dependency 'junit:junit' in project ':app'。应用程序(4.8.2)和测试应用程序(4.12)的解析版本不同。这可以通过强制所有junit模块使用相同的版本来抑制:
使用神经网络需要相当大的处理器功率,这在移动设备上是受限的。因此,必须使用一个后台线程加载训练好的神经网络,并使用AsyncTask测试用户绘制的图像。在这个应用程序中,我们将在主线程上运行canvas.draw代码,并使用AsyncTask从内存中加载绘制的图像,并在后台线程上用经过训练的模型对其进行测试。首先,让我们看看如何保存我们将在应用程序中使用的经过训练的神经网络。
现在让我们从编写AsyncTask<Params, Progress, Results>开始,在后台线程上加载并使用神经网络。异步任务将使用参数类型。Params类型设置为String,它将在执行保存的图像时将其路径传递给asyncTask。此路径将在doInBackground()方法中用于定位和加载经过训练的MNIST模型。Results参数是INDArray类型,它将存储来自神经网络的结果,并将其传递给onPostExecute方法,该方法可以访问用于更新UI的主线程。有关INDArray的更多信息,请参见https://nd4j.org/userguide。注意,AsyncTask要求我们重写另外两个方法(onProgressUpdate和onPostExecute方法),稍后我们将在演示中讨论这些方法。
现在,让我们为将在主线程上运行的绘图画布添加代码,并允许用户在屏幕上绘制一个数字。这是一个通用绘图程序,作为MainActivity中的内部类编写。它扩展了View并重写了一系列方法。图形保存到内部内存中,并在case MotionEvent.ACTION上的onTouchEvent case语句中使用传递给它的图像路径执行AsyncTask。这有一个流线型操作,即在用户完成绘图后自动返回图像的结果。
现在我们需要构建一系列的帮助方法。首先我们将编写saveDrawing()方法。它使用getDrawingCache()从drawingView中获取图形并将其存储为位图。然后我们为位图创建一个名为“drawind_image.jpg”的文件目录和文件。最后,在try/catch块中使用FileOutputStream将位图写入文件位置。方法返回loadImageFromStorage()方法将使用的文件位置的绝对路径。
接下来我们将编写loadImageFromStorage方法,该方法将使用saveDrawing()返回的绝对路径来加载保存的图像,并将其作为输出显示的一部分显示在UI中。它使用try/catch块和FileInputStream在UI布局中将图像设置为ImageView img。
我们还需要编写两种方法,从神经网络输出和置信度得分中提取预测数,我们稍后在完成AsyncTask时将调用这两种方法。
最后,我们需要调用一些方法来控制后台线程运行时“进行中…”消息的可见性。当AsyncTask执行时和后台线程完成时在onPostExecute方法中调用这些函数。
现在让我们转到onCreate方法来初始化绘图画布并设置一些全局变量。
现在,我们可以通过重写onProgress和onPostExecute方法来完成AsyncTask。AsyncTask的doInBackground方法完成后,分类结果将传递给onPostExecute,onPostExecute有权访问主线程和UI,允许我们用结果更新UI。因为我们不会使用onProgress方法,所以调用它的超类就足够了。
onPostExecute方法将接收一个INDArray,该INDArray将神经网络结果作为概率值的1x10数组,即输入图形是每个可能的数字(0..9)。由此我们需要确定数组的哪一行包含最大的值以及该值的大小。这两个值将决定神经网络将绘图分类为哪个数字以及网络分数的置信度。这些值在UI中将分别称为预测值和置信度。在下面的代码中,使用结果INDArray上的getDouble()方法将INDArray的每个位置的单个值传递给double类型的数组。然后,我们获得对TextViews的引用,这些TextViews将在UI中更新,并调用数组中的帮助方法来返回数组最大值(置信度)和最大值(预测)的索引。注意,我们还需要通过设置DecimalFormat模式来限制概率报告的小数位数。
本教程提供了使用DL4J神经网络在Android应用程序中进行图像识别的基本框架。它说明了如何从原始资源文件加载预先训练的DL4J模型,以及如何测试用户创建与模型相对应的图像。然后,AsyncTask将输出返回到主线程并更新UI。
首先,您需要遵循DeepLearning4j快速入门指南,在台式计算机上建立、训练和保存神经网络模型。训练和保存此应用程序中使用的MNIST模型的DL4J示例是MnistImagePipelineExampleSave.java,它包含在上述快速入门指南中。MNIST演示的代码也可以在找到。运行此演示将训练MNIST神经网络模型,并将其保存为dl4j-examples目录的dl4j\target文件夹中的“trained_mnist_model.zip"”。然后,可以复制该文件并将其保存在Android项目的raw文件夹中。
提供了此示例的完整代码。
支持的Keras权重初始化器
DL4J 支持所有可用的 Keras 初始化器, 名称为:
Zeros
Ones
Constant
RandomNormal
RandomUniform
TruncatedNormal
VarianceScaling
Orthogonal
Identity
lecun_uniform
lecun_normal
glorot_normal
glorot_uniform
he_normal
he_uniform
从Keras 到 DL4J的初始化器映射可以从 KerasInitilizationUtils中找到。