하이퍼파라미터 튜닝과 AutoML(Hyperparameter Tuning)

하이퍼파라미터는 학습이 시작되기 전에 돌리는 손잡이입니다. 이것을 잘 조정하는지가 평범한 모델과 훌륭한 모델의 차이를 만듭니다.

유형: Build 언어: Python 선수 지식: Phase 2, Lesson 11 (앙상블 방법) 소요 시간: 약 90분

학습 목표

  • 그리드 탐색(Grid Search), 랜덤 탐색(Random Search), 베이지안 최적화(Bayesian Optimization)를 처음부터 구현하고 표본 효율(sample efficiency)을 비교합니다.
  • 대부분의 하이퍼파라미터가 낮은 유효 차원(effective dimensionality)을 가질 때 랜덤 탐색이 그리드 탐색보다 나은 이유를 설명합니다.
  • 대리 모델(surrogate model)과 획득 함수(acquisition function)를 사용해 탐색을 이끄는 베이지안 최적화 반복문(loop)을 만듭니다.
  • 적절한 교차 검증(cross-validation)으로 검증 집합(validation set)에 과대적합되지 않는 하이퍼파라미터 튜닝 전략을 설계합니다.

문제

그래디언트 부스팅 모델에는 학습률(learning rate), 트리 수(number of trees), 최대 깊이(max depth), 잎(leaf)당 최소 샘플 수, 하위 표본 비율(subsample ratio), 열 표본 비율(column sample ratio)이 있습니다. 하이퍼파라미터가 6개입니다. 각각 합리적인 값이 5개씩 있다면 격자(grid)에는 5^6 = 15,625개 조합이 생깁니다. 각 학습이 10초씩 걸리면 전체를 시도하는 데 43시간이 걸립니다.

그리드 탐색은 가장 먼저 떠오르는 방법이지만 규모가 커지면 최악에 가깝습니다. 랜덤 탐색은 더 적은 계산으로 더 잘 작동합니다. 베이지안 최적화는 이전 평가 결과에서 학습하므로 더 효율적입니다. 어떤 전략을 써야 하는지, 어떤 하이퍼파라미터가 실제로 중요한지 알면 낭비되는 GPU 시간을 며칠 단위로 줄일 수 있습니다.

사전 테스트

2문제 · 이 강의를 시작하기 전에 얼마나 알고 있는지 확인해보세요

1.파라미터(parameter)와 하이퍼파라미터(hyperparameter)의 차이는 무엇인가요?

2.하이퍼파라미터 4개가 있고 각각 값이 5개라면 그리드 탐색(grid search)에는 몇 번의 평가가 필요한가요?

0/2 답변 완료

개념

파라미터와 하이퍼파라미터

파라미터(parameter)는 학습 중에 배워지는 값입니다. 예를 들어 가중치(weight), 편향(bias), 분할 임곗값(split threshold)이 있습니다. 하이퍼파라미터(hyperparameter)는 학습 전에 정하며, 학습이 어떻게 진행될지 제어합니다.

하이퍼파라미터제어하는 것일반적인 범위
학습률(learning rate)업데이트당 보폭(step size)0.001 ~ 1.0
트리/에폭(epoch) 수얼마나 오래 학습할지10 ~ 10,000
최대 깊이(max depth)모델 복잡도1 ~ 30
정규화(regularization, lambda)과대적합 방지0.0001 ~ 100
배치 크기(batch size)기울기(gradient) 추정 잡음16 ~ 512
드롭아웃 비율(dropout rate)제거할 뉴런(neuron) 비율0.0 ~ 0.5

그리드 탐색은 지정된 값의 모든 조합을 평가합니다. 완전탐색이라 이해하기 쉽지만, 하이퍼파라미터 수가 늘어날수록 비용이 지수적으로 증가합니다.

하이퍼파라미터 2개의 격자:

  learning_rate: [0.01, 0.1, 1.0]
  max_depth:     [3, 5, 7]

  평가 횟수: 3 x 3 = 9개 조합

  (0.01, 3)  (0.01, 5)  (0.01, 7)
  (0.1,  3)  (0.1,  5)  (0.1,  7)
  (1.0,  3)  (1.0,  5)  (1.0,  7)

그리드 탐색에는 근본적인 문제가 있습니다. 어떤 하이퍼파라미터 하나만 중요하고 다른 하나가 중요하지 않다면 대부분의 평가는 낭비됩니다. 9번 평가했지만 중요한 파라미터의 고유 값은 3개만 보게 됩니다.

랜덤 탐색은 격자가 아니라 분포에서 하이퍼파라미터를 추출(sampling)합니다. 같은 9번의 예산으로 각 하이퍼파라미터의 고유 값을 9개씩 볼 수 있습니다.

flowchart LR
    subgraph Grid Search
        G1[고유 학습률 3개]
        G2[고유 최대 깊이 3개]
        G3[총 평가 9회]
    end

    subgraph Random Search
        R1[고유 학습률 9개]
        R2[고유 최대 깊이 9개]
        R3[총 평가 9회]
    end

Bergstra & Bengio(2012)가 보인 것처럼 랜덤 탐색이 격자 탐색보다 나은 이유는 다음과 같습니다.

  • 대부분의 하이퍼파라미터는 낮은 유효 차원(effective dimensionality)을 가집니다. 주어진 문제에서 6개 중 1~2개만 중요한 경우가 많습니다.
  • 그리드 탐색은 중요하지 않은 차원에 평가를 낭비합니다.
  • 랜덤 탐색은 같은 예산으로 중요한 차원을 더 촘촘히 덮습니다.
  • 탐색 공간 안에 최적점이 있다면, 무작위 60회 시도에서 최적점의 상위 5% 근처를 찾을 확률이 약 95%입니다.

베이지안 최적화(Bayesian Optimization)

랜덤 탐색은 결과를 무시합니다. 높은 학습률이 발산을 일으키는지, 깊이 3이 깊이 10보다 계속 좋은지 학습하지 않습니다. 베이지안 최적화는 과거 평가를 사용해 다음에 어디를 볼지 결정합니다.

flowchart TD
    A[탐색 공간 정의] --> B[초기 무작위 지점 평가]
    B --> C[결과에 대리 모델 학습]
    C --> D[획득 함수로 다음 지점 선택]
    D --> E[해당 지점에서 모델 평가]
    E --> F{예산 소진?}
    F -->|아니오| C
    F -->|예| G[찾은 최선의 하이퍼파라미터 반환]

핵심 구성요소는 두 가지입니다.

대리 모델(Surrogate Model): 비싼 목적 함수(objective function)를 근사하는, 평가 비용이 낮은 모델입니다. 보통 가우시안 과정(Gaussian process)을 사용합니다. 탐색 공간의 임의 지점에서 예측값과 불확실성 추정치를 함께 제공합니다.

획득 함수(Acquisition Function): 다음에 평가할 지점을 정합니다. 알려진 좋은 지점 근처를 탐색하는 활용(exploitation)과, 불확실성이 높은 영역을 보는 탐험(exploration) 사이의 균형을 잡습니다.

  • 기대 개선(Expected Improvement; EI): 현재 최선보다 얼마나 개선될 것으로 기대되는가?
  • 상한 신뢰 구간(Upper Confidence Bound; UCB): 예측값에 불확실성의 배수를 더한 값입니다. UCB가 높으면 유망하거나 아직 잘 모르는 지점입니다.
  • 개선 확률(Probability of Improvement; PI): 이 지점이 현재 최선을 이길 확률은 얼마인가?

베이지안 최적화는 보통 랜덤 탐색보다 2~5배 적은 평가로 더 좋은 하이퍼파라미터를 찾습니다. 대리 모델을 맞추는 부가 비용(overhead)은 실제 모델 학습 비용에 비하면 작습니다.

조기 종료(Early Stopping)

모든 학습 실행(run)을 끝까지 돌릴 필요는 없습니다. 어떤 설정이 10 에폭(epoch) 후에도 명백히 나쁘다면 멈추고 다음 설정으로 넘어갑니다. 하이퍼파라미터 탐색 맥락에서의 조기 종료입니다.

전략은 다음과 같습니다.

  • 인내(patience) 기반: 검증 손실(validation loss)이 N 에폭 연속 개선되지 않으면 중지합니다.
  • 중앙값 가지치기(Median pruning): 같은 단계(step)에서 완료된 시도(trial)의 중앙값(median)보다 중간 결과가 나쁘면 중지합니다.
  • 하이퍼밴드(Hyperband): 많은 설정에 작은 예산을 주고, 좋은 설정만 점점 더 큰 예산으로 올립니다.

하이퍼밴드는 특히 효과적입니다. 예를 들어 81개 설정을 1 에폭씩 돌리고, 상위 1/3만 남겨 3 에폭을 주고, 다시 상위 1/3만 남기는 방식입니다. 모든 설정을 끝까지 평가하는 것보다 10~50배 빠르게 좋은 설정을 찾을 수 있습니다.

학습률 스케줄러(Learning Rate Scheduler)

학습률은 거의 항상 가장 중요한 하이퍼파라미터입니다. 고정값으로 두기보다 스케줄러로 학습 중에 조정하는 경우가 많습니다.

스케줄러공식언제 쓰는가
단계적 감쇠(step decay)N 에폭마다 0.1배전통적인 CNN 학습
코사인 어닐링(cosine annealing)lr * 0.5 * (1 + cos(pi * t / T))현대적인 기본 선택
워밍업 후 감쇠(warmup + decay)선형 증가 후 코사인 감쇠트랜스포머(Transformer)
단일 주기(one-cycle)한 주기 동안 증가 후 감소빠른 수렴
정체 시 감소(reduce on plateau)지표(metric)가 정체되면 비율만큼 감소안전한 기본값

하이퍼파라미터 중요도(Hyperparameter Importance)

모든 하이퍼파라미터가 똑같이 중요하지는 않습니다. 랜덤 포레스트(Probst et al., 2019)와 그래디언트 부스팅 연구는 일관된 패턴을 보입니다.

높은 중요도:

  • 학습률: 항상 먼저 튜닝합니다.
  • 추정기(estimator) 또는 에폭 수: 직접 튜닝하기보다 조기 종료를 사용합니다.
  • 정규화 강도

중간 중요도:

  • 최대 깊이 / 층(layer) 수
  • 잎당 최소 샘플 수(min samples per leaf) / 가중치 감쇠(weight decay)
  • 하위 표본 비율(subsample ratio)

낮은 중요도:

  • 최대 특성 수(max features): 랜덤 포레스트에서
  • 특정 활성화 함수(activation function) 선택
  • 배치 크기: 합리적인 범위 안에서는

중요한 항목부터 튜닝하고 나머지는 기본값으로 둡니다.

실용 전략

flowchart TD
    A[기본값으로 시작] --> B[거친 랜덤 탐색: 20~50회 시도]
    B --> C[중요한 하이퍼파라미터 식별]
    C --> D[좁힌 공간에서 정밀 랜덤 또는 베이지안 탐색: 50~100회 시도]
    D --> E[최선의 하이퍼파라미터로 최종 모델]
    E --> F[전체 학습 데이터로 재학습]

구체적인 흐름은 다음과 같습니다.

  1. 라이브러리 기본값으로 시작합니다. 경험 많은 실무자들이 고른 값이며 80% 지점까지 가는 경우가 많습니다.
  2. 거친 랜덤 탐색을 합니다. 넓은 범위에서 20~50회 시도합니다. 조기 종료로 나쁜 실행을 빠르게 중지합니다.
  3. 결과를 분석합니다. 어떤 하이퍼파라미터가 성능과 상관이 있나요? 탐색 공간을 좁힙니다.
  4. 정밀 탐색을 합니다. 좁힌 공간에서 베이지안 최적화 또는 집중 랜덤 탐색을 50~100회 실행합니다.
  5. 찾은 최선의 하이퍼파라미터로 전체 학습 데이터에서 다시 학습합니다.

교차 검증(Cross-Validation) 통합

단일 검증 분할(validation split)에서 하이퍼파라미터를 튜닝하는 것은 위험합니다. 최선의 하이퍼파라미터가 특정 검증 폴드(fold)에만 과대적합된 것일 수 있습니다. 중첩 교차 검증(nested cross-validation)은 두 개의 반복문(loop)을 사용해 이 문제를 줄입니다.

  • 외부 반복문(평가용): 데이터를 학습+검증(train+val)과 테스트(test)로 나누고 편향이 적은 성능을 보고합니다.
  • 내부 반복문(튜닝용): 학습+검증 데이터를 학습과 검증으로 나누고 최선의 하이퍼파라미터를 찾습니다.
flowchart TD
    D[전체 데이터셋] --> O1[외부 폴드 1: 테스트]
    D --> O2[외부 폴드 2: 테스트]
    D --> O3[외부 폴드 3: 테스트]
    D --> O4[외부 폴드 4: 테스트]
    D --> O5[외부 폴드 5: 테스트]

    O1 --> I1[남은 데이터에서 내부 5-폴드 CV]
    I1 --> T1[폴드 1의 최선 하이퍼파라미터]
    T1 --> E1[외부 테스트 폴드 1 평가]

    O2 --> I2[남은 데이터에서 내부 5-폴드 CV]
    I2 --> T2[폴드 2의 최선 하이퍼파라미터]
    T2 --> E2[외부 테스트 폴드 2 평가]

각 외부 폴드는 독립적으로 자기 최선의 하이퍼파라미터를 찾습니다. 외부 점수(score)는 일반화 성능에 대한 편향이 적은 추정입니다.

sklearn에서는 다음처럼 작성할 수 있습니다.

from sklearn.model_selection import cross_val_score, GridSearchCV
from sklearn.ensemble import GradientBoostingRegressor

inner_cv = GridSearchCV(
    GradientBoostingRegressor(),
    param_grid={
        "learning_rate": [0.01, 0.05, 0.1],
        "max_depth": [2, 3, 5],
        "n_estimators": [50, 100, 200],
    },
    cv=5,
    scoring="neg_mean_squared_error",
)

outer_scores = cross_val_score(
    inner_cv, X, y, cv=5, scoring="neg_mean_squared_error"
)

print(f"중첩 CV MSE: {-outer_scores.mean():.4f} +/- {outer_scores.std():.4f}")

이 방법은 비쌉니다. 5개 외부 폴드, 5개 내부 폴드, 27개 격자 지점이면 모델을 675번 맞춥니다. 하지만 논문이나 중요한 의사결정에서 최종 성능을 보고할 때는 신뢰할 만한 추정치를 줍니다.

실전 팁

학습률부터 시작합니다. 기울기 기반 방법에서는 거의 항상 가장 중요합니다. 학습률이 나쁘면 다른 모든 하이퍼파라미터가 무의미해집니다. 다른 값은 기본값으로 두고 학습률부터 훑습니다.

학습률과 정규화에는 로그 균등 분포(log-uniform)를 씁니다. 0.001과 0.01의 차이는 0.1과 1.0의 차이만큼 중요합니다. 선형 탐색은 큰 값 쪽에 예산을 낭비합니다.

n_estimators를 튜닝하기보다 조기 종료를 씁니다. 부스팅과 신경망에서는 n_estimators나 에폭 수를 높게 잡고 조기 종료가 멈추게 합니다. 이렇게 하면 탐색해야 할 하이퍼파라미터 하나가 줄어듭니다.

예산 배분을 의식합니다. 튜닝 예산의 60%는 가장 중요한 상위 2개 하이퍼파라미터에 씁니다. 나머지 40%를 그 외 항목에 씁니다. 상위 2개가 성능 변동의 대부분을 설명하는 경우가 많습니다.

규모(scale)가 중요합니다. 배치 크기를 로그 척도(log scale)로 탐색하지 않습니다. 16, 32, 64처럼 적당한 후보면 충분합니다. 학습률은 항상 로그 척도로 탐색합니다. 하이퍼파라미터가 모델에 영향을 주는 방식에 맞게 탐색 분포를 고릅니다.

모델 유형핵심 하이퍼파라미터권장 탐색예산
랜덤 포레스트(Random Forest)n_estimators, max_depth, min_samples_leaf랜덤 탐색 50회 시도낮음
그래디언트 부스팅(Gradient Boosting)learning_rate, n_estimators, max_depth베이지안 100회 시도 + 조기 종료중간
신경망(Neural Network)learning_rate, weight_decay, batch_size베이지안 또는 랜덤 100회 이상 시도높음
서포트 벡터 머신(SVM)C, gamma(RBF 커널)로그 척도 격자 25~50회 시도낮음
라쏘/릿지(Lasso/Ridge)alpha로그 척도 1차원 탐색 20회 시도매우 낮음
XGBoostlearning_rate, max_depth, subsample, colsample베이지안 100~200회 시도 + 조기 종료중간

확신이 없다면: 하이퍼파라미터 수의 2배 이상을 최소 시도 수로 두고 랜덤 탐색을 시작합니다. 예를 들어 하이퍼파라미터가 6개면 최소 12회입니다. 50회 랜덤 탐색이 정교하게 설계한 그리드 탐색을 이기는 경우가 자주 있습니다.

만들어 보기

Step 1: 그리드 탐색 처음부터 구현하기

code/tuning.py는 그리드 탐색, 랜덤 탐색, 단순한 베이지안 최적화기(optimizer)를 처음부터 구현합니다.

def grid_search(model_fn, param_grid, X_train, y_train, X_val, y_val):
    keys = list(param_grid.keys())
    values = list(param_grid.values())
    best_score = -float("inf")
    best_params = None
    n_evals = 0

    for combo in itertools.product(*values):
        params = dict(zip(keys, combo))
        model = model_fn(**params)
        model.fit(X_train, y_train)
        score = evaluate(model, X_val, y_val)
        n_evals += 1

        if score > best_score:
            best_score = score
            best_params = params

    return best_params, best_score, n_evals

Step 2: 랜덤 탐색 처음부터 구현하기

def random_search(model_fn, param_distributions, X_train, y_train,
                  X_val, y_val, n_iter=50, seed=42):
    rng = np.random.RandomState(seed)
    best_score = -float("inf")
    best_params = None

    for _ in range(n_iter):
        params = {k: sample(v, rng) for k, v in param_distributions.items()}
        model = model_fn(**params)
        model.fit(X_train, y_train)
        score = evaluate(model, X_val, y_val)

        if score > best_score:
            best_score = score
            best_params = params

    return best_params, best_score, n_iter

Step 3: 베이지안 최적화 단순 구현

핵심 아이디어는 관측된 (하이퍼파라미터, 점수) 쌍에 가우시안 과정을 맞춘 뒤, 획득 함수로 다음 탐색 지점을 정하는 것입니다.

class SimpleBayesianOptimizer:
    def __init__(self, search_space, n_initial=5):
        self.search_space = search_space
        self.n_initial = n_initial
        self.X_observed = []
        self.y_observed = []

    def _kernel(self, x1, x2, length_scale=1.0):
        dists = np.sum((x1[:, None, :] - x2[None, :, :]) ** 2, axis=2)
        return np.exp(-0.5 * dists / length_scale ** 2)

    def _fit_gp(self, X_new):
        X_obs = np.array(self.X_observed)
        y_obs = np.array(self.y_observed)
        y_mean = y_obs.mean()
        y_centered = y_obs - y_mean

        K = self._kernel(X_obs, X_obs) + 1e-4 * np.eye(len(X_obs))
        K_star = self._kernel(X_new, X_obs)

        L = np.linalg.cholesky(K)
        alpha = np.linalg.solve(L.T, np.linalg.solve(L, y_centered))
        mu = K_star @ alpha + y_mean

        v = np.linalg.solve(L, K_star.T)
        var = 1.0 - np.sum(v ** 2, axis=0)
        var = np.maximum(var, 1e-6)

        return mu, var

    def _expected_improvement(self, mu, var, best_y):
        sigma = np.sqrt(var)
        z = (mu - best_y) / (sigma + 1e-10)
        ei = sigma * (z * norm_cdf(z) + norm_pdf(z))
        return ei

    def suggest(self):
        if len(self.X_observed) < self.n_initial:
            return sample_random(self.search_space)

        candidates = [sample_random(self.search_space) for _ in range(500)]
        X_cand = np.array([to_vector(c) for c in candidates])
        mu, var = self._fit_gp(X_cand)
        ei = self._expected_improvement(mu, var, max(self.y_observed))
        return candidates[np.argmax(ei)]

    def observe(self, params, score):
        self.X_observed.append(to_vector(params))
        self.y_observed.append(score)

가우시안 과정(GP) 대리 모델은 각 후보 지점에서 예측 점수(mu)와 불확실성(var)을 제공합니다. 기대 개선(Expected Improvement)은 두 값을 함께 고려합니다. 예측 점수가 높거나 불확실성이 높은 지점을 선호합니다. 초반에는 불확실성이 높은 지점이 많아 탐험(exploration) 비중이 크고, 후반에는 유망한 영역에 집중합니다.

Step 4: 모든 방법 비교하기

세 방법을 같은 합성 목적 함수(synthetic objective)에서 실행해 비교합니다. 이 비교는 모델 학습이 아니라 직접 목적 함수를 호출하는 단순 감싸기(wrapper)를 사용하므로, 위의 모델 기반 구현과 API가 조금 다릅니다.

def synthetic_objective(params):
    lr = params["learning_rate"]
    depth = params["max_depth"]
    return -(np.log10(lr) + 2) ** 2 - (depth - 4) ** 2 + 10

param_grid = {
    "learning_rate": [0.001, 0.01, 0.1, 1.0],
    "max_depth": [2, 3, 4, 5, 6, 7, 8],
}

param_dist = {
    "learning_rate": ("log_float", 0.001, 1.0),
    "max_depth": ("int", 2, 8),
}

grid_best = None
grid_score = -float("inf")
grid_history = []
for combo in itertools.product(*param_grid.values()):
    params = dict(zip(param_grid.keys(), combo))
    score = synthetic_objective(params)
    grid_history.append((params, score))
    if score > grid_score:
        grid_score = score
        grid_best = params

rand_best = None
rand_score = -float("inf")
rand_history = []
rng = np.random.RandomState(42)
for _ in range(28):
    params = {k: sample(v, rng) for k, v in param_dist.items()}
    score = synthetic_objective(params)
    rand_history.append((params, score))
    if score > rand_score:
        rand_score = score
        rand_best = params

optimizer = SimpleBayesianOptimizer(param_dist, n_initial=5)
bayes_history = []
for _ in range(28):
    params = optimizer.suggest()
    score = synthetic_objective(params)
    optimizer.observe(params, score)
    bayes_history.append((params, score))
bayes_score = max(s for _, s in bayes_history)

print(f"{'방법':<20} {'최고 점수':>12} {'평가 횟수':>12}")
print("-" * 50)
print(f"{'그리드 탐색':<20} {grid_score:>12.4f} {len(grid_history):>12}")
print(f"{'랜덤 탐색':<20} {rand_score:>12.4f} {len(rand_history):>12}")
print(f"{'베이지안 최적화':<20} {bayes_score:>12.4f} {len(bayes_history):>12}")

같은 예산이라면 베이지안 최적화는 명백히 나쁜 영역에 평가를 낭비하지 않기 때문에 보통 가장 빠르게 좋은 점수를 찾습니다. 랜덤 탐색은 그리드 탐색보다 더 넓게 탐색합니다. 그리드 탐색은 하이퍼파라미터가 매우 적고 완전탐색이 가능한 경우에만 유리합니다.

사용하기

실전에서의 Optuna

진지한 하이퍼파라미터 튜닝에는 Optuna가 권장됩니다. 가지치기(pruning), 분산 탐색, 시각화를 기본으로 지원합니다.

import optuna

def objective(trial):
    lr = trial.suggest_float("learning_rate", 1e-4, 1e-1, log=True)
    n_est = trial.suggest_int("n_estimators", 50, 500)
    max_depth = trial.suggest_int("max_depth", 2, 10)

    model = GradientBoostingRegressor(
        learning_rate=lr,
        n_estimators=n_est,
        max_depth=max_depth,
    )
    model.fit(X_train, y_train)
    return mean_squared_error(y_val, model.predict(X_val))

study = optuna.create_study(direction="minimize")
study.optimize(objective, n_trials=100)

print(f"최선의 하이퍼파라미터: {study.best_params}")
print(f"최선의 MSE: {study.best_value:.4f}")

주요 Optuna 기능은 다음과 같습니다.

  • 로그 척도 탐색이 맞는 파라미터에는 suggest_float(..., log=True)를 사용합니다.
  • 정수 파라미터에는 suggest_int를 사용합니다.
  • 이산 선택지에는 suggest_categorical을 사용합니다.
  • 나쁜 시도를 일찍 멈추는 내장 MedianPruner가 있습니다.
  • 분석에는 study.trials_dataframe()을 사용할 수 있습니다.

가지치기(Pruning)를 사용하는 Optuna

가지치기는 유망하지 않은 시도를 일찍 멈춰 많은 계산을 아낍니다.

import optuna
from sklearn.model_selection import cross_val_score

def objective(trial):
    params = {
        "learning_rate": trial.suggest_float("lr", 1e-4, 0.5, log=True),
        "max_depth": trial.suggest_int("max_depth", 2, 10),
        "n_estimators": trial.suggest_int("n_estimators", 50, 500),
        "subsample": trial.suggest_float("subsample", 0.5, 1.0),
    }

    model = GradientBoostingRegressor(**params)
    scores = cross_val_score(model, X_train, y_train, cv=3,
                             scoring="neg_mean_squared_error")
    mean_score = -scores.mean()

    trial.report(mean_score, step=0)
    if trial.should_prune():
        raise optuna.TrialPruned()

    return mean_score

pruner = optuna.pruners.MedianPruner(n_startup_trials=10, n_warmup_steps=5)
study = optuna.create_study(direction="minimize", pruner=pruner)
study.optimize(objective, n_trials=200)

MedianPruner는 어떤 시도의 중간값이 같은 단계에서 완료된 시도들의 중앙값보다 나쁘면 중단합니다. 가지치기를 쓰려면 trial.report()로 중간 지표를 보고하고, trial.should_prune()으로 중지 여부를 확인해야 합니다. n_startup_trials=10은 가지치기가 시작되기 전 최소 10개 시도가 끝까지 완료되도록 합니다. 보통 전체 계산의 40~60%를 절약합니다.

sklearn의 내장 튜너

빠른 실험에는 sklearn의 GridSearchCV, RandomizedSearchCV, HalvingRandomSearchCV를 사용할 수 있습니다.

from sklearn.model_selection import RandomizedSearchCV
from scipy.stats import loguniform, randint

param_dist = {
    "learning_rate": loguniform(1e-4, 0.5),
    "max_depth": randint(2, 10),
    "n_estimators": randint(50, 500),
}

search = RandomizedSearchCV(
    GradientBoostingRegressor(),
    param_dist,
    n_iter=100,
    cv=5,
    scoring="neg_mean_squared_error",
    random_state=42,
    n_jobs=-1,
)
search.fit(X_train, y_train)
print(f"최선의 하이퍼파라미터: {search.best_params_}")
print(f"최선의 CV MSE: {-search.best_score_:.4f}")

학습률과 정규화에는 scipy의 loguniform을 사용합니다. 정수 하이퍼파라미터에는 randint를 사용합니다. n_jobs=-1은 모든 CPU 코어를 사용해 병렬화합니다.

하이퍼파라미터 튜닝의 흔한 실수

전처리를 통한 데이터 누수(data leakage). 교차 검증 전에 전체 데이터에 스케일러(scaler)를 적합(fit)시키면 검증 폴드의 정보가 학습에 새어 들어갑니다. 전처리는 항상 Pipeline 안에 넣어 학습 폴드에서만 적합되게 합니다.

검증 집합에 과대적합. 수천 번 시도를 돌리면 사실상 검증 집합에 맞춰 학습하는 것입니다. 최종 성능 추정에는 중첩 교차 검증을 쓰거나, 튜닝 중 절대 보지 않는 별도 테스트 집합을 둡니다.

너무 좁은 범위 탐색. 최선의 값이 탐색 공간의 경계에 있다면 충분히 넓게 탐색하지 않은 것입니다. 최적값은 범위 밖에 있을 수 있습니다. 최선의 파라미터가 가장자리(edge)에 있는지 항상 확인합니다.

상호작용 무시. 부스팅에서 학습률과 추정기 수는 강하게 상호작용합니다. 낮은 학습률은 더 많은 추정기를 필요로 합니다. 따로 튜닝하면 결과가 나빠질 수 있습니다.

반복 모델에 조기 종료를 쓰지 않음. 그래디언트 부스팅과 신경망에서는 n_estimators나 에폭 수를 크게 잡고 조기 종료를 사용합니다. 반복 횟수를 일반 하이퍼파라미터처럼 튜닝하는 것보다 낫습니다.

산출물 만들기

이 강의의 최종 산출물은 outputs/prompt-tuning-strategy.md입니다.

  • 모델 유형, 데이터 크기, 예산, 현재 성능, 최적화 지표를 입력해 튜닝 전략을 추천받습니다.
  • 산출물은 탐색 전략, 탐색 공간, 시도 횟수, 교차 검증 폴드, 예상 실행 시간(runtime), 조기 종료 사용 여부를 포함해야 합니다.
  • 학습자는 이 프롬프트를 사용해 자신의 모델 상황에 맞는 튜닝 계획을 만들고, 검증/테스트 집합을 잘못 쓰지 않는지 점검합니다.

연습문제

  1. 같은 총 예산으로 그리드 탐색과 랜덤 탐색을 실행합니다. 예를 들어 50회 평가로 비교합니다. 난수 시드(seed)를 바꿔 10번 실험하고, 랜덤 탐색이 몇 번 이기나요?

  2. 하이퍼밴드를 처음부터 구현합니다. 81개 설정을 1 에폭씩 학습하고, 각 회차(round)에서 상위 1/3만 남기며 예산(budget)을 3배로 늘립니다. 81개 설정을 전체 예산으로 끝까지 돌리는 것과 총 계산량을 비교합니다.

  3. 11강의 그래디언트 부스팅 구현에 학습률 스케줄러(코사인 어닐링)를 추가합니다. 고정 학습률과 비교해 도움이 되나요?

  4. Optuna로 실제 데이터셋, 예를 들어 sklearn의 유방암 데이터셋(breast cancer dataset)에서 RandomForestClassifier를 튜닝합니다. optuna.visualization.plot_param_importances(study)로 어떤 하이퍼파라미터가 가장 중요한지 봅니다. 이 강의의 중요도 순위와 맞나요?

  5. 간단한 획득 함수(Expected Improvement)를 구현하고 탐험과 활용을 보여줍니다. 대리 모델의 평균과 불확실성을 그래프로 그리고, EI가 다음 평가 지점을 어디로 고르는지 확인합니다.

핵심 용어

용어흔한 설명실제 의미
하이퍼파라미터(Hyperparameter)"직접 고르는 설정"데이터에서 학습되는 값이 아니라 학습 과정을 제어하기 위해 학습 전에 정하는 값
그리드 탐색(Grid Search)"모든 조합 시도"지정한 파라미터 격자를 완전탐색하는 방법. 비용이 지수적으로 증가함
랜덤 탐색(Random Search)"무작위로 뽑기"분포에서 하이퍼파라미터를 추출해 격자 탐색보다 중요한 차원을 더 잘 덮는 방법
베이지안 최적화(Bayesian Optimization)"똑똑한 탐색"목적 함수의 대리 모델을 사용해 다음 평가 지점을 고르고 탐험과 활용을 균형 있게 수행
대리 모델(Surrogate Model)"값싼 근사"비싼 목적 함수를 관측 결과로 근사하는 모델. 보통 가우시안 과정(Gaussian process) 사용
획득 함수(Acquisition Function)"다음에 볼 곳"기대 개선과 불확실성을 균형 있게 점수화하는 함수. EI와 UCB가 대표적
조기 종료(Early stopping)"낭비 중지"검증 성능이 개선되지 않을 때 학습을 일찍 멈추는 방식
하이퍼밴드(Hyperband)"설정들의 토너먼트"많은 설정을 작은 예산으로 시작해 좋은 설정만 남기고 예산을 늘리는 적응형 자원 배분
학습률 스케줄러(Learning rate scheduler)"학습 중 학습률 변경"더 나은 수렴을 위해 학습 과정에서 학습률을 조정하는 함수

더 읽을거리

실습 코드

이 강의의 실습 코드 1개

tuning
Code

산출물

이 강의에서 생성된 프롬프트, 스킬, 코드 산출물 1개

prompt-tuning-strategy

Recommend a hyperparameter tuning strategy based on model type, data size, and compute budget

Prompt

확인 문제

3문제 · 모두 맞추면 완료 표시가 가능합니다

1.같은 평가 예산에서 랜덤 탐색(random search)이 그리드 탐색(grid search)보다 더 나은 경우가 많은 이유는 무엇인가요?

2.베이지안 최적화에서 획득 함수(acquisition function)는 무엇의 균형을 잡나요?

3.테스트 집합(test set)으로 하이퍼파라미터를 튜닝하고 가장 좋은 테스트 성능을 보고했습니다. 이 접근의 문제는 무엇인가요?

0/3 답변 완료