(Java) 隐藏层反向传播的偏导数

(Java) Partial Derivatives for Back Propagation of Hidden Layer

昨天我发布了一个关于 的问题。

今天我正在努力了解隐藏层。

抱歉问了很多问题,我已经阅读了几个关于这个主题的网站和论文,但是无论我读了多少,我仍然很难将它应用到实际代码中。

这是我正在分析的代码(我在 Java 工作,所以很高兴看到 Java 示例)

// update weights for the hidden layer
    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()"。那么,期望的输出就是最后一层节点的desiredOutput?那么ak是每个节点的实际输出(求和和激活函数)还是求和+激活*权重?

编辑

我开始编写自己的代码,有人可以看看吗?

public class 反向传播 {

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,其突触将其连接到神经元 ij,和 k 在下一层。我们还假设神经元 a 的输出是 oa。那么神经元 a 的误差等于以下表达式(假设我们使用逻辑函数作为激活函数):

δa = oa (1 - oa) × (δiwai + δjwaj + δkwak)

这里,oa(1 - oa)是激活函数的导数的值。 δi是神经元iw的误差ai是分配给从ia的突触(连接)的权重;这同样适用于其余条款。

请注意我们如何考虑 每个 神经元在 next 层中的误差 a 连接到。另请注意,我们正在考虑分配给每个突触的权重。无需深入数学,直觉上就可以理解 a 的错误不仅取决于 a 连接到的神经元上的错误,但也取决于 a 和下一层神经元之间的突触(连接)的权重。

一旦出现错误,我们需要更新中连接到[=的每个神经元的突触(连接)权重40=]a(即,我们反向传播错误)。让我们假设我们有一个连接到 a 的单个神经元 z。那么我们要调整wza如下:

wza = wza + (α × δa × oz)

如果前一层有其他神经元(可能有)连接到 a,我们也将使用相同的公式更新它们的权重。现在,如果您查看您的代码,您会发现这正是正在发生的事情。

您正在为隐藏层中的每个神经元执行以下操作:

  • 您正在获取将 this 神经元连接到 previous 层的突触(连接)列表。这是 connections = n.getAllInConnections() 部分。
  • 对于每个连接,代码执行以下操作:
    • 它获取神经元的输出(这是上面公式中的 oa 项)。
    • 它获取连接到 this 神经元的神经元输出(这是 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 × (δiw ai + δjwaj + δkwak)。在数学上它是可行的,因为后来我们最终将它乘以学习率 (α × δa × oz), 所以完全一样;不同之处在于代码执行 oz 的乘法。
      • 然后计算deltaWeight,即(α×δa×oz) 在我们的公式中。代码中,α为learningRate.
      • 然后我们通过将增量添加到当前权重来更新权重。这与wza+(α×δa×oz).
    • 现在情况有点不同了。可以看到代码并没有直接设置权重,而是对momentum进行了处理。您可以看到,通过使用 momentum,我们将之前增量的一小部分添加到新权重中。这是神经网络中使用的一种技术,可确保网络不会陷入局部最小值。动量项给了我们一点 "push" 来摆脱局部最小值(误差曲面中的 "well";使用神经网络,我们遍历误差曲面以找到误差最低的曲面,但我们可能会陷入一个 "well" 中,它不像 "deep" 那样是最佳解决方案),并确保我们可以 "converge" 找到一个解决方案。但是你必须要小心,因为如果你把这个设置得太高,你可能会超过你的最佳解决方案。一旦它使用动量计算出新的权重,它就会为连接(突触)设置它。

我希望这个解释让你更清楚。数学有点难以进入,但一旦你弄明白了,它就有意义了。我认为这里的主要问题是代码的编写方式略有不同。您可以看一下我在解释反向传播算法时编写的一些代码 here that I wrote that implements the backpropagation algorithm; I did this as a class project. It runs pretty much along the same lines as the formulas I described above and so you should be able to follow through it easily. You can also take a look at this video