Keras 常見問題列表。
call()
中的 training
參數和 trainable
屬性之間有什麼區別?fit()
中,驗證分割是如何計算的?fit()
中,資料在訓練期間是否會被打亂?fit()
訓練時,監控指標的建議方法是什麼?fit()
的功能該怎麼辦?Model
方法 predict()
和 __call__()
之間有什麼區別?在多個 GPU 上執行單一模型有兩種方法:資料平行處理和裝置平行處理。Keras 涵蓋了這兩種方法。
對於資料平行處理,Keras 支援 JAX、TensorFlow 和 PyTorch 的內建資料平行分散式 API。請參閱以下指南
對於模型平行處理,Keras 有自己的分散式 API,目前僅 JAX 後端支援。請參閱 LayoutMap
API 的文件。
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.
...
重要的是,您應該
compile()
中的 experimental_steps_per_execution
參數來完成此操作。它將為小型模型帶來顯著的速度提升。所有 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_last
或 channels_first
)。epsilon
數值模糊因子。"jax"
、"tensorflow"
、"torch"
或 "numpy"
之一。同樣,快取資料集檔案(例如,使用 get_file()
下載的檔案)預設儲存在 $HOME/.keras/datasets/
中,而來自 Keras Applications 的快取模型權重檔案預設儲存在 $HOME/.keras/models/
中。
我們建議使用 KerasTuner。
有四個需要考慮的隨機性來源
keras.random
的運算或來自 keras.layers
的隨機層)。為了使 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_model
和 model_from_json
的工作方式相同
from keras.models import model_from_json
model = model_from_json(json_string, custom_objects={'AttentionLayer': AttentionLayer})
為了將您的 Keras 模型儲存為 HDF5 檔案,Keras 使用 h5py Python 套件。它是 Keras 的相依性,預設情況下應安裝。在基於 Debian 的發行版上,您還必須安裝 libhdf5
sudo apt-get install libhdf5-serial-dev
如果您不確定是否已安裝 h5py,您可以開啟 Python shell 並透過以下程式碼載入模組
import h5py
如果它匯入時沒有錯誤,則表示已安裝,否則您可以在此處找到 詳細的安裝說明。
如果 Keras 對您的研究有所幫助,請在您的出版物中引用 Keras。以下是一個 BibTeX 條目的範例
@misc{chollet2015keras,
title={Keras},
author={Chollet, Fran\c{c}ois and others},
year={2015},
howpublished={\url{https://keras.dev.org.tw}},
}
以下是一些常見的定義,為了正確使用 Keras fit()
,必須知道並理解這些定義
validation_data
或 validation_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))
trainable
和 compile()
之間的交互作用
在模型上調用 compile()
的目的是「凍結」該模型的行為。這表示在編譯模型時的 trainable
屬性值,應在該模型的生命週期內保留,直到再次調用 compile
為止。因此,如果您變更 trainable
,請務必再次在模型上調用 compile()
,以便您的變更生效。
例如,如果兩個模型 A 和 B 共用某些層,並且:
trainable
屬性值被變更。那麼模型 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
屬性之間有什麼區別?training
是 call
中的一個布林參數,用於確定是否應該在推論模式或訓練模式下執行調用。例如,在訓練模式下,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()
訓練時,監控指標的建議方法是什麼?損失值和指標值會通過調用 fit()
顯示的預設進度列報告。但是,盯著控制台中不斷變化的 ASCII 數字並不是最佳的指標監控體驗。我們建議使用 TensorBoard,它會顯示您的訓練和驗證指標的美觀圖表,在訓練期間定期更新,您可以從瀏覽器存取這些圖表。
您可以通過 TensorBoard
回調,將 TensorBoard 與 fit()
一起使用。
fit()
的功能該怎麼辦?您有兩種選擇:
1) 子類化 Model
類別並覆寫 train_step
(和 test_step
)方法。
如果您想要使用自訂更新規則,但仍然想要利用 fit()
提供的功能(例如回調、高效的步驟融合等),那麼這是一個更好的選擇。
請注意,此模式並不會阻止您使用 Functional API 建立模型,在這種情況下,您將使用您建立的類別,用 inputs
和 outputs
來實例化模型。對於 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.applications
中可用的模型,或 KerasCV 和 KerasHub 中可用的模型。
使 RNN 有狀態表示每個批次的樣本狀態將被重用作為下一個批次中樣本的初始狀態。
因此,當使用有狀態 RNN 時,會假設:
x1
和 x2
是連續的樣本批次,則對於每個 i
,x2[i]
是 x1[i]
的後續序列。若要在 RNN 中使用狀態,您需要:
batch_size
參數傳遞給模型中的第一層,明確指定您正在使用的批次大小。例如,對於 32 個樣本的批次,每個樣本是 10 個時間步長的序列,每個時間步長具有 16 個特徵,則使用 batch_size=32
。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()
請注意,predict
、fit
、train_on_batch
等方法都會更新模型中有狀態層的狀態。這不僅能讓您進行有狀態的訓練,也能進行有狀態的預測。