训练模型

本指南假设您已阅读模型和层指南。

在 TensorFlow.js 中,有两种方法可以训练机器学习模型

  1. 使用层 API 与 LayersModel.fit()LayersModel.fitDataset()
  2. 使用核心 API 与 Optimizer.minimize()

首先,我们将介绍层 API,它是一个用于构建和训练模型的高级 API。然后,我们将展示如何使用核心 API 训练相同的模型。

简介

机器学习模型是一个具有可学习参数的函数,它将输入映射到所需的输出。最佳参数是通过在数据上训练模型获得的。

训练涉及以下几个步骤

  • 将一批数据提供给模型。
  • 要求模型进行预测。
  • 将该预测与“真实”值进行比较。
  • 确定要更改每个参数的程度,以便模型将来可以对该批次做出更好的预测。

经过良好训练的模型将提供从输入到所需输出的准确映射。

模型参数

让我们使用层 API 定义一个简单的两层模型

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

在幕后,模型具有通过在数据上训练而可学习的参数(通常称为权重)。让我们打印与该模型关联的权重的名称及其形状

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

我们得到以下输出

> dense_Dense1/kernel [784, 32]
> dense_Dense1/bias [32]
> dense_Dense2/kernel [32, 10]
> dense_Dense2/bias [10]

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

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

model.summary() 是一个有用的方法,如果您想了解模型的概况并查看参数的总数

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

模型中的每个权重都由一个 Variable 对象支持。在 TensorFlow.js 中,Variable 是一个浮点 Tensor,它还有一个额外的 assign() 方法,用于更新其值。层 API 会自动使用最佳实践初始化权重。为了演示,我们可以通过在底层变量上调用 assign() 来覆盖权重

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

优化器、损失和指标

在进行任何训练之前,您需要确定三件事

  1. 优化器。优化器的作用是根据当前模型预测确定要更改模型中每个参数的程度。使用层 API 时,您可以提供现有优化器的字符串标识符(例如 'sgd''adam'),或 Optimizer 类的实例。
  2. 损失函数。模型将尝试最小化的目标。它的目标是为模型预测的“错误程度”提供一个单一数字。损失是在每批数据上计算的,以便模型可以更新其权重。使用层 API 时,您可以提供现有损失函数的字符串标识符(例如 'categoricalCrossentropy'),或任何接受预测值和真实值并返回损失的函数。请参阅我们的 API 文档中提供的损失列表
  3. 指标列表。 与损失函数类似,指标计算单个数字,总结模型的执行情况。指标通常在每个 epoch 结束时对整个数据进行计算。至少,我们希望监控损失函数随着时间的推移而下降。但是,我们通常希望使用更人性化的指标,例如准确率。使用 Layers API 时,您可以提供现有指标的字符串标识符(例如 'accuracy'),或任何接受预测值和真实值并返回分数的函数。请参阅我们的 API 文档中的 可用指标列表

确定后,通过调用 model.compile() 并提供选项来编译 LayersModel

model.compile({
  optimizer: 'sgd',
  loss: 'categoricalCrossentropy',
  metrics: ['accuracy']
});

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

训练

有两种方法可以训练 LayersModel

  • 使用 model.fit() 并将数据作为单个大型张量提供。
  • 使用 model.fitDataset() 并通过 Dataset 对象提供数据。

model.fit()

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

// Generate dummy data.
const data = tf.randomNormal([100, 784]);
const labels = tf.randomUniform([100, 10]);

function onBatchEnd(batch, logs) {
  console.log('Accuracy', logs.acc);
}

// Train for 5 epochs with batch size of 32.
model.fit(data, labels, {
   epochs: 5,
   batchSize: 32,
   callbacks: {onBatchEnd}
 }).then(info => {
   console.log('Final accuracy', info.history.acc);
 });

在幕后,model.fit() 可以为我们做很多事情

  • 将数据分成训练集和验证集,并使用验证集来衡量训练过程中的进度。
  • 对数据进行洗牌,但仅在分割之后。为了安全起见,您应该在将数据传递给 fit() 之前对其进行预洗牌。
  • 将大型数据张量分成大小为 batchSize 的较小张量。
  • 调用 optimizer.minimize(),同时计算模型相对于数据批次的损失。
  • 它可以在每个 epoch 或批次的开始和结束时通知您。在本例中,我们使用 callbacks.onBatchEnd 选项在每个批次结束时收到通知。其他选项包括:onTrainBeginonTrainEndonEpochBeginonEpochEndonBatchBegin
  • 它会让出主线程,以确保在 JS 事件循环中排队的任务能够及时处理。

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

model.fitDataset()

如果您的数据不完全适合内存,或者正在流式传输,则可以通过调用 fitDataset() 来训练模型,该方法接受 Dataset 对象。以下是使用包装生成器函数的数据集的相同训练代码

function* data() {
 for (let i = 0; i < 100; i++) {
   // Generate one sample at a time.
   yield tf.randomNormal([784]);
 }
}

function* labels() {
 for (let i = 0; i < 100; i++) {
   // Generate one sample at a time.
   yield tf.randomUniform([10]);
 }
}

const xs = tf.data.generator(data);
const ys = tf.data.generator(labels);
// We zip the data and labels together, shuffle and batch 32 samples at a time.
const ds = tf.data.zip({xs, ys}).shuffle(100 /* bufferSize */).batch(32);

// Train the model for 5 epochs.
model.fitDataset(ds, {epochs: 5}).then(info => {
 console.log('Accuracy', info.history.acc);
});

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

预测新数据

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

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

Core API

之前,我们提到过在 TensorFlow.js 中训练机器学习模型有两种方法。

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

您可能希望在以下情况下使用 Core API

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

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

使用 Core API 编写的与上面相同的模型如下所示

// The weights and biases for the two dense layers.
const w1 = tf.variable(tf.randomNormal([784, 32]));
const b1 = tf.variable(tf.randomNormal([32]));
const w2 = tf.variable(tf.randomNormal([32, 10]));
const b2 = tf.variable(tf.randomNormal([10]));

function model(x) {
  return x.matMul(w1).add(b1).relu().matMul(w2).add(b2);
}

除了 Layers API 之外,Data API 还与 Core API 无缝协作。让我们重用我们在 model.fitDataset() 部分中定义的数据集,该数据集为我们执行洗牌和批处理

const xs = tf.data.generator(data);
const ys = tf.data.generator(labels);
// Zip the data and labels together, shuffle and batch 32 samples at a time.
const ds = tf.data.zip({xs, ys}).shuffle(100 /* bufferSize */).batch(32);

让我们训练模型

const optimizer = tf.train.sgd(0.1 /* learningRate */);
// Train for 5 epochs.
for (let epoch = 0; epoch < 5; epoch++) {
  await ds.forEachAsync(({xs, ys}) => {
    optimizer.minimize(() => {
      const predYs = model(xs);
      const loss = tf.losses.softmaxCrossEntropy(ys, predYs);
      loss.data().then(l => console.log('Loss', l));
      return loss;
    });
  });
  console.log('Epoch', epoch);
}

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

  • 循环遍历 epoch 数。
  • 在每个 epoch 内,循环遍历您的数据批次。使用 Dataset 时,dataset.forEachAsync() 是循环遍历批次的便捷方法。
  • 对于每个批次,调用 optimizer.minimize(f),该方法执行 f 并通过计算相对于我们之前定义的四个变量的梯度来最小化其输出。
  • f 计算损失。它使用模型的预测和真实值调用预定义的损失函数之一。