Dropout 이해하기

Dropout을 공부하면서 헷갈렸던 내용들을 정리해보았다. 일반화 성능을 높이는 핵심적인 요소인 마스킹과, 그리고 그 마스킹에 걸리지 않은 살아남은 값들을 키우는 스케일업. 이 구분을 중심으로 글을 작성했다.

Dropout이란?#

  • Dropout 레이어에 들어온 텐서의 각 원소를 독립적으로 확률 p로 0으로 만든다.
  • 실제로 매 스탭마다 mask Tensor를 만들고, 입력 Tensor x에 곱한 후 스케일업을 진행한다. 복잡한 게 없다.
for step in training:
  mask = (torch.rand_like(x) > p).float()  # 매번 새로 샘플링
  y = x * mask / (1-p)

그리고 PyTorch가 쓰는 방식은 inverted dropout이다. 살아남은 원소를 1/(1-p)로 스케일 업한다(p=0.5면 살아남은 값 ×2). 이렇게 해두면 model.eval()과 torch.no_grad()에서 dropout이 아무것도 하지 않고 입력을 그대로 통과시키는 항등함수가 된다.

헷갈리기 쉬운 건 Dropout은 가중치를 끄는 게 아니라 출력층, 즉 출력 뉴런을 끄는 것이다.

Dropout의 스케일업#

스케일업의 목적은 학습과 평가의 출력 분포를 맞추는 데 있다. 예를 들어서, p=0.5 라고 할때 스케일업이 없다면 학습 때는 평균적으로 절반이 꺼져 다음 레이어로 가는 신호의 합이 절반으로 준다. 반면에 평가 때는 사실상 Dropout이 항등함수가 되어 전부 살게 된다. 이렇게 되면 다음 레이어가 보는 입력 스케일이 학습과 평가에서 큰 차이가 나게 된다.

원래 출력이 x일 때 dropout 후 출력 y의 기댓값을 보면 정확히 맞춰진다.

y = x/(1-p)  (확률 1-p)
y = 0        (확률 p)

E[y] = (1-p)·x/(1-p) + p·0 = x

개별 결과는 0이거나 x/(1-p)라 매번 다르지만 평균은 x로 보존된다. 그래서 학습은 1/(1-p)로 보정하고 평가는 그대로 통과시켜도 양쪽 평균 세기가 같다.

끄는 행위 자체가 정규화#

매 forward마다 활성 뉴런 집합이 달라지므로 매번 다른 케이스가 만들어진다. 이건 버그가 아니라 의도다. 스케일업은 각 forward를 dropout 없는 경우와 똑같이 만드는 게 아니라, 평균만 맞추고 분포는 일부러 흔든다.

이 흔들림이 일반화를 만든다.

  • 공동적응(co-adaptation) 방지: 어떤 뉴런이 특정 다른 뉴런이 항상 켜져 있으리라 기대하고 거기 의존하면 과적합이다. 언제 꺼질지 모르므로 각 뉴런이 독립적으로도 쓸모 있는 특징을 학습하게 된다.
  • 앙상블 근사: 매 스텝 다른 부분망(sub-network)을 학습하고, 평가 때 전체를 쓰는 것은 그 수많은 부분망의 평균을 근사하는 셈이다.

이번 스텝에 꺼진 뉴런으로는 gradient도 흐르지 않는다. backward는 forward에서 쓴 같은 마스크를 그대로 적용하기 때문이다.

forward:  y = mask · x / (1-p)
backward: dL/dx = mask · dL/dy / (1-p)

하지만 문제가 아니다. 다음 스텝에는 마스크가 새로 뽑혀 그 뉴런이 1-p 확률로 살아나 정상적으로 업데이트를 받는다. 긴 학습 과정에서 보면 모든 뉴런이 골고루 학습되고, 매 스텝 일부만 참여할 뿐이다.

레이어를 쌓아도 0은 누적되지 않음#

dropout을 여러 층 거치면 살아남는 비율이 곱해져 다 죽는 게 아닐까 싶지만, 그렇지 않다. 0.5^n은 특정 한 뉴런이 모든 층에서 연속으로 살아남을 확률일 뿐, 전체 활성 개수와는 다른 이야기다. 정보는 한 줄이 아니라 수백 개 뉴런에 분산되어 있다.

핵심은 층 사이의 Linear가 가중합(weighted sum)이라는 데 있다. Linear의 출력 뉴런 하나하나는 입력 전체의 가중합이다.

y₁ = w₁₁·x₁ + w₁₂·x₂ + w₁₃·x₃ + w₁₄·x₄ + b₁

앞 레이어 dropout이 x₃를 0으로 만들어도 x₃ 항만 사라질 뿐 나머지 항은 멀쩡히 남아 y₁은 비-0이다. 즉 0은 그 자리에서만 0이고, Linear를 통과하면 여러 출력에 한 항으로 흡수되며 사라진다. 흐름으로 보면 이렇다.

Linear₁ 출력 → 512개, 대체로 비-0
Dropout     → 절반을 0으로, 나머지 ×2
Linear₂     → 가중합으로 다시 512개 전부 비-0
Dropout     → 또 새로 절반을 0으로

Dropout이 0을 만들면 Linear가 다시 꽉 채우고, 거기에 dropout이 새 마스크로 또 걸린다. 그래서 256→128→64처럼 누적해서 줄어드는 일은 없고, 매 레이어 활성 비율이 일정하게 유지된다.

평균은 보존, 분산은 키우기#

스케일업이 보존하는 것은 평균뿐이다. 에너지, 즉 분산으로 보면 오히려 커진다.

E[y²] = (1-p)·(x/(1-p))² + p·0² = x²/(1-p)

1/(1-p) > 1이므로 E[y²] = x²/(1-p) > x²다. 살아남은 값을 키운 만큼 값들이 더 크게 출렁이게 되어 분산이 증가한다. 즉 dropout은 평균은 그대로 두고 분산을 키우는 노이즈 주입이라고 볼 수 있다.

이 분산 증가도 적절하다면 정규화에 큰 도움을 줄 수 있다.

  • 함수를 매끄럽게 만든다: 입력 표현이 매번 흔들려도 정답을 맞춰야 하므로, 입력이 조금 흔들려도 출력이 망가지지 않는 둔감한 함수를 학습한다. 결정 경계가 매끈해져 train 데이터 한 점에 과하게 붙지 못한다.
  • 암기를 막는다: 표현이 매 스텝 랜덤하게 손상되면 train set을 통째로 외우기 어렵다.
  • 평평한 최소점으로 유도한다: 노이즈가 있으면 좁고 뾰족한 골짜기에 안착하지 못하고 넓고 평평한 골짜기로 가는데, 평평한 최소점이 일반적으로 일반화가 더 좋다.

다만 적당하면 약이고 과하면 독이 될 수 있음에 주의해야 한다.

p→0          → 정규화 효과 없음, 과적합 위험
p≈0.1~0.5    → Sweet stop, 일반화 ↑
p 너무 큼     → 신호가 노이즈에 묻혀 학습 불안정, 과소적합

트랜스포머가 p=0.1 수준으로 낮게, 그것도 군데군데만 거는 건 Sweet stop을 넘기지 않기 위해서다.

dropout의 노이즈는 곱셈형이라 신호에 비례한다. x에 0 또는 1/(1-p)를 곱하므로 활성값이 큰 뉴런일수록 노이즈도 커진다.

결론#

지금까지를 한 줄로 묶으면, dropout 안에는 목적이 다른 두 장치가 들어있다.

하는 일 목적
마스킹 (누가 꺼지나) 매 스텝 부분망을 바꾼다 일반화
스케일업 (×1/(1-p)) 평균 세기를 보존한다 학습/평가 일관성

스케일업을 빼도 마스킹의 정규화 효과는 그대로 작동한다. 다만 학습과 평가의 스케일이 어긋나는 부작용만 생긴다. 그래서 스케일업은 정규화를 위한 게 아니라, 정규화의 부작용인 세기 왜곡을 없애기 위한 보정이다.

정리#

  • Dropout은 가중치가 아니라 출력 뉴런을 끄고, 살아남은 값을 1/(1-p)로 키운다.
  • 스케일업은 평균(E[y]=x)을 보존해 학습과 평가의 신호 세기를 맞추는 보정이다.
  • 일반화 효과는 매 스텝 다른 뉴런이 꺼지는 마스킹에서 나온다.
  • 층 사이 Linear가 가중합이라 0을 다시 채우므로, 여러 층을 쌓아도 활성 비율은 누적해서 줄지 않는다.
  • 스케일업은 평균은 지키지만 분산(E[y²]=x²/(1-p))은 키운다. 이 분산 증가가 곧 노이즈 주입이고 정규화에 도움을 줄 수 있다.

댓글