一段时间以来,我一直在努力用 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/