본문 바로가기
Data Science/문과생도 이해하는 딥러닝

문과생도 이해하는 딥러닝 (4) - 신경망구현, 활성화함수, 배치

by 싸코 2017. 12. 24.


2017/09/27 - 문과생도 이해하는 딥러닝 (1) - 퍼셉트론 Perceptron

2017/10/18 - 문과생도 이해하는 딥러닝 (2) - 신경망 Neural Network

2017/10/25 - 문과생도 이해하는 딥러닝 (3) - 오차 역전파, 경사하강법



간단하게 MNIST 데이터를 이용해서 숫자를 구분할 수 있는 인공신경망 구현을 실습해보았다. 그리고 앞으로 계속 사용되는 주요 개념들에 대해서 복습을 진행하였다.

Sigmoid 함수, ReLU 함수, Softmax 함수, Batch 개념 등을 또한 다루었다. 앞으로 계속 중요하게 다루는 개념이므로 직접 코드를 치면서 짚어볼 필요가 있었다.

 


신경망 복습 Neural Network

문과생도 이해하는 딥러닝 (4)


1. Sigmoid 함수

지난 포스팅에서 시그모이드 함수를 다루었고 뉴런의 활성화함수로서 계단함수와 비교했을 때 비선형적으로 매끄럽게 값을 출력할 수 있기 때문에 사용한다고 하였으며, 이진 분류 문제를 위해 주로 사용된다고 하였다.

 

아래는 Jupyter Notebook을 사용하여 시그모이드 함수를 표현해보았다

 

In [2]:
def sigmoid(x):
    return 1/(1+np.exp(-x))
In [3]:
x = np.array([-1.0, 1.0, 2.0])
In [4]:
sigmoid(x)
Out[4]:
array([ 0.26894142,  0.73105858,  0.88079708])
In [5]:
x = np.arange(-5.0, 5.0, 0.1)
y = sigmoid(x)
plt.plot(x, y)
plt.plot([0,0],[1.0,0.0], ':')
plt.ylim(-0.1, 1.1) #y축 범위 지정
plt.title('Sigmoid Function')
plt.show()
 

 

 

2. ReLU 함수

Relu 함수는 입력 값이 0 이상이면 입력 값을 그대로 출력하는 함수로 회귀 문제를 다룰 때 주로 사용된다. 최근에 주로 시그모이보다는 ReLu 함수를 주로 이용한다고 한다.

 

 

In [6]:
def relu(x):
    return np.maximum(0, x)
In [7]:
x = np.arange(-2.0, 2.0, 0.2)
y = relu(x)
plt.plot(x, y)
plt.plot([0,0],[1.0,0.0], ':')
plt.ylim(-0.1, 1.1) #y축 범위 지정
plt.title('Sigmoid Function')
plt.show()
 

 

 

 

 

3. 다차원 배열 다루기

인공신경망의 Layer들은 파이썬에서 NumPy 배열로 표현되며 가중치 매트릭스를 나타낸다. 인공신경망은 기본적으로 이 배열들 간의 곱 연산을 통해서 출력 계층에서 목표로 했던 값을 구한다.

 

 

3.1 다차원배열

In [8]:
A = np.array([1,2,3,4])
print(A)
print(np.ndim(A))
print(A.shape)
print(A.shape[0])
[1 2 3 4]
1
(4,)
4
In [9]:
B = np.array([[1,2],[3,4],[5,6]])
print(B)
print(np.ndim(B))
print(B.shape)
[[1 2]
 [3 4]
 [5 6]]
2
(3, 2)

 

 

3.2 행렬의 내적

행렬 곱을 위해서는 (a,b) * (c,d) 일 때

1) b = c 이어야 하고
2) 결과 행렬은 (a,d) 이다
In [10]:
A = np.array([[1,2],[3,4]])
A.shape
Out[10]:
(2, 2)
In [11]:
B = np.array([[5,6],[7,8]])
B.shape
Out[11]:
(2, 2)
In [12]:
np.dot(A,B)
Out[12]:
array([[19, 22],
       [43, 50]])

 

 

행렬의 내적은 순서가 바뀌면 결과도 바뀐다

In [13]:
np.dot(A,B) == np.dot(B,A)
Out[13]:
array([[False, False],
       [False, False]], dtype=bool)
In [14]:
A = np.array([[1,2],[3,4],[5,6]])
A.shape
Out[14]:
(3, 2)
In [15]:
B = np.array([7,8])
B.shape
Out[15]:
(2,)
In [16]:
np.dot(A,B)
Out[16]:
array([23, 53, 83])

 

 

3.3 신경망의 내적

In [17]:
X = np.array([1,2])
X.shape
Out[17]:
(2,)
In [18]:
W = np.array([[1,3,5],[2,4,6]])
print(W)
[[1 3 5]
 [2 4 6]]
In [19]:
np.dot(X,W)
Out[19]:
array([ 5, 11, 17])

 

 

 

4. 신경망 구현하기 

신경망 3층 구하기

In [20]:
# 첫번째 은닉층 계산
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,)
In [21]:
A1 = np.dot(X, W1) + B1
A1
Out[21]:
array([ 0.3,  0.7,  1.1])
In [22]:
Z1 = sigmoid(A1)
Z1
Out[22]:
array([ 0.57444252,  0.66818777,  0.75026011])
In [23]:
# 두번째 은닉층 계산
W2 = np.array([[0.1, 0.4],[0.2,0.5],[0.3,0.6]])
B2 = np.array([0.1, 0.2])
In [24]:
print(Z1.shape)
print(W2.shape)
print(B2.shape)
(3,)
(3, 2)
(2,)
In [25]:
A2 = np.dot(Z1, W2) + B2
Z2 = sigmoid(A2)
Z2
Out[25]:
array([ 0.62624937,  0.7710107 ])
In [26]:
def identity_function(x):
    return x
In [27]:
# 출력층 계산
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_function(A3)
Z3
Out[27]:
array([ 0.31682708,  0.69627909])
In [28]:
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 foward(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

network = init_network()
x = np.array([1.0, 0.5])
y = foward(network, x)
print(y)
[[ 0.31682708  0.69627909]]

 

 

 

5. Softmax 함수

멀티 분류에서 소프트맥스 함수를 사용한다는 이야기를 많이 들었다. 카테고리 분류 등에서 마지막 출력층에서 활성화함수로 사용하여 카테고리 별로 확률과 비슷한 값이 나와 확률과 비슷하게 해석한다고 한다.

 

In [29]:

a = np.array([0.3, 2.9, 4.0])

exp_a = np.exp(a)
print(exp_a)
[  1.34985881  18.17414537  54.59815003]
In [30]:
sum_exp_a = np.sum(exp_a)
print(sum_exp_a)
74.1221542102
In [31]:
y = exp_a / sum_exp_a
print(y)
[ 0.01821127  0.24519181  0.73659691]
In [32]:
def softmax(a):
    exp_a = np.exp(a)
    sum_exp_a = np.sum(exp_a)
    y = exp_a / sum_exp_a
    
    return y

 

 

 

소프트맥스 함수 구현시 주의할 점은 소프트맥스는 지수함수를 사용하기 때문에 값이 급격하게 증가하여 오버플로(overflow) 문제가 발생한다. 컴퓨터는 계산할 수 있는 또는 표현할 수 있는 값 이상의 값은 계산/표현이 안되기 때문에 문제가 생긴다.

 

 

In [33]:
a = np.array([1010, 1000, 990])
np.exp(a) / np.sum(np.exp(a))
/home/db2/anaconda3/lib/python3.6/site-packages/ipykernel_launcher.py:2: RuntimeWarning: overflow encountered in exp
  
/home/db2/anaconda3/lib/python3.6/site-packages/ipykernel_launcher.py:2: RuntimeWarning: invalid value encountered in true_divide
  
Out[33]:
array([ nan,  nan,  nan])

 

 

소프트맥수 함수식을 보면 exp(a)에 a 값에 임의의 상수를 더해도 결과는 변하지 않는다. exp(a+c). 오버플로 문제를 없애기 위해서 임의의 상수를 더해 a의 값이 작아지도록 만든다

 

In [34]:
c = np.max(a)
print(a - c)

np.exp(a-c)/np.sum(np.exp(a-c))
[  0 -10 -20]
Out[34]:
array([  9.99954600e-01,   4.53978686e-05,   2.06106005e-09])

 

 

따라서 수정된 소프트맥수 함수는 다음과 같다

In [35]:
def softmax(a):
    c = np.max(a)
    exp_a = np.exp(a)
    sum_exp_a = np.sum(exp_a)
    y = exp_a / sum_exp_a
    
    return y

 

 

소프트맥스 함수는 0과 0.1 사이의 실수를 출력하며 전체 결과 값의 합은 1이기 때문에 소프트맥스 함수의 출력값을 '확률'로 해석하기도 함

 

 

6. MNIST 실습

 

import sys, os
from mnist import load_mnist
(x_train, t_train), (x_test, t_test) = load_mnist(flatten=True, normalize=False)
print(x_train.shape)
print(t_train.shape)
print(x_test.shape)
print(t_test.shape)
(60000, 784)
(60000,)
(10000, 784)
(10000,)
from PIL import Image

def img_show(img):
    return Image.fromarray(np.uint8(img))
    
(x_train, t_train), (x_test, t_test) = load_mnist(flatten=True, normalize = False)

img = x_train[0]
label = t_train[0]
print(label)

print(img.shape)
img = img.reshape(28, 28)
print(img.shape)

img_show(img)
5
(784,)
(28, 28)
 

 

import pickle

def get_data():
    (x_train, t_train), (x_test, t_test) = \
    load_mnist(normalize=True, flatten=True, one_hot_label=False)
    return x_test, t_test

def init_network():
    with open("sample_weight.pkl", "rb") as f:
        network = pickle.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()

accuracy_cnt = 0
for i in range(len(x)):
    y = predict(network, x[i])
    p = np.argmax(y)
    if p == t[i]:
        accuracy_cnt += 1
    
print("Accuracy:" + str(float(accuracy_cnt)/len(x)))
Accuracy:0.9352
print('predicted:',p,',','real:',t[i])
predicted: 6 , real: 6

 

 

 

입력데이터를 특정 범위로 변환하는 처리(정규화normalization)를 하고 이렇게 특정 변환을 가하는 것을 전처리(preprocessing(이라고 한다. 신경망은 성능을 개선하고 학습속도를 개선하기 위해 전처리를 많이 한다. 단순 정규화를 수행하기도 하지만 데이터 전체의 분포를 고려해 전처리하는 경우도 많음. 표준화(standardization), 백색화(whitening) 등이 있음

 

 

 

7. 배치 Batch

 

 

배치라 함은 묶음이라고 보면 되는데, 일반적으로 100개의 샘플에 대해서 예측을 한다면 각 샘플에 예측식을 계산하여 예측을 하는데 이 때 약 총 100번의 계산을 하게 된다.

배치를 적용해서 약 배치의 개수를 50이라 한다면 예측식에 50개의 샘플을 넣어 계산하도록 하여 결과적으로 컴퓨터가 총 2번의 계산을 하게 된다.

이는 컴퓨터의 과부화를 줄여 원래 배열보다 큰 배열로 계산을 하게 하여 연산을 빠르게 해준다. 배치를 처리하면 데이터를 효율적으로 빠르게 적절하게 묶어서 학습할 수 있게 된다

예를 들어 신경망의 내적으로 본다면

[[ Batch 1 ]]

(1, 784) → (784, 50) → (50, 100) → (100, 10) → (10, 1)

하나의 샘플(행)에 대해서만 한다면 위와 같이 신경망이 구성될 것이다

[[ Batch 100 ]]

만약 배치를 100으로 한다면

(100, 784) → (784, 50) → (50, 100) → (100, 10) → (10, 100)

위와 같이 최종 결과는 100개 샘플에대한 10개 뉴런에 대한 아웃풋이 나온다.

앞에서 말했던 것처럼 (a,b) 와 (c,d)의 행렬 곱에서는 b와 c가 같아야하고, 결과는 (a,d)의 형태로 나온다.

# batch1
x, _ = get_data()
network = init_network()
W1, W2, W3 = network['W1'], network['W2'], network['W3']

print(x.shape)
print(x[0].shape)
print(W1.shape)
print(W2.shape)
print(W3.shape)
(10000, 784)
(784,)
(784, 50)
(50, 100)
(100, 10)
# batch100
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:" + str(float(accuracy_cnt)/len(x)))
Accuracy:0.9352


댓글