Структурированные файлы конфигурации в Hydra

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

Структурированные конфигурации

Позволяют использовать:

  • Инструменты для статической проверки типов
  • Run-time проверку типов во время компоновки итоговой конфигурации

Поддерживаемые типы:

  • Базовые: int, bool, float, str, Enums, bytes, pathlib.Path
  • Контейнеры (списки и словари примитивных типов и структурированных конфигов)

Существует возможность композиции (вложения) конфигов и использования опциональных полей.

ConfigStore

conf/preprocessing/roberta.yaml
padding: max_length
tokenizer_name: siebert/sentiment-roberta-large-english
test_size: 5000
train_size: 20000
max_length: 256

@dataclass
class PreprocessingConfig:
    padding: str = "max_length"
    max_length: int = 512
    tokenizer_name: str = MISSING
    test_size: int = 10000
    train_size: int = 50000


@dataclass
class RobertaPreprocessing(PreprocessingConfig):
    max_length: int = 256
    tokenizer_name: str = "siebert/sentiment-roberta-large-english"
    test_size: int = 5000
    train_size: int = 20000
from hydra.core.config_store import ConfigStore
from src import (
    RobertaPreprocessing,
    DistillBertPreprocessing,
    MainConfig,
)

#...

cs = ConfigStore.instance()
cs.store(group="preprocessing", name="distillbert", node=DistillBertPreprocessing)
cs.store(group="preprocessing", name="roberta", node=RobertaPreprocessing)

@hydra.main(version_base=None, config_name="config")
def main(cfg: MainConfig):
    ...

$ python SA_example.py preprocessing=roberta
[2024-03-15 20:26:09,268][__main__][INFO] - preprocessing:
  padding: max_length
  max_length: 256
  tokenizer_name: siebert/sentiment-roberta-large-english
  test_size: 5000
  train_size: 20000

Пример реализации параметров для передачи в instantiate

@dataclass
class ModelConfig:
    _target_: str = "transformers.AutoModelForSequenceClassification.from_pretrained"
    _convert_: str = "object"
    pretrained_model_name_or_path: str = MISSING
    problem_type: str = "single_label_classification"

Пример работы статической типизации

@hydra.main(version_base=None, config_name="config")
def main(cfg: MainConfig):
    log.info(cfg.preprocessing.pading)

$ mypy SA_example.py
SA_example.py:203: error: "PreprocessingConfig" has no attribute "pading"; maybe "padding"?  [attr-defined]

Пример детектирования ошибок в runtime

$ python SA_example.py preprocessing.test_size=0.2
Error merging override preprocessing.test_size=0.2
Value '0.2' of type 'float' could not be converted to Integer
    full_key: preprocessing.test_size
    reference_type=PreprocessingConfig
    object_type=PreprocessingConfig

$ python SA_example.py preprocessing.max_len=512
Could not override 'preprocessing.max_len'.
To append to your config use +preprocessing.max_len=512
Key 'max_len' not in 'PreprocessingConfig'
    full_key: preprocessing.max_len
    reference_type=PreprocessingConfig
    object_type=PreprocessingConfig

Использование механизмов наследования и композиции

@dataclass
class PreprocessingConfig:
    padding: str = "max_length"
    max_length: int = 512
    tokenizer_name: str = MISSING
    test_size: int = 10000
    train_size: int = 50000


@dataclass
class RobertaPreprocessing(PreprocessingConfig):
    max_length: int = 256
    tokenizer_name: str = "siebert/sentiment-roberta-large-english"
    test_size: int = 5000
    train_size: int = 20000


@dataclass
class DistillBertPreprocessing(PreprocessingConfig):
    tokenizer_name: str = "lxyuan/distilbert-base-multilingual-cased-sentiments-student"


# аналогично для ModelConfig и #TrainingConfig

@dataclass
class MainConfig:
    defaults: list[Any] = field(default_factory=lambda: defaults)
    preprocessing: PreprocessingConfig = MISSING
    model: ModelConfig = MISSING
    training: TrainingConfig = MISSING

Указание значений по умолчанию

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

defaults = [
    {"preprocessing": "distillbert"},
    {"model": "distillbert"},
    {"training": "distillbert"},
]

@dataclass
class MainConfig:
    defaults: list[Any] = field(default_factory=lambda: defaults)
    preprocessing: PreprocessingConfig = MISSING
    model: ModelConfig = MISSING
    training: TrainingConfig = MISSING
defaults = [
    {"preprocessing": MISSING},
    {"model": "distillbert"},
    {"training": "distillbert"},
]

$ python SA_example.py
You must specify 'preprocessing', e.g, preprocessing=<OPTION>
Available options:
        distillbert
        roberta

Read-only конфиги

@dataclass(frozen=True)
class ModelConfig:
    _target_: str = "transformers.AutoModelForSequenceClassification.from_pretrained"
    _convert_: str = "object"
    pretrained_model_name_or_path: str = MISSING
    problem_type: str = "single_label_classification"

@dataclass(frozen=True)
class DistillBertModel(ModelConfig):
    pretrained_model_name_or_path: str = (
        "lxyuan/distilbert-base-multilingual-cased-sentiments-student"
    )
    num_labels: int = 2
    ignore_mismatched_sizes: bool = True

$ python SA_example.py model.num_labels=10
Error merging override model.num_labels=10
Cannot change read-only config container
    full_key: model.num_labels
    reference_type=ModelConfig
    object_type=ModelConfig

Итог

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