[AI 101] 과적합과 과소적합 – 완벽한 균형으로 AI 성능 2배 높이기


핵심 요약

“AI 성능의 적은 과적합이다”는 말이 있습니다. 과적합(Overfitting)은 훈련 데이터에만 99% 정확하지만 실제 테스트에서는 60%로 폭락하는 현상입니다. 반대로 과소적합(Underfitting)은 훈련과 테스트 모두 60%로 모델이 제대로 학습하지 못한 상태입니다. 해결책은 정규화(L1/L2), 드롭아웃(Dropout), 조기종료(Early Stopping)라는 3가지 무기입니다. L2 정규화는 가중치를 작게 유지하고, 드롭아웃은 뉴런의 50%를 랜덤하게 끄며, 조기종료는 최적 타이밍에 학습을 멈춥니다. 이 3가지만 잘 사용해도 모델 성능을 20~30%p 향상시킬 수 있습니다.


📍 목차

  1. 과적합(Overfitting)이란?
  2. 과소적합(Underfitting)의 문제
  3. 정규화(Regularization) 기법: L1, L2
  4. 드롭아웃(Dropout)과 조기종료(Early Stopping)
  5. 실전 과적합 방지 전략

1. 과적합(Overfitting)이란?

1-1. 과적합의 정의

과적합(Overfitting)은 모델이 훈련 데이터에만 지나치게 적응하여 새로운 데이터에 대한 예측 성능이 저하되는 현상입니다.

비유: 시험 준비

학생 유형공부 방법모의고사수능문제
과적합 학생기출문제만 암기100점60점새 문제 못 풀음
적절한 학생개념 이해 + 문제 풀이85점85점일반화 성공

AI에서의 과적합:

Training Accuracy: 99%  ← 훈련 데이터에 완벽
Test Accuracy: 65%      ← 실전에서 실패

1-2. 과적합이 발생하는 이유

1️⃣ 모델이 너무 복잡함

예시: 다항식 회귀

모델 복잡도수식결과
단순(y = w_1x + b)과소적합
적절(y = w_1x + w_2x^2 + b)최적
복잡(y = w_1x + w_2x^2 + \cdots + w_{20}x^{20} + b)과적합

시각적 이해:

데이터: . . . . .
         .     .
       .   .     .

과소적합:  ─────────  (직선, 단순)
적절:      ───╭──╮──  (곡선, 적절)
과적합:    ─╭╮─╰─╮─╰  (지그재그, 복잡)

2️⃣ 데이터가 너무 적음

데이터 개수모델 파라미터비율결과
100개10개10:1✅ 안정적
100개1,000개1:10과적합

원칙: 파라미터 1개당 데이터 최소 10개 이상

3️⃣ 훈련 시간이 너무 길음

학습 곡선:

Loss
 │
 │  Training Loss
 │  ╲
 │   ╲___________
 │
 │  Validation Loss
 │  ╲
 │   ╲_╭─────────  ← 이 시점 이후 과적합!
 │     ╰───────
 └──────────────────> Epoch
     최적 지점

1-3. 과적합 진단 방법

1. Training vs Test 성능 비교

from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score

# 데이터 분할
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42
)

# 모델 학습
model = RandomForestClassifier(n_estimators=100, max_depth=20)
model.fit(X_train, y_train)

# 평가
train_acc = model.score(X_train, y_train)
test_acc = model.score(X_test, y_test)

print(f"Training Accuracy: {train_acc:.2%}")
print(f"Test Accuracy: {test_acc:.2%}")
print(f"Gap: {(train_acc - test_acc)*100:.1f}%p")

# 진단
if train_acc - test_acc > 0.1:  # 10%p 이상 차이
    print("⚠️ 과적합 의심!")

출력 예시:

Training Accuracy: 99.5%
Test Accuracy: 72.3%
Gap: 27.2%p
⚠️ 과적합 의심!

2. 학습 곡선 (Learning Curve)

import matplotlib.pyplot as plt

history = model.fit(X_train, y_train, 
                   validation_split=0.2,
                   epochs=100)

plt.figure(figsize=(12, 4))

# Loss 곡선
plt.subplot(1, 2, 1)
plt.plot(history.history['loss'], label='Train Loss')
plt.plot(history.history['val_loss'], label='Val Loss')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.legend()
plt.title('Loss Curve')

# Accuracy 곡선
plt.subplot(1, 2, 2)
plt.plot(history.history['accuracy'], label='Train Acc')
plt.plot(history.history['val_accuracy'], label='Val Acc')
plt.xlabel('Epoch')
plt.ylabel('Accuracy')
plt.legend()
plt.title('Accuracy Curve')

plt.show()

과적합 징후:

  • Train Loss ↓, Val Loss ↑
  • Train Acc ↑, Val Acc ↓
  • 두 곡선이 벌어짐 (Diverge)

2. 과소적합(Underfitting)의 문제

2-1. 과소적합의 정의

과소적합(Underfitting)은 모델이 너무 단순해서 데이터의 패턴을 제대로 학습하지 못하는 현상입니다.

비유: 시험 준비

학생 유형공부 방법모의고사수능문제
과소적합 학생전혀 안 함50점50점기본도 모름
적절한 학생개념 이해 + 문제 풀이85점85점일반화 성공

AI에서의 과소적합:

Training Accuracy: 60%  ← 훈련도 못함
Test Accuracy: 58%      ← 테스트도 못함

2-2. 과소적합 vs 과적합 비교

항목과소적합 (Underfitting)적절 (Good Fit)과적합 (Overfitting)
모델 복잡도너무 낮음적절너무 높음
Training 성능낮음 (60%)높음 (85%)매우 높음 (99%)
Test 성능낮음 (58%)높음 (85%)낮음 (65%)
편향(Bias)높음적절낮음
분산(Variance)낮음적절높음
문제학습 부족일반화 실패

2-3. 과소적합 발생 원인

1️⃣ 모델이 너무 단순

예시: 비선형 데이터를 선형 모델로

import numpy as np
from sklearn.linear_model import LinearRegression
from sklearn.preprocessing import PolynomialFeatures

# 비선형 데이터 생성
X = np.linspace(-3, 3, 100).reshape(-1, 1)
y = X**2 + np.random.randn(100, 1) * 0.5

# 1. 선형 모델 (과소적합)
model_linear = LinearRegression()
model_linear.fit(X, y)
score_linear = model_linear.score(X, y)

print(f"선형 모델 R² Score: {score_linear:.4f}")  # 0.02 (매우 낮음)

# 2. 다항식 모델 (적절)
poly = PolynomialFeatures(degree=2)
X_poly = poly.fit_transform(X)
model_poly = LinearRegression()
model_poly.fit(X_poly, y)
score_poly = model_poly.score(X_poly, y)

print(f"다항식 모델 R² Score: {score_poly:.4f}")  # 0.98 (높음)

2️⃣ Feature가 부족

# 부족한 Feature
X_poor = df[['나이']]  # 1개만
model.fit(X_poor, y)
print(f"Score: {model.score(X_test, y_test):.2%}")  # 65%

# 충분한 Feature
X_rich = df[['나이', '연봉', '신용점수', '직업']]  # 4개
model.fit(X_rich, y)
print(f"Score: {model.score(X_test, y_test):.2%}")  # 85%

3️⃣ 학습 시간이 부족

# 부족한 Epoch
model.fit(X_train, y_train, epochs=5)  # 5 epoch
# → 과소적합

# 충분한 Epoch
model.fit(X_train, y_train, epochs=100)  # 100 epoch
# → 적절

2-4. 과소적합 해결 방법

방법설명예시
모델 복잡도 증가더 복잡한 모델 사용Linear → Polynomial, Decision Tree → Random Forest
Feature 추가더 많은 변수 사용Feature Engineering
학습 시간 증가Epoch 늘리기10 → 100 epoch
정규화 완화λ 값 줄이기λ=0.1 → λ=0.01

3. 정규화(Regularization) 기법: L1, L2

3-1. 정규화란?

정규화(Regularization)모델 복잡도에 패널티를 부여하여 과적합을 방지하는 기법입니다.

핵심 아이디어:

“가중치가 크면 모델이 복잡하다 → 가중치를 작게 유지하자”

정규화 수식:
[
J_{reg} = J_{original} + \lambda \times \text{Penalty}
]

  • (J_{original}): 원래 손실함수 (MSE 등)
  • (\lambda): 정규화 강도 (0~1)
  • Penalty: L1 or L2

3-2. L1 정규화 (Lasso Regression)

L1 Penalty: 가중치의 절댓값 합

수식:
[
J_{L1} = J_{original} + \lambda \sum_{i=1}^{n}|w_i|
]

특징:

  • 일부 가중치를 정확히 0으로 만듦
  • Feature Selection 효과
  • 희소한 모델 (Sparse Model)

Python 구현:

from sklearn.linear_model import Lasso

# L1 정규화
model = Lasso(alpha=0.1)  # alpha = λ
model.fit(X_train, y_train)

# 가중치 확인
print("가중치:", model.coef_)
print("0인 가중치 개수:", (model.coef_ == 0).sum())

출력 예시:

가중치: [0.   2.3  0.   1.5  0.   0.   3.1]
0인 가중치 개수: 4

효과: 7개 중 4개 Feature를 자동으로 제거 (Feature Selection)

3-3. L2 정규화 (Ridge Regression)

L2 Penalty: 가중치의 제곱 합

수식:
[
J_{L2} = J_{original} + \lambda \sum_{i=1}^{n}w_i^2
]

특징:

  • 가중치를 작게 유지 (0은 아님)
  • 모든 Feature 사용
  • 일반적으로 L1보다 성능 좋음

Python 구현:

from sklearn.linear_model import Ridge

# L2 정규화
model = Ridge(alpha=0.1)  # alpha = λ
model.fit(X_train, y_train)

# 가중치 확인
print("가중치:", model.coef_)

출력 예시:

가중치: [0.15 2.30 0.08 1.52 0.03 0.11 3.05]

효과: 모든 가중치가 작지만 0은 아님

3-4. L1 vs L2 비교

항목L1 (Lasso)L2 (Ridge)
Penalty(\sumw_i
가중치일부 0으로모두 작게
Feature Selection✅ 가능❌ 불가능
희소성⭐⭐⭐⭐⭐
일반화 성능⭐⭐⭐⭐⭐⭐⭐⭐
이상치 민감도낮음 (강건)높음 (민감)
사용 시기Feature 많을 때일반적인 경우

시각적 비교:

L1 (마름모):        L2 (원):
    ╱│╲              ╭─╮
   ╱ │ ╲            ╱   ╲
  ╱  │  ╲          │     │
 ╱   │   ╲         │     │
───────────        ╰─────╯
 ╲   │   ╱          
  ╲  │  ╱           
   ╲ │ ╱            
    ╲│╱              

축과 만나는 점     축과 만나지 않음
→ 가중치 0       → 가중치 작지만 0 아님

3-5. Elastic Net (L1 + L2)

L1과 L2를 동시에 사용

수식:
[
J_{ElasticNet} = J_{original} + \lambda_1 \sum |w_i| + \lambda_2 \sum w_i^2
]

from sklearn.linear_model import ElasticNet

model = ElasticNet(alpha=0.1, l1_ratio=0.5)  # 50% L1, 50% L2
model.fit(X_train, y_train)

장점:

  • L1의 Feature Selection + L2의 안정성
  • 최고의 일반화 성능

4. 드롭아웃(Dropout)과 조기종료(Early Stopping)

4-1. 드롭아웃(Dropout)

드롭아웃학습 시 일부 뉴런을 랜덤하게 제거하여 과적합을 방지하는 기법입니다.

핵심 아이디어:

“특정 뉴런에 의존하지 않도록 매번 다른 조합으로 학습”

작동 방식:

원본 신경망:
Input → [O O O O] → [O O O] → Output
         은닉층 1     은닉층 2

Dropout (50%) 적용:
Epoch 1: Input → [X O X O] → [O X O] → Output
Epoch 2: Input → [O X O X] → [X O X] → Output
Epoch 3: Input → [O O X X] → [O O X] → Output
...

Python 구현 (PyTorch):

import torch
import torch.nn as nn

class Model(nn.Module):
    def __init__(self):
        super().__init__()
        self.fc1 = nn.Linear(100, 50)
        self.dropout1 = nn.Dropout(p=0.5)  # 50% 드롭
        self.fc2 = nn.Linear(50, 10)
        self.dropout2 = nn.Dropout(p=0.3)  # 30% 드롭
        self.fc3 = nn.Linear(10, 2)

    def forward(self, x):
        x = torch.relu(self.fc1(x))
        x = self.dropout1(x)  # 학습 시에만 작동
        x = torch.relu(self.fc2(x))
        x = self.dropout2(x)
        x = self.fc3(x)
        return x

model = Model()

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

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

Keras 구현:

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

model = Sequential([
    Dense(50, activation='relu', input_shape=(100,)),
    Dropout(0.5),  # 50% 드롭
    Dense(10, activation='relu'),
    Dropout(0.3),  # 30% 드롭
    Dense(2, activation='softmax')
])

효과:

지표Dropout 없음Dropout 50%
Training Acc98%92%
Test Acc72%88%
성능 향상+16%p

4-2. 조기종료(Early Stopping)

조기종료Validation Loss가 더 이상 개선되지 않으면 학습을 중단하는 기법입니다.

핵심 아이디어:

“과적합이 시작되기 전에 멈추자”

작동 방식:

Validation Loss
    │
0.5 │  ●
    │   ●
0.4 │    ●
    │     ●  ← 최저점
0.3 │      ●
    │       ●
0.4 │        ●
    │         ●  ← 3 epoch 연속 개선 없음
0.5 │          ●
    │           STOP! (Early Stopping)
    └───────────────────> Epoch
        1 2 3 4 5 6 7 8

Python 구현 (Keras):

from tensorflow.keras.callbacks import EarlyStopping

# Early Stopping 설정
early_stop = EarlyStopping(
    monitor='val_loss',    # 모니터링할 지표
    patience=5,            # 5 epoch 동안 개선 없으면 중단
    mode='min',            # val_loss는 작을수록 좋음
    restore_best_weights=True,  # 최적 가중치 복원
    verbose=1              # 로그 출력
)

# 학습
history = model.fit(
    X_train, y_train,
    validation_split=0.2,
    epochs=100,            # 최대 100 epoch
    callbacks=[early_stop] # Early Stopping 적용
)

print(f"실제 학습 Epoch: {len(history.history['loss'])}")

출력 예시:

Epoch 1/100: loss: 0.45 - val_loss: 0.40
Epoch 2/100: loss: 0.35 - val_loss: 0.32
...
Epoch 23/100: loss: 0.05 - val_loss: 0.18
Epoch 24/100: loss: 0.04 - val_loss: 0.19
Epoch 25/100: loss: 0.03 - val_loss: 0.20
Early stopping triggered. Restoring weights from epoch 23.
실제 학습 Epoch: 25

PyTorch 구현:

best_loss = float('inf')
patience = 5
counter = 0

for epoch in range(100):
    # 학습
    train_loss = train_one_epoch()
    val_loss = validate()

    # Early Stopping 체크
    if val_loss = patience:
            print(f"Early stopping at epoch {epoch}")
            model.load_state_dict(torch.load('best_model.pth'))
            break

4-3. 드롭아웃 vs 조기종료 비교

항목드롭아웃 (Dropout)조기종료 (Early Stopping)
방법뉴런 랜덤 제거학습 시점 제어
적용 시기학습 중 매 배치학습 전체
장점앙상블 효과, 강력한 정규화시간 절약, 간단
단점학습 시간 증가Patience 조정 필요
추천 사용딥러닝 (은닉층 많음)모든 모델
하이퍼파라미터Dropout 비율 (0.2~0.5)Patience (3~10)

5. 실전 과적합 방지 전략

5-1. 단계별 체크리스트

1단계: 데이터 확보

  • [ ] 파라미터 1개당 데이터 최소 10개
  • [ ] Training/Validation/Test 분할 (60/20/20)
  • [ ] 데이터 증강 (이미지의 경우)

2단계: 모델 선택

  • [ ] 간단한 모델부터 시작 (Linear → Tree → Neural Network)
  • [ ] Baseline 모델 성능 확인

3단계: 정규화 적용

  • [ ] L2 정규화 (λ=0.01부터 시작)
  • [ ] Feature 많으면 L1 고려

4단계: 드롭아웃 (딥러닝)

  • [ ] 은닉층에 Dropout 0.5 적용
  • [ ] 출력층 직전은 Dropout 0.2~0.3

5단계: 조기종료

  • [ ] Early Stopping (patience=5)
  • [ ] 최적 가중치 자동 저장

6단계: 평가

  • [ ] Training vs Test 성능 비교
  • [ ] Gap

5-2. 과적합 방지 기법 조합

상황 1: 데이터 부족 (

model = Ridge(alpha=0.1)  # L2 정규화
model.fit(X_train, y_train)

상황 2: 딥러닝 (은닉층 많음)

model = Sequential([
    Dense(128, activation='relu'),
    Dropout(0.5),           # 드롭아웃
    Dense(64, activation='relu'),
    Dropout(0.3),
    Dense(10, activation='softmax')
])

model.compile(
    optimizer='adam',
    loss='categorical_crossentropy',
    metrics=['accuracy']
)

early_stop = EarlyStopping(patience=5)  # 조기종료

model.fit(X_train, y_train,
         validation_split=0.2,
         epochs=100,
         callbacks=[early_stop])

상황 3: Feature 많음 (> 100개)

from sklearn.linear_model import Lasso

model = Lasso(alpha=0.01)  # L1 정규화 (Feature Selection)
model.fit(X_train, y_train)

# 중요한 Feature만 선택
important_features = X.columns[model.coef_ != 0]
print(f"선택된 Feature: {important_features}")

5-3. 하이퍼파라미터 튜닝 가이드

하이퍼파라미터권장 범위조정 방향
L1/L2 α0.0001 ~ 1.0과적합 심하면 ↑
Dropout 비율0.2 ~ 0.5과적합 심하면 ↑
Early Stop Patience3 ~ 10데이터 많으면 ↑
Learning Rate0.0001 ~ 0.1과적합이면 ↓

5-4. 실전 예제: Kaggle Titanic

과적합 버전:

from sklearn.ensemble import RandomForestClassifier

# 과도하게 복잡한 모델
model = RandomForestClassifier(
    n_estimators=1000,
    max_depth=None,  # 제한 없음
    min_samples_split=2
)
model.fit(X_train, y_train)

train_acc = model.score(X_train, y_train)
test_acc = model.score(X_test, y_test)

print(f"Train: {train_acc:.2%}, Test: {test_acc:.2%}")
# Train: 100.00%, Test: 72.00% (과적합!)

최적화 버전:

# 정규화된 모델
model = RandomForestClassifier(
    n_estimators=100,
    max_depth=5,             # 깊이 제한
    min_samples_split=10,    # 분할 제한
    min_samples_leaf=5,      # 리프 크기 제한
    max_features='sqrt'      # Feature 제한
)
model.fit(X_train, y_train)

train_acc = model.score(X_train, y_train)
test_acc = model.score(X_test, y_test)

print(f"Train: {train_acc:.2%}, Test: {test_acc:.2%}")
# Train: 85.00%, Test: 82.00% (적절!)

성능 향상: Test 72% → 82% (+10%p)


FAQ: 초보자가 자주 묻는 질문

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

A. 과적합이 더 위험합니다. 과소적합은 “학습이 부족”하다는 것을 바로 알 수 있지만, 과적합은 Training에서 99% 정확하여 “잘 학습되었다”고 착각하게 만듭니다. 실전에서 65%로 폭락할 때 뒤늦게 발견합니다.

Q2. L1과 L2 중 뭘 써야 하나요?

A. 일반적으로 L2 (Ridge)를 먼저 시도하세요. L2가 성능이 더 좋고 안정적입니다. Feature가 100개 이상으로 많으면 L1 (Lasso)로 Feature Selection하세요. 최고 성능은 Elastic Net (L1+L2).

Q3. Dropout 비율은 어떻게 정하나요?

A. 기본값 0.5부터 시작하세요. 과적합이 심하면 0.6~0.7로 증가, 과소적합이면 0.3~0.4로 감소. 출력층 직전은 0.2~0.3 권장 (너무 높으면 정보 손실).

Q4. Early Stopping의 Patience는 몇이 적당한가요?

A. 데이터 크기에 비례: 데이터 100,000개: patience=10~20. 너무 작으면 조기 중단, 너무 크면 과적합 위험.

Q5. 정규화, 드롭아웃, 조기종료를 동시에 사용해도 되나요?

A. Yes! 오히려 함께 사용하면 더 효과적입니다. L2 정규화 + Dropout + Early Stopping 조합이 실무 표준입니다. 단, λ와 Dropout 비율을 동시에 너무 크게 하면 과소적합 위험이 있으니 조심하세요.


외부 참고 자료

과적합과 정규화를 더 깊게 배우고 싶다면:


정리: 이 글에서 배운 것

과적합: Training 99%, Test 65% → 일반화 실패
과소적합: Training 60%, Test 58% → 학습 부족
L1 정규화: 가중치 일부를 0으로, Feature Selection
L2 정규화: 가중치를 작게, 일반적으로 더 좋음
드롭아웃: 뉴런 50% 랜덤 제거, 앙상블 효과
조기종료: Val Loss 개선 없으면 중단, 시간 절약

다음 편에서는 “딥러닝의 핵심 – CNN과 RNN”에 대해 자세히 알아봅니다. 특히 합성곱 신경망(CNN), 순환 신경망(RNN), LSTM을 완벽 설명하겠습니다.


내부 참고 자료


답글 남기기

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