神经网络和深度学习是一本免费的在线书。本书会教会你:
神经网络,一种美妙的受生物学启发的编程范式,可以让计算机从观测数据中进行学习
深度学习,一个强有力的用于神经网络学习的众多技术的集合
神经网络和深度学习目前给出了在图像识别、语音识别和自然语言处理领域中很多问题的最好解决方案。本书将
会教你在神经网络和深度学习背后的众多核心概念。想了解本书选择的观点的更多细节,请看这里。或者直接跳到第一章 开始你们的旅程。

注脚

展开查看详情

1.

2. 目 录 致谢 引言 第一章 使用神经网络识别手写数字 第二章 反向传播算法如何工作的? 第三章 改进神经网络的学习方法(上) 第三章 改进神经网络的学习方法(下) 第五章 深度神经网络为何很难训练 第六章 深度学习 本文档使用 书栈(BookStack.CN) 构建 - 2 -

3.致谢 致谢 当前文档 《神经网络与深度学习》 由 进击的皇虫 使用 书栈(BookStack.CN) 进行构建,生成于 2018- 07-17。 书栈(BookStack.CN) 仅提供文档编写、整理、归类等功能,以及对文档内容的生成和导出工具。 文档内容由网友们编写和整理,书栈(BookStack.CN) 难以确认文档内容知识点是否错漏。如果您在阅读文档 获取知识的时候,发现文档内容有不恰当的地方,请向我们反馈,让我们共同携手,将知识准确、高效且有效地传递 给每一个人。 同时,如果您在日常工作、生活和学习中遇到有价值有营养的知识文档,欢迎分享到 书栈(BookStack.CN) , 为知识的传承献上您的一份力量! 如果当前文档生成时间太久,请到 书栈(BookStack.CN) 获取最新的文档,以跟上知识更新换代的步伐。 文档地址:http://www.bookstack.cn/books/neural-networks-and-deep-learning-zh 书栈官网:http://www.bookstack.cn 书栈开源:https://github.com/TruthHun 分享,让知识传承更久远! 感谢知识的创造者,感谢知识的分享者,也感谢每一位阅读到此处的读者,因为我们 都将成为知识的传承者。 本文档使用 书栈(BookStack.CN) 构建 - 3 -

4.引言 引言 神经网络与深度学习 神经网络和深度学习是一本免费的在线书。本书会教会你: 神经网络,一种美妙的受生物学启发的编程范式,可以让计算机从观测数据中进行学习 深度学习,一个强有力的用于神经网络学习的众多技术的集合 神经网络和深度学习目前给出了在图像识别、语音识别和自然语言处理领域中很多问题的最好解决方案。本书将 会教你在神经网络和深度学习背后的众多核心概念。 想了解本书选择的观点的更多细节,请看这里。或者直接跳到第一章 开始你们的旅程。 译者的话: 本书是 Michael Nielsen 的 Neural Networks and Deep Learning 的中译本。目前已经完成第二章、第 三章、第五章和第六章的内容。后续会进行剩下章节的翻译。如果想要提供意见或者建议,给出翻译的笔误,都可以 直接通过 xhzhu.nju@gmail.com 联系到我。 原文: https://tigerneil.gitbooks.io/neural-networks-and-deep-learning-zh/content/index.html 本文档使用 书栈(BookStack.CN) 构建 - 4 -

5.第一章 使用神经网络识别手写数字 第一章 使用神经网络识别手写数字 第一章 使用神经网络识别手写数字 人类视觉系统是世界上众多奇迹之一。看看下面的手写数字序列: 大多数人毫不费力就能够认出这些数字为 504192. 这么容易反而让人觉着迷惑了。在人类的每个脑半球中,有着一 个初级视觉皮层,常称为 V1,包含 1 亿 4 千万个神经元及数百亿条神经元间的连接。但是人类视觉不是就只有 V1,还包括整个视觉皮层——V2、V3、V4 和 V5——他们逐步地进行更加复杂的图像处理。人类的头脑就是一台超级计 算机,通过数十亿年的进化不断地演变,最终能够极好地适应理解视觉世界的任务。识别手写数字也不是一件简单的 事。尽管人类在理解我们眼睛展示出来的信息上非常擅长,但几乎所有的过程都是无意识地。所以,我们通常并不能 体会自身视觉系统解决问题的困难。 如果你尝试写出计算机程序来识别诸如上面的数字,就会明显感受到视觉模式识别的困难。看起来人类一下子就能完 成的任务变得特别困难。关于我们识别形状——“9 顶上有一个圈,右下方则是一条竖线”这样的简单直觉——实际上算法 上就很难轻易表达出来了。而在你试着让这些识别规则越发精准时,就会很快陷入各种混乱的异常或者特殊情形的困 境中。看起来毫无希望。 神经网络以另一种方式看待这个问题。其主要思想是获取大量的手写数字,常称作训练样本, 然后开发出一个可以从这些训练样本中进行学习的系统。换言之,神经网络使用样本来自动推断出识别手写数字的规 则。另外,通过增加训练样本的数量,网络可以学到更多关于手写数字的知识,这样就能够提升自身的准确性。所 以,上面例子中我们只是展出了 100 个训练数字样本,而通过使用数千或者数百万或者数十亿的训练样本我们也许 能够得到更好的手写数字识别器。 本章我们将实现一个可以识别手写数字的神经网络。这个程序仅仅 74 行,不适用特别的神经网络库。然而,这个短 小的网络不需要人类帮助便可以超过 96% 的准确率识别数字。而且,在后面的章节,我们会发展出将准确率提升到 99% 的技术。实际上,最优的商业神经网络已经足够好到被银行和邮局分别用在账单核查和识别地址上了。 本文档使用 书栈(BookStack.CN) 构建 - 5 -

6.第一章 使用神经网络识别手写数字 手写识别常常被当成学习神经网络的原型问题,因此我们聚焦在这个问题上。作为一个原型,它具备一个关键点:挑 战性——识别手写数字并不轻松——但也不会难到需要超级复杂的解决方法,或者超大规模的计算资源。另外,这其实也 是一种发展出诸如深度学习更加高级的技术的方法。所以,整本书我们都会持续地讨论手写数字识别问题。本书后面 部分,我们会讨论这些想法如何用在其他计算机视觉的问题或者语音、自然语言处理和其他一些领域中。 [待续] 原文: https://tigerneil.gitbooks.io/neural-networks-and-deep-learning-zh/content/chapter1.html 本文档使用 书栈(BookStack.CN) 构建 - 6 -

7.第二章 反向传播算法如何工作的? 第二章 反向传播算法如何工作的? 第二章 反向传播算法如何工作的? 在上一章,我们看到了神经网络如何使用梯度下降算法来学习他们自身的权重和偏差。但是,这里还留下了一个问 题:我们并没有讨论如何计算代价函数的梯度。这是很大的缺失!在本章,我们会解释计算这些梯度的快速算法,也 就是反向传播。 反向传播算法最初在 1970 年代被发现,但是这个算法的重要性直到 David Rumelhart、Geoffrey Hinton 和 Ronald Williams 的 1986年的论文 中才被真正认可。这篇论文描述了对一些神经网络反向传播要比传统的方法 更快,这使得使用神经网络来解决之前无法完成的问题变得可行。现在,反向传播算法已经是神经网络学习的重要组 成部分了。 本章在全书的范围内要比其他章节包含更多的数学内容。如果你不是对数学特别感兴趣,那么可以跳过本章,将反向 传播当成一个黑盒,忽略其中的细节。那么为何要研究这些细节呢? 答案当然是理解。反向传播的核心是对代价函数 关于 (或者 )的偏导数 的计算表示。该表示告诉我们在权重和偏 差发生改变时,代价函数变化的快慢。尽管表达式会有点复杂,不过里面也包含一种美感,就是每个元素其实是拥有 一种自然的直觉上的解释。所以反向传播不仅仅是一种学习的快速算法。实际上它还告诉我们一些细节的关于权重和 偏差的改变影响整个网络行为方面的洞察。因此,这也是学习反向传播细节的重要价值所在。 如上面所说,如果你想要粗览本章,或者直接跳到下一章,都是可以的。剩下的内容即使你是把反向传播看做黑盒也 是可以掌握的。当然,后面章节中也会有部分内容涉及本章的结论,所以会常常给出本章的参考。不过对这些知识 点,就算你对推导的细节不太清楚你还是应该要理解主要的结论的。 热身:神经网络中使用矩阵快速计算输出的观点 在讨论反向传播前,我们先熟悉一下基于矩阵的算法来计算网络的输出。事实上,我们在上一章的最后已经能够看到 这个算法了,但是我在那里很快地略过了,所以现在让我们仔细讨论一下。特别地,这样能够用相似的场景帮助我们 熟悉在反向传播中使用的矩阵表示。 我们首先给出网络中权重的清晰定义。我们使用 表示从 层的 个神经元到 层的 个神经元的链接上的权重。例如, 下图给出了第二隐藏层的第四个神经元到第三隐藏层的第二个神经元的链接上的权重: 本文档使用 书栈(BookStack.CN) 构建 - 7 -

8.第二章 反向传播算法如何工作的? 这样的表示粗看比较奇怪,需要花一点时间消化。但是,后面你会发现这样的表示会比较方便也很自然。奇怪的一点 其实是下标 和 的顺序。你可能觉得反过来更加合理。但我接下来会告诉你为什么要这样做。 我们对网络偏差和激活值也会使用类似的表示。显式地,我们使用 表示在 层 个神经元的偏差,使用 表示 层 个神 经元的激活值。下面的图清楚地解释了这样表示的含义: 有了这些表示, 层的 个神经元的激活值 就和 层关联起来了(对比公式(4) 和上一章的讨论) 其中求和是在 层的所有神经元上进行的。为了用矩阵的形式重写这个表达式,我们对每一层 都定义一个权重矩阵 , 在 行第 列的元素是 。类似的,对每一层 ,定义一个偏差向量,。你已经猜到这些如何工作了——偏差向量的每个元 素其实就是前面给出的 ,每个元素对应于 层的每个神经元。最后,我们定义激活向量 ,其元素是那些激活值 。 最后我们需要引入向量化函数(如 )来按照矩阵形式重写公式(23) 。在上一章,我们其实已经碰到向量化了,其含 义就是作用函数(如 )到向量 中的每个元素。我们使用 表示这种按元素进行的函数作用。所以, 的每个元素其实 满足 。给个例子,如果我们的作用函数是 ,那么向量化的 的函数作用就起到下面的效果: 本文档使用 书栈(BookStack.CN) 构建 - 8 -

9.第二章 反向传播算法如何工作的? 也就是说,向量化的 仅仅是对向量的每个元素进行了平方运算。 了解了这些表示,方程(23)就可以写成下面的这种美妙而简洁的向量形式了: 这个表达式给出了一种更加全局的思考每层的激活值和前一层的关联方式:我们仅仅用权重矩阵作用在激活值上,然 后加上一个偏差向量,最后作用 函数。 其实,这就是让我们使用之前的矩阵下标 表示的初因。如果我们使用 来索引输入神经元, 索引输出神经元,那么在方程(25)中我们需要将这里的矩阵换 做其转置。这只是一个小小的困惑的改变,这会使得我们无法自然地讲出(思考)“应用权重矩阵到激活值上”这样的简单的表达。 这种全局的观点相比神经元层面的观点常常更加简明(没有更多的索引下标了!)其实可以看做是在保留清晰认识的 前提下逃离下标困境的方法。在实践中,表达式同样很有用,因为大多数矩阵库提供了实现矩阵乘法、向量加法和向 量化的快速方法。实际上,上一章的代码其实已经隐式使用了使用这种表达式来计算网络行为。 在使用方程(25)计算 时,我们计算了中间量 。这个量其实是非常有用的:我们称 为 层的带权输入。在本章后面, 我们会大量用到这个量。方程(25)有时候会写作 。同样要指出的是 关于代价函数的两个假设 反向传播的目标是计算代价函数 分别关于 和 的偏导数 和 。为了让反向传播可行,我们需要做出关于代价函数的 两个主要假设。在给出这两个假设之前,我们先看看具体的一个代价函数。我们会使用上一章使用的二次代价函数。 按照上一节给出的表示,二次代价函数有下列形式: 其中 是训练样本的总数;求和是在所有的训练样本 上进行的; 是对应的目标输出; 表示网络的层数; 是当输入 是 时的网络输出的激活值向量。 好了,为了应用反向传播,我们需要对代价函数做出什么样的前提假设呢?第一个假设就是代价函数可以被写成一个 在每个训练样本 上的代价函数 的均值 。这是关于二次代价函数的例子,其中对每个独立的训练样本其代价是 。这 个假设对书中提到的其他任何一个代价函数也都是必须满足的。 需要这个假设的原因是反向传播实际上是对一个独立的训练样本计算了 和 。然后我们通过在所有训练样本上进行平 均化获得 和 。实际上,有了这个假设,我们会认为训练样本 已经被固定住了,丢掉了其下标,将代价函数 看做 。最终我们会把下标加上,现在为了简化表示其实没有这个必要。 本文档使用 书栈(BookStack.CN) 构建 - 9 -

10.第二章 反向传播算法如何工作的? 第二个假设就是代价可以写成神经网络输出的函数: 例如,二次代价函数满足这个要求,因为对于一个单独的训练样本 其二次代价函数可以写作: 这是输出的激活值的函数。当然,这个代价函数同样还依赖于目标输出 。记住,输入的训练样本 是固定的,所以输 出同样是一个固定的参数。所以说,并不是可以随意改变权重和偏差的,也就是说,这不是神经网络学习的对象。所 以,将 看成仅有输出激活值 的函数才是合理的,而 仅仅是帮助定义函数的参数而已。 Hadamard 乘积 反向传播算法基于常规的线性代数运算——诸如向量加法,向量矩阵乘法等。但是有一个运算不大常见。特别地,假设 和 是两个同样维度的向量。那么我们使用 来表示按元素的乘积。所以 的元素就是 。给个例子, 这种类型的按元素乘法有时候被称为 Hadamard 乘积或者 Schur 乘积。我们这里取前者。好的矩阵库通常会提供 Hadamard 乘积的快速实现,在实现反向传播的时候用起来很方便。 反向传播的四个基本方程 反向传播其实是对权重和偏差变化影响代价函数过程的理解。最终极的含义其实就是计算偏导数 和 。但是为了计算 这些值,我们首先引入一个中间量,,这个我们称为在 层第 个神经元上的误差(error)。 反向传播将给出计算误差 本文档使用 书栈(BookStack.CN) 构建 - 10 -

11.第二章 反向传播算法如何工作的? 为了理解误差是如何定义的,假设在神经网络上有一个恶魔: 这个小精灵在 层的第 个神经元上。当输入进来时,精灵对神经元的操作进行搅局。他会增加很小的变化 在神经元的 带权输入上,使得神经元输出由 变成 。这个变化会向网络后面的层进行传播,最终导致整个代价函数产生 的改变。 现在,这个精灵变好了,试着帮助你来优化代价函数,他们试着找到可以让代价更小的 。假设 有一个很大的值(或 正或负)。那么这个精灵可以降低代价通过选择与 相反符号的 。相反,如果 接近 ,那么精灵并不能通过扰动带权 输入 来改变太多代价函数。在小精灵看来,这时候神经元已经很接近最优了。 这里需要注意的是,只有在 很小的时候才能够满足。我们需要假设小精灵只能进行微小的调整。 所以这里有一种启发式的认识, 是神经元的误差的度量。 按照上面的描述,我们定义 层的第 个神经元上的误差 为: 按照我们通常的惯例,我们使用 表示关联于 层的误差向量。反向传播会提供给我们一种计算每层的 的方法,然后将 这些误差和最终我们需要的量 和 联系起来。 你可能会想知道为何精灵在改变带权输入 。肯定想象精灵改变输出激活 更加自然,然后就使用 作为度量误差的方法 了。 实际上,如果你这样做的话,其实和下面要讨论的差不同。但是看起来,前面的方法会让反向传播在代数运算上 变得比较复杂。所以我们坚持使用 作为误差的度量。 在分类问题中,误差有时候会用作分类的错误率。如果神经网络正确分类了 96.0% 的数字,那么其误差是 4.0%。很明显,这和我们上面提及的误差的差 别非常大了。在实际应用中,区分这两种含义是非常容易的。 解决方案:反向传播基于四个基本方程。这些方程给我们一种计算误差和代价函数梯度的方法。我列出这四个方程。 但是需要注意:你不需要一下子能够同时理解这些公式。因为过于庞大的期望可能会导致失望。实际上,反向传播方 程内容很多,完全理解这些需要花费充分的时间和耐心,需要一步一步地深入理解。而好的消息是,这样的付出回报 巨大。所以本节对这些内容的讨论仅仅是一个帮助你正确掌握这些公式的起步。 下面简要介绍我们的探讨这些公式的计划:首先给出这些公式的简短证明以解释他们的正确性;然后以伪代码的方式 给出这些公式的算法形式,并展示这些伪代码如何转化成真实的可执行的 python 代码;在本章的最后,我们会发展 处一个关于反向传播公式含义的直觉图景,以及人们如何能够从零开始发现这个规律。按照此法,我们会不断地提及 本文档使用 书栈(BookStack.CN) 构建 - 11 -

12.第二章 反向传播算法如何工作的? 这四个基本方程,随着你对这些方程理解的加深,他们会看起来更加舒服,甚至是美妙和自然的。 输出层误差的方程,:每个元素定义如下: 这是一个非常自然的表达式。右式第一个项 表示代价随着 输出激活值的变化而变化的速度。假如 不太依赖一个特定 的输出神经元 ,那么 就会很小,这也是我们想要的效果。右式第二项 刻画了在 处激活函数 变化的速度。 注意到在 BP1 中的每个部分都是很好计算的。特别地,我们在前向传播计算网络行为时已经计算过 ,这仅仅需要一 点点额外工作就可以计算 。当然 依赖于代价函数的形式。然而,给定了代价函数,计算就没有什么大问题了。例 如,如果我们使用二次函数,那么 ,所以 ,这其实很容易计算。 方程(BP1)对 来说是个按部分构成的表达式。这是一个非常好的表达式,但不是我们期望的用矩阵表示的形式。但 是,重写方程其实很简单, 这里 被定义成一个向量,其元素师偏导数 。你可以将其看成 关于输出激活值的改变速度。方程(BP1)和方程 (BP1a)的等价也是显而易见的,所以现在开始,我们会交替地使用这两个方程。举个例子,在二次代价函数时,我们 有 ,所以(BP1)的整个矩阵形式就变成 如你所见,这个方程中的每个项都有一个很好的向量形式,所以也可以很方便地使用像 Numpy 这样的矩阵库进行计 算了。 使用下一层的误差 来表示当前层的误差 :特别地, 其中 是 权重矩阵 的转置。这其实可以很直觉地看做是后在 层的输出的误差的反向传播,给出了某种关于误差的度 量方式。然后,我们进行 Hadamard 乘积运算 。这会让误差通过 层的激活函数反向传递回来并给出在第 层的带权 输入的误差 。 通过组合(BP1)和(BP2),我们可以计算任何层的误差了。首先使用(BP1)计算,然后应用方程(BP2)来计算,然后 不断作用(BP2),一步一步地反向传播完整个网络。 代价函数关于网络中任意偏差的改变率:就是 本文档使用 书栈(BookStack.CN) 构建 - 12 -

13.第二章 反向传播算法如何工作的? 这其实是,误差 和偏导数值 完全一致。这是很好的性质,因为(BP1)和(BP2)已经告诉我们如何计算 。所以就可以 将(BP3)简记为 其中 和偏差 都是针对同一个神经元。 代价函数关于任何一个权重的改变率:特别地, 这告诉我们如何计算偏导数 ,其中 这些量我们都已经知道如何计算了。方程也可以写成下面少下标的表示: 其中 方程(32)的一个结论就是当激活值很小,梯度 也会变得很小。这样,我们就说权重学习缓慢,表示在梯度下降的时 候,这个权重不会改变太多。换言之,(BP4)的后果就是来自很低的激活值神经元的权重学习会非常缓慢。 这四个公式同样还有很多观察。让我们看看(BP1)中的项 。回忆一下上一章的 sigmoid 函数图像,当函数值接近 或者 的时候图像非常平。这就使得在这些位置的导数接近于 .所以如果输出神经元处于或者低激活值或者高激活值 时,最终层的权重学习缓慢。这样的情形,我们常常称输出神经元已经饱和了,并且,权重学习也会终止(或者学习 非常缓慢)。类似的结果对于输出神经元的偏差也是成立的。 针对前面的层,我们也有类似的观点。特别地,注意在(BP2)中的项 。这表示 很可能变小如果神经元已经接近饱 和。这就导致任何输入进一个饱和的神经元的权重学习缓慢。 如果 拥有足够大的量能够补偿 的话,这里的推导就不能成立了。但是我们上面是常见的情形。 本文档使用 书栈(BookStack.CN) 构建 - 13 -

14.第二章 反向传播算法如何工作的? 总结一下,我们已经学习到权重学习缓慢如果输入神经元激活值很低,或者输出神经元已经饱和了(过高或者过低的 激活值)。 这些观测其实也是不非常令人惊奇的。不过,他们帮助我们完善了关于神经网络学习的背后的思维模型。而且,我们 可以将这种推断方式进行推广。四个基本方程也其实对任何的激活函数都是成立的(证明中也可以看到,其实推断本 身不依赖于任何具体的代价函数)所以,我们可以使用这些方程来设计有特定属性的激活函数。我们这里给个例子, 假设我们准备选择一个(non-sigmoid)的激活函数 使得 总是正数。这会防止在原始的 sigmoid 神经元饱和时 学习速度的下降的情况出现。在本书的后面,我们会见到这种类型的对激活函数的改变。时时回顾这四个方程可以帮 助解释为何需要有这些尝试,以及尝试带来的影响。 问题 另一种反向传播方程的表示方式:我已经给出了使用了 Hadamard 乘积的反向传播的公式。如果你对这种特殊的乘 积不熟悉,可能会有一些困惑。下面还有一种表示方式,那就是基于传统的矩阵乘法,某些读者可能会觉得很有启 发。(1) 证明 (BP1) 可以写成 其中 是一个方阵,其对角线的元素是 ,其他的元素均是 。注意,这个矩阵通过一般的矩阵乘法作用在 上。(2) 证 明(BP2) 可以写成 (3) 结合(1)和(2) 证明 对那些习惯于这种形式的矩阵乘法的读者,(BP1) (BP2) 应该更加容易理解。而我们坚持使用 Hadamard 乘积的 原因在于其更快的数值实现。 本文档使用 书栈(BookStack.CN) 构建 - 14 -

15.第二章 反向传播算法如何工作的? 四个基本方程的证明(可选) 我们现在证明这四个方程。所有这些都是多元微积分的链式法则的推论。如果你熟悉链式法则,那么我鼓励你在读之 前自己证明一番。 练习 证明方程 (BP3) 和 (BP4) 这样我们就完成了反向传播四个基本公式的证明。证明本身看起来复杂。但是实际上就是细心地应用链式法则。 我们可以将反向传播看成是一种系统性地应用多元微积分中的链式法则来计算代价函数的梯度的方式。这些就是 反向传播理论上的内容——剩下的是实现细节。 反向传播算法 反向传播方程给出了一种计算代价函数梯度的方法。让我们显式地用算法描述出来: 输入 x:为输入层设置对应的激活值 a^l 。 前向传播:对每个 l=2,3,…,L 计算相应的 z^l = w^la^{l-1} + b^l 和 a^l = \sigma(z^l) 输出层误差 \delta^L:计算向量 \delta^L = \nabla_a C \odot \sigma'(z^L) 反向误差传播:对每个 l=L-1, L-2,…,2,计算 \delta^l = ((w^{l+1})^T\delta^{l+1})\odot \sigma'(z^l) 输出:代价函数的梯度由 \frac{C}{w_{jk}^l} = a_k^{l-1}\delta_j^j 和 \frac{\partial C} {\partial b_j^l} = \delta_j^l 看看这个算法,你可以看到为何它被称作反向传播。我们从最后一层开始向后计算误差向量 。这看起来有点奇 怪,为何要从后面开始。但是如果你认真思考反向传播的证明,这种反向移动其实是代价函数是网络输出的函数 的后果。为了理解代价随前面层的权重和偏差变化的规律,我们需要重复作用链式法则,反向地获得需要的表达 式。 练习 使用单个修正的神经元的反向传播假设我们改变一个前向传播网络中的单个神经元,使得那个神经元的输出是 f(\sum_j w_jx_j + b),其中 f 是和 sigmoid 函数不同的某一函数。我们如何调整反向传播算法? 线性神经元上的反向传播 假设我们将非线性神经元替换为 \sigma(z) = z。重写反向传播算法。 正如我们上面所讲的,反向传播算法对一个训练样本计算代价函数的梯度,。在实践中,通常将反向传播算法和 诸如随机梯度下降这样的学习算法进行组合使用,我们会对许多训练样本计算对应的梯度。特别地,给定一个大 小为 的 minibatch,下面的算法应用一步梯度下降学习在这个 minibatch 上: 输入训练样本的集合 对每个训练样本 x:设置对应的输入激活 a^{x,1},并执行下面的步骤: 前向传播:对每个 l=2,3,…,L 计算 z^{x,l} = w^la^{x,l-1} + b^l 和 a^{x,l} = \sigma(z^{x,l}) 输出误差 \delta^{x,L}:计算向量 \delta^{x,L} = \nabla_a C_x \odot \sigma'(z^{x,L}) 本文档使用 书栈(BookStack.CN) 构建 - 15 -

16.第二章 反向传播算法如何工作的? 反向传播误差:对每个 l=L-1, L-2, …, 2 计算 \delta^{x,l} = ((w^{l+1})^T\delta^{x,l+1})\odot \sigma'(z^{x,l}) 梯度下降:对每个 l=L-1, L-2, …, 2 根据 w^l \rightarrow w^l - \frac{\eta}{m}\sum_x \delta^{x,l}(a^{x,l-1})^T 和 b^l \rightarrow b^l - \frac{\eta}{m}\sum_x \delta^{x,l} 更新权重和偏差 当然,在实践中实现随机梯度下降,我们还需要一个产生训练样本 minibatch 的循环,还有就是训练次数的 循环。这里我们先省略了。 代码 理解了抽象的反向传播的理论知识,我们现在就可以学习上一章中使用的实现反向传播的代码了。回想上一章的代 码,需要研究的是在 Network 类中的 update_mini_batch 和 backprop 方法。这些方法的代码其实是我们上 面的算法描述的直接翻版。特别地, update_mini_batch 方法通过计算当前 mini_batch 中的训练样本对 Network 的权重和偏差进行了更新: 1. class Network(object): 2. ... 3. def update_mini_batch(self, mini_batch, eta): 4. """Update the network's weights and biases by applying 5. gradient descent using backpropagation to a single mini batch. 6. The "mini_batch" is a list of tuples "(x, y)", and "eta" 7. is the learning rate.""" 8. nabla_b = [np.zeros(b.shape) for b in self.biases] 9. nabla_w = [np.zeros(w.shape) for w in self.weights] 10. for x, y in mini_batch: 11. delta_nabla_b, delta_nabla_w = self.backprop(x, y) 12. nabla_b = [nb+dnb for nb, dnb in zip(nabla_b, delta_nabla_b)] 13. nabla_w = [nw+dnw for nw, dnw in zip(nabla_w, delta_nabla_w)] 14. self.weights = [w-(eta/len(mini_batch))*nw 15. for w, nw in zip(self.weights, nabla_w)] 16. self.biases = [b-(eta/len(mini_batch))*nb 17. for b, nb in zip(self.biases, nabla_b)] 主要工作其实是在 deltanabla_b, delta_nabla_w = self.backprop(x, y) 这里完成的,调用了 backprop 方法计算 出了偏导数, 和 l[-3] 其实是列表中的倒数第三个元素。下面 backprop 的代码,使用了一些用来计算 、导 数 及代价函数的导数帮助函数。所以理解了这些,我们就完全可以掌握所有的代码了。如果某些东西让你困惑,你可 能需要参考代码的原始描述 1. class Network(object): 2. ... 3. def backprop(self, x, y): 4. """Return a tuple "(nabla_b, nabla_w)" representing the 5. gradient for the cost function C_x. "nabla_b" and 6. "nabla_w" are layer-by-layer lists of numpy arrays, similar 7. to "self.biases" and "self.weights".""" 8. nabla_b = [np.zeros(b.shape) for b in self.biases] 9. nabla_w = [np.zeros(w.shape) for w in self.weights] 10. # feedforward 本文档使用 书栈(BookStack.CN) 构建 - 16 -

17.第二章 反向传播算法如何工作的? 11. activation = x 12. activations = [x] # list to store all the activations, layer by layer 13. zs = [] # list to store all the z vectors, layer by layer 14. for b, w in zip(self.biases, self.weights): 15. z = np.dot(w, activation)+b 16. zs.append(z) 17. activation = sigmoid(z) 18. activations.append(activation) 19. # backward pass 20. delta = self.cost_derivative(activations[-1], y) * \ 21. sigmoid_prime(zs[-1]) 22. nabla_b[-1] = delta 23. nabla_w[-1] = np.dot(delta, activations[-2].transpose()) 24. # Note that the variable l in the loop below is used a little 25. # differently to the notation in Chapter 2 of the book. Here, 26. # l = 1 means the last layer of neurons, l = 2 is the 27. # second-last layer, and so on. It's a renumbering of the 28. # scheme in the book, used here to take advantage of the fact 29. # that Python can use negative indices in lists. 30. for l in xrange(2, self.num_layers): 31. z = zs[-l] 32. sp = sigmoid_prime(z) 33. delta = np.dot(self.weights[-l+1].transpose(), delta) * sp 34. nabla_b[-l] = delta 35. nabla_w[-l] = np.dot(delta, activations[-l-1].transpose()) 36. return (nabla_b, nabla_w) 37. 38. ... 39. 40. def cost_derivative(self, output_activations, y): 41. """Return the vector of partial derivatives \partial C_x / 42. \partial a for the output activations.""" 43. return (output_activations-y) 44. 45. def sigmoid(z): 46. """The sigmoid function.""" 47. return 1.0/(1.0+np.exp(-z)) 48. 49. def sigmoid_prime(z): 50. """Derivative of the sigmoid function.""" 51. return sigmoid(z)*(1-sigmoid(z)) 问题 在 minibatch 上的反向传播的全矩阵方法我们对于随机梯度下降的实现是对一个 minibatch 中的训练样 本进行遍历。所以也可以更改反向传播算法使得它同时对一个 minibatch 中的所有样本进行梯度计算。这个 想法其实就是我们可以用一个矩阵 X=[x_1, x_2, …, x_m],其中每列就是在minibatch 中的向量,而不 是单个的输入向量,x。我们通过乘权重矩阵,加上对应的偏差进行前向传播,在所有地方应用 sigmoid 函 数。然后按照类似的过程进行反向传播。请显式写出这种方法下的伪代码。更改 network.py 来实现这个方 案。这样做的好处其实利用到了现代的线性代数库。所以,这会比在 minibatch 上进行遍历要运行得更快 (在我的笔记本电脑上,在 MNIST 分类问题上,我相较于上一章的实现获得了 2 倍的速度提升)。在实际应 本文档使用 书栈(BookStack.CN) 构建 - 17 -

18.第二章 反向传播算法如何工作的? 用中,所有靠谱的反向传播的库都是用了类似的基于矩阵或者变体的方式来实现的。 在哪种层面上,反向传播是快速的算法? 为了回答这个问题,首先考虑另一个计算梯度的方法。就当我们回到上世界50、60年代的神经网络研究。假设你是世 界上首个考虑使用梯度下降方法学习的那位!为了让自己的想法可行,就必须找出计算代价函数梯度的方法。想想自 己学到的微积分,决定试试看链式法则来计算梯度。但玩了一会后,就发现代数式看起来非常复杂,然后就退缩了。 所以就试着找另外的方式。你决定仅仅把代价看做权重 的函数。你给这些权重 进行编号,期望计算关于某个权值 关 于 的导数。而一种近似的方法就是下面这种: 其中 是一个很小的正数,而 是在第j个方向上的单位向量。换句话说,我们可以通过计算 的两个接近相同的点的值 来估计 ,然后应用公式(46)。同样方法也可以用来计算 。 这个观点看起来非常有希望。概念上易懂,容易实现,使用几行代码就可以搞定。看起来,这样的方法要比使用链式 法则还要有效。 然后,遗憾的是,当你实现了之后,运行起来这样的方法非常缓慢。为了理解原因,假设我们有 权重。对每个不同的 权重 我们需要计算 来计算 。这意味着为了计算梯度,我们需要计算代价函数 次,需要 前向传播(对每个样 本)。我们同样需要计算 ,总共是 次。 反向传播聪明的地方就是它确保我们可以同时计算所有的偏导数 使用一次前向传播,加上一次后向传播。粗略地说, 后向传播的计算代价和前向的一样。* 这个说法是合理的,但需要额外的说明来澄清这一事实。在前向传播过程中主要的计算代价消耗在权重矩阵的乘法上,而反向传播则是计算权重矩阵的转置 矩阵。这些操作显然有着类似的计算代价。 所以最终的计算代价大概是两倍的前向传播计算大家。比起直接计算导数,显然 反向传播 有着更大的优势。所以即 使 反向传播 看起来要比 (46) 更加复杂,但实际上要更快。 这个加速在1986年首次被众人接受,并直接导致神经网络可以处理的问题的扩展。这也导致了大量的研究者涌向了神 经网络方向。当然,反向传播 并不是万能钥匙。在 1980 年代后期,人们尝试挑战极限,尤其是尝试使用反向传播 来训练深度神经网络。本书后面,我们将看到现代计算机和一些聪明的新想法已经让 反向传播 成功地训练这样的深 度神经网络。 反向传播:大视野 正如我所讲解的,反向传播 提出了两个神秘的问题。首先,这个算法真正在干什么?我们已经感受到从输出处的错误 被反向传回的图景。但是我们能够更深入一些,构造出一种更加深刻的直觉来解释所有这些矩阵和向量乘法么?第二 神秘点就是,某人为什么能发现这个 反向传播?跟着一个算法跑一遍甚至能够理解证明算法可以运行这是一回事。这 并不真的意味着你理解了这个问题到一定程度,能够发现这个算法。是否有一个推理的思路可以指引我们发现 反向传 播 算法?本节,我们来探讨一下这两个谜题。为了提升我们关于算法究竟做了什么的直觉,假设我们已经对 本文档使用 书栈(BookStack.CN) 构建 - 18 -

19.第二章 反向传播算法如何工作的? 这个改变会导致在输出激活值上的相应改变: 然后,会产生对下一层激活值的改变: 接着,这些改变都将影响到一个个下一层,到达输出层,最终影响代价函数: 本文档使用 书栈(BookStack.CN) 构建 - 19 -

20.第二章 反向传播算法如何工作的? 所以代价函数 改变和 就按照下面的公式关联起来了: 这给出了一种可能的计算 我们尝试一下这个方法。 导致了在 层 神经元的激活值的变化 。这个变化由下面的公式给出: 的变化将会导致下一层的所有激活值的变化。我们聚焦到其中一个激活值上看看影响的情况,不妨设 , 实际上,这会导致下面的变化: 本文档使用 书栈(BookStack.CN) 构建 - 20 -

21.第二章 反向传播算法如何工作的? 将其代入方程(48),我们得到: 当然,这个变化又会去下一层的激活值。实际上,我们可以想象出一条从 到 的路径,然后每个激活值的变化会导致 下一层的激活值的变化,最终是输出层的代价的变化。假设激活值的序列如下 ,那么结果的表达式就是 我们已经对每个经过的神经元设置了一个 这种形式的项,还有输出层的 这里我们对路径中所有可能的中间神经元选择进行求和。对比 (47) 我们有 现在公式(53)看起来相当复杂。但是,这里其实有一个相当好的直觉上的解释。我们用这个公式计算 关于网络中一 个权重的变化率。而这个公式告诉我们的是:两个神经元之间的连接其实是关联与一个变化率因子,这仅仅是一个神 经元的激活值相对于其他神经元的激活值的偏导数。从第一个权重到第一个神经元的变化率因子是 本文档使用 书栈(BookStack.CN) 构建 - 21 -

22.第二章 反向传播算法如何工作的? 我们到现在所给出的东西其实是一种启发式的观点,一种思考权重变化对网络行为影响的方式。让我们给出关于这个 观点应用的一些流程建议。首先,你可以推导出公式(53)中所有单独的的偏导数显式表达式。只是一些微积分的运 算。完成这些后,你可以弄明白如何用矩阵运算写出对所有可能的情况的求和。这项工作会比较乏味,需要一些耐 心,但不用太多的洞察。完成这些后,就可以尽可能地简化了,最后你发现,自己其实就是在做反向传播!所以你可 以将反向传播想象成一种计算所有可能的路径变化率的求和的方式。或者,换句话说,反向传播就是一种巧妙地追踪 权重(和偏差)微小变化的传播,抵达输出层影响代价函数的技术。 现在我不会继续深入下去。因为这项工作比较无聊。如果你想挑战一下,可以尝试与喜爱。即使你不去尝试,我也希 望这种思维方式可以让你能够更好地理解反向传播。 那其他的一些神秘的特性呢——反向传播如何在一开始被发现的?实际上,如果你跟随我刚刚给出的观点,你其实是可 以发现反向传播的一种证明的。不幸的是,证明会比本章前面介绍的证明更长和更加的复杂。那么,前面那个简短 (却更加神秘)的证明如何被发现的?当你写出来所有关于长证明的细节后,你会发现其实里面包含了一些明显的可 以进行改进的地方。然后你进行一些简化,得到稍微简短的证明,写下来。然后又能发现一些更加明显的简化。进过 几次迭代证明改进后,你会发现最终的简单却看起来奇特的证明,因为你移除了很多构造的细节了!老实告诉你,其 实最早的证明的出现也不是太过神秘的事情。因为那只是很多对简化证明的艰辛工作的积累。 原文: https://tigerneil.gitbooks.io/neural-networks-and-deep-learning-zh/content/chapter2.html 本文档使用 书栈(BookStack.CN) 构建 - 22 -

23.第三章 改进神经网络的学习方法(上) 第三章 改进神经网络的学习方法(上) 第三章 改进神经网络的学习方法(上) 当一个高尔夫球员刚开始学习打高尔夫时,他们通常会在挥杆的练习上花费大多数时间。慢慢地他们才会在基本的挥 杆上通过变化发展其他的击球方式,学习低飞球、左曲球和右曲球。类似的,我们现在仍然聚焦在反向传播算法的理 解上。这就是我们的“基本挥杆”——神经网络中大部分工作学习和研究的基础。本章,我会解释若干技术能够用来提升 我们关于反向传播的初级的实现,最终改进网络学习的方式。 本章涉及的技术包括:更好的代价函数的选择——交叉熵 代价函数;四中规范化方法(L1 和 L2 规范化,dropout 和训练数据的人工扩展),这会让我们的网络在训练集之外的数据上更好地泛化;更好的权重初始化方法;还有帮助 选择好的超参数的启发式想法。同样我也会再给出一些简要的其他技术介绍。这些讨论之间的独立性比较大,所有你 们可以随自己的意愿挑着看。另外我还会在代码中实现这些技术,使用他们来提高在第一章中的分类问题上的性能。 当然,我们仅仅覆盖了大量已经在神经网络中研究发展出的技术的一点点内容。此处我们学习深度学习的观点是想要 在一些已有的技术上入门的最佳策略其实是深入研究一小部分最重要那些的技术点。掌握了这些关键技术不仅仅对这 些技术本身的理解很有用,而且会深化你对使用神经网络时会遇到哪些问题的理解。这会让你们做好在需要时快速掌 握其他技术的充分准备。 交叉熵代价函数 我们大多数人觉得错了就很不爽。在开始学习弹奏钢琴不久后,我在一个听众前做了处女秀。我很紧张,开始时将八 度音阶的曲段演奏得很低。我很困惑,因为不能继续演奏下去了,直到有个人指出了其中的错误。当时,我非常尴 尬。不过,尽管不开心,我们却能够因为明显的犯错快速地学习到正确的东西。你应该相信下次我再演奏肯定会是正 确的!相反,在我们的错误不是很好的定义的时候,学习的过程会变得更加缓慢。 理想地,我们希望和期待神经网络可以从错误中快速地学习。在实践中,这种情况经常出现么?为了回答这个问题, 让我们看看一个小例子。这个例子包含一个只有一个输入的神经元: 我们会训练这个神经元来做一件非常简单的事:让输入 转化为 。当然,这很简单了,手工找到合适的权重和偏差就 可以了,不需要什么学习算法。然而,看起来使用梯度下降的方式来学习权重和偏差是很有启发的。所以,我们来看 看神经元如何学习。 为了让事情确定化,我会首先将权重和偏差初始化为 和 。这些就是一般的开始学习的选择,并没有任何刻意的想 法。一开始的神经元的输出是 ,所以这离我们的目标输出 还差得很远。点击右下角的“运行”按钮来看看神经元如何 学习到让输出接近 的。注意到,这并不是一个已经录好的动画,你的浏览器实际上是正在进行梯度的计算,然后使用 梯度更新来对权重和偏差进行更新,并且展示结果。设置学习率 进行学习一方面足够慢的让我们跟随学习的过程,另 一方面也保证了学习的时间不会太久,几秒钟应该就足够了。代价函数是我们前面用到的二次函数,。这里我也会给 出准确的形式,所以不需要翻到前面查看定义了。注意,你可以通过点击 “Run” 按钮执行训练若干次。 本文档使用 书栈(BookStack.CN) 构建 - 23 -

24.第三章 改进神经网络的学习方法(上) 本文档使用 书栈(BookStack.CN) 构建 - 24 -

25.第三章 改进神经网络的学习方法(上) 我们这里是静态的例子,在原书中,使用的动态示例,所以为了更好的效果,请参考原书的此处动态示例。 正如你所见,神经元快速地学到了使得代价函数下降的权重和偏差,给出了最终的输出为 。这虽然不是我们的目标输 出 ,但是已经挺好了。假设我们现在将初始权重和偏差都设置为 。此时初始输出为 ,这是和目标值的差距相当大 的。现在看看神经元学习的过程。点击“Run” 按钮: 本文档使用 书栈(BookStack.CN) 构建 - 25 -

26.第三章 改进神经网络的学习方法(上) 本文档使用 书栈(BookStack.CN) 构建 - 26 -

27.第三章 改进神经网络的学习方法(上) 本文档使用 书栈(BookStack.CN) 构建 - 27 -

28.第三章 改进神经网络的学习方法(上) 虽然这个例子使用的了同样的学习率(),我们可以看到刚开始的学习速度是比较缓慢的。对前 左右的学习次数,权 重和偏差并没有发生太大的变化。随后学习速度加快,与上一个例子中类似了,神经网络的输出也迅速接近 。 强烈建议参考原书的此处动态示例感受学习过程的差异。 这种行为看起来和人类学习行为差异很大。正如我在此节开头所说,我们通常是在犯错比较明显的时候学习的速度最 快。但是我们已经看到了人工神经元在其犯错较大的情况下其实学习很有难度。而且,这种现象不仅仅是在这个小例 子中出现,也会再更加一般的神经网络中出现。为何学习如此缓慢?我们能够找到缓解这种情况的方法么? 为了理解这个问题的源头,想想神经元是按照偏导数( 和 )和学习率()的乘积来改变权重和偏差的。所以,我们 在说“学习缓慢”时,实际上就是说这些偏导数很小。理解他们为何这么小就是我们面临的挑战。为了理解这些,让我 们计算偏导数看看。我们一直在用的是二次代价函数,定义如下 其中 是神经元的输出,其中训练输入为 , 则是目标输出。显式地使用权重和偏差来表达这个,我们有 ,其中 。 使用链式法则来求偏导数就有: 本文档使用 书栈(BookStack.CN) 构建 - 28 -

29.第三章 改进神经网络的学习方法(上) 其中我已经将 和 代入了。为了理解这些表达式的行为,让我们仔细看 这一项。首先回忆一下 函数图像: 我们可以从这幅图看出,当神经元的输出接近 的时候,曲线变得相当平,所以 就很小了。方程 (55) 和 (56) 也 告诉我们 和 会非常小。这其实就是学习缓慢的原因所在。而且,我们后面也会提到,这种学习速度下降的原因实际 上也是更加一般的神经网络学习缓慢的原因,并不仅仅是在这个特例中特有的。 引入交叉熵代价函数 那么我们如何解决这个问题呢?研究表明,我们可以通过使用交叉熵代价函数来替换二次代价函数。为了理解什么是 交叉熵,我们稍微改变一下之前的简单例子。假设,我们现在要训练一个包含若干输入变量的的神经元, 对应的权重 为 和偏差,: 神经元的输出就是 ,其中 是输入的带权和。我们如下定义交叉熵代价函数: 其中 是训练数据的总数,对所有的训练数据 和 对应的目标输出 进行求和。 本文档使用 书栈(BookStack.CN) 构建 - 29 -