
如上图,经噪声干扰后的0编码为:
0 0 1 1 1 1 1 0
0 0 1 0 0 0 1 0
0 0 1 0 0 0 1 0
0 0 1 0 0 1 1 0
0 0 1 0 0 0 1 0
0 0 1 0 0 0 1 0
0 0 1 1 1 0 1 1
0 0 1 1 1 1 1 0
图中,加下划线的斜体部分就是噪声。
接下来来确定输出层的神经元个数。在输出层,我将0~9数字用8421码进行编码,这样10个数字就需要4位二进制的码。这样也就确定下来输出层有4个神经元。
下面,来确定最关键的隐层的神经元个数。一般来说,在做类似于函数逼近的课题时,隐层的神经元越多,则逼近就越精确。但对于当前的这个模式分类的题目,隐层神经元的数目必须要适当的选取,即不能过多,也不能太少。如果隐层的神经元过多,一来会大大的加大训练时间,二来,虽然神经元的增多可以更加精确的描述分类判别的空间的界限,但同时,也造成网络的容错能力下降。这对于我们当前的这个0~9数字识别是极为不利的。当然,神经元的数目过少的时候,又无法满足分类的要求。
经过反复的实验测定、对比,我认为隐层的神经元的数目为8到15为最佳。小于8个神经元的时候,网络输出误差就太大了;大于15个神经元的时候,识别率又过低。
由于这只是一个简单的10类区分问题,所以使用一个隐层足已。
最后,来确定隐层和输出层的激活函数。遵循一般的,我在隐层中使用了处处可微分的Log-sigmoid函数,
f1(n)= 
在输出层使用了线性函数,这里,不妨简单的取:
f2(n)= n
至此,BP网络的大体雏形已经设计出来了。下面,我结合着编程,具体的说一下BP网络的实现部分,包括训练样本的设计、允许误差的设定和步长的设定,并给出改进识别率或训练时间的方案及实现。
3. BP网络的编程实现
这里,我所使用的编程开发环境为:
windows 2000 with sp4 + visual c++ 6.0 with sp5
面对琳琅满目的各种开发环境和仿真软件,我放弃了简单的matlab,也没有使用自己熟悉的C++Builder和Visual Basic, 最主要的原因就是,VC对内存的掌控很好。这对运算量极大的BP算法有着关键的作用。要知道,设计一个BP网络不难,难的是设计出一个高效、高速、稳定的BP网络,这就需要我们来对内存进行优化,对算法进行最大程度的挖掘。否则,设计出来的BP算法将是不健壮或者说不够稳定、实用的。一定要注意在设计程序时要尽可能多的释放掉不再使用的内存。
为了让提高编程的效率和便于查错,我使用了BP算法的矩阵形式,也就是公式1、2、3。 这样一来,对于正向信息的传播和反向误差的传播,很容易表示成矩阵(包括向量)相加减、相乘除的形式。
在运行BP网络进行识别之前,首先我们要有足够的数据来训练这个网络。这里,我们仅仅用10组“纯净”未含噪声的数据0、1…9是绝对不够的。如果只用10组输入,虽然可以很快的收敛也可以达到很小的误差,但对于我们的识别对象-加入噪声的数据,就几乎完全失去了分辨能力。
所以,这里,经过我多次比较、测试,我选定了60组数据作为训练样本。这60组数据包括:
① 其中有10组是纯净的0~9数字
② 外的50组,是对这10组纯净数据进行随机加噪产生的,分别随机的加入5%~15%的噪声。这样以来训练样本就有了足够的健壮性和容错能力。
下面来确定隐层的神经元个数、允许误差及学习步长。
如前所述,隐层的神经元数目的选择,是BP算法设计的关键。隐层的神经元数目过多,那即将造成训练时间的加长,也将降低抗噪声能力。编程证明,当隐层神经元数目超过25的时候,网络的训练时间将无法忍受,同时抗噪声能力大大降低;当神经元数目低于7的时候,系统误差无法收敛到满意的值,又造成识别率过低。经过多次的比较分析,我最终选定了隐层为9个神经元。事实证明(从后面的训练时间和识别率可以看出),这是个不错的选择。
允许误差是系统进行训练的目标。当系统的输出误差小于这个允许的误差的时候即停止训练。误差选择过大,将造成系统识别率过低;误差选择太小,会大大延长训练时间,是一种浪费。这里,也要通过多次的对比分析来得到最佳的允许误差。我最终确定下来的允许误差在0.02~0.2之间。
学习步长的选择也是BP网络设计的关键所在。学习步长过长,则有可能造成算法无法收敛;步长过短,一来加大了训练时间,二来则很容易陷入所谓的局部极小值。这里我初步把学习步长设定在0.02~0.3之间。
最后,我们来看看初始权值和偏置的设定。
设计初始权值和偏置有两点注意,一,W和B不要太大,否则很可能处于误差平面的平坦之处,导致网络训练停滞; 二,各项权值不能设为一样的值。
我的实现方法是:利用(-1,1)之间的随机数来设定权值和偏置。
至此,网络的设计已经完成。下面我来介绍一下我开发的这个程序,简要说明一下。
下图就是这个程序的主界面: