Overfitting Underfitting Regularization Dropout Early Stopping2
|

과적합과 과소적합 완전 정복: 일반화, 정규화, 드롭아웃, 조기 종료

과적합과 과소적합의 차이: 과소적합은 모델이 훈련 데이터의 핵심 패턴조차 충분히 학습하지 못한 상태이고, 과적합은 훈련 데이터의 패턴뿐 아니라 우연한 잡음과 예외까지 외워 새로운 데이터에서 성능이 떨어지는 상태입니다.

머신러닝 모델의 목표는 훈련 데이터를 완벽하게 맞히는 것이 아닙니다. 실제 서비스에서 처음 만나는 데이터에도 안정적으로 작동하는 일반화 성능(Generalization Performance)을 확보하는 것이 목표입니다.

훈련 정확도가 99%라도 검증 정확도가 70%라면 좋은 모델이라고 보기 어렵습니다. 반대로 훈련과 검증 정확도가 모두 60%라면 두 성능의 차이는 작지만 모델 자체가 충분히 학습되지 않았을 가능성이 큽니다.

따라서 모델을 평가할 때는 하나의 정확도만 보지 말고 다음 질문을 함께 확인해야 합니다.

  • 훈련 데이터에서 충분히 학습했는가?
  • 훈련과 검증 성능의 차이가 과도하게 크지 않은가?
  • 데이터가 늘어날수록 검증 성능이 개선되는가?
  • 하이퍼파라미터 선택 과정에서 테스트 데이터를 사용하지 않았는가?
  • 운영 환경의 데이터 분포가 학습 데이터와 비슷한가?

이 글에서는 과적합과 과소적합의 개념부터 학습 곡선, 편향-분산 트레이드오프, L1·L2 정규화, 드롭아웃, 조기 종료, 교차검증, 데이터 증강까지 실전에서 필요한 내용을 단계별로 정리합니다.


핵심 요약

상태훈련 성능검증 성능대표 원인우선 대응
과소적합낮음낮음모델이 너무 단순함, 특성 부족, 학습 부족, 정규화 과다모델 표현력·특성·학습 시간 증가
적절한 적합높음높음데이터와 모델 복잡도의 균형최종 테스트 및 운영 검증
과적합매우 높음상대적으로 낮음모델 과다 복잡, 데이터 부족, 누수, 잡음 학습데이터·분할 검토, 복잡도 감소, 정규화
분포 이동높음내부 검증은 높을 수 있음운영 데이터가 학습 데이터와 다름시간·그룹·지역 기반 외부 검증

과적합을 해결하는 방법은 하나가 아닙니다. 데이터 누수가 원인인데 드롭아웃만 추가하거나, 검증 분할이 잘못됐는데 L2 값을 키우는 식으로 접근하면 문제를 가릴 뿐 해결하지 못할 수 있습니다.

진단이 먼저이고 정규화는 그다음입니다.


1. 과적합이란?

1.1 과적합의 정의

과적합(Overfitting)은 모델이 훈련 데이터에 지나치게 맞춰져 새로운 데이터에 대한 예측 성능이 낮아지는 현상입니다.

이를 오차 관점에서 표현하면 다음과 같습니다.

훈련 오차는 매우 낮음
검증·테스트 오차는 상대적으로 높음
훈련과 검증 사이의 일반화 격차가 큼

예를 들어 스팸 메일 분류 모델의 성능이 다음과 같다고 가정해 보겠습니다.

훈련 정확도: 99.4%
검증 정확도: 78.1%
테스트 정확도: 76.8%

이 모델은 훈련 메일에 포함된 표현은 거의 완벽하게 기억했지만, 처음 보는 발신자나 문장 구조에는 충분히 일반화하지 못했을 가능성이 큽니다.

시험에 비유하면 문제의 원리를 이해한 것이 아니라 기출문제의 문장과 정답을 통째로 외운 상태입니다.

1.2 일반화 오차와 일반화 격차

훈련 데이터에서 계산한 오차를 훈련 오차라고 하고, 보지 않은 데이터에서 발생하는 오차를 일반화 오차라고 합니다.

분류 문제에서 간단한 일반화 격차는 다음처럼 계산할 수 있습니다.

일반화 격차 = 훈련 점수 - 검증 점수

다만 격차가 몇 %p 이상이면 무조건 과적합이라는 보편적인 기준은 없습니다.

  • 데이터가 작으면 검증 점수 자체의 변동이 큽니다.
  • 클래스 불균형 문제에서는 정확도보다 F1, PR-AUC, 재현율이 중요할 수 있습니다.
  • 시계열에서는 무작위 분할보다 미래 구간 성능이 중요합니다.
  • 의료·안전 분야에서는 평균 성능보다 특정 집단의 실패율이 중요할 수 있습니다.

따라서 격차는 절대 기준이 아니라 비교와 추세를 위한 진단 신호로 사용해야 합니다.

1.3 과적합이 발생하는 주요 원인

모델 복잡도가 데이터에 비해 지나치게 큼

깊이가 제한되지 않은 결정 트리, 차수가 지나치게 높은 다항 회귀, 매우 큰 신경망은 훈련 샘플의 작은 잡음까지 표현할 수 있습니다.

표현력이 크다는 것은 복잡한 패턴을 배울 수 있다는 뜻이지만, 동시에 우연한 패턴까지 외울 수 있다는 뜻이기도 합니다.

데이터가 적거나 다양성이 부족함

같은 조건에서 촬영한 이미지, 특정 사용자 집단에서만 수집한 로그, 한 계절의 센서 데이터만으로 학습하면 모델은 실제 환경의 변화를 충분히 경험하지 못합니다.

단순히 샘플 개수만 늘리는 것보다 다음과 같은 커버리지가 중요합니다.

  • 서로 다른 사용자와 장비
  • 다양한 계절·시간·지역
  • 정상 사례와 경계 사례
  • 조명·각도·배경 변화
  • 클래스별 충분한 표본

라벨 오류와 잡음

라벨이 잘못된 샘플을 고용량 모델이 반복해서 학습하면 오류까지 암기할 수 있습니다. 훈련 손실은 계속 감소하지만 실제 성능은 악화될 수 있습니다.

학습을 지나치게 오래 진행함

딥러닝 모델은 초기에는 공통 패턴을 배우지만, 이후에는 훈련 데이터에만 존재하는 세부 특성이나 잡음을 맞추기 시작할 수 있습니다.

이때 흔히 나타나는 곡선은 다음과 같습니다.

훈련 손실: 계속 감소
검증 손실: 감소하다가 다시 증가

검증 손실이 최저점을 지난 이후의 학습은 과적합을 강화할 수 있습니다.

데이터 누수

데이터 누수(Data Leakage)는 학습 과정에서 실제 예측 시점에는 알 수 없는 정보가 모델에 들어가는 문제입니다.

대표 사례는 다음과 같습니다.

  • 전체 데이터로 표준화한 뒤 훈련·검증 데이터를 나눔
  • 전체 데이터로 결측치 대체 기준을 계산함
  • 타깃과 직접 연결된 사후 정보를 특성으로 사용함
  • 같은 환자, 사용자, 제품의 데이터가 훈련과 검증에 동시에 들어감
  • 시계열 데이터를 무작위로 섞어 미래 정보가 과거 학습에 포함됨
  • 데이터 증강 전에 분할해 원본과 변형본이 서로 다른 세트에 들어감

누수가 있으면 검증 성능이 비정상적으로 높게 나타나므로, 일반적인 과적합보다 더 위험합니다.


2. 과소적합이란?

2.1 과소적합의 정의

과소적합(Underfitting)은 모델이 데이터의 핵심 구조조차 충분히 학습하지 못한 상태입니다.

훈련 오차가 높음
검증 오차도 높음
훈련과 검증 점수 차이는 작을 수 있음

예를 들어 비선형 관계를 단순한 직선 하나로만 설명하려 하면 훈련 데이터에서도 좋은 성능을 얻기 어렵습니다.

훈련 정확도: 63%
검증 정확도: 61%

두 점수의 차이는 2%p에 불과하지만 이는 일반화가 잘되었다는 뜻이 아닙니다. 모델이 훈련 데이터에서도 충분한 패턴을 찾지 못한 상태일 수 있습니다.

2.2 과소적합이 발생하는 주요 원인

  • 모델이 지나치게 단순함
  • 중요한 특성이 빠져 있음
  • 학습 횟수나 최적화가 부족함
  • 정규화 강도가 지나치게 큼
  • 드롭아웃 비율이 과도함
  • 입력 전처리가 부적절함
  • 학습률이 너무 커서 최적점에 수렴하지 못함
  • 클래스 불균형을 무시해 다수 클래스만 예측함
  • 라벨 자체가 불확실해 학습 가능한 신호가 약함

2.3 과소적합 해결 방법

과소적합이 의심되면 먼저 훈련 성능을 높일 수 있는지 확인해야 합니다.

  1. 모델 복잡도를 조금 높입니다.
  2. 의미 있는 특성을 추가합니다.
  3. 학습 시간을 늘리거나 최적화 설정을 조정합니다.
  4. 과도한 L1·L2 정규화나 드롭아웃을 완화합니다.
  5. 입력값과 라벨의 품질을 검토합니다.
  6. 문제에 맞는 손실 함수와 평가 지표를 사용합니다.

훈련 점수조차 낮은데 데이터 증강이나 더 강한 정규화를 추가하면 오히려 학습이 더 어려워질 수 있습니다.


3. 편향과 분산으로 이해하기

과소적합과 과적합은 흔히 편향-분산 트레이드오프(Bias-Variance Trade-off)로 설명합니다.

3.1 편향

편향(Bias)은 모델의 가정이 실제 데이터 구조에서 얼마나 벗어나 있는지를 나타냅니다.

  • 모델이 너무 단순하면 편향이 커집니다.
  • 서로 다른 훈련 데이터로 다시 학습해도 비슷한 잘못을 반복할 수 있습니다.
  • 대표적으로 비선형 데이터를 선형 모델로만 설명하는 경우가 있습니다.

높은 편향은 과소적합과 연결됩니다.

3.2 분산

분산(Variance)은 훈련 데이터가 조금 바뀔 때 모델의 예측이 얼마나 크게 달라지는지를 나타냅니다.

  • 모델이 지나치게 복잡하면 분산이 커질 수 있습니다.
  • 훈련 샘플의 작은 차이에도 모델 구조나 경계가 크게 변합니다.
  • 깊은 결정 트리와 작은 데이터셋의 조합이 대표적입니다.

높은 분산은 과적합과 연결됩니다.

3.3 오차 분해

회귀 문제에서는 기대 제곱 오차를 개념적으로 다음처럼 나눌 수 있습니다.

기대 오차 = 편향² + 분산 + 줄일 수 없는 잡음
  • 모델을 복잡하게 만들면 편향은 줄어들 수 있지만 분산이 커질 수 있습니다.
  • 모델을 단순하게 만들거나 정규화를 강화하면 분산은 줄지만 편향이 커질 수 있습니다.
  • 데이터 품질이 낮으면 모델만 변경해도 줄이기 어려운 잡음이 남습니다.

목표는 가장 복잡한 모델을 선택하는 것이 아니라, 검증 데이터에서 오차가 가장 낮고 안정적인 지점을 찾는 것입니다.


4. 과적합과 과소적합을 진단하는 방법

4.1 훈련·검증·테스트 데이터의 역할

세 데이터의 역할을 분리해야 합니다.

데이터역할
훈련 세트모델 파라미터 학습
검증 세트모델 구조와 하이퍼파라미터 선택
테스트 세트모든 선택이 끝난 뒤 최종 일반화 성능 평가

테스트 세트를 반복해서 확인하며 모델을 수정하면 테스트 세트에도 간접적으로 과적합됩니다. 테스트 데이터는 마지막 평가까지 가능한 한 봉인하는 것이 좋습니다.

데이터가 작다면 훈련 세트 내부에서 교차검증을 수행하고, 별도의 테스트 세트를 유지하는 방식이 유용합니다.

4.2 단순 점수 비교

from sklearn.metrics import accuracy_score

train_pred = model.predict(X_train)
valid_pred = model.predict(X_valid)

train_acc = accuracy_score(y_train, train_pred)
valid_acc = accuracy_score(y_valid, valid_pred)
gap = train_acc - valid_acc

print(f"Train Accuracy: {train_acc:.3f}")
print(f"Validation Accuracy: {valid_acc:.3f}")
print(f"Generalization Gap: {gap:.3f}")

하지만 정확도 하나로는 부족할 수 있습니다.

  • 불균형 분류: 정밀도, 재현율, F1, PR-AUC
  • 확률 예측: 로그 손실, Brier Score, Calibration
  • 회귀: MAE, RMSE, R²
  • 객체 탐지: mAP, 클래스별 재현율
  • 시계열: MAE, RMSE, MASE, 시간 구간별 오차

훈련과 검증에서 동일한 지표를 비교해야 합니다.

4.3 학습 곡선

학습 곡선(Learning Curve)은 훈련 데이터 크기를 늘리면서 훈련 점수와 교차검증 점수가 어떻게 변하는지 보여줍니다.

import matplotlib.pyplot as plt
from sklearn.model_selection import LearningCurveDisplay
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression

model = make_pipeline(
    StandardScaler(),
    LogisticRegression(C=1.0, max_iter=2000)
)

LearningCurveDisplay.from_estimator(
    model,
    X,
    y,
    cv=5,
    scoring="accuracy",
    train_sizes=[0.1, 0.3, 0.5, 0.7, 1.0],
    n_jobs=-1
)

plt.show()

학습 곡선 해석은 다음과 같습니다.

훈련과 검증 점수가 모두 낮고 비슷함

과소적합 가능성이 큽니다.

  • 더 복잡한 모델
  • 더 나은 특성
  • 정규화 완화
  • 충분한 학습

을 검토합니다.

훈련 점수는 높고 검증 점수는 낮음

과적합 가능성이 큽니다.

  • 데이터 추가
  • 모델 단순화
  • 정규화 강화
  • 누수 확인
  • 그룹·시간 분할 검토

가 필요합니다.

데이터가 늘어날수록 검증 점수가 계속 상승함

추가 데이터가 효과적일 가능성이 큽니다.

두 점수가 낮은 값에서 수렴함

같은 분포의 데이터를 더 추가하는 것만으로는 개선 폭이 작을 수 있습니다. 모델과 특성을 바꾸는 편이 우선입니다.

4.4 검증 곡선

검증 곡선(Validation Curve)은 하나의 하이퍼파라미터를 바꾸며 훈련·검증 성능을 비교합니다.

예를 들어 결정 트리의 최대 깊이가 증가하면 다음 흐름이 나타날 수 있습니다.

깊이 너무 작음  → 훈련·검증 모두 낮음 → 과소적합
적절한 깊이     → 훈련·검증 모두 높음 → 균형
깊이 너무 큼    → 훈련 높음, 검증 하락 → 과적합
import numpy as np
import matplotlib.pyplot as plt
from sklearn.model_selection import validation_curve
from sklearn.tree import DecisionTreeClassifier

depths = np.arange(1, 21)

train_scores, valid_scores = validation_curve(
    DecisionTreeClassifier(random_state=42),
    X,
    y,
    param_name="max_depth",
    param_range=depths,
    cv=5,
    scoring="accuracy",
    n_jobs=-1
)

plt.plot(depths, train_scores.mean(axis=1), label="Train")
plt.plot(depths, valid_scores.mean(axis=1), label="Validation")
plt.xlabel("max_depth")
plt.ylabel("Accuracy")
plt.legend()
plt.show()

4.5 교차검증의 평균과 변동성

평균 점수가 높더라도 폴드별 편차가 크다면 모델이 데이터 구성에 민감하다는 뜻입니다.

from sklearn.model_selection import cross_validate

scores = cross_validate(
    model,
    X,
    y,
    cv=5,
    scoring=["accuracy", "f1_macro"],
    return_train_score=True,
    n_jobs=-1
)

print("Train Accuracy:", scores["train_accuracy"].mean())
print("Valid Accuracy:", scores["test_accuracy"].mean())
print("Valid Accuracy Std:", scores["test_accuracy"].std())

검증 평균과 함께 표준편차 또는 신뢰구간을 제시하면 모델의 안정성을 더 잘 판단할 수 있습니다.


5. 올바른 데이터 분할이 정규화보다 먼저다

5.1 무작위 분할이 적절한 경우

각 샘플이 서로 독립적이고 같은 분포에서 추출되었다면 무작위 분할을 사용할 수 있습니다.

from sklearn.model_selection import train_test_split

X_train, X_test, y_train, y_test = train_test_split(
    X,
    y,
    test_size=0.2,
    stratify=y,
    random_state=42
)

분류 문제에서는 stratify=y를 사용해 클래스 비율을 비슷하게 유지하는 것이 일반적입니다.

5.2 그룹 단위 분할

한 사용자에게서 여러 행이 생성되거나 한 환자의 여러 영상이 존재한다면 행 단위 무작위 분할은 누수를 만들 수 있습니다.

from sklearn.model_selection import GroupKFold

cv = GroupKFold(n_splits=5)

for train_idx, valid_idx in cv.split(X, y, groups=patient_id):
    X_train, X_valid = X.iloc[train_idx], X.iloc[valid_idx]
    y_train, y_valid = y.iloc[train_idx], y.iloc[valid_idx]

환자, 사용자, 장비, 촬영 세션, 원본 이미지처럼 서로 연관된 샘플은 같은 그룹에 묶어야 합니다.

5.3 시계열 분할

미래를 예측하는 문제에서 데이터를 무작위로 섞으면 미래 정보가 과거 학습에 들어갈 수 있습니다.

from sklearn.model_selection import TimeSeriesSplit

tscv = TimeSeriesSplit(n_splits=5)

for train_idx, valid_idx in tscv.split(X):
    X_train, X_valid = X.iloc[train_idx], X.iloc[valid_idx]
    y_train, y_valid = y.iloc[train_idx], y.iloc[valid_idx]

시계열에서는 보통 과거로 학습하고 그 이후 구간으로 검증합니다.

5.4 전처리는 Pipeline 안에서 수행

표준화, 결측치 대체, 특성 선택을 전체 데이터에 먼저 적용하면 검증 데이터의 통계가 훈련에 유입될 수 있습니다.

from sklearn.impute import SimpleImputer
from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler

model = make_pipeline(
    SimpleImputer(strategy="median"),
    StandardScaler(),
    LogisticRegression(C=1.0, max_iter=2000)
)

Pipeline을 교차검증에 전달하면 각 폴드의 훈련 부분만으로 전처리 기준을 계산하므로 누수 위험을 줄일 수 있습니다.


6. 정규화란?

정규화(Regularization)는 모델이 훈련 데이터에 지나치게 민감해지지 않도록 복잡도에 제약을 주는 방법입니다.

일반적인 목적 함수는 다음과 같이 표현할 수 있습니다.

정규화된 목적 함수 = 원래 손실 + λ × 복잡도 패널티
  • 원래 손실: 예측과 정답의 차이
  • 패널티: 가중치 크기 등에 대한 제약
  • λ: 정규화 강도

λ가 너무 작으면 제약이 거의 없어 과적합될 수 있고, 너무 크면 모델이 충분히 학습하지 못해 과소적합될 수 있습니다.

정규화 강도는 검증 데이터 또는 교차검증으로 선택해야 합니다.


7. L1 정규화

L1 정규화는 가중치 절댓값의 합을 패널티로 추가합니다.

J = 원래 손실 + λ × Σ|wᵢ|

7.1 특징

  • 일부 계수를 정확히 0으로 만들 수 있음
  • 희소한 모델을 만들 수 있음
  • 특성 선택 효과가 있음
  • 불필요한 특성이 많은 고차원 데이터에서 유용할 수 있음
  • 서로 강하게 상관된 특성이 많으면 선택 결과가 불안정할 수 있음

7.2 회귀 예제

from sklearn.linear_model import Lasso
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler

model = make_pipeline(
    StandardScaler(),
    Lasso(alpha=0.01, max_iter=10000)
)

model.fit(X_train, y_train)

L1은 특성 스케일의 영향을 받으므로, 대부분의 경우 표준화와 함께 사용합니다.

7.3 분류 예제

from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler

model = make_pipeline(
    StandardScaler(),
    LogisticRegression(
        penalty="l1",
        solver="liblinear",
        C=1.0,
        max_iter=2000
    )
)

scikit-learn의 로지스틱 회귀에서 C는 정규화 강도의 역수입니다.

C가 작음 → 정규화 강함
C가 큼   → 정규화 약함

alphaC의 방향이 반대이므로 혼동하지 않아야 합니다.


8. L2 정규화와 가중치 감쇠

L2 정규화는 가중치 제곱합을 패널티로 추가합니다.

J = 원래 손실 + λ × Σwᵢ²

8.1 특징

  • 큰 가중치에 더 큰 패널티를 줌
  • 계수를 전체적으로 작고 부드럽게 만듦
  • 일반적으로 계수를 정확히 0으로 만들지는 않음
  • 상관된 특성이 여러 개 있을 때 가중치를 분산시키는 경향이 있음
  • 선형 모델과 신경망에서 널리 사용됨

8.2 Ridge 회귀 예제

from sklearn.linear_model import Ridge
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler

model = make_pipeline(
    StandardScaler(),
    Ridge(alpha=1.0)
)

model.fit(X_train, y_train)

8.3 딥러닝의 Weight Decay

PyTorch 옵티마이저에서는 weight_decay로 가중치 감쇠를 적용할 수 있습니다.

import torch

optimizer = torch.optim.AdamW(
    model.parameters(),
    lr=1e-3,
    weight_decay=1e-4
)

AdamW는 가중치 감쇠를 적응형 경사 업데이트와 분리해 적용합니다. Adam 계열에서는 단순히 손실에 L2 항을 더한 것과 AdamW의 weight decay가 항상 동일하게 동작하는 것은 아니므로 구분해서 이해하는 것이 좋습니다.


9. Elastic Net

Elastic Net은 L1과 L2를 함께 사용합니다.

J = 원래 손실 + λ₁ × Σ|wᵢ| + λ₂ × Σwᵢ²
  • L1의 희소성
  • L2의 안정성

을 함께 얻고 싶을 때 사용합니다.

from sklearn.linear_model import ElasticNet
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler

model = make_pipeline(
    StandardScaler(),
    ElasticNet(
        alpha=0.01,
        l1_ratio=0.5,
        max_iter=10000
    )
)

model.fit(X_train, y_train)

l1_ratio=1이면 L1에 가깝고, l1_ratio=0이면 L2에 가깝습니다.

Elastic Net이 항상 L1이나 L2보다 우수한 것은 아닙니다. 데이터와 검증 결과에 따라 선택해야 합니다.


10. L1, L2, Elastic Net 비교

항목L1L2Elastic Net
패널티절댓값 합제곱합L1 + L2
계수를 0으로 만들기가능일반적으로 어려움가능
특성 선택 효과작음있음
상관된 특성 처리하나를 임의로 선택할 수 있음가중치를 분산하는 경향L1보다 안정적일 수 있음
대표 모델LassoRidgeElasticNet
주요 용도희소 모델, 특성 축소안정적 일반화희소성과 안정성 절충

정규화를 선택할 때는 단순히 “L2가 기본”, “특성이 많으면 L1”로 끝내기보다 다음을 함께 봐야 합니다.

  • 입력 특성 수
  • 특성 간 상관관계
  • 해석 가능성 요구
  • 희소 모델 필요 여부
  • 검증 점수와 변동성
  • 추론 비용

11. 드롭아웃

드롭아웃(Dropout)은 신경망 학습 중 일부 활성값을 확률적으로 0으로 만들어 특정 뉴런 조합에 대한 의존을 줄이는 기법입니다.

학습 모드: 일부 활성값을 무작위로 제거
평가 모드: 모든 활성값을 사용

PyTorch의 nn.Dropout(p=0.2)에서 p는 유지 확률이 아니라 0으로 만들 확률입니다.

11.1 PyTorch 예제

import torch
import torch.nn as nn

class Classifier(nn.Module):
    def __init__(self, input_dim, num_classes):
        super().__init__()
        self.network = nn.Sequential(
            nn.Linear(input_dim, 128),
            nn.ReLU(),
            nn.Dropout(p=0.2),
            nn.Linear(128, 64),
            nn.ReLU(),
            nn.Dropout(p=0.1),
            nn.Linear(64, num_classes)
        )

    def forward(self, x):
        return self.network(x)

학습과 평가 모드를 반드시 구분해야 합니다.

model.train()
# 학습: Dropout 활성화

model.eval()
# 평가: Dropout 비활성화

torch.no_grad()는 기울기 계산을 끄는 기능이고, model.eval()은 Dropout과 BatchNorm 같은 계층의 동작 모드를 변경하는 기능입니다. 두 기능은 목적이 다르므로 평가 시 함께 사용하는 경우가 많습니다.

model.eval()

with torch.no_grad():
    logits = model(X_valid)

11.2 Keras 예제

from keras import Sequential
from keras.layers import Dense, Dropout

model = Sequential([
    Dense(128, activation="relu", input_shape=(100,)),
    Dropout(0.2),
    Dense(64, activation="relu"),
    Dropout(0.1),
    Dense(10, activation="softmax")
])

11.3 드롭아웃 비율은 0.5가 정답이 아니다

과거 완전연결층 예제에서는 0.5가 자주 사용됐지만, 모든 모델에서 0.5가 좋은 기본값은 아닙니다.

  • 작은 모델에서 높은 드롭아웃은 과소적합을 만들 수 있습니다.
  • 합성곱 신경망에서는 더 낮은 비율이나 Spatial Dropout을 사용할 수 있습니다.
  • Transformer 계열에서는 0.1 전후가 자주 보이지만 데이터와 모델에 따라 달라집니다.
  • BatchNorm, 데이터 증강, Weight Decay가 이미 강하면 추가 드롭아웃이 불필요할 수 있습니다.

실전에서는 0, 0.05, 0.1, 0.2, 0.3처럼 비교하고 교차검증 또는 검증 세트에서 선택하는 편이 안전합니다.


12. 조기 종료

조기 종료(Early Stopping)는 검증 지표가 더 이상 개선되지 않을 때 학습을 멈추는 방법입니다.

단순히 시간을 절약하는 기능이 아니라, 검증 성능이 최적이었던 시점의 모델을 선택하는 정규화 효과를 가질 수 있습니다.

12.1 Keras 예제

from keras.callbacks import EarlyStopping

early_stopping = EarlyStopping(
    monitor="val_loss",
    mode="min",
    patience=8,
    min_delta=1e-4,
    restore_best_weights=True
)

history = model.fit(
    X_train,
    y_train,
    validation_data=(X_valid, y_valid),
    epochs=300,
    callbacks=[early_stopping]
)

각 옵션의 의미는 다음과 같습니다.

옵션의미
monitor관찰할 검증 지표
mode작을수록 좋은지, 클수록 좋은지
patience개선이 없더라도 기다릴 Epoch 수
min_delta개선으로 인정할 최소 변화량
restore_best_weights가장 좋았던 시점의 가중치 복원

12.2 PyTorch 예제

from copy import deepcopy

best_val_loss = float("inf")
best_state = None
patience = 8
counter = 0
min_delta = 1e-4

for epoch in range(300):
    model.train()
    train_one_epoch(model, train_loader, optimizer, criterion)

    model.eval()
    val_loss = evaluate(model, valid_loader, criterion)

    if val_loss < best_val_loss - min_delta:
        best_val_loss = val_loss
        best_state = deepcopy(model.state_dict())
        counter = 0
    else:
        counter += 1

    if counter >= patience:
        print(f"Early stopping at epoch {epoch + 1}")
        break

if best_state is not None:
    model.load_state_dict(best_state)

12.3 Patience는 고정값이 아니다

patience=5가 항상 적절한 것은 아닙니다.

  • 검증 지표의 변동이 크면 더 길게 기다려야 할 수 있습니다.
  • 학습률 스케줄러가 개선을 늦게 만들 수 있습니다.
  • 데이터가 작으면 검증 손실이 흔들릴 수 있습니다.
  • 한 Epoch가 매우 길면 작은 patience도 충분할 수 있습니다.

조기 종료와 학습률 감소 스케줄러를 함께 사용할 때는 순서도 고려해야 합니다. 학습률이 감소한 뒤 성능이 다시 개선될 기회를 주려면 early stopping의 patience를 더 길게 설정할 수 있습니다.


13. 데이터 증강

데이터 증강(Data Augmentation)은 기존 샘플에 라벨 의미를 유지하는 변환을 적용해 학습 데이터의 다양성을 높입니다.

13.1 이미지

  • 좌우 반전
  • 작은 회전과 이동
  • 밝기·대비 변화
  • Crop
  • Cutout, Mixup, CutMix

주의할 점은 모든 변환이 모든 문제에 적합하지 않다는 것입니다.

  • 숫자 6과 9를 뒤집으면 라벨이 바뀔 수 있습니다.
  • 의료 영상의 좌우 반전이 해부학적 의미를 훼손할 수 있습니다.
  • 도로 표지판의 과도한 회전은 실제 분포와 다를 수 있습니다.

13.2 텍스트

  • 문장 일부 마스킹
  • 의미를 보존하는 패러프레이즈
  • 역번역
  • 토큰 드롭

텍스트 증강은 의미가 조금만 바뀌어도 라벨이 달라질 수 있으므로 자동 생성 결과를 검토해야 합니다.

13.3 시계열과 센서

  • 작은 잡음 추가
  • 시간축 이동
  • 크기 조절
  • 구간 자르기
  • 주파수 영역 변환

센서 데이터에서는 물리적으로 불가능한 값을 만들지 않도록 도메인 제약을 반영해야 합니다.


14. 모델별 과적합 방지 전략

14.1 선형·로지스틱 회귀

  • L1, L2, Elastic Net
  • 특성 스케일링
  • 다중공선성 점검
  • 불필요한 고차항 제거
  • 교차검증으로 정규화 강도 선택
from sklearn.linear_model import LogisticRegressionCV
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler

model = make_pipeline(
    StandardScaler(),
    LogisticRegressionCV(
        Cs=10,
        cv=5,
        penalty="l2",
        scoring="f1_macro",
        max_iter=3000,
        n_jobs=-1
    )
)

14.2 결정 트리

  • max_depth 제한
  • min_samples_split 증가
  • min_samples_leaf 증가
  • max_features 제한
  • Cost-Complexity Pruning 적용
from sklearn.tree import DecisionTreeClassifier

model = DecisionTreeClassifier(
    max_depth=6,
    min_samples_leaf=10,
    random_state=42
)

14.3 랜덤 포레스트

랜덤 포레스트는 단일 결정 트리보다 분산을 줄이지만 여전히 과적합할 수 있습니다.

  • 트리 깊이 제한
  • 최소 리프 샘플 수 증가
  • 특성 샘플링
  • OOB 점수 확인
  • 클래스 불균형 대응
from sklearn.ensemble import RandomForestClassifier

model = RandomForestClassifier(
    n_estimators=500,
    max_depth=12,
    min_samples_leaf=3,
    max_features="sqrt",
    class_weight="balanced",
    oob_score=True,
    n_jobs=-1,
    random_state=42
)

14.4 부스팅 모델

XGBoost, LightGBM, CatBoost에서는 다음 항목이 중요합니다.

  • 트리 깊이
  • 리프 수
  • 학습률
  • 트리 개수
  • 행·열 샘플링
  • L1·L2 정규화
  • 최소 리프 데이터 수
  • 조기 종료

학습률을 낮추면 일반적으로 더 많은 트리가 필요합니다. 학습률을 낮춘다고 과적합이 자동으로 해결되는 것은 아니며, 트리 수와 함께 조정해야 합니다.

14.5 신경망

  • Weight Decay
  • Dropout
  • 데이터 증강
  • Early Stopping
  • 모델 크기 축소
  • Batch Normalization 또는 Layer Normalization
  • Label Smoothing
  • Mixup·CutMix
  • 사전학습 모델 활용
  • 학습률 스케줄링

한 번에 모든 기법을 강하게 적용하면 과소적합이 발생할 수 있으므로 하나씩 추가하며 검증해야 합니다.


15. 실전 예제: 과적합된 랜덤 포레스트 개선

다음 예제는 유방암 분류 데이터에서 제한 없는 랜덤 포레스트와 규제된 모델을 교차검증으로 비교합니다.

from sklearn.datasets import load_breast_cancer
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_validate
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression

X, y = load_breast_cancer(return_X_y=True)

models = {
    "deep_random_forest": RandomForestClassifier(
        n_estimators=500,
        max_depth=None,
        min_samples_leaf=1,
        random_state=42,
        n_jobs=-1
    ),
    "regularized_random_forest": RandomForestClassifier(
        n_estimators=500,
        max_depth=8,
        min_samples_leaf=4,
        max_features="sqrt",
        random_state=42,
        n_jobs=-1
    ),
    "l2_logistic_regression": make_pipeline(
        StandardScaler(),
        LogisticRegression(
            penalty="l2",
            C=1.0,
            max_iter=3000
        )
    )
}

for name, model in models.items():
    result = cross_validate(
        model,
        X,
        y,
        cv=5,
        scoring="accuracy",
        return_train_score=True,
        n_jobs=-1
    )

    train_mean = result["train_score"].mean()
    valid_mean = result["test_score"].mean()
    valid_std = result["test_score"].std()

    print(
        f"{name:28s} "
        f"train={train_mean:.4f}, "
        f"valid={valid_mean:.4f}±{valid_std:.4f}, "
        f"gap={train_mean - valid_mean:.4f}"
    )

여기서 중요한 것은 특정 모델이 항상 이긴다는 결론이 아닙니다.

  • 훈련 점수
  • 검증 평균
  • 검증 편차
  • 일반화 격차
  • 모델 복잡도

를 함께 비교해 합리적인 모델을 선택하는 과정이 핵심입니다.

최고 점수와 거의 차이가 없다면 더 단순하고 안정적인 모델을 선택하는 것도 좋은 전략입니다.


16. 하이퍼파라미터 튜닝 시 테스트 세트 과적합 방지

여러 모델과 수백 개의 조합을 같은 검증 세트에서 반복 비교하면 검증 세트에도 과적합될 수 있습니다.

16.1 중첩 교차검증

중첩 교차검증(Nested Cross-Validation)은 내부 교차검증에서 하이퍼파라미터를 선택하고, 외부 교차검증에서 선택된 절차의 일반화 성능을 평가합니다.

from sklearn.model_selection import GridSearchCV, cross_val_score
from sklearn.svm import SVC

param_grid = {
    "C": [0.1, 1, 10],
    "gamma": ["scale", 0.01, 0.1]
}

search = GridSearchCV(
    SVC(),
    param_grid=param_grid,
    cv=5,
    scoring="accuracy"
)

nested_scores = cross_val_score(
    search,
    X,
    y,
    cv=5,
    scoring="accuracy",
    n_jobs=-1
)

print(nested_scores.mean(), nested_scores.std())

데이터가 작고 모델 선택을 많이 반복하는 연구·평가 환경에서는 중첩 교차검증이 특히 유용합니다.

16.2 최종 테스트는 한 번만

권장 흐름은 다음과 같습니다.

1. 훈련 데이터 준비
2. 훈련 데이터 내부에서 교차검증
3. 모델과 하이퍼파라미터 결정
4. 결정된 설정으로 전체 훈련 데이터 재학습
5. 봉인했던 테스트 세트로 최종 1회 평가

17. 자주 하는 잘못된 처방

“훈련 정확도가 높으니 모델이 좋다”

훈련 정확도는 모델이 이미 본 데이터를 얼마나 잘 맞히는지를 보여줄 뿐입니다. 검증·테스트 성능과 운영 환경 성능이 더 중요합니다.

“훈련과 검증 점수 차이가 작으니 일반화가 잘됐다”

두 점수가 모두 낮다면 과소적합일 수 있습니다.

“과적합이면 드롭아웃 0.5를 넣으면 된다”

드롭아웃은 신경망 정규화 기법 중 하나일 뿐입니다. 데이터 누수, 잘못된 분할, 라벨 오류가 원인이라면 해결책이 아닙니다.

“데이터는 파라미터 수의 10배면 충분하다”

모든 문제에 적용되는 보편적인 비율은 없습니다. 데이터 차원, 노이즈, 모델 구조, 사전학습 여부, 증강, 라벨 복잡도에 따라 필요한 데이터는 크게 달라집니다.

“L2는 항상 L1보다 성능이 좋다”

데이터에 따라 다릅니다. 희소성이 중요한 고차원 문제에서는 L1이나 Elastic Net이 더 적합할 수 있습니다.

“검증 점수가 가장 높은 모델이 무조건 최선이다”

점수 차이가 오차 범위 안이라면 더 단순하고 빠르고 안정적인 모델이 운영 환경에서는 더 나을 수 있습니다.

“학습률을 낮추면 과적합이 해결된다”

학습률은 최적화 속도와 경로에 영향을 줍니다. 과적합은 모델 용량, 데이터, 학습 시간과 함께 봐야 하며 학습률만 낮춘다고 해결되지 않습니다.


18. 과적합·과소적합 실전 진단 순서

1단계: 지표가 문제에 맞는지 확인

불균형 분류에서 정확도만 보거나, 확률 품질이 중요한데 F1만 보는 식의 평가 오류를 먼저 제거합니다.

2단계: 분할과 누수를 확인

  • 같은 그룹이 훈련과 검증에 섞이지 않았는가?
  • 전처리를 분할 전에 수행하지 않았는가?
  • 미래 정보가 특성에 들어가지 않았는가?
  • 증강된 복제 샘플이 세트 간에 섞이지 않았는가?

3단계: 훈련과 검증 성능 비교

  • 둘 다 낮음: 과소적합 가능성
  • 훈련 높음, 검증 낮음: 과적합 가능성
  • 둘 다 높음: 적절할 가능성
  • 내부 검증만 높고 운영 성능 낮음: 분포 이동 또는 누수 의심

4단계: 학습 곡선 확인

데이터 추가가 효과적인지, 모델 복잡도를 바꿔야 하는지 판단합니다.

5단계: 가장 단순한 처방부터 적용

  • 트리 깊이 제한
  • 정규화 강도 조정
  • 특성 제거 또는 추가
  • 데이터 증강
  • 드롭아웃
  • 조기 종료

6단계: 한 번에 하나씩 변경

여러 기법을 동시에 바꾸면 어떤 변경이 효과가 있었는지 알기 어렵습니다. 실험 기록을 남기고 동일한 분할과 지표로 비교합니다.

7단계: 최종 테스트와 외부 검증

가능하면 시간, 지역, 장비, 사용자 집단이 다른 외부 데이터에서도 평가합니다.


19. 상황별 처방표

관찰 결과가능한 원인우선 확인추천 대응
훈련·검증 모두 낮음과소적합특성, 모델 용량, 최적화복잡도·특성·학습 시간 증가
훈련 높고 검증 낮음과적합누수, 분할, 데이터 크기정규화, 모델 축소, 데이터 추가
검증 점수 변동이 큼데이터 부족, 그룹 편향폴드별 구성반복 CV, 그룹 분할, 데이터 추가
내부 검증 높고 운영 낮음분포 이동시간·지역·사용자 차이외부 검증, 재학습, 모니터링
정규화 강화 후 모두 낮아짐정규화 과다λ, C, dropout정규화 완화
Epoch 증가 후 검증 손실 상승장시간 학습손실 곡선조기 종료
트리 훈련 점수 100%깊이 과다 가능성깊이·리프 크기max_depth, min_samples_leaf
증강 후 성능 하락부적절한 변환라벨 보존 여부증강 범위 축소
테스트를 볼수록 성능 상승테스트 과적합평가 절차테스트 봉인, 중첩 CV

20. FAQ

Q1. 과적합과 과소적합 중 어느 것이 더 나쁜가요?

둘 다 문제입니다. 과소적합은 학습 자체가 부족하고, 과적합은 훈련 데이터에서는 좋아 보이지만 실제 환경에서 실패합니다. 중요한 것은 어느 쪽인지 정확히 진단하는 것입니다.

Q2. 훈련과 검증 성능 차이가 몇 %p면 과적합인가요?

보편적인 임계값은 없습니다. 데이터 크기, 지표, 문제 난이도, 검증 편차를 함께 봐야 합니다. 단일 분할의 차이보다 교차검증 평균과 분산, 학습 곡선이 더 유용합니다.

Q3. L1과 L2 중 무엇을 먼저 사용해야 하나요?

선형 모델에서는 L2를 기준선으로 시작하기 쉽지만 정답은 아닙니다. 희소한 특성 선택이 중요하면 L1, 상관된 특성이 많고 안정성이 필요하면 L2나 Elastic Net을 비교해 볼 수 있습니다.

Q4. 드롭아웃 비율은 얼마가 적당한가요?

모델과 데이터에 따라 다릅니다. 0.1~0.3 범위를 자주 시험하지만, 0이 더 나을 수도 있고 완전연결층에서는 더 큰 값이 필요할 수도 있습니다. 검증 실험으로 결정해야 합니다.

Q5. 조기 종료와 Weight Decay를 함께 사용해도 되나요?

가능합니다. 두 기법은 서로 다른 방식으로 과적합을 줄입니다. 다만 정규화가 과도해 훈련 성능까지 낮아지지 않는지 확인해야 합니다.

Q6. 데이터가 많으면 과적합이 사라지나요?

같은 분포를 충분히 대표하는 데이터가 늘면 과적합이 줄어드는 경우가 많습니다. 그러나 라벨 오류, 데이터 누수, 분포 편향이 있으면 데이터 양만 늘려도 해결되지 않을 수 있습니다.

Q7. 사전학습 모델도 과적합되나요?

그렇습니다. 작은 데이터셋에서 전체 파라미터를 큰 학습률로 미세조정하면 과적합될 수 있습니다. 일부 계층 동결, 낮은 학습률, Weight Decay, 조기 종료, 데이터 증강 등을 고려할 수 있습니다.

Q8. 검증 손실은 증가하지만 검증 정확도는 유지됩니다. 과적합인가요?

가능성이 있습니다. 모델이 틀린 예측에 더 과도한 확신을 가지면 정확도는 같아도 로그 손실은 악화될 수 있습니다. 확률 보정과 신뢰도까지 중요하다면 손실과 Calibration 지표를 함께 봐야 합니다.


정리

과적합과 과소적합은 단순히 훈련 정확도와 테스트 정확도의 차이를 외우는 개념이 아닙니다.

핵심은 다음과 같습니다.

  1. 과소적합은 훈련 데이터의 패턴도 충분히 배우지 못한 상태입니다.
  2. 과적합은 훈련 데이터의 잡음과 예외까지 학습해 새로운 데이터에서 성능이 낮아지는 상태입니다.
  3. 훈련·검증 점수뿐 아니라 학습 곡선, 검증 곡선, 교차검증 변동성을 함께 봐야 합니다.
  4. 정규화 전에 데이터 누수와 분할 오류를 먼저 확인해야 합니다.
  5. L1, L2, Elastic Net, 드롭아웃, 조기 종료는 상황에 따라 선택하는 도구이며 고정된 정답값은 없습니다.
  6. 테스트 데이터는 모델 선택이 끝난 뒤 최종 평가에 사용해야 합니다.
  7. 실제 운영 환경과 가까운 시간·그룹·지역 기반 외부 검증이 가장 중요합니다.

좋은 모델은 훈련 데이터를 가장 완벽하게 외운 모델이 아니라, 처음 보는 데이터에서도 실수를 안정적으로 줄이는 모델입니다.


같이 보면 좋은 글


외부 참고 자료

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다