news 2026/9/20 23:11:46

Ray Tune 搜索空间(Search Space)实战指南:网格搜索、随机采样与条件搜索空间

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Ray Tune 搜索空间(Search Space)实战指南:网格搜索、随机采样与条件搜索空间

Ray Tune 搜索空间(Search Space)实战指南:网格搜索、随机采样与条件搜索空间

【免费下载链接】rayRay is an AI compute engine. Ray consists of a core distributed runtime and a set of AI Libraries for accelerating ML workloads.项目地址: https://gitcode.com/gh_mirrors/ra/ray

本文以 Ray Tune 的官方教程文档 tune-search-spaces.rst 为主体,系统讲解如何通过tune.Tuner(param_space=...)原生接口定义超参数搜索空间:既可以用tune.grid_search做确定性网格搜索,也可以用tune.uniformtune.choicetune.sample_from等随机采样原语描述概率分布,还可以借助sample_fromconfig参数构建"一个超参数取值依赖另一个超参数"的条件搜索空间。读完本文,你将掌握 Tune 搜索空间的全部核心原语、num_samples与网格组合的计数规则,以及搜索算法兼容性等实战注意事项。

一、搜索空间概述:从Tuner(param_space=...)开始

Ray Tune 提供了原生(native)的搜索空间描述接口。与旧式tune.run(config=...)不同,新式 API 统一通过Tuner构造器的param_space参数传入配置:

from ray import tune tuner = tune.Tuner( trainable, param_space={"bar": tune.grid_search([True, False])}) results = tuner.fit()

param_space是一个普通 Python 字典(Dict[str, Any]),其取值可以是三类内容:

  1. 确定性网格:用tune.grid_search([...])列出所有取值,Tune 会逐一评估每一种组合;
  2. 随机分布:用tune.choicetune.uniformtune.sample_from等原语声明概率分布,每次 trial 随机采样;
  3. 常量:直接写普通值(如"const": "hello"),这类键在每个 trial 中保持不变,可以与其他搜索原语混用。

从源码看,Tuner.__init__param_space一路透传给内部的实验配置(见 tuner.py),而真正"解析并生成 trial 配置"的是变体生成器(variant generator),其入口为generate_variants()(见 variant_generator.py)。它会将param_space中的网格项、分布项与常量项分开处理:网格项做笛卡尔积组合,分布项按采样器逐个求值,最终产出每个 trial 的完整config字典。

二、tune.grid_search:确定性网格搜索

grid_search是 Tune 搜索空间中最基础的原语,其实现位于 variant_generator.py:

@PublicAPI(stability="beta") def grid_search(values: Iterable) -> Dict[str, Iterable]: return {"grid_search": values}

它返回一个{"grid_search": values}形式的字典,Tune 的变体生成器据此识别出网格项。grid_search有两个关键语义:

  • 保证采样:网格中的每个值都会被实际评估,不会像随机采样那样可能漏掉某个取值;
  • 笛卡尔积组合:如果param_space中有多个grid_search变量,它们会按组合乘积展开——"x": grid_search([1, 2, 3])"y": grid_search(["a", "b", "c"])组合后共产生 3 × 3 = 9 种配置。

2.1 网格组合与num_samples的计数规则

文档中的一组示例清晰展示了num_samples与网格的组合计数规则:

# 4 种不同配置:num_samples=1 × 4 个网格值 tuner = tune.Tuner(trainable, tune_config=tune.TuneConfig(num_samples=1), param_space={"x": tune.grid_search([1, 2, 3, 4])}) tuner.fit() # 3 种不同配置 tuner = tune.Tuner(trainable, tune_config=tune.TuneConfig(num_samples=1), param_space={"x": tune.grid_search([1, 2, 3])}) tuner.fit() # 6 种不同配置:num_samples=2 会重复整个网格 2 次 tuner = tune.Tuner(trainable, tune_config=tune.TuneConfig(num_samples=2), param_space={"x": tune.grid_search([1, 2, 3])}) tuner.fit() # 9 种不同配置:3 × 3 网格 tuner = tune.Tuner(trainable, tune_config=tune.TuneConfig(num_samples=1), param_space={ "x": tune.grid_search([1, 2, 3]), "y": tune.grid_search(["a", "b", "c"])}) tuner.fit() # 18 种不同配置:3 × 3 网格重复 2 次 tuner = tune.Tuner(trainable, tune_config=tune.TuneConfig(num_samples=2), param_space={ "x": tune.grid_search([1, 2, 3]), "y": tune.grid_search(["a", "b", "c"])}) tuner.fit() # 45 种不同配置:3 × 3 网格重复 5 次 tuner = tune.Tuner(trainable, tune_config=tune.TuneConfig(num_samples=5), param_space={ "x": tune.grid_search([1, 2, 3]), "y": tune.grid_search(["a", "b", "c"])}) tuner.fit()

这些计数的源码依据在 variant_generator.py 的_count_spec_samples()中:网格变量数grid_count是各网格项长度的乘积,总 trial 数 =num_samples × grid_count。也就是说,num_samples控制的是"整个网格重复多少次",而不是"每个网格值采样多少次"。

2.2 网格与随机采样混合

网格搜索与随机采样原语是**可互操作(inter-operable)**的,二者既可以独立使用,也可以在同一param_space中组合:

# 6 种不同配置:x 随机采样,y 走 3 值网格,重复 2 次 tuner = tune.Tuner(trainable, tune_config=tune.TuneConfig(num_samples=2), param_space={ "x": tune.sample_from(lambda _: np.random.uniform(100)), "y": tune.grid_search(["a", "b", "c"])}) tuner.fit()

注意此时x的随机采样是针对每个网格组合分别重新采样的:网格的每种组合都会触发一次sample_from求值,从而产生不同随机取值。

三、随机采样原语:从均匀分布到自定义函数

除了grid_search,Tune 还提供了一整套随机采样原语。它们的实现集中在 sample.py,并在此文件的 docstring 与 search_space.rst 中给出了完整的语义说明。常用原语一览:

原语签名语义源码位置
tune.uniform(lower, upper)浮点[lower, upper]上均匀采样,等价于np.random.uniform(lower, upper)sample.py
tune.quniform(lower, upper, q)浮点均匀采样后量化到q的整数倍sample.py
tune.loguniform(lower, upper)浮点在对数空间均匀采样,适合跨越多个数量级的参数(如学习率 1e-4 ~ 1e-2)sample.py
tune.qloguniform(lower, upper, q)浮点对数空间采样 + 量化sample.py
tune.randn(mean=0.0, sd=1.0)浮点正态分布采样sample.py
tune.qrandn(mean, sd, q)浮点正态分布采样 + 量化sample.py
tune.randint(lower, upper)整数[lower, upper)上均匀采样(lower含、upper不含)sample.py
tune.qrandint(lower, upper, q)整数整数均匀采样后量化(q=1upper不含,其余情况含)sample.py
tune.lograndint(lower, upper)整数对数空间整数采样sample.py
tune.qlograndint(lower, upper, q)整数对数空间整数采样 + 量化sample.py
tune.choice(categories)类别从候选列表中均匀挑选一个,等价于np.random.choicesample.py
tune.sample_from(func)任意调用自定义函数生成取值,支持条件搜索空间sample.py

3.1 一个完整的分布组合示例

参考 search_space.rst,一个覆盖全部常见分布的param_space长这样:

config = { # 在 -5.0 ~ -1.0 上均匀采样浮点数 "uniform": tune.uniform(-5, -1), # 在 3.2 ~ 5.4 上均匀采样,并四舍五入到 0.2 的倍数 "quniform": tune.quniform(3.2, 5.4, 0.2), # 在 0.0001 ~ 0.01 上于对数空间均匀采样 "loguniform": tune.loguniform(1e-4, 1e-2), # 对数空间采样,并量化到 0.00005 的倍数 "qloguniform": tune.qloguniform(1e-4, 1e-1, 5e-5), # 均值 10、标准差 2 的正态分布 "randn": tune.randn(10, 2), # 正态分布采样,量化到 0.2 的倍数 "qrandn": tune.qrandn(10, 2, 0.2), # 在 -9(含)~ 15(不含)上均匀采样整数 "randint": tune.randint(-9, 15), # 在 -21 ~ 12 之间采样 3 的倍数(含 12) "qrandint": tune.qrandint(-21, 12, 3), # 在 1(含)~ 10(不含)上于对数空间采样整数 "lograndint": tune.lograndint(1, 10), # 对数空间整数采样,量化到 2 的倍数 "qlograndint": tune.qlograndint(1, 10, 2), # 从 ["a", "b", "c"] 中等概率选一个 "choice": tune.choice(["a", "b", "c"]), # 自定义函数采样,可引用本搜索空间中的其他键 "func": tune.sample_from(lambda config: config["uniform"] * 0.01), # 网格搜索,每个值会被采样 num_samples 次 "grid": tune.grid_search([32, 64, 128]), }

其中量化类原语(quniformqrandint等)的底层实现通过Domain.quantized(q)完成:采样后按round(value / q)就近取整,因此量化会把上界变为包含(详见 sample.py 中Float/Integer域的quantized逻辑)。这些分布原语本质上都返回一个Domain对象(FloatIntegerCategorical),它们携带取值边界与采样器(UniformLogUniformNormalGrid等),由Domain.sample()统一驱动采样。

四、num_samples的完整语义

num_samplesTuneConfig的核心字段,其源码定义见 tune_config.py:

@dataclass class TuneConfig: ... num_samples: int = 1

官方注释明确其语义为:

Number of times to sample from the hyperparameter space. Defaults to 1. Ifgrid_searchis provided as an argument, the grid will be repeatednum_samplestimes. If this is -1, (virtually) infinite samples are generated until a stopping condition is met.

即:

  • 默认值为 1
  • 若搜索空间中含grid_search,则同一个网格会重复num_samples(网格组合数 ×num_samples= 总 trial 数);
  • 设为-1时(近乎)无限采样,直到满足停止条件(如time_budget_s时间预算或自定义 Stopper);
  • 可搭配time_budget_smax_concurrent_trials(最大并发 trial 数,通过ConcurrencyLimiter包装搜索算法实现)等字段一起使用。

num_samples的完整示例(来自原文档,其中网格为 3×3,重复 10 次,共 90 个 trial):

tuner = tune.Tuner( my_trainable, run_config=tune.RunConfig(name="my_trainable"), # num_samples 会把整个配置重复 10 次 tune_config=tune.TuneConfig(num_samples=10), param_space={ # ``sample_from`` 创建一个生成器,每个 trial 调用一次 lambda "alpha": tune.sample_from(lambda _: np.random.uniform(100)), # ``sample_from`` 也支持"条件搜索空间" "beta": tune.sample_from(lambda config: config["alpha"] * np.random.normal()), "nn_layers": [ # tune.grid_search 会保证所有取值都被评估 tune.grid_search([16, 64, 256]), tune.grid_search([16, 64, 256]), ], }, ) tuner.fit()

这里nn_layers是一个列表,其中两项都是grid_search([16, 64, 256]),网格组合数为 3 × 3 = 9;配合num_samples=10,总共生成 90 个 trial,每个 trial 中alphabeta都会被重新随机采样。这正体现了前文所说的核心规则:网格被整体重复num_samples次,而随机分布在每次重复中重新采样

五、自定义与条件搜索空间:tune.sample_from

现实中的超参数搜索常常遇到"awkward search spaces"——即某个超参数的最优取值范围依赖于另一个超参数。例如隐藏层宽度取决于特征维度、dropout 范围取决于网络深度等。此时应使用tune.sample_from(func)提供自定义可调用函数来生成取值。

sample_from的源码实现非常简单(见 sample.py):

@PublicAPI def sample_from(func: Callable[[Dict], Any]): """Specify that tune should sample configuration values from this function. ...""" return Function(func)

它返回一个Function域对象。关键在于func的约定:它接收一个config字典,其中包含该 trial 已经采样好的其他超参数取值。正因为可以读取config中的其他键,sample_from成为构建条件分布(conditional distributions)的利器。

5.1 基础条件采样

tuner = tune.Tuner( ..., param_space={ # 一个随机函数 "alpha": tune.sample_from(lambda _: np.random.uniform(100)), # 利用 config 字典访问其他超参数 "beta": tune.sample_from(lambda config: config["alpha"] * np.random.normal()) } ) tuner.fit()

这里beta的取值 =alpha× 一个标准正态噪声,beta的分布完全依赖于alpha的采样结果。lambda 中通过config["alpha"]引用另一个键,从而表达"参数间依赖"。

5.2 网格 + 条件采样的完整组合

原文档给出了一个综合示例:两个嵌套参数的网格搜索 + 两个 lambda 的随机采样,共生成 9 个不同 trial(3 × 3 网格),且beta的值依赖alpha

tuner = tune.Tuner( my_trainable, run_config=RunConfig(name="my_trainable"), param_space={ "alpha": tune.sample_from(lambda _: np.random.uniform(100)), "beta": tune.sample_from(lambda config: config["alpha"] * np.random.normal()), "nn_layers": [ tune.grid_search([16, 64, 256]), tune.grid_search([16, 64, 256]), ], } )

nn_layers为两元素列表(对应两层网络各 16/64/256 三种宽度),网格展开后每个 trial 的alphabeta都重新采样。这种"网格确定结构 + lambda 随机填值"的混合写法是 Tune 中最常用的搜索空间组织方式。

六、使用注意事项

6.1 搜索算法兼容性

原文档开篇即给出重要警告(caution):

If you use a SearchAlgorithm, you may not be able to specify lambdas or grid search with this interface, as some search algorithms may not be compatible.

也就是说,一旦为TuneConfig指定了外部搜索算法(SearchAlgorithm,如贝叶斯优化、进化算法等),sample_from的 lambda 和grid_search可能不被支持,因为这类算法要求把搜索空间转换成自己定义的受限结构。同样地,search_space.rst 也强调:"Not all Search Algorithms support all distributions. In particular,tune.sample_fromandtune.grid_searchare often unsupported. The default BasicVariantGenerator supports all distributions."

默认的BasicVariantGenerator(即不指定search_alg时的默认变体生成器)支持全部搜索空间原语。若你依赖默认行为,无需担心兼容性问题。

6.2 条件搜索空间:仅部分算法支持

原文档进一步说明:

This format is not supported by every SearchAlgorithm, and only some SearchAlgorithms, like HyperOpt and Optuna, handle conditional search spaces at all.

  • HyperOpt:要使用条件搜索空间,需要借助 Hyperopt 自身的搜索空间 DSL(hp.choicehp.pchoice等定义条件分支的写法);
  • Optuna:支持通过其 define-by-run 接口表达条件搜索空间(在objective函数内动态定义trial.suggest_*,配合Tunerparam_space使用)。

因此,如果你的调优方案依赖条件搜索空间,务必确认所选搜索算法是否原生支持;否则应回退到默认变体生成器。

6.3 性能:避免在搜索空间中传递大对象

原文档给出 tip:

Avoid passing large objects as values in the search space, as that will incur a performance overhead.

不要把大型对象(如整个数据集、大模型权重、预训练词表等)直接作为param_space的值,否则每次 trial 生成与序列化都会产生显著开销。推荐做法:

  • 使用tune.with_parameters将大对象作为不可变参数随 trainable 传递;
  • 或者让 trainable 从磁盘 / 云存储加载大对象(需保证所有节点都能访问相应文件)。

6.4 关于文档示例中randn的说明

原文档演示num_samples时写有"y": tune.randn([0, 1, 2])的示例。需要指出,tune.randn的真实签名为randn(mean: float = 0.0, sd: float = 1.0)(见 sample.py),它返回一个正态分布域而非取值列表;若要表达离散候选,应使用tune.choicetune.grid_search。该示例属于文档中的示意性写法,实际使用时请以本节给出的签名为准。

七、小结

Ray Tune 的搜索空间体系可以用一条主线概括:Tuner(param_space=...)是唯一入口,grid_search与随机分布原语是两类基本构件,num_samples决定重复次数,sample_from提供表达参数依赖的终极灵活性

需求推荐写法
穷举少量离散候选tune.grid_search([...])
连续区间均匀采样tune.uniform(lower, upper)
跨数量级采样(学习率等)tune.loguniform(1e-4, 1e-2)
离散类别随机选择tune.choice([...])
整数范围采样tune.randint(lower, upper)
参数间存在依赖tune.sample_from(lambda config: ...)
控制总 trial 数TuneConfig(num_samples=N),网格重复 N 次

理解num_samples与网格的笛卡尔积组合规则(总 trial 数 = 网格组合数 ×num_samples),并牢记"lambda/grid 与部分搜索算法不兼容""避免大对象入搜索空间"两条经验,你就能在设计超参数调优实验时精确掌控 trial 数量与搜索策略。相关代码与文档可继续参阅 tune-search-spaces.rst、search_space.rst、sample.py 与 variant_generator.py。

【免费下载链接】rayRay is an AI compute engine. Ray consists of a core distributed runtime and a set of AI Libraries for accelerating ML workloads.项目地址: https://gitcode.com/gh_mirrors/ra/ray

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/20 23:08:34

CTF实战:如何精准判断Vigenère密钥长度并自动化解密

CTF圈里做过古典密码题的,基本绕不开 Vigenre 这个坎。攻防世界(XCTF 免费题库)里这道 “how_many_Vigenre” 乍看是个入门级的维吉尼亚密码题,但真正上手之后你会发现,它考的不是“会不会用工具解 Vigenre”&#xff…

作者头像 李华
网站建设 2026/9/20 23:06:38

教务管理学生成绩分析可视化系统:从数据清洗到图表报告

简介:面向高校教务处、任课教师及教务系统开发者的学生成绩分析管理项目,定位在成绩数据的录入、存储、多维度分析与可视化呈现,解决传统教务管理中成绩分散、统计繁琐、决策缺乏直观依据等问题。压缩包内共646个文件,约82.55MB&a…

作者头像 李华
网站建设 2026/9/20 23:05:16

昇腾Atlas 300V部署YOLOv5全流程解析:从环境搭建到性能调优

做边缘AI推理这一年多,我前前后后在昇腾的卡上折腾了不少时间,从第一次连驱动都装不利索,到后来把YOLOv5、YOLOv8在Atlas 300V 24G上跑得比较顺手,中间踩过的坑确实值得写一写。网上关于这张卡的资料不少,但大多是你抄…

作者头像 李华