模型訓練

本指南假設您已閱讀過模型與層指南。

在 TensorFlow.js 中,有兩種訓練機器學習模型的方式

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

首先,我們會介紹 Layers API,這是一種用於建構及訓練模型的高階 API。接著,我們會示範如何使用 Core API 訓練相同的模型。

簡介

機器學習模型是一個具有可學習參數的函式,可將輸入對應至所需的輸出。最佳參數是透過在資料上訓練模型取得。

訓練包含幾個步驟

  • 取得模型的資料批次
  • 要求模型進行預測。
  • 將該預測與「真實」值進行比較。
  • 決定要變更多少每個參數,以便模型在未來針對該批次做出更佳的預測。

訓練有素的模型將提供從輸入到所需輸出的精確對應。

模型參數

讓我們使用 Layers 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(),用於更新其值。Layers 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. 最佳化工具。最佳化工具的工作是根據目前的模型預測,決定要變更模型中每個參數的幅度。使用 Layers API 時,您可以提供現有最佳化工具的字串 ID (例如 'sgd''adam'),或是 Optimizer 類別的執行個體。
  2. 損失函式。模型將嘗試最小化的目標。其目標是為模型的預測「錯誤程度」提供一個數字。損失是在每批資料上計算,以便模型可以更新其權重。使用 Layers API 時,您可以提供現有損失函式的字串 ID (例如 'categoricalCrossentropy'),或是任何接受預測值和真實值並傳回損失的函式。請參閱 API 文件中的可用損失清單
  3. 指標清單。與損失類似,指標會計算一個數字,總結我們的模型執行成效。指標通常是在每個週期結束時針對整個資料計算。至少,我們希望監控損失是否隨著時間推移而減少。不過,我們通常想要更人性化的指標,例如準確度。使用 Layers API 時,您可以提供現有指標的字串 ID (例如 '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()
  • 它可以在每個週期或批次的開始和結束時通知您。在我們的案例中,我們會在每個批次結束時收到通知,方法是使用 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 訓練模型時的標準做法

  • 迴圈處理週期數。
  • 在每個週期內,迴圈處理您的資料批次。使用 Dataset 時,dataset.forEachAsync() 是迴圈處理批次的便利方法。
  • 針對每個批次,呼叫 optimizer.minimize(f),此方法會執行 f 並透過計算相對於我們稍早定義的四個變數的梯度來最小化其輸出。
  • f 會計算損失。它會使用模型預測和真實值呼叫其中一個預先定義的損失函式。