- scripts/tune_hyperparams.py: Optuna + Walk-Forward 5폴드 AUC 목적 함수 - 데이터셋 1회 캐싱으로 모든 trial 공유 (속도 최적화) - num_leaves <= 2^max_depth - 1 제약 강제 (소규모 데이터 과적합 방지) - MedianPruner로 저성능 trial 조기 종료 - 결과: 콘솔 리포트 + models/tune_results_YYYYMMDD_HHMMSS.json - requirements.txt: optuna>=3.6.0 추가 - README.md: 하이퍼파라미터 자동 튜닝 사용법 섹션 추가 - docs/plans/: 설계 문서 및 구현 플랜 추가 Made-with: Cursor
6.6 KiB
6.6 KiB
Optuna 하이퍼파라미터 자동 튜닝 설계 문서
작성일: 2026-03-02
목표: 봇 운영 로그/학습 결과를 바탕으로 LightGBM 하이퍼파라미터를 Optuna로 자동 탐색하고, 사람이 결과를 확인·승인한 후 재학습에 반영하는 수동 트리거 파이프라인 구축
배경 및 동기
현재 train_model.py의 LightGBM 파라미터는 하드코딩되어 있다. 봇 성능이 저하되거나 데이터가 축적될 때마다 사람이 직접 파라미터를 조정해야 한다. 이를 Optuna로 자동화하되, 과적합 위험을 방지하기 위해 사람이 결과를 먼저 확인하고 승인하는 구조를 유지한다.
구현 범위 (2단계)
1단계 (현재): LightGBM 하이퍼파라미터 튜닝
scripts/tune_hyperparams.py신규 생성- Optuna + Walk-Forward AUC 목적 함수
- 결과를 JSON + 콘솔 리포트로 출력
2단계 (추후): 기술 지표 파라미터 확장
- RSI 임계값, MACD 가중치, Stochastic RSI 임계값, 거래량 배수, 진입 점수 임계값 등을 탐색 공간에 추가
dataset_builder.py의_calc_signals()파라미터화 필요
아키텍처
scripts/tune_hyperparams.py
├── load_dataset() ← 데이터 로드 + 벡터화 데이터셋 1회 생성 (캐싱)
├── objective(trial, dataset) ← Optuna trial 함수
│ ├── trial.suggest_*() ← 하이퍼파라미터 샘플링
│ ├── num_leaves 상한 강제 ← 2^max_depth - 1 제약
│ └── _walk_forward_cv() ← Walk-Forward 교차검증 → 평균 AUC 반환
├── run_study() ← Optuna study 실행 (TPESampler + MedianPruner)
├── print_report() ← 콘솔 리포트 출력
└── save_results() ← JSON 저장 (models/tune_results_YYYYMMDD_HHMMSS.json)
탐색 공간 (소규모 데이터셋 보수적 설계)
| 파라미터 | 범위 | 타입 | 근거 |
|---|---|---|---|
n_estimators |
100 ~ 600 | int | 데이터 적을 때 500+ 트리는 과적합 |
learning_rate |
0.01 ~ 0.2 | float (log) | 낮을수록 일반화 유리 |
max_depth |
2 ~ 7 | int | 트리 깊이 상한 강제 |
num_leaves |
7 ~ min(31, 2^max_depth-1) | int | 핵심: leaf-wise 과적합 방지 |
min_child_samples |
10 ~ 50 | int | 리프당 최소 샘플 수 |
subsample |
0.5 ~ 1.0 | float | 행 샘플링 |
colsample_bytree |
0.5 ~ 1.0 | float | 열 샘플링 |
reg_alpha |
1e-4 ~ 1.0 | float (log) | L1 정규화 |
reg_lambda |
1e-4 ~ 1.0 | float (log) | L2 정규화 |
time_weight_decay |
0.5 ~ 4.0 | float | 시간 가중치 강도 |
핵심 제약: num_leaves <= 2^max_depth - 1
LightGBM은 leaf-wise 성장 전략을 사용하므로, num_leaves가 2^max_depth - 1을 초과하면 max_depth 제약이 무의미해진다. trial 내에서 max_depth를 먼저 샘플링한 후 num_leaves 상한을 동적으로 계산하여 강제한다.
max_depth = trial.suggest_int("max_depth", 2, 7)
max_leaves = min(31, 2 ** max_depth - 1)
num_leaves = trial.suggest_int("num_leaves", 7, max_leaves)
목적 함수: Walk-Forward AUC
기존 train_model.py의 walk_forward_auc() 로직을 재활용한다. 데이터셋은 study 시작 전 1회만 생성하여 모든 trial이 공유한다 (속도 최적화).
전체 데이터셋 (N개 샘플)
├── 폴드 1: 학습[0:60%] → 검증[60%:68%]
├── 폴드 2: 학습[0:68%] → 검증[68%:76%]
├── 폴드 3: 학습[0:76%] → 검증[76%:84%]
├── 폴드 4: 학습[0:84%] → 검증[84%:92%]
└── 폴드 5: 학습[0:92%] → 검증[92%:100%]
목적 함수 = 5폴드 평균 AUC (최대화)
Pruning (조기 종료)
MedianPruner 적용: 각 폴드 완료 후 중간 AUC를 Optuna에 보고. 이전 trial들의 중앙값보다 낮으면 나머지 폴드를 건너뛰고 trial 종료. 전체 탐색 시간 ~40% 단축 효과.
출력 형식
콘솔 리포트
============================================================
Optuna 튜닝 완료 | 50 trials | 소요: 28분 42초
============================================================
Best AUC : 0.6234 (Trial #31)
Baseline : 0.5891 (현재 train_model.py 고정값)
개선폭 : +0.0343 (+5.8%)
------------------------------------------------------------
Best Parameters:
n_estimators : 320
learning_rate : 0.0412
max_depth : 4
num_leaves : 15
min_child_samples : 28
subsample : 0.72
colsample_bytree : 0.81
reg_alpha : 0.0023
reg_lambda : 0.0891
time_weight_decay : 2.31
------------------------------------------------------------
Walk-Forward 폴드별 AUC:
폴드 1: 0.6102
폴드 2: 0.6341
폴드 3: 0.6198
폴드 4: 0.6287
폴드 5: 0.6241
평균: 0.6234 ± 0.0082
------------------------------------------------------------
결과 저장: models/tune_results_20260302_143022.json
다음 단계: python scripts/train_model.py --tuned-params models/tune_results_20260302_143022.json
============================================================
JSON 저장 (models/tune_results_YYYYMMDD_HHMMSS.json)
{
"timestamp": "2026-03-02T14:30:22",
"n_trials": 50,
"elapsed_sec": 1722,
"baseline_auc": 0.5891,
"best_trial": {
"number": 31,
"auc": 0.6234,
"fold_aucs": [0.6102, 0.6341, 0.6198, 0.6287, 0.6241],
"params": { ... }
},
"all_trials": [ ... ]
}
사용법
# 기본 실행 (50 trials, 5폴드)
python scripts/tune_hyperparams.py
# 빠른 테스트 (10 trials, 3폴드)
python scripts/tune_hyperparams.py --trials 10 --folds 3
# 데이터 경로 지정
python scripts/tune_hyperparams.py --data data/combined_15m.parquet --trials 100
파일 변경 목록
| 파일 | 변경 | 설명 |
|---|---|---|
scripts/tune_hyperparams.py |
신규 생성 | Optuna 튜닝 스크립트 |
requirements.txt |
수정 | optuna 의존성 추가 |
README.md |
수정 | 튜닝 사용법 섹션 추가 |
향후 확장 (2단계)
dataset_builder.py의 _calc_signals() 함수를 파라미터화하여 기술 지표 임계값도 탐색 공간에 추가:
# 추가될 탐색 공간 예시
rsi_long_threshold = trial.suggest_int("rsi_long", 25, 40)
rsi_short_threshold = trial.suggest_int("rsi_short", 60, 75)
vol_surge_mult = trial.suggest_float("vol_surge_mult", 1.2, 2.5)
entry_threshold = trial.suggest_int("entry_threshold", 3, 5)
stoch_low = trial.suggest_int("stoch_low", 10, 30)
stoch_high = trial.suggest_int("stoch_high", 70, 90)