Skip to main content

Optuna

Optuna는 Preferred Networks에서 개발한 하이퍼파라미터 최적화 프레임워크로, TPE(Tree-structured Parzen Estimator) 알고리즘을 사용하여 효율적으로 최적의 하이퍼파라미터를 탐색합니다.

학습 목표

  • Optuna의 Study/Trial 구조를 이해하고 목적 함수를 정의할 수 있습니다.
  • 가지치기(Pruning)를 활용하여 비효율적인 시행을 조기 중단할 수 있습니다.
  • 최적화 결과를 시각화하고 분석할 수 있습니다.

Optuna 핵심 개념

개념설명
Study하나의 최적화 세션 (여러 Trial로 구성)
Trial하이퍼파라미터 하나의 조합 시도
Objective최적화할 목적 함수
Sampler다음 파라미터 조합을 결정하는 전략 (TPE, CMA-ES 등)
Pruner성능이 낮은 Trial을 조기 중단하는 전략

Optuna 실습

1
설치 및 기본 사용
2
pip install optuna optuna-dashboard
3
import optuna
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_val_score
from sklearn.datasets import load_breast_cancer

X, y = load_breast_cancer(return_X_y=True)

# 목적 함수 정의 (최소화/최대화 가능)
def objective(trial):
    """최적화할 하이퍼파라미터를 정의하고 성능을 반환"""

    # 하이퍼파라미터 탐색 공간 정의
    n_estimators = trial.suggest_int("n_estimators", 50, 500)
    max_depth = trial.suggest_int("max_depth", 2, 32)
    min_samples_split = trial.suggest_int("min_samples_split", 2, 20)
    min_samples_leaf = trial.suggest_int("min_samples_leaf", 1, 10)
    max_features = trial.suggest_categorical(
        "max_features", ["sqrt", "log2", None]
    )

    model = RandomForestClassifier(
        n_estimators=n_estimators,
        max_depth=max_depth,
        min_samples_split=min_samples_split,
        min_samples_leaf=min_samples_leaf,
        max_features=max_features,
        random_state=42,
        n_jobs=-1,
    )

    # 교차검증 점수 반환
    score = cross_val_score(model, X, y, cv=5, scoring="accuracy").mean()
    return score

# Study 생성 및 최적화 실행
study = optuna.create_study(
    direction="maximize",       # 정확도를 최대화
    study_name="rf-optimization",
)
study.optimize(objective, n_trials=100)

# 결과 확인
print(f"최적 정확도: {study.best_value:.4f}")
print(f"최적 파라미터: {study.best_params}")
print(f"총 시행 수: {len(study.trials)}")
4
가지치기 (Pruning)
5
import optuna
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.model_selection import cross_val_score
import numpy as np

def objective_with_pruning(trial):
    """가지치기를 활용한 조기 중단"""

    n_estimators = trial.suggest_int("n_estimators", 50, 500)
    max_depth = trial.suggest_int("max_depth", 2, 10)
    learning_rate = trial.suggest_float("learning_rate", 0.01, 0.3, log=True)

    model = GradientBoostingClassifier(
        n_estimators=n_estimators,
        max_depth=max_depth,
        learning_rate=learning_rate,
        random_state=42,
    )

    # 단계별 평가로 가지치기 지원
    for step in range(5):  # 5-fold 교차검증의 각 fold
        # 현재 fold 점수 계산
        scores = cross_val_score(
            model, X, y, cv=5, scoring="accuracy"
        )
        intermediate_value = np.mean(scores[:step + 1])

        # 중간 결과 보고
        trial.report(intermediate_value, step)

        # 성능이 낮으면 조기 중단
        if trial.should_prune():
            raise optuna.exceptions.TrialPruned()

    return np.mean(scores)

# MedianPruner: 중간값보다 낮은 Trial 제거
study = optuna.create_study(
    direction="maximize",
    pruner=optuna.pruners.MedianPruner(
        n_startup_trials=5,    # 초기 5회는 가지치기 안 함
        n_warmup_steps=2,      # 각 Trial에서 2 step은 가지치기 안 함
    ),
)
study.optimize(objective_with_pruning, n_trials=50)

# 완료/가지치기된 Trial 수 확인
complete_trials = [t for t in study.trials if t.state == optuna.trial.TrialState.COMPLETE]
pruned_trials = [t for t in study.trials if t.state == optuna.trial.TrialState.PRUNED]
print(f"완료: {len(complete_trials)}, 가지치기: {len(pruned_trials)}")
6
시각화
7
import optuna.visualization as vis

# 최적화 히스토리 (시행에 따른 성능 변화)
fig1 = vis.plot_optimization_history(study)
fig1.show()

# 하이퍼파라미터 중요도
fig2 = vis.plot_param_importances(study)
fig2.show()

# 파라미터 간 관계 (등고선 플롯)
fig3 = vis.plot_contour(study, params=["max_depth", "learning_rate"])
fig3.show()

# 병렬 좌표 플롯 (모든 파라미터의 관계)
fig4 = vis.plot_parallel_coordinate(study)
fig4.show()

# 슬라이스 플롯 (각 파라미터별 성능 분포)
fig5 = vis.plot_slice(study)
fig5.show()
8
다양한 탐색 공간 타입
9
def objective_advanced(trial):
    """다양한 하이퍼파라미터 탐색 공간 타입"""

    # 정수형 파라미터
    n_estimators = trial.suggest_int("n_estimators", 50, 500, step=50)

    # 실수형 파라미터 (로그 스케일)
    learning_rate = trial.suggest_float("learning_rate", 1e-4, 1e-1, log=True)

    # 범주형 파라미터
    booster = trial.suggest_categorical("booster", ["gbtree", "dart"])

    # 조건부 파라미터 (booster에 따라 다른 파라미터)
    if booster == "dart":
        rate_drop = trial.suggest_float("rate_drop", 0.0, 0.5)
    else:
        rate_drop = 0.0

    # 고정 파라미터 (탐색하지 않음)
    random_state = 42

    from xgboost import XGBClassifier

    model = XGBClassifier(
        n_estimators=n_estimators,
        learning_rate=learning_rate,
        booster=booster,
        random_state=random_state,
        eval_metric="logloss",
    )

    score = cross_val_score(model, X, y, cv=5, scoring="accuracy").mean()
    return score
10
MLflow 연동
11
import mlflow
import optuna

def objective_mlflow(trial):
    """Optuna + MLflow 연동으로 실험 관리"""

    params = {
        "n_estimators": trial.suggest_int("n_estimators", 50, 300),
        "max_depth": trial.suggest_int("max_depth", 2, 15),
        "learning_rate": trial.suggest_float("learning_rate", 0.01, 0.3, log=True),
    }

    with mlflow.start_run(nested=True):
        mlflow.log_params(params)

        model = GradientBoostingClassifier(**params, random_state=42)
        score = cross_val_score(model, X, y, cv=5, scoring="accuracy").mean()

        mlflow.log_metric("cv_accuracy", score)

    return score

# MLflow 실험과 함께 최적화
mlflow.set_experiment("optuna-optimization")
with mlflow.start_run(run_name="optuna-study"):
    study = optuna.create_study(direction="maximize")
    study.optimize(objective_mlflow, n_trials=50)

    # 최적 결과 기록
    mlflow.log_params(study.best_params)
    mlflow.log_metric("best_accuracy", study.best_value)

Sampler 비교

Sampler알고리즘장점적합한 상황
TPESamplerTree-structured Parzen Estimator기본값, 범용적대부분의 경우
CmaEsSamplerCMA-ES연속형 파라미터에 강함수치형 파라미터가 많을 때
RandomSampler무작위 탐색베이스라인 비교용탐색 공간이 매우 넓을 때
GridSampler격자 탐색완전 탐색파라미터 수가 적을 때
GridSearchCV는 모든 조합을 시도하고, RandomizedSearchCV는 무작위로 샘플링합니다. Optuna의 TPE는 이전 시행 결과를 학습하여 점점 더 유망한 영역을 탐색하므로, 같은 시행 횟수 대비 더 좋은 결과를 찾습니다. 또한 가지치기로 비효율적인 시행을 조기 중단할 수 있습니다.
pip install optuna-dashboard를 설치한 후, optuna-dashboard sqlite:///db.sqlite3 명령으로 웹 UI를 실행합니다. Study를 SQLite에 저장하면(storage="sqlite:///db.sqlite3") 실시간으로 최적화 과정을 모니터링할 수 있습니다.

체크리스트

  • Optuna의 목적 함수를 정의하고 최적화를 실행할 수 있다
  • 가지치기를 활용하여 탐색 효율을 높일 수 있다
  • 최적화 결과를 시각화하고 해석할 수 있다
  • 조건부 파라미터 탐색을 구현할 수 있다

다음 문서