简单神经网络中的奇怪收敛

Strange convergence in simple Neural Network

一段时间以来,我一直在努力在 Java 中构建一个简单的神经网络。我在这个项目上断断续续地工作了几个月,我想完成它。我的主要问题是我不知道如何正确地实现反向传播(所有来源都使用 Python、数学术语,或者过于简单地解释这个想法)。今天我尝试自己推导意识形态,我使用的规则是:

权重更新 = error * sigmoidDerivative(error) * weight itself;
错误 = 输出 - 实际; (最后一层)
error = sigmoidDerivative(来自上一层的错误)* 将此神经元附加到给出错误的神经元的权重(中间层)

我的主要问题是输出收敛于一个平均值,我的第二个问题是权重更新到一个非常奇怪的值。 (可能是权重问题导致收敛)

我要训练的内容:对于输入 1-9 ,预期输出为:(x*1.2+1)/10。这只是我随机想到的一条规则。我正在使用结构为 1-1-1 的神经网络(3 层,1 个网络/层)。在 link 波纹管中,我附上了两个 运行s:一个我使用遵循规则 (x*1.2+1)/10 的训练集,另一个我使用 ( x*1.2+1)/100。除以 10,第一个权重趋于无穷大;除以 100,第二个权重趋向于 0.I 一直试图调试它,但我不知道我应该寻找什么或出了什么问题。非常感谢任何建议。提前谢谢大家,祝大家有个美好的一天!

https://wetransfer.com/downloads/55be9e3e10c56ab0d6b3f36ad990ebe120171210162746/1a7b6f

我按照上述规则将 1->9 及其各自的输出作为训练样本,我 运行 它们 100_000 个时期。我每 100 个周期记录一次错误,因为使用较少的数据点更容易绘制,同时 9 的每个预期输出仍然有 1000 个数据点。反向传播和权重更新代码:

    //for each layer in the Dweights array
    for(int k=deltaWeights.length-1; k >= 0; k--)
    {
        for(int i=0; i<deltaWeights[k][0].length; i++)     // for each neuron in the layer
        {
            if(k == network.length-2)      // if we're on the last layer, we calculate the errors directly
            {
                outputErrors[k][i] = outputs[i] - network[k+1][i].result;
                errors[i] = outputErrors[k][i];
            }
            else        // otherwise the error is actually the sum of errors feeding backwards into the neuron currently being processed * their respective weight
            {
                for(int j=0; j<outputErrors[k+1].length; j++)
                {                         // S'(error from previous layer) * weight attached to it
                    outputErrors[k][i] += sigmoidDerivative(outputErrors[k+1][j])[0] * network[k+1][i].emergingWeights[j];
                }
            }
        }

        for (int i=0; i<deltaWeights[k].length; i++)           // for each neuron
        {
            for(int j=0; j<deltaWeights[k][i].length; j++)     // for each weight attached to that respective neuron
            {                        // error                S'(error)                                  weight connected to respective neuron                
                deltaWeights[k][i][j] = outputErrors[k][j] * sigmoidDerivative(outputErrors[k][j])[0] * network[k][i].emergingWeights[j];
            }
        }
    }

    // we use the learning rate as an order of magnitude, to scale how drastic the changes in this iteration are
    for(int k=deltaWeights.length-1; k >= 0; k--)       // for each layer
    {
        for (int i=0; i<deltaWeights[k].length; i++)            // for each neuron
        {
            for(int j=0; j<deltaWeights[k][i].length; j++)     // for each weight attached to that respective neuron
            {
                deltaWeights[k][i][j] *=  1;       // previously was learningRate; MSEAvgSlope

                network[k][i].emergingWeights[j] += deltaWeights[k][i][j];
            }
        }
    }

    return errors;

编辑:想到一个快速的问题:因为我使用 sigmoid 作为我的激活函数,我的输入和输出神经元应该只在 0-1 之间吗?我的输出在 0-1 之间,但我的输入实际上是 1-9。

Edit2:将输入值标准化为 0.1-0.9 并更改:

    outputErrors[k][i] += sigmoidDerivative(outputErrors[k+1][j])[0] * network[k+1][i].emergingWeights[j];     

至:

    outputErrors[k][i] = sigmoidDerivative(outputErrors[k+1][j])[0] * network[k+1][i].emergingWeights[j]* outputErrors[k+1][j];       

以便我保留输出错误本身的符号。这修复了第一个权重中的 Infinity 趋势。现在,对于 /10 运行,第一个权重趋于 0,而对于 /100 运行,第二个权重趋于 0。仍然希望有人能为我解决问题。 :(

我已经看到您的代码存在一些问题,例如您的体重更新不正确。我还强烈建议您通过引入方法来组织您的代码清洁器。

反向传播通常难以有效实施,但形式定义很容易翻译成任何语言。我不建议您查看用于研究神经网络的代码。查看数学并尝试理解它。这使您可以更灵活地从头开始实施。

我可以通过伪代码描述前向和后向传递给你一些提示。

作为符号,我对输入使用 i,对隐藏层使用 j,对输出层使用 k。那么输入层的偏置就是bias_i。对于将一个节点连接到另一个节点的权重,权重为 w_mn。激活是 a(x) 及其导数 a'(x).

前传:

for each n of j
       dot = 0
       for each m of i
              dot += m*w_mn
       n = a(dot + bias_i)

同样适用于输出层 k 和隐藏层 j。因此,只需将 j 替换为 k,将 i 替换为 j

向后传球:

计算输出节点的增量:

for each n of k
       d_n = a'(n)(n - target)

这里,target是期望输出,n是当前输出节点的输出。 d_n 是这个节点的delta。 这里需要注意的是,logistic 和 tanh 函数的导数包含原始函数的输出,并且不必重新评估此值。 逻辑函数是 f(x) = 1/(1+e^(-x)) 及其导数 f'(x) = f(x)(1-f(x))。由于每个输出节点 n 的值之前都是用 f(x) 计算的,因此可以简单地应用 n(1-n) 作为导数。在上述情况下,这将按如下方式计算增量:

d_n = n(1-n)(n - target)

以同样的方式,计算隐藏节点的增量。

for each n of j
      d_n = 0
      for each m of k
             d_n += d_m*w_jk
      d_n = a'(n)*d_n

下一步是使用梯度执行权重更新。这是通过称为梯度下降的算法完成的。无需赘述,可以按如下方式完成:

for each n of j
      for each m of k
            w_nm -= learning_rate*n*d_m

同样适用于上面的图层。只需将 j 替换为 i,将 k 替换为 j

要更新偏差,只需将连接节点的增量相加,将其乘以学习率,然后从特定偏差中减去该乘积。