Optuna 라이브러리

김동준·2025년 10월 15일

Optuna 라이브러리

Optuna는 하이퍼파라미터 최적화(Hyperparameter Optimization) 를 위한 Python 라이브러리입니다. 머신러닝 모델의 최적의 하이퍼파라미터를 자동으로 찾아주는 강력한 도구입니다.

주요 특징

자동 최적화 알고리즘

  • TPE (Tree-structured Parzen Estimator): 베이지안 최적화 기반
  • CMA-ES, Grid Search, Random Search 등 다양한 샘플링 알고리즘 지원
  • 효율적인 탐색으로 시행착오를 줄여줍니다

사용 편의성

  • 직관적이고 간단한 API
  • Define-by-Run 방식: 동적으로 탐색 공간 정의 가능
  • 다양한 머신러닝 프레임워크와 통합 (scikit-learn, PyTorch, TensorFlow, XGBoost 등)

고급 기능

  • Pruning: 성능이 나쁜 trial을 조기에 중단
  • 분산 최적화: 병렬 처리 지원
  • 시각화 도구: 최적화 과정을 그래프로 확인
  • Study 저장/불러오기: 실험 결과 관리

기본 사용 예시

import optuna
from sklearn.datasets import load_iris
from sklearn.model_selection import cross_val_score
from sklearn.ensemble import RandomForestClassifier

# 목적 함수 정의
def objective(trial):
    # 하이퍼파라미터 탐색 공간 정의
    n_estimators = trial.suggest_int('n_estimators', 10, 100)
    max_depth = trial.suggest_int('max_depth', 2, 32)
    
    # 모델 학습 및 평가
    clf = RandomForestClassifier(
        n_estimators=n_estimators,
        max_depth=max_depth,
        random_state=42
    )
    
    X, y = load_iris(return_X_y=True)
    score = cross_val_score(clf, X, y, cv=3).mean()
    
    return score

# 최적화 실행
study = optuna.create_study(direction='maximize')
study.optimize(objective, n_trials=100)

# 결과 확인
print(f"최적 파라미터: {study.best_params}")
print(f"최적 점수: {study.best_value}")

주요 메서드

  • suggest_int(): 정수형 파라미터
  • suggest_float(): 실수형 파라미터
  • suggest_categorical(): 범주형 파라미터
  • suggest_loguniform(): 로그 스케일 실수

설치

pip install optuna

요약(Summarization)에서 최적 하이퍼파라미터 찾는 방법

요약 모델의 하이퍼파라미터 최적화는 일반적으로 ROUGE 점수BLEU 점수 같은 평가 지표를 목적 함수로 사용합니다.

1. Transformer 기반 요약 모델 최적화 예시

import optuna
from transformers import AutoTokenizer, AutoModelForSeq2SeqLM, Trainer, TrainingArguments
from datasets import load_dataset
import evaluate

def objective(trial):
    # 하이퍼파라미터 탐색 공간
    learning_rate = trial.suggest_float('learning_rate', 1e-5, 5e-4, log=True)
    batch_size = trial.suggest_categorical('batch_size', [8, 16, 32])
    num_epochs = trial.suggest_int('num_epochs', 3, 10)
    weight_decay = trial.suggest_float('weight_decay', 0.0, 0.3)
    
    # 모델 및 데이터 준비
    model = AutoModelForSeq2SeqLM.from_pretrained('t5-small')
    tokenizer = AutoTokenizer.from_pretrained('t5-small')
    
    # 학습 설정
    training_args = TrainingArguments(
        output_dir='./results',
        learning_rate=learning_rate,
        per_device_train_batch_size=batch_size,
        num_train_epochs=num_epochs,
        weight_decay=weight_decay,
        evaluation_strategy="epoch",
    )
    
    # 학습 및 평가
    trainer = Trainer(
        model=model,
        args=training_args,
        train_dataset=train_dataset,
        eval_dataset=eval_dataset,
    )
    
    trainer.train()
    
    # ROUGE 점수 계산
    predictions = trainer.predict(eval_dataset)
    rouge = evaluate.load('rouge')
    scores = rouge.compute(predictions=pred_texts, references=ref_texts)
    
    return scores['rouge2']  # ROUGE-2 F1 점수를 최대화

# 최적화 실행
study = optuna.create_study(direction='maximize')
study.optimize(objective, n_trials=50)

2. 요약 생성 파라미터 최적화

요약 생성 시 사용하는 디코딩 파라미터도 최적화할 수 있습니다:

def objective(trial):
    # 생성 파라미터
    max_length = trial.suggest_int('max_length', 50, 200)
    min_length = trial.suggest_int('min_length', 10, 50)
    num_beams = trial.suggest_int('num_beams', 2, 8)
    temperature = trial.suggest_float('temperature', 0.5, 2.0)
    top_p = trial.suggest_float('top_p', 0.7, 1.0)
    
    # 요약 생성
    summaries = model.generate(
        input_ids,
        max_length=max_length,
        min_length=min_length,
        num_beams=num_beams,
        temperature=temperature,
        top_p=top_p,
    )
    
    # ROUGE 점수로 평가
    rouge_score = compute_rouge(summaries, references)
    return rouge_score['rouge2']

3. 최적화 대상 주요 파라미터

학습 관련

  • Learning rate
  • Batch size
  • Epochs
  • Weight decay
  • Warmup steps

생성 관련

  • Max/Min length (요약 길이)
  • Num beams (Beam search 크기)
  • Temperature (다양성 조절)
  • Top-k, Top-p (샘플링 전략)
  • Repetition penalty (반복 방지)

4. 평가 지표 선택

# 여러 지표를 종합적으로 고려
def objective(trial):
    # ... 학습 코드 ...
    
    rouge = evaluate.load('rouge')
    bert_score = evaluate.load('bertscore')
    
    rouge_scores = rouge.compute(predictions=preds, references=refs)
    bert_scores = bert_score.compute(predictions=preds, references=refs)
    
    # 가중 평균으로 종합 점수 계산
    combined_score = (
        0.5 * rouge_scores['rouge2'] + 
        0.5 * bert_scores['f1'].mean()
    )
    
    return combined_score

5. Pruning으로 효율성 향상

def objective(trial):
    for epoch in range(num_epochs):
        train_loss = train_one_epoch()
        val_score = evaluate()
        
        # 중간 점수 보고
        trial.report(val_score, epoch)
        
        # 성능이 나쁜 trial 조기 중단
        if trial.should_prune():
            raise optuna.TrialPruned()
    
    return final_score

# Pruner 설정
study = optuna.create_study(
    direction='maximize',
    pruner=optuna.pruners.MedianPruner()
)

핵심은 ROUGE, BLEU, BERTScore 같은 요약 평가 지표를 목적 함수로 설정하고, 모델의 학습 파라미터와 생성 파라미터를 동시에 최적화하는 것임.

ROUGE (Recall-Oriented Understudy for Gisting Evaluation)

ROUGE는 자동 요약의 품질을 평가하는 대표적인 지표입니다. 생성된 요약문과 참조(정답) 요약문을 비교하여 얼마나 유사한지 측정합니다.

ROUGE의 주요 유형

1. ROUGE-N (N-gram Overlap)

N개의 연속된 단어가 얼마나 겹치는지 측정합니다.

  • ROUGE-1: 단어(unigram) 단위 비교
  • ROUGE-2: 2개 연속 단어(bigram) 단위 비교
  • ROUGE-3: 3개 연속 단어(trigram) 단위 비교
참조 요약: "the cat sat on the mat"
생성 요약: "the cat is on the mat"

ROUGE-1: 5/6 = 0.833 (6개 중 5개 단어 일치)
ROUGE-2: 2/5 = 0.400 (bigram 일치도)

2. ROUGE-L (Longest Common Subsequence)

가장 긴 공통 부분 수열(LCS)을 기반으로 측정합니다. 순서는 유지하되 연속적일 필요는 없습니다.

3. ROUGE-S (Skip-bigram)

Skip-bigram(중간에 단어를 건너뛸 수 있는 bigram)을 사용합니다.

ROUGE 점수의 3가지 측정값

Precision (정밀도) = 생성 요약의 단어 중 참조에 있는 비율
Recall (재현율) = 참조 요약의 단어 중 생성에 있는 비율
F1-Score = 2 × (Precision × Recall) / (Precision + Recall)

Python에서 ROUGE 계산

1. Hugging Face evaluate 라이브러리 사용

import evaluate

# ROUGE 로드
rouge = evaluate.load('rouge')

# 예시 데이터
predictions = [
    "the cat sat on the mat",
    "it was a sunny day"
]
references = [
    "the cat was sitting on the mat",
    "the day was bright and sunny"
]

# ROUGE 계산
results = rouge.compute(
    predictions=predictions,
    references=references
)

print(results)
# {'rouge1': 0.75, 'rouge2': 0.45, 'rougeL': 0.68, 'rougeLsum': 0.68}

2. rouge-score 라이브러리 사용

from rouge_score import rouge_scorer

scorer = rouge_scorer.RougeScorer(
    ['rouge1', 'rouge2', 'rougeL'], 
    use_stemmer=True
)

scores = scorer.score(
    'the cat sat on the mat',
    'the cat was sitting on the mat'
)

print(scores)
# {'rouge1': Score(precision=0.857, recall=0.75, fmeasure=0.8),
#  'rouge2': Score(precision=0.6, recall=0.5, fmeasure=0.545),
#  'rougeL': Score(precision=0.714, recall=0.625, fmeasure=0.667)}

3. Optuna와 함께 사용

import optuna
from transformers import pipeline
import evaluate

def objective(trial):
    # 하이퍼파라미터 설정
    max_length = trial.suggest_int('max_length', 50, 150)
    min_length = trial.suggest_int('min_length', 20, 50)
    num_beams = trial.suggest_int('num_beams', 2, 8)
    
    # 요약 모델
    summarizer = pipeline("summarization", model="t5-small")
    
    # 요약 생성
    summaries = []
    for text in texts:
        summary = summarizer(
            text,
            max_length=max_length,
            min_length=min_length,
            num_beams=num_beams,
            do_sample=False
        )[0]['summary_text']
        summaries.append(summary)
    
    # ROUGE 계산
    rouge = evaluate.load('rouge')
    results = rouge.compute(
        predictions=summaries,
        references=reference_summaries
    )
    
    # ROUGE-2 F1 점수를 목적 함수로 사용
    return results['rouge2']

# 최적화
study = optuna.create_study(direction='maximize')
study.optimize(objective, n_trials=30)

print(f"최적 파라미터: {study.best_params}")
print(f"최적 ROUGE-2: {study.best_value}")

ROUGE 점수 해석

점수 범위품질
0.5 이상우수
0.3-0.5양호
0.3 미만개선 필요

일반적으로 사용되는 지표

  • ROUGE-1: 전체적인 내용 포함 정도
  • ROUGE-2: 문맥과 의미의 정확성
  • ROUGE-L: 문장 구조의 유사성

설치

pip install rouge-score
# 또는
pip install evaluate

ROUGE의 장단점

장점

  • 계산이 빠르고 간단
  • 객관적이고 재현 가능한 평가
  • 산업 표준으로 널리 사용

단점

  • 의미적 유사성을 완벽히 포착하지 못함
  • 동의어나 paraphrasing을 인식하지 못함
  • 단순 단어 매칭 기반

더 의미적 평가가 필요하면 BERTScoreBLEURT 같은 지표를 함께 사용하는 것이 좋습니다.

Optuna 활용 방법

Optuna는 다양한 머신러닝/딥러닝 작업에서 최적의 하이퍼파라미터를 자동으로 찾는 데 활용할 수 있습니다.

1. 기본 활용 패턴

핵심 3단계

import optuna

# 1단계: 목적 함수 정의
def objective(trial):
    # 파라미터 제안
    param = trial.suggest_float('param', 0.1, 10.0)
    
    # 모델 학습 및 평가
    score = train_and_evaluate(param)
    
    # 최적화할 점수 반환
    return score

# 2단계: Study 생성
study = optuna.create_study(direction='maximize')  # 또는 'minimize'

# 3단계: 최적화 실행
study.optimize(objective, n_trials=100)

# 결과 확인
print(study.best_params)
print(study.best_value)

2. 실전 활용 예시

A. 요약 모델 최적화

from transformers import AutoTokenizer, AutoModelForSeq2SeqLM
import evaluate
import optuna

def objective(trial):
    # 생성 파라미터 탐색
    max_length = trial.suggest_int('max_length', 50, 200)
    min_length = trial.suggest_int('min_length', 10, 50)
    num_beams = trial.suggest_int('num_beams', 2, 8)
    length_penalty = trial.suggest_float('length_penalty', 0.5, 2.0)
    
    # 모델 로드
    model = AutoModelForSeq2SeqLM.from_pretrained('facebook/bart-base')
    tokenizer = AutoTokenizer.from_pretrained('facebook/bart-base')
    
    # 요약 생성
    summaries = []
    for text in texts:
        inputs = tokenizer(text, return_tensors='pt', truncation=True)
        outputs = model.generate(
            inputs['input_ids'],
            max_length=max_length,
            min_length=min_length,
            num_beams=num_beams,
            length_penalty=length_penalty,
        )
        summary = tokenizer.decode(outputs[0], skip_special_tokens=True)
        summaries.append(summary)
    
    # ROUGE 평가
    rouge = evaluate.load('rouge')
    results = rouge.compute(predictions=summaries, references=references)
    
    return results['rouge2']

# 최적화
study = optuna.create_study(direction='maximize')
study.optimize(objective, n_trials=50)

B. 딥러닝 학습 하이퍼파라미터 최적화

import torch
import torch.nn as nn
from torch.utils.data import DataLoader

def objective(trial):
    # 학습 파라미터
    lr = trial.suggest_float('lr', 1e-5, 1e-2, log=True)
    batch_size = trial.suggest_categorical('batch_size', [16, 32, 64])
    optimizer_name = trial.suggest_categorical('optimizer', ['Adam', 'SGD', 'AdamW'])
    dropout = trial.suggest_float('dropout', 0.1, 0.5)
    
    # 모델 구성
    model = YourModel(dropout=dropout)
    
    # 옵티마이저 선택
    if optimizer_name == 'Adam':
        optimizer = torch.optim.Adam(model.parameters(), lr=lr)
    elif optimizer_name == 'SGD':
        optimizer = torch.optim.SGD(model.parameters(), lr=lr)
    else:
        optimizer = torch.optim.AdamW(model.parameters(), lr=lr)
    
    # 학습
    dataloader = DataLoader(dataset, batch_size=batch_size)
    for epoch in range(10):
        train(model, dataloader, optimizer)
    
    # 검증
    val_accuracy = validate(model, val_dataloader)
    
    return val_accuracy

study = optuna.create_study(direction='maximize')
study.optimize(objective, n_trials=100)

C. 머신러닝 모델 최적화

from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_val_score

def objective(trial):
    # 하이퍼파라미터 제안
    n_estimators = trial.suggest_int('n_estimators', 50, 300)
    max_depth = trial.suggest_int('max_depth', 3, 20)
    min_samples_split = trial.suggest_int('min_samples_split', 2, 10)
    min_samples_leaf = trial.suggest_int('min_samples_leaf', 1, 5)
    
    # 모델 생성
    clf = RandomForestClassifier(
        n_estimators=n_estimators,
        max_depth=max_depth,
        min_samples_split=min_samples_split,
        min_samples_leaf=min_samples_leaf,
        random_state=42
    )
    
    # 교차 검증
    score = cross_val_score(clf, X, y, cv=5, scoring='accuracy').mean()
    
    return score

study = optuna.create_study(direction='maximize')
study.optimize(objective, n_trials=100)

3. 고급 활용 기법

A. Pruning (조기 중단)

def objective(trial):
    model = create_model()
    
    for epoch in range(100):
        train_loss = train_one_epoch(model)
        val_loss = validate(model)
        
        # 중간 결과 보고
        trial.report(val_loss, epoch)
        
        # 성능이 나쁜 trial 조기 중단
        if trial.should_prune():
            raise optuna.TrialPruned()
    
    return val_loss

# Pruner 설정
study = optuna.create_study(
    direction='minimize',
    pruner=optuna.pruners.MedianPruner(n_warmup_steps=10)
)
study.optimize(objective, n_trials=100)

B. 다중 목적 최적화

def objective(trial):
    # 파라미터 설정
    lr = trial.suggest_float('lr', 1e-5, 1e-2, log=True)
    
    model = train_model(lr)
    
    accuracy = evaluate_accuracy(model)
    inference_time = measure_inference_time(model)
    
    # 두 가지 목표 반환: 정확도는 높이고, 시간은 줄이기
    return accuracy, inference_time

# 다중 목적 최적화
study = optuna.create_study(
    directions=['maximize', 'minimize']
)
study.optimize(objective, n_trials=100)

# Pareto front 확인
print("Pareto 최적 해:")
for trial in study.best_trials:
    print(f"  정확도: {trial.values[0]}, 시간: {trial.values[1]}")

C. 병렬 처리

# 병렬로 여러 trial 실행
study = optuna.create_study(direction='maximize')
study.optimize(objective, n_trials=100, n_jobs=4)  # 4개 프로세스

D. Study 저장 및 재개

# 데이터베이스에 저장
study = optuna.create_study(
    study_name='my_study',
    storage='sqlite:///optuna.db',
    load_if_exists=True
)

# 최적화 실행 (중단해도 재개 가능)
study.optimize(objective, n_trials=100)

# 나중에 재개
study = optuna.load_study(
    study_name='my_study',
    storage='sqlite:///optuna.db'
)
study.optimize(objective, n_trials=50)  # 추가 50회

4. 시각화

import optuna.visualization as vis

# 최적화 이력
vis.plot_optimization_history(study).show()

# 파라미터 중요도
vis.plot_param_importances(study).show()

# Contour plot (2개 파라미터 관계)
vis.plot_contour(study, params=['lr', 'batch_size']).show()

# Parallel coordinate plot
vis.plot_parallel_coordinate(study).show()

# Slice plot (각 파라미터 영향)
vis.plot_slice(study).show()

5. 실용적인 팁

탐색 공간 정의 방법

def objective(trial):
    # 정수형
    n_layers = trial.suggest_int('n_layers', 1, 5)
    
    # 실수형 (로그 스케일)
    lr = trial.suggest_float('lr', 1e-5, 1e-1, log=True)
    
    # 실수형 (선형 스케일)
    dropout = trial.suggest_float('dropout', 0.0, 0.5)
    
    # 범주형
    optimizer = trial.suggest_categorical('optimizer', ['Adam', 'SGD', 'RMSprop'])
    
    # 조건부 파라미터
    if optimizer == 'SGD':
        momentum = trial.suggest_float('momentum', 0.0, 0.99)
    
    return score

콜백 사용

# 특정 조건에서 최적화 중단
def callback(study, trial):
    if study.best_value > 0.95:
        study.stop()

study.optimize(objective, n_trials=1000, callbacks=[callback])

6. 주요 파라미터 종류별 활용

작업최적화 대상
요약max_length, num_beams, temperature, length_penalty
분류learning_rate, batch_size, dropout, weight_decay
생성temperature, top_k, top_p, repetition_penalty
트리 모델n_estimators, max_depth, min_samples_split
신경망hidden_size, n_layers, activation, optimizer

Optuna의 핵심은 반복 실험을 자동화하여 최적의 설정을 효율적으로 찾는 것입니다.

profile
Story Engineer

0개의 댓글