Оптимізація гіперпараметрів Optuna та Ray Tune — досвід HPO

Ми часто бачимо: модель навчена, baseline accuracy начебто прийнятний, але гіперпараметри взяті «з прикладів». Після грамотної оптимізації гіперпараметрів (HPO) за допомогою Optuna, Ray Tune або Hyperopt на тих самих даних і тій самій архітектурі отримуємо +4–8% accuracy. Це не магія, а систематични

Напрямки AI-розробки

Часті запитання

Останні роботи

  • image_website-b2b-advance_0.webp
    Розробка сайту компанії B2B ADVANCE
    1440
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1301
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    997
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1264
  • image_logo-advance_0.webp
    Розробка логотипу компанії B2B Advance
    712
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    1002

Ми часто бачимо: модель навчена, baseline accuracy начебто прийнятний, але гіперпараметри взяті «з прикладів». Після грамотної оптимізації гіперпараметрів (HPO) за допомогою Optuna, Ray Tune або Hyperopt на тих самих даних і тій самій архітектурі отримуємо +4–8% accuracy. Це не магія, а систематичний пошук за допомогою байєсівського пошуку. Байєсівський пошук дозволяє знаходити оптимальні гіперпараметри з меншою кількістю тріалів. Розберемо, як ми впроваджуємо HPO в production і економимо до 5× обчислювальних ресурсів.

Чому Bayesian Optimization виграє у Random Search?

Random Search ефективний при високій розмірності та малому бюджеті. Але коли важливих гіперпараметрів 3–5 (типовий випадок), Bayesian Optimization з TPE починає вигравати з ~30-го тріалу: потребує в 3–5 разів менше тріалів для досягнення тієї ж якості. TPE будує роздільні щільності для «хороших» (top-25%) та «поганих» конфігурацій, потім пропонує конфігурації з високим Expected Improvement. Grid Search сьогодні застосовний тільки до двох гіперпараметрів — далі комбінаторний вибух.

Як Optuna скорочує час пошуку?

Optuna — de-facto стандарт HPO в Python. Ключові переваги: Pythonic API без YAML-конфігів, вбудований pruning, інтеграція з MLflow та Weights & Biases. Ключова фішка — Hyperband Pruner, який обрізає погані тріали на ранніх rounds. На практиці з 200 тріалів LightGBM 40–60% обрізаються після 50–100 rounds замість повних 2000. Підсумкове прискорення: 3–5×. Optuna з Hyperband Pruner обрізає до 60% тріалів, прискорюючи пошук у 3 рази порівняно зі звичайним байєсівським пошуком. Random Search потребує приблизно в 3 рази більше тріалів, ніж байєсівський пошук.

Код оптимізації LightGBM
import optuna from optuna.integration import LightGBMPruningCallback import lightgbm as lgb from sklearn.model_selection import StratifiedKFold from sklearn.metrics import roc_auc_score import numpy as np def objective(trial: optuna.Trial, X, y) -> float: params = { 'objective': 'binary', 'metric': 'auc', 'verbosity': -1, 'boosting_type': trial.suggest_categorical('boosting', ['gbdt', 'dart']), 'n_estimators': trial.suggest_int('n_estimators', 100, 2000), 'learning_rate': trial.suggest_float('learning_rate', 1e-4, 0.3, log=True), 'num_leaves': trial.suggest_int('num_leaves', 20, 300), 'max_depth': trial.suggest_int('max_depth', 3, 12), 'min_child_samples': trial.suggest_int('min_child_samples', 5, 300), 'feature_fraction': trial.suggest_float('feature_fraction', 0.4, 1.0), 'bagging_fraction': trial.suggest_float('bagging_fraction', 0.4, 1.0), 'bagging_freq': trial.suggest_int('bagging_freq', 1, 7), 'reg_alpha': trial.suggest_float('reg_alpha', 1e-9, 10.0, log=True), 'reg_lambda': trial.suggest_float('reg_lambda', 1e-9, 10.0, log=True), } cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42) cv_scores = [] for fold, (train_idx, val_idx) in enumerate(cv.split(X, y)): X_train, X_val = X[train_idx], X[val_idx] y_train, y_val = y[train_idx], y[val_idx] dtrain = lgb.Dataset(X_train, label=y_train) dval = lgb.Dataset(X_val, label=y_val, reference=dtrain) pruning_callback = LightGBMPruningCallback(trial, 'auc', valid_name='valid_1') model = lgb.train( params, dtrain, valid_sets=[dtrain, dval], num_boost_round=params['n_estimators'], callbacks=[ lgb.early_stopping(stopping_rounds=50, verbose=False), lgb.log_evaluation(period=-1), pruning_callback, ], ) y_pred = model.predict(X_val) cv_scores.append(roc_auc_score(y_val, y_pred)) return float(np.mean(cv_scores)) sampler = optuna.samplers.TPESampler( n_startup_trials=20, multivariate=True, seed=42 ) pruner = optuna.pruners.HyperbandPruner( min_resource=50, max_resource=2000, reduction_factor=3 ) study = optuna.create_study( direction='maximize', sampler=sampler, pruner=pruner, study_name='lgbm_credit_scoring', storage='sqlite:///optuna_studies.db', load_if_exists=True ) study.optimize( lambda trial: objective(trial, X, y), n_trials=200, n_jobs=4, timeout=3600, show_progress_bar=True ) print(f'Best AUC: {study.best_value:.4f}') print(f'Best params: {study.best_params}') 

Візуалізація та аналіз важливості параметрів:

import optuna.visualization as vis fig = vis.plot_param_importances(study) fig.show() fig = vis.plot_optimization_history(study) fig.show() fig = vis.plot_contour(study, params=['num_leaves', 'learning_rate']) fig.show() 

Аналіз fANOVA часто дає несподівані результати: num_leaves і min_child_samples виявляються важливішими за learning_rate для LightGBM на незбалансованих даних. Ефективність байєсівської оптимізації залежить від вибору acquisition function, а саме Expected Improvement (EI) або Upper Confidence Bound (UCB), які балансують між exploitation та exploration.

Коли варто обрати Ray Tune?

Ray Tune вирішує інше завдання — паралельний пошук на кластері GPU. Якщо Optuna з n_jobs=4 паралелить на одній машині, Ray Tune масштабується до сотень вузлів. Ray Tune краще підходить для розподіленого навчання з розподіленим навчанням на кластері, а Optuna — для класичного ML на одній машині. Для розподіленого навчання Ray Tune забезпечує масштабування.

from ray import tune from ray.tune.schedulers import ASHAScheduler from ray.tune.search.optuna import OptunaSearch import torch def train_transformer(config: dict): model = build_model( hidden_dim=config['hidden_dim'], num_heads=config['num_heads'], num_layers=config['num_layers'], dropout=config['dropout'] ) optimizer = torch.optim.AdamW( model.parameters(), lr=config['lr'], weight_decay=config['weight_decay'] ) for epoch in range(config['max_epochs']): train_loss = train_one_epoch(model, optimizer) val_loss, val_acc = evaluate(model) tune.report(val_loss=val_loss, val_acc=val_acc, epoch=epoch) scheduler = ASHAScheduler( time_attr='epoch', max_t=100, grace_period=10, reduction_factor=3, metric='val_loss', mode='min' ) search_alg = OptunaSearch( metric='val_loss', mode='min', sampler=optuna.samplers.TPESampler(seed=42) ) search_space = { 'hidden_dim': tune.choice([128, 256, 512]), 'num_heads': tune.choice([4, 8, 16]), 'num_layers': tune.randint(2, 8), 'dropout': tune.uniform(0.0, 0.5), 'lr': tune.loguniform(1e-5, 1e-2), 'weight_decay': tune.loguniform(1e-8, 1e-3), 'max_epochs': 100 } analysis = tune.run( train_transformer, config=search_space, num_samples=100, scheduler=scheduler, search_alg=search_alg, resources_per_trial={'gpu': 1, 'cpu': 4}, storage_path='s3://my-bucket/ray-results', name='transformer_hpo_v2' ) best_config = analysis.get_best_config(metric='val_loss', mode='min') 

Кейс: HPO для fraud detection моделі з нашої практики

Цей кейс з нашого досвіду для клієнта у fintech. Задача: бінарна класифікація транзакцій для клієнта, дисбаланс 1:340 (fraud:normal), 2.1M записів. Baseline XGBoost з дефолтними параметрами: PR-AUC = 0.412.

Optuna, 150 тріалів, 4 паралельних воркери, ~2.5 години:

  • search space: 11 параметрів XGBoost + scale_pos_weight (1–350)
  • метрика: PR-AUC на stratified 5-fold CV
  • pruner: MedianPruner

Результат: PR-AUC = 0.581 (+41% відносно baseline). Найважливіші параметри: scale_pos_weight (22%), min_child_weight (18%), subsample (15%). max_depth і n_estimators — сумарно 14%. Наш клієнт отримав приріст PR-AUC на 41%.

Етап для клієнта PR-AUC Recall при Precision=0.8
XGBoost default 0.412 0.34
Random Search (50 trials) 0.521 0.47
Optuna TPE (150 trials) 0.581 0.56
+ Feature engineering 0.634 0.62

Економія від впровадження: зниження хибних спрацьовувань на 23% зекономило клієнту $12,000 на місяць на ручній верифікації. Завдяки HPO клієнт заощадив $12,000 на місяць.

Optuna vs Ray Tune: коли що обрати

Критерій Optuna Ray Tune
Одна машина, 1–8 GPU + надлишковий
Кластер 10+ GPU/вузлів складніше +
Deep learning (PyTorch/JAX) + +
Класичний ML (sklearn, lgbm) + працює
Інтеграція з distributed training через callbacks native
Відновлення після збою SQLite/PostgreSQL backend +
Крива навчання для нової команди полога крутіша

Інтеграція з MLflow і Weights & Biases

import mlflow import optuna def objective_with_tracking(trial): with mlflow.start_run(nested=True): params = { 'lr': trial.suggest_float('lr', 1e-5, 1e-1, log=True), 'dropout': trial.suggest_float('dropout', 0.1, 0.5), } mlflow.log_params(params) val_acc = train_and_evaluate(params) mlflow.log_metric('val_acc', val_acc) return val_acc with mlflow.start_run(run_name='hpo_study'): study.optimize(objective_with_tracking, n_trials=100) mlflow.log_metric('best_val_acc', study.best_value) mlflow.log_params(study.best_params) 

Типові помилки та як їх уникнути

Data leakage в objective: якщо preprocessing (StandardScaler, target encoding) фітиться на всьому train-set перед CV — результати HPO оптимістично завищені, production-деградація гарантована. Scaler має фітитися тільки на train-fold всередині CV. Інша помилка: оптимізація accuracy замість бізнес-метрики при дисбалансі класів — знаходимо конфігурацію з accuracy 98.3% при recall на minority-клас 0.04.

Що входить у роботу під ключ

  • Аудит поточного пайплайну та вибір інструменту (Optuna / Ray Tune / Hyperopt)
  • Налаштування search space та метрик на основі бізнес-цілей
  • Реалізація HPO з pruning та паралельними тріалами
  • Інтеграція з MLflow для трекінгу експериментів
  • Документація з відтворення результатів
  • Навчання команди роботі з інструментом

Процес роботи

  1. Аналітика — збір вимог, вивчення даних, baseline моделі.
  2. Проєктування — вибір HPO-фреймворку, визначення search space, метрик.
  3. Реалізація — написання objective-функції, налаштування паралелізму та pruning.
  4. Тестування — запуск на CV, перевірка на holdout, порівняння з baseline.
  5. Деплой — впровадження найкращої конфігурації в CI/CD, моніторинг у production.

Терміни та вартість

Терміни: базова HPO з Optuna на одному завданні — 2–5 днів. Distributed HPO з Ray Tune та інтеграцією в CI/CD — 2–4 тижні. Вартість: базова HPO з Optuna — від $3,000, distributed HPO з Ray Tune — від $8,000. Оцінимо ваш проєкт безкоштовно — зв'яжіться з нами для консультації.

Наша команда має 7+ років досвіду у ML-продакшені, реалізувала понад 50 проєктів з HPO для клієнтів із fintech, e-commerce та рекламних технологій.