Ray Tune 的关键概念#

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

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

../_images/tune_flow.png

Ray Tune 可训练对象#

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

考虑一个优化简单目标函数(如a * (x ** 2) + b)的示例,其中ab是我们想要调优以最小化目标的超参数。由于目标函数还包含变量x,我们需要针对不同的x值进行测试。给定abx的具体选择,我们可以评估目标函数并获得需要最小化的分数 (score)

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

from ray import train


def objective(x, a, b):  # Define an objective function.
    return a * (x**2) + 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"])

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


请注意,我们在训练循环中使用session.report(...)来报告中间分数,这在许多机器学习任务中非常有用。如果您只想在此循环外部报告最终分数,只需在trainable函数的末尾使用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 类中使用。

在此了解可训练对象 (Trainable)的更多详细信息,并查看我们的示例。接下来,让我们仔细看看您传递给可训练对象的config字典是什么。

Tune 搜索空间#

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

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
}

Tune 试验 (Trials)#

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

通过调用Tuner.fit()函数可以执行和管理超参数调优,并生成您的trials。最低限度地,您的Tuner调用需要将一个可训练对象作为第一个参数,以及一个param_space字典来定义搜索空间。Tuner.fit()函数还提供了许多功能,例如日志记录检查点提前停止。在最小化a (x ** 2) + b的示例中,使用简单的ab搜索空间的简单 Tune 运行如下所示

# 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) 获取超参数配置,试验 ID (trial.trial_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 来优化超参数,例如使用随机采样或网格搜索。以下是针对 ab[0, 1] 之间进行均匀采样的示例

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 搜索算法#

为了优化训练过程的超参数,您可以使用搜索算法 (Search Algorithm),它会提出超参数配置。如果您不指定搜索算法,Tune 默认将使用随机搜索,这可以为您的超参数优化提供一个良好的起点。

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

from ray.tune.search.bayesopt import BayesOptSearch

# 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=tune.RunConfig(stop={"training_iteration": 20}),
    param_space=search_space,
)
tuner.fit()

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

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

搜索算法

摘要

网站

代码示例

随机搜索/网格搜索

随机搜索/网格搜索

tune_basic_example

AxSearch

贝叶斯/强盗优化

[Ax]

AX 示例

HyperOptSearch

Tree-Parzen 估计器

[HyperOpt]

使用 HyperOpt 运行 Tune 实验

BayesOptSearch

贝叶斯优化

[BayesianOptimization]

BayesOpt 示例

TuneBOHB

贝叶斯优化/HyperBand

[BOHB]

BOHB 示例

NevergradSearch

无梯度优化

[Nevergrad]

Nevergrad 示例

OptunaSearch

Optuna 搜索算法

[Optuna]

使用 Optuna 运行 Tune 实验

注意

Tune 的试验调度器 (Trial Schedulers)不同,Tune 搜索算法不能影响或停止训练过程。但是,您可以将它们一起使用来提前停止对不良试验的评估。

如果您想实现自己的搜索算法,接口非常易于实现,您可以在此阅读说明

Tune 还提供了可与搜索算法一起使用的实用工具

请注意,在上面的示例中,我们告诉 Tune 在 20 次训练迭代后停止。这种使用明确规则停止试验的方式很有用,但在很多情况下,使用调度器效果会更好。

Tune 调度器 (Schedulers)#

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

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

以下是使用所谓的HyperBand调度器调优实验的快速示例。所有调度器都接收一个指标 (metric),该指标是您的可训练对象报告的值。指标然后根据您提供的模式 (mode)进行最大化或最小化。要使用调度器,只需将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 包含了早期停止算法的分布式实现,例如中位数停止规则 (Median Stopping Rule)HyperBandASHA。Tune 还包括基于种群训练 (Population Based Training, PBT)基于种群的强盗算法 (Population Based Bandits, PB2) 的分布式实现。

提示

最容易上手的调度器是ASHAScheduler,它会积极终止表现不佳的试验。

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

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

调度器兼容性矩阵#

调度器

需要检查点?

兼容 SearchAlg?

示例

ASHA

链接

中位数停止规则

链接

HyperBand

链接

BOHB

仅 TuneBOHB

链接

基于种群训练

不兼容

链接

基于种群的强盗算法

不兼容

基本示例, PPO 示例

调度器 API 文档中了解有关试验调度器的更多信息。

Tune 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=tune.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 的提问