ML | Hydra

от автора

Исходники: 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://github.com/slivka83/article/tree/main/hydra

Мой телеграм-канал


ссылка на оригинал статьи https://habr.com/ru/post/696820/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *