使用 HyperOpt 运行 Tune 实验#

try-anyscale-quickstart

本教程将介绍 HyperOpt,并在运行一个简单的 Ray Tune 实验。Tune 的搜索算法与 HyperOpt 集成,因此您可以无缝地扩展 Hyperopt 的优化过程,而不会牺牲性能。

HyperOpt 提供无梯度/导数的优化,能够处理目标景观上的噪声,包括进化、老虎机和贝叶斯优化算法。HyperOpt 内部支持连续、离散或两者的混合搜索空间。它还提供了一个函数库,用于测试优化算法并与其他基准进行比较。

在本例中,我们通过 HyperOptSearch 来最小化一个简单的目标函数,以简要演示 HyperOpt 与 Ray Tune 的用法。需要注意的是,尽管这里强调的是机器学习实验,但 Ray Tune 可以优化任何隐式或显式的目标。我们假设已安装 hyperopt==0.2.5 库。如需了解更多信息,请参阅 HyperOpt 网站

我们包含了一个关于条件搜索空间的重要示例(将超参数之间的关系串联起来)。

背景信息

必要要求

  • pip install "ray[tune]" hyperopt==0.2.5

隐藏代码单元格内容
# install in a hidden cell
# !pip install "ray[tune]"
!pip install hyperopt==0.2.5
Requirement already satisfied: hyperopt==0.2.5 in /opt/miniconda3/envs/hyperopt_example/lib/python3.11/site-packages (0.2.5)
Requirement already satisfied: numpy in /opt/miniconda3/envs/hyperopt_example/lib/python3.11/site-packages (from hyperopt==0.2.5) (2.2.3)
Requirement already satisfied: scipy in /opt/miniconda3/envs/hyperopt_example/lib/python3.11/site-packages (from hyperopt==0.2.5) (1.15.2)
Requirement already satisfied: six in /opt/miniconda3/envs/hyperopt_example/lib/python3.11/site-packages (from hyperopt==0.2.5) (1.17.0)
Requirement already satisfied: networkx>=2.2 in /opt/miniconda3/envs/hyperopt_example/lib/python3.11/site-packages (from hyperopt==0.2.5) (3.4.2)
Requirement already satisfied: future in /opt/miniconda3/envs/hyperopt_example/lib/python3.11/site-packages (from hyperopt==0.2.5) (1.0.0)
Requirement already satisfied: tqdm in /opt/miniconda3/envs/hyperopt_example/lib/python3.11/site-packages (from hyperopt==0.2.5) (4.67.1)
Requirement already satisfied: cloudpickle in /opt/miniconda3/envs/hyperopt_example/lib/python3.11/site-packages (from hyperopt==0.2.5) (3.1.1)

点击下方查看本示例所需的所有导入。

隐藏代码单元格源代码
import time

import ray
from ray import tune
from ray.tune.search import ConcurrencyLimiter
from ray.tune.search.hyperopt import HyperOptSearch
from hyperopt import hp

让我们从定义一个简单的评估函数开始。我们人为地休眠一小段时间(0.1 秒)来模拟一个长时间运行的机器学习实验。此设置假定我们正在运行一个实验的多个 steps,并尝试调整两个超参数:widthheight

def evaluate(step, width, height):
    time.sleep(0.1)
    return (0.1 + width * step / 100) ** (-1) + height * 0.1

接下来,我们的 objective 函数接收一个 Tune config,在训练循环中评估实验的 score,并使用 tune.reportscore 报告给 Tune。

def objective(config):
    for step in range(config["steps"]):
        score = evaluate(step, config["width"], config["height"])
        tune.report({"iterations": step, "mean_loss": score})
ray.init(configure_logging=False)

在定义搜索算法时,我们可以选择提供一组我们认为特别有希望或信息量大的初始超参数,并将此信息作为 HyperOptSearch 对象的有用起点。

我们还使用 ConcurrencyLimiter 将最大并发任务数设置为 4

initial_params = [
    {"width": 1, "height": 2, "activation": "relu"},
    {"width": 4, "height": 2, "activation": "tanh"},
]
algo = HyperOptSearch(points_to_evaluate=initial_params)
algo = ConcurrencyLimiter(algo, max_concurrent=4)

样本数是将被尝试的超参数组合的数量。此 Tune 运行设置为 1000 个样本。(如果您的机器上运行时间过长,可以减少此数量)。

num_samples = 1000

接下来,我们定义一个搜索空间。关键假设是最优超参数存在于这个空间中。但是,如果空间非常大,那么在短时间内找到这些超参数可能会很困难。

search_config = {
    "steps": 100,
    "width": tune.uniform(0, 20),
    "height": tune.uniform(-100, 100),
    "activation": tune.choice(["relu", "tanh"])
}

最后,我们运行实验,通过 algo,执行 num_samples 次,来 "min"(最小化)objective 的“mean_loss”。前一句完整地描述了我们要解决的搜索问题。考虑到这一点,请注意执行 tuner.fit() 的效率。

tuner = tune.Tuner(
    objective,
    tune_config=tune.TuneConfig(
        metric="mean_loss",
        mode="min",
        search_alg=algo,
        num_samples=num_samples,
    ),
    param_space=search_config,
)
results = tuner.fit()

Tune 状态

当前时间2025-02-18 13:14:59
运行中00:00:36.03
内存22.1/36.0 GiB

系统信息

正在使用 FIFO 调度算法。
逻辑资源使用情况:1.0/12 CPUs, 0/0 GPUs

试验状态

试验名称状态位置activationheightstepswidth损失迭代总时间 (秒)iterations
objective_5b05c00a已终止127.0.0.1:50205relu 2 100 1 1.11743 100 10.335 99
objective_b813f49d已终止127.0.0.1:50207tanh 2 100 4 0.446305 100 10.3299 99
objective_6dadd2bd已终止127.0.0.1:50212tanh-40.9318 100 6.20615 -3.93303 100 10.3213 99
objective_9faffc0f已终止127.0.0.1:50217tanh 91.9688 100 9.25147 9.30488 100 10.353 99
objective_7834e74c已终止127.0.0.1:50266tanh-17.9521 10011.436 -1.70766 100 10.3753 99
objective_741253c7已终止127.0.0.1:50271tanh 58.1279 100 0.737879 7.01689 100 10.3565 99
objective_39682bcf已终止127.0.0.1:50272tanh-31.2589 100 4.89265 -2.92361 100 10.3225 99
objective_bfc7e150已终止127.0.0.1:50274tanh-14.7877 100 7.36477 -1.34347 100 10.3744 99
objective_e1f1a193已终止127.0.0.1:50314tanh 50.9579 10017.4499 5.15334 100 10.3675 99
objective_1192dd4e已终止127.0.0.1:50316tanh 66.5306 10016.9549 6.71228 100 10.3478 99

以下是最小化已定义目标平均损失的超参数。

print("Best hyperparameters found were: ", results.get_best_result().config)
Best hyperparameters found were:  {'steps': 100, 'width': 6.206149011253133, 'height': -40.93182668460948, 'activation': 'tanh'}

条件搜索空间#

有时我们可能希望构建一个更复杂的搜索空间,该空间具有与其他超参数的条件依赖关系。在这种情况下,我们将一个嵌套字典传递给 objective_two,它已从 objective 中进行了微调,以处理条件搜索空间。

def evaluation_fn(step, width, height, mult=1):
    return (0.1 + width * step / 100) ** (-1) + height * 0.1 * mult
def objective_two(config):
    width, height = config["width"], config["height"]
    sub_dict = config["activation"]
    mult = sub_dict.get("mult", 1)
    
    for step in range(config["steps"]):
        intermediate_score = evaluation_fn(step, width, height, mult)
        tune.report({"iterations": step, "mean_loss": intermediate_score})
        time.sleep(0.1)
conditional_space = {
    "activation": hp.choice(
        "activation",
        [
            {"activation": "relu", "mult": hp.uniform("mult", 1, 2)},
            {"activation": "tanh"},
        ],
    ),
    "width": hp.uniform("width", 0, 20),
    "height": hp.uniform("height", -100, 100),
    "steps": 100,
}

现在,我们定义一个基于 HyperOptSearch 并受 ConcurrencyLimiter 约束的搜索算法。当超参数搜索空间是条件的时,我们将其(conditional_space)传递给 HyperOptSearch

algo = HyperOptSearch(space=conditional_space, metric="mean_loss", mode="min")
algo = ConcurrencyLimiter(algo, max_concurrent=4)

现在我们运行实验,这次使用一个空的 config,因为我们已经将 space 提供了给 HyperOptSearch search_alg

tuner = tune.Tuner(
    objective_two,
    tune_config=tune.TuneConfig(
        metric="mean_loss",
        mode="min",
        search_alg=algo,
        num_samples=num_samples,
    ),
)
results = tuner.fit()

Tune 状态

当前时间2025-02-18 13:15:34
运行中00:00:34.71
内存22.9/36.0 GiB

系统信息

正在使用 FIFO 调度算法。
逻辑资源使用情况:1.0/12 CPUs, 0/0 GPUs

试验状态

试验名称状态位置activation/activatio nactivation/multheightstepswidth损失迭代总时间 (秒)iterations
objective_two_13de5867已终止127.0.0.1:50350tanh -35.0329 10013.2254 -3.42749 100 10.2102 99
objective_two_3100f5ee已终止127.0.0.1:50355relu 1.44584 76.2581 100 0.123165 15.5316 100 10.2683 99
objective_two_6b4044aa已终止127.0.0.1:50356relu 1.67475 57.9612 10019.4794 9.75866 100 10.2724 99
objective_two_4aa1269b已终止127.0.0.1:50357tanh -9.95686 10012.9749 -0.918437 100 10.2373 99
objective_two_d8c42c9f已终止127.0.0.1:50402tanh -96.6184 100 8.03869 -9.53774 100 10.2407 99
objective_two_de956d10已终止127.0.0.1:50404relu 1.06986 9.64996 100 0.672962 2.3375 100 10.2427 99
objective_two_0e2b2751已终止127.0.0.1:50413tanh -82.9292 10017.4889 -8.2355 100 10.293 99
objective_two_dad93a03已终止127.0.0.1:50415relu 1.85364-63.6309 10016.6414 -11.7345 100 10.285 99
objective_two_e472727a已终止127.0.0.1:50442relu 1.2359 -75.2253 100 7.49782 -9.16415 100 10.2506 99
objective_two_5d1eacff已终止127.0.0.1:50449tanh -20.158 100 6.18643 -1.85514 100 10.2566 99

最后,我们再次展示了最小化上面目标函数得分所定义的平均损失的超参数。

print("Best hyperparameters found were: ", results.get_best_result().config)
Best hyperparameters found were:  {'activation': {'activation': 'relu', 'mult': 1.8536380640438768}, 'height': -63.630920754630125, 'steps': 100, 'width': 16.641403933591928}