끄적거림

Dropout 고찰: output에 scaling하는 이유(Dropout 구현) 본문

Python/pytorch

Dropout 고찰: output에 scaling하는 이유(Dropout 구현)

Signing 2021. 4. 6. 17:24
728x90
반응형

베이지안 뉴럴넷을 다루면서 dropout에 대해 깊이 알고자 하여 이거저거를 찾아 보았고 그 과정을 좀 끄적여보고자 한다.

 


What Dropout?

그림1. dropout 그림(출처: Srivastava, Nitish, et al. ”Dropout: a simple way to prevent neural networks from overfitting”, JMLR 2014)

가장 기본적인 뉴럴넷은 보이는 (a)그림으로 생각해볼 수 있다.

모든 노드들에 모두 fully connected되어 있다.

반면 dropout이 걸린 뉴럴넷은 (b) 그림과 같다.

몇몇 노드들이 drop되어 남아있는 노드들끼리만 연결되어 있는 것을 볼 수 있다.

이런식으로 fully connected된 node들중 랜덤하게 일부를 버림으로써 노드의 수를 줄이고 더 간결하게 뉴럴넷 모형을 만드는 것을 dropout이라고 부른다.

이 dropout 방법론은 학습에만 사용하고 test할 때는 fully connected된 모형을 사용한다.

 

 


 

 

Why Dropout?

일단 가장 기본적으로 dropout이 왜 쓰이는지부터 살펴보면 크게 3가지 이유에서의 접근이 있다.

 

1. regularization(overfitting 예방)

regularization이라고 하는 것은 많이 들어봤겠지만, weigth들에 패널티를 주어 weight들이 과적합되는 것을 방지하는 목적으로 사용되는 것이다.

가장 기본적인 regularization은 아래와 같은 L1, L2 regularization이 있다.

 

L1 cost function(Lasso) = MSE loss + sum(|weight|)

L2 cost function(Ridge) = MSE loss + sum((weigth)^2)

 

원래는 수식으로 설명되어야하는 것이 맞지만, 간단히 표시하여 생각해보면 위와 같은 표현으로 쓸 수 있다.

원래의 기본적인 cost function인 MSE loss에 weight에 대한 항을 추가함으로써, weight들이 과하게 data에 fitting 되는 것을 방지하는 것이다.

 

그러면 dropout은 어떤식으로 regularization을 작동하게 할까?

위의 그림1.dropout 그림에서 알 수 있듯이 random하게 일부 노드들을 제거함으로써 모델이 고도화 되는 것을 막는다.

이 것은 overfitting을 예방하는 차원에서 regularization이라고 볼 수 있다.

 

 

 

 

2. Emsemble 효과

그림2. 앙상블 모형(출처: https://www.kdnuggets.com/2019/09/ensemble-learning.html)

앙상블 모형은 여러 모델들로 학습을 진행하여 각 모델들이 학습한 결과를 종합하여 task를 진행하는 모형을 말한다.

 

dropout은 학습시 한 번만 적용하는 것이 아니라, 여러번의 mini-batch를 거치면서 random하게 노드들을 버림으로써 구현된다.

이 말은 전체 학습 데이터 셋을 한 번에 학습시키기 어려우니 몇개의 작은 데이터셋 단위인 mini-batch로 나누고, 그 mini-batch마다 학습을 진행할 때 매번 다른 노드들이 선택되어 학습이 이루어진다는 것이다.

이것은 매 학습(mini-batch, epoch)마다 dropout에 의해 매번 다른 모델들이 생성됨을 의미한다.

매번 다른 모델들로 학습을 진행하고 그 결과를 반영하여 학습이 이루어지니까 이를 앙상블 모형이라 부를 수 있겠다.

실제로 dropout을 사용하면 성능이 높아지는 현상을 볼 수 있는데 이것이 앙상블 효과 때문이라고 봐도 무방하다.

 

이러한 일련의 과정을 몬테카를로라고도 부를 수 있으며, 줄여서 MC-dropout이라고도 한다.

 

 

3. 낮은 모델 복잡도

random하게 노드들이 선택되니까 모형 자체의 복잡도가 낮아지는 역할을 한다.

하지만 그렇다고 학습이 빠르게 이루지는 것은 아니다.

사실 전체 학습 시간만 두고 보았을 때, 여러 번의 dropout을 걸어야하므로 어찌보면 더 긴 시간이 필요할 수도 있다.

이 세번째 사실은 좋은점이라기보단 특징이라고 생각하면 좋을 것 같다.

 

 

 


 

 

How Dropout?

베르누이 분포

dropout의 원리는 각 layer에 있는 노드(unit)들에 베르누이 분포를 따르는 변수를 곱하여 각 노드들 random하게 선택하는 것이다.

여기서 베르누이 분포는 0과 1, 혹은 성공과 실패 로 구성된 요소들을 일정 확률 p에 따라 random하게 고르는 시행이라고 볼 수 있다.

예를 들면, 동전을 던지는 행위가 베르누이 분포를 따른다고 볼 수 있다.

이러한 베르누이 분포를 각 노드들에 곱해주면, 일정 확률 p에 따라 random하게 일부는 0, 나머지는 1이 나올것이다.

0이 나온 확률변수가 곱해진 노드는 자연스럽게 weight가 0이 되어 사라지는 효과를 볼 수 있다.

그림3. 베르누이 분포를 이용한 dropout(출처: https://towardsdatascience.com/simplified-math-behind-dropout-in-deep-learning-6d50f3f47275)

 

 

 


 

 

Dropout 구현

그러면 Dropout을 python 코드로 구현해보도록 하자.

class MyDropout(nn.Module):
  def __init__(self, p=0.5):
    super(MyDropout, self).__init__()
    self.p = p
    # multiplier is 1/(1-p)
    # Set multiplier to 0 when p = 1 to avoid error
    if self.p < 1:
      self.multiplier_ = 1.0 / (1.0 - p)
    else:
      self.multiplier_ = 0.0
    
  def forward(self, input):
    # if model.eval(), don't apply dropout
    if not self.training:
      return input
    
    # So that we have `input.shape` numbers of Bernoulli(1-p) samples
    # --> input의 데이터 사이즈를 고려하여 베르누이(1-p)의 샘플을 만들기 위한 과정
    # --> 0~1사이의 난수를 발생시켜 지정 확률 p보다 큰지 작은지를 boolean으로 설정 
    selected_ = torch.Tensor(input.shape).uniform_(0, 1) > self.p
    
    # To suppert both CPU and GPU
    # --> boolean 값을 0, 1로 바꾸는 작업
    if input.is_cuda:
      selected_ = Variable(selected_.type(torch.cuda.FloatTensor), requires_grad = False)
    else:
      selected_ = Variable(selected_.type(torch.FloatTensor), requires_grad = False)

    # Multiply output by multiplier as described in the paper [1]
    # --> 0,1의 값들을 곱하여 선택된 input을 골라내는 과정
    res = torch.mul(selected_, input)*self.multiplier_
    return res

MyDropout이라는 class를 만들어 구현해 보았다.

위의 코드에 대해 설명할 몇 가지가 있다.

  • multiplier_
    • multiplier_라는 변수가 존재한다.
    • 설명에는 1/(1-p)의 값을 가지며, 이 것은 p가 1 이상일 경우의 error를 피하기 위해서라고 나와있다.
    • 그리고 이 multiplier_ 변수는 코드의 맨 마지막에 사용된다.
    • 1/(1-p)는 p가 1보다 작은 값이므로, 결과적으로 1보다 큰 값을 곱해주는 효과를 보여준다.
    • 테스트시에는 학습때보다 예상 출력이 더 커질것이다. 왜냐하면 학습할 때에는 0으로 셋팅된 노드가 있기 때문에 weight들이 Fully connected 될 때보다 더 적은 weight들로 propagation을 해야하기 때문에 더 큰 수로 수렴하기 때문이다.
    • 하지만, dropout을 거치고 나면 각 노드에서 drop된 노드를 제외하면 input과 output이 일치하는 identity function이어야한다.
    • 그러므로 dropout output에 1/(1-p)를 곱함으로써 scaling해준다.
  • self.training
    • 실제로 학습할 때, model.train( ) 이라는 statement를 사용하는데, 이것은 모델이 학습중임을 알리는 것으로 model 안에 있는 model.training이라는 멤버변수가 True가 됨을 의미한다.
    • 반대로 테스트를 진행할 때는, model.eval( )이라는 statement를 사용하는데, 이것은 모델이 테스트 중임을 알리는 것으로 model.training이라는 멤버변수가 False가 됨을 의미한다.
  • 베르누이 분포
    • 실제 베르누이 분포를 따르는 변수를 만들기보다, 0~1 사이인 uniform 분포를 활용하여 나온 값들이 p보다 큰지 작은지로 베르누이 분포 변수를 만들어낸다.

 

 

 

 

 

 

 

 

 

 

 

 

참고 자료:

zhang-yang.medium.com/scaling-in-neural-network-dropout-layers-with-pytorch-code-example-11436098d426

 

Scaling in Neural Network Dropout Layers (with Pytorch code example)

For several times I get confused over how and why a dropout layer scales its input. I’m writing down some notes before I forget again.

zhang-yang.medium.com

datascience.stackexchange.com/questions/55333/multiply-weights-after-using-dropout-in-training-pytorch

 

Multiply weights after using dropout in training - PyTorch

I have a Pytorch regression model as follows: model = nn.Sequential(nn.Linear(n_in, n_h_1), nn.ReLU(), nn.Dropout(p=0.1), nn.L...

datascience.stackexchange.com

xuwd11.github.io/Dropout_Tutorial_in_PyTorch/

 

Tutorial: Dropout as Regularization and Bayesian Approximation

Dropout as Regularization and Bayesian Approximation

xuwd11.github.io

 

 

 

728x90
반응형
Comments