反向传播算法产生糟糕的结果

8
我正在尝试使用前馈神经网络和反向传播来解决经典的手写数字识别问题,使用MNIST数据集。我正在使用Michael Nielsen的书学习基本知识和3Blue1Brown的YouTube视频学习反向传播算法。
我在一段时间前完成了编写,并一直在调试,因为结果非常糟糕。在最佳情况下,网络可以在1个时期后识别约4000/10000个样本,而该数字仅在以下时期下降,这使我相信反向传播算法存在问题。我已经在索引地狱中挣扎了几天,试图调试此问题,但无法找出问题所在,希望能提供任何帮助。
一些背景信息:1)我没有使用任何矩阵乘法和外部框架,而是使用for循环完成所有操作,因为这是我从视频中学到的。2)与书本不同,我将权重和偏差存储在同一个数组中。每个层的偏差是该层权重矩阵末尾的一列。
最后,这是神经网络类的Backpropagate方法的代码,它在UpdateMiniBatch中被调用,而UpdateMiniBatch本身在SGD中被调用:
/// <summary>
/// Returns the partial derivative of the cost function on one sample with respect to every weight in the network.
/// </summary>
public List<double[,]> Backpropagate(ITrainingSample sample)
{
    // Forwards pass
    var (weightedInputs, activations) = GetWeightedInputsAndActivations(sample.Input);

    // The derivative with respect to the activation of the last layer is simple to compute: activation - expectedActivation
    var errors = activations.Last().Select((a, i) => a - sample.Output[i]).ToArray();

    // Backwards pass
    List<double[,]> delCostOverDelWeights = Weights.Select(x => new double[x.GetLength(0), x.GetLength(1)]).ToList();
    List<double[]> delCostOverDelActivations = Weights.Select(x => new double[x.GetLength(0)]).ToList();
    delCostOverDelActivations[delCostOverDelActivations.Count - 1] = errors;

    // Comment notation:
    // Cost function: C
    // Weight connecting the i-th neuron on the (l + 1)-th layer to the j-th neuron on the l-th layer: w[l][i, j]
    // Bias of the i-th neuron on the (l + 1)-th layer: b[l][i]
    // Activation of the i-th neuon on the l-th layer: a[l][i]
    // Weighted input of the i-th neuron on the l-th layer: z[l][i] // which doesn't make sense on layer 0, but is left for index convenience
    // Notice that weights, biases, delCostOverDelWeights and delCostOverDelActivation all start at layer 1 (the 0-th layer is irrelevant to their meanings) while activations and weightedInputs strat at the 0-th layer

    for (int l = Weights.Count - 1; l >= 0; l--)
    {
        //Calculate ∂C/∂w for the current layer:
        for (int i = 0; i < Weights[l].GetLength(0); i++)
            for (int j = 0; j < Weights[l].GetLength(1); j++)
                delCostOverDelWeights[l][i, j] = // ∂C/∂w[l][i, j]
                    delCostOverDelActivations[l][i] * // ∂C/∂a[l + 1][i]
                    SigmoidPrime(weightedInputs[l + 1][i]) * // ∂a[l + 1][i]/∂z[l + 1][i] = ∂(σ(z[l + 1][i]))/∂z[l + 1][i] = σ′(z[l + 1][i])
                    (j < Weights[l].GetLength(1) - 1 ? activations[l][j] : 1); // ∂z[l + 1][i]/∂w[l][i, j] = a[l][j] ||OR|| ∂z[l + 1][i]/∂b[l][i] = 1

        // Calculate ∂C/∂a for the previous layer(a[l]):
        if (l != 0)
            for (int i = 0; i < Weights[l - 1].GetLength(0); i++)
                for (int j = 0; j < Weights[l].GetLength(0); j++)
                    delCostOverDelActivations[l - 1][i] += // ∂C/∂a[l][i] = sum over j:
                        delCostOverDelActivations[l][j] * // ∂C/∂a[l + 1][j]
                        SigmoidPrime(weightedInputs[l + 1][j]) * // ∂a[l + 1][j]/∂z[l + 1][j] = ∂(σ(z[l + 1][j]))/∂z[l + 1][j] = σ′(z[l + 1][j])
                        Weights[l][j, i]; // ∂z[l + 1][j]/∂a[l][i] = w[l][j, i]
    }

    return delCostOverDelWeights;
}

获取加权输入和激活:
public (List<double[]>, List<double[]>) GetWeightedInputsAndActivations(double[] input)
{
    List<double[]> activations = new List<double[]>() { input }.Concat(Weights.Select(x => new double[x.GetLength(0)])).ToList();
    List<double[]> weightedInputs = activations.Select(x => new double[x.Length]).ToList();

    for (int l = 0; l < Weights.Count; l++)
        for (int i = 0; i < Weights[l].GetLength(0); i++)
        {
            double value = 0;
            for (int j = 0; j < Weights[l].GetLength(1) - 1; j++)
                value += Weights[l][i, j] * activations[l][j];// weights
            weightedInputs[l + 1][i] = value + Weights[l][i, Weights[l].GetLength(1) - 1];// bias
            activations[l + 1][i] = Sigmoid(weightedInputs[l + 1][i]);
        }

    return (weightedInputs, activations);
}

整个神经网络以及其他所有内容都可以在这里找到。
编辑:在对存储库进行了许多重大更改后,上面的链接可能不再可用,但希望考虑到答案,它应该是无关紧要的。为了完整起见,这是一个更改后的存储库的功能链接

3
尝试仅使用2个神经元并手动验证中间结果。 您可能还希望检查SigmoidPrime,因为它似乎与您的实现不同。 - mostanes
1
@mostanes 感谢您的评论,经过五天的努力,我终于找到了问题并进行了修复。在我发现问题之前,我进行了一次测试,在 [2, 3, 1] 网络上手动计算了 9 个权重的梯度,而且结果很好。我的 SigmoidPrime 只是一种更巧妙的写法,我从书中学到了这个方法。您可以尝试在纸上手动验证它。 - H. Saleh
1
你最后一行的链接已经失效了。 - Matthieu
1个回答

4

问题已解决。原因在于,我没有将像素输入值除以255。除此之外,其他所有功能都正常运作,现在我在第一次训练中得到了+9000/10000的结果。


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