Python神经网络编程.pdf
链接: https://pan.baidu.com/s/1RkNfeNgT3Qtt_sEqRhw5Bg 提取码: 98ma
神经网络的思考模式,误差值的形象比喻
直线y = ax+ b即是最最简单的分类器!
误差值
如何调整误差△A?
每一次调整误差,训练出来的结果是这样的↓
不足之处在于,最终改进的直线只与最后一次训练样本非常匹配,而不会顾及所有先前的训练样本,而是抛弃了所有先前训练样本的学习结果,只是对最近的一个实例进行了学习。
如何解决这个问题呢?
适度改进(moderate)。也就是说,我们不要使改进过于激烈。
我们采用ΔA 几分之一的一个变化值,而不是采用整个ΔA,我们小心翼翼地调整参数C,使其只是实际误差值的几分之几。
调节系数通常被称为学习率(learning rate),L
再一次重复上述过程,我们有一个初始值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。
加上学习率之后的分类效果↓
布尔逻辑运算
阈值≈激活函数
观察表明,神经元不会立即反应,而是会抑制输入,直到输入增强, 强大到可以触发输出。你可以这样认为,在产生输出之前,输入必须到达 一个阈值。就像水在杯中——直到水装满了杯子,才可能溢出。直观上, 这是有道理的——神经元不希望传递微小的噪声信号,而只是传递有意识 的明显信号。下图说明了这种思想,只有输入超过了阈值(threshold), 足够接通电路,才会产生输出信号。
S函数,有时也称为逻辑函数。
S函数的功能,可以把任何数转化成为0-1之间的数。
字母e是数学常数 2.71828 ……是一个无限不循环小数,这样的数字有一个奇特的名字——超越数(transcendental number)。
S阈值函数
如果组合信号不够强大,那么S阈值函数的效果是抑制输出信号。如 果总和x 足够大,S函数的效果就是激发神经元。有趣的是,如果只有其中 一个输入足够大,其他输入都很小,那么这也足够激发神经元。更重要的 是,如果其中一些输入,单个而言一般大,但不是非常大,这样由于信号 的组合足够大,超过阈值,那么神经元也能激发。这给读者带来了一种直 观的感觉,即这些神经元也可以进行一些相对复杂、在某种意义上有点模 糊的计算。
神经网络与神经元的类比
最明显的一点就是调整节点之间的连接强度。
在一个节点内,我们可以调整输入的总和或S阈值函数的形状,但是比起简单地调整节点之间的 连接强度,调整S阀值函数的形状要相对复杂。
权重w 1,2 减小或放大节点1传递给下一层节点2的信号
理解为,随着神经网络学习过程的进行,神经网络通过调整优化网络内部的链接权重改进输出,一些权重可能会变为零或接近于零。零或几乎为零的权重意味着这些链接对网络的贡献为零,因为没有传递信号。零权重意味着信号乘以零,结果得到零, 因此这个链接实际上是被断开了。
在神经网络中追踪信号
开始计算
第一层节点是输入层,这层所做的所有事情就是表示输入,仅此而已。无需进行计算。
第二层,我们需要做一些计算,y的输出需要用到S函数y = 1 /(1 + e -x )。
第二层的节点1:
x = (第一个节点的输出链接权重)+(第二个节点的输出链接权重)
x =(1.0 * 0.9)+(0.5 * 0.3) x = 0.9 + 0.15 x = 1.05
最终,我们可以使用激活函数y = 1 /(1 + e -x )计算该节点的 输出。你可以使用计算器来进行这个计算。答案为y = 1 /(1 + 0.3499)= 1 / 1.3499。因此,y = 0.7408。
第二层第二个节点:
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。
最终结果见上上图。
两个节点还可以手工计算,那要是成千上万个节点呢?就需要用到矩阵运算啦~
矩阵乘法点乘内积
运用到神经网络上
第一个矩阵包含两层节点之间的权重。第二个矩阵包含第一层输入层 的信号。通过两个矩阵相乘,我们得到的答案是输入到第二层节点组合调 节后的信号。仔细观察,你就会明白这点。由权重w 1,1 调节的input_1加上 由权重w 2,1 调节的input_2,就是第二层第一个节点的输入值。这些值就是 在应用S函数之前的x的值。
我们可以使用下式,非常简洁地表示:
X = W •I
此处,W 是权重矩阵,I 是输入矩阵,X 是组合调节后的信号,即输入到第二层的结果矩阵。矩阵通常使用斜体显示,表示它们是矩阵,而不是单个数字。
现在,我们不需要写出长长的一串数字或大 量的文字。我们可以简单地写为W •I ,不管I有2个元素还是有200个元素。
只要理解矩阵乘法,就可以无需花费太多精力,就可以实现神经网络了。
最后再运用一下激活函数,对矩阵X 的每个单独元素应用S函数y = 1 / (1 + e -x )。
激活函数只是简单地应用阈值,使反应变得更像是在生物神经元中观察到的行为。因此,来自第二层的最终输出是:O = sigmoid ( X )。斜体的O 代表矩阵,这个矩阵包含了来自神经网络的最后一层中的所有输出。
使用矩阵乘法的三层神经网络示例
一、第一层从输入到第一层隐藏层输出
1、 得到输出矩阵
可视化这些输入到第二层隐藏层的组合调节输入。
2、 应用sigmoid函数
Ohidden= sigmoid( Xhidden)
二、 用同样的方式计算从第二层到第三层
先前,我们通过调整节点线性函数的斜率参数,来调整简单的线性分 类器。我们使用误差值,也就是节点生成了答案与所知正确答案之间的差值,引导我们进行调整。实践证明,误差与所必须进行的斜率调整量之间 的关系非常简单,调整过程非常容易。
当只有一个节点前馈信号到输出节点,事情要简单得多。如果有两个节点,我们如何使用输出误差值呢?
可以平分误差,也可以按照链接权重分配误差。
我们在两件事情上使用了权重。
第一件事情,在神经 网络中,我们使用权重,将信号从输入向前传播到输出层。此前,我们就 是在大量地做这个工作。
第二件事情,我们使用权重,将误差从输出向后 传播到网络中。我们称这种方法为反向传播,后面介绍。
多个输出节点反向传播误差 / 如何分割误差?
误差e1要分割更大的值给较大的权重,分割较小的值给较小的权重。
如果w1,1是w2,1的2倍,比如说w1,1=6w2,1= 3,那么用于更新w1,1的e1的部分就是6 /(6 + 3)= 6/9 = 2/3。同时,这留下了1/3的e1 给较小的权重w2,1,我们可以通过表达式3 /(6 + 3)= 3/9确认这确实是1/3。
如果权重相等,正如你所期望的,各分一半。让我们确定一下,假设w1,1= 4和w2,1=4,那么针对这种情况,e1所分割的比例都等于4 /(4 +4)= 4/8 = 1/2。
反向传播误差到更多层中 / 误差如何向后传播?
让我们演示一下反向传播的误差。你可以观察到,第二个输出层节点 的误差0.5,在具有权重1.0和4.0的两个链接之间,根据比例被分割成了0.1 和0.4。你也可以观察到,在隐藏层的第二个节点处的重组误差等于连接的 分割误差之和,也就是0.48与0.4的和,等于0.88。
如下图所示,我们进一步向后工作,在前一层中应用相同的思路。
关键点
使用矩阵乘法进行反向传播误差,这就是尝试所谓的将过程(vectorise the process)矢量化。
权重、前向信号和输出误差矩阵
但是上面这个矩阵太麻烦了,所以我们要简化一下,采取下面这种矩阵↓
这个权重矩阵需要沿对角线进行翻转。因此,我们得到所希望的矩阵,使用矩阵的方法来向后传播误差:
虽然这样做看起来不错,但是将归一化因子切除,我们做得正确吗? 实践证明,这种相对简单的误差信号反馈方式,与我们先前相对复杂的方式一样有效。
如果我们要进一步思考这个问题,那么我们可以观察到,即使反馈的 误差过大或过小,在下一轮的学习迭代中,网络也可以自行纠正。重要的 是,由于链接权重的强度给出了共享误差的最好指示,因此反馈的误差应 该遵循链接权重的强度。
我们实际上如何更新权重
我们已经理解了让误差反向传播到网络的每一层。为什么这样做呢?原因就是,我们使用误差来指导如何调整链接权重,从而改进神经网络输出的总体答案。
如果使用暴力方法破解,找到最优权值?
现在,假设每个权重在-1和+1之间有1000种可能的值,如0.501、-0.203和0.999。那么对于3层、每层3个节点的神经网络,我们可以得到18个权重,因此有18000 种可能性需要测试。如果有一个相对典型的神经网络,每层有500个节点, 那么我们需要测试5亿种权重的可能性。如果每组组合需要花费1秒钟计 算,那么对于一个训练样本,我们需要花费16年更新权重!对于1 000种训 练样本,我们要花费16 000年!
你会发现这种暴力方法不切实际。事实上,当我们增加网络层、节点和权重的可能值时,这种方法马上就变得不可收拾了。
梯度下降(gradient descent)
想象一下,一个非常复杂、有波 峰波谷的地形以及连绵的群山峻岭。在黑暗中,伸手不见五指。你知道你 是在一个山坡上,你需要到坡底。对于整个地形,你没有精确的地图,只 有一把手电筒。你能做什么呢?你可能会使用手电筒,做近距离的观察。 你不能使用手电筒看得更远,无论如何,你肯定看不到整个地形。你可以 看到某一块土地看起来是下坡,于是你就小步地往这个方向走。通过这种 方式,你不需要完整的地图,也不需要事先制定路线,你一步一个脚印, 缓慢地前进,慢慢地下山。在数学上,这种方法称为梯度下降(gradient descent)。
梯度是指地面的坡度。你走的方向是最陡的坡度向下的方向。
这种酷炫的梯度下降法与神经网络之间有什么联系呢?好吧,如果我们将复杂困难的函数当作网络误差,那么下山找到最小值就意味着最小化误差。这样我们就可以改进网络输出。这就是我们希望做到的!
梯度下降的思想的理解
我们要注意:
当使用梯度下降的方法时, 我们一般不使用代数计算出最小值,我们假装函数y =(x -1)2 + 1是一个非常复杂困难的函数。即使不使用数学精确计算出斜率,我们也可以估计出斜率,在我们往一般的正确方向移动时,你可以发现这种方法也非常适用。
当函数有很多参数时,这种方法才真正地显现出它的亮点。y 也许不单单取决于x ,y 也可能取决于a、b、c、d、e和f。记得输出函数吧,神经网络的误差函数取决于许多的权重参数,这些参数通常有数百个呢!
下面以三维空间来表示。
观察这个三维曲面,你可以再次思考,梯度下降是否会终止于右侧的 另一个山谷。事实上,在更一般的意义上进行思考,由于复杂的函数有众 多的山谷,梯度下降有时会卡在错误的山谷中吗?这个错误的山谷是哪一 个呢?答案是肯定的,这种情况可能会发生,也就是我们所到达的山谷可 能不是最低的山谷。
为了避免终止于错误的山谷或错误的函数最小值,我们从山上的不同 点开始,多次训练神经网络,确保并不总是终止于错误的山谷。不同的起 始点意味着选择不同的起始参数,在神经网络的情况下,这意味着选择不同的起始链接权重。
使用梯度下降方法的三种不同尝试,其中有一次,这 种方法终止于错误的山谷中。
神经网络的输出是一个极其复杂困难的函数,这个函数具有许多参数 影响到其输出的链接权重。我们可以使用梯度下降法,计算出正确的权重 吗?只要我们选择了合适的误差函数,这是完全可以的。
神经网络本身的输出函数不是一个误差函数。但我们知道,由于误差是目标训练值与实际输出值之间的差值,因此我们可以很容易地把输出函数变成误差函数。
要使用梯度下降的方法,现在我们需要计算出误差函数相对于权重的斜率。
我们感兴趣的是,误差函数是如何依赖于神经网络中的链接权重的。询问这个问题的另一种方式是——“误差对链接权重的改变有多敏感?”
当函数具有多个参数时,要画出误差曲面相对较难,但是使用梯度下降寻找最小值的思想是相同的。使用数学的方式,写下想要取得的目标即↓
这个表达式表示了当权重w j,k 改变时,误差E是如何改变的。这是误差函数的斜率,也就是我们希望使用梯度下降的方法到达最小值的方向。
title
误差函数,这是对目标值和实际值之差的平方进行求和,这是针对所有n个输出节点的和。
但是,误差函数根本就不需要对所有输出节点求和。原因是节点的输出只取决于所连 接的链接,就是取决于链接权重。
因此,我们现在有了一个相对简单的表达式了。
t k 的部分是一个常数,因此它不会随着w j,k 的变化而变化。也就是 说,t k 不是w j,k 的函数。
我们将使用链式法则,将这个微积分任务分解成更多易于管理的小块。
现在,我们可以反过来对相对简单的部分各个击破。我们对平方函数 进行简单的微分,就很容易击破了第一个简单的项。这使我们得到了以下的式子:
对于第二项,我们需要仔细考虑一下,但是无需考虑过久。o k 是节点 k的输出,如果你还记得,这是在连接输入信号上进行加权求和,在所得到 结果上应用S函数得到的结果。让我们将这写下来,清楚地表达出来。
oj是前一个隐藏层节点的输出,而不是最终层的输出ok 。
我们如何微分S函数呢?使用附录A介绍的基本思想,对S函数求微 分,这对我们而言是一种非常艰辛的方法,但是,其他人已经完成了这项 工作。我们可以只使用众所周知的答案,就像全世界的数学家每天都在做的事情一样。
在微分后,一些函数变成了非常可怕的表达式。S函数微分后,可以得到一个非常简单、易于使用的结果。在神经网络中,这是S函数成为大受欢迎的激活函数的一个重要原因。
因此,让我们应用这个酷炫的结果,得到以下的表达式。
这个额外的最后一项是什么呢?由于在sigmoid()函数内部的表达式也 需要对w j,k 进行微分,因此我们对S函数微分项再次应用链式法则。这也非 常容易,答案很简单,为oj。
在写下最后的答案之前,让我们把在前面的2 去掉。我们只对误差函 数的斜率方向感兴趣,这样我们就可以使用梯度下降的方法,因此可以去 掉2。只要我们牢牢记住需要什么,在表达式前面的常数,无论是2、3还是 100,都无关紧要。因此,去掉这个常数,让事情变得简单。
这就是我们一直在努力要得到的最后答案,这个表达式描述了误差函数的斜率,这样我们就可以调整权重w j,k 了。
这就是我们一直在寻找的神奇表达式,也是训练神经网络的关键。
这个表达式值得再次回味,颜色标记有助于显示出表达式的各个部 分。第一部分,非常简单,就是(目标值-实际值),我们对此已经很清楚 了。在sigmoid中的求和表达式也很简单,就是进入最后一层节点的信号, 我们可以称之为i k ,这样它看起来比较简单。这是应用激活函数之前,进 入节点的信号。最后一部分是前一隐藏层节点j的输出。读者要有一种意 识,明白在这个斜率的表达式中,实际涉及哪些信息并最终优化了权重, 因此读者值得仔细观察这些表达式、这些项。
我们还需要做最后一件事情。我们所得到的这个表达式,是为了优化隐藏层和输出层之间的权重。现在,我们需要完成工作,为输入层和隐藏层之间的权重找到类似的误差斜率。
同样,我们可以进行大量的代数运算,但是不必这样做。我们可以很 简单地使用刚才所做的解释,为感兴趣的新权重集重新构建一个表达式。
因此,我们一直在努力达成的最终答案的第二部分如下所示,这是我 们所得到误差函数斜率,用于输入层和隐藏层之间权重调整。
现在,我们得到了关于斜率的所有关键的神奇表达式,可以使用这些表达式,在应用每层训练样本后,更新权重。
权重改变的方向与梯度方向相反。
我们使用学习因子,调节变化,我们可以根据特定的问题,调 整这个学习因子。当我们建立线性分类器,作为避免被错误的训练样本拉 得太远的一种方式,同时也为了保证权重不会由于持续的超调而在最小值 附近来回摆动,我们都发现了这个学习因子。让我们用数学的形式来表达 这个因子。
更新后的权重w j,k 是由刚刚得到误差斜率取反来调整旧的权重而得到 的。正如我们先前所看到的,如果斜率为正,我们希望减小权重,如果斜 率为负,我们希望增加权重,因此,我们要对斜率取反。符号α是一个因 子,这个因子可以调节这些变化的强度,确保不会超调。我们通常称这个 因子为学习率。
这个表达式不仅适用于隐藏层和输出层之间的权重,而且适用于输入 层和隐藏层之间的权重。差值就是误差梯度,我们可以使用上述两个表达 式来计算这个误差梯度。
试图按照矩阵乘法的形式进行运算
我们将按照以前那样写出权重变化矩阵的每个元素。
由于学习率只是一个常数,并没有真正改变如何组织矩阵乘法,因此 我们省略了学习率α。
权重改变矩阵中包含的值,这些值可以调整链接权重w j,k ,这个权重 链接了当前层节点j与下一层节点k。你可以发现,表达式中的第一项使用 下一层(节点k)的值,最后一项使用前一层(节点j)的值。
仔细观察上图,你就会发现,表达式的最后一部分,也就是单行的水 平矩阵,是前一层o j 的输出的转置。颜色标记显示点乘是正确的方式。如 果你不能确定,请尝试使用另一种方式的点乘,你会发现这是行不通的。
因此,权重更新矩阵有如下的矩阵形式,这种形式可以让我们通过计算机编程语言高效地实现矩阵运算。
权重更新成功范例
我们要更新隐藏层和输出层之间的权重w1,1。当前,这个值为2.0。
让我们再次写出误差斜率。
让我们一项一项地进行运算:
将这三项相乘,同时不要忘记表达式前的负号,最后我们得到-0.0265。
如果学习率为0.1,那么得出的改变量为-(0.1 * -0.02650)= + 0.002650。因此,新的w 1,1 就是原来的2.0加上0.00265等于2.00265。
虽然这是一个相当小的变化量,但权重经过成百上千次的迭代,最终 会确定下来,达到一种布局,这样训练有素的神经网络就会生成与训练样 本中相同的输出。
并不是所有使用神经网络的尝试都能够成功,我们要思考如何最好地准备训练数据,初始随机权重,甚 至设计输出值,给训练过程一个成功的机会。
权重的改变取决于激活函数的 梯度。小梯度意味着限制神经网络学习的能力。这就是所谓的饱和神经网 络。这意味着,我们应该尽量保持小的输入。
有趣的是,这个表达式也取决于输入信号(o j ),因此,我们也不应 该让输入信号太小。当计算机处理非常小或非常大的数字时,可能会丧失 精度,因此,使用非常小的值也会出现问题。
一个好的建议是重新调整输入值,将其范围控制在0.0到1.0。输入0会 将o j 设置为0,这样权重更新表达式就会等于0,从而造成学习能力的丧 失,因此在某些情况下,我们会将此输入加上一个小小的偏移,如0.01, 避免输入0带来麻烦。
由于激活函数的值域为0-1,所以,常见的使用范围为0.0~1.0,但是由于0.0和1.0这两个数也不可能是目标值,并且有驱动产生过大的权重的风险,因此一些人也使用0.01 ~0.99的范围。
随机初始权重
与输入和输出一样,同样的道理也适用于初始权重的设置。由于大的 初始权重会造成大的信号传递给激活函数,导致网络饱和,从而降低网络 学习到更好的权重的能力,因此应该避免大的初始权重值。
我们可以从-1.0~+1.0之间随机均匀地选择初始权重。比起使用非常大 的范围,比如说-1000~+1000,这是一个好得多的思路。
在此处,我们不纠结于计算细节,但是,其核心思想是,如果很多信 号进入一个节点(这也是在神经网络中出现的情况),并且这些信号的表 现已经不错了,不会太大,也不会分布得奇奇怪怪,那么在对这些信号进 行组合并应用激活函数时,权重应该支持保持这些表现良好的信号。换句 话说,我们不希望权重破坏了精心调整输入信号的努力。数学家所得到的 经验规则是,我们可以在一个节点传入链接数量平方根倒数的大致范围内 随机采样,初始化权重。因此,如果每个节点具有3条传入链接,那么初始 权重的范围应该在从 到 ,即±0.577之间。如果每个节点具有 100条传入链接,那么权重的范围应该在 至 ,即±0.1之 间。
直觉上说,这是有意义的。一些过大的初始权重将会在偏置方向上偏 置激活函数,非常大的权重将会使激活函数饱和。一个节点的传入链接越 多,就有越多的信号被叠加在一起。因此,如果链接更多,那么减小权重 的范围,这个经验法则是有道理的。
初试权重的正态分布方法
不管你做什么,禁止将初始权重设定为相同的恒定值,特别是禁止将 初始权重设定为0。要不然,事情会变得很糟糕。
如果这样做,那么在网络中的每个节点都将接收到相同的信号值,每 个输出节点的输出值也是相同的,在这种情况下,如果我们在网络中通过 反向传播误差更新权重,误差必定得到平分。你还记得误差按权重比例进 行分割吧!那么,这将导致同等量的权重更新,再次出现另一组值相等的 权重。由于正确训练的网络应该具有不等的权重(对于几乎所有的问题, 这是极有可能的情况),那么由于这种对称性,你将永远得不到这种网 络,因此这是一种很糟糕的情况。
由于0权重,输入信号归零,取决于输入信号的权重更新函数也因此归 零,这种情况更糟糕。网络完全丧失了更新权重的能力。
关键点