티스토리 뷰

AI/밑바닥부터 시작하는 딥러닝

신경망(1)

취뽀가자!! 2018. 9. 3. 11:25

신경망

퍼셉트론과 신경망

신경망은 퍼셉트론이 발전된 것이라고 볼 수 있습니다. 둘의 차이점이라면 활성화 함수와 매개변수가 가장 큰 차이점입니다. 

퍼셉트론은 매개변수를 사람이 직접 설정하지만 신경망은 이 매개변수를 사람이 정하는 것이 아니라 스스로 지정하여 올바른 값이 나올 수 있도록 하는 것입니다. 그리고 퍼셉트론은 활성화 함수가 계단식 함수이고, 신경망은 시그모이드 함수, ReLU 함수 등 여러가지를 사용합니다.

활성하 함수

활성화 함수는 일반적으로 입력 신호의 총합을 출력 신호로 변환하는 함수를 말합니다. 그림으로 표현해 보면 아래와 같습니다.

위 그림은 말로 옯기면 입력신호와 가중치를 곱한뒤 총합을 활성화 함수인 시그모이드 함수를 통과시켜 출력하는 것을 의미합니다. 


계단 함수

퍼셉트론의 활성화 함수인 계단 함수를 한번 구현해 보겠습니다.

1
2
3
4
5
6
7
import numpy as np
 
def step_function(x):
    y=x>0
    return y.astype(np.int)
 
print(step_function(np.array([1.0,2.0])))
cs

활성화 함수를 구현하는 코드는 4와 5 총 2줄입니다.

스크립트가 아닌 인터프리터에서 위 코드를 쳐보면 부등호 연산 시 bool타입으로 변환되는 것을 볼 수 있습니다. 그리고 이걸 asytpe(np.int)로 변환하면 true와 false가 아닌 1과 0으로 구분할 수 있게 됩니다.


이제 그래프를 그려서 어떤 형태로 나오는지도 알아보겠습니다.

1
2
3
4
5
6
7
8
9
10
11
import numpy as np
import matplotlib.pylab 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.2#y측 범위 지정
plt.show()
cs


시그모이드 함수

시그모이드 함수는 계단 함수처럼 0이었다가 갑자기 1이 되지 않고, 0부터 1까지의 변화가 부드럽게 바뀝니다.


시그모이드 함수를 구현해 보겠습니다.

1
2
3
4
5
6
import numpy as np
 
def sigmoid(x):
    return 1/(1+np.exp(-x))
 
print(sigmoid(np.array([-1.0,1.0,2.0])))
cs

*np.array()부분이 되는 이유는 numpy의 브로드캐스트 때문입니다. 브로드캐스트는 넘파이 배열과 스칼라값의 연산을 넘파이 배열의 원소 각각과 스칼라   값의 연산으로 바꿔 수행하는 것을 말합니다.


시그모이드를 그래프로 출력해보겠습니다.

1
2
3
4
5
6
7
8
9
10
11
import numpy as np
import matplotlib.pyplot as plt
 
def sigmoid(x):
    return 1/(1+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)
plt.show()
cs

위 코드를 실행해보면 0부터 1까지 부드럽게 연결 되는 것을 볼 수 있습니다.


시그모이드 함수와 계단 함수의 차이

아까부터 계속 언급해 왔지만 둘의 가장 큰 차이는 '매끄러움"의 차이입니다. 계단은 0이었다가 갑자기 1이 되어 0과 1만 돌려주지만 시그모이드는 연속적으로 변화하여 실수값도 돌려줍니다. 즉, 퍼셉트론에서는 0이나 1이 흘렀다면, 신경망에서는 연속적인 실수가 흐릅니다.

비선형 함수

시그모이드와 계단의 공통점은 둘은 모두 1과 0사이를 나타내고 비선형 함수라는 것입니다.

신경망에서는 활성화 함수로 비선형 함수를 사용해야 하고, 선형 함수는 사용하는 것을 권하지 않습니다. 그 이유는 선형 함수를 쓰게 되면 다층 신경망의 구현이 의미가 없어지기 때문입니다.

간단한 수식으로 증명해 보겠습니다.

h(x)=cx를 활성화 함수로 사용한 3층 네트워크라고 가정해 보겠습니다. 가정을 식으로 나타내면 y(x)=h(h(h(x)))가 되는데 이걸 계산해보면 y(x)=c*c*c*x가 됩니다. 근데 이건 c의 세제곱을 a라고 치환하면 결국 h(x)=cx와 같은 꼴이 됩니다. 따라서 선형함수로는 은닉층을 표현할 수 없게 되는 것이죠. 따라서 선형함수를 권장하지 않습니다.


ReLU 함수

ReLU함수는 신경망에서 활성화 함수로 쓰이는 또 다른 함수입니다.(일반적으로 시그모이드는 교육용으로, ReLU는 현업에서 많이 쓴다고 합니다.)
ReLU함수는 입력이 0을 넘으면 그 입력을 그대로 출력하고, 0이하이면 0을 출력하는 함수입니다.

식은 이렇게 나타내면 됩니다.


그럼 구현을 해보겠습니다.

1
2
3
4
import numpy as np
 
def relu(x):
    return np.maximum(0,x)
cs
maximum()은 두 입력 중 큰 값을 선택해 반환하는 함수입니다.

다차원 배열

다차원 배열을 통해 행렬에 대해 설명하기 전에 numpy로 다차원 배열을 먼저 작성해 보겠습니다.
1
2
3
4
5
6
7
8
9
10
>>> import numpy as np
>>> a=np.array([1,2,3,4])
>>> print(a)
[1 2 3 4]
>>> np.ndim(a)
1
>>> a.shape
(4,)
>>> a.shape[0]
4
cs
np.ndim()함수를 사용하면 배열의 차원 수를 확인 할 수 있습니다. shpae은 배열의 형상을 확인 할 수 있습니다.

이차원 배열을 확인 한 결과입니다.
1
2
3
4
5
6
7
8
9
>>> b=np.array([[1,2],[3,4],[5,6]])
>>> print(b)
[[1 2]
 [3 4]
 [5 6]]
>>> np.ndim(b)
2
>>> b.shape
(32)
cs
1차원 배열은 벡터라고 표현하고, 2차원 배열을 특히 행렬이라고 부릅니다.

행렬의 내적(행렬 곱)

위 그림에서처럼 행렬 내적은 왼쪽 행렬의 행(가로)과 오른쪽 행렬의 열(세로)을 원소별로 곱하고 그 값들을 더해서 계산합니다. 그리고 그 계산 결과가 새로운 다차원 배열의 원소가 됩니다.


코드로 구현해 보겠습니다.

1
2
3
4
5
6
7
8
9
>>> import numpy as np
>>> a=np.array([[1,2],[[3,4]])
>>> a=np.array([[1,2],[3,4]])
>>> a.shape           
(22)
>>> b=np.array([[5,6],[7,8]])           
>>> np.dot(a,b)           
array([[1922],
       [4350]])
cs

이 코드에서 a와 b는 2*2 행렬이며, 이들 두 행렬의 내적은 넘파이 함수 np.dot()으로 계산하는데, np.dot()은 넘파이 배열 2개를 인수로 받아 그 내적을 반환합니다.


행렬곱을 할 때 주의해야 할 점은 행렬의 형상에 주의해야 합니다. 이 말은 행렬 a의 1번째 차원의 원소 수(열 수)와 행렬 b의 0번째 ㅏ원의 원소 수(행 수)가 같아야 합니다. 예를 들어 보면 3x2와 2x4를 행렬곱하면 3x4가 되고, 3x2와 2를 내적하면 3이 됩니다.


행렬이 매우 중요한 이유는 다층 신경망을 빠르고 간결하게 구현하는데 핵심이 되기 때문입니다.


3층 신경망 구현

각 층의 신호 전달 구현

위 그림에서 입력층을 1층, 은닉층을 2층, 출력층을 3층이라 하겠습니다.


색칠한 노드를 식으로 나타내 보겠습니다.

a1=w11*x1+w12*x2+b1 

지금은 가중치와 입력값이 적어서 손으로 써줄 수 있지만 이보다 더 많은 경우에는 너무 귀찮을 수 있습니다. 그래서 이 식을 행렬의 내적을 이용하여 식을 간소화 할 수 있습니다.

A=XW+B

그리고 이 식을 활성화 함수에 넣어 출력하는 내용입니다.


위 내용을 코드로 구현해 보겠습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import numpy as np
 
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])
 
def sigmoid(x):
    return 1/(1+np.exp(-x))
 
print(W1.shape) #(2,3)
print(X.shape)  #(2,)
print(B1.shape) #(3,)
 
A1=np.dot(X,W1)+B1
Z1=sigmoid(A1)
 
print(A1) 
print(Z1)
cs

나머지 은닉층들도 같은 원리입니다. 여기서 2층에서 출력층으로 신호 전달할  활성화 함수가 다릅니다. 이 경우에는 출력층의 활성화 함수는 풀고자 하는 문제의 성질에 맞게 정합니다. 예를 들어 회귀에는 항등 함수를, 2클래스 분류에는 시그모이드 함수를, 다중 클래스 분류에는 소프트맥스 함수를 사용하는 것이 일반적입니다. 자세한 설명은 아래에 있는 출력층 설계에서 설명하겠습니다.


구현 정리

위에서 구현했던 코드를 정리해 보겠습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import numpy as np
 
def sigmoid(x):
    return 1/(1+np.exp(-x))
 
def identity_function(x):
    return x
 
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
 
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
 
net=init_network()
x=np.array([0.1,0.5])
y=forward(net,x)
print(y)
cs


'AI > 밑바닥부터 시작하는 딥러닝' 카테고리의 다른 글

오차역전파(2)  (0) 2018.09.18
오차역전파법(1)  (0) 2018.09.17
신경망 학습  (0) 2018.09.13
신경망(2)  (0) 2018.09.09
퍼셉트론  (0) 2018.08.27
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/02   »
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28
글 보관함