今天我们要谈的MLP其实是一种人工神经网络结构,属于非参数估计,可以用于解决分类和回归问题。我们先来了解下神经网络的背景,然后再来介绍下感知器。
神经网络
感知器
多层感知器
神经网络的应用
局部模型
人工神经网络,顾名思义起源于模拟人脑,其目的是理解人脑功能,认知科学家和神经学家共同构建了神经网络模型,并开展了模拟研究。这项技术与工程结合之后,可以帮助我们建立更好的计算机系统。
Marr认为理解一个信息处理系统具有三个层面,总称为分析层面(levels of analysis),即:
计算理论:对应计算目标和任务的抽象定义;
表示和算法:关于输入/输出如何表示以及从输入-->输出的算法说明;
硬件实现:系统的实际物理实现;
这里需要注意的是,对于同一个计算理论,可以有多种表示和算法;而对于同一种表示和算法,可以有多种硬件实现。比如对于自然和人工飞行器,计算理论都是可以“飞行”,算法就是利用“空气动力学”,而实现方式一个是“拍打翅膀”,一个是“发动引擎”。
人脑可以看作是学习或模式识别的一种硬件实现。如果我们可以逆向分析,从这种实现中提取出人脑使用的表示和算法,并且进一步获得计算理论,那么我们就可以考虑使用另一种表示和算法,然后得到更适合我们掌握的计算机硬件的实现。
神经网络可以应用于并行处理。常见的并行架构有单指令多数据(SIMD)机和多指令多数据(MSMD)机, 一种是所有的处理器执行相同的指令处理不同的数据;一种是不同的处理器执行不同的指令处理不同的数据。SIMD实现较为简单,但是应用意义小;MIMD实现复杂,但是现实中多数为此种情况。
神经网络提出了一种介于中间的模式,即引入了中间的少量局部存储器,使用处理器的指令在存储器上输入不同来实现不同的功能。其中每个处理器对应一个神经元,局部参数对应它的突出权重, 而整个结构就是一个神经网络。所以,人工神经网络是一种我们可以实用当前技术构建的、利用并行硬件的方法。
感知器(Perception)是基本的处理元素,它具有输入、输出,每个输入关联一个连接权重(connection weight),然后输出是输入的加权和。
上图就是一个单层的感知器,输入分别是X0、X1、X2,输出Y是输入的加权和:
Y = W0X0 + W1X1 + W2X2
在实际的使用中,我们的主要任务就是通过数据训练确定参数权重。在训练神经网络时,如果未提供全部样本而是逐个提供实例,则我们通常使用在线学习,然后在每个实例学习之后立刻调整网络参数,以这种方式使得网络缓慢得及时调整。具体收敛可是使用梯度下降算法。更新= 学习因子 * (期望输出 - 实际输出) * 输入
感知器具有很强的表现力,比如布尔函数AND和OR都可以使用上面的单层感知器实现。但是对于XOR操作则不行,因为单层感知器只能模拟线性函数,对于XOR这种非线性函数,我们需要新型的感知器。
即在Minsky和Papert的专著《感知器》所分析的:感知器只能解决所谓一阶谓词逻辑问题:与(AND),或(OR)等,而不能解决异或(XOR)等高阶谓词罗辑问题。
用多个感知器实现非线性
单个感知器虽然无法解决异或问题,但却可以通过将多个感知器组合,实现复杂空间的分割。如下图:
将两层感知器按照一定的结构和系数进行组合,第一层感知器实现两个线性分类器,把特征空间分割,而在这两个感知器的输出之上再加一层感知器,就可以实现异或运算。
也就是,由多个感知器组合:
来实现非线性分类面,其中θ(·)表示阶跃函数或符号函数。
多层感知器(Multiayer perceptrons, MLP)可以实现非线性判别式,如果用于回归,可以逼近输入的非线性函数。其实MLP可以用于“普适近似”,即可以证明: 具有连续输入和输出的任何函数都可以用MLP近似 ,已经证明,具有一个隐藏层(隐藏节点个数不限)的MLP可以学习输入的任意非线性函数。
实际上,上述模型就是多层感知器神经网络(Multi-layer perceptron neural networks,MLP neural netwoks)的基础模型。神经网络中每个节点为一个感知器,模型生物神经网络中神经元的基础功能:来自外界(环境或其他细胞)的电信号通过突触传递给神经元,当细胞收到的信号总和超过一定阈值后,细胞被激活,通过轴突向下一个细胞发送电信号,完成对外界信息的加工。
但是,感知器的学习算法并不能直接应用到多层感知器模型的参数学习上。因此,最初提出的学习方案是:除了最后一个神经元之外,事先固定其他所有神经元的权值,学习过程只是用感知器学习算法学习最后一个神经元的权系数。实际上,这相当于通过第一层神经元把原始的特征空间变换到一个新的特征空间,第一层的每个神经元构成新空间的一维,然后在新的特征空间用感知器学习算法构造一个线性分类器。显然,由于第一层的神经元权值需要人为给定,模型的性能很大程度取决于能否设计出恰当的第一层神经元模型,而这取决于对所面临的的问题和数据的了解,并没有针对任意问题求解第一层神经元参数的方法。其核心思想是梯度下降法,即以训练样本被错分的程度为目标函数,训练中每次出现错误时便使权系数朝着目标函数相对于权系数负梯度方向更新,知道目标中没有被错分的样本为止。
而多层感知器模型中,神经元传递函数是阶跃函数,输出端的只能对最后一个神经元系数求梯度,无法对其他权系数求梯度,所以无法利用梯度下降的方法学习其他的权值。Sigmoid函数
BP算法提出主要由于Sigmoid函数的出现,代替之前的阈值函数来构造神经元。
Sigmoid函数是单调递增的非线性函数,无限次可微。当且仅当权值较大时可以逼近阈值函数,当权值较小时逼线性函数。
Sigmoid函数通常写作如下形式:
取值范围是(-1,1),代替神经元阶跃函数可写作:
由于采用Sigmoid函数作为神经元传递函数,不管网络结构多么复杂,总可以通过计算梯度来考察各个参数。这就是多层感知器反向传播算法的基本思想。
反向传播(back-propagation,BP)
训练MLP常用的是向后传播(backpropagation),这主要是因为在我们收敛误差函数的时候,使用链接规则计算梯度:
BP算法就是通过迭代优化网络的权值使得输出与输入之间的实际映射关系与所期望的映射关系一致,采用梯度下降算法通过调整各层权值求目标函数最小化。网络在某个或所有训练样本上的预测输出和期望输出的误差平方和:
由输出层误差逐层反向计算各层各单元的误差, 并基于梯度下降法修正各权值:
其中,δj(k)是第k层第j单元的局部梯度,或敏感度(sensitivity)。
已知的两类蚊子的数据如表1:
表1
规定目标为: 当t(1)=0.9 时表示属于Apf类,t(2)=0.1表示属于Af类。
输入数据有15个,即 , p=1,…,15; j=1, 2; 对应15个输出。
即对应的(X,Y)对为:([1.78,1.14],0.9),([1.96,1.18],0.9)......([2.08,1.56],0.1)
由于此时的X有两个属性,故输入端为2个,建立神经网络如下:
设两个权重系数矩阵为:
分析如下:
为第一层的输出,同时作为第二层的输入。
在这里,a0(3)可以认为为常数
具体算法如下:
令p=0
(1)随机给出两个权矩阵的初值;例如用MATLAB软件时可以用以下语句:
(6) p=p+1,转(2)
注:仅计算一圈(p=1,2,…,15)是不够的,直到当各权重变化很小时停止,本例中,共计算了147圈,迭代了2205次。
最后结果是:
四、神经网络的应用
1.样本数据
1.1 收集和整理分组
采用BP神经网络方法建模的首要和前提条件是有足够多典型性好和精度高的样本。而且,为监控训练(学习)过程使之不发生“过拟合”和评价建立的网络模型的性能和泛化能力,必须将收集到的数据随机分成训练样本、检验样本(10%以上)和测试样本(10%以上)3部分。此外,数据分组时还应尽可能考虑样本模式间的平衡。
1.2 输入/输出变量的确定及其数据的预处理
一般地,BP网络的输入变量即为待分析系统的内生变量(影响因子或自变量)数,一般根据专业知识确定。若输入变量较多,一般可通过主成份分析方法压减输入变量,也可根据剔除某一变量引起的系统误差与原系统误差的比值的大小来压减输入变量。输出变量即为系统待分析的外生变量(系统性能指标或因变量),可以是一个,也可以是多个。一般将一个具有多个输出的网络模型转化为多个具有一个输出的网络模型效果会更好,训练也更方便。
由于BP神经网络的隐层一般采用Sigmoid转换函数,为提高训练速度和灵敏性以及有效避开Sigmoid函数的饱和区,一般要求输入数据的值在0~1之间。因此,要对输入数据进行预处理。一般要求对不同变量分别进行预处理,也可以对类似性质的变量进行统一的预处理。如果输出层节点也采用Sigmoid转换函数,输出变量也必须作相应的预处理,否则,输出变量也可以不做预处理。
预处理的方法有多种多样,各文献采用的公式也不尽相同。但必须注意的是,预处理的数据训练完成后,网络输出的结果要进行反变换才能得到实际值。再者,为保证建立的模型具有一定的外推能力,最好使数据预处理后的值在0.2~0.8之间。
2.神经网络拓扑结构的确定
2.1 隐层数
一般认为,增加隐层数可以降低网络误差(也有文献认为不一定能有效降低),提高精度,但也使网络复杂化,从而增加了网络的训练时间和出现“过拟合”的倾向。Hornik等早已证明:若输入层和输出层采用线性转换函数,隐层采用Sigmoid转换函数,则含一个隐层的MLP网络能够以任意精度逼近任何有理函数。显然,这是一个存在性结论。在设计BP网络时可参考这一点,应优先考虑3层BP网络(即有1个隐层)。一般地,靠增加隐层节点数来获得较低的误差,其训练效果要比增加隐层数更容易实现。对于没有隐层的神经网络模型,实际上就是一个线性或非线性(取决于输出层采用线性或非线性转换函数型式)回归模型。因此,一般认为,应将不含隐层的网络模型归入回归分析中,技术已很成熟,没有必要在神经网络理论中再讨论之。
2.2 隐层节点数
在BP 网络中,隐层节点数的选择非常重要,它不仅对建立的神经网络模型的性能影响很大,而且是训练时出现“过拟合”的直接原因,但是目前理论上还没有一种科学的和普遍的确定方法。
目前多数文献中提出的确定隐层节点数的计算公式都是针对训练样本任意多的情况,而且多数是针对最不利的情况,一般工程实践中很难满足,不宜采用。事实上,各种计算公式得到的隐层节点数有时相差几倍甚至上百倍。为尽可能避免训练时出现“过拟合”现象,保证足够高的网络性能和泛化能力,确定隐层节点数的最基本原则是:在满足精度要求的前提下取尽可能紧凑的结构,即取尽可能少的隐层节点数。研究表明,隐层节点数不仅与输入/输出层的节点数有关,更与需解决的问题的复杂程度和转换函数的型式以及样本数据的特性等因素有关。
在确定隐层节点数时必须满足下列条件:
(1)隐层节点数必须小于N-1(其中N为训练样本数),否则,网络模型的系统误差与训练样本的特性无关而趋于零,即建立的网络模型没有泛化能力,也没有任何实用价值。同理可推得:输入层的节点数(变量数)必须小于N-1。
(2) 训练样本数必须多于网络模型的连接权数,一般为2~10倍,否则,样本必须分成几部分并采用“轮流训练”的方法才可能得到可靠的神经网络模型。
总之,若隐层节点数太少,网络可能根本不能训练或网络性能很差;若隐层节点数太多,虽然可使网络的系统误差减小,但一方面使网络训练时间延长,另一方面,训练容易陷入局部极小点而得不到最优点,也是训练时出现“过拟合”的内在原因。因此,合理隐层节点数应在综合考虑网络结构复杂程度和误差大小的情况下用节点删除法和扩张法确定。
3.神经网络的训练
3.1 训练
BP网络的训练就是通过应用误差反传原理不断调整网络权值使网络模型输出值与已知的训练样本输出值之间的误差平方和达到最小或小于某一期望值。虽然理论上早已经证明:具有1个隐层(采用Sigmoid转换函数)的BP网络可实现对任意函数的任意逼近。但遗憾的是,迄今为止还没有构造性结论,即在给定有限个(训练)样本的情况下,如何设计一个合理的BP网络模型并通过向所给的有限个样本的学习(训练)来满意地逼近样本所蕴含的规律(函数关系,不仅仅是使训练样本的误差达到很小)的问题,目前在很大程度上还需要依靠经验知识和设计者的经验。因此,通过训练样本的学习(训练)建立合理的BP神经网络模型的过程,在国外被称为“艺术创造的过程”,是一个复杂而又十分烦琐和困难的过程。
由于BP网络采用误差反传算法,其实质是一个无约束的非线性最优化计算过程,在网络结构较大时不仅计算时间长,而且很容易限入局部极小点而得不到最优结果。目前虽已有改进BP法、遗传算法(GA)和模拟退火算法等多种优化方法用于BP网络的训练(这些方法从原理上讲可通过调整某些参数求得全局极小点),但在应用中,这些参数的调整往往因问题不同而异,较难求得全局极小点。这些方法中应用最广的是增加了冲量(动量)项的改进BP算法。所谓动量,就是对当前和上一次更新,取滑动平均。
3.2 学习率和冲量系数
学习率影响系统学习过程的稳定性。大的学习率可能使网络权值每一次的修正量过大,甚至会导致权值在修正过程中超出某个误差的极小值呈不规则跳跃而不收敛;但过小的学习率导致学习时间过长,不过能保证收敛于某个极小值。所以,一般倾向选取较小的学习率以保证学习过程的收敛性(稳定性),通常在0.01~0.8之间。
增加冲量项的目的是为了避免网络训练陷于较浅的局部极小点。理论上其值大小应与权值修正量的大小有关,但实际应用中一般取常量。通常在0~1之间,而且一般比学习率要大。同时,也可以让学习速率自适应更新。
4 网络的初始连接权值
BP算法决定了误差函数一般存在(很)多个局部极小点,不同的网络初始权值直接决定了BP算法收敛于哪个局部极小点或是全局极小点。因此,要求计算程序(建议采用标准通用软件,如Statsoft公司出品的Statistica Neural Networks软件和Matlab 软件)必须能够自由改变网络初始连接权值。由于Sigmoid转换函数的特性,一般要求初始权值分布在-0.5~0.5之间比较有效。同时,以不同的初值,想相同的网络训练多次,并且计算沿着误差的平均值,以获得期望的误差。
5.过分训练
当训练时间过长时,由于随着训练周期的增加,训练集上的误差降低,但是超过某一点时,验证集上的误差开始增加,其实质是权重在不断训练中,开始离开0值,参与到训练过程中,随着训练进行,就像是增加了新的参数添加到系统中一样,增加了系统的复杂度,导致糟糕的泛化能力。学习结束的拐点应该通过交叉验证确定。
6.构造网络
我们可以利用输入的特征,如针对像素点的特性,有边等像素的组合特性,我们可以定义一个隐藏单元,它在输入空间上定义一个输入窗口,并且仅仅与输入的一个小的局部子集相连接。我们可以在相继层重复这一做法,直到输出层。即层次椎体。
7.权重共享
在我们寻找类似有向边这种特征时,他们可能出现在输入空间的不同部分,因此,可以替代定义独立的隐藏单元学习输入空间不同部分的不同特征,我们可以有考查输入空间不同部分的相同隐藏层的复制,在学习期间,我们取不同的输入计算梯度,然后对他们求平均值,并做单个更新。这意味着单个链接定义多个权重。
8.线索
与应用有关的任何类型的知识,都应该构建在网络结构中。如模式识别中,不变形线索,对象旋转,变化,缩放。
对线索可以有不同的使用方法:
8.1 用线索创建虚拟实例,产生不同尺度的多个复制,以相同的类标号将他们添加到训练集。
8.2 预处理,例如规范化和中心化
8.3 线索可以纳入到网络结构中,例如权重共享,局部结构
8.4 也可以通过修改误差函数,将线索纳入结构中。如惩罚,近似线索,正切支撑等
9. 网络规模调整
我们可以通过在学习网络中,增加结构自适应处理,实现最佳网络结构模型。如破坏性方法,建设性方法等。实际经验表明,训练后,多层感知器的权重都围绕0正态分布。
五、局部模型
5.1.竞争学习
这里,我们讨论用竞争方法用于在线聚类的神经网络方法,在线K-均值以及两种神经网络的扩展:ART和SOM。
实际上,在线k-均值与批处理k-均值的区别在于更新均值中心时,对批处理k-均值的m进行随机梯度下降法,得到每个实例的更新规则。而在竞争网络中,我们对输入层x与权值(即在线聚类的中心位置)做点积运算,选择最大的b,并将其设为1,其他的b设为0.
为了避免死中心,即存在却没有被实际利用的中心。我们可以采用如下方法:
1.ART模型,即使用领导者聚类算法,并逐个增加单元,总是将他们添加到需要他们的地方。
2.SOM,更新时不仅更新最近单元的中心,还会更新某些中心,如领域。
3.引入良心机制,当赢得当前竞争的单元有负罪感并允许其他单元获胜。
4.随机选择输入实例来初始化m,确保他们从有数据的地方开始。
5.2 径向基函数
RBF网络能够逼近任意的非线性函数,可以处理系统内的难以解析的规律性,具有良好的泛化能力,并有很快的学习收敛速度,已成功应用于非线性函数逼近、时间序列分析、数据分类、模式识别、信息处理、图像处理、系统建模、控制和故障诊断等。
简单说明一下为什么RBF网络学习收敛得比较快。当网络的一个或多个可调参数(权值或阈值)对任何一个输出都有影响时,这样的网络称为全局逼近网络。由于对于每次输入,网络上的每一个权值都要调整,从而导致全局逼近网络的学习速度很慢。BP网络就是一个典型的例子。
如果对于输入空间的某个局部区域只有少数几个连接权值影响输出,则该网络称为局部逼近网络。常见的局部逼近网络有RBF网络、小脑模型(CMAC)网络、B样条网络等。
完全内插法要求插值函数经过每个样本点,即。样本点总共有P个。
RBF的方法是要选择P个基函数,每个基函数对应一个训练数据,各基函数形式为,由于距离是径向同性的,因此称为径向基函数。||X-Xp||表示差向量的模,或者叫2范数。
基于为径向基函数的插值函数为:
输入X是个m维的向量,样本容量为P,P>m。可以看到输入数据点Xp是径向基函数φp的中心。
隐藏层的作用是把向量从低维m映射到高维P,低维线性不可分的情况到高维就线性可分了。
将插值条件代入:
写成向量的形式为,显然Φ是个规模这P对称矩阵,且与X的维度无关,当Φ可逆时,有
。
对于一大类函数,当输入的X各不相同时,Φ就是可逆的。下面的几个函数就属于这“一大类”函数:
1)Gauss(高斯)函数
2)Reflected Sigmoidal(反常S型)函数
3)Inverse multiquadrics(拟多二次)函数
σ称为径向基函数的扩展常数,它反应了函数图像的宽度,σ越小,宽度越窄,函数越具有选择性。
完全内插存在一些问题:
1)插值曲面必须经过所有样本点,当样本中包含噪声时,神经网络将拟合出一个错误的曲面,从而使泛化能力下降。
由于输入样本中包含噪声,所以我们可以设计隐藏层大小为K,K
2)基函数个数等于训练样本数目,当训练样本数远远大于物理过程中固有的自由度时,问题就称为超定的,插值矩阵求逆时可能导致不稳定。
拟合函数F的重建问题满足以下3个条件时,称问题为适定的:
不适定问题大量存在,为解决这个问题,就引入了正则化理论。
正则化的基本思想是通过加入一个含有解的先验知识的约束来控制映射函数的光滑性,这样相似的输入就对应着相似的输出。
寻找逼近函数F(x)通过最小化下面的目标函数来实现:
加式的第一项好理解,这是均方误差,寻找最优的逼近函数,自然要使均方误差最小。第二项是用来控制逼近函数光滑程度的,称为正则化项,λ是正则化参数,D是一个线性微分算子,代表了对F(x)的先验知识。曲率过大(光滑度过低)的F(x)通常具有较大的||DF||值,因此将受到较大的惩罚。
直接给出(1)式的解:
权向量********************************(2)
G(X,Xp)称为Green函数,G称为Green矩阵。Green函数与算子D的形式有关,当D具有旋转不变性和平移不变性时,。这类Green函数的一个重要例子是多元Gauss函数:
输入样本有P个时,隐藏层神经元数目为P,且第p个神经元采用的变换函数为G(X,Xp),它们相同的扩展常数σ。输出层神经元直接把净输入作为输出。输入层到隐藏层的权值全设为1,隐藏层到输出层的权值是需要训练得到的:逐一输入所有的样本,计算隐藏层上所有的Green函数,根据(2)式计算权值。
Cover定理指出:将复杂的模式分类问题非线性地映射到高维空间将比投影到低维空间更可能线性可分。
广义RBF网络:从输入层到隐藏层相当于是把低维空间的数据映射到高维空间,输入层细胞个数为样本的维度,所以隐藏层细胞个数一定要比输入层细胞个数多。从隐藏层到输出层是对高维空间的数据进行线性分类的过程,可以采用单层感知器常用的那些学习规则,参见神经网络基础和感知器。
注意广义RBF网络只要求隐藏层神经元个数大于输入层神经元个数,并没有要求等于输入样本个数,实际上它比样本数目要少得多。因为在标准RBF网络中,当样本数目很大时,就需要很多基函数,权值矩阵就会很大,计算复杂且容易产生病态问题。另外广RBF网与传统RBF网相比,还有以下不同:
因此广义RBF网络的设计包括:
结构设计--隐藏层含有几个节点合适
参数设计--各基函数的数据中心及扩展常数、输出节点的权值。
下面给出计算数据中心的两种方法:
接下来求权值W时就不能再用了,因为对于广义RBF网络,其行数大于列数,此时可以求Φ伪逆。
数据中心的监督学习算法
最一般的情况,RBF函数中心、扩展常数、输出权值都应该采用监督学习算法进行训练,经历一个误差修正学习的过程,与BP网络的学习原理一样。同样采用梯度下降法,定义目标函数为
ei为输入第i个样本时的误差信号。
上式的输出函数中忽略了阈值。
为使目标函数最小化,各参数的修正量应与其负梯度成正比,即
具体计算式为
上述目标函数是所有训练样本引起的误差总和,导出的参数修正公式是一种批处理式调整,即所有样本输入一轮后调整一次。目标函数也可以为瞬时值形式,即当前输入引起的误差
此时参数的修正值为:
下面我们就分别用本文最后提到的聚类的方法和数据中心的监督学习方法做一道练习题。
考虑Hermit多项式的逼近问题
训练样本这样产生:样本数P=100,xi且服从[-4,4]上的均匀分布,样本输出为F(xi)+ei,ei为添加的噪声,服从均值为0,标准差为0.1的正态分布。
(1)用聚类方法求数据中心和扩展常数,输出权值和阈值用伪逆法求解。隐藏节点数M=10,隐藏节点重叠系数λ=1,初始聚类中心取前10个训练样本。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include"matrix.h"
using
namespace
std;
const
int
P=100;
//输入样本的数量
vector<
double
> X(P);
//输入样本
Matrix<
double
> Y(P,1);
//输入样本对应的期望输出
const
int
M=10;
//隐藏层节点数目
vector<
double
> center(M);
//M个Green函数的数据中心
vector<
double
> delta(M);
//M个Green函数的扩展常数
Matrix<
double
> Green(P,M);
//Green矩阵
Matrix<
double
> Weight(M,1);
//权值矩阵
/*Hermit多项式函数*/
inline
double
Hermit(
double
x){
return
1.1*(1-x+2*x*x)*
exp
(-1*x*x/2);
}
/*产生指定区间上均匀分布的随机数*/
inline
double
uniform(
double
floor
,
double
ceil
){
return
floor
+1.0*
rand
()/RAND_MAX*(
ceil
-
floor
);
}
/*产生区间[floor,ceil]上服从正态分布N[mu,sigma]的随机数*/
inline
double
RandomNorm(
double
mu,
double
sigma,
double
floor
,
double
ceil
){
double
x,prob,y;
do
{
x=uniform(
floor
,
ceil
);
prob=1/
sqrt
(2*M_PI*sigma)*
exp
(-1*(x-mu)*(x-mu)/(2*sigma*sigma));
y=1.0*
rand
()/RAND_MAX;
}
while
(y>prob);
return
x;
}
/*产生输入样本*/
void
generateSample(){
for
(
int
i=0;i
double
in=uniform(-4,4);
X[i]=in;
Y.put(i,0,Hermit(in)+RandomNorm(0,0.1,-0.3,0.3));
}
}
/*寻找样本离哪个中心最近*/
int
nearest(
const
vector<
double
>& center,
double
sample){
int
rect=-1;
double
dist=numeric_limits<
double
>::max();
for
(
int
i=0;i
if
(
fabs
(sample-center[i])
dist=
fabs
(sample-center[i]);
rect=i;
}
}
return
rect;
}
/*计算簇的质心*/
double
calCenter(
const
vector<
double
> &g){
int
len=g.size();
double
sum=0.0;
for
(
int
i=0;i
sum+=g[i];
return
sum/len;
}
/*KMeans聚类法产生数据中心*/
void
KMeans(){
assert
(P%M==0);
vector
double
gap=0.001;
//聚类中心的改变量小于为个值时,迭代终止
for
(
int
i=0;i
center[i]=X[10*i+3];
//输入是均匀分布的,所以我们均匀地选取
}
while
(1){
for
(
int
i=0;i
group[i].clear();
//先清空聚类信息
for
(
int
i=0;i
int
c=nearest(center,X[i]);
group[c].push_back(X[i]);
}
vector<
double
> new_center(M);
//存储新的簇心
for
(
int
i=0;i
vector<
double
> g=group[i];
new_center[i]=calCenter(g);
}
bool
flag=
false
;
for
(
int
i=0;i
if
(
fabs
(new_center[i]-center[i])>gap){
flag=
true
;
break
;
}
}
center=new_center;
if
(!flag)
break
;
}
}
/*生成Green矩阵*/
void
calGreen(){
for
(
int
i=0;i
for
(
int
j=0;j
Green.put(i,j,
exp
(-1.0*(X[i]-center[j])*(X[i]-center[j])/(2*delta[j]*delta[j])));
}
}
}
/*求一个矩阵的伪逆*/
Matrix<
double
> getGereralizedInverse(
const
Matrix<
double
> &matrix){
return
(matrix.getTranspose()*matrix).getInverse()*(matrix.getTranspose());
}
/*利用已训练好的神经网络,由输入得到输出*/
double
getOutput(
double
x){
double
y=0.0;
for
(
int
i=0;i
y+=Weight.get(i,0)*
exp
(-1.0*(x-center[i])*(x-center[i])/(2*delta[i]*delta[i]));
return
y;
}
int
main(
int
argc,
char
*argv[]){
srand
(
time
(0));
generateSample();
//产生输入和对应的期望输出样本
KMeans();
//对输入进行聚类,产生聚类中心
sort(center.begin(),center.end());
//对聚类中心(一维数据)进行排序
//根据聚类中心间的距离,计算各扩展常数
delta[0]=center[1]-center[0];
delta[M-1]=center[M-1]-center[M-2];
for
(
int
i=1;i
double
d1=center[i]-center[i-1];
double
d2=center[i+1]-center[i];
delta[i]=d1
}
calGreen();
//计算Green矩阵
Weight=getGereralizedInverse(Green)*Y;
//计算权值矩阵
//根据已训练好的神经网络作几组测试
for
(
int
x=-4;x<5;++x){
cout<
cout<
cout<
}
return
0;
}
|
(2)用梯度下降法训练RBF网络,设η=0.001,M=10,初始权值为[-0.1,0.1]内的随机数,初始数据中心为[-4,4]内的随机数,初始扩展常数取[0.1,0.3]内的随机数,目标误差为0.9,最大训练次数为5000。
#include
#include
#include
#include
#include
#include
#include
using
namespace
std;
const
int
P=100;
//输入样本的数量
vector<
double
> X(P);
//输入样本
vector<
double
> Y(P);
//输入样本对应的期望输出
const
int
M=10;
//隐藏层节点数目
vector<
double
> center(M);
//M个Green函数的数据中心
vector<
double
> delta(M);
//M个Green函数的扩展常数
double
Green[P][M];
//Green矩阵
vector<
double
> Weight(M);
//权值矩阵
const
double
eta=0.001;
//学习率
const
double
ERR=0.9;
//目标误差
const
int
ITERATION_CEIL=5000;
//最大训练次数
vector<
double
> error(P);
//单个样本引起的误差
/*Hermit多项式函数*/
inline
double
Hermit(
double
x){
return
1.1*(1-x+2*x*x)*
exp
(-1*x*x/2);
}
/*产生指定区间上均匀分布的随机数*/
inline
double
uniform(
double
floor
,
double
ceil
){
return
floor
+1.0*
rand
()/RAND_MAX*(
ceil
-
floor
);
}
/*产生区间[floor,ceil]上服从正态分布N[mu,sigma]的随机数*/
inline
double
RandomNorm(
double
mu,
double
sigma,
double
floor
,
double
ceil
){
double
x,prob,y;
do
{
x=uniform(
floor
,
ceil
);
prob=1/
sqrt
(2*M_PI*sigma)*
exp
(-1*(x-mu)*(x-mu)/(2*sigma*sigma));
y=1.0*
rand
()/RAND_MAX;
}
while
(y>prob);
return
x;
}
/*产生输入样本*/
void
generateSample(){
for
(
int
i=0;i
double
in=uniform(-4,4);
X[i]=in;
Y[i]=Hermit(in)+RandomNorm(0,0.1,-0.3,0.3);
}
}
/*给向量赋予[floor,ceil]上的随机值*/
void
initVector(vector<
double
> &vec,
double
floor
,
double
ceil
){
for
(
int
i=0;i
vec[i]=uniform(
floor
,
ceil
);
}
/*根据网络,由输入得到输出*/
double
getOutput(
double
x){
double
y=0.0;
for
(
int
i=0;i
y+=Weight[i]*
exp
(-1.0*(x-center[i])*(x-center[i])/(2*delta[i]*delta[i]));
return
y;
}
/*计算单个样本引起的误差*/
double
calSingleError(
int
index){
double
output=getOutput(X[index]);
return
Y[index]-output;
}
/*计算所有训练样本引起的总误差*/
double
calTotalError(){
double
rect=0.0;
for
(
int
i=0;i
error[i]=calSingleError(i);
rect+=error[i]*error[i];
}
return
rect/2;
}
/*更新网络参数*/
void
updateParam(){
for
(
int
j=0;j
double
delta_center=0.0,delta_delta=0.0,delta_weight=0.0;
double
sum1=0.0,sum2=0.0,sum3=0.0;
for
(
int
i=0;i
sum1+=error[i]*
exp
(-1.0*(X[i]-center[j])*(X[i]-center[j])/(2*delta[j]*delta[j]))*(X[i]-center[j]);
sum2+=error[i]*
exp
(-1.0*(X[i]-center[j])*(X[i]-center[j])/(2*delta[j]*delta[j]))*(X[i]-center[j])*(X[i]-center[j]);
sum3+=error[i]*
exp
(-1.0*(X[i]-center[j])*(X[i]-center[j])/(2*delta[j]*delta[j]));
}
delta_center=eta*Weight[j]/(delta[j]*delta[j])*sum1;
delta_delta=eta*Weight[j]/
pow
(delta[j],3)*sum2;
delta_weight=eta*sum3;
center[j]+=delta_center;
delta[j]+=delta_delta;
Weight[j]+=delta_weight;
}
}
int
main(
int
argc,
char
*argv[]){
srand
(
time
(0));
/*初始化网络参数*/
initVector(Weight,-0.1,0.1);
initVector(center,-4.0,4.0);
initVector(delta,0.1,0.3);
/*产生输入样本*/
generateSample();
/*开始迭代*/
int
iteration=ITERATION_CEIL;
while
(iteration-->0){
if
(calTotalError()
break
;
updateParam();
//更新网络参数
}
cout<<
"迭代次数:"
<
//根据已训练好的神经网络作几组测试
for
(
int
x=-4;x<5;++x){
cout<
cout<
cout<
}
return
0;
}
|
5.3 基于规则的知识
当然,我们也可以结合先验知识来初始化。如局部的规则提取等。实际上,这种方法与模糊逻辑有关,将模糊规则表示在RBF框架里,如约等于被高斯函数建模等。
5.4 规范化基函数
在某些应用中,我们可能有一个规范化的步骤,确保局部单元的和为1,从而确保任何输入,至少存在一个非零单元。
最后再给出一个BP及例子
用如下图1的神经网络结构(正向神经网络结构为2-4-4-2-1 )去逼近函数:
f(x1,x2) = (x1-1)^4 + 2×x2^2。
1)网络各神经元的激发函数为:s函数——F(x) = 1/(1+exp(-x)) ;
2)输入层的神经元不是真正的神经元,它们的输出等于输入。
3)取20个样本值作为训练用。
4)x1,x2的取值范围:0≤x1,x2≤1。
5)误差<0.0001
求解过程
1、对要逼近的函数f(x1,x2) = (x1-1)^4 + 2×x2^2 进行分析。x1,x2的取值范围:0≤x1,x2≤1。那么,输入不用归一化(若下x1,x2的值域不再0~1,那就要输入归一化了,因为我们可以从神经网络的激发函数可以看出,输入在0~1时,变化率是很大的,所以网络对输出很敏感)。求该函数的值域,很显然该函数的值域为:0~3,这就需要归一化了,因为神经网络输出的值只能在0~1之间。设Out_Exp[i]为第i个输入样本的期望值,那么归一化后的期望输出为:Out_Exp[i]/3,用这个值和网络的输出进行比较,来进行训练。最后在网络输出时要反归一化,即把网络的输出乘以3。
2、由于BP算 法的步骤是一定的,我们只要把其思想转化为程序就行了,即把数学表达式转换为程序。我们知道在计算机中每一种算法都需要一定的数据结构去支持。由于算法已 确定,那么我们只要分析和确定其数据结构即可。首先,我们考虑在如何计算机程序设计中表示权系数和阀值,在这里我们定义了3维数组W[Layer_Max][Node_Max][Node_Max+1]用来表示神经网络的全部权系数和阀值,我们约定W[i][j][k]存储网络的权系数,其中i表示为神经网络的第i层,j表示为第i层网络的第j个神经元,k表示为第i -1层的第j个神经网络。那么,W[i][j][k]表示为第i层的第j个神经元和第i -1层的第k个神经元的权系数。W[i][j][Layer[i-1]+1]表示第i层第j个神经元的阀值。
注:1、Layer_Max表示网络结构的层数
2、Node_Max表示整个神经网络中各层中含有神经元的最大数目的个数
3、Layer[i]数组表示网络中第i层的神经元的个数
然后,我们定义网络输入的和期望输出数组。定义2维数组Input_Net[2][21]作为网络输入数组,在这里为了方便取了21样本作为,其中x1取值从0开始,已每次加0.05的步长作为下一个样本取值。而x2的取值则与之相反。那么,由于x1和x2各有21个值,由排列组合得出网络训练样本一共有21*21=421个样本。我们再定义一个2维数组Out_Exp[21][21]表示期望输出。定义二维数组Layer_Node[i][j]存储各层神经元的输出,表示为第i层的第j个神经元的输出。定义二维数组D[i][j]存储各层神经元的的误差微分,表示为第i层的第j个神经元的的误差微分。
3、代价函数为(NetOut(i ,j)-Out_Exp[i][j])^2/2。其中:NetOut(i,j)表示输入x1的第i个值和x2的第j个值所组成的样本时,网络的实际输出。
4、确定BP算法的关键的子程序。
a) 、F( double x ) 该函数是该神经网络的唯一激发函数,它的数学表达:
F( x ) = 1/(1+exp(-x)) 。它的输入为样本值NetIn[i]。输出为一个在区间。
b)、Initialize() 该函数是网络初始化子程序,它初始化权系数和阀值,学习速率,误差精度等。
c)、 NetWorkOut( int i ,int j) 该函数的输入为表示输入x1的第i个值和x2的第j个值所组成的样本时,在计算网络输出的时候,同时计算各层神经元的输出,并保存在Layer_Node[][]数组里。输出为神经网络的实际输出。
d)、 AllLayer_D(int i , int j) 该函数的输入为输入x1的第i个值和x2的第j个值所组成的样本的数组下标,目的是计算各层神经元的误差微分,并把他们保存在D[][]数组里。
e)、 Change_W( ) 该函数是用于根据AllLayer_D( )计算出来的误差微分改变权系数,根据经典的BP算法可以写出改变权系数和阀值式子:
W[i][j][k] = W[i][j][k] – Study_Speed*D[i][j]* Layer_Node[i-1][k]
W[i][j][Layer[i-1]+1]=W[i][j][Layer[i-1]+1]+Study_Speed*D[i][j]*
Layer_Node[i-1][ [Layer[i-1]+1]
其中:Study_Speed为学习速率,取值在(0,1)之间,如果太大了,网络将会出现振荡,而不能收敛。
g)、 Train( ) 该函数是用于神经网络训练用的。它调用了上面几个函数来完成网络训练的。当训练完(即网络对于该问题是可以收敛的)时,网络就可以在特定的误差范内逼近函数。下面给出该函数的流程图:
//---------------------------------------------------------------------------------------//
// BP算法例子:用一个五层的神经网络去逼近函数 //
// f(x1,x2)=pow(x1-1,4)+2*pow(x2,2) //
// 作者:MaxMatrix //
// 2004.5.9调通 运行于VC++6.0 //
//--------------------------------------------------------------------------------------//
#include
#include
#include
#include
#include
//---------------------------------------------------------------------
#define RANDOM rand()/32767.0 //0~1随机数生成函数
const int Layer_Max=5;//神经网络的层数
const double PI=3.1415927;//圆周率
const int Layer_number[Layer_Max]={2,4,4,2,1};//神经网络各层的神经元个数
const int Neural_Max=4;//神经网络各层最大神经元个数
const int InMax=21;//样本输入的个数
ofstream Out_W_File("All_W.txt",ios::out) ;
ofstream Out_Error("Error.txt",ios::out) ;
//定义类 BP
class BP
{
public:
BP(); //BP类的构造函数
void BP_Print();//打印权系数
double F(double x);//神经元的激发函数
double Y(double x1,double x2);//要逼近的函数
//
double NetWorkOut(int x1 , int x2);//网络输出,他的输入为
//第input个样本
void AllLayer_D(int x1 , int x2);//求所有神经元的输出误差微分
void Change_W(); //改变权系数
void Train(); //训练函数
void After_Train_Out(); //经过训练后,21样本的神经网络输出
double Cost(double out,double Exp);//代价函数
private:
double W[Layer_Max][Neural_Max][Neural_Max];//保存权系数
//规定W[i][j][k]表示网络第i层的第j个神经元连接到
//第i-1层第k个神经元的权系数
double Input_Net[2][InMax];//21个样本输入,约定Input_Net[0][i]
//表示第i个样本的输入x1
//而 Input_Net[1][i]表示第i个样本的输入x2
double Out_Exp[InMax][InMax];//期望输出
double Layer_Node[Layer_Max][Neural_Max];//保存各神经元的输出
//规定Layer_Node[i][j]表示第i层的第j个神经元的输出
double D[Layer_Max][Neural_Max];//保存各神经元的误差微分
//规定D[i][j]表示第i层第j个神经元的误差微分
double Study_Speed;//学习速度
double e;//误差
};
//构造函数,用来初始化权系数,输入,期望输出和学习速度
BP::BP()
{
srand(time(NULL));//播种,以便产生随即数
for(int i=1 ; i
for(int j=0 ; j
for(int k=0 ; k
W[i][j][k] = RANDOM;//随机初始化权系数
}
// Q[i][j] = RANDOM ;//初始化各神经元的阀值
}
}
//输入归和输出归一化
for(int l=0 ; l
Input_Net[0][l] = l * 0.05 ;//把0~1分成20等分,表示x1
Input_Net[1][l] = 1 - l * 0.05 ;//表示x2
}
for(i=0 ; i
for(int j=0 ; j
Out_Exp[i][j] = Y(Input_Net[0][i],Input_Net[1][j]) ;//期望输出
Out_Exp[i][j] = Out_Exp[i][j]/3.000000;//期望输出归一化
}
}
Study_Speed=0.5;//初始化学习速度
e=0.0001;//误差精度
}//end
//激发函数F()
double BP::F(double x)
{
return(1.0/(1+exp(-x)));
}//end
//要逼近的函数Y()
//输入:两个浮点数
//输出:一个浮点数
double BP::Y(double x1,double x2)
{
double temp;
temp = pow(x1-1,4) + 2 * pow(x2,2);
return temp;
}//end
//--------------------------------------------------------
//代价函数
double BP::Cost(double Out,double Exp)
{
return(pow(Out-Exp,2));
}//end
//网络输出函数
//输入为:第input个样本
double BP::NetWorkOut(int x1 , int x2)
{
int i,j,k;
double N_node[Layer_Max][Neural_Max];
//约定N_node[i][j]表示网络第i层的第j个神经元的总输入
//第0层的神经元为输入,不用权系数和阀值,即输进什么即输出什么
N_node[0][0] = Input_Net[0][x1] ;
Layer_Node[0][0] = Input_Net[0][x1] ;
N_node[0][1] = Input_Net[1][x2] ;
Layer_Node[0][1] = Input_Net[1][x2] ;
for(i=1 ; i
for(j=0 ; j
N_node[i][j] = 0.0;
for(k=0 ; k
//神经元个数
//求上一层神经元对第i层第j个神经元的输入之和
N_node[i][j]+=Layer_Node[i-1][k] * W[i][j][k];
}
N_node[i][j] = N_node[i][j]-W[i][j][k];//减去阀值
//求Layer_Node[i][j],即第i层第j个神经元的输出
Layer_Node[i][j] = F(N_node[i][j]);
}
}
return Layer_Node[Layer_Max-1][0];//最后一层的输出
}//end
//求所有神经元的输出误差微分函数
//输入为:第input个样本
//计算误差微分并保存在D[][]数组中
void BP::AllLayer_D(int x1 , int x2)
{
int i,j,k;
double temp;
D[Layer_Max-1][0] = Layer_Node[Layer_Max-1][0] *
(1-Layer_Node[Layer_Max-1][0])*
(Layer_Node[Layer_Max-1][0]-Out_Exp[x1][x2]);
for(i=Layer_Max-1 ; i>0 ; i--)
{
for(j=0 ; j
temp = 0 ;
for(k=0 ; k
temp = temp+W[i][k][j]*D[i][k] ;
}
D[i-1][j] = Layer_Node[i-1][j] * (1-Layer_Node[i-1][j])
*temp ;
}
}
}//end
//修改权系数和阀值
void BP::Change_W()
{
int i,j,k;
for(i=1 ; i
for(j=0;j
for(k=0;k
//修改权系数
W[i][j][k]=W[i][j][k]-Study_Speed*
D[i][j]*Layer_Node[i-1][k];
}
W[i][j][k]=W[i][j][k]+Study_Speed*D[i][j];//修改阀值
}
}
}//end
//训练函数
void BP::Train()
{
int i,j;
int ok=0;
double Out;
long int count=0;
double err;
ofstream Out_count("Out_count.txt",ios::out) ;
//把其中的5个权系数的变化保存到文件里
ofstream outWFile1("W[2][0][0].txt",ios::out) ;
ofstream outWFile2("W[2][1][1].txt",ios::out) ;
ofstream outWFile3("W[1][0][0].txt",ios::out) ;
ofstream outWFile4("W[1][1][0].txt",ios::out) ;
ofstream outWFile5("W[3][0][1].txt",ios::out) ;
while(ok<441)
{
count++;
//20个样本输入
for(i=0,ok=0 ; i
for(j=0 ; j
Out = NetWorkOut(i,j);
AllLayer_D(i,j); << '['< } //打印权系数 //把结果保存到文件 ofstream W_End("W_End.txt",ios::out) ; ofstream Q_End("Q_End.txt",ios::out) ; ofstream Array("Array.txt",ios::out) ; ofstream Out_x11("x1.txt",ios::out) ; ofstream Out_x22("x2.txt",ios::out) ; ofstream Result1("result1.txt",ios::out) ; ofstream Out_x111("x11.txt",ios::out) ; ofstream Out_x222("x22.txt",ios::out) ; ofstream Result2("result2.txt",ios::out) ; Array< Array< Out_Net<<3*NetWorkOut(i,j)<<"," ; Array< Out_Exp< Array<<3*NetWorkOut(i,j)<<" " ; Array<<'\n' ; void main(void) }//end
err = Cost(Out,Out_Exp[i][j]);//计算误差
if(err
else Change_W();//否修改权系数和阀值
}
}
if((count%1000)==0)//每1000次,保存权系数
{
cout<
for(int j=0 ; j
for(int k=0 ; k
Out_W_File<<'W'<<'['<
}
}
Out_W_File<<'\n'<<'\n' ;
}
cout<
void BP::BP_Print()
{
//打印权系数
cout<<"训练后的权系数"<
for(int j=0 ; j
for(int k=0 ; k
cout<
cout<
}
cout<
void BP::After_Train_Out()
{
int i,j ;
ofstream Out_x1("Out_x1.txt",ios::out) ;
ofstream Out_x2("Out_x2.txt",ios::out) ;
ofstream Out_Net("Out_Net.txt",ios::out) ;
ofstream Out_Exp("Out_Exp.txt",ios::out) ;
for( i=0 ; i
for(j=0 ; j
Out_x11<
Out_x1<
Out_x2<
}
Out_x1<<'\n' ;
Out_x2<<'\n' ;
Out_x11<<'\n';
Out_x22<<'\n';
Result1<<'\n' ;
}
for(j=0 ; j
for(i=0 ; i
Out_x111<
}
Out_x111<<'\n';
Out_x222<<'\n' ;
Result2<<'\n' ;
}
//把经过训练后的权系数和阀值保存到文件里
for(i=1 ; i
for(int j=0 ; j
for(int k=0 ; k
W_End<
}
}//end for
}//end
{
BP B;//生成一个BP类对象B
B.Train();//开始训练
B.BP_Print();//把结果打印出来
B.After_Train_Out();//把结果保存到文件