과적합과 데이터 분할

src/content/documents/machine-learning/overfitting-and-train-valid-test-split.json

모의고사는 100점, 실전은 50점

과적합(Overfitting)은 “너무 잘 맞춘다(Fit too much)”는 뜻 그대로, 모델이 훈련 데이터의 진짜 패턴뿐 아니라 노이즈나 그 데이터에만 있는 우연한 특징까지 외워버린 상태다. 모의고사 문제를 통째로 외워 100점을 받았지만, 문제가 바뀐 실전 수능에서는 50점을 받는 것과 같다.

구분

정상적인 학습

Overfitting

학습 대상

데이터의 본질적 패턴

데이터의 노이즈나 특수 상황까지 반영

모델 복잡도

적절 — 규칙적인 구조

과도 — 지나치게 복잡

검증 성능

훈련·검증 성능이 안정적으로 유지됨

훈련 성능만 높고 검증 성능은 낮거나 불안정함

머신러닝 알고리즘은 기본적으로 훈련 데이터에 대한 성능을 계속 끌어올리는 방향으로 학습하기 때문에, 과적합은 예외적인 사고가 아니라 항상 관리해야 하는 기본 위험이다. 나무가 깊어질수록, 파라미터가 많아질수록, 학습 데이터가 적고 노이즈가 많을수록 더 쉽게 발생한다.

Train·Validation·Test, 왜 세 조각으로 나누나

과적합 여부는 훈련에 쓰지 않은 데이터로 성능을 확인해야 알 수 있다. 그래서 데이터를 보통 세 조각으로 나눈다.

조각

역할

손대는 시점

Train (훈련)

모델이 파라미터를 학습하는 데 쓰는 데이터

학습 과정 내내

Validation (검증)

하이퍼파라미터 튜닝·모델 선택에 쓰는 데이터

학습 중 반복적으로 확인

Test (평가)

최종 모델의 일반화 성능을 딱 한 번 확인하는 데이터

모든 튜닝이 끝난 뒤 마지막에 한 번

핵심은 Test 데이터를 튜닝 과정에 절대 섞지 않는 것이다. Test 성능을 보고 다시 하이퍼파라미터를 고치기 시작하면, Test는 더 이상 “한 번도 보지 못한 데이터”가 아니게 되고 이를 데이터 누수(Data Leakage)라고 부른다. 세 조각 각각에서 R²·RMSE·Accuracy 같은 지표를 구해 비교하는 방식을 Single-Split Evaluation이라고 한다.

코드로 과적합 직접 확인하기

노이즈가 섞인 분류 데이터를 만들고, 나무 깊이를 다르게 해서 Train·Validation·Test 정확도를 비교해본다.

from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import accuracy_score

X, y = make_classification(
    n_samples=150, n_features=20, n_informative=3, n_redundant=2,
    flip_y=0.15, random_state=42,
)
X_train, X_temp, y_train, y_temp = train_test_split(
    X, y, test_size=0.4, random_state=42
)
X_valid, X_test, y_valid, y_test = train_test_split(
    X_temp, y_temp, test_size=0.5, random_state=42
)

for depth in [3, None]:
    model = DecisionTreeClassifier(max_depth=depth, random_state=42).fit(X_train, y_train)
    train_acc = accuracy_score(y_train, model.predict(X_train))
    valid_acc = accuracy_score(y_valid, model.predict(X_valid))
    test_acc = accuracy_score(y_test, model.predict(X_test))
    print(f"max_depth={depth}: train={train_acc:.3f}, valid={valid_acc:.3f}, test={test_acc:.3f}")
max_depth=3: train=0.967, valid=0.767, test=0.833
max_depth=None: train=1.000, valid=0.733, test=0.800

깊이 제한이 없는 나무( max_depth=None)는 훈련 정확도가 100%까지 올라갔지만, 검증·평가 정확도는 오히려 깊이 3인 나무보다 낮다. 훈련 점수만 보고 “깊을수록 좋다”고 판단했다면 놓쳤을 신호를, Validation·Test 비교가 잡아낸 것이다.

과적합을 줄이는 방법

  • 모델 복잡도 제한 — 나무 최대 깊이 제한, 정규화(LASSO·Ridge) 패널티 강화 등.

  • 데이터 늘리기 — 가능하다면 학습 데이터를 더 확보하거나 데이터 증강을 활용한다.

  • 교차검증(Cross-Validation) — 데이터를 한 번만 나누지 않고 여러 번 나눠 평균 성능을 본다.

  • 조기 종료(Early Stopping) — 검증 성능이 더 이상 좋아지지 않는 시점에 학습을 멈춘다(부스팅·신경망에서 자주 사용).

정리

과적합은 모델이 데이터를 “이해”하지 않고 “암기”했을 때 생긴다. 훈련 성능만으로는 이를 알아챌 수 없기 때문에 Train·Validation·Test로 데이터를 나눠 한 번도 보지 않은 데이터에서의 성능을 반드시 함께 확인해야 한다. Test 데이터는 모든 튜닝이 끝난 뒤 마지막에 딱 한 번만 확인하는 것이 데이터 누수를 막는 핵심 규칙이다.

참고 자료

댓글 0

댓글을 불러오는 중…