作者: 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(
# 定义超参数。
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(
# 调整单位数量。
units=hp.Int("units", min_value=32, max_value=512, step=32),
# 调整使用的激活函数。
activation=hp.Choice("activation", ["relu", "tanh"]),
)
)
# 调整是否使用 dropout。
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_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")
# 使用超参数值调用现有的模型构建代码。
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())
# 调整层数。
for i in range(hp.Int("num_layers", 1, 3)):
model.add(
layers.Dense(
# 单独调整单元数量。
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()
搜索空间摘要
默认搜索空间大小: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))
试验 3 完成 [00h 00m 19s]
val_accuracy: 0.9665500223636627
迄今为止最佳 val_accuracy: 0.9665500223636627
总耗时: 00h 00m 40s
在search
过程中,模型构建函数会在不同的试验中使用不同的超参数值。在每个试验中,调优器会生成一组新的超参数值来构建模型。然后模型被训练和评估。指标被记录下来。调优器逐步探索空间,并最终找到一组好的超参数值。
当搜索结束后,您可以检索最佳模型。该模型保存在其在validation_data
上评估的最佳性能时期。
# 获取前 2 个模型。
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: 跳过优化器 'adam' 的变量加载,因为它有 2 个变量,而保存的优化器有 18 个变量。
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: 跳过优化器 'adam' 的变量加载,因为它有 2 个变量,而保存的优化器有 10 个变量。
trackable.load_own_variables(weights_store.get(inner_path))
模型: "sequential"
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┓ ┃ 层 (类型) ┃ 输出形状 ┃ 参数 # ┃ ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━┩ │ 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 │ └─────────────────────────────────┴───────────────────────────┴────────────┘
总参数: 556,810 (2.12 MB)
可训练参数: 556,810 (2.12 MB)
非可训练参数: 0 (0.00 B)
你也可以打印搜索结果的摘要。
tuner.results_summary()
结果摘要
结果保存在 my_dir/helloworld
显示 10 个最佳试验
目标(name="val_accuracy", direction="max")
试验 2 摘要
超参数:
num_layers: 3
units_0: 416
activation: relu
dropout: True
lr: 0.0001324166048504802
units_1: 512
units_2: 32
得分: 0.9665500223636627
试验 0 摘要
超参数:
num_layers: 1
units_0: 128
activation: tanh
dropout: False
lr: 0.001425162921397599
得分: 0.9623999893665314
试验 1 摘要
超参数:
num_layers: 2
units_0: 512
activation: tanh
dropout: True
lr: 0.0010584293918512798
units_1: 32
得分: 0.9606499969959259
你会在文件夹
my_dir/helloworld
中找到详细的日志、检查点等,即 directory/project_name
。
你也可以使用 TensorBoard 和 HParams 插件可视化调优结果。 有关更多信息,请访问 此链接。
如果你想用整个数据集训练模型,你可以获取最佳超参数并自己重新训练模型。
# 获取前 2 个超参数。
best_hps = tuner.get_best_hyperparameters(5)
# 使用最佳超参数构建模型。
model = build_model(best_hps[0])
# 用整个数据集拟合。
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 ━━━━━━━━━━━━━━━━━━━━ 17:57 575ms/step - accuracy: 0.1250 - loss: 2.3113
29/1875 [37m━━━━━━━━━━━━━━━━━━━━ 3s 2ms/step - accuracy: 0.1753 - loss: 2.2296
63/1875 [37m━━━━━━━━━━━━━━━━━━━━ 3s 2ms/step - accuracy: 0.2626 - loss: 2.1206
96/1875 ━[37m━━━━━━━━━━━━━━━━━━━ 2s 2ms/step - accuracy: 0.3252 - loss: 2.0103
130/1875 ━[37m━━━━━━━━━━━━━━━━━━━ 2s 2ms/step - accuracy: 0.3745 - loss: 1.9041
164/1875 ━[37m━━━━━━━━━━━━━━━━━━━ 2s 2ms/step - accuracy: 0.4139 - loss: 1.8094
199/1875 ━━[37m━━━━━━━━━━━━━━━━━━ 2s 2ms/step - accuracy: 0.4470 - loss: 1.7246
235/1875 ━━[37m━━━━━━━━━━━━━━━━━━ 2s 2ms/step - accuracy: 0.4752 - loss: 1.6493
270/1875 ━━[37m━━━━━━━━━━━━━━━━━━ 2s 2ms/step - accuracy: 0.4982 - loss: 1.5857
305/1875 ━━━[37m━━━━━━━━━━━━━━━━━ 2s 2ms/step - accuracy: 0.5182 - loss: 1.5293
339/1875 ━━━[37m━━━━━━━━━━━━━━━━━ 2s 2ms/step - accuracy: 0.5354 - loss: 1.4800
374/1875 ━━━[37m━━━━━━━━━━━━━━━━━ 2s 1ms/step - accuracy: 0.5513 - loss: 1.4340
409/1875 ━━━━[37m━━━━━━━━━━━━━━━━ 2s 1ms/step - accuracy: 0.5656 - loss: 1.3924
444/1875 ━━━━[37m━━━━━━━━━━━━━━━━ 2s 1ms/step - accuracy: 0.5785 - loss: 1.3545
478/1875 ━━━━━[37m━━━━━━━━━━━━━━━ 2s 1ms/step - accuracy: 0.5899 - loss: 1.3208
513/1875 ━━━━━[37m━━━━━━━━━━━━━━━ 2s 1ms/step - accuracy: 0.6006 - loss: 1.2887
548/1875 ━━━━━[37m━━━━━━━━━━━━━━━ 1s 1ms/step - accuracy: 0.6104 - loss: 1.2592
583/1875 ━━━━━━[37m━━━━━━━━━━━━━━ 1s 1ms/step - accuracy: 0.6195 - loss: 1.2318
618/1875 ━━━━━━[37m━━━━━━━━━━━━━━ 1s 1ms/step - accuracy: 0.6279 - loss: 1.2063
653/1875 ━━━━━━[37m━━━━━━━━━━━━━━ 1s 1ms/step - accuracy: 0.6358 - loss: 1.1823
688/1875 ━━━━━━━[37m━━━━━━━━━━━━━ 1s 1ms/step - accuracy: 0.6431 - loss: 1.1598
723/1875 ━━━━━━━[37m━━━━━━━━━━━━━ 1s 1ms/step - accuracy: 0.6500 - loss: 1.1387
758/1875 ━━━━━━━━[37m━━━━━━━━━━━━ 1s 1ms/step - accuracy: 0.6564 - loss: 1.1189
793/1875 ━━━━━━━━[37m━━━━━━━━━━━━ 1s 1ms/step - accuracy: 0.6625 - loss: 1.1002
828/1875 ━━━━━━━━[37m━━━━━━━━━━━━ 1s 1ms/step - accuracy: 0.6682 - loss: 1.0826
863/1875 ━━━━━━━━━[37m━━━━━━━━━━━ 1s 1ms/step - accuracy: 0.6736 - loss: 1.0658
899/1875 ━━━━━━━━━[37m━━━━━━━━━━━ 1s 1ms/step - accuracy: 0.6788 - loss: 1.0495
935/1875 ━━━━━━━━━[37m━━━━━━━━━━━ 1s 1ms/step - accuracy: 0.6838 - loss: 1.0339
970/1875 ━━━━━━━━━━[37m━━━━━━━━━━ 1s 1ms/step - accuracy: 0.6885 - loss: 1.0195
1005/1875 ━━━━━━━━━━[37m━━━━━━━━━━ 1s 1ms/step - accuracy: 0.6929 - loss: 1.0058
1041/1875 ━━━━━━━━━━━[37m━━━━━━━━━ 1s 1ms/step - accuracy: 0.6972 - loss: 0.9923
1076/1875 ━━━━━━━━━━━[37m━━━━━━━━━ 1s 1ms/step - accuracy: 0.7012 - loss: 0.9798
1111/1875 ━━━━━━━━━━━[37m━━━━━━━━━ 1s 1ms/step - accuracy: 0.7051 - loss: 0.9677
1146/1875 ━━━━━━━━━━━━[37m━━━━━━━━ 1s 1ms/step - accuracy: 0.7088 - loss: 0.9561
1182/1875 ━━━━━━━━━━━━[37m━━━━━━━━ 1s 1ms/step - accuracy: 0.7124 - loss: 0.9446
1218/1875 ━━━━━━━━━━━━[37m━━━━━━━━ 0s 1ms/step - accuracy: 0.7159 - loss: 0.9336
1254/1875 ━━━━━━━━━━━━━[37m━━━━━━━ 0s 1ms/step - accuracy: 0.7193 - loss: 0.9230
1289/1875 ━━━━━━━━━━━━━[37m━━━━━━━ 0s 1ms/step - accuracy: 0.7225 - loss: 0.9131
1324/1875 ━━━━━━━━━━━━━━[37m━━━━━━ 0s 1ms/step - accuracy: 0.7255 - loss: 0.9035
1359/1875 ━━━━━━━━━━━━━━[37m━━━━━━ 0s 1ms/step - accuracy: 0.7284 - loss: 0.8943
1394/1875 ━━━━━━━━━━━━━━[37m━━━━━━ 0s 1ms/step - accuracy: 0.7313 - loss: 0.8853
1429/1875 ━━━━━━━━━━━━━━━[37m━━━━━ 0s 1ms/step - accuracy: 0.7341 - loss: 0.8767
1465/1875 ━━━━━━━━━━━━━━━[37m━━━━━ 0s 1ms/step - accuracy: 0.7368 - loss: 0.8680
1500/1875 ━━━━━━━━━━━━━━━━[37m━━━━ 0s 1ms/step - accuracy: 0.7394 - loss: 0.8599
1535/1875 ━━━━━━━━━━━━━━━━[37m━━━━ 0s 1ms/step - accuracy: 0.7419 - loss: 0.8520
1570/1875 ━━━━━━━━━━━━━━━━[37m━━━━ 0s 1ms/step - accuracy: 0.7443 - loss: 0.8444
1605/1875 ━━━━━━━━━━━━━━━━━[37m━━━ 0s 1ms/step - accuracy: 0.7467 - loss: 0.8370
1639/1875 ━━━━━━━━━━━━━━━━━[37m━━━ 0s 1ms/step - accuracy: 0.7489 - loss: 0.8299
1674/1875 ━━━━━━━━━━━━━━━━━[37m━━━ 0s 1ms/step - accuracy: 0.7511 - loss: 0.8229
1707/1875 ━━━━━━━━━━━━━━━━━━[37m━━ 0s 1ms/step - accuracy: 0.7532 - loss: 0.8164
1741/1875 ━━━━━━━━━━━━━━━━━━[37m━━ 0s 1ms/step - accuracy: 0.7552 - loss: 0.8099
1774/1875 ━━━━━━━━━━━━━━━━━━[37m━━ 0s 1ms/step - accuracy: 0.7572 - loss: 0.8038
1809/1875 ━━━━━━━━━━━━━━━━━━━[37m━ 0s 1ms/step - accuracy: 0.7592 - loss: 0.7975
1843/1875 ━━━━━━━━━━━━━━━━━━━[37m━ 0s 1ms/step - accuracy: 0.7611 - loss: 0.7915
1875/1875 ━━━━━━━━━━━━━━━━━━━━ 3s 1ms/step - accuracy: 0.7629 - loss: 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,
# 调整每个周期是否打乱数据。
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 ━━━━━━━━━━━━━━━━━━━━ 0s 279ms/step - accuracy: 0.0000e+00 - loss: 12.2230
4/4 ━━━━━━━━━━━━━━━━━━━━ 0s 108ms/step - accuracy: 0.0679 - loss: 11.9568
4/4 ━━━━━━━━━━━━━━━━━━━━ 1s 109ms/step - accuracy: 0.0763 - loss: 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,
# 调整每个周期是否打乱数据。
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━━━━━━━━━━━━━━━ 0s 276ms/step - accuracy: 0.1250 - loss: 12.0090
4/4 ━━━━━━━━━━━━━━━━━━━━ 0s 94ms/step - accuracy: 0.0994 - loss: 12.1242
4/4 ━━━━━━━━━━━━━━━━━━━━ 1s 95ms/step - accuracy: 0.0955 - loss: 12.1594
<keras.src.callbacks.history.History at 0x7f31ba836200>
如果超参数同时在 build()
和 fit()
中使用,您可以在 build()
中定义它,并使用 hp.get(hp_name)
在 fit()
中检索它。我们以图像大小为例。它在 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,
# 调整是否在每个时期打乱数据。
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))
试验 3 完成 [00小时 00分 04秒]
val_accuracy: 0.9567000269889832
迄今为止最佳 val_accuracy: 0.9685999751091003
总耗时: 00小时 00分 13秒
使用 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━━━━━━━━━━━━━━━━━━━━ 1s 996us/step - accuracy: 0.6035 - loss: 1.3521
110/1875 ━[37m━━━━━━━━━━━━━━━━━━━ 1s 925us/step - accuracy: 0.7037 - loss: 1.0231
171/1875 ━[37m━━━━━━━━━━━━━━━━━━━ 1s 890us/step - accuracy: 0.7522 - loss: 0.8572
231/1875 ━━[37m━━━━━━━━━━━━━━━━━━ 1s 877us/step - accuracy: 0.7804 - loss: 0.7590
291/1875 ━━━[37m━━━━━━━━━━━━━━━━━ 1s 870us/step - accuracy: 0.7993 - loss: 0.6932
350/1875 ━━━[37m━━━━━━━━━━━━━━━━━ 1s 867us/step - accuracy: 0.8127 - loss: 0.6467
413/1875 ━━━━[37m━━━━━━━━━━━━━━━━ 1s 856us/step - accuracy: 0.8238 - loss: 0.6079
476/1875 ━━━━━[37m━━━━━━━━━━━━━━━ 1s 848us/step - accuracy: 0.8326 - loss: 0.5774
535/1875 ━━━━━[37m━━━━━━━━━━━━━━━ 1s 849us/step - accuracy: 0.8394 - loss: 0.5536
600/1875 ━━━━━━[37m━━━━━━━━━━━━━━ 1s 841us/step - accuracy: 0.8458 - loss: 0.5309
661/1875 ━━━━━━━[37m━━━━━━━━━━━━━ 1s 840us/step - accuracy: 0.8511 - loss: 0.5123
723/1875 ━━━━━━━[37m━━━━━━━━━━━━━ 0s 837us/step - accuracy: 0.8559 - loss: 0.4955
783/1875 ━━━━━━━━[37m━━━━━━━━━━━━ 0s 838us/step - accuracy: 0.8600 - loss: 0.4811
847/1875 ━━━━━━━━━[37m━━━━━━━━━━━ 0s 834us/step - accuracy: 0.8640 - loss: 0.4671
912/1875 ━━━━━━━━━[37m━━━━━━━━━━━ 0s 830us/step - accuracy: 0.8677 - loss: 0.4544
976/1875 ━━━━━━━━━━[37m━━━━━━━━━━ 0s 827us/step - accuracy: 0.8709 - loss: 0.4429
1040/1875 ━━━━━━━━━━━[37m━━━━━━━━━ 0s 825us/step - accuracy: 0.8738 - loss: 0.4325
1104/1875 ━━━━━━━━━━━[37m━━━━━━━━━ 0s 822us/step - accuracy: 0.8766 - loss: 0.4229
1168/1875 ━━━━━━━━━━━━[37m━━━━━━━━ 0s 821us/step - accuracy: 0.8791 - loss: 0.4140
1233/1875 ━━━━━━━━━━━━━[37m━━━━━━━ 0s 818us/step - accuracy: 0.8815 - loss: 0.4056
1296/1875 ━━━━━━━━━━━━━[37m━━━━━━━ 0s 817us/step - accuracy: 0.8837 - loss: 0.3980
1361/1875 ━━━━━━━━━━━━━━[37m━━━━━━ 0s 815us/step - accuracy: 0.8858 - loss: 0.3907
1424/1875 ━━━━━━━━━━━━━━━[37m━━━━━ 0s 814us/step - accuracy: 0.8877 - loss: 0.3840
1488/1875 ━━━━━━━━━━━━━━━[37m━━━━━ 0s 813us/step - accuracy: 0.8895 - loss: 0.3776
1550/1875 ━━━━━━━━━━━━━━━━[37m━━━━ 0s 813us/step - accuracy: 0.8912 - loss: 0.3718
1613/1875 ━━━━━━━━━━━━━━━━━[37m━━━ 0s 813us/step - accuracy: 0.8928 - loss: 0.3662
1678/1875 ━━━━━━━━━━━━━━━━━[37m━━━ 0s 811us/step - accuracy: 0.8944 - loss: 0.3607
1744/1875 ━━━━━━━━━━━━━━━━━━[37m━━ 0s 809us/step - accuracy: 0.8959 - loss: 0.3555
1810/1875 ━━━━━━━━━━━━━━━━━━━[37m━ 0s 808us/step - accuracy: 0.8973 - loss: 0.3504
1874/1875 ━━━━━━━━━━━━━━━━━━━[37m━ 0s 807us/step - accuracy: 0.8987 - loss: 0.3457
1875/1875 ━━━━━━━━━━━━━━━━━━━━ 2s 808us/step - accuracy: 0.8987 - loss: 0.3456
<keras.src.callbacks.history.History 在 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",
# 目标是其中一个指标。
metrics=[keras.metrics.MeanAbsoluteError()],
)
return model
tuner = keras_tuner.RandomSearch(
hypermodel=build_regressor,
# 目标名称和方向。
# 名称是 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()
试验 3 完成 [00h 00m 01s]
val_mean_absolute_error: 0.39589792490005493
迄今为止最佳 val_mean_absolute_error: 0.34321871399879456
总耗时: 00h 00m 03s
结果摘要
结果在 my_dir/built_in_metrics
显示 10 个最佳试验
目标(name="val_mean_absolute_error", direction="min")
试验 1 摘要
超参数:
units: 32
分数: 0.34321871399879456
试验 2 摘要
超参数:
units: 128
分数: 0.39589792490005493
试验 0 摘要
超参数:
units: 96
分数: 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):
# 将度量的名称指定为 "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",
# 将自定义指标放入指标中。
metrics=[CustomMetric()],
)
return model
tuner = keras_tuner.RandomSearch(
hypermodel=build_regressor,
# 指定目标的名称和方向。
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()
试验 3 完成 [00h 00m 01s]
val_custom_metric: 0.2830956280231476
迄今为止最佳 val_custom_metric: 0.2529197633266449
总耗时: 00h 00m 02s
结果总结
结果在 my_dir/custom_metrics
显示 10 个最佳试验
目标(name="val_custom_metric", direction="min")
试验 0 总结
超参数:
units: 32
得分: 0.2529197633266449
试验 2 总结
超参数:
units: 128
得分: 0.2830956280231476
试验 1 总结
超参数:
units: 96
得分: 0.4656866192817688
如果你的自定义目标难以放入自定义指标中,你也可以
在 HyperModel.fit()
中自行评估模型并返回目标
值。目标值默认情况下将被最小化。在这种情况下,你
不需要在初始化调整器时指定 objective
。然而,在
这种情况下,该指标值将不会在 Keras 日志中被追踪,仅在
KerasTuner 日志中。因此,这些值不会通过任何
使用 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 np.mean(np.abs(y_pred - y_val))
tuner = keras_tuner.RandomSearch(
hypermodel=HyperRegressor(),
# 不需要指定目标。
# 目标是 `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()
试验 3 完成 [00h 00m 01s]
default_objective: 0.6571611521766413
迄今为止最佳 default_objective: 0.40719249752993525
总耗时: 00h 00m 02s
结果总结
结果在 my_dir/custom_eval
显示 10 个最佳试验
目标(name="default_objective", direction="min")
试验 1 总结
超参数:
units: 128
得分: 0.40719249752993525
试验 0 总结
超参数:
units: 96
得分: 0.4992297225533352
试验 2 总结
超参数:
units: 32
得分: 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)
# 返回一个度量指标的字典供KerasTuner跟踪。
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(),
# 目标是键之一。
# 最大化负MAE,相当于最小化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()
试验 3 完成 [00小时 00分钟 01秒]
metric_a: -0.39470441501524833
迄今为止最佳 metric_a: -0.3836997988261662
总耗时: 00小时 00分钟 02秒
结果摘要
结果在 my_dir/custom_eval_dict
显示 10 个最佳试验
目标(name="metric_a", direction="max")
试验 1 摘要
超参数:
单位: 64
得分: -0.3836997988261662
试验 2 摘要
超参数:
单位: 32
得分: -0.39470441501524833
试验 0 摘要
超参数:
单位: 96
得分: -0.46081380465766364
在某些情况下,很难将代码对齐到构建和拟合函数中。你
也可以通过覆盖 Tuner.run_trial()
将端到端工作流保留在一个地方,这样你可以完全控制试验。你可以
将其视为任何事物的黑盒优化器。
例如,你可以找到一个值 x
,使得 f(x)=x*x+1
最小。在
以下代码中,我们只是将 x
定义为超参数,并返回 f(x)
作为
目标值。用于初始化调谐器的 hypermodel
和 objective
参数可以省略。
class MyTuner(keras_tuner.RandomSearch):
def run_trial(self, trial, *args, **kwargs):
# 从试验中获取超参数。
hp = trial.hyperparameters
# 将 “x” 定义为超参数。
x = hp.Float("x", min_value=-1.0, max_value=1.0)
# 返回要最小化的目标值。
return x * x + 1
tuner = MyTuner(
# 没有指定超模型或目标。
max_trials=20,
overwrite=True,
directory="my_dir",
project_name="tune_anything",
)
# 无需传递任何内容给 search()
# 除非你在 run_trial() 中使用它们。
tuner.search()
print(tuner.get_best_hyperparameters()[0].get("x"))
试验 20 完成 [00小时 00分钟 00秒]
默认目标: 1.6547719581194267
迄今为止最佳默认目标: 1.0013236767905302
总耗时: 00小时 00分钟 00秒
0.03638236922645777
你可以保持所有 Keras 代码不变,并使用 KerasTuner 对其进行调整。如果由于某种原因你无法修改 Keras 代码,这将很有用。
它还为你提供了更多的灵活性。你不必将模型构建和训练代码分开。然而,这种工作流不会帮助你 保存模型或与 TensorBoard 插件连接。
要保存模型,你可以使用 trial.trial_id
,这是一个字符串,用于唯一
识别一次试验,以构造不同路径以保存来自
不同试验的模型。
import os
def keras_code(units, optimizer, saving_path):
# 构建模型
model = keras.Sequential(
[
layers.Dense(units=units, activation="relu"),
layers.Dense(units=1),
]
)
model.compile(
optimizer=optimizer,
loss="mean_squared_error",
)
# 准备数据
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)
# 训练与评估模型
model.fit(x_train, y_train)
# 保存模型
model.save(saving_path)
# 返回一个单一的浮动数作为目标值。
# 你也可以返回一个字典
# {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()
# 重新训练模型
best_hp = tuner.get_best_hyperparameters()[0]
keras_code(**best_hp.values, saving_path="/tmp/best_model.keras")
试验 3 完成 [00小时 00分钟 00秒]
默认目标: 0.18014027375230962
迄今为止最佳默认目标: 0.18014027375230962
总耗时: 00小时 00分钟 03秒
1/4 ━━━━━[37m━━━━━━━━━━━━━━━ 0s 172ms/step - loss: 0.5030
4/4 ━━━━━━━━━━━━━━━━━━━━ 0s 60ms/step - loss: 0.5288
4/4 ━━━━━━━━━━━━━━━━━━━━ 0s 61ms/step - loss: 0.5367
1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 27ms/step
1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 28ms/step
0.5918120126201316
These are ready-to-use hypermodels for computer vision.
They come pre-compiled with loss="categorical_crossentropy"
and
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",
)