ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [NLP] 신경망의 활성화 함수들 (Activation Functions of Neural Network) - 2
    ML/NLP 2020. 3. 13. 23:53

     

     

     

     

    신경망을 구현하려면 먼저 행렬곱을 알고 지나가야 한다. 행렬곱부터 보고 신경망을 직접 구현해보자!

     

     

     

    신경망에서의 행렬 곱


    먼저 행렬곱 예제를 보자.

    A = np.array([1,2])
    print(A.shape)
    
    W = np.array([[1,3,5], [2,4,6]])
    print(W.shape)
    
    Y = np.dot(A, W)
    print(Y.shape)
    Out:  
    (2,)
    (2, 3)
    (3,)

     

    다차원 배열의 스칼라곱을 구해주는 np.dot 함수를 사용하면 단번에 결과 Y를 계산할 수 있다.

    만약 np.dot 함수를 사용하지 않으면 Y의 원소를 하나씩 따져봐야 한다.(정말 귀찮을 것)

     

    그래서 행렬의 곱으로 한꺼번에 계산해주는 기능은 신경망을 구현할 때 매우 중요하다.

     

     

     

     

    3층 신경망 구현하기


    3층 신경망에서 수행되는 입력부터 출력까지의 처리(순방향 처리)를 구현해보겠다. 이를 위해 앞에서 설명한 넘파이의 다차원 배열을 사용한다.

     

    우리가 구현해 볼 신경망은 입력층(0층) 2개, 첫 번째 은닉층(1층) 3개, 두 번째 은닉층(2층) 2개, 출력층(3층) 2개의 뉴런으로 구성된다.

     

     

    각 층에 신호 전달을 구현해보자.

     

     

    1. 입력층에서 1층으로 신호 전달

    X = np.array([1.0, 0.5])
    W1 = np.array([[0.1, 0.3, 0.5],[0.2, 0.4, 0.6]])
    B1 = np.array([0.1, 0.2, 0.3])
    
    print(X.shape)
    print(W1.shape)
    print(B1.shape)
    
    A1 = np.dot(X, W1) + B1
    Out:    array([0.3, 0.7, 1.1])

     

    A1을 활성화 함수 h(x)에 넣어서 변환된 신호를 z로 표기한다. 여기서는 시그모이드 함수를 사용하기로 한다.

     

    Z1 = sigmoid(A1)
    
    print(A1)
    print(Z1)
    Out:   
    [0.3 0.7 1.1]
    [0.57444252 0.66818777 0.75026011]

     

    이와 같은 방식으로 출력층까지 반복하여 구현하면 된다.

     

    모든 구현을 정리하여 함수화한 것이 아래와 같다.

     

    # 출력층의 활성화 함수 - 항등함수
    # 회귀: 항등함수 / 2클래스 분류: 시그모이드 함수 / 다중 클래스 분류: 소프트맥스 함수
    def identity_function(x):
        return x
    def init_network():
        network = {}
        network['W1'] = np.array([[0.1, 0.3, 0.5],[0.2, 0.4, 0.6]])
        network['b1'] = np.array([0.1, 0.2, 0.3])
        network['W2'] = np.array([[0.1, 0.4],[0.2, 0.5],[0.3, 0.6]])
        network['b2'] = np.array([0.1, 0.2])
        network['W3'] = np.array([[0.1,0.3], [0.2,0.4]])
        network['b3'] = np.array([0.1, 0.2])
        
        return network
    def forward(network, x):
        W1, W2, W3 = network['W1'], network['W2'], network['W3']
        b1, b2, b3 = network['b1'], network['b2'], network['b3']
        
        a1 = np.dot(x, W1) + b1
        z1 = sigmoid(a1)
        
        a2 = np.dot(z1, W2) + b2
        z2 = sigmoid(a2)
        
        a3 = np.dot(z2, W3) + b3
        y = identity_function(a3)
        
        return y
    network = init_network()
    x = np.array([1.0, 0.5])
    y = forward(network, x)
    print(y)
    Out:    [0.31682708 0.69627909]

     

    (* 참고)

    출력층의 활성화 함수는 출고자 하는 문제의 성질에 맞게 정한다.

    회귀에는 항등 함수, 2클래스 분류에는 시그모이드 함수, 그리고 다중 클래스 분류에는 소프트맥스 함수를 사용하는 것이 일반적이다.

     

     

    출력층 설계하기


    앞에서는 출력층의 활성화 함수로 항등 함수(identity function)을 이용했었다.

     

    1. 항등 함수 (identity function)

    입력과 출력이 항상 같다

    출력층에서 항등 함수를 사용하면 입력 신호가 그래도 출력 신호가 된다

     

    위에서 구현한 'identity_function(x)'이 그 구현이다.

     

     

    2. 소프트맥스 함수 (softmax function)

    이번엔 출력층의 활성화 함수 중 하나인 소프트맥스 함수를 구현해보자.

    # 다중 클래스 분류의 출력층 활성화함수 소프트맥스(1)
    def softmax(a):
        exp_a = np.exp(a)
        sum_exp_a = np.sum(exp_a)
        y = exp_a / sum_exp_a
        
        return y

     

     

     

     

    소프트맥스 함수 구현 시 주의점


    위에서 구현한 softmax() 함수의 코드는 식을 제대로 표현하고 있지만, 컴퓨터로 계산할 때는 결함이 있다. 

    바로 오버플로우 문제이다. 앞서 봤듯, softmax 함수의 수식은 지수함수를 포함하고 있기 때문에 분모와 분자의 값이 아주 큰 값이 될 확률이 크다.

    이런 큰 값끼리 나눗셈을 하면 결과 수치가 불안정해진다.

     

    이 문제를 해결하도록 소프트맥스 함수를 개선해보자. 다음은 개선한 수식이다.

     

    위의 식이 말하는 것은 소프트맥스의 지수 함수를 계산할 때 어떤 정수를 더해도 (혹은 빼도) 결과는 바뀌지 않는다는 것이다.

     

    여기서 C' 에 어떤 값을 대입해도 상관없지만 오버플로를 막을 목적으로는 입력 신호 중 최댓값을 이용하는 것이 일반적이다.

     

    그럼 이를 바탕으로 소프트맥스 함수를 다시 구현하면 다음과 같다.

    # 다중 클래스 분류의 출력층 활성화함수 소프트맥스(2)
    def softmax(a):
        c = np.max(a)
        exp_a = np.exp(a - c)
        sum_exp_a = np.sum(exp_a)
        y = exp_a / sum_exp_a
        
        return y
    a = np.array([0.3, 2.9, 4.0])
    y = softmax(a)
    print(y)
    np.sum(y)

     

    Out:    [0.01821127 0.24519181 0.73659691]

     

     

     

    소프트맥스 함수의 특징


    1. 방금 봤던 수정한 소프트맥스의 구현 결과값을 다시 보자.

    Out:    [0.01821127 0.24519181 0.73659691]

    출력의 총합이 1이다. 출력 총합이 1이 된다는 점은 소프트맥스 함수의 중요한 성질이다. 이 성질 덕분에 소프트맥스 함수의 출력을  '확률'로 해석할 수 있다.

     

     

    2. 소프트맥스 함수를 적용해도 각 원소의 대소 관계는 변하지 않는다. 이는 지수 함수 y=exp(x)가 단조 증가 함수이기 때문이다.

    그래서 현업에서는 신경망을 학습시킬 때는 출력층에서 소프트맥스 함수를 사용하고, 추론 단계에서는 생략하는 것이 일반적이다.

     

    3. 출력층의 뉴런 수는 풀려는 문제에 맞게 적절히 정해야 한다. 분류에서는 분류하고 싶은 클래스 수로 설정하는 것이 일반적이다. 

     

     

     

     

    밑바닥부터 시작하는 딥러닝 1
    국내도서
    저자 : 사이토 고키 / 이복연(개앞맵시)역
    출판 : 한빛미디어 2017.01.03
    상세보기

    댓글

dokylee's Tech Blog