深度学习教程:从感知器到深度网络

没有深度学习,就没有Transformer

这一次人工智能的爆发基础就是深度学习

人工智能的近期复苏在很大程度上得益于人工智能的根基,即“深度学习”。在本文中,我将从最简单的构建块开始,向您介绍深度学习背后的关键概念和算法。

本次人工智能的爆发,

这在一定程度上可以归因于社交网络用户生成的大量原始数据(其中大部分需要进行分析)、先进数据科学解决方案的兴起,以及可通过GPGPU获得的廉价计算能力。

但除了这些现象之外,这种复兴在很大程度上是由人工智能(特别是机器学习)的新趋势推动的,这种趋势被称为“深度学习”。在本教程中,我将向您介绍深度学习背后的关键概念和算法,从最简单的组合单元开始,然后构建 Java 中的机器学习概念。

(全面披露:我还是 Java 深度学习库的作者,可在此处获取,本文中的示例是使用上述库实现的。如果您喜欢它,您可以在 GitPP上给它一个星星来支持它,对此我将不胜感激。使用说明可在主页上找到。)

源代码

http://www.gitpp.com/datasets/neuralnetworks-java

30 秒机器学习教程

如果你不熟悉,请查看机器学习的介绍:

总体流程如下:

  1. 我们有一些算法,它给出了一些带标签的示例,比如 10 张带有标签 1(“狗”)的狗的图像和 10 张带有标签 0(“不是狗”)的其他事物的图像——请注意,在这篇文章中我们主要坚持监督式二元分类。
  2. 该算法“学习”识别狗的图像,当输入新图像时,希望产生正确的标签(如果是狗的图像则为 1,否则为 0)。

这种设置非常普遍:您的数据可能是症状,而您的标签是疾病;或者您的数据可能是手写字符的图像,而您的标签是它们所代表的实际字符。

感知器:早期的深度学习算法

最早的监督训练算法之一是感知器,它是神经网络的基本构建块。

假设平面上有n 个点,分别标记为“0”和“1”。我们得到一个新点,并想猜测它的标签(这类似于上面的“狗”和“不是狗”场景)。我们怎么做呢?

一种方法可能是查看最近的邻居并返回该点的标签。但更智能的做法是选择一条最能区分标记数据的线并将其用作分类器。

图片

在这种情况下,每条输入数据将表示为一个向量x = ( x_1, x_2 ),并且我们的函数将是“如果低于该线则为 ‘0’,如果高于该线则为 ‘1’”。

为了用数学方式表示这一点,让我们的分隔符由权重w和垂直偏移(或偏差)b的向量定义。然后,我们的函数将输入和权重与加权和传递函数相结合:

图片

然后,该传递函数的结果将被输入到激活函数中以产生标签。在上面的例子中,我们的激活函数是一个阈值截止值(例如,如果大于某个值,则为 1):

图片

训练感知器

感知器的训练包括向其输入多个训练样本并计算每个样本的输出。每次采样后,权重w都会进行调整,以最小化输出误差,输出误差定义为期望(目标)输出与实际输出之间的差异。还有其他误差函数,例如均方误差,但训练的基本原理保持不变。

单感知器的缺点

深度学习的单感知器方法有一个主要缺点:它只能学习线性可分函数。这个缺点有多大?以 XOR 为例,这是一个相对简单的函数,请注意它不能通过线性分离器进行分类(注意下面的失败尝试):

图片

为了解决这个问题,我们需要使用多层感知器,也称为前馈神经网络:实际上,我们将把一堆这样的感知器组合在一起,以创建一个更强大的学习机制。

用于深度学习的前馈神经网络

神经网络实际上只是感知器的组合,以不同的方式连接并以不同的激活函数进行操作。

图片

首先,我们来看一下前馈神经网络,它具有以下特性:

  • 输入、输出和一个或多个隐藏层。上图显示了一个具有 3 个单元的输入层、4 个单元的隐藏层和一个具有 2 个单元的输出层的网络(术语“单元”和“神经元”可以互换)。
  • 每个单元都是一个单独的感知器,就像上面描述的一样。
  • 输入层单元作为隐藏层单元的输入,而隐藏层单元是输出层的输入。
  • 两个神经元之间的每个连接都有一个权重w(类似于感知器权重)。
  • t的每个单元通常都与前一层t – 1的每个单元相连(尽管您可以通过将其权重设置为 0 来断开它们的连接)。
  • 要处理输入数据,您需要将输入向量“限制”到输入层,并将向量的值设置为每个输入单元的“输出”。在这种特殊情况下,网络可以处理三维输入向量(因为有 3 个输入单元)。例如,如果您的输入向量是 [7, 1, 2],那么您将顶部输入单元的输出设置为 7,中间单元的输出设置为 1,依此类推。然后使用每个隐藏单元的加权和传递函数将这些值向前传播到隐藏单元(因此称为前向传播),进而计算它们的输出(激活函数)。
  • 输出层以与隐藏层相同的方式计算其输出。输出层的结果是网络的输出。

超越线性

如果我们的每个感知器只能使用线性激活函数,结果会怎样?那么,我们网络的最终输出仍将输入的某种线性函数,只是会根据网络中收集到的大量不同权重进行调整。换句话说,一堆线性函数的线性组合仍然只是一个线性函数。如果我们仅限于线性激活函数,那么前馈神经网络不会比感知器更强大,无论它有多少层。

一堆线性函数的线性组合仍然只是一个线性函数,因此大多数神经网络都使用非线性激活函数。

因此,大多数神经网络使用非线性激活函数,如逻辑函数、双曲正切函数、二元函数或整流函数。如果没有这些函数,网络就只能学习输入的线性组合函数。

训练感知器

用于监督训练多层感知器的最常见的深度学习算法是反向传播。基本过程如下:

  1. 训练样本被呈现并通过网络向前传播。
  2. 计算输出误差,通常是均方误差:

    图片

    其中t是目标值,y是实际网络输出。其他误差计算也可以接受,但 MSE 是一个不错的选择。
  3. 使用一种称为随机梯度下降的方法来最小化网络误差。
    图片

    梯度下降是通用的,但在神经网络中,这将是训练误差与输入参数的函数关系图。每个权重的最优值是误差达到全局最小值的值。在训练阶段,权重会以小步更新(在每个训练样本或几个样本的小批量之后),这样它们就会一直试图达到全局最小值——但这并非易事,因为你经常会陷入局部最小值,就像右边的那样。例如,如果权重的值为 0.6,则需要将其改为 0.4。该图表示的是最简单的情况,即误差取决于单个参数。然而,网络误差取决于每个网络权重,误差函数要复杂得多。值得庆幸的是,反向传播提供了一种根据输出误差更新两个神经元之间每个权重的方法。推导本身相当复杂,但给定节点的权重更新具有以下(简单)形式:

    图片

    其中E是输出误差,w_i是输入i对神经元的权重。本质上,目标是沿着权重i的梯度方向移动。当然,关键术语是误差的导数,它并不总是容易计算的:如何找到大型网络中随机隐藏节点的随机权重的导数?答案:通过反向传播。首先在输出单元计算误差,其公式非常简单(基于目标值和预测值之间的差异),然后以巧妙的方式通过网络反向传播,使我们能够在训练期间有效地更新权重并(希望)达到最小值。

隐藏层

隐藏层尤其令人感兴趣。根据通用近似定理,可以训练具有有限数量神经元的单个隐藏层网络来近似任意随机函数。换句话说,单个隐藏层足以学习任何函数。话虽如此,我们在实践中通常使用多个隐藏层(即更深的网络)可以学得更好。

隐藏层是网络存储训练数据的内部抽象表示的地方。

隐藏层是网络存储训练数据的内部抽象表示的地方,类似于人类大脑(大大简化的类比)对现实世界的内部表示。在本教程的后面,我们将介绍使用隐藏层的不同方法。

示例网络

您可以在此处看到一个简单的(4-2-3 层)前馈神经网络,它通过testMLPSigmoidBP方法用Java 实现,对IRIS数据集进行分类。该数据集包含三类鸢尾植物,其特征包括萼片长度、花瓣长度等。网络为每个类别提供 50 个样本。这些特征被限制在输入单元中,而每个输出单元对应于数据集的一个类别:“1/0/0”表示植物属于 Setosa 类,“0/1/0”表示 Versicolour,“0/0/1”表示 Virginica)。分类错误为 2/150(即,它在 150 个样本中错误分类了 2 个样本)。

大型网络的问题

神经网络可以有多个隐藏层:在这种情况下,较高层在先前层之上“构建”新的抽象。正如我们之前提到的,在实践中,你通常可以通过更大的网络学到更多东西。

然而,增加隐藏层的数量会导致两个已知问题:

  1. 梯度消失:随着我们添加越来越多的隐藏层,反向传播在将信息传递到较低层时变得越来越无用。实际上,随着信息传回,梯度开始消失,相对于网络权重变得很小。
  2. 过度拟合:这也许是机器学习的核心问题。简而言之,过度拟合描述的是训练数据拟合得太过紧密的现象,可能假设过于复杂。在这种情况下,你的学习器最终会很好地拟合训练数据,但在真实样本上的表现会差很多。

让我们看看一些解决这些问题的深度学习算法。

自动编码器

大多数机器学习入门课程往往止步于前馈神经网络。但可能的网络空间要丰富得多——所以让我们继续。

自动编码器通常是前馈神经网络,旨在学习数据集的压缩、分布式表示(编码)。

图片

从概念上讲,网络经过训练可以“重新创建”输入,即输入和目标数据相同。换句话说:您试图输出与输入相同的内容,但以某种方式压缩。这是一种令人困惑的方法,所以让我们看一个例子。

压缩输入:灰度图像

假设训练数据由 28×28 灰度图像组成,每个像素的值被限制在一个输入层神经元上(即输入层将有 784 个神经元)。那么,输出层将具有与输入层相同数量的单元(784),并且每个输出单元的目标值将是图像一个像素的灰度值。

这种架构背后的直觉是,网络不会学习训练数据与其标签之间的“映射”,而是学习数据本身的内部结构和特征。(因此,隐藏层也称为特征检测器。)通常,隐藏单元的数量小于输入/输出层的数量,这迫使网络仅学习最重要的特征并实现降维。

我们希望中间的几个小节点在概念层面上学习数据,从而产生紧凑的表示。

实际上,我们希望中间的几个小节点真正在概念层面上学习数据,产生一种紧凑的表示,以某种方式捕捉我们输入的核心特征。

流感疾病

为了进一步演示自动编码器,让我们再看一个应用程序。

在本例中,我们将使用一个由流感症状组成的简单数据集(这个想法来自这篇博文)。如果您感兴趣,可以在testAEBackpropagation方法中找到此示例的代码。

数据集细分如下:

  • 有六个二进制输入特性。
  • 前三个是病情的症状,比如1 0 0 0 0 0表示这个病人发烧,0 1 0 0 0 0表示咳嗽,1 1 0 0 0 0表示咳嗽发烧等。
  • 最后三个特征是“反”症状;当患者出现其中一种症状时,他或她生病的可能性较小。例如,0 0 0 1 0 0表示该患者接种了流感疫苗。这两组特征可以组合使用:0 1 0 1 0 0表示接种疫苗的患者出现咳嗽等。

如果患者至少具备前三个特征中的两个,我们就认为患者生病了;如果患者至少具备后三个特征中的两个,我们就认为患者健康(打破平局有利于健康患者),例如:

  • 111000, 101000, 110000, 011000, 011100 = 生病
  • 000111, 001110, 000101, 000011, 000110 = 健康

我们将训练一个自动编码器(使用反向传播),它有六个输入单元和六个输出单元,但只有两个隐藏单元。

经过数百次迭代,我们观察到,当每个“病态”样本被呈现给机器学习网络时,两个隐藏单元中的一个(每个“病态”样本的单元相同)始终表现出比另一个更高的激活值。相反,当呈现“健康”样本时,另一个隐藏单元的激活值更高。

回归机器学习

本质上,我们的两个隐藏单元已经学会了流感症状数据集的紧凑表示。为了了解这与学习的关系,我们回到过度拟合的问题。通过训练我们的网络来学习数据的紧凑表示,我们倾向于更简单的表示,而不是过度拟合训练数据的高度复杂的假设。

在某种程度上,通过青睐这些更简单的表示,我们试图以更真实的意义学习数据。

受限玻尔兹曼机

下一个合乎逻辑的步骤是研究受限玻尔兹曼机(RBM),这是一种可以在其输入集上学习概率分布的生成随机神经网络

图片

RBM 由隐藏层、可见层和偏置层组成。与前馈网络不同,可见层和隐藏层之间的连接是无向的(值可以在可见层到隐藏层和隐藏层到可见层的方向传播),并且是完全连接的(给定层中的每个单元都连接到下一层中的每个单元 – 如果我们允许任何层中的任何单元连接到任何其他层,那么我们就有了一个玻尔兹曼(而不是受限玻尔兹曼)机)。

标准 RBM 具有二进制隐藏和可见单元:即,在伯努利分布下,单元激活为 0 或 1 ,但也有具有其他非线性的变体。

虽然研究人员已经了解 RBM 一段时间了,但是最近引入的对比散度无监督训练算法重新引起了人们的兴趣。

对比发散

单步对比散度算法(CD-1)的工作原理如下:

  1. 正相:
    • 输入样本v被夹紧到输入层。
    • v以与前馈网络类似的方式传播到隐藏层。隐藏层激活的结果是h
  2. 负相:
    • 将h传播回可见层并得到结果v’(可见层和隐藏层之间的连接是无向的,因此允许双向移动)。
    • 将新的v’使用激活结果h’传播回隐藏层。
  3. 体重更新:

    图片

    其中a是学习率,vv’hh’w是向量。

该算法背后的直觉是,正相(给定v 的 h 反映了网络对现实世界数据的内部表示。同时,负相表示尝试根据此内部表示(给定h的v’)重新创建数据。主要目标是使生成的数据尽可能接近现实世界,这反映在权重更新公式中。

换句话说,网络对于输入数据的表示方式有一定的认知,因此它会尝试根据这种认知来重现数据。如果重现结果与现实不够接近,它会进行调整并再次尝试。

重返流感

为了演示对比发散,我们将使用与之前相同的症状数据集。测试网络是一个具有六个可见单元和两个隐藏单元的 RBM。我们将使用对比发散训练网络,将症状v限制在可见层。在测试期间,症状再次呈现到可见层;然后,数据传播到隐藏层。隐藏单元代表生病/健康状态,与自动编码器(将数据从可见层传播到隐藏层)的架构非常相似。

经过数百次迭代后,我们可以观察到与自动编码器相同的结果:当出现任何“病态”样本时,其中一个隐藏单元具有更高的激活值,而另一个隐藏单元对于“健康”样本始终更活跃。

您可以在testContrastiveDivergence方法中看到此示例的实际效果。

深度网络

我们现在已经证明了自动编码器和 RBM 的隐藏层可以作为有效的特征检测器;但我们很少能直接使用这些特征。事实上,上面的数据集更像是一个例外,而不是一个规则。相反,我们需要找到某种方法来间接使用这些检测到的特征。

幸运的是,人们发现这些结构可以堆叠起来形成深度网络。这些网络可以逐层贪婪地进行训练,以帮助克服与传统反向传播相关的梯度消失过度拟合问题。

由此产生的结构通常非常强大,产生令人印象深刻的结果。以谷歌著名的“猫”论文为例,他们使用特殊类型的深度自动编码器根据未标记的数据“学习”人类和猫的面部检测。

让我们仔细看看。

堆叠自动编码器

顾名思义,该网络由多个堆叠的自动编码器组成。

图片

自动编码器t的隐藏层充当自动编码器t + 1的输入层。第一个自动编码器的输入层是整个网络的输入层。贪婪的逐层训练过程如下:

  1. 使用反向传播方法和所有可用的训练数据单独训练第一个自动编码器(t=1,或上图中的红色连接,但具有额外的输出层)。
  2. 训练第二个自动编码器t=2 (绿色连接)。由于t=2的输入层是t=1的隐藏层,我们不再对t=1的输出层感兴趣,因此将其从网络中删除。训练从将输入样本限制到t=1的输入层开始,然后向前传播到t=2的输出层。接下来,使用反向传播更新t=2的权重(输入隐藏和隐藏输出) 。t =2使用所有训练样本,与t=1类似。
  3. 对所有层重复上述步骤(即,删除前一个自动编码器的输出层,用另一个自动编码器替换它,并用反向传播进行训练)。
  4. 步骤 1-3 称为预训练,权重已正确初始化。但是,输入数据和输出标签之间没有映射。例如,如果训练网络识别手写数字图像,仍然无法将最后一个特征检测器(即最后一个自动编码器的隐藏层)的单元映射到图像的数字类型。在这种情况下,最常见的解决方案是向最后一层添加一个或多个完全连接的层(蓝色连接)。现在可以将整个网络视为多层感知器,并使用反向传播进行训练(此步骤也称为微调)。

那么,堆叠自动编码器就是为了提供一种有效的预训练方法来初始化网络的权重,从而让你拥有一个可以随时训练(或微调)的复杂多层感知器。

深度信念网络

与自动编码器一样,我们也可以堆叠玻尔兹曼机来创建称为深度信念网络 (DBN)的类。

图片

在这种情况下,RBM t的隐藏层充当 RBM t+1的可见层。第一个 RBM 的输入层是整个网络的输入层,贪婪的逐层预训练的工作方式如下:

  1. 使用对比散度和所有训练样本来训练第一个 RBM t=1 。
  2. 训练第二个 RBM t=2 。由于t=2的可见层是t=1的隐藏层,训练从将输入样本限制在t=1的可见层开始,然后向前传播到t=1的隐藏层。然后,该数据用于启动t=2的对比发散训练。
  3. 对所有层重复上述步骤。
  4. 与堆叠式自动编码器类似,在预训练后,可以通过将一个或多个完全连接的层连接到最终的 RBM 隐藏层来扩展网络。这形成了一个多层感知器,然后可以使用反向传播对其进行微调

该过程类似于堆叠自动编码器,但其中自动编码器被 RBM 取代,反向传播被对比散度算法取代。

(注:有关构建和训练堆叠自动编码器或深度信念网络的更多信息,请查看此处的示例代码。)

卷积网络

作为最后的深度学习架构,让我们看一下卷积网络,这是一类特别有趣且特殊的前馈网络,非常适合图像识别。

图片来自DeepLearning.net

在研究卷积网络的实际结构之前,我们首先定义一个图像过滤器,或者一个具有相关权重的正方形区域。过滤器应用于整个输入图像,并且您通常会应用多个过滤器。例如,您可以将四个 6×6 过滤器应用于给定的输入图像。然后,坐标为 1,1 的输出像素是左上角为 1,1 的 6×6 输入像素正方形与过滤器权重(也是 6×6 正方形)的加权和。输出像素 2,1 是左上角为 2,1 的输入正方形的结果,依此类推。

综上所述,这些网络由以下属性定义:

  • 卷积层将多个过滤器应用于输入。例如,图像的第一个卷积层可以有四个 6×6 过滤器。一个过滤器应用于图像的结果称为特征图(FM),特征图的数量等于过滤器的数量。如果前一层也是卷积层,则过滤器将以不同的权重应用于其所有 FM,因此每个输入 FM 都连接到每个输出 FM。图像上共享权重背后的直觉是,无论特征位于何处,都会检测到它们,而过滤器的多样性允许每个过滤器检测不同的特征集。
  • 子采样层可减小输入的大小。例如,如果输入由 32×32 图像组成,并且该层的子采样区域为 2×2,则输出值将为 16×16 图像,这意味着输入图像的 4 个像素(每个 2×2 正方形)组合成单个输出像素。子采样有多种方法,但最流行的是最大池化、平均池化和随机池化。
  • 最后的子采样(或卷积)层通常连接到一个或多个全连接层,其中最后一个全连接层代表目标数据。
  • 训练是使用改进的反向传播进行的,该反向传播考虑了子采样层,并根据应用该过滤器的所有值来更新卷积过滤器的权重。

您可以在此处看到几个使用MNIST数据集(手写字母的灰度图像)训练的卷积网络示例(使用反向传播),具体来说是testLeNet*方法(我推荐使用testLeNetTiny2,因为它在相对较短的时间内实现了约 2% 的低错误率)。此处还有一个类似网络的 JavaScript 可视化效果。

执行

现在我们已经介绍了最常见的神经网络变体,我想写一些关于在实施这些深度学习结构过程中所面临的挑战。

广义上讲,我创建深度学习库的目标是(现在仍然是)构建一个满足以下标准的基于神经网络的框架:

  • 一种能够表示不同模型的通用架构(例如,我们上面看到的所有神经网络变体)。
  • 使用多种训练算法(反向传播、对比发散等)的能力。
  • 表现不错。

为了满足这些要求,我采用了分层(或模块化)的方法来设计软件。

结构

让我们从基础开始:

  • NeuralNetworkImpl是所有神经网络模型的基类。
  • 每个网络包含一组层。
  • 每一层都有一个连接列表,其中连接是两层之间的链接,使得网络是有向无环图。

该结构足够灵活,可用于经典的前馈网络,以及RBM和更复杂的架构,如ImageNet。

它还允许一个层成为多个网络的一部分。例如,深度信念网络中的层也是其相应 RBM 中的层。

此外,这种架构允许在预训练阶段将 DBN 视为堆叠 RBM 的列表,并在微调阶段将其视为前馈网络,这在直观上很好,在编程上也很方便。

数据传播

下一个模块负责通过网络传播数据,这个过程分为两步:

  1. 确定层的顺序。例如,要从多层感知器获取结果,数据被“限制”到输入层(因此,这是要计算的第一层)并一直传播到输出层。为了在反向传播期间更新权重,输出误差必须从输出层开始以广度优先的顺序传播到每一层。这是使用LayerOrderStrategy的各种实现来实现的,它利用网络的图形结构,采用不同的图形遍历方法。一些示例包括广度优先策略和特定层的目标。顺序实际上是由层之间的连接决定的,因此策略返回有序的连接列表。
  2. 计算激活值。每一层都有一个关联的ConnectionCalculator,它获取其连接列表(来自上一步)和输入值(来自其他层)并计算结果激活。例如,在简单的 S 形前馈网络中,隐藏层的ConnectionCalculator获取输入层和偏置层的值(分别是输入数据和1的数组)以及单元之间的权重(对于完全连接层,权重实际上以Matrix的形式存储在FullyConnected连接中),计算加权和,并将结果输入到 S 形函数中。连接计算器实现了各种传输(例如加权和、卷积)和激活(例如多层感知器的逻辑和 tanh、RBM 的二进制)函数。它们中的大多数都可以使用Aparapi在 GPU 上执行,并可用于小批量训练。

使用 APARAPI 进行 GPU 计算

正如我之前提到的,神经网络近年来复兴的原因之一是它们的训练方法非常有利于并行性,允许你使用 GPGPU 显著加快训练速度。在这种情况下,我选择使用 Aparapi 库来添加GPU 支持。

Aparapi 对连接计算器施加了一些重要的限制:

  • 只允许原始数据类型的一维数组(和变量)。
  • 只允许从 GPU 可执行代码调用Aparapi Kernel类本身的成员方法。

因此,大多数数据(权重、输入和输出数组)都存储在Matrix实例中,这些实例在内部使用一维浮点数组。所有 Aparapi 连接计算器都使用AparapiWeightedSum(用于完全连接层和加权和输入函数)、AparapiSubsampling2D(用于子采样层)或AparapiConv2D (用于卷积层)。通过引入异构系统架构,可以克服其中一些限制。Aparapi 还允许在 CPU 和 GPU 上运行相同的代码。

训练

训练模块实现各种训练算法。它依赖于前两个模块。例如,BackPropagationTrainer(所有训练器都使用Trainer基类)使用前馈层计算器进行前馈阶段,并使用特殊的广度优先层计算器进行误差传播和权重更新。

我最近的工作是关于 Java 8 的支持和一些其他的改进,很快就会合并到master中。

结论

本 Java 深度学习教程的目的是向您简要介绍深度学习算法领域,从最基本的组成单元(感知器)开始,逐步介绍各种有效且流行的架构,例如受限玻尔兹曼机。

神经网络背后的理念已经存在了很长时间;但如今,如果你不了解深度网络或其他深度学习概念,你就无法踏入机器学习社区。炒作不应被误认为是正当理由,但随着 GPGPU 计算的发展以及 Geoffrey Hinton、Yoshua Bengio、Yann LeCun 和 Andrew Ng 等研究人员取得的令人瞩目的进展,该领域无疑显示出巨大的潜力。现在正是熟悉和参与该领域的最佳时机。

附录:资源

如果您有兴趣了解更多信息,我发现以下资源对我的工作非常有帮助:

源代码

http://www.gitpp.com/datasets/neuralnetworks-java

留下评论

您的邮箱地址不会被公开。 必填项已用 * 标注