运行基本调优实验#

使用 Tune 最常见的方式也是最简单的:作为一个并行实验运行器。如果你可以用 Python 函数定义实验试验,你就可以使用 Tune 在集群中运行数百到数千个独立的试验实例。Tune 管理试验执行、状态报告和容错。

并行运行独立调优试验#

作为一个一般示例,让我们考虑使用 Tune 执行 N 次独立的模型训练试验,作为一个简单的网格扫描。每个试验可以根据传入的配置字典执行不同的代码。

步骤 1: 首先,我们定义我们想要运行变体的模型训练函数。该函数接受一个配置字典作为参数,并返回一个简单的字典输出。了解更多关于记录 Tune 结果的信息,请参阅 如何在 Tune 中配置日志记录?

from ray import tune
import ray
import os

NUM_MODELS = 100

def train_model(config):
    score = config["model_id"]

    # Import model libraries, etc...
    # Load data and train model code here...

    # Return final stats. You can also return intermediate progress
    # using ray.train.report() if needed.
    # To return your model, you could write it to storage and return its
    # URI in this dict, or return it as a Tune Checkpoint:
    # https://docs.ray.io/en/latest/tune/tutorials/tune-checkpoints.html
    return {"score": score, "other_data": ...}

步骤 2: 接下来,定义要运行的试验空间。这里,我们定义了一个简单的网格扫描,从 0..NUM_MODELS 开始,这将生成要传递给每个模型函数的配置字典。了解更多关于 Tune 在定义空间时提供的功能,请参阅 使用 Tune 搜索空间

# Define trial parameters as a single grid sweep.
trial_space = {
    # This is an example parameter. You could replace it with filesystem paths,
    # model types, or even full nested Python dicts of model configurations, etc.,
    # that enumerate the set of trials to run.
    "model_id": tune.grid_search([
        "model_{}".format(i)
        for i in range(NUM_MODELS)
    ])
}

步骤 3: 可选地,配置每个试验分配的资源。Tune 使用这些资源分配来控制并行性。例如,如果每个试验配置为使用 4 个 CPU,而集群只有 32 个 CPU,那么 Tune 将限制并发试验的数量为 8,以避免集群过载。更多信息,请参阅 Ray Tune 的并行性和资源指南

# Can customize resources per trial, here we set 1 CPU each.
train_model = tune.with_resources(train_model, {"cpu": 1})

步骤 4: 使用 Tune 运行试验。Tune 将报告实验状态,实验结束后,您可以检查结果。Tune 可以自动重试失败的试验,以及整个实验;请参阅 tune-停止指南

# Start a Tune run and print the best result.
tuner = tune.Tuner(train_model, param_space=trial_space)
results = tuner.fit()

# Access individual results.
print(results[0])
print(results[1])
print(results[2])

步骤 5: 检查结果。它们看起来会像这样。Tune 会定期将状态摘要打印到 stdout,显示正在进行的实验状态,直到完成:

== Status ==
Current time: 2022-09-21 10:19:34 (running for 00:00:04.54)
Memory usage on this node: 6.9/31.1 GiB
Using FIFO scheduling algorithm.
Resources requested: 0/8 CPUs, 0/0 GPUs, 0.0/16.13 GiB heap, 0.0/8.06 GiB objects
Result logdir: /home/ubuntu/ray_results/train_model_2022-09-21_10-19-26
Number of trials: 100/100 (100 TERMINATED)
+-------------------------+------------+----------------------+------------+--------+------------------+
| Trial name              | status     | loc                  | model_id   |   iter |   total time (s) |
|-------------------------+------------+----------------------+------------+--------+------------------|
| train_model_8d627_00000 | TERMINATED | 192.168.1.67:2381731 | model_0    |      1 |      8.46386e-05 |
| train_model_8d627_00001 | TERMINATED | 192.168.1.67:2381761 | model_1    |      1 |      0.000126362 |
| train_model_8d627_00002 | TERMINATED | 192.168.1.67:2381763 | model_2    |      1 |      0.000112772 |
...
| train_model_8d627_00097 | TERMINATED | 192.168.1.67:2381731 | model_97   |      1 |      5.57899e-05 |
| train_model_8d627_00098 | TERMINATED | 192.168.1.67:2381767 | model_98   |      1 |      6.05583e-05 |
| train_model_8d627_00099 | TERMINATED | 192.168.1.67:2381763 | model_99   |      1 |      6.69956e-05 |
+-------------------------+------------+----------------------+------------+--------+------------------+

2022-09-21 10:19:35,159     INFO tune.py:762 -- Total run time: 5.06 seconds (4.46 seconds for the tuning loop).

最终结果对象包含完成的试验元数据:

Result(metrics={'score': 'model_0', 'other_data': Ellipsis, 'done': True, 'trial_id': '8d627_00000', 'experiment_tag': '0_model_id=model_0'}, error=None, log_dir=PosixPath('/home/ubuntu/ray_results/train_model_2022-09-21_10-19-26/train_model_8d627_00000_0_model_id=model_0_2022-09-21_10-19-30'))
Result(metrics={'score': 'model_1', 'other_data': Ellipsis, 'done': True, 'trial_id': '8d627_00001', 'experiment_tag': '1_model_id=model_1'}, error=None, log_dir=PosixPath('/home/ubuntu/ray_results/train_model_2022-09-21_10-19-26/train_model_8d627_00001_1_model_id=model_1_2022-09-21_10-19-31'))
Result(metrics={'score': 'model_2', 'other_data': Ellipsis, 'done': True, 'trial_id': '8d627_00002', 'experiment_tag': '2_model_id=model_2'}, error=None, log_dir=PosixPath('/home/ubuntu/ray_results/train_model_2022-09-21_10-19-26/train_model_8d627_00002_2_model_id=model_2_2022-09-21_10-19-31'))

Tune 与使用 Ray Core (ray.remote) 相比如何?#

你可能想知道 Tune 与简单使用 ray-远程函数 进行并行试验执行有何不同。确实,上述示例可以类似地重写为:

remote_train = ray.remote(train_model)
futures = [remote_train.remote({"model_id": i}) for i in range(NUM_MODELS)]
print("Submitting tasks...")
results = ray.get(futures)
print("Trial results", results)

与使用 Ray 任务相比,Tune 提供了以下附加功能:

  • 状态报告和跟踪,包括与常见监控工具的集成和回调。

  • 细粒度容错的试验检查点。

  • 多工作者试验的团伙调度。

简而言之,如果你需要状态跟踪或支持更高级的机器学习工作负载,请考虑使用 Tune。