难以理解神经网络中的反向传播算法
Posted
技术标签:
【中文标题】难以理解神经网络中的反向传播算法【英文标题】:Trouble Understanding the Backpropagation Algorithm in Neural Network 【发布时间】:2015-02-01 13:10:33 【问题描述】:我无法理解反向传播算法。我读了很多书,搜索了很多,但我不明白为什么我的神经网络不起作用。我想确认我做的每一部分都是正确的。
这是我的神经网络在初始化时以及当第一行输入 [1, 1] 和输出 [0] 设置时(如您所见,我正在尝试执行 XOR 神经网络):
我有 3 层:输入、隐藏和输出。第一层(输入)和隐藏层包含 2 个神经元,每个神经元有 2 个突触。最后一层(输出)也包含一个神经元和 2 个突触。
一个突触包含一个权重,它是之前的 delta(一开始是 0)。连接到突触的输出可以在与突触关联的 sourceNeuron 中找到,如果没有 sourceNeuron(如在输入层中),则可以在输入数组中找到。
Layer.java 类包含一个神经元列表。在我的 NeuralNetwork.java 中,我初始化了神经网络,然后在我的训练集中循环。在每次迭代中,我替换输入和输出值,并在我的 BackPropagation Algorithm 上调用 train,该算法针对当前集合运行一定时间(目前为 1000 次)。
我使用的 activation 函数是 sigmoid。
训练集AND验证集是(输入1,输入2,输出):
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);
这是上述方法的所有实现(learningRate = 0.45 和momentum = 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 个 epoch 之后的结果:
Real: 0.0
Current: 0.025012156926937503
Real: 1.0
Current: 0.022566830709341495
Real: 1.0
Current: 0.02768416343491415
Real: 0.0
Current: 0.024903432706154027
为什么输入层的突触没有更新?到处都写它只更新隐藏层和输出层。
如您所见,这是完全错误的!不会到 1.0 只到第一个训练集输出(0.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
我可能有偏见问题。我将在这个答案的帮助下进行调查:Role of Bias in Neural Networks。它不会移回下一个数据集,所以...
【问题讨论】:
你为你的函数和变量使用了令人困惑的名字。至少,它使你的代码难以理解,最多表明你对算法的理解还有些欠缺。例如,您使用this.error
存储输出的导数乘以误差(因此它是要传播的误差值,而不是该神经元中的误差)。 calculateSumWeights
似乎也错了:这个函数肯定不会计算权重的总和。尝试整理你的代码,并使用带有非常简单数据集的调试器(一两个示例,具有一两个属性)。
我应该将神经元的误差传播称为阈值吗?是什么名字?它可以帮助我找到一些答案。我将研究 sum 方法,但您是否发现它有问题?
我不记得我曾经需要存储这个值,IIRC 它只需要一次用于传播和计算增量值。但是,也许在您的版本中是需要的。我会调用传播的错误....propagatedError
:) 在你的情况下(但请注意我可能误解了你的代码),似乎更多的错误是传播到上一层,所以也许不是“传播的错误”但“传播的错误”。在这种情况下,我会称之为......(惊喜!)errorToPropagate
.
我修改了名称和我的神经元类。导数仅适用于输出层,而不适用于隐藏层。另外,我发现了一个错误,我没有正确链接我的隐藏层和输出层。我现在有更好的结果,但它总是转到第一组的第一个输出......我会进一步调查!
偏差对于解决 XOR 问题至关重要。没有偏差,所有分离平面(线)都通过原点。例如,不可能像这样将 (0,0) 与 (0,1) 分开。
【参考方案1】:
我终于找到了问题所在。对于 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 中,我更改了我重命名为 updateWeightsAndBias 的 updateWeights 方法中每个偏差的权重和增量。
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);
[...]
有了真实数据,网络正在融合。现在,寻找学习率、动量、错误率、神经元数量、隐藏层数量等的完美变量组合(如果可能的话)是一项修剪工作。
【讨论】:
以上是关于难以理解神经网络中的反向传播算法的主要内容,如果未能解决你的问题,请参考以下文章