이번 시간에는 신경망에 대해서 정리해보도록 한다.

 

퍼셉트론의 문제점

  • 1층 퍼셉트론으로는 선형분리 외의 문제를 해결할 수 없음
    • XOR 게이트는 2층 퍼셉트론으로 표현할 수 있다

 

퍼셉트론의 문제점은 다층 구조의 퍼셉트론 (MLP : Multi Layer Perceptron) 구조로 층을 늘리면 해결이 되지만, 어떻게 데이터로부터 자동으로 가중치를 학습할 것인지 배워야할 것이다.

 

신경망의 예

  • 2층 신경망 의 구조는 입력층 (0층) / 은닉층 (1층) / 출력층 (2층) 으로 구성되어있다.

2층 신경망

  • 3층으로 구성되어 있지만, 가중치를 갖는 층은 2개이기 때문에, 2층 신경망으로 표시한다.

 

활성화 함수의 등장

  • y = 0 (b + x1w1 + x2w2 <= 0)
  • y = 1 (b + x1w1 + x2w2 > 0)

 

위 식을 다르게 표현하면

  • y = h(b + x1w1 + x2w2)
  • y = h(b + x1w1 + x2w2)

그리고

  • h(x) = 0 (x <= 0) 
  • h(x) = 1 (x > 0)

이 식이 나온다.

 

활성화 함수는 입력 신호의 총합을 출력 신호로 변환하는 함수로, 입력 신호의 총합이 활성화를 일으키는지 판단한다.

  • a = b  + w1x1 + w2x2
  • y = h(a)

 

활성화 함수의 종류

  • 계단 함수
    • 임계값을 경계로 출력이 바뀜
  • 시그모이드 함수
    • 0과 1사이의 연속적인 output
    • 시그모이드 : S자 모양 뜻
    • 미분이 가능하다.

  • ReLU 함수

 

퍼셉트론 → 신경망

  • 뉴런이 여러 층으로 이어지는 구조나 신호 전달 방법은 같음
  • 활성화 함수가 계단 함수가 아닌 다른 함수로 변경
    • 신경망 부터는 활성화 함수로 시그모이드 함수를 사용

 

계산 함수 구현

import numpy as np

def step_function(x):
  y = x >0
  return y.astype( np.int )
  
x = np.array([-1.0, 1.0, 2.0])
print(x)
# [-1. 1. 2.]

y = x >0
print(y)
# [False True True]

y = y.astype(int)
print(y)
# [0 1 1]

 

계단 함수의 그래프

import matplotlib.pyplot as plt

X = np.arange(-5.0, 5.0, 0.1)
Y = step_function(X)
plt.plot(X, Y)
plt.ylim(-0.1, 1.1)
plt.show()

 

계단 함수의 그래프

 

시그모이드 함수 구현

import numpy as np

def sigmoid(x):
  return 1 / (1 + np.exp(-x))
  
x = np.array([-1.0, 1.0, 2.0])
print( sigmoid(x) )
# [0.26894142 0.73105858 0.88079708]

 

시그모이드 함수의 그래프

import matplotlib.pyplot as plt

x = np.arange(-5.0, 5.0, 0.1)
y = sigmoid(x)
plt.plot(x, y)
plt.ylim(-0.1, 1.1)
plt.show()

 

시그모이드 함수의 그래프

 

시그모이드 함수와 계단 함수 비교

  • 차이점 1
    • 시그모이드 함수는 부드러운 곡선이며, 입력에 따라 출력이 연속적으로 변화
    • 계단 함수는 0을 경계로 출력이 갑자기 변화
  • 차이점 2
    • 계단 함수는 0 아니면 1 둘 중 하나의 값 반환
    • 시그모이드는 0과 1 사이의 실수를 반환
  • 공통점
    • 입력이 작을 때 출력은 0, 입력이 커지면 출력이 1이 되는 구조
    • 입력이 아무리 작거나 커도 출력은 0에서 1 사이의 값
    • 직선 하나로는 그릴 수 없는 비선형 함수

 

신경망에서 활성화 함수로 비선형 함수를 사용해야하는 이유는 신경망의 층을 깊게하는 효과를 얻을 수 있고, 층을 늘려도 활성화 함수가 선형 함수라면 은닉층이 없는 네트워크로 표현 되기 때문에 비선형 함수를 써야한다.

 

  • 선형 함수 h(x) = cx 를 활성화 함수로 사용한 3층 네트워크 식으로 나타나면 y(x) = h(h(h(x))) 이고, 결국 y(x) = c*c*c*x 처럼 곱셈을 세번 하지만 y(x) = ax 와 같은 똑같은 식
  • 즉, 은닉층이 없는 네트워크로 표현할 수 있음

 

ReLU 함수

  • 최근에는 주로 ReLU (Rectified Linear Unit) 함수 이용
    • 입력이 0을 넘으면 입력을 그대로 출력
    • 입력이 0 이하이면 0을 출력

 

 

ReLU 함수의 구현

import numpy as np

def relu(x):
  return np.maximum(0, x)
  
A = np.array([1, 2, 3, 4])
print(A)
# [1 2 3 4]

print( np.ndim(A) )
#1

print( A.shape )
# (4,)

print( A.shape[0] )
#4

 

신경망에서의 행렬 곱

신경망에서는 가중치 합을 구할 때, ndarray(다차원 행렬 자료구조) 끼리의 곱셈으로 신경망을 구현하기 때문에 행렬에 대해 잘 알아야 한다.

 

3층 신경망 구현해보기

  • 입력층(0층) - 2개
  • 첫 번째 은닉층(1층) - 3개
  • 두 번째 은닉층(2층) - 2개
  • 출력층(3층) -2개

 

이렇게 가정해본다.

 

3층 신경망

 

 

import numpy as np

# 0 ~ 1층
X = np.array([1.0, 0.5]) # 2

W1 = np.array([[0.1, 0.3, 0.5], [0.2, 0.4, 0.6]]) # 2*3
B1 = np.array([0.1, 0.2, 0.3])
A1 = np.dot(X, W1) + B1

print(A1) # [0.3 0.7 1.1]

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

# =====================================================

# 1 ~ 2층

W2 = np.array([[0.1, 0.4], [0.2, 0.5], [0.3, 0.6]])
B2 = np.array([0.1, 0.2])
print(Z1.shape)
print(W2.shape)
print(B2.shape)
A2 = np.dot(Z1, W2) + B2
Z2 = sigmoid(A2)

print(A2) # [0.51615984 1.21402696]
print(Z2) # [0.62624937 0.7710107 ]

# =====================================================

# 2 ~ 3층

# 출력층 활성화 함수 - 항등 함수 사용
def identity_function(x):
  return x

W3 = np.array([[0.1, 0.3], [0.2, 0.4]])
B3 = np.array([0.1, 0.2])
A3 = np.dot(Z2, W3) + B3
Y = identity_function(A3)

print(Y) # [0.31682708 0.69627909]

 

더 간단히 표현하면 이렇게 된다.

조금만 더 손을 보면 반복문을 사용해서 좀 더 효율적인 구조로 짜볼 수도 있을 것 같다.

 

import numpy as np

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.4], [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)

 

지도학습 (Supervised Learning)

머신러닝의 알고리즘을 분류할 때 3가지 분류(지도, 비지도, 강화 학습) 이렇게 나뉘는데, 그 중에 지도 학습에 대한 내용이다.

  • 분류
    • 미리 정의된 클래스 중 하나를 예측하는 것
      • 스팸메일 / 정상메일 분류
      • 손글씨 분류
  • 회귀
    • 연속적인 수를 예측하는 것
      • 기상데이터로 내일 기온 예층
      • 공부시간으로 성적 예측

 

출력층 설계

신경망은 분류와 회귀 모두에 이용 가능하다.

  • 문제에 따라 출력층에서 사용하는 활성화 함수를 다르게 사용해야 한다.
    • 회귀 - 항등 함수 사용
    • 분류 - 소프트맥스 함수 사용

 

항등 함수

  • 항등 함수는 입력을 그대로 출력한다.

 

소프트맥스 함수

  • 클래스 분류를 위해 마지막 출력값을 정규화 하는 함수
  • 모든 결과 수치의 합을 1.0 으로 만들어 주기 때문에 각 클래스에 대한 확률로써 수치화하기에 좋다.
  • 학습 단계에서는 출력층에서의 소프트맥스 함수를 사용하지만, 추론 단계에서는 출력층에서의 소프트맥스 함수를 생략할 수 있다.
    • 추론에서는 가장 큰 값의 클래스만 알면 되기 때문이다.
  • 출력층의 뉴런 수는 풀려는 문제에 맞게 적절히 정해야 한다.
    • 예를 들어 숫자 손글씨 이미지를 0~9로 분류하는 문제에서, 출력층의 뉴런은 10개 이다.

 

정리

  • 신경망 활성화 함수 - 활성화 여부 판단
  • 은닉층 활성화 함수로 시그모이드, ReLU 함수 등 사용
  • 출력층 활성화 함수로 회귀 문제에서는 항등 함수를, 분류 문제에서는 소프트맥스 함수를 사용한다.
  • 분류 문제에서 출력층의 뉴런 수는 분류하려는 클래스의 수와 같게 한다.

 

우선 이론적인 내용을 쭉 다뤄봤는데

이후엔 좀 더 코드 중심으로 다뤄볼까 한다..