使用TensorFlow.js求解方程系数时损失趋于无穷并出现NaN问题求助
嘿,我之前也碰到过类似的情况,咱们一步步来排查你遇到的问题:
首先看你的代码片段和描述,损失快速趋近无穷然后变成NaN,主要有几个常见的原因,我给你逐一分析并给出解决办法:
1. 输入/输出数值范围过大,引发梯度爆炸
你的输入xs是从-100到99的整数,计算三次方后数值会达到百万级别(比如99³≈970299),对应的ys数值也会非常大。模型在反向传播计算梯度时,这么大的数值会导致梯度急剧膨胀,权重更新一步就可能溢出变成NaN。
解决办法:归一化数据
把输入和输出都缩放到小范围(比如[-1,1]或[0,1]),示例代码如下:
// 归一化xs到[-1, 1]区间 const minX = Math.min(...xs); const maxX = Math.max(...xs); const normalizedXs = xs.map(x => (x - minX) / (maxX - minX) * 2 - 1); // 归一化ys到[0, 1]区间 const minY = Math.min(...ys); const maxY = Math.max(...ys); const normalizedYs = ys.map(y => (y - minY) / (maxY - minY));
之后用normalizedXs和normalizedYs来训练模型,训练完成后如果需要还原真实系数,再做反归一化处理即可。
2. 模型结构无法表达三次函数
看你代码的隐含逻辑,应该是想用模型拟合a*x³ + b*x² + c*x + d,但如果你的模型是单层全连接(比如tf.layers.dense({units:1, inputShape:[1]})),这本质是个线性模型,只能拟合y = w*x + b这样的一次函数,完全没有能力表达三次多项式的非线性关系。这种情况下模型根本无法收敛,损失会越来越大直到溢出。
解决办法:构造多项式特征或使用非线性模型
方案一:手动构造多项式特征(推荐,更高效)
把每个输入x转换成包含x、x²、x³的特征向量,这样模型就能学习到三次多项式的系数:
// 构造特征:每个样本是 [x, x², x³] const features = normalizedXs.map(x => [x, Math.pow(x, 2), Math.pow(x, 3)]); // 构建模型,输入形状对应3个特征 const model = tf.sequential(); model.add(tf.layers.dense({units: 1, inputShape: [3]}));
此时模型的输出就是w1*x + w2*x² + w3*x³ + b,正好对应你要拟合的三次方程(注意归一化会影响系数的对应关系,后续需要反归一化还原真实值)。
方案二:使用带非线性激活的神经网络
如果不想手动构造特征,可以用多层网络加非线性激活函数来拟合:
const model = tf.sequential(); model.add(tf.layers.dense({units: 16, inputShape: [1], activation: 'relu'})); model.add(tf.layers.dense({units: 16, activation: 'relu'})); model.add(tf.layers.dense({units: 1}));
3. 学习率设置过高
如果学习率太大,哪怕数据归一化了,权重更新的步长也可能超出合理范围,导致损失突然飙升到NaN。建议降低学习率,比如:
// 把Adam优化器的学习率从默认的0.01调低到0.001甚至更小 const optimizer = tf.train.adam(0.001); model.compile({optimizer: optimizer, loss: 'meanSquaredError'});
4. 检查代码完整性
你的代码片段里ys.push(...)的部分没写完,要确保没有语法错误,比如应该补全闭合的括号:
ys.push(aReal*Math.pow(i, 3) + bReal*Math.pow(i, 2) + cReal*i + dReal);
完整修正示例代码
这里给你一个可以直接运行的完整示例,包含数据生成、归一化、模型构建和训练:
// 生成真实数据 let xs = []; let ys = []; const aReal = Math.random(); const bReal = Math.random(); const cReal = Math.random(); const dReal = Math.random(); for (let i = -100; i < 100; i += 1) { xs.push(i); ys.push(aReal * Math.pow(i, 3) + bReal * Math.pow(i, 2) + cReal * i + dReal); } // 归一化数据 const minX = Math.min(...xs); const maxX = Math.max(...xs); const normalizedXs = xs.map(x => (x - minX) / (maxX - minX) * 2 - 1); const minY = Math.min(...ys); const maxY = Math.max(...ys); const normalizedYs = ys.map(y => (y - minY) / (maxY - minY)); // 转换为TensorFlow张量 const xsTensor = tf.tensor2d(normalizedXs.map(x => [x, Math.pow(x,2), Math.pow(x,3)])); const ysTensor = tf.tensor2d(normalizedYs, [normalizedYs.length, 1]); // 构建模型 const model = tf.sequential(); model.add(tf.layers.dense({units: 1, inputShape: [3]})); // 编译模型 model.compile({optimizer: tf.train.adam(0.001), loss: 'meanSquaredError'}); // 训练模型 async function train() { await model.fit(xsTensor, ysTensor, { epochs: 100, batchSize: 8, callbacks: { onEpochEnd: (epoch, logs) => { console.log(`Epoch ${epoch}: loss = ${logs.loss}`); } } }); // 查看模型权重 model.getWeights().forEach(w => { w.print(); }); } train();
内容的提问来源于stack exchange,提问作者Em Eldar

