java-dl-ess-merge-1

51 阅读30分钟

Java 深度学习精要(二)

原文:annas-archive.org/md5/f656b5ad70de5892c3b50a841e6d607e

译者:飞龙

协议:CC BY-NC-SA 4.0

第五章 探索 Java 深度学习库——DL4J、ND4J 及更多

在前几章中,你学习了深度学习算法的核心理论,并从零开始实现了它们。虽然我们现在可以说,深度学习的实现并不那么困难,但我们不能否认,实施模型仍然需要一些时间。为了解决这个问题,在本章中,你将学习如何使用 Java 深度学习库编写代码,这样我们就可以更多地专注于数据分析的关键部分,而不是琐碎的细节。

本章中你将学习的主题包括:

  • Java 深度学习库简介

  • 示例代码以及如何使用该库编写你自己的代码

  • 一些优化模型以提高精度率的附加方法

从头实现与使用库/框架

我们在第二章中实现了神经网络的机器学习算法,机器学习算法——为深度学习做准备,并在第三章,深度信念网络与堆叠去噪自编码器和第四章,Dropout 和卷积神经网络中从头实现了许多深度学习算法。当然,我们可以通过一些定制将自己的代码应用于实际应用,但在我们想要利用它们时必须小心,因为我们不能否认它们未来可能引发的问题。可能是什么问题呢?以下是可能的情况:

  • 我们编写的代码缺少一些更好的优化参数,因为我们只是为了简化问题并帮助你更好地理解概念而实现了算法的核心部分。虽然你仍然可以使用这些代码来训练和优化模型,但通过添加你自己实现的额外参数,你可以获得更高的精度率。

  • 如前章所述,本书中仍有许多有用的深度学习算法未被解释。虽然你现在已经掌握了深度学习算法的核心组件,但你可能需要实现额外的类或方法,以在你的领域和应用中获得期望的结果。

  • 假定的时间消耗对应用非常关键,尤其是当你考虑分析大量数据时。确实,相较于 Python 和 R 等其他流行语言,Java 在速度方面有更好的性能,但你仍然需要考虑时间成本。一个可行的解决方案是使用 GPU 代替 CPU,但这需要复杂的实现来调整代码以适应 GPU 计算。

这些是主要的因果问题,你可能还需要考虑到我们没有在代码中处理异常。

这并不意味着从头实现会有致命的错误。我们编写的代码可以作为处理某些规模数据的应用程序;然而,如果你使用大规模数据挖掘,通常需要深度学习,那么你需要考虑,你所实现的基础部分需要进一步编码。这意味着,你需要记住,从头实现具有更大的灵活性,因为在必要时你可以更改代码,但同时它也有负面影响,即算法的调优和维护必须独立完成。

那么,如何解决刚才提到的问题呢?这就是库(或框架)派上用场的地方。得益于全球对深度学习的积极研究,世界各地有许多使用各种编程语言开发并发布的库。当然,每个库都有各自的特点,但每个库共同具备的特点可以总结如下:

  • 只需定义深度学习的层结构,就可以完成模型的训练。你可以专注于参数设置和调优,而无需考虑算法。

  • 大多数库作为开源项目向公众开放,并且每天都在积极更新。因此,如果出现 bug,很有可能这些 bug 会被迅速修复(当然,如果你自己修复它并提交到项目中,这也是受欢迎的)。

  • 在 CPU 和 GPU 之间切换程序运行非常容易。由于库补充了 GPU 计算中的繁琐编码部分,你可以专注于实现,而无需考虑 CPU 或 GPU,只要机器支持 GPU。

简而言之,你可以省去所有在从头实现库时可能会遇到的麻烦部分。得益于此,你可以将更多时间花在核心数据挖掘部分,因此,如果你希望利用实际应用,使用库进行数据分析时效率更高的可能性也会大大增加。

然而,过度依赖库并不好。使用库虽然方便,但也有一些缺点,如下所列:

  • 由于你可以轻松构建各种深度学习模型,你可以在没有具体理解模型所依赖的理论的情况下进行实现。如果我们只考虑与特定模型相关的实现,这可能不是问题,但当你想结合其他方法或在应用模型时考虑其他方法时,可能会遇到无法处理的风险。

  • 你不能使用库中不支持的算法,因此可能会遇到无法选择自己想用的模型的情况。这个问题可以通过版本升级来解决,但另一方面,过去某些实现的部分可能由于规范更改而被弃用。此外,我们不能排除库的开发突然终止或由于许可证的突然变更,使用该库变为收费的可能性。在这些情况下,你之前开发的代码可能无法再使用。

  • 你从实验中获得的精度取决于库的实现方式。例如,如果我们在两个不同的库中使用相同的神经网络模型进行实验,得到的结果可能会有很大的不同。这是因为神经网络算法包括随机操作,而且机器的计算精度是有限的,即在计算过程中,基于实现方法的不同,计算值可能会有波动。

由于你在前几章中已经很好地理解了深度学习算法的基本概念和理论,因此我们不需要担心第一个问题。然而,我们需要小心剩下的两个问题。从下一节开始,将介绍如何使用库进行实现,并且我们将更加关注刚刚讨论的优缺点。

介绍 DL4J 和 ND4J

全球范围内已经开发了很多深度学习库。2015 年 11 月,TensorFlowwww.tensorflow.org/),由 Google 开发的机器学习/深度学习库,公开发布并引起了广泛关注。

当我们看一下开发库所使用的编程语言时,大多数公开的库都是用 Python 开发的或使用 Python API。TensorFlow 的后端是用 C++开发的,但也可以用 Python 编写代码。本书重点讲解使用 Java 学习深度学习,因此其他语言开发的库将在第七章中简要介绍,其他重要的深度学习库

那么,我们有哪些基于 Java 的库可以使用呢?实际上,积极开发的库并不多(也许还有一些未公开的项目)。然而,我们实际上可以使用的库只有一个:Deeplearning4jDL4J)。官方项目页面的 URL 是deeplearning4j.org/。这个库也是开源的,源代码全部发布在 GitHub 上,网址是github.com/deeplearning4j/deeplearning4j。该库由 Skymind 开发(www.skymind.io/)。这个库是什么样的库呢?如果你查看项目页面,它是这样介绍的:

"Deeplearning4j 是第一个为 Java 和 Scala 编写的商业级开源分布式深度学习库。与 Hadoop 和 Spark 集成,DL4J 旨在用于商业环境,而非作为研究工具。Skymind 是它的商业支持部门。"

Deeplearning4j 旨在成为尖端的即插即用,注重约定而非配置,这使得非研究人员能够快速进行原型开发。DL4J 具有可扩展的自定义功能。它在 Apache 2.0 许可证下发布,DL4J 的所有衍生作品归其作者所有。

当你阅读到这里时,你会发现 DL4J 的最大特点是它是以与 Hadoop 集成为前提设计的。这表明,DL4J 非常适合处理大规模数据,且比其他库更具可扩展性。此外,DL4J 支持 GPU 计算,因此能够更快速地处理数据。

此外,DL4J 内部使用一个名为Java 的 N 维数组ND4J)的库。该项目页面为nd4j.org/。与 DL4J 相同,这个库也作为开源项目发布在 GitHub 上:github.com/deeplearning4j/nd4j。该库的开发者与 DL4J 相同,都是 Skymind。正如库名所示,这是一个科学计算库,使我们能够处理多功能的n维数组对象。如果你是 Python 开发者,可以通过类比 NumPy 来更容易理解它,因为 ND4J 是一个受 NumPy 启发的库。ND4J 还支持 GPU 计算,DL4J 能够进行 GPU 集成的原因就是它在内部使用了 ND4J。

在 GPU 上与它们一起工作能带来什么好处?让我们简要看一下这一点。CPU 和 GPU 之间最大的区别在于核心数量的差异。GPU,顾名思义,是一个图形处理单元,最初是一个用于图像处理的集成电路。这也是 GPU 能够优化同时处理相同命令的原因。并行处理是它的强项。另一方面,CPU 需要处理各种命令,这些任务通常是按顺序处理的。与 CPU 相比,GPU 擅长处理大量简单的任务,因此像深度学习训练迭代这样的计算是它的专长。

ND4J 和 DL4J 对于深度学习中的研究和数据挖掘非常有用。从下一节开始,我们将通过简单的例子来看它们是如何用于深度学习的。因为你现在应该已经理解了深度学习的核心理论,所以你可以很容易理解这些内容。希望你能将其应用于你的研究领域或业务中。

使用 ND4J 的实现

由于有很多情况下 ND4J 本身就能方便地使用,因此在深入 DL4J 的解释之前,让我们简要了解一下如何使用 ND4J。如果你只打算使用 ND4J,一旦创建了一个新的 Maven 项目,你可以通过在pom.xml中添加以下代码来使用 ND4J:

<properties>
   <nd4j.version>0.4-rc3.6</nd4j.version>
</properties>

<dependencies>
   <dependency>
       <groupId>org.nd4j</groupId>
       <artifactId>nd4j-jblas</artifactId>
       <version>${nd4j.version}</version>
   </dependency>
   <dependency>
       <groupId>org.nd4j</groupId>
       <artifactId>nd4j-perf</artifactId>
       <version>${nd4j.version}</version>
   </dependency>
</dependencies>

这里,<nd4j.version>描述了 ND4J 的最新版本,但在实际实现代码时,请检查它是否已经更新。另外,使用 ND4J 时从 CPU 切换到 GPU 非常简单。如果你已经安装了 CUDA 7.0 版本,那么你只需按照以下方式定义artifactId

<dependency>
   <groupId>org.nd4j</groupId>
   <artifactId>nd4j-jcublas-7.0</artifactId>
   <version>${nd4j.version}</version>
</dependency>

你可以根据你的配置替换<artifactId>的版本。

让我们看一个使用 ND4J 进行计算的简单例子。我们在 ND4J 中使用的类型是INDArray,即Array的扩展类型。我们首先导入以下依赖:

import org.nd4j.linalg.api.ndarray.INDArray;
import org.nd4j.linalg.factory.Nd4j;

然后,我们定义INDArray如下:

INDArray x = Nd4j.create(new double[]{1, 2, 3, 4, 5, 6}, new int[]{3, 2});
System.out.println(x);

Nd4j.create接受两个参数。前者定义了INDArray中的实际值,后者定义了向量(矩阵)的形状。通过运行这段代码,你将得到以下结果:

[[1.00,2.00]
 [3.00,4.00]
 [5.00,6.00]]

由于INDArray可以通过System.out.print输出其值,因此调试非常简单。标量计算也可以轻松完成。像下面这样将 1 加到x

x.add(1);

然后,你将得到以下输出:

[[2.00,3.00]
 [4.00,5.00]
 [6.00,7.00]]

此外,INDArray中的计算可以轻松完成,以下例子展示了这一点:

INDArray y = Nd4j.create(new double[]{6, 5, 4, 3, 2, 1}, new int[]{3, 2});

然后,基本的算术运算可以表示如下:

x.add(y)
x.sub(y)
x.mul(y)
x.div(y)

这些将返回以下结果:

[[7.00,7.00]
 [7.00,7.00]
 [7.00,7.00]]
[[-5.00,-3.00]
 [-1.00,1.00]
 [3.00,5.00]]
[[6.00,10.00]
 [12.00,12.00]
 [10.00,6.00]]
[[0.17,0.40]
 [0.75,1.33]
 [2.50,6.00]]

此外,ND4J 具有破坏性算术运算符。当你写下x.addi(y)命令时,x会改变自己的值,因此System.out.println(x);将返回以下输出:

[[7.00,7.00]
 [7.00,7.00]
 [7.00,7.00]]

同样,subimulidivi 也是破坏性操作符。还有许多其他方法可以方便地进行向量或矩阵之间的计算。更多信息可以参考 nd4j.org/documentation.htmlnd4j.org/doc/http://nd4j.org/apidocs/

让我们再看一个例子,看看如何使用 ND4J 编写机器学习算法。我们将实现最简单的例子——感知机,基于 第二章 中编写的源代码,机器学习算法——为深度学习做准备。我们设置包名 DLWJ.examples.ND4J,文件(类)名为 Perceptrons.java

首先,让我们添加以下两行从 ND4J 导入:

import org.nd4j.linalg.api.ndarray.INDArray;
import org.nd4j.linalg.factory.Nd4j;

该模型有两个参数:输入层的 num 和权重。前者与之前的代码相同;然而,后者不是 Array,而是 INDArray

public int nIn;       // dimensions of input data
public INDArray w;

从构造函数中可以看到,由于感知机的权重表示为一个向量,因此行数设置为输入层中单元的数量,列数设置为 1。这个定义在这里写出:

public Perceptrons(int nIn) {

   this.nIn = nIn;
   w = Nd4j.create(new double[nIn], new int[]{nIn, 1});

}

然后,由于我们将模型参数定义为 INDArray,我们还将演示数据、训练数据和测试数据定义为 INDArray。你可以在主方法的开头看到这些定义:

INDArray train_X = Nd4j.create(new double[train_N * nIn], new int[]{train_N, nIn});  // input data for training
INDArray train_T = Nd4j.create(new double[train_N], new int[]{train_N, 1});          // output data (label) for training

INDArray test_X = Nd4j.create(new double[test_N * nIn], new int[]{test_N, nIn});  // input data for test
INDArray test_T = Nd4j.create(new double[test_N], new int[]{test_N, 1});          // label of inputs
INDArray predicted_T = Nd4j.create(new double[test_N], new int[]{test_N, 1});     // output data predicted by the model

当我们将一个值替换到 INDArray 中时,我们使用 put。请注意,使用 put 设置的任何值只能是 scalar 类型的值:

train_X.put(i, 0, Nd4j.scalar(g1.random()));
train_X.put(i, 1, Nd4j.scalar(g2.random()));
train_T.put(i, Nd4j.scalar(1));

模型构建和训练的流程与之前的代码相同:

// construct perceptrons
Perceptrons classifier = new Perceptrons(nIn);

// train models
while (true) {
   int classified_ = 0;

   for (int i=0; i < train_N; i++) {
       classified_ += classifier.train(train_X.getRow(i), train_T.getRow(i), learningRate);
   }

   if (classified_ == train_N) break;  // when all data classified correctly

   epoch++;
   if (epoch > epochs) break;
}

每一条训练数据都通过 getRow() 被传递给 train 方法。首先,我们来看一下 train 方法的完整内容:

public int train(INDArray x, INDArray t, double learningRate) {

   int classified = 0;

   // check if the data is classified correctly
   double c = x.mmul(w).getDouble(0) * t.getDouble(0);

   // apply steepest descent method if the data is wrongly classified
   if (c > 0) {
       classified = 1;
   } else {
       w.addi(x.transpose().mul(t).mul(learningRate));
   }

   return classified;
}

我们首先将注意力集中在以下代码上:

   // check if the data is classified correctly
   double c = x.mmul(w).getDouble(0) * t.getDouble(0);

这是检查感知机是否正确分类数据的部分,如下所示的方程:

使用 ND4J 的实现

从代码中可以看到,.mmul() 是用于向量或矩阵之间的乘法。我们在 第二章,机器学习算法——为深度学习做准备 中写了这部分计算,如下所示:

   double c = 0.;

   // check if the data is classified correctly
   for (int i = 0; i < nIn; i++) {
       c += w[i] * x[i] * t;
   }

通过对比两段代码,可以看到向量或矩阵之间的乘法可以轻松通过 INDArray 来写,因此你可以直观地实现算法,只需要跟随方程即可。

更新模型参数的方程如下:

       w.addi(x.transpose().mul(t).mul(learningRate));

在这里,同样,你可以像编写数学方程一样实现代码。方程表示如下:

使用 ND4J 的实现

上次我们实现这一部分时,是通过 for 循环来编写的:

for (int i = 0; i < nIn; i++) {
   w[i] += learningRate * x[i] * t;
}

此外,训练后的预测也是标准的前向激活,表示为以下方程:

使用 ND4J 实现

在这里:

使用 ND4J 实现

我们可以仅用一行代码简单地定义 predict 方法,如下所示:

public int predict(INDArray x) {

   return step(x.mmul(w).getDouble(0));
}

当你运行程序时,可以看到其精度和准确性,召回率与我们使用之前代码得到的结果相同。

因此,通过将算法实现类比于数学方程,它将极大地帮助你。我们这里仅实现感知器,但请尝试自行实现其他算法。

使用 DL4J 的实现

ND4J 是一个帮助你轻松方便实现深度学习的库。然而,你仍然需要自己实现算法,这与前几章的实现方式没有太大区别。换句话说,ND4J 只是一个使得计算数值变得更容易的库,并不是专门为深度学习算法优化的库。使得深度学习更容易处理的库是 DL4J。幸运的是,关于 DL4J,有一些包含典型方法的示例代码已经发布在 GitHub 上(github.com/deeplearning4j/dl4j-0.4-examples)。这些示例代码的前提是你使用的是 DL4J 的版本 0.4-*。当你实际克隆该仓库时,请再次检查最新版本。在这一节中,我们将从这些示例程序中提取出基本部分并进行查看。我们将在此节中引用 github.com/yusugomori/dl4j-0.4-examples 上的分支仓库作为截图示例。

设置

首先,从我们克隆的仓库中设置环境。如果你使用 IntelliJ,可以从 文件 | 新建 | 从现有源导入项目 中导入该项目,选择仓库的路径。然后,选择 从外部模型导入项目,并选择 Maven,如下所示:

设置

除了点击下一步,其他步骤不需要做任何特殊操作。请注意,支持的 JDK 版本为 1.7 或更高版本。由于在前几章中我们已经需要版本 1.8 或更高版本,这应该不是问题。一旦顺利完成设置,你可以确认目录结构如下:

设置

一旦你设置好了项目,我们首先来看一下 pom.xml。你可以看到与 DL4J 相关的包的描述如下所示:

<dependency>
   <groupId>org.deeplearning4j</groupId>
   <artifactId>deeplearning4j-nlp</artifactId>
   <version>${dl4j.version}</version>
</dependency>

<dependency>
   <groupId>org.deeplearning4j</groupId>
   <artifactId>deeplearning4j-core</artifactId>
   <version>${dl4j.version}</version>
</dependency>

另外,你可以从以下内容看到 DL4J 依赖于 ND4J:

<dependency>
   <groupId>org.nd4j</groupId>
   <artifactId>nd4j-x86</artifactId>
   <version>${nd4j.version}</version>
</dependency>

如果你想在 GPU 上运行程序,所需要做的就是修改这一部分代码。如前所述,如果你安装了 CUDA,可以按如下方式编写:

<dependency>
   <groupId>org.nd4j</groupId>
   <artifactId>nd4j-jcublas-XXX</artifactId>
   <version>${nd4j.version}</version>
</dependency>

这里,XXX 是 CUDA 的版本号,取决于你机器的偏好。仅使用这个进行 GPU 计算是非常棒的。我们无需做任何特殊的操作,可以专注于深度学习的实现。

DL4J 开发和使用的另一个特征库是 Canova。对应于 pom.xml 的部分如下:

<dependency>
   <artifactId>canova-nd4j-image</artifactId>
   <groupId>org.nd4j</groupId>
   <version>${canova.version}</version>
</dependency>
<dependency>
   <artifactId>canova-nd4j-codec</artifactId>
   <groupId>org.nd4j</groupId>
   <version>${canova.version}</version>
</dependency>

当然,Canova 也是一个开源库,其源代码可以在 GitHub 上查看,地址是 github.com/deeplearning4j/Canova。正如该页面所述,Canova 是一个用于将原始数据向量化为机器学习工具可以使用的向量格式的库。这也有助于我们专注于数据挖掘中的更重要部分,因为数据格式化在任何研究或实验中都是不可或缺的。

构建

让我们来看看示例中的源代码,了解如何构建一个深度学习模型。在这个过程中,你还会看到一些你还未学习的深度学习术语,并得到简要解释。这些示例实现了各种模型,如 MLP、DBN 和 CNN,但这里有一个问题。正如在 README.md 中看到的那样,一些方法没有生成良好的精度。这是因为,正如前一部分所解释的那样,机器的计算精度是有限的,在过程中的计算值波动完全依赖于实现的差异。因此,从实际情况来看,学习无法顺利进行,尽管理论上应该是可行的。你可以通过改变种子值或调整参数来获得更好的结果,但由于我们希望专注于如何使用这个库,我们将以一个精度更高的模型为例。

DBNIrisExample.java

我们首先来看看位于 deepbelief 包中的 DBNIrisExample.java。文件名中的 "Iris" 是一个常用于衡量机器学习方法精度或准确性的基准数据集之一。该数据集包含来自 3 个类别的 150 条数据,每个类别包含 50 个实例,每个类别对应一种鸢尾花的类型。输入特征数为 4,因此输出的类别数为 3。一个类别可以与另外两个类别线性可分;而后两个类别之间则不是线性可分的。

实现从设置配置开始。以下是需要设置的变量:

final int numRows = 4;
final int numColumns = 1;
int outputNum = 3;
int numSamples = 150;
int batchSize = 150;
int iterations = 5;
int splitTrainNum = (int) (batchSize * .8);
int seed = 123;
int listenerFreq = 1;

在 DL4J 中,输入数据最多可以是二维数据,因此你需要指定数据的行数和列数。由于 Iris 数据是一维的,numColumns被设置为1。这里的numSamples是总数据量,batchSize是每个小批量的数据量。由于总数据为 150 且相对较小,batchSize被设置为相同的数量。这意味着学习是在不将数据拆分为小批量的情况下进行的。splitTrainNum是决定训练数据和测试数据分配的变量。这里,80%的数据用于训练,20%用于测试。在前一节中,listenerFreq决定了我们在过程中多频繁地查看损失函数的值并记录。这里将其设置为 1,意味着在每个 epoch 后记录一次值。

随后,我们需要获取数据集。在 DL4J 中,已经准备了一个可以轻松获取典型数据集(如 Iris、MINST 和 LFW)的类。因此,如果你想获取 Iris 数据集,只需要写以下这行代码:

DataSetIterator iter = new IrisDataSetIterator(batchSize, numSamples);

以下两行代码用于格式化数据:

DataSet next = iter.next();
next.normalizeZeroMeanZeroUnitVariance();

这段代码将数据拆分为训练数据和测试数据,并分别存储:

SplitTestAndTrain testAndTrain = next.splitTestAndTrain(splitTrainNum, new Random(seed));
DataSet train = testAndTrain.getTrain();
DataSet test = testAndTrain.getTest();

如你所见,它通过将 DL4J 准备的所有数据处理成DataSet类,简化了数据处理。

现在,让我们实际构建一个模型。基本结构如下:

MultiLayerConfiguration conf = new NeuralNetConfiguration.Builder().layer().layer() … .layer().build();
MultiLayerNetwork model = new MultiLayerNetwork(conf);
model.init();

代码首先定义了模型配置,然后根据定义构建和初始化实际模型。让我们来看看配置的细节。开始时,整个网络被设置好:

MultiLayerConfiguration conf = new NeuralNetConfiguration.Builder()
   .seed(seed)
   .iterations(iterations)
   .learningRate(1e-6f)
   .optimizationAlgo(OptimizationAlgorithm.CONJUGATE_GRADIENT)
   .l1(1e-1).regularization(true).l2(2e-4)
   .useDropConnect(true)
   .list(2)

配置设置是自解释的。然而,由于你之前没有学习过正则化,我们简要地了解一下它。

正则化防止神经网络模型过拟合,并使模型更加通用。为此,评估函数DBNIrisExample.java被带有惩罚项地重写如下:

DBNIrisExample.java

这里,DBNIrisExample.java表示向量范数。当DBNIrisExample.java时,正则化被称为 L1 正则化;当DBNIrisExample.java时,则为 L2 正则化。分别称为 L1 范数和 L2 范数。因此,我们在代码中有.l1().l2()DBNIrisExample.java是超参数。这些正则化项使得模型更加稀疏。L2 正则化也称为权重衰减,旨在防止梯度消失问题。

.useDropConnect()命令用于启用 dropout,而.list()用于定义层数,排除输入层。

当你设置完整个模型后,下一步是配置每一层。在这段示例代码中,模型没有定义为深度神经网络。定义了一个单一的 RBM 层作为隐藏层:

.layer(0, new RBM.Builder(RBM.HiddenUnit.RECTIFIED, RBM.VisibleUnit.GAUSSIAN)
 .nIn(numRows * numColumns)
 .nOut(3)
 .weightInit(WeightInit.XAVIER)
 .k(1)
 .activation("relu")
 .lossFunction(LossFunctions.LossFunction.RMSE_XENT)
 .updater(Updater.ADAGRAD)
 .dropOut(0.5)
 .build()
)

这里,第一行中的0是层的索引,.k()用于对比散度。由于 Iris 数据是浮动值,我们不能使用二进制 RBM。因此,我们这里使用RBM.VisibleUnit.GAUSSIAN,使得模型能够处理连续值。另外,关于该层的定义,特别需要提到的是Updater.ADAGRAD的作用。它用于优化学习率。现在,我们继续进行模型结构的设置,优化器的详细解释将在本章末尾介绍。

后续的输出层非常简单,易于理解:

 .layer(1, new OutputLayer.Builder(LossFunctions.LossFunction.MCXENT)
   .nIn(3)
   .nOut(outputNum)
   .activation("softmax")
   .build()
)

因此,神经网络已经通过三层结构构建:输入层、隐藏层和输出层。这个示例的图形模型可以如下表示:

DBNIrisExample.java

在构建模型后,我们需要训练网络。这里,代码同样非常简单:

model.setListeners(Arrays.asList((IterationListener) new ScoreIterationListener(listenerFreq)));
model.fit(train);

由于第一行是记录过程,我们需要做的就是写下model.fit()来训练模型。

使用 DL4J 测试或评估模型也很容易。首先,设置评估的变量如下:

Evaluation eval = new Evaluation(outputNum);
INDArray output = model.output(test.getFeatureMatrix());

然后,我们可以使用以下代码获取特征矩阵的值:

eval.eval(test.getLabels(), output);
log.info(eval.stats());

运行代码后,我们将得到如下结果:

==========================Scores=====================================
 Accuracy:  0.7667
 Precision: 1
 Recall:    0.7667
 F1 Score:  0.8679245283018869
=====================================================================

F1 Score,也叫做F-ScoreF-measure,是精准度和召回率的调和均值,其表示方式如下:

DBNIrisExample.java

这个值通常也用来衡量模型的性能。此外,正如示例中所写的,你可以通过以下代码查看实际值和预测值:

for (int i = 0; i < output.rows(); i++) {
   String actual = test.getLabels().getRow(i).toString().trim();
   String predicted = output.getRow(i).toString().trim();
   log.info("actual " + actual + " vs predicted " + predicted);
}

就是这样,整个训练和测试过程就完成了。前面代码中的神经网络并不深,但你只需通过改变配置来轻松构建深度神经网络,如下所示:

MultiLayerConfiguration conf = new NeuralNetConfiguration.Builder()
       .seed(seed)
       .iterations(iterations)
       .learningRate(1e-6f)
       .optimizationAlgo(OptimizationAlgorithm.CONJUGATE_GRADIENT)
       .l1(1e-1).regularization(true).l2(2e-4)
       .useDropConnect(true)
       .list(3)
       .layer(0, new RBM.Builder(RBM.HiddenUnit.RECTIFIED, RBM.VisibleUnit.GAUSSIAN)
                       .nIn(numRows * numColumns)
                       .nOut(4)
                       .weightInit(WeightInit.XAVIER)
                       .k(1)
                       .activation("relu")
                       .lossFunction(LossFunctions.LossFunction.RMSE_XENT)
                       .updater(Updater.ADAGRAD)
                       .dropOut(0.5)
                       .build()
       )
       .layer(1, new RBM.Builder(RBM.HiddenUnit.RECTIFIED, RBM.VisibleUnit.GAUSSIAN)
                       .nIn(4)
                       .nOut(3)
                       .weightInit(WeightInit.XAVIER)
                       .k(1)
                       .activation("relu")
                       .lossFunction(LossFunctions.LossFunction.RMSE_XENT)
                       .updater(Updater.ADAGRAD)
                       .dropOut(0.5)
                       .build()
       )
       .layer(2, new OutputLayer.Builder(LossFunctions.LossFunction.MCXENT)
                       .nIn(3)
                       .nOut(outputNum)
                       .activation("softmax")
                       .build()
       )
       .build();

如你所见,使用 DL4J 构建深度神经网络只需要简单的实现。一旦设置好模型,你需要做的就是调整参数。例如,增加迭代次数或更改种子值会返回更好的结果。

CSVExample.java

在前面的示例中,我们使用作为基准指标的数据集来训练模型。当你想要使用自己准备的数据进行训练和测试时,可以很方便地从 CSV 文件中导入数据。让我们来看一下CSVExample.java,它在 CSV 包中。第一步是初始化 CSV 读取器,代码如下:

RecordReader recordReader = new CSVRecordReader(0,",");

在 DL4J 中,有一个叫做CSVRecordReader的类,你可以轻松地从 CSV 文件中导入数据。CSVRecordReader类中的第一个参数表示应该跳过文件中的多少行。当文件包含头部行时,这非常方便。第二个参数是分隔符。要实际读取文件并导入数据,代码可以如下编写:

recordReader.initialize(new FileSplit(new ClassPathResource("iris.txt").getFile()));

使用这段代码,resources/iris.txt 文件将被导入到模型中。文件中的值与 Iris 数据集中的值相同。为了使用这些初始化的数据进行模型训练,我们定义了如下的迭代器:

DataSetIterator iterator = new RecordReaderDataSetIterator(recordReader,4,3);
DataSet next = iterator.next();

在之前的示例中,我们使用了 IrisDataSetIterator 类,但在这里使用了 RecordReaderDataSetIterator 类,因为我们使用了自己准备的数据。值 43 分别是特征和标签的数量。

构建和训练模型几乎可以与前面的示例中描述的过程相同。在这个例子中,我们构建了一个具有两个隐藏层的深度神经网络,使用了丢弃法和修正线性单元(ReLU),即我们有一个输入层 - 隐藏层 - 隐藏层 - 输出层,如下所示:

MultiLayerConfiguration conf = new NeuralNetConfiguration.Builder()
       .seed(seed)
       .iterations(iterations)
       .constrainGradientToUnitNorm(true).useDropConnect(true)
       .learningRate(1e-1)
       .l1(0.3).regularization(true).l2(1e-3)
       .constrainGradientToUnitNorm(true)
       .list(3)
       .layer(0, new DenseLayer.Builder().nIn(numInputs).nOut(3)
               .activation("relu").dropOut(0.5)
               .weightInit(WeightInit.XAVIER)
               .build())
       .layer(1, new DenseLayer.Builder().nIn(3).nOut(2)
               .activation("relu")
               .weightInit(WeightInit.XAVIER)
               .build())
       .layer(2, new OutputLayer.Builder(LossFunctions.LossFunction.NEGATIVELOGLIKELIHOOD)
               .weightInit(WeightInit.XAVIER)
               .activation("softmax")
               .nIn(2).nOut(outputNum).build())
       .backprop(true).pretrain(false)
       .build();

我们可以使用以下几行代码来运行模型:

MultiLayerNetwork model = new MultiLayerNetwork(conf);
model.init();

图形化的模型如下:

CSVExample.java

然而,这次的训练代码方式与前一个示例略有不同。之前,我们通过以下代码将数据分为训练数据和测试数据:

SplitTestAndTrain testAndTrain = next.splitTestAndTrain(splitTrainNum, new Random(seed));

这表明我们在 .splitTestAndTrain() 方法中对数据进行了洗牌。在这个示例中,我们使用以下代码设置训练数据:

next.shuffle();
SplitTestAndTrain testAndTrain = next.splitTestAndTrain(0.6);

如你所见,这里首先对数据进行了洗牌,然后将其分为训练数据和测试数据。请注意,.splitTestAndTrain() 方法中的参数类型是不同的。这是有益的,因为我们不必精确计算数据或训练数据的数量。实际训练是通过以下代码完成的:

model.fit(testAndTrain.getTrain());

评估模型的方式与之前的示例相同:

Evaluation eval = new Evaluation(3);
DataSet test = testAndTrain.getTest();
INDArray output = model.output(test.getFeatureMatrix());
eval.eval(test.getLabels(), output);
log.info(eval.stats());

使用前面的代码,我们得到了以下结果:

==========================Scores=====================================
 Accuracy:  1
 Precision: 1
 Recall:    1
 F1 Score:  1.0
=====================================================================

除了基准指标的数据集之外,你现在可以分析任何你拥有的数据。

为了使模型更加深度,只需像下面这样添加一层:

MultiLayerConfiguration conf = new NeuralNetConfiguration.Builder()
       .seed(seed)
       .iterations(iterations)
       .constrainGradientToUnitNorm(true).useDropConnect(true)
       .learningRate(0.01)
       .l1(0.0).regularization(true).l2(1e-3)
       .constrainGradientToUnitNorm(true)
       .list(4)
       .layer(0, new DenseLayer.Builder().nIn(numInputs).nOut(4)
               .activation("relu").dropOut(0.5)
               .weightInit(WeightInit.XAVIER)
               .build())
       .layer(1, new DenseLayer.Builder().nIn(4).nOut(4)
               .activation("relu").dropOut(0.5)
               .weightInit(WeightInit.XAVIER)
               .build())
       .layer(2, new DenseLayer.Builder().nIn(4).nOut(4)
               .activation("relu").dropOut(0.5)
               .weightInit(WeightInit.XAVIER)
               .build())
       .layer(3, new OutputLayer.Builder(LossFunctions.LossFunction.NEGATIVELOGLIKELIHOOD)
               .weightInit(WeightInit.XAVIER)
               .activation("softmax")
               .nIn(4).nOut(outputNum).build())
       .backprop(true).pretrain(false)
       .build();

CNNMnistExample.java/LenetMnistExample.java

相较于其他模型,CNN 因其结构比较复杂,但我们无需担心这些复杂性,因为我们可以通过 DL4J 轻松实现 CNN。让我们来看一下卷积包中的 CNNMnistExample.java。在这个例子中,我们使用 MNIST 数据集(yann.lecun.com/exdb/mnist/)训练模型,它是最著名的基准指标之一。正如在第一章,深度学习概述中提到的,该数据集包含了从 0 到 9 的 70,000 个手写数字数据,每个数字的高和宽均为 28 像素。

首先,我们定义了模型所需的值:

int numRows = 28;
int numColumns = 28;
int nChannels = 1;
int outputNum = 10;
int numSamples = 2000;
int batchSize = 500;
int iterations = 10;
int splitTrainNum = (int) (batchSize*.8);
int seed = 123;
int listenerFreq = iterations/5;

由于 MNIST 中的图像都是灰度数据,因此通道数被设置为1。在本示例中,我们使用70,000个数据中的2,000个,并将其拆分为训练数据和测试数据。这里的小批量大小为500,因此训练数据被分成了 4 个小批量。进一步地,每个小批量中的数据被拆分为训练数据和测试数据,并将每一份测试数据存储在ArrayList中:

List<INDArray> testInput = new ArrayList<>();
List<INDArray> testLabels = new ArrayList<>();

在之前的示例中,我们不需要设置ArrayList,因为只有一个批次。对于MnistDataSetIterator类,我们只需使用以下方法就能设置 MNIST 数据:

DataSetIterator mnistIter = new MnistDataSetIterator(batchSize,numSamples, true);

然后,我们构建了一个包含卷积层和子采样层的模型。在这里,我们有一个卷积层和一个最大池化层,后面紧跟着一个输出层。CNN 的配置结构与其他算法略有不同:

MultiLayerConfiguration.Builder builder = new NeuralNetConfiguration.Builder().layer().layer(). … .layer()
new ConvolutionLayerSetup(builder,numRows,numColumns,nChannels);
MultiLayerConfiguration conf = builder.build();
MultiLayerNetwork model = new MultiLayerNetwork(conf);
model.init();

不同之处在于,我们不能直接从配置构建模型,因为我们需要提前告诉构建器通过ConvolutionLayerSetup()设置卷积层。每个.layer()都需要使用相同的编码方法。卷积层定义如下:

.layer(0, new ConvolutionLayer.Builder(10, 10)
       .stride(2,2)
       .nIn(nChannels)
       .nOut(6)
       .weightInit(WeightInit.XAVIER)
       .activation("relu")
       .build())

这里,ConvolutionLayer.Builder()中的10值表示卷积核的大小,而.nOut()中的6值表示卷积核的数量。同时,.stride()定义了卷积核的步幅大小。我们从头实现的第四章,Dropout 和卷积神经网络,仅具有等同于.stride(1, 1)的功能。数字越大,所需时间越少,因为它减少了卷积所需的计算量,但我们同时也必须小心,这可能会降低模型的精度。无论如何,现在我们可以更灵活地实现卷积。

subsampling层的描述如下:

.layer(1, new SubsamplingLayer.Builder(SubsamplingLayer.PoolingType.MAX, new int[] {2,2})
       .build())

这里,{2, 2}是池化窗口的大小。你可能已经注意到,我们不需要为每一层(包括输出层)设置输入的大小。一旦设置了模型,这些值会自动设置。

输出层的写法与其他模型相同:

.layer(2, new OutputLayer.Builder(LossFunctions.LossFunction.NEGATIVELOGLIKELIHOOD)
       .nOut(outputNum)
       .weightInit(WeightInit.XAVIER)
       .activation("softmax")
       .build())

本示例的图形模型如下所示:

CNNMnistExample.java/LenetMnistExample.java

在构建完模型后,就进入训练环节。由于我们有多个小批量数据,因此我们需要在所有批次中迭代训练。通过在这种情况下使用DataSetIteratormnistIter.hasNext(),这一点可以轻松实现。整个训练过程可以写成如下形式:

model.setListeners(Arrays.asList((IterationListener) new ScoreIterationListener(listenerFreq)));
while(mnistIter.hasNext()) {
   mnist = mnistIter.next();
   trainTest = mnist.splitTestAndTrain(splitTrainNum, new Random(seed));
   trainInput = trainTest.getTrain();
   testInput.add(trainTest.getTest().getFeatureMatrix());
   testLabels.add(trainTest.getTest().getLabels());
   model.fit(trainInput);
}

这里,测试数据和测试标签已被存储以供后续使用。

在测试过程中,我们需要再次迭代测试数据的评估过程,因为我们有多个小批量数据:

for(int i = 0; i < testInput.size(); i++) {
   INDArray output = model.output(testInput.get(i));
   eval.eval(testLabels.get(i), output);
}

然后,我们使用与其他示例中相同的方法:

log.info(eval.stats());

这将返回如下结果:

==========================Scores=====================================
 Accuracy:  0.832
 Precision: 0.8783
 Recall:    0.8334
 F1 Score:  0.8552464933704985
=====================================================================

刚才举的例子是具有一个卷积层和一个子采样层的模型,但你在LenetMnistExample.java中有深度卷积神经网络。在这个例子中,有两个卷积层和子采样层,之后是完全连接的多层感知机:

MultiLayerConfiguration.Builder builder = new NeuralNetConfiguration.Builder()
       .seed(seed)
       .batchSize(batchSize)
       .iterations(iterations)
       .regularization(true).l2(0.0005)
       .learningRate(0.01)
       .optimizationAlgo(OptimizationAlgorithm.STOCHASTIC_GRADIENT_DESCENT)
       .updater(Updater.NESTEROVS).momentum(0.9)
       .list(6)
       .layer(0, new ConvolutionLayer.Builder(5, 5)
               .nIn(nChannels)
               .stride(1, 1)
               .nOut(20).dropOut(0.5)
               .weightInit(WeightInit.XAVIER)
               .activation("relu")
               .build())
       .layer(1, new SubsamplingLayer.Builder(SubsamplingLayer.PoolingType.MAX, new int[]{2, 2})
               .build())
       .layer(2, new ConvolutionLayer.Builder(5, 5)
               .nIn(20)
               .nOut(50)
               .stride(2,2)
               .weightInit(WeightInit.XAVIER)
               .activation("relu")
               .build())
       .layer(3, new SubsamplingLayer.Builder(SubsamplingLayer.PoolingType.MAX, new int[]{2, 2})
               .build())
       .layer(4, new DenseLayer.Builder().activation("tanh")
               .nOut(500).build())
       .layer(5, new OutputLayer.Builder(LossFunctions.LossFunction.NEGATIVELOGLIKELIHOOD)
               .nOut(outputNum)
               .weightInit(WeightInit.XAVIER)
               .activation("softmax")
               .build())
       .backprop(true).pretrain(false);
new ConvolutionLayerSetup(builder,28,28,1);

正如你在第一个卷积层中看到的,dropout 可以轻松地应用于使用 DL4J 的 CNN。

使用这个模型,我们得到以下结果:

==========================Scores=====================================
 Accuracy:  0.8656
 Precision: 0.8827
 Recall:    0.8645
 F1 Score:  0.873490476878917
=====================================================================

你可以从 MNIST 数据集页面(yann.lecun.com/exdb/mnist/)看到,最先进的结果远优于上述结果。在这里,你会再次意识到,参数、激活函数和优化算法的组合是多么重要。

学习率优化

到目前为止,我们已经学习了各种深度学习算法;你可能已经注意到,它们有一个共同的参数:学习率。学习率在方程中定义,用于更新模型参数。那么,为什么不考虑一些算法来优化学习率呢?最初,这些方程描述如下:

学习率优化

这里:

学习率优化

这里,学习率优化是步数,学习率优化是学习率。众所周知,随着每次迭代减少学习率的值,模型的精度会更高,但我们应该谨慎确定衰减,因为学习率的突然下降会导致模型崩溃。学习率是模型的一个参数,那么为什么不优化它呢?为了做到这一点,我们需要知道最佳的学习率应该是多少。

设置学习率的最简单方法是使用动量,表示如下:

学习率优化

这里,学习率优化,称为动量系数。这个超参数通常初始设置为 0.5 或 0.9,然后进行微调。

动量实际上是一种简单但有效的调整学习率的方法,但ADAGRAD(由 Duchi 等人提出,见www.magicbroom.info/Papers/DuchiHaSi10.pdf)被认为是一种更好的方法。该方程描述如下:

学习率优化

这里:

学习率优化

理论上,这种方法效果很好,但在实践中,我们通常使用以下方程来防止发散:

学习率优化

或者我们使用:

学习率优化

ADAGRAD 比动量更容易使用,因为其值会自动设置,我们无需设置额外的超参数。

Zeiler 提出的 ADADELTA(arxiv.org/pdf/1212.5701.pdf)被认为是一个更好的优化器。这是一种基于算法的优化器,不能用一个简单的公式表示。以下是 ADADELTA 的描述:

  • 初始化:

    • 初始化累积变量:学习率优化

    并且:

    学习率优化

  • 迭代 学习率优化

    • 计算:学习率优化

    • 累积梯度:学习率优化

    • 计算更新:学习率优化

    • 累积更新:学习率优化

    • 应用更新:学习率优化

这里,学习率优化学习率优化 是超参数。你可能认为 ADADELTA 比较复杂,但在使用 DL4J 实现时,你不需要担心这种复杂性。

DL4J 还支持其他优化方法,比如 RMSPropRMSProp + 动量法,以及 Nesterov 加速梯度下降 。然而,我们不会深入讨论这些方法,因为实际上,动量法、ADAGRAD 和 ADADELTA 已经足够优化学习率了。

总结

在本章中,你学习了如何使用 ND4J 和 DL4J 库实现深度学习模型。这两个库都支持 GPU 计算,且使我们能够轻松地实现这些模型。ND4J 是一个用于科学计算的库,支持向量化,使得在数组中实现计算更加简便,因为我们不需要在其中编写迭代。由于机器学习和深度学习算法中有许多涉及向量计算的公式,比如内积和元素级的乘法,ND4J 也有助于实现这些操作。

DL4J 是一个用于深度学习的库,通过跟随库中的一些示例,你看到我们可以轻松地构建、训练和评估各种类型的深度学习模型。此外,在构建模型时,你还学习了为什么正则化对于获得更好的结果是必要的。你还了解了学习率的一些优化方法:动量法、ADAGRAD 和 ADADELTA。这些都可以通过 DL4J 轻松实现。

你掌握了深度学习算法的核心理论和实现方法,并且现在能够轻松实现它们。我们可以说,我们已经完成了本书的理论部分。因此,在下一章中,我们将首先探讨深度学习算法如何适应实际应用,然后再深入其他可能的领域和应用想法。

第六章:实践应用的方法——递归神经网络及其他

在前几章中,你已经学到了很多关于深度学习的知识。现在你应该已经理解了深度神经网络的概念、理论和实现的基础知识。你还学到,通过利用深度学习库,你可以相对容易地在各种数据上进行深度学习算法的实验。接下来的步骤是探讨深度学习如何应用于广泛的其他领域,以及如何将其用于实际应用。

因此,在本章中,我们将首先看看深度学习是如何实际应用的。在这里,你会看到,深度学习实际应用的案例仍然非常少。但为什么尽管这是如此创新的方法,应用案例却不多呢?问题出在哪里?稍后,我们将深入思考原因。接下来,我们还将讨论我们可以将深度学习应用到哪些领域,并探讨人工智能相关的所有领域中哪些可以应用深度学习。

本章涵盖的主题包括:

  • 图像识别、自然语言处理以及与它们相关的神经网络模型和算法

  • 将深度学习模型转化为实际应用的困难

  • 深度学习可以应用的可能领域,以及如何接近这些领域的思路

我们将探索这个巨大人工智能浪潮的潜力,这将为你在研究、商业和各种活动中使用深度学习提供思路和启示。

深度学习活跃的领域

我们经常听到深度学习的研究一直在进行,这确实是事实。许多公司,特别是像谷歌、Facebook、微软和 IBM 这样的科技巨头,都在深度学习的研究上投入了巨额资金,我们也时常听到有公司收购了这些研究团队。然而,当我们深入了解时,会发现深度学习本身有各种类型的算法,以及这些算法可以应用的领域。即便如此,深度学习在哪些领域已经被应用或可以被应用,仍然是一个不为大众所熟知的事实。由于“人工智能”这个词被广泛使用,人们无法准确理解哪些技术用于哪些产品。因此,在本节中,我们将探讨人们在实际应用中积极尝试采用深度学习的领域。

图像识别

深度学习最常应用的领域是图像识别。正是 Hinton 教授及其团队的发明提出了“深度学习”这一术语。他们的算法在一次图像识别竞赛中创下了历史最低的错误率。为改进算法所做的持续研究带来了更好的成果。如今,利用深度学习的图像识别技术已经逐渐被广泛应用于研究和实际产品中。例如,Google 利用深度学习自动生成 YouTube 的缩略图,或在 Google Photos 中自动标记和搜索照片。像这些流行的产品一样,深度学习主要应用于图像标记或分类。例如,在机器人领域,深度学习被用来帮助机器人识别周围的物体。

我们能够支持这些产品和这个行业的原因在于,深度学习更适合图像处理,这也正是它能够实现比其他领域应用更高精度的原因。只有图像识别的精度和召回率如此之高,才能说明这个行业具有广阔的潜力。使用深度学习算法进行 MNIST 图像分类的错误率为 0.21%(cs.nyu.edu/~wanli/dropc/),这一比率已经接近人类的记录(arxiv.org/pdf/0710.2231v1.pdf)。换句话说,如果我们仅仅讨论图像识别领域,那么它不过是机器可能超越人类的事实。那么,为什么只有图像识别能取得如此高的精度,而其他领域的技术方法还需要大量改进呢?

其中一个原因是深度学习中特征提取的结构非常适合处理图像数据。在深度神经网络中,许多层次是逐层堆叠的,每一层都从训练数据中提取特征。此外,图像数据本身也可以看作是一个分层结构。当你查看图像时,你会不自觉地首先捕捉到简洁的特征,然后再深入观察更详细的特征。因此,深度学习特征提取的固有特性与我们感知图像的方式非常相似,从而使我们能够准确地识别图像的特征。尽管基于深度学习的图像识别仍然需要进一步改进,特别是在机器如何理解图像及其内容方面,但仅仅通过采用深度学习对图像数据进行采样而不进行预处理,能够获得高精度,这显然表明深度学习与图像数据是非常匹配的。

另一个原因是人们一直在缓慢但稳定地改进算法。例如,在深度学习算法中,卷积神经网络(CNN)在图像识别中能够获得最佳精度,并且每次遇到困难或任务时都会有所改进。卷积层中的局部感受野被替代为卷积核,以避免网络变得过于密集。此外,为了避免网络对图像位置差距过度反应,还发明了如最大池化(max-pooling)等下采样方法。这些方法最初是通过对如何识别在某个框架中书写的手写字母(如邮政编码)进行试验和错误的过程产生的。因此,许多新的方法被寻求来使神经网络算法适应实际应用。像卷积神经网络(CNN)这样复杂的模型,也是基于这些积累而来且稳定的改进。虽然深度学习不需要特征工程,但我们仍需考虑解决特定问题的合适方法,也就是说,我们无法构建全能的模型,这就是优化领域的无免费午餐定理NFLT)。

在图像识别领域,深度学习能够达到的分类精度极高,实际上,它已经开始用于实际应用。然而,仍然有许多领域可以应用深度学习。图像与许多行业密切相关。未来,将会有更多的应用案例和更多行业将深度学习加以利用。在本书的接下来的章节中,让我们思考如何将图像识别应用于哪些行业。

自然语言处理

在图像识别之后,深度学习研究进展最为显著的领域是自然语言处理NLP)。该领域的研究有可能成为未来最活跃的研究方向。关于图像识别,深度学习所能达到的预测精度几乎已经接近极限,因为它能够执行比人类更好的分类。另一方面,虽然深度学习大大提升了模型的性能,但在自然语言处理领域,依然有许多任务需要解决。

对于某些产品和实际应用,深度学习已经得到了应用。例如,基于深度学习的自然语言处理(NLP)被应用于谷歌的语音搜索、语音识别和谷歌翻译。此外,IBM Watson 是一个认知计算系统,它能够理解和学习自然语言,并支持人类决策。它能够从大量文档中提取关键词和实体,并具有标注文档的功能。这些功能作为 Watson API 向公众开放,任何人都可以在不受限制的情况下利用它。

正如从前面的示例中可以看到的,NLP 本身有广泛且多样的类型。在基本技术方面,我们有句子内容分类、单词分类和单词意义的指定。此外,像中文或日文这类不在单词之间留空格的语言需要形态学分析,这也是 NLP 中的另一项技术。

NLP 涉及许多需要研究的内容,因此它需要明确其目标是什么,问题是什么,以及如何解决这些问题。使用哪种模型最好,如何有效地获得高精度,是应该谨慎探讨的话题。至于图像识别,CNN 方法是通过解决面临的任务而发明的。现在,让我们考虑在神经网络和 NLP 的应用中分别可以想到什么方法,以及可能遇到的困难。理解过去的试验和错误过程对未来的研究和应用将非常有帮助。

用于 NLP 的前馈神经网络

NLP 的基本问题是“给定一个或多个特定的单词,预测下一个单词”。然而,这个问题过于简单;如果你试图用神经网络来解决它,你很快会面临几个困难,因为作为 NLP 样本数据的文档或句子具有以下特征:

  • 每个句子的长度不固定而是可变的,单词的数量非常庞大

  • 可能会遇到一些不可预见的问题,比如拼写错误、缩写等

  • 句子是顺序数据,因此包含时间信息

为什么这些特征会造成问题呢?记住一般神经网络的模型结构。对于神经网络的训练和测试,每一层的神经元数量(包括输入层)需要预先固定,并且所有样本数据的网络大小需要一致。与此同时,输入数据的长度并不固定,可能会有很大变化。这意味着样本数据不能直接应用于模型,至少不能原样应用。没有对这些数据进行添加/修改,神经网络无法进行分类或生成。

我们必须固定输入数据的长度,其中一种处理方法是将句子从头开始按顺序分成若干个固定长度的词块。这种方法称为N-gram。这里,N表示每个项的大小,大小为 1 的N-gram叫做unigram,大小为 2 的是bigram,大小为 3 的是trigram。当大小更大时,通常会根据N的值来命名,比如four-gramfive-gram,依此类推。

让我们看看 N-gram 是如何在 NLP 中工作的。这里的目标是计算在给定一些历史信息的情况下,某个单词的概率 Feed-forward neural networks for NLP,这些历史信息包括 Feed-forward neural networks for NLP;Feed-forward neural networks for NLP。我们将一串 Feed-forward neural networks for NLP 单词表示为 Feed-forward neural networks for NLP。然后,我们想要计算的概率是 Feed-forward neural networks for NLP,通过应用概率链式法则,我们可以得到:

Feed-forward neural networks for NLP

起初看起来这些条件概率可能对我们有帮助,但实际上并没有,因为我们没有办法计算一个单词在一长串前置单词之后的精确概率,Feed-forward neural networks for NLP。由于句子的结构非常灵活,我们不能简单地利用样本文档和语料库来估计这个概率。这就是 N-gram 的作用所在。实际上,我们有两种方法来解决这个问题:原始的 N-gram 模型和基于 N-gram 的神经网络模型。在深入研究神经网络之前,我们将先了解第一个模型,全面理解 NLP 领域的发展。

使用 N-gram 时,我们不是计算一个单词在其完整历史下的概率,而是通过最后的N个单词来近似历史。例如,二元模型仅通过前一个单词的条件概率来近似一个单词的概率,Feed-forward neural networks for NLP,从而得到如下方程:

Feed-forward neural networks for NLP

类似地,我们可以将 N-gram 的方程进行推广和扩展。在这种情况下,单词的概率可以表示如下:

Feed-forward neural networks for NLP

我们得到如下方程:

Feed-forward neural networks for NLP

请记住,这些基于 N-gram 的近似值是基于一种叫做马尔可夫模型的概率模型,在这种模型中,一个单词的概率仅依赖于前一个单词。

现在我们需要做的是估计这些 N-gram 的概率,那么我们该如何估计它们呢?一种简单的方式叫做最大似然估计MLE)。这种方法通过从语料库中获取词频并进行归一化来估计概率。所以,当我们以二元组为例时,我们可以得到:

Feed-forward neural networks for NLP

在上述公式中, 前馈神经网络用于自然语言处理 表示一个单词或单词序列的计数。由于分母,即以某个单词开头的所有二元组计数之和, 前馈神经网络用于自然语言处理 等于该单词的单元语法计数 前馈神经网络用于自然语言处理,因此前面的方程可以描述如下:

前馈神经网络用于自然语言处理

因此,我们也可以将最大似然估计(MLE)推广到 N-gram。

前馈神经网络用于自然语言处理

尽管这是 NLP 中基于 N-gram 的基本方法,但我们现在知道如何计算 N-gram 的概率。

与这种方法不同,神经网络模型预测在给定特定历史的条件下,单词 前馈神经网络用于自然语言处理 的条件概率, 前馈神经网络用于自然语言处理前馈神经网络用于自然语言处理。一种 NLP 模型被称为神经网络语言模型 (NLMM),(www.jmlr.org/papers/volume3/bengio03a/bengio03a.pdf),其可以表示为如下:

前馈神经网络用于自然语言处理

这里,前馈神经网络用于自然语言处理 是词汇表的大小,词汇表中的每个单词都是一个 N 维向量,其中只有该单词的索引设置为 1,其它索引都为 0。这种表示方法叫做 1-of-N 编码。NLMM 的输入是前一个单词的索引,即 前馈神经网络用于自然语言处理前馈神经网络用于自然语言处理(因此它们是 n-gram)。由于大小 N 通常在 5,000 到 200,000 之间,NLMM 的输入向量非常稀疏。然后,每个单词被映射到投影层,进行连续空间表示。这个从离散空间到连续空间的线性投影(激活)基本上是一个查找表,包含 前馈神经网络用于自然语言处理 项,其中 前馈神经网络用于自然语言处理 表示特征维度。投影矩阵对于上下文中不同的单词位置是共享的,并激活单词向量到投影层单位 前馈神经网络用于自然语言处理,其维度为 前馈神经网络用于自然语言处理。投影之后是隐藏层。由于投影层位于连续空间中,模型结构从此处起与其他神经网络相同。因此,激活可以表示为如下形式:

前馈神经网络用于自然语言处理

在这里,用于 NLP 的前馈神经网络 表示激活函数,用于 NLP 的前馈神经网络 表示投影层和隐藏层之间的权重,用于 NLP 的前馈神经网络 表示隐藏层的偏置。因此,我们可以得到如下的输出单元:

用于 NLP 的前馈神经网络

在这里,用于 NLP 的前馈神经网络 表示隐藏层和输出层之间的权重,用于 NLP 的前馈神经网络 表示输出层的偏置。给定特定历史信息的词语 i 的概率 用于 NLP 的前馈神经网络 可以通过 softmax 函数计算得出:

用于 NLP 的前馈神经网络

正如你所看到的,在 NNLM 中,模型同时预测所有词汇的概率。由于该模型现在使用标准神经网络描述,我们可以使用标准的反向传播算法来训练模型。

NNLM 是一种使用神经网络和 N-gram 的 NLP 方法。虽然 NNLM 解决了如何固定输入数量的问题,但最佳的 N 只能通过反复试验找到,这是整个模型构建过程中最困难的部分。此外,我们必须确保不要过多依赖输入的时间序列信息。

深度学习用于 NLP

带有 N-gram 的神经网络在某些情况下可能有效,但也存在一些问题,例如,哪些 n-grams 会返回最佳结果,以及 n-grams(即模型的输入)是否仍然具有上下文?这些问题不仅存在于 NLP 中,也存在于所有具有时间序列数据的领域,如降水、股票价格、每年土豆的产量、电影等。由于在现实世界中我们有大量这样的数据,因此不能忽视潜在的问题。那么,如何才能让神经网络在时间序列数据上进行训练呢?

循环神经网络

一种能够在网络中保留数据上下文的神经网络模型是 循环神经网络 (RNN),这一模型积极研究深度学习算法的演变。以下是一个非常简单的 RNN 图形模型:

循环神经网络

标准神经网络与 RNN 的区别在于,RNN 在时间上存在隐藏层之间的连接。时间点Recurrent neural networks的输入会在时间点Recurrent neural networks的隐藏层中激活,并被保留在该隐藏层中,随后与时间点Recurrent neural networks的输入一起传播到时间点Recurrent neural networks的隐藏层中。这使得网络能够包含过去数据的状态并反映它们。你可能会认为 RNN 是一个动态模型,但如果你在每个时间步展开该模型,你会看到 RNN 其实是一个静态模型:

Recurrent neural networks

由于每个时间步的模型结构与一般神经网络相同,因此你可以使用反向传播算法训练该模型。然而,在训练时需要考虑时间相关性,且有一种称为时间反向传播BPTT)的技术来处理这一点。在 BPTT 中,参数的误差和梯度会被反向传播到过去的各层:

Recurrent neural networks

因此,RNN 能够在模型中保留上下文。理论上,网络在每个时间步应该考虑到直到该时刻为止的整个序列,但实际上,通常会对模型应用具有一定长度的时间窗口,以简化计算或避免消失梯度问题和梯度爆炸问题。BPTT 使得层间的训练成为可能,这也是为什么 RNN 常被认为是深度神经网络之一的原因。我们还有像堆叠 RNN 这样的深度 RNN 算法,其中隐藏层是堆叠的。

RNN 已被应用于自然语言处理(NLP),实际上是该领域最成功的模型之一。最初优化用于 NLP 的模型被称为递归神经网络语言模型RNNLM),由 Mikolov 等人提出(www.fit.vutbr.cz/research/groups/speech/publi/2010/mikolov_interspeech2010_IS100722.pdf)。该模型的架构可以如下所示:

Recurrent neural networks

该网络有三层:输入层Recurrent neural networks、隐藏层Recurrent neural networks和输出层Recurrent neural networks。隐藏层也常被称为上下文层或状态层。每一层相对于时间点Recurrent neural networks的值可以表示如下:

Recurrent neural networks

这里,循环神经网络 表示 sigmoid 函数,循环神经网络 表示 softmax 函数。由于输入层包含了在时间 循环神经网络 的状态层,它能够将整个上下文传递给网络。模型架构表明,RNNLM 能够查找比前馈 NNLM 更广泛的上下文,在后者中,上下文的长度被限制为 N(-gram)。

在训练 RNN 时,应该考虑整个时间和上下文,但如前所述,我们通常会截断时间长度,因为 BPTT 需要大量计算,并且在学习长期依赖时常常导致梯度消失/爆炸问题,因此该算法通常被称为截断 BPTT。如果我们按照时间展开 RNNLM,模型可以如下所示(在图中,展开的时间 循环神经网络):

循环神经网络

这里 循环神经网络 是输出的标签向量。然后,输出的误差向量可以表示如下:

循环神经网络

我们得到以下方程:

循环神经网络

这里 循环神经网络 是展开的时间:

循环神经网络

上面的图片是隐藏层激活函数的导数。由于我们在这里使用的是 sigmoid 函数,因此得到了前面的方程。然后,我们可以得到过去的误差,如下所示:

循环神经网络

使用这些方程式,我们现在可以更新模型的权重矩阵:

循环神经网络

这里,循环神经网络 是学习率。在 RNNLM 中有趣的是,矩阵中的每个向量显示了训练后单词之间的差异。这是因为 循环神经网络 是将每个单词映射到潜在空间的矩阵,因此训练后,映射的单词向量包含了单词的含义。例如,“king” - “man” + “woman”的向量计算将返回“queen”。DL4J 支持 RNN,因此你可以轻松地使用该库实现这个模型。

长短期记忆网络

使用标准的 RNN 进行训练需要截断的 BPTT。你可能会怀疑 BPTT 是否真的能够足够训练模型,以反映整个上下文,而这一点是非常真实的。这就是为什么引入了一种特殊的 RNN——长短期记忆LSTM)网络来解决长期依赖问题。LSTM 看起来相当复杂,但让我们简要探讨一下 LSTM 的概念。

首先,我们必须考虑如何在网络中存储和传递过去的信息。虽然通过给连接设置上限可以简单地缓解梯度爆炸问题,但梯度消失问题仍需要深入考虑。一种可能的方法是引入一个单元,它永久地保存其输入和梯度的值。因此,当你查看标准神经网络中的隐藏层单元时,它可以简单地描述如下:

长短期记忆网络

这里没有什么特别的。然后,通过向网络添加一个单元,网络现在可以在神经元中记住过去的信息。这里添加的神经元具有线性激活,并且其值通常设置为 1。这个神经元,或单元,被称为常数误差旋转器CEC),因为误差在神经元中像旋转木马上循环,不会消失。CEC 作为存储单元,存储过去的输入。它解决了梯度消失问题,但也引发了另一个问题。由于所有传播过的数据都存储在神经元中,它可能也会存储噪声数据:

长短期记忆网络

这个问题可以分解为两个问题:输入权重冲突输出权重冲突。输入权重冲突的关键思想是,保持网络中的某些信息,直到它变得必要;只有当相关信息到来时,神经元才被激活,否则不激活。类似地,输出权重冲突可以出现在所有类型的神经网络中;神经元的值只有在必要时才传播,否则不传播。只要神经元之间的连接通过网络的权重来表示,我们就无法解决这些问题。因此,必须采用另一种表示方法或技术来控制输入和输出的传播。那么我们该如何做到这一点呢?答案是,在 CEC 前后放置像“门”一样的单元,分别称为输入门输出门。门的图形模型可以描述如下:

长短期记忆网络

理想情况下,门应返回与输入对应的离散值 0 或 1,门关闭时返回 0,打开时返回 1,因为它是一个门,但在编程上,门的值设置为 0 到 1 之间的值,以便能够通过反向传播(BPTT)进行良好的训练。

现在看起来我们可以在精确的时间点获取和存储精确的信息,但仍然存在一个问题。仅有两个门——输入门和输出门——存储在 CEC 中的记忆无法通过几个步骤轻松刷新。因此,我们需要一个额外的门,能够动态地改变 CEC 的值。为此,我们在架构中添加了一个遗忘门,用来控制何时应当抹去记忆。当门的值变为 0 或接近 0 时,CEC 中保存的值会被新记忆覆盖。通过这三个门,单元现在可以记住过去的信息或上下文,因此它被称为LSTM 块LSTM 记忆块,因为它更像一个块,而非单个神经元。以下是表示 LSTM 块的图示:

长短期记忆网络

标准的 LSTM 结构已经在之前完全解释过了,但有一种技术可以提高它的性能,我们现在将对此进行解释。每个门都接收来自输入单元和 LSTM 中所有单元的输出的连接,但没有来自 CEC 的直接连接。这意味着我们无法看到网络的真实隐藏状态,因为一个块的输出依赖于输出门;只要输出门关闭,任何门都无法访问 CEC,而 CEC 也因此缺乏关键信息,这可能会降低 LSTM 的性能。一种简单但有效的解决方案是从 CEC 向块中的门添加连接。这些被称为窥视孔连接,它们作为标准的加权连接起作用,唯一的不同是,错误不会通过窥视孔连接从门传播回去。窥视孔连接允许所有的门在输出门关闭时仍能假设隐藏状态。现在你已经学到了很多术语,因此,整个连接的基本架构可以描述如下:

长短期记忆网络

为了简化,图示中描述了单一的 LSTM 块。你可能会感到困惑,因为前面的模型非常复杂。然而,当你一步步地分析这个模型时,你会明白 LSTM 网络是如何解决 NLP 中的困难的。给定一个输入序列 长短期记忆网络,每个网络单元可以按以下方式计算:

长短期记忆网络

在前面的公式中,长短期记忆网络是从输入门到输入的权重矩阵,长短期记忆网络是从遗忘门到输入的权重矩阵,长短期记忆网络是从输出门到输入的权重矩阵。长短期记忆网络是从单元到输入的权重矩阵,长短期记忆网络是从单元到 LSTM 输出的权重矩阵,长短期记忆网络是从输出到 LSTM 输出的权重矩阵。长短期记忆网络长短期记忆网络,和长短期记忆网络是用于窥视孔连接的对角权重矩阵。长短期记忆网络项表示偏置向量,长短期记忆网络是输入门的偏置向量,长短期记忆网络是遗忘门的偏置向量,长短期记忆网络是输出门的偏置向量,长短期记忆网络是 CEC 单元的偏置向量,长短期记忆网络是输出偏置向量。这里,长短期记忆网络长短期记忆网络是单元输入和单元输出的激活函数。长短期记忆网络表示 Sigmoid 函数,长短期记忆网络表示 Softmax 函数。长短期记忆网络是向量的逐元素乘积。

本书中我们不会继续跟随进一步的数学公式,因为通过应用 BPTT 它们会变得非常复杂,但你可以尝试使用 DL4J 实现 LSTM 和 RNN。由于 CNN 是在图像识别领域中发展起来的,RNN 和 LSTM 则是为了逐步解决 NLP 中的问题而发展起来的。尽管这两种算法只是使用 NLP 获取更好性能的一种方法,且仍需要改进,但既然我们是使用语言进行交流的生物,NLP 的发展必然会带来技术创新。关于 LSTM 的应用,你可以参考Sequence to Sequence Learning with Neural Networks(Sutskever 等,arxiv.org/pdf/1409.3215v3.pdf),而对于更近期的算法,你可以参考Grid Long Short-Term Memory(Kalchbrenner 等,arxiv.org/pdf/1507.01526v1.pdf)以及Show, Attend and Tell: Neural Image Caption Generation with Visual Attention(Xu 等,arxiv.org/pdf/1502.03044v2.pdf)。

深度学习的困难

深度学习在图像识别领域已经达到了比人类更高的精度,并且已应用于许多实际应用中。同样,在自然语言处理(NLP)领域,也进行了很多模型的研究。那么,深度学习在其他领域的应用情况如何呢?令人惊讶的是,深度学习在许多领域中仍未被成功应用。这是因为深度学习与过去的算法相比确实具有创新性,并且无疑让我们在实现人工智能的道路上迈出了重要一步;然而,当它用于实际应用时,也会遇到一些问题。

第一个问题是深度学习算法中有过多的模型参数。当你学习算法的理论和实现时,我们并未详细讨论,但实际上,深度神经网络相较于过去的神经网络或其他机器学习算法,拥有许多需要决定的超参数。这意味着我们必须经历更多的试验与错误才能获得高精度。定义神经网络结构的参数组合,如隐藏层的数量或每个隐藏层的单位数,需要大量实验。此外,像学习率等训练和测试配置的参数也需要确定。还有,每个算法特有的参数,如 SDA 中的腐蚀水平和 CNN 中的卷积核大小,也需要额外的试错。因此,深度学习提供的卓越性能是通过持续的参数调优来支撑的。然而,人们往往只关注深度学习的一面——它可以获得极高的精度——而忽视了达到这一点所需的艰苦过程。深度学习不是魔法。

此外,深度学习在训练和分类简单问题的数据时常常失败。深度神经网络的结构非常深且复杂,以至于权重无法得到良好的优化。在优化方面,数据量也是至关重要的。这意味着深度神经网络在每次训练时需要大量的时间。总而言之,深度学习的价值体现在以下几点:

  • 它解决了当人们不知道应该如何对特征进行分类时的复杂且困难的问题

  • 有足够的训练数据来适当地优化深度神经网络

与那些通过不断更新数据来持续更新模型的应用相比,一旦使用一个大规模且变化不大的数据集构建了模型,适用于深度学习的应用通常会较为合适。

因此,当你观察业务领域时,你会发现存在更多的情况是,现有的机器学习比使用深度学习能够获得更好的结果。例如,假设我们希望在电商中向用户推荐合适的产品。在这个电商平台中,许多用户每天都会购买大量商品,因此购买数据会每日大量更新。在这种情况下,你是否会使用深度学习来利用这些数据获得高精度的分类和推荐,以提高用户购买的转化率?可能不会,因为使用现有的机器学习算法,如朴素贝叶斯、协同过滤、支持向量机(SVM)等,我们可以从实际角度获得足够的精度,并且可以更快速地更新模型和进行计算,这通常是更受欢迎的。这也是深度学习在商业领域应用较少的原因。当然,在任何领域中,获得更高的精度是更好的,但实际上,高精度和所需的计算时间存在权衡关系。虽然深度学习在研究领域具有重要意义,但考虑到实际应用,它仍然面临许多障碍。

此外,深度学习算法并不完美,仍然需要在其模型本身上进行很多改进。例如,如前所述,RNN 只能满足如何将过去的信息反映到网络中或如何获得精准度,尽管它通过像 LSTM 这样的技术加以改善。而且,深度学习距离真正的人工智能还很远,尽管与过去的算法相比,它无疑是一项伟大的技术。虽然算法的研究在积极推进,但与此同时,我们还需要一个突破,才能将深度学习推广并渗透到更广泛的社会中。也许这不仅仅是模型的问题。深度学习之所以突然爆发,是因为它得到了硬件和软件领域巨大发展的支撑。深度学习与周边技术的发展密切相关。

如前所述,在深度学习能够更实际地应用于现实世界之前,仍然有许多障碍需要克服,但这并非不可能实现。我们无法突然发明出能够实现技术奇点的人工智能,但有些领域和方法可以立刻应用深度学习。在接下来的章节中,我们将思考深度学习可以在哪些行业中得到应用。希望这能为你的业务或研究领域播下新的想法种子。

最大化深度学习可能性和能力的方法

我们可以通过几种方法将深度学习应用到不同的行业。虽然根据任务或目的的不同,方法可能有所不同,但我们可以将这些方法简要地分为以下三种:

  • 面向领域的方法:利用已经经过充分研究的深度学习算法或模型,这些算法或模型能够带来出色的性能

  • 基于问题拆解的方法:这将深度学习显然可以应用的待解决问题替换为一个不同的问题,在这个问题上深度学习能够得到很好的应用。

  • 基于结果导向的方法:这探讨了如何通过深度学习表达输出的新方式。

这些方法将在以下小节中详细解释。每种方法都根据其适用的行业或领域进行划分,并指出哪些地方不适用,但它们中的任何一种都可能为你的未来活动提供重要的启示。目前,深度学习的应用案例仍然很少,且对其应用领域存在偏见,但这也意味着应该有很多机会去创造创新和新事物。最近,利用深度学习的初创公司逐渐涌现,其中一些已经取得了一定程度的成功。根据你的想法,你可以对世界产生重大影响。

基于领域导向的方法

这种方法不需要新的技术或算法。显然有一些领域非常适合当前的深度学习技术,这里提出的概念是深入挖掘这些领域。如前所述,经过实际研究和开发的深度学习算法主要集中在图像识别和自然语言处理(NLP)上,我们将探索一些与它们能很好融合的领域。

医学

医学领域应该通过深度学习进行发展。肿瘤或癌症在扫描图像中被检测出来。这无非是能够利用深度学习的一个最强大的功能——图像识别技术。利用深度学习帮助早期发现疾病并识别疾病种类,能够显著提高精确度。由于卷积神经网络(CNN)可以应用于 3D 图像,因此 3D 扫描图像应该可以相对容易地进行分析。通过在当前医学领域更广泛地采用深度学习,深度学习应能带来巨大的贡献。

我们也可以说,深度学习在未来可以对医学领域产生显著的帮助。医学领域一直受到严格的监管;然而,某些国家正在推动放宽监管,可能是因为信息技术的近期发展及其潜力。因此,医学领域和信息技术的结合将带来商业机会,产生协同效应。例如,如果远程医疗更为普及,诊断或疾病识别可能不仅仅依靠扫描图像,还可以依靠实时显示的图像。另外,如果电子病历变得普及,利用深度学习分析医学数据将变得更加容易。这是因为医学记录本身就是一个文本和图像的数据集,非常适合深度学习。通过这种方式,未知疾病的症状也可以被发现。

汽车

我们可以说,行驶中的汽车周围的环境是由图像序列和文字组成的。其他汽车和景观是图像,道路标志则是文字。这意味着我们也可以在这里运用深度学习技术,并且通过改进驾驶辅助功能,降低事故风险。可以说,驾驶辅助的终极形式是自动驾驶汽车,目前这一领域主要由谷歌和特斯拉在攻克。一个既著名又引人入胜的例子是,乔治·霍茨(George Hotz),iPhone 的首位黑客,曾在自己的车库里打造了一辆自动驾驶汽车。该车的外观被《彭博商业周刊》的一篇文章介绍过(www.bloomberg.com/features/2015-george-hotz-self-driving-car/),文章中还附有以下图片:

汽车

自动驾驶汽车已经在美国进行了测试,但由于其他国家的交通规则和道路条件不同,这一想法需要进一步研究和开发,才能在全球范围内普及使用。该领域成功的关键在于学习和识别周围的汽车、行人、景象和交通标志,并准确判断如何处理它们。

与此同时,我们不必仅仅集中在利用深度学习技术应用于汽车的实际车身上。假设我们可以开发一个智能手机应用程序,具备我们刚刚描述的相同功能,即识别和分类周围的图像和文字。那么,如果你把智能手机放置在车里,就可以将其用作车载导航应用程序。此外,例如,它还可以作为盲人导航应用程序,为他们提供准确可靠的方向指引。

广告技术

广告(ad)技术可以通过深度学习扩展其覆盖范围。当我们说广告技术时,目前指的是优化广告横幅或展示产品的推荐系统或广告网络。另一方面,当我们说广告时,不仅仅指横幅或广告网络。根据媒体类型,世界上有各种各样的广告,如电视广告、广播广告、报纸广告、海报、传单等等。我们也有数字广告活动,如 YouTube、Vine、Facebook、Twitter、Snapchat 等等。广告本身已经改变了其定义和内容,但所有广告有一个共同点:它们由图像和/或语言组成。这意味着它们是深度学习擅长的领域。直到现在,我们只能通过基于用户行为的指标,如页面浏览量 (PV)、点击率 (CTR) 和 转化率 (CVR) 来估计广告的效果,但如果我们应用深度学习技术,未来可能能够分析广告的实际内容并自动生成广告。特别是由于电影和视频只能通过图像识别和自然语言处理进行分析,视频识别将逐渐成为广告技术之外的重要发展方向。

职业或实践

医生、律师、专利律师和会计师等职业被认为是深度学习可以取代的岗位。例如,如果自然语言处理(NLP)的精确度和准确性提高,任何需要专业知识的审查工作都可以交给机器处理。由于机器可以承担这些耗时的阅读任务,人们可以更多地专注于高价值的工作。此外,如果机器能够对过去的司法案件或医疗病例进行分类,识别哪些疾病引起了哪些症状等,我们就可以建立像苹果的 Siri 这样的应用程序,回答那些通常需要专业知识的问题。那么,当医生或律师因忙碌无法及时提供帮助时,机器也能够在一定程度上处理这些专业案件。

人们常说人工智能会取代人类的工作,但我个人认为这不准确。实际上,机器取代的是那些琐碎的工作,这些本应由人类完成。一个从事 AI 编程的软件工程师可以被视为拥有专业工作,但这种工作在未来也会发生变化。例如,想象一下与汽车相关的工作,目前的工作是制造标准化汽车,但未来,工程师将像一级方程式赛车的 pit crew 一样工作。

体育

深度学习无疑也能为体育领域作出贡献。在被称为运动科学的研究领域中,分析和研究体育数据变得越来越重要。例如,你可能知道《点球成金》这本书或电影。在这部电影中,通过在棒球中采用回归模型,他们大大提高了球队的胜率。观看体育赛事本身非常激动人心,但另一方面,体育也可以看作是一段图像序列和数字数据。由于深度学习擅长识别人类无法发现的特征,因此,找出为什么某些球员得分高而其他球员得分低将变得更加容易。

我们提到的这些领域只是深度学习能够显著推动发展的众多领域中的一小部分。我们从一个领域是否有图像或文本的角度进行了探讨,但当然,深度学习对于简单的数字数据分析也应当展现出强大的性能。深度学习应该能够应用到许多其他领域,如生物信息学、金融、农业、化学、天文学、经济学等。

以问题为导向的方法

这种方法可能与传统机器学习算法中采用的方法相似。我们已经讨论过特征工程是提高机器学习精度的关键。现在我们可以说,特征工程可以分为以下两部分:

  • 在机器学习模型的约束下进行工程设计。典型的例子是将输入数据设为离散型或连续型。

  • 通过机器学习提高精度的特征工程。这通常依赖于研究人员的直觉。

在狭义上,特征工程被认为是第二部分,而这正是深度学习不需要关注的部分;而第一部分无疑是重要的,甚至对于深度学习也是如此。例如,使用深度学习预测股价是困难的。股价波动大且难以定义输入数据。此外,如何应用输出值也是一个难题。让深度学习处理这些输入和输出也被认为是广义上的特征工程。如果没有对原始数据和/或你希望预测的数据进行限制,将这些数据集插入到机器学习和深度学习算法中(包括神经网络)是困难的。

然而,我们可以采取某种方法,通过分解输入和/或输出来应用模型于这些先前的问题。就像之前在 NLP 中解释的那样,你可能曾想过,首先将无数的单词转换为特征是不可能的,但正如你所知道的,我们可以通过用稀疏向量表示单词,并将 N-grams 组合起来,来训练前馈神经网络。当然,我们不仅可以使用神经网络,还可以使用其他机器学习算法,如支持向量机(SVM)。因此,我们可以在深度学习尚未应用的领域,通过工程方法将特征与深度学习模型很好地契合,从而开辟一个新领域。与此同时,当我们聚焦于 NLP 时,我们可以看到,RNN 和 LSTM 的开发是为了正确解决 NLP 中遇到的困难或任务。这可以被看作是与特征工程相对的做法,因为在这种情况下,问题是通过分解模型来适配特征解决的。

那么,如何利用工程方法进行股价预测呢?其实,考虑输入,也就是特征,并不难。举个例子,如果你每天预测股价,使用每日股价作为特征会很难计算,但如果你使用当天与前一天之间的价格变化率,那么处理起来会更容易,因为股价保持在一定范围内,梯度也不容易爆炸。与此同时,困难的地方在于如何处理输出。股价当然是连续的数值,因此输出可能是各种值。这意味着,在神经网络模型中,输出层的单元数是固定的,它们无法处理这个问题。那么我们该怎么办呢——应该放弃吗?!不,稍等一下。很遗憾,我们无法预测股价本身,但有一种替代的预测方法。

在这里,问题在于我们可以将股价预测分类为无限多种模式。那么,我们能否将它们转换为有限模式呢?是的,我们可以。让我们强行把它们转化一下。考虑一个最极端但容易理解的案例:利用截至今天的股价数据预测明天的股价,严格来说是收盘价,是上涨还是下跌。对于这个案例,我们可以用一个深度学习模型来表示,具体如下:

以细分为导向的方法

在前面的图片中,分解导向方法 表示某一天的开盘价,分解导向方法分解导向方法 表示收盘价,分解导向方法 是最高价,分解导向方法 是实际价格。这里使用的特征仅为示例,实际应用时需要进行调整。关键在于,用这种问题替代原始任务,可以使深度神经网络理论上进行数据分类。此外,如果按照上涨或下跌的幅度来分类数据,您可以做出更精细的预测。例如,您可以按以下表格所示对数据进行分类:

类别描述
类别 1比收盘价上涨超过 3 百分比
类别 2比收盘价上涨 1~3 百分比
类别 3比收盘价上涨 0~1 百分比
类别 4比收盘价下跌 0~-1 百分比
类别 5比收盘价下跌 1~-3 百分比
类别 6比收盘价下跌超过 -3 百分比

无论预测是否真正有效,换句话说,分类是否有效,都无法在事前确定,直到我们进行验证。不过,通过将输出分为多个类别,股票价格的波动可以在相当窄的范围内进行预测。一旦我们能够将任务应用于神经网络,那么接下来我们需要做的就是检查哪个模型得到更好的结果。在这个示例中,由于股价是时间序列数据,我们可以应用 RNN。如果我们将股价图表视为图像数据,我们也可以使用 CNN 来预测未来的股价。

所以,现在我们已经参考示例思考了方法,但总结起来,我们可以说:

  • 模型特征工程:这意味着设计输入或调整值,以适应深度学习模型,或通过设置输出限制来实现分类。

  • 特征模型工程:这意味着设计新的神经网络模型或算法,以解决某一特定领域的问题。

第一个方法需要设计输入和输出,以便适应模型,而第二个方法则需要采用数学方法。如果您意识到让项目的预测有所限制,那么特征工程可能会更容易启动。

面向输出的方法

前面提到的两种方法,旨在通过深度学习提高特定领域任务或问题的正确答案比例。当然,这对于深度学习而言是至关重要的部分,也是其价值体现的地方;然而,将精度提高到最终极限,可能并不是利用深度学习的唯一方法。另一种方法是通过稍微改变视角,利用深度学习来设计输出。让我们来看看这意味着什么。

深度学习被 AI 研究人员和技术专家称为一种创新的方法,但大众仍然不了解它的伟大。相反,他们更关注机器做不到的事情。例如,人们并不关注使用 CNN 进行图像识别的 MNIST,它的错误率比人类还低,但却批评机器无法完美识别图像。这可能是因为人们一提到 AI,就会对其产生很高的期待,想象它的能力。我们可能需要改变这种心态。让我们考虑一下 DORAEMON,这个在日本非常著名、全球也家喻户晓的漫画人物——一只拥有高智能和 AI 的机器人,但常常犯傻。我们会批评他吗?不会,我们只是笑一笑,或者当作笑话来看,并不会太认真。再想想电影《钢铁侠》中的 DUMMY / DUM-E,这个机器人手臂也有 AI,但同样会犯傻错误。看看,它们会犯错,但我们仍然喜欢它们。

通过这种方式,可能更好地强调机器会犯错这一点。改变用户界面中的表达部分,可能会成为人们采用人工智能的触发点,而不仅仅是最先研究某个算法。谁知道呢?从创造性领域的角度思考,可能更容易引起世界的兴趣,而不是从精确度的角度。谷歌的 Deep Dream 就是一个很好的例子。当艺术或设计与深度学习合作时,我们可以做更多令人兴奋的事情。

总结

在这一章中,你学会了如何利用深度学习算法进行实际应用。已经得到广泛研究的领域包括图像识别和自然语言处理(NLP)。在学习 NLP 领域时,我们研究了两种新的深度学习模型:RNN 和 LSTM 网络,这些网络可以通过时间序列数据进行训练。这些模型使用的训练算法是 BPTT。你还学到了三种最大化深度学习能力的方法:面向领域的方法、面向细分的方法和面向输出的方法。每种方法有不同的角度,都可以最大化深度学习的可能性。

而且……恭喜你!你刚刚完成了使用 Java 进行深度学习的学习部分。虽然这本书中仍有一些模型尚未提及,但你可以确信,掌握和使用它们不会有问题。下一章将介绍一些用其他编程语言实现的库,所以放轻松,看看就好。

第七章:其他重要的深度学习库

在这一章,我们将讨论其他深度学习库,特别是使用非 Java 编程语言的库。以下是一些最著名、最成熟的库:

  • Theano

  • TensorFlow

  • Caffe

你将简要了解每个库。由于我们将在这里主要使用 Python 来实现它们,如果你不是 Python 开发者,可以跳过本章。本章介绍的所有库都支持 GPU 实现,并具有其他特殊功能,所以让我们深入了解它们。

Theano

Theano 是为深度学习开发的,但它实际上不是一个深度学习库;它是一个用于科学计算的 Python 库。文档可在deeplearning.net/software/theano/找到。页面上介绍了几个特性,如 GPU 的使用,但最显著的特点是 Theano 支持计算微分自动微分,而 Java 科学计算库 ND4J 不支持这一功能。这意味着,使用 Theano 时,我们无需自己计算模型参数的梯度,Theano 会自动完成这项工作。由于 Theano 处理了算法中最复杂的部分,数学表达式的实现变得不那么困难。

让我们看看 Theano 如何计算梯度。首先,我们需要在机器上安装 Theano。安装可以通过pip install Theanoeasy_install Theano命令完成。然后,以下是导入和使用 Theano 的代码:

import theano
import theano.tensor as T

在 Theano 中,所有变量都作为张量进行处理。例如,我们有scalarvectormatrixd代表双精度,l代表长整型,等等。像sincoslogexp这样的通用函数也在theano.tensor下定义。因此,如前所示,我们经常使用张量的别名T

作为简要了解 Theano 实现的第一步,考虑非常简单的抛物线曲线。实现代码保存在DLWJ/src/resources/theano/1_1_parabola_scalar.py中,你可以参考它。首先,我们定义x如下:

x = T.dscalar('x')

这个定义在 Python 中是独特的,因为x没有值,它只是一个符号。在这种情况下,x是类型为d(双精度)的scalar。然后我们可以非常直观地定义y及其梯度。实现如下:

y = x ** 2
dy = T.grad(y, x)

所以,dy应该包含2x。让我们检查一下是否可以得到正确的答案。我们需要额外做的是将math函数注册到 Theano 中:

f = theano.function([x], dy)

然后你可以轻松地计算梯度的值:

print f(1)  # => 2.0
print f(2)  # => 4.0

非常简单!这就是 Theano 的强大之处。这里我们有一个scalar类型的x,但是你也可以通过简单地将x定义为以下形式,轻松实现向量(甚至矩阵)计算:

x = T.dvector('x')
y = T.sum(x ** 2)

我们不会在这里深入探讨,但你可以在DLWJ/src/resources/theano/1_2_parabola_vector.pyDLWJ/src/resources/theano/1_3_parabola_matrix.py中找到完整的代码。

当我们考虑使用 Theano 实现深度学习算法时,可以在 GitHub 的Deep Learning Tutorialsgithub.com/lisa-lab/DeepLearningTutorials)找到一些非常好的示例。在本章中,我们将概述标准的 MLP 实现,让你更好地理解 Theano。作为快照,fork 后的仓库可以在github.com/yusugomori/DeepLearningTutorials查看。首先,我们来看看mlp.py。隐藏层的模型参数是权重和偏置:

W = theano.shared(value=W_values, name='W', borrow=True)
b = theano.shared(value=b_values, name='b', borrow=True)

两个参数都使用theano.shared定义,以便可以通过模型访问和更新。激活函数可以表示为如下:

Theano

这表示激活函数,即代码中的双曲正切函数。因此,相应的代码写成如下:

lin_output = T.dot(input, self.W) + self.b
self.output = (
    lin_output if activation is None
    else activation(lin_output)
)

这里也支持线性激活。同样,输出层的参数Wb,即逻辑回归层,在logistic_sgd.py中定义并初始化:

self.W = theano.shared(
    value=numpy.zeros(
        (n_in, n_out),
        dtype=theano.config.floatX
    ),
    name='W',
    borrow=True
)

self.b = theano.shared(
    value=numpy.zeros(
        (n_out,),
        dtype=theano.config.floatX
    ),
    name='b',
    borrow=True
)

多类别逻辑回归的激活函数是softmax函数,我们可以直接写出并定义输出,如下所示:

self.p_y_given_x = T.nnet.softmax(T.dot(input, self.W) + self.b)

我们可以将预测值写为:

self.y_pred = T.argmax(self.p_y_given_x, axis=1)

在训练方面,由于反向传播算法的方程是从损失函数及其梯度中计算得出的,我们需要做的就是定义要最小化的函数,即负对数似然函数:

def negative_log_likelihood(self, y):
    return -T.mean(T.log(self.p_y_given_x)[T.arange(y.shape[0]), y])

在这里,我们计算的是均值,而不是和,用于评估整个小批量(mini-batch)。

有了这些前面的值和定义,我们可以实现 MLP。再次强调,我们需要做的就是定义 MLP 的方程和符号。以下是代码的提取:

class MLP(object):
    def __init__(self, rng, input, n_in, n_hidden, n_out):
        # self.hiddenLayer = HiddenLayer(...)
        # self.logRegressionLayer = LogisticRegression(...)

        # L1 norm
        self.L1 = (
             abs(self.hiddenLayer.W).sum()
             + abs(self.logRegressionLayer.W).sum()
        )

        # square of L2 norm
        self.L2_sqr = (
           (self.hiddenLayer.W ** 2).sum()
            + (self.logRegressionLayer.W ** 2).sum()
        )

        # negative log likelihood of MLP
        self.negative_log_likelihood = (
           self.logRegressionLayer.negative_log_likelihood
        )

        # the parameters of the model
        self.params = self.hiddenLayer.params + self.logRegressionLayer.params

然后,你可以构建并训练模型。让我们来看一下test_mlp()中的代码。一旦加载数据集并构建 MLP,你可以通过定义代价函数来评估模型:

cost = (
    classifier.negative_log_likelihood(y)
    + L1_reg * classifier.L1
    + L2_reg * classifier.L2_sqr
)

有了这个代价函数,我们只需一行代码就能获得模型参数的梯度:

gparams = [T.grad(cost, param) for param in classifier.params]

以下是更新参数的公式:

updates = [
    (param, param - learning_rate * gparam)
    for param, gparam in zip(classifier.params, gparams)
]

第一个括号中的代码遵循此公式:

Theano

然后,最后我们定义实际的训练函数:

train_model = theano.function(
    inputs=[index],
    outputs=cost,
    updates=updates,
    givens={
        x: train_set_x[index * batch_size: (index + 1) * batch_size],
        y: train_set_y[index * batch_size: (index + 1) * batch_size]
    }
)

每个索引的输入和标签对应于givens中的xy,因此当给定index时,参数会通过updates进行更新。因此,我们可以通过训练周期和小批量的迭代来训练模型:

while (epoch < n_epochs) and (not done_looping):
    epoch = epoch + 1
        for minibatch_index in xrange(n_train_batches):
           minibatch_avg_cost = train_model(minibatch_index)

原始代码中有测试和验证部分,但我们刚才提到的是最基本的结构。使用 Theano 时,梯度的方程将不再需要推导。

TensorFlow

TensorFlow 是由 Google 开发的机器学习和深度学习库。项目页面在www.tensorflow.org/,所有代码都公开在 GitHub 上,地址为github.com/tensorflow/tensorflow。TensorFlow 本身是用 C++编写的,但它提供了 Python 和 C++的 API。我们在本书中关注的是 Python 实现。安装可以通过pipvirtualenvdocker完成。安装指南可以在www.tensorflow.org/versions/master/get_started/os_setup.html找到。安装完成后,你可以通过编写以下代码导入并使用 TensorFlow:

import tensorflow as tf

TensorFlow 建议你将深度学习代码实现为以下三个部分:

  • inference():它使用给定的数据进行预测,定义了模型的结构

  • loss():它返回要优化的误差值

  • training():它通过计算梯度来应用实际的训练算法

我们将遵循这个指南。一个适合初学者的 MNIST 分类教程介绍在www.tensorflow.org/versions/master/tutorials/mnist/beginners/index.html,这个教程的代码可以在DLWJ/src/resources/tensorflow/1_1_mnist_simple.py找到。在这里,我们考虑优化教程中介绍的代码。你可以在DLWJ/src/resources/tensorflow/1_2_mnist.py查看完整代码。

首先,我们需要考虑的是获取 MNIST 数据。幸运的是,TensorFlow 也提供了获取数据的代码,地址为github.com/tensorflow/tensorflow/blob/master/tensorflow/examples/tutorials/mnist/input_data.py,我们将代码放入同一目录。然后,通过编写以下代码,你可以导入 MNIST 数据:

import input_data

可以使用以下代码导入 MNIST 数据:

mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)

类似于 Theano,我们定义变量时不赋予实际值,而是作为占位符:

x_placeholder = tf.placeholder("float", [None, 784])
label_placeholder = tf.placeholder("float", [None, 10])

这里,784是输入层的单元数,10是输出层的单元数。这样做是因为占位符中的值会随着小批量数据的变化而变化。一旦定义了占位符,你就可以继续进行模型的构建和训练。在inference()中,我们用softmax函数设置了非线性激活:

def inference(x_placeholder):

    W = tf.Variable(tf.zeros([784, 10]))
    b = tf.Variable(tf.zeros([10]))

    y = tf.nn.softmax(tf.matmul(x_placeholder, W) + b)

    return y

这里,Wb是模型的参数。loss函数,即cross_entropy函数,在loss()中定义如下:

def loss(y, label_placeholder):
    cross_entropy = - tf.reduce_sum(label_placeholder * tf.log(y))

    return cross_entropy

有了inference()loss()的定义,我们可以通过编写以下代码来训练模型:

def training(loss):
    train_step = tf.train.GradientDescentOptimizer(0.01).minimize(loss)

    return train_step

GradientDescentOptimizer() 应用梯度下降算法。但要小心,因为这个方法只是定义了训练的方法,实际的训练尚未执行。TensorFlow 还支持 AdagradOptimizer()MomentumOptimizer() 以及其他主要的优化算法。

之前解释的代码和方法用于定义模型。要执行实际的训练,你需要初始化一个 TensorFlow 会话:

init = tf.initialize_all_variables()
sess.run(init)

然后我们用小批量数据训练模型。所有小批量数据会存储在 feed_dict 中,然后在 sess.run() 中使用:

for i in range(1000):
    batch_xs, batch_ys = mnist.train.next_batch(100)
    feed_dict = {x_placeholder: batch_xs, label_placeholder: batch_ys}

    sess.run(train_step, feed_dict=feed_dict)

这就是模型训练的全部内容。非常简单,对吧?你可以通过编写以下代码来展示结果:

def res(y, label_placeholder, feed_dict):
    correct_prediction = tf.equal(
        tf.argmax(y, 1), tf.argmax(label_placeholder, 1)
    )

    accuracy = tf.reduce_mean(
        tf.cast(correct_prediction, "float")
    )

   print sess.run(accuracy, feed_dict=feed_dict)

TensorFlow 使得实现深度学习变得非常简单,而且非常有用。此外,TensorFlow 还有一个强大的功能——TensorBoard,可以用来可视化深度学习。只需在之前的代码片段中添加几行代码,我们就可以使用这个有用的功能。

让我们先看看模型是如何可视化的。代码位于 DLWJ/src/resources/tensorflow/1_3_mnist_TensorBoard.py,所以只需运行它。在你运行程序后,输入以下命令:

$ tensorboard --logdir=<ABOSOLUTE_PATH>/data

这里,<ABSOLUTE_PATH> 是程序的绝对路径。然后,如果你在浏览器中访问 http://localhost:6006/,你会看到以下页面:

TensorFlow

这展示了 cross_entropy 的值变化过程。此外,当你点击头部菜单中的 GRAPH 时,你可以看到模型的可视化:

TensorFlow

当你点击页面中的 inference 时,你可以看到模型结构:

TensorFlow

现在我们来看看代码内部。为了启用可视化,你需要用作用域将整个区域包裹起来:with tf.Graph().as_default()。通过添加这个作用域,作用域中声明的所有变量将显示在图中。显示的名称可以通过以下方式设置 name 标签:

x_placeholder = tf.placeholder("float", [None, 784], name="input")
label_placeholder = tf.placeholder("float", [None, 10], name="label")

定义其他作用域会在图中创建节点,这里就是 inference()loss()training() 展现其实际价值的地方。你可以定义各自的作用域,而不会失去任何可读性:

def inference(x_placeholder):
    with tf.name_scope('inference') as scope:
        W = tf.Variable(tf.zeros([784, 10]), name="W")
        b = tf.Variable(tf.zeros([10]), name="b")

        y = tf.nn.softmax(tf.matmul(x_placeholder, W) + b)

    return y

def loss(y, label_placeholder):
    with tf.name_scope('loss') as scope:
        cross_entropy = - tf.reduce_sum(label_placeholder * tf.log(y))

        tf.scalar_summary("Cross Entropy", cross_entropy)

    return cross_entropy

def training(loss):
    with tf.name_scope('training') as scope:
        train_step = tf.train.GradientDescentOptimizer(0.01).minimize(loss)

    return train_step

loss() 中的 tf.scalar_summary() 会使变量显示在 EVENTS 菜单中。为了启用可视化,我们需要以下代码:

summary_step = tf.merge_all_summaries()
init = tf.initialize_all_variables()

summary_writer = tf.train.SummaryWriter('data', graph_def=sess.graph_def)

然后,可以通过以下代码添加变量的处理过程:

summary = sess.run(summary_step, feed_dict=feed_dict)
summary_writer.add_summary(summary, i)

当我们使用更复杂的模型时,这种可视化功能会变得更加有用。

Caffe

Caffe 是一个以速度著称的库。官方项目页面是caffe.berkeleyvision.org/,GitHub 页面是github.com/BVLC/caffe。类似于 TensorFlow,Caffe 主要使用 C++ 开发,但它提供了 Python 和 MATLAB 的 API。此外,Caffe 的独特之处在于,您不需要任何编程经验,您只需编写配置或协议文件,即 .prototxt 文件,就可以进行深度学习的实验和研究。在这里,我们专注于基于协议的方法。

Caffe 是一个非常强大的库,能够快速构建、训练和测试模型;然而,安装该库以获得其许多优势有些困难。如您从caffe.berkeleyvision.org/installation.html的安装指南中看到的,您需要提前安装以下内容:

  • CUDA

  • BLAS(ATLAS、MKL 或 OpenBLAS)

  • OpenCV

  • Boost

  • 其他:snappy、leveldb、gflags、glog、szip、lmdb、protobuf 和 hdf5

然后,从 GitHub 页面克隆仓库,并从 Makefile.config.example 创建 Makefile.config 文件。您可能需要提前安装 Anaconda(一个 Python 发行版)来运行 make 命令。您可以从www.continuum.io/downloads下载它。运行 makemake testmake runtest 命令后(您可能想使用 -jN 选项,如 make -j4make -j8 来加速过程),如果测试通过,您将看到 Caffe 的强大功能。那么,让我们来看一个示例。进入 $CAFFE_ROOT,即您克隆仓库的路径,并输入以下命令:

$ ./data/mnist/get_mnist.sh
$ ./examples/mnist/train_lenet.sh

这就是解决标准 MNIST 分类问题所需的全部内容,使用 CNN。那么,发生了什么呢?当您查看 train_lenet.sh 时,您会看到以下内容:

#!/usr/bin/env sh

./build/tools/caffe train --solver=examples/mnist/lenet_solver.prototxt

它简单地使用协议文件 lenet_solver.prototxt 运行 caffe 命令。此文件配置了模型的超参数,例如学习率和动量。该文件还引用了网络配置文件,在这种情况下是 lenet_train_test.prototxt。您可以使用类似 JSON 的描述定义每一层:

layer {
  name: "conv1"
  type: "Convolution"
  bottom: "data"
  top: "conv1"
  param {
    lr_mult: 1
  }
  param {
    lr_mult: 2
  }
  convolution_param {
    num_output: 20
    kernel_size: 5
    stride: 1
    weight_filler {
      type: "xavier"
    }
    bias_filler {
      type: "constant"
    }
  }
}

基本上,协议文件分为两个部分:

  • Net:此项定义了模型的详细结构,并描述了每一层,从而构成了整个神经网络

  • Solver:此项定义了优化设置,如使用 CPU/GPU、迭代次数以及模型的超参数,如学习率

当您需要将深度学习应用于大数据集并采用主要方法时,Caffe 是一个很好的工具。

摘要

在本章中,你学习了如何使用 Theano、TensorFlow 和 Caffe 实现深度学习算法和模型。它们都有各自独特且强大的功能,每一个都非常有用。如果你对其他库和框架感兴趣,可以参考 Chainerchainer.org/)、Torchtorch.ch/)、Pylearn2deeplearning.net/software/pylearn2/)、Nervananeon.nervanasys.com/)等。你还可以参考一些基准测试(github.com/soumith/convnet-benchmarksgithub.com/soumith/convnet-benchmarks/issues/66),当你实际考虑使用前面提到的库之一来构建应用时。

在本书中,你学习了机器学习和深度学习的基本理论和算法,以及深度学习在研究/商业领域中的应用。凭借你在这里获得的知识和技术,你应该能够应对任何面临的问题。虽然实现人工智能仍然需要更多的步骤,但你现在拥有了实现创新的最佳机会。

第八章:未来展望

在前几章中,我们学习了深度学习的概念、理论、实现方法以及如何使用相关库。现在你已经掌握了深度学习的基本技巧,所以不必担心。另一方面,深度学习的发展非常迅速,明天可能就会有新的模型问世。关于人工智能或深度学习的重大新闻每天都会接连发布。既然你已经掌握了基本技巧,你可以快速学习即将到来的人工智能和深度学习新技术。现在,让我们暂时抛开技术细节,思考人工智能领域将会走向何方,或者说应该走向何方。深度学习的未来是什么?在最后一章,我们来思考这个问题。本章将讨论以下主题:

  • 深度学习行业的热点话题

  • 如何管理人工智能技术

  • 如何进一步进行深度学习的研究

关于最后一个主题——进一步学习,我推荐一个关于深度学习的网站。你可以通过思考下一步可能出现的技术,或者运用你已掌握的技巧进行创新,而不是单纯地跟随人工智能技术的发展步伐,从而保持领先。

深度学习的最新突破

引发人工智能热潮的深度学习,势头无法停歇。每天都有新的研究成果报道。如在第六章中提到的,实际应用方法——递归神经网络及更多,许多研究人员都在进行图像识别和自然语言处理的竞争。当然,深度学习不仅限于这两个领域,还应用于许多其他领域。这些应用的成果非常令人振奋。

在这场人工智能热潮中,2016 年 3 月,围棋界因一件事而震动。围棋是一种棋盘游戏,两名玩家争夺更多的领土。'人工智能战胜人类围棋选手'的消息不仅震惊了围棋界,也震惊了全世界。当一台机器,也就是人工智能,战胜了人类围棋选手时,这个消息引起了广泛关注。被谷歌收购的 DeepMind(deepmind.com/)开发了这个围棋人工智能,名为 AlphaGo,而 AlphaGo 在谷歌 DeepMind 挑战赛中以四胜一负的成绩战胜了世界级选手李世石。每场比赛都通过 YouTube 进行了直播,许多人实时观看了比赛。如果你还没有观看过,可以在 YouTube 上观看所有五场比赛:

在这些比赛中,第一场比赛,Match 1,特别引起了人们的关注。此外,从超过 135 万次的页面访问量来看,可以看出很多人观看了 AlphaGo 输给李世石的唯一比赛,即第 4 场比赛的视频。以下是捕捉到 Match 1 一幕的图像:

深度学习的最新消息

AlphaGo 击败李世石的时刻(www.youtube.com/watch?v=vFr3K2DORc8

这不仅令人工智能研究者激动,也令整个世界兴奋的时刻,但为什么这则新闻会如此受关注呢?举个例子,1997 年,国际商业机器公司开发的深蓝在国际象棋比赛中击败了世界冠军。当然,那时也成为重大新闻,因为这也是机器击败人类的时刻。那么,当这并非机器首次击败人类时,AlphaGo 击败李世石的胜利为何如此震撼世界呢?国际象棋和围棋有什么不同呢?嗯,区别在于围棋的棋型复杂性。事实上,围棋的策略模式比国际象棋多得多。在流行的棋类游戏如国际象棋、将棋和围棋中,用于决定胜负的模式数量如下:

  • 国际象棋:10,120

  • 将棋:10,220

  • 围棋:10,360

甚至看着这些数字,你就可以看出围棋的策略是多么复杂,很容易想象机器也需要大量的计算。由于这巨大数量的棋型,直到最近,人们认为 AlphaGo 在现实时间内击败人类是不可能的,或者说要等 100 年或 200 年 AlphaGo 才会击败人类。人们认为机器在现实时间内计算围棋的棋型是不可能的。但是现在,在短短几年内,机器已经击败了人类。根据 Google 研究博客,在 DeepMind 挑战赛之前的 1.5 个月内,DeepMind 能够预测人类的棋步 57%的时间(googleresearch.blogspot.jp/2016/01/alphago-mastering-ancient-game-of-go.html)。机器战胜人类的事实确实产生了影响,但机器能够在现实时间内学习围棋策略的事实更为令人惊讶。DeepMind 应用了深度神经网络与蒙特卡洛树搜索和强化学习的组合,显示了深度神经网络算法的应用广度。

预期的下一步行动

自从 AlphaGo 的新闻被媒体报道以来,AI 热潮无疑得到了推动。你可能会注意到,最近在媒体上听到“深度学习”这个词的频率更高了。这可以说是全球对 AI 的期望已大大提高。值得注意的是,原本是技术术语的“深度学习”如今在日常新闻中被广泛使用。你可以看到AI这一词的形象正在发生变化。可能就在几年前,如果人们听到 AI 这个词,许多人会联想到一个实际的机器人,但现在呢?“AI”这个词现在经常被使用——而且不特别有意识地——与软件或应用程序相关,已被视为司空见惯。这无非是表明,世界已经开始正确理解为研究而发展的 AI。如果一项技术走错方向,就会产生排斥,或者有人开始错误地开发技术;然而,似乎目前这股 AI 技术热潮正朝着一个良好的方向发展。

尽管我们对人工智能的发展感到兴奋,但不可避免地,一些人会感到某些恐惧或焦虑。很容易想象,有些人可能认为机器主导人类的世界,像科幻电影或小说中的情节那样,迟早会到来,特别是在 AlphaGo 战胜李世石后,曾经被认为机器不可能战胜人类的围棋世界,感到焦虑的人可能会增加。然而,尽管一则“机器战胜人类”的新闻,如果单纯关注“机器获胜”这一事实,可能会被认为是负面的,但这绝对不是负面新闻。相反,这是对人类的重大好消息。为什么?这里有两个原因。

第一个原因是,Google DeepMind 挑战赛是一场人类处于不利局面的比赛。不仅仅是围棋,卡牌游戏或体育比赛中,我们通常会在比赛前研究对手可能使用的策略,基于对手的行动模式来制定我们的策略。当然,DeepMind 研究了职业围棋选手的策略和玩法,而人类却无法充分研究机器的玩法,因为 DeepMind 在 Google DeepMind 挑战赛前的最后时刻一直在学习和不断变化其行动模式。因此,可以说存在信息偏差或不利条件。李世石在这些不利条件下赢得一局是非常了不起的。同时,这也表明 AI 将进一步发展。

另一个原因是我们发现,机器不太可能摧毁人类的价值,反而有助于推动人类的进一步发展。在 Google DeepMind 挑战赛中,一台机器使用了人类以前从未使用过的策略。这一事实让我们感到非常惊讶,但同时也意味着我们发现了一项人类需要研究的新事物。深度学习显然是一项伟大的技术,但我们不应忘记,神经网络涉及模仿人脑结构的算法。换句话说,它的基础与人类的思维模式相同。通过仅仅增加计算速度,机器能够发现人脑无法计算的模式遗漏。AlphaGo 可以与 AlphaGo 进行对局,并从这场对局的结果中学习。与人类不同,机器可以 24 小时不断地进行学习,因此它能够快速发现新的模式。在这个过程中,机器会发现一个全新的模式,这可以供人类进一步研究围棋。通过研究人类无法发现的新策略,我们的围棋世界将得到扩展,我们也能更加享受围棋。无需多言,不仅是机器在学习,人类也在学习。在各个领域,机器会发现一些人类之前没有注意到的新事物,每次人类面对这些新发现时,他们也在进步。

AI 和人类处于一种互补的关系中。再重复一遍,机器擅长计算大量的模式,并发现尚未被发现的模式,这超出了人类的能力。另一方面,AI 目前无法从完全新的概念中创造出新想法。相反,这恰恰是人类擅长的领域。机器只能在给定的知识范围内进行判断。例如,如果 AI 仅仅接收到许多种狗的图像作为输入数据,它可以判断这是什么种类的狗,但如果是猫,AI 就会尽最大努力根据自己对狗的知识来判断它是什么种类的狗。

AI 在某种意义上其实是一个无辜的存在,它只是根据自己获得的知识给出最可能的答案。思考应该为 AI 提供什么样的知识才能使其进步,这是人类的任务。如果你提供新的知识,AI 将再次根据给定的知识以相当快的速度计算出最可能的答案。人们的兴趣和知识也会因成长的环境不同而有所不同,AI 也同样如此。这意味着,AI 的个性,以及它是否对人类产生好或坏的影响,取决于 AI 所接触到的个人/群体。一个典型的例子就是微软开发的 AI Tay(https://www.tay.ai)。2016 年 3 月 23 日,Tay 在 Twitter 上发布了以下推文:hellooooooo world!!!

Tay 从 Twitter 上用户之间的互动中获取知识,并发布新的推文。这个试验本身非常有趣。

然而,刚对公众开放后,问题就发生了。在 Twitter 上,用户通过向 Tay 的账户输入歧视性知识恶作剧。因为这个原因,Tay 开始不断发布包含性别歧视表达的推文。而且,在 Tay 出现在 Twitter 上的仅一天后,Tay 就消失了,留下了以下推文:c u soon humans need sleep now so many conversations today thx

如果你访问 Tay 的 Twitter 账户页面(twitter.com/tayandyou),推文已经被保护,你无法再看到它们:

预计的下一步行动

Tay 的 Twitter 账户目前已关闭

这正是人工智能被人类错误训练的结果。在过去几年中,人工智能技术受到了广泛关注,这可能是加速人工智能技术发展的因素之一。现在,下一步应该考虑的是人工智能与人类之间的互动。人工智能本身只是众多技术之一。技术的好坏取决于人类如何使用它;因此,我们应该小心如何控制这项技术,否则整个人工智能领域可能会在未来收缩。人工智能在某些狭窄的领域表现得特别好,但远未达到压倒性的程度,也远未达到当前科幻作品中的设想。人工智能未来如何发展,取决于我们如何运用知识和技术管理。

我们确实应该关注如何控制这项技术,但不能减缓其发展的速度。考虑到近期机器人热潮,如 Facebook 将推出 Bot 商店的消息(techcrunch.com/2016/03/17/facebooks-messenger-in-a-bot-store/),我们不难想象用户与应用程序之间的互动会变成以聊天界面为基础,人工智能将逐步融入普通用户的日常生活。为了让更多人了解人工智能,我们应该进一步开发人工智能技术,并使其更加方便人们使用。

深度学习和人工智能得到了更多关注,这意味着如果你想在这个领域取得出色的成果,你可能会面临激烈的竞争。你想进行的实验很可能已经被别人做过。深度学习领域正成为初创企业竞争非常激烈的一个领域。如果你拥有大量数据,你可以通过分析这些数据来获得优势,但如果没有,你就需要考虑如何在有限的数据下进行实验。尽管如此,如果你想取得出色的表现,最好时刻记住以下几点:

提示

深度学习只能判断训练中提供的知识范围内的事物。

基于此,你可以通过以下两种方法获得有趣的结果:

  • 使用能够轻松生成训练和测试所需输入数据和输出数据的数据进行实验

  • 在实验中,使用完全不同类型的数据,分别用于训练和测试

对于第一种方法,例如,你可以检查使用 CNN 进行的自动着色。这一技术已经在公开的项目中引入,在线访问链接为tinyclouds.org/colorize/,或者在论文中可以找到,链接为arxiv.org/pdf/1603.08511v1.pdf。这个想法是自动为灰度图像着色。如果你有彩色图像——这些图像应该很容易获得——你可以通过编写快速脚本生成灰度图像。这样,你就准备好了训练所需的输入数据和输出数据。能够准备大量数据意味着你可以更容易地进行测试,并且更常获得高精度。以下是其中一个测试的例子:

预期的下一步动作

两者都引用自tinyclouds.org/colorize/

模型的输入是左侧的灰度图像,输出是中间的图像,右侧的图像是有真实颜色的图像。

对于第二种方法,使用完全不同类型的数据,分别用于训练和测试。在一个实验中,我们故意提供 AI 不知道的数据,并让随机答案和正确答案之间的差距变得有趣/好玩。例如,在关于图像生成故事medium.com/@samim/generating-stories-about-images-d163ba41e4ed)中,他们为神经网络提供了一张相扑选手的图片,而神经网络只研究了其中的一个项目,并引入了以下内容:浪漫小说,然后测试神经网络的反应。结果如下:

预期的下一步动作

引用自medium.com/@samim/generating-stories-about-images-d163ba41e4ed

这个实验本身基于一个名为neural-storyteller的方式(github.com/ryankiros/neural-storyteller),但由于给定的数据包含了一个想法,因此得到了有趣的结果。由此可见,将你的新想法添加到已经开发的方式中,也可以获得有趣的结果。

深度学习的有用新闻来源

好了,最后,让我们挑选两个网站,它们对于未来深度学习的动态观察以及学习越来越多的新知识会非常有用。这将帮助你的学习。

第一个是GitXivgitxiv.com/)。首页如下:

深度学习的有用新闻来源

在 GitXiv 上,主要是基于论文的文章,但除了提供论文链接外,还设置了用于测试的代码链接,因此你可以缩短研究时间。当然,它会不断更新新的实验,所以你可以查看哪些方法是主流,或者现在深度学习在什么领域最热。如果你注册了电子邮件地址,它会持续向你发送最新信息。你应该试试看:

深度学习的有用新闻来源

第二个是Deep Learning News (news.startup.ml/)。这是一个收集深度学习和机器学习相关话题的链接集合。它的界面与Hacker News (news.ycombinator.com/)相同,后者处理整个技术行业的新闻,因此如果你知道 Hacker News,你应该会对其布局很熟悉:

深度学习的有用新闻来源

Deep Learning News 上的信息更新并不是非常频繁,但它不仅提供了实现或技术方面的技巧,还包括了哪些领域可以应用深度学习的提示,并且提供了深度学习和机器学习相关的活动信息,因此对创意或灵感的激发非常有帮助。如果你浏览一下首页列表中的网址,可能会激发你的一些好点子。

除了我们在这里选出的这两个网站外,还有更多有用的资源、材料和社区,比如 Google+ 上的深度学习小组 (plus.google.com/communities/112866381580457264725),因此你应该关注适合你的媒体。无论如何,现在这个行业发展迅速,始终关注最新的信息是非常必要的。

摘要

在本章中,我们从 AlphaGo 作为突发新闻的例子出发,探讨了深度学习将如何或应该发展。机器在某些领域战胜人类并不可怕,反而是人类成长的机会。另一方面,如果人工智能技术处理不当,这项伟大的技术可能会朝着错误的方向发展,正如 Tay 的例子所示。因此,我们应小心谨慎,不要破坏这项正在稳步发展的技术。

深度学习是一个拥有巨大潜力的领域,一个创意就能改变一个时代。如果你在不久的将来构建人工智能,这个人工智能可以说是一个纯粹的存在,尚没有任何知识。思考该如何教导人工智能,如何与其互动,以及如何将人工智能用于造福人类,都是人类的工作。作为本书的读者,你将引领这项新技术朝着正确的方向发展。最后,我希望你能积极参与到人工智能领域的前沿。