作者: Luca Invernizzi、James Long、Francois Chollet、Tom O'Malley、Haifeng Jin
建立日期 2019/05/31
上次修改日期 2021/10/27
描述: 使用 KerasTuner 調整模型超參數的基本知識。
!pip install keras-tuner -q
KerasTuner 是一個通用的超參數調整庫。它與 Keras 工作流程有很強的整合性,但不侷限於此:您可以使用它來調整 scikit-learn 模型,或任何其他東西。在本教學中,您將了解如何使用 KerasTuner 調整模型架構、訓練過程和資料預處理步驟。讓我們從一個簡單的範例開始。
我們需要做的第一件事是編寫一個函式,該函式會回傳一個已編譯的 Keras 模型。它接受一個 hp
引數,用於在建立模型時定義超參數。
在以下程式碼範例中,我們定義一個具有兩個 Dense
層的 Keras 模型。我們想要調整第一個 Dense
層中的單元數量。我們只使用 hp.Int('units', min_value=32, max_value=512, step=32)
定義一個整數超參數,其範圍從 32 到 512(含)。當從中取樣時,遍歷間隔的最小步長為 32。
import keras
from keras import layers
def build_model(hp):
model = keras.Sequential()
model.add(layers.Flatten())
model.add(
layers.Dense(
# Define the hyperparameter.
units=hp.Int("units", min_value=32, max_value=512, step=32),
activation="relu",
)
)
model.add(layers.Dense(10, activation="softmax"))
model.compile(
optimizer="adam",
loss="categorical_crossentropy",
metrics=["accuracy"],
)
return model
您可以快速測試模型是否成功建立。
import keras_tuner
build_model(keras_tuner.HyperParameters())
<Sequential name=sequential, built=False>
還有許多其他類型的超參數。我們可以在函式中定義多個超參數。在以下程式碼中,我們使用 hp.Boolean()
調整是否使用 Dropout
層,使用 hp.Choice()
調整要使用的激活函式,並使用 hp.Float()
調整最佳化器的學習率。
def build_model(hp):
model = keras.Sequential()
model.add(layers.Flatten())
model.add(
layers.Dense(
# Tune number of units.
units=hp.Int("units", min_value=32, max_value=512, step=32),
# Tune the activation function to use.
activation=hp.Choice("activation", ["relu", "tanh"]),
)
)
# Tune whether to use dropout.
if hp.Boolean("dropout"):
model.add(layers.Dropout(rate=0.25))
model.add(layers.Dense(10, activation="softmax"))
# Define the optimizer learning rate as a hyperparameter.
learning_rate = hp.Float("lr", min_value=1e-4, max_value=1e-2, sampling="log")
model.compile(
optimizer=keras.optimizers.Adam(learning_rate=learning_rate),
loss="categorical_crossentropy",
metrics=["accuracy"],
)
return model
build_model(keras_tuner.HyperParameters())
<Sequential name=sequential_1, built=False>
如下所示,超參數是實際的值。事實上,它們只是回傳實際值的函式。例如,hp.Int()
回傳一個 int
值。因此,您可以將它們放入變數、for 迴圈或 if 條件中。
hp = keras_tuner.HyperParameters()
print(hp.Int("units", min_value=32, max_value=512, step=32))
32
您也可以預先定義超參數,並將您的 Keras 程式碼保留在單獨的函式中。
def call_existing_code(units, activation, dropout, lr):
model = keras.Sequential()
model.add(layers.Flatten())
model.add(layers.Dense(units=units, activation=activation))
if dropout:
model.add(layers.Dropout(rate=0.25))
model.add(layers.Dense(10, activation="softmax"))
model.compile(
optimizer=keras.optimizers.Adam(learning_rate=lr),
loss="categorical_crossentropy",
metrics=["accuracy"],
)
return model
def build_model(hp):
units = hp.Int("units", min_value=32, max_value=512, step=32)
activation = hp.Choice("activation", ["relu", "tanh"])
dropout = hp.Boolean("dropout")
lr = hp.Float("lr", min_value=1e-4, max_value=1e-2, sampling="log")
# call existing model-building code with the hyperparameter values.
model = call_existing_code(
units=units, activation=activation, dropout=dropout, lr=lr
)
return model
build_model(keras_tuner.HyperParameters())
<Sequential name=sequential_2, built=False>
每個超參數都由其名稱(第一個引數)唯一識別。為了將不同 Dense
層中的單元數量分別調整為不同的超參數,我們將它們命名為不同的名稱,例如 f"units_{i}"
。
值得注意的是,這也是建立條件超參數的範例。有許多超參數指定 Dense
層中的單元數量。此類超參數的數量由層數決定,這也是一個超參數。因此,每次試驗使用的超參數總數可能不同。某些超參數僅在滿足特定條件時才使用。例如,units_3
僅在 num_layers
大於 3 時才使用。使用 KerasTuner,您可以在建立模型時輕鬆動態地定義此類超參數。
def build_model(hp):
model = keras.Sequential()
model.add(layers.Flatten())
# Tune the number of layers.
for i in range(hp.Int("num_layers", 1, 3)):
model.add(
layers.Dense(
# Tune number of units separately.
units=hp.Int(f"units_{i}", min_value=32, max_value=512, step=32),
activation=hp.Choice("activation", ["relu", "tanh"]),
)
)
if hp.Boolean("dropout"):
model.add(layers.Dropout(rate=0.25))
model.add(layers.Dense(10, activation="softmax"))
learning_rate = hp.Float("lr", min_value=1e-4, max_value=1e-2, sampling="log")
model.compile(
optimizer=keras.optimizers.Adam(learning_rate=learning_rate),
loss="categorical_crossentropy",
metrics=["accuracy"],
)
return model
build_model(keras_tuner.HyperParameters())
<Sequential name=sequential_3, built=False>
在定義搜尋空間後,我們需要選擇一個調整器類別來執行搜尋。您可以從 RandomSearch
、BayesianOptimization
和 Hyperband
中選擇,它們對應於不同的調整演算法。在這裡,我們以 RandomSearch
為例。
若要初始化調整器,我們需要在初始化器中指定幾個引數。
hypermodel
。模型建立函式,在我們的例子中是 build_model
。objective
。要優化的目標名稱(是否最小化或最大化是針對內建指標自動推斷的)。我們將在本教學稍後介紹如何使用自訂指標。max_trials
。在搜尋期間要執行的試驗總數。executions_per_trial
。每個試驗應建立和擬合的模型數量。不同的試驗具有不同的超參數值。同一試驗內的執行具有相同的超參數值。每個試驗有多個執行的目的是為了減少結果變異,因此能夠更準確地評估模型的效能。如果您想更快地獲得結果,可以設定 executions_per_trial=1
(每個模型組態的單輪訓練)。overwrite
。控制是否覆寫同一目錄中的先前結果,或改為恢復先前的搜尋。在這裡,我們設定 overwrite=True
以開始新的搜尋,並忽略任何先前的結果。directory
。用於儲存搜尋結果的目錄路徑。project_name
。directory
中的子目錄名稱。tuner = keras_tuner.RandomSearch(
hypermodel=build_model,
objective="val_accuracy",
max_trials=3,
executions_per_trial=2,
overwrite=True,
directory="my_dir",
project_name="helloworld",
)
您可以列印搜尋空間的摘要
tuner.search_space_summary()
Search space summary
Default search space size: 5
num_layers (Int)
{'default': None, 'conditions': [], 'min_value': 1, 'max_value': 3, 'step': 1, 'sampling': 'linear'}
units_0 (Int)
{'default': None, 'conditions': [], 'min_value': 32, 'max_value': 512, 'step': 32, 'sampling': 'linear'}
activation (Choice)
{'default': 'relu', 'conditions': [], 'values': ['relu', 'tanh'], 'ordered': False}
dropout (Boolean)
{'default': False, 'conditions': []}
lr (Float)
{'default': 0.0001, 'conditions': [], 'min_value': 0.0001, 'max_value': 0.01, 'step': None, 'sampling': 'log'}
在開始搜尋之前,讓我們先準備 MNIST 資料集。
import keras
import numpy as np
(x, y), (x_test, y_test) = keras.datasets.mnist.load_data()
x_train = x[:-10000]
x_val = x[-10000:]
y_train = y[:-10000]
y_val = y[-10000:]
x_train = np.expand_dims(x_train, -1).astype("float32") / 255.0
x_val = np.expand_dims(x_val, -1).astype("float32") / 255.0
x_test = np.expand_dims(x_test, -1).astype("float32") / 255.0
num_classes = 10
y_train = keras.utils.to_categorical(y_train, num_classes)
y_val = keras.utils.to_categorical(y_val, num_classes)
y_test = keras.utils.to_categorical(y_test, num_classes)
然後,開始搜尋最佳的超參數組態。傳遞給 search
的所有引數都會傳遞給每次執行中的 model.fit()
。請記得傳遞 validation_data
以評估模型。
tuner.search(x_train, y_train, epochs=2, validation_data=(x_val, y_val))
Trial 3 Complete [00h 00m 19s]
val_accuracy: 0.9665500223636627
Best val_accuracy So Far: 0.9665500223636627
Total elapsed time: 00h 00m 40s
在 search
期間,會以不同試驗中的不同超參數值呼叫模型建立函式。在每次試驗中,調整器都會產生一組新的超參數值來建立模型。然後擬合並評估模型。會記錄指標。調整器會逐步探索空間,並最終找到一組良好的超參數值。
搜尋完成後,您可以擷取最佳模型。模型會儲存在 validation_data
上評估的最佳執行週期中。
# Get the top 2 models.
models = tuner.get_best_models(num_models=2)
best_model = models[0]
best_model.summary()
/usr/local/python/3.10.13/lib/python3.10/site-packages/keras/src/saving/saving_lib.py:388: UserWarning: Skipping variable loading for optimizer 'adam', because it has 2 variables whereas the saved optimizer has 18 variables.
trackable.load_own_variables(weights_store.get(inner_path))
/usr/local/python/3.10.13/lib/python3.10/site-packages/keras/src/saving/saving_lib.py:388: UserWarning: Skipping variable loading for optimizer 'adam', because it has 2 variables whereas the saved optimizer has 10 variables.
trackable.load_own_variables(weights_store.get(inner_path))
Model: "sequential"
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┓ ┃ Layer (type) ┃ Output Shape ┃ Param # ┃ ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━┩ │ flatten (Flatten) │ (32, 784) │ 0 │ ├─────────────────────────────────┼───────────────────────────┼────────────┤ │ dense (Dense) │ (32, 416) │ 326,560 │ ├─────────────────────────────────┼───────────────────────────┼────────────┤ │ dense_1 (Dense) │ (32, 512) │ 213,504 │ ├─────────────────────────────────┼───────────────────────────┼────────────┤ │ dense_2 (Dense) │ (32, 32) │ 16,416 │ ├─────────────────────────────────┼───────────────────────────┼────────────┤ │ dropout (Dropout) │ (32, 32) │ 0 │ ├─────────────────────────────────┼───────────────────────────┼────────────┤ │ dense_3 (Dense) │ (32, 10) │ 330 │ └─────────────────────────────────┴───────────────────────────┴────────────┘
Total params: 556,810 (2.12 MB)
Trainable params: 556,810 (2.12 MB)
Non-trainable params: 0 (0.00 B)
您也可以列印搜尋結果的摘要。
tuner.results_summary()
Results summary
Results in my_dir/helloworld
Showing 10 best trials
Objective(name="val_accuracy", direction="max")
Trial 2 summary
Hyperparameters:
num_layers: 3
units_0: 416
activation: relu
dropout: True
lr: 0.0001324166048504802
units_1: 512
units_2: 32
Score: 0.9665500223636627
Trial 0 summary
Hyperparameters:
num_layers: 1
units_0: 128
activation: tanh
dropout: False
lr: 0.001425162921397599
Score: 0.9623999893665314
Trial 1 summary
Hyperparameters:
num_layers: 2
units_0: 512
activation: tanh
dropout: True
lr: 0.0010584293918512798
units_1: 32
Score: 0.9606499969959259
您會在資料夾 my_dir/helloworld
中找到詳細的記錄、檢查點等,即 directory/project_name
。
您也可以使用 TensorBoard 和 HParams 外掛程式視覺化調整結果。如需更多資訊,請依照此連結。
如果您想使用整個資料集訓練模型,您可以擷取最佳超參數並自行重新訓練模型。
# Get the top 2 hyperparameters.
best_hps = tuner.get_best_hyperparameters(5)
# Build the model with the best hp.
model = build_model(best_hps[0])
# Fit with the entire dataset.
x_all = np.concatenate((x_train, x_val))
y_all = np.concatenate((y_train, y_val))
model.fit(x=x_all, y=y_all, epochs=1)
1/1875 [37m━━━━━━━━━━━━━━━━━━━━ 17:57 575ms/step - accuracy: 0.1250 - loss: 2.3113
29/1875 [37m━━━━━━━━━━━━━━━━━━━━ 3 秒 2 毫秒/步驟 - 準確度:0.1753 - 損失:2.2296
63/1875 [37m━━━━━━━━━━━━━━━━━━━━ 3 秒 2 毫秒/步驟 - 準確度:0.2626 - 損失:2.1206
96/1875 ━[37m━━━━━━━━━━━━━━━━━━━ 2 秒 2 毫秒/步驟 - 準確度:0.3252 - 損失:2.0103
130/1875 ━[37m━━━━━━━━━━━━━━━━━━━ 2 秒 2 毫秒/步驟 - 準確度:0.3745 - 損失:1.9041
164/1875 ━[37m━━━━━━━━━━━━━━━━━━━ 2 秒 2 毫秒/步驟 - 準確度:0.4139 - 損失:1.8094
199/1875 ━━[37m━━━━━━━━━━━━━━━━━━ 2 秒 2 毫秒/步驟 - 準確度:0.4470 - 損失:1.7246
235/1875 ━━[37m━━━━━━━━━━━━━━━━━━ 2 秒 2 毫秒/步驟 - 準確度:0.4752 - 損失:1.6493
270/1875 ━━[37m━━━━━━━━━━━━━━━━━━ 2 秒 2 毫秒/步驟 - 準確度:0.4982 - 損失:1.5857
305/1875 ━━━[37m━━━━━━━━━━━━━━━━━ 2 秒 2 毫秒/步驟 - 準確度:0.5182 - 損失:1.5293
339/1875 ━━━[37m━━━━━━━━━━━━━━━━━ 2 秒 2 毫秒/步驟 - 準確度:0.5354 - 損失:1.4800
374/1875 ━━━[37m━━━━━━━━━━━━━━━━━ 2 秒 1 毫秒/步驟 - 準確度:0.5513 - 損失:1.4340
409/1875 ━━━━[37m━━━━━━━━━━━━━━━━ 2 秒 1 毫秒/步驟 - 準確度:0.5656 - 損失:1.3924
444/1875 ━━━━[37m━━━━━━━━━━━━━━━━ 2 秒 1 毫秒/步驟 - 準確度:0.5785 - 損失:1.3545
478/1875 ━━━━━[37m━━━━━━━━━━━━━━━ 2 秒 1 毫秒/步驟 - 準確度:0.5899 - 損失:1.3208
513/1875 ━━━━━[37m━━━━━━━━━━━━━━━ 2 秒 1 毫秒/步驟 - 準確度:0.6006 - 損失:1.2887
548/1875 ━━━━━[37m━━━━━━━━━━━━━━━ 1 秒 1 毫秒/步驟 - 準確度:0.6104 - 損失:1.2592
583/1875 ━━━━━━[37m━━━━━━━━━━━━━━ 1 秒 1 毫秒/步驟 - 準確度:0.6195 - 損失:1.2318
618/1875 ━━━━━━[37m━━━━━━━━━━━━━━ 1 秒 1 毫秒/步驟 - 準確度:0.6279 - 損失:1.2063
653/1875 ━━━━━━[37m━━━━━━━━━━━━━━ 1 秒 1 毫秒/步驟 - 準確度:0.6358 - 損失:1.1823
688/1875 ━━━━━━━[37m━━━━━━━━━━━━━ 1 秒 1 毫秒/步驟 - 準確度:0.6431 - 損失:1.1598
723/1875 ━━━━━━━[37m━━━━━━━━━━━━━ 1 秒 1 毫秒/步驟 - 準確度:0.6500 - 損失:1.1387
758/1875 ━━━━━━━━[37m━━━━━━━━━━━━ 1 秒 1 毫秒/步驟 - 準確度:0.6564 - 損失:1.1189
793/1875 ━━━━━━━━[37m━━━━━━━━━━━━ 1 秒 1 毫秒/步驟 - 準確度:0.6625 - 損失:1.1002
828/1875 ━━━━━━━━[37m━━━━━━━━━━━━ 1 秒 1 毫秒/步驟 - 準確度:0.6682 - 損失:1.0826
863/1875 ━━━━━━━━━[37m━━━━━━━━━━━ 1 秒 1 毫秒/步驟 - 準確度:0.6736 - 損失:1.0658
899/1875 ━━━━━━━━━[37m━━━━━━━━━━━ 1 秒 1 毫秒/步驟 - 準確度:0.6788 - 損失:1.0495
935/1875 ━━━━━━━━━[37m━━━━━━━━━━━ 1 秒 1 毫秒/步驟 - 準確度:0.6838 - 損失:1.0339
970/1875 ━━━━━━━━━━[37m━━━━━━━━━━ 1 秒 1 毫秒/步驟 - 準確度:0.6885 - 損失:1.0195
1005/1875 ━━━━━━━━━━[37m━━━━━━━━━━ 1 秒 1 毫秒/步驟 - 準確度:0.6929 - 損失:1.0058
1041/1875 ━━━━━━━━━━━[37m━━━━━━━━━ 1 秒 1 毫秒/步驟 - 準確度:0.6972 - 損失:0.9923
1076/1875 ━━━━━━━━━━━[37m━━━━━━━━━ 1 秒 1 毫秒/步驟 - 準確度:0.7012 - 損失:0.9798
1111/1875 ━━━━━━━━━━━[37m━━━━━━━━━ 1 秒 1 毫秒/步驟 - 準確度:0.7051 - 損失:0.9677
1146/1875 ━━━━━━━━━━━━[37m━━━━━━━━ 1 秒 1 毫秒/步驟 - 準確度:0.7088 - 損失:0.9561
1182/1875 ━━━━━━━━━━━━[37m━━━━━━━━ 1 秒 1 毫秒/步驟 - 準確度:0.7124 - 損失:0.9446
1218/1875 ━━━━━━━━━━━━[37m━━━━━━━━ 0 秒 1 毫秒/步驟 - 準確度:0.7159 - 損失:0.9336
1254/1875 ━━━━━━━━━━━━━[37m━━━━━━━ 0 秒 1 毫秒/步驟 - 準確度:0.7193 - 損失:0.9230
1289/1875 ━━━━━━━━━━━━━[37m━━━━━━━ 0 秒 1 毫秒/步驟 - 準確度:0.7225 - 損失:0.9131
1324/1875 ━━━━━━━━━━━━━━[37m━━━━━━ 0 秒 1 毫秒/步驟 - 準確度:0.7255 - 損失:0.9035
1359/1875 ━━━━━━━━━━━━━━[37m━━━━━━ 0 秒 1 毫秒/步驟 - 準確度:0.7284 - 損失:0.8943
1394/1875 ━━━━━━━━━━━━━━[37m━━━━━━ 0 秒 1 毫秒/步驟 - 準確度:0.7313 - 損失:0.8853
1429/1875 ━━━━━━━━━━━━━━━[37m━━━━━ 0 秒 1 毫秒/步驟 - 準確度:0.7341 - 損失:0.8767
1465/1875 ━━━━━━━━━━━━━━━[37m━━━━━ 0 秒 1 毫秒/步驟 - 準確度:0.7368 - 損失:0.8680
1500/1875 ━━━━━━━━━━━━━━━━[37m━━━━ 0 秒 1 毫秒/步驟 - 準確度:0.7394 - 損失:0.8599
1535/1875 ━━━━━━━━━━━━━━━━[37m━━━━ 0 秒 1 毫秒/步驟 - 準確度:0.7419 - 損失:0.8520
1570/1875 ━━━━━━━━━━━━━━━━[37m━━━━ 0 秒 1 毫秒/步驟 - 準確度:0.7443 - 損失:0.8444
1605/1875 ━━━━━━━━━━━━━━━━━[37m━━━ 0 秒 1 毫秒/步驟 - 準確度:0.7467 - 損失:0.8370
1639/1875 ━━━━━━━━━━━━━━━━━[37m━━━ 0 秒 1 毫秒/步驟 - 準確度:0.7489 - 損失:0.8299
1674/1875 ━━━━━━━━━━━━━━━━━[37m━━━ 0 秒 1 毫秒/步驟 - 準確度:0.7511 - 損失:0.8229
1707/1875 ━━━━━━━━━━━━━━━━━━[37m━━ 0 秒 1 毫秒/步驟 - 準確度:0.7532 - 損失:0.8164
1741/1875 ━━━━━━━━━━━━━━━━━━[37m━━ 0 秒 1 毫秒/步驟 - 準確度:0.7552 - 損失:0.8099
1774/1875 ━━━━━━━━━━━━━━━━━━[37m━━ 0 秒 1 毫秒/步驟 - 準確度:0.7572 - 損失:0.8038
1809/1875 ━━━━━━━━━━━━━━━━━━━[37m━ 0 秒 1 毫秒/步驟 - 準確度:0.7592 - 損失:0.7975
1843/1875 ━━━━━━━━━━━━━━━━━━━[37m━ 0 秒 1 毫秒/步驟 - 準確度:0.7611 - 損失:0.7915
1875/1875 ━━━━━━━━━━━━━━━━━━━━ 3 秒 1 毫秒/步驟 - 準確度:0.7629 - 損失:0.7858
<keras.src.callbacks.history.History at 0x7f31883d9e10>
若要調整模型建立過程,我們需要繼承 HyperModel
類別,這也讓超模型的共用和重複使用變得容易。
我們需要覆寫 HyperModel.build()
和 HyperModel.fit()
,分別調整模型建立和訓練過程。HyperModel.build()
方法與模型建立函式相同,該函式使用超參數建立 Keras 模型並回傳它。
在 HyperModel.fit()
中,您可以存取 HyperModel.build()
、hp
和傳遞給 search()
的所有引數回傳的模型。您需要訓練模型並回傳訓練歷史記錄。
在以下程式碼中,我們將調整 model.fit()
中的 shuffle
引數。
通常不需要調整執行週期的數量,因為會將內建的回呼傳遞給 model.fit()
,以在由 validation_data
評估的最佳執行週期儲存模型。
注意:
**kwargs
應始終傳遞給model.fit()
,因為它包含用於模型儲存和 TensorBoard 外掛程式的回呼。
class MyHyperModel(keras_tuner.HyperModel):
def build(self, hp):
model = keras.Sequential()
model.add(layers.Flatten())
model.add(
layers.Dense(
units=hp.Int("units", min_value=32, max_value=512, step=32),
activation="relu",
)
)
model.add(layers.Dense(10, activation="softmax"))
model.compile(
optimizer="adam",
loss="categorical_crossentropy",
metrics=["accuracy"],
)
return model
def fit(self, hp, model, *args, **kwargs):
return model.fit(
*args,
# Tune whether to shuffle the data in each epoch.
shuffle=hp.Boolean("shuffle"),
**kwargs,
)
同樣地,我們可以快速檢查程式碼是否正常運作。
hp = keras_tuner.HyperParameters()
hypermodel = MyHyperModel()
model = hypermodel.build(hp)
hypermodel.fit(hp, model, np.random.rand(100, 28, 28), np.random.rand(100, 10))
1/4 ━━━━━[37m━━━━━━━━━━━━━━━ 0 秒 279 毫秒/步驟 - 準確度:0.0000e+00 - 損失:12.2230
4/4 ━━━━━━━━━━━━━━━━━━━━ 0秒 108毫秒/步驟 - 準確度: 0.0679 - 損失: 11.9568
4/4 ━━━━━━━━━━━━━━━━━━━━ 1秒 109毫秒/步驟 - 準確度: 0.0763 - 損失: 11.8941
<keras.src.callbacks.history.History at 0x7f318865c100>
若要調整資料預處理,我們只需在 HyperModel.fit()
中加入一個額外步驟,即可從參數存取資料集。在下列程式碼中,我們調整是否在訓練模型前正規化資料。這次我們明確地將 x
和 y
放入函式簽名中,因為我們需要使用它們。
class MyHyperModel(keras_tuner.HyperModel):
def build(self, hp):
model = keras.Sequential()
model.add(layers.Flatten())
model.add(
layers.Dense(
units=hp.Int("units", min_value=32, max_value=512, step=32),
activation="relu",
)
)
model.add(layers.Dense(10, activation="softmax"))
model.compile(
optimizer="adam",
loss="categorical_crossentropy",
metrics=["accuracy"],
)
return model
def fit(self, hp, model, x, y, **kwargs):
if hp.Boolean("normalize"):
x = layers.Normalization()(x)
return model.fit(
x,
y,
# Tune whether to shuffle the data in each epoch.
shuffle=hp.Boolean("shuffle"),
**kwargs,
)
hp = keras_tuner.HyperParameters()
hypermodel = MyHyperModel()
model = hypermodel.build(hp)
hypermodel.fit(hp, model, np.random.rand(100, 28, 28), np.random.rand(100, 10))
1/4 ━━━━━[37m━━━━━━━━━━━━━━━ 0秒 276毫秒/步驟 - 準確度: 0.1250 - 損失: 12.0090
4/4 ━━━━━━━━━━━━━━━━━━━━ 0秒 94毫秒/步驟 - 準確度: 0.0994 - 損失: 12.1242
4/4 ━━━━━━━━━━━━━━━━━━━━ 1秒 95毫秒/步驟 - 準確度: 0.0955 - 損失: 12.1594
<keras.src.callbacks.history.History at 0x7f31ba836200>
如果超參數在 build()
和 fit()
中都有用到,您可以在 build()
中定義它,並在 fit()
中使用 hp.get(hp_name)
來擷取它。我們以圖片大小為例。它在 build()
中用作輸入形狀,並在 fit()
中由資料預處理步驟用來裁剪圖片。
class MyHyperModel(keras_tuner.HyperModel):
def build(self, hp):
image_size = hp.Int("image_size", 10, 28)
inputs = keras.Input(shape=(image_size, image_size))
outputs = layers.Flatten()(inputs)
outputs = layers.Dense(
units=hp.Int("units", min_value=32, max_value=512, step=32),
activation="relu",
)(outputs)
outputs = layers.Dense(10, activation="softmax")(outputs)
model = keras.Model(inputs, outputs)
model.compile(
optimizer="adam",
loss="categorical_crossentropy",
metrics=["accuracy"],
)
return model
def fit(self, hp, model, x, y, validation_data=None, **kwargs):
if hp.Boolean("normalize"):
x = layers.Normalization()(x)
image_size = hp.get("image_size")
cropped_x = x[:, :image_size, :image_size, :]
if validation_data:
x_val, y_val = validation_data
cropped_x_val = x_val[:, :image_size, :image_size, :]
validation_data = (cropped_x_val, y_val)
return model.fit(
cropped_x,
y,
# Tune whether to shuffle the data in each epoch.
shuffle=hp.Boolean("shuffle"),
validation_data=validation_data,
**kwargs,
)
tuner = keras_tuner.RandomSearch(
MyHyperModel(),
objective="val_accuracy",
max_trials=3,
overwrite=True,
directory="my_dir",
project_name="tune_hypermodel",
)
tuner.search(x_train, y_train, epochs=2, validation_data=(x_val, y_val))
Trial 3 Complete [00h 00m 04s]
val_accuracy: 0.9567000269889832
Best val_accuracy So Far: 0.9685999751091003
Total elapsed time: 00h 00m 13s
使用 HyperModel
也允許您自行重新訓練最佳模型。
hypermodel = MyHyperModel()
best_hp = tuner.get_best_hyperparameters()[0]
model = hypermodel.build(best_hp)
hypermodel.fit(best_hp, model, x_all, y_all, epochs=1)
1/1875 [37m━━━━━━━━━━━━━━━━━━━━ 9:00 289ms/step - accuracy: 0.0000e+00 - loss: 2.4352
52/1875 [37m━━━━━━━━━━━━━━━━━━━━ 1秒 996微秒/步驟 - 準確度: 0.6035 - 損失: 1.3521
110/1875 ━[37m━━━━━━━━━━━━━━━━━━━ 1秒 925微秒/步驟 - 準確度: 0.7037 - 損失: 1.0231
171/1875 ━[37m━━━━━━━━━━━━━━━━━━━ 1秒 890微秒/步驟 - 準確度: 0.7522 - 損失: 0.8572
231/1875 ━━[37m━━━━━━━━━━━━━━━━━━ 1秒 877微秒/步驟 - 準確度: 0.7804 - 損失: 0.7590
291/1875 ━━━[37m━━━━━━━━━━━━━━━━━ 1秒 870微秒/步驟 - 準確度: 0.7993 - 損失: 0.6932
350/1875 ━━━[37m━━━━━━━━━━━━━━━━━ 1秒 867微秒/步驟 - 準確度: 0.8127 - 損失: 0.6467
413/1875 ━━━━[37m━━━━━━━━━━━━━━━━ 1秒 856微秒/步驟 - 準確度: 0.8238 - 損失: 0.6079
476/1875 ━━━━━[37m━━━━━━━━━━━━━━━ 1秒 848微秒/步驟 - 準確度: 0.8326 - 損失: 0.5774
535/1875 ━━━━━[37m━━━━━━━━━━━━━━━ 1秒 849微秒/步驟 - 準確度: 0.8394 - 損失: 0.5536
600/1875 ━━━━━━[37m━━━━━━━━━━━━━━ 1秒 841微秒/步驟 - 準確度: 0.8458 - 損失: 0.5309
661/1875 ━━━━━━━[37m━━━━━━━━━━━━━ 1秒 840微秒/步驟 - 準確度: 0.8511 - 損失: 0.5123
723/1875 ━━━━━━━[37m━━━━━━━━━━━━━ 0秒 837微秒/步驟 - 準確度: 0.8559 - 損失: 0.4955
783/1875 ━━━━━━━━[37m━━━━━━━━━━━━ 0秒 838微秒/步驟 - 準確度: 0.8600 - 損失: 0.4811
847/1875 ━━━━━━━━━[37m━━━━━━━━━━━ 0秒 834微秒/步驟 - 準確度: 0.8640 - 損失: 0.4671
912/1875 ━━━━━━━━━[37m━━━━━━━━━━━ 0秒 830微秒/步驟 - 準確度: 0.8677 - 損失: 0.4544
976/1875 ━━━━━━━━━━[37m━━━━━━━━━━ 0秒 827微秒/步驟 - 準確度: 0.8709 - 損失: 0.4429
1040/1875 ━━━━━━━━━━━[37m━━━━━━━━━ 0秒 825微秒/步驟 - 準確度: 0.8738 - 損失: 0.4325
1104/1875 ━━━━━━━━━━━[37m━━━━━━━━━ 0秒 822微秒/步驟 - 準確度: 0.8766 - 損失: 0.4229
1168/1875 ━━━━━━━━━━━━[37m━━━━━━━━ 0秒 821微秒/步驟 - 準確度: 0.8791 - 損失: 0.4140
1233/1875 ━━━━━━━━━━━━━[37m━━━━━━━ 0秒 818微秒/步驟 - 準確度: 0.8815 - 損失: 0.4056
1296/1875 ━━━━━━━━━━━━━[37m━━━━━━━ 0秒 817微秒/步驟 - 準確度: 0.8837 - 損失: 0.3980
1361/1875 ━━━━━━━━━━━━━━[37m━━━━━━ 0秒 815微秒/步驟 - 準確度: 0.8858 - 損失: 0.3907
1424/1875 ━━━━━━━━━━━━━━━[37m━━━━━ 0秒 814微秒/步驟 - 準確度: 0.8877 - 損失: 0.3840
1488/1875 ━━━━━━━━━━━━━━━[37m━━━━━ 0秒 813微秒/步驟 - 準確度: 0.8895 - 損失: 0.3776
1550/1875 ━━━━━━━━━━━━━━━━[37m━━━━ 0秒 813微秒/步驟 - 準確度: 0.8912 - 損失: 0.3718
1613/1875 ━━━━━━━━━━━━━━━━━[37m━━━ 0秒 813微秒/步驟 - 準確度: 0.8928 - 損失: 0.3662
1678/1875 ━━━━━━━━━━━━━━━━━[37m━━━ 0秒 811微秒/步驟 - 準確度: 0.8944 - 損失: 0.3607
1744/1875 ━━━━━━━━━━━━━━━━━━[37m━━ 0秒 809微秒/步驟 - 準確度: 0.8959 - 損失: 0.3555
1810/1875 ━━━━━━━━━━━━━━━━━━━[37m━ 0秒 808微秒/步驟 - 準確度: 0.8973 - 損失: 0.3504
1874/1875 ━━━━━━━━━━━━━━━━━━━[37m━ 0秒 807微秒/步驟 - 準確度: 0.8987 - 損失: 0.3457
1875/1875 ━━━━━━━━━━━━━━━━━━━━ 2秒 808微秒/步驟 - 準確度: 0.8987 - 損失: 0.3456
<keras.src.callbacks.history.History at 0x7f31884b3070>
在之前的所有範例中,我們都只使用驗證準確度 ("val_accuracy"
) 作為調整目標來選擇最佳模型。實際上,您可以使用任何指標作為目標。最常用的指標是 "val_loss"
,即驗證損失。
在 Keras 中,您可以使用許多其他內建指標作為目標。這是內建指標的列表。
若要使用內建指標作為目標,您需要遵循以下步驟
MeanAbsoluteError()
。您需要使用 metrics=[MeanAbsoluteError()]
編譯模型。您也可以使用其名稱字串來代替:metrics=["mean_absolute_error"]
。指標的名稱字串始終是類別名稱的蛇形命名法。f"val_{metric_name_string}"
的格式。例如,在驗證資料上評估的均方誤差的目標名稱字串應為 "val_mean_absolute_error"
。keras_tuner.Objective
中。我們通常需要將目標包裝到 keras_tuner.Objective
物件中,以指定優化目標的方向。例如,我們想要最小化均方誤差,可以使用 keras_tuner.Objective("val_mean_absolute_error", "min")
。方向應為 "min"
或 "max"
。您可以看到以下簡化的程式碼範例。
def build_regressor(hp):
model = keras.Sequential(
[
layers.Dense(units=hp.Int("units", 32, 128, 32), activation="relu"),
layers.Dense(units=1),
]
)
model.compile(
optimizer="adam",
loss="mean_squared_error",
# Objective is one of the metrics.
metrics=[keras.metrics.MeanAbsoluteError()],
)
return model
tuner = keras_tuner.RandomSearch(
hypermodel=build_regressor,
# The objective name and direction.
# Name is the f"val_{snake_case_metric_class_name}".
objective=keras_tuner.Objective("val_mean_absolute_error", direction="min"),
max_trials=3,
overwrite=True,
directory="my_dir",
project_name="built_in_metrics",
)
tuner.search(
x=np.random.rand(100, 10),
y=np.random.rand(100, 1),
validation_data=(np.random.rand(20, 10), np.random.rand(20, 1)),
)
tuner.results_summary()
Trial 3 Complete [00h 00m 01s]
val_mean_absolute_error: 0.39589792490005493
Best val_mean_absolute_error So Far: 0.34321871399879456
Total elapsed time: 00h 00m 03s
Results summary
Results in my_dir/built_in_metrics
Showing 10 best trials
Objective(name="val_mean_absolute_error", direction="min")
Trial 1 summary
Hyperparameters:
units: 32
Score: 0.34321871399879456
Trial 2 summary
Hyperparameters:
units: 128
Score: 0.39589792490005493
Trial 0 summary
Hyperparameters:
units: 96
Score: 0.5005304217338562
您可以實作自己的指標,並將其用作超參數搜尋目標。在這裡,我們以均方誤差 (MSE) 為例。首先,我們透過子類化 keras.metrics.Metric
來實作 MSE 指標。請記住使用 super().__init__()
的 name
引數為您的指標命名,這將在稍後使用。注意:MSE 實際上是內建指標,可以使用 keras.metrics.MeanSquaredError
匯入。這只是一個範例,展示如何使用自訂指標作為超參數搜尋目標。
有關實作自訂指標的更多資訊,請參閱本教學。如果您想要的指標的函式簽名與 update_state(y_true, y_pred, sample_weight)
不同,您可以按照本教學覆寫模型的 train_step()
方法。
from keras import ops
class CustomMetric(keras.metrics.Metric):
def __init__(self, **kwargs):
# Specify the name of the metric as "custom_metric".
super().__init__(name="custom_metric", **kwargs)
self.sum = self.add_weight(name="sum", initializer="zeros")
self.count = self.add_weight(name="count", dtype="int32", initializer="zeros")
def update_state(self, y_true, y_pred, sample_weight=None):
values = ops.square(y_true - y_pred)
count = ops.shape(y_true)[0]
if sample_weight is not None:
sample_weight = ops.cast(sample_weight, self.dtype)
values *= sample_weight
count *= sample_weight
self.sum.assign_add(ops.sum(values))
self.count.assign_add(count)
def result(self):
return self.sum / ops.cast(self.count, "float32")
def reset_state(self):
self.sum.assign(0)
self.count.assign(0)
使用自訂目標執行搜尋。
def build_regressor(hp):
model = keras.Sequential(
[
layers.Dense(units=hp.Int("units", 32, 128, 32), activation="relu"),
layers.Dense(units=1),
]
)
model.compile(
optimizer="adam",
loss="mean_squared_error",
# Put custom metric into the metrics.
metrics=[CustomMetric()],
)
return model
tuner = keras_tuner.RandomSearch(
hypermodel=build_regressor,
# Specify the name and direction of the objective.
objective=keras_tuner.Objective("val_custom_metric", direction="min"),
max_trials=3,
overwrite=True,
directory="my_dir",
project_name="custom_metrics",
)
tuner.search(
x=np.random.rand(100, 10),
y=np.random.rand(100, 1),
validation_data=(np.random.rand(20, 10), np.random.rand(20, 1)),
)
tuner.results_summary()
Trial 3 Complete [00h 00m 01s]
val_custom_metric: 0.2830956280231476
Best val_custom_metric So Far: 0.2529197633266449
Total elapsed time: 00h 00m 02s
Results summary
Results in my_dir/custom_metrics
Showing 10 best trials
Objective(name="val_custom_metric", direction="min")
Trial 0 summary
Hyperparameters:
units: 32
Score: 0.2529197633266449
Trial 2 summary
Hyperparameters:
units: 128
Score: 0.2830956280231476
Trial 1 summary
Hyperparameters:
units: 96
Score: 0.4656866192817688
如果您的自訂目標難以放入自訂指標中,您也可以在 HyperModel.fit()
中自行評估模型並傳回目標值。預設情況下,目標值將被最小化。在這種情況下,您不需要在初始化調整器時指定 objective
。但是,在這種情況下,指標值只會由 KerasTuner 日誌追蹤,而不會在 Keras 日誌中追蹤。因此,這些值將不會顯示在使用 Keras 指標的任何 TensorBoard 視圖中。
class HyperRegressor(keras_tuner.HyperModel):
def build(self, hp):
model = keras.Sequential(
[
layers.Dense(units=hp.Int("units", 32, 128, 32), activation="relu"),
layers.Dense(units=1),
]
)
model.compile(
optimizer="adam",
loss="mean_squared_error",
)
return model
def fit(self, hp, model, x, y, validation_data, **kwargs):
model.fit(x, y, **kwargs)
x_val, y_val = validation_data
y_pred = model.predict(x_val)
# Return a single float to minimize.
return np.mean(np.abs(y_pred - y_val))
tuner = keras_tuner.RandomSearch(
hypermodel=HyperRegressor(),
# No objective to specify.
# Objective is the return value of `HyperModel.fit()`.
max_trials=3,
overwrite=True,
directory="my_dir",
project_name="custom_eval",
)
tuner.search(
x=np.random.rand(100, 10),
y=np.random.rand(100, 1),
validation_data=(np.random.rand(20, 10), np.random.rand(20, 1)),
)
tuner.results_summary()
Trial 3 Complete [00h 00m 01s]
default_objective: 0.6571611521766413
Best default_objective So Far: 0.40719249752993525
Total elapsed time: 00h 00m 02s
Results summary
Results in my_dir/custom_eval
Showing 10 best trials
Objective(name="default_objective", direction="min")
Trial 1 summary
Hyperparameters:
units: 128
Score: 0.40719249752993525
Trial 0 summary
Hyperparameters:
units: 96
Score: 0.4992297225533352
Trial 2 summary
Hyperparameters:
units: 32
Score: 0.6571611521766413
如果您在 KerasTuner 中有多個指標要追蹤,但只使用其中一個作為目標,您可以傳回字典,其鍵為指標名稱,值為指標值,例如,傳回 {"metric_a": 1.0, "metric_b", 2.0}
。使用其中一個鍵作為目標名稱,例如,keras_tuner.Objective("metric_a", "min")
。
class HyperRegressor(keras_tuner.HyperModel):
def build(self, hp):
model = keras.Sequential(
[
layers.Dense(units=hp.Int("units", 32, 128, 32), activation="relu"),
layers.Dense(units=1),
]
)
model.compile(
optimizer="adam",
loss="mean_squared_error",
)
return model
def fit(self, hp, model, x, y, validation_data, **kwargs):
model.fit(x, y, **kwargs)
x_val, y_val = validation_data
y_pred = model.predict(x_val)
# Return a dictionary of metrics for KerasTuner to track.
return {
"metric_a": -np.mean(np.abs(y_pred - y_val)),
"metric_b": np.mean(np.square(y_pred - y_val)),
}
tuner = keras_tuner.RandomSearch(
hypermodel=HyperRegressor(),
# Objective is one of the keys.
# Maximize the negative MAE, equivalent to minimize MAE.
objective=keras_tuner.Objective("metric_a", "max"),
max_trials=3,
overwrite=True,
directory="my_dir",
project_name="custom_eval_dict",
)
tuner.search(
x=np.random.rand(100, 10),
y=np.random.rand(100, 1),
validation_data=(np.random.rand(20, 10), np.random.rand(20, 1)),
)
tuner.results_summary()
Trial 3 Complete [00h 00m 01s]
metric_a: -0.39470441501524833
Best metric_a So Far: -0.3836997988261662
Total elapsed time: 00h 00m 02s
Results summary
Results in my_dir/custom_eval_dict
Showing 10 best trials
Objective(name="metric_a", direction="max")
Trial 1 summary
Hyperparameters:
units: 64
Score: -0.3836997988261662
Trial 2 summary
Hyperparameters:
units: 32
Score: -0.39470441501524833
Trial 0 summary
Hyperparameters:
units: 96
Score: -0.46081380465766364
在某些情況下,很難將您的程式碼對齊到建置和擬合函式中。您也可以透過覆寫 Tuner.run_trial()
將您的端對端工作流程保留在一個地方,這可讓您完全控制試驗。您可以將其視為任何內容的黑箱最佳化器。
例如,您可以找到一個使 f(x)=x*x+1
最小化的 x
值。在下列程式碼中,我們只將 x
定義為超參數,並傳回 f(x)
作為目標值。可以省略初始化調整器的 hypermodel
和 objective
引數。
class MyTuner(keras_tuner.RandomSearch):
def run_trial(self, trial, *args, **kwargs):
# Get the hp from trial.
hp = trial.hyperparameters
# Define "x" as a hyperparameter.
x = hp.Float("x", min_value=-1.0, max_value=1.0)
# Return the objective value to minimize.
return x * x + 1
tuner = MyTuner(
# No hypermodel or objective specified.
max_trials=20,
overwrite=True,
directory="my_dir",
project_name="tune_anything",
)
# No need to pass anything to search()
# unless you use them in run_trial().
tuner.search()
print(tuner.get_best_hyperparameters()[0].get("x"))
Trial 20 Complete [00h 00m 00s]
default_objective: 1.6547719581194267
Best default_objective So Far: 1.0013236767905302
Total elapsed time: 00h 00m 00s
0.03638236922645777
您可以保持所有 Keras 程式碼不變,並使用 KerasTuner 來調整它。如果您因某些原因無法修改 Keras 程式碼,這會很有用。
它也為您提供了更大的彈性。您不必將模型建置和訓練程式碼分開。但是,此工作流程無法幫助您儲存模型或連接 TensorBoard 外掛程式。
若要儲存模型,您可以使用 trial.trial_id
,它是一個唯一識別試驗的字串,以建構不同的路徑來儲存來自不同試驗的模型。
import os
def keras_code(units, optimizer, saving_path):
# Build model
model = keras.Sequential(
[
layers.Dense(units=units, activation="relu"),
layers.Dense(units=1),
]
)
model.compile(
optimizer=optimizer,
loss="mean_squared_error",
)
# Prepare data
x_train = np.random.rand(100, 10)
y_train = np.random.rand(100, 1)
x_val = np.random.rand(20, 10)
y_val = np.random.rand(20, 1)
# Train & eval model
model.fit(x_train, y_train)
# Save model
model.save(saving_path)
# Return a single float as the objective value.
# You may also return a dictionary
# of {metric_name: metric_value}.
y_pred = model.predict(x_val)
return np.mean(np.abs(y_pred - y_val))
class MyTuner(keras_tuner.RandomSearch):
def run_trial(self, trial, **kwargs):
hp = trial.hyperparameters
return keras_code(
units=hp.Int("units", 32, 128, 32),
optimizer=hp.Choice("optimizer", ["adam", "adadelta"]),
saving_path=os.path.join("/tmp", f"{trial.trial_id}.keras"),
)
tuner = MyTuner(
max_trials=3,
overwrite=True,
directory="my_dir",
project_name="keep_code_separate",
)
tuner.search()
# Retraining the model
best_hp = tuner.get_best_hyperparameters()[0]
keras_code(**best_hp.values, saving_path="/tmp/best_model.keras")
Trial 3 Complete [00h 00m 00s]
default_objective: 0.18014027375230962
Best default_objective So Far: 0.18014027375230962
Total elapsed time: 00h 00m 03s
1/4 ━━━━━[37m━━━━━━━━━━━━━━━ 0秒 172毫秒/步驟 - 損失: 0.5030
4/4 ━━━━━━━━━━━━━━━━━━━━ 0秒 60毫秒/步驟 - 損失: 0.5288
4/4 ━━━━━━━━━━━━━━━━━━━━ 0秒 61毫秒/步驟 - 損失: 0.5367
1/1 ━━━━━━━━━━━━━━━━━━━━ 0秒 27毫秒/步驟
1/1 ━━━━━━━━━━━━━━━━━━━━ 0秒 28毫秒/步驟
0.5918120126201316
這些是適用於電腦視覺的現成超模型。
它們預先編譯了 loss="categorical_crossentropy"
和 metrics=["accuracy"]
。
from keras_tuner.applications import HyperResNet
hypermodel = HyperResNet(input_shape=(28, 28, 1), classes=10)
tuner = keras_tuner.RandomSearch(
hypermodel,
objective="val_accuracy",
max_trials=2,
overwrite=True,
directory="my_dir",
project_name="built_in_hypermodel",
)