第一章

使用神经网络识别手写数字

神经网络和深度学习

本书是关于什么的

关于练习和问题

使用神经网络识别手写数字

反向传播算法的工作原理

改进神经网络的学习方式

神经网络可以计算任何函数的视觉证明

为什么深度神经网络很难训练?

深度学习

附录:有没有简单的智能算法?

确认

常见问题解答



赞助商

深度学习工作站、服务器和笔记本电脑


资源

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

大多数人毫不费力地将这些数字识别为 504192。那种轻松 具有欺骗性。在我们大脑的每个半球,人类都有一个主要的 视觉皮层,也称为 V1,包含 1.4 亿个神经元,具有 它们之间的数百亿个连接。然而,人类的视觉 不仅涉及 V1,还涉及一整套视觉皮层 - V2, V3、V4 和 V5 - 执行逐渐更复杂的图像处理。 我们的脑海中携带着一台超级计算机,它经过进化而调整 数亿年,并且极好地适应了理解 视觉世界。识别手写数字并不容易。相反,我们 人类非常善于理解我们的 眼睛告诉我们。但几乎所有的工作都是无意识地完成的。所以 我们通常不会意识到我们的视觉系统是一个多么棘手的问题 解决。

如果您 尝试编写计算机程序来识别类似的数字 以上。当我们自己做时看似容易的事情,突然变得容易了 非常困难。关于我们如何识别形状的简单直觉 - “9 的顶部有一个环,底部有一个垂直笔画 对“——事实证明,用算法来表达并不是那么简单。 当您尝试使这些规则精确时,您很快就会迷失在 例外、警告和特殊情况的泥潭。看来 绝望的。

神经网络以不同的方式处理问题。这个想法是 取大量手写数字,称为训练 例子

然后开发一个可以从这些培训中学习的系统 例子。换句话说,神经网络使用这些示例来 自动推断识别手写数字的规则。 此外,通过增加训练样本的数量, Network 可以了解有关手写的更多信息,从而提高其准确性。 因此,虽然我在上面只展示了 100 个训练数字,但也许我们可以 通过使用数千个甚至 数百万或数十亿个训练示例。

在本章中,我们将编写一个计算机程序,实现一个神经 学习识别手写数字的网络。该程序是 只有 74 行长,并且不使用特殊的神经网络库。但 这个简短的程序可以识别准确率超过 96 的数字 %,无需人工干预。此外,在后面的章节中 我们将开发可以将准确率提高到 99% 以上的想法。在 事实上,最好的商业神经网络现在已经非常出色,以至于它们 被银行用于处理支票,被邮局用于识别 地址。

我们专注于手写识别,因为它非常出色 原型问题,用于一般学习神经网络。作为 原型 它击中了一个最佳点:它具有挑战性 - 它不小 识别手写数字的壮举 - 但这并不像 需要一个极其复杂的解决方案,或者巨大的 计算能力。此外,这是开发更多的好方法 高级技术,例如深度学习。因此,在整个 书中,我们将反复回到笔迹的问题上 识别。在本书的后面,我们将讨论这些想法可能是怎样的 应用于计算机视觉中的其他问题,也适用于语音, 自然语言处理和其他领域。

当然,如果这一章的重点只是写一台计算机 程序来识别手写数字,那么该章节将是 短得多!但在此过程中,我们将发展出许多关于 神经网络,包括两种重要的人工神经元 (感知器和 S 样神经元)和标准学习 神经网络的算法,称为随机梯度下降。 在整个过程中,我专注于解释为什么事情会以这种方式完成 他们是,并且需要构建您的神经网络直觉。那 需要比我只介绍基本 正在发生的事情的机制,但更深的是值得的 理解你会达到。在回报中,到 章节中,我们将了解什么是深度学习,以及 为什么重要。

感知器

什么是神经网络?首先,我将介绍一种 称为感知器的人工神经元。 感知器是由科学家弗兰克在 1950 年代和 1960 年代开发的 罗森布拉特,受到沃伦早期工作的启发 麦卡洛克沃尔特 皮茨。今天,使用其他 人工神经元的模型 - 在本书中,以及许多现代作品中 在神经网络上,使用的主要神经元模型称为 S 形神经元。我们很快就会讨论 S 形神经元。但要 了解为什么 S 样神经元是这样定义的,这是 值得花时间先了解感知器。

那么感知器是如何工作的呢?感知器接受多个二进制输入,x1,x2,,并生成单个二进制输出:

在所示的示例中,感知器有三个输入x1,x2,x3. 一般来说,它可以有更多或更少的输入。Rosenblatt 提出了一个 simple 规则来计算输出。他引入了砝码w1,w2,、实数 表示各个 inputs 对 output 的重要性。这 Neuron 的产出,01的 API 由jwjxj小于或大于某个阈值 值。就像权重一样, threshold 是一个实数,它是神经元的一个参数。将 用更精确的代数术语来说:
(1)输出={0如果jwjxj门槛1如果jwjxj>门槛
这就是感知器工作原理的全部内容!

这是基本的数学模型。一种你可以考虑 Perceptron 是一种通过权衡来做出决策的设备 证据。让我举个例子。这不是一个非常现实的例子, 但这很容易理解,我们很快就会变得更加现实 例子。假设周末快到了,并且您已经听说过 你的城市将举办奶酪节。你喜欢奶酪, 并试图决定是否参加音乐节。你 可能会通过权衡三个因素来做出决定:

  1. 天气好吗?
  2. 你的男朋友或女朋友想陪你吗?
  3. 音乐节靠近公共交通吗?(你没有车)。
我们可以通过相应的二进制变量来表示这三个因素x1,x2x3.例如,我们有x1=1如果 天气好,以及x1=0如果天气不好。同样地x2=1如果你的男朋友或女朋友想去,并且x2=0如果 不。同样,对于x3和公共交通。

现在,假设你绝对喜欢奶酪,以至于你很开心 即使你的男朋友或女朋友是 不感兴趣,音乐节很难到达。但也许你 真的很讨厌坏天气,你不可能去参加音乐节 如果天气不好。您可以使用感知器对此类 决策。一种方法是选择权重w1=6天气,以及w2=2w3=2对于其他条件。 较大的w1表示天气对 你,远不止你的男朋友或女朋友加入你,或者 公共交通的近度。最后,假设您选择一个 threshold 为5对于感知器。通过这些选择, Perceptron 实现所需的决策模型,输出1只要天气好,以及0每当天气不好时。 无论是你的男朋友还是 女朋友想走,或者附近是否有公共交通。

通过改变权重和阈值,我们可以得到不同的模型 的决策。例如,假设我们改为选择阈值 之3.然后,感知器将决定您应该转到 节日,只要天气好两者兼而有之 音乐节靠近公共交通你的男朋友或 女朋友愿意加入你。换句话说,它将是一个 不同的决策模式。降低阈值意味着 你更愿意去参加音乐节。

显然,感知器并不是一个完整的人类模型 决策!但这个例子说明了感知器 可以权衡各种证据以做出决定。 而且,一个复杂的感知器 可以做出非常微妙的决定:

在这个网络中,第一列感知器 - 我们称之为 第一感知器 - 就是让三个变得非常简单 决定,通过权衡输入证据。感知器呢 在第二层?这些感知器中的每一个都在做出决定 通过权衡第一层决策的结果。 通过这种方式,第二层中的感知器可以在 比第一个中的感知器更复杂、更抽象的层次 层。感知器可以做出更复杂的决策 在第三层。通过这种方式,多层感知器网络 可以参与复杂的决策。

顺便说一句,当我定义感知器时,我说感知器具有 只有一个输出。在上面的网络中,感知器看起来像 他们有多个输出。事实上,它们仍然是单输出。 多个输出箭头只是一种有用的方式,用于指示 感知器的输出被用作多个 其他感知器。它比绘制单个输出要简单 行,然后拆分。

让我们简化描述感知器的方式。条件jwjxj>门槛很麻烦,我们可以做两个 符号更改以简化它。 第一个变化是写jwjxj作为点积,wxjwjxj, 哪里wx是向量,其分量是权重, inputs 中。第二个变化是将阈值移动到 不等式的另一面,并用所谓的 感知器的偏差b门槛.使用偏差而不是阈值, Perceptron 规则可以是 重写:

(2)输出={0如果wx+b01如果wx+b>0
您可以将偏差视为衡量获得 perceptron 输出一个1.或者用更生物学的术语来说, 偏差是衡量触发 Perceptron 的难易程度的指标。对于具有非常大偏差的感知器来说,这是非常 易于 Perceptron 输出一个1.但是,如果偏差非常 negative,则感知器很难输出1. 显然,引入偏见只是我们 描述感知器,但我们稍后会看到它会导致进一步的 符号简化。因此,在 book 中,我们不会使用阈值,我们将始终使用 Bias。

我已经将感知器描述为一种权衡证据的方法 决定。使用感知器的另一种方法是计算 我们通常认为是底层的基本逻辑函数 计算、 、 和 等函数。例如,假设我们有一个具有两个 inputs,每个输入的权重ANDORNAND2,总体偏差为3.这是我们的 感知器:

然后我们看到该输入00产生输出1因为(2)0+(2)0+3=3是积极的。在这里,我介绍了符号使乘法明确。类似的计算 显示输入0110生成输出1.但是输入11产生输出0因为(2)1+(2)1+3=1为负数。 因此,我们的感知器实现了一个门!NAND

该示例表明,我们可以使用 perceptrons 来计算 简单的逻辑函数。 事实上,我们可以使用 感知器网络来计算任何逻辑函数。 原因是 gate 是通用的 computation 的 TIME 中,也就是说,我们可以在 Gates 之外构建任何计算。例如,我们可以使用 gates 来 构建一个加 2 位的电路,NANDNANDNANDNANDx1x2.这需要 计算按位和,x1x2,以及 carry bit ,设置为1当两者x1x21,即 carry bit 只是按位乘积x1x2:

为了获得等效的感知器网络,我们将所有门替换为具有两个输入的感知器,每个输入的权重NAND2,总体偏差为3.下面是生成的网络。注意 我稍微移动了对应于右下角门的感知器,只是为了更容易绘制箭头 在图上:这个感知器网络的一个值得注意的方面是 output 从最左侧的感知器到最底部的输入使用两次 感知器。当我定义感知器模型时,我没有说 这种双输出到同一位置是允许的。实际上 这并不重要。如果我们不想允许这种事情发生, 然后,可以简单地将这两行合并为一行 连接,而不是两个 -2 的连接 权重。(如果你觉得这不明显,你应该停下来证明 对你自己来说,这是等价的。随着这一变化,网络 如下所示,所有未标记的权重等于 -2,所有偏差 等于 3,单个权重为 -4,如下所示:到目前为止,我一直在绘制类似NAND
x1x2作为变量 浮动到感知器网络的左侧。事实上,它是 conventional 绘制额外的感知器层 - 输入 layer - 对输入进行编码:这种表示法用于输入感知器,其中我们有一个输出,但 No inputs,是简写。它实际上并不意味着没有输入的感知器。 为了看到这一点,假设我们确实有一个没有输入的感知器。然后 加权和
jwjxj将始终为零,因此 perceptron 将输出1如果b>00如果b0.那 是,感知器只会输出一个固定值,而不是所需的值 值 (x1,在上面的示例中)。最好考虑一下 输入 perceptrons 根本不是真正的 perceptrons,而是 特殊单位,这些单位被简单地定义为输出所需的值,x1,x2,.

adder 示例演示了感知器网络如何 用于模拟包含许多门的电路。和 因为门对于计算是通用的,所以它遵循 感知器对于计算也是通用的。NANDNAND

感知器的计算普遍性同时是 令人放心和失望。这让人放心,因为它告诉我们 感知器网络可以像任何其他计算一样强大 装置。但这也令人失望,因为它让它看起来像 尽管感知器只是一种新型的门。 这算不上什么大新闻!NAND

然而,情况比这种观点所暗示的要好。它转 我们可以设计学习 算法,这些算法可以 自动调整人工网络的权重和偏差 神经元。这种调整是对外部刺激的响应,没有 程序员的直接干预。这些学习算法使 我们以一种与 传统逻辑门。而不是显式布置电路 of 和其他门,我们的神经网络可以简单地学习 解决问题,有时是极其 难以直接设计常规电路。NAND

乙状结肠神经元

学习算法听起来很棒。但是我们怎么能设计出这样的 算法?假设我们有一个 我们想用来学习解决某些问题的感知器。为 例如,网络的输入可能是来自 数字的扫描手写图像。我们希望网络能够 学习权重和偏差,以便正确地从网络输出 对数字进行分类。为了了解学习是如何运作的,假设我们制作 网络中某些权重(或偏差)的微小变化。我们想要什么 喜欢是这个微小的重量变化只会导致一个小的 网络输出的相应变化。正如我们将在 片刻,此属性将使学习成为可能。示意图上, 这是我们想要的(显然这个网络太简单了,无法做到 手写识别!

如果权重(或偏差)的微小变化确实只会导致 output 的一个小变化,那么我们就可以利用这个事实来修改 权重和偏差,使我们的网络以 要。例如,假设网络错误地将 image 显示为 “8”,而它应该是 “9”。我们可以弄清楚怎么做 对权重和偏差进行小幅更改,以便网络获得 离将图像归类为“9”更近了一点。然后我们会 重复此操作,一遍又一遍地更改 Weight(权重)和 Bias(偏差)以生成 越来越好的输出。网络将正在学习。

问题在于,当我们的网络包含 感知器。事实上,任何 网络中的单个感知器有时会导致 那个感知器完全翻转,比如说从01.那个翻转 可能会导致网络其余部分的行为完全 以某种非常复杂的方式进行更改。因此,虽然您的“9”现在可能是 分类正确,则网络在所有其他 图像可能在一些难以控制的地方已经完全改变了 道路。这使得很难看到如何逐渐修改 权重和偏差,以便网络更接近所需的 行为。也许有一些聪明的方法可以解决这个问题 问题。但是,我们如何才能获得一个 感知器来学习。

我们可以通过引入一种新型的人工 神经元称为 S 形神经元。 S 样神经元类似于感知器,但经过修改后,小 它们的权重和偏差的变化只会导致它们的 输出。这是允许 sigmoid 网络的关键事实 神经元学习。

好的,让我描述一下 S 形神经元。我们将描绘 sigmoid 神经元,就像我们描述感知器一样:

就像感知器一样,S 形神经元也有输入x1,x2,.但不仅仅是01,这些输入也可以 采用 01.所以,例如,0.638是 sigmoid 神经元的有效输入。也就像 perceptron,sigmoid 神经元具有每个输入的权重,w1,w2,和总体偏差b.但输出并非如此01. 相反,它是σ(wx+b)哪里σ称为 sigmoid 函数**偶然σ有时 称为物流 函数和这个 称为 Logistic 的新型神经元 神经元。这很有用 记住这个术语,因为这些术语被许多人使用 使用神经网络的人。但是,我们将坚持使用 sigmoid 术语。,并已定义 由:
(3)σ(z)11+ez.
更明确地说,sigmoid 神经元的输出 带输入x1,x2,权重w1,w2,和 biasb
(4)11+exp (英文)(jwjxjb).

乍一看,S 形神经元看起来与感知器非常不同。 sigmoid 函数的代数形式可能看起来不透明,并且 如果您还不熟悉它,请禁止。事实上,有 感知器和 S 样神经元之间有许多相似之处,并且 sigmoid 函数的代数形式更像是一个 技术细节而不是理解的真正障碍。

为了理解与感知器模型的相似性,假设zwx+b是一个较大的正数。然后ez0等等σ(z)1.换句话说,当z=wx+b大且为正,则 S 形神经元的输出 大约为1,就像感知器一样。 另一方面,假设z=wx+b非常消极。 然后ezσ(z)0.因此,当z=wx+b非常消极,即 S 样神经元的行为 也非常接近感知器。只有当wx+b大小适中,与感知器有很大偏差 型。

的代数形式呢σ?我们如何理解 那?事实上,确切的σ不是那么重要 - 什么 真正重要的是绘制时函数的形状。这是 形状:

-4-3-2-1012340.00.20.40.60.81.0zsigmoid 函数

此形状是阶跃函数的平滑版本:

-4-3-2-1012340.00.20.40.60.81.0zstep 函数

如果σ实际上是一个阶跃函数,然后是 S 形神经元 将是一个感知器,因为输出将是10取决于wx+b为阳性或 阴性**实际上,当wx+b=0感知器 输出0,而 step 函数输出1.所以,严格来说 也就是说,我们需要在那个点修改 Step 函数。 但你明白了。.通过使用实际的σ函数 we get,如上所述,一个平滑的感知器。事实上 而是σ功能是关键事实, 而不是它的详细形式。平滑度σ意味着小 变化Δwj在 weights 和Δb在 bias 中 将 产生小小的改变Δ输出在 神经元。事实上,微积分告诉我们Δ输出是 很好地近似于

(5)Δ输出j输出wjΔwj+输出bΔb,
其中 sum 大于所有权重,wj输出/wj输出/b表示输出关于wjb分别。如果你不舒服,不要惊慌 与部分导数!虽然上面的表达式看起来 复杂,对于所有的偏导数,它实际上是说 一些非常简单的事情(这是个非常好的消息):Δ输出是变化的线性函数ΔwjΔb在权重和偏差中。这种线性度使其变得容易 选择权重和偏差的微小变化以实现任何 希望输出中的小变化。因此,虽然 S 样神经元具有 与感知器的定性行为大致相同,它们做到了 更容易弄清楚更改权重和偏差将如何 更改 OUTPUT。

如果是σ这真的很重要,而不是确切的 表单,那么为什么使用用于σ在 方程 (3)

σ(z)11+ez
?事实上,在本书的后面,我们将 偶尔考虑输出为f(wx+b)对于其他一些激活函数 f().最主要的是 当我们使用不同的激活函数时,情况会发生变化,因为 中偏导数的特定值 方程 (5)改变。事实证明,当我们 稍后使用σ将简化 代数,仅仅因为指数在以下时间具有可爱的性质 分化。无论如何,σ常用于 神经网络,并且是我们最常使用的激活函数 这本书。

我们应该如何解释 sigmoid 神经元的输出?明显地 感知器和 S 样神经元之间的一大区别是 sigmoid 神经元不仅输出01.他们可以有 as output 之间的任何实数01,因此0.1730.689是合法的输出。这可能很有用,因为 例如,如果我们想使用 Output 值来表示平均值 输入神经网络的图像中像素的强度。但 有时这可能会很麻烦。假设我们想要 network 来指示“输入图像是 9”或“输入 图像不是 9 英寸。显然,如果 output 是一个01,就像在感知器中一样。但在实践中,我们可以 设置一个约定来处理这个问题,例如,通过决定 解释至少0.5表示 “9”,并且任何 输出小于0.5表示 “not a 9”。我会永远 明确说明何时使用此类约定,因此它不应该 造成任何混淆。

习题

神经网络的架构

在下一节中,我将介绍一个神经网络,它可以执行 对手写数字进行分类相当不错。准备 那,它有助于解释一些让我们命名不同的术语 网络的一部分。假设我们有网络:

如前所述,此网络中最左侧的层称为 input 层,以及 层称为输入神经元。 最右侧或输出图层 包含输出神经元,或者 在本例中,单个输出神经元。中间层称为隐藏层,因为 该层既不是 inputs 也不是 outputs。术语 “隐藏” 也许听起来有点神秘 - 我第一次听到这个词 我认为它一定有一些深奥的哲学或数学意义 significance - 但它实际上的含义无非是“不是输入 或输出”。上面的网络只有一个隐藏层,但 某些网络具有多个隐藏层。例如,以下 四层网络有两个隐藏层:有点令人困惑,并且由于历史原因,这样的多层 网络有时被称为多层感知器MLP,尽管它由 S 形神经元组成,而不是 感知器。我不打算在本书中使用 MLP 术语, 因为我认为它令人困惑,但想警告你它的存在。

网络中输入和输出层的设计通常是 简单。例如,假设我们尝试确定 手写图像是否描绘了“9”。一种自然的方式 设计网络是为了编码图像像素的强度 到输入神经元中。如果图像是6464灰度 image,那么我们就有4,096=64×64input 神经元,使用 强度在01.输出 layer 将仅包含一个神经元,其输出值为 less 比0.5指示“输入图像不是 9”,并且值更大 比0.5指示“输入图像为 9”。

虽然神经网络的输入层和输出层的设计是 通常很简单,但设计可能有相当多的艺术 隐藏图层。特别是,无法对设计进行总结 使用一些简单的经验法则处理隐藏图层。 相反,神经网络研究人员开发了许多设计 隐藏层的启发式方法,帮助人们了解行为 他们想从他们的网中出来。例如,可以使用这样的启发式方法 帮助确定如何权衡隐藏层的数量 训练网络所需的时间。我们将遇到几个这样的 设计启发式方法。

到目前为止,我们一直在讨论神经网络,其中 一个层用作下一个层的输入。这样的网络是 称为前馈神经网络。这意味着网络中没有环路 - 信息总是向前反馈,从不反馈。如果我们确实有 循环中,我们最终会遇到这样的情况:σ函数依赖于输出。那会很难理解,而且 所以我们不允许这样的循环。

但是,还有其他人工神经网络模型,其中 反馈循环是可能的。这些模型称为递归模型 神经网络。这些模型中的想法是让 在变为静止状态之前,会触发一段时间。 这种放电可以刺激其他神经元,这些神经元可能会发射一会儿 后来,也是有限的持续时间。这会导致更多的神经元 fire,所以随着时间的推移,我们得到了一连串的神经元放电。循环 不会在这样的模型中引起问题,因为只有神经元的输出 在稍后的某个时间影响其输入,而不是立即影响。

递归神经网络的影响力不如前馈 网络,部分原因是循环网络的学习算法 (至少到目前为止)没有那么强大。但循环网络是 仍然非常有趣。它们在精神上更接近我们的 大脑比前馈网络工作。而且有可能 循环网络可以解决一些重要的问题,这些问题只能是 通过前馈网络解决非常困难。但是,要 限制我们的范围,在本书中,我们将专注于 广泛使用的前馈网络。

用于对手写数字进行分类的简单网络

定义了神经网络之后,让我们回到手写 识别。我们可以拆分识别手写体的问题 digits 转换为两个子问题。首先,我们想要一种打破 图像包含许多数字,并生成一系列单独的图像,每个图像 包含单个数字。例如,我们想打破图像

分成 6 个单独的图像,

我们人类解决了这个细分 问题很容易,但具有挑战性 让计算机程序正确地分解图像。一旦 图像已被分割,则程序需要对每个 单个数字。因此,例如,我们希望我们的程序能够 认识到上面的第一个数字,

是 5。

我们将专注于编写一个程序来解决第二个问题,即 对单个数字进行分类。我们这样做是因为事实证明 分割问题并不难解决,只要你有一个 对单个数字进行分类的好方法。有很多方法 解决分割问题。一种方法是多次尝试 使用单个数字分割图像的不同方法 classifier 对每个试验分段进行评分。试验细分 如果单个数字分类器对 它在所有 Segments 中的分类,如果分类器 在一个或多个区段中遇到很多问题。这个想法是 如果分类器在某处遇到问题,则可能是 由于选择不正确的分段而出现问题。 这个想法和其他变体可用于解决分割问题 问题相当好。因此,与其担心细分,不如 专注于开发一个可以解决更多 有趣而困难的问题,即识别个体 手写数字。

为了识别单个数字,我们将使用三层神经 网络:

网络的输入层包含编码 输入像素。如下一节所述,我们的训练数据 因为这个网络将由许多2828像素图像 扫描的手写数字,因此输入图层包含784=28×28神经元。为简单起见,我省略了大部分784input 神经元。输入像素为灰度, 的值为0.0表示白色,则值为1.0表示黑色,介于两者之间表示逐渐 灰色阴影变暗。

网络的第二层是隐藏层。我们表示 此隐藏层中的神经元数n,我们将进行实验 具有不同的值n.所示示例说明了一个小的 hidden 图层,仅包含n=15神经元。

网络的输出层包含 10 个神经元。如果第一个 神经元触发,即有一个输出1,则表示 网络认为该数字是0.如果第二个神经元 触发,则表明网络认为该数字是1.等等。更准确地说,我们对输出进行编号 神经元来自0通过9,并找出哪个神经元具有 最高激活值。如果该神经元是,例如,神经元数6, 然后我们的网络会猜测输入数字是一个6.等等 对于其他输出神经元。

您可能想知道为什么我们使用10输出神经元。毕竟,目标 的网络是告诉我们哪个数字 (0,1,2,,9) 对应于输入图像。一种看似自然的方式 就是只用4output 神经元,将每个神经元视为具有 binary 值,具体取决于神经元的输出是否更接近01.四个神经元就足以编码答案,因为24=16大于输入数字的 10 个可能值。 为什么我们的网络应该使用10神经元?不是吗 低 效?最终的理由是实证的:我们可以尝试一下 两种网络设计,事实证明,对于这个特定的 问题,网络与10输出神经元学会识别 数字优于网络4输出神经元。但是那个 让我们想知道为什么使用10Output Neurons 效果更好。 是否有一些启发式方法可以提前告诉我们我们应该 使用10-output 编码,而不是4-output 编码?

要了解我们为什么要这样做,思考一下 网络从第一原则开始做。首先考虑以下情况 我们使用10输出神经元。让我们专注于第一个输出 神经元,它试图确定该数字是否为0.它通过权衡隐藏层的证据来实现这一点 神经元。那些隐藏的神经元在做什么?好吧,假设 为了争论隐藏层中的第一个神经元检测到 是否存在如下图像:

它可以通过对与 图像,并且仅对其他输入进行较轻的加权。在类似的 方式,为了论证起见,让我们假设第二个、第三个、 隐藏层中的第四个神经元检测 存在以下图像:

您可能已经猜到了,这四张图片共同构成了0我们在前面显示的数字行中看到的图像:

因此,如果这四个隐藏的神经元都在发射,那么我们可以得出结论 该数字是0.当然,这并不是唯一的类型 我们可以用来断定该图像是0-我们 可以合法地获得0以许多其他方式(例如,通过 上述图像的翻译,或轻微失真)。但是它 似乎可以肯定地说,至少在这种情况下,我们会得出结论, input 是0.

假设神经网络以这种方式运行,我们可以给出一个 为什么最好拥有10输出 网络,而不是4.如果我们有4outputs,然后是第一个 output neuron 将尝试确定最高有效位 的数字是。没有简单的方法可以将这一点最紧密地联系起来 有效位转换为简单形状,如上所示。这很难 想象一下,组件形状 的数字将与 (比如) 最高有效位密切相关 在输出中。

现在,说了这么多,这都只是一种启发式方法。什么都没说 三层神经网络必须按照 I 描述,隐藏的神经元检测简单的组件形状。 也许一个聪明的学习算法会找到一些权重分配 那让我们只使用4输出神经元。但作为启发式方法 认为我已经描述的运行得很好,可以为您节省很多 设计良好的神经网络架构的时间。

锻炼

使用梯度下降法进行学习

现在我们已经为神经网络设计了,它如何学习 识别数字?我们首先需要一个要学习的数据集 from - 所谓的训练数据集。我们将使用 MNIST 数据集,其中包含数万张扫描图像 手写数字及其正确分类。 MNIST 的名称来源于这样一个事实,即它是两个 NIST 采集的数据集, 美国国家标准研究院 (National Institute of Standards) 和 科技。以下是来自 MNIST 的一些图片:

如您所见,这些数字实际上与显示的数字相同 在本章的开头作为挑战 来识别。当然,在测试我们的网络时,我们会要求它 识别不在训练集中的图像!

MNIST 数据分为两部分。第一部分包含 60,000 要用作训练数据的图像。扫描这些图像 来自 250 人的笔迹样本,其中一半是美国人口普查 该局的员工,其中一半是高中生。这 图像为灰度图像,大小为 28 x 28 像素。的第二部分 MNIST 数据集是 10,000 张图像,用作测试数据。再 这些是 28 x 28 灰度图像。我们将使用测试数据来 评估我们的神经网络学习识别数字的能力。 为了使这成为一个好的性能测试,测试数据取自 与原始训练数据不同的 250 人集 (尽管仍然是一个由人口普查局员工和高 学校学生)。这有助于我们确信我们的系统可以 识别 ping 期间未看到其写作的人的数字 训练。

我们将使用表示法x来表示训练输入。这将是 方便关注每个培训输入x作为28×28=784-维度向量。向量中的每个条目都表示灰色 值。我们将表示相应的 期望输出y=y(x)哪里y是一个10-维度向量。 例如,如果特定训练图像x,描绘了6然后y(x)=(0,0,0,0,0,0,1,0,0,0)T是所需的输出 网络。请注意,T这是转置操作,将 row 向量转换为普通(列)向量。

我们想要的是一种算法,它可以让我们找到权重和偏差 因此,网络的输出近似y(x)为了所有人 培训输入x.量化我们实现这一目标的程度 我们定义了一个成本函数**有时称为损失函数或目标函数。我们使用术语 cost 在本书中发挥作用,但您应该注意其他 术语,因为它经常用于研究论文和其他 神经网络的讨论。:

(6)C(w,b)12nxy(x)一个2.
这里w表示网络中所有权重的集合,b所有的偏见,n是训练输入的总数,一个是 当x是 input,总和 在所有训练输入中,x.当然,输出一个取决于x,wb,但为了保持符号简单,我没有明确表示 表明了这种依赖性。符号v仅表示 向量的 usual-length 函数v.我们将调用C二次成本函数;它也是 有时称为均方误差或简称为 MSE。 检查二次成本函数的形式,我们看到C(w,b)为非负数,因为 sum 中的每个项都是非负数。 此外,成本C(w,b)变小,即C(w,b)0,则恰好当y(x)近似等于输出一个, 对于所有训练输入,x.所以我们的训练算法做了一个 如果它能找到权重和偏差,那么就干得好,这样C(w,b)0. 相比之下,当C(w,b)很大 - 即 的意思是y(x)不接近输出一个对于大号 输入数量。因此,我们的训练算法的目标是 最大限度地降低成本C(w,b)作为权重和偏差的函数。 换句话说,我们想找到一组权重和偏差,它们使 成本尽可能低。我们将使用已知的算法来执行此操作 作为梯度下降

为什么要引入二次成本?毕竟,我们不主要不是吗 对由 网络?为什么不尝试直接最大化这个数字,而不是 最小化像二次成本这样的代理度量?问题 也就是说,正确分类的图像数量并不是平稳的 网络中权重和偏差的函数。在大多数情况下, 对 WEIGHTS 和 Biases 进行微小的更改不会导致任何更改 完全取决于正确分类的训练图像的数量。那 使很难弄清楚如何更改权重和偏差 以获得改进的性能。如果我们改用平滑成本函数 就像二次成本一样,事实证明很容易弄清楚如何 对权重和偏差进行小幅更改,以获得 成本的改善。这就是为什么我们首先关注最小化 二次成本,只有在那之后,我们才会检查分类 准确性。

即使我们想使用平滑的 cost 函数,您仍然可以 想知道为什么我们选择 方程 (6)

C(w,b)12nxy(x)a2
.这不是一个相当的广告吗 Hoc 选择?也许如果我们选择不同的 cost 函数,我们会得到 一组完全不同的最小化权重和偏差?这是一个 Valid 关注点,稍后我们将重新审视 cost 函数,并将 一些修改。但是,的二次成本函数 方程 (6)效果非常好 了解神经网络学习的基础知识,因此我们将 现在坚持下去。

回顾一下,我们训练神经网络的目标是找到权重 和最小化二次成本函数的偏差C(w,b).这 是一个摆定合理的问题,但它有很多分散注意力的结构 如目前所提出的那样 - 对wb作为权重 和偏见,则σ函数中,则 选择网络架构、MNIST 等。事实证明, 我们可以通过忽略其中的大部分来理解大量的 结构,只专注于最小化方面。所以对于 现在我们要忘记所有关于成本的具体形式 函数、与神经网络的连接等。相反 我们将想象我们只是被赋予了 many 的函数 变量,我们希望最小化该函数。我们将 开发一种称为梯度下降的技术,可以使用 来解决此类最小化问题。然后我们回到 特定函数。

好的,假设我们试图最小化一些函数C(v). 这可以是许多变量的任何实值函数v=v1,v2,.请注意,我已将wb表示法v强调这可以是任何函数 - 我们不是 特别是不再在神经网络上下文中思考。自 最小化C(v)想象会有所帮助C作为 2 的函数 variables,我们将其称为v1v2:

我们想要的是找到C实现其全局最小值。现在 当然,对于上面绘制的函数,我们可以盯着图 并找到最小值。从这个意义上说,我展示的函数可能有点太简单了!通用函数C,可以是 complex 函数,通常不会是 可以只盯着图表找到最小值。

解决这个问题的一种方法是使用 calculus 来尝试找到 最低的分析。我们可以计算导数,然后尝试使用 他们找到C是一个极值。幸运的是 在以下情况下可能会起作用C是仅包含一个或几个变量的函数。但 当我们有更多的变量时,它会变成一场噩梦。而对于 神经网络中,我们通常需要更多的变量 - 最大的神经网络具有依赖于数十亿的 cost 函数 的权重和偏差。使用 Calculus 尽量减少这种情况是行不通的!

(在断言我们将通过想象获得洞察力之后C作为 函数,我已经把 2 个变量转了两次 段落,然后说,“嘿,但如果它是更多 比两个变量多吗?很抱歉。请相信我 想象确实有帮助C作为 2 的函数 变量。只是碰巧有时候那张照片会坏掉, 最后两段是关于这种细分的。好 思考数学通常涉及处理多个直觉 图片, 了解何时适合使用每张图片以及何时 不是。

好吧,所以微积分不起作用。幸运的是,有一个美丽的 类比,这表明一种算法运行良好。我们开始 通过将我们的功能视为一种山谷。如果你只是眯着眼睛 稍微看一下上面的情节,这应该不会太难。而我们 想象一个球从山谷的斜坡上滚下来。我们的日常 经验告诉我们,球最终会滚到底部 山谷的。也许我们可以用这个想法来找到一个 函数的最小值?我们会随机选择一个起点 一个(假想的)球,然后模拟球的运动 滚到山谷的底部。我们可以进行这种模拟 只需计算导数(也许还有一些二阶导数) 之C- 这些衍生品会告诉我们我们需要知道的一切 关于山谷的当地“形状”,以及我们的球如何 应该滚动。

根据我刚才写的内容,你可能会认为我们会 试图写下球的牛顿运动方程, 考虑摩擦力和重力的影响,等等。实际上 我们不会那么认真地对待滚球的类比 - 我们正在设计一种算法来最小化C,而不是开发 精确模拟物理定律!球眼视图为 旨在激发我们的想象力,而不是限制我们的思考。所以 与其深入探讨物理学的所有杂乱细节,不如简单地 问问自己:如果我们被宣布为上帝一天,并且能够弥补 我们自己的物理定律,决定球应该如何滚动, 我们可以选择什么或哪些运动定律来使它成为 球总是滚到谷底?

为了更准确地回答这个问题,让我们考虑一下会发生什么 当我们少量移动球时Δv1v1direction 和少量Δv2v2方向。 微积分告诉我们C更改如下:

(7)ΔCCv1Δv1+Cv2Δv2.
我们将找到一种选择Δv1Δv2所以 使ΔC阴性;即,我们将选择它们,以便球是 滚落到山谷中。弄清楚如何做出这样的选择 它有助于定义Δv作为v,Δv(Δv1,Δv2)T哪里T再次是 transpose 操作,将行向量转换为列向量。我们将 同时定义C为偏导数的向量,(Cv1,Cv2)T.我们 用C,即:
(8)C(Cv1,Cv2)T.
稍后我们将重写更改ΔCΔv和梯度C.不过,在开始之前,我想 澄清一些有时会让人们挂断电话的事情 梯度。当满足C符号, 人们有时会想,他们应该如何看待象征。具体作用意味 着?事实上,它是完美的 想想就好C作为单个数学对象 - 使用 vector - 恰好使用两个 符号。从这个角度来看,只是一块 符号挥舞着旗帜,告诉你 “嘿,C是一个渐变 向量”。还有更高级的观点,其中能 被视为一个独立的数学实体(对于 example,作为微分运算符),但我们不需要 视图。

根据这些定义,表达式 (7)

ΔCCv1Δv1+Cv2Δv2
ΔC可以重写为
(9)ΔCCΔv.
这个方程有助于解释原因C称为渐变 向量:C关联v更改为C,就像 我们期望一个叫做 gradient 的东西来做。但真正的 这个方程式令人兴奋的是,它让我们看到如何选择Δv以便使ΔC阴性。具体而言,假设 我们选择
(10)Δv=ηC,
哪里η是一个较小的正参数(称为学习率)。 然后方程 (9)告诉我们ΔCηCC=ηC2.因为C20,这保证了ΔC0,即C将 如果我们改变,总是减少,永远不会增加v根据 处方 (10).(当然,在 方程 (9) 中近似的极限).这是 正是我们想要的房产!所以我们来拿 方程 (10)定义“运动定律” 对于我们的 Gradient Descent 算法中的球。也就是说,我们将使用 方程 (10)要计算Δv,然后移动球的位置v按该数量:
(11)vv=vηC.
然后,我们将再次使用此更新规则进行另一次操作。如果我们 继续这样做,一遍又一遍,我们会不断减少C直到 - 我们 希望 - 我们达到了全球最低限度。

总而言之,梯度下降算法的工作原理是 重复计算梯度C,然后向相反方向移动,“落下”山谷的斜坡。 我们可以像这样可视化它:

请注意,使用此规则时,梯度下降不会重现实数 身体运动。在现实生活中,球有动量,而这种动量 可能会允许它在斜坡上滚动,甚至(暂时)滚动 上坡。只有在摩擦作用作用下,球 保证会滚入山谷。相比之下,我们的 选择Δv只是说“现在就下去”。这仍然是一个 找到最小值的好规则!

要使梯度下降正常工作,我们需要选择 学习率η要小 足够了,方程 (9)

ΔCCΔv
是一个很好的近似值。如果 我们没有,我们最终可能会得到ΔC>0,这显然会 不要好!同时,我们不希望η太小, 因为这将进行更改Δvtiny,因此 梯度下降算法将非常缓慢地工作。在实践中 实现η经常变化,因此 方程 (9)仍然是一个很好的近似值,但 算法不会太慢。我们稍后会看到这个 工程。

我已经解释了C是 2 的函数 变量。但是,事实上,即使C是更多变量的函数。特别假设Cm变量v1,,vm.然后是变化ΔCC由 A Small Change 制作Δv=(Δv1,,Δvm)T

(12)ΔCCΔv,
其中 gradientC是向量
(13)C(Cv1,,Cvm)T.
就像两个变量的情况一样,我们可以 选择
(14)Δv=ηC,
并且我们保证我们的 (近似) 表情 (12)ΔC将为负数。 这为我们提供了一种将梯度降至最低的方法,即使C是许多变量的函数,通过重复应用 update 统治
(15)vv=vηC.
您可以将此更新规则视为定义梯度 descent 算法。它为我们提供了一种重复更改 位置v为了找到函数的最小值C.规则 并不总是有效 - 有几件事可能会出错并阻止 gradient descent 从找到全局最小值C,我们将 返回以在后面的章节中探索。但是,在实践中梯度 Descent 通常效果非常好,在神经网络中我们会发现 它是最小化成本函数的强大方法,因此 帮助网络学习。

事实上,从某种意义上说,梯度下降是最佳的 策略来搜索最小值。假设我们正在尝试 进行移动Δv在位置以减少C多达 可能。这相当于最小化ΔCCΔv.我们将限制移动的大小,以便Δv=ε对于一些小的固定ε>0.在其他 words,我们想要一个固定大小的一小步 move,而我们是 尝试找到减小的移动方向C多达 可能。可以证明,选择Δv哪 最大限度 地 减少CΔvΔv=ηC, 哪里η=ε/C由大小决定 约束Δv=ε.所以梯度下降可以是 被视为一种朝着执行 most 立即减少C.

习题

人们已经研究了梯度下降的许多变化, 包括更接近真实物理球的变体。 这些模拟球的变体有一些优点,但也具有 主要缺点:事实证明需要计算秒 的偏导数C,而这可能非常昂贵。了解原因 这很昂贵,假设我们想计算所有第二个部分 衍生物2C/vjvk.如果存在 万这样的vj变量,那么我们需要计算类似 一万亿(即一百万平方)秒的部分 衍生物**实际上,更像是五万亿,因为2C/vjvk=2C/vkvj.不过,你明白了。!那将是 计算成本高。话虽如此,有一些避免的技巧 这种问题,寻找梯度下降的替代方案是 一个活跃的调查领域。但在本书中,我们将使用 gradient descent (and variations) 作为我们在 neural 中学习的主要方法 网络。

我们如何应用梯度下降法在神经网络中学习?这 思路是使用梯度下降来查找权重wk和偏见bl这最大限度地降低了 方程 (6)

C(w,b)12nxy(x)a2
.要了解其工作原理,我们来了解 重述 Gradient descent 更新规则,包括 Weights 和 Biases 替换变量vj.换句话说,我们现在的“位置” 有组件wkbl和 gradient vectorC具有 对应组件C/wkC/bl.在 组件条款,我们有
(16)wkwk=wkηCwk(17)blbl=blηCbl.
通过反复应用此更新规则,我们可以 “roll down the hill”, 并希望找到 cost 函数的最小值。换句话说, 这是一个可用于在神经网络中学习的规则。

应用梯度下降法存在许多挑战 统治。我们将在后面的章节中深入探讨这些。但就目前而言 我只想提一个问题。要了解问题所在 是,让我们回顾一下 方程 (6)

C(w,b)12nxy(x)a2
.请注意,此成本 function 的形式为C=1nxCx,即它是一个 平均超额成本Cxy(x)一个22个人版 训练示例。在实践中,要计算梯度C我们 需要计算梯度Cx单独用于每个 训练投入,x,然后对它们进行平均,C=1nxCx.遗憾的是,当训练输入的数量 非常大,这可能需要很长时间,因此会发生学习 慢慢。

一个叫做随机梯度下降的想法可以用来加速 向上学习。这个想法是估计梯度C由 计算机科学Cx对于随机选择的训练的小样本 输入。通过对这个小样本进行平均,事实证明我们可以 快速获得真实梯度的良好估计C和这个 有助于加快梯度下降,从而加快学习速度。

为了使这些想法更精确,随机梯度下降的工作原理是 随机挑选出一个小数字m随机选择的训练 输入。我们将标记这些随机训练输入X1,X2,,Xm,并将其称为 mini-batch。提供了示例 大小m足够大,我们预计CXj将大致等于总体Cx那是

(18)j=1mCXjmxCxn=C,
其中,第二个总和是整个训练数据集。 我们得到的换边
(19)C1mj=1mCXj,
确认我们可以通过计算来估计整体梯度 gradients 仅用于随机选择的小批量。

为了将其与神经网络中的学习明确联系起来,假设wkbl表示神经网络中的权重和偏差。 然后随机梯度下降的工作原理是随机选择一个 选择小批量的训练输入,并使用这些输入进行训练,

(20)wkwk=wkηmjCXjwk(21)blbl=blηmjCXjbl,
其中 sum 覆盖所有训练示例Xj在当前 mini-batch 的 MICRO-BATCH 中。然后我们挑选出另一个随机选择的小批量,然后 用这些训练。以此类推,直到我们用尽了训练 inputs,据说可以完成一个 epoch 的训练。在那时 我们从一个新的训练 epoch 重新开始。

顺便说一句,值得注意的是,约定对 成本函数和小批量更新权重和偏差。 在方程 (6)

C(w,b)12nxy(x)a2
我们调整了总体成本 按因子计算的函数1n.人们有时会省略1n,将单个训练示例的成本相加 而不是平均。这在总的 训练样本的数量事先是未知的。如果出现 例如,正在实时生成更多的训练数据。 并且,以类似的方式,小批量更新规则 (20)(21)有时省略1mterm out 的 前面 总和。从概念上讲,这几乎没有区别,因为 这相当于重新调整学习率η.但是当执行 值得关注的不同工作的详细比较。

我们可以将随机梯度下降视为政治 轮询:对小批量进行采样比对小批量进行采样要容易得多 将梯度下降法应用于整个批次,就像执行轮询一样 比进行全面选举更容易。例如,如果我们有一个 大小n=60,000,然后选择 (比如)m=10,这意味着我们将得到一个因子6,000加速估计梯度!当然,估计 不会完美 - 会有统计波动 - 但它 不需要完美:我们真正关心的只是在 有助于减少的总体方向C,这意味着我们不会 需要精确计算梯度。在实践中,随机指标 梯度下降是一种常用且强大的技术 在神经网络中学习,它是大多数 学习我们将在本书中开发的技巧。

锻炼

让我通过讨论有时 bug 刚接触 Gradient Descent 的人。在神经网络中,成本C是 当然,这是许多变量的函数 - 所有的权重和偏差 - 所以在某种意义上定义了一个非常高维的表面 空间。有些人挂断电话想:“嘿,我必须能够 可视化所有这些额外的维度”。他们可能会开始担心: “我不能用四个维度来思考,更不用说五个(或五个) 万)”。他们是否缺少什么特殊能力,一些 “真正的”超数学家所具有的能力?当然,答案是 是 NO。即使是大多数专业的数学家也无法想象出 4 尺寸特别好,如果有的话。相反,他们使用的技巧是 是开发其他方式来表现正在发生的事情。那是 正如我们上面所做的:我们使用代数(而不是视觉) 表示ΔC弄清楚如何移动以便 减少C.善于高维思考的人有 一个包含许多不同技术的心理图书馆 线;我们的代数技巧只是一个例子。这些技术可能会 没有我们在可视化 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])
偏见 和 Network 对象中的权重都是随机初始化的, 使用 Numpy np.random.randn 函数生成高斯 具有平均值的分布0和标准差1.这个随机 初始化为我们的随机梯度下降算法提供了一席之地 开始。在后面的章节中,我们将找到更好的 初始化 weights 和 biass,但目前这已经足够了。注意 Network 初始化代码假定第一个 层是 Neurons 的输入层,省略了为 这些神经元,因为偏差仅用于计算 来自后续层的输出。

另请注意,偏差和权重存储为 Numpy 的列表 矩阵。因此,例如 net.weights[1] 是一个 Numpy 矩阵 存储连接第二层和第三层神经元的权重。 (这不是第一层和第二层,因为 Python 的列表索引 从 0 开始。由于 net.weights[1] 相当冗长, 我们只表示该矩阵w.它是一个矩阵,使得wjkkthneuron 的 second 层和jthneuron 在第三层。此排序 的jk指数可能看起来很奇怪 - 肯定会让更多 sense 来交换jk指数周围?最大的优势 使用这种排序意味着 神经元的第三层是:

(22)一个=σ(w一个+b).
这个方程式中发生了很多事情,所以让我们来解读一下 一块一块。一个是第二层的激活向量 神经元。要获得一个我们倍增一个按权重矩阵w, 并添加向量b的偏见。然后,我们应用函数σ元素复制到向量中的每个条目w一个+b.(这称为矢量化函数σ.)验证 方程 (22)给出的结果与我们的 较早的规则,方程 (4)为 计算 sigmoid 神经元的输出。

锻炼

考虑到所有这些,编写计算输出的代码很容易 从 Network 实例。我们首先定义 sigmoid 功能:

def sigmoid(z):
    return 1.0/(1.0+np.exp(-z))
请注意,当输入 z 是向量或 Numpy 数组时,Numpy 自动应用函数 sigmoid 元素,即 是矢量化形式。

然后,我们将前馈方法添加到 Network 类 给定网络的输入 a,则返回 相应的输出**假设输入 a 为 一个 (n, 1) 个 Numpy ndarray,而不是一个 (n,) 向量。其中, n 是网络的输入数量。如果您尝试使用 一个 (n,) 向量作为输入,你会得到奇怪的结果。虽然 使用 (n,) 向量似乎是更自然的选择,使用 (n, 1) ndarray 使得修改 代码一次前馈多个输入,这有时是 方便。.该方法所做的只是应用 方程 (22)

a=σ(wa+b)
对于每个层:

    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) 列表,表示训练输入和相应的所需输出。 变量 epochsmini_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)
这会调用一种称为反向传播算法 这是计算成本函数梯度的一种快速方法。 所以update_mini_batch 只是通过计算这些梯度来工作 对于mini_batch中的每个训练示例,然后适当地更新 self.weightsself.biases

我现在不打算展示 self.backprop 的代码。 我们将在下一章中研究反向传播的工作原理,包括 self.backprop 的代码。现在,假设它 的行为与声称的一样,返回成本的适当梯度 关联到训练示例 X

让我们看看完整的程序,包括文档字符串 我在上面省略了。除了 self.backprop 之外,该程序是 不言自明 - 所有繁重的工作都是自己完成的。SGDself.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 数据后,我们将设置一个网络,其中包含30隐藏的神经元。我们在导入列出的 Python 程序后执行此操作 上面,名为 network

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

最后,我们将使用随机梯度下降法从 30 多个 epoch 的 MNIST training_data学习,小批量大小为 10,以及一个 学习率η=3.0,

>>> 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

也就是说,经过训练的网络给我们的分类率约为95百分之-95.42百分比达到峰值(“第 28 纪元”)!那是 作为第一次尝试,相当令人鼓舞。然而,我应该警告你, 如果你运行代码,那么你的结果不一定会成功 与我的非常相似,因为我们将初始化我们的网络 使用(不同的)随机权重和偏差。要在 本章我采取了三局两胜的做法。

让我们重新运行上面的实验,更改 hidden 的数量 神经元到100.与之前的情况一样,如果您正在运行代码 当您继续阅读时,应该警告您,这需要相当长的时间才能 execute (在我的计算机上,每个 Experiment 需要数十秒 training epoch),因此在 代码执行。

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

果然,这将结果提高到96.59百分之。至少 在这种情况下,使用更多隐藏的神经元有助于我们变得更好 结果**读者反馈表明 此实验的结果,并且一些训练运行会给出结果 相当糟糕。使用第 3 章中介绍的技术 将大大减少不同 训练运行。.

当然,为了获得这些精度,我必须做出具体的选择 对于训练的 epoch 数、小批量大小和 学习率 /η.正如我上面提到的,这些被称为 hyper 参数,以便区分它们 从我们学习中学到的参数(权重和偏差) 算法。如果我们选择不当的超参数,我们可能会变得很糟糕 结果。例如,假设我们将学习率选择为 是η=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),这与我们之前的 实验。因此,即使我们最初做出了一个糟糕的选择 hyper-parameters 的 Alpha 参数,我们至少获得了足够的信息来帮助我们 改进我们对超参数的选择。

通常,调试神经网络可能具有挑战性。这是 当 hyper-parameters 的初始选择产生 结果并不比随机噪声好。假设我们尝试成功的 30 隐藏的神经元网络架构,但随着学习 Rate 更改为η=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
现在想象一下,我们是第一次遇到这个问题。 当然,我们从早期的实验中知道,正确的 要做的是降低学习率。但是,如果我们要来 第一次解决这个问题,那么 output 来指导我们做什么。我们可能不仅担心 学习率,而是关于我们神经网络的所有其他方面。我们 可能会想知道我们是否以某种方式初始化了权重和偏差 使网络难以学习?或者我们可能没有足够的 训练数据以获得有意义的学习?也许我们还没有竞选 足够的纪元?或者,对于具有 这种架构要学会识别手写数字吗?也许 学习率太?或者,也许,学习率太高了 高?当您第一次遇到问题时,您不是 永远确定。

从中可以得到的教训是,调试神经网络 并非微不足道,而且,就像普通编程一样,有一门艺术 到它。你需要学习调试的技巧才能变得更好 来自神经网络的结果。更广泛地说,我们需要开发 用于选择好的超参数和好的架构的启发式方法。 我们将在本书中详细讨论所有这些,包括我如何 选择了上面的超参数。

锻炼

之前,我跳过了有关如何加载 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:我们来看看图像有多。例如, 图像2通常比1,只是因为更多的像素被涂黑,如下所示 示例说明:

这表明使用训练数据来计算平均暗度 对于每个数字,0,1,2,,9.当显示新图像时, 我们计算图像的暗度,然后猜测它是 digit 具有最接近的平均暗度。这是一个简单的过程, 并且很容易编码,所以我不会显式写出代码 - 如果您有兴趣,可以在 GitHub 中 存储库。但这比随机猜测有了很大的改进, 获取2,22510,000测试图像是否正确,即22.25百分比精度。

不难找到其他在2050百分比范围。如果你努力一点,你就能站起来 多50百分之。但是为了获得更高的精度,使用 已建立的机器学习算法。让我们尝试使用其中一个 最知名的算法,支持向量 machineSVM 的 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 张进行分类 正确。这是李 WanMatthew Zeiler, 西辛 Zhang、Yann LeCunRob 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 构建复杂概念层次结构的能力。 这有点像传统编程语言使用模块化的方式 关于抽象的设计和想法,以实现复杂的创建 计算机程序。将深度网络与浅层网络进行比较是 有点像将编程语言与 函数调用对精简语言的调用,无法进行 这样的电话。抽象在神经网络中采用不同的形式 比在传统编程中更重要,但它同样重要。