神经网络反向传播算法陷入XOR训练模式

Overview

所以我完全掌握了它背后的数学,但我想我明白了如何实现它 . 我目前有一个可以学习AND,OR和NOR训练模式的神经网络 . 但是,我似乎无法实现XOR模式 . 我的 feed forward 神经网络由 2 inputs, 3 hidden, and 1 output. 组成 . 权重和偏差是在 -0.5 and 0.5 之间随机设置的,输出是用 sigmoidal activation function 生成的

Algorithm

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

  • 对于输出图层中的每个神经元,提供一个 errordesiredOutput - actualOutput - 到步骤3

  • 对于隐藏或输入层中的每个神经元(向后工作),提供 error 值,该值是所有 forward connection weights * the errorGradient of the neuron at the other end of the connection 的总和 - 到步骤3

  • 对于每个神经元,使用提供的 error 值生成 error gradient ,等于 output * (1-output) * error . - 到第4步

  • 对于每个神经元,将偏差调整为等于 current bias + LEARNING_RATE * errorGradient . 然后调整每个反向连接的权重等于 current weight + LEARNING_RATE * output of neuron at other end of connection * this neuron's errorGradient

我正在网上训练我的神经网络,所以这在每个训练样本之后运行 .

Code

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

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;

    }

}

}

Results

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

Edit

按照其他人的建议,我重新开始实施我的神经网络,没有课程......而且它有效 . 我仍然不确定我的问题出在上面的代码中,但是它存在于某个地方 .

回答(8)

2 years ago

这是令人惊讶的,因为您正在使用足够大的网络(几乎没有)来学习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 是学习率

一种选择是 very 慢慢减少 mu 的值 .

另一种方法是更改 deltaWeight 的定义以包含"momentum"

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

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

在视觉上,您可以将梯度下降看作是通过在该表面上放置一个物体来尝试找到曲面的最小点,然后逐步移动该物体的少量,其中任何指向都是根据当前的位置向下倾斜位于 . 问题在于你并不真正做渐变下降:相反,你会做随机梯度下降,你可以通过从一组训练向量中采样来移动方向,然后移动到样本看起来像是向下的任何方向 . 平均在整个训练数据中,随机梯度下降应该起作用,但不能保证,因为你可以进入一个你来回跳跃而不能取得进步的情况 . 慢慢降低学习速度意味着每次采取越来越小的步骤,因此不会陷入无限循环 .

另一方面,动量使算法成为类似于滚动橡胶球的东西 . 作为球的角色,它倾向于沿着向下的方向前进,但是它也倾向于继续前进的方向,并且如果它一直处于下坡倾斜相同方向一段时间它将会加速 . 因此,球将跳过一些局部最小值,并且它将更有弹性抵抗在目标上来回踩踏,因为这样做意味着抵抗动量的力量 .


有了一些代码并且更多地考虑了这一点,很明显你的问题在于训练早期层 . 您已经成功学习的函数都是线性可分的,因此只有单个层才能正确学习才有意义 . 虽然您的方法应该有效,但我同意LiKao的一般实施策略 . 我对如何调试它的建议是弄清楚输入层和输出层之间的连接权重的进展情况 .

你应该发布 Neuron 的其余实现 .

2 years ago

我不久前遇到了同样的问题 . 最后我找到了解决方案,如何用MLP算法编写解决XOR的代码 .

XOR问题似乎是一个容易学习的问题,但它不适用于MLP,因为它不是线性可分的 . 因此,即使你的MLP没问题(我的意思是你的代码中没有错误),你必须找到能够学习XOR问题的好参数 .

两个隐藏和一个输出神经元是好的 . 你需要设置的两件事:

  • 虽然你只有4个训练样本,你必须进行几千个纪元的训练 .

  • 如果使用sigmoid隐藏层但线性输出,网络将更快收敛

以下是详细说明和示例代码:http://freeconnection.blogspot.hu/2012/09/solving-xor-with-mlp.html

2 years ago

小提示 - 如果NN的输出似乎向 0.5 漂移,那么一切都还可以!

仅使用学习速率和偏差的算法太简单,无法快速学习XOR . 您可以增加纪元数或更改算法 .

我的建议是使用势头:

  • 1000个时代

  • learningRate = 0.3

  • 动量= 0.8
    从[0,1]中抽取

  • 个权重

  • 偏向绘制形式[-0.5,0.5]

一些关键的伪代码(假设后向和前向传播有效):

for every edge:
    previous_edge_weight_change = -1 * learningRate * edge_source_neuron_value * edge_target_neuron_delta + previous_edge_weight * momentum

    edge_weight += previous_edge_weight_change

for every neuron:
    previous_neuron_bias_change = -1 * learningRate * neuron_delta + previous_neuron_bias_change * momentum

    bias += previous_neuron_bias_change

2 years ago

我建议你生成一个网格(例如从[-5,-5]到[5,5],步长为0.5),在XOR上学习你的MLP并将其应用到网格中 . 用彩色绘图你可以看到某种边界 . 如果你在每次迭代时都这样做,你会看到边界的演变并且可以控制学习 .

2 years ago

自从我上次自己实施神经网络以来已经有一段时间了,但我认为你的错误在于:

bias += Parameters.LEARNING_RATE * errorGradient;

connection.weight += Parameters.LEARNING_RATE * connection.input.getOutput() * errorGradient;

这些行中的第一行根本不应该存在 . 偏差最好建模为神经元的输入,固定为1.这将使您的代码更简单,更清晰,因为您不必以任何特殊方式处理偏差 .

另一点是,我认为这两个表达式中的符号都是错误的 . 想想这样:

  • 你的渐变指向最陡峭的方向,所以如果你走向那个方向,你的错误就会变大 .

  • 你在这里做的是在权重中添加一些东西,以防错误已经是正面的,即你正在使它更积极 . 如果它是负数,你就会减少某些东西,即你使它更负面 .

除非我遗漏了关于错误定义或渐变计算的内容,否则应将这些行更改为:

bias -= Parameters.LEARNING_RATE * errorGradient;

connection.weight -= Parameters.LEARNING_RATE * connection.input.getOutput() * errorGradient;

我在其中一个早期实现中遇到了类似的错误,它导致完全相同的行为,即它导致在简单情况下学习的网络,但是一旦训练数据变得更复杂,就不再存在 .

2 years ago

LiKao的评论简化了我的实现并摆脱了面向对象的方面解决了我的问题 . 如上所述,算法中的缺陷是未知的,但是现在我的工作神经网络要小得多 .

请随意继续提供我之前实施的问题的洞察力,因为其他人可能会在将来遇到同样的问题 .

2 years ago

我在神经网络上有点生疏,但我认为用一个感知器实现XOR存在一个问题:基本上神经元能够通过直线分离两组解,但是一条直线对于异或问题......

Here应该是答案!

2 years ago

我看不出代码有什么问题,但是我的网络遇到了类似的问题而没有为XOR收敛,所以我想发布我的工作配置 .

3个输入神经元(其中一个是1.0的固定偏差)
3隐藏的神经元
1输出神经元

在-0.5和0.5之间随机选择的权重 .
Sigmoid激活功能 .

学习率= 0.2
动量= 0.4
时代= 50,000

融合了10/10次 .

我所犯的错误之一是没有将偏置输入连接到输出神经元,这意味着对于相同的配置,它只会收敛10次,而其他8次失败,因为1和1会输出0.5 .

另一个错误是没有做足够的时代 . 如果我只做了1000,那么每个测试用例的输出往往大约为0.5 . 对于每个测试用例,epochs> = 8000所以2000次,它开始看起来可能正在工作(但只有使用动量) .

当做50000个时期时,是否使用动量并不重要 .

我尝试的另一件事是不将sigmoid函数应用于输出神经元输出(我认为这是早先的帖子所建议的),但这破坏了网络,因为错误方程的输出*(1输出)部分现在可以是负面含义权重是以导致错误增加的方式更新的 .