使用Keras Tuner进行超参数自动调优
本文将使用 Tensorflow 中包含的 Fashion MNIST 数据集。该数据集在训练集中包含 60,000 张灰度图像,在测试集中包含 10,000 张图像。 每张图片代表属于 10 个类别之一的单品(“T 恤/上衣”、“裤子”、“套头衫”等)。 因此这是一个多类分类问题。
首先我们需要安装keras_tuner,它将帮助我们轻松调整神经网络的超参数:
pip install keras-tuner
- 加载数据。
- 分为训练集、验证集和测试集。
- 将像素值从 0–255 标准化到 0–1 范围。
- One-hot 编码目标变量。
import tensorflow as tf
from tensorflow import keras
import keras_tuner as kt
from sklearn.model_selection import train_test_split
- 加载数据
(train_images, train_labels), (test_images, test_labels) = keras.datasets.fashion_mnist.load_data()
- 分为训练集、验证集和测试集。
train_x, val_x, train_y, val_y = train_test_split(train_images, train_labels, stratify=train_labels, random_state=48, test_size=0.05)
(test_x, test_y)=(test_images, test_labels)
- 将像素值从 0–255 标准化到 0–1 范围。
# normalize pixels to range 0-1
train_x = train_x / 255.0
val_x = val_x / 255.0
test_x = test_x / 255.0
- One-hot 编码目标变量。
train_y=tf.one_hot(train_y,10)
val_y=tf.one_hot(val_y,10)
test_y=tf.one_hot(test_y,10)
参看训练集、验证集、测试集的形状
print(train_x.shape) #(57000, 28, 28)
print(train_y.shape) #(57000, 10)
print(val_x.shape) #(3000, 28, 28)
print(val_y.shape) #(3000, 10)
print(test_x.shape) #(10000, 28, 28)
print(test_y.shape) #(10000, 10)
超参数调整是机器学习项目的基础部分。 有两种类型的超参数:
- 结构超参数:定义模型的整体架构(例如隐藏单元的数量、层数)
- 优化器超参数:影响训练速度和质量的参数(例如学习率和优化器类型、批量大小、轮次数等)
为什么需要超参数调优库? 我们不能尝试所有可能的组合,看看验证集上什么是最好的吗?
这肯定是不行的因为深度神经网络需要大量时间来训练,甚至几天。 如果在云服务器上训练大型模型,那么每个实验实验都需要花很多的钱。
因此,需要一种限制超参数搜索空间的剪枝策略。
keras_tuner提供了贝叶斯优化器。 它搜索每个可能的组合,而是随机选择前几个。 然后根据这些超参数的性能,选择下一个可能的最佳值。因此每个超参数的选择都取决于之前的尝试。 根据历史记录选择下一组超参数并评估性能,直到找到最佳组合或到达最大试验次数。 我们可以使用参数“max_trials”来配置它。
除了贝叶斯优化器之外,keras-tuner还提供了另外两个常见的方法:RandomSearch 和 Hyperband。 我们将在本文末尾讨论它们。
接下来就是对我们的网络应用超参数调整。我们尝试两种网络架构,标准多层感知器(MLP)和卷积神经网络(CNN)。
1 MLP
调优过程需要两种主要方法:
hp.Int()
:设置超参数的范围,其值为整数 - 例如,密集层中隐藏单元的数量:
model.add(Dense(units = hp.Int('dense-bot', min_value=50, max_value=350, step=50))
hp.Choice():为超参数提供一组值——例如,Adam 或 SGD 作为最佳优化器?
hp_optimizer=hp.Choice('Optimizer', values=['Adam', 'SGD'])
在我们的 MLP 示例中,我们测试了以下超参数:
隐藏层数:1-3
第一密集层大小:50–350
第二和第三密集层大小:50–350
Dropout:0、0.1、0.2
优化器:SGD(nesterov=True,momentum=0.9) 或 Adam
学习率:0.1、0.01、0.001
def build_model_MLP(hp):
model = keras.Sequential()
model.add(keras.layers.Flatten(input_shape=(28, 28)))
model.add(keras.layers.Dense(units = hp.Int('dense-bot', min_value=50, max_value=350, step=50), input_shape=(784,), activation='relu'))
for i in range(hp.Int('num_dense_layers', 1, 2)):
model.add(keras.layers.Dense(units=hp.Int('dense_' + str(i), min_value=50, max_value=100, step=25), activation='relu'))
model.add(keras.layers.Dropout(hp.Choice('dropout_'+ str(i), values=[0.0, 0.1, 0.2])))
model.add(keras.layers.Dense(10,activation="softmax"))
hp_optimizer=hp.Choice('Optimizer', values=['Adam', 'SGD'])
if hp_optimizer == 'Adam':
hp_learning_rate = hp.Choice('learning_rate', values=[1e-1, 1e-2, 1e-3])
elif hp_optimizer == 'SGD':
hp_learning_rate = hp.Choice('learning_rate', values=[1e-1, 1e-2, 1e-3])
nesterov=True
momentum=0.9
model.compile(optimizer = hp_optimizer, loss='categorical_crossentropy', metrics=['accuracy'])
return model
这里需要注意第 5 行的 for 循环:让模型决定网络的深度!
最后,就是运行了.
tuner = kt.Hyperband(build_model_MLP,
objective='val_loss', # 优化的目标,验证集accuracy
max_epochs=30, # 最大迭代次数
factor=3,
directory='my_dir_MLP', # my_dir/intro_to_kt目录包含超参数搜索期间运行的详细日志和checkpoints
project_name='intro_to_kt_MLP')
stop_early = tf.keras.callbacks.EarlyStopping(monitor='val_loss', patience=5)
tuner.search(train_x, train_y, epochs=10, validation_data=(val_x, val_y),
callbacks=[stop_early])
运行的过程是这样的
运行完之后是这样的:
们还可以使用以下命令打印模型的最佳超参数:
best_hps=tuner.get_best_hyperparameters(num_trials=1)[0]
print("Best Hyper-parameters")
best_hps.values
现在我们可以使用最优超参数重新训练我们的模型:
model_mlp = keras.Sequential()
model_mlp.add(keras.layers.Flatten(input_shape=(28, 28)))
model_mlp.add(keras.layers.Dense(best_hps['dense-bot'], input_shape=(784,), activation='relu'))
for i in range(best_hps['num_dense_layers']):
model_mlp.add(keras.layers.Dense(units=best_hps['dense_' +str(i)], activation='relu'))
model_mlp.add(keras.layers.Dropout(rate=best_hps['dropout_' +str(i)]))
model_mlp.add(keras.layers.Dense(10,activation="softmax"))
model_mlp.summary()
或者,我们可以用这些参数重新训练我们的模型:
model_mlp=tuner_mlp.hypermodel.build(best_mlp_hyperparameters)
history_mlp=model_mlp.fit(train_x, train_y, epochs=100, batch_size=32,
validation_data=(dev_x, dev_y), callbacks=callback)
然后测试准确率
mlp_test_loss, mlp_test_acc = model_mlp.evaluate(test_x, test_y, verbose=2)
print('\nTest accuracy:', mlp_test_acc)
然后测试集上的准确率可以达到0.8921999931335449
然后我们换一个模型试试:
def build_model_MLP(hp):
model = keras.Sequential()
model.add(keras.layers.Flatten(input_shape=(28, 28)))
model.add(keras.layers.Dense(units = hp.Int('dense-bot', min_value=50, max_value=350, step=50), input_shape=(784,), activation='relu'))
model.add(keras.layers.Dropout(hp.Choice('dropout', values=[0.0, 0.1, 0.2])))
for i in range(hp.Int('num_dense_layers', 1, 2 )):
model.add(keras.layers.Dense(units=hp.Int('dense_' + str(i), min_value=50, max_value=100, step=25), activation='relu'))
model.add(keras.layers.Dropout(hp.Choice('dropout_'+ str(i), values=[0.0, 0.1, 0.2])))
model.add(keras.layers.Dense(10,activation="softmax"))
hp_optimizer=hp.Choice('Optimizer', values=['Adam', 'SGD'])
if hp_optimizer == 'Adam':
hp_learning_rate = hp.Choice('learning_rate', values=[1e-1, 1e-2, 1e-3])
elif hp_optimizer == 'SGD':
hp_learning_rate = hp.Choice('learning_rate', values=[1e-1, 1e-2, 1e-3])
nesterov=True
momentum=0.9
model.compile(optimizer = hp_optimizer, loss='categorical_crossentropy', metrics=['accuracy'])
return model
tuner = kt.Hyperband(build_model_MLP,
objective='val_loss', # 优化的目标,验证集accuracy
max_epochs=30, # 最大迭代次数
factor=3,#有人知道这个有啥作用吗???
directory='my_dir_MLP_2', # my_dir/intro_to_kt目录包含超参数搜索期间运行的详细日志和checkpoints
project_name='intro_to_kt_MLP_2')
stop_early = tf.keras.callbacks.EarlyStopping(monitor='val_loss', patience=5)
tuner.search(train_x, train_y, epochs=10, validation_data=(val_x, val_y),
callbacks=[stop_early])
训练完之后:
best_hps=tuner.get_best_hyperparameters(num_trials=1)[0]
print("Best Hyper-parameters")
best_hps.values
查看并加载模型
model = tuner.hypermodel.build(best_hps)
model.summary()
history_mlp=model.fit(train_x, train_y, epochs=50, batch_size=32, validation_data=(val_x, val_y))
用这个模型准确率可以提高到0.8949999809265137
test_loss, test_acc = model.evaluate(test_x, test_y, verbose=2)
print('\nTest accuracy:', test_acc)
与基线的模型测试精度相比:
最佳 MLP 模型:89.4 %。
下面我们使用相同的流程,将MLP改为CNN,这样可以测试更多参数。
2 CNN
于调优,我们将测试以下内容:
- 卷积、MaxPooling 和 Dropout 层的“块”数
- 每个块中 Conv 层的过滤器大小:32、64
- 转换层上的有效或相同填充
- 最后一个额外层的隐藏层大小:25-150,乘以 25
- 优化器:SGD(nesterov=True,动量=0.9)或 Adam
- 学习率:0.01、0.001
def build_model_CNN(hp):
model = keras.Sequential()
# model.add(Input(shape=(28, 28, 1)))
for i in range(hp.Int('num_blocks', 1, 2)):
hp_padding=hp.Choice('padding_'+ str(i), values=['valid', 'same'])
hp_filters=hp.Choice('filters_'+ str(i), values=[32, 64])
model.add(keras.layers.Conv2D(hp_filters, (3, 3), padding=hp_padding, activation='relu', kernel_initializer='he_uniform', input_shape=(28, 28, 1)))
model.add(keras.layers.MaxPooling2D((2, 2)))
model.add(keras.layers.Dropout(hp.Choice('dropout_'+ str(i), values=[0.0, 0.1, 0.2])))
model.add(keras.layers.Flatten())
hp_units = hp.Int('units', min_value=25, max_value=150, step=25)
model.add(keras.layers.Dense(hp_units, activation='relu', kernel_initializer='he_uniform'))
model.add(keras.layers.Dense(10,activation="softmax"))
hp_learning_rate = hp.Choice('learning_rate', values=[1e-2, 1e-3])
hp_optimizer=hp.Choice('Optimizer', values=['Adam', 'SGD'])
if hp_optimizer == 'Adam':
hp_learning_rate = hp.Choice('learning_rate', values=[1e-2, 1e-3])
elif hp_optimizer == 'SGD':
hp_learning_rate = hp.Choice('learning_rate', values=[1e-2, 1e-3])
nesterov=True
momentum=0.9
model.compile( optimizer=hp_optimizer,loss='categorical_crossentropy', metrics=['accuracy'])
return model
tuner = kt.Hyperband(build_model_CNN,
objective='val_loss', # 优化的目标,验证集accuracy
max_epochs=30, # 最大迭代次数
factor=3,
directory='my_dir_CNN_2', # 目录包含超参数搜索期间运行的详细日志和checkpoints
project_name='intro_to_kt_CNN_2')
stop_early = tf.keras.callbacks.EarlyStopping(monitor='val_loss', patience=5)
tuner.search(train_x, train_y, epochs=10, validation_data=(val_x, val_y),
callbacks=[stop_early])
训练完之后就是这样的
best_hps=tuner.get_best_hyperparameters(num_trials=1)[0]
best_hps.values
查看并训练模型
model = tuner.hypermodel.build(best_hps)
print(model.summary())
history = model.fit(train_x, train_y, epochs=50, validation_split=0.2)
查看最好的epoch
val_acc_per_epoch = history.history['val_accuracy']
best_epoch = val_acc_per_epoch.index(max(val_acc_per_epoch)) + 1
print('Best epoch: %d' % (best_epoch,))
重新实例化超模型,并用上面的最佳历元数对其进行训练。
#重新实例化超模型,并用上面的最佳历元数对其进行训练。
hypermodel = tuner.hypermodel.build(best_hps)
# 重新选练model
hypermodel.fit(train_x, train_y, epochs=best_epoch)
在测试数据上评估超模型
#在测试数据上评估超模型
eval_result = hypermodel.evaluate(test_x, test_y)
print("[test loss, test accuracy]:", eval_result)
然后准确率有92%
在每种情况下都选择了一个非零的 Dropout 值,即使我们也提供了零 Dropout。 这是意料之中的,因为 Dropout 是一种减少过拟合的机制。有趣的是,最好的 CNN 架构是标准CNN,其中过滤器的数量在每一层中逐渐增加。 这是意料之中的,因为随着后续层的增加,模式变得更加复杂(这也是我们在学习各种模型和论文时被证明的结果)需要更多的过滤器才能捕获这些模式组合。
以上例子也说明Keras Tuner 是使用 Tensorflow 优化深度神经网络的很好用的工具。
我们上面也说了本文选择是贝叶斯优化器。 但是还有两个其他的选项:
RandomSearch:随机选择其中的一些来避免探索超参数的整个搜索空间。 但是,它不能保证会找到最佳超参数
Hyperband:选择一些超参数的随机组合,并仅使用它们来训练模型几个 epoch。 然后使用这些超参数来训练模型,直到用尽所有 epoch 并从中选择最好的。