使用內建方法進行訓練與評估

作者: fchollet

在 TensorFlow.org 上檢視 在 Google Colab 中執行 在 GitHub 上檢視原始碼 在 keras.io 上檢視

設定

import tensorflow as tf
import keras
from keras import layers

簡介

本指南涵蓋使用內建 API 進行訓練與驗證時的模型訓練、評估和預測 (推論),例如 Model.fit()Model.evaluate()Model.predict()

如果您有興趣在指定自己的訓練步驟函式時運用 fit(),請參閱自訂 fit() 中的行為指南

如果您有興趣從頭開始編寫自己的訓練與評估迴圈,請參閱「從頭開始編寫訓練迴圈」指南

一般而言,無論您是使用內建迴圈還是編寫自己的迴圈,模型訓練與評估在每一種 Keras 模型 (Sequential 模型、使用 Functional API 建構的模型,以及透過模型子類別化從頭開始編寫的模型) 中的運作方式都完全相同。

本指南未涵蓋分散式訓練,此主題在我們的多 GPU 與分散式訓練指南中說明。

API 總覽:第一個端對端範例

將資料傳遞至模型的內建訓練迴圈時,您應該使用 NumPy 陣列 (如果您的資料量小且適合放入記憶體) 或 tf.data.Dataset 物件。在接下來的幾個段落中,我們將使用 MNIST 資料集作為 NumPy 陣列,以示範如何使用最佳化工具、損失和指標。

讓我們考慮以下模型 (在此範例中,我們使用 Functional API 進行建構,但它也可以是 Sequential 模型或子類別化模型)

inputs = keras.Input(shape=(784,), name="digits")
x = layers.Dense(64, activation="relu", name="dense_1")(inputs)
x = layers.Dense(64, activation="relu", name="dense_2")(x)
outputs = layers.Dense(10, activation="softmax", name="predictions")(x)

model = keras.Model(inputs=inputs, outputs=outputs)

以下是典型的端對端工作流程,包含:

  • 訓練
  • 在從原始訓練資料產生的保留集中進行驗證
  • 在測試資料上進行評估

在此範例中,我們將使用 MNIST 資料。

(x_train, y_train), (x_test, y_test) = keras.datasets.mnist.load_data()

# Preprocess the data (these are NumPy arrays)
x_train = x_train.reshape(60000, 784).astype("float32") / 255
x_test = x_test.reshape(10000, 784).astype("float32") / 255

y_train = y_train.astype("float32")
y_test = y_test.astype("float32")

# Reserve 10,000 samples for validation
x_val = x_train[-10000:]
y_val = y_train[-10000:]
x_train = x_train[:-10000]
y_train = y_train[:-10000]

我們指定訓練設定 (最佳化工具、損失、指標)

model.compile(
    optimizer=keras.optimizers.RMSprop(),  # Optimizer
    # Loss function to minimize
    loss=keras.losses.SparseCategoricalCrossentropy(),
    # List of metrics to monitor
    metrics=[keras.metrics.SparseCategoricalAccuracy()],
)

我們呼叫 fit(),這會透過將資料分割成大小為 batch_size 的「批次」,並針對給定的 epochs 數重複疊代整個資料集,來訓練模型。

print("Fit model on training data")
history = model.fit(
    x_train,
    y_train,
    batch_size=64,
    epochs=2,
    # We pass some validation for
    # monitoring validation loss and metrics
    # at the end of each epoch
    validation_data=(x_val, y_val),
)
Fit model on training data
Epoch 1/2
782/782 [==============================] - 4s 3ms/step - loss: 0.3414 - sparse_categorical_accuracy: 0.9024 - val_loss: 0.1810 - val_sparse_categorical_accuracy: 0.9466
Epoch 2/2
782/782 [==============================] - 2s 2ms/step - loss: 0.1594 - sparse_categorical_accuracy: 0.9523 - val_loss: 0.1376 - val_sparse_categorical_accuracy: 0.9598

傳回的 history 物件會保存訓練期間損失值和指標值的記錄

history.history
{'loss': [0.341447114944458, 0.15940724313259125],
 'sparse_categorical_accuracy': [0.9024400115013123, 0.9523400068283081],
 'val_loss': [0.18102389574050903, 0.13764098286628723],
 'val_sparse_categorical_accuracy': [0.9466000199317932, 0.9598000049591064]}

我們透過 evaluate() 在測試資料上評估模型

# Evaluate the model on the test data using `evaluate`
print("Evaluate on test data")
results = model.evaluate(x_test, y_test, batch_size=128)
print("test loss, test acc:", results)

# Generate predictions (probabilities -- the output of the last layer)
# on new data using `predict`
print("Generate predictions for 3 samples")
predictions = model.predict(x_test[:3])
print("predictions shape:", predictions.shape)
79/79 [==============================] - 0s 2ms/step - loss: 0.1448 - sparse_categorical_accuracy: 0.9537
1/1 [==============================] - 0s 73ms/step
predictions shape: (3, 10)

現在,讓我們詳細檢閱此工作流程的每個部分。

compile() 方法:指定損失、指標和最佳化工具

若要使用 fit() 訓練模型,您需要指定損失函式、最佳化工具,以及選擇性地指定一些要監控的指標。

您將這些傳遞至模型作為 compile() 方法的引數

model.compile(
    optimizer=keras.optimizers.RMSprop(learning_rate=1e-3),
    loss=keras.losses.SparseCategoricalCrossentropy(),
    metrics=[keras.metrics.SparseCategoricalAccuracy()],
)

metrics 引數應該是清單,您的模型可以有任意數量的指標。

如果您的模型有多個輸出,您可以為每個輸出指定不同的損失和指標,並且可以調整每個輸出對模型總損失的貢獻。您會在將資料傳遞至多輸入、多輸出模型章節中找到更多關於此主題的詳細資訊。

請注意,如果您對預設設定感到滿意,在許多情況下,最佳化工具、損失和指標可以透過字串識別碼作為捷徑來指定

model.compile(
    optimizer="rmsprop",
    loss="sparse_categorical_crossentropy",
    metrics=["sparse_categorical_accuracy"],
)

為了稍後重複使用,讓我們將模型定義和編譯步驟放入函式中;我們將在本指南的不同範例中多次呼叫它們。

def get_uncompiled_model():
    inputs = keras.Input(shape=(784,), name="digits")
    x = layers.Dense(64, activation="relu", name="dense_1")(inputs)
    x = layers.Dense(64, activation="relu", name="dense_2")(x)
    outputs = layers.Dense(10, activation="softmax", name="predictions")(x)
    model = keras.Model(inputs=inputs, outputs=outputs)
    return model


def get_compiled_model():
    model = get_uncompiled_model()
    model.compile(
        optimizer="rmsprop",
        loss="sparse_categorical_crossentropy",
        metrics=["sparse_categorical_accuracy"],
    )
    return model

提供許多內建的最佳化工具、損失和指標

一般而言,您不需要從頭開始建立自己的損失、指標或最佳化工具,因為您需要的內容可能已經是 Keras API 的一部分

最佳化工具

  • SGD() (有或沒有動量)
  • RMSprop()
  • Adam()
  • 等等。

損失

  • MeanSquaredError()
  • KLDivergence()
  • CosineSimilarity()
  • 等等。

指標

  • AUC()
  • Precision()
  • Recall()
  • 等等。

自訂損失

如果您需要建立自訂損失,Keras 提供三種方法來執行此操作。

第一種方法是建立一個接受輸入 y_truey_pred 的函式。以下範例顯示一個損失函式,用於計算真實資料與預測之間的均方誤差

def custom_mean_squared_error(y_true, y_pred):
    return tf.math.reduce_mean(tf.square(y_true - y_pred), axis=-1)


model = get_uncompiled_model()
model.compile(optimizer=keras.optimizers.Adam(), loss=custom_mean_squared_error)

# We need to one-hot encode the labels to use MSE
y_train_one_hot = tf.one_hot(y_train, depth=10)
model.fit(x_train, y_train_one_hot, batch_size=64, epochs=1)
782/782 [==============================] - 3s 2ms/step - loss: 0.0158
<keras.src.callbacks.History at 0x7fd65c343310>

如果您需要的損失函式除了 y_truey_pred 之外還需要參數,您可以將 keras.losses.Loss 類別子類別化,並實作以下兩種方法

  • __init__(self):接受要在損失函式呼叫期間傳遞的參數
  • call(self, y_true, y_pred):使用目標 (y_true) 和模型預測 (y_pred) 來計算模型的損失

假設您想要使用均方誤差,但要新增一個附加項,以減少遠離 0.5 的預測值 (我們假設類別目標是單熱編碼,且取值介於 0 和 1 之間)。這會鼓勵模型不要過於自信,這可能有助於減少過度擬合 (在我們嘗試之前,我們不會知道它是否有效!)。

以下說明如何執行此操作

@keras.saving.register_keras_serializable()
class CustomMSE(keras.losses.Loss):
    def __init__(self, regularization_factor=0.1, name="custom_mse"):
        super().__init__(name=name)
        self.regularization_factor = regularization_factor

    def call(self, y_true, y_pred):
        mse = tf.math.reduce_mean(tf.square(y_true - y_pred), axis=-1)
        reg = tf.math.reduce_mean(tf.square(0.5 - y_pred), axis=-1)
        return mse + reg * self.regularization_factor

    def get_config(self):
        return {
            "regularization_factor": self.regularization_factor,
            "name": self.name,
        }


model = get_uncompiled_model()
model.compile(optimizer=keras.optimizers.Adam(), loss=CustomMSE())

y_train_one_hot = tf.one_hot(y_train, depth=10)
model.fit(x_train, y_train_one_hot, batch_size=64, epochs=1)
782/782 [==============================] - 3s 2ms/step - loss: 0.0385
<keras.src.callbacks.History at 0x7fd65c197c10>

自訂指標

如果您需要的指標不是 API 的一部分,您可以透過將 keras.metrics.Metric 類別子類別化,輕鬆建立自訂指標。您需要實作 4 種方法

  • __init__(self):您將在其中為您的指標建立狀態變數。
  • update_state(self, y_true, y_pred, sample_weight=None):使用目標 y_true 和模型預測 y_pred 來更新狀態變數。
  • result(self):使用狀態變數來計算最終結果。
  • reset_state(self):重新初始化指標的狀態。

狀態更新和結果計算保持分離 (分別在 update_state()result() 中),因為在某些情況下,結果計算可能非常耗時,並且只會定期完成。

以下是一個簡單的範例,說明如何實作 CategoricalTruePositives 指標,以計算有多少樣本被正確分類為屬於給定類別

@keras.saving.register_keras_serializable()
class CategoricalTruePositives(keras.metrics.Metric):
    def __init__(self, name="categorical_true_positives", **kwargs):
        super().__init__(name=name, **kwargs)
        self.true_positives = self.add_weight(name="ctp", initializer="zeros")

    def update_state(self, y_true, y_pred, sample_weight=None):
        y_pred = tf.reshape(tf.argmax(y_pred, axis=1), shape=(-1, 1))
        values = tf.cast(y_true, "int32") == tf.cast(y_pred, "int32")
        values = tf.cast(values, "float32")
        if sample_weight is not None:
            sample_weight = tf.cast(sample_weight, "float32")
            values = tf.multiply(values, sample_weight)
        self.true_positives.assign_add(tf.reduce_sum(values))

    def result(self):
        return self.true_positives

    def reset_state(self):
        # The state of the metric will be reset at the start of each epoch.
        self.true_positives.assign(0.0)


model = get_uncompiled_model()
model.compile(
    optimizer=keras.optimizers.RMSprop(learning_rate=1e-3),
    loss=keras.losses.SparseCategoricalCrossentropy(),
    metrics=[CategoricalTruePositives()],
)
model.fit(x_train, y_train, batch_size=64, epochs=3)
Epoch 1/3
782/782 [==============================] - 2s 2ms/step - loss: 0.3449 - categorical_true_positives: 45089.0000
Epoch 2/3
782/782 [==============================] - 2s 2ms/step - loss: 0.1618 - categorical_true_positives: 47589.0000
Epoch 3/3
782/782 [==============================] - 2s 2ms/step - loss: 0.1192 - categorical_true_positives: 48198.0000
<keras.src.callbacks.History at 0x7fd64c415be0>

處理不符合標準簽章的損失和指標

絕大多數的損失和指標可以從 y_truey_pred 計算出來,其中 y_pred 是模型的輸出,但並非全部。例如,正規化損失可能只需要層的啟動 (在這種情況下沒有目標),而此啟動可能不是模型輸出。

在這種情況下,您可以從自訂層的呼叫方法內呼叫 self.add_loss(loss_value)。以這種方式新增的損失會在訓練期間新增至「主要」損失 (傳遞至 compile() 的損失)。以下是一個簡單的範例,說明如何新增活動正規化 (請注意,活動正規化已內建於所有 Keras 層中,此層僅用於提供具體範例)

@keras.saving.register_keras_serializable()
class ActivityRegularizationLayer(layers.Layer):
    def call(self, inputs):
        self.add_loss(tf.reduce_sum(inputs) * 0.1)
        return inputs  # Pass-through layer.


inputs = keras.Input(shape=(784,), name="digits")
x = layers.Dense(64, activation="relu", name="dense_1")(inputs)

# Insert activity regularization as a layer
x = ActivityRegularizationLayer()(x)

x = layers.Dense(64, activation="relu", name="dense_2")(x)
outputs = layers.Dense(10, name="predictions")(x)

model = keras.Model(inputs=inputs, outputs=outputs)
model.compile(
    optimizer=keras.optimizers.RMSprop(learning_rate=1e-3),
    loss=keras.losses.SparseCategoricalCrossentropy(from_logits=True),
)

# The displayed loss will be much higher than before
# due to the regularization component.
model.fit(x_train, y_train, batch_size=64, epochs=1)
782/782 [==============================] - 2s 2ms/step - loss: 2.4921
<keras.src.callbacks.History at 0x7fd64c257340>

請注意,當您透過 add_loss() 傳遞損失時,可以呼叫 compile() 而不使用損失函式,因為模型已具有要最小化的損失。

考慮以下 LogisticEndpoint 層:它將目標和 logits 作為輸入,並透過 add_loss() 追蹤交叉熵損失。

@keras.saving.register_keras_serializable()
class LogisticEndpoint(keras.layers.Layer):
    def __init__(self, name=None):
        super().__init__(name=name)
        self.loss_fn = keras.losses.BinaryCrossentropy(from_logits=True)

    def call(self, targets, logits, sample_weights=None):
        # Compute the training-time loss value and add it
        # to the layer using `self.add_loss()`.
        loss = self.loss_fn(targets, logits, sample_weights)
        self.add_loss(loss)

        # Return the inference-time prediction tensor (for `.predict()`).
        return tf.nn.softmax(logits)

您可以在具有兩個輸入 (輸入資料和目標) 的模型中使用它,編譯時不使用 loss 引數,如下所示

import numpy as np

inputs = keras.Input(shape=(3,), name="inputs")
targets = keras.Input(shape=(10,), name="targets")
logits = keras.layers.Dense(10)(inputs)
predictions = LogisticEndpoint(name="predictions")(targets, logits)

model = keras.Model(inputs=[inputs, targets], outputs=predictions)
model.compile(optimizer="adam")  # No loss argument!

data = {
    "inputs": np.random.random((3, 3)),
    "targets": np.random.random((3, 10)),
}
model.fit(data)
1/1 [==============================] - 1s 522ms/step - loss: 0.7258
<keras.src.callbacks.History at 0x7fd64c188400>

如需關於訓練多輸入模型的詳細資訊,請參閱將資料傳遞至多輸入、多輸出模型章節。

自動設定保留驗證集

在您看到的第一個端對端範例中,我們使用 validation_data 引數將 NumPy 陣列元組 (x_val, y_val) 傳遞至模型,以在每個 epoch 結束時評估驗證損失和驗證指標。

以下是另一個選項:validation_split 引數可讓您自動保留部分訓練資料用於驗證。引數值代表要保留用於驗證的資料比例,因此應設定為大於 0 且小於 1 的數字。例如,validation_split=0.2 表示「使用 20% 的資料進行驗證」,而 validation_split=0.6 表示「使用 60% 的資料進行驗證」。

驗證的計算方式是取 fit() 呼叫接收的陣列的最後 x% 樣本,在任何隨機排序之前。

請注意,當使用 NumPy 資料進行訓練時,您只能使用 validation_split

model = get_compiled_model()
model.fit(x_train, y_train, batch_size=64, validation_split=0.2, epochs=1)
625/625 [==============================] - 2s 3ms/step - loss: 0.3743 - sparse_categorical_accuracy: 0.8947 - val_loss: 0.2315 - val_sparse_categorical_accuracy: 0.9293
<keras.src.callbacks.History at 0x7fd64c050e20>

從 tf.data Dataset 進行訓練與評估

在過去的幾個段落中,您已了解如何處理損失、指標和最佳化工具,並且已了解當您的資料以 NumPy 陣列形式傳遞時,如何在 fit() 中使用 validation_datavalidation_split 引數。

現在讓我們看看您的資料以 tf.data.Dataset 物件形式呈現的情況。

tf.data API 是 TensorFlow 2.0 中的一組公用程式,用於以快速且可擴充的方式載入和預先處理資料。

如需關於建立 Datasets 的完整指南,請參閱 tf.data 文件

您可以將 Dataset 執行個體直接傳遞至方法 fit()evaluate()predict()

model = get_compiled_model()

# First, let's create a training Dataset instance.
# For the sake of our example, we'll use the same MNIST data as before.
train_dataset = tf.data.Dataset.from_tensor_slices((x_train, y_train))
# Shuffle and slice the dataset.
train_dataset = train_dataset.shuffle(buffer_size=1024).batch(64)

# Now we get a test dataset.
test_dataset = tf.data.Dataset.from_tensor_slices((x_test, y_test))
test_dataset = test_dataset.batch(64)

# Since the dataset already takes care of batching,
# we don't pass a `batch_size` argument.
model.fit(train_dataset, epochs=3)

# You can also evaluate or predict on a dataset.
print("Evaluate")
result = model.evaluate(test_dataset)
dict(zip(model.metrics_names, result))
Epoch 1/3
782/782 [==============================] - 2s 2ms/step - loss: 0.3365 - sparse_categorical_accuracy: 0.9042
Epoch 2/3
782/782 [==============================] - 2s 2ms/step - loss: 0.1628 - sparse_categorical_accuracy: 0.9523
Epoch 3/3
782/782 [==============================] - 2s 2ms/step - loss: 0.1185 - sparse_categorical_accuracy: 0.9648
Evaluate
157/157 [==============================] - 0s 2ms/step - loss: 0.1247 - sparse_categorical_accuracy: 0.9627
{'loss': 0.12467469274997711,
 'sparse_categorical_accuracy': 0.9627000093460083}

請注意,Dataset 會在每個 epoch 結束時重設,因此可以在下一個 epoch 中重複使用。

如果您只想針對此 Dataset 中特定數量的批次執行訓練,您可以傳遞 steps_per_epoch 引數,其指定模型應使用此 Dataset 執行多少訓練步驟,然後再繼續下一個 epoch。

如果您執行此操作,則資料集不會在每個 epoch 結束時重設,而是我們只會持續繪製下一個批次。資料集最終會耗盡資料 (除非它是無限迴圈資料集)。

model = get_compiled_model()

# Prepare the training dataset
train_dataset = tf.data.Dataset.from_tensor_slices((x_train, y_train))
train_dataset = train_dataset.shuffle(buffer_size=1024).batch(64)

# Only use the 100 batches per epoch (that's 64 * 100 samples)
model.fit(train_dataset, epochs=3, steps_per_epoch=100)
Epoch 1/3
100/100 [==============================] - 1s 2ms/step - loss: 0.7870 - sparse_categorical_accuracy: 0.7933
Epoch 2/3
100/100 [==============================] - 0s 2ms/step - loss: 0.3748 - sparse_categorical_accuracy: 0.8969
Epoch 3/3
100/100 [==============================] - 0s 2ms/step - loss: 0.3169 - sparse_categorical_accuracy: 0.9070
<keras.src.callbacks.History at 0x7fd6242a8790>

使用驗證資料集

您可以將 Dataset 執行個體作為 fit() 中的 validation_data 引數傳遞

model = get_compiled_model()

# Prepare the training dataset
train_dataset = tf.data.Dataset.from_tensor_slices((x_train, y_train))
train_dataset = train_dataset.shuffle(buffer_size=1024).batch(64)

# Prepare the validation dataset
val_dataset = tf.data.Dataset.from_tensor_slices((x_val, y_val))
val_dataset = val_dataset.batch(64)

model.fit(train_dataset, epochs=1, validation_data=val_dataset)
782/782 [==============================] - 3s 2ms/step - loss: 0.3470 - sparse_categorical_accuracy: 0.9005 - val_loss: 0.1883 - val_sparse_categorical_accuracy: 0.9491
<keras.src.callbacks.History at 0x7fd65c343580>

在每個 epoch 結束時,模型將疊代驗證資料集並計算驗證損失和驗證指標。

如果您只想針對此資料集中特定數量的批次執行驗證,您可以傳遞 validation_steps 引數,其指定模型應使用驗證資料集執行多少驗證步驟,然後再中斷驗證並繼續下一個 epoch

model = get_compiled_model()

# Prepare the training dataset
train_dataset = tf.data.Dataset.from_tensor_slices((x_train, y_train))
train_dataset = train_dataset.shuffle(buffer_size=1024).batch(64)

# Prepare the validation dataset
val_dataset = tf.data.Dataset.from_tensor_slices((x_val, y_val))
val_dataset = val_dataset.batch(64)

model.fit(
    train_dataset,
    epochs=1,
    # Only run validation using the first 10 batches of the dataset
    # using the `validation_steps` argument
    validation_data=val_dataset,
    validation_steps=10,
)
782/782 [==============================] - 2s 2ms/step - loss: 0.3375 - sparse_categorical_accuracy: 0.9046 - val_loss: 0.3063 - val_sparse_categorical_accuracy: 0.9234
<keras.src.callbacks.History at 0x7fd64c2b4280>

請注意,驗證資料集會在每次使用後重設 (以便您始終評估每個 epoch 中的相同樣本)。

Dataset 物件進行訓練時,不支援 validation_split 引數 (從訓練資料產生保留集),因為此功能需要能夠為資料集的樣本建立索引,這在使用 Dataset API 時通常不可能。

支援的其他輸入格式

除了 NumPy 陣列、eager 張量和 TensorFlow Datasets 之外,也可以使用 Pandas 資料框架或產生資料和標籤批次的 Python 產生器來訓練 Keras 模型。

特別是,keras.utils.Sequence 類別提供一個簡單的介面,用於建構可感知多處理且可以隨機排序的 Python 資料產生器。

一般而言,我們建議您使用:

  • 如果您的資料量小且適合放入記憶體,則使用 NumPy 輸入資料
  • 如果您有大型資料集且需要執行分散式訓練,則使用 Dataset 物件
  • 如果您有大型資料集且需要執行大量無法在 TensorFlow 中完成的自訂 Python 端處理 (例如,如果您依賴外部程式庫進行資料載入或預先處理),則使用 Sequence 物件。

使用 keras.utils.Sequence 物件作為輸入

keras.utils.Sequence 是一個公用程式,您可以將其子類別化以取得具有兩個重要屬性的 Python 產生器:

  • 它與多處理良好協作。
  • 它可以隨機排序 (例如,在 fit() 中傳遞 shuffle=True 時)。

Sequence 必須實作兩種方法:

  • __getitem__
  • __len__

__getitem__ 方法應傳回完整的批次。如果您想要在 epoch 之間修改資料集,您可以實作 on_epoch_end

以下是一個快速範例

from skimage.io import imread
from skimage.transform import resize
import numpy as np

# Here, `filenames` is list of path to the images
# and `labels` are the associated labels.

class CIFAR10Sequence(Sequence):
    def __init__(self, filenames, labels, batch_size):
        self.filenames, self.labels = filenames, labels
        self.batch_size = batch_size

    def __len__(self):
        return int(np.ceil(len(self.filenames) / float(self.batch_size)))

    def __getitem__(self, idx):
        batch_x = self.filenames[idx * self.batch_size:(idx + 1) * self.batch_size]
        batch_y = self.labels[idx * self.batch_size:(idx + 1) * self.batch_size]
        return np.array([
            resize(imread(filename), (200, 200))
               for filename in batch_x]), np.array(batch_y)

sequence = CIFAR10Sequence(filenames, labels, batch_size)
model.fit(sequence, epochs=10)

使用樣本權重和類別權重

使用預設設定時,樣本的權重由其在資料集中的頻率決定。有兩種方法可以權衡資料,與樣本頻率無關:

  • 類別權重
  • 樣本權重

類別權重

這是透過將字典傳遞至 Model.fit()class_weight 引數來設定。此字典將類別索引對應至應適用於屬於此類別的樣本的權重。

這可用於在不重新取樣的情況下平衡類別,或訓練更重視特定類別的模型。

例如,如果類別「0」在您的資料中表示的頻率是類別「1」的一半,您可以使用 Model.fit(..., class_weight={0: 1., 1: 0.5})

以下是一個 NumPy 範例,我們在其中使用類別權重或樣本權重來更重視正確分類類別 #5 (在 MNIST 資料集中為數字「5」)。

import numpy as np

class_weight = {
    0: 1.0,
    1: 1.0,
    2: 1.0,
    3: 1.0,
    4: 1.0,
    # Set weight "2" for class "5",
    # making this class 2x more important
    5: 2.0,
    6: 1.0,
    7: 1.0,
    8: 1.0,
    9: 1.0,
}

print("Fit with class weight")
model = get_compiled_model()
model.fit(x_train, y_train, class_weight=class_weight, batch_size=64, epochs=1)
Fit with class weight
782/782 [==============================] - 3s 2ms/step - loss: 0.3721 - sparse_categorical_accuracy: 0.9007
<keras.src.callbacks.History at 0x7fd5a032de80>

樣本權重

如需精細控制,或者如果您未建構分類器,則可以使用「樣本權重」。

  • 從 NumPy 資料訓練時:將 sample_weight 引數傳遞至 Model.fit()
  • tf.data 或任何其他種類的疊代器訓練時:產生 (input_batch, label_batch, sample_weight_batch) 元組。

「樣本權重」陣列是一個數字陣列,用於指定批次中每個樣本在計算總損失時應具有多少權重。它通常用於不平衡分類問題 (其概念是為罕見類別提供更多權重)。

當使用的權重為 1 和 0 時,陣列可以用作損失函式的遮罩 (完全捨棄某些樣本對總損失的貢獻)。

sample_weight = np.ones(shape=(len(y_train),))
sample_weight[y_train == 5] = 2.0

print("Fit with sample weight")
model = get_compiled_model()
model.fit(x_train, y_train, sample_weight=sample_weight, batch_size=64, epochs=1)
Fit with sample weight
782/782 [==============================] - 2s 2ms/step - loss: 0.3753 - sparse_categorical_accuracy: 0.9019
<keras.src.callbacks.History at 0x7fd5a01eafa0>

以下是一個相符的 Dataset 範例

sample_weight = np.ones(shape=(len(y_train),))
sample_weight[y_train == 5] = 2.0

# Create a Dataset that includes sample weights
# (3rd element in the return tuple).
train_dataset = tf.data.Dataset.from_tensor_slices((x_train, y_train, sample_weight))

# Shuffle and slice the dataset.
train_dataset = train_dataset.shuffle(buffer_size=1024).batch(64)

model = get_compiled_model()
model.fit(train_dataset, epochs=1)
782/782 [==============================] - 2s 2ms/step - loss: 0.3794 - sparse_categorical_accuracy: 0.9023
<keras.src.callbacks.History at 0x7fd5a00a0f40>

將資料傳遞至多輸入、多輸出模型

在先前的範例中,我們考慮的是具有單一輸入 (形狀為 (764,) 的張量) 和單一輸出 (形狀為 (10,) 的預測張量) 的模型。但是,具有多個輸入或輸出的模型又該如何處理?

考慮以下模型,其具有形狀為 (32, 32, 3) (即 (height, width, channels)) 的圖片輸入和形狀為 (None, 10) (即 (timesteps, features)) 的時間序列輸入。我們的模型將具有從這些輸入的組合計算出的兩個輸出:一個「分數」(形狀為 (1,)) 和五個類別上的機率分佈 (形狀為 (5,))。

image_input = keras.Input(shape=(32, 32, 3), name="img_input")
timeseries_input = keras.Input(shape=(None, 10), name="ts_input")

x1 = layers.Conv2D(3, 3)(image_input)
x1 = layers.GlobalMaxPooling2D()(x1)

x2 = layers.Conv1D(3, 3)(timeseries_input)
x2 = layers.GlobalMaxPooling1D()(x2)

x = layers.concatenate([x1, x2])

score_output = layers.Dense(1, name="score_output")(x)
class_output = layers.Dense(5, name="class_output")(x)

model = keras.Model(
    inputs=[image_input, timeseries_input], outputs=[score_output, class_output]
)

讓我們繪製此模型,以便您可以清楚地看到我們在此處執行的操作 (請注意,圖表中顯示的形狀是批次形狀,而不是每個樣本的形狀)。

keras.utils.plot_model(model, "multi_input_and_output_model.png", show_shapes=True)

png

在編譯時,我們可以透過將損失函式作為清單傳遞,為不同的輸出指定不同的損失

model.compile(
    optimizer=keras.optimizers.RMSprop(1e-3),
    loss=[keras.losses.MeanSquaredError(), keras.losses.CategoricalCrossentropy()],
)

如果我們只將單一損失函式傳遞至模型,則相同的損失函式將套用至每個輸出 (在此處不適用)。

指標也是如此

model.compile(
    optimizer=keras.optimizers.RMSprop(1e-3),
    loss=[keras.losses.MeanSquaredError(), keras.losses.CategoricalCrossentropy()],
    metrics=[
        [
            keras.metrics.MeanAbsolutePercentageError(),
            keras.metrics.MeanAbsoluteError(),
        ],
        [keras.metrics.CategoricalAccuracy()],
    ],
)

由於我們為輸出層命名,因此我們也可以透過字典指定每個輸出的損失和指標

model.compile(
    optimizer=keras.optimizers.RMSprop(1e-3),
    loss={
        "score_output": keras.losses.MeanSquaredError(),
        "class_output": keras.losses.CategoricalCrossentropy(),
    },
    metrics={
        "score_output": [
            keras.metrics.MeanAbsolutePercentageError(),
            keras.metrics.MeanAbsoluteError(),
        ],
        "class_output": [keras.metrics.CategoricalAccuracy()],
    },
)

如果您有超過 2 個輸出,我們建議使用明確的名稱和字典。

可以為不同的輸出特定損失指定不同的權重 (例如,可能希望優先考慮我們範例中的「分數」損失,方法是讓其重要性是類別損失的 2 倍),方法是使用 loss_weights 引數

model.compile(
    optimizer=keras.optimizers.RMSprop(1e-3),
    loss={
        "score_output": keras.losses.MeanSquaredError(),
        "class_output": keras.losses.CategoricalCrossentropy(),
    },
    metrics={
        "score_output": [
            keras.metrics.MeanAbsolutePercentageError(),
            keras.metrics.MeanAbsoluteError(),
        ],
        "class_output": [keras.metrics.CategoricalAccuracy()],
    },
    loss_weights={"score_output": 2.0, "class_output": 1.0},
)

您也可以選擇不計算某些輸出的損失,如果這些輸出旨在用於預測而非訓練

# List loss version
model.compile(
    optimizer=keras.optimizers.RMSprop(1e-3),
    loss=[None, keras.losses.CategoricalCrossentropy()],
)

# Or dict loss version
model.compile(
    optimizer=keras.optimizers.RMSprop(1e-3),
    loss={"class_output": keras.losses.CategoricalCrossentropy()},
)

fit() 中將資料傳遞至多輸入或多輸出模型的方式與在編譯中指定損失函式的方式類似:您可以傳遞 NumPy 陣列清單 (與接收損失函式的輸出進行 1:1 對應) 或 將輸出名稱對應至 NumPy 陣列的字典

model.compile(
    optimizer=keras.optimizers.RMSprop(1e-3),
    loss=[keras.losses.MeanSquaredError(), keras.losses.CategoricalCrossentropy()],
)

# Generate dummy NumPy data
img_data = np.random.random_sample(size=(100, 32, 32, 3))
ts_data = np.random.random_sample(size=(100, 20, 10))
score_targets = np.random.random_sample(size=(100, 1))
class_targets = np.random.random_sample(size=(100, 5))

# Fit on lists
model.fit([img_data, ts_data], [score_targets, class_targets], batch_size=32, epochs=1)

# Alternatively, fit on dicts
model.fit(
    {"img_input": img_data, "ts_input": ts_data},
    {"score_output": score_targets, "class_output": class_targets},
    batch_size=32,
    epochs=1,
)
4/4 [==============================] - 2s 16ms/step - loss: 10.9556 - score_output_loss: 0.3131 - class_output_loss: 10.6425
4/4 [==============================] - 0s 5ms/step - loss: 14.1855 - score_output_loss: 0.2088 - class_output_loss: 13.9767
<keras.src.callbacks.History at 0x7fd7f0d47040>

以下是 Dataset 用例:與我們對 NumPy 陣列執行的操作類似,Dataset 應傳回字典元組。

train_dataset = tf.data.Dataset.from_tensor_slices(
    (
        {"img_input": img_data, "ts_input": ts_data},
        {"score_output": score_targets, "class_output": class_targets},
    )
)
train_dataset = train_dataset.shuffle(buffer_size=1024).batch(64)

model.fit(train_dataset, epochs=1)
2/2 [==============================] - 0s 40ms/step - loss: 21.0572 - score_output_loss: 0.2223 - class_output_loss: 20.8349
<keras.src.callbacks.History at 0x7fd64c2b4b20>

使用回呼

Keras 中的回呼是在訓練期間不同時間點 (在 epoch 開始時、在批次結束時、在 epoch 結束時等等) 呼叫的物件。它們可用於實作某些行為,例如:

  • 在訓練期間的不同時間點執行驗證 (超出內建的每個 epoch 驗證)
  • 定期或在模型超出特定準確度閾值時檢查模型
  • 在訓練似乎趨於平穩時變更模型的學習率
  • 在訓練似乎趨於平穩時微調頂層
  • 在訓練結束或超出特定效能閾值時傳送電子郵件或即時訊息通知
  • 等等。

回呼可以作為清單傳遞至您對 fit() 的呼叫

model = get_compiled_model()

callbacks = [
    keras.callbacks.EarlyStopping(
        # Stop training when `val_loss` is no longer improving
        monitor="val_loss",
        # "no longer improving" being defined as "no better than 1e-2 less"
        min_delta=1e-2,
        # "no longer improving" being further defined as "for at least 2 epochs"
        patience=2,
        verbose=1,
    )
]
model.fit(
    x_train,
    y_train,
    epochs=20,
    batch_size=64,
    callbacks=callbacks,
    validation_split=0.2,
)
Epoch 1/20
625/625 [==============================] - 2s 3ms/step - loss: 0.3630 - sparse_categorical_accuracy: 0.8962 - val_loss: 0.2388 - val_sparse_categorical_accuracy: 0.9296
Epoch 2/20
625/625 [==============================] - 1s 2ms/step - loss: 0.1720 - sparse_categorical_accuracy: 0.9490 - val_loss: 0.1713 - val_sparse_categorical_accuracy: 0.9485
Epoch 3/20
625/625 [==============================] - 1s 2ms/step - loss: 0.1246 - sparse_categorical_accuracy: 0.9624 - val_loss: 0.1597 - val_sparse_categorical_accuracy: 0.9525
Epoch 4/20
625/625 [==============================] - 2s 2ms/step - loss: 0.0977 - sparse_categorical_accuracy: 0.9704 - val_loss: 0.1550 - val_sparse_categorical_accuracy: 0.9538
Epoch 5/20
625/625 [==============================] - 2s 2ms/step - loss: 0.0795 - sparse_categorical_accuracy: 0.9770 - val_loss: 0.1438 - val_sparse_categorical_accuracy: 0.9596
Epoch 6/20
625/625 [==============================] - 2s 2ms/step - loss: 0.0664 - sparse_categorical_accuracy: 0.9804 - val_loss: 0.1394 - val_sparse_categorical_accuracy: 0.9589
Epoch 7/20
625/625 [==============================] - 2s 2ms/step - loss: 0.0567 - sparse_categorical_accuracy: 0.9835 - val_loss: 0.1235 - val_sparse_categorical_accuracy: 0.9660
Epoch 8/20
625/625 [==============================] - 1s 2ms/step - loss: 0.0483 - sparse_categorical_accuracy: 0.9847 - val_loss: 0.1396 - val_sparse_categorical_accuracy: 0.9615
Epoch 9/20
625/625 [==============================] - 2s 2ms/step - loss: 0.0422 - sparse_categorical_accuracy: 0.9872 - val_loss: 0.1406 - val_sparse_categorical_accuracy: 0.9627
Epoch 9: early stopping
<keras.src.callbacks.History at 0x7fd7f1ec0c10>

提供許多內建回呼

Keras 中已提供許多內建回呼,例如:

  • ModelCheckpoint:定期儲存模型。
  • EarlyStopping:當訓練不再改善驗證指標時停止訓練。
  • TensorBoard:定期寫入可以在 TensorBoard 中視覺化的模型記錄 (在「視覺化」章節中有更多詳細資訊)。
  • CSVLogger:將損失和指標資料串流至 CSV 檔案。
  • 等等。

請參閱回呼文件以取得完整清單。

編寫您自己的回呼

您可以透過擴充基底類別 keras.callbacks.Callback 來建立自訂回呼。回呼可以透過類別屬性 self.model 存取其關聯的模型。

請務必閱讀編寫自訂回呼的完整指南

以下是一個簡單的範例,說明如何在訓練期間儲存每個批次的損失值清單

class LossHistory(keras.callbacks.Callback):
    def on_train_begin(self, logs):
        self.per_batch_losses = []

    def on_batch_end(self, batch, logs):
        self.per_batch_losses.append(logs.get("loss"))

檢查模型

當您在相對大型的資料集上訓練模型時,務必定期儲存模型的檢查點。

實現此目的最簡單的方法是使用 ModelCheckpoint 回呼

model = get_compiled_model()

callbacks = [
    keras.callbacks.ModelCheckpoint(
        # Path where to save the model
        # The two parameters below mean that we will overwrite
        # the current checkpoint if and only if
        # the `val_loss` score has improved.
        # The saved model name will include the current epoch.
        filepath="mymodel_{epoch}",
        save_best_only=True,  # Only save a model if `val_loss` has improved.
        monitor="val_loss",
        verbose=1,
    )
]
model.fit(
    x_train, y_train, epochs=2, batch_size=64, callbacks=callbacks, validation_split=0.2
)
Epoch 1/2
608/625 [============================>.] - ETA: 0s - loss: 0.3786 - sparse_categorical_accuracy: 0.8939
Epoch 1: val_loss improved from inf to 0.24249, saving model to mymodel_1
INFO:tensorflow:Assets written to: mymodel_1/assets
INFO:tensorflow:Assets written to: mymodel_1/assets
625/625 [==============================] - 3s 4ms/step - loss: 0.3756 - sparse_categorical_accuracy: 0.8946 - val_loss: 0.2425 - val_sparse_categorical_accuracy: 0.9307
Epoch 2/2
619/625 [============================>.] - ETA: 0s - loss: 0.1808 - sparse_categorical_accuracy: 0.9458
Epoch 2: val_loss improved from 0.24249 to 0.18221, saving model to mymodel_2
INFO:tensorflow:Assets written to: mymodel_2/assets
INFO:tensorflow:Assets written to: mymodel_2/assets
625/625 [==============================] - 2s 3ms/step - loss: 0.1809 - sparse_categorical_accuracy: 0.9458 - val_loss: 0.1822 - val_sparse_categorical_accuracy: 0.9430
<keras.src.callbacks.History at 0x7fd7f1e11be0>

ModelCheckpoint 回呼可用於實作容錯能力:在訓練隨機中斷的情況下,能夠從模型的最後儲存狀態重新啟動訓練。以下是一個基本範例

import os

# Prepare a directory to store all the checkpoints.
checkpoint_dir = "./ckpt"
if not os.path.exists(checkpoint_dir):
    os.makedirs(checkpoint_dir)


def make_or_restore_model():
    # Either restore the latest model, or create a fresh one
    # if there is no checkpoint available.
    checkpoints = [checkpoint_dir + "/" + name for name in os.listdir(checkpoint_dir)]
    if checkpoints:
        latest_checkpoint = max(checkpoints, key=os.path.getctime)
        print("Restoring from", latest_checkpoint)
        return keras.models.load_model(latest_checkpoint)
    print("Creating a new model")
    return get_compiled_model()


model = make_or_restore_model()
callbacks = [
    # This callback saves the model every 100 batches.
    # We include the training loss in the saved model name.
    keras.callbacks.ModelCheckpoint(
        filepath=checkpoint_dir + "/model-loss={loss:.2f}", save_freq=100
    )
]
model.fit(x_train, y_train, epochs=1, callbacks=callbacks)
Creating a new model
  96/1563 [>.............................] - ETA: 3s - loss: 0.9908 - sparse_categorical_accuracy: 0.7197INFO:tensorflow:Assets written to: ./ckpt/model-loss=0.98/assets
INFO:tensorflow:Assets written to: ./ckpt/model-loss=0.98/assets
193/1563 [==>...........................] - ETA: 5s - loss: 0.7186 - sparse_categorical_accuracy: 0.7970INFO:tensorflow:Assets written to: ./ckpt/model-loss=0.71/assets
INFO:tensorflow:Assets written to: ./ckpt/model-loss=0.71/assets
295/1563 [====>.........................] - ETA: 5s - loss: 0.5922 - sparse_categorical_accuracy: 0.8303INFO:tensorflow:Assets written to: ./ckpt/model-loss=0.59/assets
INFO:tensorflow:Assets written to: ./ckpt/model-loss=0.59/assets
396/1563 [======>.......................] - ETA: 5s - loss: 0.5166 - sparse_categorical_accuracy: 0.8506INFO:tensorflow:Assets written to: ./ckpt/model-loss=0.51/assets
INFO:tensorflow:Assets written to: ./ckpt/model-loss=0.51/assets
495/1563 [========>.....................] - ETA: 5s - loss: 0.4744 - sparse_categorical_accuracy: 0.8625INFO:tensorflow:Assets written to: ./ckpt/model-loss=0.47/assets
INFO:tensorflow:Assets written to: ./ckpt/model-loss=0.47/assets
589/1563 [==========>...................] - ETA: 5s - loss: 0.4410 - sparse_categorical_accuracy: 0.8725INFO:tensorflow:Assets written to: ./ckpt/model-loss=0.44/assets
INFO:tensorflow:Assets written to: ./ckpt/model-loss=0.44/assets
692/1563 [============>.................] - ETA: 4s - loss: 0.4102 - sparse_categorical_accuracy: 0.8805INFO:tensorflow:Assets written to: ./ckpt/model-loss=0.41/assets
INFO:tensorflow:Assets written to: ./ckpt/model-loss=0.41/assets
788/1563 [==============>...............] - ETA: 4s - loss: 0.3882 - sparse_categorical_accuracy: 0.8867INFO:tensorflow:Assets written to: ./ckpt/model-loss=0.39/assets
INFO:tensorflow:Assets written to: ./ckpt/model-loss=0.39/assets
888/1563 [================>.............] - ETA: 3s - loss: 0.3695 - sparse_categorical_accuracy: 0.8923INFO:tensorflow:Assets written to: ./ckpt/model-loss=0.37/assets
INFO:tensorflow:Assets written to: ./ckpt/model-loss=0.37/assets
993/1563 [==================>...........] - ETA: 3s - loss: 0.3538 - sparse_categorical_accuracy: 0.8965INFO:tensorflow:Assets written to: ./ckpt/model-loss=0.35/assets
INFO:tensorflow:Assets written to: ./ckpt/model-loss=0.35/assets
1093/1563 [===================>..........] - ETA: 2s - loss: 0.3387 - sparse_categorical_accuracy: 0.9007INFO:tensorflow:Assets written to: ./ckpt/model-loss=0.34/assets
INFO:tensorflow:Assets written to: ./ckpt/model-loss=0.34/assets
1195/1563 [=====================>........] - ETA: 1s - loss: 0.3260 - sparse_categorical_accuracy: 0.9041INFO:tensorflow:Assets written to: ./ckpt/model-loss=0.33/assets
INFO:tensorflow:Assets written to: ./ckpt/model-loss=0.33/assets
1292/1563 [=======================>......] - ETA: 1s - loss: 0.3158 - sparse_categorical_accuracy: 0.9072INFO:tensorflow:Assets written to: ./ckpt/model-loss=0.31/assets
INFO:tensorflow:Assets written to: ./ckpt/model-loss=0.31/assets
1389/1563 [=========================>....] - ETA: 0s - loss: 0.3068 - sparse_categorical_accuracy: 0.9097INFO:tensorflow:Assets written to: ./ckpt/model-loss=0.31/assets
INFO:tensorflow:Assets written to: ./ckpt/model-loss=0.31/assets
1493/1563 [===========================>..] - ETA: 0s - loss: 0.2979 - sparse_categorical_accuracy: 0.9124INFO:tensorflow:Assets written to: ./ckpt/model-loss=0.30/assets
INFO:tensorflow:Assets written to: ./ckpt/model-loss=0.30/assets
1563/1563 [==============================] - 10s 6ms/step - loss: 0.2930 - sparse_categorical_accuracy: 0.9137
<keras.src.callbacks.History at 0x7fd7f1f22ee0>

您也可以編寫自己的回呼來儲存和還原模型。

如需關於序列化和儲存的完整指南,請參閱模型儲存與序列化指南

使用學習率排程器

在訓練深度學習模型時,一個常見的模式是隨著訓練的進展逐步降低學習率。這通常被稱為「學習率衰減」。

學習率衰減排程可以是靜態的(預先固定,作為當前 epoch 或當前批次索引的函數),也可以是動態的(回應模型的當前行為,特別是驗證損失)。

將排程傳遞給最佳化器

您可以輕鬆地使用靜態學習率衰減排程,方法是在最佳化器中將排程物件作為 learning_rate 引數傳遞

initial_learning_rate = 0.1
lr_schedule = keras.optimizers.schedules.ExponentialDecay(
    initial_learning_rate, decay_steps=100000, decay_rate=0.96, staircase=True
)

optimizer = keras.optimizers.RMSprop(learning_rate=lr_schedule)

有幾個內建排程可供使用:ExponentialDecayPiecewiseConstantDecayPolynomialDecayInverseTimeDecay

使用回呼來實作動態學習率排程

動態學習率排程(例如,在驗證損失不再改善時降低學習率)無法使用這些排程物件實現,因為最佳化器無法存取驗證指標。

但是,回呼可以存取所有指標,包括驗證指標!因此,您可以透過使用回呼來修改最佳化器上的當前學習率來實現此模式。實際上,這甚至作為 ReduceLROnPlateau 回呼內建於其中。

視覺化訓練期間的損失和指標

在訓練期間關注模型的最佳方法是使用 TensorBoard - 一個您可以本地執行的瀏覽器應用程式,它為您提供

  • 訓練和評估的損失和指標的即時繪圖
  • (可選)您的層啟動直方圖視覺化
  • (可選)您的 Embedding 層所學習的嵌入空間的 3D 視覺化

如果您已使用 pip 安裝 TensorFlow,您應該可以從命令列啟動 TensorBoard

tensorboard --logdir=/full_path_to_your_logs

使用 TensorBoard 回呼

將 TensorBoard 與 Keras 模型和 fit() 方法搭配使用的最簡單方法是 TensorBoard 回呼。

在最簡單的情況下,只需指定您希望回呼寫入記錄的位置,即可開始使用

keras.callbacks.TensorBoard(
    log_dir="/full_path_to_your_logs",
    histogram_freq=0,  # How often to log histogram visualizations
    embeddings_freq=0,  # How often to log embedding visualizations
    update_freq="epoch",
)  # How often to write logs (default: once per epoch)
<keras.src.callbacks.TensorBoard at 0x7fd64c425310>

如需更多資訊,請參閱 TensorBoard 回呼的文件