Optuna는 하이퍼파라미터 최적화(Hyperparameter Optimization) 를 위한 Python 라이브러리입니다. 머신러닝 모델의 최적의 하이퍼파라미터를 자동으로 찾아주는 강력한 도구입니다.
자동 최적화 알고리즘
사용 편의성
고급 기능
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
요약 모델의 하이퍼파라미터 최적화는 일반적으로 ROUGE 점수나 BLEU 점수 같은 평가 지표를 목적 함수로 사용합니다.
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)
요약 생성 시 사용하는 디코딩 파라미터도 최적화할 수 있습니다:
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']
학습 관련
생성 관련
# 여러 지표를 종합적으로 고려
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
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는 자동 요약의 품질을 평가하는 대표적인 지표입니다. 생성된 요약문과 참조(정답) 요약문을 비교하여 얼마나 유사한지 측정합니다.
N개의 연속된 단어가 얼마나 겹치는지 측정합니다.
참조 요약: "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 일치도)
가장 긴 공통 부분 수열(LCS)을 기반으로 측정합니다. 순서는 유지하되 연속적일 필요는 없습니다.
Skip-bigram(중간에 단어를 건너뛸 수 있는 bigram)을 사용합니다.
Precision (정밀도) = 생성 요약의 단어 중 참조에 있는 비율
Recall (재현율) = 참조 요약의 단어 중 생성에 있는 비율
F1-Score = 2 × (Precision × Recall) / (Precision + Recall)
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}
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)}
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}")
| 점수 범위 | 품질 |
|---|---|
| 0.5 이상 | 우수 |
| 0.3-0.5 | 양호 |
| 0.3 미만 | 개선 필요 |
일반적으로 사용되는 지표
pip install rouge-score
# 또는
pip install evaluate
장점
단점
더 의미적 평가가 필요하면 BERTScore나 BLEURT 같은 지표를 함께 사용하는 것이 좋습니다.
Optuna는 다양한 머신러닝/딥러닝 작업에서 최적의 하이퍼파라미터를 자동으로 찾는 데 활용할 수 있습니다.
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)
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)
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)
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)
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)
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]}")
# 병렬로 여러 trial 실행
study = optuna.create_study(direction='maximize')
study.optimize(objective, n_trials=100, n_jobs=4) # 4개 프로세스
# 데이터베이스에 저장
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회
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()
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])
| 작업 | 최적화 대상 |
|---|---|
| 요약 | 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의 핵심은 반복 실험을 자동화하여 최적의 설정을 효율적으로 찾는 것입니다.