出于学习目的,我正在用JavaScript从头开始实现我自己的神经网络,作为第一项任务,我想解决XOR问题。我已经可以解决OR和AND,但是一旦我需要一个隐藏层,我的权重就不能正确收敛。
我使用一个3层网络,其中包含2个输入神经元1个偏置神经元,1个隐藏层,其中包含2个神经元1个偏置神经元和1个输出神经元。
这个网络架构应该肯定能解决任务我手动设置权重的时候
let W1 = new Matrix([ // weights for mapping between layer 1 and layer 2
[-10, 20, 20], // OR
[30, -20, -20] // NAND
]);
let W2 = new Matrix([ // weights for mapping between layer 2 and layer 3
[-30, 20, 20] // AND
]);
我得到了正确的输出(非常接近[0,1,1,0]
)。
但是当我尝试学习异或问题的权重时,我总是得到接近[0.5,0.5,0.5,0.5]
的输出,而不是[0,1,1,0]
。我尝试了各种不同的权重初始化、学习率和梯度下降迭代次数,没有改进。
所以我很确定我的反向传播算法(W1grad的计算)有错误,但我就是找不到问题所在…任何帮助都将不胜感激!
// X inputs, W1, W2 = weights, y = outputs, alpha = learning rate
function gradientDescent(X, W1, W2, y, alpha, n_iterations) {
for (let i = 0; i < n_iterations; i++) {
// forward propagate
let a1 = addBias(X); // addBias just adds a column of 1's at the front of the matrix
let z2 = a1.times(W1.t()); // t() = transpose
let a2 = addBias(z2.map(sigmoid));
let z3 = a2.times(W2.t());
let a3 = z3.map(sigmoid);
// calculate error
let error = logCost(a3, y);
// back propagate
let outputDelta = a3.minus(y);
let hiddenDelta = outputDelta.times(W2).etimes(addBias(z2.map(sigmoidGradient))); // etimes is element-wise multiplication
let W2grad = outputDelta.t().times(a2).timess(1 / X.h); // timess (with 2 s) is scalar multiplication. this gradient seems to be right!
let W1grad = hiddenDelta.cols(1, hiddenDelta.w - 1).t().times(a1).timess(1 / X.h); // TODO this seems to be wrong...
// update weights
W1 = W1.minus(W1grad.timess(alpha));
W2 = W2.minus(W2grad.timess(alpha));
}
return [W1, W2];
}
完整的代码可以在这里找到(相关部分在底部,输出在控制台):https://codepen.io/anon/pen/oqagqd
原来是权重初始化!
出于某种原因,我的算法似乎对权重的初始化非常敏感…
使用-2.5和2.5之间的范围内的随机值以及5000次梯度下降迭代通常会产生异或问题的正确解决方案。许多其他范围根本不起作用…
使用
W1 = rand(2, 3).map(x => (x-.5)*5); // values between -2.5 and +2.5
W2 = rand(1, 3).map(x => (x-.5)*5);
返回输出
0.0676236578905123
0.9425132775668613
0.9095288663122072
0.05522288831217417
这是XOR问题的一个令人满意的近似(地面实况=[0,1,1,0]
)。
BTW:通过添加更多隐藏的神经元,更容易获得良好的结果。