본문으로 건너뛰기

073 조기 종료 전략 커스터마이징

키워드: 조기 종료, early stopping, 효율적 학습

개요

조기 종료(Early Stopping)는 불필요한 학습을 방지하고 과적합을 예방하는 중요한 기법입니다. FLAML에서 제공하는 다양한 조기 종료 옵션과 커스터마이징 방법을 알아봅니다.

실습 환경

  • Python 버전: 3.11 권장
  • 필요 패키지: flaml[automl]
pip install flaml[automl] pandas numpy matplotlib

조기 종료의 필요성

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split

# 073 데이터 준비
X, y = make_classification(n_samples=3000, n_features=20,
n_informative=10, random_state=42)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# 073 과적합 시각화
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.metrics import accuracy_score

train_scores = []
test_scores = []
n_estimators_range = range(10, 500, 10)

for n_est in n_estimators_range:
model = GradientBoostingClassifier(n_estimators=n_est, random_state=42)
model.fit(X_train, y_train)
train_scores.append(accuracy_score(y_train, model.predict(X_train)))
test_scores.append(accuracy_score(y_test, model.predict(X_test)))

plt.figure(figsize=(10, 6))
plt.plot(n_estimators_range, train_scores, label='Train')
plt.plot(n_estimators_range, test_scores, label='Test')
plt.axvline(x=n_estimators_range[np.argmax(test_scores)], color='red',
linestyle='--', label=f'Optimal: {n_estimators_range[np.argmax(test_scores)]}')
plt.xlabel('n_estimators')
plt.ylabel('Accuracy')
plt.title('Overfitting: Why Early Stopping Matters')
plt.legend()
plt.show()

print(f"최적 n_estimators: {n_estimators_range[np.argmax(test_scores)]}")
print(f"최고 테스트 정확도: {max(test_scores):.4f}")
print("조기 종료 없이 500까지 학습하면 과적합 발생!")

FLAML 기본 조기 종료

from flaml import AutoML

# 073 기본 조기 종료
automl = AutoML()
automl.fit(
X_train, y_train,
task="classification",
time_budget=60,
early_stop=True, # 조기 종료 활성화
verbose=1
)

print(f"\n최적 모델: {automl.best_estimator}")
print(f"최적 설정: {automl.best_config}")

조기 종료 파라미터

# 073 상세 조기 종료 설정
automl_detailed = AutoML()
automl_detailed.fit(
X_train, y_train,
task="classification",
time_budget=60,
early_stop=True,

# 조기 종료 관련 파라미터
eval_method="cv", # 평가 방법
n_splits=5, # CV fold 수
split_type="stratified", # 분할 방식

verbose=1
)

print(f"\n상세 조기 종료 결과: {automl_detailed.best_config}")

LightGBM 조기 종료

import lightgbm as lgb

# 073 LightGBM 직접 조기 종료
X_tr, X_val, y_tr, y_val = train_test_split(X_train, y_train, test_size=0.2, random_state=42)

train_data = lgb.Dataset(X_tr, label=y_tr)
valid_data = lgb.Dataset(X_val, label=y_val, reference=train_data)

params = {
'objective': 'binary',
'metric': 'binary_logloss',
'num_leaves': 31,
'learning_rate': 0.05,
'verbose': -1
}

# 073 조기 종료로 학습
evals_result = {}
model_lgb = lgb.train(
params,
train_data,
num_boost_round=1000, # 최대 반복
valid_sets=[train_data, valid_data],
valid_names=['train', 'valid'],
callbacks=[
lgb.early_stopping(stopping_rounds=50), # 50 라운드 개선 없으면 종료
lgb.log_evaluation(period=100),
lgb.record_evaluation(evals_result)
]
)

print(f"\n조기 종료된 반복: {model_lgb.best_iteration}")
print(f"최적 점수: {model_lgb.best_score['valid']['binary_logloss']:.4f}")

# 073 학습 곡선 시각화
plt.figure(figsize=(10, 6))
plt.plot(evals_result['train']['binary_logloss'], label='Train')
plt.plot(evals_result['valid']['binary_logloss'], label='Valid')
plt.axvline(x=model_lgb.best_iteration, color='red', linestyle='--', label='Early Stop')
plt.xlabel('Iteration')
plt.ylabel('Log Loss')
plt.title('LightGBM Early Stopping')
plt.legend()
plt.show()

XGBoost 조기 종료

import xgboost as xgb

# 073 XGBoost 조기 종료
dtrain = xgb.DMatrix(X_tr, label=y_tr)
dvalid = xgb.DMatrix(X_val, label=y_val)

params_xgb = {
'objective': 'binary:logistic',
'eval_metric': 'logloss',
'max_depth': 6,
'learning_rate': 0.1,
}

evals_xgb = [(dtrain, 'train'), (dvalid, 'valid')]
evals_result_xgb = {}

model_xgb = xgb.train(
params_xgb,
dtrain,
num_boost_round=500,
evals=evals_xgb,
early_stopping_rounds=30, # 30 라운드 개선 없으면 종료
evals_result=evals_result_xgb,
verbose_eval=False
)

print(f"\nXGBoost 조기 종료: {model_xgb.best_iteration} 반복")
print(f"최적 점수: {model_xgb.best_score:.4f}")

ASHA 스케줄러

from flaml import tune

print("ASHA (Asynchronous Successive Halving Algorithm):")
print("=" * 50)

asha_explanation = {
'개념': ['Successive Halving', 'Asynchronous', 'Resource Allocation'],
'설명': [
'유망하지 않은 설정 점진적 제거',
'비동기 실행으로 효율성',
'좋은 설정에 더 많은 리소스'
]
}

print(pd.DataFrame(asha_explanation).to_string(index=False))

# 073 ASHA 스케줄러 설정
from flaml.tune.scheduler import ASHAScheduler

scheduler = ASHAScheduler(
max_resource=100, # 최대 리소스 (epochs)
min_resource=10, # 최소 리소스
reduction_factor=3, # 각 라운드마다 1/3 제거
grace_period=10 # 최소 실행 리소스
)

print("\nASHA 스케줄러 파라미터:")
print(f" max_resource: 100 (최대 epochs)")
print(f" min_resource: 10 (최소 epochs)")
print(f" reduction_factor: 3 (33%만 다음 라운드)")

커스텀 조기 종료 콜백

class CustomEarlyStopping:
"""커스텀 조기 종료 클래스"""

def __init__(self, patience=10, min_delta=0.001):
self.patience = patience
self.min_delta = min_delta
self.best_score = None
self.counter = 0
self.should_stop = False

def __call__(self, score):
if self.best_score is None:
self.best_score = score
elif score < self.best_score - self.min_delta:
self.best_score = score
self.counter = 0
else:
self.counter += 1
if self.counter >= self.patience:
self.should_stop = True

return self.should_stop

# 073 사용 예시
early_stop = CustomEarlyStopping(patience=20, min_delta=0.0001)

scores = [0.5, 0.4, 0.35, 0.33, 0.32, 0.32, 0.32, 0.32] # 개선 없음
for i, score in enumerate(scores):
stop = early_stop(score)
print(f"Epoch {i+1}: score={score:.2f}, should_stop={stop}")

조기 종료 전략 비교

strategies = {
'전략': ['Patience', 'Min Delta', 'ASHA', 'Time-based'],
'기준': [
'N회 연속 개선 없음',
'최소 개선폭 미달',
'성능 기반 리소스 배분',
'시간 제한'
],
'장점': [
'간단, 직관적',
'미세 과적합 방지',
'병렬 탐색에 효과적',
'예측 가능한 실행 시간'
],
'적합한 상황': [
'일반적인 학습',
'노이즈가 적은 데이터',
'하이퍼파라미터 탐색',
'시간 제약이 중요할 때'
]
}

print("\n조기 종료 전략 비교:")
print(pd.DataFrame(strategies).to_string(index=False))

모델별 조기 종료 설정

model_early_stop = {
'모델': ['LightGBM', 'XGBoost', 'CatBoost', 'Neural Network'],
'파라미터': [
'early_stopping_rounds',
'early_stopping_rounds',
'early_stopping_rounds',
'EarlyStopping callback'
],
'권장값': ['50-100', '30-50', '50-100', 'patience=10-20'],
'주의사항': [
'valid_sets 필요',
'evals 필요',
'eval_set 필요',
'validation_data 필요'
]
}

print("\n모델별 조기 종료 설정:")
print(pd.DataFrame(model_early_stop).to_string(index=False))

조기 종료와 FLAML

# 073 FLAML에서 조기 종료 효과 비교
# 073 조기 종료 없이
automl_no_early = AutoML()
automl_no_early.fit(
X_train, y_train,
task="classification",
time_budget=30,
early_stop=False,
verbose=0
)

# 073 조기 종료 있음
automl_with_early = AutoML()
automl_with_early.fit(
X_train, y_train,
task="classification",
time_budget=30,
early_stop=True,
verbose=0
)

print("조기 종료 효과 비교:")
print(f" 조기 종료 없음: {automl_no_early.best_estimator}")
print(f" 탐색 횟수: {len(automl_no_early.config_history)}")
print(f" 조기 종료 있음: {automl_with_early.best_estimator}")
print(f" 탐색 횟수: {len(automl_with_early.config_history)}")

베스트 프랙티스

best_practices = {
'항목': ['patience', 'min_delta', '검증 세트', '모니터링'],
'권장사항': [
'10-50 (데이터/모델에 따라)',
'1e-4 ~ 1e-3',
'학습 데이터의 10-20%',
'학습 곡선 시각화로 적정값 확인'
]
}

print("\n조기 종료 베스트 프랙티스:")
print(pd.DataFrame(best_practices).to_string(index=False))

정리

  • 조기 종료: 과적합 방지, 학습 시간 절약
  • patience: 개선 없는 연속 에폭 수
  • min_delta: 개선으로 인정할 최소 변화
  • ASHA: 하이퍼파라미터 탐색에 효과적
  • 검증 세트: 조기 종료 기준 평가용
  • FLAML은 early_stop=True로 간단히 활성화

다음 글 예고

다음 글에서는 병렬 처리로 학습 가속화에 대해 알아보겠습니다. FLAML에서 병렬 처리를 활용하는 방법을 다룹니다.


FLAML AutoML 마스터 시리즈 #073