Deep learning - Chapt 1 - Using neural nets to recognize handwritten digits
深度学习 - 第一章 - 使用神经网来识别手写数字
2017-03-26 | python
Overview
[转][译] 《Neural Networks and Deep Learning - Chapt 1》 — Michael Nielsen
人类的视觉系统是这个世界上最为奇妙的事物之一,面对一些手写的数字,人们可以很轻易的识别它,然而这种轻松是具有欺诈性的。在我们每一半大脑中,人们都有一个主要的视觉皮层也就是我们已知的V1(初级视皮层,亦称纹状皮层),包含了1.4亿个神经元以及100亿个相互作用的连接。当然,人类的视力还不仅仅只是是拥有V1,而是一整个系列的视觉皮层 – V2、V3、V4和V5会连续渐进的进行复杂的图像处理。我们给我们的头脑携带了一个超级电脑,他经历了数亿年的进化,使自己适应并理解这个可视的世界,所以辨识手写数字并不简单。我们人类是神奇的,擅长于理解我们眼睛所看的东西,但是完成这些工作却是不经意间的,令人惊讶。所以我们通常无法领会我们的视觉系统如何解决这样困难的问题。
如果你尝试去写出一个类似以上描述的电脑程序来辨识数字,视觉辨识部分很明显是很困难的。在我们看来似乎很简单的东西突然变得极为困难,简单的凭直觉如何来辨识外形 - “一个数字9,上面有一个圈,右下方又一个竖线”,结果是并不能那么简单的在算法上表示出来。当你尝试去明确这些规则,你很快会迷失在一堆异议、警告和特殊情况的困境中,这是不希望的。
神经网络利用了不同的方法来着手处理这个问题,就是利用大量的手写数字作为已知的训练样本

然后开发一个系统来学习这些训练样本,换句话说,神经网络利用样本来自动的推出规则来辨识手写数字,更进一步的说就是,利用不断增多的悬链样本,神经网络能够学到更多关于手写内容来提升准确率,因此我已经展示了以上100个训练数字,也许我们可以利用成千上万甚至上亿的训练样本来创建一个更好的手写识别器。
在这一章节中,我们会编写一个电脑程序来实现这个神经网络来学习识别手写数字,这段程序只有74行,不利用任何神经网络库,但这个小程序能够在没有人介入并拥有96%准确率的情况下来识别手写数字,进一步说在之后的章节中我们会开发新的主意来提升性能至99%以上。实际上,最好的商业神经网络现在已经被很好的使用在银行中来处理支票,利用邮件办公室来识别地址。
我们致力于手写内容识别是因为它可以作为一个通用的神经网络模型,他可以用来开发更高级的技术,比如深度学习。在之后的内容中,我们会讨论这些点子是如何被应用在计算机视觉、语音、自然语言处理甚至是其他领域。
当然,如果只是为了写一个程序来识别手写数字,那么这章节就太短了!所以沿途我们会开发许多关关于神经网络键的点子,包括2个重要的人造神经元类型(感知器和sigmoid神经元),还有标准的神经网络学习算法,如已知的SGD(随机梯度下降算法),全文我都会专注于解释为什么事物会被这样完成,还有建立你的神经网络直觉。比起如果我只是陈述基础的原理,那更需要一个漫长的讨论,但是对你达到深入理解是很有价值的,在章节结束我们能够理解什么是深度学习,还有为什么他有影响。
Contents
- Perceptrons - 感知器
- Sigmoid Neurons - Sigmoid神经元
- The architecture of neural networks - 神经网络结构
- A Simple network to classify handwritten digits - 一个分类手写数字的简单网络
- Learning with gradient descent - 学习梯度下降
Perceptrons
什么是神经网络?作为开始,我会解释一个叫做感知器的人工神经,感知器是在1950和1960年由Frank Rosenblatt开发,受到更早Warren McCulloch和]Walter Pitts的启发。如今它已经非常普遍的在其他模型中使用人工神经元 - 在本书中,并且在大多数神经网络模型中,主要使用的是一个叫Sigmoid神经元的东西。我们会对sigmoid有个简短的接触,但是可以让我们了解为什么sigmoid神经元被定义为这种工作方式,让我们一起来花点时间来看下第一个感知器。
感知器怎么工作?一个感知器有多个二进制输入\(x_1\), \(x_2\), …, 并且处理成单独一个二进制输出:

这个例子中显示的感知器有3个输入\(x_1\), \(x_2\)和\(x_3\),通常来说它可以有更多的输入,Rosenblatt提出一个简单的规则来计算输出,他介绍了权重\(\text{weights }\), \(w_2\)和\(w_2\), …,真实的数字表示各个输入对于输出的重要性,神经元的输出是0或1由权重总和\(\sum_jw_jx_j\)是否小于或大于某个阈值来决定,就比如权重,这个阈值是神经元的一个真实的数据,明确一点的代数表达式如下:
\[output = \begin{cases} 0 & \text{if } \sum_jw_jx_j \leq \text{ threshold} \\ 1 & \text{if } \sum_jw_jx_j > \text{ threshold} \end{cases}\]这感知器如何工作的全部。
这是个基础的数学模型,你可以把感知器认为是一个可以权衡证据来做决策的设备,这还不是一个非常现实的示例,但他可以让你简单的理解,我们很亏会接触一个更现实的例子。假设下周末即将来临,你已经听说了在你的城市会举办一个国际象棋活动,你也喜欢国际象棋,你在尝试做决定是否要参加这个活动,你做决定可能会权衡以下3个因素:
- 天气是否晴朗?
- 你的男/女朋友是否陪你一起?
- 活动地点是否在公共交通设施附近?(你没有自己的车)
我们可以把这3个因素展示成二进制的变量\(x_1\), \(x_2\)和\(x_3\), 在这个实例中,如果天气好我们有\(x_1 = 1\), 如果天气不好我们有\(x_1 = 0\), 同样的\(x_2 = 1\)代表如果你的男/女朋友也想去,而\(x_2 = 0\)表示不去,同样的也可以用在公共交通设施上。
现在,假设你是绝对的喜欢国际象棋,甚至说你去参加活动无关男/女朋友他是否对活动感兴趣或者去达场地有多困难,但是你真的非常讨厌坏天气,而且如果天气不好你没有办法去活动场地,你可以使用感知器来完成这个决策模型,一种发式是选择天气的权重\(w_1 = 6\), 其他的设为\(w_2 = 2, w_3 = 2\),\(w_1\)这个更大点的说字说明了他影响你更多,比男女朋友陪你参加货距离公共交通设施更近更重要,最后,你选择了一个阈值为5的感知器,根据这些选择,感知器得到了一个决策模型,只要天气好就输出1,只要天气差就输出0,这个无关另外两个条件。
利用多样的权重和阈值,我们可以得到不同的决策模型,比如我们利用阈值3来代替,那么我们的感知器会决定当天气是好的或者另外两个条件同时满足时你去参加活动,换句话说,这就是一个不同的决策模型了,降低阈值代表了你有更强烈的欲望去参加活动。
很明显,感知器不适宜颚完成的人类决策模型,但这个例子阐明了感知器是如何权衡各种类型的证据来做决策的,并且一个复杂的感知器网络可以做一个相当精确的决策,这个决策还是比较可信的。

在这个网络中,第一列感知器(我们称之为第一层感知器),会根据输入的证据做3个非常简单的决策,那么第二层感知器呢?每一个感知器会权衡第一层做的决定结果做一个决策,这样的话第二层的感知器可以做一个比第一层感知器更复杂更抽象级别的决策,而且还可以在第三层感知器上做更复杂的决定,这么一来,一个多层感知器网络可以作用于更复杂的决策。
顺带一提,当我定义多个感知器的时候我说过一个感知器直邮一个输出,在上面的网络中感知器看上去有多个输出,实际上她们仍然是单个输出,这多个输出箭头仅仅只是表示某个感知器的输出作为其他多个感知器的输入,
让我们简化感知器的描述,条件\(\sum_jw_jx_j > \text{threshold}\)是笨重的,我们可以创造2个计数变化来简化它,第一个变化是将\(\sum_jw_jx_j\)写成一个点积\(w\cdot x = \sum_jw_jx_j\), 这里\(w\)和\(x\)是权重和输入的向量,第二个改变是将\(\text{threshold}\)改动到不等式的另一边,并且把它改成我们已知的感知器的偏差\(bias\),\(b \equiv -\text{threshold}\),利用偏差来代替阈值,感知器规则可以被重写成:
\[output = \begin{cases} 0 & \text{if } w\cdot x + b \leq 0 \\ 1 & \text{if } w\cdot x + b > 0 \end{cases}\]你可以将偏差想成是一个感知器有多简单输出1的测量标准,或者说测量一个感知器有多容易被触发,当感知器拥有一个相当大的偏差时,他会非常容易的输出1,但是当它为负时,感知器就很难输出1了,很明显偏差在我们描述感知器的时候只会有一个小的改变,但是我们会在之后看到它进一步简单化计算。正是因为如此,这本书中我们不会再用阈值,我们会一直使用偏差\(bias\)。
我已经描述过感知器是一个权衡证据来做决策的方法,另一方面,感知器可以被用来计算我们经常隐藏于计算下的基础逻辑功能,功能比如 AND, OR和NAND,举个例子,假设我们友谊颚感知器有两个输出,权重都是-2,并且全部的偏差时3,如下图:

然后我们输入00,便输出了1,因为\((-2) * 0 + (-2) * 0 + 3 = 3\)是正数,我已经介绍过符号*是显式的乘法,同样的计算输入为01和10时产出1,但是输入为11的时候产出是0,因为\((-2) * 1 + (-2) * 1 + 3 = -1\)是一个负值,因此我们的感知器实现了一个NAND(与非门)!
NAND与非门例子表明我们可以用感知器来计算简单的逻辑功能,实际上我们可以使用的感知器网络来计算任何逻辑功能,原因是因为与非门是通用的计算,那就是说,我们可以基于与非门创立任何的计算,比如,我们可以利用与非门来创建一个添加2个比特的电路,\(x_1\)和\(x_2\),这就需要进行按位求和(bitwise sum), \(x_1 \bigoplus x_2\),同样的当\(x_1\)和\(x_2\)同为1将会被设置为1时进位位(carry bit)会被设置成1,就好比进位位时它们的积\(x_1x_2\):

为了得到一个相等的感知器网络,我们将所有的与非门替换成2个输入的感知器,每个的权重为-2偏差为3,以下就是这个网络:

感知器网络中一个值得注意的方面是最左边的感知器的输出被2次用来作为最下边感知器的输入,当我定义感知器模型时,我不曾说过是否这种“两次输出到同一地方”是被允许的,实际上,这没什么太大关系,如果我们不希望允许这类事,那么我们也许可以简单的将两条线合并到一条中去,并且设置权重为-4来代替-2(如果你没很明显的发现,你因该先停下来然后证明给你自己看这是相等的),根据这些改动,这图看上去如下,所有未标记的权重都是-2,所有偏差都是3,仅有一个权重为-4:

到目前为止,我已经在感知器左边绘制出了像\(x_1\)和\(x_2\)这样的变量,实际上给感知器在额外绘制一个层更符合传统 – 输入层,来给输入编码:

这个感知器输入的符号它有一个输出,但是没有输入:

这是一个简化部分,他并不意味着感知器可以没有输入,看这个,我们只是假设有这么一个感知器没有输入,然后这个权重和总是为0,\(\sum_jw_jx_j\), 因此如果\(b > 0\)感知器会输出1,如果\(b \leq 0\)感知器会输出0,就是说感知器会简单的输出一个修正过的数值,而不是所期望的值(比如之前的\(x_1\)), 因此我们可以更好的把输入感知器不当成感知器,但是是一个更特殊的单元,他只是简单的定义了一个我们所期望的输出值\(x_1, x_2,\)…
这个计数器例子展示了感知器网络如何使用多个NAND与非门来模拟电路,因为与非门是普遍的计算,所以感知器也是通用的计算。
这种感知器的通用性计算,既让我们安心又让我们失望,让我们安心是因为他告诉我们感知器网络可以像其他计算设备一样强大,但是同样也让我们失望是因为他让我们看起来感知器仅仅只是一个新的与非门,他几乎不是什么大的新东西!
但情况比这篇陈述的观点要好,他给了我们一个结果,我们可以设计一个自动调整人工神经元网络权重weights和偏差biases的学习算法,这个调整发生在受到外部刺激时,而不是有程序人员的直接介入,这种学习算法让我们有了一种方式能够使用与传统逻辑门完全不同的人工神经元,替代显式的与非门或是其他门展示的电路,我们的神经网络能很轻松的来学习解决问题,有时候直接的设计传统电路时非常困难的一个问题。
Sigmoid Neurons
学习算法听起来很可怕,但我们怎么能设计一个这样的神经网络算法呢?假设我们有一个感知器网络,我们要用来学习去解决一些问题,比如,这个网络可能会通过扫描输入一些手写数字图片的像素源数据,然后我们希望网络来学习权重和偏差来实现对输出数字的正确分类,来看下这个学习时怎么工作的,假设我们对权重或是偏差做一些小的改动,我们希望看到的是他只会对输出产生一个相应很小的变化,我们会在之后看到这个属性将会使学习成为可能,按计划,这就是我们想要的(很显然这个网络来做手写内容分类太过于简单了!)

如果权重或是偏差的小变动引起输出的小变动成立的话,那么我们就可以利用改动权重和偏差来让我们的网络按照我们想要的方式运行,比如,假设网络分类8时犯了个错,他应该是9,那么我们就应该指出他并且对权重和偏差做个小改动来让她更接近于可以分类识别出9,之后不断重复他,不断的改变权重和偏差来产出更好的输出,网络便学习了。
这里的问题是当我们的网络包含了感知器时的表现并不是这么表现的,实际上,任何一个感知器的权重或者偏差的一个小变动有时会引起感知器输出完全跳跃,就是说0到1,那么这种跳跃可能会引起网络行为的完全改变,所以你的9可能会被正确识别了,但是你的网络对其他图片的识别会变的难以控制,这就导致他很难看到他如何渐进的改变权重和偏差来得到我们想要的行为,也许这里有一些聪明的方式来避开这个问题,但是他不能立刻明显的看出我们是如何让感知器网络去学习的。
我们利用一个叫\(sigmoid\)神经元的新类型人工神经元来克服这个问题,Sigmoid神经元蕾丝与感知器,但是他的改动可以让权重或偏差的变动只对输出产生很小的影响,这是一个允许引用sigmoid神经元网络来学习的重要原因。
好吧,接下来让我来介绍下sigmoid神经元,我们会像描述感知器那样描述sigmoid神经元:

就像一个感知器,sigmoid神经元有多个输入,\(x_1, x_2, ....\)但是替代仅仅为0或1,他的输入可以是任何0到1之间的值,所以,实例中, 0.638 …是sigmoid神经元的一个有效输入,同样像感知器,sigmoid神经元对每个输入有自己的权重\(w_1, w_2, ...\), 和一个总偏差bias, \(b\)。但是输出不再是0或1,而是\(\sigma(w\cdot x + b)\),\(\sigma\)被称之为\(sigmoid function_*\),他的定义是:
\[\sigma(z) \equiv \frac{1}{1 + e^{-z}}\]显式的把全部放进去,sigmoid神经元的输出就变成了:
\[\frac{1}{1 + exp(-\sum_jw_jx_j - b)}\]第一眼看去,sigmoid神经元跟感知器非常不一样,Sigmoid函数的代数形式看上不那么透明,如果你不熟悉可以禁用他,实际上sigmoid神经元和感知器有很多相似的地方,Sigmoid函数的代数形式产生更多的是技术细节,而不是真正的理解障碍。
为了了解感知器模型的相似性,假设\(z \equiv w\cdot x + b\)是一个非常大的正数,那么\(e^{-z} \approx 0\)所以\(\sigma(z) \approx 1\),换句话说当\(z \equiv w\cdot x + b\)是很大的正数,sigmoid神经元的输出约等于1,他就好像感知器一样,假设另一方面,如果\(z \equiv w\cdot x + b\)是一个非常大的负数,那么\(e^{-z} \rightarrow \infty\), \(\sigma(z) \approx 0\), 所以当\(z \equiv w\cdot x + b\)是个很大负数的时候,Sigmoid神经元也近乎约等于感知器,仅仅当\(w\cdot x + b\)有适当的模型尺寸时,才会与感知器模型有比较大的偏差。
\(\sigma\)的代数形式怎么样呢?我们怎么理解他?实际上他真实的形式并不是很重要 – 真正重要的其实是绘出的他的函数形状,如下:

这种形状是一个平缓输出版本的阶梯函数:

如果\(\sigma\)是个阶梯函数,那么sigmoid神经元将会是一个感知器,因为输出会根据\(w\cdot x + b\)是正数或负数来决定是1或是0。
实际上, 当\(w\cdot x + b = 0\),感知器输出0,阶梯函数输出为1,所以严格的说,我们需要修改阶梯函数的一头,但你需要一个想法。
通过使用我们得到的实际的\(\sigma\)函数,我们发现,实际上\(\sigma\)函数的平滑度才是他的重要因素,而不是他的具体形式,\(\sigma\)函数的平滑度意味着在神经元中权重的小变动\(\Delta w\)和偏差的小变动\(\Delta b\)会产生一个输出的小变动\(\Delta \mbox{output}\),计算告诉我们\(\Delta \mbox{output}\)的值约是:
\[\Delta \mbox{output} \approx \sum_j \frac{\partial \, \mbox{output}}{\partial w_j} \Delta w_j + \frac{\partial \, \mbox{output}}{\partial b}\Delta b\]这里总和大于所有的权重\(w_j\),\(\partial \mbox{output}/\partial w_j\)和\(\partial \mbox{output}/\partial b\)分别表示输出相对于\(w_j\)和\(b\)的偏导数。你如果对偏导数感到不舒服也不用惊慌!上面的表达式看上去很复杂,所有的偏导数实际上说的还是很简单的(这是个好消息): \(\Delta \mbox{output}\)在权重和偏差中是针对\(\Delta w_j\)和\(\Delta b\)变化的一个线性函数(\(linear function\)), 这种线性特性让我们很容易的可以选择权重或偏差的小改动来达到我们希望的输出值的小改动。所以当Sigmoid神经元和感知器拥有很多相同性质的行为时,它可以更轻松的指出输出变化是如何受到权重和偏差变化影响的。
如果\(\sigma\)他的形状真的很重要,而不是他的形式,那么为什么要用等式\(\sigma(z) \equiv \frac{1}{1 + e^{-z}}\), 实际上,在之后我们会偶尔的考虑下让神经元使用其他的激活函数\(f(\cdot)\)来输出\(f(w\cdot x + b)\)。当我们使用不同激活函数时,最主要变化的是等式\(\Delta \mbox{output} \approx \sum_j \frac{\partial \, \mbox{output}}{\partial w_j} \Delta w_j + \frac{\partial \, \mbox{output}}{\partial b}\Delta b\)中特定值的偏导数。事实证明,但我们稍后计算偏导数的时候,使用\(\sigma\)会简化代数,只是因为指数在微分时有它可爱的特性,无论如何,\(\sigma\)通畅用于神经网络工作,他同样也是我们本书中常用的激活函数。
我们如何来描述一个sigmoid神经元的输出呢?很明显,sigmoid神经元与感知器的一个最大的不同就是输出不止为0或1,它可以输出任意0到1之间的真实数字,所以这些值可以是0.173 … 和0.689 …。这是很有用的,比如,我们希望从神经元网络的输出中展示出输入图片像素值的平均强度。但有时候也是令人讨厌的,假设我们希望从神经网络的输出来确定“输入图片是9”或者“输入图片不是9”,很明显,如果像感知器一样输出是0或1的时候是很容易做到的,但在实际当中,我们可以建立一个约定来处理他,比如,有任意至少大于0.5的输出来决定这是个“9”,有任意小于0.5的输出就“不是9”,当我们使用这个约定的时候,我总是会声明他,这样就不会引起混淆。
Exercises 1
- Sigmoid神经元模拟感知器,第一部分:
假设我们让感知器网络中的所有权重和偏差乘以一个正数常数\(c > 0\),证明网络的行为不会发生变化。
- Sigmoid神经元模拟感知器,第二部分:
假设我们有与最后一个问题相同的设置 - 感知器网络。假设同样的感知器网络全部的输入都已经被选择了,我们不一定要的是真实的输入址,我们只需要一个被修正过的值。假设在网络中任意特殊感知器的输入为\(X\),权重和偏差为\(w\cdot x + b \neq 0\), 现在将网络中所有的感知器替换成sigmoid神经元,然后乘上一个正的常数\(c > 0\), 当\(c \rightarrow \infty\)时证明sigmoid神经元网络跟感知器网络的行为几乎是一样的,当其中一个感知器\(w\cdot x + b = 0\)时他为什么会失效?
The architecture of neural networks
在下一步分钟,我会介绍一个能很好分类手写数字的神经网络,为了做准备,我们来命名网络的不同部分,这对我们解释一些术语很有帮助,假设我们有如下网络:

在之前提到过,网络最左边的这层叫输入层,这层中的神经元被称之为输入神经元,最右侧层或者说输出层,包含了输出神经元,在这个例子中就是一个单独的输出神经元,中间层我们称之为隐藏层,因为这层中的神经元既不是输入也不是输出,术语“hidden(隐藏)”听起来可能还有些神秘 – 我第一次听说他的时候我觉得他肯定有一些很深的哲学或是数学的意义 – 但事实上他她什么意义都没有,甚至都不能说“既不是输入也不是输出”,以上的网络里仅仅只有一层隐藏层,但是很多的其他网络也许有很多隐藏层,比如以下这个4层网络就有2个隐藏层:

有些混乱,由于历史原因,这种多层网络有时候也被称之为多层感知器(Multilayer perceptrons)或者MLPs, 尽管它使用的是sigmoid神经元而不是感知器。我不会在这本书中使用MLP,因为我觉得他有点混乱,但是必须要提醒你他的存在。
通常在网络中定义输入和输出是简单的。比如我们要确定一个手写数字图片是否描述为9,一个很自然的方式来设计网络就是将图片编码成强壮的像素放到输入神经元中, 如果图片是一个64乘64的灰度图片,那么我们就需要\(4096 = 64 \times 64\)的输入神经元,他们各自的强度在0到1之间适当的调整,输出层只需要包含一个神经元,输出值小于0.5表明他不是9,大于0.5表明它是9。
虽然神经网络的输入和输出层通常是简单的,但隐藏层的设计可以是非常艺术的。特别要说的是,我们不可能用一些简单的经验法则来总结隐藏层的设计过程。相反,神经网络研究人员已经为隐藏层开发了很多设计启发式,比如,这种启发式可以被用来指出如何针对需要的时间减少隐藏层来训练网络,我们会看到一些类似的设计启发式在稍后的文章中。
到目前为止,我们已经讨论了神经网络是一层的输出用来作为下一层的输入的,像这样的网络我们称之为前馈神经网络(feedforward),这就意味着这个神经网络中是没有循环的 – 信息总是向前的,从不会向后,如果我们将其循环,那么我们就必须结束这种将输出输入到\(\sigma\)函数的情况,那就会变的很难讲通,所以这里我们不允许这种循环。
但是,也存在另外一种人工神经网络模型,他让反馈循环变得可能,我们称之为“循环神经网络”(recurrent neural networks),这个模型的想法是在静止前的有限的持续的一段时间内激发神经元。那种激发可以刺激其他的神经元,而那些神经元可能也会在稍后激发,也可能持续有限。这也会导致更多的神经元激发,随着时间的推移,我们会得到一个级联的激发神经元群。在这个模型中,循环不引起问题,因为一个神经元的输出仅仅只在稍后的时间内影响他的输入,而不是瞬时的。
循环神经网络的影响力要比前馈神经网络的影响力小,一部分原因是因为他的学习算法还比较弱。(到目前为止)但是循环神经网络仍然是相当有趣的,他在思想上比前馈神经网络更接近于我们大脑的工作。而且他可能有时能解决一些前馈神经网络难以解决的重要问题。但是,为了缩小我们的范围,这本书中我们会更专注于广泛使用的前馈神经网络。
A Simple network to classify handwritten digits
定义好了神经网络,现在让我们回归手写分类问题,我们可以拆分这个问题成2个小问题。首先,我们需要把一个包含了很多数字的图片拆成一系列只包含一个数字的单独的图片,比如我们想要把下面的图片拆分:

将其变成6个单独的图片:

我们人类可以很轻易的分解问题,但是这对电脑程序来说,正确的分割图片是一个挑战。当图片被真确分割后,程序就需要来分类没个单独的数字,所以,这个实例中,我们希望程序来识别上面的第一个数字,

是一个“5”。
我们着重来写一个程序来解决第二个问题,分类这些单独的数字。事实证明当你有一个很好的分类数字的方法时,分割问题不是很难去解决,有很多方法可以去解决分割问题,其中之一就是尝试不同的分割法,利用单独的数字分类器来记录每次尝试行的分割。如果分类器对所有分割片段有把握,那么它会有一个高的评分,如果分类器在分类时在一个或多个片段上遇到麻烦,那么就会给个低分,这个想法是,如果分类器在哪遇到了麻烦,说明他的片段可能选错了,这个方法和其他更多样的方法都可以很好的用来解决切割问题。所以与其担心图片分割,我们会专注于开发一个可以解决更多有趣和复杂问题的神经网络,也就是识别单独的手写数字。
识别一个单独的数字,我们会用到一个3层神经网络:

神经网络的输入层包含了图片编码后的所有值,如下一节所述,我们的网络的的训练数据是由臊面的手写数字,\(28 \times 28\)像素的图片组成,所以输入层有\(784 = 28 \times 28\)个神经元,简单起见,我已经省略了上图中784个输入中的大部分神经元,输入的像素是灰度的,0.0表示白色,1.0表示黑色,中间的值表示逐渐变黑的灰色。
第二层为隐藏层,我们能将隐藏层中的神经元个数用n表示,我们会尝试不同数值的n,这个例子当中展示了一个包含n=15个神经元的隐藏层。
网络的输出层包含了10个神经元,如果第一个神经元激发了,比如他的输出约等于1,那么那就说表明网络认为这个数字是0,如果第二个神经元激发了那么网络认为这个数字是1,以此类推。再说准确点,我们将输出神经元从0到9编号,并且指出哪个神经元有最大的激活值,如果哪个神经元是编号为6的神经元,那么网络会猜测输入的数字是6,其他输出神经元也是一样。
你可能会疑惑,为什么我们用了10个输出神经元,毕竟这个网络的目标是告诉我们输入的是哪个数字(0,1,2,…,9)。一个看似自然的方法是利用4个神经元,根据神经元输出是否接近0或1,将其视为一个二进制值。4个神经元足够用来编码结果了,因为\(2^4 = 16\)比输入数字的10个可能的值还要多,为什么我们要用10个神经元来代替?是不是没有效率?最终的理由是经验性的:我们可以尝试两种网络的设计,他的结果会显示,10个输出神经元学习识别数字会比4个输出神经元好。但是这会留给我们疑问,为什么使用10个输出神经元会工作的更好,有没有一些启发式教育告诉我们应该用10个输出来编码还是用4个输出来编码?
来理解为什么我们这么做,他帮助我们理解神经网络从第一原则处做了什么,首先考虑下我们用的10歌输出神经元的情况,让我们集中精力在第一个输出神经元上,这个神经元决定了数字是否为0,他依靠权衡从隐藏层获得的证据来做到这些,那么这些隐藏层神经元是做什么的?那么,让我们设想下隐藏层的第一个神经元参数的目标是检查出图片是否如下图所呈现出的那样:

它可以加重重叠部分图像输入像素的权重,减轻其他输入部分权重,同样的,让我们假设下第二、三和四个隐藏层神经元,他们的参数的目标也是检查图片的呈现是否如下:

可能你已经猜到了,这4张图组成了数字0,就是之前那张一行数字中的一个数字:

所以,如果全部的这4个隐藏神经元激发了,那么我们可以得出结论这个数字是0,当然,这一系列证据并不是我们能够得出图片是0的唯一证据,我们也可以从其他合理的途径来得到这个0(比如,通过上图的翻译,或者轻微的变形)。但似乎我们可以肯定地说,在这个例子中我们至少能得出结论输入的是0.
假设神经网络是以这种方式工作,我们就可以给出一个可信的解释,为什么10个输出好,而不是4。如果我们是有4个输出,那么第一个输出神经元将会尝试决定数字的最高有效位,然而目前并没有什么简单的方法将最高有效位与上述图片形状联系起来,很难想象,有一些好的历史因素是能够让数字组成形状与最高有效位密切相关联的。
现在,所有的这一切都仅仅只是个启发,没说三层神经网络必须要以我描述的这种利用隐藏层简单检测组建形状的方式来操作,也许一个更聪明的学习算法可以找到让我们只用4个输出神经元的权重赋值。但是作为一个启发式教育,我所描述的思维通常比较好,可以帮你节省很多时间来设计一个不错的神经网络架构。
Exercises 2
- 现在有一种方式,可以通过给上面的3层网络额外的添加一层来逐位的确定一个数字,额外的这一层把从上一层得到的输出转换成二进制输出,如下图所示,找到一层新的权重和偏差的输出层,假设前面3层神经元在第三层上有一个正确的输出(即,原先的输出层),正确的大于0.99,错误的小于0.01:

Learning with gradient descent
现在我们已经有了一个自己设计的神经网络。那么他如何来学习识别数字呢?首先我们需要一个用来学习的数据集 – 一个所谓的训练数据集,我们会使用MNIST data set,他包含了上万的手写数字扫描图,同时在一起的还有他正确的分类,MNIST的名字实际上是由NIST收集来的两个数据集修改后的子集得来的,美国国家标准和技术研究所,如下就是一些MNIST中的图片:

你可以看到,这些数字实际上根这一张之前为了挑战识别所展现的数字是一样的,当然,当我们测试我们的网络时,我们会让她去识别哪些不在训练数据集中的图片。
MNIST的数据有两部分,第一部分包含60,000张图片用作训练,他们是由250人的手写扫描图所得,其中一半是美国人口普查局的员工,另一半是高校学生。这些图片是由\(28 \times 28\)像素组成的灰度图,第二部分是10,000张用作与测试的数据,也是\(28 \times 28\)的灰度图。我们会用测试数据来评价我们的网络学习辨识数字的情况,为了让测试数据有更好的效果,这些数据是来源于另外250个人而不是原先的250人(虽然仍然是人口普查局员工和高校学生)。这有助于我们确信我们的系统可以识别在训练期间没有看到过的数字。
我们会用\(x\)来表示训练的输入,我们将输入\(x\)视为\(28 \times 28 = 784\)维的向量数组将给我们带来便利,数组中每一项都代表一个单独的像素的灰度值,我们会表示对应希望得到的输出为\(y = y(x)\),\(y\)是一个10维的数组。比如,如果一个特定的训练图像描述为6,那么\(y(x) = (0,0,0,0,0,0,1,0,0,0)^T\)是我们希望从网络中得到的输出
*注意这里的T是转置操作,他将一个行向量数组转换为普通的(列向量)数组。
我们希望有一个算法可以让我们找到这样的权重和偏差,以便网络的输出对于所有的网络输入\(x\)近似于\(y(x)\),为了量化我们达到这个目标的程度,我么你定义了一个成本函数*:
\[C(w, b) \equiv \frac{1}{2n} \sum_x \|y(x)-a\|^2\]*有时候我们也称之为损失函数(loss)或目标函数(objective),我们在本书中使用术语“成本函数”,但是你需要注意他的其他术语,因为他们经常被用在一些研究论文或是其他神经网络讨论中。
这里,\(x\)表示所有权重的集合,\(b\)表示所有的偏差,\(n\)表示训练输入数字的总数,\(a\)是当\(x\)为输入时从神经网络输出来的数组,并且他的总和大于所有的训练输入,当然输出a是依赖于x, w和b的,但是为了让符号足够简单,我并没有显示的指出他的依赖,符号\(\|v\|\)通常表示为v数组的长度函数,我们称C为二次成本函数(quadratic cost function);他有时候也被称为均方误差(mean squared error)或MSE,检查下二次成本函数,我们可以看到\(C(w, b)\)不为负,因为总和中每一项都不为负,此外,对于所有的训练输入x来说,精确的当\(y(x)\)近乎等于输出a的时候\(C(w,b) \approx 0\),所以我们的学习算法如果能找到权重和偏差让\(C(w,b) \approx 0\),那么他就已经完成了一个很好的工作,相比之下,当\(C(w, b)\)值很大时就做的不是很好 – 这就意味着在大量数字的输入下\(y(x)\)并不接近于输出a,所以我们训练算法的目标是将成本\(C(w, b)\)作为权重和偏差的函数来最小化他,换句话说,我们要找到一个权重和偏差的集合来让成本尽可能的小,我们会用一个算法名叫梯度下降(gradient descent)来做到他。
为什么要引入二次成本?毕竟,我们的主要兴趣难道不是在于网络能够正确分类的图片数量吗?为什么不试图直接最大化那个数字,而是以最小化二次成本来做为代理衡量?问题在于,网络正确分类图像并不是一个对于权重和偏差的平滑函数,大部分情况下,权重和偏差的小改动并不会对正确分类训练图片数量产生影响。这就导致我们很难指出如何去调整权重和偏差来优化性能。如果我们使用一个平滑的像二次成本这样的成本函数来代替,那么结果很容易指出如何通过对权重和偏差做很小的改动来优化成本。这就是为什么我们专注于最小化二次成本,也只有那样之后,我们才能测试我们的分类精度。
即使我们想要用一个平滑的成本函数,你仍然可能会质疑为什么我们选择上面这个二次函数,这不是一个相当特别的选择吗?也许我们选择一个不同的成本函数我们会得到一个完全不同的最小化权重和偏差的集合,这是一个有效的注意点,稍后我们会重新审查成本函数,并且做一些修改,但是上面的这个二次成本函数非常适用于理解神经网络学习的基础知识,因此目前为止我们会坚持使用它。
重申,我们训练神经网络的目标是找到可以最小化二次成本函数\(C(w, b)\)的权重和偏差,这是一个很恰当的问题,但是目前有很多令人分心的结构,比如将w和b解释为权重和偏差、潜藏在背后的\(\sigma\)函数、网络架构的选择、MNIST等等。事实证明,我们可以忽略大部分的结构而理解大量信息,我们只需要专注于最小化方面,所以现在我们要忘记所有关于成本函数的具体形式、神经网络连接等等…相反的,我们要想象,我们被给予了一个函数,他已经拥有很多的变量,我们要最小化这个函数。我们会开发一个技术名叫梯度下降(gradient descent)来解决这种最小化问题,之后我们才会回到我们希望最小化神经网络的特定函数。
好,那么让我们来假设下我们正在尝试最小化一个函数\(C(v)\),这可以是任意多参数的真实数值函数,\(v = v_1, v_2,...\),注意我已经将w和b用v来替换了,为了强调这可以是任意函数 – 我们不用在神经网络上下文中特别的考虑。为了最小化\(C(v)\)他帮助我们想象C是一个仅有两个被称之为\(v_1\)和\(v_2\)的参数的函数:
.png)
我们想要的是找到C能达到的最低水平的地方,现在,当然,根据上面这张绘制的函数图,我们可以看到图并找到最小值,在这层意义上,我们可能把图稍微显示的太简单了点!一个正常的函数C,可能是一个有很多变量的复杂函数,并且他通常不太可能只通过肉眼看来找到最小值。
一种针对问题的方式是利用微积分尝试在分析中找到最小值,我们可以利用求导,然后尝试去找到函数C中的极值的地方,运气好,当C是一个直邮1个或几个变量的函数时他可能会工作,但是当变量更多时,他就会变成噩梦。对于神经网络,我们通常都拥有非常多的变量 – 最大的神经网络的成本函数依赖于几十亿的权重和偏差,这是非常复杂的,使用微积分来最小化是不工作的。
(在申明将仅用两个变量的函数C来了解后,我已经在两个段落中来回两次说道:“嘿,但是如果它是一个多变量的函数呢?”,对此很抱歉,请相信我,当我说一个两变量的函数C真的很有帮助,他只发生在有时图片破裂的时候,在接下来的两段中回处理这类破裂问题,对数学的良好思考通常包含欺诈多个直观的图片,学习合适去使用适当的图片,何时不用)
好,所以微积分不工作,幸运的是,有一个很好的比喻,他表明了哪个算法工作的很好。我们从把我们的函数看作是一个山谷开始,如果我们只要瞥一眼上面的绘图,那不会很困难,然后我们想象一下有一个球从山谷的斜坡滚落,我们的尝试烤酥我们球最终会滚到山谷的底部,也许我们可以利用这个想法做为一个方法来找到函数的最小值?我们会随机选择一个(虚拟的)球的起点,然后模拟球滚动到山谷底部的运动。我们可以很简单的通过求导(也许会需要二次求导)来模拟C – 那些求导会告诉我们所有我们需要知道的山谷的“形状”,并且因此得到的球是如何滚动的。
依据刚才缩写的,你可能会猜想我们会来尝试写下球的牛顿的运动学公式、考虑摩擦力影响还有中立等等…实际上,我们并不需要把球的滚动那么严格的类比 – 我们在设计一个最小化C的算法,而不是开发一个物理定律的精确模拟。球的角度旨在激发我们的想象力,而不是限制我们的想法。所以不用进入复杂的物理学细节,就让我们简单的问自己:如果我们被宣告做一天的上帝,并且可以编造我们自己的物理定律,命令球可以如何滚动,并切选择何种运动,那么球还总能滚到山谷底部吗?
为了让这个问题更准确,让我来想一下当我们在\(v_1\)方向上移动球一小段距离\(\Delta v_1\),在\(v_2\)方向上移动球一小段距离\(\Delta v_2\)的时候发生了什么,微积分告诉我们C的变动如下:
\[\Delta C \approx \frac{\partial C}{\partial v_1} \Delta + \frac{\partial C}{\partial v_2} \Delta v_2\]我们尝试去选择\(\Delta v_1\)和\(\Delta v_2\)来让\(\Delta C\)为负,
未完待续…
