
Александр Рыжков
Ментор 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 и МИФИ «Прикладной анализ данных и машинное обучение».