본문 바로가기
Tensorflow

[ANN] 05. Output Layer and Forward Propagation

by 청양호박이 2020. 5. 12.

이번에는 세번째 글로 인공신경망(ANN)의 출력층 설계 및 순전파에 대해서 알아보겠습니다. 예전에  Regression에서 알아본 내용을 다시 회상해보면서 시작해 보겠습니다.

 

  • 인공신경망(ANN)의 개요 및 퍼셉트론과의 차이점
  • 인공신경망(ANN)의 활성화함수(AF, Activation Function)
  • 인공신경망(ANN)의 출력층 설계 및 순전파(Forward Propagation)
  • 인공신경망(ANN)의 손실함수(Loss) 구현
  • 수치미분 및 편미분, 그리고 GDA
  • 인공신경망(ANN)의 역전파(Back Propagation) 개념
  • 인공신경망(ANN)의 역전파(Back Propagation) 구현

 

 

0. Regression 회상


이전에 Regression에 대해서 목적과 실제 코드로 구현하여 3가지 종류에 대해서 알아보았습니다. Regression이란 간단하게 input 변수들에 대해서 각 변수들의 관계를 정의하고 output을 통해서 적합성을 판단하는... 것이라고 정의하겠습니다. Regression은 output의 형태에 따라 크게 2가지로 분류됩니다. output이 연속형일때는 Linear Regression, 이산형일때는 Logistic Regression 이 바로 그것입니다. 

 

[Linear Regression]

Hypothesis H = W * x + b (W는 weight 기울기, b는 bias 절편)

 

위에 보시다시피 Linear는 연속형 output을 위해서 그냥 summation의 결과를 바로 출력해 줍니다. 

 

[Logistic Regression]

Hypothesis (가설) : Sigmoid Function - 「hypothesis = P = 1 / (1 + np.exp(-f(x)))

 

위에 보시다시피 이산형 output을 위해서 summation의 결과를 바로 출력하지 않고 sigmoid (2분류를 사용 시), softmax (3분류 이상 사용 시)를 AF로 거치게 됩니다.

 

이렇게 상황별로 활성화함수(AF, Active Function)를 제거할지, 아니면 적당한 AF를 사용할지가 결정되게 됩니다.

 

 

1. 출력층의 설계


위에 간단하게 살펴보았듯이, 상황별로 즉 출력의 용도에 맞게 output layer의 설계가 필요합니다.

 

(1) 연속형 : summation만 유지하고 AF제거

(2) 이산형 (2분류) : summation 후 AF로 sigmoid 적용

(3) 이산형 (3분류 이상) : summation 후 AF로 softmax 적용

 

여기서 기존에 알아보지 않은 softmax만 추가로 확인해 보겠습니다. softmax는 3분류 이상에서 input에 따른 각 분류당 결과값에서 가장 큰 값을 선택하는 함수입니다. 예를들어, 유명한 데이터셋(Dataset)인 mnist는 숫자 손글씨에 대한 이미지 셋입니다. 이를 가지고 해당 이미지가 0 ~ 9중 어디에 속하는지를 분류하는게 문제라고 한다면... 

 

적어도 output layer의 node는 10분류 문제이기 때문에 총 10개 이어야하고 (왜냐하면 결과가 0 ~ 9까지 10가지가 존재) 그 중에서 답으로 하나를 뽑아야 합니다. 이때 사용하는 것이 바로 softmax입니다. 어떻게 이런 동작이 가능하냐면!!!

 

softmax  Y = np.exp(input) / np.sum(np.exp(input))

 

으로 구현되기 때문입니다. 즉 exp를 적용한 각 결과를 그 결과의 총 합으로 각 각 나눠준 결과를 제공해 줍니다. 코드로 구현해보면 다음과 같습니다.

 

[Softmax - Normal]

import numpy as np

def softmax(x):
    x_exp = np.exp(x)
    x_exp_sum = np.sum(x_exp)
    return x_exp / x_exp_sum

결국 input에 대해서 output은 1x10 배열로 나올 것이기 때문에 함수의 입력은 np.array로 가정하고 구현합니다. 나머지는 일반적인 내용이라 생략하겠습니다.

 

하지만 여기서 문제가 발생합니다. 이유는 바로 exp( )함수때문입니다. exp는 그 인자가 커지면 커질수록 기하급수적으로 커집니다. 하물며 1000만 되도 inf(infinity)가 리턴되어 더이상 연산이 불가능해 집니다. 따라서 이 문제를 해결해야 합니다.

 

우선 exp( )를 직접 확인해 보겠습니다.

import numpy as np
import matplotlib.pyplot as plt

x = np.arange(-5.0, 5.0, 0.1)
y = np.exp(x)

plt.plot(x, y)
plt.show()

-5 ~ 5 범위 내에서 0.1씩 증가시키면서 exp( )를 살펴보면 아래의 그림과 같이 점점 기하급수적으로 증가하는 모습을 볼 수 있습니다. 그래서 어느 정도에서 infinity가 발생하는지 살펴보면...

temp = np.array([100, 500, 1000])
y_hat = np.exp(temp)
print(y_hat)
===================================

[2.68811714e+043 1.40359222e+217   inf]

500만 되도 어마어마한 수가 되고... 1000이면 일단 발생하게 됩니다.

 

이를 피하는 방법은 간단합니다. softmax로 들어가는 원소들 중에서 가장 큰 수를 찾아서 모든 원소에서 그 최대값을 뺴주는 방법입니다. 그렇게되면 모든 원소가 0 or 음수로 inf가 날 수가 없습니다. 물론 결과가 도출되는데도 전혀 문제가 없습니다. 이를 적용해서 구현하면 아래와 같습니다.

 

[Softmax - Infinity 회피]

def softmax(x):
    x_max = np.max(x)
    x_exp = np.exp(x - x_max)
    x_exp_sum = np.sum(x_exp)
    return x_exp / x_exp_sum
x = np.array([1,2,3])
print(softmax(x))
======================

[0.09003057 0.24472847 0.66524096]

softmax를 통해서 나온 결과를 찬찬히 살펴보면 한가지 특징을 알아챌 수 있습니다. 결과로 도출된 각 원소를 다 더하면 1이 나오는데... 이 말은 역으로 각 원소의 값은 그 결과에 대한 확률을 의미한다고 볼 수 있습니다.

 

따라서 위의 결과를 보면, output 0이 나올 확률은 9%, ...... 2가 나올 확률은 66.5%가 되는 것이지요.  

 

 

2. 순전파 (Forward Propagation)


순전파는 용어 그대로 ANN의 Layer구성대로 왼쪽에서 오른쪽으로 물 흘러가듯이 흘러가는 것을 말합니다. 따라서 ANN에 세팅된 weight, bias가 적용되어 결과가 나오게 됩니다. 이는 Deep Learning의 개념으로 표현하면, 학습과정없이 혹은 학습이 완료되어 내가 원하는 input에 대한 output을 추론하는 과정이라고 할 수 있습니다.

 

한가지 예를 들어보겠습니다. 제가 예전에 블로깅했던 내용중에 Titanic 탑승자 정보로 생존을 예측하는 문제가 있었습니다. 이를 가정하여 순전파를 판단해 보겠습니다. 

 

input : 탑승권등급, 성별, 탑승지 + bias

hidden : 2 layer ( 5 nodes, 3 nodes)

output : 1 node (생존1, 사망0)

 

hidden은 ReLU, output은 sigmoid를 사용하고 변수는 랜덤으로 생성하여 진행하겠습니다. 전체 ANN의 구조는 아래와 같습니다. 각 단계에 해당하는 weight변수는 다음의 표로 필요하게 됩니다.

 

 

총 weight의 집합은 각 구간별로 총 3개가 필요하게 됩니다. 

 

[step 1 - 4 x 5]

Layer 표식은 제거하고 from - to 정보만 기재하겠습니다.

W11 W21 W31 W41 W51
W12 W22 W32 W42 W52
W13 W23 W33 W43 W53
b1 b2 b3 b4 b5

[step 2 - 5 x 3]

W11 W21 W31
W12 W22 W32
W13 W23 W33
W14 W24 W34
W15 W25 W35

[step 3 - 3 x 1]

W11
W12
W13

이 과정을 코드로 구현하면 아래와 같습니다.

def sigmoid(x):
    return 1 / (1 + np.exp(x))

def ReLU(x):
    return np.maximum(0, x)
    

class LayerSigmoid:
    def __init__(self, rows, cols):
        np.random.seed(22)
        self.w = np.random.uniform(low=0.0, high=1.0, size=[rows,cols])

    def forward(self, inputs):
        matmul = np.matmul(inputs, self.w)
        return sigmoid(matmul)


class LayerReLU:
    def __init__(self, rows, cols):
        np.random.seed(22)
        self.w = np.random.uniform(low=0.0, high=1.0, size=[rows,cols])

    def forward(self, inputs):
        matmul = np.matmul(inputs, self.w)
        return ReLU(matmul)
        
        
        
inputs = np.array([[1,2,3,1],[1,2,3,1],[1,2,3,1]])

hidden1 = LayerReLU(4, 5)
hidden2 = LayerReLU(5, 3)
output = LayerSigmoid(3, 1)

step1 = hidden1.forward(inputs)
step2 = hidden2.forward(step1)
outputs = output.forward(step2)

print(outputs)

Layer에 대해서 sigmoid를 사용하는 class와 ReLU를 사용하는 class로 2가지로 생성을 합니다. inputs에 대해서는 bini-batch를 가정해서 3개의 데이터를 넣어주고, hidden layer로 ReLU를 2단계 적용하고 마지막으로 sigmoid output layer를 구성합니다.

 

실제로 구현은 크게 어려운 점은 없습니다. 이제 그 결과를 확인해 보면, 실제 결과와 차이가 당연히 날 것 입니다. 왜냐하면 오차에 대해서 다시 재학습해서 보정하는 절차가 없기 때문입니다. 다음에는 학습을 위한 오차(Loss)에 대해서 알아보도록 하겠습니다. 

 

- Ayotera Lab -

댓글