,神经网络是一种模拟人脑的神经网络,以期能够实现类人工智能的机器学习技术。
一.预测器和分类器
1.什么叫预测器:一个简单的机器接受了一个输入,并做出应有的预测,输出结果,所以我们将其称为预测器。
2.什么叫分类器:用线性函数将不同的事物分离开,那么这个线性函数就叫做分类器
如下所示,直线将两种不同的虫分离开,那就可以说这条直线是一个分类器
3.训练分类器
我们希望训练线性分类器,使其能够正确分类瓢虫和毛虫,在上图中,根据观察,我们知道要做到这一点,简单说来,就是要调整分界线的斜率,使其能够基于小虫的宽度和长度将两组点划分开来。
如何做到呢?当然对我们来说很简单,画条如上图的直线就可以了,但是计算机怎么知道该怎么画,怎么知道直线的斜率是多少,这就需要我们来告知计算机了。
我们可以根据下面两个实例来告知计算机分类器的斜率应该是多少:
实例 | 宽度 | 长度 | 小虫 |
1 | 3.0 | 1.0 | 瓢虫 |
2 | 1.0 | 3.0 | 毛虫 |
这些实例帮助我们调整分类函数的斜率,用来训练预测器或分类器的真实实例,我们称为训练数据。如上的直线我们定义为y=Ax,我们的目的就是找到正确的斜率,让函数能分开毛虫和瓢虫
1.>我们可以定义一个初始斜率,然后慢慢调试达到我们的要求,比如我们定义初始斜率为0.25,那会是什么样子呢。如下图所示
我们可以观察直线y=0.25x不是一台很好的分类器,这条直线未将两种类型的小虫区分开来。由于瓢虫也处于直线之上,因此我们不能说“如果小虫在直线之上,则这是一条毛虫”
直观上,我们观察到需要将直线向上移动一点,但是我们要抵制诱惑,不能通过观察图就画出一条合适的直线。我们希望能够找到一种可重复的办法,也就是用一系列的计算机指令来达到这个目标,计算机科学家称这一系列指令为 算法。
2.>.我们应该考虑y应该是什么值,如果y为1.0,那么直线就会恰好经过瓢虫所在的坐标点(x,y)=(3,1),这并不是我们希望的情况,我们希望直线处于这个点的上方。为什么呢?因为我们希望所有瓢虫的点处于直线的下方,而不是在直线上,这条直线需要成为瓢虫和毛虫之间的一条分界线,而不是给定小虫宽度,预测小虫长度的一个预测器。
因此当x=3.0时,我们尝试使用y=1.1的目标值,在上面的y=2.5x初始预测器时,当x=3.0时,y=0.25*3.0=0.75,与我们期望的目标值1.1有一定误差,误差=期望目标值-实际输出值,为E=1.1-0.75=0.35
如下所示:
3.>现在,我们需要对这个E做些什么,才能更好的指导我们调整参数A呢,那我们就需要找到这两个之间的关系。我们将正确的期望值t称为目标值,为了得到这个t,我们就需要稍微调整A的值,科学家使用增量符号Δ表示“微小的变化量”,下面我们将这个变化量写出来:t=(A+ΔA)x,如下图所示:在图中,我们可以看见新的斜率为(A+ΔA)
如下我们就可以算出A和E之间的关系:E=t-y=(ΔA)x,ΔA=E/x,上面我们已经知道了误差值为0.35,可以计算出ΔA=0.1167,这意味当前的A=0.25+0.1167=0.3667,当A=0.3667时,使用这个A值计算得到的y值为1.1,正如你说期望,这就是我们的目标值。
4.>如上我们已经完成了一个实例训练,让我们从下一个实例中学习,此时,我们已知正确值对应x=1.0,y=3.0,带入线性函数中,我们得到y=0.3667*1.0=0.3667,这与训练样本中y=3.0相去甚远,基于与先前同样的推理,我们希望直线不要经过训练数据,而是稍微高于或低于训练数据,我们将目标值设为2.9,这样,毛虫的训练样本就在直线的上方,而不是在直线之上,误差值E=2.9-0.3667=2.5333,让我们根据ΔA=E/x,计算出ΔA=2.5333,则A=2.5333+0.3667=2.9,这也意味着,对于x=1.0,函数得出了2.9的答案,这正是所期望的值。我们可以从下图看出初始直线、向第一个训练样本学习后的改进直线和向第二个训练样本学习后的最终直线。
5.>看着这幅图,我们似乎并没有做到让直线以我们所希望的方式倾斜,这条直线没有整齐的划分出瓢虫和毛虫。实际上,最终改进的直线不会顾及所有先前的训练样本,而是抛弃了所有先前训练样本的学习结果,只是对最近的一个实例进行了学习。怎么解决这个情况呢?我们可以在ΔA=E/x公式中,增加一个调节系数,慢慢的调节,不要一次调节过大。
新的公式为:ΔA=L(E/x),调节系数通常被称为学习率。因此,我们称之为L,我们就挑L=0.5作为一个合理的系数开始学习过程。简单来说,这就意味着我们只更新原更新值的一半。
下来再重复上述过程,我们有一个初始值A=0.25,使用第一个训练样本,我们得到y=0.25*3.0=0.75,期望值为1.1,得到误差值为0.35,ΔA=L(E/x)=0.5*(0.35/3.0)=0.0583,更新后的A值为0.25+0.0583=0.3083,尝试使用新的A值计算训练样本,在x=3.0时,得到y=0.3083*3.0=0.9250.现在,由于这个值小于1.1,因此这条直线落在了训练样本错误的一边,但是,如果你将这视为后续的众多调整步骤的第一步,则这个结果不算太差。与初始直线相比,这条直线确实向正确方向移动了。
我们继续使用第二个训练数据实例,x=1.0时,使用A=0.3083,我们得到y=0.3083*1.0=0.3083.所需值为2.9,因此误差值是2.9-0.3083=2.5917,ΔA=L(E/x)=0.5*2.5917/1.0=1.2958,当前,第二个更新的值A等于0.3083+1.2958=1.6042,下面然我们在此观察初始直线、改进后的直线和最终直线,观察这种有节制的改进是否在瓢虫和毛虫区域之间是否得到了更好的分界线。
如上结果真的很不错,即使使用这两个简单的训练样本,当然在真正的过程中训练数据特别多,利用带有调节学习速率的一种相对简单的改进方法,我们也非常迅速地得到了一条很好的分界线y=Ax,其中A=1.6042
让我们先放下已经取得的成就,我们已经实现了自动化的学习方法,虽然方法非常简单,但卓有成就地对若干实例进行分类。
4.有时候一个分类器不足以求解问题
上面的例子我们都是输入一个值,简单的分类两种不同事物,要是我们输入了两个值该怎么分类呢?下面我们来简单观察下布尔逻辑函数
如上,我们似乎不能只用一条单一的直线将红色区域和绿色区域分开来。这就是简单的线性分类器的一个主要限制,如果不能用一条直线把根本性的问题划分开来,那么简单的线性分类器是没有用处的。在一些任务中,根本性问题不是线性可分的,也就是说单一的一条直线于事无补,而我们希望神经网络能够解决此类的任务。
因此神经网络的核心思想就是:使用多个分类器一起工作。在我们深入探讨由多个分类器组合所构建的神经网络之前,让我们先观察动物的大脑,这些动物的大脑启发了神经网络的方法,
二.神经网络
1.神经元
1.>工作方式:神经元接受了一个电输入,输出另一个电信号,这看起来,与我们先前所观察的分类或预测的机器一模一样,这些机器也是接受了一个输入,进行一些处理,然后弹出一个输出。因此,我们可以与以前一样,将神经元表示为线性函数吗?虽然这是好主意,但是不可以这么做。生物神经元与简单的线性函数不一样,不能简单地对输入做出的响应生成输出,也就是说,它的输出不能采用这种形式:输出=常量*输入+另一常量。
2.>激活函数(阈值):观察表明,神经元不会立即反应,而是会抑制输入,直到输入增强,强大到可以触发输出。你可以这样认为,在产生输出之前,输入必须达到一个阈值。就像水在杯中直到水装满了杯子,才可能溢出。直观上,这是有道理的--神经元不希望传递微小的噪声信号,而只是传递有意识的明显信号。在数学上,有很多激活函数可以达到这样的效果,如下:
我们可以看到,再输入值较小的情况下,输出为0,然而,一旦输入达到阈值,输出就一跃而起。具有这种行为的人工神经元就像一个真正的生物神经元,输入达到阈值时,神经元就激发了。下面的S形函数称为S函数,我们将继续使用这种平滑的S形函数制作神经网络。虽然人工智能研究人员还使用其他外形类似的函数,但是S函数简单,并且事实上非常常见,因此S函数对我们非常重要,S函数,有时候也称为逻辑函数。
3.>怎么根据输入的值正确输出输出呢:我们只需对输入的值进行相加,得到最终总和,作为S函数的输入,然后输出结果,这实际上反应了神经元的工作机制,下图说明了这种组合输入,然后对最终输入总和使用阈值的思路。
说明:如果组合信号不够强大,那么S阈值函数效果是抑制输出信号。如果总和x足够大,S函数的效果就是激发神经元。有趣的是,如果只有其中一个输入足够大,其他输入都很小,那么这也足够激发神经元。更重要的是,如果其中一些输入,单个而言一般大,但不是非常大,这样由于信号的组合足够大,超过阈值,那么神经元也能激活。
4>那么神经元是怎么构建出来的呢?如下构建多层神经元,每一层中的神经元都与在其前后层的神经元互相连接。
你可以看到三层神经元,每一层有三个人工神经元或节点。你还可以看到每个节点都与前一层或后续层的其他每一个节点互相连接。
5.>如何调节节点间的连接强度。
在一个节点中,我们可以调整输入的总和或S阈值函数的形状,但是比起简单的调整节点之间的连接强度,调整S阈值函数的形状相对复杂。我们可以在每个连接增加相关的权重,神经网络通过调整优化网络内部的链接权重改进输出。如下:
我们不采用创造性的方式将神经元连接起来。原因有两个:第一是这种一致的完全连接形式事实上可以相对容易地编码成计算机指令。第二是神经网络的学习过程将会弱化这些实际上不需要的连接。
2.在神经网络中追踪信号。
1.>如下神经网络我们怎么计算输出呢?
如下给出输入值和初始权重:
输入值:1.0 0.5
权重:w1.1=0.3 w1.2=0.2 w2.1=0.3 w2.2=0.8
第一层节点是输入层,不做其他事情,仅表示输入信号,也就是说,输入节点不对输入值应用激活函数。
第二层我们需要算出组合输入,并应用激活函数,算出输出
第一个节点输出为:x1=(1.0*0.3+0.5*0.3)=1.05 计算组合输入
Y1=1(1+e-x)=0.7408 对组合输入应用激活函数,计算输出
第二个节点输出为:x1=(1.0*0.2+0.5*0.8)=0.6 计算组合输入
Y1=1(1+e-x)=0.6457 对组合输入应用激活函数,计算输出
如下图:
平心而论这样计算特别麻烦,尤其是当有很多层很多神经元时,我们该怎么计算呢?
2.>矩阵
如下,上面的乘法用矩阵表示,表示一个2乘以2的矩阵和1乘以1的矩阵点乘,我们可以使用如下公式:X=W * I,W是权重矩阵,I是输入矩阵,X是组合调节后的信号,即输入到第二层的结果矩阵。矩阵通常使用斜体显示,表示他们是矩阵,而不是单个数字。使用公式后我们不用管神经元有多少层,直接使用公式计算即可,此公式适用于前后层之间的计算。
还需要对结果矩阵使用激活函数,才可以输出值:O=sigmoid(X),斜体O表示矩阵,这个矩阵包含了来自神经网络的最后一层中的所有输出。
3.使用矩阵乘法的三层神经网络示例
在下面这个示例中,我们需要观察如何处理中间层的输出以作为最后第三层的出入。
1.>术语介绍
如下图所示:有一些连接权重没写全,具体可以看下面的计算中矩阵的值
第一层为输入层,中间层为隐藏层,最后一层为输出层
2.>输入层到隐藏层的计算
如上所示隐藏层的输出为:0.761、0.603、0.650
3.>隐藏层到输出层的计算
如上所示神经网络的最终输出为:0.726、0.708、0.778
4.>我们可以用一个图来展示数据的计算过程:
4.我们如何根据误差更新权重呢?
1.>当输出和误差(实际值减去输出值等于误差)是多个节点共同作用的结果时,我们如何更新链接权重呢?如下图所示
2.>当只有一个节点前馈信号到输出节点,事情要简单得多。如果有两个节点,我们如何使用输出误差值呢?使用所有的误差值,只对一个权重进行更新,这种做法忽略了其他链接及权重,毫无意义,多条链接都对这个误差值有影响。只有一条链接造成了误差,这种机会微乎其微。
一种思想就是在所有造成误差的节点中平分误差,如下图:
另一种思想是不等分误差,与前一种思想相反,我们为较大链接权重的连接分配更多的误差。为什么这么做呢?这是因为这些链接对造成误差的贡献较大,如下图:
如上所示,我们是根据链接权重的比例来分配误差值。我们在两件事情上使用了权重。第一件事情,在神经网络中,我们使用权重,将信号从输入向前传播到输出层。第二件事情,我们使用权重,将误差从输出向后传播到网络中。我们称这种方法为反向传播。
5.反向传播误差到更多层中
1.>下图显示了具有3层的简单神经网络,一个输入层、一个隐藏层和一个最终输出层。我们可以看到传播过程,先使用在输出层的误差值引导调整馈送到最终层的链接权重,通过将误差值按权重的比例进行分割,我们计算出与每条链接相关的特定误差值,再通过相加每条链接的误差值,计算出eoutput,在使用相同的方法计算出einput。
2.>下面我们利用实例说明传播过程。我们只计算出隐藏层的第一个节点的误差,其他节点方法类似。
如下,第一个隐藏层节点的误差是与这个节点前向连接所有链接中分割误差的和,计算公式如下,其他节点计算方法类似:
我们可以根据下图看出计算结果,具体计算过程可以根据上面的公式计算:
3.>还记得我们练习三层神经网络是怎么计算输出结果的吗?我们当时是利用矩阵计算的,那么在反向传播中计算误差是能不能也利用矩阵呢,当然是可以的。如下:
输出层误差矩阵为:
根据输出层误差矩阵和个链接的权重计算隐藏层误差为:
有没有感觉上面的表达式有点复杂,再次观察上面的表达式,我们可以观察到,最重要的事情是输出误差与链接权重Wij的乘法,较大的权重就意味着携带较多的输出误差给隐藏层。这是非常重要的一点,这些分数的分母是一种归一化因子。如果我们忽略了这个因子,那么我们仅仅失去后馈误差的大小。也就是说,我们使用简单的多的e1*w1.1代替e1*w1.1/(w1.1+w2.1),如果我们采用这种方法,那么矩阵乘法就非常容易辨认,如下:
不知你们有没有发现这个权重矩阵与我们先前构建的矩阵很像,但是这个矩阵沿对角线进行了翻转,因此现在右上方的元素变成了左下方的元素,左下方的元素变成了左上方的元素,我们称此为转置矩阵,记为wT,因此我们得到所希望的矩阵,使用矩阵的方法来向后传播误差,如下所示:
6.实际如何更新权重呢.
到目前为止,我们已经理解了让误差反向传播到网络的每一层。为什么这么做呢,原因就是,我们使用误差来指导如何调整链接权重,从而改进神经网络输出的总体答案,这基本上就是先前所讨论的线性分类器所做的事情。但是这些节点都不是简单的线性分类器,这些稍微复杂的节点,对加权后的信号进行求和,并应用了S阈值函数,将所得到的结果输出给下一层的节点。因此,我们如何才能真正地更新连接这些相对复杂节点链接的权重呢?
1.>梯度下降法
那么什么是梯度下降法呢?想象一下,一个非常复杂、有波峰波谷的地形以及连绵的群山峻岭。在黑暗中,伸手不见五指。你知道你是在一个山坡上,你需要到坡底。对于整个地形,你没有精确的地图,只有一把手电筒。你能做什么呢?你可能会使用手电筒,做近距离的观察。你不能使用手电筒看得更远,无论如何,你肯定看不到整个地形。你可以看到某一块土地看起来是下坡,于是你就小步地往这个方向走。通过这种方式,你不需要完整的地图,也不需要事先制定路线,你一步一个脚印,缓慢地前行,慢慢的下山。当你迈出一步之后,再次观察周围的地形,看看你下一步往哪个方向走,才能更接近目标,然后,你就往哪个方向走出一步,你一直保持这种方式,直到非常欣喜地到达了山底。梯度是指地面的坡度。你走的方向是最陡的坡度向下的方向。如下所示:
这种酷炫的梯度下降法与神经网络之间有什么联系呢?好吧,如果我们将复杂困难的函数当做网络误差,那么下山找到最小值就意味着最小化误差。这样我们就可以改进网络输出。这就是我们希望做到的。我们可以这样理解,当误差为0是,此时的权重就是最优的,在误差函数中也就是说,当误差y最小时,此时的权重x就是最优的值,此时误差函数的斜率为0,下面来讲解怎么通过梯度下降法改进权重。
2.>如何选取误差函数?
我们选取的误差函数是:(目标值-实际值)2,当然还有其他,比如直接使用目标值-实际值或者使用差的绝对值,但是以上两种方法都有一定的缺陷,而我们选取的误差函数好很多优点,比如:第一:使用误差的平方,我们可以很容易使用代数计算出梯度下降的斜率。第二:误差函数平滑连续,这使得梯度下降法很好地发挥作用—没有间断,也没有突然的跳跃。第三:越接近最小值,梯度越小,这意味着,如果我们使用这个函数调节步长,超调的风险就会变得较小。
3.>怎么得到误差函数相对于权重的斜率。
要使用梯度下降的方法,我们需要计算出误差函数相对于权重的斜率,也就是误差对链接权重的改变有多敏感。我们借助如下图:
简化后的斜率为:
隐藏层到输出层的误差斜率:
输入层到隐藏层的误差斜率
4.>新的权重为:
更新后的权重Wj,k是由刚刚得到误差斜率取反来调整旧的权重而得到的。正如我们先前所看到的,如果斜率为正,我们希望减少权重,如果斜率为负,我们希望增加权重,因此,我们要对斜率取反。符号∂是一个因子,这个因子可以调节这些变化的强度,确保不会超调。我们通常称这个因子为学习率,这个表达式不仅适用于隐藏层和输出层之间的权重,而且适合于输入层和隐藏层之间的权重。差值就是误差梯度。
简化后权重的变化量为:
7.权重更新范例
如下图所示,我们增加了隐藏层第一个节点Oj=1和隐藏层第二个节点Oj=2的示例输出值
我们要更新隐藏层和输出层之间的权重W1.1。当前,这个值为2.0,让我们再次写出误差斜率。
下面我们一项一项地运行运算:
- 第一项( tk -ok )得到误差e1 = 0.8
- S函数内的求和∑jwj,koj为(2.0*0.4)+(3.0*0.5) = 2.3
- sigmoid 1/(1+e-2.3)为0.909,中间的表达式为0.909 *(1-0.909)=0.083
- 由于我们感兴趣的是权重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
虽然这是一个相当小的变化量,但权重经过成百上千次的迭代,最终会确定下来,达到一个布局,这样训练有素的神经网络就会生成与训练样本中相同的输出。
8.准备数据
最后我们要思考如何最好地准备训练数据,包括初始化权重,甚至设计输出值,给训练过程一个成功的机会。并不是所有使用神经网路的尝试都能够成功,这有许多原因,一些问题可以通过改进训练数据、初始权重、设计良好的输出方案来解决。
1.>输入值:仔细观察下图的S激活函数,你可以发现,如果输入变大,激活函数就会变得非常平坦。
由于我们使用梯度学习新的权重,因此一个平坦的激活函数会出问题。
回头仔细观察关于权重变化的表达式。权重的改变取决于激活函数的梯度。小梯度意味着限制神经网络学习的能力。这就是所谓的饱和神经网络,这意味着,我们应该尽量保持小的输入。
有趣的是,这个表达式也取决于输入信号,因此,我们也不应该让输入信号太小。当计算机处理非常小或非常大的数字时,可能会丧失精度,因此,使用非常小的值也会出现问题
一个好的建议是重新调整输入值,将其范围控制在0.0到1.0。输入0会将oj设置为0,这样权重更新表达式就会等于0,从而造成学习能力的丧失,因此在某些情况下,我们会将此输入加上一个小小的偏移,如0.01,避免输入0带来的麻烦。
2.>输出:神经网络的输出是最后一层节点弹出的信号。如果我们使用的激活函数不能生成大于1的值,那么尝试将训练目标值设置为比较大的值就是有点愚蠢了。请记住,逻辑函数甚至不能取到1.0,只能接近1.0,数学家称之为渐进于1.0。因此我们的取值范围为0.01~0.99
3.>随机初始权重。
与输入和输出一样,同样的道理也适用于初始权重的设置。由于大的初始权重会造成大的信号传递给激活函数,导致网络饱和,从而降低网络学习到更好的权重的能力,因此应该避免大的初始权重值。我们可以从-1.0~+1.0之间随机均匀地选择初始权重。
我们可以在一个节点传入链接数量平方根倒数的大致范围内随机采样,初始化权重。比如:每个节点具有3条传入链接,那么初始权重的范围应该在从到即±0.577之间。如果每个节点具有100条传入链接,那么权重的范围应该在到 ,即±0.1之间。