開始使用 / Keras 常見問題

Keras 常見問題

Keras 常見問題列表。

一般問題

與訓練相關的問題

與建模相關的問題


一般問題

如何在單一機器上的多個 GPU 上訓練 Keras 模型?

在多個 GPU 上執行單一模型有兩種方法:資料平行處理裝置平行處理。Keras 涵蓋了這兩種方法。

對於資料平行處理,Keras 支援 JAX、TensorFlow 和 PyTorch 的內建資料平行分散式 API。請參閱以下指南

對於模型平行處理,Keras 有自己的分散式 API,目前僅 JAX 後端支援。請參閱 LayoutMap API 的文件


如何在 TPU 上訓練 Keras 模型?

TPU 是用於深度學習的快速高效硬體加速器,可在 Google Cloud 上公開使用。您可以透過 Colab、Kaggle 筆記本和 GCP 深度學習 VM 使用 TPU(前提是 VM 上已設定 TPU_NAME 環境變數)。

所有 Keras 後端(JAX、TensorFlow、PyTorch)都支援 TPU,但在這種情況下,我們建議使用 JAX 或 TensorFlow。

使用 JAX

連線到 TPU 執行階段時,只需在模型建構之前插入這段程式碼片段

import jax
distribution = keras.distribution.DataParallel(devices=jax.devices())
keras.distribution.set_distribution(distribution)

使用 TensorFlow

連線到 TPU 執行階段時,使用 TPUClusterResolver 來偵測 TPU。然後,建立 TPUStrategy 並在策略範圍內建構您的模型

try:
    tpu = tf.distribute.cluster_resolver.TPUClusterResolver.connect()
    print("Device:", tpu.master())
    strategy = tf.distribute.TPUStrategy(tpu)
except:
    strategy = tf.distribute.get_strategy()
print("Number of replicas:", strategy.num_replicas_in_sync)

with strategy.scope():
    # Create your model here.
    ...

重要的是,您應該

  • 確保您能夠以足夠快的速度讀取資料,以維持 TPU 的使用率。
  • 考慮在每次圖形執行中執行多個梯度下降步驟,以維持 TPU 的使用率。您可以透過 compile() 中的 experimental_steps_per_execution 參數來完成此操作。它將為小型模型帶來顯著的速度提升。

Keras 的設定檔儲存在哪裡?

所有 Keras 資料儲存的預設目錄是

$HOME/.keras/

例如,對我來說,在 MacBook Pro 上,它是 /Users/fchollet/.keras/

請注意,Windows 使用者應將 $HOME 替換為 %USERPROFILE%

如果 Keras 無法建立上述目錄(例如,由於權限問題),則會使用 /tmp/.keras/ 作為備份。

Keras 設定檔是一個 JSON 檔案,儲存在 $HOME/.keras/keras.json 中。預設設定檔如下所示

{
    "image_data_format": "channels_last",
    "epsilon": 1e-07,
    "floatx": "float32",
    "backend": "tensorflow"
}

它包含以下欄位

  • 影像處理層和公用程式預設使用的影像資料格式(channels_lastchannels_first)。
  • 用於防止某些運算中除以零的 epsilon 數值模糊因子。
  • 預設的浮點資料類型。
  • 預設的後端。它可以是 "jax""tensorflow""torch""numpy" 之一。

同樣,快取資料集檔案(例如,使用 get_file() 下載的檔案)預設儲存在 $HOME/.keras/datasets/ 中,而來自 Keras Applications 的快取模型權重檔案預設儲存在 $HOME/.keras/models/ 中。


如何使用 Keras 進行超參數調整?

我們建議使用 KerasTuner


如何在開發期間使用 Keras 獲得可重現的結果?

有四個需要考慮的隨機性來源

  1. Keras 本身(例如,來自 keras.random 的運算或來自 keras.layers 的隨機層)。
  2. 目前的 Keras 後端(例如,JAX、TensorFlow 或 PyTorch)。
  3. Python 執行階段。
  4. CUDA 執行階段。在 GPU 上執行時,某些運算具有非決定性的輸出。這是因為 GPU 並行執行許多運算,因此執行順序並不總是能保證。由於浮點數的精度有限,即使將幾個數字加在一起,也可能會根據您將它們相加的順序產生略有不同的結果。

為了使 Keras 和目前的後端框架具有決定性,請使用以下程式碼

keras.utils.set_random_seed(1337)

為了使 Python 具有決定性,您需要在程式啟動之前(而不是在程式本身內)將 PYTHONHASHSEED 環境變數設定為 0。在 Python 3.2.3 及更高版本中,這對於某些基於雜湊的運算(例如,集合或字典中的項目順序,請參閱 Python 的文件)具有可重現的行為是必要的。

為了使 CUDA 執行階段具有決定性:如果使用 TensorFlow 後端,請呼叫 tf.config.experimental.enable_op_determinism。請注意,這會產生效能成本。對於其他後端,要執行的操作可能會有所不同 – 請直接查看您的後端框架的文件。


有哪些儲存模型的選項?

注意:不建議使用 pickle 或 cPickle 來儲存 Keras 模型。

1) 整體模型儲存(設定 + 權重)

整體模型儲存表示建立一個檔案,其中包含

  • 模型的架構,允許您重新建立模型
  • 模型的權重
  • 訓練設定(損失、最佳化器)
  • 最佳化器的狀態,允許您從上次中斷的地方繼續訓練。

儲存整個模型的預設和建議方法是直接執行:model.save(your_file_path.keras)

以任一格式儲存模型後,您可以使用 model = keras.models.load_model(your_file_path.keras) 重新實例化它。

範例

from keras.saving import load_model

model.save('my_model.keras')
del model  # deletes the existing model

# returns a compiled model
# identical to the previous one
model = load_model('my_model.keras')

2) 僅權重儲存

如果您需要儲存模型的權重,您可以使用以下程式碼,使用檔案擴展名 .weights.h5,以 HDF5 格式進行儲存

model.save_weights('my_model.weights.h5')

假設您有程式碼可以實例化您的模型,那麼您可以使用相同架構將儲存的權重載入模型中

model.load_weights('my_model.weights.h5')

如果您需要將權重載入不同的架構中(具有一些共同的層),例如進行微調或遷移學習,您可以按層名稱載入它們

model.load_weights('my_model.weights.h5', by_name=True)

範例

"""
Assuming the original model looks like this:

model = Sequential()
model.add(Dense(2, input_dim=3, name='dense_1'))
model.add(Dense(3, name='dense_2'))
...
model.save_weights(fname)
"""

# new model
model = Sequential()
model.add(Dense(2, input_dim=3, name='dense_1'))  # will be loaded
model.add(Dense(10, name='new_dense'))  # will not be loaded

# load weights from the first model; will only affect the first layer, dense_1.
model.load_weights(fname, by_name=True)

另請參閱 如何安裝 HDF5 或 h5py 來儲存我的模型?,以瞭解如何安裝 h5py 的說明。

3) 僅設定儲存(序列化)

如果您只需要儲存模型的架構,而不需要儲存其權重或訓練設定,您可以執行

# save as JSON
json_string = model.to_json()

產生的 JSON 檔案是人類可讀的,並且可以在需要時手動編輯。

然後,您可以從此資料建立一個新的模型

# model reconstruction from JSON:
from keras.models import model_from_json
model = model_from_json(json_string)

4) 處理已儲存模型中的自訂層(或其他自訂物件)

如果您要載入的模型包含自訂層或其他自訂類別或函數,您可以透過 custom_objects 參數將它們傳遞給載入機制

from keras.models import load_model
# Assuming your model includes instance of an "AttentionLayer" class
model = load_model('my_model.h5', custom_objects={'AttentionLayer': AttentionLayer})

或者,您可以使用 自訂物件範圍

from keras.utils import CustomObjectScope

with CustomObjectScope({'AttentionLayer': AttentionLayer}):
    model = load_model('my_model.h5')

自訂物件處理對於 load_modelmodel_from_json 的工作方式相同

from keras.models import model_from_json
model = model_from_json(json_string, custom_objects={'AttentionLayer': AttentionLayer})

如何安裝 HDF5 或 h5py 來儲存我的模型?

為了將您的 Keras 模型儲存為 HDF5 檔案,Keras 使用 h5py Python 套件。它是 Keras 的相依性,預設情況下應安裝。在基於 Debian 的發行版上,您還必須安裝 libhdf5

sudo apt-get install libhdf5-serial-dev

如果您不確定是否已安裝 h5py,您可以開啟 Python shell 並透過以下程式碼載入模組

import h5py

如果它匯入時沒有錯誤,則表示已安裝,否則您可以在此處找到 詳細的安裝說明


我應該如何引用 Keras?

如果 Keras 對您的研究有所幫助,請在您的出版物中引用 Keras。以下是一個 BibTeX 條目的範例

@misc{chollet2015keras,
  title={Keras},
  author={Chollet, Fran\c{c}ois and others},
  year={2015},
  howpublished={\url{https://keras.dev.org.tw}},
}


與訓練相關的問題

「樣本」(sample)、「批次」(batch) 和「週期」(epoch) 是什麼意思?

以下是一些常見的定義,為了正確使用 Keras fit(),必須知道並理解這些定義

  • 樣本:資料集的一個元素。例如,在卷積網路中,一張影像是一個樣本。一段音訊片段是語音辨識模型的樣本
  • 批次:一組N 個樣本。批次中的樣本會獨立、平行處理。如果進行訓練,則一個批次只會導致對模型進行一次更新。與單一輸入相比,批次通常可以更好地近似輸入資料的分佈。批次越大,近似效果越好;但是,批次也會需要更長的時間來處理,並且仍然只會導致一次更新。對於推論(評估/預測),建議選擇您可以負擔的最大批次大小,而不會超出記憶體(因為較大的批次通常會導致更快的評估/預測)。
  • 週期:一個任意的截止值,通常定義為「對整個資料集的一次傳遞」,用於將訓練分成不同的階段,這對於記錄和定期評估很有用。當使用 validation_datavalidation_split 與 Keras 模型的 fit 方法時,評估將在每個週期結束時執行。在 Keras 中,可以新增專門設計在週期結束時執行的回呼。這些範例包括學習率變更和模型檢查點(儲存)。

為什麼我的訓練損失遠高於測試損失?

Keras 模型有兩種模式:訓練和測試。正規化機制,例如 Dropout 和 L1/L2 權重正規化,會在測試時關閉。它們會反映在訓練時間損失中,但不會反映在測試時間損失中。

此外,Keras 顯示的訓練損失是每個訓練資料批次的損失平均值,在當前週期(epoch)內。由於您的模型會隨著時間推移而改變,因此一個週期中前幾個批次的損失通常會高於最後幾個批次。這可能會拉低整個週期的平均值。另一方面,一個週期的測試損失是使用該週期結束時的模型計算的,因此會產生較低的損失。


如何確保我的訓練執行可以從程式中斷中恢復?

為了確保能夠隨時從中斷的訓練執行中恢復(容錯能力),您應該使用 keras.callbacks.BackupAndRestore 回調,定期將您的訓練進度(包括週期數和權重)儲存到磁碟,並在您下次調用 Model.fit() 時載入它。

import numpy as np
import keras

class InterruptingCallback(keras.callbacks.Callback):
  """A callback to intentionally introduce interruption to training."""
  def on_epoch_end(self, epoch, log=None):
    if epoch == 15:
      raise RuntimeError('Interruption')

model = keras.Sequential([keras.layers.Dense(10)])
optimizer = keras.optimizers.SGD()
model.compile(optimizer, loss="mse")

x = np.random.random((24, 10))
y = np.random.random((24,))

backup_callback = keras.callbacks.experimental.BackupAndRestore(
    backup_dir='/tmp/backup')
try:
  model.fit(x, y, epochs=20, steps_per_epoch=5,
            callbacks=[backup_callback, InterruptingCallback()])
except RuntimeError:
  print('***Handling interruption***')
  # This continues at the epoch where it left off.
  model.fit(x, y, epochs=20, steps_per_epoch=5,
            callbacks=[backup_callback])

回調文件 中瞭解更多資訊。


當驗證損失不再減少時,我該如何中斷訓練?

您可以使用 EarlyStopping 回調。

from keras.callbacks import EarlyStopping

early_stopping = EarlyStopping(monitor='val_loss', patience=2)
model.fit(x, y, validation_split=0.2, callbacks=[early_stopping])

回調文件 中瞭解更多資訊。


如何凍結層並進行微調?

設定 trainable 屬性

所有層和模型都有一個 layer.trainable 布林屬性。

>>> layer = Dense(3)
>>> layer.trainable
True

在所有層和模型上,都可以設定 trainable 屬性(為 True 或 False)。當設定為 False 時,layer.trainable_weights 屬性會是空的。

>>> layer = Dense(3)
>>> layer.build(input_shape=(None, 3)) # Create the weights of the layer
>>> layer.trainable
True
>>> layer.trainable_weights
[<KerasVariable shape=(3, 3), dtype=float32, path=dense/kernel>, <KerasVariable shape=(3,), dtype=float32, path=dense/bias>]
>>> layer.trainable = False
>>> layer.trainable_weights
[]

在層上設定 trainable 屬性會遞迴地在所有子層(self.layers 的內容)上設定它。

1) 使用 fit() 訓練時

若要使用 fit() 進行微調,您應該:

  • 實例化一個基礎模型並載入預訓練的權重。
  • 凍結該基礎模型。
  • 在頂部新增可訓練的層。
  • 調用 compile()fit()

像這樣:

model = Sequential([
    ResNet50Base(input_shape=(32, 32, 3), weights='pretrained'),
    Dense(10),
])
model.layers[0].trainable = False  # Freeze ResNet50Base.

assert model.layers[0].trainable_weights == []  # ResNet50Base has no trainable weights.
assert len(model.trainable_weights) == 2  # Just the bias & kernel of the Dense layer.

model.compile(...)
model.fit(...)  # Train Dense while excluding ResNet50Base.

您可以使用 Functional API 或模型子類化 API 遵循類似的工作流程。請務必在變更 trainable 的值之後調用 compile(),以便您的變更生效。調用 compile() 將會凍結模型訓練步驟的狀態。

2) 使用自訂訓練迴圈時

在編寫訓練迴圈時,請務必只更新 model.trainable_weights 的權重(而不是所有 model.weights)。以下是一個簡單的 TensorFlow 範例:

model = Sequential([
    ResNet50Base(input_shape=(32, 32, 3), weights='pretrained'),
    Dense(10),
])
model.layers[0].trainable = False  # Freeze ResNet50Base.

# Iterate over the batches of a dataset.
for inputs, targets in dataset:
    # Open a GradientTape.
    with tf.GradientTape() as tape:
        # Forward pass.
        predictions = model(inputs)
        # Compute the loss value for this batch.
        loss_value = loss_fn(targets, predictions)

    # Get gradients of loss wrt the *trainable* weights.
    gradients = tape.gradient(loss_value, model.trainable_weights)
    # Update the weights of the model.
    optimizer.apply_gradients(zip(gradients, model.trainable_weights))

trainablecompile() 之間的交互作用

在模型上調用 compile() 的目的是「凍結」該模型的行為。這表示在編譯模型時的 trainable 屬性值,應在該模型的生命週期內保留,直到再次調用 compile 為止。因此,如果您變更 trainable,請務必再次在模型上調用 compile(),以便您的變更生效。

例如,如果兩個模型 A 和 B 共用某些層,並且:

  • 模型 A 被編譯。
  • 共用層上的 trainable 屬性值被變更。
  • 模型 B 被編譯。

那麼模型 A 和 B 對於共用層會使用不同的 trainable 值。此機制對於大多數現有的 GAN 實作至關重要,這些實作會:

discriminator.compile(...)  # the weights of `discriminator` should be updated when `discriminator` is trained
discriminator.trainable = False
gan.compile(...)  # `discriminator` is a submodel of `gan`, which should not be updated when `gan` is trained

call() 中的 training 參數和 trainable 屬性之間有什麼區別?

trainingcall 中的一個布林參數,用於確定是否應該在推論模式或訓練模式下執行調用。例如,在訓練模式下,Dropout 層會應用隨機 dropout 並重新縮放輸出。在推論模式下,同一層不會執行任何操作。範例:

y = Dropout(0.5)(x, training=True)  # Applies dropout at training time *and* inference time

trainable 是一個布林層屬性,用於確定層的可訓練權重是否應該在訓練期間更新,以最小化損失。如果 layer.trainable 設定為 False,則 layer.trainable_weights 將永遠是空列表。範例:

model = Sequential([
    ResNet50Base(input_shape=(32, 32, 3), weights='pretrained'),
    Dense(10),
])
model.layers[0].trainable = False  # Freeze ResNet50Base.

assert model.layers[0].trainable_weights == []  # ResNet50Base has no trainable weights.
assert len(model.trainable_weights) == 2  # Just the bias & kernel of the Dense layer.

model.compile(...)
model.fit(...)  # Train Dense while excluding ResNet50Base.

如您所見,「推論模式與訓練模式」和「層權重可訓練性」是兩個非常不同的概念。

您可以想像以下情況:一個 dropout 層,其中縮放因子是在訓練期間通過反向傳播學習的。我們將其命名為 AutoScaleDropout。此層將同時具有可訓練的狀態,以及在推論和訓練中的不同行為。由於 trainable 屬性和 training 調用參數是獨立的,因此您可以執行以下操作:

layer = AutoScaleDropout(0.5)

# Applies dropout at training time *and* inference time  
# *and* learns the scaling factor during training
y = layer(x, training=True)

assert len(layer.trainable_weights) == 1
# Applies dropout at training time *and* inference time  
# with a *frozen* scaling factor

layer = AutoScaleDropout(0.5)
layer.trainable = False
y = layer(x, training=True)

BatchNormalization 層的特殊情況

對於 BatchNormalization 層,設定 bn.trainable = False 也會使其 training 調用參數預設為 False,這表示該層在訓練期間不會更新其狀態。

此行為僅適用於 BatchNormalization。對於每個其他層,權重可訓練性和「推論與訓練模式」仍然是獨立的。


fit() 中,驗證分割是如何計算的?

如果您在 model.fit 中將 validation_split 參數設定為例如 0.1,那麼使用的驗證資料將是資料的最後 10%。如果您將其設定為 0.25,它將是資料的最後 25%,依此類推。請注意,在提取驗證分割之前不會打亂資料,因此驗證實際上只是您傳遞的輸入中最後的 x% 樣本。

相同的驗證集會用於所有週期(在同一次調用 fit 中)。

請注意,只有在您的資料以 NumPy 陣列形式傳遞時(而不是 tf.data.Datasets,它們是不可索引的)才可以使用 validation_split 選項。


fit() 中,資料在訓練期間是否會被打亂?

如果您將資料作為 NumPy 陣列傳遞,並且 model.fit() 中的 shuffle 參數設定為 True(這是預設值),則訓練資料將在每個週期中隨機全局打亂。

如果您將資料作為 tf.data.Dataset 物件傳遞,並且 model.fit() 中的 shuffle 參數設定為 True,則資料集將會局部打亂(緩衝打亂)。

當使用 tf.data.Dataset 物件時,請盡量預先打亂您的資料(例如,通過調用 dataset = dataset.shuffle(buffer_size)),以便控制緩衝區大小。

驗證資料永遠不會被打亂。


損失值和指標值會通過調用 fit() 顯示的預設進度列報告。但是,盯著控制台中不斷變化的 ASCII 數字並不是最佳的指標監控體驗。我們建議使用 TensorBoard,它會顯示您的訓練和驗證指標的美觀圖表,在訓練期間定期更新,您可以從瀏覽器存取這些圖表。

您可以通過 TensorBoard 回調,將 TensorBoard 與 fit() 一起使用。


如果我需要自訂 fit() 的功能該怎麼辦?

您有兩種選擇:

1) 子類化 Model 類別並覆寫 train_step(和 test_step)方法。

如果您想要使用自訂更新規則,但仍然想要利用 fit() 提供的功能(例如回調、高效的步驟融合等),那麼這是一個更好的選擇。

請注意,此模式並不會阻止您使用 Functional API 建立模型,在這種情況下,您將使用您建立的類別,用 inputsoutputs 來實例化模型。對於 Sequential 模型也是如此,在這種情況下,您將子類化 keras.Sequential 並覆寫其 train_step,而不是 keras.Model

請參閱以下指南:

2) 編寫低階自訂訓練迴圈

如果您想要控制每一個細節,這是一個不錯的選擇 – 儘管它可能有點冗長。

請參閱以下指南:


Model 方法 predict()__call__() 之間有什麼區別?

讓我們用 Deep Learning with Python, Second Edition 中的節錄來回答:

y = model.predict(x)y = model(x)(其中 x 是輸入資料的陣列)都表示「在 x 上執行模型並檢索輸出 y」。然而,它們並不完全相同。

predict() 會逐批次迴圈資料(實際上,您可以通過 predict(x, batch_size=64) 指定批次大小),並且它會提取輸出的 NumPy 值。它在示意上等效於此:

def predict(x):
    y_batches = []
    for x_batch in get_batches(x):
        y_batch = model(x_batch).numpy()
        y_batches.append(y_batch)
    return np.concatenate(y_batches)

這表示 predict() 調用可以縮放到非常大的陣列。同時,model(x) 會在記憶體中發生,且無法縮放。另一方面,predict() 是不可微分的:如果您在 GradientTape 範圍中調用它,則無法檢索其梯度。

當您需要檢索模型調用的梯度時,您應該使用 model(x),如果您只需要輸出值,則應該使用 predict()。換句話說,除非您正在編寫低階梯度下降迴圈(正如我們現在所做的),否則請務必使用 predict()


與建模相關的問題

如何取得中間層的輸出(特徵提取)?

在 Functional API 和 Sequential API 中,如果某個層只被調用過一次,您可以通過 layer.output 檢索其輸出,並通過 layer.input 檢索其輸入。這讓您可以快速實例化特徵提取模型,如下所示:

import keras
from keras import layers

model = Sequential([
    layers.Conv2D(32, 3, activation='relu'),
    layers.Conv2D(32, 3, activation='relu'),
    layers.MaxPooling2D(2),
    layers.Conv2D(32, 3, activation='relu'),
    layers.Conv2D(32, 3, activation='relu'),
    layers.GlobalMaxPooling2D(),
    layers.Dense(10),
])
extractor = keras.Model(inputs=model.inputs,
                        outputs=[layer.output for layer in model.layers])
features = extractor(data)

當然,對於子類化 Model 並覆寫 call 的模型,這是不可能的。

以下是另一個範例:實例化一個 Model,它會返回特定命名層的輸出:

model = ...  # create the original model

layer_name = 'my_layer'
intermediate_layer_model = keras.Model(inputs=model.input,
                                       outputs=model.get_layer(layer_name).output)
intermediate_output = intermediate_layer_model(data)

如何在 Keras 中使用預訓練模型?

您可以利用 keras.applications 中可用的模型,或 KerasCVKerasHub 中可用的模型。


如何使用狀態式 RNN?

使 RNN 有狀態表示每個批次的樣本狀態將被重用作為下一個批次中樣本的初始狀態。

因此,當使用有狀態 RNN 時,會假設:

  • 所有批次都具有相同的樣本數。
  • 如果 x1x2 是連續的樣本批次,則對於每個 ix2[i]x1[i] 的後續序列。

若要在 RNN 中使用狀態,您需要:

  • 通過將 batch_size 參數傳遞給模型中的第一層,明確指定您正在使用的批次大小。例如,對於 32 個樣本的批次,每個樣本是 10 個時間步長的序列,每個時間步長具有 16 個特徵,則使用 batch_size=32
  • 在您的 RNN 層中設定 stateful=True
  • 在調用 fit() 時指定 shuffle=False

若要重置累積的狀態:

  • 使用 model.reset_states() 重置模型中所有層的狀態。
  • 使用 layer.reset_states() 重置特定有狀態 RNN 層的狀態。

範例

import keras
from keras import layers
import numpy as np

x = np.random.random((32, 21, 16))  # this is our input data, of shape (32, 21, 16)
# we will feed it to our model in sequences of length 10

model = keras.Sequential()
model.add(layers.LSTM(32, input_shape=(10, 16), batch_size=32, stateful=True))
model.add(layers.Dense(16, activation='softmax'))

model.compile(optimizer='rmsprop', loss='categorical_crossentropy')

# we train the network to predict the 11th timestep given the first 10:
model.train_on_batch(x[:, :10, :], np.reshape(x[:, 10, :], (32, 16)))

# the state of the network has changed. We can feed the follow-up sequences:
model.train_on_batch(x[:, 10:20, :], np.reshape(x[:, 20, :], (32, 16)))

# let's reset the states of the LSTM layer:
model.reset_states()

# another way to do it in this case:
model.layers[0].reset_states()

請注意,predictfittrain_on_batch 等方法都會更新模型中有狀態層的狀態。這不僅能讓您進行有狀態的訓練,也能進行有狀態的預測。