使用Keras Tuner进行自动超参数调优的实用教程

在本文中将介绍如何使用 KerasTuner,并且还会介绍其他教程中没有的一些技巧,例如单独调整每一层中的参数或与优化器一起调整学习率等。Keras-Tuner 是一个可帮助您优化神经网络并找到接近最优的超参数集的工具,它利用了高级搜索和优化方法,例如 HyperBand 搜索和贝叶斯优化。所以只需要定义搜索空间,Keras-Tuner 将负责繁琐的调优过程,这要比手动的Grid Search强的多!

加载数据

我们这里使用手语数据集,假设想在图像分类数据集上训练 CNN,我们将使用 KerasTuner 优化神经网络。

首先,使用 pip 安装 Keras-Tuner 库并导入必要的库。

 !pip install keras-tuner

然后导入需要的包:

 import keras_tuner
 from tensorflow import keras
 from keras import backend as K
 from tensorflow.keras import layers, losses
 import numpy as np
 import matplotlib.pyplot as plt
 import os

下面我们就需要加载数据, 我们选择使用美国手语 (ASL) 数据集,该数据集可在 Kaggle 上下载。它包含代表手语的 400x400 RGB 手势图像。它共有 37 个类,每个类有 70 张图像。我们将训练一个 CNN 模型来对这些手势进行分类。

由于数据集已经基于类在文件夹目录进行了分类,加载数据集的最简单方法是使用 keras.utils.image_dataset_from_directory。使用 directory 参数指定父目录路径,并使用 labels=‘inferred’ 自动加载基于文件夹名称的标签。使用 label_mode=‘categorical’ 可以将标签作为 one-hot 向量加载,这样我们加载数据就别的非常简单了。

 BATCH_SIZE = 64
 train_data = keras.utils.image_dataset_from_directory(
                   directory="../input/asl-dataset/asl_dataset",
                   labels= 'inferred',
                   label_mode='categorical',
                   color_mode='rgb',
                   batch_size=BATCH_SIZE,
                   seed=777,
                   shuffle=True,
                   image_size=(400, 400) )

现在就可以使用下面的函数将 tf.data.dataset 项拆分为 train-val-test 集。我们数使用 0.7–0.15–0.15 拆分规则。

 def split_tf_dataset(ds,  train_split=0.8, val_split=0.1, test_split=0.1, shuffle=True, seed=None, shuffle_size=10000):
     assert (train_split + test_split + val_split) == 1
     
     # get the dataset size (in batches)
     ds_size = len(ds)
     
     if shuffle:
         # Specify seed to always have the same split distribution between runs
         ds = ds.shuffle(shuffle_size, seed=seed)
     
     train_size = int(train_split * ds_size)
     val_size = int(val_split * ds_size)
     test_size = int(test_split * ds_size)
     
     train_ds = ds.take(train_size)                 # Take train_size number of batches
     val_ds = ds.skip(train_size).take(val_size)    # Ignore the first train_size batches and take the rest val_size batches
     test_ds = ds.skip(train_size).skip(val_size).take(test_size)
     
     return train_ds, val_ds, test_ds
 
 
 train_data, val_data, test_data = split_tf_dataset(train_data, 0.7, 0.15, 0.15, True, 777)
 
 print(f"Train dataset has {sum(1 for _ in train_data.unbatch())} elements")
 print(f"Val dataset has {sum(1 for _ in val_data.unbatch())} elements")
 print(f"Test dataset has {sum(1 for _ in test_data.unbatch())} elements")

数据集加载已完成。让我们进入下一部分

Keras Tuner 基础知识

在使用之前,先简单介绍一下 Keras-Tuner 的工作流程。

build()函数接收keras_tuner的Hyperparameter的对象,这个对象定义了模型体系结构和超参数搜索空间。

为了定义搜索空间,hp对象提供了4个方法。hp.Choice(), hp.Int(), hp.Float()和hp.Boolean()。hp.Choice()方法是最通用的,它接受一个由str、int、float或boolean值组成的列表,但所有值的类型必须相同。

 units = hp.Choice(name="neurons", values=[150, 200])
 
 units = hp.Int(name="neurons", min=100, max=200, step=10)
 
 dropout = hp.Int(name="dropout", min=0.0, max=0.3, step=0.05)
 shuffle = hp.Boolean("shuffle", default=False)

Keras-Tuner 提供 3 种不同的搜索策略,RandomSearch、贝叶斯优化和 HyperBand。对于所有Tuner都需要指定一个 HyperModel、一个要优化的指标和一个计算预期时间(轮次),以及一个可选的用于保存结果的目录。例如下面的示例

 tuner = keras_tuner.Hyperband(
                        hypermodel=MyHyperModel(),
                        objective = "val_accuracy", #准确率
                        max_epochs=50, #每个模型训练50轮
                        overwrite=True,  
                        directory='hyperband_search_dir', #保存目录
                        project_name='sign_language_cnn')

然后就可以使用命令启动超参数的搜索了。

 tuner.search(x=train_data, 
              max_trials=50,  
              validation_data=val_data,
              batch_size=BATCH_SIZE)

以上是Keras Tuner的基本工作流程,现在我们把这个流程应用到我们这个示例中

代码实现

首先,我们定义一个继承自 keras_tuner.HyperModel 的 HyperModel 类,并定义 build 和 fit 方法。

通过 build 方法,定义模型的架构并使用 hp 参数来设置超参数搜索空间。

fit 方法接受 hp 参数、将训练数据 x 传递给 keras model.fit() 方法的 *args 和 **kwargs。**kwargs 需要传递给 model.fit() 因为它包含模型保存的回调和可选的 tensorboard 等回调。

在 HyperModel 类中定义 fit() 方法是因为需要在训练过程中灵活地搜索参数,而不仅仅是在构建过程中。

 class MyHyperModel(keras_tuner.HyperModel) :
     def build(self, hp, classes=37) : 
         model = keras.Sequential()
         model.add(layers.Input( (400,400,3)))
         model.add(layers.Resizing(128, 128, interpolation='bilinear'))
         # Whether to include normalization layer
         if hp.Boolean("normalize"):
             model.add(layers.Normalization())
         
         drop_rate = hp.Float("drop_rate", min_value=0.05, max_value=0.25, step=0.10)
         # Number of Conv Layers is up to tuning
         for i in range( hp.Int("num_conv", min_value=7, max_value=8, step=1)) :
             # Tune hyperparams of each conv layer separately by using f"...{i}"
             model.add(layers.Conv2D(filters=hp.Int(name=f"filters_{i}", min_value=20, max_value=50, step=15),
                                     kernel_size= hp.Int(name=f"kernel_{i}", min_value=5, max_value=7, step=2),
                                     strides=1, padding='valid',
                                     activation=hp.Choice(name=f"conv_act_{i}", ["relu","leaky_relu", "sigmoid"] )))
             # Batch Norm and Dropout layers as hyperparameters to be searched
             if hp.Boolean("batch_norm"):
                 model.add(layers.BatchNormalization())
             if hp.Boolean("dropout"):
                 model.add(layers.Dropout(drop_rate))
 
         model.add(layers.Flatten())
         for i in range(hp.Int("num_dense", min_value=1, max_value=2, step=1)) :
             model.add(layers.Dense(units=hp.Choice("neurons", [150, 200]),
                                        activation=hp.Choice("mlp_activ", ['sigmoid', 'relu'])))
             if hp.Boolean("batch_norm"):
                     model.add(layers.BatchNormalization())
             if hp.Boolean("dropout"):
                     model.add(layers.Dropout(drop_rate))
         
         # Last layer
         model.add(layers.Dense(classes, activation='softmax'))
         
         # Picking an opimizer and a loss function
         model.compile(optimizer=hp.Choice('optim',['adam','adamax']),
                       loss=hp.Choice("loss",["categorical_crossentropy","kl_divergence"]),
                       metrics = ['accuracy'])
         
         # A way to optimize the learning rate while also trying different optimizers
         learning_rate = hp.Choice('lr', [ 0.03, 0.01, 0.003])
         K.set_value(model.optimizer.learning_rate, learning_rate)
         
         return model
     
     
     def fit(self, hp, model,x, *args, **kwargs) :
         
         return model.fit( x, 
                          *args,
                          shuffle=hp.Boolean("shuffle"),
                          **kwargs)

以上网络及参数仅作为示例。可以自定义网络和搜索空间,使其更适合你的应用。让我们详细解释以下代码:

在第 3-5 行中,构建 Keras 模型并添加一个调整大小的层。在第 7-8 行中,使用 hp.Boolean 来评估是否需要添加归一化层,在第 10 行中,为 dropout 定义了不同值。

第 12-17 动态地指定模型应该有多少卷积层,同时为每一层定义不同的超参数空间。将卷积层的数量设置为 7-8,并且在每一层中独立搜索最佳的核数量、内核大小和激活函数。这里是通过使用字符串 name=f”kernel_{i}” 中的索引 i 为循环中的每次迭代使用不同的 name 参数来做到的。这样就有了很大的灵活性,可以极大地扩展搜索空间,但是因为可能的组合可能会变得非常大,需要大量的计算能力。

在循环内使用 name=f”kernel_{i}” 可以为每一层上的每个参数定义不同的搜索空间。

在第 18-22 行中,搜索 conv 块内添加(或不添加)dropout 和批量归一化层。在 28-31 行也做了同样的事情。

在第 24-27 行中,我们添加了一个展平层,然后是可搜索数量的具有不同参数的全连接层,以在每个层中进行优化,类似于第 12-17 行。

在第 36-39 行,对模型进行了编译了,这里优化器也变为了一个可搜索的超参数。因为参数的类型限制所以不能直接传递 keras.optimizer 对象。所以这里将超参数搜索限制为 Keras 字符串别名,例如 keras.optimizers.Adam() -> ‘adam’ 。

如何调整学习率也并不简单。在第 41-43 行,我们以一种“hacky”的方式来做这件事。下面的代码可以更改优化器的超参数,例如学习率这是 keras Tuner 目前做不到的,我们只能手动完成

  lr = hp.Choice('lr', [0.03, 0.01, 0.003])
  K.set_value(model.optimizer.learning_rate, learning_rate)

在第 48-53 行,定义了模型类的 fit(self, hp, model,x, *args, **kwargs) 方法。将 hp 定义为参数这样可以在训练过程中调整超参数值。例如,我在每个 epoch 之前对使用了训练数据进行重新打乱,等等

在完成上述代码后,可以通过运行以下代码进行测试

 classes = 37
 hp = keras_tuner.HyperParameters()
 hypermodel = MyHyperModel()
 model = hypermodel.build(hp, classes)
 hypermodel.fit(hp, model, np.random.rand(BATCH_SIZE, 400, 400,3), np.random.rand(BATCH_SIZE, classes))

进行超参数搜索

这里我们使用了贝叶斯优化策略。它是 AutoML 中使用的最佳搜索方法之一。

传递一个模型对象,将目标设置为希望优化的指标(例如“val_accuracy”、“train_loss”),并使用 max_trials 参数和保存模型的路径定义计算预期轮次。

 tuner = keras_tuner.BayesianOptimization(
                         hypermodel=MyHyperModel(),
                         objective = "val_accuracy",
                         max_trials =10, #max candidates to test
                         overwrite=True,
                         directory='BO_search_dir',
                         project_name='sign_language_cnn')

使用下面的命令Keras-Tuner 就会开始工作了。

 tuner.search(x=train_data, epochs=10,
              validation_data=val_data)

搜索完成后,可以使用 tuner.results_summary(1) 访问结果。可以看到为每个超参数选择了哪个值,以及在训练期间获得的最佳模型的验证分数。

如果要自动提取和构建最佳的模型,请运行以下代码。

 best_hps = tuner.get_best_hyperparameters(1)
 h_model = MyHyperModel()
 model = h_model.build(best_hps[0])

如果您想提取多个模型可以更改 tuner.get_best_hyperparameters(1) 中的数字。

有了模型,我们可以在完整数据集和使用更多 epoch 上训练这个模型。还可以传递回调函数,例如早停、保存最佳模型和学习率调度等等。

 from tensorflow.keras.callbacks import EarlyStopping,ReduceLROnPlateau,ModelCheckpoint
 
 def get_callbacks(weights_file, patience, lr_factor):
   ''' Callbacks used for saving the best weights, early stopping and learning rate scheduling.'''
   return [
       # Only save the weights that correspond to the maximum validation accuracy.
       ModelCheckpoint(filepath= weights_file,
                       monitor="val_accuracy",
                       mode="max",
                       save_best_only=True, 
                       save_weights_only=True),
       # If val_loss doesn't improve for a number of epochs set with 'patience' var 
       # training will stop to avoid overfitting.    
       EarlyStopping(monitor="val_loss",
                     mode="min",
                     patience = patience,
                     verbose=1),
       # Learning rate is reduced by 'lr_factor' if val_loss stagnates
       # for a number of epochs set with 'patience/2' var.     
       ReduceLROnPlateau(monitor="val_loss", mode="min",
                         factor=lr_factor, min_lr=1e-6, patience=patience//2, verbose=1)]
                       
 history = model.fit(x=train_data, validation_data=val_data, epochs=100,
                     callbacks=get_callbacks('Net_weights.h5',
                                             patience=10,
                                             lr_factor=0.3))

这样就可以训练出完整的模型,训练完成后还可以绘制图表以进行检查并评估测试数据集,还有就是保存模型。

 model.load_weights(‘Net_weights.h5’)
 model.evaluate(test_data)
 model.save(‘Best_model’)

一些小技巧

  • 如果数据集非常大并且搜索时间过长,可以在搜索期间仅使用一小部分进行训练,例如 30%。这通常会在很短的时间内提供类似的结果。然后你再在整个集合上重新训练最好的模型。
  • 为了加快搜索过程的速度,可以减少训练周期数。虽然这样这可能会降低搜索优化的精度,因为这样倾向于早期表现更好的超参数会进一步进步,但是这样做是可以找到时间和结果精度之间的最佳平衡点。
  • 搜索过程中可能出现的一个问题是磁盘空间不足。因为tuner 会自动将所有模型保存在工程目录下,但表现不好的模型不会被动态删除,这将快速占用磁盘空间,尤其是在 Kaggle 或 Google Colab 上运行代码时。开发人员已将其标记为 Keras Tuners 中的增强功能,但还未解决,所以如果磁盘空间不足了,需要考虑限制搜索空间或将搜索拆分为多个较小的搜索。

总结

在本文中我们介绍了 Keras Tuner的使用。并且通过一个完整的项目实现了通过Keras Tuner自动搜索超参数的流程。与手动或网格搜索方法相比,KerasTuner 中实现的搜索策略允许更快、更轻松地进行微调。利用贝叶斯优化或 HyperBand 搜索等搜索方法不仅可以节省时间,还会会得到一个更好的模型。

https://avoid.overfit.cn/post/bbeec4bc93a64a928faabac7f238d7fa

作者:Poulinakis Kon

### 关于超参数的方法、工具及最佳实践 #### 方法概述 超参数是指那些在训练之前需要人为设定的参数,这些不同于模型内部通过训练得到的参数。常见的超参数包括但不限于学习率、批量大小(batch size)、正则化系数以及网络层数等[^1]。 #### 常见方法 一种有效的方式是在验证集上进行评估以找到最合适的超参数配置。此过程中,每次整一组新的超参数后,在验证集上的表现会用来衡量这组参数的好坏,从而避免直接利用测试集造成的结果偏差。 对于具体的优化策略而言,贝叶斯优化提供了一种基于概率建模的技术路径。该技术能够有效地探索可能的空间,并且借助获取函数(acquisition function)实现对未知区域的有效探测与已知良好解附近的精细化搜索之间的平衡。 针对特定类型的优化器比如Momentum优化器,则可以通过节诸如动量项这样的额外超参来改善其性能特性。这类优化器特别适用于具有大量特征的数据集或者复杂的神经架构之中[^2]。 另外还有像遗传算法(GA),这是一种模拟自然选择机制来进行寻的过程,可用于自动化地搜寻最超参数组合方案,进而提升整体系统的效能表现[^3]。 #### 工具介绍 为了简化上述流程中的工作负担,市面上存在多种专门用于辅助完成这项任务的软件包和服务平台: - **Hyperopt**: 支持随机搜索、TPE (Tree of Parzen Estimators) 和自定义空间定义等功能; - **Optuna**: 提供易于使用的API接口,支持分布式计算环境下的大规模实验管理; - **Keras Tuner**: 专门为TensorFlow/Keras框架定制开发的一款轻量化解决方案; - **AutoML platforms like Google Cloud AI Platform or AWS SageMaker**: 这些云服务平台提供了图形界面友好的方式让用户快速启动和监控多个试验实例。 #### 最佳实践经验分享 当着手开展任何一次完整的超参数项目时,建议遵循以下几个原则: - 明确目标:确定想要解决的具体问题是什么样的性质——分类?回归? - 数据准备充分:确保有足够的高质量数据可用作训练、验证和最终评测三个不同阶段。 - 合理范围界定:为每一个待选超参数设立合理的取值区间而不是盲目尝试过大跨度的变化。 - 记录保存详尽日志:记录每一次迭代后的结果变化情况以便后续分析总结规律。 - 平衡资源消耗考量:考虑到实际硬件条件限制合理规划时间成本开销。 ```python from sklearn.model_selection import train_test_split X_train, X_val, y_train, y_val = train_test_split(X, y, test_size=0.2) def objective_function(params): model = create_model_with_params(params) score = evaluate_on_validation_set(model, X_val, y_val) return {'loss': -score, 'status': STATUS_OK} ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值