Исходники: https://github.com/facebookresearch/hydra
Документация: https://hydra.cc/docs/intro/
Hydra это фреймворк для управления файлами конфигурации, заточенный под ML-проекты.
Преимущества конфигурационных файлов очевидны: вы можете изменять параметры вашего проекта не затрагивая основную бизнес-логику. Но не все так гладко. Работа с моделями машинного обучения несколько отличается от классической разработки:
Вам потребуется проводить множество экспериментов с гиперпараметрами и их нужно как-то отслеживать. Вы можете просто каждый раз делать копию файла с новым именем. Но это не удобно и очень скоро ваша папка с проектом превратится в мусорку.
Вы можете захотеть попробовать различные библиотеки, для которых потребуются разные настройки. Как вариант, их можно поместить в один конфиг-файл, но это сделает его трудно читаемым. Или вы можете разнести их на отдельные конфиги, с дублированием общей части, что вскоре станет не удобно поддерживать. Помимо этого вам потребуется как-то разделить их вызов в коде.
Все эти проблемы (и даже больше) позволяет решить Hydra. Ее ключевые особенности:
Иерархическая конфигурация
Автоматическое логирование
Переопределение параметров из командной строки
Запуск множества заданий с помощью одной команды
И пр.
А теперь посмотрим как все это работает. Но начнем как обычно с установки…
Установка
Тут все просто. Выполняем в командной строке:
pip install hydra-core --upgrade
Вместе с гидрой будет установлена библиотека OmegaConf, которая в фоне используется гидрой. OmegaConf предназначена для доступа и управления файлам конфигурации на основе YAML. Именно в YAML задаются все конфиги для гидры.
Начало
Сначала самым простым образом задействуем гидру.
Создадим ML-проект с такой структурой:
config
- config.yaml
app.py
df.csv
Содержимое файлов:
#config.yaml
model: lgbm
params:
learning_rate: 0.05
n_estimators: 100
max_depth: 9
Здесь мы указываем модель, для которой этот конфиг, и задаем параметры ее обучения.
#app.py
import hydra
import pandas as pd
from omegaconf import OmegaConf, DictConfig
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error
from lightgbm import LGBMRegressor
import logging
log = logging.getLogger(__name__)
@hydra.main(version_base=None, config_path='config', config_name='config')
def my_app(cfg: DictConfig):
print(OmegaConf.to_yaml(cfg))
# Загружаем данные
df = pd.read_csv('df.csv')
X = df.drop(columns='target')
y = df['target']
# Формируем трейн/тест
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.30, random_state=42)
# Обучение модели
model = LGBMRegressor()
lgbm_params = OmegaConf.to_container(cfg['params'])
model.set_params(**lgbm_params)
model.fit(X_train, y_train)
# Оценка
y_pred = model.predict(X_test)
mse = round(mean_squared_error(y_test, y_pred), 2)
log.info(f'MSE: {mse}')
if __name__ == "__main__":
my_app()
Что мы тут делаем:
Посредством декоратора @hydra.main подгружаем конфиги из указанной папки.
Загружаем датасет и делим его на трейн и тест.
Обучаем модель на параметрах, указанных в конфиге. При этом мы конвертируем исходный конфиг DictConfig в стандартный Dict, поскольку именно его принимает метод set_params.
Делаем предсказание и подсчитываем скор. Обратите внимание, что вывод информации осуществляется через модуль logger − это важно − только данные выведенные через logger будут логироваться гидрой (увидим дальше). Обычный print останется только в консоли.
З.Ы. Вы можете использовать одни параметры конфига как значения для других:
server:
ip: "127.0.0.1"
port: "8080"
address: "${server.ip}:${server.port}"
Дальше мы будем модифицировать части этого кода, исследуя разные возможности гидры...
Конфигурация загружается как объект DictConfig, который похож на стандартный словарь питона, но предоставляет несколько дополнительных функций (например, вложенность). Подробнее о возможносях DictConfig можно прочитать в официальной документации: https://omegaconf.readthedocs.io/en/latest/usage.html#access-and-manipulation
Логирование
Запустим приложение:
> python3 app.py
model: lgbm
params:
learning_rate: 0.05
n_estimators: 100
max_depth: 9
[2022-10-31 19:53:29,960][main][INFO] - MSE: 2948.27
Сразу после запуска появится папка outputs, в которой hydra будет логировать все запуски. Каждый запуск сохраняется в отдельной папке: outputs/YYYY-mm-dd/HH-MM-SS
Теперь пойдем поменяем в конфиге какой-нибудь гиперпараметр и заново запустим программу. Вуаля − у нас новая папка со всеми логами.
Таким образом вам больше не нужно помнить, какую скорость обучения вы использовали для запуска этой модели и прочие подробности экспериментов, так как у вас всегда будет храниться резервная копия файла конфигурации и результаты выполнения.
Вы можете при запуске изменить папку выгрузки логов:
> python3 app.py hydra.run.dir=my_folder
Что содержат эти логи:
config.yaml — копия файла конфигурации, переданного через декоратор в функцию.
hydra.yaml — копия служебного конфигурационного файла Hydra.
overrides.yaml — копия параметров, заданных через командную строку и которые изменяют одно из значений из конфиг файла (посмотрим как это делается далее).
<file_name>.log — здесь будут храниться данные переданные через модуль logging.
По умолчанию в лог попадают категории info и warning, но посредством параметров можно логировать и debug:
> python3 app.py hydra.verbose=true
Переопределение
При запуске мы можем переопределить значения конфига в командной строке:
> python3 app.py params.max_depth=11
model: lgbm
params:
learning_rate: 0.05
n_estimators: 100
max_depth: 11
[2022-10-31 21:40:13,889][main][INFO] - MSE: 2943.45
Переопределенные таким образом параметры будут сохранены в отдельном логе - overrides.yaml.
Помимо этого вы можете добавить новые параметры, которых нет в конфиге, или удалить существующие:
> python3 app.py +params.reg_alpha=0.5
> python3 app.py ~params.n_estimators
Группы конфигураций
Предположим, что мы изобрели ряд фичей для нашей задачи и хотим протестировать разные их комбинации. И тут нам помогут Группы конфигурации. По сути это отдельные конфиги для каждой “подсистемы” вашего приложения.
Изменим структуру проекта, добавим в папку config подпапку для управления параметрами датасета:
config
- dataset
- ds1.yaml
- ds2.yaml
- config.yaml
app.py
df.csv
config.yaml останется прежнем, а вот два новых YAML файла содержит следующую информацию:
#ds1.yaml
name: ds1
columns: [age,bp,s1,s2,s5,s6]
target: target
#ds2.yaml
name: ds2
columns: [sex,bmi,s3,s4]
target: target
Также изменим способ загрузки датасета, чтобы брал информацию о колонках из конфига:
# Загружаем данные
df = pd.read_csv('df.csv')
X = df[cfg['dataset']['columns']]
y = df[cfg['dataset']['target']]
К значениям конфига можно обращаться двумя способами:
cfg.dataset.image.channels
cfg['dataset']['classes']
Далее при запуске нам нужно указать какой конфиг использовать:
> python3 app.py dataset=ds1
dataset:
name: ds1
columns:
- age
- bp
- s1
- s2
- s5
- s6
target: target
model: lgbm
params:
learning_rate: 0.05
n_estimators: 100
max_depth: 9
[2022-10-31 22:15:28,575][main][INFO] - MSE: 3580.19
Как вы можете видеть, хотя наши конфиги разделены, в приложение приходит один целостный конфиг.
Но каждый раз запускать может быть неудобно, поэтому вы можете в основном конфиге определить дефолтные модули:
#config.yaml
defaults:
- dataset: ds1
model: lgbm
params:
learning_rate: 0.05
n_estimators: 100
max_depth: 9
Аналогичным образом мы можем определить настройки для других типовых “модулей”, для машинного обучения:
Разные архитектуры моделей (AlexNet, ResNet50).
Разные наборы данных.
Разные оптимизаторы.
Разные планировщики.
Разные способы заполнения пропусков.
Разные функции потерь.
И куча всего другого…
Multirun
Допустим мы таким образом наизобретали кучу “модулей”. Запускать каждый из них по отдельности, перебирая всевозможные варианты − утомительно. Поэтому гидра может это делать в автоматическом режиме с помощью функции multirun:
> python3 app.py -m dataset=ds1,ds2
[2022-10-31 22:48:22,886][HYDRA] Launching 2 jobs locally
[2022-10-31 22:48:22,886][HYDRA] #0 : dataset=ds1
...
[2022-10-31 22:48:25,233][main][INFO] - MSE: 3580.19
[2022-10-31 22:48:25,234][HYDRA] #1 : dataset=ds2
...
[2022-10-31 22:48:25,656][main][INFO] - MSE: 3414.61
Здесь мы попросили перебрать два конфига для датасета. Каждый из них будет объединен с базовым конфигом и передан на вход приложению. Если бы у нас были еще конфиги, например для моделей, то гидра бы сформировала и выполнила все их возможные комбинации.
В отличии от одиночного запуска при мульти-запуске логи сохраняются в папке multirun, в которой помимо даты и времени также создается отдельная подпапка (с порядковым номером) для каждого отдельного задания.
Базовая функция multirun запускает задания локально и последовательно, но используя специальные плагины вы можете запускать код параллельно или даже удаленно на нескольких узлах:
Инициализация объектов
Усложним задачу. Допустим мы хотим попробовать различные модели. Но мы не хотим это как-то обыгрывать в коде − мы просто хотим задавать необходимый класс в конфиге. Добавим в нашу структуру новую папку model и два конфига под две модели:
config
- dataset
- ds1.yaml
- ds2.yaml
- model
- catboost.yaml
- lgbm.yaml
- config.yaml
app.py
df.csv
Содержимое новых YAML-файлов:
#catboost.yaml
_target_: catboost.CatBoostRegressor
learning_rate: 0.05
n_estimators: 100
max_depth: 9
silent: True
#lgbm.yaml
_target_: lightgbm.LGBMRegressor
learning_rate: 0.05
n_estimators: 100
max_depth: 9
verbose: -1
Обратите внимание на специальный ключ _target_ в котором указываем класс, который хотим задействовать для создания модели (при этом отдельно импортировать этот класс в приложении не нужно). Все остальные значения, указанные на том же уровне, будут использованы как параметры при инициализации класса.
Поменяем код обучения модели:
...
from hydra.utils import instantiate
...
# Обучение модели
model = instantiate(cfg['model'])
model.fit(X_train, y_train)
...
Здесь мы использовали специальную функцию hydra.utils.instantiate, через которую создали экземпляр модели, заданный в конфиге. В остальном код остался прежнем.
Укажем дефолтную модель в базовом конфиге и уберем оттуда все предыдущие настройки модели:
defaults:
- dataset: ds1
- model: lgbm
Запускаем:
> python3 app.py
dataset:
name: ds1
columns:
- age
- bp
- s1
- s2
- s5
- s6
target: target
model:
target: lightgbm.LGBMRegressor
learning_rate: 0.05
n_estimators: 100
max_depth: 9
[2022-11-01 11:30:49,648][main][INFO] - MSE: 3580.19
Тут гидра взяла дефолтные конфиги. И попробуем запустить мультиран на всем, что у нас есть:
> python3 app.py -m dataset=ds1,ds2 model=catboost,lgbm
[2022-11-01 12:33:16,334][HYDRA] Launching 4 jobs locally
[2022-11-01 12:33:16,334][HYDRA] #0 : dataset=ds1 model=catboost
...
[2022-11-01 12:33:17,089][main][INFO] - MSE: 3295.95
[2022-11-01 12:33:17,090][HYDRA] #1 : dataset=ds1 model=lgbm
...
[2022-11-01 12:33:17,652][main][INFO] - MSE: 3580.19
[2022-11-01 12:33:17,653][HYDRA] #2 : dataset=ds2 model=catboost
...
[2022-11-01 12:33:17,863][main][INFO] - MSE: 3330.02
[2022-11-01 12:33:17,864][HYDRA] #3 : dataset=ds2 model=lgbm
...
[2022-11-01 12:33:18,124][main][INFO] - MSE: 3414.61
Как видите, гидра перебрала все возможные комбинации.
Jupyter Notebook
Иногда нам может понадобиться загрузить наш конф-файл отдельно, а не передавать в основную функцию программы. Например, в ячейке Jupyter Notebook. Делается это посредством Compose API − альтернативного способа загрузки файлов конфигурации. Делается это примерно так:
from hydra import initialize, compose
from omegaconf import OmegaConf
with initialize(version_base=None, config_path="cloud_app/conf"):
cfg = compose(overrides=["+db=mysql"])
print(cfg)
Более подробно читайте в документации: https://hydra.cc/docs/advanced/compose_api/
Прочее
В этой статье содержится лишь часть возможностей гидры. С остальными вы сможете ознакомится в документации. Например:
Автоматическое закрытие вкладок: https://hydra.cc/docs/0.11/tutorial/tab_completion/
Тонкая настройка логирования: https://hydra.cc/docs/0.11/configure_hydra/logging/
Добавить конфиги гидры в ваш питон пакет: https://hydra.cc/docs/advanced/app_packaging/
Настройка рабочего каталога: https://hydra.cc/docs/configure_hydra/intro/
И много чего еще…
---------------
Код из статьи: https://github.com/slivka83/article/tree/main/hydra