昨天我发布了一个关于反向传播算法第一部分的问题。
今天我在研究理解隐藏层。
抱歉问了很多问题,我已经阅读了许多相关网站和论文,但无论我读了多少,仍然很难将这些知识应用到实际代码中。
这是我正在分析的代码(我在使用Java,所以看Java的例子对我很有帮助)
// 更新隐藏层的权重 for (Neuron n : hiddenLayer) { ArrayList<Connection> connections = n.getAllInConnections(); for (Connection con : connections) { double output = n.getOutput(); double ai = con.leftNeuron.getOutput(); double sumKoutputs = 0; int j = 0; for (Neuron out_neu : outputLayer) { double wjk = out_neu.getConnection(n.id).getWeight(); double desiredOutput = (double) expectedOutput[j]; double ak = out_neu.getOutput(); j++; sumKoutputs = sumKoutputs + (-(desiredOutput - ak) * ak * (1 - ak) * wjk); } double partialDerivative = output * (1 - output) * ai * sumKoutputs; double deltaWeight = -learningRate * partialDerivative; double newWeight = con.getWeight() + deltaWeight; con.setDeltaWeight(deltaWeight); con.setWeight(newWeight + momentum * con.getPrevDeltaWeight()); } }
这里的一个真正问题是,我不完全了解所有方法的具体工作原理。
这段代码遍历隐藏层中的所有神经元,并逐一处理每个神经元到隐藏层中每个连接的连接。它获取每个连接的输出?所以,这是对输入连接的总和(可能通过Sig函数处理),然后乘以连接权重?然后“double ai”获取到这个特定节点的输入连接值?它是获取一个还是神经元输入的总和?
然后第三个for循环基本上是总和“out_neu.getConnection(n.id).getWeight()”,我不太理解。然后,期望输出是最终层节点的期望输出?然后ak是每个节点的实际输出(总和和激活函数)还是总和+激活*权重?
编辑
我开始编写自己的代码,有人可以看一下吗?
public class BackProp {
public int layers = 3; public int hiddenNeuronsNum = 5;public int outputNeuronsNum = 1;public static final double eta = .1;public double[][][] weights; //holds the network -- weights[layer][neuron][forwardConnetion] public void Back(){for(int neuron = 0; neuron < outputNeuronsNum; neuron++){ for(int connection = 0; connection < hiddenNeuronsNum; connection++){ double expOutput = expectedOutput[neuron]; //the expected output from the neuron we're on double actOutput = actualOutput[neuron]; double previousLayerOutput = holdNeuronValues[layers-1][neuron]; double delta = eta *(actOutput * (1-actOutput) *(expOutput - actOutput)* previousLayerOutput); weights[layers-1][neuron][connection] += delta; //OKAY M&M said YOU HAD THIS MESSED UP, 3rd index means end neuron, 2nd means start.. moving from left to right }} //Hidden Layer.. for(int neuron = 0; neuron < outputNeuronsNum; neuron++){ for(int connection = 0; connection < hiddenNeuronsNum; connection++){ double input = holdNeuronValues[layers-3][connection]; //what this neuron sends on, -2 for the next layer double output = holdNeuronValues[layers-2][connection]; double sumKoutputs = 0; //for the output layer for (int outputNeurons = 0; outputNeurons < weights[layers].length; outputNeurons++) { double wjk = weights[layers-2][neuron][outputNeurons]; //get the weight double expOutput = expectedOutput[outputNeurons]; double out = actualOutput[outputNeurons]; sumKoutputs += (-(expOutput - out) * wjk); } double partialDerivative = -eta * output * (1 - output) * input * sumKoutputs; }}
}}
回答:
这是标准的反向传播算法,它通过所有隐藏层反向传播误差。
除非我们在输出层,否则隐藏层中一个神经元的误差取决于后续层。假设我们有一个特定的神经元a,它通过突触连接到下一层的神经元i、j和k。我们还假设神经元a的输出是oa。那么神经元a的误差等于以下表达式(假设我们使用逻辑函数作为激活函数):
δa = oa(1 – oa) × (δiwai + δjwaj + δkwak)
这里,oa(1 – oa) 是激活函数导数的值。δi 是神经元i的误差,wai 是从i到a的突触(连接)的权重;其余项同理。
请注意,我们考虑了a连接到的每个下一层神经元的误差。还要注意,我们考虑了每个突触的权重。无需深入数学,从直觉上讲,a的误差不仅取决于a连接到的神经元的误差,还取决于a与下一层神经元之间的突触(连接)的权重。
一旦我们有了误差,我们需要更新前一层中每个连接到a的神经元的突触(连接)的权重(即我们反向传播误差)。假设我们有一个单一的神经元z连接到a。那么我们需要按以下方式调整wza:
wza = wza + (α × δa × oz)
如果前一层中有其他神经元(很可能有)连接到a,我们将使用相同的公式更新它们的权重。现在,如果你查看你的代码,你会发现这正是发生的事情。
你对隐藏层中的每个神经元执行以下操作:
- 你获取连接此神经元到前一层的突触(连接)列表。这是
connections = n.getAllInConnections()
部分。 - 对于每个连接,代码然后执行以下操作:
- 它获取神经元的输出(这是上述公式中的oa项)。
- 它获取连接到此神经元的神经元的输出(这是oz项)。
- 然后对于输出层中的每个神经元,它计算每个输出神经元的误差乘以从隐藏层中的我们神经元到输出层中一个神经元的权重的总和。这里,
sumKoutputs
与我们在表达式(δiwai + δjwaj + δkwak)中所做的相同。δi的值来自-(desiredOutput - ak) * ak * (1 - ak)
,因为这是你计算输出层的误差的方式;你可以简单地将输出层神经元的激活函数导数乘以实际输出与期望输出之间的差异。最后,你可以看到我们将整个东西乘以wjk;这与我们公式中的wai项相同。 - 我们现在有了所有需要的值,可以将它们代入我们的公式中,以调整从前一层连接到我们神经元的每个突触的权重。代码的问题在于它以稍微不同的方式计算了一些东西:
- 在我们的公式中,我们有oa(1 – oa) × (δiwai + δjwaj + δkwak)来计算神经元a的误差。但在代码中,它通过包括
ai
来计算partialDerivative
。在我们的术语中,这相当于oa(1 – oa) × oz × (δiwai + δjwaj + δkwak)。数学上它是有效的,因为后来我们无论如何都会将它乘以学习率(α × δa × oz),所以它完全相同;不同之处仅在于代码提前执行了对oz的乘法。 - 然后它计算
deltaWeight
,这是我们公式中的(α × δa × oz)。在代码中,α是learningRate
。 - 然后我们通过将增量添加到当前权重来更新权重。这与wza + (α × δa × oz)相同。
- 在我们的公式中,我们有oa(1 – oa) × (δiwai + δjwaj + δkwak)来计算神经元a的误差。但在代码中,它通过包括
- 现在事情有点不同。你可以看到代码没有直接设置权重,而是处理
momentum
。你可以看到通过使用momentum
,我们将前一个增量的分数添加到新权重中。这是在神经网络中使用的技术,以确保网络不会陷入局部最小值。动量项给我们一个小“推动”以摆脱局部最小值(误差表面的“井”;在神经网络中,我们遍历误差表面以找到误差最低的一个,但我们可能会陷入一个“井”中,这个“井”不如最优解“深”),并确保我们可以“收敛”到一个解。但你必须小心,因为如果你设置得太高,你可能会超过最优解。一旦它使用动量计算出新权重,它就会为连接(突触)设置它。
我希望这个解释对你来说更清楚了。数学有点难理解,但一旦你弄明白了,它就说得通了。我认为这里的主要问题是代码的编写方式略有不同。你可以查看我编写的这里的代码,它实现了反向传播算法;我作为一个班级项目做了这个。它基本上与我上面描述的公式一致,所以你应该能够轻松地跟随它。你还可以查看我制作的这个视频,我在其中解释了反向传播算法。