神经网络和深度学习,第1章:使用神经网络识别手写数字

注意事项


迈克尔·尼尔森 这是迈克尔·尼尔森(Michael Nielsen)的免费在线书籍《神经网络和深度学习》的翻译,该书根据知识共享署名-非商业3.0非端口许可证分发 。 创建它的动机是翻译编程教科书Expressive JavaScript的成功经验。 关于神经网络的书也很受欢迎;英语文章的作者积极地引用它。 除了第一章开头带缩写翻译外,我没有找到她的翻译。

那些要感谢本书作者的人可以在其官方页面上进行此操作,方法是通过PayPal或比特币进行转移。 为了支持Habré上的翻译,有一种形式“支持作者”。


引言


本教程将详细介绍一些概念,例如:

  • 神经网络-在生物学的影响下创建的一种出色的软件范例,可以使计算机根据观察结果进行学习。
  • 深度学习是一组强大的神经网络训练技术。

如今,神经网络(NS)和深度学习(GO)为图像识别,语音和自然语言处理领域的许多问题提供了最佳解决方案。 本教程将教您许多支撑NS和GO的关键概念。

这本书是关于什么的


NS是人类发明的最好的软件范例之一。 使用标准的编程方法,我们可以告诉计算机该怎么做,将大型任务分解为许多小任务,并精确确定计算机可以轻松执行的任务。 相反,就国民议会而言,我们不告诉计算机如何解决问题。 他本人是根据数据的“观察”而学到的,“发明”了自己的解决方案。

自动化的基于数据的学习听起来很有希望。 但是,直到2006年,除少数特殊情况外,我们都不知道如何训练国民议会,使国民议会能够超越更传统的做法。 2006年,所谓的培训技术 深度神经网络(GNS)。 现在,这些技术被称为深度学习(GO)。 它们不断发展,今天,GNS和GO在与计算机视觉,语音识别和自然语言处理有关的许多重要任务中均取得了惊人的成绩。 大规模地,它们被Google,Microsoft和Facebook等公司部署。

本书的目的是帮助您掌握神经网络的关键概念,包括现代的GO技术。 学习完本教程后,您将编写使用NS和GO解决复杂的模式识别问题的代码。 您将具有在解决自己的问题的方法中使用NS和民防的基础。

基于原理的方法


这本书的基本信念之一是,比从大量不同的想法中获取知识要好,对国民议会和公民社会的主要原则要有扎实的理解。 如果您对关键思想有很好的了解,您将很快理解其他新材料。 可以说,用程序员的语言,我们将学习新语言的基本语法,库和数据结构。 您可能只识别整个语言的一小部分-许多语言都有大量的标准库-但是,您可以快速,轻松地理解新的库和数据结构。

因此,这本书绝对不是关于如何为国民议会使用任何特定图书馆的教育材料。 如果您只是想学习如何使用图书馆,请不要阅读本书! 查找所需的库,并使用培训材料和文档。 但请记住:尽管这种方法的优点是可以立即解决问题,但是如果您想了解国民议会内部到底发生了什么,如果您想掌握许多年来可能有意义的想法,那么仅仅学习某种方法是不够的。时尚图书馆。 您需要了解国民议会工作所依据的可靠的长期想法。 技术来了又去了,思想永远长存。

实用方法


我们将以一个特定任务为例研究基本原理:教计算机识别手写数字。 使用传统的编程方法,此任务极难解决。 但是,我们可以用一个简单的NS和几十行代码就可以很好地解决它,而无需任何特殊的库。 此外,我们将逐步完善该计划,并不断纳入有关国民议会和民防的越来越多的关键构想。

这种实用的方法意味着您将需要一些编程经验。 但是您不必成为专业的程序员。 我编写了python代码(2.7版),即使您还没有编写python程序,也应该很清楚。 在学习过程中,我们将为国民议会创建我们自己的图书馆,您可以将其用于实验和进一步的培训。 所有代码都可以在这里下载 。 完成本书或在阅读过程中,您可以为国民议会选择更完整的图书馆之一,以适合这些项目使用。

了解材料的数学要求相当平均。 大多数章节都有数学部分,但通常它们是基本代数和函数图。 有时我会使用更高级的数学,但是我对材料进行了结构设计,以便您可以理解它,即使有些细节无法理解。 第2章中使用了大多数数学运算,这需要一些矩阵分析和线性代数。 对于他们不熟悉的人,我从第二章开始对数学进行介绍。 如果发现困难,请跳过本章,直到进行汇报。 无论如何,不​​要担心。

一本书很少同时面向原理和实践方法的理解。 但是我认为,最好是根据国民议会的基本思想进行研究。 我们将编写有效的代码,而不仅仅是研究抽象理论,您还可以探索和扩展该代码。 通过这种方式,您将了解理论和实践的基础知识,并能够进一步学习。

练习和任务


技术书籍的作者经常警告读者,他只需要完成所有练习并解决所有问题即可。 在向我阅读此类警告时,它们似乎总是有些奇怪。 如果我不做运动并解决问题,会发生不好的事情吗? 不,当然。 我只是通过较少的了解来节省时间。 有时值得。 有时不是。

这本书值得做什么? 我建议您尝试完成大多数练习,但不要尝试解决大多数任务。

大多数练习都需要完成,因为这是对材料的正确理解的基本检查。 如果您不能相对轻松地进行锻炼,那么您一定错过了一些基本知识。 当然,如果您真的陷入某种运动中-丢下它,也许这是一种小误解,或者我的措辞不好。 但是,如果大多数练习使您感到困难,那么您很可能需要重读以前的材料。

任务是另一回事。 它们比练习要困难得多,并且与某些练习相比,您将很难。 这很烦人,但是面对这种失望,耐心当然是真正理解和吸收主题的唯一方法。

因此,我不建议您解决所有问题。 更好的是-选择您自己的项目。 您可能要使用NS对音乐收藏进行分类。 或预测股票的价值。 或其他。 但是,为您找到一个有趣的项目。 然后,您可以忽略书中的任务,或者纯粹将它们用作开发项目的灵感。 您自己项目的问题将比处理任何数量的任务给您更多的帮助。 情感投入是精通成就的关键因素。

当然,尽管您可能没有这样的项目。 这很正常。 解决您内在动力的任务。 使用本书中的材料来帮助您找到有关个人创意项目的想法。

第一章


人类视觉系统是世界奇迹之一。 请考虑以下手写数字序列:



像504192一样,大多数人都可以轻松阅读它们。但是这种简单性在欺骗。 在大脑的每个半球中,一个人都有一个主要的视觉皮层 ,也称为V1,其中包含1.4亿个神经元,它们之间有数百亿个连接。 同时,不仅V1参与人类视觉,而且涉及整个大脑区域序列-V2,V3,V4和V5-它们参与越来越复杂的图像处理。 我们的头脑中装有一台经过数亿年进化而优化的超级计算机,它非常适合理解可见的世界。 识别手写数字并非易事。 只是,我们的人们惊奇地,好得令人惊奇地认识到我们的眼睛向我们展示了什么。 但是几乎所有这些工作都是在不知不觉中进行的。 通常,我们不重视视觉系统解决的难题。

当您尝试编写计算机程序来识别上述数字时,识别视觉模式的困难变得显而易见。 在我们执行过程中看起来容易的事情突然变得极其复杂。 对于我们如何识别形式的简单概念-“九个在顶部有一个循环,右下角是一个竖线”-对于算法表达式而言,并不是那么简单。 通过尝试清楚地阐明这些规则,您很快就会陷入异常,陷阱和特殊场合的泥潭。 这项任务似乎无望。

NS以不同的方式解决问题。 这个想法是采用许多手写的数字作为教学示例,



并开发一个可以从这些示例中学到的系统。 换句话说,国民议会使用示例来自动构建手写数字识别规则。 此外,通过增加训练示例的数量,网络可以了解有关手写数字的更多信息并提高其准确性。 因此,尽管我仅列举了100多个案例研究,但也许我们可以使用成千上万甚至数十亿个案例研究创建一个更好的手写识别系统。

在本章中,我们将编写一个计算机程序,该程序实现NS学习以识别手写数字。 该程序只有74行,不会为国民议会使用特殊的库。 但是,这个简短的程序将能够以超过96%的精度识别手写数字,而无需人工干预。 此外,在以后的章节中,我们将提出可以将准确性提高到99%或更高的想法。 实际上,最好的商业NS可以做得很好,银行可以使用它们来处理支票,而邮政可以用来识别地址。

我们专注于手写识别,因为这是学习NS的绝佳范例。 这样的原型对我们来说是理想的:这是一项艰巨的任务(识别手写数字不是一件容易的事),但又没有那么复杂,以至于需要极其复杂的解决方案或巨大的计算能力。 此外,这是开发更复杂的技术(如GO)的好方法。 因此,在书中我们将不断地回到手写识别的任务。 稍后,我们将讨论如何将这些思想应用于计算机视觉的其他任务,语音识别,自然语言处理和其他领域。

当然,如果本章的目的仅是编写用于识别手写数字的程序,那么本章将短很多! 但是,在此过程中,我们将开发出许多与NS有关的关键思想,包括两种重要类型的人工神经元( 感知器神经元和S型神经元)以及标准的NS学习算法( 随机梯度下降) 。 在本文中,我将集中于解释为什么一切都以这种方式进行,以及集中于您对国民议会的理解。 与我只介绍正在发生的事情的基本原理相比,这需要更长的对话时间,但需要您加深了解。 在其他优点中-在本章的最后,您将了解什么是民防以及其为何如此重要。

感知器


什么是神经网络? 首先,我将讨论一种称为感知器的人工神经元。 感知器是科学家弗兰克·罗森布拉特Frank Rosenblatt)在1950年代和60年代发明的,灵感来自沃伦·麦卡洛克Warren McCallock)沃尔特·皮茨Walter Pitts)的早期工作。 如今,人工神经元的其他模型得到了越来越多的使用-在本书中,关于NS的大多数现代著作主要使用神经元的S形模型。 我们将很快见到她。 但是要了解为什么以这种方式定义了乙状神经元,值得花时间分析感知器。

那么感知器如何工作? 感知器接收几个x 1 ,x 2 ,...的二进制数,并给出一个二进制数:



在此示例中,感知器具有三个输入数字x 1 ,x 2 ,x 3 。 通常,它们可能更多或更少。 Rosenblatt提出了一个简单的规则来计算结果。 他介绍了权重w 1 ,w 2 ,实数,表达了相应输入数字对于结果的重要性。 神经元的输出是0还是1,取决于加权和是小于还是大于某个阈值[阈值]  小号ü Ĵ 瓦特Ĵ X Ĵ 。 像权重一样,阈值为实数,即神经元的参数。 用数学术语来说:

Ò ù p Ù = \开Ç 一个小号ë 小号 0 š ù 中号Ĵ 瓦特Ĵ X Ĵë q ħ ř Ë 小号ħ ö d  1if sumjwjxj>threshold endcases tag1



这就是感知器的全部描述!

这是基本的数学模型。 通过权衡证据,可以将感知器视为决策者。 让我给你一个不太现实但简单的例子。 假设周末快到了,您听说您所在的城市将举办奶酪节。 您喜欢奶酪,并尝试决定是否参加音乐节。 您可以通过权衡以下三个因素来做出决定:

  1. 天气好吗?
  2. 您的伴侣想和您一起去吗?
  3. 节日距离公共交通工具远吗? (你没有车)。

这三个因子可以表示为二进制变量x 1 ,x 2 ,x 3 。 例如,如果天气好,则x 1 = 1,如果天气不好,则x 0 = 1。 x 2 = 1(如果您的伴侣想离开),否则为0。 x 3相同。

现在,假设您非常喜欢奶酪,即使您的伴侣对此不感兴趣,也很难做到,就可以参加这个节日了。 但是也许您只是讨厌恶劣的天气,如果天气不好,您就不会参加音乐节。 您可以使用感知器对此类决策过程进行建模。 一种方法是针对天气选择权重w 1 = 6,对于其他条件,选择w 2 = 2,w 3 = 2。 w 1的值越大,意味着天气对您的影响就比您的伴侣是否会加入您或节庆活动临近的时间长得多。 最后,假设您为感知器选择阈值5,通过这些选项,感知器将实现所需的决策模型,天气好的时候给出1,坏的时候给出0。 合作伙伴的期望和止损的接近程度不会影响输出值。

通过更改权重和阈值,我们可以获得不同的决策模型。 例如,假设我们取阈值为3。然后,感知器决定您需要去参加节日,无论天气好转还是节日在公共汽车站附近,而您的伴侣也同意与您一起去。 换句话说,模型是不同的。 降低阈值意味着您想更多地参加音乐节。

显然,感知器不是完整的人类决策模型! 但是此示例显示了感知器如何权衡不同类型的证据以做出决策。 复杂的感知器网络似乎可以做出非常复杂的决定:



在这个网络中,感知器的第一列-我们称为感知器的第一层-做出三个非常简单的决定,权衡了输入证据。 第二层的感知器呢? 他们每个人都做出决定,权衡第一层决策层的结果。 这样,与第一层的感知器相比,第二层的感知器可以在更复杂和抽象的级别上做出决策。 第三层的感知器甚至可以做出更复杂的决定。这样,感知器的多层网络可以处理复杂的决策。

顺便说一句,当我确定感知器时,我说它只有一个输出值。但是在顶部的网络中,感知器看起来好像有多个输出值。实际上,它们只有一条出路。许多输出箭头只是显示感知器的输出用作其他几个感知器的输入的便捷方式。这比绘制单个分支出口要麻烦得多。

让我们简化感知器的描述。条件j w j x j > t r e s h o l d很尴尬,我们可以就记录的两个更改达成一致以简化记录。首先是记录j w j x j作为标量积,瓦特X = Σ Ĵ 瓦特Ĵ X Ĵ,其中w和x -一个其分量分别是重量和输入数据,矢量。第二种是将阈值转移到不等式的另一部分,并用称为感知器位移[bias]的值代替,b - ħ ř Ë 小号ħ ö d 使用位移而不是阈值,我们可以重写感知器规则:

Ò ù p Ù = { 0 ˚F 瓦特X + b 0   1 ˚F 瓦特X + b > 0  



偏移量可以表示为在感知器的输出上获得1值的难易程度的度量。或者,就生物学而言,位移是衡量感知器激活的难易程度的一种度量。具有非常大的偏差的感知器非常容易给出1。但是,如果有非常大的负偏差,这很难做到。显然,偏差的引入是感知器描述中的一个小变化,但是稍后我们将看到它导致记录的进一步简化。因此,我们将不再使用阈值,而是将始终使用偏移量。

我用权衡决策依据的方法描述了感知器。使用它们的另一种方法是基本逻辑函数的计算,我们通常将其视为主要计算,例如AND,OR和NAND。例如,假设我们有一个带有两个输入的感知器,每个输入的权重为-2,其偏移为3。这里是:



输入00给出输出1,因为(−2)* 0 +(-2 )* 0 + 3 = 3大于零。相同的计算结果是输入01和10给出1。但是输入11给出输出0,这是因为(-2)* 1 +(-2)* 1 + 3 = -1,小于零。因此,我们的感知器实现了NAND​​功能!

此示例说明感知器可用于计算基本逻辑功能。实际上,我们一般可以使用感知器网络来计算任何逻辑函数。事实是,NAND 逻辑门是计算通用的-可以在其基础上进行任何计算。例如,您可以使用“与非”门创建一个将x 1和x 2两位相加的电路。为此,请计算按位和X 1X 2进位标志,等于1当x1和X2等于1 -那就是,进位标志仅仅是按比特的乘法的X结果1X2为了得到感知的等效网络,我们全部更换NAND门是具有两个输入的感知器,每个输入的权重为-2,偏移为3。这是结果网络。请注意,我移动了与右下阀相对应的感知器,只是为了更方便地绘制箭头:







该感知器网络的一个值得注意的方面是,最左边的一个的输出两次用作底部的输入。在定义感知器模型时,我没有在同一地方提及这种双重退出方案的可采性。实际上,这并不重要。如果我们不想这样做,我们可以简单地将权重为-2的两行合并为权重为-4的一行。 (如果这对您似乎并不明显,请停止并向您自己证明)。进行此更改之后,网络如下所示,所有未分配的权重等于-2,所有偏移量等于3,并且标记了一个权重-4:



这样的感知器记录具有输出但没有输入:



只是一个缩写。这并不意味着他没有投入。为了理解这一点,假设我们有一个没有输入的感知器。则加权总和∑ j w j x j始终为零,因此对于b> 0,感知器将给出1,对于b≤0,感知器将给出0。也就是说,感知器将给出一个固定值,而不是我们所需的值(在示例中为x 1以上)。最好不要将输入感知器视为感知器,而应将其视为简单定义的特殊单位,以便产生所需的值x 1,x 2,...

加法器示例演示了如何使用感知器网络来模拟包含许多与非门的电路。并且由于这些门对于计算是通用的,因此感知器对于计算是通用的。

感知器的计算通用性既令人鼓舞,也令人失望。令人鼓舞的是,确保感知器网络可以与任何其他计算设备一样强大。令人失望,给人的印象是感知器只是一种新型的NAND逻辑门。马马虎虎的发现!

但是,情况实际上更好。事实证明,我们可以开发出训练算法,该算法可以自动调整来自人工神经元的网络的权重和位移。这种调整是对外部刺激的响应,无需程序员的直接干预。这些学习算法使我们能够以与普通逻辑门完全不同的方式使用人工神经元。我们的神经网络无需从NAND门及其他门显式注册电路,而可以简单地学习如何解决问题,有时是解决问题的方法,而直接设计常规电路将非常困难。

乙状神经元


学习算法很棒。 但是,如何开发这样的神经网络算法? 假设我们有一个感知器网络,我们想用它来训练我们解决问题。 假设到网络的输入可能是手写数字扫描图像的像素。 我们希望网络知道正确分类数字所需的权重和偏移量。 为了了解这种培训的工作原理,让我们想象一下我们正在稍微改变网络中的一定权重(或偏见)。 我们希望这个小的变化导致网络输出的一个小的变化。 正如我们将很快看到的,此属性使学习成为可能。 从原理上讲,我们需要以下内容(显然,这样的网络太简单了,无法识别手写!):



如果权重的微小变化(或偏差)导致输出结果的微小变化,我们可以改变权重和偏差,以使我们的网络表现得更接近我们想要的。 例如,假设网络将图像错误地分配为“ 8”,尽管它应该已经分配为“ 9”。 我们可以弄清楚如何对重量和位移进行小的更改,以便网络更接近地将图像分类为“ 9”。 然后,我们将重复此操作,改变权重并一次又一次地移动以获得最佳效果。 网络将学习。

问题是,如果网络中存在感知器,则不会发生这种情况。 任何感知器的权重或位移的微小变化有时都可能导致其输出向相反的方向(例如,从0到1)变化。这种翻转会以非常复杂的方式改变其余网络的行为。 即使现在我们的“ 9”被正确识别,网络与所有其他图像的行为也可能以难以控制的方式完全改变了。 因此,很难想象我们如何逐步调整权重和偏移,以使网络逐渐接近所需的行为。 也许有一些解决此问题的聪明方法。 但是,没有简单的解决方案来学习感知器网络。

可以通过引入一种称为S形神经元的新型人工神经元来解决此问题。 它们与感知器相似,但经过修改后,权重和偏移量的细微变化只会导致输出的细微变化。 这是一个基本的事实,可以使乙状神经元网络学习。

让我描述一个乙状神经元。 我们将以与感知器相同的方式绘制它们:



它具有相同的输入x 1 ,x 2 ,...,但不是等于0或1,这些输入可以具有0到1范围内的任何值。例如,值0.638将是有效的输入乙状结肠神经元(CH)。 就像感知器一样,SN对每个输入w 1 ,w 2 ,...和总偏差b具有权重。 但是它的输出值将不是0或1。它将是σ(w⋅x+ b),其中σ是S型。

顺便说一下,有时将σ称为对数函数 ,并将这类神经元称为对数神经元。 记住该术语很有用,因为许多使用神经网络的人都在使用这些术语。 但是,我们将坚持使用S形术语。

该函数定义如下:

 sigmaz equiv frac11+ez tag3



在我们的情况下,具有权重w 1 ,w 2 ,...和偏移b的输入数据为x 1 ,x 2 ,...的乙状神经元的输出值将被视为:

 frac11+exp sumjwjxjb\标4



乍看之下,CH似乎完全不同于神经元。 如果您不熟悉S形的代数外观,可能会感到困惑和模糊。 实际上,感知器和SN之间有许多相似之处,而S形的代数形式则更多地是技术细节,而不是严重的理解障碍。

为了理解与感知器模型的相似性,假设z≡w⋅x + b是一个大正数。 则e-z≈0,因此σ(z)≈1。换句话说,当z = w⋅x + b大且为正数时,与感知器一样,SN产量约为1。 假设z = w⋅x + b大,带有负号。 然后e-z→∞,并且σ(z)≈0。因此,对于带有负号的大z,SN的行为也接近感知器。 并且只有当w⋅x + b具有平均大小时,才会观察到与感知器模型的严重偏差。

σ的代数形式呢? 我们如何理解他? 实际上,σ的确切形式并不那么重要-图上函数的形状很重要。 这是:



这是step函数的平滑版本:



如果σ是阶梯式的,则SN将是一个感知器,因为根据符号w⋅x + b(实际上,在z = 0时,感知器将给出0,而阶跃函数为1),其SN输出将为0或1。 ,因此此时必须更改功能)。

使用实函数σ,我们得到一个平滑的感知器。 最主要的是函数的平滑度,而不是其确切形式。 平滑度意味着权重Δwj的较小变化和δb偏移将使输出的Δoutput产生较小变化。 代数告诉我们Δoutput近似如下:

 Delta\大 sumj frac\部\部wj Deltawj+ frac\部\部b Deltab\标5



在所有权重w j上的总和为时,∂output/ / wj和∂output/∂b分别表示输出相对于w j和b的偏导数。 如果您对私人衍生品公司感到不安全,请不要惊慌! 尽管公式看起来很复杂,但是使用所有这些偏导数,它实际上都说得很简单(有用):Δoutput是一个线性函数,取决于Δwj和Δb的权重和偏差。 它的线性特性使选择权重和偏移的小变化变得容易,以实现任何所需的小输出偏置。 因此,尽管SN在质性行为上与感知器相似,但是它们使人们更容易理解如何通过改变权重和位移来改变输出。

如果一般形式σ对我们很重要,而不是它的确切形式重要,那么为什么我们要使用这样的公式(3)? 实际上,稍后我们有时会考虑输出为f(w⋅x + b)的神经元,其中f()是其他一些激活函数。 当函数改变时,主要改变的是方程(5)中偏导数的值。 事实证明,当我们随后计算这些偏导数时,使用σ可以大大简化代数,因为微分时指数具有非常好的特性。 无论如何,σ通常用于神经网络,在本书中,我们最常使用这种激活函数。

如何解释CH工作的结果? 显然,感知器和CH之间的主要区别在于CH不会只给出0或1。它们的输出可以是0到1之间的任何实数,因此像0.173或0.689这样的值是有效的。 例如,如果您希望输出值指示在NS输入处接收到的图像像素的平均亮度,则此功能很有用。 但有时可能会带来不便。 假设我们希望网络输出说“输入了图像9”或“输入的图像不是9”。 显然,如果输出值为0或1(如感知器)会更容易。 但是实际上,我们可以同意,任何输出值至少为0.5时在输入端都意味着“ 9”,而任何值小于0.5时都将意味着其为“非9”。 我将始终明确指出此类协议的存在。

练习题

  • CH模拟感知器,第1部分

假设我们从感知器网络中获取所有权重和偏移,然后将它们乘以一个正常数c> 0。 证明网络行为没有改变。

  • CH模拟感知器,第2部分

假设我们与上一个问题具有相同的情况-感知器网络。 还假定选择了网络的输入数据。 我们不需要特定的值,主要是它是固定的。 假设权重和位移使得w⋅x+ b≠0,其中x是网络中任何感知器的输入值。 现在我们用SN替换网络中的所有感知器,并将权重和位移乘以正常数c> 0。 表明在极限c→∞中,来自SN的网络的行为将与感知器网络完全相同。 如果对于一个感知器w⋅x+ b = 0,将如何违反该陈述?

神经网络架构


在下一节中,我将介绍能够对手写数字进行良好分类的神经网络。 在此之前,解释一下允许我们指向网络不同部分的术语很有用。 假设我们有以下网络:



正如我提到的,网络中最左边的层称为输入层,其神经元称为输入神经元。 最右边的层或输出层包含输出神经元,或者在我们的情况下包含一个输出神经元。 中间层称为隐藏层,因为它的神经元既不是输入也不是输出。 “隐藏”一词听起来可能有点神秘-当我第一次听到它时,我认为它应该具有深远的哲学或数学重要性-但是,它仅表示“既不进入也不退出”。 上面的网络只有一个隐藏层,但是某些网络有几个隐藏层。 例如,在下面的四层网络中,有两个隐藏层:



这可能会造成混淆,但出于历史原因,尽管此类多层网络由乙状神经元而非感知器组成,但有时也称为多层感知器(MLP)。 我不会使用这样的术语,因为它会造成混淆,但是我必须警告它的存在。

设计输入和输出层有时是一个简单的任务。 例如,假设我们正在尝试确定手写数字是否表示“ 9”。 自然网络电路将对输入神经元中图像像素的亮度进行编码。 如果图像是黑白的,尺寸为64x64像素,那么我们将有64x64 = 4096个输入神经元,亮度范围为0到1。输出层将仅包含一个神经元,其值小于0.5表示“在输入不是9“,但是更多的值表示”输入为9“。

尽管设计输入和输出层通常是一个简单的任务,但是设计隐藏层却是一项困难的工作。 特别是,无法通过一些简单的经验法则来描述开发隐藏层的过程。 国民议会的研究人员开发了许多启发式规则来设计隐藏层,以帮助获得所需的神经网络行为。 例如,可以使用这种试探法来了解如何在隐藏层的数量和可用于训练网络的时间之间达成折衷。 稍后我们将遇到其中一些规则。

到目前为止,我们一直在讨论NS,其中将一层的输出用作下一层的输入。 这种网络称为直接分布神经网络。 这意味着网络中没有环路-信息始终向前传播,永不反馈。 如果有循环,则会遇到输入S形依赖于输出的情况。 很难理解,我们不允许这样的循环。

但是,还有其他人造NS模型可以使用反馈回路。 这些模型称为递归神经网络 (RNS)。 这些网络的思想是在有限的时间内激活它们的神经元。 这种激活可以刺激其他中子,这些中子可以在有限的时间内再被激活。 这导致随后神经元的激活,随着时间的流逝,我们得到了一系列激活神经元。 这样的模型中的循环不会出现问题,因为神经元的输出会在以后而不是立即影响其输入。

RNS不如直接分发的NS那样有影响力,特别是因为到目前为止RNS的训练算法潜力较小。 但是,RNS仍然非常有趣。 按照工作的精神,它们比直接分布的NS更接近大脑。 RNS可能能够解决直接分发NS所能解决的重大难题。 但是,为了限制我们的研究范围,我们将专注于更广泛使用的直接分布NS。

简单的油墨分类网络


定义了神经网络后,我们将回到手写识别。 识别手写数字的任务可以分为两个子任务。 首先,我们想找到一种将包含多个数字的图像拆分为一系列单独图像的方法,每个图像都包含一个数字。 例如,我们想分割图像



分为六个



我们人类可以轻松解决此分割问题,但是计算机程序很难正确分割图像。 分割后,程序需要对每个数字进行分类。 因此,例如,我们希望程序识别出第一位数字



是5

我们将集中精力创建一个程序来解决第二个问题,即个人数字的分类。 事实证明,只要我们找到了一种对单个数字进行分类的好方法,就不会很难解决分割问题。 有许多方法可以解决分割问题。 其中之一是使用单个数字的分类器尝试多种不同的图像分割方法,并对每次尝试进行评估。 如果单个数字的分类器对所有段的分类都具有信心,则高度重视试验分割,如果单个或多个段中存在问题,则较低。 想法是,如果分类器在某处存在问题,则最有可能意味着分割不正确。 这个想法和其他选项可以用来很好地解决分割问题。 因此,我们不用担心分割,而将重点放在开发能够解决更有趣和更复杂任务(即识别单个手写数字)的NS。

为了识别单个数字,我们将从三层使用NS:



输入网络层包含对输入像素的各种值进行编码的神经元。 就像在下一节中将要指出的那样,我们的训练数据将由许多扫描后的手写数字图像组成,这些图像的大小为28x28像素,因此输入层包含28x28 = 784个神经元。 为简单起见,我没有在图中指示大多数784个神经元。 传入像素为黑色和白色,值0.0表示白色,值1.0表示黑色,中间值表示灰度越来越深。

网络的第二层是隐藏的。 我们表示该层n中神经元的数量,并且我们将使用n的不同值进行实验。 上面的示例显示了一个仅包含n = 15个神经元的小隐藏层。

网络的输出层中有10个神经元。 如果第一个神经元被激活,即其输出值为≈1,则表明网络认为输入为0。如果第二个神经元被激活,则网络认为输入为1,依此类推。 严格来说,我们将输出神经元的编号从0编号为9,然后看看它们中哪个具有最大的激活值。 例如,如果这是6号神经元,那么我们的网络将认为输入是6号。依此类推。

您可能想知道为什么我们需要使用十个神经元。 毕竟,我们想知道从0到9的哪个数字对应于输入图像。 自然会只使用4个输出神经元,每个神经元取一个二进制值,具体取决于它的输出值是接近于0还是1。四个神经元就足够了,因为2 4 = 16,所以有10个以上的可能值。 为什么我们的网络应该使用10个神经元? 这无效吗? 这是基于经验的基础; 我们可以尝试两种网络变体,事实证明,对于此任务,具有10个输出神经元的网络比具有4个神经网络的网络受过更好的训练以识别数字。 但是,问题仍然在于,为什么10个输出神经元更好。 是否有任何启发式方法可以提前告诉我们,应使用10个输出神经元而不是4个?

要了解原因,考虑一下神经网络的作用很有用。 首先,考虑具有10个输出神经元的选项。 我们专注于第一个输出神经元,它试图确定传入的图像是否为零。 他通过权衡从隐藏层获得的证据来做到这一点。 隐藏的神经元做什么? 假设隐藏层中的第一个神经元确定图片中是否存在这样的内容:



他可以通过为与该图像匹配的像素分配较大的权重,为其余图像分配较小的权重来实现。 以同样的方式,假设隐藏层中的第二,第三和第四神经元正在寻找图像中是否存在相似的片段:



就像您可能已经猜到的那样,这四个片段一起为图像提供了0,这是我们之前看到的:



因此,如果激活了四个隐藏的神经元,我们可以得出结论,数字为0。当然,这不是唯一的证据表明那里显示了0-我们可以通过许多其他方式(通过稍微移动这些图像或使其稍微变形)获得0。但是,可以肯定地说,至少在这种情况下,我们可以得出结论,输入为0。

如果我们假设网络是这样工作的,那么我们可以给出一个合理的解释,说明为什么最好使用10个输出神经元而不是4个。如果我们有4个输出神经元,那么第一个神经元将尝试确定输入数字的最高有效位。而且,没有一种简单的方法可以将最高有效位与上述简单形式相关联。很难想象有任何历史原因会导致数字形式的某些部分以某种方式与输出的最高有效位相关。

但是,以上所有内容仅受启发式方法支持。正如我所说,没有什么可以支持三层网络起作用的事实,而隐藏的神经元应该找到形式的简单组成部分。也许棘手的学习算法会找到一些权重,使我们只能使用4个输出神经元。但是,作为一种启发式方法,我的方法效果很好,并且可以为您节省开发良好NS体系结构的大量时间。

练习题


  • , . , . . , 3 , ( ) 0,99, 0,01.




因此,我们有NA方案-如何学习识别数字?我们需要的第一件事是训练数据,即所谓的训练数据集。我们将使用MNIST套件,其中包含成千上万个手写数字的扫描图像及其正确分类。由于MNIST是美国国家标准技术研究院NIST收集的两个数据集的修改子集,因此获得了MNIST这个名称。这是MNIST的一些图像:



这些是本章开始时作为识别任务给出的数字。当然,在检查NS时,我们会要求她识别训练集中已经存在的错误图像!

MNIST数据由两部分组成。第一个包含用于训练的60,000张图像。这些是250人的扫描手稿,其中一半是美国人口普查局的雇员,另一半是高中学生。图像是黑白的,尺寸为28x28像素。 MNIST数据集的第二部分是10,000张图像,用于测试网络。这也是28x28像素的黑白图像。我们将使用此数据来评估网络学会识别数字的程度。为了提高评估的质量,这些数字取自另外250名未参加培训记录的人员(尽管他们也是局职员和高中生)。这有助于我们确保我们的系统可以识别出培训期间未遇到的人的笔迹。

训练输入将用x表示。将每个输入图像x视为具有28x28 = 784个测量值的向量将很方便。向量内的每个值表示图像中一个像素的亮度。我们将输出值表示为y = y(x),其中y是十维向量。例如,如果某个训练图像x包含6,则y(x)=(0,0,0,0,0,0,1,0,0,0)T将是我们需要的向量。 T是将行向量转换为列向量的转置操作。

我们想找到一种算法,允许我们寻找这样的权重和偏移量,以使网络输出对于所有训练输入x都接近y(x)。为了量化该目标的近似值,我们定义了成本函数(有时称为损失函数); 在本书中,我们将使用成本函数,但请记住另一个名称):

C w b = 12 nx| | ŸX-一个| | 2



这里w表示一组网络权重,b是一组偏移量,n是训练输入数据的数量,a是当x是输入数据时输出数据的矢量,并且总和通过所有训练输入数据x。当然,输出取决于x,w和b,但为简单起见,我没有指定这种依赖性。表示法|| v ||表示向量v的长度。我们将C称为二次成本函数;有时也称为标准错误或MSE。如果仔细观察C,您会发现它不是负数,因为总和的所有成员都是非负数。另外,恰恰当y(x)近似等于所有训练输入数据x的输出矢量a时,C(w,b)的开销变小,即C(w,b)≈0。因此,如果我们设法找到权重和偏移量,使得C(w,b)≈0,那么我们的算法就可以很好地工作。反之亦然,当C(w,b,b)大-这意味着对于大量输入,y(x)与输出不匹配。事实证明,训练算法的目标是将C(w,b)的成本最小化,将其作为权重和偏移的函数。换句话说,我们需要找到一组权重和偏移量,以最小化成本值。我们将使用称为梯度下降的算法进行此操作。

为什么我们需要一个平方值?我们不是主要对网络正确识别的图像数量感兴趣吗?是否可以简单地直接最大化此数字,而不最小化二次值的中间值?问题在于,正确识别的图像数量不是网络权重和偏移的平滑函数。在大多数情况下,权重和偏移的微小变化不会改变正确识别的图像的数量。因此,很难理解如何改变权重和偏见以提高效率。如果我们使用平滑的成本函数,我们将很容易理解如何对权重和偏移量进行小的更改以提高成本。因此,我们将首先关注二次值,然后研究分类的准确性。

即使考虑到我们要使用平滑成本函数,您可能仍然会对为什么我们为方程式(6)选择二次函数感兴趣?不能随意选择吗?也许,如果我们选择其他函数,我们将获得一套完全不同的最小化权重和偏移量的方法?一个合理的问题,稍后我们将再次检查成本函数并对其进行一些更正。但是,二次成本函数对于理解学习NS的基本知识很有用,因此现在我们将坚持下去。

总结:我们训练NS的目标是找到最小化二次成本函数C(w,b)的权重和偏移量。这项任务是适当的,但到目前为止,它具有许多分散注意力的结构-将w和b解释为权重和偏移量,隐藏在后台的函数σ,网络体系结构的选择,MNIST等。事实证明,我们可以了解很多,而忽略了大多数这种结构,而只专注于最小化方面。因此,现在,我们将忘记成本函数的特殊形式,与国民议会的沟通等等。相反,我们将想象我们只有一个具有许多变量的函数,并且希望将其最小化。我们将开发一种称为梯度下降的技术,该技术可用于解决此类问题。然后我们回到某个功能,我们希望将其减少到国民议会。

好吧,假设我们正在尝试最小化某些函数C(v)。它可以是具有许多变量v = v 1,v 2,...的实数值的任何函数。请注意,我用v代替了符号w和b以表明它可以是任何函数-我们不再痴迷于HC。可以想象函数C只有两个变量-v 1和v 2



我们想找到C达到全局最小值的位置。当然,使用上面绘制的功能,我们可以研究图形并找到最小值。从这个意义上讲,我可能已经给您提供了一个过于简单的功能!在一般情况下,C可以是许多变量的复杂函数,通常不可能只看图并找到最小值。

解决问题的一种方法是使用代数来分析求最小值。我们可以计算导数,并尝试使用它们来找到极值。如果幸运的话,当C是一个或两个变量的函数时,这将起作用。但是,由于变量众多,这变成了一场噩梦。对于NS,我们通常需要更多的变量-对于最大的NS,成本函数以复杂的方式取决于数十亿的重量和位移。使用代数最小化这些功能将失败!

(已经说过,将C视为两个变量的函数会更方便,我在两段中说了两次:“是的,但是如果它是大量变量的函数呢?”我很抱歉。相信我们,将C表示为函数确实很有用。两个变量,只是有时这张图片会散开,这就是为什么需要前两段的原因,为了进行数学推理,经常需要弄乱几个直观的表示,同时学习何时可以使用表示以及何时不使用ZYA)。

好的,这意味着代数将不起作用。幸运的是,有一个很好的类比提供了功能完善的算法。我们想象我们的功能就像山谷一样。按照最新的时间表,这将不会很困难。我们想象一个球沿着山谷的斜坡滚动。我们的经验告诉我们,球最终将滑到最底端。也许我们可以用这个想法找到一个函数的最小值?我们随机选择一个假想球的起点,然后模拟球的运动,就好像它滚动到山谷的底部一样。我们可以简单地通过计算C的导数(可能还有二阶导数)来使用此模拟-它们将告诉我们有关山谷局部形状的所有信息,从而告诉我们球如何滚动。

根据您写的内容,您可能会认为我们将写下牛顿的球运动方程式,考虑摩擦和重力的影响,等等。实际上,我们不会像对球这样的类比-我们正在开发一种最小化C的算法,而不是物理定律的精确模拟!这种类比应该激发我们的想象力,而不是限制我们的思维。因此,除了深入研究物理的复杂细节之外,我们还提出一个问题:如果我们被任命为上帝一天,我们将创建自己的物理定律,告诉球如何滚动选择哪个定律或运动定律,从而使球始终滚动谷底?

为了澄清这个问题,考虑如果我们转移球很短的距离ΔV会发生什么1到v 1和短距离ΔV 2中V的方向2代数告诉我们C的变化如下:

三角洲的C ∂&的Cv 1 Δv1+Çv 2增量v2



我们选择这种ΔVnaodem方法1和ΔV 2至ΔC小于零; 也就是说,我们将选择它们以使球滚下来。为了理解如何做到这一点,它是定义矢量平均的变化,即,ΔV有用≡(ΔV 1,平均2Ť,其中T -转置操作,接通行向量进列向量。我们还定义梯度向量C作为偏导数(∂S/∂v 1,∂S/∂v 2Ť我们用∇表示梯度向量:

Ç Çv 1与cv 2Ť



很快,我们将通过Δv和梯度∇C重写ΔC的变化。同时,我想澄清一些问题,因为这些问题经常使人们陷入困境。初次与∇C会面时,人们有时不了解他们应如何看待symbol符号。具体是什么意思?实际上,您可以放心地将∇C视为单个数学对象-先前定义的向量-只需使用两个字符即可编写。从这个角度来看,∇就像挥舞着旗子,通知“∇C是梯度矢量”。可以将points视为独立的数学实体(例如,作为微分算子),但有很多更高级的观点,但是我们不需要它们。

使用这样的定义,表达式(7)可以重写为:

三角洲的C 的C 三角洲v



该方程式有助于解释为什么∇C被称为梯度向量:它将v的变化与C的变化联系在一起,正如从称为“ gradient”的实体所期望的那样。[eng。梯度-偏差/约 但是,更有趣的是,该方程式使我们能够看到如何选择Δv,以使ΔC为负。假设我们选择

德尔塔v = - Ñ 与c



其中η是一个小的正参数(学习速度)。然后,等式(9)告诉我们,ΔC≈-η∇C⋅∇C=-η||∇C|| 2由于||∇C || 2 ≥0,这确保了ΔC≤0,即,所有的时间将被如果我们要改变V,减少了如(10)规定的(当然,在等式(9)的近似范围内)。这正是我们所需要的!因此,我们采用等式(10)来确定我们的梯度下降算法中球的“运动定律”。也就是说,我们将使用公式(10)计算Δv值,然后将球移动到该值:

v v ' = v - η ▿ C ^



然后,我们再次将此规则应用于下一步。继续重复,我们将降低C值,直到希望达到全局最小值。

总结起来,梯度下降是通过依次计算梯度∇C以及随后在相反方向上的位移来进行的,从而导致沿山谷的坡度“下降”。可以如下所示:



请注意,使用此规则,梯度下降不会重现实际的物理运动。在现实生活中,球有一种冲动,可以使它在斜坡上滚动,甚至滚动一段时间。只有在摩擦力作用后,球才能保证从山谷中滚下来。我们的选择规则Δv只是说“下去”。找到最小值的一个很好的规则!

为了使梯度下降正常工作,我们需要选择足够小的学习速度η值,以便方程式(9)成为一个很好的近似值。否则,可能会发现ΔC> 0-不好!同时,η不必太小,因为这样Δv的变化将很小,并且算法将运行得太慢。实际上,η会发生变化,因此方程式(9)可以给出良好的近似值,并且该算法的运行速度不会太慢。稍后我们将看到它是如何工作的。

我解释了函数C仅取决于两个变量时的梯度下降。但是,如果C是许多变量的函数,则一切工作原理都相同。假设她有m个变量v 1,...,v m则改变ΔC,由于轻微的变化ΔV=(ΔV 1,...,平均Ť将等于

三角洲的C 的C 三角洲v



梯度∇C是矢量

Ç Çv 1...的Cv Ť



与两个变量一样,我们可以选择

德尔塔v = - Ñ 与c



并确保我们的ΔC近似表达式(12)为负。即使C是许多变量的函数,这也为我们提供了一种将梯度最小化的方法,一遍又一遍地应用更新规则。

v v ' = v - η ▿ C ^



可以将此更新规则视为定义梯度下降算法。它为我们提供了一种反复改变v的位置以搜索函数C的最小值的方法。此规则并不总是有效-可能会出错,从而阻止了梯度下降找到C的全局最小值-至此,我们将返回下一章。但是在实践中,梯度下降通常效果很好,我们将在国民议会中看到这是最小化成本函数并因此训练网络的有效方法。

从某种意义上说,梯度下降可以认为是最佳的最小搜索策略。假设我们试图将Δv移动到最小C的位置。这等效于最小化ΔC≈∇C⋅Δv。我们将限制步长,以便||Δv|| =ε表示一些小的常数ε>0。换句话说,我们想移动一个固定大小的小距离,并尝试找到尽可能减小C的运动方向。可以证明,使∇C⋅Δv最小的Δv的选择为Δv=-η∇C,其中η=ε/ ||∇C||由限制条件||Δv||决定。=ε。因此,梯度下降可以被认为是在最大降低C.方向上采取小步骤的一种方法。

练习题


  • . : , , , .
  • , , . , ? ?

人们研究了许多关于梯度下降的选择,包括那些可以更精确地再现真实球的选择。这样的选择有其优点,但也有一个很大的缺点:需要计算C的二阶导数,这会消耗很多资源。要理解这一点,让我们假设,我们需要计算所有二阶偏导数∂ 2的C /∂v Ĵ ∂v k个。如果这些变量v Ĵ百万,那么我们需要在第二偏导数(实际上大约一兆(一百万平方)计算,半兆因为∂ 2的C /∂v Ĵ ∂v 第k =∂ 2的C /∂v 第k ∂v Ĵ。但您抓住了本质)。这将需要大量的计算资源。有避免这种情况的技巧,寻找梯度下降的替代方法是积极研究的领域。但是,在本书中,我们将使用梯度下降及其变体作为学习NS的主要方法。

我们如何将梯度下降应用于NA学习?我们需要使用它来搜索权重w k和偏移b l,以最小化成本公式(6)。让我们通过用权重和偏移量替换v j变量来重写梯度下降更新规则。换句话说,现在我们的“位置”具有分量w k和b l,并且梯度矢量∇C具有对应的分量∂C/∂wķ和∂C/∂b 用新组件编写更新规则后,我们得到:

瓦特ķ瓦特' ķ = 瓦特ķ - η ∂&Ç的瓦特第k



b b ' = b - η ∂&Çb



通过重新应用此更新规则,我们可以“下坡路”,如果碰巧的话,可以找到最小成本函数。 换句话说,这条规则可以用来训练国民议会。

应用梯度下降规则有几个障碍。 在接下来的章节中,我们将更详细地研究它们。 但是目前,我只想提一个问题。 为了理解它,让我们回到等式(6)中的二次值。 注意,这个成本函数看起来像C = 1 / n ∑ x C x ,也就是说,它是单个训练示例的平均成本C x≡(|| y(x)-a || 2 )/ 2。 在实践中,要计算梯度∇C,我们需要为每个训练输入x分别计算梯度∇Cx,然后将其平均,即∇C= 1 / n ∑ x∇Cx。 不幸的是,当输入量很大时,将花费很长时间,并且这种训练将很慢。

为了加快学习速度,您可以使用随机梯度下降法。 这个想法是通过为训练输入的一个小的随机样本计算∇Cx来近似计算calculateC梯度。 通过计算它们的平均值,我们可以快速获得真实梯度∇C的良好估计,这有助于加速梯度下降,因此可以进行训练。

精确地说,随机梯度下降是通过对少量m个训练输入数据进行随机采样来实现的。 我们将这个随机数据称为X 1 ,X 2 ,..,X m ,并将其称为迷你数据包。 如果样本大小m足够大,则∇CX j的平均值将足够接近所有∇Cx的平均值,即

 frac summj=1 nablaCXjm about frac sumx nablaCxn= nablaC\标18



第二个数据遍及整个训练数据集。 通过互换零件,我们得到

 nablaC\大 frac1m summj=1 nablaCXj tag19



这证实了我们可以通过计算随机选择的迷你包装的梯度来估算总体梯度。

要将其直接与NS的训练相关联,让我们假设w k和b l表示我们NS的权重和位移。 然后,随机梯度下降选择输入数据的随机小数据包,并从中学习

wk rightarrowwk=wk frac etam sumj frac\部CXj\部wk\标20



bl rightarrowbl=bl frac etam sumj frac\部CXj\部bl\标21



当前迷你包装中所有训练示例X j的总和在哪里。 然后,我们选择另一个随机迷你包装并对其进行研究。 依此类推,直到我们用尽所有训练数据为止,这被称为训练时代的终结。 此刻,我们正在开始一个新的学习时代。

顺便说一句,值得注意的是,在小型包装中,有关成本函数的缩放比例以及权重和补偿的更新的协议是不同的。 在等式(6)中,我们将成本函数缩放为1 / n倍。 有时,人们通过累加各个培训示例的费用而不是计算平均值来忽略1 / n。 如果事先不知道训练示例的总数,这将很有用。 例如,当其他数据实时出现时,可能会发生这种情况。 以同样的方式,小包装更新规则(20)和(21)有时会忽略总和前面的1 / m成员。 从概念上讲,这什么都不会影响,因为它等效于学习速度η的变化。 但是,在比较各种作品时值得注意。

随机梯度下降可被视为政治投票:以小包装形式取样比将梯度下降应用于完整样本要容易得多-就像在现场出口进行的民意调查比正式选举更容易进行一样。 例如,如果我们的训练集的大小为n = 60,000(例如MNIST),并且我们制作了一个大小为m = 10的小型包装的样本,那么我们可以将梯度估计速度提高6000倍! 当然,估计值并不理想-会有统计上的波动-但这并不一定是理想的:我们只需要沿着减小C的方向移动,这意味着我们不需要精确地计算梯度。 在实践中,随机梯度下降法是国民议会常用且强大的教学技术,也是我们将在本书中开发的大多数教学技术的基础。

练习题


  • 梯度下降的极端版本​​使用的最小数据包大小为1。也就是说,对于输入x,我们根据以下规则更新权重和偏移量:w k →w'k = w k -η∂Cx /∂wk和b l →b ′ L = b l -η∂Cx /∂bl。 然后,我们选择训练输入的另一个示例,并再次更新权重和偏移。 依此类推。 此过程称为在线学习或增量学习。 在在线学习中,NS会一次学习输入数据的一个训练副本(例如人)。 与最小分组大小为20的随机梯度下降相比,在线学习的优缺点有哪些?

让我在本节结束时讨论一个主题,该主题有时会使最初遇到梯度下降的人们感到困扰。 在NS中,C的值是许多变量(所有权重和偏移量)的函数,并且在某种意义上决定了非常多维空间中的曲面。 人们开始思考:“我将不得不可视化所有这些其他方面。” 他们开始担心:“我无法在四个维度上导航,更不用说五个(或五百万)了。” 它们是否具有“真实”超数学所具有的特殊品质? 当然不是 即使是专业的数学家,也无法很好地将三维空间可视化-甚至根本无法可视化。 他们会骗人,开发其他方式来表示正在发生的事情。 我们只是这样做:我们使用ΔC的代数(而不是视觉)表示来理解如何移动以使C减小。 在很多方面做得很好的人在他们的脑海中都有大量类似技术的图书馆。 我们的代数技巧只是一个例子。 当可视化三个维度时,这些技术可能并不像我们习惯的那么简单,但是当您创建类似技术的库时,您就开始在更高维度上进行思考。 我不会详细介绍,但是如果您有兴趣,您可能希望习惯于更高维度思考专业数学家对其中一些技术进行讨论 。 尽管讨论的某些技术非常复杂,但是大多数最佳答案都是直观的,每个人都可以使用。

实施数字分类网络


好的,现在让我们编写一个程序,该程序使用随机梯度下降和MNIST的训练数据来学习识别手写数字。 我们将使用仅包含74行的python 2.7中的一个简短程序来完成此操作! 我们需要的第一件事是下载MNIST数据。 如果使用git,则可以通过克隆本书的存储库来获得它们:

git clone https://github.com/mnielsen/neural-networks-and-deep-learning.git

如果不是,请从链接下载代码。

顺便说一句,当我较早提到MNIST数据时,我说过将它们分为60,000个训练图像和10,000个测试图像。 这是MNIST的官方描述。 我们将以不同的方式破坏数据。 我们将使验证图像保持不变,但会将训练集分为两部分:50,000张图像(将用于训练国民议会)和10,000张图像以进行进一步确认。 虽然我们不会使用它们,但是以后当我们了解NS的一些超级参数(学习速度等)的配置时,它们将对我们有用,我们的算法不会直接选择这些参数。 尽管确证数据不是原始MNIST规范的一部分,但许多人都以这种方式使用MNIST,并且在HC领域中,确证数据的使用是常见的。 现在,说到“ MNIST培训数据”,我的意思是我们的50,000 karitnoks,而不是原始的60,000。

除了MNIST数据外,我们还需要一个名为Numpy的python库,用于快速线性代数计算。 如果您没有它,可以从链接中获取它。

在给您整个程序之前,让我解释一下NS代码的主要功能。 网络类占据了中心位置,我们用它来代表国民议会。 这是Network对象的初始化代码:

 class Network(object): def __init__(self, sizes): self.num_layers = len(sizes) self.sizes = sizes self.biases = [np.random.randn(y, 1) for y in sizes[1:]] self.weights = [np.random.randn(y, x) for x, y in zip(sizes[:-1], sizes[1:])] 

大小数组包含相应层中神经元的数量。 因此,如果我们要创建一个在第一层具有两个神经元,在第二层具有三个神经元,在第三层具有一个神经元的Network对象,则我们将这样编写:

 net = Network([2, 3, 1]) 

使用numpy函数np.random.randn随机初始化Network对象中的偏移量和权重,该函数生成数学期望为0和标准偏差为1的高斯分布。这种随机初始化为我们的随机梯度下降算法提供了一个起点。 在接下来的章节中,我们将找到初始化权重和偏移量的最佳方法,但是到目前为止,这已经足够了。 请注意,网络初始化代码假定将输入神经元的第一层,并且不给它们分配偏差,因为它们仅用于计算输出。

还要注意,偏移量和权重存储为Numpy矩阵的数组。 例如,net.weights [1]是一个Numpy矩阵,存储连接神经元的第二层和第三层的权重(这不是第一层和第二层,因为在python中,数组元素的编号从头开始)。 由于写net.weights [1]会花费太长时间,因此我们将此矩阵表示为w。 这样的矩阵使得w jk是第二层中的第k个神经元和第三层中的第j个神经元之间的连接权重。 索引j和k的顺序似乎很奇怪-交换它们是否更合乎逻辑? 但是,这样的记录的最大优点是可以获得第三层神经元的激活向量:

a= sigmawa+b\标22



让我们看一下这个非常丰富的方程式。 a是第二层神经元的激活向量。 为了得到一个',我们将a乘以权重矩阵w,然后加上位移矢量b。 然后我们将S型σ元素逐个元素应用于向量wa + b的每个元素(这称为函数σ的向量化)。 容易验证公式(22)给出的结果与规则(4)相同,以计算出乙状神经元。

锻炼身体


  • 以组件形式编写方程式(22),并确保其得出与计算S形神经元的规则(4)相同的结果。

考虑到所有这些,编写用于计算Network对象输出的代码很容易。 我们先定义一个S形:

 def sigmoid(z): return 1.0/(1.0+np.exp(-z)) 

请注意,当z参数是Numpy向量或数组时,Numpy会自动以元素形式(即向量形式)应用S形。

将直接传播方法添加到Network类,该方法将网络中的a作为输入并返回相应的输出。 假定参数a是(n,1)Numpy ndarray,而不是向量(n,)。 这里n是输入神经元的数量。 如果尝试使用向量(n,),将会得到奇怪的结果。

该方法仅将方程式(22)应用于每一层:

  def feedforward(self, a): """       "a"""" for b, w in zip(self.biases, self.weights): a = sigmoid(np.dot(w, a)+b) return a 

当然,基本上我们从网络对象中需要它们来学习。 为此,我们将为他们提供SGD方法,该方法实现了随机梯度下降。 这是他的代码。 在某些地方它是相当神秘的,但是下面我们将更详细地分析它。

  def SGD(self, training_data, epochs, mini_batch_size, eta, test_data=None): """    -    . training_data –   "(x, y)",       .       .  test_data ,          ,     .     ,    . """ if test_data: n_test = len(test_data) n = len(training_data) for j in xrange(epochs): random.shuffle(training_data) mini_batches = [ training_data[k:k+mini_batch_size] for k in xrange(0, n, mini_batch_size)] for mini_batch in mini_batches: self.update_mini_batch(mini_batch, eta) if test_data: print "Epoch {0}: {1} / {2}".format( j, self.evaluate(test_data), n_test) else: print "Epoch {0} complete".format(j) 

training_data是表示训练输入和所需输出的元组“(x,y)”的列表。 纪元和mini_batch_size变量是要学习的纪元数和要使用的微型数据包的大小。 eta-学习速度,η。 如果设置了test_data,则将在每个时代之后根据验证数据对网络进行评估,并显示当前进度。 这对于跟踪进度很有用,但会大大降低工作速度。

该代码是这样的。 在每个时代,他都从不经意间混合训练数据开始,然后将它们分解为适当大小的微型数据包。 这是创建训练数据样本的简便方法。 然后,对于每个mini_batch,我们应用一个梯度下降步骤。 这是通过self.update_mini_batch代码(mini_batch,eta)完成的,该代码仅使用mini_batch中的训练数据,根据梯度下降的一次迭代来更新网络权重和偏移。 这是update_mini_batch方法的代码:

  def update_mini_batch(self, mini_batch, eta): """    ,          -. mini_batch –    (x, y),  eta –  .""" nabla_b = [np.zeros(b.shape) for b in self.biases] nabla_w = [np.zeros(w.shape) for w in self.weights] for x, y in mini_batch: delta_nabla_b, delta_nabla_w = self.backprop(x, y) nabla_b = [nb+dnb for nb, dnb in zip(nabla_b, delta_nabla_b)] nabla_w = [nw+dnw for nw, dnw in zip(nabla_w, delta_nabla_w)] self.weights = [w-(eta/len(mini_batch))*nw for w, nw in zip(self.weights, nabla_w)] self.biases = [b-(eta/len(mini_batch))*nb for b, nb in zip(self.biases, nabla_b)] 

大部分工作由生产线完成。

  delta_nabla_b, delta_nabla_w = self.backprop(x, y) 

它调用了反向传播算法-一种计算成本函数梯度的快速方法。 因此,update_mini_batch只需根据mini_batch为每个训练示例计算这些梯度,然后更新self.weights和self.biases。

到目前为止,我不会演示self.backprop的代码。 在下一章中,我们将学习反向传播,并且会有self.backprop代码。 现在,假设它的行为如上所述,为与训练示例x相关的成本返回适当的梯度。

让我们看一下整个程序,包括解释性注释。 除了self.backprop函数外,该程序不言自明-主要工作由self.SGD和self.update_mini_batch完成。 self.backprop方法使用几个附加函数来计算梯度,即sigmoid_prime和self.cost_derivative,sigmoid_prime计算Sigmoid的导数,而self.cost_derivative在此不做描述。 您可以通过查看代码和注释来了解它们。 在下一章中,我们将更详细地考虑它们。 请记住,尽管程序看起来很长,但是大多数代码都是注释,使注释更易于理解。 实际上,程序本身仅包含74行非代码-非空且非注释。 所有代码都可以在GitHub上获得

 """ network.py ~~~~~~~~~~           .      .     ,    .   ,       . """ ####  #   import random #   import numpy as np class Network(object): def __init__(self, sizes): """  sizes      .  ,      Network      ,     ,     ,    ,  [2, 3, 1].               0    1. ,      ,       ,        . """ self.num_layers = len(sizes) self.sizes = sizes self.biases = [np.random.randn(y, 1) for y in sizes[1:]] self.weights = [np.random.randn(y, x) for x, y in zip(sizes[:-1], sizes[1:])] def feedforward(self, a): """   ,  ``a`` -  .""" for b, w in zip(self.biases, self.weights): a = sigmoid(np.dot(w, a)+b) return a def SGD(self, training_data, epochs, mini_batch_size, eta, test_data=None): """    -    . training_data –   "(x, y)",       .       .  test_data ,          ,     .     ,    . """ if test_data: n_test = len(test_data) n = len(training_data) for j in xrange(epochs): random.shuffle(training_data) mini_batches = [ training_data[k:k+mini_batch_size] for k in xrange(0, n, mini_batch_size)] for mini_batch in mini_batches: self.update_mini_batch(mini_batch, eta) if test_data: print "Epoch {0}: {1} / {2}".format( j, self.evaluate(test_data), n_test) else: print "Epoch {0} complete".format(j) def update_mini_batch(self, mini_batch, eta): """    ,          -. mini_batch –    (x, y),  eta –  .""" nabla_b = [np.zeros(b.shape) for b in self.biases] nabla_w = [np.zeros(w.shape) for w in self.weights] for x, y in mini_batch: delta_nabla_b, delta_nabla_w = self.backprop(x, y) nabla_b = [nb+dnb for nb, dnb in zip(nabla_b, delta_nabla_b)] nabla_w = [nw+dnw for nw, dnw in zip(nabla_w, delta_nabla_w)] self.weights = [w-(eta/len(mini_batch))*nw for w, nw in zip(self.weights, nabla_w)] self.biases = [b-(eta/len(mini_batch))*nb for b, nb in zip(self.biases, nabla_b)] def backprop(self, x, y): """  ``(nabla_b, nabla_w)``,      C_x. ``nabla_b``  ``nabla_w`` -    numpy,   ``self.biases`` and ``self.weights``.""" nabla_b = [np.zeros(b.shape) for b in self.biases] nabla_w = [np.zeros(w.shape) for w in self.weights] #   activation = x activations = [x] #      zs = [] #     z- for b, w in zip(self.biases, self.weights): z = np.dot(w, activation)+b zs.append(z) activation = sigmoid(z) activations.append(activation) #   delta = self.cost_derivative(activations[-1], y) * \ sigmoid_prime(zs[-1]) nabla_b[-1] = delta nabla_w[-1] = np.dot(delta, activations[-2].transpose()) """ l      ,      . l = 1    , l = 2 – ,   .    ,   python      .""" for l in xrange(2, self.num_layers): z = zs[-l] sp = sigmoid_prime(z) delta = np.dot(self.weights[-l+1].transpose(), delta) * sp nabla_b[-l] = delta nabla_w[-l] = np.dot(delta, activations[-l-1].transpose()) return (nabla_b, nabla_w) def evaluate(self, test_data): """    ,      .    –          .""" test_results = [(np.argmax(self.feedforward(x)), y) for (x, y) in test_data] return sum(int(x == y) for (x, y) in test_results) def cost_derivative(self, output_activations, y): """    ( C_x /  a)   .""" return (output_activations-y) ####   def sigmoid(z): """.""" return 1.0/(1.0+np.exp(-z)) def sigmoid_prime(z): """ .""" return sigmoid(z)*(1-sigmoid(z)) 

程序对手写数字的识别程度如何? 让我们从加载MNIST数据开始。 我们将使用小的帮助程序mnist_loader.py进行此操作,下面将对其进行描述。 在python shell中运行以下命令:

 >>> import mnist_loader >>> training_data, validation_data, test_data = \ ... mnist_loader.load_data_wrapper() 

当然,这可以在单独的程序中完成,但是如果您与书本并行工作,则将更加容易。

下载MNIST数据后,建立30个隐藏神经元的网络。 我们将在导入上述程序(称为网络)之后执行此操作:

 >>> import network >>> net = network.Network([784, 30, 10]) 

最后,我们使用随机梯度下降来训练30个时代的训练数据,最小数据包大小为10,学习速度为η= 3.0:

 >>> net.SGD(training_data, 30, 10, 3.0, test_data=test_data) 

如果您在阅读书的同时执行代码,请记住,这将需要几分钟的时间来执行。 我建议您开始一切,继续阅读,并定期检查程序产生了什么。 如果您很着急,可以通过减少隐藏神经元的数量或仅使用部分训练数据来减少时代的数量。 最终的工作代码将更快地运行:这些python脚本旨在让您了解网络的工作原理,并且性能不高! 而且,当然,经过培训后,该网络几乎可以在任何计算平台上快速运行。 例如,当我们教网络如何选择权重和偏移量时,可以轻松地将其移植以在Web浏览器中的JavaScript上运行,或在移动设备上作为本机应用程序移植。 在任何情况下,训练神经网络的程序都会得出大致相同的结论。 在每次训练之后,她都会写下正确识别的测试图像的数量。 如您所见,即使在一个时代之后,它的精确度仍达到10,000的9,129,并且这个数字还在不断增长:

Epoch 0: 9129 / 10000
Epoch 1: 9295 / 10000
Epoch 2: 9348 / 10000
...
Epoch 27: 9528 / 10000
Epoch 28: 9542 / 10000
Epoch 29: 9534 / 10000


事实证明,经过训练的网络最多可以给出约95-95.42%的正确分类百分比! 相当有希望的首次尝试。 我警告您,由于我们使用随机权重和偏移量初始化网络,因此您的代码不一定会产生完全相同的结果。 在本章中,我选择了三种尝试中的最好的一种。

让我们通过将隐藏的神经元的数量更改为100来重新开始实验。像以前一样,如果您在阅读的同时运行代码,请记住,这会花费很多时间(在我的机器上,每个时代都需要几十秒),因此最好并行阅读与代码执行。

 >>> net = network.Network([784, 100, 10]) >>> net.SGD(training_data, 30, 10, 3.0, test_data=test_data) 

自然地,这将结果提高到96.59%。 至少在这种情况下,使用更多的隐藏神经元有助于获得更好的结果。

读者的反馈表明,该实验的结果相差很大,并且一些学习成果要差得多。 使用第3章中的技术来严重减少从一次运行到另一次运行的工作效率差异。

当然,为了获得这种准确性,我必须选择一定数量的学习时代,迷你包装的尺寸和学习速度η。 如上所述,它们被称为国民议会的超参数-将它们与算法在训练过程中调整的简单参数(权重和偏移量)区分开。 如果我们错误地选择了超参数,那么结果将会很糟糕。 例如,假设我们选择学习率η= 0.001:

 >>> net = network.Network([784, 100, 10]) >>> net.SGD(training_data, 30, 10, 0.001, test_data=test_data) 

结果不那么令人印象深刻:

Epoch 0: 1139 / 10000
Epoch 1: 1136 / 10000
Epoch 2: 1135 / 10000
...
Epoch 27: 2101 / 10000
Epoch 28: 2123 / 10000
Epoch 29: 2142 / 10000


但是,您可以看到网络效率随着时间的推移而缓慢增长。 这表明您可以尝试将学习速度提高到例如0.01。 在这种情况下,结果会更好,这表明需要进一步提高速度(如果更改可以改善情况,请进一步更改!)。 如果多次这样做,我们最终将得出η= 1.0(有时甚至是3.0),这与我们之前的实验很接近。 因此,尽管我们最初选择的超参数很差,但至少我们收集了足够的信息以改善我们对参数的选择。

通常,调试NA是一件复杂的事情。 当选择初始超参数产生的结果不超过随机噪声时,尤其如此。 假设我们尝试使用30个神经元的成功架构,但将学习速度更改为100.0:

 >>> net = network.Network([784, 30, 10]) >>> net.SGD(training_data, 30, 10, 100.0, test_data=test_data) 

最后,事实证明我们走得太远,速度太快了:

Epoch 0: 1009 / 10000
Epoch 1: 1009 / 10000
Epoch 2: 1009 / 10000
Epoch 3: 1009 / 10000
...
Epoch 27: 982 / 10000
Epoch 28: 982 / 10000
Epoch 29: 982 / 10000


现在想象一下,我们是第一次完成这项任务。 当然,我们从早期的实验中知道,降低学习速度是正确的。 但是,如果我们是第一次执行此任务,那么我们将没有可以引导我们找到正确解决方案的输出。 我们可以开始考虑也许我们为权重和偏移量选择了错误的初始参数,并且网络难以学习吗? 还是我们没有足够的培训数据来获得有意义的结果? 也许我们没有等足够的时代? 也许具有这种架构的神经网络根本无法学会识别手写数字? 也许学习速度太慢? 当您第一次执行任务时,您将永远没有信心。

值得一提的是,调试NS不是一件容易的事,这与常规编程一样,是本领域的一部分。 您必须学习这种调试技巧,才能从NS中获得良好的结果。 通常,我们需要开发一种启发式方法来选择良好的超参数和良好的体系结构。 我们将在书中对此进行详细讨论,包括如何选择上述超参数。

锻炼身体


  • 尝试创建仅由两层组成的网络-输入和输出,无隐藏-分别具有784和10个神经元。 用随机梯度下降训练网络。 您得到什么分类精度?

我之前跳过了加载MNIST数据的详细信息。 这很简单。 这是完成图片的代码。 注释中描述了数据结构-一切都很简单,元组和Numpy ndarray对象的数组(如果您不熟悉此类对象,可以将它们想象成向量)。

 """ mnist_loader ~~~~~~~~~~~~      MNIST.       ``load_data``  ``load_data_wrapper``.  , ``load_data_wrapper`` -  ,     . """ ####  #  import cPickle import gzip #  import numpy as np def load_data(): """  MNIST   ,  ,    . ``training_data``      .    .  numpy ndarray  50 000 .   –     numpy ndarray  784 ,  28 * 28 = 784    MNIST.  –  numpy ndarray  50 000 .   –   0  9   ,    . ``validation_data``  ``test_data`` ,    10 000 .    ,           ``training_data``.    - ``load_data_wrapper()``. """ f = gzip.open('../data/mnist.pkl.gz', 'rb') training_data, validation_data, test_data = cPickle.load(f) f.close() return (training_data, validation_data, test_data) def load_data_wrapper(): """ ,  ``(training_data, validation_data, test_data)``.   ``load_data``,         .  , ``training_data`` -    50 000    , ``(x, y)``. ``x`` -  784- numpy.ndarray,   . ``y`` -  10- numpy.ndarray,   ,     ``x``. ``validation_data``  ``test_data`` -  ,   10 000    , ``(x, y)``. ``x`` -  784- numpy.ndarray,   ,  ``y`` -   ,  ,   ( ),  ``x``. ,  ,           .         .""" tr_d, va_d, te_d = load_data() training_inputs = [np.reshape(x, (784, 1)) for x in tr_d[0]] training_results = [vectorized_result(y) for y in tr_d[1]] training_data = zip(training_inputs, training_results) validation_inputs = [np.reshape(x, (784, 1)) for x in va_d[0]] validation_data = zip(validation_inputs, va_d[1]) test_inputs = [np.reshape(x, (784, 1)) for x in te_d[0]] test_data = zip(test_inputs, te_d[1]) return (training_data, validation_data, test_data) def vectorized_result(j): """ 10-    1.0   j     .      (0..9)     .""" e = np.zeros((10, 1)) e[j] = 1.0 return e 

我说我们的计划取得了不错的成绩。这是什么意思?比什么好?获得一些简单的基本测试的结果很有用,您可以与这些测试进行比较以了解“好的结果”的含义。当然,最简单的基础级别是随机猜测。这可以在大约10%的情况下完成。而且我们显示出更好的结果!

那么平凡的基础水平又如何呢?让我们看看图片有多暗。例如,图像2通常会比图像1更暗,仅因为它具有更多的暗像素,如以下示例所示:



因此,我们可以计算出从0到9的每个数字的平均暗度。当我们得到一个新图像时,我们计算它的暗度,并且我们猜测它显示的是具有最接近平均暗度的图形。这是一个易于编程的简单过程,因此我不会编写代码-如果感兴趣,它位于GitHub上。但是与随机猜测相比,这是一个严重的改进-该代码可以正确识别10,000张图像中的2,225张,即准确度为22.25%。

不难发现其他实现精度在20%到50%之间的想法。工作多一点后,您可以超过50%。但是要获得更高的精度,最好使用权威的MO算法。让我们尝试最著名的算法之一,支持向量法或SVM。如果您不熟悉SVM,请放心,我们不需要了解这些详细信息。我们只使用一个名为scikit-learn的python库,该库为SVM的快速C库(称为LIBSVM)提供了简单的接口

如果我们在默认设置下运行SVM分类器scikit-learn,则将得到10,000中的9,435的正确分类(该代码可在链接中找到))与通过黑暗对图像进行分类的天真的方法相比,这已经是一个很大的改进。这意味着SVM的性能与我们的NS差不多,只是差一点。在接下来的章节中,我们将熟悉可使我们改进NS的新技术,使它们大大优于SVM。

但这还不是全部。scikit-learn的10 000结果中的9 435被指定为默认设置。 SVM具有许多可以调整的参数,您可以寻找可以改善此结果的参数。我不会详细介绍;它们可以在Andreas Muller 文章中阅读。他表明,通过进行参数优化工作,可以达到至少98.5%的精度。换句话说,经过良好调整的SVM在70个错误中仅产生一位,结果很好! NA能取得更多成就吗?

事实证明他们可以。如今,经过精心调校的NS可以取代MNIST解决方案中的任何其他已知技术,包括SVM。2013年的记录正确分类了10,000张图像中的9,979张。我们将在本书中看到用于此目的的大多数技术。这种精确度接近于人类,甚至可能超过人类,因为例如即使对于人类来说,MNIST的几幅图像也难以破译:例如,



我认为您将很难对它们进行分类!对于MNIST数据集中的此类图像,令人惊讶的是,NS可以正确识别10,000之外的所有图像(除了21个)。通常,程序员认为解决这一复杂任务需要复杂的算法。但即使是国家情报局也在起作用记录保存者使用相当简单的算法,这与我们在本章中研究的算法相比是很小的变化。所有复杂性会在训练期间根据训练数据自动显示。从某种意义上说,我们的结果以及更复杂的作品中包含的结果的寓意是,对于某些任务
复杂算法≤简单训练算法+良好的训练数据

深度学习


尽管我们的网络显示出令人印象深刻的性能,但是它是通过神秘的方式实现的。重量和混合网络会自动检测。因此,我们没有关于网络如何运行的现成解释。有没有办法通过手写数字网络来理解分类的基本原理?并给予他们改善结果的可能吗?

我们更严格地重新提出这些问题:让我们假设,在几十年后,NS将变成人工智能(AI)。我们会了解这种AI的工作原理吗?由于网络是自动分配的,因此它们的权重和偏移量可能会使我们难以理解。在AI研究的早期,人们希望尝试创建AI也可以帮助我们理解智能的基本原理,甚至可能是人脑的工作。但是,最终可能会导致我们无法理解大脑或AI的工作原理!

为了解决这些问题,让我们回想起我在本章开始时对人工神经元的解释-这是一种权衡证据的方法。假设我们要确定一个人的脸是否在图像上:







可以通过与手写识别相同的方式解决此问题:使用图像像素作为NS的输入,并且NS的输出将是一个神经元,它将说“是,这是一张脸”,或者“否,这不是一张脸”。 ”。

假设我们这样做了,但是没有使用学习算法。我们将尝试手动创建一个网络,选择适当的权重和偏移。我们该如何处理?有一会儿,忘记了国民议会,我们可以将任务分为子任务:眼睛的图像在左上角吗?右上角有眼睛吗?有中鼻梁吗?中部有嘴巴吗?顶部有头发吗?依此类推。

如果对这些问题中的几个问题的回答是肯定的,或者甚至是“可能是”,那么我们得出的结论是图像可能有脸。相反,如果答案是否定的,则可能没有人。

当然,这是一种近似的启发式方法,并且有很多缺点。也许这是个光头男人,而且他没有头发。也许我们只能看到脸的一部分,或者只看到某个角度,所以脸的某些部分是封闭的。尽管如此,启发式方法建议,如果我们可以借助神经网络解决子问题,那么也许我们可以通过组合用于子任务的网络来创建用于人脸识别的NS。以下是这种网络的可能架构,其中子网由矩形表示。这不是解决人脸识别问题的完全现实的方法:需要它帮助我们直观地了解神经网络的工作。


在矩形中有子任务:眼睛的图像在左上角吗?右上角有眼睛吗?有中鼻梁吗?中部有嘴巴吗?顶部有头发吗?依此类推。

子网也可以分解为组件。提出在左上角注视的问题。它可以分为以下几个问题:“有眉毛吗?”,“有睫毛吗?”,“有瞳孔吗?”等等。当然,这些问题应包含有关位置的信息-“眉毛位于左上方,在瞳孔上方吗?”,依此类推-现在让我们简化一下。因此,可以将回答存在眼睛问题的网络分解为以下组件:


“有眉毛吗?”,“有睫毛吗?”,“有瞳孔吗?”

这些问题可以通过许多层进一步细分为小问题。结果,我们将使用子网来回答如此简单的问题,以便可以在像素级别轻松拆卸子网。这些问题可能涉及例如图像某些位置中是否存在简单形式。与像素关联的单个神经元将能够对其做出反应。

结果是一个网络将非常复杂的问题(是否有人在图像中)分解为可以在单个像素级别回答的非常简单的问题。她将通过一系列多层来完成此操作,其中第一层回答有关图像的非常简单和特定的问题,而第二层将创建更复杂和抽象概念的层次结构。具有这种多层结构(两个或多个隐藏层)的网络称为深度神经网络(GNS)。

当然,我没有谈论如何进行递归子网划分。手动选择权重和偏移量绝对是不切实际的。我们想使用训练算法,以便网络根据训练数据自动学习权重和偏移,并通过它们学习概念的层次结构。 1980年代和1990年代的研究人员尝试使用随机梯度下降和反向传播训练GNS。不幸的是,除了一些特殊的体系结构,它们没有成功。网络训练得很慢,但速度非常慢,实际上,以某种方式使用它太慢了。

自2006年以来,已经开发了多种技术来训练STS。它们基于随机梯度下降和反向传播,但它们也包含新的思想。他们允许训练更深的网络-今天,人们悄悄地训练5到10层的网络。事实证明,它们解决许多问题要比浅层NS(即具有一层隐藏层的网络)要好得多。当然,原因是STS可以创建复杂的概念层次。这类似于编程语言如何使用模块化方案和抽象思想,以便它们可以创建复杂的计算机程序。比较深层NS和浅层NS大约是如何比较可以进行函数调用的编程语言和不进行函数调用的语言。 NS中的抽象看起来与编程语言不同,但同样重要

Source: https://habr.com/ru/post/zh-CN456738/


All Articles