DL4J中文文档/分布式深度学习/操作指南

Spark上的DL4J:操作指南

此页面包含许多用于常见分布式训练任务的操作指南。注意,对于构建数据管道的指南,请参见这里
在阅读这些指南之前,请确保你已经阅读了有关DL4J spark训练的介绍指南。

在训练指南前之前

  • 如何用Maven构建用于通过Spark submit训练的uber jar 
  • 如何在Spark上使用GPU进行训练
  • 如何在主节点上使用CPU,在工作机上用GPU
  • 如何配置Spark的内存设置
  • 如何为工作机配置垃圾收集
  • 如何与DL4J和ND4J一起使用Kryo序列化
  • 如何使用YARN与GPU
  • 如何配置Spark位置配置

训练指南期间和之后

  • 如何配置编码阈值
  • 如何进行分布式测试集评估
  • 如何保存(加载)Spark训练的神经网络
  • 如何执行分布式推理

问题及故障排除指南

  • 如何调试常见的Spark依赖问题(NoClassDefFoundExcption等)
  • 如何修复“Error querying NTP server”错误
  • 如何安全地缓存RDD[INDArray]和RDD[DataSet ]
  • 在Amazon Elastic MapReduce上修复libgomp问题
  • Ubuntu 16.04的失败训练(Ubuntu bug可能影响DL4J Spark用户)


训练之前 操作指南

如何用Maven构建用于通过Spark submit训练的uber jar

当向集群提交训练作业时,典型的工作流程是构建提交给Spark submit的“uber-jar”。uber-jar是包含运行作业所需的所有依赖项(库、类文件等)的单个JAR文件。注意,Spark submit是随Spark发行版一起提供的脚本,用户为了开始执行Spark作业,向其提交作业(以JAR文件的形式)。
本指南假定你已经把代码设置为在 Spark上训练神经网络。

步骤1:决定所需的依赖。

与用DL4J和ND4J的单机训练有很多重叠。例如,对于单机训练和Spark训练,你都应该包括标准的DL4J依赖集,例如:

  • deeplearning4j-core
  • deeplearning4j-spark
  • nd4j-native-platform (仅用于CPU训练)

此外,你还需要包括DL4J的Spark模块,dl4j-spark_2.10或dl4j-spark_2.11。这个模块对于DL4J Spark作业的开发和执行都是必需的。小心使用与集群匹配的spark版本——对于Spark版本(Spark 1 vs.Spark 2)和Scala版本(2.10vs.2.11)。如果这些不匹配,你的作业可能会在运行时失败。

依赖示例: Spark 2, Scala 2.11:

<dependency>
  <groupId>org.deeplearning4j</groupId>
  <artifactId>dl4j-spark_2.11</artifactId>
  <version>1.0.0-beta2_spark_2</version>
</dependency>

依赖示例, Spark 1, Scala 2.10:

<dependency>
  <groupId>org.deeplearning4j</groupId>
  <artifactId>dl4j-spark_2.10</artifactId>
  <version>1.0.0-beta2_spark_1</version>
</dependency>

注意,如果添加Spark依赖项,例如spark-core_2.11,可以将其设置为pom.xml中提供的scope(有关更多细节,请参阅Maven文档),因为Spark submit将向类路径添加Spark。在集群上执行时不需要添加此依赖项,但是如果希望在本地机器上测试或调试基于Sspark的作业,则可能需要添加此依赖项。

当在CUDA GPU上进行训练时,在添加CUDA依赖项时存在以下几种可能的情况:

案例1:集群节点在主节点和工作节点上安装了CUDA工具包

当CUDA工具包和CuDNN在集群节点上可用时,我们可以使用较小的依赖:

  • 如果构建uber-jar的OS与集群的OS相同:引入 nd4j-cuda-x.x
  • 如果构建uber-jar的OS与集群OS不同(即,在Windows上构建,在Linux集群上执行Spark):引入cuda-x.x-platform
  • 在这两种情况中,引入x.x是CUDA版本——例如,对于CUDA 9.2,x.x=9.2。

案例2:集群节点在主节点和工作节点上没有安装CUDA工具箱

当在集群节点上没有安装CUDA/CUDNN时,我们可以按以下几点来做:

  • 首先,根据上面的“案例1”引入依赖关系。
  • 然后为集群操作系统的引入 “redist” javacpp-presets,如这里所述:DL4J CuDNN文档

步骤2:配置POM.xml文件以构建uber-jar

当使用Spark submit时,你将需要一个uber-jar来提交以启动和运行作业。在步骤1中配置相关依赖项之后,我们需要配置pom.xml文件以正确构建uber-jar。

我们建议你使用Maven shade插件来构建uber-jar。有用于此目的的替代工具/插件,但这些并不总是包括源JAR中的所有相关文件,例如Java的ServiceLoader机制正确运行所需的文件。(ND4J和许多其他软件库使用ServiceLoader机制)。

示例独立示例项目pom.xml文件中提供了适合于此目的的Maven shade配置:

<build>
    <plugins>
        <!-- Other plugins here if required -->

        <!-- Configure maven shade to produce an uber-jar when running "mvn package" -->
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-shade-plugin</artifactId>
            <version>${maven-shade-plugin.version}</version>
            <configuration>
                <shadedArtifactAttached>true</shadedArtifactAttached>
                <shadedClassifierName>bin</shadedClassifierName>
                <createDependencyReducedPom>true</createDependencyReducedPom>
                <filters>
                    <filter>
                        <artifact>*:*</artifact>
                        <excludes>
                            <exclude>org/datanucleus/**</exclude>
                            <exclude>META-INF/*.SF</exclude>
                            <exclude>META-INF/*.DSA</exclude>
                            <exclude>META-INF/*.RSA</exclude>
                        </excludes>
                    </filter>
                </filters>
            </configuration>

            <executions>
                <execution>
                    <phase>package</phase>
                    <goals>
                        <goal>shade</goal>
                    </goals>
                    <configuration>
                        <transformers>
                            <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
                                <resource>reference.conf</resource>
                            </transformer>
                            <transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
                            <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                            </transformer>
                        </transformers>
                    </configuration>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

步骤3:构建uber jar

最后,打开一个命令行窗口(Linux上的bash、Windows上的cmd等),只需运行mvn package -DskipTests 来为项目构建uber-jar。注意,uber-jar应该出现在<project_root>/target/<project_name>-bin.jar。一定要使用大的...-bin.jar文件,因为这是带有所有依赖项的shaded jar。

这是-你现在应该有一个uber-jar,适合提交到spark-submit 用于在spark上与CPU或NVIDA(CUDA)GPU一起训练网络。

如何在Spark上使用GPU进行训练

DL4J和ND4J支持使用NVIDA GPU的GPU加速。DL4J Spark训练也可以使用GPU来执行。

DL4J和ND4J是这样设计的:代码(神经网络配置,数据管道代码)是“后端独立的”。也就是说,你可以编写一次代码,并在CPU或GPU上执行它,只需要包括适当的后端(用于CPU的nd4j-native后端,或用于GPU的nd4j-cuda-x.x)。在这方面,在Spark上执行与在单个节点上执行没有什么不同:你只需要引入适当的ND4J后端,并确保你的机器(在本例中是主/工作节点)用CUDA库适当地设置(参见uber-jar指南 来实现无需在每个节点上安装CUDA/CUDNN情况下在CUDA上运行uber jar。)

当在GPU上运行时,有几个组件:(a)ND4J CUDA后端(nd4j-cuda-x.x依赖)(b)CUDA工具箱(c)DL4J CUDA依赖项以获得cuDNN支持(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,请参见使用CuDNN的DL4J.

如何在主节点使用CPU,工作节点上使用GPU

在某些情况下,只使用CPU运行主节点和使用GPU的工作节点可能是有意义的。如果资源(即,可用GPU机器的数量)不受限制,那么使用相同构成的集群可能更容易:即,设置集群,以便主节点也使用GPU来执行。

假设主/驱动程序在CPU机器上执行,而工作节点在GPU机器上执行,则可以简单地引入两个后端(即,如uber-jar部分中所述,nd4j-cuda-x.x 和nd4j-native 依懒)。

当类路径上有多个后端时,默认情况下首先尝试CUDA后端。如果无法加载,则CPU(nd4j-native)后端将被第二个加载。因此,如果驱动程序没有GPU,它应该回落到使用CPU。但是,可以通过在主/驱动器上设置BACKEND_PRIORITY_CPUBACKEND_PRIORITY_GPU环境变量来改变这种默认行为,如这里所述。设置环境变量的确切过程可能取决于集群管理器——Spark standalone vs.YARN vs.Mesos。请查阅每个文档,了解如何为驱动程序/主程序设置Spark作业的环境变量。

如何配置Spark的内存设置

关于DL4J和ND4J的内存和内存配置如何工作的重要背景,首先阅读ND4J/DL4J的内存管理
Spark上的内存管理类似于用于单节点训练的内存管理:

  • 堆内存使用标准的Java Xms和Xmx内存配置设置来配置。
  • 使用javacpp系统属性配置堆外内存

然而,Spark上下文中的内存配置增加了一些额外的复杂性:

  1. 通常,对于驱动程序/主节点和工作节点,内存配置必须分开进行(有时使用不同的机制)。
  2. 配置内存的方法取决于集群资源管理器——Spark standalone vs.YARN vs.Mesos,等等
  3. 集群资源管理器默认内存设置通常不适合严重依赖堆外内存的库(如DL4J/ND4J)。

请参阅群集管理器的Spark文档:

你应该设置4件事:

  1. 工作节点堆内存(Xmx)——通常设置为Spark submit 的参数(例如,YARN的 --executor-memory 4g
  2. 工作节点堆外内存(javacpp系统属性选项)(例如,--conf "spark.executor.extraJavaOptions=-Dorg.bytedeco.javacpp.maxbytes=8G"
  3. 驱动程序堆内存
  4. 驱动程序堆外内存

要注意的地方:

  • 在YARN上,通常需要设置spark.driver.memoryOverhead 和 spark.executor.memoryOverhead。默认设置对于DL4J训练来说太小了。
  • 在Spark standalone 上,还可以通过修改每个节点上的conf/spark-env.sh文件来配置内存,如Spark配置文档中所述。例如,可以添加以下行来设置驱动程序的堆大小为8GB、驱动程序的堆外内存为12GB、工作节点的堆内存为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堆外内存开销):

--class my.class.name.here --num-executors 4 --executor-cores 8 --executor-memory 4G --driver-memory 4G --conf "spark.executor.extraJavaOptions=-Dorg.bytedeco.javacpp.maxbytes=5G" --conf "spark.driver.extraJavaOptions=-Dorg.bytedeco.javacpp.maxbytes=5G" --conf spark.yarn.executor.memoryOverhead=6144

 

如何为工作机配置垃圾收集

训练效果的一个决定因素是垃圾收集的频率。当使用默认启用的工作间(参见本文)时,减少垃圾收集的频率会很有帮助。对于简单的机器训练(和驱动程序)来说,这是很容易的:

//这将限制GC调用的频率为5000毫秒。
Nd4j.getMemoryManager().setAutoGcWindow(5000)

// 或者你可以完全禁用它
Nd4j.getMemoryManager().togglePeriodicGc(false);

但是,在驱动程序上设置此选项不会更改工作节点的设置。相反,它可以为工作节点设置如下:

new SharedTrainingMaster.Builder(voidConfiguration, minibatch)
    <other configuration>
    .workerTogglePeriodicGC(true)       //启用定期垃圾收集…
    .workerPeriodicGCFrequency(5000)    //并且配置为每5秒执行一次(每5000毫秒)
    .build();

默认值(从1.0.0-beta3开始)是每5秒对工节点执行一次定期的垃圾收集。

如何与DL4J和ND4J一起使用Kryo序列化

DL4J和ND4J可以利用Kryo序列化,使用适当的配置。注意,由于INDArrays的堆外内存,与其他上下文中使用Kryo相比,Kryo将提供更少的性能优势。
为了启用KRYO序列化,首先添加 ND4J KRYO依赖

<dependency>
  <groupId>org.nd4j</groupId>
  <artifactId>nd4j-kryo_2.11</artifactId>
  <version>${dl4j-version}</version>
</dependency>

${dl4j-version} 是 DL4J 与 ND4J的版本号

然后,在训练工作开始时,添加以下代码:

    SparkConf conf = new SparkConf();
    conf.set("spark.serializer", "org.apache.spark.serializer.KryoSerializer");
    conf.set("spark.kryo.registrator", "org.nd4j.Nd4jRegistrator");

注意,当使用DL4J的SparkDl4jMultiLayer或SparkComputationGraph类时,如果Kryo配置不正确,将记录一个警告。

如何使用YARN与GPU

对于DL4J,CUDA GPU的唯一要求是使用适当的后端,在每个节点上安装或在uber-JAR中提供适当的NVIDIA库。对于最新版本的YARN,在某些情况下可能需要一些额外的配置——有关更多细节,请参阅YARN GPU文档

早期版本的YARN(例如,2.7.x和类似版本)在本地不支持GPU。对于这些版本,可以使用节点标签来确保作业仅被调度到GPU的节点上。有关更多细节,请参阅Hadoop Yarn文档

注意,还需要特定于YARN的内存配置(参见内存操作)。

如何配置Spark位置配置

配置Spark locality设置是可选的配置选项,可以提高训练性能。
摘概:通过在Spark submit 配置中添加--conf spark.locality.wait=0 ,可以稍微减少训练时间,通过调度网络拟合操作更快地开始来实现。
有关详细信息,请参阅链接1链接2

训练指南期间和之后

如何配置编码阈值

DL4J的Spark实现使用阈值编码方案在节点之间发送参数更新。这种编码方案导致小的量化消息,这显著降低了通信更新的网络成本。有关这个编码过程的更多细节,请参见技术说明页。

这个阈值编码过程引入了一个“分布式训练专用”的超参数器——编码阈值。过大阈值和过小阈值都可能导致次优性能:

  • 大的阈值意味着稀少的通信-太稀少和收敛会受到影响
  • 小阈值意味着更频繁的通信,但是在每个步骤中都传递更小的更改

所使用的编码阈值由ThresholdAlgorithm(阈值算法)控制。阈值算法的具体实现确定应该使用什么阈值。
DL4J的默认行为是使用AdaptiveThresholdAlgorithm,它试图将稀疏率保持在一定范围内。

  • 稀疏比率定义为numValues(encodedUpdate)/numParameters - 1.0表示完全密集(所有值都传递),0.0表示完全稀疏(没有值传递)
  • 较大的阈值意味着更多的稀疏值(更少的网络通信),而较小的阈值意味着更少的稀疏值(更多的网络通信)
  • 默认情况下,AdaptiveThresholdAlgorithm尝试将稀疏率保持在0.01和0.0001之间。如果更新的稀疏性落在这个范围之外,则阈值要么增加要么减小,直到它落在这个范围内。
  •  一个初始阈值仍然需要设置

在实践中,我们已经看到,这种自适应阈值处理工作良好。阈值算法的内置实现包括:

此外,DL4J有一个ResidualPostProcessor(残差后置处理器) 接口,默认的实现是ResidualClippingPostProcessor,它每5步将残差向量剪辑到最大值为当前阈值的5倍。其动机是更新的“遗留”部分(即那些未被通信的部分)被存储在残差向量中。如果更新远远大于阈值,则可能出现我们称之为“残差爆炸”的现象——即,残差值可以继续增长到阈值的许多倍(因此将采取许多步骤来传递梯度)。残差后置处理器被用来避免这种现象。

阈值算法(和初始阈值)和ResidualPostProcessor可以设置如下:

TrainingMaster tm = new SharedTrainingMaster.Builder(voidConfiguration, minibatch)
    .thresholdAlgorithm(new AdaptiveThresholdAlgorithm(this.gradientThreshold))
    .residualPostProcessor(new ResidualClippingPostProcessor(5, 5))
    <other config>
    .build();

最后,DL4J的SharedTrainingMaster还具有编码调试模式,通过在SharedTrainingMaster构建器中设置.encodingDebugMode(true)启用。当启用此功能时,每个工作节点将记录当前阈值、稀疏性和关于编码的各种其他统计信息。这些统计信息可以用来确定阈值是否被适当设置:例如,许多更新是阈值的十倍或几百倍可能表明阈值太低并且应该增加;在频谱的另一端,非常稀疏的更新(小于1/10000的值被传递)可以指示阈值应该降低。

如何进行分布式测试集评估

DL4J支持神经网络最标准的评估指标。有关评估的基本信息,请参见DL4J评估页面
DL4J支持的所有评估指标都可以使用Spark以分布式方式计算。

第1步:准备数据

DL4J在Spark上的评估数据与训练数据非常相似。也就是说,你可以使用:

  • RDD<DataSet> 或 JavaRDD<DataSet> 用于单输入/输出网络评估
  • RDD<MultiDataSet> 或 JavaRDD<MultiDataSet> 用于多输入/输出网络评估
  • RDD<String> 或 JavaRDD<String>它的每个字符串都是一个指向例如HDFS网络存储器上的DataSet/MultiDataSet (或其他基于小批量的文件格式)系列化的路径。

有关如何将数据准备为这些格式之一的详细信息,请参阅数据页。

第2步:准备你的网络

创建你的网络是很简单的。首先,使用 如何保存(和加载)在Spark上训练的神经网络 指南中的信息,将网络(MultiLayerNetwork或ComputationGraph)加载到驱动程序的内存中

然后,简单地创建你的网络:

JavaSparkContext sc = new JavaSparkContext();
MultiLayerNetwork net = <code to load network>
SparkDl4jMultiLayer sparkNet = new SparkDl4jMultiLayer(sc, cgForEval, null);
JavaSparkContext sc = new JavaSparkContext();
ComputationGraph net = <code to load network>
SparkComputationGraph sparkNet = new SparkComputationGraph(sc, net, null);

注意,你不需要配置TrainingMaster(即,上面的第三个参数为空),因为评估不使用它。

第3步:调用适当的评估方法

对于常见情况,可以在SparkDl4jMultiLayer或SparkComputationGraph上调用标准评估方法之一:

evaluate(RDD<DataSet>)                //Accuracy/F1 etc for classifiers
evaluate(JavaRDD<DataSet>)            //Accuracy/F1 etc for classifiers
evaluateROC(JavaRDD<DataSet>)         //ROC for single output binary classifiers
evaluateRegression(JavaRDD<DataSet>)  //For regression metrics

为了同时执行多个评估(比顺序执行更有效),可以使用以下方法:

IEvaluation[] evaluations = new IEvaluation[]{new Evaluation(), new ROCMultiClass()};
JavaRDD<DataSet> data = ...;
sparkNet.doEvaluation(data, 32, evaluations);

注意,一些评估方法具有带有额外参数的过载,包括:

  • int evalNumWorkers - 用于评估的工作节点的数量-即,在每个节点上用于评估的网络的副本的数量(每个工作节点上最大的Spark线程数量)。对于大型网络(或有限的集群内存),你可能希望减少这个值以避免遇到内存问题。
  • int evalBatchSize -  执行评估时使用的小批量大小。这需要足够大,以有效地使用硬件资源,但足够小,以免耗尽内存。32-128之间的值是一个好的起点;对于较小的网络当有更多的内存可用时增加;如果内存有问题,则减少。
  • DataSetLoader loader 与 MultiDataSetLoader loader - 当对一个“RDD<String>”或“JavaRDD<String>”进行评估时,这些都是可用的。它们是使用自定义用户定义函数将路径加载到DataSet或MultiDataSet的接口。大多数用户不需要使用这些功能,但是这个功能提供了更大的灵活性。例如,如果保存的小批处理文件格式不是DataSet/MultiDataSet,而是其他格式(可能是定制的),它们将被使用。

 最后,如果希望保存(任何类型的)评估结果,可以将其保存为JSON格式,直接保存到远程存储,例如HDFS,如下所示:

JavaSparkContext sc = new JavaSparkContext();
Evaluation eval = ...
String json = eval.toJson();
String writeTo = "hdfs:///output/directory/evaluation.json";
SparkUtils.writeStringToFile(writeTo, json, sc); //Also supports local file paths - file://

用于SparkUtils的输入是org.datavec.spark.transform.utils.SparkUtils

评估可以使用如下加载:

String json = SparkUtils.readStringFromFile(writeTo, sc);
Evaluation eval = Evaluation.fromJson(json);

 

如何保存(加载)Spark训练的神经网络

DL4J的Spark功能是围绕包装类的思想构建的,即,SparkDl4jMultiLayer和SparkComputationGraph内部使用标准的多层网络和计算图类。你可以分别使用SparkDl4jMultiLayer.getNetwork()和SparkComputationGraph.getNetwork()访问内部MultiLayerNetwork/ComputationGraph类。
为了在主机/驱动程序的本地文件系统上进行保存,请按照上述方式获得网络,并简单地使用ModelSerializer类或MultiLayerNetwork.save(File).load(File)ComputationGraph.save(File).load(File)方法。
为了保存(或从远程位置或分布式文件系统(如HDFS)加载,可以使用输入和输出流。
例如,

JavaSparkContext sc = new JavaSparkContext();
FileSystem fileSystem = FileSystem.get(sc.hadoopConfiguration());
String outputPath = "hdfs:///my/output/location/file.bin";
MultiLayerNetwork net = sparkNet.getNetwork();
try (BufferedOutputStream os = new BufferedOutputStream(fileSystem.create(new Path(outputPath)))) {
    ModelSerializer.writeModel(net, os, true);
}

读取是一个类似的过程:

JavaSparkContext sc = new JavaSparkContext();
FileSystem fileSystem = FileSystem.get(sc.hadoopConfiguration());
String outputPath = "hdfs:///my/output/location/file.bin";
MultiLayerNetwork net;
try(BufferedInputStream is = new BufferedInputStream(fileSystem.open(new Path(outputPath)))){
    net = ModelSerializer.restoreMultiLayerNetwork(is);
}


 

如何执行分布式推理

DL4J的Spark实现支持分布式推理。也就是说,我们可以使用一个机器集群轻松地在输入RDD上生成预测。这种分布式推理还可以用于在单台机器上训练网络并被Spark加载(有关如何加载保存的网络以便与Spark一起使用的详细信息,请参阅保存/加载章节)。
注:如果要进行评估(即,计算精度、F1、MSE等),请参考评估操作指南
用于执行分布式推理的方法签名如下:

SparkDl4jMultiLayer.feedForwardWithKey(JavaPairRDD<K, INDArray> featuresData, int batchSize) : JavaPairRDD<K, INDArray>
SparkComputationGraph.feedForwardWithKey(JavaPairRDD<K, INDArray[]> featuresData, int batchSize) : JavaPairRDD<K, INDArray[]>

当需要时,还存在接受输入掩码数组的重载。

注意,参数K-这是一个泛型类型,用于表示用于标识每个示例的唯一“键”。关值不被用作推理过程的一部分。这个键是必需的,因为Spark的RDD是无序的——如果没有这个键,我们就无法知道预测RDD中的哪个元素对应于输入RDD中的哪个元素。批量大小参数用于在执行推断时指定小批量大小。它不影响返回的值,而是用于平衡内存使用与计算效率:大批量计算总体上可能更快一些,但是需要更多的内存。在许多情况下,如果你不确定要使用什么,那么64的批量大小是尝试的好起点。
 

问题及故障排除指南

如何调试常见的Spark依赖问题(NoClassDefFoundExcption等)

Unfortunately, dependency problems at runtime can occur on a cluster if your project is not configured correctly. These problems can occur with any Spark jobs, not just those using DL4J - and they may be caused by other dependencies or libraries on the classpath, not by Deeplearning4j dependencies.

When dependency problems occur, they typically produce exceptions like:

不幸的是,如果项目配置不正确,则集群在运行时可能出现依赖性问题。这些问题可能出现在任何Spark作业中,而不仅仅是那些使用DL4J的作业,它们可能由类路径上的其他依赖项或库引起,而不是由DL4J依赖项引起。
当发生依赖性问题时,它们通常会产生如下异常:

  • NoSuchMethodException
  • ClassNotFoundException
  • AbstractMethodError

例如,不匹配的Spark版本(试图在Spark 2集群上使用Spark 1)可以如下所示:

java.lang.AbstractMethodError: org.deeplearning4j.spark.api.worker.ExecuteWorkerPathMDSFlatMap.call(Ljava/lang/Object;)Ljava/util/Iterator;

另一类错误是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)集群上,使用:

<dependency>
    <groupId>org.deeplearning4j</groupId>
    <artifactId>dl4j-spark_2.11</artifactId>
    <version>1.0.0-beta2_spark_2</version>
</dependency>

而在 Spark 1 (Scala 2.11) 集群,你应该使用:

<dependency>
    <groupId>org.deeplearning4j</groupId>
    <artifactId>dl4j-spark_2.11</artifactId>
    <version>1.0.0-beta2_spark_1</version>
</dependency>

步骤3: 检查 Scala 版本

Apache Spark发布了支持Scala 2.10和Scala 2.11的版本。

为了避免Scala版本的问题,你需要做两件事:(a)确保你的项目类路径上不存在Scala 2.10和Scala 2.11(或2.12)的混合依赖。检查依赖关系树中以_2.10或_2.11结尾的条目:例如,org.apache.spark:spark-core_2.11:jar:1.6.3:compile是Spark 1(1.6.3)依赖关系,使用Scala 2.11(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在类路径上时将无法运行。此外,这些库中的许多被划分为多个模块(即,多个独立的模块依赖项),当混合不同版本时,这些模块不能正确工作。

一些常见的会导致问题的库包括:

  • Jackson
  • Guava

DL4J和ND4J使用这些库的版本,应该避免与Spark的依赖冲突。然而,其他的(第三方库)可能引入这些依赖项的版本。
通常,异常将给出在哪里查找的提示——即,堆栈跟踪可以包括特定的类,该类可用于识别有问题的库。

步骤5:一旦被标识,修复依赖冲突

要调试这类问题,请仔细检查依赖树( mvn dependency:tree -Dverbose 的输出)。必要时,可以使用排除项或添加有问题的依赖项作为直接依赖项,以便在问题中强制使用其版本。要做到这一点,你需要将你想要的版本的依赖项直接添加到项目中。通常情况下,这足以解决这个问题。

请记住,当使用Spark submit时,Spark将向驱动程序和主节点的类路径添加Spark及其依赖库的副本。这意味着对于Spark添加的依赖项,你不能简单地在项目中排除它们——Spark submit将在运行时添加它们,无论你是否在项目中排除它们。

另一个值得了解的额外设置是(实验)Spark配置选项,spark.driver.userClassPathFirst 和 spark.executor.userClassPathFirst(有关详细信息,请参阅Spark配置文档)。在某些情况下,这些选项可能修复依赖性问题。

如何安全地缓存RDD[INDArray]和RDD[DataSet ]

Spark对于如何处理具有大的堆外组件的Java对象(例如DL4J中使用的DataSet和INDArray对象)存在一些问题。
要知道的要点是:

  • MEMORY_ONLY 和 MEMORY_AND_DISK 由于Spark没有正确估计RDD中对象的大小,对于堆外内存,持久性可能会有问题。这可能导致内存不足(堆外)问题。
  • 当持久化一个 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(块)大小来删除RDD(块)的一部分。Spark估计块大小的方式取决于持久化级别。对于MEMORY_ONLY 和 MEMORY_AND_DISK持久化,这是通过移动Java对象图来完成的,即,查看对象中的字段并递归地估计这些对象的大小。然而,这个过程并没有考虑DL4J或ND4J使用的堆外内存。对于像DataSets和INDArrays这样的对象(它们几乎全部存储在非堆中),Spark使用这个过程大大低估了对象的真实大小。此外,在决定是保留块还是删除块时,Spark只考虑堆内存的使用量。因为DataSet和INDArray对象在堆上的大小非常小,所以Spark将使用MEMORY_ONLY 和 MEMORY_AND_DISK 持久化保存太多对象,从而导致堆外内存耗尽,导致内存不足。

但是,对于MEMORY_ONLY_SER 和  MEMORY_AND_DISK_SER,在Java堆上存储了序列化形式的块。Spark可以精确地估计以序列化形式存储的对象的大小(序列化对象没有堆外内存组件),因此Spark在需要时将丢弃块,从而避免任何内存不足的问题。

如何修复“Error querying NTP server”错误

DL4J的参数平均实现可以通过使用SparkDl4jMultiLayer.setCollectTrainingStats(true)收集训练统计数据。当启用此功能时,需要互联网访问来连接到NTP(网络时间协议)服务器。

有可能出现类似于NTPTimeSource错误:Error querying NTP server, attempt 1 of 10. Sometimes these failures are transient (later retries will work) and can be ignored。然而,如果Spark集群被配置为使一个或多个工作节点不能访问因特网(或者具体地说,NTP服务器),则所有重试都可能失败。

有两种解决方案:

  1. 不使用 sparkNet.setCollectTrainingStats(true) - 此功能是可选的(训练不需要),默认情况下禁用
  2. 将系统设置为使用本地机器时钟而不是NTP服务器作为时间源(但是要注意,结果时间线信息可能非常不准确)要使用系统时钟时间源,请在Spark submit中添加以下内容:
    --conf spark.driver.extraJavaOptions=-Dorg.deeplearning4j.spark.time.TimeSource=org.deeplearning4j.spark.time.SystemClockTimeSource
    --conf spark.executor.extraJavaOptions=-Dorg.deeplearning4j.spark.time.TimeSource=org.deeplearning4j.spark.time.SystemClockTimeSource
    

 

Ubuntu 16.04的失败训练(Ubuntu bug可能影响DL4J Spark用户)

当在Ubuntu 16.04机器上的YARN集群上运行Spark时,很可能在完成作业后,运行Hadoop/YARN的用户拥有的所有进程都被杀死。这与Ubuntu中的一个bug有关,它被记录在https://bugs.launchpad.net/ubuntu/+source/procps/+bug/1610499。在Stackoverflow上也有关于此BUG的讨论http://stackoverflow.com/questions/38419078/logouts-while-running-hadoop-under-ubuntu-16-04。

提出了一些解决办法。

选项 1

添加

[login]
KillUserProcesses=no

到 /etc/systemd/logind.conf, 并重启。

选项 2

复制Ubuntu 14.04中的/bin /kill 二进制文件,并使用该二进制文件。

选项 3

降级到 Ubuntu 14.04

选项 4

在集群节点上运行 sudo loginctl enable-linger hadoop_user_name 

 

注:下文中的 *** 代表文件名中的组件名称。 # 包含: 中文-英文对照文档:【***-javadoc-API文档-中文(简体)-英语-对照版.zip】 jar包下载地址:【***.jar下载地址(官方地址+国内镜像地址).txt】 Maven依赖:【***.jar Maven依赖信息(可用于项目pom.xml).txt】 Gradle依赖:【***.jar Gradle依赖信息(可用于项目build.gradle).txt】 源代码下载地址:【***-sources.jar下载地址(官方地址+国内镜像地址).txt】 # 本文件关键字: 中文-英文对照文档,中英对照文档,java,jar包,Maven,第三方jar包,组件,开源组件,第三方组件,Gradle,中文API文档,手册,开发手册,使用手册,参考手册 # 使用方法: 解压 【***.jar中文文档.zip】,再解压其中的 【***-javadoc-API文档-中文(简体)版.zip】,双击 【index.html】 文件,即可用浏览器打开、进行查看。 # 特殊说明: ·本文档为人性化翻译,精心制作,请放心使用。 ·本文档为双语同时展示,一行原文、一行译文,可逐行对照,避免了原文/译文来回切换的麻烦; ·有原文可参照,不再担心翻译偏差误导; ·边学技术、边学英语。 ·只翻译了该翻译的内容,如:注释、说明、描述、用法讲解 等; ·不该翻译的内容保持原样,如:类名、方法名、包名、类型、关键字、代码 等。 # 温馨提示: (1)为了防止解压后路径太长导致浏览器无法打开,推荐在解压时选择“解压到当前文件夹”(放心,自带文件夹,文件不会散落一地); (2)有时,一套Java组件会有多个jar,所以在下载前,请仔细阅读本篇描述,以确保这就是你需要的文件;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值