本指南假設您已閱讀過模型與層指南。
在 TensorFlow.js 中,有兩種訓練機器學習模型的方式
- 使用 Layers API 和
LayersModel.fit()
或LayersModel.fitDataset()
。 - 使用 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);
});
最佳化工具、損失和指標
在進行任何訓練之前,您需要決定三件事
- 最佳化工具。最佳化工具的工作是根據目前的模型預測,決定要變更模型中每個參數的幅度。使用 Layers API 時,您可以提供現有最佳化工具的字串 ID (例如
'sgd'
或'adam'
),或是Optimizer
類別的執行個體。 - 損失函式。模型將嘗試最小化的目標。其目標是為模型的預測「錯誤程度」提供一個數字。損失是在每批資料上計算,以便模型可以更新其權重。使用 Layers API 時,您可以提供現有損失函式的字串 ID (例如
'categoricalCrossentropy'
),或是任何接受預測值和真實值並傳回損失的函式。請參閱 API 文件中的可用損失清單。 - 指標清單。與損失類似,指標會計算一個數字,總結我們的模型執行成效。指標通常是在每個週期結束時針對整個資料計算。至少,我們希望監控損失是否隨著時間推移而減少。不過,我們通常想要更人性化的指標,例如準確度。使用 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
選項。其他選項包括:onTrainBegin
、onTrainEnd
、onEpochBegin
、onEpochEnd
和onBatchBegin
。 - 它會讓步給主執行緒,以確保可以及時處理在 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
會計算損失。它會使用模型預測和真實值呼叫其中一個預先定義的損失函式。