드롭아웃: 과적합을 막는 앙상블 정규화

2014년 Srivastava et al.이 제안한 드롭아웃(Dropout)의 원리, Inverted Dropout 구현, 훈련/추론 모드 차이, Dropout2d·DropPath 변형, 실무 사용 가이드를 코드와 함께 완전히 이해한다.

· 7 min read · PALDYN Team

지난 글에서 레이어 정규화가 학습 안정화에 기여한다는 것을 배웠다. 신경망의 또 다른 중요한 정규화 기법인 **드롭아웃(Dropout)**을 이번 글에서 다룬다. 2014년 Srivastava et al.이 발표한 드롭아웃은 매우 단순한 아이디어임에도 과적합 방지에 매우 효과적이다. 훈련 시 무작위로 뉴런을 끄는 것만으로 실질적인 앙상블 효과를 낸다.

드롭아웃의 아이디어

드롭아웃의 핵심 아이디어는 단순하다: 훈련 중 각 미니배치마다 뉴런을 확률 p로 무작위로 비활성화(출력을 0으로)한다.

import torch
import torch.nn as nn

# 기본 사용
dropout = nn.Dropout(p=0.5)  # p: 비활성화 확률

x = torch.ones(3, 10)  # 배치 크기 3, 특성 10

# 훈련 모드: p=0.5로 랜덤 마스킹
dropout.train()
out_train = dropout(x)
print(out_train)
# tensor([[0., 2., 0., 2., 0., 2., 0., 2., 0., 2.],
#         [2., 0., 2., 0., 2., 0., 2., 0., 2., 0.],
#         ...])
# → 약 50%가 0, 나머지는 1/(1-0.5)=2로 스케일

# 추론 모드: 모든 뉴런 활성, 스케일 없음
dropout.eval()
out_eval = dropout(x)
print(out_eval)
# tensor([[1., 1., 1., 1., 1., 1., 1., 1., 1., 1.], ...])

Inverted Dropout (역 드롭아웃)

나이브한 드롭아웃은 훈련 시 뉴런을 끄면 추론 시 기대값이 달라지는 문제가 있다. 이를 해결하기 위해 Inverted Dropout을 사용한다: 훈련 시 남겨진 뉴런의 출력을 1/(1-p)로 스케일업하여 기대값을 유지한다.

def inverted_dropout(x, p, training=True):
    if not training or p == 0:
        return x
    # Bernoulli 마스크: p 확률로 0 (비활성화)
    mask = (torch.rand_like(x) > p).float()
    # Inverted: 1/(1-p)로 스케일하여 기대값 유지
    return x * mask / (1.0 - p)

# 기대값 확인
x = torch.ones(10000)
out = inverted_dropout(x, p=0.3, training=True)
print(f"기대값: {out.mean():.4f}")  # ≈ 1.0 (유지!)

# 추론 시 스케일 보정 필요 없음
out_eval = inverted_dropout(x, p=0.3, training=False)
print(f"추론 기대값: {out_eval.mean():.4f}")  # 1.0 (동일)

PyTorch nn.Dropout은 자동으로 Inverted Dropout을 구현한다. 훈련 시 스케일업, 추론 시 통과(no-op).

드롭아웃 메커니즘

앙상블로서의 드롭아웃

드롭아웃을 다른 관점에서 바라볼 수 있다. n개의 뉴런이 있을 때 p=0.5 드롭아웃을 적용하면 이론적으로 2ⁿ가지 서로 다른 서브네트워크가 존재한다. 훈련 중 매 스텝마다 다른 서브네트워크를 학습하는 것은 사실상 수많은 모델을 동시에 앙상블 학습하는 것과 유사하다.

추론 시 모든 뉴런을 사용하는 것은 이 앙상블 모델들의 기하 평균(geometric mean) 예측을 근사하는 것이다.

실전 구현: MLP with Dropout

import torch.nn as nn

class MLPWithDropout(nn.Module):
    def __init__(self, in_dim, hidden_dims, out_dim,
                 dropout_p=0.5):
        super().__init__()
        layers = []
        prev_dim = in_dim
        for h_dim in hidden_dims:
            layers.extend([
                nn.Linear(prev_dim, h_dim),
                nn.BatchNorm1d(h_dim),
                nn.ReLU(),
                nn.Dropout(p=dropout_p),
            ])
            prev_dim = h_dim
        # 출력층에는 드롭아웃 적용하지 않음
        layers.append(nn.Linear(prev_dim, out_dim))
        self.net = nn.Sequential(*layers)

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

model = MLPWithDropout(784, [512, 256, 128], 10, dropout_p=0.4)

# 훈련 루프
model.train()
optimizer = torch.optim.Adam(model.parameters())
criterion = nn.CrossEntropyLoss()

# 평가 루프 — model.eval() 필수!
model.eval()
with torch.no_grad():
    preds = model(X_test)

Monte Carlo Dropout: 추론 시 불확실성 추정

드롭아웃을 추론 시에도 활성화하면 베이지안 추론의 근사로 사용할 수 있다. 동일한 입력에 여러 번 순전파하여 예측의 분산을 측정한다.

def mc_dropout_predict(model, x, n_samples=50):
    model.train()  # 드롭아웃 활성화 (추론 시에도)
    preds = []
    with torch.no_grad():
        for _ in range(n_samples):
            preds.append(torch.softmax(model(x), dim=-1))
    preds = torch.stack(preds)  # (n_samples, B, C)
    mean = preds.mean(0)        # 평균 예측
    std  = preds.std(0)         # 불확실성 (표준편차)
    return mean, std

# 불확실성이 높은 샘플 탐지
mean_pred, uncertainty = mc_dropout_predict(model, X_test[:10])
print(f"예측 불확실성: {uncertainty.max(1).values}")
# 높은 값 → 모델이 확신하지 못하는 샘플

드롭아웃 코드 구현

드롭아웃 변형들

# Dropout2d: CNN에서 채널 단위로 비활성화
# (공간 전체 채널을 끔 — 특성 맵 단위 정규화)
conv_dropout = nn.Dropout2d(p=0.2)
feat_map = torch.randn(8, 64, 16, 16)  # (B, C, H, W)
out = conv_dropout(feat_map)

# DropPath (Stochastic Depth): 잔차 경로 단위로 끔
# — 샘플별로 전체 블록을 스킵 (ViT, DeiT에서 사용)
import torch

def drop_path(x, drop_prob, training):
    if not training or drop_prob == 0:
        return x
    keep = 1 - drop_prob
    shape = (x.shape[0],) + (1,) * (x.ndim - 1)
    mask = x.new_empty(shape).bernoulli_(keep) / keep
    return x * mask

p 값 선택 가이드

적용 위치권장 p이유
FC 은닉층 (MLP)0.4~0.5과적합 강하게 방지
CNN 마지막 FC0.3~0.5과적합 방지
CNN 컨볼루션 후0.1~0.2낮게 (공간 의존성)
트랜스포머 FFN0.1~0.2너무 크면 성능 저하
DropPath (ViT)0.0~0.2모델 깊이에 따라
출력층0 (적용 안 함)마지막 예측에 노이즈 금지

드롭아웃은 단순하고 효과적이지만, 배치 정규화와 함께 사용할 때 주의가 필요하다. 둘 다 훈련/추론 모드에 따라 다르게 동작하므로 반드시 model.train()model.eval()을 적절히 전환해야 한다.


지난 글: 레이어 정규화: 트랜스포머가 선택한 정규화

다음 글: 기울기 소실과 폭발: 깊은 네트워크의 고질적 문제


읽어주셔서 감사합니다. 😊