[학습 일기 #35] 딥러닝 3일차: 과적합(Overfitting) 잡는 법

Ariel_Jeong·2026년 3월 2일

[학습 일기 시리즈]

목록 보기
36/44

1. 과적합(Overfitting)이란?

패턴을 배우는 게 아니라, 노이즈까지 외워버린 상태


1.1. 왜 과적합이 생기나? - Bias–Variance Tradeoff

  • Bias(편향): 모델이 너무 단순할 때

    • 패턴을 충분히 못 담음 → 과소적합(Underfitting)
  • Variance(분산): 모델이 너무 복잡할 때

    • 데이터의 작은 흔들림(노이즈)에도 민감 → 과적합(Overfitting)
  • 핵심:

    • 모델을 복잡하게 하면 Bias는 줄어들 수 있지만 Variance가 커질 수 있음
    • 딱 적당한 복잡도에서 Total Error가 최소가 되는 지점을 찾는 게 목표

1.2. 과적합 감지: 학습 곡선(Learning Curve)으로 잡아내기

가장 현실적인 감지는 loss 추이(학습 손실 vs 검증 손실) 를 보는 것이다.

  • 정상적인 학습

    • train loss: 감소 ↓
    • val loss: 감소 ↓ (train과 비슷한 추세)
  • 과적합이 시작될 때

    • train loss: 계속 감소 ↓
    • val loss: 어느 순간부터 증가 ↑
      → 이 순간부터 모델은 “외우기 모드” 로 들어갔을 가능성이 높다

1.3. 과적합 해결 전략

1.3.1. 데이터 더 모으기

  • 가장 확실하지만 비용이 큼(시간/돈).

1.3.2. 데이터 증강(Data Augmentation)

  • 특히 이미지 분야에서 효과가 강력
  • 학습 데이터를 인위적으로 변형해서 “다양한 상황을 본 것처럼” 만들어 외우기 어렵게 만든다.
  • 대표 이미지 증강 예시
    • 좌우 반전(Horizontal Flip)
    • 회전(Rotation)
    • 랜덤 크롭(Crop)
    • 밝기/대비/채도 조절(ColorJitter)
    • 블러(Blur)
    • 노이즈 추가(Noise)

⚠️ 헬스케어 데이터 증강에서 주의

  • “의미가 바뀌는 변형”은 피해야 함
    예: 특정 질환에서 방향 정보가 중요한데 무작정 flip을 하면 라벨 의미가 흔들릴 수 있음

PyTorch 예시 코드(이미지 증강)

from torchvision import transforms

train_transform = transforms.Compose([
    transforms.RandomHorizontalFlip(p=0.5),
    transforms.RandomRotation(10),
    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]),
])

1.3.3. 정규화(Regularization)

  • 모델에 제약(constraint) 을 추가해서 과적합을 줄이는 방법

(자세한 내용은 아래에 기술)




2. 정규화(Regularization)

2.1. Weight Decay (L2 Regularization)

가중치가 너무 커지면 패턴을 “과하게” 확신하는 방향으로 갈 수 있기 때문에
손실함수에 페널티를 추가하는 아이디어이다.

왜 효과가 있나?

  • 특정 피처에만 “과몰입”해서 가중치가 커지는 걸 억제
  • 전체적으로 고르게, 부드럽게 학습하게 유도
    → 일반화 성능에 도움

PyTorch 구현 1) 옵티마이저에 설정(가장 흔함)

optimizer = torch.optim.Adam(model.parameters(), lr=0.001, weight_decay=0.01)

PyTorch 구현 2) 손실에 직접 더하기

l2_lambda = 0.01
l2_reg = torch.tensor(0., device=device)

for p in model.parameters():
    l2_reg += torch.norm(p, 2) ** 2

loss = criterion(pred, y) + l2_lambda * l2_reg

2.2. Dropout

아이디어

  • 학습 중에 랜덤하게 뉴런을 꺼버림(출력을 0으로)
    → 특정 뉴런 조합에 의존하는 “협업(공모)”을 막는다.

작동 원리

  • 학습 시: 각 뉴런을 확률 𝑝로 비활성화
  • 추론 시: Dropout 사용 X (전체 뉴런 사용)

왜 효과가 있나?

  • Co-adaptation 방지: “너가 있어야만 작동” 같은 의존 관계를 끊음
  • 앙상블 효과: 작은 서브 네트워크들을 여러 개 학습한 것과 비슷
  • 노이즈 주입: 데이터가 조금 흔들려도 버티는 모델이 됨

train / eval이 중요한 이유

  • Dropout은 model.train() / model.eval()에 따라 동작이 바뀐다.
# 학습
model.train()     # dropout ON
out = model(x)

# 추론/평가
model.eval()      # dropout OFF
with torch.no_grad():
    out = model(x)

Dropout 비율 가이드(예시)

  • 입력층: 0.1 ~ 0.2 (정보 손실 방지)
  • 은닉층: 0.4 ~ 0.6 (일반적 범위)
  • 출력층 근처: 0.0 ~ 0.2 (너무 높이면 성능 크게 하락)

2.3. Early Stopping: 과적합 시작 전에 멈추기

핵심 개념: patience

  • “몇 번까지는 개선이 없어도 기다려준다”는 횟수
    • 예: patience=10이면 10 epoch 동안 개선 없으면 stop

동작 흐름

1) 매 epoch마다 val loss 계산
2) 최저 val loss 갱신하면 best model 저장
3) 갱신 못하면 counter 증가
4) counter가 patience를 넘으면 종료


2.4. Batch Normalization(BatchNorm)

  • BatchNorm은 “정규화”이기도 하고 “학습 안정화 장치”이기도 하다.
  • 핵심은 각 층의 입력 분포를 안정적으로 유지시키는 것.

BatchNorm 수식(2단계)

Step 1: 정규화

Step 2: 스케일/시프트(학습 가능한 파라미터)

BatchNorm의 장점

  • 더 빠른 학습(더 큰 학습률도 가능해지는 경우가 많음)
  • 정규화 효과(미니배치 기반 노이즈로 과적합 완화)
  • 초기화에 덜 민감(학습이 안정적)
  • Gradient flow 개선(깊은 네트워크에서도 학습이 수월)

학습 vs 추론에서 다르게 동작하는 이유

  • BatchNorm은 학습 중에는 미니배치 통계(평균/분산) 를 쓰고,
    추론 때는 학습 중 축적한 running mean/var를 사용한다.

  • Training:

    • 미니배치 평균/분산 사용
    • running mean/var 업데이트
  • Inference:

    • running mean/var 사용(미니배치에 의존하지 않음)

그래서 model.eval()을 안 하면 추론 결과가 흔들릴 수 있다.

BatchNorm은 어디에 넣나? (추천 순서)

  • MLP: Linear → BatchNorm → ReLU → Dropout

  • CNN: Conv → BatchNorm → ReLU → Pooling

  • 포인트: BatchNorm과 Dropout을 같이 쓸 때는 순서가 중요(보통 BN 먼저)


2.5. 다른 정규화 기법들: LayerNorm / InstanceNorm / GroupNorm

2.5.1. Layer Normalization

  • 정규화 축: 특성(feature) 축
  • 주 사용처: Transformer, RNN
  • 장점: 배치 크기에 영향 적음
nn.LayerNorm(normalized_shape)

2.5.2. Instance Normalization

  • 정규화 축: 샘플(이미지)별, 채널별 독립
  • 주 사용처: 스타일 변환(Style Transfer)
  • 특징: 스타일 정보를 지우는 쪽으로 도움
nn.InstanceNorm2d(num_features)

2.5.3. Group Normalization

  • 정규화 축: 채널을 그룹으로 나눠 정규화
  • 주 사용처: 작은 배치(배치가 작아 BN이 불안정할 때)
nn.GroupNorm(num_groups, num_channels)




[과제 회고]

원래 과제 회고


추가 실험



랜덤 시드를 루프 안에 고정해서 진짜 공정하게 비교하면,
아이러니하게도 베이스라인 모델이 가장 좋게 나옴.

→ 이는 지금 여기서 사용한 데이터셋이 너무 크기가 작아서
과적합을 방지하고자 넣은 기법들이 너무 강하게 작용하여 오히려 모델의 과소적합을 유발했기 때문.

사실상 위의(Weight Decay가 가장 성능이 좋았던) 결과는
운 좋게 초기 가중치가 해당 모델이 학습하기 좋도록 부여되어 나타난 결과인 셈.

  • 해당 데이터를 그대로 사용하면서
  • 공정한 비교를 위해 루프안에 랜덤시드를 고정하면서
  • 과적합 방지 기법의 힘을 확인하려면
    → 지금 상태에서는 규제 강도를 좀 낮춰야 함.

또한 아래의 베이스라인의 그래프에서 train data는 Accuracy는 상승하지만,
validation의 Accuracy는 정체될 뿐 하락하지는 않는다.


Loss 그래프를 통해 과적합 상태라고 판단이 되나 이는 사실상 필요한 과적합 상태일수도 있다는 것.

이런 경우 강제로 규제를 걸어 Train 성능을 억제하면, Val 성능이 같이 따라 내려가는 Underfitting(과소적합) 영역으로 들어서게 된다.

실험 1. 규제 강도 낮추기



→ Dropout 0.3 아니고 0.1임(오타)

결과:

위의 경우에 비해서 Dropout, Weight Decay모델의 성능이 올라가긴 했지만,
여전히 베이스 모델에 비해 낮다

실험2. 레이어 추가하여 모델 복잡도 늘리고 규제 낮추기


→ 이 실험 역시 Dropout은 0.1로 진행했음

결과:

Dropout 모델이 가장 안정적이고 성능도 높게 나타났다.

종합적인 고찰:

  • 데이터의 특성에 따라 최적의 Architecture를 디자인하는 것이 매우 중요하다.
  • 초기 가중치(시작점)이 모델의 성능에 미치는 영향이 제일 크다

고려하면 좋을 추가 옵션

  • 시드 앙상블 (Seed Ensemble): 최적의 시드 하나를 고르는 대신, 성능이 괜찮게 나오는 서로 다른 시드의 모델 3~5개를 만든 뒤 그 결과값들을 평균(Soft Voting) 내는 방법. (단일 시드 모델보다 훨씬 강력하고 안정적인 성능을 낼 가능성 있음)
  • 성능의 분산(Variance) 확인: 하이퍼파라미터 고정 후 시드를 5개 정도 바꿔가며 돌렸을 때, 성능이 일정하게 유지되는 모델이 진짜로 '아키텍처 디자인이 잘 된 모델'
profile
R&D 분야의 경험을 토대로 커리어 확장에 도전중인 개발꿈나무입니다.

0개의 댓글