3장 신경망을 배우기 전, 

퍼셉트론에 대해 알고 가야할 필요가 있다.

 

퍼셉트론이란 다수의 신호를 입력으로 받아 하나의 신호를 출력하는 것을 말한다.

또한 이를 통해서 AND,OR,NAND 게이트를 구현해 보았다. 

 

퍼셉트론은 신경망의 기초가 되는 개념으로, 신경망과 유사한 개념이라고도 볼 수 있다.

 

신경망

위 그림을 신경망이라고 하고, 왼쪽부터 각각 입력층, 은닉층, 출력층이라고 한다.

'은닉층'이라고 하는 이유는 사람의 눈에는 보이지 않는다. (입력, 출력층과 달리)

 

 또한 왼쪽부터 책에서는 0층,1층,2층이라고도 말한다.

 

활성화 함수

활성화 함수란 입력 신호의 총합을 출력 신호로 변환하는 함수를 말한다.

 

앞에서 배운 시그모이드, 소프트맥스, 렐루 등이 그 예이다.

시그모이드 함수

exp^-x는 e^-x를 뜻하며,  e는 자연상수로 2.7182...의 값을 가지는 실수이다.

 

계단함수를 이용해서 시그모이드 함수를 더 알아보도록 하자.

계단함수는 입력이 0을 넘으면 1을, 그외에는 0을 출력하는 함수이다.

이를 파이썬으로 구현해보면 다음과 같다.

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

여기서 만약 x인수에 단일 실수값이 아닌, 배열을 넣고 싶다면??

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

계단함수의 그래프

import numpy as np
import matplotlib.pyplot as plt
def step_function(x):
    return np.array(x>0, dtype=np.int)

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()

계단 함수라는 말 그대로 0을 기준으로 출력이 0->1로 바로 바뀌는 것을 볼 수 있다.

 

이어서 시그모이드 함수를 직접 구현해보자.

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

 인수 x가 배열이여도 결과가 정상적으로 출력된다!

x = np.array([-1.0, 1.0, 2.0])
sigmoid(x)

array([0.26894142, 0.73105858, 0.88079708])

배열을 넣어도 올바르게 결과가 나오는 것은 넘파이 라이브러리의 브로드캐스트 기능덕분이다.

브로드캐스트는 넘파이 배열과 스칼라값의 연산을 넘파이 배열의 원소 각각과 스칼라값의 연산으로 바꿔 수행하는 것을 말한다.

 

예를 들면

t = np.array([1.0, 2.0, 3.0])
1.0 + t

array([2., 3., 4.])
1.0 / t

array([1.        , 0.5       , 0.33333333])

스칼라값 1.0 과 배열 t 사이에서 연산이 각 원소별로 이루어진것을 브로드캐스팅이라고 하는것.!

 

이제 시그모이드 함수를 그래프로 그려보자.

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

 

위의 계단함수와 비교해봤을때 차이는 매끄럽냐vs아니다. (연속적)

이 매끈한것이 신경망 학습에서 중요한 역할을 하게된다고 한다.

 

비선형 함수

계단함수, 시그모이드 함수의 공통점은 모두 비선형 함수라는 것이다. 

시그모이드 = 곡선, 계단 함수 = 구부러진 직선

 

신경망에서 사용하는 활성화 함수로는 비선형 함수만을 사용해야 한다.  (선형 함수 =>   y= ax+ b인 직선)

(신경망의 층을 깊게 하는 의미가 없어지기 때문에)

 

선형 함수의 문제는 층을 아무리 깊게 해도 '은닉층이 없는 네트워크'로도 똑같은 기능을 할 수 있다는 데 있다.

ex) h(x) =cx이고 3층 네트워크라고 하면 y(x)=h(h(h(x)))이고, 이는 y(x)=c*c*c , c^3이다.

즉, 은닉층이 필요없는 구조라고 볼 수 있다. 이러면 은닉층이 갖는 benefits을 포기하는 것이기 때문에 

더 구체적이고 예민한 비선형함수를 사용해야 하는것이다.

 

ReLU 함수

 

시그모이드 함수는 신경망에서 예전부터 많이 이용해왔는데, 최근에는 ReLU함수를 주로 이용한다고 한다.

 

ReLU함수는 입력이 0을 넘으면 그 입력을 그대로 출력, 0이하이면 0을 출력하는 함수다.

식으로는

 

코드로는

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

 

다차원 배열

신경망을 효율적으로 구현하려면 다차원 배열의 개념을 알고 있어야 한다.

다차원 배열의 기본 개념은 "숫자의 집합"이라고 할 수 있다.

숫자가 한 줄로 늘어선 것, 직사각형으로 늘어놓은 것, 3차원으로 늘어놓은 것 등등... 을 모두 다차원 배열이라고 할 수 있다.

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

[1 2 3 4]
np.ndim(A)

1
A.shape

(4,)

배열의 차원수를 알려주는 np.ndim()

배열의 모양 shape (함수아님 인스턴수 변수임)

.shape은 또한 튜플을 반환하는데, 그 이유는 1차원 배열일지라도 다차원 배열일 때와 통일된 형태로 반환하기 위함이다.

 

ex)2차원 배열일 때는 (4,3) , 3차원 배열일 때는 (4,3,2) 를 반환한다.

 

이제 2차원 배열을 구현해보자.

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

[[1 2]
 [3 4]
 [5 6]]
np.ndim(B)
2
B.shape
(3,2)

여기서는 3X2 배열인 B를 작성했다.

3x2 행렬이라고 볼 수 있다.

-----행렬 생략-------

 

3층 신경망 구현하기

이제 3층 신경망을 구현해보고자 한다. ( 3층 = 0층~3층, 총 4개의 층)

 

0층 : 입력층 - 2개

1층 : 은닉층 - 3개

2층 : 은닉층 - 2개

3층 : 출력층 - 2개의 뉴런으로 구성되어 있다.

 

여기서 각 뉴런사이의 화살표에 집중해보자.

입력층의 뉴런에서 은닉층의 뉴런으로 이어져 있는 선이 있다.

 

더 자세히 말하자면 입력층의 두 번째 뉴런에서 은닉층의 첫 번째 뉴런으로 이어져 있는데,

이를 W1,2라고 표현한다. (앞에가 도착지, 뒤에가 출발지)

 

이제 절편 (편향)을 추가하여 신호 전달을 구현해보자.

위의 그림에서 a1을 수식으로 나타내보자.

a1 = w11 * x1 + w12 * x2 + b

 

여기서 행렬곱을 사용하여 1층의 가중치 부분을 간소화 할 수 있다.

A = XW + B

행렬 A, X, B, W는 각각 다음과 같다.

 

A = (a1 a2 a3) , X = (x1 x2) , B = (b1, b2 , b3)

W = (w11, w21, w31)

        (w12, w22, w32)

 

이제 식 A = XW +B를 구현해보자.

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(W1.shape)
print(X.shape)
print(B1.shape)

(2, 3)
(2,)
(3,)

A1 = np.dot(X, W1) + B1

위 코드를 그림으로 보면 아래처럼 된다.

h(), 즉 은닉층의 노드를 보면 a1이 z1로 변하는데,

a1은 앞의 입력층에서 1 (편향, 절편) + w11 * x1 + w12 * x2의 합이고,

이 a1이 활성화 함수를 지나서 z1로 변환된 것이다.

 

이제 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)
(3,)
(3, 2)
(2,)

A2 = np.dot(Z1, W2) + B2
Z2 = sigmoid(A2)

1층과 마찬가지로 같다!

 

이제 마지막으로 2층에서 출력층으로 가는 신호 전달과정을 살펴보자.

 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)  # 혹은 Y=A3

 

'밑바닥 딥러닝' 카테고리의 다른 글

Ch 5-2 오차 역전파 (Error BackPropagation)  (0) 2023.05.27
Ch 5-1 오차 역전파 ( Error Backpropagation )  (0) 2023.05.27
Ch4-2 신경망 학습  (0) 2023.05.26
Ch4-1 신경망 학습  (0) 2023.05.17
Ch3-2 출력층 설계, 실습  (0) 2023.05.17

+ Recent posts