미리보기 · 검수 전목차 3 / 6

XGBoost의 정규화 — 과적합 제어

ML · L2

부스팅이 훈련 데이터를 암기하지 않도록, 목적함수에 벌점을 넣어 브레이크를 겁니다. 목표 — 규제 전후의 train/valid 격차를 코드로 직접 확인하기.

개념

1. 부스팅은 과적합으로 달려간다

부스팅은 앞 모델이 틀린 부분을 다음 나무가 계속 보정하는 방식입니다. 그래서 놔두면 훈련 데이터의 노이즈까지 외워버립니다 — 족보를 통째로 암기한 학생처럼, 새 문제(검증 데이터)에서 무너집니다. 라운드가 늘수록 훈련 오차는 계속 줄지만 일반화 오차는 U자 곡선을 그리며 다시 나빠지기 때문에, XGBoost는 학습을 늦추는 장치(규제·서브샘플링·조기 종료)를 목적함수 수준에서 내장했습니다.

2. 목적함수에 벌점을 더한다

XGBoost는 '얼마나 틀렸나(손실)'에 '나무가 얼마나 복잡한가(벌점)'를 더해 최소화합니다. 벌점 항은 Ω(f) = γT + ½λ‖w‖² 로 구성됩니다 — T는 리프 개수로 γ가 분기 자체에 과금하고, w는 리프 출력값으로 λ가 L2로 눌러 예측을 보수적으로 유지합니다. 분기 이득이 벌점 γ보다 작으면 그 분기는 채택되지 않으므로, 이득이 벌점보다 클 때만 가지를 치는 사전 가지치기가 됩니다.

함께 보기우측 셀 2에서 규제를 껐다 켜며 train/valid AUC 격차가 좁혀지는 것을 확인해 보세요.

3. 무엇을 조이면 되나

실전에서는 나무를 얕게(max_depth), 잎에 최소 데이터를(min_child_weight), 데이터·피처를 일부만 뽑아(subsample·colsample) 학습을 느리고 신중하게 만듭니다. 규제는 세 층위로 정리됩니다 — ① 구조 규제: max_depth·gamma·min_child_weight, ② 가중치 규제: reg_lambda(L2)·reg_alpha(L1), ③ 확률적 규제: subsample·colsample_bytree. 실무 기본기는 learning_rate를 낮추고 n_estimators를 늘린 뒤 조기 종료를 거는 조합입니다.

Obj = Σᵢ l(yᵢ, ŷᵢ) + Σₖ [ γT + ½λ‖w‖² ]

쉽게 말하면 — 틀린 만큼 + 복잡한 만큼, 둘 다 벌점

l(yᵢ, ŷᵢ)손실 — 예측이 얼마나 틀렸나
γT리프 개수 벌점 — 분기 자체에 과금 (사전 가지치기)
½λ‖w‖²리프 출력값의 L2 — 예측을 보수적으로

핵심 파라미터

max_depth나무 깊이 제한 — 가장 직관적인 브레이크 (3~6부터)
gamma분기 최소 이득 — 이득이 이보다 작으면 가지를 안 침
min_child_weight잎이 가져야 할 최소 데이터 양 — 노이즈 잎 방지
reg_lambda / reg_alpha리프 가중치의 L2 / L1 규제
subsample · colsample_bytree행·열 일부만 샘플링 — 나무마다 다른 시야
learning_rate한 걸음 크기 — 낮출수록 신중, 나무 수는 늘려야

한 줄 정리XGBoost의 규제는 후처리가 아니라 목적함수에 내장된 브레이크다 — γ는 분기에, λ는 잎 값에 과금해 '외우는 학습'을 막는다.

실습 · 코드 학습

sklearnXGBoostLightGBM

완성된 코드를 셀 단위로 읽으며 흐름을 익히세요. 다 이해했다면 아래 문제 풀기로 직접 확인합니다.

이 노트북에서는 고객 이탈 데이터(합성, 8,000행)를 만들어 규제 없는 XGBoost와 규제를 건 XGBoost의 train/valid 격차를 비교합니다.

셀은 위에서 아래로 실행한다고 가정합니다. 다 읽었다면 아래 문제 풀기로 이해를 확인하세요.

In [1] · 이탈 데이터를 만들고 train/valid로 나눕니다.

import xgboost as xgb
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split

X, y = make_classification(n_samples=8000, n_features=20,
                           n_informative=6, weights=[0.8],
                           flip_y=0.06, random_state=42)
X_tr, X_va, y_tr, y_va = train_test_split(
    X, y, test_size=0.25, stratify=y, random_state=42)
X_tr.shape, X_va.shape

출력

((6000, 20), (2000, 20))

In [2] · 규제를 끈 모델 vs 켠 모델 — train/valid AUC 격차 비교.

from sklearn.metrics import roc_auc_score

def auc_gap(model):
    model.fit(X_tr, y_tr)
    tr = roc_auc_score(y_tr, model.predict_proba(X_tr)[:, 1])
    va = roc_auc_score(y_va, model.predict_proba(X_va)[:, 1])
    return round(tr, 3), round(va, 3), round(tr - va, 3)

loose = xgb.XGBClassifier(max_depth=10, n_estimators=400,
                          learning_rate=0.3)
tight = xgb.XGBClassifier(max_depth=4, n_estimators=400,
                          learning_rate=0.1, gamma=1.0,
                          reg_lambda=5.0, subsample=0.8,
                          colsample_bytree=0.8)
print('규제 OFF  train/valid/gap:', auc_gap(loose))
print('규제 ON   train/valid/gap:', auc_gap(tight))

출력

규제 OFF  train/valid/gap: (1.0, 0.871, 0.129)
규제 ON   train/valid/gap: (0.941, 0.902, 0.039)

In [3] · gamma를 키우면 분기가 줄어듭니다 — 나무가 실제로 단순해지는지 확인.

for g in [0, 1, 5, 10]:
    m = xgb.XGBClassifier(max_depth=6, gamma=g,
                          n_estimators=100).fit(X_tr, y_tr)
    leaves = sum(t.count('leaf') for t in
                 m.get_booster().get_dump())
    va = roc_auc_score(y_va, m.predict_proba(X_va)[:, 1])
    print(f'gamma={g:>2}  잎 개수={leaves:>5}  valid AUC={va:.3f}')

출력

gamma= 0  잎 개수= 6104  valid AUC=0.883
gamma= 1  잎 개수= 2311  valid AUC=0.897
gamma= 5  잎 개수=  704  valid AUC=0.901
gamma=10  잎 개수=  312  valid AUC=0.894
빈칸 채우기 1문항 — 이 목차의 핵심 파라미터