본문 바로가기
Artificial Intelligence/Deep Learning

[딥러닝 기초] 신경망(Neural Network)에 대하여

by raphael3 2018. 7. 11.
반응형
deeplearning_lec_03_설명
In [1]:
import numpy as np
import matplotlib.pylab as plt

퍼셉트론 복습

  • 퍼셉트론의 한계
    • 사람이 직접 가중치를 찾아야 함
  • 신경망
    • 사람이 아닌 컴퓨터가 가중치를 찾아낼 수 있음
    • 컴퓨터는 주어진 데이터로부터 학습하여 가중치 값을 찾아냄

활성화 함수

activation function

  • 뉴런의 전기신호의 임계값을 결정

활성화 함수 1: 계단 함수

In [2]:
# 활성화 함수 1: 계단 함수
def step_function_3(x):
    return np.array(x>0, dtype=np.int) #
x = np.arange(-5.0, 5.0, 0.1)
y = step_function_3(x)
print("y>", y)
print()
print("step function")
plt.plot(x, y)
plt.ylim(-0.1, 1.1)
plt.show()
y> [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1]

step function
  • 0을 넘지 않는 모든 값들은 0, 0을 넘는 모든 값들은 1
  • 이산적(discrete)

활성화 함수 2: 시그모이드(Sigmoid) 함수

In [3]:
# 활성화 함수 2: 시그모이드(Sigmoid) 함수
def sigmoid(x):
    return 1/(1+np.exp(-x)) #

x = np.arange(-5.0, 5.0, 0.1)
y = sigmoid(x)
print("y>", y)
print()
print("sigmoid function")
plt.plot(x, y)
plt.ylim(-0.1, 1.1)
plt.show()
y> [0.00669285 0.00739154 0.00816257 0.0090133  0.0099518  0.01098694
 0.01212843 0.01338692 0.01477403 0.0163025  0.01798621 0.01984031
 0.02188127 0.02412702 0.02659699 0.02931223 0.03229546 0.03557119
 0.03916572 0.04310725 0.04742587 0.05215356 0.05732418 0.06297336
 0.06913842 0.07585818 0.0831727  0.09112296 0.09975049 0.10909682
 0.11920292 0.13010847 0.14185106 0.15446527 0.16798161 0.18242552
 0.19781611 0.21416502 0.23147522 0.24973989 0.26894142 0.2890505
 0.31002552 0.33181223 0.35434369 0.37754067 0.40131234 0.42555748
 0.450166   0.47502081 0.5        0.52497919 0.549834   0.57444252
 0.59868766 0.62245933 0.64565631 0.66818777 0.68997448 0.7109495
 0.73105858 0.75026011 0.76852478 0.78583498 0.80218389 0.81757448
 0.83201839 0.84553473 0.85814894 0.86989153 0.88079708 0.89090318
 0.90024951 0.90887704 0.9168273  0.92414182 0.93086158 0.93702664
 0.94267582 0.94784644 0.95257413 0.95689275 0.96083428 0.96442881
 0.96770454 0.97068777 0.97340301 0.97587298 0.97811873 0.98015969
 0.98201379 0.9836975  0.98522597 0.98661308 0.98787157 0.98901306
 0.9900482  0.9909867  0.99183743 0.99260846]

sigmoid function
  • 0과 1 사이의 값을 가짐
  • 극단의 값으로 갈수록 값의 편차가 크지 않음
    • 매우 큰 값은 1로 수렴하고 매우 작은 값은 0으로 수렴
  • 연속적(continuous)

활성화 함수 3: ReLU(Rectified Linear Unit) 함수

In [4]:
# 활성화 함수 3: ReLU(Rectified Linear Unit) 함수
def relu(x):
    return np.maximum(0, x) #

x = np.arange(-5.0, 5.0, 0.1)
y = relu(x)
print("y>", y)
print()
print("ReLU function")
plt.plot(x, y)
plt.ylim(-0.1, 1.1)
plt.show()
y> [0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.
 0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.
 0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.1 0.2 0.3
 0.4 0.5 0.6 0.7 0.8 0.9 1.  1.1 1.2 1.3 1.4 1.5 1.6 1.7 1.8 1.9 2.  2.1
 2.2 2.3 2.4 2.5 2.6 2.7 2.8 2.9 3.  3.1 3.2 3.3 3.4 3.5 3.6 3.7 3.8 3.9
 4.  4.1 4.2 4.3 4.4 4.5 4.6 4.7 4.8 4.9]

ReLU function
  • 0이 넘는 값은 그 입력 그대로 출력, 0을 넘지 못하는 값은 0을 출력
  • 다시 말해 모든 양수 값은 그냥 통과, 모든 음수 값은 0으로
  • Rectified: 정류된
    • 정류 <-> 교류
    • 한 방향으로만 흐르는 전류를 만듦
    • 0 이하의 입력을 차단하여 아무 값도 출력하지 않게 함

활성화 함수 4: 소프트맥스(Softmax) 함수

In [5]:
# 활성화 함수 5: 소프트맥스(Softmax) 함수
def softmax(x):
    #return (x)/np.sum(np.exp(x))
    
    e_x = np.exp(x - np.max(x)) 
    # 지수값의 기하급수적인 증가로 인한 오버플로우 문제(컴퓨터가 표현할 수 있는 값의 범위의 한계를 넘는 것)를 막기 위해서 
    # 입력값 각각을 가장 큰 값으로 빼어 스케일링 해준다.
    return e_x / e_x.sum()

x = np.arange(0.0, 3.0, 1)
y = softmax(x)
print("y>", y)
print()
print("softmax function")
# plt.plot(x, y)
# plt.ylim(-0.1, 1.1)
plt.pie(y, labels=y, shadow=True, startangle=90)
plt.show()
y> [0.09003057 0.24472847 0.66524096]

softmax function
  • 활성화 함수
    1. 계단 함수
      • 퍼셉트론에서 주로 쓰임
      • 계단형
    2. 시그모이드(Sigmoid) 함수
      • 딥 뉴럴 네트워크의 hidden layer에서 주로 쓰임
      • s자 형
      • p. 68
    3. ReLU(Rectified Linear Unit) 함수
      • 딥 뉴럴 네트워크의 hidden layer에서 주로 쓰이고, 가장 많이 쓰임
      • p.76
    4. 소프트맥스(Softmax) 함수
      • 다중분류에서 쓰이며, 출력계층 직전에 쓰임.
      • 소프트맥스 함수 출력의 총합은 항상 1 --> 확률로 해석 가능
      • p.91
- 출력층의 활성화 함수는 풀고자 하는 문제의 성질에 맞게 정한다. 
- 예를 들면, 이진분류 문제에는 시그모이드 함수를 쓰고, 다중분류 문제에는 소프트맥스 함수를 사용하는 식이다.
- 모두 비선형함수다.
  - why? p.75 참고

numpy

행렬 생성

In [6]:
A = np.array([1,2,3,4])
print("A")
print(A)
# 배열 A의 차원
print("np.ndim(A): ", np.ndim(A)) # 
# A의 shape
print("A.shape: ", A.shape) #
print()
print()
# 2차원 배열 B 생성
B = np.array([[1,2], [3,4], [5,6]]) #
print("B> ")
print(B)
# B의 차원
print("np.ndim(B): ", np.ndim(B))
# (3, 2) B의 shape
print("B.shape: ", B.shape)
A
[1 2 3 4]
np.ndim(A):  1
A.shape:  (4,)


B> 
[[1 2]
 [3 4]
 [5 6]]
np.ndim(B):  2
B.shape:  (3, 2)
In [7]:
# (2, 2) 행렬 A 생성
A = np.array([[1,2], [3,4]]) #
print(A)
print("A.shape: ", A.shape)
print()

# (2, 2) 행렬 B 생성
B = np.array([[5,6],[7,8]]) # 
print(B)
print("B.shape: ", B.shape)
print()

# 내적
print("np.dot(A, B)> ") #
print(np.dot(A, B))
[[1 2]
 [3 4]]
A.shape:  (2, 2)

[[5 6]
 [7 8]]
B.shape:  (2, 2)

np.dot(A, B)> 
[[19 22]
 [43 50]]

행렬의 내적

In [8]:
# (2, 3) 행렬 A 생성
A = np.array([[1,2,3], [4,5,6]]) #
print("A>")
print(A) #
print("A.shape: ", A.shape)
print()

# (3, 2) 행렬 B 생성
B = np.array([[1,2], [3,4], [5,6]]) #
print("B>")
print(B) #
print("B.shape: ", B.shape)
print()

# 내적
print("np.dot(A, B)> ")
print(np.dot(A, B)) #
print()
print("-------------")
print("A>")
print(A) #
print("A.shape: ", A.shape)
print()

# (2, 2) 행렬 C 생성
C = np.array([[1,2],[3,4]]) #
print(C)
print("C.shape: ", C.shape)

# 행렬 A와 행렬 C 내적
np.dot(A, C) #
A>
[[1 2 3]
 [4 5 6]]
A.shape:  (2, 3)

B>
[[1 2]
 [3 4]
 [5 6]]
B.shape:  (3, 2)

np.dot(A, B)> 
[[22 28]
 [49 64]]

-------------
A>
[[1 2 3]
 [4 5 6]]
A.shape:  (2, 3)

[[1 2]
 [3 4]]
C.shape:  (2, 2)
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-8-b9c2a0c2604a> in <module>()
     29 
     30 # 행렬 A와 행렬 C 내적
---> 31 np.dot(A, C) #

ValueError: shapes (2,3) and (2,2) not aligned: 3 (dim 1) != 2 (dim 0)
  • 내적에서 한 가지 더 주의할 점은 np.dot(A, B)np.dot(B, A)가 같지 않다는 점이다.

신경망

  • 생물학에서, 뉴런은 전기적인 신호를 전달받고 전달하는 세포다.
  • 신호가 전달되려면 일정 기준(임계값, threshold) 이상의 전기 신호가 존재해야 한다.

간단한 신경망 구현

  • 내적 연산을 통해서 간단한 신경망 구축 가능
  • 신경망은 인간의 입력을 X로, 입력에 대한 가중치를 W로, XW의 곱의 합(곧 내적을 의미함)의 결과를 Y로 나타낸다.
  • 뉴런 여러 개가 모여 층 한 개를 이루고, 층 여러 개가 겹겹이 쌓여 네트워크를 이룬다.
    • 층을 여러 개로 쌓을수록 풀려는 문제(또는 대상)에 대한 추상화(또는 모델링, 표현)수준이 높아진다고 함
  • 신경망의 수학적 모델> A = XW + B
    • X: 입력, feature(주어짐. 인간이 모델에 제시하는 값.)
      • 월급, 나이, 성별, 거주지역
    • W: 가중치(Weight). 구해야 하는 값
      어떤 feature에 더 큰 가중치를 둘 것인지 사람이 아닌 신경망이 계산을 통해 Weight들을 찾아낸다. 이에 따라 가중치 값에 따라 feature별로 영향력이 달라진다.
    • B: 편향, 편견(Bias). A값에 영향을 준다. 즉 값이 쉽게 바뀌느냐 마느냐를 결정한다.
    • A: 가중치를 곱한 것들의 합(뉴런의 활성화를 결정하는 값)
      • "대출 가능/불가능"
  • 더 일반적인 표현> f(X) = b + sum(Wi*Xi)
  • 신경망 계산을 사람이 직접 일일이 하지 않고, numpy를 이용하여 행렬의 내적 계산으로 한 번에 계산 가능하다는 장점이 있다.
In [9]:
# 입력이 (2,)인 행렬 X 생성
X = np.array([1,2]) #
X
print("X> ", X)
print()
# 가중치가 (2, 3)인 행렬 W 생성
W = np.array([[1,3,5],[2,4,6]]) #
print("W> ")
print(W)
print()
# X와 W의 내적(요소 별 곱의 합)
Y = np.dot(X, W) #
print("Y> ", Y)
X>  [1 2]

W> 
[[1 3 5]
 [2 4 6]]

Y>  [ 5 11 17]

3층 신경망 구현

In [10]:
# 입력층에서 첫번째 Hidden Layer로 전달되는 가중치의 합(A2)과 활성화 함수를 거친 값(Z2)
'''
shape이 (2,)
shape이 (2, 3)
shpae이 (3,)인 행렬 X, W1, B1 생성
'''
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("X.shape: ", X.shape)
print("W1.shape: ", W1.shape)
print("B1.shape: ", B1.shape)

# 가중치 합
A1 = np.dot(X, W1) + B1 
# A1의 값
print("A1> ", A1) #
# 가중치 합에 대한 활성화 함수 적용(시그모이드 함수 적용)
Z1 = sigmoid(A1)
print("Z1> ", Z1)
X.shape:  (2,)
W1.shape:  (2, 3)
B1.shape:  (3,)
A1>  [0.3 0.7 1.1]
Z1>  [0.57444252 0.66818777 0.75026011]

In [11]:
# 첫번째 Hidden Layer에서 두번째 Hidden Layer로 전달되는 가중치의 합(A2)과 활성화 함수를 거친 값(Z2)
W2 = np.array([[0.1, 0.4], [0.2, 0.5], [0.3, 0.6]])
B2 = np.array([0.1, 0.2])
print("Z1> ", Z1)
print("Z1.shape: ", Z1.shape)
print()
print("W2> ", W2)
print("W2.shape: ", W2.shape)
print()
print("B2> ", B2)
print("B2.shape: ", B2.shape)
A2 = np.dot(Z1, W2) + B2 #
Z2 = sigmoid(A2) #
print()
print("A2> ", A2)
print("A2.shape: ", A2.shape)
print()
print("Z2> ", Z2)
print("Z2.shape: ", Z2.shape)
Z1>  [0.57444252 0.66818777 0.75026011]
Z1.shape:  (3,)

W2>  [[0.1 0.4]
 [0.2 0.5]
 [0.3 0.6]]
W2.shape:  (3, 2)

B2>  [0.1 0.2]
B2.shape:  (2,)

A2>  [0.51615984 1.21402696]
A2.shape:  (2,)

Z2>  [0.62624937 0.7710107 ]
Z2.shape:  (2,)

In [12]:
# 두번째 Hidden Layer에서 출력층으로 전달되는 가중치의 합(A2)과 활성화 함수를 거친 값(Z2)
def identify_function(x):
    return x #
# 활성화 함수 적용이라는 문맥상 흐름에 맞추기 위해

W3 = np.array([[0.1, 0.3], [0.2, 0.4]])
B3 = np.array([0.1, 0.2])
print("Z2> ", Z2)
print("Z2.shape: ", Z2.shape)
print()
print("W3> ", W3)
print("W3.shape: ", W3.shape)
print()
print("B3> ", B3)
print("B3.shape: ", B3.shape)
A3 = np.dot(Z2, W3) + B3 #
Y = identify_function(A3) #
print()
print("A3> ", A3)
print("A3.shape: ", A3.shape)
print()
print("Y> ", Y)
print("Y.shape: ", Y.shape)
Z2>  [0.62624937 0.7710107 ]
Z2.shape:  (2,)

W3>  [[0.1 0.3]
 [0.2 0.4]]
W3.shape:  (2, 2)

B3>  [0.1 0.2]
B3.shape:  (2,)

A3>  [0.31682708 0.69627909]
A3.shape:  (2,)

Y>  [0.31682708 0.69627909]
Y.shape:  (2,)
In [13]:
def init_network():
    network = {}
    network['W1'] = np.array([[0.1, 0.3, 0.5], [0.2, 0.4, 0.6]])
    network['W2'] = np.array([[0.1, 0.4], [0.2, 0.5], [0.3, 0.6]])
    network['W3'] = np.array([[0.1, 0.3], [0.2, 0.4]])
    network['b1'] = np.array([0.1, 0.2, 0.3])
    network['b2'] = np.array([0.1, 0.2])
    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 = identify_function(a3)
    
    return y
    
network = init_network()
x = np.array([1.0, 0.5])
y = forward(network, x)
print(y)
[0.31682708 0.69627909]

  • 위 문제의 경우, 3개의 입력을 받아서 2개의 출력을 내는 이진 분류 문제라고 할 수 있음
    • 대출 가능,불가 등의 yes, no 문제
In [14]:
# HW
# 1) 아래 코드를 본 후, 다음주까지 네트워크를 손으로 그려오기
#    입력, 가중치, bias, 출력, 활성화 함수가 모두 표현되어 있어야 함

def init_network():
    network = {}
    network['W1'] = np.array([[0.1, 0.3, 0.5, 0.2, 0.4, 0.6, 0.3, 0.6], 
                              [0.2, 0.4, 0.6, 0.1, 0.3, 0.5, 0.2, 0.4],
                              [-0.24, 0.46, 0.76, -0.31, 0.33, 0.54, 0.52, 0.44],
                              [0.32, -0.44, 0.86, 0.16, -0.33, -0.51, 0.72, 0.84],
                              [0.72, 0.43, -0.62, 0.18, 0.32, -0.52, 0.12, -0.41]])
    network['W2'] = np.array([[0.1, 0.4, 0.2, 0.5, 0.3, 0.6, 0.3],
                             [0.4, 0.2, 0.1, 0.5, 0.3, 0.6, 0.3], 
                             [0.2, 0.5, 0.4, 0.5, 0.1, 0.1, 0.3],
                             [0.5, 0.3, 0.2, 0.5, 0.4, 0.4, 0.1],
                             [0.3, 0.6, 0.5, 0.5, 0.2, 0.2, 0.4],
                             [0.6, 0.3, 0.3, 0.5, 0.5, 0.5, 0.2],
                             [0.3, 0.4, 0.6, 0.5, 0.3, 0.3, 0.5],
                             [0.1, 0.4, 0.3, 0.5, 0.3, 0.6, 0.3]])
    network['W3'] = np.array([[0.1, 0.3, 0.2, 0.4, 0.1], 
                             [0.3, 0.3, 0.3, 0.4, 0.9], 
                             [0.2, 0.3, 0.2, 0.4, 0.3], 
                             [0.4, 0.2, 0.2, 0.4, 0.2], 
                             [0.1, 0.4, 0.4, 0.3, 0.4], 
                             [0.9, 0.1, 0.2, 0.2, 0.9], 
                             [0.1, 0.3, 0.2, 0.4, 0.3]])
    network['W4'] = np.array([[0.1, 0.3, 0.2, 0.4], 
                             [0.3, 0.3, 0.3, 0.4], 
                             [0.2, 0.3, 0.2, 0.4], 
                             [0.4, 0.2, 0.2, 0.4], 
                             [0.1, 0.4, 0.4, 0.3]])
    network['W5'] = np.array([[0.1, 0.3, 0.5, 0.2], 
                             [0.46, 0.76, -0.31, 0.33], 
                             [0.2, -0.62, 0.18, 0.32], 
                             [0.4, 0.43, -0.62, 0.4]])
    network['W6'] = np.array([[0.1, 0.3], 
                             [0.76, -0.31], 
                             [0.18, 0.32], 
                             [-0.62, 0.4]])
    
    network['b1'] = np.array([0.1, 0.2, 0.3, 0.3, -0.3, 0.3, 0.2, 0.2])
    network['b2'] = np.array([0.1, 0.2, 0.3, 0.3, 0.3, 0.3, 0.1])
    network['b3'] = np.array([0.1, 0.1, 0.2, 0.2, 0.3])
    network['b4'] = np.array([0.1, 0.1, 0.2, 0.2])
    network['b5'] = np.array([0.1, 0.1, 0.2, 0.2])
    network['b6'] = np.array([0.1, 0.1])
    
    return network

def forward(network, x):
    W1, W2, W3, W4, W5, W6 = network['W1'], network['W2'], network['W3'], network['W4'], network['W5'], network['W6']
    b1, b2, b3, b4, b5, b6 = network['b1'], network['b2'], network['b3'], network['b4'], network['b5'], network['b6']
    
    a1 = np.dot(x, W1) + b1
    z1 = sigmoid(a1)
    
    a2 = np.dot(z1, W2) + b2
    z2 = sigmoid(a2)
    
    a3 = np.dot(z2, W3) + b3
    z3 = sigmoid(a3)
    
    a4 = np.dot(z3, W4) + b4
    z4 = relu(a4)
    
    a5 = np.dot(z4, W5) + b5
    z5 = relu(a5)
    
    a6 = np.dot(z5, W6) + b6
    y_softmax = softmax(a6)
    y_identify_function = identify_function(a6)
    
    return y_softmax, y_identify_function

network = init_network()
x = np.array([1.0, 0.5, 0.4, 0.9, -1.3])
y_softmax, y_identify_function  = forward(network, x)
print("{:25s}: {}".format("y_identify_function: ", y_identify_function))
print("{:25s}: {}".format("y_softmax: ", y_softmax))
y_identify_function:     : [0.12410253 1.04452448]
y_softmax:               : [0.28487193 0.71512807]

Neural Network가 할 일은, 1)가장 적합한 파라미터를 찾아내어, 2)결과값을 개선하는 것이다.

  • 즉 훈련 데이터(학습 데이터)를 사용하여 가중치 매개변수를 학습한 후, --> 학습 단계 : 다음 시간(역전파 파트 때)에.
  • 학습하여 구한 매개변수를 이용하여 새로운 입력 데이터를 예측 또는 분류해낸다. --> 추론 단계
  • 출력층의 뉴런수는 풀려는 문제에 맞게 적절히 정해야 한다.
  • 분류문제의 경우, 분류하려는 클래스의 개수에 맞게 출력층의 뉴런 개수를 정하는 것이 일반적
    • Yes, no -> 출력층에 두 개의 뉴런
    • MNIST -> 0~9까지 10개의 뉴런

MNIST

  • 손글씨 숫자 이미지 집합
  • 기계학습의 대표적인 데이터셋, 성능 벤치마크 기준

    • 데이터 집합
    • 연령, 나이, 연봉, 직업, 사는지역, [대출상환여부:1/0]
    • 매개변수 학습에 쓰이는 데이터셋을 훈련 데이터셋(training dataset)
    • 모델의 성능을 측정하고, 좋은 모델을 선택하기 위해 쓰이는 검증 데이터셋(validation dataset). 모델의 학습시에는 쓰일 때도 있고 쓰이지 않을때도 있다.
    • 최종적인 모델이 얼마나 좋은지를 평가하기 위해 쓰이는 시험 데이터셋(test dataset). 학습시에는 절대 쓰이지 않는다.
  • MNIST는 0부터 9까지의 손글씨 숫자를 이미지화 한 데이터셋

  • 훈련 이미지: 60,000장
  • 시험 이미지: 10,000장 http://yann.lecun.com/exdb/mnist/
  • 784(=28*28) 사이즈의 그레이 스케일 이미지 --> 1x28x28
  • 각각의 필셀값은 0부터 255(2^8)까지의 값을 갖음
  • MNIST 데모: https://ml4a.github.io/demos/forward_pass_mnist/

MNIST 데이터셋 -> numpy.ndarray

In [15]:
# coding: utf-8
try:
    import urllib.request
except ImportError:
    raise ImportError('You should use Python 3.x')
import os.path
import gzip
import pickle
import os
import numpy as np


url_base = 'http://yann.lecun.com/exdb/mnist/'
key_file = {
    'train_img':'train-images-idx3-ubyte.gz',
    'train_label':'train-labels-idx1-ubyte.gz',
    'test_img':'t10k-images-idx3-ubyte.gz',
    'test_label':'t10k-labels-idx1-ubyte.gz'
}

dataset_dir = os.getcwd() #os.path.dirname(os.path.abspath(__file__))
save_file = dataset_dir + "/mnist.pkl"

train_num = 60000
test_num = 10000
img_dim = (1, 28, 28)
img_size = 784


def _download(file_name):
    file_path = dataset_dir + "/" + file_name
    
    if os.path.exists(file_path):
        return

    print("Downloading " + file_name + " ... ")
    urllib.request.urlretrieve(url_base + file_name, file_path)
    print("Done")
    
def download_mnist():
    for v in key_file.values():
        _download(v)
        
def _load_label(file_name):
    file_path = dataset_dir + "/" + file_name
    
    print("Converting " + file_name + " to NumPy Array ...")
    with gzip.open(file_path, 'rb') as f:
            labels = np.frombuffer(f.read(), np.uint8, offset=8)
    print("Done")
    
    return labels

def _load_img(file_name):
    file_path = dataset_dir + "/" + file_name
    
    print("Converting " + file_name + " to NumPy Array ...")    
    with gzip.open(file_path, 'rb') as f:
            data = np.frombuffer(f.read(), np.uint8, offset=16)
    data = data.reshape(-1, img_size)
    print("Done")
    
    return data
    
def _convert_numpy():
    dataset = {}
    dataset['train_img'] =  _load_img(key_file['train_img'])
    dataset['train_label'] = _load_label(key_file['train_label'])    
    dataset['test_img'] = _load_img(key_file['test_img'])
    dataset['test_label'] = _load_label(key_file['test_label'])
    
    return dataset

def init_mnist():
    download_mnist()
    dataset = _convert_numpy()
    print("Creating pickle file ...")
    with open(save_file, 'wb') as f:
        pickle.dump(dataset, f, -1)
    print("Done!")

def _change_one_hot_label(X):
    T = np.zeros((X.size, 10))
    for idx, row in enumerate(T):
        row[X[idx]] = 1
        
    return T
    

def load_mnist(normalize=True, flatten=True, one_hot_label=False):
    """MNIST 데이터셋 읽기
    
    Parameters
    ----------
    normalize : 이미지의 픽셀 값을 0.0~1.0 사이의 값으로 정규화할지 정한다.
    one_hot_label : 
        one_hot_label이 True면、레이블을 원-핫(one-hot) 배열로 돌려준다.
        one-hot 배열은 예를 들어 [0,0,1,0,0,0,0,0,0,0]처럼 한 원소만 1인 배열이다.
    flatten : 입력 이미지를 1차원 배열로 만들지를 정한다. 본래는 1*28*28의 3차원 이미지.
    
    Returns
    -------
    (훈련 이미지, 훈련 레이블), (시험 이미지, 시험 레이블)
    """
    if not os.path.exists(save_file):
        init_mnist()
        
    with open(save_file, 'rb') as f:
        dataset = pickle.load(f)
    
    if normalize:
        for key in ('train_img', 'test_img'):
            dataset[key] = dataset[key].astype(np.float32)
            dataset[key] /= 255.0
            
    if one_hot_label:
        dataset['train_label'] = _change_one_hot_label(dataset['train_label'])
        dataset['test_label'] = _change_one_hot_label(dataset['test_label'])    
    
    if not flatten:
         for key in ('train_img', 'test_img'):
            dataset[key] = dataset[key].reshape(-1, 1, 28, 28)

    return (dataset['train_img'], dataset['train_label']), (dataset['test_img'], dataset['test_label']) 


if __name__ == '__main__':
    init_mnist()
Downloading train-images-idx3-ubyte.gz ... 
Done
Downloading train-labels-idx1-ubyte.gz ... 
Done
Downloading t10k-images-idx3-ubyte.gz ... 
Done
Downloading t10k-labels-idx1-ubyte.gz ... 
Done
Converting train-images-idx3-ubyte.gz to NumPy Array ...
Done
Converting train-labels-idx1-ubyte.gz to NumPy Array ...
Done
Converting t10k-images-idx3-ubyte.gz to NumPy Array ...
Done
Converting t10k-labels-idx1-ubyte.gz to NumPy Array ...
Done
Creating pickle file ...
Done!

훈련 데이터, 시험 데이터

In [16]:
(x_train, t_train), (x_test, t_test) = load_mnist(flatten=True, normalize=False, one_hot_label=False)
# (x_train, t_train), (x_test, t_test) = load_mnist(flatten=True, normalize=True, one_hot_label=False)
# (x_train, t_train), (x_test, t_test) = load_mnist(flatten=True, normalize=True, one_hot_label=True)
print("x_train> ")
print(x_train)
print("x_train.shape: ", x_train.shape)
print("x_train[0][230:250]> ", x_train[0][230:250])
print("x_train[0].shape> ", x_train[0].shape)
print()
print("t_train> ")
print(t_train)
print("t_train.shape: ", t_train.shape)
print("t_train[0]> ", t_train[0])
print("t_train[0].shape> ", t_train[0].shape)
print()
print("x_test> ")
print(x_test)
print("x_test.shape: ", x_test.shape)
print("x_test[0][230:250]> ", x_test[0][230:250])
print("x_test[0].shape> ", x_test[0].shape)
print()
print("t_test> ")
print(t_test)
print("t_test.shape: ", t_test.shape)
print("t_test[0]> ", t_test[0])
print("t_test[0].shape> ", t_test[0].shape)
print()
x_train> 
[[0 0 0 ... 0 0 0]
 [0 0 0 ... 0 0 0]
 [0 0 0 ... 0 0 0]
 ...
 [0 0 0 ... 0 0 0]
 [0 0 0 ... 0 0 0]
 [0 0 0 ... 0 0 0]]
x_train.shape:  (60000, 784)
x_train[0][230:250]>  [  0  18 219 253 253 253 253 253 198 182 247 241   0   0   0   0   0   0
   0   0]
x_train[0].shape>  (784,)

t_train> 
[5 0 4 ... 5 6 8]
t_train.shape:  (60000,)
t_train[0]>  5
t_train[0].shape>  ()

x_test> 
[[0 0 0 ... 0 0 0]
 [0 0 0 ... 0 0 0]
 [0 0 0 ... 0 0 0]
 ...
 [0 0 0 ... 0 0 0]
 [0 0 0 ... 0 0 0]
 [0 0 0 ... 0 0 0]]
x_test.shape:  (10000, 784)
x_test[0][230:250]>  [222 254 254 254 254 241 198 198 198 198 198 198 198 198 170  52   0   0
   0   0]
x_test[0].shape>  (784,)

t_test> 
[7 2 1 ... 4 5 6]
t_test.shape:  (10000,)
t_test[0]>  7
t_test[0].shape>  ()

MNIST 숫자 분류/추론

In [37]:
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()
print("x.shape: ", x.shape)
print("x[0].shape: ", x[0].shape)
print("batchsize * x[0].shape: ({}, {})".format(1, x[0].shape[0])) # 개념적인 표현
print("network['W1'].shape: ", network['W1'].shape)
print("network['W2'].shape: ", network['W2'].shape)
print("network['W3'].shape: ", network['W3'].shape)
print("output layer's shape: ({}, {})".format(1, 10)) # 개념적인 표현
print()
accuracy_cnt = 0
for i in range(len(x)): # 불러온 이미지 x를 한 장씩 꺼내서 predict()함수에 넣는다. predict()함수는 이미지가 어떤 숫자인지 분류/추론한다.
    y = predict(network, x[i]) # softmax이므로 0.0과 1.0사이의 확률값으로 나옴
    p= np.argmax(y) # 확률값이 가장 높은 원소의 인덱스를 얻는다.
    if p == t[i]: # 예측한 결과인 p와 실제 정답인 t[i]를 비교
        accuracy_cnt += 1

print("Accuracy:" + str(float(accuracy_cnt) / len(x))) # 93.52%의 정확도로 정확하게 분류
x.shape:  (10000, 784)
x[0].shape:  (784,)
batchsize * x[0].shape: (1, 784)
network['W1'].shape:  (784, 50)
network['W2'].shape:  (50, 100)
network['W3'].shape:  (100, 10)
output layer's shape: (1, 10)

Accuracy:0.9352
  • 세 개의 Layer
  • sample_weight.pkl
    • 미리 학습된 모델의 매개변수를 불러옴
    • 학습 과정은 다음 시간에.
  • 입력층의 뉴런수: 784
  • 첫번째 은닉층의 뉴런수: 50
  • 두번째 은닉층의 뉴런수: 100
  • 출력층의 뉴런수: 10

배치 처리

  • 이미지를 여러 장을 하나로 묶어서 한꺼번에 predict()에 넣고자 할 때
    • 하나로 묶은 입력 데이터를 배치(batch)라 함
In [36]:
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

accuracy_cnt = 0

x, t = get_data()
network = init_network()
batch_size = 100 # 배치 크기

# for i in range(len(x)): 
#     y = predict(network, x[i]) 
#     p= np.argmax(y) 
#     if p == t[i]: 
#         accuracy_cnt += 1
        
for i in range(0, len(x), batch_size):
    y_batch = predict(network, x[i:i+batch_size])
    p = np.argmax(y_batch, axis=1)
    accuracy_cnt += np.sum(p == t[i:i+batch_size])

print("x.shape: ", x.shape)
print("x[0].shape: ", x[0].shape)
print("batchsize * x[0].shape: ({}, {})".format(batch_size, x[0].shape[0])) # 개념적인 표현
print("network['W1'].shape: ", network['W1'].shape)
print("network['W2'].shape: ", network['W2'].shape)
print("network['W3'].shape: ", network['W3'].shape)
print("output layer's shape: ({}, {})".format(batch_size, 10)) # 개념적인 표현
print()
print("Accuracy:" + str(float(accuracy_cnt) / len(x)))
x.shape:  (10000, 784)
x[0].shape:  (784,)
batchsize * x[0].shape: (100, 784)
network['W1'].shape:  (784, 50)
network['W2'].shape:  (50, 100)
network['W3'].shape:  (100, 10)
output layer's shape: (100, 10)

Accuracy:0.9352
  • 배치 처리의 이점
    • 계산 효율: 큰 배열을 효율적으로 처리할 수 있도록 최적화되어 있음
    • I/O 횟수를 줄여 버스 부하를 줄임 --> 계산 속도 향상에 도움을 줌
반응형