关于深度学习:使用Keras-Tuner进行自动超参数调优的实用教程

50次阅读

共计 8486 个字符,预计需要花费 22 分钟才能阅读完成。

在本文中将介绍如何应用 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 上下载。它蕴含代表手语的 400×400 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

正文完
 0