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

标签 java neural-network convergence

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

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

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

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

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];       

这样我就可以保留输出错误本身的符号。这修复了第一个重量中的无穷大趋势。现在,在/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 是该节点的增量。 这里需要注意的是,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

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

关于java - 简单神经网络中的奇怪收敛,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47741187/

相关文章:

r - 在 R 中通过 glmer 捕捉收敛与否

java - 在 android 中进行语音识别后,文本不会打印在 editText 上

tensorflow - 使用可用的训练 Hook 在 tf.estimator.DNNRegressor 中实现提前停止

java - 学习PDFBox;示例代码有问题

machine-learning - 感知器算法/线性回归的基本直觉

neural-network - 如何计算 tensorflow 模型中可训练参数的总数?

java - 如何防止遗传算法收敛于局部最小值?

r - 神经网络 : overcoming the non convergence of algorithm

java - 使用 Jersey 2.6 返回 jsp

java - 使用 log4j 的 Sentry 日志记录不起作用