文/朱先忠编译
训练神经网络
为实现本文的目的,我们将指导JOONE去识别一个很简单的模式。在这种模式中,我们将考察一个二进制的布尔操作,例如XOR。这个XOR操作的真值表列举如下:
X
|
Y
|
X XOR Y
|
0 |
0 |
0 |
0 |
1 |
1 |
1 |
0 |
1 |
1 |
1 |
0 |
正如你从上表中看到的,XOR运算的结果是只有当 X和Y具有不同值时,结果才为真(1)。其它情况下,XOR运算结果均为假(0)。默认地,JOONE从存储在你的系统中的文本文件中取得输入。这些文本 文件通过使用一种称为FileInputSynapse的特殊触角来读取。为了训练XOR运算问题,你必须建立一个输入文件-该文件包含上面显示的数据。 该文件显示在列表1中。
列表1:为解决XOR问题的输入文件的内容
0.0;0.0;0.0
0.0;1.0;1.0
1.0;0.0;1.0
1.0;1.0;0.0
我们现在分析一个简单的程序,它指导JOONE来识别XOR运算并产生正确的结果。我们现在分析训练该神经网络必须被处理的过程。训练过程包括把XOR 问题提交给神经网络,然后观察结果。如果这个结果不是所预期的,该训练算法将调整存储在触角中的重量。在神经网络的实际输出和预料的输出之间的差距称为误 差。训练将继续到误差小于一个可接受值为止。这个级别通常是一个百分数,如10%。我们现在分析必须用于训练一个神经网络的代码。
训练过程通过建立神经网络开始,同时也必须创建隐蔽的输入层和输出层。
// 首先,创造这三个层 input = new SigmoidLayer(); hidden = new SigmoidLayer(); output = new SigmoidLayer(); |
每个层被使用JOONE对象SigmoidLayer创建。Sigmoidlayer基于自然对数生成一个输出。JOONE还包含另外的层,而不是你可能选择使用的S形的层类型。
下一步,每一层被赋于一个名字。这些名字将有助于后面在调试期间识别该层。
input.setLayerName("input"); hidden.setLayerName("hidden"); output.setLayerName("output"); |
现在必须定义每个层。我们将指定在每一层中的"行"号。该"行"号对应于这一层中的神经原的数目。
input.setRows(2); hidden.setRows(3); output.setRows(1); |
从上面的代码看出,输入层有两个神经原,隐蔽层有三个隐蔽神经原,输出层包含一个神经原。这对于神经网络包含两个输入神经原和一个输出神经原是具有重要意义的,因为XOR运算符接收两个参数而产生一个结果。
为使用该神经原层,我们也必须创建触角。在本例中,我们要使用多个触角。这些触角用下面的代码实现。
// 输入-> 隐蔽的连接。 FullSynapse synapse_IH = new FullSynapse(); // 隐蔽-> 输出连接。 FullSynapse synapse_HO = new FullSynapse(); |
就象神经原层的情况一样,触角也可能命名以有助于程序的调试。下面的代码命名新建的触角。
synapse_IH.setName("IH"); synapse_HO.setName("HO"); |
最后,我们必须把触角联接到适当神经原层。下面的代码实现这一点。
// 联接输入层到隐蔽层 input.addOutputSynapse(synapse_IH); hidden.addInputSynapse(synapse_IH); // 联接隐蔽层到输出层 hidden.addOutputSynapse(synapse_HO); output.addInputSynapse(synapse_HO); |
现在既然神经网络已被创建,我们必须创建一个用于调节该神经网络的监视器对象。下面的代码创建监视器对象。
//创建监视器对象并且设置学习参数 monitor = new Monitor(); monitor.setLearningRate(0.8); monitor.setMomentum(0.3); |
学习速度和动力作为参数以用于指定训练的产生方式。JOONE利用backpropagation学习算法。要更多了解关于学习速度或者动力的信息,你应该参考backpropagation算法。
这个监视器对象应该被赋值给每一个神经原层。下面的代码实现这一点。
input.setMonitor(monitor); hidden.setMonitor(monitor); output.setMonitor(monitor); |
就象许多Java对象本身一样,JOONE监视器允许听者可以添加到它上面去。随着训练的进行,JOONE将通知听者有关训练进程的信息。在这个简单的例子中,我们使用:
monitor.addNeuralNetListener(this); |
我 们现在必须建立输入触角。如前所述,我们将使用一个FileInputSynapse来读取一个磁盘文件。磁盘文件不是JOONE唯一能够接受的输入种 类。JOONE对于不同的输入源具有很强的灵活性。为使JOONE能够接收其它输入类型,你只需创建一个新的触角来接受输入。在本例中,我们将简单地使用 FileInputSynapse。FileInputSynapse首先被实例化。
inputStream = new FileInputSynapse(); |
然后,必须通知FileInputSynapse要使用哪些列。列表1中显示的文件使用了输入数据的前两列。下面代码建立起前两列用于输入到神经网络。
// 前两列包含输入值 inputStream.setFirstCol(1); inputStream.setLastCol(2); |
然后,我们必须提供输入文件的名字,这个名字直接来源于用户接口。然后,提供一个编辑控件用于收集输入文件的名字。下面代码为FileInputSynapse设置文件名。
// 这是包含输入数据的文件名 inputStream.setFileName(inputFile.getText()); |
如前所述,一个触角仅是一个神经原层之间的数据导管。FileInputSynapse正是这里的数据导管,通过它数据进入到神经网络。为了更容易实现这点,我们必须要把FileInputSynapse加到神经网络的输入层。这由下面一行实现。
input.addInputSynapse(inputStream); |
现 在既然已经建立起神经网络,我们必须创建一个训练员和一个监视器。训练员用于训练该神经网络,因为该监视器通过一个事先设置好的训练重复数来运行这个神经 网络。对于每一次训练重复,数据被提供到神经网络,然后就可以观察到结果。该神经网络的权重(存储在穿梭在神经原层之间的触角连接中)将根据误差作适当调 整。随着训练的进行,误差级将下降。下列代码建立训练员并把它依附到监视器。
trainer = new TeachingSynapse(); trainer.setMonitor(monitor); |
你会记得列表1中提供的输入文件包含三个列。到目前为止,我们仅仅使用了第一、二列,它们指定了到神经网络的输入。第三列包含当提供给神经网络第一列中 的数字时的期盼的输出值。我们必须使得训练员能够存取该列以便能确定误差。该错误是神经网络的实际输出和期盼的输出之间的差距。下列代码创建另外一个 FileInputSynapse并作好准备以读取与前面相同的输入文件。
// 设置包含期望的响应值的文件,这由FileInputSynapse来提供 samples = new FileInputSynapse(); samples.setFileName(inputFile.getText()); |
这时,我们想指向在第三列的FileInputSynapse。下列代码实现了这一点,然后让训练员使用这个FileInputSynapse。
//输出值在文件中的第三列上 samples.setFirstCol(3); samples.setLastCol(3); trainer.setDesired(samples); |
最后,训练员被连结到神经网络输出层,这将使训练员接收神经网络的输出。
// 连接训练员到网络的最后一层 output.addOutputSynapse(trainer); |
我们现在已为所有的层准备好后台线程,包括训练员。
input.start(); hidden.start(); output.start(); trainer.start(); |
最后,我们为训练设置一些参数。我们指定在输入文件中共有四行,而且想训练20,000个周期,而且还在不段学习。如果你设置学习参数为false,该神经网络将简单地处理输入并不进行学习。我们将在下一节讨论输入处理。
monitor.setPatterns(4); monitor.setTotCicles(20000); monitor.setLearning(true); |
现在我们已经为训练过程作好准备。调用监视器的Go方法将在后台启动训练过程。
神经网络现在将要被训练20,000个周期。当神经网络训练完成,误差层应该在一个合理的低级别上。一般低于10%的误差级是可接受的。