[밑바닥부터 시작하는 딥러닝] CH3. 신경망(1) - 계단함수, 시그모이드 함수, ReLU 함수, 다차원 배열, 행렬의 곱

CH3. 신경망

  • 퍼셉트론으로 가중치를 설정하는 작업(원하는 결과를 출력하도록 가중치 값을 적절히 정하는 작업)은 할 수 없음
  • 신경망가중치 매개변수의 적절한 값을 데이터로부터 자동으로 학습하는 능력이 있음

1. 퍼셉트론에서 신경망으로

1) 신경망

  • (왼->오 방향 순서대로) 입력층, 은닉층, 출력층이고, 차례로 0층, 1층, 2층이라고 함
  • 은닉층의 뉴런은 보이지 않음

    그림의 신경망은 3층으로 구성되지만, 가중치를 갖는 층은 2개뿐이기에 '2층 신경망'이라고 함.
    책에서는 실제로 가중치를 갖는 층의 개수(입력층, 은닉층, 출력층의 함계에서 1을 뺀 값)를 기준으로 함

2) 퍼셉트론 복습

b : 편향 - 뉴런이 얼마나 쉽게 활성화되느냐를 제어함
w1, w2 : 가중치 - 각 신호의 영향력 제어

  • 가중치가 b이고, 입력이 1인 뉴런이 추가됨
  • 이 퍼셉트론의 동작은 x1, x2, 1이라는 3개의 신호가 뉴런에 입력되어, 각 신호에 가중치를 곱한 후, 다음 뉴런에 전달 됨
  • 다음 뉴런에서는 이 신호들의 값을 더하여, 그 합이 0을 넘으면 1을 출력하고 그렇지 않으면 0을 출력

    편향의 입력신호는 항상 1이기 때문에 다른 뉴런과 구별하기 위해 해당 뉴런을 빗금칠함.

  • 조건 분기의 동작(0 넘으면 1출력, 그렇지 않으면 0 출력)을 하나의 함수로 나타냄
  • 입력 신호의 총합이 h(X)라는 함수를 거쳐 변환되어, 그 변환된 값이 y의 출력이 됨을 보여줌
  • 즉, h(x) 합수 입력이 0을 넘으면 1을, 그렇지 않으면 0 출력

2) 활성화 함수

활성화 함수: 입력 신호의 총합을 출력 신호로 변환하는 함수(즉, 입력 신호의 총합이 활성화를 일으키는지를 정하는 역할)

  • a = b + w1x1 + w2x2 (가중치가 곱해진 입력 신호와 편향의 총합 계산)
  • y = h(a) (합을 활성화 함수에 입력해 결과를 냄, a를 gkatn h()에 넣어 y 출력)

  • 가중치 신호 조합한 결과 a라는 노드 되고, 활성화 함수 h()를 통과하여 y라는 노드로 변환되는 과정이 나타남
  • 책에서 뉴런 = 노드
  • 단순 퍼셉트론 = 단층 네트워크에서 계단 함수(임계값을 경계로 출력이 바뀌는 함수)를 활성화 함수로 사용한 모델
  • 다층 퍼셉트론 = 신경망(여러 층으로 구성되고 시그모이드 함수 등의 매끈한 활성화 함수를 사용하는 네트워크)을 가리킴

2-1) 계단 함수

계단 함수(step function)
: 임계값을 경계로 출력이 바뀌는 활성화 함수

  • 퍼셉트론은 활성화 함수로 계단 함수 이용
  • 활성화 함수를 계단 함수에서 다른 함수로 변경하는 것이 신경망의 세계로 나아가는 열쇠

계단 함수 구현하기

# 입력이 0을 넘으면 1 출력, 그 외에는 0 출력 
# 인수 x는 실수만 받아들여서, 넘파이 배열을 인수로 넣을 수 없음 
def step_function_1(x):
    if x > 0:
        return 1
    else:
        return 0
# 넘파이 배열 인수로 넣기 
import numpy as np 
def step_fucntion_2(x):
    y = x > 0
    return y.astype(np.int)

# 넘파이 배열에 부등호 연산을 수행하면 배열의 원소 각각에 부등호 연산을 수행한 bool 배열이 생성됨 
# 즉, y는 bool 배열 
# 하지만 계단함수는 0이나 1의 'int형'dmf cnffurgksms gkatn -> y의 원소를 bool에서 int형으로 바꿔줌
# numpy 자료형 변환 astype() 메서드 사용 

계단 함수 그래프

import matplotlib.pylab as plt
def step_function_3(x):
    return np.array(x > 0, dtype=np.int)


x = np.arange(-5.0, 5.0, 0.1)  # -5.0에서 5.0 전까지 0.1 간격의 넘파이 배열 생성 
y = step_function_3(x)  # 인수로 받은 넘파이 배열의 원소 각각을 인수로 계단 함수 실행, 그 결과를 다시 배열로 만들어 돌려줌
plt.plot(x, y)
plt.ylim(-0.1, 1.1)  # y축의 범위 지정
plt.show()

  • 이렇게 계단 함수는 0을 경계로 출력이 0에서 1로 바뀜
  • 바뀌는 형태가 계단처럼 생겨서 '계단함수'

신경망에서 이용하는 활성화 함수

2-2) 시그모이드 함수

시그모이드 함수(sigmoid function)


시그모이드 함수 구현하기

def sigmoid(x):
    return 1 / (1 + np.exp(-x))
  • 인수 x가 넘파이 배열이어도 결과 올바르게 나옴
  • np.exp(-x)가 넘파이 배열을 반환하기 때문!

시그모이드 함수 그래프

x = np.arange(-5.0, 5.0, 0.1)
y = sigmoid(x)
plt.plot(x, y)
plt.ylim(-0.1, 1.1)  # y축의 범위 지정
plt.show()

  • '시므도이드 = S자 모양'이라는 점만 확실히 기억하기

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

  • 시그모이드 함수는 부드러운 곡선이며 입력에 따라 출력이 연속적으로 변화함
    => 신경망에서는 연속적인 실수가 흐름
  • 둘 다 입력이 작을 때의 출력은 0에 가깝고, 입력이 커지면 출력이 1에 가까워 지는 구조
    => 두 함수 모두 입력이 중요하면 큰 값을 출력하고, 중요하지 않으면 작은 값을 출력함
  • 입력이 아무리 작거나 커도 두 함수의 출력은 0에서 1 사이

계단 함수와 시그모이드 함수 비교(공통점) - 비선형 함수
선형 함수 : 출력이 입력의 상수배만큼 변하는 함수. 즉, 곧은 1개의 직선
비선형 함수 : '선형이 아닌' 함수. 즉, 직선 1개로는 그릴 수 없는 함수.

2-3) ReLU 함수

ReLU 함수

  • 시그모이드 함수는 신경망 분야에서 오래전부터 이용해왔으나, 최근엔는 ReLU 함수를 주로 이용함
  • 입력이 0을 넘으면 그 입력을 그대로 출력하고, 0 이하면 0을 출력하는 함수
    <수식 그림>

ReLU 함수 구현하기

def relu(x):
    return np.maximum(0,x)
 
# numpy의 maximum 함수 사용, 두 입력 중 큰 값을 선택해 반환하는 함수 

ReLU 함수 그래프

x = np.arange(-6.0, 6.0, 2)  
y = relu(x)  
plt.plot(x, y)
plt.ylim(-1, 5, 1)  # y축의 범위 지정
plt.show()

참고) 다차원 배열

  • 넘파이의 다차원 배열을 사용한 계산법을 숙달하면 신경망을 효율적으로 구현할 수 있음
  • 숫자가 한 줄로 늘어선 것, 직사각형으로 늘어놓은 것, n차원으로 나열한 것을 통틀어 다차원 배열이라고 함

1차원 배열

A = np.array([1, 2, 3, 4])
print(A)
# array([1, 2, 3, 4])

np.ndim(A) # 배열의 차원 수 확인
# 1 1차원 배열

A.shape  # 배열의 형상 
# (4,) 4개 원소로 구성 

2차원 배열

B = np.array([[1,2],[3,4],[5,6]])
print(B)
# array([[1, 2],
#      [3, 4],
#      [5, 6]])
       
np.ndim(B)
# 2

B.shape
# (3, 2)
# 3*2 배열 
# 0번째 차원에는 원소가 3개, 1번째 차원에는 원소가 2개 있다는 의미 
# 2차원 배열은 행렬(matrix)라 부르고, 배열의 가로 방향을 행row, 세로 방향을 열column이라고 함 

행렬의 곱

  • 행렬의 곱은 왼쪽 행렬의 행(가로)와 오르쪽 행렬의 열(세로)을 원소별로 곱하고, 그 값들을 더해서 계산함
A = np.array([[1,2], [3,4]])
A.shape
# (2, 2)

B = np.array([[5,6], [7,8]])
B.shape
# (2, 2)

np.dot(A, B)  # 두 행렬의 곱 
# 참고로 np.dot(A, B)과 np.dot(B, A)는 다른 값이 될 수 있음 
# array([[19, 22],
       [43, 50]])
A = np.array([[1,2,3], [4,5,6]])
A.shape
# (2, 3)

B = np.array([[1,2], [3,4], [5,6]])
B.shape
# (3, 2)

np.dot(A, B)
# array([[22, 28],
       [49, 64]])
  • 행렬의 shape에 주의해야 함. 행렬 A의 1번째 차원의 원소 수 (열 수)와 행렬 B의 0번째 차원의 원소 수 (행수)가 같아야 함
  • 즉, 다차원 배열을 곱하려면 두 행렬의 대응하는 차원의 원소 수를 일치시켜야 함

신경망에서의 행렬 곱

X = np.array([1,2])
X.shape
# (2,)

W = np.array([[1, 3, 5], [2, 4, 6]])
print(W)
# array([[1, 3, 5],
       [2, 4, 6]])

W.shape
# (2, 3)

Y = np.dot(X, W)
print(Y)
# array([ 5, 11, 17])
  • 스칼라곱을 구해주는 np.dot 함수를 사용하면 단번에 결과 Y를 계산할 수 있음
  • 행렬의 곱으로 한꺼번에 계산해주는 기능은 신경망 구현할 때 매우 중요

좋은 웹페이지 즐겨찾기