神经网络识别手写数字

你可以在这里阅读 上一篇

neural nets

感谢英文原版在线书籍,这是我学习机器学习过程中感觉非常适合新手入门的一本书。鉴于知识分享的精神,我希望能将其翻译过来,并分享给所有想了解机器学习的人。

人类的视觉系统是世界上最美妙的系统之一。来看一下下面的手写字:

大多数人都能很容易识别出来是这些数字是:504192。在我们的每一个大脑半球,都有一个视觉皮层,也被称作V1,它包含1.4亿个神经元,在他们之间有数以百亿级的联系。而且不光有V1,一个完整的视觉皮层还有V2、V3、V4和V5,用来处理更加复杂的图像。这就好比我们的大脑中有一台经过几百万年进化的超级计算机,用它非常适合理解世界的景象。识别手写字是非常困难的,但是我们人类却能很轻松的做到,而且这些所有的工作似乎也是在无意识的情况下被执行完成的,所以我们通常都不会意识到我们的视觉系统是在处理多么复杂的问题。

如果你尝试写一个计算机程序来识别像上面那种数字的话,看起来很简单但是在我们动手做的时候去发现突然变得异常的困难。就像上面的数字9,有一个圈在顶部,和右下部的一竖。当你尝试定制这样的程序规则来识别这个9的时候,你可能会很快迷失在各种特殊情况和意外处理等的沼泽中(原因是基本每个人写的9都是不一样的),这看起来是没有希望的。

然而,神经网络处理这个问题是使用不同的方法: 使用大量的手写数字作为训练集合,就像下面这样的:

mnist_100_digits.png

然后开发一个系统,让它从这个训练集中学习。换句话说,就是神经网络使用这些例子来自动推断手写数字的规则(就像上面的9那样的规则)。此外,通过增加训练集的数量,让网络学习更多的手写字,来提高它的准确性。上面的图中我们仅仅展示了100个训练数字,但是或许我们需要成千上万或者上百万个数字来训练我们的网络才能建立更出色的手写字识别系统。

在这个章节我们会写一个计算机程序来实现一个神经网络,并用它来学习手写数字。这个程序仅仅74行代码,并且不使用特殊的神经网络库。这个程序能在没有人为干预的情况下识别手写数字,其准确率超过96%。另外,在后面的章节我们将会开发出将精度提高到99%的程序。实际上,现在有一些很好的商业用途的神经网络例子,如银行识别支票、邮局识别地址等。

我们选择识别手写字的例子是因为它在学习机器学习的时候是一个杰出的例子原型,这个例子有一个优点:识别手写字是一个挑战,但是它却没有使用极其复杂的解决方案和巨大的计算能力。另外,深度学习是一个更佳高级的开发技术。所以在这个系列的文章中我们会经常回顾识别手写字的问题。在后面,我们会讨论如何将这些知识应用在其他的领域,如计算机视觉、语音识别等。

当然,如果这里仅仅写计算机识别手写字的话,那么内容或许会显得有点少!所以我们将讨论很多关于神经网络的知识,包括两种重要的人工神经元(感知器和sigmoid 神经元),标准的神经网络学习算法是随机梯度下降算法。在这整个过程中,我将注重解释下神经网络的原理并给你建立神经网络的概念。这就需要花费很长的篇幅来讨论,但是为了更深层次的理解这是值得的。在本章结束的时候,你能学习到什么是深度学习已经它的重要性。

感知器(Perceptrons)


什么是神经网络?首先,我将介绍一种人工神经元叫感知器(perceptron)。它是被科学家 Frank Rosenblatt 在1950年和1960年发明的。但是在工作中更常用的是另外一种人工神经元,叫 sigmoid neuron。我们会很快的了解到sigmoid神经元,但在这之前,为了了解为什么sigmoid神经元被设计成它的样式,先花点时间学习perceptrons是非常有必要的。

Perceptrons是如何工作的呢?一个perceptron需要几个二进制输入,x1,x2,…,并且产生一个二进制的输出:

这个例子展示的perceptron有三个输入,x1,x2,x3。但一般它会有更多或者更少的输入。Rosenblatt(那个科学家的名字)设计了简单的规则来计算输出。他 引入了权重 w1,w2,…,这些值代表了每个输入对输出产生影响的程度大小,也就是代表了每个输入的重要性。这个神经元的输出结果是1或者0,其结果取决于输入的所有和

大于或者小于一个(threshold)阀值(规定的一个门槛值)。就像权重一样,阀值也是以一个真实的数字作为神经元的参数。给出更清晰的表达式:

这就是一个perceptron工作的方式了。

这是一个基本的数学模型。你可以认为perceptron是一种通过权衡输入数据来做决定的装置。让我举个不是特别恰当的例子,但是非常容易理解,并且后面我会举出更加恰当的例子。假设周末的时候,你的城市将会有一个奶酪节日。你喜欢奶酪,而且正在考虑要不要去参加这个节日,你可能通过三个因素来做出这个决定:
– 天气情况
– 你男朋友或女朋友是否陪你去
– 是否有便利的交通工具

我们可以用三个二进制变量 x1、x2 和x3来表示这三个影响因素。例如,如果天气好,我们有x1 = 1,如果天气不好,x1 = 0。 同样,如果你的男朋友或女朋友想去,x2 = 1,如果不是x2 = 0。 对于x3和公共交通也同样如此。

现在,假设你超级喜欢奶酪,那么即使你的男朋友或女朋友不陪你去,你也很乐意去参加这个节日。但也许你真的厌恶恶劣的天气,如果天气不好,你不可能去参加这个节日。这时候可以使用perceptron来对这种决策进行建模。一种方法是为天气选择一个权重w1 = 6,其他条件选择w2 = 2和w3 = 2。 w1的值较大表示天气对你来说很重要,重要性远远大于你的男朋友还是女朋友是否陪你去,也大于是否有便利的交通工具。最后,假设你为perceptron选择了一个5的阈值。通过这些选择,perceptron实现所需的决策模型,每当天气好的时候输出1,天气不好的时候输出0。无论你的男朋友还是女朋友想去,或者公交是否在附近,输出都没有什么不同。

通过改变权重和阈值,我们可以得到不同的决策模型。例如,假设我们选择了一个3的门槛,那么perceptron会决定在天气好的时候或交通便利的时候,你的男朋友或女朋友愿意陪你的时候去参加这个节日。换句话说,这将是一个不同的决策模式。降低门槛意味着你更愿意去参加这个节日。

显然,感知器不是一个完整的人类决策模型! 但是,这个例子说明了感知器如何权衡不同因素以作出决定。 而似乎合理的是复杂的感知器网络可能会做出相当微妙的决定:

在这个网络中,第一列感知器 :称之为第一层感知器 ,它通过权衡输入的数据,做出三个非常简单的决定。那么第二层感知器呢?这些感知器中的每一个都通过权衡第一层决策的结果来做出决定。通过这种方式,第二层中的感知器可以在比第一层中的感知器更复杂和更抽象的层次上做出决定。第三层感知器可以做出更复杂的决定。通过这种方式,感知器的多层网络可以进行复杂的决策。

让我们简化描述感知器的方式。我们用w⋅x代替所有w和x的乘积之和,这里的w和x是所有权重和输入的向量。用b代替阀值threshold,使得b=-threshold。所以perceptron公式可以写成:

我们已经知道,感知器是一种可以衡量输入因素权重并作出决定的方法。现在我们可以用感知器来表示计算机的基本逻辑函数,例如与非、或、与等函数。下面我们假设有两个输入,每个输入的权重都是-2,阀值为3的感知器:

然后我们看到,输入00时,输出为1。因为-2)* 0 +( – 2)* 0 + 3 = 3是正数。类似的,输入01或10时,输出1。但是输入11时,输出为0。所以这就实现了与非门NAND的逻辑。

而我们可以使用与非门建立任何的逻辑门阵列。例如,我们可以使用与非门来建立一个电路,它增加了两个比特x1和x2。 这需要计算按位和,x1⊕x2,当x1和x2均为1时进位位被设置为1,即进位位只是乘积x1x2:

为了得到和感知器perceptrons等效的网络图,我们用感知器替换上图中的与非门NAND,感知器包括两个输入,每一个的权重都是-2,b=3,如下图(注意下图省略了一点右下方的部分NAND图,以便更加容易画出来):

我们将图中carry部分的两个输入变成一个,只需要将权重变为原来的2倍即可:

我们再将变量也变成一个输入层:

感知器的计算普遍性(就像上面的加法器)同时让人放心有失望。这令人放心的是,感知器网络可以像其他任何计算设备一样强大。但也令人失望,因为它让人觉得感知器只是一种新型的NAND门,而不是什么大新闻!

但是,事实证明,我们可以设计出自动调整网络的权重和偏差的学习算法。这种调整是响应外部刺激而发生的,没有程序员的直接干预。这些学习算法使我们能够以与传统逻辑门完全不同的方式使用人造神经元。我们的神经网络可以简单地学会解决问题,而不是直接设计一个NAND或其他门电路。

Sigmoid 神经元


学习算法听起来是非常糟糕的。但是我们要如何为神经网络设计算法呢?假设我们有一个感知器网络,并想用它来解决一些问题。例如,输入手写数字图片的像素数据,我们希望通过神经网络学习调整权重和b的值来正确的识别这些数字。我们来看一下神经网络是怎么学习的,当我们调整权重的值时,相应的会改变一些输出的值。所以,目标就变得可以学习了,学习的过程就是不断调整权重和b的值,来改变输出的值,使其输出更加接近真实的值:

例如:当神经网络错误的把9识别成8时,我们可以通过稍微调整权重和b的值,是其输出结果为9.我们不断的这么做,直到结果更加好,这就是网络的学习。

但是当权重和b的值有一个小的调整的时候可能会造成感知器的输出完全的反转,也就是说从0到1.这可能会造成网络的工作变的失控,例如当识别9正确了,但是却造成了别的值识别发生很大的错误。或许我们会有一些更加聪明的做法来规避这个问题。

我们能够解决这个问题通过介绍一种新型的人工神经元叫做:sigmoid神经元。它和perceptron 是相似的,但是不同之处是当权重和b的值做了小的改动时对于输出的结果也只是小小的改变。正是因为这个特性,我们才选择了它。

OK,让我们来看一下sigmoid神经元吧。让我们就像画perceptions时一样来画一下sigmoid神经元:

同perceptron一样,sigmoid神经元有一些输入:x1、x2…但是不同的是这些输入的值由原来的0或者1替换为0到1之间的值。例如:0.638…等等都是有效的输入值,sigmoid神经元对每一个输入值也有权重,w1、w2…,并且也有阀值b。但是输出不是0或1了,而是σ(w⋅x+b),这个σ被叫做sigmoid函数的统称,并被定义为:

把上面的z值用我们定义的输入、权重和b值替换就得到如下公式:

如果你是第一次看到类似的数学公式,可能会觉得这个超难懂,但是实际上,sigmoid神经元和perceptrons有很多的相似处,只是sigmoid神经元可以看到更多的细节。

为了理解sigmoid神经元和perceptrons的相似之处,假设z≡w⋅x+b是一个超大的数,那么e^−z≈0(注意这里的代表幂运算),也就是说σ(z)≈1。换句话说,当z=w⋅x+b这个值很大的时候,输出就是1,相反的当z=w⋅x+b很小的时候,输出就是0,这样的行为是不是和perceptrons的输出很相似呢。那么我们如何理解σ函数呢?下面是它的形状:

这个形状是阶梯函数的平滑输出版:

如果σ是一个阶梯函数,那么sigmoid神经元就是perceptron,因为其输出会依赖于w⋅x+b是大还是小而变成1或者0。但是真实的sigmoid神经元可以看作是一个平滑版的perceptron。这就是说,当试图改变权重Δwj和b的值Δb时,输出值也会有一些小的改变Δoutput。而不会像perceptrons那样发生反转突变。我们可以将Δoutput的变化理解成下面的公式:

上面的公式代表的是:所有的权重改变量和输出相对于权重偏导数的乘积之和加上输出相对于b的偏导数与 Δb的乘积。如果你对偏导数感觉到难以理解也不要慌张,你可以理解成Δoutput相对与Δwj 和 Δb的改变时线性的函数,也就是当稍微改变权重和b的时候,其输出也会跟着有一点小的改变。

我们应该如何理解sigmoid的输出呢?显然,和感知器不同的是,sigmoid神经元不仅仅输出0和1,它能输出0到1之间的任何实数。这个特性非常有用,例如,我们想使用输出代表神经网络中像素的强度的时候就能派上用场。但有时候也可能会带来干扰,例如我们想知道输出是不是9的时候,这时候输出是小数倒不如直接输出0或者1来的直接。但在实践中,我们可以约定一个值来处理这个问题,例如,决定把输出大于0.5的值代表成9,而小于0.5代表不是9(这里的9只是判断的一个例子而已)。

搭建神经网络


接下里的一小节我会介绍一个能很好处理识别手写字的神经网络。前期准备,我们需要理解一下网络各个部分的专业术语。例如我们有这样一个网络:

最左边一列叫做输入层(input layer),该层的神经元称为输入神经元层。最右边或输出层称为输出神经元(output neurons),例子中只有一个输出神经元。中间这一层叫做隐藏层(hidden layer),例子中只有一个隐藏层,但是很多网络都有多个隐藏层。例如下面图中的4层网络中有2个隐藏层:

由于历史原因,多层网络也经常被称为多层perceptrons或者MLPs,但是这里我们只使用单层网络,只是想提醒你一下多层结构的存在。

通常设计网络中的输入和输出层是容易的。例如,判断一个数字是不是9的时候。如果一个image是64*64像素的灰度图,我们就可以有4,096=64×64个输入神经元,其灰度可以在0和1之间适当调整。输出层会只有一个神经元。其值小于0.5代表图片不是9,大于0.5代表图片是9.

隐藏层的设计就不那么容易了,不可能通过几个简单的经验法则就总结出来隐藏层的设计过程。神经网络的研究人员已经为隐藏层提供了许多设计方案,来帮助我们从网络中获取我们想要的行为。例如,可以根据网络学习的时间长短来衡量隐藏层的数量等等。

在神经网络中,如果一层的输出被用作下一层的输入,这种网络叫做前馈神经网络(feedforward neural networks)。意思就是信息总是前馈,没有反馈。但是还是存在有反馈的人工神经网络模型。这些模型被称为递归神经网络(recurrent neural networks)。在这种模型中,神经元在一定时间内被激活。但是这里我们将集中精力在前馈神经网络上。

识别手写数字的一个简单网络


定义了神经网络了,让我们回到手写字的识别上。我们把识别手写字分成两个子问题。第一,我们需要将一系列的数字图片分成一个个的,例如我们想要将下面的图片:

区分成6部分图片:

第二,当图片被分开以后,程序就需要开始识别每一个数字了,例如第一个数字:

我们会将重点放在解决更难的问题上,就是识别手写数字。我们将会使用一个3层的神经网络:

上图中输入是784=28×28的图片的像素灰度值,但是图中为了表示方便省略了很多输入神经元。输入中的像素灰度值0代表白色,1.0代表黑色,0和1之间的值代表逐渐变黑的灰色。

第二层是隐藏层。我们用n代表隐藏层的神经元数量,上图中n=15.

输出层有10个神经元。如果output=1,代表数字是0.

你可能会说代表10个数字为什么要用10个输出神经元呢?用4个不就可以表示了么,因为2的4次方是16大于10,足以表示10个数字了。但是实验表明,用10个确实比用4个的学习效果好的多。

让我们来看一下第一个输出的神经元,这个神经元决定数字是不是0.它通过权衡来自隐藏层的证据来判断这一点,那么隐藏层是怎么做的呢?隐藏层中的第一个神经元检测是否存在如下的图片:

隐藏层通过对目标区域(重叠的区域)进行加权,对其他区域进行轻微加权来实现识别这个图。以这种方式,隐藏层中的第二、第三和第四层来检测下面的图片:

这四部分就是把0分割成的四部分:

如果这个神经网络使用这样的方式来识别数字的,那么我们来解释一下上面的问题,为什么10个输出比4个好。如果有4个输出,那第一次输出的神经元将需要决定图像和哪个数字的某一个部位更相似,这将会很难处理,因为数字的每一部分组件的形状很难和某个数字密切相关。

学习梯度下降


现在我们来设计我们自己的神经网络,第一件事就是我们需要收集数据。我们会使用MNIST data set.关于这个数据集的详解可以去到官网了解。

我们会使用MINIST中的60000个数据进行训练,称之为训练集。然后用10000个数据进行测试,称之为测试集。

我们用x代表训练输入,它是28×28=784的多维向量。每一个向量代表一张图片的像素灰度值。用 y=y(x)代表输出,是一个10维的向量,例如当输入x=6时,输出y(x)=(0,0,0,0,0,0,1,0,0,0)^T,注意T代表向量的反转(不懂向量类似操作的读者可以先去学习线性代数的基础知识),将横向量转换成列向量。

我们需要怎么样调整权重和b的值才能使所有的输入x对应的输出都最接近真实的值呢?这里我们定义损失函数:

式子中w表示权重,b表示阀值,n表示输入个数,a表示x的输入时的输出向量。输出a依赖于w、b和x的值。为了表示简单, ‖v‖代表v向量的长度。我们把C叫做二次方损失函数;它也叫做均方误差MSE。C是非负的,如果输出的值a越接近真实值,那么C的值就会越小C(w,b)≈0。所以我们的目的就是找到一种最好的权重和b值,使得C的值接近0.我们将使用梯度下降算法来实现这一点。

Okay,我们用C(v)代替C(w,b),目的是为了表示这可能是任何函数。为了最小化C(v),我们想象一下一个方程只有两个变量,我们称之为V1和V2:

我们想要找到C的最小点,这时候我们可以使用微积分来试图寻找这个最小点。我们可以使用导数来寻找C的极值点,对于只有几个变量的函数这样似乎是行得通的,但是对于大型神经网络的成本函数依赖于数十亿的权重和极其复杂的b值,使用微积分来最小化这个是行不通的,会导致运算量极其庞大。

幸运的是,有一种算法或许可行。假设你在山谷里,想要寻找一条下山最快的路,我们可以通过计算C的导数来模拟这种清况。为了使问题简单化,我们来思考一下V1和V2发生一点小变化的时候我们会在山谷里如何移动?微积分告诉我们C的变化如下:

我们的目的就是选择Δv1和Δv2让ΔC最小。我们把v 的变化定义为向量, Δv≡(Δv1,Δv2)^T,那么C的梯度的偏导数是∇C:

有了这些定义,表达式(7)就可以写成:

这个式子更能表示为什么∇C被称为梯度向量:∇C影响着v的改变对C改变的程度。但是不要忘记我们的目的,是选择Δv使ΔC最小,特别的,假设我们选择:

其中η是一个小的正参数(称为学习率)。 那么等式(9)ΔC≈-η∇C⋅∇C=-η‖∇C‖2。 因为||C||2≥0,则ΔC≤0,即C总是减小,从不增加。 这正是我们想要的目的!因此,我们将采用方程(10)来定义我们的梯度下降算法中球的“运动定律”。 也就是说,我们将使用等式(10)来计算Δv的值,然后将球的位置v移动:

只要我们不断的迭代,就能使C一直减小,直到我们达到最小点。

为了使梯度下降正确的工作,我们需要选择学习速率η使得公式(9)足够的小,如果我们不这么做,可能会造成ΔC>0,这明显是不好的。同时我们也不能让η太小,那会造成算法执行太慢。

以上解释了当C只有两个变量时的梯度下降,但是实际中C会有很多的变量,假设有m个,v1,…,vm。这时C的变化量ΔC由 Δv=(Δv1,…,Δvm)^T产生:

梯度向量∇C是:

仅仅有两个变量时,我们能选择:

为了保证公式(12)最小,我们遵循梯度下降的方法,发现即使是在C有很多变量的时候,也可以通过迭代更新规则达到目的:

你可以将此方法称为梯度下降算法。这种方法给我们提供了一种通过不断改变v寻找最小C的方法。但是这个方法不是在所有情况都有效的,在后面我们会提到一些情况。但是在实践中,我们会发现这是一种使成本函数最小化的有效方式。

那么我们怎么在神经网络中使用梯度下降呢?在公式(6)中我们用权重和b来替换v,梯度向量 ∇C有两个组件 ∂C/∂wk 和 ∂C/∂bl,分开写就是:

我们看一下公式(6),注意一下损失函数的格式:

这是对每一个训练例子的平均消耗:

但是当训练数据集过大的时候,计算量也会太大,消耗时间太长。

这时,我们可以通过随机梯度下降来加速学习,方法是通过随机的从训练数据集中选择小样本计算梯度∇C,通过对这个小样本平均我们就可以快速得到真实的梯度∇C估计,这有助于加速梯度下降从而学习。

来让随机梯度下降更加的清晰点,随机的从训练集中挑选m个输入X1,X2,…,Xm,我们希望选择的m的大小足够可以使这些数据的梯度平均值等于所有数据的梯度:

所以:


证实我们可以通过为随机选择的小批量计算梯度来估计整体梯度。

随机梯度下降中的权重和b的变化:

如果你认真阅读完这篇文章,应该可以从中学习到一些有关机器学习的基础入门概念,那么下一篇文章就让我们根据这里学到的知识来实现识别手写数字的神经网络吧。

如果有问题,欢迎跟我联系讨论space-x@qq.com.我会尽量及时回复。

发表评论

关闭菜单