使用 HyperOpt 运行 Tune 实验#

try-anyscale-quickstart

在本教程中,我们将介绍 HyperOpt,同时运行一个简单的 Ray Tune 实验。Tune 的搜索算法与 HyperOpt 集成,因此允许你无缝扩展 Hyperopt 优化过程,而不会牺牲性能。

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

在此示例中,我们最小化一个简单的目标函数,以简要演示通过 HyperOptSearch 在 Ray Tune 中使用 HyperOpt。请记住,尽管侧重于机器学习实验,但 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 秒)来模拟一个长时间运行的 ML 实验。此设置假设我们正在运行实验的多个 step,并尝试调优两个超参数,即 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 搜索 search_config 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 CPU,0/0 GPU

Trial 状态

Trial 名称状态位置激活函数高度步数宽度损失迭代次数总时间 (s)迭代次数
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 CPU,0/0 GPU

Trial 状态

Trial 名称状态位置activation/activatio nactivation/mult高度步数宽度损失迭代次数总时间 (s)迭代次数
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}