python?配置管理框架Hydra使用指南
Hydra是Facebook Research開發(fā)的開源Python配置管理框架,旨在解決復(fù)雜項(xiàng)目中配置混亂、多環(huán)境與多參數(shù)組合管理的難題。該框架采用分層配置與動(dòng)態(tài)組合設(shè)計(jì),支持以YAML文件實(shí)現(xiàn)結(jié)構(gòu)化配置。Hydra尤其適用于簡(jiǎn)化機(jī)器學(xué)習(xí)實(shí)驗(yàn)、軟件開發(fā)及其他復(fù)雜應(yīng)用的配置管理。它的名字來源于希臘神話中的九頭蛇,寓意其能夠靈活管理多種配置組合。Hydra的核心特性包括支持多源分層配置組合、可通過命令行直接覆蓋配置、提供動(dòng)態(tài)命令補(bǔ)全功能,同時(shí)支持本地與遠(yuǎn)程運(yùn)行,并能通過單命令執(zhí)行批量參數(shù)作業(yè)。

Hydra的官方倉庫地址為:hydra,詳細(xì)文檔可參閱:hydra-doc。Hydra功能全面,本文主要介紹其基本使用方法,更多高級(jí)功能請(qǐng)參考官方文檔。截至本文撰寫時(shí),Hydra的穩(wěn)定版本為1.3,該版本兼容Python 3.6至3.11,并全面支持Linux、macOS和Windows操作系統(tǒng)。安裝命令如下:
pip install hydra-core --upgrade
1 基礎(chǔ)教程
1.1 快速入門
簡(jiǎn)單示例
以下代碼是一個(gè)簡(jiǎn)單的Hydra應(yīng)用示例,它會(huì)打印出配置信息,其中my_app函數(shù)是編寫業(yè)務(wù)邏輯的入口。
from omegaconf import DictConfig, OmegaConf
import hydra
@hydra.main(version_base=None)
def my_app(cfg: DictConfig) -> None:
print(OmegaConf.to_yaml(cfg))
if __name__ == "__main__":
my_app()如果你直接執(zhí)行這段代碼(沒有任何命令行參數(shù)),程序會(huì)輸出一個(gè)空的配置對(duì)象:
{}這是因?yàn)?,?dāng)運(yùn)行my_app.py時(shí),@hydra.main裝飾器會(huì)自動(dòng)攔截對(duì) my_app()的調(diào)用。此時(shí)Hydra會(huì)初始化一個(gè)空的DictConfig對(duì)象(類似于Python字典),并將其作為參數(shù)cfg 傳遞給函數(shù)。由于當(dāng)前配置為空,OmegaConf.to_yaml(cfg)將其轉(zhuǎn)換為YAML格式后,僅輸出一個(gè)空對(duì)象。OmegaConf是Hydra的底層配置引擎,Hydra基于OmegaConf實(shí)現(xiàn)上層的復(fù)雜應(yīng)用配置與運(yùn)行管理,且OmegaConf可獨(dú)立使用。
此外默認(rèn)情況下,Hydra會(huì)創(chuàng)建以下目錄結(jié)構(gòu)以追蹤和管理程序的運(yùn)行結(jié)果:
outputs/ ├── yyyy-mm-dd/ # 按日期分組 │ └── hh-mm-ss/ # 按時(shí)間精確到秒 │ └── .hydra/ # 保存本次運(yùn)行的配置 │ ├── config.yaml # 完整的配置 │ ├── hydra.yaml # Hydra 自身的配置 │ └── overrides.yaml # 命令行覆蓋的參數(shù) │ └── my_app.log # 日志文件(如果配置了日志) │ └── 其他輸出文件 # 你的程序生成的文件
可以通過以下方式為配置添加內(nèi)容:
通過命令行添加:
# 不支持直接在 +key=value 語法中傳入非 ASCII 字符 python my_app.py +name="zhangsan" +age=25
輸出:
name: zhangsan age: 25
創(chuàng)建配置文件:
創(chuàng)建一個(gè)config.yaml文件,然后運(yùn)行:
python my_app.py --config-path=. --config-name=config
在代碼中設(shè)置默認(rèn)配置:
可以修改代碼,為@hydra.main裝飾器添加配置參數(shù):
from omegaconf import DictConfig, OmegaConf
import 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"
使用++前綴可實(shí)現(xiàn)若配置中已存在該參數(shù)則覆蓋,若不存在則新增:
python my_app.py ++name="wangwu" ++password=1234
要注意??:Hydra通過命令行修改配置時(shí),僅會(huì)覆蓋或新增程序運(yùn)行時(shí)內(nèi)存中的配置數(shù)據(jù),不會(huì)改動(dòng)磁盤上的原始配置文件,重啟程序后仍會(huì)配置加載文件的原始配置。
配置對(duì)象使用
通過Hydra加載配置后,可通過屬性或字典式訪問或修改已有的配置項(xiàng),訪問不存在的配置項(xiàng)時(shí)會(huì)拋出異常:
from omegaconf import DictConfig, OmegaConf
import 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
# 訪問缺失值會(huì)拋出異常
try:
cfg.birth_year
except Exception as e:
print("error !!")
print(e)
if __name__ == "__main__":
my_app()之所以不允許訪問不存在的配置鍵,僅能操作已有配置鍵,是因?yàn)镠ydra默認(rèn)啟用了struct模式以嚴(yán)格結(jié)構(gòu)化配置。如需新增或修改配置,可先關(guān)閉嚴(yán)格模式,允許動(dòng)態(tài)新增鍵。但如果嵌套層級(jí)也未提前聲明,則需要先創(chuàng)建空嵌套,再為其添加子項(xiàng):
from omegaconf import DictConfig, OmegaConf
import hydra
import os
@hydra.main(version_base=None, config_path=".", config_name="config")
def my_app(cfg: DictConfig):
# 關(guān)閉struct模式,允許新增配置鍵
OmegaConf.set_struct(cfg, False)
# 同一級(jí)新增配置
cfg.birth_year = 1995
cfg["hobby"] = ["籃球", "編程"]
# 無法直接給不存在的嵌套層級(jí)鏈?zhǔn)劫x值
# cfg.address.city = "北京"
# 需要先創(chuàng)建嵌套層級(jí),再賦值子鍵
cfg.address = OmegaConf.create({}) # 顯式創(chuàng)建空的嵌套
cfg.address.city = "北京"
cfg.address["district"] = "朝陽區(qū)"
# 驗(yàn)證寫入結(jié)果
assert cfg.birth_year == 1995
assert cfg.address.district == "朝陽區(qū)"
print("配置寫入驗(yàn)證通過!")
if __name__ == "__main__":
my_app()對(duì)配置文件進(jìn)行分組
若希望分別使用CNN和Transformer模型對(duì)數(shù)據(jù)集進(jìn)行訓(xùn)練基準(zhǔn)測(cè)試,可通過配置組(Config Group)實(shí)現(xiàn)這一需求。配置組是一個(gè)帶有名稱的分組,包含一組有效的配置項(xiàng)。若選擇不存在的配置項(xiàng),系統(tǒng)會(huì)生成錯(cuò)誤提示,并列出所有有效的配置項(xiàng)。
創(chuàng)建配置組時(shí),需先新建一個(gè)目錄(例如model),用于存放各模型配置項(xiàng)對(duì)應(yīng)的文件。由于預(yù)計(jì)會(huì)創(chuàng)建多個(gè)配置組,建議提前將所有配置文件統(tǒng)一移至conf目錄下管理。
目錄結(jié)構(gòu)如下:
├─ conf │ └─ model │ ├─ cnn.yaml │ └─ transformer.yaml └── my_app.py
model/cnn.yaml:
backbone: resnet50 learning_rate: 0.001 batch_size: 32 epochs: 20 dropout: 0.2
model/transformer.yaml:
backbone: vit_base learning_rate: 0.0001 batch_size: 16 epochs: 30 attention_heads: 12
所有配置文件已統(tǒng)一存放至conf目錄,需通過config_path參數(shù)告知Hydra該目錄位置,并在代碼中指定待加載的配置文件名config_name。若未明確指定具體配置文件名,Hydra無法自動(dòng)推斷加載目標(biāo),最終會(huì)輸出空配置:
from omegaconf import DictConfig, OmegaConf
import 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()也可以通過命令行從配置組中選擇特定配置項(xiàng),命令行使用+分組名=配置項(xiàng)的格式,例如:
python my_app.py +model=transformer
與常規(guī)用法一致,仍可覆蓋最終配置中的單個(gè)參數(shù)值:
python my_app.py +model=transformer model.epochs=40
多文件處理
可以生成一個(gè)配置文件,在配置文件中用defaults參數(shù)添加默認(rèn)配置列表。該列表用于指定Hydra組合最終配置對(duì)象的規(guī)則,按照約定,它需作為配置文件的首個(gè)配置項(xiàng)。如下所示:
defaults: - model: cnn
然后這個(gè)配置文件可以命名為任意名字,如conf文件夾下的config.yaml,這樣運(yùn)行會(huì)默認(rèn)加載model對(duì)應(yīng)的文件配置:
from omegaconf import DictConfig, OmegaConf
import 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()默認(rèn)配置列表支持疊加多個(gè)深度學(xué)習(xí)相關(guān)配置項(xiàng)。若同一配置組存在兩個(gè)配置文件,系統(tǒng)會(huì)將這兩個(gè)配置文件合并為一個(gè)新字典;當(dāng)配置中出現(xiàn)相同鍵名時(shí),后加載的配置項(xiàng)會(huì)覆蓋先加載的配置項(xiàng)。示例默認(rèn)配置如下:
defaults:
- model:
- cnn
- transformer
若在配置文件夾conf下的dataset目錄中,存在如下配置文件model/cifar10.yaml:
name: CIFAR-10 path: ./data/cifar10 num_classes: 10 learning_rate: 0.0001 augmentation: true # 是否開啟數(shù)據(jù)增強(qiáng)
當(dāng)默認(rèn)配置文件conf/config.yaml中默認(rèn)配置列表的內(nèi)容如下:
defaults: - model: cnn - dataset: cifar10
由于model和dataset分屬不同的配置組,Hydra會(huì)將這兩個(gè)配置組的默認(rèn)配置進(jìn)行獨(dú)立合并。最終生成的完整配置結(jié)構(gòu)中,會(huì)包含model和dataset兩個(gè)一級(jí)配置項(xiàng),各自保留對(duì)應(yīng)配置組的完整參數(shù):
model: # 此處為conf/model/cnn.yaml中的配置內(nèi)容 dataset: # 此處為conf/dataset/cifar10.yaml中的配置內(nèi)容
即使設(shè)置了默認(rèn)配置,仍可手動(dòng)確定參數(shù)并覆蓋部分配置參數(shù):
python my_app.py model=cnn model.epochs=30
在配置項(xiàng)前添加~前綴,可從默認(rèn)配置列表中移除該默認(rèn)項(xiàng):
python my_app.py ~model
主配置的組合順序
主配置文件中可同時(shí)包含配置參數(shù)和默認(rèn)配置列表。在此情況下,若需調(diào)整默認(rèn)配置列表與主配置之間的覆蓋關(guān)系,可通過添加_self_關(guān)鍵字實(shí)現(xiàn):將_self_置于默認(rèn)配置列表末尾,則主配置參數(shù)將覆蓋默認(rèn)配置列表中的對(duì)應(yīng)項(xiàng);若將其置于列表開頭,則默認(rèn)配置列表中的參數(shù)將覆蓋主配置中的內(nèi)容。
需注意的是,從Hydra 1.1版本開始,默認(rèn)行為為主配置覆蓋默認(rèn)配置列表中的配置;而在此之前的版本中,默認(rèn)配置列表會(huì)覆蓋主配置的參數(shù)。
例如默認(rèn)配置文件config.yaml內(nèi)容如下,會(huì)進(jìn)行數(shù)據(jù)覆蓋,也就是說配置文件里dataset部分會(huì)覆蓋默認(rèn)配置中的同名部分:
defaults: - model: cnn - dataset: cifar10 - _self_ dataset: name: my_dataset version: 1.0
1.2 整合應(yīng)用
隨著軟件復(fù)雜度的不斷提升,我們會(huì)采用模塊化與組合化的設(shè)計(jì)思路來保證其可維護(hù)性。這種思路同樣適用于配置文件的管理。假設(shè)我們需要為示例程序配置多類深度學(xué)習(xí)模型支持,且每個(gè)模型對(duì)應(yīng)多種訓(xùn)練策略、搭配不同的數(shù)據(jù)預(yù)處理流程。使用Hydra時(shí),既不必為模型、策略、預(yù)處理流程的各類組合編寫?yīng)毩㈩悾矡o需為其單獨(dú)編寫配置文件。我們可以借鑒底層軟件開發(fā)的核心思路:通過組合化配置來解決這一問題。

多輪運(yùn)行(Multi-run)
對(duì)于使用多套配置運(yùn)行同一應(yīng)用程序的場(chǎng)景,可以通過命令行或配置文件兩種方式為Hydra應(yīng)用啟用多輪運(yùn)行功能。該功能自Hydra 1.2版本起引入,通過設(shè)置hydra.mode配置項(xiàng)實(shí)現(xiàn)。hydra.mode的合法取值包括RUN(單次運(yùn)行)和MULTIRUN(多輪運(yùn)行)。若在輸入配置中將hydra.mode設(shè)為MULTIRUN,應(yīng)用程序?qū)⒛J(rèn)以多輪運(yùn)行模式啟動(dòng)。
例如默認(rèn)配置文件為:
defaults: - model: cnn - dataset: cifar10
多輪運(yùn)行命令如下:
python my_app.py hydra.mode=MULTIRUN model=cnn,transformer dataset=cifar10
只要參數(shù)值用逗號(hào)分隔,就會(huì)被Hydra識(shí)別為多取值參數(shù),Hydra會(huì)把所有帶多個(gè)取值的參數(shù)做笛卡爾積(全組合),Hydra會(huì)把每個(gè)參數(shù)的取值兩兩配對(duì),生成以下多個(gè)任務(wù),依次運(yùn)行:
python my_app.py hydra.mode=MULTIRUN model=cnn,transformer dataset=cifar10 # 本地啟動(dòng)2個(gè)任務(wù) #0 : model=cnn dataset=cifar10 #1 : model=transformer dataset=cifar10
該命令可以用命令行參數(shù)簡(jiǎn)化:
python my_app.py --multirun model=cnn,transformer dataset=cifar10 # 或 python my_app.py -m model=cnn,transformer dataset=cifar10
注意Hydra會(huì)在任務(wù)啟動(dòng)時(shí)延遲組合配置。若在啟動(dòng)任務(wù)參數(shù)遍歷后修改代碼或配置文件,最終組合生成的配置可能會(huì)受影響。
也可以在輸入配置中通過覆蓋hydra.sweeper.params來定義參數(shù)遍歷規(guī)則并通過mode設(shè)置運(yùn)行模式。沿用上述示例,以下配置可實(shí)現(xiàn)完全相同的多輪運(yùn)行效果:
defaults:
- model: cnn
- dataset: cifar10
hydra:
mode: MULTIRUN # 設(shè)置運(yùn)行模式
sweeper:
params:
dataset: cifar10
model: transformer, cnn直接運(yùn)行程序不使用任何附加參數(shù),結(jié)果如下:
$ python my_app.py # 本地啟動(dòng)2個(gè)任務(wù) #0 : model=transformer dataset=cifar10 #1 : model=cnn dataset=cifar10
1.3 信息管理
輸出目錄
Hydra能夠解決每次運(yùn)行程序時(shí)需要手動(dòng)指定新輸出目錄的問題,它會(huì)為每次運(yùn)行自動(dòng)創(chuàng)建一個(gè)專屬目錄,并在該輸出目錄中執(zhí)行代碼。默認(rèn)情況下,每次運(yùn)行應(yīng)用程序時(shí),都會(huì)生成一個(gè)全新的輸出目錄??梢酝ㄟ^讀取Hydra配置來獲取本次運(yùn)行該輸出目錄的路徑,示例如下:
from omegaconf import DictConfig, OmegaConf
import hydra
import 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()通過設(shè)置hydra.job.chdir=True,可以讓Hydra的@hydra.main裝飾器在執(zhí)行用戶的主函數(shù)前,調(diào)用os.chdir將Python工作目錄切換到輸出目錄:
python my_app.py hydra.job.chdir=True
可以通過覆蓋配置項(xiàng)hydra.output_subdir將設(shè)為null,則會(huì)完全禁用該子目錄的創(chuàng)建。
日志
由于標(biāo)準(zhǔn)logging模塊配置較為復(fù)雜,為實(shí)現(xiàn)常規(guī)的日志功能通常需要編寫較多代碼,且配置過程不夠簡(jiǎn)便。Hydra能夠自動(dòng)完成Python logging的配置,從而有效解決這一問題。默認(rèn)情況下,Hydra會(huì)以INFO級(jí)別向控制臺(tái)輸出日志,同時(shí)在當(dāng)前工作目錄自動(dòng)生成日志文件留存記錄。以下為使用Hydra進(jìn)行日志記錄的示例:
# hydra_log_demo.py
import logging
from omegaconf import DictConfig
import hydra
# 為本文件創(chuàng)建日志器
log = logging.getLogger(__name__)
@hydra.main(version_base=None)
def my_app(_cfg: DictConfig) -> None:
log.info("Info 級(jí)別日志消息")
log.debug("Debug 級(jí)別日志消息")
if __name__ == "__main__":
my_app()
可通過在命令行中指定hydra.verbose配置項(xiàng)來啟用DEBUG級(jí)別的日志輸出。該配置項(xiàng)支持布爾值、字符串或列表類型的取值,開啟全部或指定日志器的DEBUG級(jí)別輸出如下:
python hydra_log_demo.py hydra.verbose=true
若要將特定函數(shù)對(duì)應(yīng)日志器的級(jí)別設(shè)為DEBUG,可使用如下命令:
python hydra_log_demo.py hydra.verbose="[__main__,my_custom_logger]"
其效果等同于代碼:
import logging logging.getLogger(NAME).setLevel(logging.DEBUG)
如果不希望Hydra自動(dòng)配置日志系統(tǒng),可以將hydra/job_logging(對(duì)應(yīng)程序的日志)和hydra/hydra_logging(對(duì)應(yīng)Hydra框架自身的日志)均設(shè)為none:
python my_app.py hydra/job_logging=None hydra/hydra_logging=None
調(diào)試功能
Hydra提供多種配置選項(xiàng),可有效提升程序的可調(diào)試性。在命令行中使用--cfg或-c參數(shù),即可在不運(yùn)行目標(biāo)函數(shù)的情況下打印應(yīng)用程序的配置信息。該參數(shù)需配合一個(gè)選項(xiàng)來指定打印的配置范圍:
- job:打印業(yè)務(wù)代碼的配置
- hydra:打印hydra框架自身的配置
- all:打印完整配置內(nèi)容,即業(yè)務(wù)配置與hydra配置的合集
僅打印業(yè)務(wù)配置指令如下:
$ python my_app.py --cfg job
若只展示配置中的某一子集,可搭配參數(shù)--package或簡(jiǎn)寫-p使用:
python my_app.py --cfg hydra --package hydra.job
默認(rèn)情況下,配置中的插值表達(dá)式不會(huì)被解析。若需打印解析后的最終配置,可在--cfg參數(shù)基礎(chǔ)上,額外添加--resolve參數(shù)。
信息查詢功能
使用--info參數(shù)可查詢Hydra框架及應(yīng)用程序的各類相關(guān)信息:
- --info all:默認(rèn)模式,打印所有可用信息
- --info config:打印配置組合相關(guān)的輔助信息,包括:配置搜索路徑、默認(rèn)配置樹、默認(rèn)配置列表及最終生效的配置內(nèi)容
- --info defaults:打印最終的默認(rèn)配置列表
- --info defaults-tree:打印默認(rèn)配置樹結(jié)構(gòu)
- --info plugins:打印已安裝的插件信息
2 結(jié)構(gòu)化配置
在復(fù)雜項(xiàng)目中,配置文件常面臨類型模糊、配置錯(cuò)誤難排查、缺少靜態(tài)校驗(yàn)等問題。例如:字段類型不明確易引發(fā)運(yùn)行時(shí)異常、多層級(jí)配置的結(jié)構(gòu)一致性難以保障、協(xié)作時(shí)難以通過工具提前發(fā)現(xiàn)配置沖突。

為此,Hydra基于Python數(shù)據(jù)類(dataclasses)定義了配置結(jié)構(gòu)與類型,其核心價(jià)值在于提供運(yùn)行時(shí)類型檢查與靜態(tài)類型檢查雙重保障。它支持基礎(chǔ)類型(int、str、bool、float、Enum 等)、嵌套結(jié)構(gòu)、容器類型(List、Dict)以及可選字段,但也存在部分限制,例如僅部分支持聯(lián)合類型,且不支持自定義方法。
Hydra中結(jié)構(gòu)化配置主要有兩種使用模式,均完整保留其核心功能:
- 直接作為配置使用(替代配置文件),適合快速入門;
- 作為配置模式(schema)使用,用于校驗(yàn)現(xiàn)有配置文件,適合大型或協(xié)作項(xiàng)目。
本教程將按此順序依次詳解兩種模式。
2.1 Hydra代碼配置
在后續(xù)的教程中,我們將使用ConfigStore類把數(shù)據(jù)類(dataclasses)注冊(cè)為Hydra中的輸入配置。ConfigStore是一個(gè)在內(nèi)存中存儲(chǔ)配置的單例(singleton)對(duì)象,與它交互的核心API是下文將要介紹的store方法。
class ConfigStore(metaclass=Singleton):
def store(
self,
name: str,
node: Any,
group: Optional[str] = None,
package: Optional[str] = "_group_",
provider: Optional[str] = None,
) -> None:
"""
將配置節(jié)點(diǎn)存儲(chǔ)至配置倉庫中
:param name: 配置名稱
:param node: 配置節(jié)點(diǎn),支持 DictConfig、ListConfig、
結(jié)構(gòu)化配置(Structured configs),甚至普通的 dict 和 list 類型
:param group: 配置分組,子分組分隔符為 '/',
例如 hydra/launcher
:param package: 配置節(jié)點(diǎn)的父級(jí)層級(jí)結(jié)構(gòu)。
子節(jié)點(diǎn)分隔符為 '.',例如 foo.bar.baz
:param provider: 提供該配置的模塊/應(yīng)用名稱,
有助于調(diào)試排查問題。
"""
...ConfigStore具備與YAML輸入配置完全一致的功能,除此之外還提供類型校驗(yàn)?zāi)芰?。它既可單?dú)使用,也可與YAML配合使用。
基礎(chǔ)用例
假設(shè)我們有一個(gè)簡(jiǎn)單的應(yīng)用程序,且存在一個(gè)包含cnn選項(xiàng)的model配置分組:
from omegaconf import DictConfig, OmegaConf
import 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()目錄結(jié)構(gòu):
├─ conf │ └─ model │ └─ cnn.yaml └── my_app.py
model/cnn.yaml:
backbone: resnet50 learning_rate: 0.001 batch_size: 32 epochs: 20 dropout: 0.2
如果現(xiàn)在想要新增一個(gè)transformer選項(xiàng)該怎么做?我們可以直接新增model/transformer.yaml配置分組文件,但這并非唯一方式!也可以通過ConfigStore為Hydra新增model配置分組的transformer選項(xiàng)。
要實(shí)現(xiàn)這個(gè)需求,只需在上述代碼文件中添加幾行代碼:
from dataclasses import dataclass
import hydra
from omegaconf import DictConfig, OmegaConf
from hydra.core.config_store import ConfigStore
@dataclass
class TransformerConfig:
optimizer: str = "sgd"
lr: float = 0.0005
hidden_dim: int = 256
cs = ConfigStore.instance()
# 將名為transformer的配置類注冊(cè)至model配置分組
# 注意出現(xiàn)實(shí)體文件會(huì)報(bào)錯(cuò)
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()上述代碼不會(huì)生成實(shí)際的物理配置文件,它僅用于在內(nèi)存中注冊(cè)配置類。現(xiàn)在應(yīng)用程序已經(jīng)能夠識(shí)別model配置組中的兩個(gè)選項(xiàng),您可以通過以下命令運(yùn)行程序來驗(yàn)證效果:
python my_app.py +model=cnn
或
python my_app.py +model=transformer
在深度學(xué)習(xí)實(shí)驗(yàn)中管理多個(gè)模型配置時(shí),我們還可以借助ConfigStore支持的三種注冊(cè)方式靈活控制配置節(jié)點(diǎn),實(shí)現(xiàn)不同方案間的快速切換:
from dataclasses import dataclass
import hydra
from omegaconf import DictConfig, OmegaConf
from hydra.core.config_store import ConfigStore
@dataclass
class TransformerConfig:
optimizer: str = "sgd"
lr: float = 0.0005
hidden_dim: int = 256
cs = ConfigStore.instance()
# 直接使用類類型
cs.store(name="config1", node=TransformerConfig)
# 使用類實(shí)例(覆蓋部分默認(rèn)值)
cs.store(name="config2", node=TransformerConfig(optimizer="rmsprop", lr=0.002))
# 使用字典(會(huì)失去運(yùn)行時(shí)類型安全保障)
cs.store(name="config3", node={"optimizer": "adam", "lr": 0.003, "hidden_dim": 256})
# 3. Hydra主函數(shù):加載并打印配置
@hydra.main(version_base=None, config_name="config1") # 默認(rèn)加載config1
def main(cfg: DictConfig) -> None:
print("當(dāng)前加載的配置內(nèi)容:")
print(cfg)
if __name__ == "__main__":
main()配置組
在Hydra框架中,配置組是一種用于組織互斥但相關(guān)配置項(xiàng)的機(jī)制。以深度學(xué)習(xí)場(chǎng)景為例,訓(xùn)練CNN與Transformer屬于不同的模型配置,它們都屬于模型配置這一大類,但一次訓(xùn)練只能選擇其中一種,這就是配置組的典型應(yīng)用。
# 導(dǎo)入必要的庫
from dataclasses import dataclass
from typing import Any
import hydra
from hydra.core.config_store import ConfigStore
from omegaconf import OmegaConf
# 1. 定義CNN模型的配置
@dataclass # 裝飾器:將普通類轉(zhuǎn)為結(jié)構(gòu)化配置類(自動(dòng)生成初始化、比較等方法)
class CNNConfig:
"""CNN模型的訓(xùn)練配置(包含該模型特有的所有參數(shù))"""
model_type: str = "cnn"
batch_size: int = 32
learning_rate: float = 0.001
# 2. 定義Transformer模型的配置
@dataclass
class TransformerConfig:
"""Transformer模型的訓(xùn)練配置(包含該模型特有的所有參數(shù))"""
model_type: str = "transformer"
batch_size: int = 16
learning_rate: float = 0.0001
num_heads: int = 8
@dataclass
class Config:
"""整個(gè)訓(xùn)練程序的主配置類"""
# model字段:用于接收配置組中選擇的模型配置(暫時(shí)標(biāo)注為Any類型)
model: Any
# 1. 獲取配置存儲(chǔ)庫的單例實(shí)例(整個(gè)程序只有一個(gè)ConfigStore)
cs = ConfigStore.instance()
# 2. 注冊(cè)主配置(名稱為"config",對(duì)應(yīng)后續(xù)hydra.main的config_name)
cs.store(name="config", node=Config)
# 3. 注冊(cè)配置組:組名是"model",包含兩個(gè)選項(xiàng):
cs.store(group="model", name="cnn", node=CNNConfig)
cs.store(group="model", name="transformer", node=TransformerConfig)
# hydra.main裝飾器:標(biāo)記程序入口,指定配置名稱為"config"
@hydra.main(version_base=None, config_name="config")
def train_model(cfg: Config) -> None:
"""深度學(xué)習(xí)模型訓(xùn)練的主函數(shù)"""
print("===== 當(dāng)前使用的訓(xùn)練配置 =====")
print(OmegaConf.to_yaml(cfg))
# 程序啟動(dòng)入口
if __name__ == "__main__":
train_model()代碼運(yùn)行直接輸出為:
model: ???
??? 表示:該字段本應(yīng)有值,但目前處于缺失狀態(tài)。由于我們未給模型配置組設(shè)置默認(rèn)值,因此必須通過命令行顯式指定要使用的模型配置。注意命令中的+是必需的,因?yàn)槟P团渲媒M沒有默認(rèn)值,+在這里表示添加并覆蓋該配置字段:
python my_app.py +model=cnn
在上面實(shí)現(xiàn)中,model字段被標(biāo)注為Any類型,這雖然不會(huì)阻礙程序運(yùn)行,但卻把配置對(duì)象當(dāng)作一個(gè)缺乏類型信息的黑箱字典,使得IDE無法提供智能提示,靜態(tài)類型檢查也完全失效,從而降低了代碼的可維護(hù)性和長(zhǎng)期可靠性。要解決這一問題,解決方法是將不同模型配置之間的公共字段進(jìn)行抽象,創(chuàng)建一個(gè)BaseModelConfig基礎(chǔ)配置類:
from dataclasses import dataclass
from typing import Any
import hydra
from omegaconf import MISSING # 標(biāo)記字段“無默認(rèn)值”
from hydra.core.config_store import ConfigStore
from omegaconf import OmegaConf
@dataclass
class BaseModelConfig:
"""所有深度學(xué)習(xí)模型的基礎(chǔ)配置(抽離公共字段)"""
model_type: str = MISSING # 模型類型:無默認(rèn)值(必須由子類指定)
batch_size: int = 32 # 公共字段:默認(rèn)批次大?。ㄗ宇惪芍貙懀?
learning_rate: float = 0.001 # 公共字段:默認(rèn)學(xué)習(xí)率(子類可重寫)
# 1. 定義CNN模型的配置
@dataclass # 裝飾器:將普通類轉(zhuǎn)為結(jié)構(gòu)化配置類(自動(dòng)生成初始化、比較等方法)
class CNNConfig(BaseModelConfig):
"""CNN模型的訓(xùn)練配置(包含該模型特有的所有參數(shù))"""
model_type: str = "cnn"
batch_size: int = 32
learning_rate: float = 0.001
# 2. 定義Transformer模型的配置
@dataclass
class TransformerConfig(BaseModelConfig):
"""Transformer模型的訓(xùn)練配置(包含該模型特有的所有參數(shù))"""
model_type: str = "transformer"
batch_size: int = 16
learning_rate: float = 0.0001
num_heads: int = 8
@dataclass
class Config:
"""整個(gè)訓(xùn)練程序的主配置類"""
# 不再是Any,而是BaseModelConfig
model: BaseModelConfig
# 1. 獲取配置存儲(chǔ)庫的單例實(shí)例(整個(gè)程序只有一個(gè)ConfigStore)
cs = ConfigStore.instance()
# 2. 注冊(cè)主配置(名稱為"config",對(duì)應(yīng)后續(xù)hydra.main的config_name)
cs.store(name="config", node=Config)
# 3. 注冊(cè)配置組:組名是"model",包含兩個(gè)選項(xiàng):
cs.store(group="model", name="cnn", node=CNNConfig)
cs.store(group="model", name="transformer", node=TransformerConfig)
# hydra.main裝飾器:標(biāo)記程序入口,指定配置名稱為"config"
@hydra.main(version_base=None, config_name="config")
def train_model(cfg: Config) -> None:
"""深度學(xué)習(xí)模型訓(xùn)練的主函數(shù)"""
print("===== 當(dāng)前使用的訓(xùn)練配置 =====")
print(OmegaConf.to_yaml(cfg))
# 程序啟動(dòng)入口
if __name__ == "__main__":
train_model()可以在主結(jié)構(gòu)化配置中設(shè)置默認(rèn)值,方法與在config.yaml配置文件中定義相似。以下是一個(gè)深度學(xué)習(xí)模型配置的示例,新增了默認(rèn)配置列表,使其默認(rèn)加載model=cnn。只需在代碼中添加默認(rèn)列表,并相應(yīng)修改配置類即可:
from dataclasses import dataclass, field
from typing import Any, List
# 定義默認(rèn)配置列表:從配置組"model"中加載名為"cnn"的配置
defaults = [
{"model": "cnn"}
# 設(shè)為 MISSING,則可強(qiáng)制用戶在命令行中指定該參數(shù)的值。
# {"model": MISSING}
]
@dataclass
class Config:
"""整個(gè)訓(xùn)練程序的主配置類"""
# 受@dataclass限制,此處需通過field定義默認(rèn)配置列表(默認(rèn)加載cnn配置)
defaults: List[Any] = field(default_factory=lambda: defaults)
# Hydra會(huì)根據(jù)默認(rèn)配置列表自動(dòng)填充該字段,類型為BaseModelConfig
model: BaseModelConfig = MISSING你也可以通過命令行覆蓋默認(rèn)配置,指定使用Transformer模型,注意不要+號(hào),因?yàn)檫@是覆蓋操作:
python my_app.py model=transformer
2.2 配置模式
Hydra的結(jié)構(gòu)化配置本質(zhì)是用代碼定義的結(jié)構(gòu)化規(guī)則來管理配置。它除了可以用代碼定義的結(jié)構(gòu)化配置替代傳統(tǒng)的YAML配置文件外,還能將結(jié)構(gòu)化配置作為配置規(guī)則模板(Schema),用于校驗(yàn)已有YAML配置文件是否符合規(guī)范。Schema能夠在程序啟動(dòng)時(shí)校驗(yàn)配置的合法性,提前攔截所有配置錯(cuò)誤。這對(duì)于保障大型項(xiàng)目的穩(wěn)定性至關(guān)重要,尤其適合多人協(xié)作的場(chǎng)景,可以有效避免配置錯(cuò)寫、漏寫字段等問題。
同一配置組內(nèi)的Schema校驗(yàn)
以深度學(xué)習(xí)訓(xùn)練場(chǎng)景的模型配置為例,下文將拆解如何通過預(yù)設(shè)的Schema模板,校驗(yàn)同一配置分組下各配置文件的字段、類型等是否符合規(guī)范要求。
給定如下配置目錄結(jié)構(gòu):
conf/
├── config.yaml # 主配置(包含訓(xùn)練、模型、數(shù)據(jù)等)
└── model # 模型配置組
├── resnet.yaml # ResNet模型配置
└── transformer.yaml # Transformer模型配置需為上述每個(gè)配置文件添加結(jié)構(gòu)化配置Schema,核心方式是在YAML文件的defaults列表中聲明要繼承的Schema模板,并將這些Schema以base_config、model/resnet.yaml、model/transformer.yaml為名注冊(cè)至Hydra配置倉庫。各配置文件的defaults列表配置如下:
config.yaml:
defaults: - base_config # 繼承基礎(chǔ)配置Schema - model: resnet # 默認(rèn)使用ResNet模型配置 - _self_ # 自身配置覆蓋默認(rèn)值 # 自定義訓(xùn)練參數(shù) train: batch_size: 32 lr: 0.001 debug: true
model/resnet.yaml:
defaults: - base_resnet # 繼承ResNet基礎(chǔ)Schema # ResNet專屬參數(shù) layers: 50 pretrained: true num_classes: 1000
model/transformer.yaml:
defaults: - base_transformer # 繼承Transformer基礎(chǔ)Schema # Transformer專屬參數(shù) num_heads: 8 num_layers: 6 hidden_dim: 512 max_seq_len: 512
通過Python dataclass定義各種Schema規(guī)則,并注冊(cè)到Hydra配置倉庫,使YAML配置文件可關(guān)聯(lián)到對(duì)應(yīng)的校驗(yàn)規(guī)則:
from dataclasses import dataclass
from omegaconf import OmegaConf, MISSING
import hydra
from hydra.core.config_store import ConfigStore
# -------------------------- 基礎(chǔ)Schema定義 --------------------------
@dataclass
class BaseModelConfig:
"""所有模型的基礎(chǔ)配置Schema"""
model_type: str = MISSING # 必選字段,無默認(rèn)值
device: str = "cuda" # 可選字段,默認(rèn)值cuda
dropout: float = MISSING # 必選字段,無默認(rèn)值
@dataclass
class ResNetConfig(BaseModelConfig):
"""ResNet模型專屬Schema(繼承基礎(chǔ)模型配置)"""
model_type: str = "resnet" # 固定值,標(biāo)識(shí)模型類型
dropout: float = 0.1 # 覆蓋默認(rèn)值
layers: int = MISSING # ResNet專屬必選字段
pretrained: bool = MISSING
num_classes: int = MISSING
@dataclass
class TransformerConfig(BaseModelConfig):
"""Transformer模型專屬Schema(繼承基礎(chǔ)模型配置)"""
model_type: str = "transformer" # 固定值
dropout: float = 0.1 # 覆蓋默認(rèn)值
num_heads: int = MISSING # Transformer專屬必選字段
num_layers: int = MISSING
hidden_dim: int = MISSING
max_seq_len: int = MISSING
@dataclass
class TrainConfig:
"""訓(xùn)練配置Schema"""
batch_size: int = MISSING
lr: float = MISSING
epochs: int = 10 # 默認(rèn)訓(xùn)練10輪
@dataclass
class Config:
"""整體配置Schema"""
model: BaseModelConfig = MISSING # 模型配置(必選)
train: TrainConfig = MISSING # 訓(xùn)練配置(必選)
debug: bool = False # 調(diào)試模式(可選)
# -------------------------- 注冊(cè)Schema到配置倉庫 --------------------------
cs = ConfigStore.instance()
cs.store(name="base_config", node=Config) # 注冊(cè)主配置Schema
cs.store(group="model", name="base_resnet", node=ResNetConfig) # 注冊(cè)ResNet Schema
cs.store(group="model", name="base_transformer", node=TransformerConfig) # 注冊(cè)Transformer Schema
# -------------------------- 主函數(shù) --------------------------
@hydra.main(version_base=None, config_path="conf", config_name="config")
def train_app(cfg: Config) -> None:
"""深度學(xué)習(xí)訓(xùn)練入口,打印最終配置"""
print("最終訓(xùn)練配置:")
print(OmegaConf.to_yaml(cfg))
if __name__ == "__main__":
train_app()運(yùn)行代碼時(shí),Hydra會(huì)先加載YAML配置文件,再通過關(guān)聯(lián)的Schema完成合法性校驗(yàn),若配置存在錯(cuò)誤會(huì)立即拋出異常:
# 正常運(yùn)行(使用默認(rèn)ResNet配置) python my_app.py # 模擬配置錯(cuò)誤(layers字段應(yīng)為int類型,傳入字符串觸發(fā)校驗(yàn)失敗) python my_app.py model.layers='attention'
跨配置組的Schema校驗(yàn)
在前文的模型訓(xùn)練場(chǎng)景中,Schema都定義在主程序里。但實(shí)際開發(fā)中,常會(huì)遇到第三方庫提供標(biāo)準(zhǔn)化的Schema,我們需要跨配置組引用這些Schema來校驗(yàn)自己的配置文件,而非把所有Schema都寫在主程序中。
假設(shè)存在一個(gè)公共的optimizer_lib庫,該庫預(yù)先定義了所有模型的標(biāo)準(zhǔn)Schema并注冊(cè)在獨(dú)立配置組中;本地conf/optimizer配置組下的YAML文件(如sgd.yaml、adam.yaml)僅需編寫YAML配置文件,并關(guān)聯(lián)這個(gè)外部庫的Schema完成校驗(yàn),無需重復(fù)定義模型規(guī)則,配置目錄結(jié)構(gòu)如下:
# 項(xiàng)目整體目錄
├── my_app.py # 主程序
├── optimizer_lib.py # 獨(dú)立的優(yōu)化器Schema庫
└── conf/
├── config.yaml # 主配置
└── optimizer/ # 本地優(yōu)化器配置組
├── sgd.yaml
└── adam.yaml
optimizer_lib.py代碼如下:
from dataclasses import dataclass
from omegaconf import MISSING
from hydra.core.config_store import ConfigStore
# -------------------------- 優(yōu)化器基礎(chǔ)Schema --------------------------
@dataclass
class BaseOptimizerConfig:
"""所有優(yōu)化器的基礎(chǔ)Schema(獨(dú)立庫定義)"""
opt_type: str = MISSING # 必選字段:優(yōu)化器類型
lr: float = MISSING # 必選字段:學(xué)習(xí)率
weight_decay: float = 0.0 # 可選字段:權(quán)重衰減,默認(rèn)0
# -------------------------- 具體優(yōu)化器Schema --------------------------
@dataclass
class SGDConfig(BaseOptimizerConfig):
"""SGD優(yōu)化器專屬Schema"""
opt_type: str = "sgd" # 固定標(biāo)識(shí)
momentum: float = MISSING # SGD專屬必選字段
nesterov: bool = False # 可選字段:是否使用Nesterov動(dòng)量
@dataclass
class AdamConfig(BaseOptimizerConfig):
"""Adam優(yōu)化器專屬Schema"""
opt_type: str = "adam" # 固定標(biāo)識(shí)
betas: tuple[float, float] = (0.9, 0.999) # 可選字段:beta參數(shù)
eps: float = MISSING # Adam專屬必選字段
# -------------------------- 注冊(cè)跨配置組的Schema --------------------------
def register_optimizer_configs() -> None:
cs = ConfigStore.instance()
# 注冊(cè)到獨(dú)立分組:optimizer_lib/optimizer
cs.store(
group="optimizer_lib/optimizer",
name="sgd",
node=SGDConfig
)
cs.store(
group="optimizer_lib/optimizer",
name="adam",
node=AdamConfig
)主程序my_app.py中定義整體配置Schema,并調(diào)用optimizer_lib的注冊(cè)函數(shù),將跨配置組的Schema納入Hydra配置倉庫:
from dataclasses import dataclass
from omegaconf import MISSING, OmegaConf
import hydra
from hydra.core.config_store import ConfigStore
import optimizer_lib # 導(dǎo)入獨(dú)立的優(yōu)化器庫
# -------------------------- 整體配置Schema --------------------------
@dataclass
class TrainConfig:
"""訓(xùn)練配置Schema"""
batch_size: int = 32
epochs: int = 10
@dataclass
class Config:
"""主配置Schema"""
optimizer: optimizer_lib.BaseOptimizerConfig = MISSING # 引用獨(dú)立庫的Schema
train: TrainConfig = MISSING
debug: bool = False
# -------------------------- 注冊(cè)本地Schema并加載跨組Schema --------------------------
cs = ConfigStore.instance()
cs.store(name="base_config", node=Config) # 注冊(cè)主配置Schema
optimizer_lib.register_optimizer_configs() # 注冊(cè)跨配置組的優(yōu)化器Schema
# -------------------------- 主函數(shù) --------------------------
@hydra.main(version_base=None, config_path="conf", config_name="config")
def train_app(cfg: Config) -> None:
"""訓(xùn)練入口,打印最終配置并觸發(fā)Schema校驗(yàn)"""
print("最終訓(xùn)練配置(含跨組優(yōu)化器配置):")
print(OmegaConf.to_yaml(cfg))
if __name__ == "__main__":
train_app()本地conf/optimizer下的YAML文件需要通過絕對(duì)路徑引用optimizer_lib中的Schema,并通過@_here_指定包路徑,確保Schema的校驗(yàn)作用域與當(dāng)前配置一致。
conf/optimizer/sgd.yaml:
defaults: - /optimizer_lib/optimizer/sgd@_here_ # 絕對(duì)路徑引用跨組Schema,@_here_統(tǒng)一包作用域 # SGD專屬配置(需符合SGDConfig的Schema規(guī)則) lr: 0.01 momentum: 0.9 weight_decay: 0.0001
conf/optimizer/adam.yaml:
defaults: - /optimizer_lib/optimizer/adam@_here_ # 絕對(duì)路徑引用跨組Schema - _self_ # 自身配置覆蓋Schema默認(rèn)值(組合順序:Schema先加載,自身配置后覆蓋) # Adam專屬配置(需符合AdamConfig的Schema規(guī)則) lr: 0.001 betas: (0.9, 0.999) eps: 1e-08 weight_decay: 0.0005
主配置conf/config.yaml:
defaults: - base_config # 主配置Schema - optimizer: sgd # 默認(rèn)使用SGD優(yōu)化器配置 - _self_ # 自定義訓(xùn)練參數(shù) train: batch_size: 64 epochs: 20 debug: true
運(yùn)行代碼時(shí),Hydra會(huì)先加載第三方庫的Schema,再校驗(yàn)本地YAML配置:
# 正常運(yùn)行(SGD配置符合Schema規(guī)則) python my_app.py # 配置錯(cuò)誤(momentum應(yīng)為float,傳入字符串觸發(fā)校驗(yàn)失?。? python my_app.py optimizer.momentum='high' # 配置錯(cuò)誤(Adam必選字段eps缺失,啟動(dòng)時(shí)直接報(bào)錯(cuò)) python my_app.py optimizer=adam optimizer.eps=none
3 參考
到此這篇關(guān)于python 配置管理框架Hydra使用指北的文章就介紹到這了,更多相關(guān)python 配置管理框架Hydra使用指北內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Python自動(dòng)化操作Excel/Word/PDF的實(shí)戰(zhàn)指南
在現(xiàn)代辦公環(huán)境中,我們經(jīng)常需要處理各種文檔格式,如Excel表格、Word文檔和PDF文件,下面我們就來看看如何使用Python自動(dòng)化進(jìn)行相關(guān)操作吧2025-09-09
使用matplotlib.pyplot繪制多個(gè)圖片和圖表實(shí)現(xiàn)方式
這篇文章主要介紹了使用matplotlib.pyplot繪制多個(gè)圖片和圖表的實(shí)現(xiàn)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-08-08
基于Python實(shí)現(xiàn)語音識(shí)別和語音轉(zhuǎn)文字
這篇文章主要為大家詳細(xì)介紹了如何利用Python實(shí)現(xiàn)語音識(shí)別和語音轉(zhuǎn)文字功能,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解一下2022-09-09
Python全局變量與global關(guān)鍵字常見錯(cuò)誤解決方案
這篇文章主要介紹了Python全局變量與global關(guān)鍵字常見錯(cuò)誤解決方案,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-10-10
django如何計(jì)算兩個(gè)TimeField的時(shí)差
在Django中,可以使用datetime模塊來計(jì)算兩個(gè)TimeField字段的時(shí)間差,下面通過示例代碼介紹django?計(jì)算兩個(gè)TimeField的時(shí)差,需要的朋友可以參考下2023-05-05

