65.9K
CodeProject 正在变化。 阅读更多。
Home

Neuroph、Encog 和 JOONE 中的神经网络对比

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.25/5 (8投票s)

2010年6月3日

LGPL3

8分钟阅读

viewsIcon

54333

downloadIcon

905

重点介绍在 Neuroph、Encog 和 JOONE 中创建 XOR 网络的不同之处

引言

在本文中,我将向您展示如何创建前馈神经网络。我将向您展示如何创建前馈神经网络来识别 XOR 运算符。互联网上有很多示例会教您如何做到这一点。但是,我将向您展示三次如何做到这一点。我们将使用三个不同的神经网络框架。所有这些都将使您能够了解互联网上可用的三个主要的 Java 开源神经网络,以及它们如何处理问题。这可能有助于您决定哪个神经网络框架最适合您的项目。

首先,请允许我解释一下我将要教授神经网络识别的 XOR 操作。XOR 操作本质上是神经网络的“Hello World”。神经网络接受输入并产生输出。它们通过神经元层处理数据。前馈神经网络具有输入层、零个或多个隐藏层以及一个输出层。输入层接受浮点数数组。输出层产生浮点数数组。这些数组的大小不必相等。输入层和输出层中的神经元数量决定了数组的大小。

然后,您可以创建训练数据。对于 XOR 运算符,您将有四个训练项。训练项将输入映射到理想输出。对于 XOR,这些训练数据将如下所示

[0,0] -> [0]
[1,0] -> [1]
[0,1] -> [1]
[1,1] -> [0]

这些值对应于数学 XOR 运算符。神经网络将一直训练,直到神经网络的输出接近理想输出。训练被分解为多个迭代。每次迭代都会使神经网络输出更接近理想输出。神经网络的实际输出与理想输出之间的差异是神经网络的误差率。有不同的方法可以训练神经网络,但它们通常都涉及通过迭代,直到误差降低到可接受的水平。

本文将展示如何在三个不同的 Java 开源神经网络框架中实现 XOR 运算符。互联网上可用的三个主要开源神经网络框架是

在 Encog 中创建 XOR 运算符

Encog 根据 GNU 宽通用公共许可证获得许可。Encog 支持各种训练技术。在本文中,我们将使用一种称为 Levenberg Marquardt 或 LMA 的训练技术。LMA 是神经网络最先进的训练技术之一。它并非对所有训练集都有效,但它可以在其他训练技术所需的时间的一小部分内学习 XOR。LMA 要求神经网络的输出是单个神经元。对于 XOR 运算符来说,这是可以接受的。此外,随着训练集大小的增加,LMA 的有效性会降低。对于更大的训练集,您可能需要考虑 Resilient Propagation 或 Resilient Propagation。

首先,我们设置训练数据。XOR 数据存储在两个数组中。

public static double XOR_INPUT[][] = {
 { 0.0, 0.0 },
 { 1.0, 0.0 },
{ 0.0, 1.0 },
{ 1.0, 1.0 } };
public static double XOR_IDEAL[][] = {
 { 0.0 },
{ 1.0 },
{ 1.0 },
 { 0.0 } };

接下来,我们创建一个 BasicNetwork 对象。此对象将包含构成神经网络的层。

BasicNetwork network = new BasicNetwork();
network.addLayer(new BasicLayer(new ActivationTANH(),true,2));
network.addLayer(new BasicLayer(new ActivationTANH(),true,3));
network.addLayer(new BasicLayer(new ActivationTANH(),true,1));

您可以看到,添加了三层。第一层是输入层。它有两个神经元,并使用 TANH 激活函数。true 值表示该层具有偏置。

第二层是隐藏层。它有三个神经元。这些神经元有助于识别发送到神经网络的信号。输出层有一个输出神经元。

Encog 可以支持各种神经网络架构。这些被定义为逻辑对象。该神经网络是一个前馈神经网络,因此我们将使用 FeedforwardLogic 类。

network.setLogic(new FeedforwardLogic());
network.getStructure().finalizeStructure();
network.reset();

最后,必须创建神经网络结构,并重置神经网络。重置神经网络的过程会随机化权重。

我们现在创建一个训练集。Encog 使用训练集来训练 XOR 运算符。

NeuralDataSet trainingSet = new BasicNeuralDataSet(XOR_INPUT, XOR_IDEAL);

所有 Encog 训练都通过 Train 接口完成。我们将使用 LevenbergMarquardtTraining 训练。

final Train train = new LevenbergMarquardtTraining(network, trainingSet);

Levenberg Marquardt 训练有时会因随机权重而无法收敛。对于像这样的小型网络尤其如此。我们将使用一种重置策略,如果在 5 次迭代中改进不到 1%,则重新随机化。

train.addStrategy(new RequiredImprovementStrategy(5));

现在我们准备通过 epoch 或迭代进行训练。我们从 epoch 1 开始。

int epoch = 1;

我们将循环进行训练迭代,直到误差降至 1% 以下。

do {
  train.iteration();
  System.out.println("Epoch #" + epoch + " Error:" + train.getError());
  epoch++;
} while(train.getError() > 0.01);

现在我们将测试神经网络的输出。

System.out.println("Neural Network Results:");
for(NeuralDataPair pair: trainingSet ) {
  final NeuralData output = network.compute(pair.getInput());
  System.out.println(pair.getInput().getData(0) + "," + pair.getInput().getData(1)
   + ", actual=" + output.getData(0) + ",ideal=" + pair.getIdeal().getData(0));
}

使用相同的训练数据来导出神经网络。

Encog XOR 示例的输出显示在此

Epoch #1 Error:0.5455122488506147
Epoch #2 Error:0.5052657193615671
Epoch #3 Error:0.4807114538448516
Epoch #4 Error:0.43616509724573044
Epoch #5 Error:0.20566912505617155
Epoch #6 Error:0.17638897570315684
Epoch #7 Error:0.028373668231531972
Epoch #8 Error:0.026258952179473653
Epoch #9 Error:0.023244078646272336
Epoch #10 Error:0.021221881866343273
Epoch #11 Error:0.019887606329834745
Epoch #12 Error:0.018885747329580156
Epoch #13 Error:0.018047468600671735
Epoch #14 Error:0.017288643933811593
Epoch #15 Error:0.016572218727452955
Epoch #16 Error:0.01595505187120417
Epoch #17 Error:0.010300974511088516
Epoch #18 Error:0.008141364550377145
Neural Network Results:
0.0,0.0, actual=0.020564759593838522,ideal=0.0
1.0,0.0, actual=0.9607289095427742,ideal=1.0
0.0,1.0, actual=0.8966620525621667,ideal=1.0
1.0,1.0, actual=0.06032304565618274,ideal=0.0

您可以看到,使用 Levenberg Marquardt 训练,Encog 进行了 18 次迭代。并非所有训练技术都像 Levenberg Marquardt 那样迭代次数少。我们将在下一节中看到这一点。

在 Neuroph 中创建 XOR 运算符

Neuroph 是另一个神经网络框架。它在 Apache 许可证下获得许可。它目前正在讨论中,将被合并到 Apache 机器学习项目中。对于 Neuroph,我们将使用他们提供的反向传播算法的自动变体。它远不如 Levenberg Marquard 先进,因此需要更多的训练迭代。

我们从创建一个训练集开始。

TrainingSet trainingSet = new TrainingSet(2, 1);
trainingSet.addElement(new SupervisedTrainingElement
		(new double[]{0, 0}, new double[]{0}));
trainingSet.addElement(new SupervisedTrainingElement
		(new double[]{0, 1}, new double[]{1}));
trainingSet.addElement(new SupervisedTrainingElement
		(new double[]{1, 0}, new double[]{1}));
trainingSet.addElement(new SupervisedTrainingElement
		(new double[]{1, 1}, new double[]{0}));

接下来,我们创建神经网络。Neuroph 使用一行代码创建神经网络。

MultiLayerPerceptron network =
	new MultiLayerPerceptron(TransferFunctionType.TANH, 2, 3, 1);

这将创建与 Encog 创建的相同的神经网络。它将使用 TANH、2 个输入、3 个隐藏神经元和一个输出神经元。

对于训练,我们将使用动态反向传播。

DynamicBackPropagation train = new DynamicBackPropagation();
train.setNeuralNetwork(network);
network.setLearningRule(train);

现在我们开始循环进行训练迭代,直到训练到低于 1%。

int epoch = 1;
do
{
  train.doOneLearningIteration(trainingSet);
  System.out.println("Epoch " + epoch + ", error=" + train.getTotalNetworkError());
  epoch++;

  } while(train.getTotalNetworkError()>0.01);
  Once we are done, we display the trained network’s results.
  System.out.println("Neural Network Results:");
  for(TrainingElement element : trainingSet.trainingElements()) {
    network.setInput(element.getInput());
    network.calculate();
    Vector<Double> output = network.getOutput();
    SupervisedTrainingElement ste = (SupervisedTrainingElement)element;

  System.out.println(element.getInput().get(0) + ","
     + element.getInput().get(0)
     + ", actual=" + output.get(0) + ",ideal=" + ste.getDesiredOutput().get(0));

现在网络已完成训练。输出如下

Epoch 1, error=0.8077190599388583
Epoch 2, error=0.6743673707323136
Epoch 3, error=0.6059014056204383
Epoch 4, error=0.5701909436997877
Epoch 5, error=0.5508436432846441
Epoch 6, error=0.5399519500630455
Epoch 7, error=0.5336030007276679
Epoch 8, error=0.5297843855391863
Epoch 9, error=0.5274201294309249
Epoch 10, error=0.5259144756152534
...
Epoch 611, error=0.010100940088620529
Epoch 612, error=0.010028500485140078
Epoch 613, error=0.009956945579398092
Neural Network Results:
0.0,0.0, actual=0.014203702898690052,ideal=0.0
0.0,0.0, actual=0.8971874832025113,ideal=1.0
1.0,1.0, actual=0.909728369858769,ideal=1.0
1.0,1.0, actual=0.01578509009382128,ideal=0.0

您可以看到,误差降低到 1% 以下需要 613 次迭代。显然,反向传播远不如 Levenberg Marquard 健壮。自动反向传播是 Neuroph 目前提供的最先进的训练方法。这是 Encog 真正擅长的领域之一。Encog 提供高级训练方法,如 Resilient Propagation、Simulated Annealing、Genetic Training、Levenberg Marquard 和 Scaled Conjugate Gradient。

然而,Neuroph 对反向传播的自动改进确实比仅使用标准反向传播的神经网络框架具有优势。

在 JOONE 中创建 XOR 运算符

JOONE 是我们正在检查的最古老的神经网络框架。Encog 和 Neuroph 都始于 2008 年。JOONE 可追溯到 2001 年。JOONE 并不积极支持,尽管有许多系统是使用它实现的。JOONE 使用反向传播。JOONE 拥有声称支持 Resilient Propagation 的类,但我未能使其正常工作。JOONE 也以“有 bug”而闻名,而且因为它不再是一个活跃的项目,这可能会使事情变得困难。

我们检查的最后两个框架具有相当简单的 API,使程序员可以轻松创建神经网络。JOONE 则不然。JOONE 的 API 是一个庞然大物。它隐藏的内容不多,并且很难使其正常工作。在这三个示例中,这一个花费了我最长的时间。它也是最长的。

JOONE 定义数组来接受输入。

public static double XOR_INPUT[][] = { { 0.0, 0.0 }, { 1.0, 0.0 },
{ 0.0, 1.0 }, { 1.0, 1.0 } };
public static double XOR_IDEAL[][] = { { 0.0 }, { 1.0 }, { 1.0 }, { 0.0 } };

我们将为输入创建一个线性层。

LinearLayer	input = new LinearLayer();
SigmoidLayer	hidden = new SigmoidLayer();
SigmoidLayer	output = new SigmoidLayer();

隐藏层和输出层都是 sigmoid。接下来,标记层。

input.setLayerName("input");
hidden.setLayerName("hidden");
output.setLayerName("output");

我们设置每个层的神经元数量。

input.setRows(2);
hidden.setRows(3);
output.setRows(1);

必须创建突触以连接层。

FullSynapse synapse_IH = new FullSynapse();	/* input -&gtl hidden conn. */
FullSynapse synapse_HO = new FullSynapse();	/* hidden -&gtl output conn. */

我们应该标记突触。

synapse_IH.setName("IH");
synapse_HO.setName("HO");

现在将突触实际连接到层。

input.addOutputSynapse(synapse_IH);
hidden.addInputSynapse(synapse_IH);

突触的另一侧已连接。

hidden.addOutputSynapse(synapse_HO);
output.addInputSynapse(synapse_HO);

这是我非常讨厌 JOONE 的一个功能。在 Neuroph 和 Encog 中,您创建一个神经网络然后训练它。使用 JOONE,您必须物理更改网络才能在训练和使用之间切换。这里我们正在设置训练。我们将使用内存突触将 XOR 的数组馈送到网络。

MemoryInputSynapse  inputStream = new MemoryInputSynapse();

我们必须指定要使用的数组和要使用的列。

inputStream.setInputArray(XOR_INPUT);
inputStream.setAdvancedColumnSelector("1,2");

我们将输入流添加到突触。

input.addInputSynapse(inputStream);

我们创建一个教学突触来训练网络。

TeachingSynapse trainer = new TeachingSynapse();
MemoryInputSynapse samples = new MemoryInputSynapse();

我们还必须指定理想输出。

samples.setInputArray(XOR_IDEAL);
samples.setAdvancedColumnSelector("1");
trainer.setDesired(samples);

我们将训练器连接起来。

output.addOutputSynapse(trainer);

现在我们创建一个神经网络来容纳整个结构。

this.nnet = new NeuralNet();

层现在已添加到网络中。

nnet.addLayer(input, NeuralNet.INPUT_LAYER);
nnet.addLayer(hidden, NeuralNet.HIDDEN_LAYER);
nnet.addLayer(output, NeuralNet.OUTPUT_LAYER);

我们创建一个监视器来监视训练过程。

this.monitor = nnet.getMonitor();
monitor.setTrainingPatterns(4);	// # of rows (patterns) contained in the input file
monitor.setTotCicles(1000);	// How many times the net must be trained on the input patterns
monitor.setLearningRate(0.7);
monitor.setMomentum(0.3);
monitor.setLearning(true);	// The net must be trained
monitor.setSingleThreadMode(true);  // Set to false for multi-thread mode
/* The application registers itself as monitor's listener so it can receive
the notifications of termination from the net. */
monitor.addNeuralNetListener(this);
}

以下是 JOONE 示例的输出。

...
Epoch: 4991 Error:0.01818486701740271
Epoch: 4992 Error:0.018182516392244434
Epoch: 4993 Error:0.01818016665209608
Epoch: 4994 Error:0.018177817796408376
Epoch: 4995 Error:0.018175469824632386
Epoch: 4996 Error:0.01817312273621985
Epoch: 4997 Error:0.01817077653062278
Epoch: 4998 Error:0.018168431207293823
Epoch: 4999 Error:0.018166086765686006

您可以看到,它花费了近 5000 次迭代,JOONE 尚未像 Neuroph 那样训练得好。这是因为它使用的是常规反向传播。Neuroph 使用的自动传播会改变学习率和阈值以优化学习过程。这里的差异显而易见。

结论

在本文中,我向您展示了解决同一问题的三种不同方法。在我看来,Encog 和 Neuroph 是比 JOONE 更好的选择。JOONE 实际上不再受支持,尽管 JOONE 的迭代速度比 Neuroph 快得多,但 Neuroph 的自动训练使其优于 JOONE。此外,JOONE 非常难以使用。

Encog 比 Neuroph 具有更多的功能,并支持非常先进的训练方法。Encog 需要 18 次迭代,Neuroph 需要 613 次,JOONE 需要 5000 多次。我发现 Neuroph 的内部代码比 Encog 更容易理解。尽管我认为 Encog 和 Neuroph 的 API 是相当的。Encog 以速度为构建目标,这使其内部结构更加复杂。Encog 甚至可以利用您的 GPU 来提高训练速度。

在我的下一篇文章中,我将对这三者进行基准测试,并展示各种情况下的时间。

历史

  • 2010 年 6 月 3 日:初稿 
© . All rights reserved.