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