☰
TensorFlow.js Node.js 回归实战:abalone-node 示例从 CSV 数据加载到模型训练与保存全解析
2026/10/15 16:08:08 网站建设 项目流程
  • 示例工程

【免费下载链接】tfjs-examples

Examples built with TensorFlow.js

项目地址:https://gitcode.com/gh_mirrors/tf/tfjs-examples
点击查看免费下载

本篇文章围绕 tfjs-examples 仓库中的 abalone-node 示例展开,完整讲解如何在 Node.js 环境中使用 TensorFlow.js 完成一次真实的回归任务:根据鲍鱼的物理测量数据预测其年龄(rings 环数)。读者学完后,将掌握tf.data.csv加载本地 CSV、特征预处理、tf.Sequential构建多层感知机、model.fitDataset()训练、model.save()保存与tf.loadLayersModel()推理的完整工程链路,并了解 CPU/GPU 后端的选择方式。

一、示例概览:在 Node.js 中用物理测量预测鲍鱼年龄

abalone-node是 tfjs-examples 中典型的Node.js 环境下的数值回归示例。它使用的数据集来自 UCI Machine Learning Repository 的 Abalone 数据集(仓库中已随附 abalone.csv,共 4177 行数据)。每行记录一只鲍鱼的 8 个物理测量特征与目标变量rings(环数),任务即是从这些测量值回归预测环数,从而推断鲍鱼年龄。

主仓库 README.md 的示例总览表中对该示例的定位是:输入数据类型为数值(Numeric)、任务类型为"从本地文件加载数据并在 Node.js 中训练"、模型类型为多层感知机(Multilayer perceptron)、训练与推理均在 Node.js 中完成、使用Layers API、支持将模型保存到文件系统并在 Node.js 中重新加载。这正是本示例的教学价值所在——它完整串联了 TensorFlow.js 在 Node.js 侧的数据管道、模型训练与序列化三个环节。

数据集字段结构(对应 abalone.csv 首行表头):

字段含义(鲍鱼数据集惯例)类型
sex性别:M(雄)、F(雌)、I(幼体)类别
length最长壳长度数值(毫米)
diameter与长度垂直的直径数值(毫米)
height带壳肉的高度数值(毫米)
weight.w整体重量数值(克)
weight.s去壳肉重数值(克)
weight.v内脏重量数值(克)
weight.sh壳重数值(克)
rings环数(目标变量)整数(2~16)

在鲍鱼数据集的惯例中,环数常被用作年龄的代理指标;本示例直接以rings作为回归目标,这一点在训练日志与推理输出中均有体现。

二、快速开始:安装依赖并启动训练

由于仓库中每个示例目录都是独立、可拷贝到其他项目直接运行的,因此进入示例目录后依次执行即可:

cd abalone-node yarn yarn train
  • yarn:依据 package.json 与yarn.lock安装依赖。核心运行时依赖为@tensorflow/tfjs-node(^2.6.0)与命令行解析库argparse(^1.0.10),开发依赖为jasmine测试框架等。
  • yarn train:等价于执行node train.js(见 package.json 中的scripts配置)。

启动训练脚本 train.js 后,会先通过https.get将数据集从远程地址下载为本地./abalone.csv(仓库已随附该文件,脚本运行时会重新拉取覆盖),随后自动完成训练。默认配置下会训练100 个 epoch,最终输出类似以下日志:

... Epoch 100 / 100 eta=0.0 =================================================> 402ms 57414us/step - loss=7.42 val_loss=5.60 The actual test abalone age is 10, the inference result from the model is 11.929240226745605

其中loss与val_loss分别是训练集与验证集上的均方误差(MSE),最后一行展示了模型对测试样本的推理结果:真实年龄为 10,模型预测为约 11.93,属于同一数量级的合理回归结果。

命令行参数

train.js 使用argparse暴露了三个可调参数,便于在不改代码的情况下调节训练配置:

参数默认值说明
--epochs100训练的轮数(epoch)
--batch_size500训练时每个 batch 的样本数
--savePathfile://trainedModel模型保存路径,需带file://前缀

例如,想训练 200 轮并把模型保存到自定义目录:

node train.js --epochs 200 --batch_size 256 --savePath file://my_model

CPU 与 GPU 后端的选择

训练默认使用@tensorflow/tfjs-node,它在 Node.js 中通过 libtensorflow 运行,默认跑在 CPU 上。如果你的机器配备支持 CUDA 的 GPU,并且系统已正确安装配置好 CUDA 与 CuDNN 库,则可以将依赖中的tfjs-node替换为tfjs-node-gpu,即可让训练跑在 GPU 上,显著加快大规模迭代场景下的训练速度。注意该替换需要自行调整 package.json 中的依赖项并重新安装,且对 CUDA/CuDNN 版本有环境要求。

三、数据加载与预处理:tf.data.csv 与特征编码

数据加载与预处理逻辑全部封装在 data.js 的createDataset(csvPath)函数中,它展示了 TensorFlow.jstf.data管道的标准用法:读取 → 配置 → 转换。

1. 从本地 CSV 创建 Dataset

const dataset = tf.data.csv( csvPath, {hasHeader: true, columnConfigs: {'rings': {isLabel: true}}}); const numOfColumns = (await dataset.columnNames()).length - 1;
  • tf.data.csv支持file://前缀的本地路径(调用处传入'file://' + csvPath),也支持远程 URL。
  • hasHeader: true表明 CSV 首行是列名;columnConfigs指定rings列为标签列(isLabel: true),其余列自动作为特征xs。
  • 由于标签列被单独拆分出去,输入特征维度为总列数减 1,即9 - 1 = 8,这正是numOfColumns的计算方式。

2. 特征与标签的映射转换

tf.data.csv返回的行结构中,特征在row['xs'](对象形式),标签在row['ys']。随后通过dataset.map()完成两件事:

const convertedFeatures = Object.keys(rawFeatures).map(key => { switch (rawFeatures[key]) { case 'F': return 0; case 'M': return 1; case 'I': return 2; default: return Number(rawFeatures[key]); } }); const convertedLabel = [rawLabel['rings']]; return {xs: convertedFeatures, ys: convertedLabel};
  • 类别特征编码:sex列是字符串类别,无法直接送入网络,因此按F→0、M→1、I→2映射为数值。
  • 数值特征转换:其余 7 个测量值通过Number()转为浮点数,形成 8 维特征向量。
  • 标签整形:rings被包装成单元素数组[rings],作为回归目标(输出维度为 1)。

最终createDataset返回{dataset, numOfColumns},其中dataset已经过 map 转换、可直接进入训练管道。

3. 数据测试对预处理逻辑的验证

data_test.js 使用仓库随附的 test_data.csv(与主数据集同结构)验证了上述逻辑:

  • numOfColumns等于8;
  • 特征向量长度为8,其中features[0](编码后的sex)取值只能是0、1、2三者之一;
  • 其余 7 个数值特征均在(0, 1)区间内;
  • 标签label[0]是整数,且落在2~16范围内。

这些断言与 abalone.csv 的实际数据分布一致,可作为理解预处理正确性的可执行依据。

四、模型构建:三层全连接回归网络

模型定义位于 model.js 的createModel(inputShape),它构建了一个标准的多层感知机(MLP)回归模型:

const model = tf.sequential(); model.add(tf.layers.dense({inputShape: inputShape, activation: 'sigmoid', units: 50})); model.add(tf.layers.dense({activation: 'sigmoid', units: 50})); model.add(tf.layers.dense({units: 1})); model.compile({optimizer: tf.train.sgd(0.01), loss: 'meanSquaredError'});
层单元数激活函数说明
隐藏层 1(Dense)50sigmoid输入维度inputShape(= 8)
隐藏层 2(Dense)50sigmoid中间隐藏层
输出层(Dense)1线性(默认)回归输出单个连续值
  • 任务类型是回归,因此输出层只有 1 个单元且不带激活函数(线性输出),损失函数选用meanSquaredError(均方误差)。
  • 优化器为SGD,学习率0.01。该配置可通过修改 model.js 调整,例如换成tf.train.adam()或更大的学习率以加速收敛。

仓库中已保存的训练产物 trainedModel/model.json 从序列化层面印证了上述结构:模型拓扑为Sequential,包含 3 个 Dense 层,batch_input_shape为[null, 8];权重清单显示dense_Dense1/kernel形状为[8, 50]、dense_Dense2/kernel为[50, 50]、dense_Dense3/kernel为[50, 1],与"8 维输入 → 50 → 50 → 1 输出"的网络完全吻合。

五、训练流程:数据切分、批量化与 fitDataset

训练主流程位于 train.js 的run(epochs, batchSize, savePath)函数中,展示了tf.data管道与fitDataset组合使用的完整模式。

1. 数据集切分:训练集与验证集

数据集共4177 行,示例将其拆为两部分:

const trainBatches = Math.floor(3500 / batchSize); const dataset = datasetObj.dataset.shuffle(1000).batch(batchSize); const trainDataset = dataset.take(trainBatches); const validationDataset = dataset.skip(trainBatches);
  • shuffle(1000):在 1000 个样本的缓冲窗口内打乱数据顺序,保证训练样本分布的随机性。
  • batch(batchSize):按 batch 大小切分,默认500。
  • take(trainBatches):取前Math.floor(3500 / batchSize)个 batch 作为训练集(默认 500 时即 7 个 batch,共 3500 行)。
  • skip(trainBatches):跳过上述 batch,剩余约677 行作为验证集(validationData)。

这种"按 batch 数切分"的方式要求训练数据量是 batch 大小的整数倍,3500 恰好是 500 的整数倍,设计上避免了余数问题。

2. 训练与保存

await model.fitDataset( trainDataset, {epochs: epochs, validationData: validationDataset}); await model.save(savePath);
  • model.fitDataset()接受一个tf.data.Dataset作为输入,无需手动准备xs/ys张量对,这正是tf.data管道与fit系列 API 的结合点;validationData传入验证集,在每轮结束后评估val_loss。
  • model.save(savePath)将模型保存到本地,默认路径为file://trainedModel,输出model.json与weights.bin(对应仓库中的 trainedModel/model.json 与trainedModel/weights.bin)。

3. 模型测试对训练与序列化的验证

model_test.js 用 jasmine 写了两个关键测试:

  • 训练可行性测试:创建输入长度为 6 的模型,断言model.inputs[0].shape为[null, 6]、model.outputs[0].shape为[null, 1],并调用model.fit(ones, ones, {epochs: 2})确认能正常训练且 loss 历史长度为 2。
  • 保存-加载往返测试(roundtrip):将模型保存到临时目录后,通过tf.loadLayersModel(file://.../model.json)重新加载,再对同一输入分别推理,用tf.test_util.expectArraysClose断言两次输出完全一致,从而证明序列化不会损失模型行为。

六、加载模型进行推理

训练完成后,train.js 还会演示标准的"加载 → 推理"流程:

const loadedModel = await tf.loadLayersModel(savePath + '/model.json'); const result = loadedModel.predict( tf.tensor2d([[0, 0.625, 0.495, 0.165, 1.262, 0.507, 0.318, 0.39]]));
  • tf.loadLayersModel()从本地model.json(含weightsManifest指向的weights.bin)恢复完整的 Layers 模型。
  • predict()接收一个形状为[1, 8]的张量:0表示性别编码(F),随后依次是length=0.625、diameter=0.495、height=0.165、weight.w=1.262、weight.s=0.507、weight.v=0.318、weight.sh=0.39。
  • 输出为该样本的预测年龄(环数),如示例日志中的11.929240226745605,而该样本真实值为 10。

这一小段代码同时展示了"模型保存到文件系统、在新进程中重新加载并复用"的完整闭环,也是主 README 概览表中"保存到文件系统并在 Node.js 中加载"能力的直接体现。

七、单元测试与工程实践

除训练脚本外,示例还配套了完整的测试体系,体现了 tfjs-examples 仓库"示例即工程"的规范:

  • 运行测试:在示例目录执行yarn test(等价于node run_tests.js)。
  • run_tests.js 复用仓库根目录的 test_util.js:后者封装了 jasmine 初始化逻辑,将./*test.js作为 spec 文件加载执行。
  • 测试覆盖两类核心逻辑:数据预处理(data_test.js)与模型构建/训练/序列化(model_test.js)。

这种"训练脚本 + 单元测试 + 数据文件"的目录组织方式,使得示例既可以一键复现训练结果,也可以作为 TensorFlow.js Node.js API 的可靠参考实现。

结语

abalone-node虽是一个小而精的示例,却完整覆盖了 TensorFlow.js 在 Node.js 端从数据到部署的五步闭环:用tf.data.csv加载本地 CSV、通过map()完成类别编码与特征转换、用tf.Sequential从零搭建 MLP 回归模型、借助model.fitDataset()配合验证集切分完成训练、最后通过model.save()/tf.loadLayersModel()实现模型的持久化与复用。读者既可以按上文步骤直接运行yarn && yarn train复现日志中的训练效果,也可以把它作为模板,将同样的数据管道与训练流程迁移到自己的数值回归任务中;若拥有 CUDA 环境,还可将后端切换为tfjs-node-gpu获得训练加速。

  • 示例工程

【免费下载链接】tfjs-examples

Examples built with TensorFlow.js

项目地址:https://gitcode.com/gh_mirrors/tf/tfjs-examples
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询