神经网络编程(python实现)

1.神经网络如何工作

1.1 生物与计算机的优劣

在这里插入图片描述

1.2 简单的预测机

一台基本的机器,接受了一个问题,做了一些“思考”,并输出了一个答案。
这台机器看起来的样子:
在这里插入图片描述

实际:
在这里插入图片描述
例如计算3*4:
在这里插入图片描述
试想一下将千米转化为英里的一台机器:
在这里插入图片描述
假设我们不知道千米和英里之间的转换公式。我们所知道的就是,两者之间的关系是线性的。这意味着,如果英里数加倍,那么表示相同距离的千米数也是加倍的。

千米和英里之间的这种线性关系,即它的形式应该是“英里=千米×C”
获得数据:
在这里插入图片描述
试着让c=0.5:
在这里插入图片描述
们将C从0.5稍微增加到0.6:
在这里插入图片描述
再次重复这个过程。输出值60还是太小了。我们再次微调C,将其从0.6调到0.7:
在这里插入图片描述

结果超过了已知的正确答案。先前的误差值为2.137,现在的误差值为-7.863。

如此说来,C = 0.6比C = 0.7好得多,微调C,将C从0.6调到0.61:
在这里插入图片描述

因此,最后的这次尝试告诉我们,应该适度调整C值。如果输出值越来越接近正确答案,即误差值越来越小,那么我们就不要做那么大的调整。使用这种方式,我们就可以避免像先前那样得到超调的结果。

关键点:

  1. 所有有用的计算机系统都有一个输入和一个输出,并在输入和输出之间进行某种类型的计算。神经网络也是如此。
  2. 当我们不能精确知道一些事情如何运作时,我们可以尝试使用模型来估计其运作方式,在模型中,包括了我们可以调整的参数。如果我们不知道如何将千米转换为英里,那么我们可以使用线性函数作为模型,并使用可调节的梯度值作为参数。
  3. 改进这些模型的一种好方法是,基于模型和已知真实示例之间的比较,得到模型偏移的误差值,调整参数。

1.3 分类器与预测器并无太大差别

上述的简单机器接受了一个输入,并做出应有的预测,输出结果,所以我们将其称为预测器。我们根据结果与已知真实示例进行比较所得到的误差,调整内部参数,使预测更加精确。

测量得到的花园中小虫子的宽度和长度:
在这里插入图片描述

可以使用直线将不同性质的事物分开:
在这里插入图片描述

如果直线可以将毛虫与瓢虫划分开来,那么这条直线就可以根据测量值对未知小虫进行分类。由于有一半的毛虫与瓢虫在分界线的同一侧,因此上述的直线并没有做到这一点。

再次调整斜率,尝试不同的直线:
在这里插入图片描述
这条直线整齐地将瓢虫与毛虫区分开来了。现在可以用这条直线作为小虫的分类器。

假设没有未经发现的其他类型的小虫,测量其宽度和长度,然后它可以使用上面的分界线,将小虫正确归类为毛虫或瓢虫:
在这里插入图片描述
在简单的预测器中,使用线性函数对先前未知的数据进行分类。但是,忽略了一个至关重要的因素。如何得到正确的斜率?如何改进不能很好划分这两种小虫的分界线呢?

1.4 训练简单的分类器

我们希望训练线性分类器,使其能够正确分类瓢虫或毛虫。根据观察,我们知道要做到这一点,简单说来,就是要调整分界线的斜率,使其能够基于小虫的宽度和长度将两组点划分开来。

为了简单化这项工作,下表显示了两个实例。
在这里插入图片描述
我们知道这组实例是正确的。这些实例帮助我们调整分类函数的斜率。用来训练预测器或分类器的真实实例,我们称为训练数据。

让我们绘制出这两个训练数据实例。通过观察数字列表或数字表格是不容易理解和感知数据的,而可视化数据有助于我们做到这一点:
在这里插入图片描述
让我们使用一条随机的分界线开始我们的讨论。此处,由于分界线是一条直线,因此我们也可以进行相同的处理: y =Ax
由于严格来说,此处的直线不是一台预测器,因此我们有意使用名称y和x ,而不使用名称长度和宽度。与先前我们将千米转换为英里不一样,这条直线不将宽度转换为长度。相反,它是一条分界线,是一台分类器。

尝试从A = 0.25开始,分界线为y = 0.25x 。在与训练数据的同一张图中,绘制这条直线,观察情况:
在这里插入图片描述
无需任何计算,我们可以观察到直线y = 0.25x 不是一台很好的分类器。要抵制诱惑,不能通过观察图就画出一条合适的直线。我们希望能够找到一种可重复的方法,也就是用一系列的计算机指令来达到这个目标。计算机科学家称这一系列指令为算法(algorithm)。

观察第一个训练样本数据:宽度为3.0和长度为1.0瓢虫。如果使用这个实例测试函数y =Ax ,其中x 为3.0,我们得到:y =0.25*3.0= 0.75 。在调整参数A之前,考虑y 应该是什么值。如果y 为1.0,那么直线就会恰好经过瓢虫所在的坐标点(x ,y )=(3.0,1.0)。但是实际上,并不希望出现这种情况。我们希望直线处于这个点上方。因为我们希望所有瓢虫的点处于直线下方,而不是在直线上。这条直线需要成为瓢虫和毛虫之间的一条分界线,而不是给定小虫宽度、预测小虫长度的一个预测器。

当x = 3.0时,我们尝试使用y =1.1的目标值。这只是比1.0大一点的数。我们也可以选择1.2甚至1.3。因此,期望的目标值是1.1,误差值E为误差值=(期望目标值-实际输出值)这样
E = 1.1-0.75 = 0.35:
在这里插入图片描述
由于A的初始猜测值给出了错误的y值,y 值应该等于训练数据给定的值。我们将正确的期望值t 称为目标值。为了得到t 值,需要稍微调整A的值。t = (A + ΔA)x :
在这里插入图片描述
误差值E是期望的正确值与基于A的猜测值计算出来的值之间的差值。E等于t -y 。
t -y = (A + ΔA)x - Ax。 E = (ΔA)x。 ΔA= E /x

修正后的A值为(A+ΔA),训练两个数据:
在这里插入图片描述
如果继续这样操作,使用各个训练数据样本进行改进,那么所得到的是,最终改进的直线与最后一次训练样本非常匹配。实际上,最终改进的直线不会顾及所有先前的训练样本,而是抛弃了所有先前训练样本的学习结果,只是对最近的一个实例进行了学习。

在机器学习中,这是一个重要的思路。我们应该进行适度改进(moderate)。采用ΔA几分之一的一个变化值,而不是采用整个ΔA,跳跃到每一个新的A值。
改进公式中,将添加一个调节系数:ΔA= L(E / x ),调节系数通常被称为学习率(learning rate)。

将L设为0.5:
在这里插入图片描述

关键点:

  1. 我们使用简单的数学,理解了线性分类器输出误差值和可调节斜率参数之间的关系。也就是说,我们知道了在何种程度上调整斜率,可以消除输出误差值。
  2. 使用朴素的调整方法会出现一个问题,即改进后的模型只与最后一次训练样本最匹配,忽略了所有以前的训练样本。解决这个问题的一种好方法是使用学习率,调节改进速率,这样单一的训练样本就不能主导整个学习过程。
  3. 来自真实世界的训练样本可能充满噪声或包含错误。适度更新有助于限制这些错误样本的影响。

1.5 有时候一个分类器不足以求解问题

在这里插入图片描述

下图,这幅图在坐标系中显示了两个输入值A和B与逻辑函数的关系。这幅图显示,只有当两个输入均为真时,即具有值 1时,输出才为真,使用绿色表示。否则输出为假,显示为红色。
在这里插入图片描述
一条直线将绿色区域和红色区域划分开来。这条直线是线性分类器可以学习到的一个线性函数。对于形如y = ax + b的简单的线性分类器,确实可以学习到布尔AND函数。

观察使用类似的方式绘制出的布尔OR函数:
在这里插入图片描述
由于仅有点(0,0)对应于输入A和B同时为假的情况,因此只有这个点是红色的。

另一种布尔函数称为XOR:
在这里插入图片描述
事实上,对于布尔XOR函数而言,不可能使用一条单一的直线成功地将红色区域和蓝色区域划分开来。也就是说,如果出现的是由XOR函数支配的训练数据,那么一个简单的线性分类器无法学习到布尔XOR函数。

好在解决的办法很容易,下图使用两条直线对不同的区域进行划分。这暗示了一种解决的办法,也就是说,可以使用多个分类器一起工作。这是神经网络的核心思想。多条直线可以分离出异常形状的区域,对各个区域进行分类。
在这里插入图片描述

关键点:

  1. 如果数据本身不是由单一线性过程支配,那么一个简单的线性分类器不能对数据进行划分。例如,由逻辑XOR运算符支配的数据说明了这一点。
  2. 但是解决方案很容易,你只需要使用多个线性分类器来划分由单一直线无法分离的数据。

1.6 神经元——大自然的计算机器

传统的计算机按照严格的串行顺序,相当准确具体地处理数据。对于这些冰冷坚硬的计算机而言,不存在模糊性或不确定性。而另一方面,动物的大脑表面上看起来以慢得多的节奏运行,却似乎以并行方式处理信号,模糊性是其计算的一种特征。

生物大脑中的基本单元——神经元:
在这里插入图片描述

虽然神经元有各种形式,但是所有的神经元都是将电信号从一端传输到另一端,沿着轴突,将电信号从树突传到树突。然后,这些信号从一个神经元传递到另一个神经元。

来看看一个神经元是如何工作的。它接受了一个电输入,输出另一个电信号。这看起来,与我们先前所观察的分类或预测的机器一模一样,这些机器也是接受了一个输入,进行一些处理,然后弹出一个输出。

生物神经元与简单的线性函数不一样,不能简单地对输入做出的响应,生成输出。也就是说,它的输出不能采用这种形式:输出=(常数*输入)+(另一常数)。

神经元不会立即反应,而是会抑制输入,直到输入增强,强大到可以触发输出。

在这里插入图片描述
只有输入超过了阈值(threshold),足够接通电路,才会产生输出信号。

在数学上,有许多激活函数可以达到这样的效果。一个简单的阶跃函数可以实现这种效果。
在这里插入图片描述
在输入值较小的情况下,输出为零。然而,一旦输入达到阈值,输出就一跃而起。

S函数(sigmoidfunction)更自然、更接近现实。有时也称为逻辑函数:在这里插入图片描述
在这里插入图片描述
如何建模人工神经
生物神经元可以接受许多输入,而不仅仅是一个输入。对于所有这些输入,我们只需对它们进行相加,
得到最终总和,作为S函数的输入,然后输出结果。这实际上反映了神经元的工作机制。
在这里插入图片描述
如果组合信号不够强大,那么S阈值函数的效果是抑制输出信号。如果总和x 足够大,S函数的效果就是激发神经元。

树突收集了这些电信号,将其组合形成更强的电信号。如果信号足够强,超过阈值,神经元就会发射信号,沿着轴突,到达终端,将信号传递给下一个神经元的树突。下图显示了使用这种方式连接的若干神经元。
在这里插入图片描述
每个神经元接受来自其之前多个神经元的输入,并且如果神经元被激发了,它也同时提供信号给更多的神经元。

将这种自然形式复制到人造模型的一种方法是,构建多层神经元,每一层中的神经元都与在其前后层的神经元互相连接。下图详细描述了这种思想:
在这里插入图片描述
三层神经元,每一层有三个人工神经元或节点。每个节点都与前一层或后续层的其他每一个节点互相连接。

执行学习功能,调整节点之间的连接强度。
在这里插入图片描述
这种一致的完全连接形式事实上可以相对容易地编码成计算机指令,二是神经网络的学习过程将会弱化这些实际上不需要的连接(也就是这些连接的权重将趋近于0),因此对于解决特定任务所需最小数量的连接冗余几个连接,也无伤大雅。
在这里插入图片描述
这意味着,随着神经网络学习过程的进行,神经网络通过调整优化网络内部的链接权重改进输出,一些权重可能会变为零或接近于零。零或几乎为零的权重意味着这些链接对网络的贡献为零,因为没有传递信号。零权重意味着信号乘以零,结果得到零,因此这个链接实际上是被断开了。

关键点

  1. 虽然比起现代计算机,生物大脑看起来存储空间少得多,运行速度比较慢,但是生物大脑却可以执行复杂的任务,如飞行、寻找食物、学习语言和逃避天敌。
  2. 相比于传统的计算机系统,生物大脑对损坏和不完善信号具有难以置信的弹性。
  3. 由互相连接的神经元组成的生物大脑是人工神经网络的灵感来源。

1.7 在神经网络中追踪信号

尝试使用只有两层、每层两个神经元的较小的神经网络,来演示神经网络如何工作:在这里插入图片描述
两个输入值分别为1.0和0.5,每个节点使用激活函数。使用一些随机权重:
在这里插入图片描述
在这里插入图片描述
第一层节点是输入层,这一层不做其他事情,仅表示输入信号。也就是说,输入节点不对输入值应用激活函数。

第二层,需要做一些计算。对于这一层的每个节点,我们需要算出组合输入。
在这里插入图片描述
这个函数中的x表示一个节点的组合输入。此处组合的是所连接的前一层中的原始输出,但是这些输出得到了链接权重的调节。

节点1:x = (第一个节点的输出链接权重)+(第二个节点的输出链接权重)
x =(1.0 * 0.9)+(0.5 * 0.3)
x = 0.9 + 0.15
x = 1.05
计算该节点的输出。答案为y = 1 /(1 + 0.3499)= 1/ 1.3499。因此,y = 0.7408。

权重是神经网络进行学习的内容,这些权重持续进行优化,得到越来越好的结果。

节点2:x = (第一个节点的输出链接权重)+(第二个节点的输出链接权重)
x =(1.0 * 0.2)+(0.5 * 0.8)
x = 0.2 + 0.4
x = 0.6
使用S激活函数y = 1/(1 + 0.5488) = 1/1.5488计算节点输出,得到y = 0.6457。
在这里插入图片描述

1.8 矩阵乘法的用途

矩阵在两个方面帮助了我们。首先,矩阵允许我们压缩所有这些计算,把它们变成一种非常简单
的缩写形式。由于人类不擅长于做大量枯燥的工作,而且也很容易出错,因此矩阵对人类帮助很大。第二个好处是,许多计算机编程语言理解如何与矩阵一起工作,计算机编程语言能够认识到实际的工作是重复性的,因此能够高效高速地进行计算。

下面是两个简单矩阵相乘的一个示例:
在这里插入图片描述
这样的矩阵乘法称为点乘(dot product)或内积(inner product)。

第一个矩阵包含两层节点之间的权重。第二个矩阵包含第一层输入层的信号。通过两个矩阵相乘,我们得到的答案是输入到第二层节点组合调节后的信号:
在这里插入图片描述
可以使用矩阵乘法表示所有计算,计算出组合调节后的信号x ,输入到第二层的节点中:
在这里插入图片描述

X = W •I
W 是权重矩阵,I 是输入矩阵,X 是组合调节后的信号。

激活函数其实很简单,并不需要矩阵乘法。我们所需做的,是对矩阵X 的每个单独元素应用S函数
y = 1 /(1 + e^(-x ))。
激活函数只是简单地应用阈值,使反应变得更像是在生物神经元中观察到的行为。因此,来自第二层的最终输出是:O = sigmoid ( X )

关键点

  1. 通过神经网络向前馈送信号所需的大量运算可以表示为矩阵乘法。
  2. 不管神经网络的规模如何,将输入输出表达为矩阵乘法,使得我们可以更简洁地进行书写。
  3. 更重要的是,一些计算机编程语言理解矩阵计算,并认识到潜在的计算方法的相似性。这允许计算机高速高效地进行这些计算。

1.9 使用矩阵乘法的三层神经网络示例

观察如何处理中间层的输出以作为最后第三层的输入

具有3层、每层具有3个节点的神经网络示例:
在这里插入图片描述
第一层为输入层,最后一层为输出层,中间层我们称之为隐藏层。

3个输入是0.9、0.1和0.8。因此,输入矩阵I 为:
在这里插入图片描述
接下来是中间的隐藏层。需要计算出输入到中间层每个节点的组合(调节)信号。隐藏层输入的链接权重矩阵:
在这里插入图片描述
输入层和隐藏层之间的权重。

第二个矩阵W hidden_output:
在这里插入图片描述
继续算出输入到隐藏层的组合调节输入值。
X hidden = W input_hidden • I
在这里插入图片描述
可视化这些输入到第二层隐藏层的组合调节输入:
在这里插入图片描述
对这些节点应用了S激活函数,使得节点对信号的反应更像自然界中节点的反应:
O hidden = sigmoid( X hidden)
在这里插入图片描述
更新:
在这里插入图片描述
继续计算最终层的组合调节输入X = W •I

这一层的输入信号是第二层的输出信号,也就是我们刚刚解出的O hidden 。所使用的权重就是第二层和第三层之间的链接权重W hidden_output
X output = W hidden_output • O hidden
在这里插入图片描述
更新:
在这里插入图片描述
是应用S激活函数:
在这里插入图片描述
得到了最终输出层的输出信号。
在这里插入图片描述
下一步,将神经网络的输出值与训练样本中的输出值进行比较,计算出误差。我们需要使用这个误差值来调整神经网络本身,进而改进神经网络的输出值。

1.10 学习来自多个节点的权重

当输出和误差是多个节点共同作用的结果时,如何更新链接权重?
在这里插入图片描述
较大链接权重的连接分配更多的误差。
在这里插入图片描述
将同样的思想扩展到多个节点。如果我们拥有100个节点链接到输出节点,那么我们要在这100条链接之间,按照每条链接对误差所做贡献的比例(由链接权重的大小表示),分割误差。

我们使用权重,将误差从输出向后传播到网络中。我们称这种方法为反向传播。

1.11 多个输出节点反向传播误差

具有2个输入节点和2个输出节点的简单网络。
在这里插入图片描述
将第一个输出节点的误差标记为e1,这个值等于由训练数据提供的所期望的输出值t1 与实际输出值o1 之间的差。也就是,e1 = ( t1 - o1)。

按照所连接链接的比例,也就是权重w 1,1 和w2,1 ,我们对误差e 1 进行了分割。类似地,我们按照权重w 1,2 和w 2,2 的比例分割了e2 。
更新w1,1 :
在这里插入图片描述
更新w2,1:

在这里插入图片描述

1.12 反向传播误差到更多层中

在这里插入图片描述
使用在输出层的误差值引导调整馈送到最终层的链接权重。

将输出误差标记为eoutput ,将在输出层和隐藏层之间的链接权重标记为who 。通过将误差值按权重的比例进行分割,我们计算出与每条链接相关的特定误差值。采用与隐藏层节点相关联的这些误差ehidden ,再次将这些误差按照输入层和隐藏层之间的链接权重wih 进行分割。
在这里插入图片描述
如果神经网络具有多个层,那么我们就从最终输出层往回工作,对每一层重复应用相同的思路。
在这里插入图片描述
在这里插入图片描述
具有实际数字的3层网络中,误差如何向后传播:
在这里插入图片描述
们进一步向后工作:
在这里插入图片描述
关键点:

  1. 神经网络通过调整链接权重进行学习。这种方法由误差引导,误差就是训练数据所给出正确答案和实际输出之间的差值。
  2. 简单地说,在输出节点处的误差等于所需值与实际值之间的差值。
  3. 然而,与内部节点相关联的误差并不显而易见。一种方法是按照链路权重的比例来分割输
    出层的误差,然后在每个内部节点处重组这些误差。

1.13 使用矩阵乘法进行反向传播误差

尝试所谓的将过程(vectorise the process)矢量化。

计算的起始点是在神经网络最终输出层中出现的误差。此时,在输出层,神经网络只有两个节点,因此误差只有e1 和e2 。在这里插入图片描述

为隐藏层的误差构建矩阵。
节点一:误差信号e1* w1,1/ ( w1,1 + w2,1) 和 e2* w 1,2/ ( w1,2 + w2,2)。
节点二:误差信号e1* w2,1/ ( w2,1 + w1,1) 和 e2* w 2,2/ ( w2,2 + w1,2)。
在这里插入图片描述
可以观察到,最重要的事情是输出误差与链接权重wij 的乘法。较大的权重就意味着携带较多的输出误差给隐藏层。这是非常重要的一点。这些分数的分母是一种归一化因子。如果我们忽略了这个因子,那么我们仅仅失去后馈误差的大小。也就是说,我们使用简单得多的
e1* w1,1 来代替e1* w1,1/ ( w1,1 + w2,1)。
在这里插入图片描述
在这里插入图片描述
因此,我们得到所希望的矩阵,使用矩阵的方法来向后传播误差:
在这里插入图片描述
关键点:

  1. 反向传播误差可以表示为矩阵乘法。
  2. 无论网络规模大小,这使我们能够简洁地表达反向传播误差,同时也允许理解矩阵计算的计算机语言更高效、更快速地完成工作。
  3. 这意味着前向馈送信号和反向传播误差都可以使用矩阵计算而变得高效。

1.14 更新权重

梯度下降(gradient descent)如果我们将复杂困难的函数当作网络误差,那么找到最小值就意味着最小化误差。这样我们就可以改进网络输出。

下图显示了一个简单的函数y =(x -1)2 + 1。如果在这个函数中,y 表示误差,我们希望找到x ,可以最小化y :
在这里插入图片描述
要应用梯度下降的方法,我们必须找一个起点。上图显示了随机选择的起点。
在这里插入图片描述
这一次,我们所在之处的斜率为正,因此我们向左移动。

要改变步子大小,避免超调,这样就会避免在最小值的地方来回反弹,这是一个必要的优化。
在这里插入图片描述
同样,下面我们将使用稍微复杂的、依赖2个参数的函数,详细说明梯度下降法。这可以使用三维空间来表示,同时使用高来表示函数的值。
在这里插入图片描述
这种情况可能会发生,也就是我们所到达的山谷可能不是最低的山谷。

为了避免终止于错误的山谷或错误的函数最小值,我们从山上的不同点开始,多次训练神经网络,确保并不总是终止于错误的山谷。不同的起始点意味着选择不同的起始参数,在神经网络的情况下,这意味着选择不同的起始链接权重。

下面详细说明了使用梯度下降方法的三种不同尝试,其中有一次,这种方法终止于错误的山谷中:
在这里插入图片描述
关键点:

  1. 梯度下降法是求解函数最小值的一种很好的办法,当函数非常复杂困难,并且不能轻易使用数学代数求解函数时,这种方法却发挥了很好的作用。
  2. 更重要的是,当函数有很多参数,一些其他方法不切实际,或者会得出错误答案,这种方法依然可以适用。
  3. 这种方法也具有弹性,可以容忍不完善的数据,如果我们不能完美地描述函数,或我们偶
    尔意外地走错了一步,也不会错得离谱。

注意一些事情。观察下表,这是3个输出节点的目标值和实际值以及误差函数的候选项:
在这里插入图片描述

第三种选择是差的平方,即(目标值-实际值)2 。我们更喜欢使用第三种误差函数,而不喜欢使用第二种误差函数,原因有以下几点:

  • 使用误差的平方,我们可以很容易使用代数计算出梯度下降的斜率。
  • 误差函数平滑连续,这使得梯度下降法很好地发挥作用——没有间断,也没有突然的跳跃。
  • 越接近最小值,梯度越小,这意味着,如果我们使用这个函数调节步长,超调的风险就会变得较小。

在这里插入图片描述
下图显示了两个链接权重,这次,误差函数是三维曲面,这个曲面随着两个链接权重的变化而变化。
在这里插入图片描述
在这里插入图片描述
这个表达式表示了当权重wj,k 改变时,误差E是如何改变的。这是误差函数的斜率,也就是我们希望使用梯度下降的方法到达最小值的方向。
在这里插入图片描述
们展开误差函数,这是对目标值和实际值之差的平方进行求和,这是针对所有n个输出节点的和。
在这里插入图片描述
直接简化这个表达式。这意味着,由于这些权重是链接到节点k的权重,因此节点k的输出ok 只取决于权重wj,k 。
在这里插入图片描述
t k 的部分是一个常数,因此它不会随着wj,k 的变化而变化。也就是说,tk 不是wj,k 的函数。仔细想想,如果真实示例所提供的目标值根据权重变化,就太让人匪夷所思了!由于我们使用权重前馈信号,得到输出值ok ,因此这个表达式留下了我们所知的依赖于wj,k 的ok 部分。
在这里插入图片描述
对平方函数进行简单的微分
在这里插入图片描述
ok 是节点k的输出,这是在连接输入信号上进行加权求和,在所得到结果上应用S函数得到的结果。
在这里插入图片描述
微分S函数
在这里插入图片描述
得到以下的表达式:
在这里插入图片描述
把在前面的2 去掉。我们只对误差函数的斜率方向感兴趣
在这里插入图片描述
颜色标记有助于显示出表达式的各个部分。第一部分,非常简单,就是(目标值-实际值),我们对此已经很清楚了。在sigmoid中的求和表达式也很简单,就是进入最后一层节点的信号,我们可以称之为ik ,这样它看起来比较简单。这是应用激活函数之前,进入节点的信号。最后一部分是前一隐藏层节点j的输出。

重新构建一个表达式:

  1. 第一部分的(目标值-实际值)误差,现在变成了隐藏层节点中重组的向后传播误差,正如在前面所看到的那样。我们称之为ej 。
  2. sigmoid部分可以保持不变,但是内部的求和表达式指的是前一层,因此求和的范围是所有由权重调节的进入隐藏层节点j的输入。我们可以称之为ij 。
  3. 现在,最后一部分是第一层节点的输出oi ,这碰巧是输入信号。

得到误差函数斜率,用于输入层和隐藏层之间权重调整:
在这里插入图片描述
权重改变的方向与梯度方向相反,们用数学的形式来表达这个因子。在这里插入图片描述
更新后的权重wj,k 是由刚刚得到误差斜率取反来调整旧的权重而得到的。正如我们先前所看到的,如果斜率为正,我们希望减小权重,如果斜率为负,我们希望增加权重,因此,我们要对斜率取反。符号α是一个因子,这个因子可以调节这些变化的强度,确保不会超调。我们通常称这个因子为学习率。

这个表达式不仅适用于隐藏层和输出层之间的权重,而且适用于输入层和隐藏层之间的权重。差值就误差梯度,我们可以使用上述两个表达式来计算这个误差梯度。
在这里插入图片描述
由于学习率只是一个常数,并没有真正改变如何组织矩阵乘法,因此我们省略了学习率α。

在这里插入图片描述
关键点:

  1. 神经网络的误差是内部链接权重的函数。
  2. 改进神经网络,意味着通过改变权重减少这种误差。
  3. 直接选择合适的权重太难了。另一种方法是,通过误差函数的梯度下降,采取小步长,迭代地改进权重。所迈出的每一步的方向都是在当前位置向下斜率最大的方向,这就是所谓的梯度下降。
  4. 使用微积分可以很容易地计算出误差斜率。

1.15 权重更新成功范例

演示几个有数字的示例:
在这里插入图片描述
更新隐藏层和输出层之间的权重w1,1 。当前,这个值为2.0。
在这里插入图片描述

  1. 第一项(tk-ok )得到误差e 1 = 0.8。
  2. S函数内的求和Σj wj,k oj 为(2.0×0.4)+(3.0 * 0.5)= 2.3。
  3. sigmoid 1/(1 + e-2.3) 为0.909。中间的表达式为0.909 *(1-0.909)=0.083。
  4. 由于我们感兴趣的是权重w1,1 ,其中j=1,因此最后一项oj 也很简单,也就是oj = 1 。此处,oj 值就是0.4。

将这三项相乘,最后我们得到-0.0265。

如果学习率为0.1,那么得出的改变量为- (0.1 * -0.02650)= +0.002650。因此,新的w1,1 就是原来的2.0加上0.00265等于2.00265。

1.16 准备数据

1.16.1 输入

观察下图的S激活函数:
在这里插入图片描述
如果输入变大,激活函数就会变得非常平坦。

由于我们使用梯度学习新的权重,因此一个平坦的激活函数会出问题。

权重的改变取决于激活函数的梯度。小梯度意味着限制神经网络学习的能力。这就是所谓的饱和神经网络。这意味着,我们应该尽量保持小的输入。

这个表达式也取决于输入信号(oj ),因此,我们也不应该让输入信号太小。当计算机处理非常小或非常大的数字时,可能会丧失精度,因此,使用非常小的值也会出现问题。

一个好的建议是重新调整输入值,将其范围控制在0.0到1.0。输入0会将oj设置为0,这样权重更新表达式就会等于0,从而造成学习能力的丧失,因此在某些情况下,我们会将此输入加上一个小小的偏移,如0.01,避免输入0带来麻烦。

1.16.2 输出

神经网络的输出是最后一层节点弹出的信号。如果我们使用的激活函数不能生成大于1的值,那么尝试将训练目标值设置为比较大的值就有点愚蠢了。请记住,逻辑函数甚至不能取到1.0,只能接近1.0。数学家称之为渐近于1.0。
在这里插入图片描述
如果我们将目标值设置在这些不可能达到的范围,训练网络将会驱使更大的权重,以获得越来越大的输出,而这些输出实际上是不可能由激活函数生成的。这使得网络饱和。

因此,我们应该重新调整目标值,匹配激活函数的可能输出,注意避开激活函数不可能达到的值。

虽然,常见的使用范围为0.0~1.0,但是由于0.0和1.0这两个数也不可能是目标值,并且有驱动产生过大的权重的风险,因此一些人也使用0.01~0.99的范围。

1.16.3 随机初始权重

由于大的初始权重会造成大的信号传递给激活函数,导致网络饱和,从而降低网络学习到更好的权重的能力,因此应该避免大的初始权重值。

可以从-1.0~+1.0之间随机均匀地选择初始权重。

我们不希望权重破坏了精心调整输入信号的努力。数学家所得到的经验规则是,我们可以在一个节点传入链接数量平方根倒数的大致范围内随机采样,初始化权重。

如果每个节点具有3条传入链接,那么初始权重的范围应该在从 在这里插入图片描述
在这里插入图片描述
,即±0.577之间。如果每个节点具有100条传入链接,那么权重的范围应该在 在这里插入图片描述
在这里插入图片描述

直觉上说,这是有意义的。一些过大的初始权重将会在偏置方向上偏置激活函数,非常大的权重将会使激活函数饱和。
在这里插入图片描述

禁止将初始权重设定为相同的恒定值,特别是禁止将初始权重设定为0。

由于0权重,输入信号归零,取决于输入信号的权重更新函数也因此归零,这种情况更糟糕。网络完全丧失了更新权重的能力。

关键点:

  1. 如果输入、输出和初始权重数据的准备与网络设计和实际求解的问题不匹配,那么神经网络并不能很好地工作。
  2. 一个常见的问题是饱和。在这个时候,大信号(这有时候是由大权重带来的)导致了应用在信号上的激活函数的斜率变得非常平缓。这降低了神经网络学习到更好权重的能力。另一个问题是零值信号或零值权重。这也可以使网络丧失学习更好权重的能力。
  3. 内部链接的权重应该是随机的,值较小,但要避免零值。如果节点的传入链接较多,有一些人会使用相对复杂的规则,如减小这些权重的大小。
  4. 输入应该调整到较小值,但不能为零。一个常见的范围为0.01~0.99,或-1.0~1.0,使用哪个范围,取决于是否匹配了问题。
  5. 输出应该在激活函数能够生成的值的范围内。逻辑S函数是不可能生成小于等于0或大于等于1的值。将训练目标值设置在有效的范围之外,将会驱使产生越来越大的权重,导致网络饱和。一个合适的范围为0.01~0.99。

2.使用python制作神经网路

2.1 框架代码

勾勒神经网络类的大概样子。我们知道,它应该至少有3个函数:
初始化函数——设定输入层节点、隐藏层节点和输出层节点的数量。
训练——学习给定训练集样本后,优化权重。
查询——给定输入,从输出节点给出答案。

"""定义神经网络类"""
class NeuealNetwork:

    #初始化神经网络
    def __init__(self, inputNodes, hiddenNodes, outputNodes, learningRate):
		pass



    #训练神经网络
    def train(self):
        pass



    #查询神经网络
    def query(self):
        pass

2.2 初始化网络

#初始化神经网络
def __init__(self, inputNodes, hiddenNodes, outputNodes, learningRate):
	self.inputNodes = inputNodes
    self.hiddenNodes = hiddenNodes
    self.outputNodes = outputNodes
    self.learningRate = learningRate

2.3权重——网络的核心

是创建网络的节点和链接。网络中最重要的部分是链接权重,我们使用这些权重来计算前馈信号、反向传播误差,并且在试图改进网络时优化链接权重本身。

  • 在输入层与隐藏层之间的链接权重矩阵Winput_hidden ,大小为hidden_nodes 乘以 input_nodes。
  • 在隐藏层和输出层之间的链接权重矩阵Whidden_output ,大小为hidden_nodes乘以 output_nodes。
#Wih(输入层到隐藏层的权重矩阵)
self.Wih = (np.random.random((self.hiddenNodes, self.inputNodes)) - 0.5) \
/ 5 * 10 * (1 / np.sqrt(self.inputNodes))
#Who(隐藏层到输出层的权重矩阵)
self.Who = (np.random.random((self.outputNodes, self.hiddenNodes)) - 0.5) \
/ 5 * 10 * (1 / np.sqrt(self.hiddenNodes))

2.4 查询网络

query()函数接受神经网络的输入,返回网络的输出。
在这里插入图片描述
在这里插入图片描述

	#查询神经网络
    def query(self, input_list):
        """
        查询神经网络
        :param input_signal: 1 x n 的矩阵
        :return: 1 x m 的矩阵
        """
        input_signal = np.array(input_list, ndmin=2).T
        #计算隐藏层输入信号:w * i
        hidden_input = np.dot(self.Wih, input_signal)
        #j计算隐藏层输出信号:sigmoid(hi)
        hidden_output = sp.expit(hidden_input)
        #计算输出层输入信号:w * ho
        final_input = np.dot(self.Who, hidden_output)
        #计算输出层输出信号:sigmoid(fi)
        final_output = sp.expit(final_input)

        return final_output

2.5 训练网络

现在来解决这个稍微复杂的训练任务。训练任务分为两个部分:

  1. 第一部分,针对给定的训练样本计算输出。这与我们刚刚在query()函数上所做的没什么区别。
  2. 第二部分,将计算得到的输出与所需输出对比,使用差值来指导网络权重的更新。

在这里插入图片描述
在这里插入图片描述
newW = oldW + deltaW

	#训练神经网络
    def train(self, input_list, target_list):
        """

        :param input_list: 1 x n
        :param target_list: 1 x m
        :return:
        """
        #第一部分与查询函数相同
        input_signal = np.array(input_list, ndmin=2).T
        # 计算隐藏层输入信号:w * i
        hidden_input = np.dot(self.Wih, input_signal)
        # j计算隐藏层输出信号:sigmoid(hi)
        hidden_output = sp.expit(hidden_input)
        # 计算输出层输入信号:w * ho
        final_input = np.dot(self.Who, hidden_output)
        # 计算输出层输出信号:sigmoid(fi)
        final_output = sp.expit(final_input)

        #第二部分
        targets = np.array(target_list, ndmin=2).T
        #误差(t - output)
        Eo = targets - final_output
        #误差反向传播Eh = Who.T * Eo
        Eh = np.dot(self.Who.T, Eo)
        #更新Who: oldWho + lr * Eo * fo * (1 - fo) dot ho
        self.Who += self.learningRate * np.dot((Eo * final_output * (1 - final_output)), hidden_output.T)
        #更新Wih: oldWih + lr * Eh * ho * (1 - ho) dot in
        self.Wih += self.learningRate * np.dot((Eh * hidden_output * (1 - hidden_output)), input_signal.T)

2.6 目前全部代码

在这里插入代码片

3.神经网络识别数字

3.1 准备手写数字的数据集

csv格式:
在这里插入图片描述

import numpy as np
import matplotlib.pyplot as plt

with open("Data/mnist_train_100.csv", "r") as f:
    data_list = f.readlines()

values = data_list[2].split(",")
img = np.asfarray(values[1:]).reshape(28,28)
plt.imshow(img, cmap="Greys")
plt.show()

在这里插入图片描述

3.2 训练数据

输入:
需要将所得到的输入乘以0.99,把它们的范围变成0.0 到0.99。接下来,加上0.01,将这些值整体偏移到所需的范围0.01到1.00。

data = np.asfarray(values[0][1:]) / 255 * 0.99 + 0.01

输出:
在这里插入图片描述

output_nodes = 10
targets = np.zeros(output_nodes) + 0.01
targets[int(values[0][0])] = 0.99
print(targets)
#5

out:
[0.01 0.01 0.01 0.01 0.01 0.99 0.01 0.01 0.01 0.01]

3.3 训练模型

#初始化节点、学习率
input_nodes = 784
hidden_nodes = 100
output_nodes = 10
learning_rate = 0.1
#实例化神经网络
n = NeuealNetwork(input_nodes, hidden_nodes, output_nodes, learning_rate)
#加载训练数据集到列表
with open("Data/mnist_train_100.csv") as f:
    data_list = f.readlines()
#开始训练数据
for data in data_list:
    all_values = data.split(",")
    #处理输入信号
    inputs = np.asfarray(all_values[1:]) / 255.0 * 0.99 + 0.01
    #处理输出信号
    targets = np.zeros(output_nodes) + 0.01
    targets[int(all_values[0])] = 0.99
    #开始训练
    n.train(inputs, targets)

3.4 测试网络

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
40% 的准确率

3.5使用完整的数据集

from build_model import NeuealNetwork
import numpy as np
import matplotlib.pyplot as plt

#初始化节点、学习率
input_nodes = 784
hidden_nodes = 100
output_nodes = 10
learning_rate = 0.1
#实例化神经网络
n = NeuealNetwork(input_nodes, hidden_nodes, output_nodes, learning_rate)
#加载训练数据集到列表
with open("Data/mnist_train.csv") as f:
    data_list = f.readlines()
#开始训练数据
for data in data_list:
    all_values = data.split(",")
    #处理输入信号
    inputs = np.asfarray(all_values[1:]) / 255.0 * 0.99 + 0.01
    #处理输出信号
    targets = np.zeros(output_nodes) + 0.01
    targets[int(all_values[0])] = 0.99
    #开始训练
    n.train(inputs, targets)

# 测试网络
with open("Data/mnist_test.csv", "r") as f:
    test_data_list = f.readlines()

#测试神经网络
scorecard = []
for record in test_data_list:
    #分割
    all_values = record.split(",")
    #设置标签
    test_lable = all_values[0]
    print("test lable : ", test_lable)
    inputs = (np.asfarray(all_values[1:]) / 255.0 * 0.99) + 0.01
    outputs = n.query(inputs)
    real_lable = np.argmax(outputs)
    print("output lable : ", real_lable)
    #统计结果
    if(int(test_lable) == int(real_lable)):
        scorecard.append(1)
    else:
        scorecard.append(0)

right_rate = sum(scorecard) / float(len(scorecard))
print(right_rate)

94.63的准确率

3.6学习率与性能的关系

在这里插入图片描述

right_rates = []
#初始化节点、学习率
input_nodes = 784
hidden_nodes = 100
output_nodes = 10
#学习率与性能的关系
for lr in learning_rates:
    #实例化神经网络
    n = NeuealNetwork(input_nodes, hidden_nodes, output_nodes, lr)
    #加载训练数据集到列表
    with open("Data/mnist_train.csv") as f:
        train_data_list = f.readlines()
    #开始训练数据
    for data in train_data_list:
        all_values = data.split(",")
        #处理输入信号
        inputs = np.asfarray(all_values[1:]) / 255.0 * 0.99 + 0.01
        #处理输出信号
        targets = np.zeros(output_nodes) + 0.01
        targets[int(all_values[0])] = 0.99
        #开始训练
        n.train(inputs, targets)
        
    # 加载测试数据
    with open("Data/mnist_test.csv", "r") as f:
        test_data_list = f.readlines()

    #测试神经网络
    scorecard = []
    for record in test_data_list:
        #分割
        all_values = record.split(",")
        #设置标签
        test_lable = all_values[0]
        # print("test lable : ", test_lable)
        inputs = (np.asfarray(all_values[1:]) / 255.0 * 0.99) + 0.01
        outputs = n.query(inputs)
        real_lable = np.argmax(outputs)
        # print("output lable : ", real_lable)
        #统计结果
        if(int(test_lable) == int(real_lable)):
            scorecard.append(1)
        else:
            scorecard.append(0)

    right_rate = sum(scorecard) / float(len(scorecard))
    right_rates.append(right_rate)

在这里插入图片描述

在这里插入图片描述
学习率在0.06至0.26之间性能最好

3.7世代与性能的关系

epochs = np.linspace(1, 20, 20, np.int)
lrs = [0.06, 0.26]
low_right_rates = []
up_right_rates = []

right_rates = []
#初始化节点、学习率
input_nodes = 784
hidden_nodes = 100
output_nodes = 10
#不同的学习率
a = 0
for l in range(len(lrs)):
    #不同的世代
    for i in range(len(epochs)):
        #迭代当前世代
        for e in range(int(epochs[i])):
            #实例化神经网络
            n = NeuealNetwork(input_nodes, hidden_nodes, output_nodes, lrs[l])
            #加载训练数据集到列表
            with open("Data/mnist_train.csv") as f:
                train_data_list = f.readlines()
            #开始训练数据
            for data in train_data_list:
                all_values = data.split(",")
                #处理输入信号
                inputs = np.asfarray(all_values[1:]) / 255.0 * 0.99 + 0.01
                #处理输出信号
                targets = np.zeros(output_nodes) + 0.01
                targets[int(all_values[0])] = 0.99
                #开始训练
                n.train(inputs, targets)
                
        # 加载测试数据
        with open("Data/mnist_test.csv", "r") as f:
            test_data_list = f.readlines()

        #测试神经网络
        scorecard = []
        for record in test_data_list:
            #分割
            all_values = record.split(",")
            #设置标签
            test_lable = all_values[0]
            # print("test lable : ", test_lable)
            inputs = (np.asfarray(all_values[1:]) / 255.0 * 0.99) + 0.01
            outputs = n.query(inputs)
            real_lable = np.argmax(outputs)
            # print("output lable : ", real_lable)
            #统计结果
            if(int(test_lable) == int(real_lable)):
                scorecard.append(1)
            else:
                scorecard.append(0)

        right_rate = sum(scorecard) / float(len(scorecard))
        if l == 0:
            low_right_rates.append(right_rate)
        else:
            up_right_rates.append(right_rate)

在这里插入图片描述
在这里插入图片描述

3.8网络形状与性能的关系

from build_model import NeuealNetwork
import numpy as np
import matplotlib.pyplot as plt

#初始化节点、学习率
input_nodes = 784
output_nodes = 10
learning_rate = 0.15

XN_list = []
hidden_Nodess = [10, 20, 40, 80, 160, 240, 320, 400, 480, 560]
for hn in hidden_Nodess:
    # 实例化神经网络
    n = NeuealNetwork(input_nodes, hn, output_nodes, learning_rate)
    # 加载训练数据集到列表
    with open("Data/mnist_train.csv") as f:
        data_list = f.readlines()
    # 开始训练数据
    for data in data_list:
        all_values = data.split(",")
        # 处理输入信号
        inputs = np.asfarray(all_values[1:]) / 255.0 * 0.99 + 0.01
        # 处理输出信号
        targets = np.zeros(output_nodes) + 0.01
        targets[int(all_values[0])] = 0.99
        # 开始训练
        n.train(inputs, targets)


    # 测试网络
    with open("Data/mnist_test.csv", "r") as f:
        test_data_list = f.readlines()

    #测试神经网络
    scorecard = []
    for record in test_data_list:
        #分割
        all_values = record.split(",")
        #设置标签
        test_lable = all_values[0]
        # print("test lable : ", test_lable)
        inputs = (np.asfarray(all_values[1:]) / 255.0 * 0.99) + 0.01
        outputs = n.query(inputs)
        real_lable = np.argmax(outputs)
        # print("output lable : ", real_lable)
        #统计结果
        if(int(test_lable) == int(real_lable)):
            scorecard.append(1)
        else:
            scorecard.append(0)

    right_rate = sum(scorecard) / float(len(scorecard))
    XN_list.append(right_rate)

plt.plot(hidden_Nodess, XN_list)
plt.scatter(hidden_Nodess, XN_list)
plt.show()

在这里插入图片描述
最佳隐藏节点数:240

3.9 自己手写数字

for i in range(10):
    file_name = "{}.png".format(i)
    img_arr = cv2.imread(file_name, cv2.IMREAD_GRAYSCALE)
    img_arr = np.asfarray(img_arr).reshape(1, 28 * 28)
    img_arr = img_arr / 255 * 0.99 + 0.01
    print(file_name, "  :   ", n.query(img_arr).argmax())

在这里插入图片描述

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值