在 TensorFlow.js 中,您可以通过以下两种方式训练机器学习模型:

  1. 使用 Layers API 与 LayersModel.fit()LayersModel.fitDataset()
  2. 使用 Core API 与 Optimizer.minimize()

首先,我们将了解 Layers API,它是一种用于构建和训练模型的高级 API。然后,我们将展示如何使用 Core API 训练相同的模型。

简介

机器学习模型是一种具有可学习参数的函数,可将输入映射到所需输出。基于数据训练模型可以获得最佳参数。

训练涉及多个步骤:

  • 获取一批次数据来训练模型。
  • 让模型做出预测。
  • 将该预测与“真实”值进行对比。
  • 确定每个参数的更改幅度,使模型在未来能够针对该批次数据做出更好的预测。

训练得当的模型将提供从输入到所需输出的准确映射。

模型参数

让我们使用 Layers API 来定义一个简单的 2 层模型:

  1. const model = tf.sequential({
  2. layers: [
  3. tf.layers.dense({inputShape: [784], units: 32, activation: 'relu'}),
  4. tf.layers.dense({units: 10, activation: 'softmax'}),
  5. ]
  6. });

模型以可学习参数(常称为权重)为基础,基于数据进行训练。让我们打印与此模型及其形状关联的权重的名称:

  1. model.weights.forEach(w => {
  2. console.log(w.name, w.shape);
  3. });

我们得到以下输出:

  1. > dense_Dense1/kernel [784, 32]
  2. > dense_Dense1/bias [32]
  3. > dense_Dense2/kernel [32, 10]
  4. > dense_Dense2/bias [10]

共有 4 个权重,每个密集层 2 个。这是可以预期的,因为密集层表示一个函数,通过等式 y = Ax + b 将输入张量 x 映射到输出张量 y,其中 A(内核)和 b(偏差)为密集层参数。

注:默认情况下,密集层将包含偏差,但您可以通过在创建密集层时的选项中指定 {useBias: false} 将其排除。

如果您想简要了解模型并查看参数总数,model.summary() 是一种实用的方法:

层(类型) 输出形状 参数数量
dense_Dense1(密集) [null,32] 25120
dense_Dense2(密集) [null,10] 330
参数总数:25450
可训练参数:25450
不可训练参数:0

模型中的每个权重均由[Variable](https://js.tensorflow.org/api/0.14.2/#class:Variable)对象提供支持。在 TensorFlow.js 中,Variable为浮点型 Tensor,具有一个用于更新值的附加方法 assign()。Layers API 会使用最佳做法自动初始化权重。出于演示目的,我们可以通过在基础变量上调用 assign() 来覆盖权重:

  1. model.weights.forEach(w => {
  2. const newVals = tf.randomNormal(w.shape);
  3. // w.val is an instance of tf.Variable
  4. w.val.assign(newVals);
  5. });

优化器、损失和指标

进行任何训练之前,您需要确定以下三项内容:

  1. 优化器。优化器的作用是在给定当前模型预测的情况下,决定对模型中每个参数实施更改的幅度。使用 Layers API 时,您可以提供现有优化器的字符串标识符(例如 ‘sgd‘ 或 'adam'),也可以提供 Optimizer 类的实例。
  2. 损失函数。模型将以最小化损失作为目标。该函数旨在将模型预测的“误差程度”量化为具体数字。损失以每一批次数据为基础计算,因此模型可以更新其权重。使用 Layers API 时,您可以提供现有损失函数的字符串标识符(例如 'categoricalCrossentropy'),也可以提供任何采用预测值和真实值并返回损失的函数。请参阅我们的 API 文档中的可用损失列表
  3. 指标列表。与损失类似,指标也会计算一个数字,用于总结模型的运作情况。通常要在每个周期结束时基于整体数据来计算指标。至少,我们要监控损失是否随着时间推移而下降。但是,我们经常需要准确率等更人性化的指标。使用 Layers API 时,您可以提供现有指标的字符串标识符(例如 'accuracy'),也可以提供任何采用预测值和真实值并返回分数的函数。请参阅我们的 API 文档中的可用指标列表

确定后,使用提供的选项调用 model.compile()来编译 LayersModel:

  1. model.compile({
  2. optimizer: 'sgd',
  3. loss: 'categoricalCrossentropy',
  4. metrics: ['accuracy']
  5. });

在编译过程中,模型将进行一些验证以确保您所选择的选项彼此兼容。

训练

您可以通过以下两种方式训练LayersModel

  • 使用 model.fit() 并以一个大型张量形式提供数据。
  • 使用 model.fitDataset() 并通过 Dataset 对象提供数据。

    model.fit()

如果您的数据集适合装入主内存,并且可以作为单个张量使用,则您可以通过调用fit()方法来训练模型:

  1. // Generate dummy data.
  2. const data = tf.randomNormal([100, 784]);
  3. const labels = tf.randomUniform([100, 10]);
  4. function onBatchEnd(batch, logs) {
  5. console.log('Accuracy', logs.acc);
  6. }
  7. // Train for 5 epochs with batch size of 32.
  8. model.fit(data, labels, {
  9. epochs: 5,
  10. batchSize: 32,
  11. callbacks: {onBatchEnd}
  12. }).then(info => {
  13. console.log('Final accuracy', info.history.acc);
  14. });

model.fit() 在后台可以完成很多操作:

  • 将数据拆分为训练集和验证集,并使用验证集衡量训练期间的进度。
  • 打乱数据顺序(仅在拆分后)。为了安全起见,您应该在将数据传递至fit()之前预先打乱数据顺序。
  • 将大型数据张量拆分成大小为 batchSize 的小型张量。
  • 在计算相对于一批次数据的模型损失的同时,调用 optimizer.minimize()
  • 可以在每个周期或批次的开始和结尾为您提供通知。我们的示例使用 callbacks.onBatchEnd 选项在每个批次的结尾提供通知。其他选项包括:onTrainBeginonTrainEndonEpochBeginonEpochEndonBatchBegin
  • 受制于主线程,确保 JS 事件循环中排队的任务可以得到及时处理。

有关更多信息,请参阅 fit()文档。请注意,如果您选择使用 Core API,则必须自行实现此逻辑。

本实例的完整代码

  1. const model = tf.sequential({
  2. layers: [
  3. tf.layers.dense({ inputShape: [784], units: 32, activation: 'relu' }),
  4. tf.layers.dense({ units: 10, activation: 'softmax' }),
  5. ]
  6. });
  7. model.weights.forEach(w => {
  8. console.log(w.name, w.shape);
  9. });
  10. model.compile({
  11. optimizer: 'sgd',
  12. loss: 'categoricalCrossentropy',
  13. metrics: ['accuracy']
  14. });
  15. const data = tf.randomNormal([100, 784]);
  16. const labels = tf.randomUniform([100, 10]);
  17. function onBatchEnd(batch, logs) {
  18. console.log('Accuracy', logs.acc);
  19. // Epoch 1 / 5
  20. // eta=0.0 ===========================>------------------------------------------------------------------------------------------------------------------ acc=0.0938 loss=13.45 Accuracy 0.09375
  21. // Accuracy 0.125
  22. // Accuracy 0.28125
  23. // Accuracy 0
  24. // eta=0.0 =============================================================================================================================================>
  25. // 130ms 1298us/step - acc=0.160 loss=13.18
  26. // Epoch 2 / 5
  27. // eta=0.0 ===========================>------------------------------------------------------------------------------------------------------------------ acc=0.156 loss=12.32 Accuracy 0.15625
  28. // Accuracy 0.15625
  29. // Accuracy 0.28125
  30. // Accuracy 0
  31. // eta=0.0 =============================================================================================================================================>
  32. // 46ms 459us/step - acc=0.190 loss=12.63
  33. // Epoch 3 / 5
  34. // eta=0.0 ===========================>------------------------------------------------------------------------------------------------------------------ acc=0.0938 loss=12.48 Accuracy 0.09375
  35. // Accuracy 0.25
  36. // Accuracy 0.28125
  37. // Accuracy 0
  38. // eta=0.0 =============================================================================================================================================>
  39. // 30ms 296us/step - acc=0.200 loss=12.28
  40. // Epoch 4 / 5
  41. // eta=0.0 ===========================>------------------------------------------------------------------------------------------------------------------ acc=0.219 loss=12.35 Accuracy 0.21875
  42. // Accuracy 0.28125
  43. // Accuracy 0.125
  44. // Accuracy 0.5
  45. // eta=0.0 =============================================================================================================================================>
  46. // 25ms 247us/step - acc=0.220 loss=12.01
  47. // Epoch 5 / 5
  48. // eta=0.0 ===========================>------------------------------------------------------------------------------------------------------------------ acc=0.219 loss=11.96 Accuracy 0.21875
  49. // Accuracy 0.375
  50. // Accuracy 0.1875
  51. // Accuracy 0
  52. // eta=0.0 =============================================================================================================================================>
  53. // 24ms 240us/step - acc=0.250 loss=11.82
  54. }
  55. // Train for 5 epochs with batch size of 32.
  56. model.fit(data, labels, {
  57. epochs: 5,
  58. batchSize: 32,
  59. callbacks: { onBatchEnd }
  60. }).then(info => {
  61. console.log('Final accuracy', info.history.acc);
  62. // Final accuracy [
  63. // 0.1599999964237213,
  64. // 0.1899999976158142,
  65. // 0.19999998807907104,
  66. // 0.2199999988079071,
  67. // 0.25
  68. // ]
  69. });

model.fitDataset()

如果您的数据不能完全装入内存或进行流式传输,则您可以通过调用 fitDataset() 来训练模型,它会获取一个 Dataset 对象。以下为相同的训练代码,但具有包装生成器函数的数据集:

  1. function* data() {
  2. for (let i = 0; i < 100; i++) {
  3. // Generate one sample at a time.
  4. yield tf.randomNormal([784]);
  5. }
  6. }
  7. function* labels() {
  8. for (let i = 0; i < 100; i++) {
  9. // Generate one sample at a time.
  10. yield tf.randomUniform([10]);
  11. }
  12. }
  13. const xs = tf.data.generator(data);
  14. const ys = tf.data.generator(labels);
  15. // We zip the data and labels together, shuffle and batch 32 samples at a time.
  16. const ds = tf.data.zip({xs, ys}).shuffle(100 /* bufferSize */).batch(32);
  17. // Train the model for 5 epochs.
  18. model.fitDataset(ds, {epochs: 5}).then(info => {
  19. console.log('Accuracy', info.history.acc);
  20. });

有关数据集的更多信息,请参阅 model.fitDataset() 文档

预测新数据

在模型完成训练后,您可以调用 model.predict(),基于未见过的数据进行预测:

  1. // Predict 3 random samples.
  2. const prediction = model.predict(tf.randomNormal([3, 784]));
  3. prediction.print();

注:正如我们在模型和层指南中所讲,LayersModel 期望输入的最外层维度为批次大小。在上例中,批次大小为 3。

Core API

之前,我们提到您可以通过两种方式在 TensorFlow.js 中训练机器学习模型。

根据常规经验法则,可以首先尝试使用 Layers API,因为它是由广为采用的 Keras API 建模而成。Layers API 还提供了各种现成的解决方案,例如权重初始化、模型序列化、监控训练、可移植性和安全性检查。

在以下情况下,您可以使用 Core API:

  • 您需要最大的灵活性或控制力。
  • 并且您不需要序列化,或者可以实现自己的序列化逻辑。

有关此 API 的更多信息,请参阅模型和层指南中的“Core API”部分。

使用 Core API 编写上述相同模型,方法如下:

  1. // The weights and biases for the two dense layers.
  2. const w1 = tf.variable(tf.randomNormal([784, 32]));
  3. const b1 = tf.variable(tf.randomNormal([32]));
  4. const w2 = tf.variable(tf.randomNormal([32, 10]));
  5. const b2 = tf.variable(tf.randomNormal([10]));
  6. function model(x) {
  7. return x.matMul(w1).add(b1).relu().matMul(w2).add(b2);
  8. }

除了 Layers API 以外,Data API 也可与 Core API 无缝协作。让我们重用先前在[model.fitDataset()](https://www.tensorflow.org/js/guide/train_models#model.fitDataset())部分中定义的数据集,该数据集已完成打乱顺序和批处理操作:

  1. const xs = tf.data.generator(data);
  2. const ys = tf.data.generator(labels);
  3. // Zip the data and labels together, shuffle and batch 32 samples at a time.
  4. const ds = tf.data.zip({xs, ys}).shuffle(100 /* bufferSize */).batch(32);
  5. 让我们训练模型:
  6. const optimizer = tf.train.sgd(0.1 /* learningRate */);
  7. // Train for 5 epochs.
  8. for (let epoch = 0; epoch < 5; epoch++) {
  9. await ds.forEachAsync(({xs, ys}) => {
  10. optimizer.minimize(() => {
  11. const predYs = model(xs);
  12. const loss = tf.losses.softmaxCrossEntropy(ys, predYs);
  13. loss.data().then(l => console.log('Loss', l));
  14. return loss;
  15. });
  16. });
  17. console.log('Epoch', epoch);
  18. }

以上代码是使用 Core API 训练模型时的标准方法:

  • 循环周期数。
  • 在每个周期内,循环各批次数据。使用 Dataset 时,[dataset.forEachAsync()](https://js.tensorflow.org/api/0.15.1/#tf.data.Dataset.forEachAsync)可方便地循环各批次数据。
  • 针对每个批次,调用[optimizer.minimize(f)](https://js.tensorflow.org/api/latest/#tf.train.Optimizer.minimize),它可以执行 f 并通过计算相对于我们先前定义的四个变量的梯度来最小化其输出。
  • f 可计算损失。它使用模型的预测和真实值调用预定义的损失函数之一。