다차원 배열을 이용하여 직접 신경망을 구현해본다.
import numpy as np
import matplotlib.pylab as plt
X = np.array([1,2])
print(X, X.shape)
W = np.array([[1,3,5],[2,4,6]])
print(W, W.shape)
Y = np.dot(X,W)
print(Y)
$$\mathbf{X} = \left[\begin{array} {rrr} 1 & 2 \end{array}\right]$$
$$\mathbf{W} = \left[\begin{array} {rrr} 1 & 3 & 5 \\ 2 & 4 & 6 \end{array}\right] $$
행렬 곱은 그냥 하면 된다. numpy가 다 해줌. 이걸로 3층 신경망을 구현해본다.
입력층은 2개, 첫 번째 은닉층은 3개, 두 번째 은닉층은 2개, 출력층은 2개의 뉴런으로 구성되어 있다. 여기에 편향까지 추가해본다.
이제 수식으로 나타내보자.
$$a^{(1)}_1 = w^{(1)}_{11}x_1 + w^{(1)}_{12}x_2 + b^{(11)}_1$$
이것을 원소마다 다 해야할까? 행렬을 이용하면 간편하게 나타낼 수 있다.
$$A^{(1)} = XW^{(1)} + B^{(1)}$$
$$A^{(1)} = (a^{(1)}_1 a^{(1)}_2 a^{(1)}_3), X = (x_1 x_2), B^{(1)} = (b^{(1)}_1 b^{(1)}_2 b^{(1)}_3)$$
$$\mathbf{W} = \left[\begin{array} {rrr} w^{(1)}_{11} & w^{(1)}_{21} & w^{(1)}_{31} \\ w^{(1)}_{12} & w^{(1)}_{22} & 6w^{(1)}_{32} \end{array}\right] $$
파이썬으로 직접 구현해본다.
import numpy as np
import matplotlib.pylab as plt
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)
A1 = np.dot(X, W1) + B1
X는 초기의 입력값이고 W1은 가중치, B1은 편향이다. 이 것은 한 층(0층)만 다루고 있는 것이므로 3층으로 설계하려면 이 다음의 W2, B2값 그 이후의 W3, B3 값을 알고 있어야 한다. 구현을 정리해보면 다음과 같다.
import numpy as np
import matplotlib.pylab as plt
def sigmoid(x):
return 1 / (1+np.exp(-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 = a3
return y
network = init_network()
x = np.array([1.0,0.5])
y = forward(network, x)
print(y)
흐름을 보면 어렵지 않다. network에서 가중치와 편향을 세팅해준 후 입력을 받아 흐름대로 진행한다. 그리고 최종 값을 넘겨주고 있다. 신경망은 분류(데이터가 어느 클래스에 속하는지)와 회귀(입력한 데이터에서 연속적인 수치를 예측할 때)에 사용되는데 어떤 문제냐에 따라서 출력이 다르다. 일반적으로 회귀에는 항등 함수를, 분류에는 소프트맥스 함수를 사용한다.
항등 함수(identity function)는 입력을 그대로 출력한다. 입력과 출력이 항상 같다는 의미이다. 하지만 소프트맥스 함수는 조금 다르다.
$$ y_k = \frac{exp(a_k)}{\sum_{i=1}^{n} exp(a_i)}$$
$$exp(x) = e^x$$
소프트맥스 함수의 분자는 입력 신호의 지수 함수, 분모는 모든 입력 신호의 지수 함수의 합으로 구성된다. 어려우니까 구현을 먼저 해본다.
import numpy as np
import matplotlib.pylab as plt
a = np.array([0.3, 2.9, 4.0])
exp_a = np.exp(a)
print(exp_a)
sum_exp_a = np.sum(exp_a)
print(sum_exp_a)
y = exp_a / sum_exp_a
print(y)
def softmax(a):
exp_a = np.exp(a)
sum_exp_a = np.sum(exp_a)
y = exp_a / sum_exp_a
return y
하지만 소프트맥스 함수는 결함이 하나 있다. 바로 오버플로가 생길 수 있다는 점이다. 자연상수를 이용하고 있기 때문에 2.7어쩌고저쩌고 웅앵웅에 값이 계속 커지고 문제가 생길 수 있다. 바로 아래와 같이 말이다.
import numpy as np
import matplotlib.pylab as plt
def softmax(a):
exp_a = np.exp(a)
sum_exp_a = np.sum(exp_a)
y = exp_a / sum_exp_a
return y
a = np.array([1010,1000,990])
print(softmax(a))
이렇게 오버플로가 난다. 이 것을 해결하기 위하여 입력 신호의 최댓값을 빼줘서 구현한다. 솔직히 먼 소리인지 잘 모르겠고 알고리즘에서 이런식으로 많이 쓰기때문에 가슴으로 이해하자.
import numpy as np
import matplotlib.pylab as plt
def softmax(a):
c = np.max(a)
exp_a = np.exp(a - c)
sum_exp_a = np.sum(exp_a)
y = exp_a / sum_exp_a
return y
a = np.array([1010,1000,990])
print(softmax(a))
소프트맥스 함수의 특징은 출력이 0에서 1.0 사이의 실수이며 출력의 총 합이 1이라는 사실이다. 이는 확률로 생각할 수 있다. 그렇기 때문에 출력 뉴런을 구할 때 분류하고 싶은 갯수만큼 정하면 된다.
'Python > DeepLearning' 카테고리의 다른 글
퍼셉트론에서 신경망으로(5) (0) | 2019.04.23 |
---|---|
퍼셉트론에서 신경망으로(4) (1) | 2019.04.22 |
퍼셉트론에서 신경망으로(2) (0) | 2019.04.22 |
퍼셉트론에서 신경망으로(1) (0) | 2019.04.22 |
퍼셉트론(Perceptron) 알고리즘 (0) | 2019.04.22 |