当前位置: 首页 > 知识库问答 >
问题:

反向传播算法产生不良结果

申颖逸
2023-03-14

我正在尝试使用MNIST数据集,使用前馈神经网络和反向传播来解决经典的手写数字识别问题。我正在使用迈克尔·尼尔森的书来学习反向传播算法的要点和3Blue1Brown的youtube视频。

我在一段时间前写完了它,并一直在调试,因为结果很糟糕。在最好的情况下,网络在一个历元后可以识别大约4000/10000个样本,而这个数字只会在接下来的历元上下降,这让我相信反向传播算法存在一些问题。在过去的几天里,我一直沉浸在索引地狱中,试图调试它,但我不知道问题出在哪里,如果能帮我指出它,我将不胜感激。

一点背景知识:1)我没有使用任何矩阵乘法和外部框架,而是使用for循环进行所有操作,因为这是我从视频中学到的。2) 与本书不同,我将权重和偏差存储在同一个数组中。每个层的偏差是该层权重矩阵末尾的一列。

最后对于代码,这是NeuralNetwork类的反向传播方法,在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;
}

GetWeightedInputsAndActivations:

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);
}

整个NeuralNetwork以及其他一切都可以在这里找到。

编辑:在回购协议发生许多重大变化后,上述链接可能不再起作用,但考虑到答案,希望与此无关。为完整起见,这是指向已更改存储库的功能链接。

共有1个答案

帅德惠
2023-03-14

固定的问题是:我没有将像素输入除以255。其他一切似乎都正常工作,我现在在第一个时代得到9000/10000。

 类似资料:
  • 前面几节里我们使用了小批量随机梯度下降的优化算法来训练模型。在实现中,我们只提供了模型的正向传播(forward propagation)的计算,即对输入计算模型输出,然后通过autograd模块来调用系统自动生成的backward函数计算梯度。基于反向传播(back-propagation)算法的自动求梯度极大简化了深度学习模型训练算法的实现。本节我们将使用数学和计算图(computationa

  • 随时间反向传播(BPTT)算法 $$s_t = \tanh (Ux_t+Ws_{t-1})$$ $$\hat y_t=softmax(Vs_t)$$ RNN的损失函数定义为交叉熵损失: $$E_t(y_t,\hat y_t)=-y_t\log\hat y_t $$ $$E(y,\hat y)=\sum_{t}E_t(y_t, \hat y_t)=-\sum_{t}y_t\log\hat y_t$$

  • 在RNN模型里,我们讲到了RNN具有如下的结构,每个序列索引位置t都有一个隐藏状态h^{(t)}。 如果我们略去每层都有的$$o{(t)}, L{(t)}, y^{(t)}$$,则RNN的模型可以简化成如下图的形式: 图中可以很清晰看出在隐藏状态$$h{(t)}$$由$$x{(t)}$$和$$h{(t-1)}$$得到。得到$$h{(t)}$$后一方面用于当前层的模型损失计算,另一方面用于计算下一层

  • 卷积神经网络其实是神经网络特征学习的一个典型例子。传统的机器学习算法其实需要人工的提取特征,比如很厉害的SVM。而卷积神经网络利用模板算子的参数也用以学习这个特点,把特征也学习出来了。其实不同的模板算子本质上就是抽象了图像的不同方面的特征。比如提取边缘,提取梯度的算子。用很多卷积核去提取,那就是 提取了很多的特征。一旦把参数w,b训练出来,意味着特征和目标之间的函数就被确定。今天分享下CNN的关键

  • 训练发散 理想的分类器应当是除了真实标签的概率为1,其余标签概率均为 0,这样计算得到其损失函数为 -ln(1) = 0 损失函数越大,说明该分类器在真实标签上分类概率越小,性能也就越差。一个非常差的分类器,可能在真实标签上的匪类概率接近于0,那么损失函数就接近于正无穷,我们成为训练发散,需要调小学习速率。 6.9 高原反应 在 ImageNet-1000 分类问题中,初始状态为均匀分布,每个类别

  • 1 正向传播(Forward propagation) 回忆一下,给出一个输入特征$x$的时候,我们定义了$a^{[0]}=x$。然后对于层(layer)$l=1,2,3,\dots,N$,其中的$N$是网络中的层数,则有: $z^{[l]}=W^{[l]}a^{[l-1]}+b^{[l]}$ $a^{[l]}=g^{[l]}(z^{[l]})$ 在讲义中都是假设了非线性特征$g^{[l]}$对除