在 GitHub 上编辑

Hydra 组合

Hydra 是一个用于配置复杂应用程序的框架。DVC 支持 Hydra 的 配置组合,以此作为配置 实验运行 的方式。

你必须通过以下命令显式启用此功能:

$ dvc config hydra.enabled True

工作原理

在执行 dvc exp run 时,Hydra 将被用来在运行实验前组合生成单个 params.yaml 文件。该文件中的 参数 将用于配置包含你实验的 底层 DVC 流水线

DVC 流水线 可以运行多个阶段,并使用不同的 shell 命令而非单一脚本,还提供诸如 模板化foreach 阶段 等高级功能。

设置 Hydra

首先,你需要一个 conf/ 目录来存放 Hydra 的 配置组。例如:

conf
├── config.yaml
├── dataset
│   ├── imagenette.yaml
│   └── imagewoof.yaml
└── train
    ├── baseline.yaml
    ├── model
    │   ├── alexnet.yaml
    │   ├── efficientnet.yaml
    │   └── resnet.yaml
    └── optimizer
        ├── adam.yaml
        └── sgd.yaml

你还需创建一个 conf/config.yaml 文件,定义 Hydra 的 默认列表

defaults:
  - dataset: imagenette
  - train/model: resnet
  - train/optimizer: sgd

使用 dvc config hydra 选项可更改配置组目录和默认列表文件的默认位置。

现在我们来看一下基于上述设置生成的 params.yaml 文件可能是什么样子:

dataset:
  url: https://s3.amazonaws.com/fast-ai-imageclas/imagenette2-160.tgz
  output_folder: imagenette
train:
  model:
    name: ResNet
    size: 50
    weights: ResNet50_Weights.IMAGENET1K_V2
  optimizer:
    name: SGD
    lr: 0.001
    momentum: 0.9

运行实验

让我们构建一个包含两个阶段的 实验流水线。第一阶段下载数据集,并使用 params.yamldataset 部分定义的参数;第二阶段训练机器学习模型,并使用其余参数(整个 train 组)。

stages:
  setup-dataset:
    cmd:
      - wget ${dataset.url} -O tmp.tgz
      - mkdir -p ${dataset.output_folder}
      - tar zxvf tmp.tgz -C ${dataset.output_folder}
      - rm tmp.tgz
    outs:
      - ${dataset.output_folder}
  train:
    cmd: python train.py
    deps:
      - ${dataset.output_folder}
    params:
      - train

我们对上述 shell 命令(mkdirtarwget)以及 输出依赖项 路径(outsdeps)使用 模板化${} 表达式)进行参数化。

你可以使用任意 YAML 解析库加载这些参数。在 Python 中,可以使用内置的 dvc.api.params_show() 或 Hydra 自带的 OmegaConf.load("params.yaml")

现在我们可以使用 dvc exp run --set-param 在运行时动态修改配置组合,例如从 train/model/efficientnet.yaml 加载模型配置(而不是来自 默认列表resnet.yaml):

$ dvc exp run --set-param 'train/model=efficientnet'
Stage 'setup-dataset' didn't change  skipping
Running stage 'train':
> python train.py
--model.name 'EfficientNet' --model.size 'b0' --model.weights 'ResNet50_Weights.IMAGENET1K_V2'
--optimizer.name 'SGD' --optimizer.lr 0.001 --optimizer.momentum 0.9
...

如果需要 调试 组合后的值,请添加 -vv 参数(dvc exp run -vv --set-param 'train/model=efficientnet')。

我们也可以修改任意配置部分中的特定值:

$ dvc exp run --set-param 'train.optimizer.lr=0.1'
Stage 'setup-dataset' didn't change, skipping
Running stage 'train':
> python train.py
--model.name 'EfficientNet' --model.size 'b0' --model.weights 'ResNet50_Weights.IMAGENET1K_V2'
--optimizer.name 'SGD' --optimizer.lr 0.01 --optimizer.momentum 0.9
...

我们还可以在 实验队列 中加载多个 配置组,例如运行机器学习超参数的 网格搜索

$ dvc exp run --queue \
              -S 'train/optimizer=adam,sgd' \
              -S 'train/model=resnet,efficientnet'

Queueing with overrides '{'params.yaml': ['optimizer=adam', 'model=resnet']}'.
Queued experiment 'ed3b4ef' for future execution.
Queueing with overrides '{'params.yaml': ['optimizer=adam', 'model=efficientnet']}'.
Queued experiment '7a10d54' for future execution.
Queueing with overrides '{'params.yaml': ['optimizer=sgd', 'model=resnet']}'.
Queued experiment '0b443d8' for future execution.
Queueing with overrides '{'params.yaml': ['optimizer=sgd', 'model=efficientnet']}'.
Queued experiment '0a5f20e' for future execution.

$ dvc queue start
...

请注意,DVC 会缓存所有运行记录,因此许多组合将无需实际运行实验即可完成。在上面的例子中,带有 ['optimizer=sgd', 'model=resnet'] 的实验不会浪费计算时间,因为结果已存在于 运行缓存 中。你可以使用 dvc queue logs 来确认这一点:

$ dvc queue logs 0b443d8
Stage 'setup-dataset' didn't change, skipping
Stage 'train' didn't change, skipping

dvc exp run 每次运行时都会生成一个新的 params.yaml,因此它不是可靠地复现已往实验的方法。相反,在需要复现先前运行的实验时,请使用 dvc repro

迁移 Hydra 项目

如果你已经配置了 Hydra 并希望开始同时使用 DVC,可能需要对代码进行轻微重构。DVC 不会将 Hydra 配置传递给 @hydra.main(),因此应从代码中移除该装饰器。取而代之的是,DVC 会在你的代码运行前组合 Hydra 配置,并将结果写入 params.yaml

以上述示例为例,以下是使用 Hydra 但不使用 DVC 时 train.py 中 Python 代码的可能写法:

import hydra
from omegaconf import DictConfig

@hydra.main(version_base=None, config_path="conf", config_name="config")
def main(cfg: DictConfig) -> None:
    # train model using cfg parameters

if __name__ == "__main__":
    main()

将相同代码转换为使用 DVC 和启用 Hydra 配置组合的方式如下:

from omegaconf import OmegaConf

def main() -> None:
    cfg = OmegaConf.load("params.yaml")
    # train model using cfg parameters

if __name__ == "__main__":
    main()

你现在不再需要在代码中导入 Hydra。本示例中包含了一个 main() 函数,因为这是一种良好的编程实践,但它并不是必需的。这种配置与代码的分离有助于调试,因为在实验开始之前,Hydra 生成的完整配置会被写入 params.yaml。你可以选择是否使用 Hydra(或 DVC)来运行相同的代码。你还可以在 DVC 管道不同阶段的多个脚本之间复用 params.yaml

高级 Hydra 配置

你可以配置 DVC 与 Hydra 的协作方式。

默认情况下,DVC 会在 conf 目录中查找 Hydra 配置组,但你可以通过使用 dvc config hydra.config_dir other_dir 设置其他目录。这相当于 @hydra.main() 中的 config_path 参数。

在该目录内,DVC 会在 config.yaml 中查找 默认列表,但你可以通过使用 dvc config hydra.config_name other.yaml 设置不同的路径。这相当于 @hydra.main() 中的 config_name 参数。

Hydra 会自动发现 hydra_plugins 目录中的 插件。默认情况下,DVC 会在 DVC 仓库的根目录中查找 hydra_plugins,但你可以通过 dvc config hydra.plugins_path other_path 设置其他路径。

自定义解析器

你可以通过将 OmegaConf 自定义解析器 编写为 hydra_plugins 目录内的文件,将其注册为插件。DVC 在组合 Hydra 配置时会使用这些自定义解析器。例如,在 hydra_plugins/my_resolver.py 中添加一个自定义解析器:

import os
from omegaconf import OmegaConf

OmegaConf.register_new_resolver('join', lambda x, y : os.path.join(x, y))

你可以在 Hydra 配置内部使用该自定义解析器:

dir: raw/data
relpath: dataset.csv
fullpath: ${join:${dir},${relpath}}

最终生成的 params.yaml 将如下所示:

dir: raw/data
relpath: dataset.csv
fullpath: raw/data/dataset.csv
内容

🐛 发现问题?告诉我们!或者修复它:

在 GitHub 上编辑

有疑问?加入我们的聊天,我们会为您提供帮助:

Discord 聊天