Ray Tune 的关键概念#

让我们快速浏览一下使用 Tune 需要了解的关键概念。如果你想立即查看实际教程,请访问我们的 用户指南。本质上,Tune 有六个你需要理解的关键组件。

首先,您在 搜索空间 中定义要调整的超参数,并将它们传递给指定要调整的目标的 可训练对象。然后,您选择一个 搜索算法 来有效地优化您的参数,并可选择使用 调度器 来提前停止搜索并加快您的实验速度。与其他配置一起,您的 可训练对象、搜索算法和调度器被传递给 Tuner,它运行您的实验并创建 试验Tuner 返回一个 结果网格 以检查您的实验结果。下图概述了这些组件,我们将在接下来的章节中详细介绍。

../_images/tune_flow.png

Ray Tune 训练器#

简而言之,可训练对象 是一个可以传递给 Tune 运行的对象。Ray Tune 有两种定义 trainable 的方式,即 函数 API类 API。两者都是定义 trainable 的有效方式,但通常推荐使用函数 API,并且在本指南的其余部分中都会使用它。

假设我们想要优化一个简单的目标函数,比如 a (x ** 2) + b,其中 ab 是我们想要调整的超参数,以 最小化 目标函数。由于目标函数还有一个变量 x,我们需要测试不同的 x 值。给定 abx 的具体选择,我们可以评估目标函数并得到一个 分数 以最小化。

使用 基于函数的API,您可以创建一个函数(此处称为 trainable),该函数接收一个超参数字典。此函数在“训练循环”中计算一个 score,并将此分数 报告 回 Tune:

from ray import train


def objective(x, a, b):  # Define an objective function.
    return a * (x**0.5) + b


def trainable(config):  # Pass a "config" dictionary into your trainable.

    for x in range(20):  # "Train" for 20 iterations and compute intermediate scores.
        score = objective(x, config["a"], config["b"])

        train.report({"score": score})  # Send the score to Tune.


请注意,我们在训练循环中使用 session.report(...) 来报告中间的 score,这在许多机器学习任务中非常有用。如果你只想在循环外报告最终的 score,可以直接在 trainable 函数的末尾返回 score,即 return {"score": score}。你也可以使用 yield {"score": score} 来代替 session.report()

以下是使用 基于类的API 指定目标函数的示例:

from ray import tune


def objective(x, a, b):
    return a * (x**2) + b


class Trainable(tune.Trainable):
    def setup(self, config):
        # config (dict): A dict of hyperparameters
        self.x = 0
        self.a = config["a"]
        self.b = config["b"]

    def step(self):  # This is called iteratively.
        score = objective(self.x, self.a, self.b)
        self.x += 1
        return {"score": score}

小技巧

session.report 不能在 Trainable 类中使用。

了解更多关于 Trainables 的细节查看我们的示例。接下来,让我们更详细地看看你传递给 trainables 的 config 字典是什么。

调整搜索空间#

要优化你的 超参数,你需要定义一个 搜索空间。搜索空间定义了超参数的有效值,并可以指定这些值是如何采样的(例如,从均匀分布或正态分布中)。

Tune 提供了多种功能来定义搜索空间和采样方法。您可以在这里找到这些搜索空间定义的文档

以下是一个涵盖所有搜索空间函数的示例。同样,这里是所有这些函数的完整解释

config = {
    "uniform": tune.uniform(-5, -1),  # Uniform float between -5 and -1
    "quniform": tune.quniform(3.2, 5.4, 0.2),  # Round to multiples of 0.2
    "loguniform": tune.loguniform(1e-4, 1e-1),  # Uniform float in log space
    "qloguniform": tune.qloguniform(1e-4, 1e-1, 5e-5),  # Round to multiples of 0.00005
    "randn": tune.randn(10, 2),  # Normal distribution with mean 10 and sd 2
    "qrandn": tune.qrandn(10, 2, 0.2),  # Round to multiples of 0.2
    "randint": tune.randint(-9, 15),  # Random integer between -9 and 15
    "qrandint": tune.qrandint(-21, 12, 3),  # Round to multiples of 3 (includes 12)
    "lograndint": tune.lograndint(1, 10),  # Random integer in log space
    "qlograndint": tune.qlograndint(1, 10, 2),  # Round to multiples of 2
    "choice": tune.choice(["a", "b", "c"]),  # Choose one of these options uniformly
    "func": tune.sample_from(
        lambda spec: spec.config.uniform * 0.01
    ),  # Depends on other value
    "grid": tune.grid_search([32, 64, 128]),  # Search over all these values
}

调优试验#

您可以使用 Tuner.fit 来执行和管理超参数调优并生成您的 trials。至少,您的 Tuner 调用需要将一个可训练对象作为第一个参数,并使用一个 param_space 字典来定义搜索空间。

Tuner.fit() 函数还提供了许多功能,例如 日志记录检查点提前停止。在示例中,最小化 a (x ** 2) + b,一个简单的 Tune 运行,带有 ab 的简单搜索空间,看起来像这样:

# Pass in a Trainable class or function, along with a search space "config".
tuner = tune.Tuner(trainable, param_space={"a": 2, "b": 4})
tuner.fit()

Tuner.fit 会根据其参数生成一些超参数配置,并将它们封装到 Trial 对象 中。

试验包含大量信息。例如,你可以使用 (trial.config) 获取超参数配置,使用 (trial.trial_id) 获取试验ID,使用 (resources_per_trialtrial.placement_group_factory) 获取试验的资源规格,以及其他许多值。

默认情况下,Tuner.fit 将执行直到所有试验停止或出错。以下是一个试验运行的示例输出:

== Status ==
Memory usage on this node: 11.4/16.0 GiB
Using FIFO scheduling algorithm.
Resources requested: 1/12 CPUs, 0/0 GPUs, 0.0/3.17 GiB heap, 0.0/1.07 GiB objects
Result logdir: /Users/foo/ray_results/myexp
Number of trials: 1 (1 RUNNING)
+----------------------+----------+---------------------+-----------+--------+--------+----------------+-------+
| Trial name           | status   | loc                 |         a |      b |  score | total time (s) |  iter |
|----------------------+----------+---------------------+-----------+--------+--------+----------------+-------|
| Trainable_a826033a | RUNNING  | 10.234.98.164:31115 | 0.303706  | 0.0761 | 0.1289 |        7.54952 |    15 |
+----------------------+----------+---------------------+-----------+--------+--------+----------------+-------+

你也可以通过指定样本数量 (num_samples) 轻松运行10次试验。Tune 自动 确定将有多少次试验并行运行。请注意,除了样本数量,你还可以通过 time_budget_s 指定时间预算(以秒为单位),如果你设置 num_samples=-1

tuner = tune.Tuner(
    trainable, param_space={"a": 2, "b": 4}, tune_config=tune.TuneConfig(num_samples=10)
)
tuner.fit()

最后,你可以使用更有趣的搜索空间通过 Tune 的 搜索空间 API 来优化你的超参数,比如使用随机采样或网格搜索。以下是一个在 [0, 1] 之间均匀采样 ab 的例子:

space = {"a": tune.uniform(0, 1), "b": tune.uniform(0, 1)}
tuner = tune.Tuner(
    trainable, param_space=space, tune_config=tune.TuneConfig(num_samples=10)
)
tuner.fit()

要了解更多关于配置 Tune 运行的各种方法,请查看 Tuner API 参考

调整搜索算法#

要优化训练过程的超参数,您可以使用 搜索算法 来建议超参数配置。如果您没有指定搜索算法,Tune 将默认使用随机搜索,这可以为您提供超参数优化的良好起点。

例如,要通过 bayesian-optimization 包使用 Tune 进行简单的贝叶斯优化(确保首先运行 pip install bayesian-optimization),我们可以使用 BayesOptSearch 定义一个 algo。只需将 search_alg 参数传递给 tune.TuneConfig,该参数由 Tuner 接收:

from ray.tune.search.bayesopt import BayesOptSearch
from ray import train

# Define the search space
search_space = {"a": tune.uniform(0, 1), "b": tune.uniform(0, 20)}

algo = BayesOptSearch(random_search_steps=4)

tuner = tune.Tuner(
    trainable,
    tune_config=tune.TuneConfig(
        metric="score",
        mode="min",
        search_alg=algo,
    ),
    run_config=train.RunConfig(stop={"training_iteration": 20}),
    param_space=search_space,
)
tuner.fit()

Tune 有与许多流行的 优化 库集成的搜索算法,例如 HyperOptOptuna。Tune 会自动将提供的搜索空间转换为搜索算法和底层库所期望的搜索空间。有关更多详细信息,请参阅 搜索算法API文档

以下是 Tune 中所有可用搜索算法的概述:

搜索算法

摘要

网站

代码示例

随机搜索/网格搜索

随机搜索/网格搜索

tune_basic_example

AxSearch

贝叶斯/强盗优化

[Ax]

AX Example

HyperOptSearch

树-Parzen 估计器

[HyperOpt]

使用HyperOpt运行Tune实验

贝叶斯优化搜索

贝叶斯优化

[贝叶斯优化]

BayesOpt Example

TuneBOHB

贝叶斯优化/超带

[BOHB]

BOHB Example

NevergradSearch

无梯度优化

[Nevergrad]

Nevergrad Example

OptunaSearch

Optuna 搜索算法

[Optuna]

使用Optuna运行Tune实验

备注

Tune 的试验调度器 不同,Tune 搜索算法不能影响或停止训练过程。然而,你可以将它们一起使用,以提前停止对不良试验的评估。

如果你想实现自己的搜索算法,接口很容易实现,你可以 在这里阅读说明

Tune 还提供了一些有用的工具,可以与搜索算法一起使用:

请注意,在上面的例子中,我们告诉 Tune 在 20 次训练迭代后 停止。这种通过显式规则停止试验的方法很有用,但在许多情况下,我们可以通过 调度器 做得更好。

调整调度器#

为了使您的训练过程更加高效,您可以使用 试验调度器。例如,在我们 trainable 示例中,通过训练循环最小化函数时,我们使用了 session.report()。这报告了 增量 结果,给定由搜索算法选择的超参数配置。基于这些报告的结果,Tune 调度器可以决定是否提前停止试验。如果您没有指定调度器,Tune 将默认使用先进先出(FIFO)调度器,它只是按照选择的顺序传递由您的搜索算法选择的试验,并且不执行任何提前停止。

简而言之,调度器可以停止、暂停或调整正在运行的试验的超参数,从而可能使您的超参数调优过程更快。与搜索算法不同,试验调度器 不选择要评估的超参数配置。

以下是一个使用所谓的 HyperBand 调度器来调整实验的快速示例。所有调度器都需要一个 metric 参数,这是由你的可训练对象报告的值。根据你提供的 modemetric 将被最大化或最小化。要使用调度器,只需将 scheduler 参数传递给 tune.TuneConfig,它由 Tuner 接收:

from ray.tune.schedulers import HyperBandScheduler

# Create HyperBand scheduler and minimize the score
hyperband = HyperBandScheduler(metric="score", mode="max")

config = {"a": tune.uniform(0, 1), "b": tune.uniform(0, 1)}

tuner = tune.Tuner(
    trainable,
    tune_config=tune.TuneConfig(
        num_samples=20,
        scheduler=hyperband,
    ),
    param_space=config,
)
tuner.fit()

Tune 包含了早期停止算法的分布式实现,例如 中位数停止规则HyperBandASHA。Tune 还包括 基于种群的训练 (PBT)基于种群的 bandits (PB2) 的分布式实现。

小技巧

最简单的调度器是 ASHAScheduler,它会积极地终止表现不佳的试验。

在使用调度器时,您可能会遇到兼容性问题,如下面的兼容性矩阵所示。某些调度器不能与搜索算法一起使用,某些调度器要求您实现 检查点

调度器可以在调优过程中动态更改试验的资源需求。这是在 ResourceChangingScheduler 中实现的,它可以包装任何其他调度器。

调度器兼容性矩阵#

调度器

需要检查点吗?

SearchAlg 兼容?

示例

ASHA

链接

中位数停止规则

链接

HyperBand

链接

BOHB

仅限 TuneBOHB

链接

基于种群的训练

不兼容

链接

基于种群的Bandits

不兼容

基本示例PPO示例

了解更多关于试验调度器的信息,请参阅 调度器API文档

调整 ResultGrid#

Tuner.fit() 返回一个 ResultGrid 对象,该对象具有可用于分析训练的方法。以下示例展示了如何从 ResultGrid 对象访问各种指标,例如最佳可用试验,或该试验的最佳超参数配置:

tuner = tune.Tuner(
    trainable,
    tune_config=tune.TuneConfig(
        metric="score",
        mode="min",
        search_alg=BayesOptSearch(random_search_steps=4),
    ),
    run_config=train.RunConfig(
        stop={"training_iteration": 20},
    ),
    param_space=config,
)
results = tuner.fit()

best_result = results.get_best_result()  # Get best result object
best_config = best_result.config  # Get best trial's hyperparameters
best_logdir = best_result.path  # Get best trial's result directory
best_checkpoint = best_result.checkpoint  # Get best trial's best checkpoint
best_metrics = best_result.metrics  # Get best trial's last results
best_result_df = best_result.metrics_dataframe  # Get best result as pandas dataframe

此对象还可以将所有训练运行作为数据框检索,使您能够对结果进行即席数据分析。

# Get a dataframe with the last results for each trial
df_results = results.get_dataframe()

# Get a dataframe of results for a specific score or mode
df = results.get_dataframe(filter_metric="score", filter_mode="max")

更多使用示例请参见 结果分析用户指南

下一步是什么?#

既然你对 Tune 有了基本的了解,请查看:

  • 用户指南:使用 Tune 与您首选的机器学习库的教程。

  • Ray Tune 示例: 使用您首选的机器学习库与Tune的端到端示例和模板。

  • 开始使用 Ray Tune:一个简单的教程,指导你完成设置 Tune 实验的过程。

还有其他问题或疑问吗?#

您可以通过以下渠道提出问题、发布问题或反馈:

  1. 讨论板: 用于 关于Ray使用的疑问功能请求

  2. GitHub Issues: 用于 错误报告

  3. Ray Slack: 用于 联系 Ray 维护者

  4. StackOverflow: 使用 [ray] 标签 关于 Ray 的问题