TensorFlow.js在浏览器中运行机器学习

1,840 阅读7分钟

[译]来自p1-jj.byteimg.com/tos-cn-i-t2…

这篇文章解释了如何使用TensorFlow.js在浏览器中运行机器学习。 它涵盖了用于模型训练,传递学习和预测功能的TensorFlow.js API。这里呈现了一个APP demo, 它有助于预测业务报告执行的等待时间。

我已经实现了一个包含TensorFlow.js API使用情况的APP。 首先,我将向您介绍其功能,然后深入探讨实现细节。 该APP实现了业务报告执行时间预测用例(在JavaScript中),在我之前的文章《使用Keras和TensorFlow进行报告时间执行预测》中对此进行了解释。 对于模型训练,我使用了50个epochs (数据以10个为批处理),学习率设置为0.001。 神经网络基于两个处理层和一个输出层。 模型训练过程在浏览器中运行:

APP部署并在此处实时可用: regressiontfjs-node.herokuapp.com/

训练模型可以预测业务报告执行的预期等待时间。实现是基于这本书中的解释和资料完成的-《使用JavaScript进行深度学习》和多元回归示例-《Boston Housing》

功能性

首次打开APP时,需要对模型进行训练。训练模型后,它将被保存到本地indexeddb。重新打开浏览器后,该模型将从indexeddb中保持可用,您可以选择要重用它或训练新模型(以前保存的模型将被替换)。 训练过程完成后,它将打印一组显示模型情况的参数:

  • Baseline loss(基线损失):这是根据训练数据计算出的平均平方误差。平方根值:237秒。这意味着,如果我们始终假设预测的平均值,则错误范围将为237秒。
  • Average time(平均时间):为训练数据计算的平均时间
  • Final train-set loss(最终训练设定损失):训练期间计算出的损失
  • Final validation-set loss(最终验证集损失):通过验证训练期间计算出的损失
  • Test-set loss(测试集损失):针对训练数据针对训练数据计算出的损失
  • Prediction off by (sec)(预测偏差(秒)):测试集损失的平方根值。这表明模型出错能力。此模型训练得很好,只有4.5秒的错误,比基线损失要好

对以下数据运行预测功能-结果514秒:

将时隙更改为下午,预计时间将增加到573秒。 这意味着根据训练数据对模型进行了正确的训练,报告在下午的运行时间更长:

增加参数数量。 使用更多参数,可处理的数据更少-报告运行速度应更快。 模型通过预测来确认这一点,该预测将返回更快的时间:

让我们更改报告ID和参数数量。 观察预计时间:

现在,我们将进行转移学习,重新训练具有新目标的模型(在此示例中,出于简单原因,我仅使用一行数据,但我建议重新训练需要多行数据的模型)。 call训练方法-它应该运行的非常快。 测试集丢失可能比基于原始训练的情况更糟(此练习很好),因为我们使用的是之前的测试集数据,该数据与新目标没有直接关系。 新目标——400秒,报告ID = 1(假设特定用户时间与训练中的时间不同,并且该用户可以更新模型)。 重新训练(当我们在现有模型之上训练时——转移学习)的结果:

重新训练模型后,使用之前相同的参数运行后的预测——您将看到预测结果将被调整与我们用于重新训练的目标一致:

现在,将Report ID更改为最初使用的ID,并将报告参数的数量以及时隙更改为原始值。 您将看到,现在我们预测了相对更短的时间,这是由于最近的模型重新训练而将更短的时间设置为目标:

尝试更改参数并查看结果。

实操

APP结构非常简单。 所有逻辑均在appController.js中实现。 UIindex.html中实现。 Web应用程序是通过Oracle JavaScript库— Oracle JET实现的。 要在本地运行该应用程序,请执行以下两个步骤:

  • 使用NPM安装Oracle JETnpm install -g @ oracle / ojet-cli
  • 进入应用程序并运行:ojet restore
  • 运行应用程序:ojet服务

appController.js中定义了一个侦听器。 加载应用程序时,将调用此侦听器,并负责加载数据,将其转换为张量并计算基线。 数据通过帮助程序模块data.js加载。 我正在使用Papaparse库来解析CSV。 原始数据集基于四列:

我发现模型不能直接从这些数据中很好地训练。 报表ID和时段字段需要数据转换。 这些列是分类别的,并且需要通过创建尽可能多的新列进行转换,并保证存在唯一的分类值:

var features = results['data'].map(report => ({
 report_params: parseFloat(report.report_params),
 report_1: parseFloat(report.report_id) === 1 ? 1 : 0,
 report_2: parseFloat(report.report_id) === 2 ? 1 : 0,
 report_3: parseFloat(report.report_id) === 3 ? 1 : 0,
 report_4: parseFloat(report.report_id) === 4 ? 1 : 0,
 report_5: parseFloat(report.report_id) === 5 ? 1 : 0,
 day_morning: parseFloat(report.day_part) === 1 ? 1 : 0,
 day_midday: parseFloat(report.day_part) === 2 ? 1 : 0,
 day_afternoon: parseFloat(report.day_part) === 3 ? 1 : 0,
}));

这种数据转换有助于进行更精确的训练。 我正在使用1200行数据进行训练,并使用300行数据进行测试。 在将数据拆分为训练数据集和测试数据集之前,请确保先对其进行混洗。 我正在使用helper方法,该方法取自Boston HousingAPP。 使用TensorFlow.js函数tensor2d将数据数组转换为张量:

tensors.rawTrainFeatures = tf.tensor2d(dataHelper.trainFeatures);
tensors.trainTarget = tf.tensor2d(dataHelper.trainTarget);
tensors.rawTestFeatures = tf.tensor2d(dataHelper.testFeatures);
tensors.testTarget = tf.tensor2d(dataHelper.testTarget);

TensorFlow.js模型由两个处理层和一个输出层构成,以返回预测值:

const model = tf.sequential();
model.add(tf.layers.dense({
 inputShape: [dataHelper.trainFeatures[0].length],
 units: 25,
 activation: 'sigmoid',
 kernelInitializer: 'leCunNormal'
}));
model.add(tf.layers.dense({
 units: 25,
 activation: 'sigmoid',
 kernelInitializer: 'leCunNormal'
}));
model.add(tf.layers.dense({ units: 1 }));

构建模型后,对其进行编译并运行拟合函数以训练模型。 我建议使用验证拆分选项,这样在训练过程中它将验证训练的质量

model.compile({ optimizer: tf.train.sgd(LEARNING_RATE), loss: 'meanSquaredError' });
await model.fit(tensors.trainFeatures, tensors.trainTarget, {
 batchSize: BATCH_SIZE,
 epochs: NUM_EPOCHS,
 shuffle: true,
 validationSplit: 0.2,
 callbacks: {
  onEpochEnd: async (epoch, logs) => {

函数拟合提供onEpochEnd回调,我们可以在其中记录训练进度并将数据推送到UI。 训练完成后,通过针对测试数据运行模型来评估模型。 从返回的数字中取平方根,这将基于当前模型训练在几秒钟内被容忍的错误(如果结果不能容忍,请尝试通过更改神经网络层的结构来重新训练模型,并尝试调整训练参数):

result = model.evaluate(tensors.testFeatures, tensors.testTarget, { batchSize: BATCH_SIZE });
testLoss = result.dataSync()[0];

最后保存模型。 有多个保存模型的选项,您甚至可以将其推送到服务器。 在此示例中,我将模型保存在浏览器本地的indexeddb中:

await model.save('indexeddb://report-exec-time-model');

训练模型后,我们可以运行预测功能。 我正在从indexeddb中的保存状态加载模型,构造输入张量并执行TensorFlow.js预测:

model = await tf.loadLayersModel('indexeddb://report-exec-time-model');
input = [{
 report_id: parseFloat(self.reportIdP()),
 report_params: self.reportParamsP(),
 day_part: parseFloat(self.reportExecSlotP())
}];
convertInputToTensor(input);
res = model.predict(tensors.inputFeatures);
score = res.dataSync()[0];

最后一步——转移学习,使用附加数据对现有模型进行再训练(在这种情况下,数据形状相同,我们为新目标进行训练)。 为了使重新训练更有效率,我们必须冻结原始模型中的部分图层。 这样,先前训练的模型权重将不会受到影响,并且模型训练将运行得更快。 通常,您应该使用多个新元素进行培训,在这种情况下,出于简单原因,我会使用一个新条目进行培训。 可以通过将可训练属性设置为false来冻结模型层:

model = await tf.loadLayersModel('indexeddb://report-exec-time-model');
model.layers[0].trainable = false;
model.compile({
 loss: 'meanSquaredError',
 optimizer: tf.train.sgd(LEARNING_RATE)
});
model.summary();
input = [{
 report_id: parseFloat(self.reportId()),
 report_params: self.reportParams(),
 day_part: parseFloat(self.reportExecSlot()),
 exec_time: self.reportExecTime()
}];
convertInputToTensor(input, 'Y');

在现有模型之上使用新数据运行函数拟合:

await model.fit(tensors.inputFeatures, tensors.inputTarget, {
 batchSize: BATCH_SIZE,
 epochs: NUM_EPOCHS,
 shuffle: true,
 callbacks: {
  onEpochEnd: async (epoch, logs) => {