Practice makes perfect

[DeepLearning] 신경망(neural network) 본문

Anaconda/DeepLearning

[DeepLearning] 신경망(neural network)

kerpect 2020. 8. 4. 13:22

신경망 (neural network)

: 인공신경망( artificial neural network, ANN)은 기계학습 인지과학에서 생물학의 신경망(동물의 중추신경계중 특히 )에서 영감을 얻은 통계학적 학습 알고리즘입니다. 

 

 

신경망은 아래와 같이 왼쪽부터 Input(입력층), Hidden(은닉층), Output(출력층)으로 표현할 수 있습니다.

은닉층은 양쪽의 입력층과 출력층과는 달리 우리 눈에는 보이지 않기 때문에 'Hidden(은닉)'이라고 합니다. 

 

 

퍼셉트론 복습

- y는 출력 신호
- x1 과 x2는 입력 신호
- w1 과 w2는 가중치(weight) : 각 신호가 결과에

  주는 영향력을 조절하는 요소로 작용
- b(편향) : 뉴런이 얼마나 쉽게 활성화 제어
                                                                                        -뉴런(혹은 노드) : 그림의 원

 

 

Bias(편향)를 명시한 퍼셉트론

 

 

y = h(b + w1x1 + w2+x2)

 

 

h(x) =  - > 0 ( x <= 0 )

          - > 1 ( x  >  0 )

 

 

 

 

 

 

 

 

퍼셉트론이 하나의 뉴런 단위로 다루어진다면 각 뉴런이 모여 하나의 뇌가 되는 것과 같은 신경망은 퍼셉트론의 하위요소로 볼 수 있습니다. 단층 퍼셉트론이 하나의 나무와 같이 신경망은 나무가 모여 숲을 이룬 것과 같은 느낌으로 이해하면 됩니다. 

 

 

 

활성화 함수 (Activation Function)

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

- 활성화 함수를 통해서 비선형성을 가지게 됩니다.( 경계면의 부근의 모양을 결정 ) 

 

 

 

 

  a = b + w1x1 + w2x2 

 

  y = h(a) 

 (a는 입력 신호의 총합, h()는 활성화 함수, y는 출력)

 

 

 

 

 

 

 

 

 

 

계단함수 (step function) 

: 퍼셉트론은 활성화 함수로 step function(계단 함수)를 이용합니다. 특정 임계값을 넘기면 활성화되는 함수입니다. 아래 왼쪽(a)가 계단 함수이다. 0에서 멈추어있다가 어느 기점에서 1로 바뀝니다. 임계값을 경계로 출력이 바뀌는 활성화되며, 활성화 함수를 계단함수에서 다른 함수로 변경하는 것이 신경망의 세계로 나아가는 열쇠가 됩니다. 

 

 

 

h(x) =  0 ( x <= 0 )

          1 ( x > 0 )

 

 

 

 

 

 

 

 

 

 

 

# 계단 함수(Step Function)

import numpy as np

def step_function(x):
    if x > 0:
        return 1
    else:
        return 0
    
def step_func_ndarray(x):
    y = x > 0 
    

if __name__ =="__main__":
    x = step_function(3)
    print(x) # 1

    x = step_function(-3)
    print(x) # 0

 

주의 )  error : if 을 통해서 하나의 값이 비교가 되어야 하는데 배열로 다수의 값이 오기 때문에 처리 되지 않습니다. 

# 계단 함수(Step Function)

import numpy as np

def step_function(x):
    if x > 0:
        return 1
    else:
        return 0

if __name__ =="__main__":
    x = step_function(3)
    print(x) # 1

    x = step_function(-3)
    print(x) # 0

    z = np.array([-1,1,2])
    x = step_function(z)
    print(x)

 

해결점 1)  함수를 정의해서 boolean 값으로 출력.  

# 계단 함수(Step Function)

import numpy as np

def step_function(x):
    if x > 0:
        return 1
    else:
        return 0

def step_func_ndarray(x):
    y = x > 0
    print(y)


if __name__ =="__main__":
    x = step_function(3)
    print(x) # 1

    x = step_function(-3)
    print(x) # 0

    z = np.array([-1,1,2])
    step_func_ndarray(z)

 

해결점 2) int로 변환하여 0, 1 출력. 

# 계단 함수(Step Function)

import numpy as np

def step_function(x):
    if x > 0:
        return 1
    else:
        return 0

def step_func_ndarray(x):
    y = x > 0
    return y.astype(np.int)


if __name__ =="__main__":
    x = step_function(3)
    print(x) # 1

    x = step_function(-3)
    print(x) # 0

    z = np.array([-1,1,2])
    print(step_func_ndarray(z))

 

- 시각화 

x = np.arange(-5,5,0.1) # -5 ~ 4.9 까지.
y = step_func_ndarray(x

plt.plot(x,y)
plt.ylim(-0.1, 1.1)
plt.show()

 

 

 

시그모이드 함수 (sigmoid fuction)

: 신경망에서 자주 이용하는 활성화 함수로 시그모이드 함수를 이용하여 신호를 변환하고, 그 변환된 신호를 다음 뉴런에 전달합니다. 변화와 간격이 완만하게 하는데 이것이 비선형성을띄게 되는 것입니다. 

 

영역을 구분하는 일차 함수의 선형성이 완만하게 가게 되면서 굴곡이 가해질 수 있게 되었습니다. - 비선형성

import numpy as np

# - sigmoid(활성화) 함수의 특징
# 1) 음수 입력 값의 경우 양수로 변환
# 2) 0 < y < 1 사이의 연속적인 실수 값으로 변환

def sigmoid(x):
    return  1 / (1 + np.exp(-x)) # np.exp(-x) : 배열로 전달 받아도 전처리 과정 없이 한번에 수행된다

if __name__ == "__main__":
    x = np.array([-1, 0, 1, 2])
    y = sigmoid(x)
    print(y) 출력값 :  [0.26894142 0.5 0.73105858 0.88079708] - 실수 값으로 출력.

 

 

계단 함수 (Step Function)   vs    시그모이드 함수 (Sigmoid Function) 

퍼셉트론과 신경망의 주된 차이는

활성화 함수의 차이


- 계단 함수가 0과 1 중 하나의 값만 돌려주는 반면 시그모이드 함수는 실수를 돌려준줍니다. 

 
- 즉, 퍼셉트론에서는 뉴런 사이에 0 혹은 1이 흘렀다면, 신경망에서는 연속적인 실수가 흐릅니다. 


- 두 함수 모두 비선형 함수입니다. 


- 신경망에서는 활성화 함수로 비선형

  함수를 사용해야 합니다. 

시그모이드 함수는 값을 실수형으로 가지는 것을 볼 수 있습니다. 시그모이드 함수의 매끄러움은 가중치 값을 전달할 때 좀 더 부드럽게 양을 조절해서 전달할 수 있습니다. 

 비선형 함수를 사용해야 하는 이유?

: 선형함수를 사용했을 때는 은닉층을 사용하는 이점이 없기 때문입니다.  즉, 선형함수를 여러층으로 구성한다 하더라도 이는 선형함수를 세번 연속 반복한 것에 지나지 않는다는 의미와 같기 때문입니다.  y = ax라는 선형함수가 있다고 한다면 이 것을 3층으로 구성하면 y = a(a(a(x))) 와 동일한 것으로 이는 y = a3(x)와 같습니다. 굳이 은닉층 없이 선형함수로 네트워크를 구성하는 것은 의미가 없어집니다. 

 

 

 

ReLU 함수(Rectified Linear Unit Function)


: 최근 신경망 분야에서 이용하는 함수로 입력이 0을 넘으면 그 입력을 그대로 출력하고, 0 이하이면 0을 출력하는 함수입니다. 

 

 

h(x) = x ( x > 0 )

         0 ( x <= 0 )

 

 

 

 

 

 

 

 

 

 

 

import numpy as np
import matplotlib.pyplot as plt

def relu(x): #  입력이 0을 넘으면 그 입력을 그대로 출력하고, 0 이하이면 0을 출력
    return np.maximum(0, x) # np.maximum : 두 입력 중 큰 값을 선택해 반환하는 함수


if __name__=="__main__":
    print(relu(5)) #5
    print(relu(-5)) #0

    x = np.arange(-5,5,0.1) # 배열형
    y = relu(x)
    print(y)

 

- 시각화 

    x = np.arange(-5,5,0.1) # 배열형
    y = relu(x)
    print(y)

    plt.plot(x,y)
    plt.ylim(-1.0,5.5)
    plt.show()

 

 

다차원 배열

 

Vector(1차원 배열)

X = np.array([1,2,3,4,5])
print(X.shape) 출력값 : (5,) 1차원의 배열 - 열 개수 카운팅 / ndarray

 

3x2 행렬과 2x3 행렬의 내적(Matrix: 2차원 배열)

A = np.array([[1,2],[3,4],[5,6]])
print(A.shape) 출력값 :  (3, 2) 3행 2열
B = np.array(([[1,2,3],[4,5,6]]))
print(B.shape) 출력값 :  (2, 3) 2행 3열

Z = np.dot(A,B)
print(Z)

- 출력값 -
[[ 9 12 15] (1x1 + 2x4) (1x2 + 2x5) (1x3 + 2x6)
 [19 26 33] (3x1 + 4x4) (3x2 + 4x5) (3x3 + 4x6)   
 [29 40 51]] (5x1 + 6x4) (5x2 + 6x5) (5x3 + 6x6)

 

- 교환 법칙 성립 X

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

Y = np.dot(A,B) # 3x2 - 2x3 = 3x3
Z = np.dot(B,A) # 2x3 - 3x2 = 2x2

print(Y) # 3행 3열 
- 출력값 -
[[ 9 12 15]
 [19 26 33]
 [29 40 51]]


print(Z) # 2행 2열
- 출력값 -
[[22 28]
 [49 64]]

 

- A 2차원 행렬, B 1차원 배열 곱

A = np.array([[1,2],[3,4],[5,6]]) # 3행 2열
B = np.array([7,8]) # 1행 2열로 보이지만 내적의 곱을 할 때 2행 1열로 구성된다.

print(B.shape) 출력값 : (2,) - 열
print(A.shape) 출력값 : (3, 2) - 행, 열 


print(np.dot(A,B)) 출력값 :  [23 53 83] 

: 1행 2열로 보이지만 내적의 곱을 할 때 2행 1열로 구성된다.

 

 

Array(3차원 배열 이상) 2 3 4

K = np.array([[[1,2,3,4],[5,6,7,8],[9,10,11,12]],[[13,14,15,16],[17,18,19,20],[21,22,23,24]]])
print(K .shape) 출력값 : (2, 3, 4) - 2면 3행 4열
print(K )

- 출력값 - 
[[[ 1  2  3  4]
  [ 5  6  7  8]
  [ 9 10 11 12]]

 [[13 14 15 16]
  [17 18 19 20]
  [21 22 23 24]]]

print(np.ndim(K ))  출력값 : 3(차원)

 

 

신경망의 내적

X = np.array([1,2]) # x1 = 1, x2 = 2
W = np.array([[1,3,5],[2,4,6]])
Y = np.dot(X,W) # 1행 2열 x 2행 3열 -- # Y = X * W + B

print(Y) 출력값 : [ 5 11 17] - (1x1+ 2x2) (1x3 + 2x4) (1x5 + 2x6)

 

- 각 층의 신호 전달 구현 – 입력층에서 1층으로 신호 전달

 

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

import numpy as np

# 1층에서의 활성화 함수 처리
def sigmoid(x): # 시그모이드 함수 정의
    return 1/ (1 + np.exp(-x))

# 입력층에서 1층오로의 신호 전달
X = np.array([1.0,0.5]) # x1 , x2
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])

print(W1.shape) 츨력값: (2, 3)
print(X.shape)  츨력값: (2,)
print(B1.shape) 츨력값: (3,)

A1 = np.dot(X,W1) + B1 # =  A(1) = XW(1) + B(1)

Z1 = sigmoid(A1)
print(A1) 츨력값: [0.3 0.7 1.1] # 활성화 함수를 거치기 전
print(Z1) 츨력값: [0.57444252 0.66818777 0.75026011] # 활성화 함수를 거친후, 0 초과 1미만의 값.

# x : 0.3 0.7 1.1 - y : 0.57444252 0.66818777 0.75026011

 

- 1층에서 2층으로의 신호 전달

W2 = np.array([[0.1,0.4],[0.2,0.5],[0.3,0.6]]) # 3행 2열
B2 = np.array([0.1,0.2])

A2 = np.dot(Z1, W2) + B2
Z2 = sigmoid(A2)
print(A2) 출력값 :  [0.51615984 1.21402696]
print(Z2) 출력값 :  [0.62624937 0.7710107 ]

 

- 2층에서 3(출력층)으로의 신호 전달

W3 = np.array([[0.1,0.3],[0.2,0.4]])
B3 = np.array([0.1,0.2])

A3 = np.dot(Z2,W3) + B3 출력값 : [0.31682708 0.69627909]

 

출력층 설계 – 항등함수와 소프트맥스 함수 구현

 

 

항등함수(identity function) : 입력을 그대로 출력, 회귀(연속적인 값) 

임시적으로 버퍼를 하는 것으로, 잠깐 보관했다가 한번에 보내주는 역할을 합니다. 

항등 함수의 정의 - 출력단 
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.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

# 1층에서의 활성화 함수 처리
def sigmoid(x): # 시그모이드 함수 정의
    return 1/ (1 + np.exp(-x))

# 항등 함수의 정의 - 출력단
def identity_function(x):
    return x

# 데이터 전달
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

if __name__ == "__main__":
    network = init_network()

    x = np.array([1.0,0.5])
    y = forward(network, x)

    print(y) 출력값 : [0.31682708 0.69627909]

 

- 소프트맥스 함수

: 비율적으로 빠르게 변환할 수 있도록 도와주는 함수입니다. 

소프트맥스 함수의 특징

- 소프트맥스 함수는 0에서 1사이의 실수로 출력합니다. 
- 출력의 총합은 1 입니다.  
- 출력을 확률로 해석할 수 있습니다. 
- 신경망을 학습시킬 때는 출력층에서 소프트맥스 함수를 사용합니다. 
- 추론 단계에서는 출력층의 소프트맥스 함수를 생략하는 것이 일반적 입니다. 
    각 원소의 대소 관계는 변하지 않습니다.  
    지수 함수 y = exp(x)가 단조 증가 함수이기 때문입니다.

import numpy as np

a = np.array([0.3,2.9,4.0])
exp_a = np.exp(a)
print(exp_a) 출력값 :  [ 1.34985881 18.17414537 54.59815003] : 분자

sum_exp_a = np.sum(exp_a)
print(sum_exp_a) 출력값 :  74.1221542101633 : 분모

y = exp_a / sum_exp_a
print(y) 출력값 :  [0.01821127 0.24519181 0.73659691] 
         -> 비율 ( 1.8% , 24.5% ,73.6% )

 

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


- 오버플로 문제 ( 출력되는 값이 아주 크게 나올 때) 
: 소프트맥스의 지수 함수를 계산할 때 어떤 중수를 더하거나 빼도 결과는 바뀌지 않는다는 것을 이용합니다. 
: 오버플로를 막을 목적으로는 입력 신호 중 최댓값을 이용하는 것이 일반적입니다. 

a = np.array([1010,1000,990])

def softmax_computer(a):
    max =  np.max(a)
    exp_a = np.exp(a-max) # 오버플로 처리 - 작은 값으로 바꿈.
    sum_exp_a = np.sum(exp_a)
    y = exp_a / sum_exp_a

    return y

y = softmax_computer(a)
print(y) 
출력값 : [9.99954600e-01 4.53978686e-05 2.06106005e-09]