近年来,人工智能领域再次活跃起来。除了传统学术界,谷歌、微软、Facebook等业内优秀企业也成立了相关研究团队,并取得了许多骄人的成果。这是由于社交网络用户产生的大量数据,其中大部分是原始数据,需要进一步分析和处理;也是由于廉价而强大的计算资源的出现,比如GPGPU的快速发展。除了这些因素之外,这种复兴在很大程度上是由人工智能的新趋势推动的,尤其是机器学习——深度学习。在这篇文章中,我将介绍深度学习背后的关键概念和算法,从最简单的元素开始,并以此为基础。如果不熟悉相关知识,通常的机器学习流程如下:1.机器学习算法需要输入少量标记样本,比如10张小狗图片,其中一张标记为1(meaningdog)andothersMarkedas0(meaningnotadog)-本文主要使用有监督的二元分类。2.这些算法“学习”如何正确分类狗的图片,然后当输入一张新图片时,算法可以预期输出正确的图片标签(比如输入一张小狗的图片,输出1;否则输出0).这通常是难以置信的:您的数据可能含糊不清,标签可能有误;或者你的数据可能是一张手写字母的图片,用它实际代表的字母来标记它。PerceptronPerceptron是最早的有监督训练算法,是构建神经网络的基础。假设平面上有n个点,分别标记为“0”和“1”。此时添加一个新点。如果我们想知道这个点的标记是什么(类似于上面提到的小狗图片的识别),我们应该怎么做呢?一个很简单的方法就是找到离这个点最近的点是多少,返回和这个点一样的marker。而一个稍微“聪明”一点的办法就是在平面上找一条线来分隔不同标签的数据点,用这条线作为“分类器”来区分新数据点的标签。在这个例子中,每个输入数据都可以表示为一个向量x=(x_1,x_2),我们的功能是实现“如果直线在下方,输出0;在直线上方,输出1”。用数学表达,定义一个表示权重的向量w和一个垂直偏移量b。然后,我们结合输入、权重和偏移量得到以下传递函数:该传递函数的结果将被送入激活函数以产生令牌。在上面的例子中,我们的激活函数是一个阈值截止函数(即大于某个阈值后输出1):训练感知器的训练包括多个训练样本的输入和输出的计算每个样品。每次计算后,调整权重w以最小化输出误差,即输入样本的标记值与实际计算值之间的差异。还有其他误差计算方法,如均方误差等,但基本原理是一样的。缺点这个简单的感知器有一个明显的缺点:它只能学习线性可分函数。这个缺陷重要吗?比如异或,这么简单的函数,用线性分类器是无法分类的(如下图,两类点分离失败):为了解决这个问题,我们需要使用多层感知器,也就是——前馈神经网络:事实上,我们将组合一组这样的感知器来创建一个更强大的学习机器。前馈神经网络神经网络实际上是前面提到的大量感知器的组合,以不同的方式连接,作用于不同的激活函数。我们简要介绍前馈神经网络,它具有以下属性:输入层、输出层和一个或多个隐藏层。上图所示的神经网络具有三个神经元的输入层、四个神经元的隐藏层和两个神经元的输出层。每个神经元就是上面提到的一个感知器。输入层的神经元作为隐藏层的输入,隐藏层的神经元同时也是输出层神经元的输入。神经元之间建立的每个连接都有一个权重w(类似于感知器中提到的权重)。t层中的每个神经元通常与前一层(t-1层)中的每个神经元都有连接(但您可以通过将其权重设置为0来断开这种连接)。为了处理输入数据,将输入向量分配给输入层。在上面的例子中,这个网络可以计算一个3D输入向量(因为只有3个输入层神经元)。如果输入向量是[7,1,2],您可以将7馈送到第一个输入神经元,将1馈送到中间的神经元,将2馈送到第三个。这些值会传播到隐藏层,通过加权传递函数(这就是前向传播)传递给每个隐藏层神经元,隐藏层神经元计算输出(激活函数)。输出层和隐藏层一样进行计算,输出层的计算结果就是整个神经网络的输出。超线性如果每个感知器只能使用一个线性激活函数怎么办?整个网络的最终输出仍然是通过一些线性函数对输入数据进行计算,只是暂时用网络中收集的一些不同的权重进行调整。换句话说,无论多少个线性函数组合起来,它们仍然是线性函数。如果我们将自己局限于线性激活函数,则无论网络有多少层,前馈神经网络都不会比感知器强大多少。正是由于这个原因,大多数神经网络都使用非线性激活函数,例如对数函数、双曲正切函数、阶跃函数、整流函数等。没有这些非线性函数的神经网络只能学习输入数据的线性组合。训练多层感知器中最常用的监督训练算法是反向传播算法。基本过程如下:1.将训练样本通过神经网络进行前向传播计算。2.计算输出误差,常用均方误差:其中t为目标值,y为实际神经网络计算输出。其他误差计算方法也是可能的,但MSE(均方误差)通常是更好的选择。3.通过随机梯度下降最小化网络误差。梯度下降很常见,但在神经网络中,输入参数是一条训练误差曲线。每个权重的最大值应该是误差曲线中的全局最小值(上图中的全局最小值)。在训练过程中,权重会以非常小的步长(在每个样本或一小组样本训练后)改变以找到全局最小值,但这并不容易,训练通常以局部最小值(高于局部最小值)结束。如示例中所示,如果当前权重值为0.6,则移至0.4。此图代表最简单的情况,其中错误仅取决于单个参数。但是网络误差取决于每个网络权重,误差函数非常非常复杂。好消息是,反向传播算法提供了一种利用输出误差来校正两个神经元之间权重的方法。该关系本身相当复杂,但是给定节点的权重校正如下(简单):其中E是输出误差,w_i是输入i的权重。本质上,这样做的目的是利用权重i来修正梯度的方向。关键是误差导数的使用,这不一定容易计算:如何在大型网络中的随机节点中找到随机权重的导数?答案是:通过反向传播。误差的绝对计算非常简单(只需求出期望值和实际值的差值),然后通过巧妙的方法将其传回网络,让我们在训练过程中有效地修正权重和(期望)达到最小值。隐藏层隐藏层非常有趣。根据万能逼近原理,可以训练一个具有有限个神经元的隐藏层来逼近任意随机函数。换句话说,一个隐藏层足以学习任何功能。这表明我们在实践中使用多个隐藏层(例如深度网络)可以获得更好的结果。隐藏层存储训练数据的内部抽象表示,就像人脑(简化类比)存储现实世界的抽象一样。接下来,我们将使用各种方法来搞这个隐藏层。网络示例可以看一下这个简单的(4-2-3)前馈神经网络,它通过对IRIS数据集进行分类的testMLPSigmoidBP方法在Java中实现。该数据集包含三种鸢尾属植物,特征包括萼片长度、花瓣长度等。每个类提供50个样本用于训练该神经网络。特征被分配给输入神经元,每个输出神经元代表一类数据集(“1/0/0”表示该植物是Setosa,“0/1/0”表示Versicolour,“0/0/1”代表弗吉尼亚)。分类的错误率为2/150(即每150次分类,错误2次)。大规模网络中的困难神经网络中可以有多个隐藏层:通过这种方式,可以在先前隐藏层之上的更高隐藏层中构建新的抽象。并且如前所述,这可以更好地学习大规模网络。增加隐藏层的数量通常会导致两个问题:1.Vanishinggradient:随着我们添加越来越多的隐藏层,反向传播传递给较低层的信息越来越少。事实上,由于信息的前向反馈,不同层之间的梯度开始消失,对网络中权重的影响也会变小。2.过拟合:或许这就是机器学习的核心问题。简而言之,过拟合是指对训练数据的识别效果太好,导致模型非常复杂。这样的结果会导致对训练数据的识别效果非常好,而对真实样本的识别效果却很差。让我们来看看一些深度学习算法是如何面对这些困难的。自动编码器大多数介绍性的机器学习课程都会让您放弃前馈神经网络。但实际上这里发生了很多事情-请继续阅读。自编码器是一个典型的前馈神经网络,其目标是为数据集学习一种压缩的、分布式的表示方法(编码思想)。从概念上讲,神经网络的目的是训练“重新创建”输入数据,就好像输入数据和目标输出数据相同一样。换句话说:您正在使神经网络的输出与输入相同,只是经过压缩。这个还是比较难理解,先看个例子。压缩输入数据:灰度图这里是一个由28x28像素的灰度图组成的训练集,每个像素的值作为一个输入层神经元的输入(输入层会有784个神经元)。输出层神经元数量必须相同(784),每个输出神经元的输出值与输入图像对应的像素灰度值相同。在这样的算法架构背后,神经网络学习的其实不是训练数据到标签的“映射”,而是学习数据本身的内部结构和特征(也正是因为如此,隐藏层也被称为作为特征检测器)。通常隐藏层的神经元数量少于输入/输入层的数量,这是为了让神经网络只学习最重要的特征,实现特征约简。我们希望在中间层使用很少的节点来学习概念层的数据并产生紧凑的表示。流行性感冒为了更好地描述自动编码器,让我们看一下另一个应用程序。这次我们使用一个包含一些感冒症状的简单数据集。如果有兴趣,请在此处发布此示例的源代码。数据结构如下:输入数据一共有六位二进制数,前三位是疾病的症状。比如100000代表发烧的病人;010000代表咳嗽;110000代表咳嗽和发烧,等等。最后三位数字表示抵抗力,如果患者有抵抗力,则意味着他/她患病的可能性较小。例如,000100表示患者接种了流感疫苗。一种可能的组合是:010100,代表咳嗽的病人接种了流感疫苗,等等。当一个病人同时出现前三个中的两个时,我们就认为他生病了;如果他有最后三个中的至少两个,那么他是健康的,例如:111000,101000,110000,011000,011100=sick000111,001110,000101,000011,000110=healthy让我们训练一个自动编码器(使用反向传播)六个输入,六个输出神经元,只有两个隐藏神经元。经过几百次迭代,我们发现每当输入一个“病态”样本时,两个隐藏层神经元之一(对于病态样本总是这个)总是显示更高的激活值。另一个隐藏层在输入“健康”样本时显示出更高的激活值。从学习的角度来看,本质上,这两个隐藏的神经元从数据集中学习了流感症状的紧凑表示。为了检验是否真的在学习,我们来看一下过拟合的问题。通过训练我们的神经网络学到的是一个紧凑而简单的表示,而不是一个高度复杂和过度拟合的数据集。在某种程度上,我们不是在寻找一种简单的表示方法,而是试图从“感觉”中学习数据。受限玻尔兹曼机下一步是研究受限玻尔兹曼机(RBM),这是一种生成式随机神经网络,可以学习输入数据集上的概率分布。RBM由隐藏层、可见层和偏置层组成。与前馈神经网络不同,可见层和隐藏层之间的连接是无方向的(值可以从可见层->隐藏层或隐藏层->可见层传输)和全连接(当前层中的每个神经元连接到下一层中的每个神经元-如果允许任何层中的任何神经元连接到任何层,我们就有了玻尔兹曼机(不受限制))。在标准的RBM中,隐藏层和可见层的神经元是二元的(即神经元的激活值只能为0或1,服从伯努利分布),但也有其他非线性变体。虽然学者们长期以来一直在研究RBM,但最近出现的对比差异无监督训练算法使该领域重新焕发活力。单步比较差分算法的原理:1.前向过程:输入样本v输入到输入层。v以类似于前馈网络的方式传播到具有激活值h的隐藏层。2.逆过程:将h传回可见层得到v'(可见层和隐藏层之间的连接是无向的,可以这样传)。然后把v'传给隐藏层得到h'。3.权重更新:其中a为学习率,v、v’、h、h’、w均为向量。算法的思想是在前向过程中影响真实数据在网络中的内部表示。同时,反向过程试图通过这种受影响的表示来重建数据。主要目的是让生成的数据尽可能地与原始数据相似,而这种差异会影响权重的更新。换句话说,这样的网络具有感知输入数据的表示程度的能力,并试图通过这种感知来重构数据。如果重建后的数据与原始数据有很大差异,请进行调整并重新重建。让我们再看看流感的例子。为了说明对比,让我们使用与上一个示例相同的流感症状数据集。测试网络是一个具有6个可见层神经元和2个隐藏层神经元的RBM。我们通过对比地将症状v分配给可见层来训练网络。在测试中,将这些症状值重新传递给可见层;然后传递到隐藏层。隐藏层中的神经元代表健康/疾病状态,类似于自动编码器。经过几百次迭代后,我们得到了与自动编码器相同的结果:喂一个生病的样本,其中一个隐藏层神经元具有更高的激活值;喂一个健康的样本,另一个神经元更兴奋。DeepNetworks到目前为止,我们已经学习了隐藏层中强大的特征检测器——自动编码器和RBMs,但是没有办法有效地使用这些特征。事实上,上面使用的数据集是特定的。我们需要找到一些方法来间接使用这些检测到的特征。好消息是,已经发现这些结构可以通过堆叠实现深度网络。这些网络可以用贪心法的思想训练,一次一层,克服上面提到的反向传播中梯度消失和过拟合的问题。这样的算法架构非常强大,可以产生非常好的结果。例如谷歌著名的“猫”识别,在实验中,通过使用特定的深度自编码器,在未标记的图像库中学习人脸和猫脸的识别。下面我们再深入一点。堆叠式自动编码器顾名思义,该网络由多个堆叠式自动编码器组成。自编码器的隐藏层t将用作t+1层的输入层。第一输入层是整个网络的输入层。使用贪心法训练每一层的步骤如下:1.使用所有数据通过反向传播的方法训练***层的autoencoder(t=1,上图中红色连接部分).2.训练第二层的autoencodert=2(绿色连接部分)。由于t=2时的输入层是t=1时的隐藏层,我们不再关心t=1时的输入层,可以从整个网络中去掉。整个训练从在t=1时将输入样本数据分配给输入层开始,并在t=2时向前传播到输出层。t=2以下的权重(输入->隐藏和隐藏->输出)使用反向传播的方法更新。t=2的层和t=1的层一样,都必须通过所有样本的训练。3.对所有层重复步骤1-2(即删除之前的自动编码器的输出层,用另一个自动编码器替换它,并使用反向传播进行训练)。4.步骤1-3称为预训练,将网络中的权重值初始化到合适的位置。但是这种训练没有得到从输入数据到输出标签的映射。例如,目标是训练识别手写数字的网络无法将最佳特征检测器(即隐藏层中最好的自动编码器)的输出映射到图像上。这样,一种常见的做法是在网络的最后一层(即蓝色连接部分)之后添加一个或多个全连接层。整个网络可以看作是一个多层感知器,并使用反向传播进行训练(这一步也称为微调)。Stackedautoencoders,提供了一种高效的预训练方法来初始化网络的权重,这样你就可以得到一个复杂的、多层的可用于训练的感知器。深度信念网络与自动编码器一样,我也可以堆叠玻尔兹曼机来构建深度信念网络(DBN)。在这个例子中,隐藏层RBMt可以看作是RBMt+1的可见层。第一个RBM的输入层是整个网络的输入层。层间贪心预训练的工作模式如下:1.通过比较差法t=12对所有训练样本训练第一个RBM。训练第一个RBM两个RBMt=1。由于t=2的可见层是t=1的隐藏层,所以训练开始时将数据赋值给t=1的可见层,通过前向传播的方式传给t=1的隐藏层.然后作为t=2时对比训练的初始数据。3.对所有层重复前面的过程。4.与堆叠自动编码器一样,在预训练之后,网络可以通过连接到层与层之间的一个或多个完全连接的RBM隐藏层来扩展。这构成了一个多层感知器,可以通过反向传播进行微调。这个过程和stackedautoencoder很相似,只是autoencoder换成了RBM,反向传播换成了contrastivedifferencealgorithm。卷积网络这是本文唯一的软件架构——卷积网络,一种特殊类型的前馈网络,对图像识别非常有效。在深入研究实际的卷积网络之前,我们首先定义一个图像过滤器,或称为具有相关权重的方阵。一个滤镜可以应用于整个图像,通常可以应用多个滤镜。例如,您可以对图像应用四个6x6滤镜。那么输出中坐标(1,1)处的像素值就是输入图像左上角的一个6x6区域的加权和,其他像素也一样。有了上述基础,让我们介绍定义卷积网络的属性:卷积层对输入数据应用多个过滤器。例如,图像的第一个卷积层使用4个6x6滤波器。将过滤器应用于图像后获得的结果称为特征图(FM),特征图的数量等于过滤器的数量。如果precursorlayer也是卷积层,那么filter作用于FM,相当于输入一个FM,输出另一个FM。直观来说,如果给整幅图像分配一个权重,那么这个特征与位置无关,多个filter可以分别检测不同的特征。下采样层减少了输入数据的大小。例如输入一张32x32的图像,通过2x2的降采样,可以得到一张16x16的输出图像,也就是说将原图像上的四个像素合并为输出图像中的一个像素。实现下采样的方法有很多种,最常见的有值池化、均值池化和随机池化。***一个下采样层(或卷积层)通常连接一个或多个全连接层,全连接层的输出就是最终的输出。训练过程是通过改进的反向传播实现的,将下采样层考虑在内,并根据所有值更新卷积滤波器的权重。您可以在此处查看几个应用于MNIST数据集的卷积网络示例,以及在此处使用JavaScript的类似网络的可视化实现。实现到目前为止,我们已经学习了常见神经网络的主要元素,但我只写了一些在实现过程中遇到的挑战。简而言之,我的目标是实现一个深度学习库,一个基于满足以下条件的神经网络的框架:一个通用的架构,可以表示各种模型(比如上面提到的神经网络中的所有元素)可以使用各种训练算法(反向传播、对比差异等)。体面的性能为了满足这些要求,我在软件的设计中采用了分层的思想。结构我们从以下基础知识开始:NeuralNetworkImpl是所有神经网络模型实现的基类。每个网络都由一组层组成。每一层都有一个连接的链表,连接是指两层之间的连接,形成整个网络的有向无环图。这种结构对于经典反馈网络、RBM和更复杂的网络(如ImageNet)来说足够灵活。这种结构还允许一个层成为多个网络的元素。比如深度信念网络(DeepBeliefNetwork)中的层也可以用在它的RBM中。此外,通过该架构,DBN的预训练阶段可以显示为堆叠的RBM列表,微调阶段可以显示为前馈网络。这些都非常直观,并且程序实施得很好。数据流下一节介绍网络中的数据流,一个两步过程:定义层与层之间的顺序。例如,为了得到多层感知器的结果,输入数据被馈送到输入层(因此,这也是首先计算的层),然后数据通过不同的方法流向输出层。为了更新反向传播中的权重,输出误差通过广度优先方法从输出层传播回每一层。这部分是通过LayerOrderStrategy实现的,它利用了网络图结构的优点,采用了不同的图遍历方式。其中一些示例包括广度优先策略和特定层的本地化。层的顺序实际上是由层与层之间的连接决定的,所以策略部分总是返回一个有序的连接列表。计算激活值。每个层都有一个关联的ConnectionCalculator,它采用连接列表(来自上一步)和输入值(来自其他层)并计算生成的激活。例如,在一个简单的S形前馈网络中,隐藏层的ConnectionCalculator接受输入层和偏置层的值(分别为输入值和全1的数组)和权重值神经元之间(如果是全连接层,则权重值实际上以矩阵的形式存储在一个FullyConnected结构中,计算加权和,并将结果传递给S函数。一些传递函数(如weightedsum,convolution)在ConnectionCalculator和激活函数(比如多层感知器对应的对数函数和双曲正切函数,RBM对应的二值函数)中实现。大部分可以通过Aparapi在GPU上计算,并且可以使用mini-batches进行训练。通过Aparapi进行GPU计算前面提到过,神经网络近年来复兴的一个重要原因是它的训练方法可以高y并行化,使我们能够通过GPGPU有效地加速训练。在本文中,我选择Aparapi库来支持GPU。Aparapi对连接计算施加了一些重要的限制:只允许原始数据类型的一维数组(变量)。在GPU上运行的程序只能调用AparapiKernel类本身的成员函数。这样一来,很大一部分数据(权重,输入和输出数据)必须存储在Matrix实例中,它是一个一维浮点数组。所有Aparapi连接计算都使用AparapiWeightedSum(应用于全连接层和加权求和函数)、AparapiSubsampling2D(应用于下采样层)或AparapiConv2D(应用于卷积层)。其中一些限制可以通过异构系统架构 中描述的内容来解决。而Aparapi允许相同的代码在CPU和GPU上运行。训练模块实现各种训练算法。该模块依赖于上述两个模块。例如BackPropagationTrainer(所有训练算法都是基于Trainer)在前馈阶段使用前馈层计算,在误差传播和权重更新中使用特殊的广度优先层计算。我最近的工作是在Java8环境下开发,其他一些更新的功能可以在这个分支下获取,这部分工作很快会合并到主干。结论本文的目的是简要介绍深度学习算法领域,从最基本的构建块(感知器)开始,逐步深入到各种当前流行且有效的架构,例如受限玻尔兹曼机。神经网络的思想由来已久,但在今天,如果你不懂深度学习或其他相关知识,就不要涉足机器学习领域。不应夸大其词,但不可否认的是,凭借GPGPU提供的计算能力以及GeoffreyHinton、YoshuaBengio、YannLeCun和AndrewNg等研究学者提出的高效算法,这一领域展现出了巨大的前景。现在是深入研究这些研究领域的最佳时机。
