Keras-Tuner:适用于TensorFlow 2.0和keras的超参数调优器 (个人翻译版)
欢迎在知乎阅读此文章
随着炼丹(深度学习)技术的日益发展,在工业界和学术界都对上好的仙丹(深度学习模型)趋之若鹜,纷纷不惜招收耗费大量钱财去研究炼丹;同时大量招纳炼丹大师以及炼丹药童。目前能使用的炼丹工具也越来越多,根据炼丹使用的丹炉(使用的框架),可以分为如下的类别:
- 以美利坚谷歌的tensorflow和脸书的Pytorch等为代表的丹炉,这也目前炼丹界流传最为广泛的炼丹工具;此类丹炉理论上能炼制世上万物,但需要炼丹师自己仔细搭建丹炉(模型结构),配置丹方(调参)以及控制稀有而昂贵的真火(GPU)进行炼制(训练)。根据需要炼制的灵材和灵火纯度差异,少则几刻钟;多则需七七四十九天。炼制时需步步谨慎,若有半点偏差,小则丹药不纯,药效不佳(underfitting);大则前功尽弃(内存爆炸,停电)或走火入魔(overfitting)。如要称为江湖上人人敬仰的炼丹大师,熟悉此类丹炉为必经之路!
- 某些炼丹师不懈使用谷歌和脸书的丹炉,他们根据独特的炼丹需求,使用自己铸造的丹炉,例如整个丹炉全采用纯金(C++)打造。虽然造炉辛苦,炉子每一个部分可由自己亲手锻造;但能极大地提高炼丹效率,可谓炼丹界“明教”!
- 以AutoML和Autokeras等为代表的全自动炼丹炉,这是为了让非专业炼丹的人也能够轻松的炼制出较好的仙丹。这些丹炉里已有一些炼丹大师已经调制好的丹方和炼制秘籍。此类方法优点明显,搭建容易,能快速开始进行炼丹;缺点是目前的丹方(模型)数量有限,同时能炼制的灵材仅限于灵草(图像)和宝石(文本),而目前无法对千奇百怪的灵兽(Graph data)进行炼制。
- 以NNI (Neural Network Intelligence)和keras-tuner为代表的半自动炼丹炉,可以看做是介于全自动炼丹炉和全手动丹炉之间的工具。此类工具仍需要炼丹者自己搭建丹炉,但能自动进行配置丹方(超差调优),本人认为这是炼丹过程中最耗时的步骤;得到最好的配方后就能更好的炼制的仙丹。
以上背景内容受启发于李沐大神的炼丹文 ,而本文主要介绍的keras-tuner就是一种半自动炼丹炉,主要用于超参调优,免去调参之苦。下面主要内容翻译自keras-tuner的官方文档,同时也补充了些个人注释。在查看本文内容时建议先熟悉一下keras的语法,简单容易上手。
keras-tuner的环境要求:
- Python 3.6
- TensorFlow 2.0
安装最新版本的keras-tuner:
pip install -U keras-tuner
或者直接从源(source)安装:
git clone https://github.com/keras-team/keras-tuner.git
cd keras-tuner
pip install .
下面介绍如何使用随机搜索(random search)为单层全连接神经网络(dense neural network)执行超参数调优。
首选定义一个model-building函数,通过这个函数中的参数hp,你可以进行超参数的采样,例如:
hp.Int('units', min_value=32, max_value=512, step=32) #该句定义了一个范围内的整数
该model-building函数会返回一个构建好的模型(compiled model,指整体模型结构已经搭好,包括损失函数的定义等等,但模型还未经过训练):
from tensorflow import keras
from tensorflow.keras import layers
from kerastuner.tuners import RandomSearch
def build_model(hp):
model = keras.Sequential()
model.add(layers.Dense(units=hp.Int('units',
min_value=32,
max_value=512,
step=32),
activation='relu')) #定义了一层全连接层
model.add(layers.Dense(10, activation='softmax')) #定义了用于输出的softmax层
model.compile(
optimizer=keras.optimizers.Adam(
hp.Choice('learning_rate',
values=[1e-2, 1e-3, 1e-4])),
loss='sparse_categorical_crossentropy',
metrics=['accuracy'])
return model
接下来举例说明如何定义一个tuner(调参器)。首先应该指定model-building函数,需要要优化的目标的名称(其中优化目标是最小化还是最大化是根据内置metrics自动推断出来的),用于测试参数的试验次数 (max_trials
),每一次试验中需要build和fit(拟合)的模型数量(executions_per_trial
)。
tuner = RandomSearch(
build_model,
objective='val_accuracy', #优化目标
max_trials=5,
executions_per_trial=3,
directory='my_dir',
project_name='helloworld')
目前可用的tuners有RandomSearch和[Hyperband](https://openreview.net/pdf?id=ry18Ww5ee)
Note: 每次试验中执行多次的目的是减少结果的方差(variance),因此能够更准确地评估模型的性能。但如果希望更快地获得结果,可以设置executions_per_trial=1(只为每个模型配置进行单轮训练)。
可以通过以下的语句输出搜索空间的总结(a summary of the search space):
tuner.search_space_summary()
然后,开始搜索最佳的超参数配置。调用tuner.search与keras中的model.fit()具有相同的语法(signature):
tuner.search(x, # 训练样本特征
y, # 训练样本标签
epochs=5,
validation_data=(val_x, val_y)) #定义验证集
下面是在tuner.search中发生的过程: 通过调用model-building函数迭代地构建模型,该函数使用hp对象在超参数空间(search space)中进行搜寻(track)。定义的tuner将逐步搜寻超参空间,同时记录下每个配置下的评估指标(metrics)。
但超参搜索结束时,可以通过以下的语句来取回最佳模型:
models = tuner.get_best_models(num_models=2)
或者输出模型结果的总结(a summary of the results):
tuner.results_summary()
还能在文件夹 my_dir/helloworld 中查看本次搜索的详细的日志(logs)和检查点(checkpoints)等等。
下面使用一个for循环,用于创建了一个可调的层数(a tunable number of layers),其中这些层本身包含一个可调参数unit。
def build_model(hp):
model = keras.Sequential() # a plain stack of layers
for i in range(hp.Int('num_layers', 2, 20)):
model.add(layers.Dense(units=hp.Int('units_' + str(i),
min_value=32,
max_value=512,
step=32),
activation='relu'))
model.add(layers.Dense(10, activation='softmax'))
model.compile(
optimizer=keras.optimizers.Adam(
hp.Choice('learning_rate', [1e-2, 1e-3, 1e-4])),
loss='sparse_categorical_crossentropy',
metrics=['accuracy'])
return model
此方法可以推广到任何层级的具有相互依赖的参数(any level of parameter interdependency),包括递归。注意所有所有参数命名都应该是唯一的(在上述的例子中,在第i次循环里程序将内部参数命名为 'units_' + str(i) )。
此方法使得共享和重用HyperModel变得容易。HyperModel子类只需要实现一个build(self, hp)方法:
from kerastuner import HyperModel
class MyHyperModel(HyperModel):
def __init__(self, classes):
self.classes = classes #分类数
def build(self, hp):
model = keras.Sequential()
model.add(layers.Dense(units=hp.Int('units',
min_value=32,
max_value=512,
step=32),
activation='relu'))
model.add(layers.Dense(self.classes, activation='softmax'))
model.compile(
optimizer=keras.optimizers.Adam(
hp.Choice('learning_rate',
values=[1e-2, 1e-3, 1e-4])),
loss='sparse_categorical_crossentropy',
metrics=['accuracy'])
return model
hypermodel = MyHyperModel(classes=10)
tuner = RandomSearch(
hypermodel,
objective='val_accuracy',
max_trials=10,
directory='my_dir',
project_name='helloworld')
tuner.search(x, y,
epochs=5,
validation_data=(val_x, val_y))
这些applications是属于能够立即使用(ready-to-use)的计算机视觉超模型,他们都使用 loss="categorical_crossentropy"
和 metrics=["accuracy"]
预先构建完成了。
from kerastuner.applications import HyperResNet
from kerastuner.tuners import Hyperband
hypermodel = HyperResNet(input_shape=(128, 128, 3), classes=10) #直接使用
tuner = Hyperband(
hypermodel,
objective='val_accuracy',
max_epochs=40,
directory='my_dir',
project_name='helloworld')
tuner.search(x, y,
validation_data=(val_x, val_y))
如果您已经有一个现有的hypermodel,并且希望只搜索指定参数(如学习速率),你可以通过将hyperparameters参数传给tuner constructor,以及设置tune_new_entries=False 来指定那些没有在hyperparameters中列出的参数不应该调优。对于这些参数,将使用默认值。
from kerastuner import HyperParameters
hypermodel = HyperXception(input_shape=(128, 128, 3), classes=10)
hp = HyperParameters()
# This will override the `learning_rate` parameter with your
# own selection of choices
hp.Choice('learning_rate', values=[1e-2, 1e-3, 1e-4])
tuner = Hyperband(
hypermodel,
hyperparameters=hp,
# `tune_new_entries=False` prevents unlisted parameters from being tuned
tune_new_entries=False, # 只会对hp中的超参数调优
objective='val_accuracy',
max_epochs=40,
directory='my_dir',
project_name='helloworld')
tuner.search(x, y,
validation_data=(val_x, val_y))
无论何时在model-building函数或hypermodel的build方法中申明( register )了一个超参数,都可以指定该超参数的默认值:
hp.Int('units',
min_value=32,
max_value=512,
step=32,
default=128)
如果你没有手动指定,超参数仍然有一个系统默认值(对于int变量,默认值为min_value)。
如果你想做与上述相反的事情——在一个hypermodel中,将所有可用参数调优,但只有一个参数除外(例如学习速率)。在hyperparameters参数中,设置一个Fixed项(或者任意数量的Fixed项),同时设置tune_new_entries=True。
hypermodel = HyperXception(input_shape=(128, 128, 3), classes=10)
hp = HyperParameters()
hp.Fixed('learning_rate', value=1e-4)
tuner = Hyperband(
hypermodel,
hyperparameters=hp,
tune_new_entries=True, #除了将learning_rate设定为固定值,模型中其它的所有参数都参与调优
objective='val_accuracy',
max_epochs=40,
directory='my_dir',
project_name='helloworld')
tuner.search(x, y,
validation_data=(val_x, val_y))
如果您有一个hypermodel,但希望更改现有的优化器(optimizer)、损失函数(loss)或评估指标(metrics),那么可以通过以下的操作将这些参数传递给tuner constructor来实现:
hypermodel = HyperXception(input_shape=(128, 128, 3), classes=10)
tuner = Hyperband(
hypermodel,
optimizer=keras.optimizers.Adam(1e-3),
loss='mse',
metrics=[keras.metrics.Precision(name='precision'),
keras.metrics.Recall(name='recall')],
# 覆盖了原来已经构建好的hypermodel中的optimizer,loss和metrics。
objective='val_precision',
max_epochs=40,
directory='my_dir',
project_name='helloworld')
tuner.search(x, y,
validation_data=(val_x, val_y))
如果您的论文中使用到了 Keras Tuner,可以引用如下(BibTex格式):
@misc{omalley2019kerastuner,
title = {Keras {Tuner}},
author = {O'Malley, Tom and Bursztein, Elie and Long, James and Chollet, Fran\c{c}ois and Jin, Haifeng and Invernizzi, Luca and others},
year = 2019,
howpublished = {\url{https://github.com/keras-team/keras-tuner}}
}
两年前接触到深度学习时,就纠结是学tensorflow还是Pytorch时。当时想能够快速上手,同时无意看到了Keras,就被其简洁明了的语法所吸引,有种第一次使用Python的感觉,也符合Keras宣传的标语Deep learning for humans
(暗示其他框架不是人类用的,手动滑稽)。在2018年,Keras正式被Google收入麾下,作为tensorflow 2.x 的官方API (说明我的当年眼光还可以~),也从原来的多后端支持变为逐渐只支持tensorflow了。祝Keras这个项目能越来越好!
最后,此文是从官方文档翻译而来,由于个人水平有限,错误在所难免,欢迎指正! 有些单词总感觉翻译不太准确,最好还是阅读官方英文文档 。向科研大佬们致敬!