神经网络中的反向传播算法理解困难

17

我有困难理解误差反向传播算法。我阅读了很多并搜索了很多,但是我无法理解为什么我的神经网络不起作用。我想确认我每个部分都做得对。

这是我的神经网络在初始化时以及在输入 [1, 1] 和输出 [0] 设置第一行时的情况(您可以看到,我试图做异或神经网络):

My Neural Network

我有3个层:输入层、隐藏层和输出层。第一层(输入层)和隐藏层各包含2个神经元,每个神经元中有2个突触。最后一层(输出层)也包含一个神经元,其中有2个突触。

突触包含权重和其先前的 delta 值(在开始时,它为 0)。与突触相连的输出可以在与突触相关联的 sourceNeuron 中找到,如果没有源神经元(例如在输入层中),则可以在输入数组中找到。

Layer.java 类包含神经元列表。在我的 NeuralNetwork.java 中,我初始化神经网络,然后在我的训练集中进行循环。在每次迭代中,我替换输入和输出值,并在我的误差反向传播算法上调用 train 方法,该算法为当前数据集运行特定次数(暂定为 1000 次 epoch)。

我使用的激活函数是sigmoid函数。

训练集和验证集为(input1,input2,output):

1,1,0
0,1,1
1,0,1
0,0,0

这是我的 Neuron.java 实现:

public class Neuron {

    private IActivation activation;
    private ArrayList<Synapse> synapses; // Inputs
    private double output; // Output
    private double errorToPropagate;

    public Neuron(IActivation activation) {
        this.activation = activation;
        this.synapses = new ArrayList<Synapse>();
        this.output = 0;
        this.errorToPropagate = 0;
    }

    public void updateOutput(double[] inputs) {
        double sumWeights = this.calculateSumWeights(inputs);

        this.output = this.activation.activate(sumWeights);
    }

    public double calculateSumWeights(double[] inputs) {
        double sumWeights = 0;

        int index = 0;
        for (Synapse synapse : this.getSynapses()) {
            if (inputs != null) {
                sumWeights += synapse.getWeight() * inputs[index];
            } else {
                sumWeights += synapse.getWeight() * synapse.getSourceNeuron().getOutput();
            }

            index++;
        }

        return sumWeights;
    }

    public double getDerivative() {
        return this.activation.derivative(this.output);
    }

    [...]
}

Synapse.java包含:

public Synapse(Neuron sourceNeuron) {
    this.sourceNeuron = sourceNeuron;
    Random r = new Random();
    this.weight = (-0.5) + (0.5 - (-0.5)) * r.nextDouble();
    this.delta = 0;
}

[... getter and setter ...]

我的类BackpropagationStrategy.java中的train方法运行一个while循环,并在训练集的一行上进行1000次(epoch)后停止。代码如下:

this.forwardPropagation(neuralNetwork, inputs);

this.backwardPropagation(neuralNetwork, expectedOutput);

this.updateWeights(neuralNetwork);

以下是上述方法的全部实现(学习率为0.45,动量为0.9):

public void forwardPropagation(NeuralNetwork neuralNetwork, double[] inputs) {

    for (Layer layer : neuralNetwork.getLayers()) {

        for (Neuron neuron : layer.getNeurons()) {
            if (layer.isInput()) {
                neuron.updateOutput(inputs);
            } else {
                neuron.updateOutput(null);
            }
        }
    }
}

public void backwardPropagation(NeuralNetwork neuralNetwork, double realOutput) {

    Layer lastLayer = null;

    // Loop à travers les hidden layers et le output layer uniquement
    ArrayList<Layer> layers = neuralNetwork.getLayers();
    for (int i = layers.size() - 1; i > 0; i--) {
        Layer layer = layers.get(i);

        for (Neuron neuron : layer.getNeurons()) {

            double errorToPropagate = neuron.getDerivative();

            // Output layer
            if (layer.isOutput()) {

                errorToPropagate *= (realOutput - neuron.getOutput());
            }
            // Hidden layers
            else {
                double sumFromLastLayer = 0;

                for (Neuron lastLayerNeuron : lastLayer.getNeurons()) {
                    for (Synapse synapse : lastLayerNeuron.getSynapses()) {
                        if (synapse.getSourceNeuron() == neuron) {
                            sumFromLastLayer += (synapse.getWeight() * lastLayerNeuron.getErrorToPropagate());

                            break;
                        }
                    }
                }

                errorToPropagate *= sumFromLastLayer;
            }

            neuron.setErrorToPropagate(errorToPropagate);
        }

        lastLayer = layer;
    }
}

public void updateWeights(NeuralNetwork neuralNetwork) {

    for (int i = neuralNetwork.getLayers().size() - 1; i > 0; i--) {

        Layer layer = neuralNetwork.getLayers().get(i);

        for (Neuron neuron : layer.getNeurons()) {

            for (Synapse synapse : neuron.getSynapses()) {

                double delta = this.learningRate * neuron.getError() * synapse.getSourceNeuron().getOutput();

                synapse.setWeight(synapse.getWeight() + delta + this.momentum * synapse.getDelta());

                synapse.setDelta(delta);
            }
        }
    }
}

对于验证集,我只运行了这个:

this.forwardPropagation(neuralNetwork, inputs);

然后检查输出层神经元的输出。

我做错了什么吗?需要一些解释...

这是我在1000个时期之后得到的结果:

Real: 0.0
Current: 0.025012156926937503
Real: 1.0
Current: 0.022566830709341495
Real: 1.0
Current: 0.02768416343491415
Real: 0.0
Current: 0.024903432706154027

为什么输入层的突触没有被更新?到处都写着只更新隐藏层和输出层,但是这完全是错误的!它不会只更新第一个训练集输出(0.0),而不是更新到1.0。

更新1

这是对此集合进行一次迭代的结果:[1.0,1.0,0.0]。以下是前向传播方法的结果:

=== Input Layer

== Neuron #1

= Synapse #1
Weight: -0.19283583155573614
Input: 1.0

= Synapse #2
Weight: 0.04023817185601586
Input: 1.0

Sum: -0.15259765969972028
Output: 0.461924442180935

== Neuron #2

= Synapse #1
Weight: -0.3281099260608612
Input: 1.0

= Synapse #2
Weight: -0.4388250065958519
Input: 1.0

Sum: -0.7669349326567131
Output: 0.31714251453174147

=== Hidden Layer

== Neuron #1

= Synapse #1
Weight: 0.16703288052854093
Input: 0.461924442180935

= Synapse #2
Weight: 0.31683996162148054
Input: 0.31714251453174147

Sum: 0.17763999229679783
Output: 0.5442935820534444

== Neuron #2

= Synapse #1
Weight: -0.45330313978424686
Input: 0.461924442180935

= Synapse #2
Weight: 0.3287014377113835
Input: 0.31714251453174147

Sum: -0.10514659949771789
Output: 0.47373754172497556

=== Output Layer

== Neuron #1

= Synapse #1
Weight: 0.08643751629154495
Input: 0.5442935820534444

= Synapse #2
Weight: -0.29715579267218695
Input: 0.47373754172497556

Sum: -0.09372646936373039
Output: 0.47658552081912403

更新 2

我可能存在偏见问题。我将在此答案的帮助下进行研究:神经网络中偏差的作用。它不会在下一个数据集中移回...


我相信如果有人发现错误,他们一定会告诉你,不需要再问了。然而,正如我所提到的,仅仅通过看代码很难诊断其他人的代码。特别是当涉及到像神经网络这样复杂的东西时。请尝试使用调试器并缩小问题范围,也许可以使用我在之前评论中推荐的方法(我没有注意到您的训练集已经很简单 - 所以没关系,应该很容易看出一个神经元的输出应该向1移动,但实际上却更接近0)。 - BartoszKP
1
我不记得曾经需要存储这个值,如果我没记错的话,它只是为了传播和计算增量值而需要一次。然而,在你的版本中可能需要。我会称传播误差为.... propagatedError :) 在你的情况下(但请注意我可能误解了你的代码),似乎更多的是将误差传播到前一层,因此也许不是“传播误差”,而是“要传播的误差”。在这种情况下,我会称之为...(惊喜!)errorToPropagate - BartoszKP
1
我修改了名称和我的神经元类。导数仅应用于输出层而不是隐藏层。此外,我发现一个错误,即我没有正确链接我的隐藏层和输出层。现在我有更好的结果,但它总是转到第一组的第一个输出...我将进一步调查! - Annie Caron
1
偏置对于解决XOR问题至关重要。如果没有偏置,所有的分离平面(线)都会经过原点。例如,无法像这样将(0,0)与(0,1)分开。 - BartoszKP
您可能会对这个答案感兴趣:http://stackoverflow.com/a/38767930/5082406 - Abhijay Ghildyal
显示剩余10条评论
1个回答

0

我终于找到了问题所在。对于XOR,我不需要任何偏差,并且它正在收敛到期望值。当你将最终输出四舍五入时,我得到了完全一样的输出。需要的是训练,然后验证,再次训练,直到神经网络满意为止。我以前是针对每个集合进行训练,直到满意,但没有整个集合一遍又一遍地训练。

// Initialize the Neural Network
algorithm.initialize(this.numberOfInputs);

int index = 0;
double errorRate = 0;

// Loop until satisfaction or after some iterations
do {
    // Train the Neural Network
    algorithm.train(this.trainingDataSets, this.numberOfInputs);

    // Validate the Neural Network and return the error rate
    errorRate = algorithm.run(this.validationDataSets, this.numberOfInputs);

    index++;
} while (errorRate > minErrorRate && index < numberOfTrainValidateIteration);

有了真实数据后,我需要一个偏差值,因为输出开始发散。这是我添加偏差的方法:

Neuron.java类中,我添加了一个带有权重和输出为1.0的偏置突触。我将它与所有其他突触相加,然后放入我的激活函数中。

public class Neuron implements Serializable {

    [...]

    private Synapse bias;

    public Neuron(IActivation activation) {
        [...]
        this.bias = new Synapse(this);
        this.bias.setWeight(0.5); // Set initial weight OR keep the random number already set
    }

    public void updateOutput(double[] inputs) {
        double sumWeights = this.calculateSumWeights(inputs);

        this.output = this.activation.activate(sumWeights + this.bias.getWeight() * 1.0);
    }

    [...]

BackPropagationStrategy.java 文件中,我在 updateWeights 方法中更改了每个偏差的权重和 delta,并将其重命名为 updateWeightsAndBias。
public class BackPropagationStrategy implements IStrategy, Serializable {

    [...]

    public void updateWeightsAndBias(NeuralNetwork neuralNetwork, double[] inputs) {

        for (int i = neuralNetwork.getLayers().size() - 1; i >= 0; i--) {

            Layer layer = neuralNetwork.getLayers().get(i);

            for (Neuron neuron : layer.getNeurons()) {

                [...]

                Synapse bias = neuron.getBias();
                double delta = learning * 1.0;
                bias.setWeight(bias.getWeight() + delta + this.momentum * bias.getDelta());

                bias.setDelta(delta);
            }
        }
    }

    [...]

有了真实数据,网络正在收敛。现在需要进行修剪工作,找到学习率、动量、误差率、神经元数量、隐藏层数量等完美变量组合(如果可能的话)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接