定义要最小化的函数

在指定要最小化的目标函数时,Hyperopt 提供了几个递增的灵活性/复杂性级别。作为设计者需要考虑的问题是

  • 您是否想保存函数返回值以外的额外信息,例如在目标计算过程中收集的其他统计数据和诊断信息?
  • 您是否想使用需要不仅仅是函数值的优化算法?
  • 您是否想在并行进程之间进行通信?(例如,其他工作进程或最小化算法)

接下来的几节将探讨实现目标函数的各种方法,该目标函数最小化一个单变量上的二次目标函数。在每一节中,我们将在一个从 -10 到 +10 的有界范围内进行搜索,这可以用一个 搜索空间 来描述

space = hp.uniform('x', -10, 10)

请参阅 此页面 以获取有关如何指定更复杂的搜索空间的信息。

最简单的情况

hyperopt 的优化算法与您的目标函数之间最简单的通信协议是,您的目标函数接收来自搜索空间的一个有效点,并返回与该点相关的浮点数 损失(也称为负效用)。

from hyperopt import fmin, tpe, hp
best = fmin(fn=lambda x: x ** 2,
            space=hp.uniform('x', -10, 10),
            algo=tpe.suggest,
            max_evals=100)
print(best)

此协议的优点是非常易读且输入快速。正如您所见,它几乎是一行代码。此协议的缺点是 (1) 这种类型的函数无法将每次评估的额外信息返回到 trials 数据库中,以及 (2) 这种类型的函数无法与搜索算法或其他并发函数评估进行交互。在接下来的示例中,您将看到为什么您可能想要做这些事情。

通过 Trials 对象附加额外信息

如果您的目标函数很复杂且运行时间很长,您几乎肯定会希望保存比最终输出的单个浮点数损失更多的统计数据和诊断信息。对于这种情况,fmin 函数被编写为可以处理字典返回值。其思想是您的损失函数可以返回一个嵌套字典,其中包含您想要的所有统计数据和诊断信息。然而,现实情况比这稍微不那么灵活:例如,在使用 mongodb 时,字典必须是一个有效的 JSON 文档。尽管如此,仍然有很多灵活性来存储领域特定的辅助结果。

当目标函数返回一个字典时,fmin 函数会在返回值中查找一些特殊的键值对,并将其传递给优化算法。有两个强制性的键值对

  • status - hyperopt.STATUS_STRINGS 中的一个键,例如表示成功完成的 'ok',以及函数未定义的 'fail'。
  • loss - 您尝试最小化的浮点函数值,如果 status 是 'ok',则必须存在此值。

fmin 函数也支持一些可选的键

  • attachments - 一个键值对字典,其键是短字符串(如文件名),其值是潜在的长字符串(如文件内容),这些不应在每次访问记录时从数据库加载。(此外,MongoDB 限制普通键值对的长度,因此一旦您的值达到兆字节,您可能必须将其设为附件。)
  • loss_variance - 浮点数 - 随机目标函数中的不确定性
  • true_loss - 浮点数 - 在进行超参数优化时,如果您使用此名称存储模型的泛化误差,有时可以从内置的绘图程序中获得更漂亮的输出。
  • true_loss_variance - 浮点数 - 泛化误差中的不确定性

由于字典旨在与各种后端存储机制配合使用,您应确保它与 JSON 兼容。只要它是字典、列表、元组、数字、字符串和日期时间组成的树状图结构,您就不会有问题。

提示:要存储 numpy 数组,将其序列化为字符串,并考虑将其存储为附件。

提示:如果您需要重现随机搜索的结果(例如用于演示),请使用 rstate 可选参数,将类型为 np.random.Generator 的对象传递给 fmin 函数。

以上函数以返回字典的方式编写,将如下所示

from hyperopt import fmin, tpe, hp, STATUS_OK


def objective(x):
    return {'loss': x ** 2, 'status': STATUS_OK }

best = fmin(objective,
            space=hp.uniform('x', -10, 10),
            algo=tpe.suggest,
            max_evals=100)

print(best)

Trials 对象

为了真正理解返回字典的目的,让我们修改目标函数以返回更多内容,并将一个显式的 trials 参数传递给 fmin

import pickle
import time
from hyperopt import fmin, tpe, hp, STATUS_OK, Trials


def objective(x):
    return {
        'loss': x ** 2,
        'status': STATUS_OK,
        # -- store other results like this
        'eval_time': time.time(),
        'other_stuff': {'type': None, 'value': [0, 1, 2]},
        # -- attachments are handled differently
        'attachments':
            {'time_module': pickle.dumps(time.time)}
        }
trials = Trials()
best = fmin(objective,
            space=hp.uniform('x', -10, 10),
            algo=tpe.suggest,
            max_evals=100,
            trials=trials)

print(best)

在这种情况下,对 fmin 的调用像以前一样进行,但通过直接传入一个 trials 对象,我们可以检查实验期间计算出的所有返回值。

例如

  • trials.trials - 一个字典列表,表示关于搜索的所有信息
  • trials.results - 在搜索期间由 'objective' 返回的字典列表
  • trials.losses() - 一个损失列表(每个 'ok' trial 的浮点数)
  • trials.statuses() - 一个状态字符串列表

此 trials 对象可以保存、传递给内置绘图程序,或使用您自己的自定义代码进行分析。这里是一个简单示例,说明如何保存并随后加载一个 trials 对象。

import pickle
from hyperopt import fmin, tpe, hp, Trials, STATUS_OK


def objective(x):
    return {'loss': x ** 2, 'status': STATUS_OK }

# Initialize an empty trials database
trials = Trials()

# Perform 100 evaluations on the search space
best = fmin(objective,
            space=hp.uniform('x', -10, 10),
            algo=tpe.suggest,
            trials=trials,
            max_evals=100)

# The trials database now contains 100 entries, it can be saved/reloaded with pickle or another method
pickle.dump(trials, open("my_trials.pkl", "wb"))
trials = pickle.load(open("my_trials.pkl", "rb"))

# Perform an additional 100 evaluations
# Note that max_evals is set to 200 because 100 entries already exist in the database
best = fmin(objective,
    space=hp.uniform('x', -10, 10),
    algo=tpe.suggest,
    trials=trials,
    max_evals=200)

print(best)

附件 由一种特殊的机制处理,使得 TrialsMongoTrials 可以使用相同的代码。

您可以像这样检索试验附件,这将检索第 5 个试验的 'time_module' 附件

msg = trials.trial_attachments(trials.trials[5])['time_module']
time_module = pickle.loads(msg)

语法有点复杂,因为其思想是附件是大型字符串,因此在使用 MongoTrials 时,我们不想下载超出必要的内容。字符串也可以通过 trials.attachments 全局附加到整个 trials 对象,其行为类似于一个字符串到字符串的字典。

注:目前,Trials 对象的试验特定附件会被放入同一个全局试验附件字典中,但这将来可能会改变,并且 MongoTrials 不是这样。

用于与 MongoDB 实时通信的 Ctrl 对象

fmin() 可以为您的目标函数提供并行实验所使用的 mongodb 的句柄。此机制使得可以用部分结果更新数据库,并与其他同时评估不同点的进程进行通信。您的目标函数甚至可以添加新的搜索点,就像 rand.suggest 一样。

基本技术包括

  • 使用 fmin_pass_expr_memo_ctrl 装饰器
  • 在您自己的函数中调用 pyll.rec_eval,从 exprmemo 构建搜索空间点。
  • 使用 ctrl,一个 hyperopt.Ctrl 的实例,与实时的 trials 对象通信。

如果您在这个简短教程后对此不太理解,这是正常的,但我希望提及当前代码库可能实现的功能,并提供一些术语供您在 hyperopt 源代码、单元测试和示例项目(例如 hyperopt-convnet)中搜索。如果您需要有关这部分代码的帮助,请给我发送电子邮件或提交 github 问题。