Contents

Deeplearning4j 简介

1. 简介

在本文中,我们将使用deeplearning4j (dl4j) 库创建一个简单的神经网络,该库是一种现代且强大的机器学习工具。

在我们开始之前,并不是说本指南不需要深入了解线性代数、统计学、机器学习理论和许多其他知识渊博的 ML 工程师所必需的主题。

2. 什么是深度学习?

神经网络是由互连的节点层组成的计算模型。

节点是数字数据的类似神经元的处理器。他们从输入中获取数据,对这些数据应用一些权重和函数,然后将结果发送到输出。这种网络可以用一些源数据的例子来训练。

训练本质上是在节点中保存一些数字状态(权重),这些状态随后会影响计算。训练示例可能包含具有特征的数据项和这些项的某些已知类别(例如,“这组 16×16 像素包含一个手写字母“a”)。

训练完成后,神经网络可以 从新数据中获取信息,即使它以前没有见过这些特定的数据项。一个模型良好且训练有素的网络可以识别图像、手写字母、语音、处理统计数据以产生商业智能结果等等。

近年来,随着高性能和并行计算的进步,深度神经网络成为可能。这种网络不同于简单的神经网络,因为它们由多个中间(或隐藏层 )组成。这种结构允许网络以更复杂的方式(以递归、循环、卷积等方式)处理数据,并从中提取更多信息。

3. 设置项目

要使用该库,我们至少需要 Java 7。此外,由于一些原生组件,它仅适用于 64 位 JVM 版本。 在开始使用指南之前,让我们检查是否满足要求:

$ java -version
java version "1.8.0_131"
Java(TM) SE Runtime Environment (build 1.8.0_131-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.131-b11, mixed mode)

首先,让我们将所需的库添加到我们的 Maven pom.xml文件中。我们将库的版本提取到一个属性条目(对于最新版本的库,请查看Maven 中央存储库 ):

<properties>
    <dl4j.version>0.9.1</dl4j.version>
</properties>
<dependencies>
    <dependency>
        <groupId>org.nd4j</groupId>
        <artifactId>nd4j-native-platform</artifactId>
        <version>${dl4j.version}</version>
    </dependency>
    <dependency>
        <groupId>org.deeplearning4j</groupId>
        <artifactId>deeplearning4j-core</artifactId>
        <version>${dl4j.version}</version>
    </dependency>
</dependencies>

请注意,nd4j-native-platform依赖项是几个可用的实现之一。

它依赖于可用于许多不同平台(macOS、Windows、Linux、Android 等)的本机库。如果我们想在支持 CUDA 编程模型的显卡上执行计算,我们也可以将后端切换到nd4j-cuda-8.0-platform

4. 准备数据

4.1. 准备数据集文件

我们将编写机器学习的“Hello World”——鸢尾花数据集 的分类。这是一组从不同物种的花中收集的数据(Iris setosaIris versicolorIris virginica)。

这些物种的花瓣和萼片的长度和宽度不同。很难编写一个精确的算法来对输入数据项进行分类(即确定一朵特定的花属于什么物种)。但是一个训练有素的神经网络可以快速分类它并且几乎没有错误。

我们将使用此数据的 CSV 版本,其中第 0..3 列包含物种的不同特征,第 4 列包含记录的类别或物种,用值 0、1 或 2 编码:

5.1,3.5,1.4,0.2,0
4.9,3.0,1.4,0.2,0
4.7,3.2,1.3,0.2,0
7.0,3.2,4.7,1.4,1
6.4,3.2,4.5,1.5,1
6.9,3.1,4.9,1.5,1

4.2. 向量化和读取数据

我们用数字对类进行编码,因为神经网络使用数字。将现实世界的数据项转换为一系列数字(向量)称为向量化——deeplearning4j 使用datavec 库来执行此操作。

首先,让我们使用这个库来输入带有矢量化数据的文件。创建CSVRecordReader时,我们可以指定要跳过的行数(例如,如果文件有标题行)和分隔符(在我们的例子中是逗号):

try (RecordReader recordReader = new CSVRecordReader(0, ',')) {
    recordReader.initialize(new FileSplit(
      new ClassPathResource("iris.txt").getFile()));
    // …
}

要遍历记录,我们可以使用DataSetIterator接口的多种实现中的任何一种。数据集可能非常庞大,分页或缓存值的能力可能会派上用场。

但是我们的小数据集只包含 150 条记录,所以让我们通过调用*iterator.next()*一次将所有数据读入内存。

**我们还指定了类列的索引,**在我们的例子中它与特征计数 (4)和类的总数(3) 相同。

另外,请注意,我们需要打乱数据集以摆脱原始文件中的类排序。

我们指定一个常量随机种子 (42) 而不是默认的*System.currentTimeMillis()*调用,这样洗牌的结果总是相同的。这使我们每次运行程序时都能获得稳定的结果:

DataSetIterator iterator = new RecordReaderDataSetIterator(
  recordReader, 150, FEATURES_COUNT, CLASSES_COUNT);
DataSet allData = iterator.next();
allData.shuffle(42);

4.3. 规范化和拆分

在训练之前我们应该对数据做的另一件事是对其进行标准化。标准化 是一个两阶段的过程:

  • 收集有关数据的一些统计信息(拟合)
  • 以某种方式更改(转换)数据以使其统一

对于不同类型的数据,标准化可能会有所不同。

例如,如果我们要处理各种尺寸的图像,我们应该首先收集尺寸统计信息,然后将图像缩放到统一的尺寸。

但是对于数字,归一化通常意味着将它们转换为所谓的正态分布。NormalizerStandardize类可以帮助我们:

DataNormalization normalizer = new NormalizerStandardize();
normalizer.fit(allData);
normalizer.transform(allData);

现在数据已经准备好,我们需要将集合分成两部分。

第一部分将用于培训课程。我们将使用数据的第二部分(网络根本看不到)来测试经过训练的网络。

这将使我们能够验证分类是否正确工作。我们将 65% 的数据 (0.65) 用于训练,剩下的 35% 用于测试:

SplitTestAndTrain testAndTrain = allData.splitTestAndTrain(0.65);
DataSet trainingData = testAndTrain.getTrain();
DataSet testData = testAndTrain.getTest();

5. 准备网络配置

5.1. 流畅的配置生成器

现在我们可以使用花哨的 fluent 构建器构建我们的网络配置:

MultiLayerConfiguration configuration 
  = new NeuralNetConfiguration.Builder()
    .iterations(1000)
    .activation(Activation.TANH)
    .weightInit(WeightInit.XAVIER)
    .learningRate(0.1)
    .regularization(true).l2(0.0001)
    .list()
    .layer(0, new DenseLayer.Builder().nIn(FEATURES_COUNT).nOut(3).build())
    .layer(1, new DenseLayer.Builder().nIn(3).nOut(3).build())
    .layer(2, new OutputLayer.Builder(
      LossFunctions.LossFunction.NEGATIVELOGLIKELIHOOD)
        .activation(Activation.SOFTMAX)
        .nIn(3).nOut(CLASSES_COUNT).build())
    .backprop(true).pretrain(false)
    .build();

即使使用这种简化、流畅的方式来构建网络模型,也有很多需要消化和调整的参数。让我们分解这个模型。

5.2. 设置网络参数

**iterations()构建器方法指定优化迭代的次数。

迭代优化意味着在训练集上执行多次传递,直到网络收敛到一个好的结果。

通常,在对真实和大型数据集进行训练时,我们使用多个 epoch(数据通过网络的完整传递)和每个 epoch 的一次迭代。但是由于我们的初始数据集是最小的,我们将使用一个 epoch 和多次迭代。

**activation()是一个在节点内部运行以确定其输出的函数。

最简单的激活函数是线性*f(x) = x。*但事实证明,只有非线性函数才能让网络通过使用几个节点来解决复杂的任务。

我们可以在org.nd4j.linalg.activations.Activation枚举中查找许多不同的激活函数。如果需要,我们还可以编写激活函数。但我们将使用提供的双曲正切 (tanh) 函数。

***weightInit()*方法指定了为网络设置初始权重的多种方法之一。**正确的初始权重可以深刻地影响训练的结果。无需过多讨论数学,让我们将其设置为一种高斯分布 ( WeightInit.XAVIER ),因为这通常是一个不错的选择。

所有其他权重初始化方法都可以在org.deeplearning4j.nn.weights.WeightInit枚举中查找。

学习率是一个重要的参数,它深刻地影响着网络的学习能力。

在更复杂的情况下,我们可能会花费大量时间来调整此参数。但是对于我们的简单任务,我们将使用一个非常重要的值 0.1,并使用*learningRate()*构建器方法进行设置。

训练神经网络的问题之一是当网络“记忆”训练数据时过度拟合的情况。

当网络为训练数据设置过高的权重并对任何其他数据产生不良结果时,就会发生这种情况。

为了解决这个问题,我们将使用*.regularization(true).l2(0.0001)*行设置 l2 正则化。正则化“惩罚”网络权重过大并防止过度拟合。

5.3. 构建网络层

接下来,我们创建一个密集(也称为完全连接)层的网络。

第一层应包含与训练数据中的列相同数量的节点 (4)。

第二个密集层将包含三个节点。这是我们可以改变的值,但前一层的输出数量必须相同。

最终输出层应包含与类数(3)匹配的节点数。网络结构如图所示:

/uploads/deeplearning4j/1.png

成功训练后,我们将拥有一个通过其输入接收四个值并向其三个输出之一发送信号的网络。这是一个简单的分类器。

最后,为了完成网络的构建,我们设置了反向传播(最有效的训练方法之一)并使用*.backprop(true).pretrain(false)*行禁用预训练。

6. 创建和训练网络

现在让我们从配置中创建一个神经网络,初始化并运行它:

MultiLayerNetwork model = new MultiLayerNetwork(configuration);
model.init();
model.fit(trainingData);

现在我们可以使用数据集的其余部分来测试训练好的模型,并使用三个类别的评估指标来验证结果:

INDArray output = model.output(testData.getFeatureMatrix());
Evaluation eval = new Evaluation(3);
eval.eval(testData.getLabels(), output);

如果我们现在打印出eval.stats(),我们会看到我们的网络非常擅长对鸢尾花进行分类,尽管它确实将第 1 类误认为第 2 类 3 次。

Examples labeled as 0 classified by model as 0: 19 times
Examples labeled as 1 classified by model as 1: 16 times
Examples labeled as 1 classified by model as 2: 3 times
Examples labeled as 2 classified by model as 2: 15 times
==========================Scores========================================
# of classes: 3
Accuracy: 0.9434
Precision: 0.9444
Recall: 0.9474
F1 Score: 0.9411
Precision, recall & F1: macro-averaged (equally weighted avg. of 3 classes)
========================================================================

fluent 配置构建器允许我们快速添加或修改网络层,或调整一些其他参数以查看我们的模型是否可以改进。