使用贝叶斯优化调整深度神经网络

2023-02-05 12:05 428 阅读 ID:760
磐创AI
磐创AI

在前一篇文章中,我们介绍了一个使用Tensorflow和深度学习方法进行图像分类的案例研究。

尽管案例研究很少,但它展示了机器学习项目的每个阶段:清理、预处理、模型构建、训练和评估。但我们跳过了调优。

在本文中,我们将深入研究超参数优化。同样,我们将使用Tensorflow中包含的Fashion MNIST[1]数据集。

提醒一下,数据集包含60000个 训练集中的灰度图像和10,000 测试集中的图像。每个图片代表属于10个类别(“T恤/上衣”、“裤子”、“套头衫”等)之一的时尚项目。因此,我们有一个多类分类问题。

设置

我将简要介绍准备数据集的步骤。有关更多信息,请查看上一篇文章的第一部分:简而言之,步骤如下:

  1. 加载数据。
  2. 分为训练、验证和测试集。
  3. 规范化0–255到0–1范围内的像素值。
  4. one-hot目标变量。
#load data
(train_images, train_labels), (test_images, test_labels) = fashion_mnist.load_data()

# split into train, validation and test sets
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)

# 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 encode target variable
train_y = to_categorical(train_y)
val_y = to_categorical(val_y)
test_y = to_categorical(test_y)

概括来说,所有训练、验证和测试集的形状如下:

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库[2]:它将帮助我们轻松地调整神经网络的超参数。要安装它,请执行以下操作:

pip install keras-tuner

注意:Keras Tuner需要Python 3.6+和TensorFlow 2.0+

快速提醒一下,超参数调整是机器学习项目的基本部分。超参数有两种类型:

  1. 结构超参数:定义模型整体架构的超参数(例如,隐藏单元的数量、层数)
  2. 优化器超参数:影响训练速度和质量的参数(例如,优化器的学习速度和类型、批次大小、epoch数)

为什么调优很棘手?

为什么需要超参数调优库?我们不能尝试所有可能的组合,看看验证集上什么是最好的吗?

不幸的是,没有:

  • 深层神经网络需要大量时间来训练,甚至需要几天。
  • 如果你在云上训练大型模型(比如亚马逊Sagemaker),记住每次实验都要花钱。

因此,一种限制超参数搜索空间的剪枝策略是必要的。

贝叶斯优化

幸运的是,Keras调整器提供了贝叶斯优化调整器。贝叶斯优化调整器没有搜索每一个可能的组合,而是遵循一个迭代过程,随机选择前几个。然后,基于这些超参数的性能,贝叶斯调整器选择下一个可能的最佳。

因此,每个超参数的选择都取决于先前的尝试。根据历史选择下一组超参数并评估性能的迭代次数将持续,直到调整器找到最佳组合或用尽最大次数的尝试。我们可以用参数“max_trials”来配置它。

除了贝叶斯优化调整器之外,Keras调整器还提供了两个调整器:RandomSearch和Hyperband。我们将在本文末尾讨论它们。

回到我们的例子

接下来,我们将对网络应用超参数调整。在上一篇文章中,我们尝试了两种网络架构,标准多层感知器(MLP)和卷积神经网络(CNN)。

多层感知器(MLP)

但首先,让我们记住我们的基线MLP模型是什么:

model_mlp = Sequential()
model_mlp.add(Flatten(input_shape=(28, 28)))
model_mlp.add(Dense(350, activation='relu'))
model_mlp.add(Dense(10, activation='softmax'))
print(model_mlp.summary())
model_mlp.compile(optimizer="adam",loss='categorical_crossentropy')

调整过程需要两种主要方法:

  1. hp.Int:设置值为整数的超参数范围-例如,“Dense”层中的隐藏单位数:
model.add(Dense(units = hp.Int('dense-bot', min_value=50, max_value=350, step=50))

    2.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
model = Sequential()

model.add(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(Dense(units=hp.Int('dense_' + str(i), min_value=50, max_value=100, step=25), activation='relu'))
  model.add(Dropout(hp.Choice('dropout_'+ str(i), values=[0.0, 0.1, 0.2])))

model.add(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

注意第5行的for循环:我们让模型决定网络的深度!

最后,我们启动调整器。注意前面提到的max_trials参数。

model.compile(optimizer = hp_optimizer, loss='categorical_crossentropy', metrics=['accuracy'])

tuner_mlp = kt.tuners.BayesianOptimization(
    model,
    seed=random_seed,
    objective='val_loss',
    max_trials=30,
    directory='.',
    project_name='tuning-mlp')
tuner_mlp.search(train_x, train_y, epochs=50, batch_size=32, validation_data=(dev_x, dev_y), callbacks=callback)

这将打印:

该过程耗尽了迭代次数,花费了约1小时完成。我们还可以使用以下命令打印模型的最优超参数:

best_mlp_hyperparameters = tuner_mlp.get_best_hyperparameters(1)[0]
print("Best Hyper-parameters")
best_mlp_hyperparameters.values

就这样!我们现在可以使用最优超参数重新训练我们的模型:

model_mlp = Sequential()

model_mlp.add(Dense(best_mlp_hyperparameters['dense-bot'], input_shape=(784,), activation='relu'))

for i in range(best_mlp_hyperparameters['num_dense_layers']):
  model_mlp.add(Dense(units=best_mlp_hyperparameters['dense_' +str(i)], activation='relu'))
  model_mlp.add(Dropout(rate=best_mlp_hyperparameters['dropout_' +str(i)]))

model_mlp.add(Dense(10,activation="softmax"))

model_mlp.compile(optimizer=best_mlp_hyperparameters['Optimizer'], loss='categorical_crossentropy',metrics=['accuracy'])
history_mlp= model_mlp.fit(train_x, train_y, epochs=100, batch_size=32, validation_data=(dev_x, dev_y), callbacks=callback)

或者,我们可以用更少的详细信息重新训练我们的模型:

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)
# Test accuracy: 0.8823

与基线的模型测试精度相比:

  • 基线MLP模型:86.6%
  • 最佳MLP模型:88.2%

事实上,我们观察到测试准确度相差约3%!

卷积神经网络(CNN)

同样,我们将遵循相同的程序。使用CNN,我们可以测试更多的参数。

首先,这是我们的基线模型:

model_cnn = Sequential()
model_cnn.add(Conv2D(32, (3, 3), activation='relu', input_shape=(28, 28, 1)))
model_cnn.add(MaxPooling2D((2, 2)))
model_cnn.add(Flatten())
model_cnn.add(Dense(100, activation='relu'))
model_cnn.add(Dense(10, activation='softmax'))
model_cnn.compile(optimizer="adam", loss='categorical_crossentropy', metrics=['accuracy'])

基线模型仅包含一组滤波和池层。对于我们的调优,我们将测试以下内容:

  • 卷积、MaxPooling和Dropout层的“块”数
  • 每个块中Conv层的滤波器尺寸:32、64
  • Conv层上的有效或相同填充
  • 最终附加层的隐藏层大小:25–150,乘以25
  • 优化器:SGD(nesterov=真,动量=0.9)或Adam
  • 学习率:0.01,0.001
model = Sequential()

model = 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(Conv2D(hp_filters, (3, 3), padding=hp_padding, activation='relu', kernel_initializer='he_uniform', input_shape=(28, 28, 1)))
    model.add(MaxPooling2D((2, 2)))
    model.add(Dropout(hp.Choice('dropout_'+ str(i), values=[0.0, 0.1, 0.2])))

model.add(Flatten())

hp_units = hp.Int('units', min_value=25, max_value=150, step=25)
model.add(Dense(hp_units, activation='relu', kernel_initializer='he_uniform'))

model.add(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

像以前一样,我们让网络决定其深度。

现在,我们准备好调用贝叶斯调整器了。最大迭代次数设置为100:

model.compile( optimizer=hp_optimizer,loss='categorical_crossentropy', metrics=['accuracy'])

tuner_cnn = kt.tuners.BayesianOptimization(
    model,
    objective='val_loss',
    max_trials=100,
    directory='.',
    project_name='tuning-cnn')

这将打印:

最好的超参数是:

最后,我们使用最佳超参数训练我们的CNN模型:

model_cnn = Sequential()

model_cnn.add(Input(shape=(28, 28, 1)))

for i in range(best_cnn_hyperparameters['num_blocks']):
  hp_padding=best_cnn_hyperparameters['padding_'+ str(i)]
  hp_filters=best_cnn_hyperparameters['filters_'+ str(i)]

  model_cnn.add(Conv2D(hp_filters, (3, 3), padding=hp_padding, activation='relu', kernel_initializer='he_uniform', input_shape=(28, 28, 1)))
  model_cnn.add(MaxPooling2D((2, 2)))
  model_cnn.add(Dropout(best_cnn_hyperparameters['dropout_'+ str(i)]))

model_cnn.add(Flatten())
model_cnn.add(Dense(best_cnn_hyperparameters['units'], activation='relu', kernel_initializer='he_uniform'))

model_cnn.add(Dense(10,activation="softmax"))

model_cnn.compile(optimizer=best_cnn_hyperparameters['Optimizer'], 
                                                 loss='categorical_crossentropy', 
                                                 metrics=['accuracy'])
print(model_cnn.summary())

history_cnn= model_cnn.fit(train_x, train_y, epochs=50, batch_size=32, validation_data=(dev_x, dev_y), callbacks=callback)

并检查测试集的准确性:

cnn_test_loss, cnn_test_acc = model_cnn.evaluate(test_x,  test_y, verbose=2)
print('\nTest accuracy:', cnn_test_acc)
# Test accuracy: 0.92

与基线的CNN模型测试精度相比(来自我们上一篇文章):

  • 基线CNN模型:90.8%
  • 最佳CNN模型:92%

同样,我们看到了优化模型的性能提高!

除了准确性之外,我们可以确认调整器的工作做得很好,原因如下:

  • 调整器在每种情况下都选择了一个非零的Dropout值,尽管我们也为调整器提供了零Dropout。这是意料之中的,因为Dropout是一种减少过拟合的机制。
  • 有趣的是,最好的CNN架构是标准管道,其中每层中的滤波器数量逐渐增加。这是意料之中的,因为随着后续层中的计算向前推进,模式变得更加复杂。因此,有更多的模式组合需要更多的滤波器才能被捕获。

结束

毫无疑问,Keras Tuner是使用Tensorflow优化深度神经网络的通用工具。

最明显的选择是贝叶斯优化调整器。但是,还有两个选项可以使用:

  • 随机搜索:这种类型的调整器通过随机选择几个超参数来避免探索超参数的整个搜索空间。但是,它不能保证此调整器会找到最佳调整器。
  • Hyperband:这个调整器选择一些超参数的随机组合,并使用它们来训练模型,只用于几个epoch。然后,调整器使用这些超参数来训练模型,直到所有的epoch都用完,并从中选择最好的。

感谢阅读!

参考引用

  1. Fashion MNIST dataset by Zalando, https://www.kaggle.com/datasets/zalando-research/fashionmnist, MIT Licence (MIT) Copyright © [2017]
  2. Keras Tuner, https://keras.io/keras_tuner/
免责声明:作者保留权利,不代表本站立场。如想了解更多和作者有关的信息可以查看页面右侧作者信息卡片。
反馈
to-top--btn