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 실습
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)}")
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)}")
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()
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
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 | 알고리즘 | 장점 | 적합한 상황 |
|---|---|---|---|
TPESampler | Tree-structured Parzen Estimator | 기본값, 범용적 | 대부분의 경우 |
CmaEsSampler | CMA-ES | 연속형 파라미터에 강함 | 수치형 파라미터가 많을 때 |
RandomSampler | 무작위 탐색 | 베이스라인 비교용 | 탐색 공간이 매우 넓을 때 |
GridSampler | 격자 탐색 | 완전 탐색 | 파라미터 수가 적을 때 |
Q: Optuna와 GridSearchCV/RandomizedSearchCV의 차이는 무엇인가요?
Q: Optuna와 GridSearchCV/RandomizedSearchCV의 차이는 무엇인가요?
GridSearchCV는 모든 조합을 시도하고, RandomizedSearchCV는 무작위로 샘플링합니다. Optuna의 TPE는 이전 시행 결과를 학습하여 점점 더 유망한 영역을 탐색하므로, 같은 시행 횟수 대비 더 좋은 결과를 찾습니다. 또한 가지치기로 비효율적인 시행을 조기 중단할 수 있습니다.
Q: Optuna Dashboard는 어떻게 사용하나요?
Q: Optuna Dashboard는 어떻게 사용하나요?
pip install optuna-dashboard를 설치한 후, optuna-dashboard sqlite:///db.sqlite3 명령으로 웹 UI를 실행합니다. Study를 SQLite에 저장하면(storage="sqlite:///db.sqlite3") 실시간으로 최적화 과정을 모니터링할 수 있습니다.체크리스트
- Optuna의 목적 함수를 정의하고 최적화를 실행할 수 있다
- 가지치기를 활용하여 탐색 효율을 높일 수 있다
- 최적화 결과를 시각화하고 해석할 수 있다
- 조건부 파라미터 탐색을 구현할 수 있다

