点击上方“Deephub Imba”,关注公众号,好文章不错过 !
在本文中将介绍如何使用 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 的工作流程。
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’)
在本文中我们介绍了 Keras Tuner的使用。并且通过一个完整的项目实现了通过Keras Tuner自动搜索超参数的流程。与手动或网格搜索方法相比,KerasTuner 中实现的搜索策略允许更快、更轻松地进行微调。利用贝叶斯优化或 HyperBand 搜索等搜索方法不仅可以节省时间,还会会得到一个更好的模型。
最后 Keras Tuner官网:
https://keras-team.github.io/keras-tuner/
作者:Poulinakis Kon
MORE
kaggle比赛交流和组队
加我的微信,邀你进群
喜欢就关注一下吧:
点个 在看 你最好看!
本文分享自 DeepHub IMBA 微信公众号,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文参与 腾讯云自媒体同步曝光计划 ,欢迎热爱写作的你一起参与!