Александр Рыжков
Ментор Skillfactory, руководитель команды LightAutoML и 4х Kaggle Grandmaster
Если вы уже пробовали обучать модели, то знаете: выбрал не тот гиперпараметр — получил плохой результат. А перебирать их вручную или даже с помощью GridSearchCV из scikit-learn — долго, муторно и не всегда эффективно. Поэтому сегодня поговорим о том, как заставить компьютер делать эту скучную работу за нас.
В этом поможет Optuna — библиотека для автоматической оптимизации гиперпараметров. Она умнее простого перебора и часто находит отличные комбинации параметров гораздо быстрее.
Что такое Optuna?
Optuna — это фреймворк для автоматизации процесса поиска оптимальных гиперпараметров.
Что такое гиперпараметры? Это настройки модели, которые мы задаем до начала обучения. Например, в модели случайного леса (Random Forest) это может быть количество деревьев (n_estimators) или максимальная глубина каждого дерева (max_depth). Они не выучиваются из данных, как веса модели, а определяют сам процесс обучения.
Почему Optuna — это круто?
-
Умные алгоритмы. Optuna использует продвинутые алгоритмы (например, TPE, Tree-structured Parzen Estimator, по умолчанию), которые учатся на предыдущих попытках. Если какая-то область значений параметра дает плохие результаты, Optuna будет реже ее исследовать и сосредоточится на более перспективных зонах. Это гораздо эффективнее слепого перебора (Grid Search) или случайного поиска (Random Search).
-
Гибкость. Легко задавать разные типы параметров (числовые, категориальные) и их диапазоны.
-
Простота использования. Базовый сценарий использования интуитивно понятен.
-
Визуализация. Встроенные инструменты для анализа процесса оптимизации.
-
Параллелизация. Можно легко распараллелить поиск на несколько ядер или машин.
-
Поддержка Pruning (Отсечения). Optuna может досрочно останавливать бесперспективные попытки обучения модели, экономя время и ресурсы.
Установка Optuna
Установить Optuna проще простого. Откройте ваш терминал или командную строку и введите:
pip install optuna
И все! Библиотека готова к работе. Для визуализации может понадобиться plotly, установим и его на всякий случай:
pip install plotly
Определение функции цели и создание первого исследования с Optuna
Сердце работы с Optuna — это два компонента: исследование (Study) и функция цели (Objective Function).
Объект Study. Это менеджер всего процесса оптимизации. Он следит за всеми попытками (trials), хранит их результаты и решает, какие параметры попробовать следующими, используя выбранный алгоритм оптимизации. Создается он очень просто: optuna.create_study(). Важный параметр при создании — direction. Он указывает, что мы хотим сделать с результатом функции цели: минимизировать (‘minimize’, например, ошибку) или максимизировать (‘maximize’, например, точность).
Функция цели (Objective Function). Это функция, которую Optuna будет пытаться оптимизировать (минимизировать или максимизировать ее результат). Именно здесь происходит самое интересное:
-
Она принимает один аргумент — trial (попытка). Этот объект trial используется, чтобы «предложить» значения гиперпараметров для текущей попытки.
-
Внутри функции вы получаете предложенные гиперпараметры от trial с помощью методов trial.suggest_float() (для вещественных чисел), trial.suggest_int() (для целых), trial.suggest_categorical() (для выбора из списка) и т. д.
-
Используя эти параметры, вы обучаете и оцениваете вашу модель машинного обучения (например, с помощью кросс-валидации).
-
Функция должна вернуть значение метрики, которую вы хотите оптимизировать (например, среднюю точность на кросс-валидации).
Алгоритмы оптимизации. По умолчанию Optuna использует алгоритм TPE, который хорошо работает для большинства задач. Но можно выбрать и другие, например CMA-ES (хорош для непрерывных параметров) или простой Random Search. Это делается при создании study через параметр sampler. Но для начала стандартный TPE — отличный выбор. Он как раз реализует ту «умную» логику: анализирует прошлые запуски и решает, какие параметры пробовать дальше, чтобы быстрее прийти к хорошему результату.
Пример использования библиотеки Optuna
Давайте попробуем подобрать гиперпараметры для простой модели логистической регрессии из scikit-learn на классическом датасете Iris. Мы будем оптимизировать параметр регуляризации C.
import optuna import sklearn.datasets import sklearn.linear_model import sklearn.model_selection # 1. Загружаем данные X, y = sklearn.datasets.load_iris(return_X_y=True) # 2. Определяем функцию цели (Objective Function) def objective(trial): # Предлагаем значение для гиперпараметра C # Ищем C в логарифмическом масштабе от 1e-5 до 1e2 (обычная практика для C) logreg_c = trial.suggest_float("logreg_c", 1e-5, 1e2, log=True) # Создаем модель с предложенным параметром model = sklearn.linear_model.LogisticRegression(C=logreg_c, max_iter=1000) # Увеличим max_iter для сходимости # Оцениваем модель с помощью кросс-валидации (3 фолда) # Используем accuracy как метрику score = sklearn.model_selection.cross_val_score(model, X, y, n_jobs=-1, cv=3) accuracy = score.mean() # Возвращаем метрику (accuracy), которую хотим максимизировать return accuracy # 3. Создаем исследование (Study) # Мы хотим максимизировать accuracy, поэтому direction='maximize' study = optuna.create_study(direction="maximize") # 4. Запускаем оптимизацию # n_trials - количество попыток подбора параметров study.optimize(objective, n_trials=50) # 5. Выводим лучшие результаты print("Количество завершенных испытаний: ", len(study.trials)) print("Лучшее испытание:") trial = study.best_trial print(" Значение метрики (accuracy): ", trial.value) print(" Лучшие гиперпараметры: ") for key, value in trial.params.items(): print(f" {key}: {value}") # Можно получить лучшие параметры и напрямую: best_params = study.best_params print("Лучшие параметры в виде словаря:", best_params)
После запуска этого кода Optuna 50 раз вызовет функцию objective, каждый раз предлагая новое значение для logreg_c. Она будет «запоминать», какие значения C давали лучшую точность, и использовать эту информацию для следующих попыток. В конце мы получим оптимальное (или близкое к нему) значение C и соответствующую ему максимальную точность, найденную в ходе оптимизации. Просто, не правда ли?
Многокритериальная оптимизация с использованием Optuna
Иногда нам важно оптимизировать не один, а сразу несколько показателей. Классический пример: мы хотим модель, которая будет не только точной, но и быстрой (например, чтобы предсказания делались мгновенно) или компактной (чтобы занимала меньше памяти). Часто эти цели противоречат друг другу: более сложная модель может быть точнее, но медленнее и больше.
Выбирая машину, вы можете хотеть, чтобы она была одновременно и очень быстрой (максимальная скорость), и очень экономичной (минимальный расход топлива). Найти машину, лучшую по обоим параметрам сразу, сложно. Обычно приходится искать компромисс — парето-оптимальное решение. Это решение, которое нельзя улучшить по одному критерию, не ухудшив при этом другой. Например, нельзя сделать машину экономичнее, не пожертвовав скоростью, и наоборот.
Optuna поддерживает такую многокритериальную оптимизацию (Multi-objective Optimization). Вместо того чтобы искать одно «лучшее» решение, она ищет набор таких вот компромиссных, парето-оптимальных решений.
Как это работает в Optuna? Очень похоже на обычную оптимизацию:
-
При создании study вы указываете несколько directions (например, directions=[«maximize», «minimize»], если хотите максимизировать точность и минимизировать время работы).
-
Ваша objective функция должна возвращать не одно число, а кортеж (tuple) из нескольких значений, по одному для каждой цели.
Optuna постарается найти набор таких гиперпараметров, которые дают хорошие компромиссные результаты по всем заданным критериям.
Практический пример многокритериальной оптимизации с Optuna
Давайте модифицируем наш предыдущий пример. Будем искать параметры для LogisticRegression, которые дают высокую accuracy (максимизируем) и при этом используют не слишком большое значение C (будем минимизировать сам параметр C как прокси для простоты модели, хотя на практике лучше минимизировать, например, время предсказания или размер модели).
import optuna import sklearn.datasets import sklearn.linear_model import sklearn.model_selection import numpy as np # Понадобится для логарифма # 1. Загружаем данные (те же) X, y = sklearn.datasets.load_iris(return_X_y=True) # 2. Определяем многокритериальную функцию цели def multi_objective(trial): # Предлагаем значение для C (как и раньше) logreg_c = trial.suggest_float("logreg_c", 1e-5, 1e2, log=True) # Создаем модель model = sklearn.linear_model.LogisticRegression(C=logreg_c, max_iter=1000, solver='liblinear') # Выберем solver явно # Оцениваем точность (accuracy) score = sklearn.model_selection.cross_val_score(model, X, y, n_jobs=-1, cv=3) accuracy = score.mean() # Наша вторая цель - минимизировать C (или его логарифм для стабильности) # Чем меньше C, тем проще модель (сильнее регуляризация) complexity_proxy = np.log(logreg_c) # Минимизируем логарифм C # Возвращаем КОРТЕЖ из двух значений: (accuracy, complexity_proxy) return accuracy, complexity_proxy # 3. Создаем исследование с ДВУМЯ целями # Первая цель - accuracy (maximize), вторая - complexity_proxy (minimize) study_multi = optuna.create_study(directions=["maximize", "minimize"]) # 4. Запускаем оптимизацию study_multi.optimize(multi_objective, n_trials=100) # Увеличим число попыток для многокритериальной # 5. Выводим результаты print("Количество завершенных испытаний: ", len(study_multi.trials)) print("Парето-оптимальные испытания:") # Optuna хранит лучшие компромиссные решения в best_trials for trial in study_multi.best_trials: print(" Испытание номер:", trial.number) print(" Значения целей (accuracy, log(C)): ", trial.values) print(" Гиперпараметры: ", trial.params) print("-" * 20)
После выполнения этого кода Optuna покажет не одно «лучшее» решение, а несколько — те самые парето-оптимальные варианты (на графике ниже они отображены красным, тогда как все попробованные Optuna варианты показаны синим). Вы сможете выбрать тот компромисс между точностью и значением C, который вас больше устраивает.
Интеграция запусков Optuna с MLflow
Когда вы проводите много экспериментов по подбору гиперпараметров, становится важно их как-то отслеживать: какие параметры пробовали? Какие результаты получили? Какая модель была лучшей?
Здесь на помощь приходит MLflow — это платформа для управления жизненным циклом машинного обучения. Одна из ее ключевых функций — MLflow Tracking: она позволяет логировать (записывать) параметры, метрики, код и артефакты (например, обученные модели) для каждого вашего эксперимента.
Представьте, что вы ведете лабораторный журнал. Каждый раз, когда вы проводите эксперимент (обучаете модель), вы записываете в журнал:
-
Какие «реагенты» использовали (гиперпараметры)?
-
Какая была процедура (код)?
-
Какой результат получили (метрики)?
-
Какие образцы получили на выходе (артефакты, модель)?
MLflow — это цифровой, автоматизированный и очень удобный лабораторный журнал для ML-экспериментов.
Optuna прекрасно интегрируется с MLflow. Вы можете настроить Optuna так, чтобы она автоматически записывала информацию о каждом trial (попытке) в MLflow.
Для этого нужно:
-
Установить MLflow: pip install mlflow и дополнительный модуль optuna-integration: pip install optuna-integration
-
Использовать MLflowCallback при запуске оптимизации в Optuna.
Вот как можно модифицировать наш первый пример для логирования в MLflow:
import optuna import sklearn.datasets import sklearn.linear_model import sklearn.model_selection import mlflow # Импортируем mlflow from optuna.integration import MLflowCallback # Импортируем callback # --- Настройка MLflow --- # Указываем имя эксперимента и путь для сохранения логов (можно использовать удаленный сервер) # Здесь для простоты логи будут сохраняться в локальную папку ./mlruns tracking_uri = "file:./mlruns" # Можно заменить на http://your-mlflow-server:5000 experiment_name = "Optuna Iris LogReg Optimization" mlflc = MLflowCallback( tracking_uri=tracking_uri, metric_name="accuracy", # Имя метрики, которую логируем create_experiment=True, # Создать эксперимент, если его нет ) mlflow.set_tracking_uri(tracking_uri) # Устанавливаем URI для mlflow mlflow.set_experiment(experiment_name) # Устанавливаем активный эксперимент # --- Код Optuna (как в первом примере) --- X, y = sklearn.datasets.load_iris(return_X_y=True) # Функция цели не меняется def objective(trial): logreg_c = trial.suggest_float("logreg_c", 1e-5, 1e2, log=True) model = sklearn.linear_model.LogisticRegression(C=logreg_c, max_iter=1000) score = sklearn.model_selection.cross_val_score(model, X, y, n_jobs=-1, cv=3) accuracy = score.mean() return accuracy study = optuna.create_study(direction="maximize", study_name=experiment_name) # Запускаем оптимизацию, ПЕРЕДАВАЯ CALLBACK в optimize # @mlflc.track_in_mlflow() # Можно использовать декоратор для objective, но callback проще study.optimize(objective, n_trials=50, callbacks=[mlflc]) # Передаем callback здесь # --- Вывод результатов (как раньше) --- print("Количество завершенных испытаний: ", len(study.trials)) # Теперь можно запустить MLflow UI для просмотра результатов: # В терминале выполните: mlflow ui --backend-store-uri file:./mlruns # И откройте http://localhost:5000 в браузере
Теперь, после запуска этого скрипта, вся информация о 50 попытках (trials) будет записана в MLflow. Вы сможете запустить веб-интерфейс MLflow (mlflow ui) и удобно (на скриншотах ниже) просмотреть все запуски, отсортировать их по точности, посмотреть, какие параметры C соответствовали лучшим результатам, сравнить разные запуски оптимизации и т.д. Это невероятно удобно для анализа и воспроизводимости экспериментов!
Если вы еще не пробовали Optuna — настоятельно рекомендую! Начните с простых примеров, как в этой статье, а затем применяйте ее к своим реальным задачам. Уверен, вы оцените, насколько проще и эффективнее становится процесс настройки моделей.
Обучиться работе с моделями машинного обучения: от базовой математики до написания собственного алгоритма — можно на совместной магистратуре Skillfactory и МИФИ «Прикладной анализ данных и машинное обучение».
ссылка на оригинал статьи https://habr.com/ru/articles/900658/
Добавить комментарий