深度学习教程:从感知器到深度网络
已发表: 2022-03-11近年来,人工智能领域出现了复苏。 它已经扩展到学术界之外,谷歌、微软和 Facebook 等主要参与者创建了自己的研究团队并进行了一些令人印象深刻的收购。
部分原因可归因于社交网络用户生成的大量原始数据(其中大部分需要分析)、高级数据科学解决方案的兴起以及可通过 GPGPU 获得的廉价计算能力。
但除了这些现象之外,这种复苏在很大程度上是由人工智能的新趋势推动的,特别是在机器学习领域,被称为“深度学习”。 在本教程中,我将向您介绍深度学习背后的关键概念和算法,从最简单的组合单元开始,然后构建 Java 中的机器学习概念。
(全面披露:我也是Java深度学习库的作者,可在此处获取,本文中的示例是使用上述库实现的。如果您喜欢它,可以在GitHub上给它一个star 来支持它, 不胜感激。使用说明可在主页上找到。)
机器学习 32 秒教程
如果您不熟悉,请查看此机器学习简介:
一般程序如下:
- 我们有一些算法,它给出了一些带标签的示例,比如 10 张带有标签 1(“狗”)的狗图像和 10 张带有标签 0(“非狗”)的其他事物的图像——请注意,我们主要是坚持对这篇文章进行有监督的二元分类。
- 该算法“学习”识别狗的图像,并在输入新图像时希望生成正确的标签(如果是狗的图像则为 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 维输入向量(因为有 3 个输入单元)。 例如,如果您的输入向量是 [7, 1, 2],那么您可以将顶部输入单元的输出设置为 7,将中间单元的输出设置为 1,依此类推。 然后使用每个隐藏单元的加权和传递函数将这些值向前传播到隐藏单元(因此称为前向传播),进而计算它们的输出(激活函数)。
- 输出层以与隐藏层相同的方式计算其输出。 输出层的结果就是网络的输出。
超越线性
如果我们的每个感知器只允许使用线性激活函数怎么办? 然后,我们网络的最终输出仍然是输入的一些线性函数,只是通过在整个网络中收集的大量不同权重进行调整。 换句话说,一堆线性函数的线性组合仍然只是一个线性函数。 如果我们仅限于线性激活函数,那么前馈神经网络并不比感知器强大,无论它有多少层。
正因为如此,大多数神经网络使用非线性激活函数,如逻辑、tanh、二进制或整流器。 没有它们,网络只能学习作为其输入线性组合的函数。
训练感知器
用于多层感知器的监督训练的最常见的深度学习算法称为反向传播。 基本程序:
- 训练样本被呈现并通过网络向前传播。
计算输出误差,通常为均方误差:
其中t是目标值, y是实际的网络输出。 其他误差计算也是可以接受的,但 MSE 是一个不错的选择。
使用称为随机梯度下降的方法最小化网络误差。
梯度下降是通用的,但在神经网络的情况下,这将是作为输入参数函数的训练误差图。 每个权重的最佳值是误差达到全局最小值的那个值。 在训练阶段,权重会以小步长(在每个训练样本或几个样本的小批量之后)更新,以使它们总是试图达到全局最小值——但这不是一件容易的事,因为你通常以局部最小值结束,如右边的那个。 例如,如果权重的值为 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” 表示弗吉尼亚)。 分类误差为 2/150(即,它对 150 个样本中的 2 个样本进行了错误分类)。
大型网络的问题
一个神经网络可以有多个隐藏层:在这种情况下,更高的层是在之前的层之上“构建”新的抽象。 正如我们之前提到的,您通常可以在更大的网络中学习更好的实践。
但是,增加隐藏层的数量会导致两个已知问题:
- 梯度消失:随着我们添加越来越多的隐藏层,反向传播在将信息传递到较低层时变得越来越没用。 实际上,随着信息被传回,梯度开始消失并相对于网络的权重变小。
- 过度拟合:可能是机器学习的核心问题。 简而言之,过度拟合描述了对训练数据的拟合过于紧密的现象,可能是假设过于复杂。 在这种情况下,您的学习器最终会非常好地拟合训练数据,但在真实示例上的表现会差很多。
让我们看一些深度学习算法来解决这些问题。
自动编码器
大多数介绍性机器学习课程往往会停止使用前馈神经网络。 但是可能的网络空间要丰富得多——所以让我们继续。
自编码器通常是一种前馈神经网络,旨在学习数据集的压缩、分布式表示(编码)。
从概念上讲,网络被训练来“重新创建”输入,即输入和目标数据是相同的。 换句话说:您尝试输出与输入相同的内容,但以某种方式压缩。 这是一个令人困惑的方法,所以让我们看一个例子。
压缩输入:灰度图像
假设训练数据由 28x28 灰度图像组成,每个像素的值被钳制到一个输入层神经元(即输入层将有 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) 的工作原理如下:
- 正相:
- 输入样本v被钳制到输入层。
- v以与前馈网络类似的方式传播到隐藏层。 隐藏层激活的结果是h 。
- 负相:
- 用结果v'将h传播回可见层(可见层和隐藏层之间的连接是无向的,因此允许双向移动)。
- 使用激活结果h'将新的v'传播回隐藏层。
体重更新:
其中a是学习率, v 、 v' 、 h 、 h'和w是向量。
该算法背后的直觉是正相位( h给定v )反映了网络对现实世界数据的内部表示。 同时,否定阶段表示尝试基于此内部表示(给定h的 v' )重新创建数据。 主要目标是生成的数据尽可能接近真实世界,这反映在权重更新公式中。
换句话说,网络对如何表示输入数据有一些感知,因此它试图基于这种感知来再现数据。 如果它的再现不够接近现实,它会进行调整并再次尝试。
重返流感
为了证明对比分歧,我们将使用与以前相同的症状数据集。 测试网络是一个具有六个可见单元和两个隐藏单元的 RBM。 我们将使用对比散度来训练网络,并将症状v限制在可见层上。 在测试过程中,症状再次呈现到可见层; 然后,数据被传播到隐藏层。 隐藏单元代表生病/健康状态,与自动编码器非常相似的架构(将数据从可见层传播到隐藏层)。
经过数百次迭代,我们可以观察到与自动编码器相同的结果:当出现任何“生病”样本时,其中一个隐藏单元具有更高的激活值,而另一个对于“健康”样本总是更活跃。
您可以在testContrastiveDivergence方法中看到这个示例。
深度网络
我们现在已经证明了自动编码器和 RBM 的隐藏层可以作为有效的特征检测器; 但我们很少能直接使用这些功能。 事实上,上面的数据集与其说是规则,不如说是例外。 相反,我们需要找到某种方法来间接使用这些检测到的特征。
幸运的是,人们发现这些结构可以堆叠形成深层网络。 这些网络可以一次一层地进行贪婪训练,以帮助克服与经典反向传播相关的梯度消失和过拟合问题。
由此产生的结构通常非常强大,产生令人印象深刻的结果。 以谷歌著名的“猫”论文为例,他们使用特殊类型的深度自动编码器来“学习”基于未标记数据的人和猫的面部检测。
让我们仔细看看。
堆叠自动编码器
顾名思义,这个网络由多个堆叠的自动编码器组成。
自编码器t的隐藏层充当自编码器t + 1的输入层。 第一个自编码器的输入层是整个网络的输入层。 贪婪的逐层训练过程是这样工作的:
- 使用所有可用训练数据的反向传播方法单独训练第一个自动编码器( t=1或上图中的红色连接,但带有额外的输出层)。
- 训练第二个自动编码器t=2 (绿色连接)。 由于t=2 的输入层是 t=1的隐藏层,我们不再对t=1的输出层感兴趣,我们将其从网络中移除。 训练首先将输入样本钳制到t=1的输入层,然后将其向前传播到t=2的输出层。 接下来,使用反向传播更新t=2的权重(输入隐藏和隐藏输出)。 t=2使用所有训练样本,类似于t=1 。
- 对所有层重复前面的过程(即,删除前一个自动编码器的输出层,用另一个自动编码器替换它,并使用反向传播进行训练)。
- 步骤 1-3 称为预训练,并正确初始化权重。 但是,输入数据和输出标签之间没有映射。 例如,如果网络被训练来识别手写数字的图像,它仍然不可能将来自最后一个特征检测器(即最后一个自动编码器的隐藏层)的单元映射到图像的数字类型。 在这种情况下,最常见的解决方案是将一个或多个全连接层添加到最后一层(蓝色连接)。 整个网络现在可以被视为一个多层感知器,并使用反向传播进行训练(此步骤也称为微调)。
因此,堆叠式自动编码器都是为了提供一种有效的预训练方法来初始化网络的权重,从而为您提供一个可以训练(或微调)的复杂的多层感知器。
深度信念网络
与自动编码器一样,我们也可以堆叠玻尔兹曼机来创建一个称为深度信念网络 (DBN)的类。
在这种情况下,RBM t的隐藏层充当 RBM t+1的可见层。 第一个 RBM 的输入层是整个网络的输入层,贪婪的逐层预训练是这样工作的:
- 使用所有训练样本的对比散度训练第一个 RBM t=1 。
- 训练第二个 RBM t=2 。 由于t=2 的可见层是 t=1的隐藏层,因此训练首先将输入样本钳制到t=1的可见层,然后向前传播到t=1的隐藏层。 然后,该数据用于启动t=2的对比散度训练。
- 对所有层重复前面的过程。
- 与堆叠式自动编码器类似,在预训练之后,可以通过将一个或多个全连接层连接到最终的 RBM 隐藏层来扩展网络。 这形成了一个多层感知器,然后可以使用反向传播对其进行微调。
这个过程类似于堆叠式自动编码器,但自动编码器被 RBM 取代,反向传播被对比散度算法取代。
(注意:有关构建和训练堆叠自动编码器或深度信念网络的更多信息,请查看此处的示例代码。)
卷积网络
作为最终的深度学习架构,让我们看一下卷积网络,这是一种特别有趣且特殊的前馈网络,非常适合图像识别。
在我们查看卷积网络的实际结构之前,我们首先定义一个图像过滤器,或者一个带有相关权重的正方形区域。 过滤器应用于整个输入图像,您通常会应用多个过滤器。 例如,您可以将四个 6x6 过滤器应用于给定的输入图像。 然后,坐标为 1,1 的输出像素是左上角为 1,1 的 6x6 平方输入像素与滤波器的权重(也是 6x6 平方)的加权和。 输出像素 2,1 是左上角为 2,1 的输入正方形的结果,依此类推。
综上所述,这些网络由以下属性定义:
- 卷积层将许多过滤器应用于输入。 例如,图像的第一个卷积层可能有四个 6x6 滤波器。 在图像上应用一个过滤器的结果称为特征图(FM),特征图的数量等于过滤器的数量。 如果前一层也是卷积层,则滤波器将应用于所有具有不同权重的 FM,因此每个输入 FM 都连接到每个输出 FM。 图像中共享权重背后的直觉是,无论它们的位置如何,都会检测到特征,而滤波器的多样性允许它们中的每一个检测不同的特征集。
- 二次采样层减少了输入的大小。 例如,如果输入由 32x32 图像组成,并且该层的子采样区域为 2x2,则输出值将是 16x16 图像,这意味着输入图像的 4 个像素(每个 2x2 正方形)被组合成单个输出像素。 有多种子采样方式,但最流行的是最大池化、平均池化和随机池化。
- 最后一个子采样(或卷积)层通常连接到一个或多个全连接层,其中最后一个代表目标数据。
- 使用修改后的反向传播执行训练,该反向传播将子采样层考虑在内,并根据应用该过滤器的所有值更新卷积过滤器权重。
您可以在这里看到在 MNIST 数据集(手写字母的灰度图像)上训练(使用反向传播)的卷积网络的几个示例,特别是在testLeNet*方法中(我推荐testLeNetTiny2 ,因为它实现了大约 2% 的低错误率在相对较短的时间内)。 这里还有一个类似网络的不错的 JavaScript 可视化。
执行
既然我们已经介绍了最常见的神经网络变体,我想我应该写一些关于在实施这些深度学习结构过程中所面临的挑战。
从广义上讲,我创建深度学习库的目标是(现在仍然是)构建一个满足以下标准的基于神经网络的框架:
- 一种能够表示不同模型的通用架构(例如,我们在上面看到的所有神经网络变体)。
- 使用多种训练算法(反向传播、对比散度等)的能力。
- 体面的表现。
为了满足这些要求,我采用分层(或模块化)方法来设计软件。
结构
让我们从基础开始:
- NeuralNetworkImpl 是所有神经网络模型的基类。
- 每个网络都包含一组层。
- 每层都有一个连接列表,其中连接是两层之间的链接,因此网络是有向无环图。
这种结构足够灵活,可用于经典的前馈网络,以及 RBM 和更复杂的架构,如 ImageNet。
它还允许一个层成为多个网络的一部分。 例如,深度信念网络中的层也是其相应 RBM 中的层。
此外,这种架构允许在预训练阶段将 DBN 视为堆叠 RBM 的列表,在微调阶段将其视为前馈网络,这既直观又方便。
数据传播
下一个模块负责通过网络传播数据,这是一个两步过程:
- 确定图层的顺序。 例如,为了从多层感知器中获取结果,数据被“钳制”到输入层(因此,这是要计算的第一层)并一直传播到输出层。 为了在反向传播期间更新权重,输出误差必须以广度优先顺序传播到每一层,从输出层开始。 这是使用LayerOrderStrategy的各种实现来实现的,它利用网络的图结构,采用不同的图遍历方法。 一些示例包括广度优先策略和特定层的定位。 顺序实际上是由层之间的连接决定的,因此这些策略会返回一个有序的连接列表。
- 计算激活值。 每个层都有一个关联的ConnectionCalculator ,它获取它的连接列表(来自上一步)和输入值(来自其他层)并计算结果激活。 例如,在一个简单的 sigmoid 前馈网络中,隐藏层的ConnectionCalculator获取输入层和偏置层的值(分别是输入数据和1的数组)以及单元之间的权重(在完全连接的情况下)层,权重实际上作为Matrix存储在完全连接的连接中,计算加权和,并将结果输入 sigmoid 函数。 连接计算器实现了各种传输(例如,加权和、卷积)和激活(例如,多层感知器的逻辑和 tanh,RBM 的二进制)功能。 它们中的大多数可以使用 Aparapi 在 GPU 上执行,并可用于小批量训练。
使用 Aparapi 进行 GPU 计算
正如我前面提到的,近年来神经网络重新兴起的原因之一是它们的训练方法非常有利于并行性,使用 GPGPU 可以显着加快训练速度。 在这种情况下,我选择使用 Aparapi 库来添加 GPU 支持。
Aparapi 对连接计算器施加了一些重要的限制:
- 只允许原始数据类型的一维数组(和变量)。
- 仅允许从 GPU 可执行代码调用 Aparapi Kernel类本身的成员方法。
因此,大部分数据(权重、输入和输出数组)都存储在Matrix实例中,这些实例在内部使用一维浮点数组。 所有 Aparapi 连接计算器都使用AparapiWeightedSum (用于全连接层和加权和输入函数)、 AparapiSubsampling2D (用于子采样层)或AparapiConv2D (用于卷积层)。 引入异构系统架构可以克服其中一些限制。 Aparapi 还允许在 CPU 和 GPU 上运行相同的代码。
训练
训练模块实现了各种训练算法。 它依赖于前两个模块。 For example, BackPropagationTrainer (all the trainers are using the Trainer base class) uses feedforward layer calculator for the feedforward phase and a special breadth-first layer calculator for propagating the error and updating the weights.
My latest work is on Java 8 support and some other improvements, will soon be merged into master.
结论
The aim of this Java deep learning tutorial was to give you a brief introduction to the field of deep learning algorithms, beginning with the most basic unit of composition (the perceptron) and progressing through various effective and popular architectures, like that of the restricted Boltzmann machine.
The ideas behind neural networks have been around for a long time; but today, you can't step foot in the machine learning community without hearing about deep networks or some other take on deep learning. Hype shouldn't be mistaken for justification, but with the advances of GPGPU computing and the impressive progress made by researchers like Geoffrey Hinton, Yoshua Bengio, Yann LeCun and Andrew Ng, the field certainly shows a lot of promise. There's no better time to get familiar and get involved like the present.
Appendix: Resources
If you're interested in learning more, I found the following resources quite helpful during my work:
- DeepLearning.net: a portal for all things deep learning. It has some nice tutorials, software library and a great reading list.
- An active Google+ community.
- Two very good courses: Machine Learning and Neural Networks for Machine Learning, both offered on Coursera.
- The Stanford neural networks tutorial.