ANN原来如此简单!——用Excel实现的MNIST手写数字识别(之三)

ANN原来如此简单

人工神经网络目前仍然是一个火热的话题,许多人都对它充满了兴趣。然而,对于想了解ANN具体是怎么回事的同学来说,往往缺乏一个足够简单可视化的方法去了解神经网络的内部构造。网络上的各种文章除了数学公式以外,剩下的多是利用Tensorflow等现成的python库实现的样例,虽然实现起来简单,但是底层函数全部封装起来,十分难以理解。因此,神经网络往往在大家的心目中呈现出高深莫测的样子。然而,这篇文章的写作目的就是为了把神经网络拉下神坛,让任何对Excel有最基本了解的人也能在自己的电脑上搭建出一个能准确识别手写数字(准确率达到97%以上)的神经网络出来,同时还能对神经网络的运行底层算法有一个非常直观的了解。对于仅仅是感兴趣的初学者来说,能够非常直观地看到神经元长什么样子,能够非常清晰地看到网络的前馈计算和反向传播的参数传递过程,对于希望了解神经忘咯具体算法的研究者或爱好者来说,能够直观地了解各种不同的算法参数的区别,从而对神经网络各个超参数(hyper parameter)的选择和调整产生更深刻的理解。

本文在写作时考虑到需要照顾各个不同层次的读者,因此会力求从浅到深,按照下面的结构来编排,同时每一个章节又相对独立,如果对某些部分已经熟悉的读者,大可跳过一部分,直接阅读感兴趣的章节即可

  • 第一节,神经网络的历史
    神经网络和人工智能
    人工神经网络是一个很老的概念
    神经元和神经系统
  • 第二节,开始了解人工神经网络
    感知器模型和多层感知器模型
    神经网络的前向传播
    神经网络的有监督学习和反向传播
  • 第三节,Excel中实现MNIST手写数字识别
    MNIST数据集
    神经元基本计算的实现
    损失函数及反向传播算法的实现
    用简单的宏实现随机梯度下降训练
    训练成果的比较和检验
  • 第四节,神经网络的优化:实现CNN卷积网络
    卷积网络的结构
    Excel中逐步实现卷积池化和全联接计算
    随机梯度下降算法及训练成果
  • 第五节,更多的内容
    使用VBA实现的ANN Excel工具箱
    使用tensorflow实现ANN

第三节,Excel中实现MNIST手写数字识别

Excel可以说是我们这个星球上使用最广泛的的办公软件之一了,界面直观、上手简单,同时超级强大的可扩展性又让它能够胜任几乎一切能想到的工作,所以在办公软件界称为一株常青藤,老而弥坚,经久不衰。

得益于Excel强大的表格计算功能以及强大的可视化功能,我们可以用它来展示大量复杂计算的过程,将难以理解的数学公式用可视化的形式直观地展现出来。在这一方面,可以说Excel具有得天独厚的优势。现在,我们就要好好地利用Excel的这项优势,用它创建一个人工神经网络,用来对MNIST手写数字进行识别,同时,利用Excel强大的可视化能力,把神经网络内部的每一个神经元用一种可视化的方式呈现在大家面前,记得我第一次把这样能被“看见”的神经元展现在朋友面前时,他们给我的反馈只有两个字:“震撼!”。

也许本文的读者已经具备了相当的相关知识,不过为了照顾对神经网络了解不多但是又想体会那种震撼的读者,我会从最基础的步骤开始介绍,直到读者可以按图索骥,一步步搭建起自己的第一个Excel中的人工大脑

MNIST数据集

学计算机的同学都知道,不管学习什么计算机语言,通常要学的第一课都是“Hello World!”。这两个简单的单词已经成为了编程学习的经典第一课。而在神经网络尤其是机器视觉领域,MNIST不仅仅是“Hello World”那样的经典例程,同时也是一个最常见的性能测试标准,很多机器学习程序都用MNIST的刷分成绩来证明自己。

MNIST是一个手写数字数据库。MNIST数据库是在NIST数据库的基础上经过重新编辑形成的。NIST数据库的全称是“国家标准技术委员会(National Institute of Standards and Technology, NIST)”数据库,这个数据库搜集了数万个来自美国人口统计局雇员书写的手写数字作为训练数据(用于训练一个机器学习程序),同时搜集了大量美国普通高中生的手写数字作为测试数据(用于为训练好的机器进行测试评分),并被广泛用在许多机器学习的项目中。MNIST的创建者认为训练数据和测试数据来自学历和年龄段完全不同的两个群体容易导致测试结果有偏差,因此在NIST数据库的基础上进行了修改,做成了今天我们使用的这个“Modified NIST Database, MNIST”。

在新的MNIST数据库中,作者从NIST数据库中提取了60000个不同的手写数字,其中三万个来自于政府雇员,三万个来自于高中生,调整所有数字使它们墨迹浓度相近,大小尺寸相仿,并且把每一个数字去除颜色信息后变成一幅256级,尺寸为28X28的灰度图像。每一幅图像上一个数字,而且数字基本上都在图像的正中间。上面的工作在机器学习的数据处理中非常常见,我们叫做“Normalization”。最后,把这60000个数字打乱顺序后,随机抽出50000个作为训练数据,剩下的10000个作为测试数据,这样,MNIST数据库便成型了。由于训练数据和测试数据中政府雇员和高中生的手写字体分别各占一半,因此用MNIST的测试数据来进行测试就显得很公平合理了。下面的图片显示了MNIST数据集中的部分数字:

这里写图片描述

MNIST数据现在可以很容易地从网络上下载到,整个数据集以一种很高效的方式存储在几个bin文件中,大家可以猛击这里下载整个数据集:

整个数据集由四个文件组成:

  • 训练数据图像集合:train-images.idx3-ubyte
  • 训练数据标签集合:train-labels.idx1-ubyte
  • 测试数据图像集合:t10k-images.idx3-ubyte
  • 测试数据标签集合:t10k-labels.idx1-ubyte

四个文件都是字节码格式存储的,我们可以方便地使用VBA从文件中的指定位置读取一个字节,从而在Excel中建立起一幅手写图像出来。MNIST图像数据文件包含两类,一类是图像文件(文件名中有Image字样),另一类是标签文件(文件名中有Lables字样)。图像文件的前16个字节是文件头信息,不包含任何图像信息,手写数字图像从文件的第17个字节开始存储。因为每幅图片都是28X28的256级灰度图像,每个像素正好用一个字节的空间存储(0~255),因此一幅图片占用768字节,50000幅图片按照顺序存储在文件中,共占用约38,000,000字节的空间。神经网络的训练是一种被称为“有监督训练”的方式,我们将一幅训练图像展示给神经网络后,还需要同时告诉神经网络这幅图片代表的数字具体是几,这个信息存储在标签文件中。与图像文件类似,它也是字节型文件,前8个字节是文件头,接下来是50000幅图像所代表的数字(0~9中的任意一个),每一幅图片的标签占用一个字节,共50000个字节。

使用下面的VBA代码可以将文件中的图像和标签读取出来:

Private Function getImageData(ByVal n As Long) As Variant
'从指定文件中读取一幅MNIST手写数字图像,n用于指定被读取的数字的位置
Dim image(1 To 28, 1 To 28) As Double  '定义一个28x28的数组用于存储图像

    fName = "D:\Trainingdata\train-images-idx3-ubyte"

    Open fName For Binary As #1
    loc = 16 + (n - 1) * 28 * 28  '根据指定值n确定第一个字节的起始位置
    Seek #1, loc 
    For i = 1 To 28
        For j = 1 To 28
            Get #1, , bData            '从loc位置开始读取后续28*28=768个字节
            image(i, j) = bData / 255  '读取的字节型整数转化为浮点数
        Next
    Next
    Close #1
    getImageData = image  '返回包含768个浮点数的数组
End Function

Private Function getImageLabel(ByVal n As Long) As Byte
'从指定文件中读取一幅MNIST手写数字图像的训练标签(正确答案),n用于指定被读取的数字的位置
    fName = "D:\Trainingdata\train-labels.idx1-ubyte"

    Open fName For Binary As #1
    loc = n + 8       '根据指定值n确定第一个字节的起始位置
    Seek #1, loc
    Get #1, , bData
    Close #1
    getImageLabel = bData     '返回从文件中读取的数字
End Function

上面代码包含两个函数,getImage(n)函数从文件中读取第n个手写数字的图像,并且返回一个尺寸为28x28的二维数组;getImageLabel(n)函数从文件中读取第n个手写数字的正确值,返回一个整数。新建一个Excel文件,把上面两个VBA函数添加到一个新模块中,就可以直接在工作表中使用这两个自定义函数了:如果要显示一幅手写数字图像,可以选中一个28x28的单元格区域,输入公式“{=getImageData(28)}“,由于公式返回一个数组,因此需要使用Alt+Ctrl+Enter输入一个数组公式,如果文件的路径正确,可以直接读出第28幅手写数字图片,用条件格式可以把这个数字显示出来(可以调整一下单元格使它们行高和列宽相等,使视觉效果更好):
这里写图片描述

那么,下面我们就开始一步一步地搭建我们的第一个Excel版的MNIST手写数字识别神经网络。首先我们新建一个Excel文件,将第一张工作表重命名为“Input”,这张工作表将作为整个神经网络的图像输入以及结果输出的主要工作界面。

这里写图片描述

我们需要首先在工作界面中创建一个手写数字显示区域,并且使用一个控制单元格,用于选择当前显示的数字。为此,我们必须先下载MNIST手写数字集合,将文件解压缩包存在本地后,在Excel中创建两个自定义函数“getimagedata”以及“getimagelabel”(这两个函数的源代码在前文中,如果对VBA不熟悉的朋友,可以参考相关教程)。完成这两个步骤后,我们可以用A2单元格作为控制单元格,同时用“B3:AC30”这个28X28的单元格区域作为手写数字显示区域:选中B3:AC30区域,在公式栏中键入“{=getimagedata(A2)}”并使用Alt+Ctrl+Enter输入公式。为了保证显示效果,选中“B3:AC30“单元格并如下图所示设置条件格式:
这里写图片描述

这样设置条件格式的目的是为了用不同深浅的颜色对每个单元格进行填充,这样单元格中就会显示出不同的墨迹深浅,并组成需要显示的手写数字了。同时,为了提高显示效果,可以把显示区域底色填充为白色,并且把行高和列宽全部修改为11,把单元格区域添加上边框后,显示效果如下:

这里写图片描述

如果看不到显示的数字,那么可以检查一下控制单元格A2的值是否正确,如果MNIST数据文件的路径和文件读取函数代码都正确的话,只要在A2单元格中输入任意一个1~50000之间的整数,那么数字显示区域就能自动显示出50000个训练数字中的一个。为了便于浏览数字,可以在工作表中添加一个“微调按钮”工作表控件这里写图片描述,并设置控件的参数如下:
这里写图片描述

这样就可以通过单击微调按钮来切换当前显示的数字了。切换数字显示的效果如下:

这里写图片描述

这里写图片描述

这里写图片描述

另外,我们可以在A3单元格中写入公式“=getImageLabel(A2)”来显示当前数字图像的“标签”,也就是正确答案。

这里写图片描述

现在可以单击微调按钮,来测试一下数字的显示效果,是不是很方便?到了这一步,如果手写数字能够正确显示的话,恭喜你,可以进入下一步,建立神经元了,OK,跟我来!

神经元基本计算的实现

为了演示方便,我们会搭建一个相对比较简单的神经网络。在这里,博主选择了一个结构为“784-100-10”的全联接神经网络作为我们的第一个Demo,关于神经网络的结构以及前向传播、反向传播的计算及推导,请参见本文的前一节。在这里我们不会进行过多的原理介绍和公式推导,会直接进入实际环节。

为了让神经网络的结构更清晰,我们会在不同的工作表上搭建神经网络的不同层,因此,我们可以将第二、第三张工作表分别重命名为“Hidden”和“Output”,分别用于放置隐藏层的100个神经元以及输出层的10个神经元,两张表都建好了?let‘s go!

既然我们开始搭建神经元,就先从Hidden layer开始,那么,我们的神经元应该是什么样子呢?根据神经元的知识我们知道,由于隐藏层的神经元是与输入层的28X28=784个输入值全联接的,因此在隐藏层中,每个神经元也有784个输入权值 w w <script type="math/tex" id="MathJax-Element-1">w</script>,外加一个偏置b<script type="math/tex" id="MathJax-Element-2">b</script>。那么我们如何摆放这785个数值最合适呢?经过几番不同的尝试,博主发现最简单的办法就是把784个单元权值排列成与输入完全一致的形式,也就是28X28单元格的区域,如下图所示。
这里写图片描述

由于我们需要100个神经元,因此我们可以把100个神经元按照10个一排的方式排列成10排,因此,100个单元格总共会占据280X280个单元格的区域,我们可以选择“C3:JV282”的区域用来存储这100个单元格的权值,同时,100个神经元有100个偏置b值,我们同样可以用一个单元格区域来存储这100个值,例如,存储在“C285:L294”这个10X10的单元格区域中
这里写图片描述
为了显示效果起见,我们仍然可以把所有单元格的行高和列宽都设置为10,同时对整个权值区域和偏置区域应用条件格式,这样使我们可以更加直观地在未来看到“神经元”长什么样子。
这里写图片描述

OK,到这里,隐藏层的神经元就已经搭建完成了!是的,就这么简单!
不过,在隐藏层的工作还没有结束,因为输出层的结果是取决于隐藏层的输出的,因此我们最好还要在隐藏层中计算出隐藏层的输出值,才能用于下一层的计算中。所以,我们可以在偏置区域的旁边开辟出一个10X10的区域“S285:AB294”,用于存储隐藏层的输出。

回忆一下神经元的输出计算公式,假设我们采用Logistic Sigmoid单元,那么单元格输出公式为:

Output=11+eyy=i=1Nxiwi+b O u t p u t = 1 1 + e − y 其 中 : y = ∑ i = 1 N x i w i + b
<script type="math/tex; mode=display" id="MathJax-Element-3">Output = \frac 1 {1+e^{-y}}\\其中:y = \sum_{i=1} ^N x_iw_i + b</script>

由于我们特意使神经元权值区域的形状与输入区域形状相同,因此公式中的 y=Ni=1xiwi+b y = ∑ i = 1 N x i w i + b <script type="math/tex" id="MathJax-Element-4">y = \sum_{i=1} ^N x_iw_i + b</script>可以非常方便地用sumproduct函数来实现。所以,S285单元格的公式可以写为:

=1/(1+exp(-1*sumproduct(C3:AD30, Input!$B$3:$AC$30)+C285))

其中Input是输入数据所在的工作表,而$B$3:$AC$30正是我们从MNIST文件中读取的一幅手写数字图像的所在区域,将这个区域内的所有784个实数与C3:AD3也就是第一个神经元的权值所在区域的所有实数相乘并加总,再加上偏置 b b <script type="math/tex" id="MathJax-Element-5">b</script>(单元格C285),得到y的值再套入公式Output=11+ey<script type="math/tex" id="MathJax-Element-6">Output = \frac 1 {1+e^{-y}}</script>中。

将上述公式复制100次并填充到整个输出区域,我们就完成了属于隐藏层的所有前向传播计算。在填充单元格的时候务必注意权值输入区域,比如第二个神经元的输出(T285)应该是

=1/(1+exp(-1*sumproduct(AE3:BF30, Input!$B$3:$AC$30)+D285))

注意神经元权值区域引用的是AE3:BF30单元格区域,偏置b引用的是D285单元格,其余单元格依此类推。这样我们就得到了一个10X10的输出区域“S285:AB294”

接下来我们需要建立最重要的输出层,跳转到“Output”工作表,按照类似的方法创建10个输出神经元:在C3:CX12单元格(10X10单元格为一个神经元,10个神经元排成一排,因此共有10行X100列共1000个单元格用于存储10个输出神经元的权值 w w <script type="math/tex" id="MathJax-Element-7">w</script>),另外用区域“C16:L16”来存储10个神经元的偏置b<script type="math/tex" id="MathJax-Element-8">b</script>。
这里写图片描述

那么这一层的输出如何计算呢?按照类似的方法,用隐藏层的输出作为本层的输入,用工作表区域“C20:L20“来存储输出层单元的输出(同时也是整个网络的输出)

=1/(1+exp(-1*sumproduct(C3:L12, Hidden!$S$285:$AB$294)+C16))

看出来了么?这次公式中的输入变成了Hidden!$S$285:$AB$294,也就是Hidden工作表(隐藏层)的输出所在的单元格区域

OK!到现在为止,我们已经完成了一次完整的前向传播计算了,为了让整个神经网络的输出和输入数据直观可见,我们可以在Input工作表中AF34:AO34单元格区域放置整个单元的输出(使用Alt+Ctrl+Enter输入公式):

={Output!C20:L20}

神经网络的输出是十个实数,那么它们的含义到底是什么呢?我们可以这样定义:十个实数分别对应着0~9这十个数字,同时这十个输出实数值分别代表置信度:如果某个输出值显著地大于其他值,就说明神经网络十分确信输入的图片代表某一个数字,但如果好几个输出值大小差不多,就说明神经网络十分犹豫,拿不定主意。不过我们总可以取十个数字中最大的一个作为神经网络的判断,因此,我们可以在AF33:AO33单元格中分别写上0~9的数字,并且在单元格AF35到AO35中输入并填充以下公式:

=if(AF34=max($AF$34:$AO$34),AF33,0)

上面的公式对输出进行判断,只有最大的输出值被挑选出来并填上对应的数字,接着在A13单元格中输入以下公式

=max(AF35:AO35)

使用上面的公式就可以把神经网络的向量输出转化为有具体含义的输出值——神经网络识别出来的数字了
这里写图片描述

到现在为止,如果一切顺利,你已经可以顺利地读取MNIST数据库中的数字并正确显示,可以任意切换想要的数字图片,同时还能让神经网络进行前向传播计算并得到第一步结果了——尽管输出的结果并不理想——神经网络的输出值全都是0.5。事实上,这是由于网络所有的权值w和偏置b都是0导致的,在网络层数超过一层的情况下,全零会导致反向传播过程中所有隐藏层神经元学到同样的信息,从而极大地削弱网络的学习能力。因此我们必须在开始训练前用一组随机数初始化部分权值,以是每个神经元获得不同的初始学习能力。在这里,我们仅仅简单地初始化隐藏层的100个偏置值b,使网络具备最基本的学习能力就好,在未来的章节中我们再讨论参数的优化及性能提升的问题。

我们可以在Hidden工作表中利用随机数生成器来产生100个随机数,初始值选择0~1之间的均匀分布即可,直接将随机数填充到“C285:L294”这个单元格区域中。这时如果再返回“Input”工作表,会发现网络输出仍然全部都是0.5,不过,这时网络已经有了学习能力。

不过,点击工作表上的微调按钮切换不同的输入数字,会发现网络的输出值与输入完全没有任何联系,这代表神经网络没有任何识别能力!不过别着急,下面我们就要开始进入反向传播的阶段,完成反向传播结构的搭建后,我们就可以训练神经网络了。

损失函数及反向传播算法的实现

从现在开始,我们将要开始进行反向传播算法的搭建。
我们知道,为了让神经网络具备模式识别的能力,必须对网络中神经元的连接权值进行修改,而通常我们会使用渐进式的方法调整权值:每次对神经元的权值 w w <script type="math/tex" id="MathJax-Element-76">w</script>和偏置b<script type="math/tex" id="MathJax-Element-77">b</script>进行一点点调整,经过若干次调整后就能让神经网络的实现所需要的性能——关于这一部分的理论介绍,可以参见本文的第二节

权值调整的关键是计算每一层神经元的权值对于误差函数的偏导数,在这里我们选用最简单的误差函数

E=12(yiti)2 E = 1 2 ∑ ( y i − t i ) 2
<script type="math/tex; mode=display" id="MathJax-Element-78">E=\frac1 2\sum(y_i - t_i)^2</script>,同时选择最简单的Logistics Sigmoid函数作为所有神经元的激活函数(如果想了解更加详细的内容,请移步 本文第二节,我们将在本文的最后再来探讨性能更好的SoftMax激活函数和Cross Entropy交叉熵误差函数。OK,让我们再次打开前一小节中完成的Excel 文件,继续神经网络的旅程吧!

从“反向传播”这个名字我们可以知道,神经网络的误差计算是“反向”的,也就是从最后一层(输出层)开始逐层计算误差函数,逐层计算权值 w w <script type="math/tex" id="MathJax-Element-79">w</script>的偏导数的,因此,我们首先应该从输出层开始计算每一层的误差和偏导,并逐层反推到第一层,因此我们的计算首先从输出层开始。为了让神经网络的输出误差直观可见,我们可以把输出层的误差显示在“Input”工作表中,与输出结果放在一起,如下图:
<此处插入图片>
可以看到,误差函数的计算非常简单,直白地说,它就是网络输出与“正确答案”之间的差距之平方和,之所以用平方和是为了消除正负号的影响。因此,我们也可以很直观地理解,只要误差函数达到最小值0,那么网络输出就与正确答案完全相同。

接下来我们需要计算输出层的权值关于误差函数的偏导数,偏导数实际上定义了当前点的一个“最陡峭”的下山方向,在本文的第二节中我们讨论过爬山法,如果我们一直沿着这个方向小步移动,最后一定能够到达一个(局部)最低点,同时,我们还讨论了这个偏导数的推导过程和计算方法,在这里我们直接给出最终的式子并在Excel中实现,需要了解推导过程的同好可以猛击这里阅读本文第二节。输出层的权值对误差函数的偏导数计算公式如下

δEδwij=(tiyi)yi(1yi)xijδEδbi=(tiyi)yi(1yi)
<script type="math/tex; mode=display" id="MathJax-Element-80">\frac{\delta E}{\delta w_{ij}}=-(t_i-y_i)y_i(1-y_i)x_{ij}\\\frac{\delta E}{\delta b_{i}}=-(t_i-y_i)y_i(1-y_i)</script>
从上面的式子中可以看出 w w <script type="math/tex" id="MathJax-Element-81">w</script>的偏导数其实就是b<script type="math/tex" id="MathJax-Element-82">b</script>的偏导数与输入值的乘积,因此关键是先计算出 δEδbi δ E δ b i <script type="math/tex" id="MathJax-Element-83">\frac{\delta E}{\delta b_{i}}</script>,为了简单起见,我们把它简称为 Δ Δ <script type="math/tex" id="MathJax-Element-84">\Delta</script>。 Δ Δ <script type="math/tex" id="MathJax-Element-85">\Delta</script>的计算非常方便,只需要在单元格“”中填充公式即可:

=(AF35-AF36)*AF36*(1-AF36)

就可以计算出输出层的 Δ Δ <script type="math/tex" id="MathJax-Element-113">\Delta</script>,由于输出层有10个神经元,因此填充公式计算十次,分别得到第一个到第十个神经元的 Δ Δ <script type="math/tex" id="MathJax-Element-114">\Delta</script>值,如下图:
<此处插入图片>

接下来可以计算 Δ Δ <script type="math/tex" id="MathJax-Element-115">\Delta</script>与输入 xij x i j <script type="math/tex" id="MathJax-Element-116">x_{ij}</script>的乘积,由于输出层的前一层有100个神经元,而且我们把这100个神经元排列成了10X10的矩阵形式,因此每个输出层神经元的 δEδwij δ E δ w i j <script type="math/tex" id="MathJax-Element-117">\frac{\delta E}{\delta w_{ij}}</script>也是一个包含100个实数值的10X10矩阵,十个神经元共有10个矩阵,排列出来的形状正好与 w w <script type="math/tex" id="MathJax-Element-118">w</script>权值矩阵一模一样,计算公式只要非常简单地使用一个矩阵公式即可(Alt+Ctrl+Enter输入公式):

={Input!AF35*Hidden!$S$285:$AB$294}

还记得么,单元格区域Hidden!$S$285:$AB$294就是隐藏层单元的输出值,在这里被用作输出层单元的输入值,以计算δEδwij<script type="math/tex" id="MathJax-Element-25">\frac{\delta E}{\delta w_{ij}}</script>,最后的计算结果我们也填充到工作表中,是一个100列10行的矩阵,与输出层的权值矩阵相同,如下图所示:
<此处插入图片>

完成了输出层的 Δ Δ <script type="math/tex" id="MathJax-Element-26">\Delta</script>和 δEδwij δ E δ w i j <script type="math/tex" id="MathJax-Element-27">\frac{\delta E}{\delta w_{ij}}</script>计算以后,接下来就要进行 Δ Δ <script type="math/tex" id="MathJax-Element-28">\Delta</script>的反向传播传播,逐层计算隐藏层的 Δ Δ <script type="math/tex" id="MathJax-Element-29">\Delta</script>和 δEδwij δ E δ w i j <script type="math/tex" id="MathJax-Element-30">\frac{\delta E}{\delta w_{ij}}</script>。我们在本文的第二节中讨论过,对于隐藏层,由于并没有定义误差函数,因此我们无法直接计算全值对误差函数的偏导数,而是通过链式法则间接计算 δEδwij δ E δ w i j <script type="math/tex" id="MathJax-Element-31">\frac{\delta E}{\delta w_{ij}}</script>的。<公式如下>

为此,我们需要首先在输出层计算所有权值 w w <script type="math/tex" id="MathJax-Element-32">w</script>和偏置b<script type="math/tex" id="MathJax-Element-33">b</script>与对应的 Δ Δ <script type="math/tex" id="MathJax-Element-34">\Delta</script>的乘积,再把它们加总,以便计算隐藏层单元对输出层单元的“贡献值”。因此,在“Output”工作表中,我们需要在单元格“C24:L33”中键入公式:

={Input!AF38*C3:L12}

其中Input!AF38存储了输出层第一个神经元的 Δ Δ <script type="math/tex" id="MathJax-Element-35">\Delta</script>值,用这个值乘以这个神经元的所有权值,填入第一个单元格区域中。对剩下的九个神经元重复同样的公式,依次填充到“M24:CX33”等单元格中,同时,再分别在“D23,N23,X23……”单元格中存放 Δ×b Δ × b <script type="math/tex" id="MathJax-Element-36">\Delta \times b</script>的值。将所有神经元的权值和偏置分别加总,存储在“C36:L46”单元格区域中:

={C23:L33+M23:V33+W23:AF33+AG23:AP33:AP33+AQ23:AZ33+BA23:BJ33+BK23:BT33+BU23:CD33+CE23:CN33+CO23:CX33}

用Alt+Ctrl+Enter进行填充后,我们就得到了一个 10×10 10 × 10 <script type="math/tex" id="MathJax-Element-98">10\times10</script>的矩阵(D36:L46),这个矩阵就是隐藏层所有100个神经元的 Δ Δ <script type="math/tex" id="MathJax-Element-99">\Delta</script>值!
这里写图片描述
既然已经算出了隐藏层100个神经元的 Δ Δ <script type="math/tex" id="MathJax-Element-100">\Delta</script>值,这100个神经元的偏导数 δEδwij δ E δ w i j <script type="math/tex" id="MathJax-Element-101">\frac{\delta E}{\delta w_{ij}}</script>也就可以顺理成章地计算出来了:

用简单的宏实现随机梯度下降训练

根据反向传播算法,上面所计算出的 δEδwij δ E δ w i j <script type="math/tex" id="MathJax-Element-102">\frac{\delta E}{\delta w_{ij}}</script>矩阵值,就确定了当前的状态下“最陡峭”的下山方向(参见本文第二节所介绍的“爬山法”及梯度下降算法)。我们只要沿着这个方向前进一小步,并且不断地重新计算下一个最陡峭的下山方向并再次走一小步,最终一定能够到达一个误差函数的局部最小值点。然而,我们怎么定义且实现这“一小步”呢?在梯度下降算法中,我们可以简单地选择一个较小的实数“学习速率“ η η <script type="math/tex" id="MathJax-Element-103">\eta</script>来定义这”一小步“。具体来说,只要把 δEδwij δ E δ w i j <script type="math/tex" id="MathJax-Element-104">\frac{\delta E}{\delta w_{ij}}</script>与 η η <script type="math/tex" id="MathJax-Element-105">\eta</script>相乘,然后与原来的神经元权值相加就可以了

wijΔwij=wij+Δw=η×δEδwij w i j ′ = w i j + Δ w 其 中 : Δ w i j = η × δ E δ w i j
<script type="math/tex; mode=display" id="MathJax-Element-106">\begin{split} w'_{ij}&=w_{ij}+\Delta w\\其中:\\\Delta w_{ij} &= \eta \times \frac{\delta E}{\delta w_{ij}}\end{split}</script>

为了
训练成果的比较和检验

<未完待续>

posted @ 2018-08-28 23:45  JackiePENG  阅读(4)  评论(0编辑  收藏  举报  来源