안 쓰던 블로그

역 드롭아웃 inverted dropout 구현 시 추가 값을 곱하는 이유 본문

머신러닝/머신러닝

역 드롭아웃 inverted dropout 구현 시 추가 값을 곱하는 이유

proqk 2021. 8. 31. 15:26
반응형

dropout을 적용한 forward propagation(순전파)을 계산할 때 주의할 점이 있다.

최종 cost가 dropout을 사용하지 않았을 때와 동일한 값을 유지하도록 마지막에 keep_prob값으로 나눠주는 것이다.

아래 순전파 코드가 있다.

 

# GRADED FUNCTION: forward_propagation_with_dropout

def forward_propagation_with_dropout(X, parameters, keep_prob = 0.5):
    """
    Implements the forward propagation: LINEAR -> RELU + DROPOUT -> LINEAR -> RELU + DROPOUT -> LINEAR -> SIGMOID.
    
    Arguments:
    X -- input dataset, of shape (2, number of examples)
    parameters -- python dictionary containing your parameters "W1", "b1", "W2", "b2", "W3", "b3":
                    W1 -- weight matrix of shape (20, 2)
                    b1 -- bias vector of shape (20, 1)
                    W2 -- weight matrix of shape (3, 20)
                    b2 -- bias vector of shape (3, 1)
                    W3 -- weight matrix of shape (1, 3)
                    b3 -- bias vector of shape (1, 1)
    keep_prob - probability of keeping a neuron active during drop-out, scalar
    
    Returns:
    A3 -- last activation value, output of the forward propagation, of shape (1,1)
    cache -- tuple, information stored for computing the backward propagation
    """
    
    np.random.seed(1)
    
    # retrieve parameters
    W1 = parameters["W1"]
    b1 = parameters["b1"]
    W2 = parameters["W2"]
    b2 = parameters["b2"]
    W3 = parameters["W3"]
    b3 = parameters["b3"]
    
    # LINEAR -> RELU -> LINEAR -> RELU -> LINEAR -> SIGMOID
    Z1 = np.dot(W1, X) + b1
    A1 = relu(Z1)

    #A1.shape[0]=2, A1.shape[0]=5
    #2x5 사이즈로 랜덤값 뽑는다
    D1 = np.random.rand(A1.shape[0], A1.shape[1]) # Step 1: initialize matrix D1 = np.random.rand(..., ...)
    print("원본 D1= \n" + str(D1))
    #keep_prob보다 작으면 True고 1넣는다 크면 0으로
    D1 = (D1 < keep_prob).astype(int)              # Step 2: convert entries of D1 to 0 or 1 (using keep_prob as the threshold)
    print("keep_prob if문 통과한 D1= \n" + str(D1))
    # 원본이랑 곱해서 0인 부분이랑 곱해지면 0으로 만든다
    print("D1 곱하기 전 A1= \n" + str(A1))
    A1 = D1 * A1                                   # Step 3: shut down some neurons of A1
    print("D1 곱하기 후 A1= \n" + str(A1))
    #
    A1 = A1 / keep_prob                            # Step 4: scale the value of neurons that haven't been shut down
    print("keep_prob로 나눈 후 A1= \n" + str(A1))
    
    Z2 = np.dot(W2, A1) + b2
    A2 = relu(Z2)
    
    #A2.shape[0]=3, A2.shape[0]=5
    D2 = np.random.rand(A2.shape[0], A2.shape[1]) # Step 1: initialize matrix D1 = np.random.rand(..., ...)
    D2 = (D2 < keep_prob).astype(int)              # Step 2: convert entries of D1 to 0 or 1 (using keep_prob as the threshold)
    A2 = D2 * A2                                   # Step 3: shut down some neurons of A1
    A2 = A2 / keep_prob                            # Step 4: scale the value of neurons that haven't been shut down
    
    Z3 = np.dot(W3, A2) + b3
    A3 = sigmoid(Z3)
    
    cache = (Z1, D1, A1, W1, b1, Z2, D2, A2, W2, b2, Z3, A3, W3, b3)
    
    return A3, cache

 

keep_prob값은 dropout 시 뉴런을 얼마나 남길 것인지를 결정하는 매개변수이다.

예를 들어 keep_prop = 0.8 이라고 가정한다. 이는 각 층마다 80%의 확률로 뉴런을 유지하고, 20%의 확률로 뉴런을 제거한다는 의미다.

 

이 코드에서는 1과 0로 된 벡터를 만들어, 그 중 80%는 1이고 20%는 0으로 초기화하는 방법으로 dropout을 구현한다.

 

<과정>

1. A1과 동일한 크기로 0~1사이 랜덤 값을 가진 D1 행렬을 만든다.

2. D1의 각 요소를 보면서 keep_prob값보다 작으면 True=1값, 크면 False=0값으로 변환한다.

X = (X < keep_prob).astype(int)

이런 코드는 다음과 같은 의미를 가진다

for i,v in enumerate(x):
     if v < keep_prob:
         x[i] = 1
     else: # v >= keep_prob
         x[i] = 0

3. A1과 D1을 곱해서 특정 요소를 0으로 만든다.

4. A1을 keep_prob로 나눈다.

 

이 마지막 단계가 중요하다.

1~3 단계는 dropout이라고 직관적으로 이해 가능한데, 처음 보면 keep_prob로 왜 나누는지 의미를 알 수 없다.

keep_prob로 나누는 이유는, 최초 데이터 크기를 유지하기 위함이다.

 

예를 들어 전체 데이터양이 100이고 keep_prob = 0.8이라고 가정한다. dropout을 한 번 진행하면 남은 데이터양은 80이다. 이를 계속 반복한다면 데이터양이 결국 0에 가까워지고, 학습시키는 의미가 사라지게 된다. 그래서 각 단계에서 keep_prob을 나눠주어서, 최초 데이터 크기를 유지한다. 남은 데이터들에게 사라진 노드만큼의 양을 분배한다는 느낌으로 이해했다.

또한 keep_prob가 소수점 값이므로, 1/keep_prob를 곱해준다고 표현할 수도 있다.

 

코드 결과값으로 다시 보자.

 

D1의 형태를 보면 0이 하나 있다.

D1을 A1과 곱함으로써 D1의 0부분에 있던 값이 0으로 바뀌었다. (드롭아웃)

D1을 곱한 후의 A1은 값이 조금씩 증가했다.

 

이를 어떤 분은 다음과 같이 표현했다. 잘 와 닿는 설명이라 인용한다.

"확률론에서 확률 변수의 기대값은 각 사건이 벌어졌을 때의 이득과, 그 사건이 벌어질 확률을 곱한 것을 전체 사건에 대해 합한 값입니다.
전체 사건은 그대로 두되, layer 들 간의 연결이 강한 부분을 느슨하게 하는 등의 효과를 통해 overfitting 을 막아줘야 합니다.
keep_prop 을 적용하면 각 사건이 없어지게 되어, 전체 사건이 줄어들게 됩니다.
전체 사건이 줄어들면, keep_prop 을 적용하기 전과 후의 문맥이 달라지게 되겠죠.
그래서 1/keep_prop 을 곱해줌으로써 문맥(기댓값)을 keep_prop을 적용하지 않았을 떄와 유사하게 만들어 주는것이라고 생각했습니다."

 

그리고 순전파에서 dropout을 했다면 역전파 backward Propagation 구현 시에도 dropout을 해 주어야 한다.

역전파 dropout을 할 때도 keep_prob 값을 나누어 주어서 shutdown되지 않는 뉴런의 값을 확장시켜 준다.

반응형
Comments