大多数人毫不费力地将这些数字识别为 504192。那种轻松 具有欺骗性。在我们大脑的每个半球,人类都有一个主要的 视觉皮层,也称为 V1,包含 1.4 亿个神经元,具有 它们之间的数百亿个连接。然而,人类的视觉 不仅涉及 V1,还涉及一整套视觉皮层 - V2, V3、V4 和 V5 - 执行逐渐更复杂的图像处理。 我们的脑海中携带着一台超级计算机,它经过进化而调整 数亿年,并且极好地适应了理解 视觉世界。识别手写数字并不容易。相反,我们 人类非常善于理解我们的 眼睛告诉我们。但几乎所有的工作都是无意识地完成的。所以 我们通常不会意识到我们的视觉系统是一个多么棘手的问题 解决。
如果您 尝试编写计算机程序来识别类似的数字 以上。当我们自己做时看似容易的事情,突然变得容易了 非常困难。关于我们如何识别形状的简单直觉 - “9 的顶部有一个环,底部有一个垂直笔画 对“——事实证明,用算法来表达并不是那么简单。 当您尝试使这些规则精确时,您很快就会迷失在 例外、警告和特殊情况的泥潭。看来 绝望的。
神经网络以不同的方式处理问题。这个想法是 取大量手写数字,称为训练 例子
然后开发一个可以从这些培训中学习的系统 例子。换句话说,神经网络使用这些示例来 自动推断识别手写数字的规则。 此外,通过增加训练样本的数量, Network 可以了解有关手写的更多信息,从而提高其准确性。 因此,虽然我在上面只展示了 100 个训练数字,但也许我们可以 通过使用数千个甚至 数百万或数十亿个训练示例。
在本章中,我们将编写一个计算机程序,实现一个神经 学习识别手写数字的网络。该程序是 只有 74 行长,并且不使用特殊的神经网络库。但 这个简短的程序可以识别准确率超过 96 的数字 %,无需人工干预。此外,在后面的章节中 我们将开发可以将准确率提高到 99% 以上的想法。在 事实上,最好的商业神经网络现在已经非常出色,以至于它们 被银行用于处理支票,被邮局用于识别 地址。
我们专注于手写识别,因为它非常出色 原型问题,用于一般学习神经网络。作为 原型 它击中了一个最佳点:它具有挑战性 - 它不小 识别手写数字的壮举 - 但这并不像 需要一个极其复杂的解决方案,或者巨大的 计算能力。此外,这是开发更多的好方法 高级技术,例如深度学习。因此,在整个 书中,我们将反复回到笔迹的问题上 识别。在本书的后面,我们将讨论这些想法可能是怎样的 应用于计算机视觉中的其他问题,也适用于语音, 自然语言处理和其他领域。
当然,如果这一章的重点只是写一台计算机 程序来识别手写数字,那么该章节将是 短得多!但在此过程中,我们将发展出许多关于 神经网络,包括两种重要的人工神经元 (感知器和 S 样神经元)和标准学习 神经网络的算法,称为随机梯度下降。 在整个过程中,我专注于解释为什么事情会以这种方式完成 他们是,并且需要构建您的神经网络直觉。那 需要比我只介绍基本 正在发生的事情的机制,但更深的是值得的 理解你会达到。在回报中,到 章节中,我们将了解什么是深度学习,以及 为什么重要。
什么是神经网络?首先,我将介绍一种 称为感知器的人工神经元。 感知器是由科学家弗兰克在 1950 年代和 1960 年代开发的 罗森布拉特,受到沃伦早期工作的启发 麦卡洛克和沃尔特 皮茨。今天,使用其他 人工神经元的模型 - 在本书中,以及许多现代作品中 在神经网络上,使用的主要神经元模型称为 S 形神经元。我们很快就会讨论 S 形神经元。但要 了解为什么 S 样神经元是这样定义的,这是 值得花时间先了解感知器。
那么感知器是如何工作的呢?感知器接受多个二进制输入,,并生成单个二进制输出:
这是基本的数学模型。一种你可以考虑 Perceptron 是一种通过权衡来做出决策的设备 证据。让我举个例子。这不是一个非常现实的例子, 但这很容易理解,我们很快就会变得更加现实 例子。假设周末快到了,并且您已经听说过 你的城市将举办奶酪节。你喜欢奶酪, 并试图决定是否参加音乐节。你 可能会通过权衡三个因素来做出决定:
现在,假设你绝对喜欢奶酪,以至于你很开心 即使你的男朋友或女朋友是 不感兴趣,音乐节很难到达。但也许你 真的很讨厌坏天气,你不可能去参加音乐节 如果天气不好。您可以使用感知器对此类 决策。一种方法是选择权重天气,以及和对于其他条件。 较大的表示天气对 你,远不止你的男朋友或女朋友加入你,或者 公共交通的近度。最后,假设您选择一个 threshold 为对于感知器。通过这些选择, Perceptron 实现所需的决策模型,输出只要天气好,以及每当天气不好时。 无论是你的男朋友还是 女朋友想走,或者附近是否有公共交通。
通过改变权重和阈值,我们可以得到不同的模型 的决策。例如,假设我们改为选择阈值 之.然后,感知器将决定您应该转到 节日,只要天气好或两者兼而有之 音乐节靠近公共交通和你的男朋友或 女朋友愿意加入你。换句话说,它将是一个 不同的决策模式。降低阈值意味着 你更愿意去参加音乐节。
显然,感知器并不是一个完整的人类模型 决策!但这个例子说明了感知器 可以权衡各种证据以做出决定。 而且,一个复杂的感知器 可以做出非常微妙的决定:
顺便说一句,当我定义感知器时,我说感知器具有 只有一个输出。在上面的网络中,感知器看起来像 他们有多个输出。事实上,它们仍然是单输出。 多个输出箭头只是一种有用的方式,用于指示 感知器的输出被用作多个 其他感知器。它比绘制单个输出要简单 行,然后拆分。
让我们简化描述感知器的方式。条件很麻烦,我们可以做两个 符号更改以简化它。 第一个变化是写作为点积,, 哪里和是向量,其分量是权重, inputs 中。第二个变化是将阈值移动到 不等式的另一面,并用所谓的 感知器的偏差,.使用偏差而不是阈值, Perceptron 规则可以是 重写:
我已经将感知器描述为一种权衡证据的方法
决定。使用感知器的另一种方法是计算
我们通常认为是底层的基本逻辑函数
计算、 、 和 等函数。例如,假设我们有一个具有两个
inputs,每个输入的权重AND
OR
NAND
,总体偏差为.这是我们的
感知器:
NAND
该示例表明,我们可以使用 perceptrons 来计算
简单的逻辑函数。
事实上,我们可以使用
感知器网络来计算任何逻辑函数。
原因是 gate 是通用的
computation 的 TIME 中,也就是说,我们可以在 Gates 之外构建任何计算。例如,我们可以使用 gates 来
构建一个加 2 位的电路,NAND
NAND
NAND
NAND
和.这需要
计算按位和,,以及 carry bit
,设置为当两者和是,即 carry
bit 只是按位乘积:
NAND
,总体偏差为.下面是生成的网络。注意
我稍微移动了对应于右下角门的感知器,只是为了更容易绘制箭头
在图上:这个感知器网络的一个值得注意的方面是 output
从最左侧的感知器到最底部的输入使用两次
感知器。当我定义感知器模型时,我没有说
这种双输出到同一位置是允许的。实际上
这并不重要。如果我们不想允许这种事情发生,
然后,可以简单地将这两行合并为一行
连接,而不是两个 -2 的连接
权重。(如果你觉得这不明显,你应该停下来证明
对你自己来说,这是等价的。随着这一变化,网络
如下所示,所有未标记的权重等于 -2,所有偏差
等于 3,单个权重为 -4,如下所示:到目前为止,我一直在绘制类似NAND
adder 示例演示了感知器网络如何
用于模拟包含许多门的电路。和
因为门对于计算是通用的,所以它遵循
感知器对于计算也是通用的。NAND
NAND
感知器的计算普遍性同时是
令人放心和失望。这让人放心,因为它告诉我们
感知器网络可以像任何其他计算一样强大
装置。但这也令人失望,因为它让它看起来像
尽管感知器只是一种新型的门。
这算不上什么大新闻!NAND
然而,情况比这种观点所暗示的要好。它转
我们可以设计学习
算法,这些算法可以
自动调整人工网络的权重和偏差
神经元。这种调整是对外部刺激的响应,没有
程序员的直接干预。这些学习算法使
我们以一种与
传统逻辑门。而不是显式布置电路
of 和其他门,我们的神经网络可以简单地学习
解决问题,有时是极其
难以直接设计常规电路。NAND
学习算法听起来很棒。但是我们怎么能设计出这样的 算法?假设我们有一个 我们想用来学习解决某些问题的感知器。为 例如,网络的输入可能是来自 数字的扫描手写图像。我们希望网络能够 学习权重和偏差,以便正确地从网络输出 对数字进行分类。为了了解学习是如何运作的,假设我们制作 网络中某些权重(或偏差)的微小变化。我们想要什么 喜欢是这个微小的重量变化只会导致一个小的 网络输出的相应变化。正如我们将在 片刻,此属性将使学习成为可能。示意图上, 这是我们想要的(显然这个网络太简单了,无法做到 手写识别!
如果权重(或偏差)的微小变化确实只会导致 output 的一个小变化,那么我们就可以利用这个事实来修改 权重和偏差,使我们的网络以 要。例如,假设网络错误地将 image 显示为 “8”,而它应该是 “9”。我们可以弄清楚怎么做 对权重和偏差进行小幅更改,以便网络获得 离将图像归类为“9”更近了一点。然后我们会 重复此操作,一遍又一遍地更改 Weight(权重)和 Bias(偏差)以生成 越来越好的输出。网络将正在学习。
问题在于,当我们的网络包含 感知器。事实上,任何 网络中的单个感知器有时会导致 那个感知器完全翻转,比如说从自.那个翻转 可能会导致网络其余部分的行为完全 以某种非常复杂的方式进行更改。因此,虽然您的“9”现在可能是 分类正确,则网络在所有其他 图像可能在一些难以控制的地方已经完全改变了 道路。这使得很难看到如何逐渐修改 权重和偏差,以便网络更接近所需的 行为。也许有一些聪明的方法可以解决这个问题 问题。但是,我们如何才能获得一个 感知器来学习。
我们可以通过引入一种新型的人工 神经元称为 S 形神经元。 S 样神经元类似于感知器,但经过修改后,小 它们的权重和偏差的变化只会导致它们的 输出。这是允许 sigmoid 网络的关键事实 神经元学习。
好的,让我描述一下 S 形神经元。我们将描绘 sigmoid 神经元,就像我们描述感知器一样:
乍一看,S 形神经元看起来与感知器非常不同。 sigmoid 函数的代数形式可能看起来不透明,并且 如果您还不熟悉它,请禁止。事实上,有 感知器和 S 样神经元之间有许多相似之处,并且 sigmoid 函数的代数形式更像是一个 技术细节而不是理解的真正障碍。
为了理解与感知器模型的相似性,假设是一个较大的正数。然后等等.换句话说,当大且为正,则 S 形神经元的输出 大约为,就像感知器一样。 另一方面,假设非常消极。 然后和.因此,当非常消极,即 S 样神经元的行为 也非常接近感知器。只有当大小适中,与感知器有很大偏差 型。
的代数形式呢?我们如何理解 那?事实上,确切的不是那么重要 - 什么 真正重要的是绘制时函数的形状。这是 形状:
此形状是阶跃函数的平滑版本:
如果实际上是一个阶跃函数,然后是 S 形神经元 将是一个感知器,因为输出将是或取决于为阳性或 阴性**实际上,当感知器 输出,而 step 函数输出.所以,严格来说 也就是说,我们需要在那个点修改 Step 函数。 但你明白了。.通过使用实际的函数 we get,如上所述,一个平滑的感知器。事实上 而是功能是关键事实, 而不是它的详细形式。平滑度意味着小 变化在 weights 和在 bias 中 将 产生小小的改变在 神经元。事实上,微积分告诉我们是 很好地近似于
如果是这真的很重要,而不是确切的 表单,那么为什么使用用于在 方程 (3) ?事实上,在本书的后面,我们将 偶尔考虑输出为对于其他一些激活函数 .最主要的是 当我们使用不同的激活函数时,情况会发生变化,因为 中偏导数的特定值 方程 (5) 改变。事实证明,当我们 稍后使用将简化 代数,仅仅因为指数在以下时间具有可爱的性质 分化。无论如何,常用于 神经网络,并且是我们最常使用的激活函数 这本书。
我们应该如何解释 sigmoid 神经元的输出?明显地 感知器和 S 样神经元之间的一大区别是 sigmoid 神经元不仅输出或.他们可以有 as output 之间的任何实数和,因此和是合法的输出。这可能很有用,因为 例如,如果我们想使用 Output 值来表示平均值 输入神经网络的图像中像素的强度。但 有时这可能会很麻烦。假设我们想要 network 来指示“输入图像是 9”或“输入 图像不是 9 英寸。显然,如果 output 是一个或,就像在感知器中一样。但在实践中,我们可以 设置一个约定来处理这个问题,例如,通过决定 解释至少表示 “9”,并且任何 输出小于表示 “not a 9”。我会永远 明确说明何时使用此类约定,因此它不应该 造成任何混淆。
在下一节中,我将介绍一个神经网络,它可以执行 对手写数字进行分类相当不错。准备 那,它有助于解释一些让我们命名不同的术语 网络的一部分。假设我们有网络:
网络中输入和输出层的设计通常是 简单。例如,假设我们尝试确定 手写图像是否描绘了“9”。一种自然的方式 设计网络是为了编码图像像素的强度 到输入神经元中。如果图像是由灰度 image,那么我们就有input 神经元,使用 强度在和.输出 layer 将仅包含一个神经元,其输出值为 less 比指示“输入图像不是 9”,并且值更大 比指示“输入图像为 9”。
虽然神经网络的输入层和输出层的设计是 通常很简单,但设计可能有相当多的艺术 隐藏图层。特别是,无法对设计进行总结 使用一些简单的经验法则处理隐藏图层。 相反,神经网络研究人员开发了许多设计 隐藏层的启发式方法,帮助人们了解行为 他们想从他们的网中出来。例如,可以使用这样的启发式方法 帮助确定如何权衡隐藏层的数量 训练网络所需的时间。我们将遇到几个这样的 设计启发式方法。
到目前为止,我们一直在讨论神经网络,其中 一个层用作下一个层的输入。这样的网络是 称为前馈神经网络。这意味着网络中没有环路 - 信息总是向前反馈,从不反馈。如果我们确实有 循环中,我们最终会遇到这样的情况:函数依赖于输出。那会很难理解,而且 所以我们不允许这样的循环。
但是,还有其他人工神经网络模型,其中 反馈循环是可能的。这些模型称为递归模型 神经网络。这些模型中的想法是让 在变为静止状态之前,会触发一段时间。 这种放电可以刺激其他神经元,这些神经元可能会发射一会儿 后来,也是有限的持续时间。这会导致更多的神经元 fire,所以随着时间的推移,我们得到了一连串的神经元放电。循环 不会在这样的模型中引起问题,因为只有神经元的输出 在稍后的某个时间影响其输入,而不是立即影响。
递归神经网络的影响力不如前馈 网络,部分原因是循环网络的学习算法 (至少到目前为止)没有那么强大。但循环网络是 仍然非常有趣。它们在精神上更接近我们的 大脑比前馈网络工作。而且有可能 循环网络可以解决一些重要的问题,这些问题只能是 通过前馈网络解决非常困难。但是,要 限制我们的范围,在本书中,我们将专注于 广泛使用的前馈网络。
定义了神经网络之后,让我们回到手写 识别。我们可以拆分识别手写体的问题 digits 转换为两个子问题。首先,我们想要一种打破 图像包含许多数字,并生成一系列单独的图像,每个图像 包含单个数字。例如,我们想打破图像
分成 6 个单独的图像,
我们人类解决了这个细分 问题很容易,但具有挑战性 让计算机程序正确地分解图像。一旦 图像已被分割,则程序需要对每个 单个数字。因此,例如,我们希望我们的程序能够 认识到上面的第一个数字,
是 5。
我们将专注于编写一个程序来解决第二个问题,即 对单个数字进行分类。我们这样做是因为事实证明 分割问题并不难解决,只要你有一个 对单个数字进行分类的好方法。有很多方法 解决分割问题。一种方法是多次尝试 使用单个数字分割图像的不同方法 classifier 对每个试验分段进行评分。试验细分 如果单个数字分类器对 它在所有 Segments 中的分类,如果分类器 在一个或多个区段中遇到很多问题。这个想法是 如果分类器在某处遇到问题,则可能是 由于选择不正确的分段而出现问题。 这个想法和其他变体可用于解决分割问题 问题相当好。因此,与其担心细分,不如 专注于开发一个可以解决更多 有趣而困难的问题,即识别个体 手写数字。
为了识别单个数字,我们将使用三层神经 网络:
网络的输入层包含编码 输入像素。如下一节所述,我们的训练数据 因为这个网络将由许多由像素图像 扫描的手写数字,因此输入图层包含神经元。为简单起见,我省略了大部分input 神经元。输入像素为灰度, 的值为表示白色,则值为表示黑色,介于两者之间表示逐渐 灰色阴影变暗。
网络的第二层是隐藏层。我们表示 此隐藏层中的神经元数,我们将进行实验 具有不同的值.所示示例说明了一个小的 hidden 图层,仅包含神经元。
网络的输出层包含 10 个神经元。如果第一个 神经元触发,即有一个输出,则表示 网络认为该数字是.如果第二个神经元 触发,则表明网络认为该数字是.等等。更准确地说,我们对输出进行编号 神经元来自通过,并找出哪个神经元具有 最高激活值。如果该神经元是,例如,神经元数, 然后我们的网络会猜测输入数字是一个.等等 对于其他输出神经元。
您可能想知道为什么我们使用输出神经元。毕竟,目标 的网络是告诉我们哪个数字 () 对应于输入图像。一种看似自然的方式 就是只用output 神经元,将每个神经元视为具有 binary 值,具体取决于神经元的输出是否更接近或.四个神经元就足以编码答案,因为大于输入数字的 10 个可能值。 为什么我们的网络应该使用神经元?不是吗 低 效?最终的理由是实证的:我们可以尝试一下 两种网络设计,事实证明,对于这个特定的 问题,网络与输出神经元学会识别 数字优于网络输出神经元。但是那个 让我们想知道为什么使用Output Neurons 效果更好。 是否有一些启发式方法可以提前告诉我们我们应该 使用-output 编码,而不是-output 编码?
要了解我们为什么要这样做,思考一下 网络从第一原则开始做。首先考虑以下情况 我们使用输出神经元。让我们专注于第一个输出 神经元,它试图确定该数字是否为.它通过权衡隐藏层的证据来实现这一点 神经元。那些隐藏的神经元在做什么?好吧,假设 为了争论隐藏层中的第一个神经元检测到 是否存在如下图像:
它可以通过对与 图像,并且仅对其他输入进行较轻的加权。在类似的 方式,为了论证起见,让我们假设第二个、第三个、 隐藏层中的第四个神经元检测 存在以下图像:
您可能已经猜到了,这四张图片共同构成了我们在前面显示的数字行中看到的图像:
因此,如果这四个隐藏的神经元都在发射,那么我们可以得出结论 该数字是.当然,这并不是唯一的类型 我们可以用来断定该图像是-我们 可以合法地获得以许多其他方式(例如,通过 上述图像的翻译,或轻微失真)。但是它 似乎可以肯定地说,至少在这种情况下,我们会得出结论, input 是.
假设神经网络以这种方式运行,我们可以给出一个 为什么最好拥有输出 网络,而不是.如果我们有outputs,然后是第一个 output neuron 将尝试确定最高有效位 的数字是。没有简单的方法可以将这一点最紧密地联系起来 有效位转换为简单形状,如上所示。这很难 想象一下,组件形状 的数字将与 (比如) 最高有效位密切相关 在输出中。
现在,说了这么多,这都只是一种启发式方法。什么都没说 三层神经网络必须按照 I 描述,隐藏的神经元检测简单的组件形状。 也许一个聪明的学习算法会找到一些权重分配 那让我们只使用输出神经元。但作为启发式方法 认为我已经描述的运行得很好,可以为您节省很多 设计良好的神经网络架构的时间。
现在我们已经为神经网络设计了,它如何学习 识别数字?我们首先需要一个要学习的数据集 from - 所谓的训练数据集。我们将使用 MNIST 数据集,其中包含数万张扫描图像 手写数字及其正确分类。 MNIST 的名称来源于这样一个事实,即它是两个 NIST 采集的数据集, 美国国家标准研究院 (National Institute of Standards) 和 科技。以下是来自 MNIST 的一些图片:
如您所见,这些数字实际上与显示的数字相同 在本章的开头作为挑战 来识别。当然,在测试我们的网络时,我们会要求它 识别不在训练集中的图像!
MNIST 数据分为两部分。第一部分包含 60,000 要用作训练数据的图像。扫描这些图像 来自 250 人的笔迹样本,其中一半是美国人口普查 该局的员工,其中一半是高中生。这 图像为灰度图像,大小为 28 x 28 像素。的第二部分 MNIST 数据集是 10,000 张图像,用作测试数据。再 这些是 28 x 28 灰度图像。我们将使用测试数据来 评估我们的神经网络学习识别数字的能力。 为了使这成为一个好的性能测试,测试数据取自 与原始训练数据不同的 250 人集 (尽管仍然是一个由人口普查局员工和高 学校学生)。这有助于我们确信我们的系统可以 识别 ping 期间未看到其写作的人的数字 训练。
我们将使用表示法来表示训练输入。这将是 方便关注每个培训输入作为-维度向量。向量中的每个条目都表示灰色 值。我们将表示相应的 期望输出哪里是一个-维度向量。 例如,如果特定训练图像,描绘了然后是所需的输出 网络。请注意,这是转置操作,将 row 向量转换为普通(列)向量。
我们想要的是一种算法,它可以让我们找到权重和偏差 因此,网络的输出近似为了所有人 培训输入.量化我们实现这一目标的程度 我们定义了一个成本函数**有时称为损失函数或目标函数。我们使用术语 cost 在本书中发挥作用,但您应该注意其他 术语,因为它经常用于研究论文和其他 神经网络的讨论。:
为什么要引入二次成本?毕竟,我们不主要不是吗 对由 网络?为什么不尝试直接最大化这个数字,而不是 最小化像二次成本这样的代理度量?问题 也就是说,正确分类的图像数量并不是平稳的 网络中权重和偏差的函数。在大多数情况下, 对 WEIGHTS 和 Biases 进行微小的更改不会导致任何更改 完全取决于正确分类的训练图像的数量。那 使很难弄清楚如何更改权重和偏差 以获得改进的性能。如果我们改用平滑成本函数 就像二次成本一样,事实证明很容易弄清楚如何 对权重和偏差进行小幅更改,以获得 成本的改善。这就是为什么我们首先关注最小化 二次成本,只有在那之后,我们才会检查分类 准确性。
即使我们想使用平滑的 cost 函数,您仍然可以 想知道为什么我们选择 方程 (6) .这不是一个相当的广告吗 Hoc 选择?也许如果我们选择不同的 cost 函数,我们会得到 一组完全不同的最小化权重和偏差?这是一个 Valid 关注点,稍后我们将重新审视 cost 函数,并将 一些修改。但是,的二次成本函数 方程 (6) 效果非常好 了解神经网络学习的基础知识,因此我们将 现在坚持下去。
回顾一下,我们训练神经网络的目标是找到权重 和最小化二次成本函数的偏差.这 是一个摆定合理的问题,但它有很多分散注意力的结构 如目前所提出的那样 - 对和作为权重 和偏见,则函数中,则 选择网络架构、MNIST 等。事实证明, 我们可以通过忽略其中的大部分来理解大量的 结构,只专注于最小化方面。所以对于 现在我们要忘记所有关于成本的具体形式 函数、与神经网络的连接等。相反 我们将想象我们只是被赋予了 many 的函数 变量,我们希望最小化该函数。我们将 开发一种称为梯度下降的技术,可以使用 来解决此类最小化问题。然后我们回到 特定函数。
好的,假设我们试图最小化一些函数. 这可以是许多变量的任何实值函数.请注意,我已将和表示法强调这可以是任何函数 - 我们不是 特别是不再在神经网络上下文中思考。自 最小化想象会有所帮助作为 2 的函数 variables,我们将其称为和:
我们想要的是找到实现其全局最小值。现在 当然,对于上面绘制的函数,我们可以盯着图 并找到最小值。从这个意义上说,我展示的函数可能有点太简单了!通用函数,可以是 complex 函数,通常不会是 可以只盯着图表找到最小值。
解决这个问题的一种方法是使用 calculus 来尝试找到 最低的分析。我们可以计算导数,然后尝试使用 他们找到是一个极值。幸运的是 在以下情况下可能会起作用是仅包含一个或几个变量的函数。但 当我们有更多的变量时,它会变成一场噩梦。而对于 神经网络中,我们通常需要更多的变量 - 最大的神经网络具有依赖于数十亿的 cost 函数 的权重和偏差。使用 Calculus 尽量减少这种情况是行不通的!
(在断言我们将通过想象获得洞察力之后作为 函数,我已经把 2 个变量转了两次 段落,然后说,“嘿,但如果它是更多 比两个变量多吗?很抱歉。请相信我 想象确实有帮助作为 2 的函数 变量。只是碰巧有时候那张照片会坏掉, 最后两段是关于这种细分的。好 思考数学通常涉及处理多个直觉 图片, 了解何时适合使用每张图片以及何时 不是。
好吧,所以微积分不起作用。幸运的是,有一个美丽的 类比,这表明一种算法运行良好。我们开始 通过将我们的功能视为一种山谷。如果你只是眯着眼睛 稍微看一下上面的情节,这应该不会太难。而我们 想象一个球从山谷的斜坡上滚下来。我们的日常 经验告诉我们,球最终会滚到底部 山谷的。也许我们可以用这个想法来找到一个 函数的最小值?我们会随机选择一个起点 一个(假想的)球,然后模拟球的运动 滚到山谷的底部。我们可以进行这种模拟 只需计算导数(也许还有一些二阶导数) 之- 这些衍生品会告诉我们我们需要知道的一切 关于山谷的当地“形状”,以及我们的球如何 应该滚动。
根据我刚才写的内容,你可能会认为我们会 试图写下球的牛顿运动方程, 考虑摩擦力和重力的影响,等等。实际上 我们不会那么认真地对待滚球的类比 - 我们正在设计一种算法来最小化,而不是开发 精确模拟物理定律!球眼视图为 旨在激发我们的想象力,而不是限制我们的思考。所以 与其深入探讨物理学的所有杂乱细节,不如简单地 问问自己:如果我们被宣布为上帝一天,并且能够弥补 我们自己的物理定律,决定球应该如何滚动, 我们可以选择什么或哪些运动定律来使它成为 球总是滚到谷底?
为了更准确地回答这个问题,让我们考虑一下会发生什么 当我们少量移动球时在direction 和少量在方向。 微积分告诉我们更改如下:
总而言之,梯度下降算法的工作原理是 重复计算梯度,然后向相反方向移动,“落下”山谷的斜坡。 我们可以像这样可视化它:
请注意,使用此规则时,梯度下降不会重现实数 身体运动。在现实生活中,球有动量,而这种动量 可能会允许它在斜坡上滚动,甚至(暂时)滚动 上坡。只有在摩擦作用作用下,球 保证会滚入山谷。相比之下,我们的 选择只是说“现在就下去”。这仍然是一个 找到最小值的好规则!
要使梯度下降正常工作,我们需要选择 学习率要小 足够了,方程 (9) 是一个很好的近似值。如果 我们没有,我们最终可能会得到,这显然会 不要好!同时,我们不希望太小, 因为这将进行更改tiny,因此 梯度下降算法将非常缓慢地工作。在实践中 实现经常变化,因此 方程 (9) 仍然是一个很好的近似值,但 算法不会太慢。我们稍后会看到这个 工程。
我已经解释了是 2 的函数 变量。但是,事实上,即使是更多变量的函数。特别假设是变量.然后是变化在由 A Small Change 制作是
事实上,从某种意义上说,梯度下降是最佳的 策略来搜索最小值。假设我们正在尝试 进行移动在位置以减少多达 可能。这相当于最小化.我们将限制移动的大小,以便对于一些小的固定.在其他 words,我们想要一个固定大小的一小步 move,而我们是 尝试找到减小的移动方向多达 可能。可以证明,选择哪 最大限度 地 减少是, 哪里由大小决定 约束.所以梯度下降可以是 被视为一种朝着执行 most 立即减少.
人们已经研究了梯度下降的许多变化, 包括更接近真实物理球的变体。 这些模拟球的变体有一些优点,但也具有 主要缺点:事实证明需要计算秒 的偏导数,而这可能非常昂贵。了解原因 这很昂贵,假设我们想计算所有第二个部分 衍生物.如果存在 万这样的变量,那么我们需要计算类似 一万亿(即一百万平方)秒的部分 衍生物**实际上,更像是五万亿,因为.不过,你明白了。!那将是 计算成本高。话虽如此,有一些避免的技巧 这种问题,寻找梯度下降的替代方案是 一个活跃的调查领域。但在本书中,我们将使用 gradient descent (and variations) 作为我们在 neural 中学习的主要方法 网络。
我们如何应用梯度下降法在神经网络中学习?这 思路是使用梯度下降来查找权重和偏见这最大限度地降低了 方程 (6) .要了解其工作原理,我们来了解 重述 Gradient descent 更新规则,包括 Weights 和 Biases 替换变量.换句话说,我们现在的“位置” 有组件和和 gradient vector具有 对应组件和.在 组件条款,我们有
应用梯度下降法存在许多挑战 统治。我们将在后面的章节中深入探讨这些。但就目前而言 我只想提一个问题。要了解问题所在 是,让我们回顾一下 方程 (6) .请注意,此成本 function 的形式为,即它是一个 平均超额成本个人版 训练示例。在实践中,要计算梯度我们 需要计算梯度单独用于每个 训练投入,,然后对它们进行平均,.遗憾的是,当训练输入的数量 非常大,这可能需要很长时间,因此会发生学习 慢慢。
一个叫做随机梯度下降的想法可以用来加速 向上学习。这个想法是估计梯度由 计算机科学对于随机选择的训练的小样本 输入。通过对这个小样本进行平均,事实证明我们可以 快速获得真实梯度的良好估计和这个 有助于加快梯度下降,从而加快学习速度。
为了使这些想法更精确,随机梯度下降的工作原理是 随机挑选出一个小数字随机选择的训练 输入。我们将标记这些随机训练输入,并将其称为 mini-batch。提供了示例 大小足够大,我们预计将大致等于总体那是
为了将其与神经网络中的学习明确联系起来,假设和表示神经网络中的权重和偏差。 然后随机梯度下降的工作原理是随机选择一个 选择小批量的训练输入,并使用这些输入进行训练,
顺便说一句,值得注意的是,约定对 成本函数和小批量更新权重和偏差。 在方程 (6) 中 我们调整了总体成本 按因子计算的函数.人们有时会省略,将单个训练示例的成本相加 而不是平均。这在总的 训练样本的数量事先是未知的。如果出现 例如,正在实时生成更多的训练数据。 并且,以类似的方式,小批量更新规则 (20) 和 (21) 有时省略term out 的 前面 总和。从概念上讲,这几乎没有区别,因为 这相当于重新调整学习率.但是当执行 值得关注的不同工作的详细比较。
我们可以将随机梯度下降视为政治 轮询:对小批量进行采样比对小批量进行采样要容易得多 将梯度下降法应用于整个批次,就像执行轮询一样 比进行全面选举更容易。例如,如果我们有一个 大小,然后选择 (比如),这意味着我们将得到一个因子加速估计梯度!当然,估计 不会完美 - 会有统计波动 - 但它 不需要完美:我们真正关心的只是在 有助于减少的总体方向,这意味着我们不会 需要精确计算梯度。在实践中,随机指标 梯度下降是一种常用且强大的技术 在神经网络中学习,它是大多数 学习我们将在本书中开发的技巧。
让我通过讨论有时 bug 刚接触 Gradient Descent 的人。在神经网络中,成本是 当然,这是许多变量的函数 - 所有的权重和偏差 - 所以在某种意义上定义了一个非常高维的表面 空间。有些人挂断电话想:“嘿,我必须能够 可视化所有这些额外的维度”。他们可能会开始担心: “我不能用四个维度来思考,更不用说五个(或五个) 万)”。他们是否缺少什么特殊能力,一些 “真正的”超数学家所具有的能力?当然,答案是 是 NO。即使是大多数专业的数学家也无法想象出 4 尺寸特别好,如果有的话。相反,他们使用的技巧是 是开发其他方式来表现正在发生的事情。那是 正如我们上面所做的:我们使用代数(而不是视觉) 表示弄清楚如何移动以便 减少.善于高维思考的人有 一个包含许多不同技术的心理图书馆 线;我们的代数技巧只是一个例子。这些技术可能会 没有我们在可视化 Three 时习惯的简单性 维度,但是一旦构建了此类技术的库,您 可以变得相当擅长高维思考。我不会深入探讨 更多细节在这里,但如果你有兴趣,那么你可能会喜欢阅读这篇文章 专业数学家对一些技术的讨论 用于在高维度思考。虽然一些技术 讨论的内容相当复杂,大部分最好的内容都是直观的,并且 易于理解,任何人都可以掌握。
好了,让我们编写一个学习如何识别 手写数字,使用随机梯度下降法和 MNIST 训练数据。我们将使用一个简短的 Python (2.7) 程序来执行此操作,只需 74 行代码!我们需要做的第一件事是获取 MNIST 数据。 如果你是 git 用户,那么你可以通过克隆来获取数据 本书的代码仓库
git clone https://github.com/mnielsen/neural-networks-and-deep-learning.git
如果您不使用 git,则可以在此处下载数据和代码。
顺便说一句,当我之前描述 MNIST 数据时,我说它是 拆分为 60000 张训练图像和 10000 张测试图像。那就是 官方 MNIST 描述。实际上,我们将把数据拆分为 略有不同。我们将测试图像保持原样,但将 60000 张图像的 MNIST 训练集分为两部分:一组 50000 张 图像,我们将使用它来训练我们的神经网络,以及一个单独的 10,000 个图像验证集。我们不会 在本章中使用验证数据,但在本书的后面部分,我们将 发现它有助于弄清楚如何设置神经网络的某些超参数 - 比如 learning rate 等,这些不是由我们的 学习算法。尽管验证数据不是 最初的 MNIST 规范,很多人都是这样使用 MNIST 的, 验证数据的使用在神经网络中很常见。当我 从现在开始,我将参考“MNIST 训练数据” 我们的 50,000 张图像数据集,而不是原始的 60,000 张图像数据 设置**如前所述,MNIST 数据集基于两个数据 由 NIST(美国国家研究所)收集的 标准和技术。构造 NIST 数据集 MNIST 被 Yann 剥离并放入更方便的格式 LeCun、Corinna Cortes 和 Christopher JC Burges。请参阅此链接了解更多信息 详。我的存储库中的数据集采用某种形式,使 易于在 Python 中加载和操作 MNIST 数据。我获得了 来自 LISA 机器学习的这种特定形式的数据 蒙特利尔大学实验室 (链接)。.
除了 MNIST 数据,我们还需要一个名为 Numpy 的 Python 库,用于进行快速线性代数。如果你 尚未安装 Numpy,您可以在此处获取它。
让我解释一下神经网络代码的核心功能,在 在下面给出了完整的列表。核心是一个 Network 类,我们用它来表示神经网络。这是我们的代码 用于初始化 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:])]
在此代码中,列表大小包含 相应的层。因此,例如,如果我们想在第一层创建一个具有 2 个神经元的 Network 对象,则 第二层和最后一层中的 1 个神经元,我们将使用 代码:
net = Network([2, 3, 1])
另请注意,偏差和权重存储为 Numpy 的列表 矩阵。因此,例如 net.weights[1] 是一个 Numpy 矩阵 存储连接第二层和第三层神经元的权重。 (这不是第一层和第二层,因为 Python 的列表索引 从 0 开始。由于 net.weights[1] 相当冗长, 我们只表示该矩阵.它是一个矩阵,使得是neuron 的 second 层和neuron 在第三层。此排序 的和指数可能看起来很奇怪 - 肯定会让更多 sense 来交换和指数周围?最大的优势 使用这种排序意味着 神经元的第三层是:
考虑到所有这些,编写计算输出的代码很容易 从 Network 实例。我们首先定义 sigmoid 功能:
def sigmoid(z):
return 1.0/(1.0+np.exp(-z))
然后,我们将前馈方法添加到 Network 类 给定网络的输入 a,则返回 相应的输出**假设输入 a 为 一个 (n, 1) 个 Numpy ndarray,而不是一个 (n,) 向量。其中, n 是网络的输入数量。如果您尝试使用 一个 (n,) 向量作为输入,你会得到奇怪的结果。虽然 使用 (n,) 向量似乎是更自然的选择,使用 (n, 1) ndarray 使得修改 代码一次前馈多个输入,这有时是 方便。.该方法所做的只是应用 方程 (22) 对于每个层:
def feedforward(self, a):
"""Return the output of the network if "a" is input."""
for b, w in zip(self.biases, self.weights):
a = sigmoid(np.dot(w, a)+b)
return a
当然,我们希望 Network 对象执行的主要操作是 要了解。为此,我们将为他们提供一个 SGD 方法,该方法 实现随机梯度下降。这是代码。这是一个 在几个地方有点神秘,但我会在下面分解它,之后 列表。
def SGD(self, training_data, epochs, mini_batch_size, eta,
test_data=None):
"""Train the neural network using mini-batch stochastic
gradient descent. The "training_data" is a list of tuples
"(x, y)" representing the training inputs and the desired
outputs. The other non-optional parameters are
self-explanatory. If "test_data" is provided then the
network will be evaluated against the test data after each
epoch, and partial progress printed out. This is useful for
tracking progress, but slows things down substantially."""
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) 列表,表示训练输入和相应的所需输出。 变量 epochs 和 mini_batch_size 就是你想要的 expect - 要训练的 epoch 数,以及 采样时使用的 mini-batch。eta 是学习率,.如果提供了可选参数 test_data,则 该程序将在每个训练 epoch 之后评估网络, 并打印出部分进度。这对于跟踪进度很有用, 但大大减慢了速度。
代码的工作原理如下。在每个 epoch 中,它以 随机 对训练数据进行随机排序,然后将其划分为小批量 适当大小。这是一种简单的随机采样方法 从训练数据中。然后,对于每个mini_batch我们应用 单步梯度下降。这是由代码 self.update_mini_batch(mini_batch, eta) 完成的,该代码更新 根据 Gradient 的单次迭代的网络权重和偏差 descent,仅使用 mini_batch 中的训练数据。这是 update_mini_batch 方法的代码:
def update_mini_batch(self, mini_batch, eta):
"""Update the network's weights and biases by applying
gradient descent using backpropagation to a single mini batch.
The "mini_batch" is a list of tuples "(x, y)", and "eta"
is the learning rate."""
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)
我现在不打算展示 self.backprop 的代码。 我们将在下一章中研究反向传播的工作原理,包括 self.backprop 的代码。现在,假设它 的行为与声称的一样,返回成本的适当梯度 关联到训练示例 X。
让我们看看完整的程序,包括文档字符串 我在上面省略了。除了 self.backprop 之外,该程序是 不言自明 - 所有繁重的工作都是自己完成的。SGD 和 self.update_mini_batch,我们已经讨论过了。self.backprop 方法利用了一些额外的函数来帮助 在计算梯度时,即 sigmoid_prime,它计算 的导数函数和 self.cost_derivative,我不会在这里介绍。您可以获得 这些的要点(也许还有细节)只需查看 代码和文档字符串。我们将在 下一章。 请注意,虽然该程序看起来很长,但大部分代码都是 文档字符串,旨在使代码易于理解。 事实上,该程序仅包含 74 行非空格, non-comment 代码。所有代码都可以在 GitHub 上找到 这里.
"""
network.py
~~~~~~~~~~
A module to implement the stochastic gradient descent learning
algorithm for a feedforward neural network. Gradients are calculated
using backpropagation. Note that I have focused on making the code
simple, easily readable, and easily modifiable. It is not optimized,
and omits many desirable features.
"""
#### Libraries
# Standard library
import random
# Third-party libraries
import numpy as np
class Network(object):
def __init__(self, sizes):
"""The list ``sizes`` contains the number of neurons in the
respective layers of the network. For example, if the list
was [2, 3, 1] then it would be a three-layer network, with the
first layer containing 2 neurons, the second layer 3 neurons,
and the third layer 1 neuron. The biases and weights for the
network are initialized randomly, using a Gaussian
distribution with mean 0, and variance 1. Note that the first
layer is assumed to be an input layer, and by convention we
won't set any biases for those neurons, since biases are only
ever used in computing the outputs from later layers."""
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):
"""Return the output of the network if ``a`` is input."""
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):
"""Train the neural network using mini-batch stochastic
gradient descent. The ``training_data`` is a list of tuples
``(x, y)`` representing the training inputs and the desired
outputs. The other non-optional parameters are
self-explanatory. If ``test_data`` is provided then the
network will be evaluated against the test data after each
epoch, and partial progress printed out. This is useful for
tracking progress, but slows things down substantially."""
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):
"""Update the network's weights and biases by applying
gradient descent using backpropagation to a single mini batch.
The ``mini_batch`` is a list of tuples ``(x, y)``, and ``eta``
is the learning rate."""
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):
"""Return a tuple ``(nabla_b, nabla_w)`` representing the
gradient for the cost function C_x. ``nabla_b`` and
``nabla_w`` are layer-by-layer lists of numpy arrays, similar
to ``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]
# feedforward
activation = x
activations = [x] # list to store all the activations, layer by layer
zs = [] # list to store all the z vectors, layer by layer
for b, w in zip(self.biases, self.weights):
z = np.dot(w, activation)+b
zs.append(z)
activation = sigmoid(z)
activations.append(activation)
# backward pass
delta = self.cost_derivative(activations[-1], y) * \
sigmoid_prime(zs[-1])
nabla_b[-1] = delta
nabla_w[-1] = np.dot(delta, activations[-2].transpose())
# Note that the variable l in the loop below is used a little
# differently to the notation in Chapter 2 of the book. Here,
# l = 1 means the last layer of neurons, l = 2 is the
# second-last layer, and so on. It's a renumbering of the
# scheme in the book, used here to take advantage of the fact
# that Python can use negative indices in lists.
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):
"""Return the number of test inputs for which the neural
network outputs the correct result. Note that the neural
network's output is assumed to be the index of whichever
neuron in the final layer has the highest activation."""
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):
"""Return the vector of partial derivatives \partial C_x /
\partial a for the output activations."""
return (output_activations-y)
#### Miscellaneous functions
def sigmoid(z):
"""The sigmoid function."""
return 1.0/(1.0+np.exp(-z))
def sigmoid_prime(z):
"""Derivative of the sigmoid function."""
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()
当然,这也可以在单独的 Python 程序中完成,但是 如果你跟着做,在 Python 中可能是最简单的 壳。
加载 MNIST 数据后,我们将设置一个网络,其中包含隐藏的神经元。我们在导入列出的 Python 程序后执行此操作 上面,名为 network,
>>> import network
>>> net = network.Network([784, 30, 10])
最后,我们将使用随机梯度下降法从 30 多个 epoch 的 MNIST training_data学习,小批量大小为 10,以及一个 学习率,
>>> net.SGD(training_data, 30, 10, 3.0, test_data=test_data)
请注意,如果您在阅读时运行代码,则需要 执行时间 - 对于典型的机器(截至 2015 年),它将 可能需要几分钟才能运行。我建议你让一切顺利进行, continue read,并定期检查代码的输出。如果 你很着急,你可以通过减少 epochs,通过减少隐藏神经元的数量,或仅使用 训练数据的一部分。请注意,生产代码会很多, 更快:这些 Python 脚本旨在帮助您理解 神经网络是如何工作的,而不是高性能代码!而且,的 当然,一旦我们训练了一个网络,它就可以非常快速地运行 事实上,几乎在任何计算平台上。例如,一旦我们 为网络学习了一套好的权重和偏差,它可以很容易地 移植以在 Web 浏览器中的 Javascript 中运行,或作为本机应用程序运行 移动设备。无论如何,这里是 神经网络的一次训练运行的输出。文字记录 显示神经正确识别的测试图像的数量 network 进行训练。如您所见,在仅 单个纪元已达到 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
也就是说,经过训练的网络给我们的分类率约为百分之-百分比达到峰值(“第 28 纪元”)!那是 作为第一次尝试,相当令人鼓舞。然而,我应该警告你, 如果你运行代码,那么你的结果不一定会成功 与我的非常相似,因为我们将初始化我们的网络 使用(不同的)随机权重和偏差。要在 本章我采取了三局两胜的做法。
让我们重新运行上面的实验,更改 hidden 的数量 神经元到.与之前的情况一样,如果您正在运行代码 当您继续阅读时,应该警告您,这需要相当长的时间才能 execute (在我的计算机上,每个 Experiment 需要数十秒 training epoch),因此在 代码执行。
>>> net = network.Network([784, 100, 10])
>>> net.SGD(training_data, 30, 10, 3.0, test_data=test_data)
果然,这将结果提高到百分之。至少 在这种情况下,使用更多隐藏的神经元有助于我们变得更好 结果**读者反馈表明 此实验的结果,并且一些训练运行会给出结果 相当糟糕。使用第 3 章中介绍的技术 将大大减少不同 训练运行。.
当然,为了获得这些精度,我必须做出具体的选择 对于训练的 epoch 数、小批量大小和 学习率 /.正如我上面提到的,这些被称为 hyper 参数,以便区分它们 从我们学习中学到的参数(权重和偏差) 算法。如果我们选择不当的超参数,我们可能会变得很糟糕 结果。例如,假设我们将学习率选择为 是,
>>> 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
通常,调试神经网络可能具有挑战性。这是 当 hyper-parameters 的初始选择产生 结果并不比随机噪声好。假设我们尝试成功的 30 隐藏的神经元网络架构,但随着学习 Rate 更改为:
>>> 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
从中可以得到的教训是,调试神经网络 并非微不足道,而且,就像普通编程一样,有一门艺术 到它。你需要学习调试的技巧才能变得更好 来自神经网络的结果。更广泛地说,我们需要开发 用于选择好的超参数和好的架构的启发式方法。 我们将在本书中详细讨论所有这些,包括我如何 选择了上面的超参数。
之前,我跳过了有关如何加载 MNIST 数据的详细信息。 这很简单。为了完整起见,这是代码。这 用于存储 MNIST 数据的数据结构在 文档字符串 - 它是简单明了的东西、元组和列表 的 Numpy ndarray 对象(如果你是 不熟悉 NDARRAYs):
"""
mnist_loader
~~~~~~~~~~~~
A library to load the MNIST image data. For details of the data
structures that are returned, see the doc strings for ``load_data``
and ``load_data_wrapper``. In practice, ``load_data_wrapper`` is the
function usually called by our neural network code.
"""
#### Libraries
# Standard library
import cPickle
import gzip
# Third-party libraries
import numpy as np
def load_data():
"""Return the MNIST data as a tuple containing the training data,
the validation data, and the test data.
The ``training_data`` is returned as a tuple with two entries.
The first entry contains the actual training images. This is a
numpy ndarray with 50,000 entries. Each entry is, in turn, a
numpy ndarray with 784 values, representing the 28 * 28 = 784
pixels in a single MNIST image.
The second entry in the ``training_data`` tuple is a numpy ndarray
containing 50,000 entries. Those entries are just the digit
values (0...9) for the corresponding images contained in the first
entry of the tuple.
The ``validation_data`` and ``test_data`` are similar, except
each contains only 10,000 images.
This is a nice data format, but for use in neural networks it's
helpful to modify the format of the ``training_data`` a little.
That's done in the wrapper function ``load_data_wrapper()``, see
below.
"""
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():
"""Return a tuple containing ``(training_data, validation_data,
test_data)``. Based on ``load_data``, but the format is more
convenient for use in our implementation of neural networks.
In particular, ``training_data`` is a list containing 50,000
2-tuples ``(x, y)``. ``x`` is a 784-dimensional numpy.ndarray
containing the input image. ``y`` is a 10-dimensional
numpy.ndarray representing the unit vector corresponding to the
correct digit for ``x``.
``validation_data`` and ``test_data`` are lists containing 10,000
2-tuples ``(x, y)``. In each case, ``x`` is a 784-dimensional
numpy.ndarry containing the input image, and ``y`` is the
corresponding classification, i.e., the digit values (integers)
corresponding to ``x``.
Obviously, this means we're using slightly different formats for
the training data and the validation / test data. These formats
turn out to be the most convenient for use in our neural network
code."""
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):
"""Return a 10-dimensional unit vector with a 1.0 in the jth
position and zeroes elsewhere. This is used to convert a digit
(0...9) into a corresponding desired output from the neural
network."""
e = np.zeros((10, 1))
e[j] = 1.0
return e
我在上面说过,我们的计划得到了很好的结果。什么 那是什么意思?与什么相比更好?拥有一些 简单(非神经网络)基线测试进行比较,以 了解表现良好意味着什么。最简单的基线 当然,所有的事情都是随机猜测数字。没错 大约有 10% 的时间。我们做得比这好得多!
不那么琐碎的基线呢?让我们尝试一个非常简单的 IDEA:我们来看看图像有多暗。例如, 图像通常比,只是因为更多的像素被涂黑,如下所示 示例说明:
这表明使用训练数据来计算平均暗度 对于每个数字,.当显示新图像时, 我们计算图像的暗度,然后猜测它是 digit 具有最接近的平均暗度。这是一个简单的过程, 并且很容易编码,所以我不会显式写出代码 - 如果您有兴趣,可以在 GitHub 中 存储库。但这比随机猜测有了很大的改进, 获取的测试图像是否正确,即百分比精度。
不难找到其他在自百分比范围。如果你努力一点,你就能站起来 多百分之。但是为了获得更高的精度,使用 已建立的机器学习算法。让我们尝试使用其中一个 最知名的算法,支持向量 machine 或 SVM 的 SVM 中。如果你不是 熟悉 SVM,不用担心,我们不需要 了解 SVM 工作原理的详细信息。相反,我们将使用 Python 名为 scikit-learn 的库、 它为基于 C 的快速库提供了一个简单的 Python 接口,用于 称为 LIBSVM 的 SVM。
如果我们使用默认设置运行 scikit-learn 的 SVM 分类器, 然后,它会正确获取 10,000 张测试图像中的 9,435 张。(代码为 可在此处获得。 这比我们天真地将 图像。事实上,这意味着 SVM 是 性能与我们的神经网络大致一样好,只是一点点 更 糟。在后面的章节中,我们将介绍使 我们改进我们的神经网络,使它们的性能更好 比 SVM 更重要。
然而,这并不是故事的结局。10,000 个结果中的 9,435 个 用于 scikit-learn 的 SVM 默认设置。SVM 有一个编号 of tunable parameters 的 这提高了这种开箱即用的性能。我不会明确地这样做 这个搜索,而是将您推荐给这个 Andreas 的博客文章 穆勒,如果你想了解更多。穆勒表明,通过一些 优化 SVM 的参数时,可以获取 性能准确率超过 98.5%。换句话说, 经过良好调整的 SVM 仅在 70 分之 1 位数字上出现错误。那是 挺好的!神经网络能做得更好吗?
事实上,他们可以。目前,设计良好的神经网络 优于解决 MNIST 的所有其他技术,包括 SVM。 当前(2013 年)记录对 10,000 张图像中的 9,979 张进行分类 正确。这是李 Wan, Matthew Zeiler, 西辛 Zhang、Yann LeCun 和 Rob Fergus。 我们将在本书后面看到他们使用的大部分技术。在那个时候 水平上的表现接近人类的水平,可以说是 更好,因为相当多的 MNIST 图像即使对于 人类自信地识别,例如:
我相信你会同意这些很难分类!带图片 就像 MNIST 数据集中的这些一样,神经网络 可以准确分类 10,000 张测试图像中除 21 张之外的所有图像。 通常,在编程时,我们认为解决一个复杂的 像识别 MNIST 数字这样的问题需要一个复杂的 算法。但即使是 Wan 等人论文中的神经网络 刚才提到的涉及非常简单的算法,以及 算法。所有的复杂性都是学习的, 自动。从某种意义上说,《 我们的结果和更复杂的论文中的结果都是,对于 一些问题:
虽然我们的神经网络提供了令人印象深刻的性能,但 性能有些神秘。的 网络。这意味着我们不会 立即解释网络如何执行其功能。 我们能否找到一些方法来理解我们的网络所依据的原理 是否对手写数字进行分类?而且,鉴于这些原则,我们能否 做得更好?
为了更清楚地提出这些问题,假设几十年后 神经网络导致了人工智能 (AI)。我们会 了解此类智能网络的工作原理吗?也许是网络 对我们来说是不透明的,带有我们无法理解的权重和偏见, 因为它们是自动学习的。在 AI 的早期 研究人员希望构建 AI 的努力也会有所帮助 我们了解智能背后的原理,也许 人脑的功能。但也许结果会是 我们最终既不了解大脑,也不了解人工程度 智能有效!
为了回答这些问题,让我们回想一下对 我在章节开头给出的人工神经元,作为一种手段 的权衡证据。假设我们想要确定图像 显示人脸或不显示人脸:
学分: 1. Ester Inbar。2. 未知。3. 美国宇航局、欧洲航天局、G. Illingworth、D. Magee 和 P. Oesch (加州大学圣克鲁斯分校)、R. Bouwens(莱顿大学) 大学)和 HUDF09 团队。点击图片了解更多信息 详。
我们可以像处理手写一样解决这个问题 识别 - 使用图像中的像素作为神经的输入 网络,网络的输出是一个表示 “Yes, it's a face” 或 “No, it's not a face” 要么 “Yes, it's a face” 要么 “Yes, it's a face” 要么 “No, it's a face” 要么 “No, it's a face” 要么 “No
假设我们这样做,但我们没有使用 learning 算法。相反,我们将尝试手动设计一个网络 选择合适的权重和偏差。我们该怎么做呢? 暂时完全忘记神经网络,一个启发式的 we 可以使用将问题分解为子问题:是否 图片左上角有一只眼睛?它的顶部有一只眼睛吗 右?它中间有鼻子吗?它有嘴吗 最底层的中间?上面有头发吗?等等。
如果其中几个问题的答案是 “是”,甚至只是 “可能是”,那么我们得出结论,该图像很可能是一个 脸。相反,如果大多数问题的答案都是 “否”, 那么图像可能不是人脸。
当然,这只是一个粗略的启发式方法,它受到许多 不足。也许这个人是秃顶的,所以他们没有头发。或 我们只能看到脸的一部分,或者脸是有角度的,所以有些 的面部特征被遮挡。尽管如此,启发式方法表明 如果我们可以使用神经网络解决子问题,那么 也许我们可以构建一个用于人脸检测的神经网络,通过将 子问题的网络。这是一个可能的架构 其中矩形表示子网络。请注意,这不是 旨在作为解决人脸检测的现实方法 问题;相反,它是为了帮助我们建立关于网络如何的直觉 功能。这是架构:
子网络也可以分解。假设 我们正在考虑这个问题:“左上角有一只眼睛吗? 这可以分解为问题,例如:“是否有 眉毛?有睫毛吗?有虹膜吗?等等 上。当然,这些问题真的应该包括位置 信息 - “眉毛在左上角和上方 The Iris?“,诸如此类的事情 - 但让我们保持简单。这 网络来回答问题“左上角有一只眼睛吗? 现在可以分解:
这些问题也可以被分解,越来越深入 多层。最终,我们将与满足以下条件的子网络合作 回答的问题非常简单,很容易在 单个像素。例如,这些问题可能是关于 在 图像。这些问题可以通过连接到 图像中的 Raw 像素。
最终结果是一个网络,它打破了一个非常复杂的 问题 - 这张图片是否显示人脸 - 非常简单 在单个像素级别可回答的问题。它这样做 通过一系列的许多层,早期层回答非常 关于输入图像和后续图层的简单而具体的问题 构建一个越来越复杂和抽象的概念层次结构。 具有这种多层结构的网络 - 两个或多个隐藏 层 - 称为深度神经网络。
当然,我还没有说如何将这种递归分解为 子网络。手工设计权重当然是不切实际的 以及网络中的偏见。相反,我们想使用学习 算法,以便网络可以自动学习权重和 来自训练数据的偏差 - 因此,概念的层次结构。 1980 年代和 1990 年代的研究人员尝试使用随机梯度 descent 和 backpropagation 来训练深度网络。不幸 除了少数特殊的架构外,他们没有太多的运气。 网络会学习,但速度非常慢,而且在实践中也经常学习 慢慢地有用。
自 2006 年以来,已经开发了一系列技术,使 在深度神经网络中学习。这些深度学习技术是 基于随机梯度下降和反向传播,而且还 引入新想法。这些技术已经实现了更深层次(并且 更大)网络 - 人们现在定期训练网络 具有 5 到 10 个隐藏图层。而且,事实证明,这些性能很远 在许多问题上比浅层神经网络(即网络)更好 只有一个隐藏层。原因当然是 Deep Nets 构建复杂概念层次结构的能力。 这有点像传统编程语言使用模块化的方式 关于抽象的设计和想法,以实现复杂的创建 计算机程序。将深度网络与浅层网络进行比较是 有点像将编程语言与 函数调用对精简语言的调用,无法进行 这样的电话。抽象在神经网络中采用不同的形式 比在传统编程中更重要,但它同样重要。