使用 HyperOpt 运行 Tune 实验#
在本教程中,我们将介绍 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
,并尝试调优两个超参数,即 width
和 height
。
def evaluate(step, width, height):
time.sleep(0.1)
return (0.1 + width * step / 100) ** (-1) + height * 0.1
接下来,我们的 objective
函数接受一个 Tune config
,在训练循环中评估实验的 score
,并使用 tune.report
将 score
报告回 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:50205 | relu | 2 | 100 | 1 | 1.11743 | 100 | 10.335 | 99 |
objective_b813f49d | 已终止 | 127.0.0.1:50207 | tanh | 2 | 100 | 4 | 0.446305 | 100 | 10.3299 | 99 |
objective_6dadd2bd | 已终止 | 127.0.0.1:50212 | tanh | -40.9318 | 100 | 6.20615 | -3.93303 | 100 | 10.3213 | 99 |
objective_9faffc0f | 已终止 | 127.0.0.1:50217 | tanh | 91.9688 | 100 | 9.25147 | 9.30488 | 100 | 10.353 | 99 |
objective_7834e74c | 已终止 | 127.0.0.1:50266 | tanh | -17.9521 | 100 | 11.436 | -1.70766 | 100 | 10.3753 | 99 |
objective_741253c7 | 已终止 | 127.0.0.1:50271 | tanh | 58.1279 | 100 | 0.737879 | 7.01689 | 100 | 10.3565 | 99 |
objective_39682bcf | 已终止 | 127.0.0.1:50272 | tanh | -31.2589 | 100 | 4.89265 | -2.92361 | 100 | 10.3225 | 99 |
objective_bfc7e150 | 已终止 | 127.0.0.1:50274 | tanh | -14.7877 | 100 | 7.36477 | -1.34347 | 100 | 10.3744 | 99 |
objective_e1f1a193 | 已终止 | 127.0.0.1:50314 | tanh | 50.9579 | 100 | 17.4499 | 5.15334 | 100 | 10.3675 | 99 |
objective_1192dd4e | 已终止 | 127.0.0.1:50316 | tanh | 66.5306 | 100 | 16.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 n | activation/mult | 高度 | 步数 | 宽度 | 损失 | 迭代次数 | 总时间 (s) | 迭代次数 |
---|---|---|---|---|---|---|---|---|---|---|---|
objective_two_13de5867 | 已终止 | 127.0.0.1:50350 | tanh | -35.0329 | 100 | 13.2254 | -3.42749 | 100 | 10.2102 | 99 | |
objective_two_3100f5ee | 已终止 | 127.0.0.1:50355 | relu | 1.44584 | 76.2581 | 100 | 0.123165 | 15.5316 | 100 | 10.2683 | 99 |
objective_two_6b4044aa | 已终止 | 127.0.0.1:50356 | relu | 1.67475 | 57.9612 | 100 | 19.4794 | 9.75866 | 100 | 10.2724 | 99 |
objective_two_4aa1269b | 已终止 | 127.0.0.1:50357 | tanh | -9.95686 | 100 | 12.9749 | -0.918437 | 100 | 10.2373 | 99 | |
objective_two_d8c42c9f | 已终止 | 127.0.0.1:50402 | tanh | -96.6184 | 100 | 8.03869 | -9.53774 | 100 | 10.2407 | 99 | |
objective_two_de956d10 | 已终止 | 127.0.0.1:50404 | relu | 1.06986 | 9.64996 | 100 | 0.672962 | 2.3375 | 100 | 10.2427 | 99 |
objective_two_0e2b2751 | 已终止 | 127.0.0.1:50413 | tanh | -82.9292 | 100 | 17.4889 | -8.2355 | 100 | 10.293 | 99 | |
objective_two_dad93a03 | 已终止 | 127.0.0.1:50415 | relu | 1.85364 | -63.6309 | 100 | 16.6414 | -11.7345 | 100 | 10.285 | 99 |
objective_two_e472727a | 已终止 | 127.0.0.1:50442 | relu | 1.2359 | -75.2253 | 100 | 7.49782 | -9.16415 | 100 | 10.2506 | 99 |
objective_two_5d1eacff | 已终止 | 127.0.0.1:50449 | tanh | -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}