「导语」 当我们创建了 Keras 模型并开始进行训练时,一般都会指定一些超参数(如学习率)的值来对训练的过程进行调控,而这些超参数的取值会对模型训练的结果产生很大的影响,因此在机器学习工作流程中一项十分重要的步骤就是要确定模型超参数的最佳取值,亦即超参数调优。在 TensorFlow 中,我们可以使用 HParams 插件很方便地完成这一调优过程。
机器学习和深度学习的模型中往往包含成千上万个参数,其中有的参数可以通过模型训练并利用反向传播算法来进行优化,比如模型中的权重 (weights
) 和偏差 (bias
) 等,我们称之为参数 (parameters
)。还有一些参数不能通过模型训练来进行优化,比如学习率 (learning rate
) 、深度神经网络中的隐含层 (hidden layers
) 的个数以及隐含层的神经元 (hidden units
) 个数等,我们称之为超参数 (hyper parameters
)。
超参数是用来调节整个模型的训练过程的,它们只是配置变量,并不直接参与到训练的过程中,因此需要我们不断调整与尝试,以使得模型效果达到最优。需要注意,在一次训练迭代的过程中,参数是不断进行更新的,而超参数是恒定不变的。
一般而言,我们会根据模型在训练集和验证集上的损失大小,以及预定义的评估指标来选择最优超参数组合,并最终将该组超参数应用于正式训练以及线上的 Serving
服务。
模型的超参数一般都有很多,而且每个超参数也会有众多的候选值,形成的参数空间较大,如果仅依赖人工逐项测试,时间和精力成本无疑是巨大的,因此有许多自动调参算法被提出。
目前比较常用的超参数自动调优策略包括网格搜索 (grid search
) 和随机搜索 (random search
) 等。
2
个超参数,每个超参数有 3
个候选值,那么所有的超参数组合就有 3*3=9
种,网格搜索会尝试所有 9
组超参数并从中选择最佳组合。TensorFlow
提供了 HParams
插件来辅助我们进行超参数调优, HParams
插件支持网格搜索和随机搜索两种策略。下面以 Keras
模型训练为例,介绍使用 HParams
进行超参数调优的步骤。
HParam
类初始化定义所有的超参数,并指定超参数的取值域 (Domain
)。 Domain
有三种类型,一种为 IntInterval
表示连续的整数取值, Discrete
表示离散取值,可以是整数,浮点数以及字符串等, RealInterval
表示连续的浮点数取值。HP_DEEP_LAYERS = hp.HParam("deep_layers", hp.IntInterval(1, 3))
表示连续的整数取值的超参数,1
为最小值, 3
为最大值,取值范围是 [1, 3]
。HP_DEEP_LAYER_SIZE = hp.HParam("deep_layer_size", hp.Discrete([32, 64, 128]))
表示离散取值的超参数,可以取参数列表 (list
) 中的任一元素。HP_LEARNING_RATE = hp.HParam("learning_rate", hp.RealInterval(0.001, 0.1))
表示连续的浮点数取值的超参数, 0.001
为最小值, 0.1
为最大值,取值范围是 [0.001, 0.1]
。Metric
类定义我们要用到的评估指标,后面会根据这些指标来选取最优的超参数组合。hp.Metric("epoch_auc", group="validation", display_name="auc (val.)")
表示要用到的评估指标为 epoch_auc
。指标必须是被 Tensorboard
回调函数记录的或者自定义的标量 (scalar
) ,一般存储在日志文件中,可视化展示时会被 HPARAMS
面板调用并显示。Metric
构造函数的第一个参数 tag
表示指标的名称,对于使用 Tensorboard
回调函数 (callbacks
) 记录的指标,其名称一般为 epoch_tag
或 batch_tag
,如 epoch_auc
。对于自定义的指标, tag
则为 tf.summary.scalar("test_auc", auc, step=1)
中设定的名称,这里为 test_auc
。Metric
构造函数的第二个参数 group
表示指标存储的路径,比如训练的指标存储在 train
目录下,验证的指标存储在 validation
目录下,自定义的指标可以存储在 test
目录下等,详见示例程序。Metric
构造函数的第三个参数 display_name
表示指标在 HPARAMS
面板中显示的名称。hp.hparams_config(hparams=HPARAMS, metrics=METRICS)
可以按需设置将要选取的超参数以及用于评估的指标。hparams_config
方法有两个参数,它们分别表示所有待选超参数 HParam
的列表 (list
) 和所有评估指标 Metric
的列表 (list
) 。HParams
默认会记录所有在模型中使用到的
超参数以及模型输出的 所有指标值
并在 HPARAMS
面板中显示。dict
) 的形式传递给模型构造函数以完成模型的构建。def model_fn(hparams): model = keras.models.Sequential() for _ in range(hparams[HP_DEEP_LAYERS]): model.add( keras.layers.Dense( units=hparams[HP_DEEP_LAYER_SIZE], activation="relu", use_bias=True, )) model.add(keras.layers.Dense(units=1, activation="sigmoid")) model.compile( optimizer=tf.keras.optimizers.Adam( learning_rate=hparams[HP_LEARNING_RATE]), loss=tf.keras.losses.BinaryCrossentropy(), metrics=["AUC"], ) return model
key
为上面定义的 HParam
对象, value
为基本数据类型的值。value
值。当然,也可以预先定义好一个可配置的 subclass
模型,然后将超参数传入该模型,即可更加方便地完成模型的构建。fit
方法的 callback
参数,不仅需要包含 Tensorboard
回调函数,还需要包括 hp.KerasCallback
回调函数。loss
) 以及指标 (metrics
) 的值,第二个回调函数用来记录本次训练使用的超参数组合以及计算最后的损失值以及指标值。hp.KerasCallback(logdir, hparams)
中第一个参数为记录 HParams
日志的目录,第二个参数为超参数字典,与传递给模型构造函数的字典相同。HPARAMS
面板的显示结果为多次评估结果的平均值。2
次超参数选择,其日志根目录 mlp
的结构如下所示:mlp├── 0│ ├── events.out.tfevents.1589257272.alexander.4918.34.v2│ ├── test│ │ └── events.out.tfevents.1589257274.alexander.4918.2418.v2│ ├── train│ │ └── events.out.tfevents.1589257272.alexander.4918.95.v2│ └── validation│ └── events.out.tfevents.1589257273.alexander.4918.1622.v2├── 1│ ├── events.out.tfevents.1589257274.alexander.4918.2575.v2│ ├── test│ │ └── events.out.tfevents.1589257275.alexander.4918.4958.v2│ ├── train│ │ └── events.out.tfevents.1589257274.alexander.4918.2636.v2│ └── validation│ └── events.out.tfevents.1589257274.alexander.4918.4162.v2└── events.out.tfevents.1589257272.alexander.4918.5.v2
0
和 1
目录分别存储了一组超参数训练与验证后的结果数据。loss
以及 metrics
) 会以 events.out.tfevents
文件的形式保存在 Tensorboard
回调函数指定的目录下,本示例中为 0
或 1
目录下的 train
和 validation
目录。HParams
记录的日志会保存在 0
或 1
根目录下的 events.out.tfevents
文件中。Tensorboard
并指定其 logdir
参数为 mlp
,然后选择 HPARAMS
面板即可看到可视化的调参结果。网格搜索需要遍历所有的超参数组合,所以此时在初始化 HParam
超参数对象时应该尽量使用 Discrete
域类型,方便数据遍历。当然 IntInterval
和 RealInterval
域类型的数据也可以通过指定步长的方式来进行遍历。
如果超参数对象的域类型是 IntInterval
和 RealInterval
时,可以通过该对象的 domain.min_value
和 domain.max_value
属性获取超参数候选值的最小值和最大值。如果是 Discrete
类型,可以通过 domain.values
属性获取到该超参数所有候选值的列表 (list
)。
网格搜索的示例代码如下所示(搜索步骤参见 run_all
函数):
import osimport tensorflow as tffrom tensorflow import kerasfrom tensorboard.plugins.hparams import api as hpfrom absl import app, flagsimport shutilimport numpy as npFLAGS = flags.FLAGSflags.DEFINE_string("logdir", "mlp", "logs dir")HP_DEEP_LAYERS = hp.HParam("deep_layers", hp.IntInterval(1, 3))HP_DEEP_LAYER_SIZE = hp.HParam("deep_layer_size", hp.Discrete([32, 64, 128]))HP_LEARNING_RATE = hp.HParam("learning_rate", hp.RealInterval(0.001, 0.1))HPARAMS = [ HP_DEEP_LAYERS, HP_DEEP_LAYER_SIZE, HP_LEARNING_RATE,]METRICS = [ hp.Metric( "epoch_auc", group="validation", display_name="auc (val.)", ), hp.Metric( "epoch_loss", group="validation", display_name="loss (val.)", ), hp.Metric( "batch_auc", group="train", display_name="auc (train)", ), hp.Metric( "batch_loss", group="train", display_name="loss (train)", ), hp.Metric( "test_auc", group="test", display_name="auc (test)", ),]def model_fn(hparams): model = keras.models.Sequential() for _ in range(hparams[HP_DEEP_LAYERS]): model.add( keras.layers.Dense( units=hparams[HP_DEEP_LAYER_SIZE], activation="relu", use_bias=True, )) model.add(keras.layers.Dense(units=1, activation="sigmoid")) model.compile( optimizer=tf.keras.optimizers.Adam( learning_rate=hparams[HP_LEARNING_RATE]), loss=tf.keras.losses.BinaryCrossentropy(), metrics=["AUC"], ) return modeldef run(data, hparams, base_logdir, session_id): model = model_fn(hparams) logdir = os.path.join(base_logdir, session_id) tensorboard_callback = tf.keras.callbacks.TensorBoard( log_dir=logdir, update_freq=10, profile_batch=0, ) hparams_callback = hp.KerasCallback(logdir, hparams) ((x_train, y_train), (x_val, y_val), (x_test, y_test)) = data model.fit( x=x_train, y=y_train, epochs=2, batch_size=128, validation_data=(x_val, y_val), callbacks=[tensorboard_callback, hparams_callback], ) test_dir = os.path.join(logdir, "test") with tf.summary.create_file_writer(test_dir).as_default(): _, auc = model.evaluate(x_test, y_test) tf.summary.scalar("test_auc", auc, step=1)def prepare_data(): x_train, y_train = ( np.random.rand(6000, 32), np.random.randint(2, size=(6000, 1)), ) x_val, y_val = ( np.random.rand(1000, 32), np.random.randint(2, size=(1000, 1)), ) x_test, y_test = ( np.random.rand(1000, 32), np.random.randint(2, size=(1000, 1)), ) return ((x_train, y_train), (x_val, y_val), (x_test, y_test))def run_all(logdir): data = prepare_data() with tf.summary.create_file_writer(logdir).as_default(): hp.hparams_config(hparams=HPARAMS, metrics=METRICS) session_index = 0 for deep_layers in range(HP_DEEP_LAYERS.domain.min_value, HP_DEEP_LAYERS.domain.max_value): for deep_layer_size in HP_DEEP_LAYER_SIZE.domain.values: for learning_rate in np.arange(HP_LEARNING_RATE.domain.min_value, HP_LEARNING_RATE.domain.max_value, 0.01): hparams = { HP_DEEP_LAYERS: deep_layers, HP_DEEP_LAYER_SIZE: deep_layer_size, HP_LEARNING_RATE: learning_rate, } session_id = str(session_index) session_index += 1 print("--- Running training session %d" % (session_index)) hparams_string = str(hparams) print(hparams_string) run( data=data, hparams=hparams, base_logdir=logdir, session_id=session_id, )def main(argv): del argv # Unused args logdir = FLAGS.logdir shutil.rmtree(logdir, ignore_errors=True) print("Saving output to %s." % logdir) run_all(logdir=logdir) print("Done. Output saved to %s." % logdir)if __name__ == "__main__": app.run(main)
通过调用超参数对象的域 (domain
) 属性的 sample_uniform()
方法可以从该超参数的候选值中随机选取一个值,然后就可以使用随机生成的超参数组合进行训练了。
sample_uniform
方法还可以接收一个带有种子 (seed
) 的伪随机数生成器,如 random.Random(seed)
,这在分布式训练的超参数调优过程中十分重要,通过指定同一个伪随机数生成器,可以保证所有 worker
节点每次获取到的超参数组合都是一致的,从而确保分布式训练能够正常进行。
随机搜索 run_all
部分的代码如下所示,其它部分的代码与网格搜索相同。
def run_all(logdir): data = prepare_data() with tf.summary.create_file_writer(logdir).as_default(): hp.hparams_config(hparams=HPARAMS, metrics=METRICS) session_index = 0 for _ in range(8): hparams = {h: h.domain.sample_uniform() for h in HPARAMS} hparams_string = str(hparams) session_id = str(session_index) session_index += 1 print("--- Running training session %d" % (session_index)) print(hparams_string) run( data=data, hparams=hparams, base_logdir=logdir, session_id=session_id, )
启动 Tensorboard
后,即可在页面的上方看到 HPARAMS
选项,点击该选项就能够看到 HPARAMS
面板 (Dashboard
) 了。
HPARAMS
面板提供了左右两个窗格,左边的窗格提供了筛选功能,右边的窗格提供了可视化评估结果的功能,下面来分别对它们的功用进行说明。
筛选窗格提供了筛选功能,以控制右边窗格的可视化渲染。它可以选择用于展示的超参数以及指标,可以筛选被展示的超参数以及指标的值,还可以对可视化的结果进行排序等。如下图所示:
可视化窗格包含三个视图 (view
) ,分别包含不同的信息。
表格视图 (Table View
) 以表格的形式列出了所有的超参数组合以及对应的各项指标的值,还可以通过点击 Show Metrics
来展示指标随 batch
或 epoch
的变化趋势图。如下图所示:
平行坐标视图 (Parallel Coordinates View
) 由一系列表示超参数和指标的竖向坐标轴组成,对于每一超参数的取值以及对应的指标值,都会通过一条线连接起来。点击任意一条线都会对该组取值进行高亮显示,可以在坐标轴上用鼠标标记一个区域,这时只会显示在该区域内的取值,这对于判断哪组超参数更为重要十分有帮助。如下图所示:
散点图视图 (Scatter Plot View
) 由一系列超参数与指标之间关联的散点图组成,它可以帮助发现超参数之间或超参数与指标之间的潜在关联。如下图所示:
RealInterval
的取值如果从 0
开始,则要以浮点数 0.
形式表示。fit
中的 metrics
参数要设置为字符串类型或者一个全局的 Metric
对象。这样 Tensorboard
记录的 metrics
名称才能在多次训练中保持一致如 epoch_auc
,而不会出现 epoch_auc_1
, epoch_auc_2
这种情况,从而使得 HPARAMS
面板能够正常获取到 metrics
的值并进行展示。worker
节点选取到的随机值都一致,从而确保分布式训练能够正常进行。领取专属 10元无门槛券
私享最新 技术干货