bp算法

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_40432828/article/details/82192709
先向各位小伙伴道歉,文中可能会出现许多错别字,表达不清楚,病句,标点符号使用不当,图片难看且潦草的情况,必须诚恳地向大家表示:凑合看吧,还能咬我咋的…

在之前的文章中,有提到过,所谓的 AI 技术,本质上是一种数据处理处理技术,它的强大来自于两方面:1.互联网的发展带来的海量数据信息  2.计算机深度学习算法的快速发展。 所以说 AI 其实并没有什么神秘,只是在算法上更为复杂。要想理解这一点,我们要从一个问题说起:找数据的规律…

如果你是一名上过大学的人,有几个数学上的方法你应该不太陌生:线性拟合,多项式拟合,最小二乘法…如果这个你都不知道的话,我建议你现在 假装明白 ,然后往下看,应该不难。

先看一下下面这组数据:

x y
0 1
1 2
2 9
3 28
4 65
5 126
6 217
7 344
8 513
9 730
        这里没什么好说的,这个表是一个 x 与 y 的对应关系,我们现在的目标比较明确,找到x与y的对应关系,也就是求y=f(x)的关系式。

第 1 部分  传统数学方法回顾
        这部分内容就很简单了,关键记住一个名词 ——  一通(tòng)操作 …

方法一:线性拟合

我们知道,在大多数情况下,当我们拿来一组数据,进行拟合时,首先想到的肯定是线性拟合,因为其方法简单暴力直接有效,往往很快就能得到一个差不多的结论。虽然不是很精确,但是有句名言说得好,要啥自行车?直接来看结果。

关于线性拟合在数学上的方法,这里就不讲了,随便找本教材应该就有,我相信看到上面的图,你应该已经理解了。总之就是经过 一通操作 ,得到线性关系。我这里并没有用数学方法亲自去进行计算和拟合关系式,而是用了一种很高端的工具,叫 Excel … 以示说明,领会精神即可。

红色虚线为拟合线,蓝色实线为实际点的连线,可以看到,利用线性拟合,得到的结果是 y = 75.4x - 135.8 这样一个数学关系,很显然,它的效果不是他别理想,可以看到,误差还是不小的。

方法二:多项式拟合

这个的思路也比较直接,其实就是假设   , 比如我们要拟合一个2次的多项式,就可以假设

。同样的,3次方的关系就是 。应该是很好理解吧······

跟刚才一样,又是一通操作,拟合的方法我不就不赘述了,直接用我们的高端工具 Excel 来完成这个工作。效果如下:

可以看到,二次拟合的效果比线性拟合的结果要更接近于真实的结果,而三次曲线就是真实的关系(当然大多数实际情况下并不是严格对应)。

通常利用更高阶的多项式,得到的结果就更加接近于实际的数据。

方法三:其他

最小二乘法,指数拟合,对数拟合,根据数据的不同,用不同的方法来进行拟合得到接近真实情况的数学关系。区别就是利用不同的 一通操作 … 但是无论是哪一种,解决的数学问题相对来说比较有限,并不能准确拟合出很复杂的数学关系。

如果利用逻辑回归、贝叶斯、决策树、KNN、套袋法等等,也能够解决很多很复杂的数学问题,但这又是另外一个很大的领域,不过建议有机会还是要把这些基础打好,但是这篇博客中,我们不探讨,完全不熟悉也没关系,只要知道这些都是传统的数据处理方法就好。

第 2 部分  现代技术中的难题
        下面我们来思考一个 重!要!问!题!:别人爸爸 跟 你爸爸 的不同之处,在数学上的表达是怎么样的 ?

多么深奥的问题,也许你觉得这是一个显而易见的问题,你爸爸就是你爸爸,他爸爸就是他爸爸。这点我十分相信,虽然你不知道如何去回答这个问题,但是你这辈子应该是没喊错过你父亲… 可是问题来了,你怎么让计算机去熟练的分辨出两个人谁是谁?这就必须要依赖数学了…

所以回到问题中来:别人爸爸 跟 你爸爸 的不同之处,在数学上的表达是怎么样的 ?你可能要打我了。但是先别急,先来分析分析。首先,必须明搞明白一件事,这个世界上的事情可以分为两种,可归纳的问题 与 不可归纳的问题。

首先什么是不可归纳的问题,举个例子,你不能用一套完美的数学公式去表达 所有的质数 , 因为目前的研究表明,还没有什么方法是能够表达质数的,也就是说,质数的出现,本身不具备严格的数学规律,所以无法归纳。

可归纳问题就比较好理解了,一只猫 和 一只狗 出现在你的面前时 ,你能够清晰地将他们进行分辨,这说明在猫和狗之间,确实存在着不同,虽然你很难说清楚它们的不同到底是什么,但是可以知道,这背后是可以通过一套数学表达来完成的,只是很复杂而已。理论上来讲,凡是人类能够掌握的事情,比如再怎么复杂的语言,人类的快速分辨物体的视觉,复杂的逻辑思考,都是可以用数学来表达的可归纳问题。我们人类之所以能够快速地对这些复杂的问题进行快速地反应,得益于我们的大脑内部复杂的神经网络构造。当我们不经意间看到一些物体时,大脑其实是在高速的进行计算,我们天生拥有这种能力,以至于我们根本没有察觉。多么神奇,可以说我们每个人其实都是超级算法工程师…

对比第一部分的那个表格,和如何分辨爸爸的问题,可以得到结论是,这是同一个层次的问题:可归纳数学问题,只是用到的方法不同,复杂度不同而已。都可以用公式来表达:

问题一: x 与 y 的对应关系  y = f(x)

问题二:你爸爸 = f (你爸爸的特征)

这当然是一个复杂的问题,因为首先需要将人的特征转化为数字信息,比如图像(图像本质上就是二位的数组),然后根据不同人的特征,对应的不同人的代号,来拟合一个复杂的,一一对应的函数关系,就是现在技术中的一个难题。解决的方法就是 AI :神经网络。

所以所谓的 AI 技术 ,说到底 是找规律的问题,是拟合复杂函数的问题,是数据处理的问题。

下面将通过实例,来给大家搭建一个最简单的神经网络:Bp神经网络,来理解 AI 。

第 3 部分  最简单的神经网络Bp神经网络
1.Bp 神经网络的简单理解
        这里要从名字开始说起了,首先从名称中可以看出,Bp神经网络可以分为两个部分,bp和神经网络。

bp是 Back Propagation 的简写 ,意思是反向传播。而神经网络,听着高大上,其实就是一类相对复杂的计算网络。举个简单的例子来说明一下,什么是网络。

看这样一个问题,假如我手里有一笔钱,N个亿吧(既然是假设那就不怕吹牛逼),我把它分别投给5个公司,分别占比 M1,M2,M3,M4,M5(M1到M5均为百分比 %)。而每个公司的回报率是不一样的,分别为 A1, A2, A3, A4, A5,(A1到A5也均为百分比 %)那么我的收益应该是多少?这个问题看起来应该是够简单了,你可能提笔就能搞定  收益 = NM1A1 + NM2A2+NM3A3+NM4A4+NM5A5 。这个完全没错,但是体现不出水平,我们可以把它转化成一个网络模型来进行说明。如下图:

图有点丑,领会精神,领会精神。上面的问题是不是莫名其妙的就被整理成了一个三层的网络,N1到N5表示每个公司获得的钱,R表示最终的收益。R = NM1A1 + NM2A2+NM3A3+NM4A4+NM5A5 。我们可以把 N 作为输入层 ,R作为输出层,N1到N5则整体作为隐藏层,共三层。而M1到M5则可以理解为输入层到隐藏层的权重,A1到A5为隐藏层到输出层的权重。

这里提到了四个重要的概念 输入层(input) , 隐藏层 (hidden),输出层(output)和权重(weight) 。而所有的网络都可以理解为由这三层和各层之间的权重组成的网络,只是隐藏层的层数和节点数会多很多。

输入层:信息的输入端,上图中 输入层 只有 1 个节点(一个圈圈),实际的网络中可能有很多个

隐藏层:信息的处理端,用于模拟一个计算的过程,上图中,隐藏层只有一层,节点数为 5 个。

输出层:信息的输出端,也就是我们要的结果,上图中,R 就是输出层的唯一一个节点,实际上可能有很多个输出节点。

权重:连接每层信息之间的参数,上图中只是通过乘机的方式来体现。

在上面的网络中,我们的计算过程比较直接,用每一层的数值乘以对应的权重。这一过程中,权重是恒定的,设定好的,因此,是将 输入层N 的 信息 ,单向传播到 输出层R 的过程,并没有反向传播信息,因此它不是神经网络,只是一个普通的网络。

而神经网络是一个信息可以反向传播的网络,而最早的Bp网络就是这一思想的体现。先不急着看Bp网络的结构,看到这儿你可能会好奇,反向传播是什么意思。再来举一个通俗的例子,猜数字:

当我提前设定一个数值 50,让你来猜,我会告诉你猜的数字是高了还是低了。你每次猜的数字相当于一次信息正向传播给我的结果,而我给你的提示就是反向传播的信息,往复多次,你就可以猜到我设定的数值 50 。 这就是典型的反向传播,即根据输出的结果来反向的调整模型,只是在实际应用中的Bp网络更为复杂和数学,但是思想很类似。

2.Bp 神经网络的结构与数学原理(可以不细看)
        此节的内容 极!其!重!要!但是要涉及到一些数学,所以我尽量用人话去跟大家细细解释,并且结合实例来给大家进行一下分析。

如果你不想看太多的推导和数学,那么只需要大概理解 Bp 网络的运行思想就好:我们知道,一个函数是由自变量x和决定它的参数θ组成。比如 y=ax + b 中,a,b为函数的固定参数 θ ,x为自变量。那么对于任意一个函数我们可以把它写成 y = f(θ,x)的形式,这里的 θ 代表所有参数的集合[,…],x代表所有自变量的集合[,…]。而 Bp 网络的运行流程就是根据已有的 x 与 y 来不停的迭代反推出参数 θ 的过程,这一过程结合了最小二乘法与梯度下降等特殊的计算技巧。这一节看到这儿就基本上可以了,但是如果还想继续深入理解,可以跟着思路,往下接着看。

事实上,这些内容已经被各路神仙们写烂了,因为 Bp网络对于 AI 技术来说,实在太基础,太重要,但是由于在实际学习中,我也遇到过一些困难,现在根据我的学习过程和理解过程,还是要再拿出来写一遍。大神们勿喷···

还是老样子,先来看一个问题,找到下列数据中,y 与 x1,x2,x3的关系,即 y = f(x1,x2,x3)的数学表达式。

表 3.1
x1 x2 x3 y
1 1 2 2
1 2 3 6
2 1 6 12
5 2 5 50
8 3 4 96
7 7 4 196
7 7 7 343
13 8 3 312
6 10 11 660
13 0 17 0
14 7 12 1176
        这里一共是 11 组数据(数据量很少),很明显 y 是关于 x1,x2,x3 的三元函数,通常情况下,想要通过一套固定的套路来拟合出一个三元函数的关系式,是一件很复杂的事。而实际问题中的参数往往不止三个,可能成千上百,也就是说 决定 y 的参数会有很多,这样的问题更是复杂的很,用常规的方法去拟合,几乎不可能,那么换一种思路,用 Bp神经网络的方法来试一下。

根据上表给出的条件和问题,我们先来分析一下。首先,我们的输入信息是 3 个参数,x1,x2,x3 。输出结果是 1 个数 y 。那么可以画一个这样的关系网路图(直接手画了,凑合看吧···):

在这个网络中,输入层(input )有三个节点(因为有三个参数),隐藏层(hidden )先不表示,输出层(output )有1个节点(因为我们要的结果只有一个 y )。那么关键的问题来了,如何进行这一通操作,它的结构究竟是怎样的?

2.1 正向传播
        正向传播就是让信息从输入层进入网络,依次经过每一层的计算,得到最终输出层结果的过程。

我直接把设计好的结构图给大家画出来,然后再一点一点地解释。结构如下:

看到这儿你可能会有点懵,不过不要紧,一步一步来分析。先来看网络的结构,输入层(input )没有变,还是三个节点。输出层(input )也没有变。重点看隐藏层(hidden ),就是图中红色虚线框起的部分,这里我设计了一个隐藏层为两层的网络,hidden_1和hidden_2 ,每层的节点为 2 个,至于为什么是两层,节点数为什么是 2 两个 ,这里你只需要知道,实验证明,解决这个问题,这样的网络就够用了。具体的一会儿讲。

关键看一下连线代表的意义,和计算过程。可以从图上看到,每层的节点都与下一层的每个节点有一一对应的连线,每条连线代表一个权重,这里你可以把它理解为信息传输的一条通路,但是每条路的宽度是不一样的,每条通路的宽度由该通道的参数,也就是该通路的权重来决定。为了说明这个问题,拿一个节点的计算过程来进行说明,看下图:

这上上图中的一部分,输入层(input )与 第一层隐藏层(hidden )的第一个节点 的连接关系。根据上边的图你可能自然的会想到:   。如果你这么想,那就说明你已经开窍了,不过实际过程要复杂一些。我们可以把 这个节点看做是一个有输入,有输出的节点,我们规定输入为 , 输出为 ,则真实的过程如下:

计算的方法我直接写到图里了,字儿丑,但是应该能看清楚···解释一下,就是x1,x2,x3与各自权重乘积的和,但是为什么非要搞一个 sigmoid() ,这是什么鬼? 其实最早人们在设计网络的时候,是没有这个过程的,统统使用线性的连接来搭建网络,但是线性函数没有上界,经常会造成一个节点处的数字变得很大很大,难以计算,也就无法得到一个可以用的网络。因此人们后来对节点上的数据进行了一个操作,利用sigmoid()函数来处理,使数据被限定在一定范围内。此外sigmoid函数的图像是一个非线性的曲线,因此,能够更好的逼近非线性的关系,因为绝大多数情况下,实际的关系是非线性的。sigmoid在这里被称为 激励函数 ,这是神经网络中的一个非常重要的基本概念。下面来具体说一下什么是 sigmoid() 函数。

不作太具体的分析,直接看公式和图像:

图像来自百度百科,可以看到sigmoid函数能够将函数限制在 0到1 的范围之内。

这里还要进行一下说明,sigmoid 是最早使用的激励函数,实际上还有更多种类的激励函数 ,比如 Relu ,tanh 等等,性质和表达式各有不同,以后再说,这里先用 sigmoid 来说明。

如果说看到这儿,你对 激励函数 这个概念还是不太懂的话 ,没关系,可以假装自己明白了,你就知道这个东西很有用,里面必有道道就行了,以后慢慢体会,慢慢理解,就行了。接着往下看。

刚刚解释了一个节点的计算过程,那么其他节点也就可以举一反三,一一计算出来。现在我们来简化一下网络。我们可以把x1,x2,x3作为一个向量 [x1,x2,x3] ,权重矩阵 u 也作为一个 3x2 的矩阵 ,w 作为一个 2x2 的矩阵 ,v作为一个 2x1 的矩阵,三个矩阵如下:

可以看到这三个矩阵与网络中的结构图中是一一对应的。下面我们把隐藏层与输出层也写成矩阵的形式:

可以看到这两层隐藏层(hidden)的输入Hi 与 Ho 均为 1x2 的矩阵,输出层(output )为 1x1 的矩阵。下面就可以把网络简化为下面的结构:

根据我们刚才讲过的每个节点的计算方法,以及我们简化后的网络,则可以将整个计算过程等效的化为以下几个矩阵相城的步骤(矩阵相乘是怎么会回事,请复习线性代数…):

注意:下式中,除sigmoid代表激励函数以外,其余各个符号都代表一个矩阵(或者向量),而非常数,乘积符号“ x ”代表常规的矩阵乘法计算。

※ 由于矩阵 x 维度为 13 ,u 维度为 32 ,所以自然得到维度为1*2的矩阵(或者向量) 。

※ 维度不变

※ (12) x (22) →(1*2) 括号内为各矩阵维度

※ 维度不变

※ (12) x (21) →(1*1)  括号内为各矩阵维度

注意:细心的小伙伴应该发现公式中出现了几个之前没有提到的符号 ,, 。它们也各自代表一个矩阵,它们的概念为阈值,通常用符号b来表示。阈值的意义是,每个节点本身就具有的一个数值,设置阈值能够使网络更快更真实的去逼近一个真实的关系。

以上这个过程,就是该网络的信息进行了一次 正向传播 。

2.2 反向传播
        那么有正向传播,就必须得有反向传播,下面来讲一下 反向传播 的过程。首先明确一点,反向传播的信息是什么,不卖关子,直接给答案,反向传播的信息是误差,也就是 输出层(output )的结果 与 输入信息 x 对应的真实结果 之间的差距(表达能力比较差,画个图说明…)。

拿出上文的数据表中的第一组数据  x1 = 1,x2=1,x3=2,y=2 为例。

假设我们将信息x1,x2,x3 输入给网络,得到的结果为  = 8 ,而我们知道真实的 y 值为 2,因此此时的误差为 |-y|  ,也就是 6 。 真实结果与计算结果的误差被称作 损失 loss , loss = | - y|  记作 损失函数 。这里有提到了一个很重要的概念,损失函数,其实在刚才的例子中,损失函数 loss = | - y|  只是衡量误差大小的一种方式,称作L1损失(先知道就行了),在实际搭建的网络中,更多的用到的损失函数为 均方差损失,和交叉熵损失。原则是分类问题用交叉熵,回归问题用均方差,综合问题用综合损失,特殊问题用特殊损失···以后慢慢说吧,因为损失函数是一个超级庞大的问题。

总之我们先知道,损失函数 loss 是一个关于 网络输出结果  与真实结果 y 的,具有极小值的函数 。那么我们就可以知道,如果一个网络的计算结果  与 真是结果 y 之间的损失总是很小,那么就可以说明这个网络非常的逼近真实的关系。所以我们现在的目的,就是不断地通过调整权重u,w,v(也就是网络的参数)来使网络计算的结果  尽可能的接近真实结果 y ,也就等价于是损失函数尽量变小。那么如何调整u,w,v 的大小,才能使损失函数不断地变小呢?这理又要说到一个新的概念:梯度下降法 。

梯度下降法 是一个很重要很重要的计算方法,要说明这个方法的原理,就又涉及到另外一个问题:逻辑回归。为了简化学习的过程,不展开讲,大家可以自己去搜一下逻辑回归,学习一下。特别提醒一下,逻辑回归是算法工程师必须掌握的内容,因为它对于 AI 来说是一个很重要的基础。下面只用一个图(图片来自百度)进行一个简单地说明。

假设上图中的曲线就是损失函数的图像,它存在一个最小值。梯度是一个利用求导得到的数值,可以理解为参数的变化量。从几何意义上来看,梯度代表一个损失函数增加最快的方向,反之,沿着相反的方向就可以不断地使损失逼近最小值,也就是使网络逼近真实的关系。

那么反向传播的过程就可以理解为,根据 损失loss ,来反向计算出每个参数(如  , 等)的梯度 d() ,d() …等等,再将原来的参数分别加上自己对应的梯度,就完成了一次反向传播。

来看看 损失loss 如何完成一次反向传播,这里再定义一些变量  ,  和  。注意:它们都代表矩阵(向量),而非一个数值。它们分别代表第一层,第二层隐藏层,以及输出层每个神经元节点反向输出的值。 分别代表权值矩阵与阈值矩阵对应的梯度矩阵,用符号  代表损失,来表示sigmoid函数的导数。这里只简单的说一下计算公式,推导过程后边讲。

计算梯度,注意:下式中未标红的都代表一个矩阵(或者向量),标红符号的代表一个常数。

更新权值与阈值

※ 公式中的 乘号 “ x ”表示常规的矩阵乘积运算,运算后会发生维度的变化。 符号 “ · ” 表示按位乘积,运算后维度不变。

以上就是一次完整的反向传播过程,需要说明的是,上式当中用到了一个符号  ,这又是一个重要的概念,学习率,一个小于1的实数,它的大小会影响网络学习的速率以及准确度。可以把它理解为梯度下降时的步长。

反向传播过程实际上还是有点复杂的,下面我来简单说一下为什么梯度是这样求的。

我们知道,整个网络可以简化成一个函数 ,也就是说这个函数的表达式,主要由各个参数  来决定,而现在为了确定网络的参数,则可以把  作为函数的自变量,而x作为参数,对  求偏导     ,这个偏导的结果就是该参数  对应的梯度,这个思想实际上来自于最小二乘法,反正求完就是上边式子中的结果,这里不再进行推导。

2.3 网络的训练
        通过一次正向传播,和一次反向传播,我们就可以将网络的参数更新一次,所谓训练网络,就是让正向传播和反向传播不断的往复进行,不断地更新网络的参数,最终使网络能够逼近真实的关系。

理论上,只要网络的层数足够深,节点数足够多,可以逼近任何一个函数关系。但是这比较考验你的电脑性能,事实上,利用 Bp 网络,能够处理的数据其实还是有限的,比如 Bp 网络在图像数据的识别和分类问题中的表现是很有限的。但是这并不影响 Bp 网络是一种高明的策略,它的出现也为后来的 AI 技术做了重要的铺垫。

3.Bp 神经网络的代码实现
        回到 表 3.1 中的数据,将用 python 来实现一个 Bp 网络 ,对数据的关系建立一个网络模型。

这里有几点需要说明,首先在数据进入网络之前,要先进行归一化处理,即将数据除以一个数,使它们的值都小于 1 ,这样做的目的是避免梯度爆炸。其次为了更好、更快的收敛得到准确的模型,这里采用了对数据进行特征化的处理。最后,这段代码中用到的激励函数是Relu,并非我们之前所讲的 sigmoid ,因为Relu的计算速度更快,更容易收敛。

这里有几个参数和数组需要说明,其中 p_s 中的数组代表 表 3.1 中 11组数据的 [x1,x2,x3] ,t_s代表对应的 y 。p_t 与t_t用来存放测试网络训练效果的 测试数据集 。我们用p_s与t_s来训练 Bp 网络 ,用 p_t 与 t_t 来检验训练的效果。表 3.1 的数据中,y 与 x1,x2,x3 的对应关系实际上是 y = x1 * x2 * x3 。

代码如下:

import time
from numpy import *

######## 数据集 ########

p_s = [[1,1,2],[1,2,3],[2,1,6],[5,2,5],[8,3,4],[7,7,4],[7,7,7],[13,8,3],[6,10,11],[13,0,17],[14,7,12]] # 用来训练的数据集 x
t_s = [[2],[6],[12],[50],[96],[196],[343],[312],[660],[0],[1176]] # 用来训练的数据集 y

p_t = [[6,9,1017],[2,3,4],[5,9,10]] # 用来测试的数据集 x_test
t_t = [[54918],[24],[450]] # 用来测试的数据集 对应的实际结果 y_test

######## 超参数设定 ########

n_epoch = 20000 # 训练次数

HNum = 2; # 各层隐藏层节点数

HCNum = 2; # 隐藏层层数

AFKind = 3; # 激励函数种类
emax = 0.01; # 最大允许均方差根
LearnRate = 0.01; # 学习率

######## 中间变量设定 ########
TNum = 7; # 特征层节点数 (特征数)

SNum = len(p_s); # 样本数

INum = len(p_s[0]); # 输入层节点数(每组数据的维度)
ONum = len(t_s[0]); # 输出层节点数(结果的维度)
StudyTime = 0; # 学习次数
KtoOne = 0.0; # 归一化系数
e = 0.0; # 均方差跟

######################################################### 主要矩阵设定 ######################################################

I = zeros(INum);

Ti = zeros(TNum);
To = zeros(TNum);

Hi = zeros((HCNum,HNum));
Ho = zeros((HCNum,HNum));

Oi = zeros(ONum);
Oo = zeros(ONum);

Teacher = zeros(ONum);

u = 0.2ones((TNum,HNum)) # 初始化 权值矩阵u
w = 0.2
ones(((HCNum-1,HNum,HNum))) # 初始化 权值矩阵w
v = 0.2*ones((HNum,ONum)) # 初始化 权值矩阵v

dw = zeros((HCNum-1,HNum,HNum))

Hb = zeros((HCNum,HNum));
Ob = zeros(ONum);

He = zeros((HCNum,HNum));
Oe = zeros(ONum);

p_s = array(p_s)
t_s = array(t_s)
p_t = array(p_t)

################################# 时间参数 #########################################

time_start = 0.0
time_gyuyihua = 0.0
time_nnff = 0.0
time_nnbp = 0.0
time_begin = 0.0

time_start2 = 0.0

time_nnff1 = 0.0
time_nnff2 = 0.0
time_nnbp_v = 0.0
time_nnbp_w = 0.0
time_nnbp_u = 0.0
time_nnbp_b = 0.0

######################################################### 方法 #######################################################

def Calcu_KtoOne(p,t): # 确定归一化系数
p_max = p.max();
t_max = t.max();
return max(p_max,t_max);

def trait§: # 特征化
t = zeros((p.shape[0],TNum));
for i in range(0,p.shape[0],1):
t[i,0] = p[i,0]*p[i,1]*p[i,2]
t[i,1] = p[i,0]*p[i,1]
t[i,2] = p[i,0]*p[i,2]
t[i,3] = p[i,1]*p[i,2]
t[i,4] = p[i,0]
t[i,5] = p[i,1]
t[i,6] = p[i,2]

return t

def AF(p,kind): # 激励函数
t = []
if kind == 1: # sigmoid
pass
elif kind == 2: # tanh
pass
elif kind == 3: # ReLU

	return where(p<0,0,p)
else:
	pass

def dAF(p,kind): # 激励函数导数
t = []
if kind == 1: # sigmoid
pass
elif kind == 2: # tanh
pass
elif kind == 3: # ReLU

	return where(p<0,0,1) 
else:
	pass

def nnff(p,t):
pass

def nnbp(p,t):
pass

def train(p,t): # 训练

global e
global v
global w
global dw
global u	
global I 
global Ti 
global To 
global Hi 
global Ho 
global Oi 
global Oo 
global Teacher 
global Hb 
global Ob 
global He 
global Oe
global StudyTime
global KtoOne

global time_start
global time_gyuyihua
global time_nnff
global time_nnbp	
global time_start2
global time_nnff1
global time_nnff2
global time_nnbp_v
global time_nnbp_w
global time_nnbp_u
global time_nnbp_b


time_start = time.clock()


e = 0.0
p = trait(p)
	
KtoOne = Calcu_KtoOne(p,t)

time_gyuyihua += (time.clock()-time_start)

time_start = time.clock()
	
for isamp in range(0,SNum,1):
	To = p[isamp]/KtoOne
	Teacher = t[isamp]/KtoOne
	
	
	################ 前向 nnff #############################
		
	time_start2 = time.clock()
	######## 计算各层隐藏层输入输出 Hi Ho ########
	
	for k in range(0,HCNum,1):
		if k == 0:
			Hi[k] = dot(To,u)
			Ho[k] = AF(add(Hi[k],Hb[k]),AFKind)
		else:
			Hi[k] = dot(Ho[k-1],w[k-1])
			Ho[k] = AF(add(Hi[k],Hb[k]),AFKind)
	
	
	time_nnff1 += (time.clock()-time_start2)	
	time_start2 = time.clock()
	
	########   计算输出层输入输出 Oi Oo    ########
	Oi = dot(Ho[HCNum-1],v)
	Oo = AF(add(Oi,Ob),AFKind)
	
	
	time_nnff2 += (time.clock()-time_start2)	
	time_start2 = time.clock()	
	time_nnff += (time.clock()-time_start)	
	time_start = time.clock()
			
	################ 反向 nnbp #############################
	
	######## 反向更新 v ############
	
	Oe = subtract(Teacher,Oo)
	Oe = multiply(Oe,dAF(add(Oi,Ob),AFKind))
					
	e += sum(multiply(Oe,Oe))
	
	
	
	#### v 梯度 ####		
	
	dv = dot(array([Oe]),array([Ho[HCNum-1]])).transpose()			  # v 的梯度

	v = add(v,dv*LearnRate)    # 更新 v
	
	time_nnbp_v += (time.clock()-time_start2)

	time_start2 = time.clock()
	
	######## 反向更新 w #############
	He = zeros((HCNum,HNum))

	for c in range(HCNum-2,-1,-1):
		if c == HCNum-2:
			He[c+1] = dot(v,Oe)
			He[c+1] = multiply(He[c+1],dAF(add(Hi[c+1],Hb[c+1]),AFKind))
			
			
			#dw[c] = dot(array([He[c+1]]),array([Ho[c]]).transpose())
			dw[c] = dot(array([Ho[c]]).transpose(),array([He[c+1]]))
			#dw[c] = dw[c].transpose()  #@@@@@@ 若结果不理想,可尝试用此条语句
			
			w[c] = add(w[c],LearnRate*dw[c])
			
	
			
		else:
			He[c+1] = dot(w[c+1],He[c+2])
			He[c+1] = multiply(He[c+1],dAF(add(Hi[c+1],Hb[c+1]),AFKind))
			
			dw[c] = dot(array([Ho[c]]).transpose(),array([He[c+1]]))	
			
			w[c] = add(w[c],LearnRate*dw[c])

	time_nnbp_w += (time.clock()-time_start2)

	time_start2 = time.clock()
	
	######## 反向更新 u #############
	
	He[0] = dot(w[0],He[1])
	He[0] = multiply(He[0],dAF(add(Hi[0],Hb[0]),AFKind))
			
			
	du = dot(array([To]).transpose(),array([He[0]]))
			
	u = add(u,du)
	
	time_nnbp_u += (time.clock()-time_start2)

	time_start2 = time.clock()
	
	######### 更新阈值 b ############
	
	Ob = Ob + Oe*LearnRate
			
	Hb = Hb + He*LearnRate
	
	time_nnbp += (time.clock()-time_start)

	time_start = time.clock()
	
	time_nnbp_b += (time.clock()-time_start2)

	time_start2 = time.clock()

e = sqrt(e)

def predict§:

p = trait(p)
p = p/KtoOne
p_result = zeros((p.shape[0],1))

for isamp in range(0,p.shape[0],1):
	for k in range(0,HCNum,1):
		if k == 0:
			Hi[k] = dot(p[isamp],u)
			Ho[k] = AF(add(Hi[k],Hb[k]),AFKind)
		else:
			Hi[k] = dot(Ho[k-1],w[k-1])
			Ho[k] = AF(add(Hi[k],Hb[k]),AFKind)
		
		
	########   计算输出层输入输出 Oi Oo    ########
	Oi = dot(Ho[HCNum-1],v)
	Oo = AF(add(Oi,Ob),AFKind)
	Oo = Oo*KtoOne
	p_result[isamp] = Oo
return p_result

time_begin = time.clock()

for i in range(1,n_epoch,1):
if i%1000 == 0:
print(‘已训练 %d 千次 ,误差均方差 %f’%((i/1000),e))
train(p_s,t_s)
print(‘训练完成,共训练 %d 次,误差均方差 %f’%(i,e))

print('共耗时: ',time.clock()-time_begin)

print()

result = predict(p_t)

print(‘模型预测结果 : ‘)
for i in result:
print(’%.2f’%i)

print(’\n实际结果 : ')
for i in t_t:
print(i)

运行代码后,得到的结果如下图:

可以看到,经过训练后,该 Bp 网络确实从原始数据中学到了特征 , 并且较为准确地对测试数据进行了推测。

此外还要说明,此段代码历史较为悠久,因此很多地方写的很不规范(很多地方保持了C的习惯···实际上是多余的),符号使用的也比较混乱(但是实在懒得整理),仅拿来供大家参考和理解,望小伙伴们见谅。

4.Bp 神经网络的经验总结
        以上内容对 Bp 网络的基本用法和数学关系 进行了讲解。下面有几个重要的知识点,需要特别指出:

a.对于一个神经网络来说,更宽更深的网络,能够学到更加复杂的特征,其能够解决的问题也就越复杂,但是其计算过程也越繁琐,参数越多,越容易出现过拟合的情况(过拟合即网络过度学习了数据的特征,将噪声也同时考虑到了网络中,造成网络只在训练集上表现良好,而无法泛化到其他数据上,说白了就是这个网络已经学傻了…),因此要根据数据的实际情况来设计网络的层数,节点数,激励函数类型 以及 学习率。

b.对于一个神经网络来说,用来训练神经网络的数据集的质量,很大程度上决定了网络的预测效果。数据越丰富,神经网络越能够贴近实际关系,泛化能力越强。

c.Bp神经网络是区别于传统数据处理的一种方法,其特点在于寻找数据之间的相关性,并非严格地数学关系,因此是一种有效但是并非严格地网络。对于实际问题的处理非常有用,但不能作为严谨数学计算的方法。

Bp网络的出现,为后来的 AI 技术提供了理论基础,无论是 AlphaGo ,计算机视觉,还是自然语言处理等复杂问题,都可以理解为这一结构的升级和变种(不过升级幅度有点大,变化样式有点多···)。因此这一对于这一网络的理解,大家应该亲自写写代码,多看一看大神们写的推导过程,深入理解。
————————————————
版权声明:本文为CSDN博主「本三儿」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_40432828/article/details/82192709

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值