java - 神经网络反向传播算法卡在异或训练模式上

标签 java algorithm artificial-intelligence machine-learning neural-network

概览

所以我想掌握神经网络的机制。我仍然没有完全掌握它背后的数学原理,但我想我知道如何实现它。我目前有一个可以学习 AND、OR 和 NOR 训练模式的神经网络。但是,我似乎无法让它实现 XOR 模式。我的前馈神经网络由2个输入、3个隐藏层和1个输出组成。权重和偏差随机设置在-0.5和0.5之间,并且输出是使用S形激活函数

生成的

算法

到目前为止,我猜我在下面描述的训练算法中犯了一个错误:

  1. 对于输出层中的每个神经元,提供一个 error 值,即 desiredOutput - actualOutput --转到步骤 3<
  2. 对于隐藏层或输入层(向后工作)中的每个神经元,提供一个error 值,该值是所有前向连接权重的总和 * 另一端神经元的 errorGradient连接 --转到第 3 步
  3. 对于每个神经元,使用提供的error 值,生成一个error gradient,它等于output * (1-output) * error。 --转到第 4 步
  4. 对于每个神经元,将偏置调整为等于current bias + LEARNING_RATE * errorGradient。然后调整每个后向连接的权重等于当前权重+LEARNING_RATE *连接另一端神经元的输出*本神经元的errorGradient

我正在在线训练我的神经网络,所以它在每个训练样本之后运行。

代码

这是运行神经网络的主要代码:

private void simulate(double maximumError) {

    int errorRepeatCount = 0;
    double prevError = 0;

    double error; // summed squares of errors
    int trialCount = 0;

    do {

        error = 0;

        // loop through each training set
        for(int index = 0; index < Parameters.INPUT_TRAINING_SET.length; index++) {

            double[] currentInput = Parameters.INPUT_TRAINING_SET[index];
            double[] expectedOutput = Parameters.OUTPUT_TRAINING_SET[index];
            double[] output = getOutput(currentInput);

            train(expectedOutput);

            // Subtracts the expected and actual outputs, gets the average of those outputs, and then squares it.
            error += Math.pow(getAverage(subtractArray(output, expectedOutput)), 2); 



        }

    } while(error > maximumError);

现在 train() 函数:

public void train(double[] expected) {

    layers.outputLayer().calculateErrors(expected);

    for(int i = Parameters.NUM_HIDDEN_LAYERS; i >= 0; i--) {
        layers.allLayers[i].calculateErrors();
    }

}

输出层calculateErrors()函数:

public void calculateErrors(double[] expectedOutput) {

    for(int i = 0; i < numNeurons; i++) {

        Neuron neuron = neurons[i];
        double error = expectedOutput[i] - neuron.getOutput();
        neuron.train(error);

    }

}

普通(隐藏和输入)层 calculateErrors() 函数:

public void calculateErrors() {

    for(int i = 0; i < neurons.length; i++) {

        Neuron neuron = neurons[i];

        double error = 0;

        for(Connection connection : neuron.forwardConnections) {

            error += connection.output.errorGradient * connection.weight;

        }

        neuron.train(error);

    }

}

完整的神经元类:

package neuralNet.layers.neurons;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import neuralNet.Parameters;
import neuralNet.layers.NeuronLayer;

public class Neuron {

private double output, bias;
public List<Connection> forwardConnections = new ArrayList<Connection>(); // Forward = layer closer to input -> layer closer to output
public List<Connection> backwardConnections = new ArrayList<Connection>(); // Backward = layer closer to output -> layer closer to input

public double errorGradient;
public Neuron() {

    Random random = new Random();
    bias = random.nextDouble() - 0.5;

}

public void addConnections(NeuronLayer prevLayer) {

    // This is true for input layers. They create their connections differently. (See InputLayer class)
    if(prevLayer == null) return;

    for(Neuron neuron : prevLayer.neurons) {

        Connection.createConnection(neuron, this);

    }

}

public void calcOutput() {

    output = bias;

    for(Connection connection : backwardConnections) {

        connection.input.calcOutput();
        output += connection.input.getOutput() * connection.weight;

    }

    output = sigmoid(output);

}

private double sigmoid(double output) {
    return 1 / (1 + Math.exp(-1*output));
}

public double getOutput() {
    return output;
}

public void train(double error) {

    this.errorGradient = output * (1-output) * error;

    bias += Parameters.LEARNING_RATE * errorGradient;

    for(Connection connection : backwardConnections) {

        // for clarification: connection.input refers to a neuron that outputs to this neuron
        connection.weight += Parameters.LEARNING_RATE * connection.input.getOutput() * errorGradient;

    }

}

}

结果

当我训练 AND、OR 或 NOR 时,网络通常可以在大约 1000 个时期内收敛,但是当我训练 XOR 时,输出变得固定并且永远不会收敛。那么,我做错了什么?有什么想法吗?

编辑

听从其他人的建议,我重新开始并在没有类的情况下实现了我的神经网络......并且它有效。我仍然不确定我的问题出在上面的代码中,但它就在那里。

最佳答案

这很令人惊讶,因为您正在使用足够大的网络(勉强)来学习 XOR。你的算法看起来不错,所以我真的不知道发生了什么。了解如何生成训练数据可能会有所帮助:您只是在处理样本 (1,0,1),(1,1,0),(0,1,1),(0,0, 0) 或类似的东西一遍又一遍?也许问题是随机梯度下降导致你跳到稳定的最小值周围。您可以尝试一些方法来解决此问题:也许从您的训练示例中随机抽样而不是重复它们(如果那是您正在做的)。或者,您也可以修改您的学习算法:

目前你有等同于:

weight(epoch) = weight(epoch - 1) + deltaWeight(epoch)
deltaWeight(epoch) = mu * errorGradient(epoch)

其中 mu 是学习率

一种选择是非常缓慢地降低mu的值。

另一种方法是更改​​ deltaWeight 的定义以包含“动量”

deltaWeight(epoch) = mu * errorGradient(epoch) + alpha * deltaWeight(epoch -1)

其中 alpha 是动量参数(介于 0 和 1 之间)。

从视觉上看,您可以将梯度下降视为尝试通过在曲面上放置一个物体来找到曲面的最小点,然后逐步移动该物体少量,其中一直指向向下倾斜的位置它目前位于。问题是你并没有真正进行梯度下降:相反,你进行随机梯度下降,你通过从一组训练 vector 中采样并沿着样本看起来向下的任何方向移动来移动方向。平均而言,在整个训练数据上,随机梯度下降应该有效,但不能保证它有效,因为你可能会陷入来回跳跃而永远不会取得进展的情况。慢慢降低学习率意味着你每次都采取越来越小的步骤,这样就不会陷入无限循环。

另一方面,动量使算法类似于滚动橡皮球。随着球的作用,它趋向于向下移动,但它也趋向于继续沿它之前的方向移动,如果它曾经处于下斜坡沿相同方向一段时间的延伸,它会加速。因此,球会跳过一些局部最小值,并且它会更有弹性地防止在目标上来回走动,因为这样做意味着要对抗动量。


有了一些代码并对此进行了更多思考,很明显您的问题出在训练早期层上。您成功学习的函数都是线性可分的,因此只有一个层被正确学习是有意义的。我同意 LiKao 的一般实现策略,尽管你的方法应该有效。我对如何调试它的建议是弄清楚输入层和输出层之间连接的权重的进展情况。

您应该发布 Neuron 的其余实现。

关于java - 神经网络反向传播算法卡在异或训练模式上,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9369198/

相关文章:

c++ - 如何将 for_each 与作为重载 operator() 的函数一起使用

artificial-intelligence - 11pt 平均精度指标是多少?

java - DataBinding - boolean 条件未按应有的方式评估

java - spark-3.0.1-bin-hadoop2.7无法启动,java:无此文件或目录

c# - 是否有标准算法将重叠对象平衡到桶中?

python - 为什么这个图形路由会重复

c# - 无法理解ID3算法

C++/SDL,井字轮流

java - Java 内的 SAP GUI 脚本

java - 带去抖功能的 RxJava2 缓冲区