들어가며
딥러닝 모델을 학습시키다 보면 훈련 데이터에서는 성능이 좋은데 실제 데이터에서는 성능이 떨어지는 현상을 자주 만난다. 이게 바로 과적합이다. 웹 개발로 비유하면 특정 브라우저에서만 완벽하게 동작하는 코드를 짠 것과 비슷하다. 범용적으로 동작해야 하는데 특정 케이스에만 최적화된 것이다. 과적합을 이해하고 해결하는 방법을 정리해둔다.
과적합 (Overfitting)이란?
모델이 훈련 데이터를 너무 잘 외워서 새로운 데이터에 일반화하지 못하는 현상이다.
과적합 신호:
- 훈련 손실은 계속 감소
- 검증 손실은 어느 순간부터 증가
- 훈련 정확도 >> 검증 정확도
과적합 vs 과소적합
훈련 손실 검증 손실 상태
과소적합 (Underfitting): 높음 높음 모델이 너무 단순
적절한 적합 (Good fit): 낮음 낮음 이상적
과적합 (Overfitting): 매우 낮음 높음 모델이 너무 복잡
과적합 원인
1. 모델이 너무 복잡 (파라미터 수가 데이터보다 훨씬 많음)
2. 훈련 데이터가 너무 적음
3. 훈련을 너무 오래 함 (에폭 수가 많음)
4. 데이터 다양성 부족 (특정 패턴만 있는 데이터)
해결 방법 1: 데이터 증강 (Data Augmentation)
훈련 데이터를 인위적으로 늘리는 방법이다. 이미지 분류에서 가장 효과적이다.
import torchvision.transforms as transforms
# 이미지 데이터 증강
train_transform = transforms.Compose([
transforms.RandomHorizontalFlip(p=0.5), # 50% 확률로 좌우 반전
transforms.RandomRotation(degrees=15), # ±15도 회전
transforms.RandomCrop(224, padding=4), # 랜덤 크롭
transforms.ColorJitter(
brightness=0.2, contrast=0.2, saturation=0.2
), # 밝기/대비/채도 조정
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225])
])
# 테스트 시에는 증강 없이
test_transform = transforms.Compose([
transforms.Resize(256),
transforms.CenterCrop(224),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225])
])
해결 방법 2: 드롭아웃 (Dropout)
학습 중에 랜덤하게 일부 뉴런을 비활성화하는 방법이다.
def dropout(x, p=0.5, training=True):
if not training:
return x # 추론 시에는 드롭아웃 적용 안 함
# p 확률로 뉴런을 0으로 만들고 1/(1-p)로 스케일링
mask = np.random.binomial(1, 1 - p, size=x.shape)
return x * mask / (1 - p)
x = np.array([1.0, 2.0, 3.0, 4.0, 5.0])
print(f"원본: {x}")
print(f"드롭아웃 p=0.5: {dropout(x, p=0.5).round(2)}")
print(f"추론 시: {dropout(x, p=0.5, training=False)}")
PyTorch에서 드롭아웃
import torch.nn as nn
class ModelWithDropout(nn.Module):
def __init__(self, dropout_rate=0.5):
super().__init__()
self.network = nn.Sequential(
nn.Linear(784, 512),
nn.ReLU(),
nn.Dropout(p=dropout_rate), # 드롭아웃 추가
nn.Linear(512, 256),
nn.ReLU(),
nn.Dropout(p=dropout_rate), # 드롭아웃 추가
nn.Linear(256, 10)
)
def forward(self, x):
return self.network(x)
model = ModelWithDropout(dropout_rate=0.5)
model.train() # 학습 모드: 드롭아웃 활성화
model.eval() # 추론 모드: 드롭아웃 비활성화 (필수!)
드롭아웃 비율 설정:
- FC 레이어: 0.5
- Conv 레이어: 0.1 ~ 0.3
- Transformer: 0.1
- 너무 크면 과소적합 위험
드롭아웃으로 학습하면 매번 다른 뉴런 조합이 학습되어 마치 여러 모델의 앙상블 효과를 낸다. 각 뉴런이 독립적으로 유용한 특성을 학습하게 된다.
해결 방법 3: L1/L2 정규화 (Weight Decay)
손실 함수에 가중치 크기에 대한 패널티를 추가한다.
# L2 정규화: Loss_total = Loss + λ * Σ w²
# L1 정규화: Loss_total = Loss + λ * Σ |w|
# PyTorch에서 L2 정규화: weight_decay 파라미터
optimizer = torch.optim.Adam(
model.parameters(),
lr=0.001,
weight_decay=1e-4 # L2 정규화 강도
)
optimizer_sgd = torch.optim.SGD(
model.parameters(),
lr=0.01,
momentum=0.9,
weight_decay=1e-4
)
L1 정규화:
- 일부 가중치를 정확히 0으로 만듦 (희소성)
- 중요하지 않은 특성 제거에 효과적
L2 정규화:
- 가중치를 0에 가깝게 만들지만 정확히 0은 안 됨
- 딥러닝에서 더 일반적으로 사용
weight_decay 일반적인 값:
- 0.01 (강한 정규화)
- 0.001 (중간)
- 0.0001 (약한 정규화, 자주 쓰임)
해결 방법 4: 배치 정규화 (Batch Normalization)
각 레이어의 입력을 정규화해서 학습을 안정화하는 방법이다.
def batch_norm_manual(x, gamma, beta, epsilon=1e-5):
mean = x.mean(axis=0)
var = x.var(axis=0)
x_norm = (x - mean) / np.sqrt(var + epsilon)
return gamma * x_norm + beta # 스케일 & 이동 (학습 가능)
x = np.random.randn(4, 3) * 5 + 10
gamma = np.ones(3)
beta = np.zeros(3)
out = batch_norm_manual(x, gamma, beta)
print(f"입력 평균: {x.mean(axis=0).round(2)}") # ≈ [10, 10, 10]
print(f"출력 평균: {out.mean(axis=0).round(4)}") # ≈ [0, 0, 0]
print(f"출력 분산: {out.var(axis=0).round(4)}") # ≈ [1, 1, 1]
PyTorch에서 배치 정규화
# FC 레이어
model_bn = nn.Sequential(
nn.Linear(784, 512),
nn.BatchNorm1d(512), # 배치 정규화
nn.ReLU(),
nn.Linear(512, 256),
nn.BatchNorm1d(256),
nn.ReLU(),
nn.Linear(256, 10)
)
# CNN 레이어
cnn_model = nn.Sequential(
nn.Conv2d(3, 64, kernel_size=3, padding=1),
nn.BatchNorm2d(64), # 채널별 배치 정규화
nn.ReLU(),
)
배치 정규화 효과:
1. 각 레이어 입력을 정규화 → 학습 안정
2. 더 큰 학습률 사용 가능 → 빠른 학습
3. 가중치 초기화에 덜 민감
4. 학습 속도 2~10배 향상
주의사항:
- 배치 크기가 너무 작으면 (1~4) 효과 없음
- 작은 배치에는 LayerNorm, GroupNorm 사용
- model.train() / model.eval() 필수
해결 방법 5: 조기 종료 (Early Stopping)
검증 손실이 더 이상 개선되지 않으면 학습을 중단한다.
class EarlyStopping:
def __init__(self, patience=10, min_delta=0.001):
self.patience = patience
self.min_delta = min_delta
self.counter = 0
self.best_loss = float('inf')
self.early_stop = False
self.best_model_state = None
def __call__(self, val_loss, model):
if val_loss < self.best_loss - self.min_delta:
self.best_loss = val_loss
self.counter = 0
self.best_model_state = {
k: v.clone() for k, v in model.state_dict().items()
}
else:
self.counter += 1
if self.counter >= self.patience:
self.early_stop = True
# 학습 루프에서 사용
early_stopping = EarlyStopping(patience=10)
for epoch in range(200):
train_loss = train_one_epoch(model, optimizer, criterion)
val_loss = validate(model, criterion)
early_stopping(val_loss, model)
if early_stopping.early_stop:
print(f"Early stopping at epoch {epoch}")
model.load_state_dict(early_stopping.best_model_state)
break
해결 방법 6: Layer Normalization
배치 정규화의 대안이다. Transformer에서 주로 사용된다.
# BatchNorm: 배치 방향으로 정규화 (각 특성의 배치 평균/분산)
# LayerNorm: 특성 방향으로 정규화 (각 샘플의 전체 특성 평균/분산)
# LayerNorm은 배치 크기에 독립적 → Transformer에서 적합
layer_norm = nn.LayerNorm(512)
batch_norm = nn.BatchNorm1d(512)
x = torch.randn(4, 512)
out_ln = layer_norm(x)
print(f"LayerNorm 출력 평균: {out_ln[0].mean().item():.4f}") # ≈ 0
print(f"LayerNorm 출력 std: {out_ln[0].std().item():.4f}") # ≈ 1
정규화 기법 사용 위치
BatchNorm1d : 완전연결층 (FC Layer)
BatchNorm2d : CNN 컨볼루션 레이어
LayerNorm : Transformer, RNN, NLP 모델
GroupNorm : 배치 크기 작을 때, 세그멘테이션
InstanceNorm : 이미지 스타일 변환 (Style Transfer)
과적합 방지 체크리스트
1. 훈련/검증 손실 그래프 확인
→ 검증 손실이 올라가는 시점 파악
2. 데이터 확인
→ 훈련 데이터가 충분한가? (최소 1000개 이상)
→ 데이터 증강 적용했는가?
3. 모델 복잡도 줄이기
→ 레이어 수 또는 유닛 수 감소
4. 정규화 추가
→ Dropout (FC: 0.5, Conv: 0.1~0.3)
→ L2 정규화 (weight_decay=1e-4)
→ BatchNorm 추가
5. 조기 종료
→ patience=10~20으로 Early Stopping 적용
6. 학습률 조정
→ 학습률 스케줄러 적용
정리
| 방법 | 설명 | 효과 | 주의사항 |
|---|---|---|---|
| 데이터 증강 | 훈련 데이터 인위적으로 늘림 | 매우 효과적 | 도메인에 맞는 증강 선택 |
| Dropout | 랜덤 뉴런 비활성화 | 효과적 | 추론 시 반드시 eval() |
| L2 정규화 | 가중치 크기 패널티 | 효과적 | weight_decay로 강도 조절 |
| BatchNorm | 레이어 입력 정규화 | 학습 안정화 | 배치 크기 최소 16 이상 |
| Early Stopping | 검증 손실 기준 학습 중단 | 간단하고 효과적 | patience 적절히 설정 |
| LayerNorm | 샘플 단위 정규화 | Transformer에 적합 | 배치 크기 무관 |
'딥러닝 > 딥러닝 기초 개념' 카테고리의 다른 글
| 손실 함수 (Loss Function) (0) | 2026.05.15 |
|---|---|
| 경사하강법과 최적화 (Gradient Descent, Adam, SGD) (0) | 2026.05.15 |
| 순전파와 역전파 (Feedforward & Backpropagation) (0) | 2026.05.15 |
| 신경망이란? (퍼셉트론, 활성화 함수) (0) | 2026.05.13 |