You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

从零实现的JS神经网络解决XOR问题时输出收敛至0.5的问题排查

从零实现的JS神经网络解决XOR问题时输出收敛至0.5的问题排查

看起来你碰到了从零搭建神经网络时的经典“卡壳”问题——训练XOR后所有输出都收敛到0.5左右。这大概率是反向传播逻辑错误+初始化的低级bug共同导致的,咱们一步步拆解问题,逐个修复:

1. 致命bug:所有节点共享同一组权重和偏置

先看你的构造函数里这段代码:

var weightsAndBiases = [Array(layerSizes[i-1]).fill(random(0, 1))]
weightsAndBiases.push(0)
nodes.fill(weightsAndBiases)
this.layers.push(nodes)

这里的问题是:nodes.fill(weightsAndBiases)会把同一个数组引用填充到所有节点里!也就是说,同一层的所有节点的权重和偏置完全共享,根本没法独立学习。相当于你的每个隐藏层/输出层只有一个“有效节点”,这怎么可能学出XOR的非线性边界?

修复方案:给每个节点单独创建权重和偏置数组,不要用fill填充引用:

// 替换构造函数里的循环部分
for (let i=0; i<this.numLayers; i++){
    const nodes = [];
    if (i === 0) {
        // 输入层不需要权重偏置,初始化空结构即可
        for (let j=0; j<layerSizes[i]; j++) {
            nodes.push(null);
        }
    } else {
        for (let j=0; j<layerSizes[i]; j++) {
            // 每个节点单独生成权重和偏置
            const weights = Array(layerSizes[i-1]).fill().map(() => random(-1, 1)); // 改成对称初始化
            const bias = random(-1, 1); // 偏置也随机初始化
            nodes.push([weights, bias]);
        }
    }
    this.layers.push(nodes);
}

另外,权重初始化全用random(0,1)的正数也有问题——会导致激活函数输入全正(比如用sigmoid的话都在右半段,导数接近0,梯度消失),改成对称的random(-1,1)会好很多。

2. 反向传播核心错误:隐藏层Delta计算逻辑完全错了

看你计算隐藏层delta的代码:

deltas[i][j] += this.state[i+1][k] * this.layers[i+1][k][0][j]

正确的反向传播中,隐藏层节点的delta应该是下一层每个节点的delta乘以对应权重的总和,再乘以当前节点激活函数的导数。你这里用了this.state[i+1][k](下一层的输出),而不是deltas[i+1][k](下一层的误差项),这直接导致梯度完全传不回隐藏层,网络根本学不到有用的参数!

修复方案:把this.state[i+1][k]换成deltas[i+1][k]:

for (let k=0; k<this.layerSizes[i+1]; k++){
    deltas[i][j] += deltas[i+1][k] * this.layers[i+1][k][0][j];
}
deltas[i][j] *= this.activationPrime(this.inverseActivation(this.state[i][j]));

3. 权重梯度计算错误

看你计算权重更新量的代码:

let dCostdWeight = this.layers[this.numLayers-1][i][0][j] * this.state[this.numLayers-2][j] * deltas[this.numLayers-1][i]

正确的权重梯度应该是上一层节点的输出乘以当前节点的delta,你这里多乘了一个当前权重this.layers[...],这完全扭曲了梯度的方向和大小,导致参数更新完全错误。

修复方案:去掉多余的权重相乘:

// 输出层权重梯度
let dCostdWeight = this.state[this.numLayers-2][j] * deltas[this.numLayers-1][i];
// 隐藏层权重梯度同理
let dCostdWeight = this.state[i-1][k] * deltas[i][j];

4. 其他小问题(非致命但影响训练效率)

  • 你用JSON.parse(JSON.stringify(this.layers))来拷贝参数的方式虽然能工作,但效率很低,建议直接遍历层和节点,逐个拷贝权重和偏置。
  • 训练时setInterval(() => {render(); }, 1)的频率太高,一个epoch还没跑完就开始下一个,其实可以改成每次run执行多个epoch,或者控制训练节奏。

修复后验证

把上面几个核心bug修复后,再调整学习率(比如0.1-0.5之间),训练几十到几百个epoch,你应该就能看到网络输出逐渐趋近XOR的正确结果:[0,1,1,0],而不是全0.5了。

备注:内容来源于stack exchange,提问作者R. Reed

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.15 03:23:03