☰
使用 MongoDB 并行化 Hyperopt 超参数搜索:MongoTrials 与 hyperopt-mongo-worker 完整指南
2026/9/28 2:50:58 网站建设 项目流程
  • 机器学习
  • AutoML

【免费下载链接】hyperopt

Distributed Asynchronous Hyperparameter Optimization in Python

项目地址:https://gitcode.com/gh_mirrors/hy/hyperopt
点击查看免费下载

本文是 Hyperopt 分布式异步超参数优化实战指南,围绕MongoTrials后端与hyperopt-mongo-worker工作进程,完整讲解如何利用 MongoDB 将单机串行的fmin搜索扩展为多进程并行、可持久化的异步搜索。读完本文,你将掌握 mongod 的启动与配置、fmin的异步调用改造、worker 的命令行参数与工作循环原理,以及实验数据的持久化复用方法。

为什么需要 MongoDB 后端

Hyperopt 在设计上支持多种不同的 trial 数据库(trial database)。默认的Trials对象基于 Python 列表与字典实现(见 hyperopt/base.py 中asynchronous = False的定义),它是官方提供的参考实现,易于使用,但不支持异步更新——也就是说,它无法支撑多个进程同时写入各自的评估结果,因此无法用于并行搜索。

为了并行化搜索,Hyperopt 在 hyperopt/mongoexp.py 中提供了MongoTrials实现:它以 MongoDB 作为进程间通信(IPC)与持久化存储介质,将搜索建议、评估结果、日志统一存入库中,天然支持异步更新。MongoTrials在源码中显式声明了asynchronous = True(mongoexp.py),fmin内部的FMinIter会从trials.asynchronous继承这一行为(fmin.py),从而走异步调度路径。

要运行并行化搜索,需要依次完成三件事(前提是已安装 MongoDB,参考 安装说明):

  1. 在网络可达的位置启动一个mongod进程;
  2. 把对hyperopt.fmin的调用改为使用连接该 mongod 的MongoTrials后端;
  3. 启动一个或多个hyperopt-mongo-worker进程,同样连接到该 mongod,在fmin阻塞期间真正执行搜索。

第一步:启动 mongod 进程

MongoDB 安装完成后,启动数据库进程mongod非常简单,例如:

mongod --dbpath . --port 1234 # 或者为每个数据库分配独立目录,推荐方式: mongod --dbpath . --port 1234 --directoryperdb --journal --nohttpinterface # 或者以守护进程方式启动: mongod --dbpath . --port 1234 --directoryperdb --fork --journal --logpath log.log --nohttpinterface

几条关键的部署建议(均来自原文档,实践中值得逐一落实):

  • 注意磁盘预分配:Mongo 出于性能考虑会习惯性预分配数 GB 空间(可用--noprealloc关闭),所以在创建数据库之前要想清楚把库放在哪里。
  • 避免网络文件系统:把数据库建在网络文件系统上,不仅会让你的数据库性能极差,还会拖累网络上其他所有用户,务必谨慎。
  • 注意安全暴露:如果机器对互联网可见,要么只绑定回环接口(loopback)并通过 ssh 连接,要么阅读 MongoDB 官方文档启用密码保护。

本教程后续内容均基于localhost 的 1234 端口上运行的 mongo。

第二步:使用 MongoTrials 改造 fmin 调用

假设一个最简单的场景:用 hyperopt 最小化math.sin函数。在进程内串行运行时,可以这样写:

import math from hyperopt import fmin, tpe, hp, Trials trials = Trials() best = fmin(math.sin, hp.uniform('x', -2, 2), trials=trials, algo=tpe.suggest, max_evals=10)

若要改用 MongoDB 做实验的持久化存储,把Trials换成MongoTrials即可:

import math from hyperopt import fmin, tpe, hp from hyperopt.mongoexp import MongoTrials trials = MongoTrials('mongo://localhost:1234/foo_db/jobs', exp_key='exp1') best = fmin(math.sin, hp.uniform('x', -2, 2), trials=trials, algo=tpe.suggest, max_evals=10)

MongoTrials 连接串的语义

  • 第一个参数告诉MongoTrials使用哪个 mongod 进程、以及该进程中的哪个数据库(这里为foo_db)。从源码看,连接串会被MongoJobs.new_from_connection_str解析为协议、用户名、密码、主机名、端口、数据库名与集合名(mongoexp.py),并支持带认证的 URI 格式,例如mongo://hyperopt:foobar@127.0.0.1:27017/hyperoptdb/jobs?authSource=db1(该格式由 test_mongoexp.py 中的test_parse_url用例验证)。
  • 第二个参数exp_key='exp1'用于在同一个数据库内部给某一组 trial 打标签,便于区分实验;该参数技术上可选。
  • 注意(N.B.):当前实现有一个硬性要求——数据库名后面必须跟/jobs(即形如<db>/jobs)。main_worker_helper中也有相应兜底逻辑:若连接串不含/jobs会自动补上(mongoexp.py)。

数据库 vs exp_key:两种组织策略

把 trial 放在不同数据库里,还是用 exp_key 区分,取决于你的偏好:

  • 用独立数据库:可以从 shell 直接操作(每个库表现为独立文件),且实验之间的独立性/隔离性更强;
  • 用 exp_key:hyperopt-mongo-worker进程是在数据库级别轮询的,因此同一数据库下的多个实验可以同时被同一批 worker 支撑,资源复用更高效。

第三步:运行 hyperopt-mongo-worker

如果你直接运行上面的代码片段,会看到调用在fmin处阻塞(挂起)。原因在于:MongoTrials对fmin自述为一个异步(asynchronous)trial 对象,fmin在算法提出新的搜索点后并不会亲自评估目标函数,而是静静等待其他进程完成评估并把结果写回 mongodb(这正是FMinIter中block_until_done=self.asynchronous的行为,见 fmin.py)。

hyperopt-mongo-worker脚本位于 hyperopt 的bin目录,安装 hyperopt 时它应当已被放入你的$PATH。在fmin阻塞期间,另开一个 shell 执行:

hyperopt-mongo-worker --mongo=localhost:1234/foo_db --poll-interval=0.1

该 worker 会从 mongodb 中取出(dequeue)一个工作任务,评估math.sin函数,再把结果写回数据库。当fmin尝试完足够多的搜索点后便会返回,上面的脚本随之终止;hyperopt-mongo-worker会继续在附近等待几分钟以发现新的任务,然后自行退出。

这里显式设置了--poll-interval=0.1,因为 worker 的默认轮询节奏是为单个任务耗时至少一两分钟的作业设计的(源码中默认值为 5 秒,见下节参数表);对math.sin这种微秒级任务,必须调小轮询间隔,否则大量时间浪费在空等上。

hyperopt-mongo-worker 完整参数表

main_worker使用optparse解析命令行参数(mongoexp.py),所有可用参数及默认值如下:

参数默认值说明
--mongolocalhost/hyperopt<host>[:port]/<db>,用于 IPC 与任务存储的连接串
--poll-interval5每 1 < T < N 秒检查一次工作队列(单位:秒)
--exp-keyNone本 worker 任务的标识符(按 exp_key 限定可领取的任务)
--reserve-timeout120.0轮询数据库领取任务的超时上限(秒),超时抛ReserveTimeout
--max-jobssys.maxsize运行完这么多任务后停止(默认相当于无限)
--max-jobs-in-dbsys.maxsize数据库中的任务数达到该阈值后停止
--max-consecutive-failures4连续 N 个任务失败则停止
--last-job-timeoutNone超过 T 秒后不再领取新任务
--workdirNone工作根目录(默认从 mongo 中加载)
--no-subprocesses使用子进程(默认开启)关闭子进程模式,让目标函数与 worker 同进程运行,便于跨评估复用大块内存数据,但需警惕内存泄漏

其中--poll-interval同时控制 worker 空转时的随机休眠区间:MongoWorker在未领取到任务时,会按1 + random * (poll_interval - 1)秒随机休眠后再次轮询(mongoexp.py),避免多 worker 同时打满数据库。

源码级原理:worker 的工作循环与任务状态机

要理解hyperopt-mongo-worker为何能支撑并行搜索,需要从 hyperopt/mongoexp.py 顶部的模块文档与MongoWorker.run_one(mongoexp.py)看起。

IPC 依赖的三个集合

实验通过三个 MongoDB 集合完成进程间通信:

  • jobs:存储建议的 trial 及其结果的标准文档,关键字段包括:
    • spec:搜索算法suggest返回的子文档;
    • exp_key:哪个 driver 建议了该 trial 的标识;
    • cmd:标识如何调用evaluate的(protocol, ...)元组;
    • state:任务状态 0/1/2/3(新建、运行中、成功、失败);
    • owner:新任务为None,被领取后为(hostname, pid);
    • book_time/refresh_time:任务被保留的时间与运行进程最近一次签到时间;
    • result:evaluate返回的结果子文档;
    • error:失败(state=3)任务的原因;
    • logs:由 ctrl 对象写入的 info/warn/error 消息序列字典。
  • fs:GridFS 存储集合,用于 pickle 序列化大对象;
  • drivers:描述 driver 的文档,用于防止两个 driver 同时使用同一 exp_key,并可挂载实验类保存状态、pickle 的 bandit 重建参数等附件。

状态机与抢占式领取

任务状态常量定义在 hyperopt/base.py:JOB_STATE_NEW = 0、JOB_STATE_RUNNING = 1、JOB_STATE_DONE = 2、JOB_STATE_ERROR = 3。

worker保留(reserve)任务的方式是:在jobs集合中原子地找到一个owner为None且state为 0 的文档,并将其状态置为 1(mongoexp.py)。若找不到这样的任务,就随机休眠几秒后继续轮询。MongoWorker.run_one的完整流程是:循环mj.reserve()直到成功(受reserve_timeout限制)→ 从job['misc']还原 spec → 按cmd协议反序列化目标函数 → 在临时工作目录中执行worker_fn(spec, ctrl)→ 将结果SONify后通过ctrl.checkpoint(result)写回 → 最后把任务状态更新为JOB_STATE_DONE(失败则写JOB_STATE_ERROR并记录异常类型与信息)。

信号控制与子进程模式

main_worker_helper(mongoexp.py)提供了两类运维能力:

  • 信号处理:SIGINT/SIGTERM/SIGHUP触发干净关闭(Shutdown);非 Windows 平台下SIGUSR1触发“等当前子进程正常结束后退出”(WaitQuit)。
  • 子进程隔离:默认开启子进程模式——每个任务通过递归Popen以--max-jobs=1再启动一个独立进程执行,以防护内存泄漏与不良清理;代价是大型数据集需要在每个子进程中重新加载一次。这就是--no-subprocesses参数存在的意义。

MongoTrials 是持久化对象:重跑、扩展与只读分析

MongoTrials的数据全部落在 mongodb 中,因此它天然具备持久化语义。如果你把上面的示例再运行一次:

best = fmin(math.sin, hp.uniform('x', -2, 2), trials=trials, algo=tpe.suggest, max_evals=10)

会看到它立刻返回、什么都没发生——因为所连接的数据库里已经有足够的 trial 了,第一次实验的结果早已计算并存储在其中。基于这一特性,可以灵活操作:

  • 开始一次新搜索:更换数据库名或exp_key;
  • 扩展当前搜索:用更大的max_evals再次调用fmin,继续追加搜索点;
  • 只读分析:另起进程创建MongoTrials,仅用于分析库中已有结果,完全不需要调用fmin。

从实现上看,MongoTrials本质上是MongoJobs集合的包装器(mongoexp.py),它面向“读取trial 数据库”设计;若需要直接修改集合中的文档,应取.handle属性(MongoJobs实例)使用底层方法或 pymongo 接口,写完后调用refresh()/refresh_tids()让本地视图与数据库同步。

验证与测试:仓库里的集成测试怎么做

仓库在 hyperopt/tests/integration/test_mongoexp.py 中提供了针对 MongoDB 后端的集成测试,可直接作为本地环境验证的参考:

  • TempMongo上下文管理器(test_mongoexp.py)会以mongod --dbpath ... --noprealloc --port=22334启动临时数据库实例,等待就绪后返回连接串localhost:22334/<db>/jobs,退出时清理进程与目录;
  • test_parse_url覆盖了带用户名密码与authSource参数的连接串解析;
  • 测试中还通过main_worker_helper、MongoWorker、ReserveTimeout等组件验证 worker 侧的领取与执行链路(test_mongoexp.py)。

你可以用pytest hyperopt/tests/integration/test_mongoexp.py运行这组测试,确认本机 mongod 与 hyperopt 的 mongo 后端配合正常。

小结:从串行到并行的三步改造

把一次 hyperopt 搜索从单进程串行改为 MongoDB 驱动的并行异步搜索,核心动作可以浓缩为三步:启动 mongod(注意预分配、网络文件系统与安全绑定问题)→ 将fmin的trials参数从Trials换成指向mongo://host:port/db/jobs的MongoTrials(可用exp_key在库内隔离实验)→ 启动一个或多个hyperopt-mongo-worker承担实际评估(按任务耗时调节--poll-interval,按资源情况决定是否使用子进程模式)。之后,MongoDB 会替你持久化每一次评估,搜索可以随时扩展、复用或由只读进程分析——这正是 Hyperopt 分布式异步超参数优化的核心工作方式。

  • 机器学习
  • AutoML

【免费下载链接】hyperopt

Distributed Asynchronous Hyperparameter Optimization in Python

项目地址:https://gitcode.com/gh_mirrors/hy/hyperopt
点击查看免费下载

相关推荐

上一篇:3个步骤搞定手机变摄像头:DroidCam高效部署指南
下一篇:虚拟手柄驱动解放双手:Windows即插即用游戏控制器模拟完全攻略

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询