开发者指南 / 超参数调优 / 分布式超参数调优

分布式超参数调优

作者:Tom O'Malley,Haifeng Jin
创建日期 2019/10/24
上次修改时间 2021/06/02
描述:使用多个 GPU 和多台机器调优模型的超参数。

在 Colab 中查看 GitHub 源代码

!pip install keras-tuner -q

简介

KerasTuner 使分布式超参数搜索变得容易。无需对代码进行任何更改即可从本地单线程运行扩展到并行运行数十或数百个工作程序。分布式 KerasTuner 使用主节点-工作节点模型。主节点运行一个服务,工作节点将结果报告到该服务,并查询要尝试的下一个超参数。主节点应在单线程 CPU 实例上运行(或者作为工作节点之一上的单独进程运行)。

配置分布式模式

配置 KerasTuner 的分布式模式只需要设置三个环境变量

KERASTUNER_TUNER_ID:对于主节点进程,此变量应设置为“chief”。其他工作节点应传入一个唯一的 ID(按照惯例,“tuner0”、“tuner1”等)。

KERASTUNER_ORACLE_IP:主节点服务应运行的 IP 地址或主机名。所有工作节点都应能够解析并访问此地址。

KERASTUNER_ORACLE_PORT:主节点服务应运行的端口。可以自由选择,但必须是其他工作节点可以访问的端口。实例通过 gRPC 协议进行通信。

所有工作节点都可以运行相同的代码。分布式模式的其他注意事项包括

  • 所有工作节点都应能够访问一个集中式文件系统,以供它们写入结果。
  • 所有工作节点都应能够访问进行调优所需的必要训练和验证数据。
  • 为了支持容错,overwrite 应在 Tuner.__init__ 中保留为 FalseFalse 是默认值)。

主节点服务的示例 bash 脚本(页面底部有 run_tuning.py 的示例代码)

export KERASTUNER_TUNER_ID="chief"
export KERASTUNER_ORACLE_IP="127.0.0.1"
export KERASTUNER_ORACLE_PORT="8000"
python run_tuning.py

工作节点的示例 bash 脚本

export KERASTUNER_TUNER_ID="tuner0"
export KERASTUNER_ORACLE_IP="127.0.0.1"
export KERASTUNER_ORACLE_PORT="8000"
python run_tuning.py

使用 tf.distribute 进行数据并行

KerasTuner 还支持通过 tf.distribute 进行数据并行。数据并行和分布式调优可以结合使用。例如,如果你的 10 个工作节点每个工作节点有 4 个 GPU,则可以通过使用 tf.distribute.MirroredStrategy 在 4 个 GPU 上运行 10 个并行试验,每个试验训练 4 个 GPU。你还可以通过 tf.distribute.TPUStrategy 在 TPU 上运行每个试验。目前不支持 tf.distribute.MultiWorkerMirroredStrategy,但对此的支持已列入路线图。

示例代码

当设置了上面描述的环境变量时,下面的示例将运行分布式调优,并通过 tf.distribute 在每个试验中使用数据并行。示例从 tensorflow_datasets 加载 MNIST,并使用 Hyperband 进行超参数搜索。

import keras
import keras_tuner
import tensorflow as tf
import numpy as np


def build_model(hp):
    """Builds a convolutional model."""
    inputs = keras.Input(shape=(28, 28, 1))
    x = inputs
    for i in range(hp.Int("conv_layers", 1, 3, default=3)):
        x = keras.layers.Conv2D(
            filters=hp.Int("filters_" + str(i), 4, 32, step=4, default=8),
            kernel_size=hp.Int("kernel_size_" + str(i), 3, 5),
            activation="relu",
            padding="same",
        )(x)

        if hp.Choice("pooling" + str(i), ["max", "avg"]) == "max":
            x = keras.layers.MaxPooling2D()(x)
        else:
            x = keras.layers.AveragePooling2D()(x)

        x = keras.layers.BatchNormalization()(x)
        x = keras.layers.ReLU()(x)

    if hp.Choice("global_pooling", ["max", "avg"]) == "max":
        x = keras.layers.GlobalMaxPooling2D()(x)
    else:
        x = keras.layers.GlobalAveragePooling2D()(x)
    outputs = keras.layers.Dense(10, activation="softmax")(x)

    model = keras.Model(inputs, outputs)

    optimizer = hp.Choice("optimizer", ["adam", "sgd"])
    model.compile(
        optimizer, loss="sparse_categorical_crossentropy", metrics=["accuracy"]
    )
    return model


tuner = keras_tuner.Hyperband(
    hypermodel=build_model,
    objective="val_accuracy",
    max_epochs=2,
    factor=3,
    hyperband_iterations=1,
    distribution_strategy=tf.distribute.MirroredStrategy(),
    directory="results_dir",
    project_name="mnist",
    overwrite=True,
)

(x_train, y_train), (x_test, y_test) = keras.datasets.mnist.load_data()

# Reshape the images to have the channel dimension.
x_train = (x_train.reshape(x_train.shape + (1,)) / 255.0)[:1000]
y_train = y_train.astype(np.int64)[:1000]
x_test = (x_test.reshape(x_test.shape + (1,)) / 255.0)[:100]
y_test = y_test.astype(np.int64)[:100]

tuner.search(
    x_train,
    y_train,
    steps_per_epoch=600,
    validation_data=(x_test, y_test),
    validation_steps=100,
    callbacks=[keras.callbacks.EarlyStopping("val_accuracy")],
)
Trial 2 Complete [00h 00m 18s]
val_accuracy: 0.07000000029802322
Best val_accuracy So Far: 0.07000000029802322
Total elapsed time: 00h 00m 26s