Основы фреймворка Hydra

Владислав Ефимов — ML Engineer

Начальные сведения

Hydra – инструмент для создания и компоновки файлов конфигурации различных Python-приложений с акцентом на использование в ML-проектах.

  • Выделение параметров в группы, компоновка групп в общую иерархическую структуру
  • Переопределение групп конфигураций и отдельных параметров
  • Автоматическое логирование
  • Запуски с перебором различных комбинаций параметров

Практическое применение

  • Задача: Sentiment Analysis (анализ тональности текста)
  • Датасет: Amazon reviews (отзывы клиентов Amazon на различные товары)
  • Модели: DistillBERT, RoBERTa
  • Hugging Face transformers API: Trainer, TrainingArguments, Auto Model

Базовая структура

├── src
    ├── conf
      ├── config.yaml
      └── data
      └── model
          ├── distillbert.yaml
          ├── roberta.yaml
      └── preprocessing
          ├── distillbert.yaml
          ├── roberta.yaml
      └── secrets
          ├── example.yaml
          ├── .gitignore
      └── training
          ├── distillbert.yaml
          ├── roberta.yaml
├── SA_example.py

Главный файл конфигурации

conf/config.yaml
defaults:
  - preprocessing: distillbert
  - training: distillbert
  - model: distillbert
  - secrets: example

Приоритет параметров

conf/preprocessing/roberta.yaml
padding: max_length
tokenizer_name: siebert/sentiment-roberta-large-english
test_size: 5000
train_size: 20000
max_length: 256
conf/config.yaml
defaults:
  - preprocessing: distillbert
  - training: distillbert
  - model: distillbert
  - secrets: example
  - _self_

preprocessing:
  test_size: 10000
  train_size: 50000
  max_length: 512
$ python SA_example.py
[2024-03-21 23:52:30,126][__main__][INFO] - preprocessing:
  padding: max_length
  max_length: 512
  tokenizer_name: siebert/sentiment-roberta-large-english
  test_size: 10000
  train_size: 50000
conf/preprocessing/roberta.yaml
padding: max_length
tokenizer_name: siebert/sentiment-roberta-large-english
test_size: 5000
train_size: 20000
max_length: 256
conf/config.yaml
defaults:
  - _self_
  - preprocessing: distillbert
  - training: distillbert
  - model: distillbert
  - secrets: example

preprocessing:
  test_size: 10000
  train_size: 50000
  max_length: 512
$ python SA_example.py
[2024-03-21 23:52:30,126][__main__][INFO] - preprocessing:
  test_size: 5000
  train_size: 20000
  max_length: 256
  padding: max_length
  tokenizer_name: siebert/sentiment-roberta-large-english

Интеграция Hydra в Python-код, способы запуска

hydra_main(), запуск .py-модулей из терминала

@hydra.main(version_base=None, config_path="src/conf", config_name="config")
def main(cfg: DictConfig):
    param = cfg["group_name"]["param"]
    # some logic with param...
  • Увеличение числа эпох обучения модели DistillBERT (по умолчанию):
$ python SA_example.py training.num_train_epochs=10
  • Пример замены группы параметров (переход к модели RoBERTa):
$ python SA_example.py model=roberta preprocessing=roberta training=roberta
  • Существующие в конфиге параметры можно удалять (learning_rate будет сброшен на стандартное значение):
$ python SA_example.py ~training.learning_rate
  • Добавление нового параметра, не присутствующего ранее:
$ python SA_example.py +training.use_cpu=True
  • Также в таком варианте использования Hydra доступна возможность multirun запусков - выполнения кода со всеми перечисленными значениями параметров:
$ python SA_example.py -m preprocessing.max_length=128,512

Расширения для multirun-режима

  • Joblib (распараллеливание)
  • Ray (запуск на кластере)
  • Optuna (оптимизация гиперпараметров)

Compose-API, использование в jupyter-notebooks

В случае:

  • Jupyter-like форматов
  • Юнит-тестов
  • Когда нет доступа к терминалу

можно воспользоваться комбинацией методов compose() и initialize()

src/config.py
def compose_config(
    overrides: list[str] | None = None,
    config_path: Path = Path("conf"),
    config_name: str = "config",
) -> dict:
    with initialize(version_base=None, config_path=str(config_path)):
        hydra_config = compose(config_name=config_name, overrides=overrides)
        return hydra_config

example.ipynb
from src.config import compose_config

main_conf = compose_config()

prepr_conf = main_conf["preprocessing"]
train_conf = main_conf["training"]
model_conf = main_conf["model"]

Пример изменения группы параметров:

from src.config import compose_config
main_conf = compose_config(overrides=["model='some_other_model'"])
model_conf = main_conf["model"]

Данный способ не позволяет использовать multirun-запуски, настраивать логирование, менять стандартные пути для побочных артефактов работы Hydra и некоторые другие возможности.

Некоторые полезные расширения синтаксиса для yaml-файлов

Variable interpolation

OmegaConf docs

  • Абсолютный путь: ${config_name.param_name}
  • Относительный путь: ${.param_name}
_target_: transformers.TrainingArguments
output_dir: distillbert_training_outputs
evaluation_strategy: epoch
save_strategy: epoch
learning_rate: 2e-5
per_device_train_batch_size: 64
per_device_eval_batch_size: ${training.per_device_train_batch_size}
num_train_epochs: 3
weight_decay: 0.01
load_best_model_at_end: True
metric_for_best_model: f1
fp16: True

Resolvers

secrets/example.yaml
name: ${oc.env:USER}
$ python SA_example.py
[2024-03-22 00:12:14,867][__main__][INFO] - secrets:
  user: iref

Инициализация объектов

from hydra.utils import instantiate
model = instantiate(confifg['model'])

_target_: transformers.AutoModelForSequenceClassification.from_pretrained
_convert_: object
pretrained_model_name_or_path: lxyuan/distilbert-base-multilingual-cased-sentiments-student
problem_type: single_label_classification
num_labels: 2
ignore_mismatched_sizes: True

Логирование

Каждый запуск сопровождается сохранением некоторого набора логов. В них содержатся:

  • config.yaml - скомпонованный конфиг, с которым прошел запуск
  • hydra.yaml - некоторые внутренние настройки Hydra, с которыми прошел запуск
  • overrides.yaml - список параметров и их значений, измененных при помощи командной строки или функции compose
  • <filename.log> - данные, переданные через модуль logging.
$ python rubert_tiny_hydra_example.py hydra.run.dir=some_other_out_dir

config.yaml
hydra:
  run:
    dir: ./some_other_out_dir/${now:%Y-%m-%d}/${now:%H-%M-%S}

$ python rubert_tiny_hydra_example.py hydra.verbose=True

Итог

Прежде всего, Hydra — удобный инструмент для организации множества файлов с параметрами в единую конфигурацию, который упрощает процесс разработки и исследований.