Bootstrap

Keras深度学习框架第二十七讲:KerasTuner超参数优化基础

1、超参数优化概念

1.1 什么是超参数优化

超参数调优,也称为超参数优化或参数调优,是寻找学习算法或模型最佳超参数组合的过程。超参数是在训练过程开始之前设置的参数,模型无法直接从数据中学习这些参数。它们控制着学习算法的行为,并对模型的性能产生重大影响。

超参数调优的目标是搜索导致给定任务上最佳性能的超参数组合,例如最大化准确率或最小化误差。该过程通常包括以下步骤:

  1. 定义搜索空间:确定要调优的每个超参数的可能值范围。这个搜索空间可以是离散的(例如,一组预定义的值)或连续的(例如,一系列浮点数)。

  2. 选择调优策略:有多种超参数调优策略可供选择,包括网格搜索、随机搜索、贝叶斯优化、基于梯度的优化和进化算法。这些策略在计算效率、样本效率和找到良好解决方案的能力方面有不同的权衡。

  3. 评估模型:对于每一组超参数组合,训练模型并在验证集上评估其性能。使用的性能指标(例如,准确率、损失、F1分数)取决于特定任务。

  4. 选择最佳超参数:选择在验证集上表现最好的超参数组合。

  5. 重新训练模型(可选):使用在调优过程中找到的最佳超参数,在整个数据集(包括验证集)上重新训练模型。这一步不是必需的,但有助于提高模型在测试集上的性能。

超参数调优是机器学习流程中的一个重要步骤,因为超参数的选择可以对模型的性能产生显著影响。然而,它也可能是计算密集型的,特别是对于具有大量超参数和大型搜索空间的复杂模型。因此,选择一个在计算效率和找到良好解决方案的能力之间取得平衡的调优策略是非常重要的。

1.2 KerasTuner简介

KerasTuner 是一个用于超参数调优的通用库,它与 Keras 工作流程紧密结合,但不仅限于此,它也可以用于调优 scikit-learn 模型或其他任何需要调优的模型。以下是一个使用 KerasTuner 来调优模型架构、训练过程和数据预处理步骤的简单示例。

1.2.1 安装 KerasTuner

如果你还没有安装 KerasTuner,你可以使用 pip 来安装:

pip install keras-tuner
1.2.2 准备数据和模型

假设我们有一个简单的 Keras 模型和一个数据集,我们想要调优这个模型的超参数。

from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split

# 假设我们有一个简单的分类数据集
X, y = make_classification(n_samples=1000, n_features=20, n_informative=2, n_redundant=10, random_state=42)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# 定义一个简单的 Keras 模型构建函数
def build_model(hp):
    model = Sequential()
    model.add(Dense(units=hp.Int('units_1', min_value=32, max_value=128, step=16), activation='relu', input_shape=(20,)))
    model.add(Dense(units=hp.Int('units_2', min_value=32, max_value=64, step=16), activation='relu'))
    model.add(Dense(1, activation='sigmoid'))
    model.compile(optimizer='adam',
                  loss='binary_crossentropy',
                  metrics=['accuracy'])
    return model
1.2.3使用 KerasTuner 搜索超参数

接下来,我们使用 KerasTuner 来搜索最佳的超参数。在这个例子中,我们将使用 RandomSearch 策略,但 KerasTuner 还提供了 HyperbandBayesianOptimization 等其他策略。

from kerastuner import RandomSearch
from kerastuner.engine.hyperparameters import HyperParameters

# 创建一个 RandomSearch 对象
tuner = RandomSearch(
    build_model,
    objective='val_accuracy',
    max_trials=10,
    executions_per_trial=3,
    directory='my_dir',
    project_name='helloworld')

# 运行搜索
tuner.search(X_train, y_train, epochs=2, validation_data=(X_test, y_test))

# 获取最佳模型
best_model = tuner.get_best_models(num_models=1)[0]

# 使用最佳模型进行评估
loss, accuracy = best_model.evaluate(X_test, y_test)
print(f'Loss: {loss}, Accuracy: {accuracy}')

在这个例子中,RandomSearch 会随机尝试不同的超参数组合,每个组合都会进行三次训练和验证(executions_per_trial=3)。max_trials=10 表示总共会尝试 10 个不同的超参数组合。

1.2.4 数据预处理步骤的调优

虽然 KerasTuner 主要用于模型架构和训练过程的调优,但你也可以在构建模型之前定义一些可调优的数据预处理步骤。例如,你可以定义不同的归一化或特征选择方法,并在搜索超参数时一起调优它们。

这通常需要在 build_model 函数中封装一些预处理逻辑,并使用 hp(HyperParameters 对象)来定义预处理步骤中的可调参数。

2、KerasTuner使用技巧

2.1调整模型架构

首先,我们需要编写一个函数,它返回一个编译好的Keras模型。该函数接受一个hp参数来在构建模型时定义超参数。

2.1.1定义搜索空间

在以下代码示例中,我们定义了一个具有两个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())

当然,超参数的种类很多。我们可以在函数中定义多个超参数。在以下代码中,我们将使用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())

正如下面所展示的,超参数看起来像是实际的值。但实际上,它们只是返回实际值的函数。例如,hp.Int() 返回一个整数(int)值。因此,你可以将这些超参数放入变量中、用于循环(for loops)或条件判断(if conditions)中。

在Keras Tuner或类似的超参数搜索框架中,这些超参数函数(如 hp.Int(), hp.Float(), hp.Choice(), hp.Boolean() 等)是在模型构建阶段定义的占位符。在模型训练和评估的过程中,这些占位符会被搜索算法赋予具体的值,以找到最佳的模型配置。

在模型构建函数中,程序员可以使用这些超参数函数来动态地创建模型的不同版本,这些版本具有不同的层配置、激活函数、优化器设置等。然后,搜索算法会评估这些不同版本的模型,并基于某种性能指标(如验证集上的损失或准确率)来选择最佳的超参数组合。

hp = keras_tuner.HyperParameters()
print(hp.Int("units", min_value=32, max_value=512, step=32))

程序员也可以提前定义超参数,并将你的Keras代码放在一个单独的函数中。

这意味着可以将超参数的定义(例如使用hp.Int(), hp.Float(), hp.Choice(), hp.Boolean()等)与实际的模型构建代码分开。这样做的好处是,程序员可以更清晰地组织代码,使得超参数的搜索和模型的定义更加模块化。

在Keras Tuner中,通常会定义一个模型构建函数,该函数接受一个HyperParameters对象作为输入,并使用该对象中的超参数来构建模型。这个HyperParameters对象是在搜索过程中由Keras Tuner自动生成的,并包含了所有需要调整的超参数值。

但是,程序员也可以选择提前定义一些超参数的默认值或范围,并将这些定义与模型构建函数分开。然后,在搜索过程中,可以将这些预定义的超参数传递给模型构建函数,或者使用Keras Tuner来覆盖这些默认值,并进行更广泛的搜索。

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())

每个超参数都通过其名称(第一个参数)唯一标识。为了将不同Dense层中的单元数作为不同的超参数分别调整,我们给它们赋予不同的名称,如f"units_{i}"

值得注意的是,这也是创建条件超参数的一个例子。有许多超参数指定了Dense层中的单元数。这些超参数的数量由层数决定,而层数本身也是一个超参数。因此,每次试验(trial)中使用的超参数总数可能会有所不同。一些超参数仅在满足特定条件时才会被使用。例如,units_3仅在num_layers大于3时才会被使用。使用Keras Tuner,你可以在创建模型时轻松地动态定义这样的超参数。

在Keras Tuner中,程序员可以通过条件逻辑(如if语句)来定义这样的条件超参数。这允许你根据其他超参数的值来动态地确定哪些超参数是相关的,并需要被调整。这种灵活性使得Keras Tuner成为一个强大的工具,用于自动化神经网络架构的超参数搜索和优化。

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())
2.1.2 开始搜索

在定义了搜索空间之后,我们需要选择一个搜索器类来运行搜索。你可以从RandomSearch、BayesianOptimization和Hyperband中选择,它们分别对应不同的调优算法。这里我们以RandomSearch为例。

为了初始化搜索器,我们需要在初始化器中指定几个参数。

  • hypermodel:这是构建模型的函数,在我们的例子中build_model
  • objective:要优化的目标名称(对于内置指标,是否最小化或最大化会自动推断)。稍后在本教程中我们将介绍如何使用自定义指标。
  • max_trials:在搜索期间运行的总试验次数。
  • executions_per_trial:每个试验应该构建和拟合的模型数量。不同的试验有不同的超参数值。在相同的试验中,执行的模型具有相同的超参数值。每次试验中进行多次执行的目的是减少结果的方差,从而能够更准确地评估模型的性能。如果你希望更快地获得结果,你可以设置executions_per_trial=1(每个模型配置进行一轮训练)。
  • overwrite:控制是否覆盖相同目录中的先前结果,还是恢复先前的搜索。这里我们设置overwrite=True以开始新的搜索并忽略任何先前的结果。
  • directory:用于存储搜索结果的目录的路径。
  • project_name:在目录中的子目录的名称。

通过配置这些参数,你可以启动搜索过程,并根据定义的搜索空间来寻找最佳的模型配置。

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()

在开始搜索之前,应提前准备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))

在搜索过程中,模型构建函数会使用不同的超参数值在不同的试验中被调用。在每个试验中,搜索器会生成一组新的超参数值来构建模型。然后模型会被训练并评估,评估指标会被记录下来。搜索器会逐步探索搜索空间并最终找到一组良好的超参数值。

2.1.3 查询结果

当搜索结束后,你可以检索到表现最好的模型或多个模型。这些模型是在验证集上评估时表现最佳的epoch时被保存的。

# Get the top 2 models.
models = tuner.get_best_models(num_models=2)
best_model = models[0]
best_model.summary()

打印查询结果概要

tuner.results_summary()

程序员可以在my_dir/helloworld文件夹中找到详细的日志、检查点等,即directory/project_name

程序员还可以使用TensorBoard和HParams插件来可视化调优结果。要获取更多信息,请遵循这个链接。

2.2 重新训练模型

如果你想要使用整个数据集来训练模型,你可以检索出最佳的超参数,并自己重新训练模型。

# 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)

2.3 模型训练调优

为了调优模型构建过程,我们需要继承HyperModel类,这也使得超模型易于共享和重用。

我们需要重写HyperModel.build()和HyperModel.fit()方法来分别调优模型构建和训练过程。HyperModel.build()方法与模型构建函数相同,它使用超参数创建Keras模型并返回该模型。

在HyperModel.fit()中,你可以访问由HyperModel.build()返回的模型、hp以及传递给search()的所有参数。你需要训练模型并返回训练历史记录。

在以下代码中,我们将调整model.fit()中的shuffle参数。

通常不需要调整epoch的数量,因为model.fit()会传递一个内置回调来保存由验证数据评估的最佳epoch时的模型。

注意:**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))
2.3.1数据预处理调优

为了调优数据预处理,我们只需在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))

如果某个超参数在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))
2.3.2 重新训练模型

使用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)
2.3.3 指定调优目标

在之前的所有示例中,我们都只是使用验证准确率(“val_accuracy”)作为调优目标来选择最佳模型。实际上,你可以使用任何指标作为调优目标。最常用的指标是"val_loss",即验证损失。

使用内置指标作为调优目标
Keras中有许多其他内置指标可以作为调优目标。以下是内置指标的列表。

要使用内置指标作为调优目标,你需要遵循以下步骤:

  1. 使用内置指标编译模型。例如,如果你想要使用平均绝对误差(MeanAbsoluteError)。你需要用metrics=[MeanAbsoluteError()]来编译模型。你也可以使用其名称字符串代替:metrics=[“mean_absolute_error”]。指标的名称字符串始终是类名的蛇形命名(snake_case)形式。

  2. 确定目标名称字符串。目标名称字符串的格式通常是f"val_{metric_name_string}"。例如,在验证数据上评估的均方误差的目标名称字符串应该是"val_mean_absolute_error"。

  3. 将其包装到keras_tuner.Objective中。我们通常需要将目标包装到keras_tuner.Objective对象中,以指定要优化目标的方向。例如,如果我们想要最小化均方误差,我们可以使用keras_tuner.Objective(“val_mean_absolute_error”, “min”)。方向应该是"min"或"max"。

  4. 将包装后的目标传递给调优器。

以下是简化版的代码示例。

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()
2.3.4 自定义指标作为调优目标

程序员可以实现自己的指标并将其用作超参数搜索的目标。这里,我们以均方误差(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_states(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()

如果自定义目标很难放入一个自定义指标中,程序员还可以在HyperModel.fit()中自行评估模型并返回目标值。默认情况下,这个目标值会被最小化。在这种情况下,你不需要在初始化调优器时指定目标。但是,这样做的话,只有KerasTuner会记录这个指标值,而不是通过Keras日志来跟踪。因此,这些值不会通过任何使用Keras指标的TensorBoard视图来显示。

简而言之,如果选择在HyperModel.fit()中直接计算并返回目标值,那么程序员将失去通过Keras的日志和TensorBoard来跟踪和可视化这些指标值的能力。相反,程序员需要依赖KerasTuner的日志来查看和评估这些自定义目标值。

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()

如果程序员在KerasTuner中要跟踪多个指标,但只使用其中一个作为目标,可以在HyperModel.fit()中返回一个字典,其中键是指标名称,值是指标值。例如,返回{"metric_a": 1.0, "metric_b": 2.0}。然后,程序员可以使用这些键中的一个作为目标名称来配置KerasTuner的Objective,例如keras_tuner.Objective("metric_a", "min")

这样,KerasTuner将使用这个指定的指标(“metric_a”)作为优化目标,但也会记录并跟踪所有在字典中返回的其他指标("metric_b"等)。这样,你就可以在调优过程中同时观察多个指标的变化,而不仅仅是最小化的目标指标。

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()
2.3.5 端到端工作流程的调优

在某些情况下,很难将代码拆分为构建和拟合函数。你还可以通过重写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):
        # 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"))

2.4 保持Keras代码独立

程序员可以保持所有的Keras代码不变,并使用KerasTuner来调优它。这在由于某种原因程序员不能修改Keras代码时特别有用。

同时,这也给了你更多的灵活性。程序员不需要将模型构建和训练代码分开。然而,这种工作流程不会帮助程序员保存模型或连接TensorBoard插件。

要保存模型,程序员可以使用trial.trial_id,它是一个唯一标识一个试验的字符串,来构造不同的路径以保存来自不同试验的模型。可以将这些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")

2.5 预制调用

KerasTuner 包含预制的可调应用:HyperResNet 和 HyperXception

这些是专为计算机视觉准备的即用型超模型(hypermodels)。

这些模型预编译时设置了损失函数为 “categorical_crossentropy” 和评估指标为 [“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",
)

3、总结

Keras超参数调优是指在使用Keras深度学习框架进行模型训练时,对模型的超参数进行搜索和调整,以找到最优的超参数组合,从而提高模型的性能。

  1. 超参数定义:超参数是在模型训练之前需要设置的参数,它们影响模型的训练过程和最终性能。在Keras中,常见的超参数包括学习率、批处理大小、训练轮次(epochs)、优化器类型、损失函数、正则化参数、层数、每层神经元数量、激活函数等。

  2. 调优方法

    • 网格搜索(Grid Search):在预定义的超参数空间中,对每一组超参数组合都进行训练并评估性能,最后选择性能最好的一组。这种方法简单但计算量大。
    • 随机搜索(Random Search):在超参数空间中随机选择一组超参数进行训练,这种方法通常比网格搜索更有效率,因为不是所有超参数都是等价的。
    • 贝叶斯优化(Bayesian Optimization):使用贝叶斯定理来智能地选择下一个要评估的超参数组合,基于之前的评估结果。这种方法能够更快地找到最优解。
    • 基于模型的优化:使用如高斯过程、决策树等方法来构建超参数和性能之间的模型,从而指导超参数的搜索。
  3. KerasTuner

    • KerasTuner是一个Keras官方提供的超参数调优工具,它封装了上述的调优方法,并提供了易于使用的API。
    • KerasTuner允许用户定义可调的超参数范围或选择,并在后台自动执行调优过程,包括创建模型、编译、训练、评估等步骤。
    • KerasTuner还支持分布式调优,可以在多个计算节点或GPU上并行执行调优试验。
  4. 注意事项

    • 评估指标:选择合适的评估指标来衡量模型的性能,如准确率、损失函数值、召回率、F1分数等。
    • 早停(Early Stopping):当模型性能在连续多个轮次中没有提升时,提前停止训练,以节省资源和时间。
    • 结果分析:分析调优结果,了解哪些超参数对模型性能影响最大,并根据结果进行进一步的调整。
  5. 集成和扩展

    • KerasTuner可以与Keras的其他功能(如数据预处理、模型保存/加载、回调函数等)无缝集成,提供完整的模型训练和调优流程。
    • 用户还可以根据自己的需求扩展KerasTuner的功能,如添加自定义的超参数搜索策略、评估指标等。

通过使用KerasTuner等工具进行超参数调优,可以更有效地找到最优的超参数组合,提高模型的性能,并节省大量的调优时间和计算资源。

使用Keras Tuner进行模型训练的过程可以分为以下几个步骤:

1. 安装并导入Keras Tuner

首先,你需要确保已经安装了Keras Tuner。如果还没有安装,可以使用pip进行安装:

pip install keras-tuner

然后在你的Python脚本中导入Keras Tuner:

import keras_tuner as kt

2. 准备数据集

根据你的任务准备数据集,并进行必要的预处理。例如,对于图像分类任务,你可以使用Keras的内置数据集(如MNIST或Fashion MNIST),或者加载你自己的数据集。

3. 定义模型构建函数

在Keras Tuner中,你需要定义一个模型构建函数,该函数接受一个hp(HyperParameters)对象作为输入,并返回一个编译好的Keras模型。在这个函数中,你可以使用hp对象来定义可调的超参数。

def build_model(hp):
    model = keras.Sequential()
    model.add(keras.layers.Flatten(input_shape=(28, 28)))  # 假设输入是28x28的图像
    
    # 搜索最佳的隐藏层单元数
    hp_units = hp.Int('units', min_value=32, max_value=512, step=32)
    model.add(keras.layers.Dense(units=hp_units, activation='relu'))
    
    # 如果需要,可以添加更多层
    
    model.add(keras.layers.Dense(10, activation='softmax'))  # 假设有10个类别
    
    # 编译模型
    model.compile(
        optimizer=keras.optimizers.Adam(),
        loss='sparse_categorical_crossentropy',
        metrics=['accuracy'],
    )
    return model

4. 实例化Tuner类

接下来,你需要实例化一个Tuner类(如RandomSearch, Hyperband, BayesianOptimization等),并传入模型构建函数、数据集、训练轮次等参数。

tuner = kt.RandomSearch(
    build_model,
    objective='val_accuracy',  # 要优化的指标
    max_trials=10,  # 最大试验次数
    executions_per_trial=3,  # 每个试验的重复次数
    directory='my_dir',  # 保存结果的目录
    project_name='helloworld',  # 项目名称
)

5. 运行Tuner进行搜索

使用search方法来运行Tuner进行超参数搜索。你可以传入训练数据和验证数据。

(x_train, y_train), (x_val, y_val) = keras.datasets.mnist.load_data()
# 对数据进行必要的预处理...

tuner.search(x_train, y_train, epochs=10, validation_data=(x_val, y_val))

6. 获取最佳模型

搜索完成后,你可以从Tuner中获取最佳模型。

best_model = tuner.get_best_models(num_models=1)[0]

7. 评估并保存模型

你可以使用测试集来评估最佳模型的性能,并保存模型以供将来使用。

# 加载测试集...
test_loss, test_acc = best_model.evaluate(x_test, y_test)
print('Test accuracy:', test_acc)

# 保存模型...
best_model.save('best_model.h5')
;