在本文中将介绍如何应用 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