高效超参数优化(HPO)使用 EvoX#
在本章中,我们将探讨如何使用 EvoX 进行超参数优化 (HPO)。
HPO 在许多机器学习任务中起着至关重要的作用,但由于其高计算成本(有时需要几天的处理时间)以及部署过程中涉及的挑战,常常被忽视。
使用 EvoX,我们可以通过 HPOProblemWrapper
简化 HPO 部署,并通过利用 vmap
方法和 GPU 加速实现高效计算。
将工作流转化为问题#

将HPO与EvoX一起部署的关键是使用HPOProblemWrapper
将workflows
转换为problems
。一旦转换完成,我们可以将workflows
视为标准的problems
。'HPO问题'的输入由超参数组成,输出是评估指标。
关键组件 -- `HPOProblemWrapper#
为了确保 HPOProblemWrapper
识别超参数,我们需要使用 Parameter
来包装它们。通过这个简单的步骤,超参数将被自动识别。
class ExampleAlgorithm(Algorithm):
def __init__(self,...):
self.omega = Parameter([1.0, 2.0]) # wrap the hyper-parameters with `Parameter`
self.beta = Parameter(0.1)
pass
def step(self):
# run algorithm step depending on the value of self.omega and self.beta
pass
利用 `HPOFitnessMonitor#
我们提供了一个HPOFitnessMonitor
,支持计算多目标问题的“IGD”和“HV”指标,以及单目标问题的最小值。
请注意,HPOFitnessMonitor
是一个为 HPO 问题设计的基本监视器。您还可以使用使用自定义算法部署 HPO中概述的方法灵活地创建自己的自定义监视器。
一个简单的示例#
在这里,我们将演示一个使用 EvoX 进行 HPO 的简单示例。具体来说,我们将使用 PSO 算法来优化 PSO 算法的超参数,以解决球体问题。
请注意,本章仅提供 HPO 部署的简要概述。有关更详细的指南,请参阅使用自定义 Algorithms 部署 HPO。
要开始,让我们导入必要的模块。
import torch
from evox.algorithms.pso_variants.pso import PSO
from evox.core import Problem, jit_class
from evox.problems.hpo_wrapper import HPOFitnessMonitor, HPOProblemWrapper
from evox.workflows import EvalMonitor, StdWorkflow
接下来,我们定义一个简单的 Sphere 问题。
@jit_class
class Sphere(Problem):
def __init__(self):
super().__init__()
def evaluate(self, x: torch.Tensor):
return (x * x).sum(-1)
接下来,我们可以使用 StdWorkflow
来包装 problem
、algorithm
和 monitor
。然后我们使用 HPOProblemWrapper
将 StdWorkflow
转换为一个 HPO 问题。
# the inner loop is a PSO algorithm with a population size of 50
torch.set_default_device("cuda" if torch.cuda.is_available() else "cpu")
inner_algo = PSO(50, -10 * torch.ones(10), 10 * torch.ones(10))
inner_prob = Sphere()
inner_monitor = HPOFitnessMonitor()
inner_monitor.setup()
inner_workflow = StdWorkflow()
inner_workflow.setup(inner_algo, inner_prob, monitor=inner_monitor)
# Transform the inner workflow to an HPO problem
hpo_prob = HPOProblemWrapper(iterations=30, num_instances=128, workflow=inner_workflow, copy_init_state=True)
HPOProblemWrapper
接受 4 个参数:
iterations
:在优化过程中要执行的迭代次数。num_instances
: 在优化过程中的并行执行实例数量。workflow
: 在优化过程中使用的工作流。必须由jit_class
包装。copy_init_state
: 是否为每次评估复制工作流的初始状态。默认为True
。如果你的工作流包含对初始状态中的张量进行原地修改的操作,则应将其设置为True
。否则,可以将其设置为False
以节省内存。
我们可以验证 HPOProblemWrapper
是否正确识别我们定义的超参数。由于在这5个实例中没有对超参数进行修改,它们在所有实例中应该保持一致。
params = hpo_prob.get_init_params()
print("init params:\n", params)
init params:
{'self.algorithm.w': Parameter containing:
tensor([0.6000, 0.6000, 0.6000, 0.6000, 0.6000, 0.6000, 0.6000, 0.6000, 0.6000,
0.6000, 0.6000, 0.6000, 0.6000, 0.6000, 0.6000, 0.6000, 0.6000, 0.6000,
0.6000, 0.6000, 0.6000, 0.6000, 0.6000, 0.6000, 0.6000, 0.6000, 0.6000,
0.6000, 0.6000, 0.6000, 0.6000, 0.6000, 0.6000, 0.6000, 0.6000, 0.6000,
0.6000, 0.6000, 0.6000, 0.6000, 0.6000, 0.6000, 0.6000, 0.6000, 0.6000,
0.6000, 0.6000, 0.6000, 0.6000, 0.6000, 0.6000, 0.6000, 0.6000, 0.6000,
0.6000, 0.6000, 0.6000, 0.6000, 0.6000, 0.6000, 0.6000, 0.6000, 0.6000,
0.6000, 0.6000, 0.6000, 0.6000, 0.6000, 0.6000, 0.6000, 0.6000, 0.6000,
0.6000, 0.6000, 0.6000, 0.6000, 0.6000, 0.6000, 0.6000, 0.6000, 0.6000,
0.6000, 0.6000, 0.6000, 0.6000, 0.6000, 0.6000, 0.6000, 0.6000, 0.6000,
0.6000, 0.6000, 0.6000, 0.6000, 0.6000, 0.6000, 0.6000, 0.6000, 0.6000,
0.6000, 0.6000, 0.6000, 0.6000, 0.6000, 0.6000, 0.6000, 0.6000, 0.6000,
0.6000, 0.6000, 0.6000, 0.6000, 0.6000, 0.6000, 0.6000, 0.6000, 0.6000,
0.6000, 0.6000, 0.6000, 0.6000, 0.6000, 0.6000, 0.6000, 0.6000, 0.6000,
0.6000, 0.6000], device='cuda:0'), 'self.algorithm.phi_p': Parameter containing:
tensor([2.5000, 2.5000, 2.5000, 2.5000, 2.5000, 2.5000, 2.5000, 2.5000, 2.5000,
2.5000, 2.5000, 2.5000, 2.5000, 2.5000, 2.5000, 2.5000, 2.5000, 2.5000,
2.5000, 2.5000, 2.5000, 2.5000, 2.5000, 2.5000, 2.5000, 2.5000, 2.5000,
2.5000, 2.5000, 2.5000, 2.5000, 2.5000, 2.5000, 2.5000, 2.5000, 2.5000,
2.5000, 2.5000, 2.5000, 2.5000, 2.5000, 2.5000, 2.5000, 2.5000, 2.5000,
2.5000, 2.5000, 2.5000, 2.5000, 2.5000, 2.5000, 2.5000, 2.5000, 2.5000,
2.5000, 2.5000, 2.5000, 2.5000, 2.5000, 2.5000, 2.5000, 2.5000, 2.5000,
2.5000, 2.5000, 2.5000, 2.5000, 2.5000, 2.5000, 2.5000, 2.5000, 2.5000,
2.5000, 2.5000, 2.5000, 2.5000, 2.5000, 2.5000, 2.5000, 2.5000, 2.5000,
2.5000, 2.5000, 2.5000, 2.5000, 2.5000, 2.5000, 2.5000, 2.5000, 2.5000,
2.5000, 2.5000, 2.5000, 2.5000, 2.5000, 2.5000, 2.5000, 2.5000, 2.5000,
2.5000, 2.5000, 2.5000, 2.5000, 2.5000, 2.5000, 2.5000, 2.5000, 2.5000,
2.5000, 2.5000, 2.5000, 2.5000, 2.5000, 2.5000, 2.5000, 2.5000, 2.5000,
2.5000, 2.5000, 2.5000, 2.5000, 2.5000, 2.5000, 2.5000, 2.5000, 2.5000,
2.5000, 2.5000], device='cuda:0'), 'self.algorithm.phi_g': Parameter containing:
tensor([0.8000, 0.8000, 0.8000, 0.8000, 0.8000, 0.8000, 0.8000, 0.8000, 0.8000,
0.8000, 0.8000, 0.8000, 0.8000, 0.8000, 0.8000, 0.8000, 0.8000, 0.8000,
0.8000, 0.8000, 0.8000, 0.8000, 0.8000, 0.8000, 0.8000, 0.8000, 0.8000,
0.8000, 0.8000, 0.8000, 0.8000, 0.8000, 0.8000, 0.8000, 0.8000, 0.8000,
0.8000, 0.8000, 0.8000, 0.8000, 0.8000, 0.8000, 0.8000, 0.8000, 0.8000,
0.8000, 0.8000, 0.8000, 0.8000, 0.8000, 0.8000, 0.8000, 0.8000, 0.8000,
0.8000, 0.8000, 0.8000, 0.8000, 0.8000, 0.8000, 0.8000, 0.8000, 0.8000,
0.8000, 0.8000, 0.8000, 0.8000, 0.8000, 0.8000, 0.8000, 0.8000, 0.8000,
0.8000, 0.8000, 0.8000, 0.8000, 0.8000, 0.8000, 0.8000, 0.8000, 0.8000,
0.8000, 0.8000, 0.8000, 0.8000, 0.8000, 0.8000, 0.8000, 0.8000, 0.8000,
0.8000, 0.8000, 0.8000, 0.8000, 0.8000, 0.8000, 0.8000, 0.8000, 0.8000,
0.8000, 0.8000, 0.8000, 0.8000, 0.8000, 0.8000, 0.8000, 0.8000, 0.8000,
0.8000, 0.8000, 0.8000, 0.8000, 0.8000, 0.8000, 0.8000, 0.8000, 0.8000,
0.8000, 0.8000, 0.8000, 0.8000, 0.8000, 0.8000, 0.8000, 0.8000, 0.8000,
0.8000, 0.8000], device='cuda:0')}
我们也可以定义一组自定义的超参数值。确保超参数集的数量与HPOProblemWrapper
中的实例数量匹配是很重要的。此外,自定义超参数必须以字典形式提供,其值需要使用Parameter
进行包装。
params = hpo_prob.get_init_params()
# since we have 128 instances, we need to pass 128 sets of hyperparameters
params["self.algorithm.w"] = torch.nn.Parameter(torch.rand(128, 1), requires_grad=False)
params["self.algorithm.phi_p"] = torch.nn.Parameter(torch.rand(128, 1), requires_grad=False)
params["self.algorithm.phi_g"] = torch.nn.Parameter(torch.rand(128, 1), requires_grad=False)
result = hpo_prob.evaluate(params)
print("The result of the first 3 parameter sets:\n", result[:3])
The result of the first 3 parameter sets:
tensor([2.2974, 3.4748, 4.1416], device='cuda:0')
确保PSO的种群大小与实例数量匹配非常重要,否则可能会发生意外错误。
此外,解决方案需要在外部工作流中进行转换,因为HPOProblemWrapper
要求输入为字典形式。
class solution_transform(torch.nn.Module):
def forward(self, x: torch.Tensor):
return {
"self.algorithm.w": x[:, 0],
"self.algorithm.phi_p": x[:, 1],
"self.algorithm.phi_g": x[:, 2],
}
outer_algo = PSO(128, 0 * torch.ones(3), 10 * torch.ones(3)) # search each hyperparameter in the range [0, 10]
monitor = EvalMonitor(full_sol_history=False)
outer_workflow = StdWorkflow()
outer_workflow.setup(outer_algo, hpo_prob, monitor=monitor, solution_transform=solution_transform())
outer_workflow.init_step()
for _ in range(100):
outer_workflow.step()
monitor = outer_workflow.get_submodule("monitor")
print("params:\n", monitor.topk_solutions, "\n")
print("result:\n", monitor.topk_fitness)
params:
tensor([[0.1865, 1.0439, 2.1565]], device='cuda:0')
result:
tensor([7.2361e-05], device='cuda:0')
monitor.plot()