快速入门指南¶
什么是 HpBandSter?¶
HpBandSter (HyperBand 的增强版,STERoids 指增强) 实现了最近发表的用于优化机器学习算法超参数的方法。我们设计 HpBandSter 的初衷是使其能够从本地机器的顺序运行扩展到分布式系统的并行运行
其中一种实现的算法是 BOHB,它结合了贝叶斯优化和 HyperBand,可以有效地搜索性能良好的配置。您可以通过阅读我们发表在 ICML 2018 的论文来了解更多关于这种方法的信息
如何安装 HpBandSter¶
HpBandSter 可以通过 pip 在 python3 环境下安装
pip install hpbandster
如果您想基于代码进行开发,可以通过以下方式安装
git clone git@github.com:automl/HpBandSter.git
cd HpBandSter
python3 setup.py develop --user
注意
HpBandSter 仅支持 Python3!
基本组成部分¶
无论您是喜欢在本地机器上使用 HpBandSter,还是在集群上使用,基本设置始终相同。现在,让我们重点关注将优化器应用于新问题所需的最重要组成部分
- 实现 Worker
- Worker 负责在给定预算下,使用单个配置评估给定的模型。
接下来,需要定义待优化的参数。HpBandSter 依赖于 ConfigSpace 包来实现此功能。
- 选择预算和迭代次数
- 为了获得良好的性能,HpBandSter 需要知道可用的有意义的预算。您还需要指定优化器执行的迭代次数。
1. 实现 Worker¶
Worker" 负责评估超参数设置并返回最小化的相关损失。通过从
基类
派生,编码新问题包括实现两个方法:__init__ 和 compute。前者允许在 Worker 启动时执行初始计算,例如加载数据集,而后者在优化过程中重复调用,评估给定配置并产生相关的损失。下面的 Worker 演示了这个概念。它实现了一个简单的玩具问题,配置中有一个参数 x,我们试图最小化它。函数评估会受到高斯噪声的干扰,噪声会随着预算的增加而减小。
import numpy
import time
import ConfigSpace as CS
from hpbandster.core.worker import Worker
class MyWorker(Worker):
def __init__(self, *args, sleep_interval=0, **kwargs):
super().__init__(*args, **kwargs)
self.sleep_interval = sleep_interval
def compute(self, config, budget, **kwargs):
"""
Simple example for a compute function
The loss is just a the config + some noise (that decreases with the budget)
For dramatization, the function can sleep for a given interval to emphasizes
the speed ups achievable with parallel workers.
Args:
config: dictionary containing the sampled configurations by the optimizer
budget: (float) amount of time/epochs/etc. the model can use to train
Returns:
dictionary with mandatory fields:
'loss' (scalar)
'info' (dict)
"""
res = numpy.clip(config['x'] + numpy.random.randn()/budget, config['x']/2, 1.5*config['x'])
time.sleep(self.sleep_interval)
return({
'loss': float(res), # this is the a mandatory field to run hyperband
'info': res # can be used for any user-defined information - also mandatory
})
@staticmethod
def get_configspace():
config_space = CS.ConfigurationSpace()
2. 搜索空间定义¶
每个问题都需要完整的搜索空间描述。在 HpBandSter 中,一个 ConfigurationSpace 对象定义了所有超参数、它们的范围以及它们之间潜在的依赖关系。在我们的玩具示例中,搜索空间包含一个介于零和一之间的连续参数 x。为了方便起见,我们将配置空间定义作为静态方法附加到 Worker 上。这样,Worker 的计算函数及其参数就可以整齐地组合在一起。
class MyWorker(Worker):
@staticmethod
def get_configspace():
config_space = CS.ConfigurationSpace()
config_space.add_hyperparameter(CS.UniformFloatHyperparameter('x', lower=0, upper=1))
return(config_space)
注意
我们也支持整数和分类超参数。为了表达依赖关系,ConfigSpace 包还可以表达参数之间的条件和禁止关系。有关更多示例,请参阅 ConfigSpace 的文档,或者请查看高级示例。
3. 合理的预算和迭代次数¶
为了利用较低保真度的近似值,即低于 max_budget 的预算,这些 较低精度 的评估必须有意义。由于这些预算可能意味着非常不同的事物(神经网络训练的 epoch 数、训练模型的数据点数或交叉验证折叠数等等),因此必须由用户指定。这是通过所有优化器的两个参数 min_budget 和 max_budget 来完成的。为了更好地加速,较低的预算应该尽可能小,同时仍然具有信息量。所谓信息量,是指其性能是更高预算下损失的 尚可的 指标。对于一般情况,很难更具体。这两个预算取决于具体问题,需要一些领域知识。
迭代次数通常是一个更容易选择的参数。根据优化器的不同,一次迭代需要的计算预算相当于在 max_budget 上进行几次函数评估。一般来说,迭代次数越多越好,当多个 Worker 并行运行时,事情会变得更复杂。目前,迭代次数仅控制评估的配置数量。
第一个玩具示例¶
现在让我们在几种不同的设置下使用上面的 Worker 及其搜索空间。具体来说,我们将运行
每个示例都展示了如何在不同的环境中设置 HpBandSter,并突出其具体细节。每个计算环境都略有不同,但应该很容易从其中一个示例开始并根据任何特定需求进行调整。第一个示例慢慢介绍了任何 HpBandSter 运行的主要工作流程。接下来的示例通过包含更多功能逐渐增加复杂性。
1. 本地顺序运行¶
现在我们准备看第一个真实的示例来演示如何使用 HpBandSter。每次运行都包含相同的 5 个基本步骤,我们现在将介绍这些步骤。
步骤 1:启动 Nameserver
为了启动 Worker 和优化器之间的通信,HpBandSter 需要一个 Nameserver。这是一个小型服务,用于跟踪所有正在运行的进程及其 IP 地址和端口。它是 HpBandSter 从 Pyro4 继承的一个构建块。在第一个示例中,我们将使用回环接口和 IP 127.0.0.1 运行它。使用参数 port=None 将使其使用默认端口 9090。run_id 用于识别单个运行,也需要提供给所有其他组件(见下文)。现在,我们将其固定为 example1。
NS = hpns.NameServer(run_id='example1', host='127.0.0.1', port=None) NS.start()
步骤 2:启动 Worker
Worker 实现了要优化的实际问题。通过从
基本 Worker
派生并实现 compute 方法,它可以轻松地实例化,包含您的特定 __init__ 所需的所有参数以及基类的附加参数。最基本的是 Nameserver 的位置和 run_id。w = MyWorker(sleep_interval = 0, nameserver='127.0.0.1',run_id='example1') w.run(background=True)
步骤 3:运行 Optimizer
至少,我们必须提供搜索空间的描述、run_id、Nameserver 和预算。当调用 run 方法时,优化开始,迭代次数是唯一的强制参数。
bohb = BOHB( configspace = w.get_configspace(), run_id = 'example1', nameserver='127.0.0.1', min_budget=args.min_budget, max_budget=args.max_budget ) res = bohb.run(n_iterations=args.n_iterations)
步骤 4:停止所有服务
运行完成后,需要关闭上面启动的服务。这确保 Worker、Nameserver 和 Master 都正常退出,并且之后没有(守护)线程继续运行。特别是,我们关闭优化器(它会关闭所有 Worker)和 Nameserver。
bohb.shutdown(shutdown_workers=True) NS.shutdown()
步骤 5:结果分析
运行完成后,人们可能会对各种信息感兴趣。HpBandSter 提供对所有已评估配置的完全访问,包括计时信息以及失败运行的潜在错误消息。在第一个示例中,我们简单地查找最佳配置(称为 incumbent),计算配置和评估的数量,以及花费的总预算。有关更多详细信息,请参阅其他一些示例以及
Result
类的文档。id2config = res.get_id2config_mapping()
incumbent = res.get_incumbent_id()
print('Best found configuration:', id2config[incumbent]['config'])
print('A total of %i unique configurations where sampled.' % len(id2config.keys()))
print('A total of %i runs where executed.' % len(res.get_all_runs()))
print('Total budget corresponds to %.1f full function evaluations.'%(sum([r.budget for r in res.get_all_runs()])/args.max_budget))
此示例的完整源代码可以在此处找到
2. 使用线程的本地并行运行¶
现在让我们扩展这个示例,启动多个 Worker,每个 Worker 在一个单独的线程中。如果各个 Worker 能够避开 Python 的全局解释器锁,这是一种利用多核 CPU 系统的好模式。例如,许多 scikit learn 算法将繁重的计算外包给某些 C 模块,即使是线程化的,也能使其真正并行运行。
下面,我们可以实例化指定数量的 Worker。为了强调效果,我们引入了一个一秒的 sleep_interval,这使得每次函数评估都需要花费一些时间。请注意额外的 id 参数,它有助于区分各个 Worker。这是必需的,因为此处所有线程都使用其进程 ID,它们是相同的。
# Step 2: Start the workers
# Now we can instantiate the specified number of workers. To emphasize the effect,
# we introduce a sleep_interval of one second, which makes every function evaluation
# take a bit of time. Note the additional id argument that helps separating the
# individual workers. This is necessary because every worker uses its processes
启动优化器时,我们可以向 run 方法添加 min_n_workers 参数,以使优化器等待所有 Worker 启动。这不是强制性的,Worker 可以随时添加,但如果运行的 timing 很重要,可以使用此参数在启动时同步所有 Worker。
# at any time, but if the timing of the run is essential, this can be used to
源代码可以在此处找到。尝试通过更改命令行参数 –n_worker 来使用不同数量的 Worker 运行它。
3. 使用不同进程的本地并行运行¶
在我们转向分布式系统之前,我们首先将我们的玩具示例扩展到在不同的进程中运行。为此,我们添加 –worker 标志
parser.add_argument('--max_budget', type=float, help='Maximum budget used during the optimization.', default=243)
这将允许我们为专用的 Worker 运行相同的脚本。这些 Worker 只需实例化 Worker 类并调用其 run 方法,但这一次 Worker 在前台运行。在处理完所有配置并接收到 Master 的关闭信号后,Worker 就会简单地退出。
if args.worker:
您可以在此处下载源代码。尝试在三个不同的 shell 中运行此脚本,其中两次带有 –worker 标志。为了查看发生了什么,此脚本的日志级别设置为 INFO,因此会显示来自优化器和 Worker 的消息。