首页 > 人工智能 > 深度学习 > [python] 配置管理框架Hydra使用指北
这是因为,当运行
my_app.py时,
@hydra.main装饰器会自动拦截对
my_app()的调用。此时Hydra会初始化一个空的
DictConfig对象(类似于Python字典),并将其作为参数
cfg 传递给函数。由于当前配置为空,
OmegaConf.to_yaml(cfg)将其转换为YAML格式后,仅输出一个空对象。OmegaConf是Hydra的底层配置引擎,Hydra基于OmegaConf实现上层的复杂应用配置与运行管理,且OmegaConf可独立使用。
此外默认情况下,Hydra会创建以下目录结构以追踪和管理程序的运行结果:
outputs/ ├── yyyy-mm-dd/ # 按日期分组 │ └── hh-mm-ss/ # 按时间精确到秒 │ └── .hydra/ # 保存本次运行的配置 │ ├── config.yaml # 完整的配置 │ ├── hydra.yaml # Hydra 自身的配置 │ └── overrides.yaml # 命令行覆盖的参数 │ └── my_app.log # 日志文件(如果配置了日志) │ └── 其他输出文件 # 你的程序生成的文件
可以通过以下方式为配置添加内容:
通过命令行添加:
# 不支持直接在 +key=value 语法中传入非 ASCII 字符python my_app.py +name="zhangsan" +age=25
输出:
name: zhangsanage: 25
创建配置文件:
创建一个
config.yaml文件,然后运行:
python my_app.py --config-path=. --config-name=config
在代码中设置默认配置:
可以修改代码,为
@hydra.main装饰器添加配置参数:
from omegaconf import DictConfig, OmegaConfimport hydra@hydra.main(version_base=None, config_path=".", config_name="config")def my_app(cfg): print(OmegaConf.to_yaml(cfg))if __name__ == "__main__": my_app()
可以通过命令行覆盖已加载配置中的值,但是注意无需添加+前缀:
python my_app.py name="lisi"
使用++前缀可实现若配置中已存在该参数则覆盖,若不存在则新增:
python my_app.py ++name="wangwu" ++password=1234
要注意🤖:Hydra通过命令行修改配置时,仅会覆盖或新增程序运行时内存中的配置数据,不会改动磁盘上的原始配置文件,重启程序后仍会配置加载文件的原始配置。
配置对象使用
通过Hydra加载配置后,可通过属性或字典式访问或修改已有的配置项,访问不存在的配置项时会抛出异常:
from omegaconf import DictConfig, OmegaConfimport hydra@hydra.main(version_base=None, config_path=".", config_name="config")def my_app(cfg: DictConfig):
# 属性式访问配置值
assert cfg.name == "张三"
# 字典式访问配置值
assert cfg["age"] == 25
# 修改已有配置值
cfg.name = "李四"
cfg["age"] = 30
assert cfg.name == "李四"
assert cfg["age"] == 30
# 访问缺失值会抛出异常
try:
cfg.birth_year except Exception as e:
print("error !!")
print(e)if __name__ == "__main__":
my_app()
之所以不允许访问不存在的配置键,仅能操作已有配置键,是因为Hydra默认启用了struct模式以严格结构化配置。如需新增或修改配置,可先关闭严格模式,允许动态新增键。但如果嵌套层级也未提前声明,则需要先创建空嵌套,再为其添加子项:
from omegaconf import DictConfig, OmegaConfimport hydraimport os@hydra.main(version_base=None, config_path=".", config_name="config")def my_app(cfg: DictConfig):
# 关闭struct模式,允许新增配置键
OmegaConf.set_struct(cfg, False)
# 同一级新增配置
cfg.birth_year = 1995
cfg["hobby"] = ["篮球", "编程"]
# 无法直接给不存在的嵌套层级链式赋值
# cfg.address.city = "北京"
# 需要先创建嵌套层级,再赋值子键
cfg.address = OmegaConf.create({}) # 显式创建空的嵌套
cfg.address.city = "北京"
cfg.address["district"] = "朝阳区"
# 验证写入结果
assert cfg.birth_year == 1995
assert cfg.address.district == "朝阳区"
print("配置写入验证通过!")if __name__ == "__main__":
my_app()
对配置文件进行分组
若希望分别使用CNN和Transformer模型对数据集进行训练基准测试,可通过配置组(Config Group)实现这一需求。配置组是一个带有名称的分组,包含一组有效的配置项。若选择不存在的配置项,系统会生成错误提示,并列出所有有效的配置项。
创建配置组时,需先新建一个目录(例如model),用于存放各模型配置项对应的文件。由于预计会创建多个配置组,建议提前将所有配置文件统一移至conf目录下管理。
目录结构如下:
├─ conf │ └─ model │ ├─ cnn.yaml │ └─ transformer.yaml └── my_app.py
model/cnn.yaml:
backbone: resnet50 learning_rate: 0.001batch_size: 32epochs: 20dropout: 0.2
model/transformer.yaml:
backbone: vit_base learning_rate: 0.0001batch_size: 16epochs: 30attention_heads: 12
所有配置文件已统一存放至conf目录,需通过config_path参数告知Hydra该目录位置,并在代码中指定待加载的配置文件名config_name。若未明确指定具体配置文件名,Hydra无法自动推断加载目标,最终会输出空配置:
from omegaconf import DictConfig, OmegaConfimport hydra@hydra.main(version_base=None, config_path="conf", config_name="model/cnn")def my_app(cfg: DictConfig) -> None: print(OmegaConf.to_yaml(cfg))if __name__ == "__main__": my_app()
也可以通过命令行从配置组中选择特定配置项,命令行使用
+分组名=配置项的格式,例如:
python my_app.py +model=transformer
与常规用法一致,仍可覆盖最终配置中的单个参数值:
python my_app.py +model=transformer model.epochs=40
多文件处理
可以生成一个配置文件,在配置文件中用
defaults参数添加默认配置列表。该列表用于指定Hydra组合最终配置对象的规则,按照约定,它需作为配置文件的个配置项。如下所示:
defaults: - model: cnn
然后这个配置文件可以命名为任意名字,如conf文件夹下的config.yaml,这样运行会默认加载model对应的文件配置:
from omegaconf import DictConfig, OmegaConfimport hydra@hydra.main(version_base=None, config_path="conf", config_name="config")def my_app(cfg: DictConfig) -> None: print(OmegaConf.to_yaml(cfg))if __name__ == "__main__": my_app()
默认配置列表支持叠加多个深度学习相关配置项。若同一配置组存在两个配置文件,系统会将这两个配置文件合并为一个新字典;当配置中出现相同键名时,后加载的配置项会覆盖先加载的配置项。示例默认配置如下:
defaults: - model: - cnn - transformer
若在配置文件夹conf下的dataset目录中,存在如下配置文件model/cifar10.yaml:
name: CIFAR-10 path: ./data/cifar10 num_classes:www.nme.zuqba.info 10learning_rate: 0.0001augmentation: true # 是否开启数据增强
当默认配置文件conf/config.yaml中默认配置列表的内容如下:
defaults: - model: cnn - dataset: cifar10
由于model和dataset分属不同的配置组,Hydra会将这两个配置组的默认配置进行独立合并。最终生成的完整配置结构中,会包含model和dataset两个一级配置项,各自保留对应配置组的完整参数:
model: # 此处为conf/model/cnn.yaml中的配置内容dataset: # 此处为conf/dataset/cifar10.yaml中的配置内容
即使设置了默认配置,仍可手动确定参数并覆盖部分配置参数:
python my_app.py model=cnn model.epochs=30
在配置项前添加~前缀,可从默认配置列表中移除该默认项:
python my_app.py ~model
主配置的组合顺序
主配置文件中可同时包含配置参数和默认配置列表。在此情况下,若需调整默认配置列表与主配置之间的覆盖关系,可通过添加
_self_关键字实现:将
_self_置于默认配置列表末尾,则主配置参数将覆盖默认配置列表中的对应项;若将其置于列表开头,则默认配置列表中的参数将覆盖主配置中的内容。
需注意的是,从Hydra 1.1版本开始,默认行为为主配置覆盖默认配置列表中的配置;而在此之前的版本中,默认配置列表会覆盖主配置的参数。
例如默认配置文件config.yaml内容如下,会进行数据覆盖,也就是说配置文件里dataset部分会覆盖默认配置中的同名部分:
defaults: - model: cnn - dataset: cifar10 - _self_ dataset: name: my_dataset version: 1.0
随着软件复杂度的不断提升,我们会采用模块化与组合化的设计思路来保证其可维护性。这种思路同样适用于配置文件的管理。假设我们需要为示例程序配置多类深度学习模型支持,且每个模型对应多种训练策略、搭配不同的数据预处理流程。使用Hydra时,既不必为模型、策略、预处理流程的各类组合编写独立类,也无需为其单独编写配置文件。我们可以借鉴底层软件开发的核心思路:通过组合化配置来解决这一问题。
多轮运行(Multi-run)
对于使用多套配置运行同一应用程序的场景,可以通过命令行或配置文件两种方式为Hydra应用启用多轮运行功能。该功能自Hydra 1.2版本起引入,通过设置hydra.mode配置项实现。hydra.mode的合法取值包括RUN(单次运行)和MULTIRUN(多轮运行)。若在输入配置中将hydra.mode设为MULTIRUN,应用程序将默认以多轮运行模式启动。
例如默认配置文件为:
defaults: - model: cnn - dataset: cifar10
多轮运行命令如下:
python my_app.py hydra.mode=MULTIRUN model=cnn,transformer dataset=cifar10
只要参数值用逗号分隔,就会被Hydra识别为多取值参数,Hydra会把所有带多个取值的参数做笛卡尔积(全组合),Hydra会把每个参数的取值两两配对,生成以下多个任务,依次运行:
python my_app.py hydra.mode=MULTIRUN model=cnn,transformer dataset=cifar10# 本地启动2个任务#0 : model=cnn dataset=cifar10#1 : model=transformer dataset=cifar10
该命令可以用命令行参数简化:
python my_app.py --multirun model=cnn,transformer dataset=cifar10# 或python my_app.py -m model=cnn,transformer dataset=cifar10
注意Hydra会在任务启动时延迟组合配置。若在启动任务参数遍历后修改代码或配置文件,最终组合生成的配置可能会受影响。
也可以在输入配置中通过覆盖hydra.sweeper.params来定义参数遍历规则并通过mode设置运行模式。沿用上述示例,以下配置可实现完全相同的多轮运行效果:
defaults: - model: cnn - dataset: cifar10 hydra: mode: MULTIRUN # 设置运行模式 sweeper: params: dataset: cifar10 model: transformer, cnn
直接运行程序不使用任何附加参数,结果如下:
$ python my_app.py# 本地启动2个任务#0 :blog.euakj.com model=transformer dataset=cifar10#1 : model=cnn dataset=cifar10
输出目录
Hydra能够解决每次运行程序时需要手动指定新输出目录的问题,它会为每次运行自动创建一个专属目录,并在该输出目录中执行代码。默认情况下,每次运行应用程序时,都会生成一个全新的输出目录。可以通过读取Hydra配置来获取本次运行该输出目录的路径,示例如下:
from omegaconf import DictConfig, OmegaConfimport hydraimport os@hydra.main(version_base=None, config_path="conf", config_name="config")def my_app(_cfg: DictConfig) -> None:
print(f"工作目录:{os.getcwd()}")
print(f"输出目录:{hydra.core.hydra_config.HydraConfig.get().runtime.output_dir}")if __name__ == "__main__":
my_app()
通过设置hydra.job.chdir=soccer.yc.zuq8.info True,可以让Hydra的
@hydra.main装饰器在执行用户的主函数前,调用os.chdir将Python工作目录切换到输出目录:
python my_app.py hydra.job.chdir=True
可以通过覆盖配置项hydra.output_subdir将设为null,则会完全禁用该子目录的创建。
日志
由于标准logging模块配置较为复杂,为实现常规的日志功能通常需要编写较多代码,且配置过程不够简便。Hydra能够自动完成Python logging的配置,从而有效解决这一问题。默认情况下,Hydra会以INFO级别向控制台输出日志,同时在当前工作目录自动生成日志文件留存记录。以下为使用Hydra进行日志记录的示例:
# hydra_log_demo.pyimport loggingfrom omegaconf import DictConfigimport hydra# 为本文件创建日志器log = logging.getLogger(__name__)@hydra.main(version_base=None)def my_app(_cfg: DictConfig) -> None:
log.info("Info 级别日志消息")
log.debug("Debug 级别日志消息")if __name__ == "__main__":
my_app()
可通过在命令行中指定
hydra.verbose配置项来启用DEBUG级别的日志输出。该配置项支持布尔值、字符串或列表类型的取值,开启全部或指定日志器的DEBUG级别输出如下:
python hydra_log_demo.py hydra.verbose=true
若要将特定函数对应日志器的级别设为DEBUG,可使用如下命令:
python hydra_log_demo.py hydra.verbose="[__main__,my_custom_logger]"
其效果等同于代码:
import logging logging.getLogger(NAME).setLevel(logging.DEBUG)
如果不希望Hydra自动配置日志系统,可以将hydra/job_logging(对应程序的日志)和hydra/hydra_logging(对应Hydra框架自身的日志)均设为none:
python my_app.py hydra/job_logging=None hydra/hydra_logging=None
调试功能
Hydra提供多种配置选项,可有效提升程序的可调试性。在命令行中使用
--cfg或
-c参数,即可在不运行目标函数的情况下打印应用程序的配置信息。该参数需配合一个选项来指定打印的配置范围:
仅打印业务配置指令如下:
$ python my_app.py --cfg job
若只展示配置中的某一子集,可搭配参数
--package或简写
-p使用:
python my_app.py -gov.hld.zuzb.info-cfg hydra --package hydra.job
默认情况下,配置中的插值表达式不会被解析。若需打印解析后的最终配置,可在
--cfg参数基础上,额外添加
--resolve参数。
信息查询功能
使用
--info参数可查询Hydra框架及应用程序的各类相关信息:
在复杂项目中,配置文件常面临类型模糊、配置错误难排查、缺少静态校验等问题。例如:字段类型不明确易引发运行时异常、多层级配置的结构一致性难以保障、协作时难以通过工具提前发现配置冲突。
为此,Hydra基于Python数据类(dataclasses)定义了配置结构与类型,其核心价值在于提供运行时类型检查与静态类型检查双重保障。它支持基础类型(int、str、bool、float、Enum 等)、嵌套结构、容器类型(List、Dict)以及可选字段,但也存在部分限制,例如仅部分支持联合类型,且不支持自定义方法。
Hydra中结构化配置主要有两种使用模式,均完整保留其核心功能:
本教程将按此顺序依次详解两种模式。
在后续的教程中,我们将使用ConfigStore类把数据类(dataclasses)注册为Hydra中的输入配置。ConfigStore是一个在内存中存储配置的单例(singleton)对象,与它交互的核心API是下文将要介绍的store方法。
class ConfigStore(metaclass=Singleton): def store( self, name: str, node: Any, group: Optional[str] = None, package: Optional[str] = "_group_", provider:gov.www.qiu.jrzb2.info Optional[str] = None, ) -> None: """ 将配置节点存储至配置仓库中 :param name: 配置名称 :param node: 配置节点,支持 DictConfig、ListConfig、 结构化配置(Structured configs),甚至普通的 dict 和 list 类型 :param group: 配置分组,子分组分隔符为 '/', 例如 hydra/launcher :param package: 配置节点的父级层级结构。 子节点分隔符为 '.',例如 foo.bar.baz :param provider: 提供该配置的模块/应用名称, 有助于调试排查问题。 """ ...
ConfigStore具备与YAML输入配置完全一致的功能,除此之外还提供类型校验能力。它既可单独使用,也可与YAML配合使用。
基础用例
假设我们有一个简单的应用程序,且存在一个包含cnn选项的model配置分组:
from omegaconf import DictConfig, OmegaConfimport hydra@hydra.main(version_base=None, config_path="conf", config_name="model/cnn")def my_app(cfg: DictConfig) -> None: print(OmegaConf.to_yaml(cfg))if __name__ == "__main__": my_app()
目录结构:
├─ conf │ └─ model │ └─ cnn.yaml └── my_app.py
model/cnn.yaml:
backbone: resnet50learning_rate: 0.001batch_size: 32epochs: 20dropout: 0.2
如果现在想要新增一个transformer选项该怎么做?我们可以直接新增
model/transformer.yaml配置分组文件,但这并非唯一方式!也可以通过ConfigStore为Hydra新增model配置分组的transformer选项。
要实现这个需求,只需在上述代码文件中添加几行代码:
from dataclasses import dataclassimport hydrafrom omegaconf import DictConfig, OmegaConffrom hydra.core.config_store import ConfigStore@dataclassclass TransformerConfig: optimizer: str = "sgd" lr: float = 0.0005 hidden_dim: int = 256cs = ConfigStore.instance()# 将名为transformer的配置类注册至model配置分组# 注意出现实体文件会报错cs.store(name="transformer", group="model", node=TransformerConfig)@hydra.main(version_base=None, config_path="conf")def my_app(cfg: DictConfig) -> None: print(OmegaConf.to_yaml(cfg))if __name__ == "__main__": my_app()
上述代码不会生成实际的物理配置文件,它仅用于在内存中注册配置类。现在应用程序已经能够识别model配置组中的两个选项,您可以通过以下命令运行程序来验证效果:
python my_app.py +model=cnn
或
python my_app.py +model=transformer
在深度学习实验中管理多个模型配置时,我们还可以借助ConfigStore支持的三种注册方式灵活控制配置节点,实现不同方案间的快速切换:
from dataclasses import dataclassimport hydrafrom omegaconf import DictConfig, OmegaConffrom hydra.core.config_store import ConfigStore@dataclassclass TransformerConfig:
optimizer: str = "sgd"
lr: float = 0.0005
hidden_dim: int = 256cs = ConfigStore.instance()# 直接使用类类型cs.store(name="config1", node=TransformerConfig)# 使用类实例(覆盖部分默认值)cs.store(name="config2", node=TransformerConfig(optimizer="rmsprop", lr=0.002))# 使用字典(会失去运行时类型安全保障)cs.store(name="config3", node={"optimizer": "adam", "lr": 0.003, "hidden_dim": 256})# 3. Hydra主函数:加载并打印配置@hydra.main(version_base=None, config_name="config1") # 默认加载config1def main(cfg: DictConfig) -> None:
print("当前加载的配置内容:")
print(cfg) if __name__ == "__main__":
main()
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70048957/viewspace-3107282/ ,如需转载,请注明出处,否则将追究法律责任。