* 퍼셉트론을 통해 논리 연산을 하기 위해서는 사람이 직접 가중치, 편향을 설정해야 했다.
* 하지만, 신경망은 학습을 통해 자동으로 가중치를 갱신하기 때문에 인간이 가중치를 직접 설정할 필요가 없다.
3층 신경망 구현하기
* input layer, hidden layer1, hidden layer2, output layer(전달된 값을 forwarding만 해주는 층)로 이뤄진 3층 신경망 모형은 위와 같다.
* 오늘은 x1, x2의 입력이 신경망을 거쳐 output y1, y2으로 도출되는 과정을 살펴보고 python code로 구현해보겠다.
1) input layer to hidden layer1
* 위 $a_1^{(1)}$은 다음과 같은 수식으로 나타낼 수 있다.
$a_1^{(1)} = w_11^{(1)} x_1 + w_12^{(1)} x_2 + b_1^{(1)}$
* 위와 같이 표현한 1층의 가중치 부분은 아래와 같이 행렬의 곱을 통해 나타낼 수 있다.
$A_1^{(1)} = X W^{(1)} + B^{(1)}$
* 각 요소들은 아래와 같다.
$A^{(1)} = \begin{bmatrix} a_1^{(1)} \\ a_2^{(1)} \\ a_3^{(1)} \end{bmatrix}$, $X = \begin{bmatrix} x_1 \\ x_2 \end{bmatrix}$, $B^{(1)} = \begin{bmatrix} b_1^{(1)} \\ b_2^{(1)} \\ b_3^{(1)} \end{bmatrix}$, $W^{(1)} = \begin{bmatrix} w_11^{(1)} & w_21^{(1)} & w_31^{(1)} \\ w_12^{(1)} & w_22^{(1)} & w_32^{(1)} \end{bmatrix}$
import numpy as np
X = np.array([1.0, 0.5])
B1 = np.array([0.1, 0.2, 0.3])
W1 = np.array([[0.1,0.3,0.5],
[0.2,0.4,0.6]])
A1 = np.dot(X, W1) + B1
A1 #weighted sum
#array([0.3, 0.7, 1.1])
* 이 weighted sum이 activation function인 sigmoid를 거치면 아래와 같이 값이 바뀐다.
def sigmoid(X):
return 1/(1+np.exp(-X))
Z1=sigmoid(A1)
Z1
#array([0.57444252, 0.66818777, 0.75026011])
2) hidden layer1 to hidden layer2
*hidden layer1의 output을 input으로 사용하여 hidden layer2의 output을 도출한다.
W2=np.array([[0.1, 0.4],
[0.2, 0.5],
[0.3, 0.6]])
B2=np.array([0.1, 0.2])
A2=np.dot(Z1, W2) + B2
Z2=sigmoid(A2)
Z2
#array([0.62624937, 0.7710107 ])
3) hidden layer2 to output layer
* 우리가 구현하는 output layer는 acitvation function이 특별한 역할을 하지 않고 전달된 값을 forwarding하기만 하므로 새로운 activation function을 정의한다.
def identity(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
Z3=identity(A3)
Z3 #array([0.31682708, 0.69627909])
3층 신경망 구현 정리 코드
def sigmoid(X):
return 1/(1+np.exp(-X))
def identity(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(a3)
return y
network=init_network()
x=np.array([1.0, 0.5])
y=forward(network, x)
y
#array([0.31682708, 0.69627909])
출력층 설계하기
* 출력층의 acitvation function은 model의 목적에 따라 달라진다.
* 만약 회귀를 위한 모델을 생성한다면 출력층의 activation function은 항등 함수(전달되는 값을 그대로 forwarding해주는)등을 사용한다.
* 분류를 위한 모델에는 softmax함수 등을 사용한다.
소프트맥스 함수(softmax function)
$y_k = \frac{exp(a_k)}{\sum\limits_{i=1}^n {exp(a_i)}}$
* n은 출력층의 뉴런 수(분류할 class 개수)이다.
* $y_k$는 output y 중 k번째라는 뜻과 같다.
#softmax
def softmax(x):
exp_x=np.exp(x)
sum_exp_x=np.sum(exp_x)
result=exp_x/sum_exp_x
return result
a=np.array([0.3, 2.9, 4.0])
softmax(a)
#array([0.01821127, 0.24519181, 0.73659691])
소프트맥스 함수 구현시 overflow 문제
* softmax함수를 구현할 때 문제점은 지수함수이기 때문에 exp()안에 큰 수가 주어진 상태에서 연산을 수행하는 것이 불가능하다는 점이다(값이 너무 커져서 계산이 불가하다 - overflow).
* 이 문제는 다음과 같이 해결 가능하다.
* 소프트맥수 함수는 다음이 성립한다.
$y_k = \frac{exp(a_k)}{\sum\limits_{i=1}^n {exp(a_i)}} = \frac{C exp(a_k)}{C \sum\limits_{i=1}^n {exp(a_i)}} = \frac{exp(a_k + logC)}{\sum\limits_{i=1}^n {exp(a_i+logC)}} = \frac{exp(a_k + C')}{\sum\limits_{i=1}^n {exp(a_i+C')}}$
* 즉, exponential 지수에 어떤 값을 더하거나 빼도 softmax함수의 결과는 동일하다는 것이다.
* 이 idea를 활용하여 input값에 일정 값을 빼준다면 overflow를 방지하면서 동일한 결과를 얻을 수 있다.
* 이 때, input값 중에 가장 큰 값을 빼주는 것이 일반적이다.
def softmax(x):
C=np.max(x)
exp_x=np.exp(x-C)
sum_exp_x=np.sum(exp_x)
result=exp_x/sum_exp_x
return result
a=np.array([1010, 1000, 990])
softmax(a)
#array([9.99954600e-01, 4.53978686e-05, 2.06106005e-09])
소프트맥스 함수의 특징
* 소프트맥스 함수의 출력은 0에서 1.0 사이의 실수이다. 또한 모든 출력 값을 더하면 1이된다. 이 성질은 소프트맥스 출력값을 확률로 간주할 수 있게 해준다.
* 소프트맥스의 또 다른 특징은 exponential 함수가 단조함수이기 때문에(a, b가 a<=b일 때 f(a) <= f(b)) 원소간의 대소 관계와는 관계가 없다는 점이다. 다시 말하면, 가장 높은 값을 가지는 원소의 class로 최종 분류를 하고 싶다면, 소프트맥스 함수를 사용하는게 의미가 없다는 뜻이다. 소프트맥스 함수를 취함으로서 얻을 수 있는 것은 0~1 사이의 실수로 표현되는 확률 값 뿐이다. 원소들의 scale이 조정될 뿐 대소관계는 변하지 않기 때문에 필요한 경우만 소프트맥스 함수를 사용하기로 한다(보통 훈련 단계에서는 소프트맥스 함수를 사용하고 추론 단계에서는 사용하지 않는 것이 일반적이다).
출력층의 뉴런 수 정하기
* 출력 층의 뉴런 개수는 분류하고 싶은 class의 개수대로 정하는 것이 일반적이다. 만약 0~9까지 써있는 손 글씨를 분류하고 싶다면 10개의 출력층 뉴런을 설정해 놓아야 할 것이다.
* 주어진 문제가 회귀 문제일 경우 단일 값을 출력해야 하므로 출력층의 뉴런 개수는 1이 된다.
배치 처리
* input layer, hidden layer1, hidden layer2, output layer로 구성되어 있는 모델을 생각해보자.
* 데이터는 mnist dataset을 사용한다고 가정한다.
* input layer는 28x28 image를 flatten한 784개의 픽셀 정보를 받아야 하므로 neuron 개수는 784개이다.
* hidden layer1은 neuron 개수가 50개라고 해보자.
* hidden layer2는 neuron 개수가 100개라고 해보자.
* output layer는 10개의 class를 구분해야 하므로 뉴런이 10개이다.
* 모델의 가중치 행렬은 아래와 같을 것이다.
W1 : 784 x 50
W2 : 50 x 100
W3 : 100 x 10
* 만약 784 차원의 벡터를 1개 입력하면 output은 10차원의 벡터가 될 것이다.
* 1 배치에 100개의 데이터가 들어가는 배치처리를 거친 데이터셋을 사용한다면 output은 100x10 행렬이 될 것이다.
배치 처리의 장점
* 수치 계산 라이브러리들이 큰 배열을 효율적으로 처리할 수 있도록 최적화 되어 있기 때문에 데이터를 1개씩 계산하는 것 보다 여러 개를 배치로 묶어 계산하는게 속도가 더 빠르다.
* 여러 데이터를 한 번에 읽어오는 것을 통해 I/O 횟수가 줄어든다. I/O시에는 CPU가 연산을 멈추고 I/O처리를 하게 되는데, 이러한 I/O 횟수를 줄임으로서 훈련에 더욱 집중할 수 있게 된다(I/O는 CPU입장에서 매우 시간이 오래 걸리는 task이다).
def get_data():
(x_train, y_train), (x_test, y_test) = \
load_mnist(normalize=True, flatten=True, one_hot_label=False)
def init_network():
with open("sample_weight.pkl", 'rb') as f: #pickle file에 저장된 weight 불러오기
network=picle.load(f)
return network
def predict(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 = softmax(a3)
return y
x, t = get_data()
network=init_network()
batch_size=100
accuracy_cnt=0
for i in range(0, len(x), batch_size):
x_batch=x[i:i+batch_size]
y_batch=predict(network, x_batch)
p=np.argmax(y_batch, axis=1)
accuracy_cnt += np.sum(p==t[i:i+batch_size])
print('Accuracy: {}'.format(float(accuracy_cnt)/len(x)))
참고자료 :
https://book.naver.com/bookdb/book_detail.nhn?bid=11492334
'Deep Learning > 밑바닥부터 시작하는 딥러닝' 카테고리의 다른 글
Backpropagation (0) | 2022.01.27 |
---|---|
경사하강법(Gradient Descent method) (0) | 2022.01.25 |
수치 미분 (0) | 2022.01.25 |
손실 함수 (0) | 2022.01.25 |
퍼셉트론 (0) | 2021.11.17 |