[DL from Scratch] Chapter 3: Neural Networks
해당 Post는 밑바닥부터 시작하는 딥러닝
1권을 읽으면서 정리한 내용들로 구성되어 있다.
이번 Chapter 3에서는 Neural Network
에 대해 설명한다.
앞의 Chapter 2 Perceptron
에서는 아래의 좋은 점과 나쁜 점들이 있었다:
1) 임의의 복잡한 continuous function $f$ 을 하나의 hidden layer만으로도 이론상 표현할 수 있다.
2) 원하는 결과를 출력하도록 weight을 적절히 조정하는 작업은 사람이 여전히 수동으로 해야한다. (AND, OR Gate 등)
여기서 Neural Network를 이용하여 데이터로부터 자동으로 가중치 매개변수의 적절한 값을 학습할 수 있게 된다.
3.1) Peceptron에서 Neural Network으로
3.1)
은 Perceptron과 Neural Network의 다른 점을 위주로 설명한다.
Input Layer
Hidden Layer
Output Layer
각 Layer들은 사이의 연결하는 선 / weight가 부여되는 부분이 아니라, 세로로 쌓인 Node들의 집합이다.
위 그림의 경우 Input Layer부터 Output Layer까지 차례로 0층, 1층, 2층이라 하며,
Hidden layer는 사람의 눈에 보이지 않기에 'Hidden'이라 한다.
실제로 위 그림은 3개의 Layer로 구성되지만, 가중치를 갖는 층은 2개뿐이므로 `2층 신경망' 이라고도 한다.
1) 신경망을 구성하는 층수를 기준으로 '3층 신경망': 총 Layer의 개수
2) 실제 가중치를 갖는 층의 개수를 기준으로 '2층 신경망': 총 Layer 개수 - 1
Perceptron에 대해 보다 자세한 설명은 아래의 포스트를 참고하자.
위의 그림은 입력으로 총 $d$개의 input signal을 받은 Perceptron
의 예시이다.
- $x_i$: Input Signal
- $w_i$: Weight(가중치, $i = 1, 2, ... , d$)
- $w_0$: Bias(편향)
Weight(Bias)은 학습에 의해 Neural Network가 알아서 설정하는 값이므로 Bias
($b = w_0 x_0 = w_0$)는 Weight($w_0$)이 아닌 Input Signal ($x_0 = 1$)을 1로 fix시킨다.
위 그림의 원
을 Neuron or Node라 하고, Input Signal이 Output Neuron으로 전달될 때 각각 고유한 weight가 곱해진다.
즉, 각 weight($w_i$)와 input signal($x_i$)간의 linear combination인 신호의 총합 $\mathbf{w}^T\mathbf{x}$가 Output Neuron으로 전달된다.
$\mathbf{w}$와 $\mathbf{x}$가 모두 column vector
이므로 앞의 $\mathbf{w}$에 Transpose $\mathbf{w}^T$를 취한다.
그리고 이 $\mathbf{w}^T\mathbf{x}$가 정해진 한계를 넘어설 때만 1을 출력하고, 그렇지 않으면 0을 출력한다.
1을 출력할 때, 뉴런이 활성화되었다고도 표현한다.
책에서는 그 한계를 임계값
이라 하며 Threshold $\theta$로 표현한다.
$$y = \begin{cases} 0 \: (\mathbf{w}^T\mathbf{x} \leq \theta) \\ 1 \:(\mathbf{w}^T\mathbf{x} > \theta) \end{cases}$$
위의 식에서 $\theta$를 $-b$로 치환하면 Perceptron의 동작이 아래와 같이 바뀐다.
$$y = \begin{cases} 0 \: (\mathbf{w}^T\mathbf{x} + b \leq 0) \\ 1 \:(\mathbf{w}^T\mathbf{x} + b > 0) \end{cases}$$
weight $\mathbf{w}$는 input signal이 output에 주는 영향력(중요도
)를 조절하는 paramter고, bias $b=w_0$는 뉴런이 얼마나 쉽게 활성화(민감도
)하느냐를 조정하는 parameter이다.
bias $b$는 Neuron이 얼마나 쉽게 활성화되는지에 대한 민감도를 나타낸다고 볼 수 있다.
최종적으로 아래와 같이 heaviside step function $h$을 이용하여 표현가능하다.
$$y = h(\mathbf{w}^T\mathbf{x} + b)$$
$$h(x) = \begin{cases} 0 \: (x \leq 0) \\ 1 \:(x > 0) \end{cases}$$
위의 식에서 function $h$를 activation function
이라 한다.
- 입력 신호의 총합을 출력 신호로 변환하는 함수
- 입력 신호의 총합이 활성화를 일으키는지 정하는 역할
1) input signal $x$와 weight matrix $\mathbf{w}$과의 linear transformation
$$a = \mathbf{w}^T\mathbf{x} + b$$
2) applying non-linear
activation function $h$
$$y = h(a)$$
3.2) Activation Function
지금까지 위에서 설명한 Perceptron
은 heaviside step function을 activation function $h$로 사용하고 있음을 알 수 있다.
그렇다면 위의 함수가 아닌 다른 함수들도 activation function이 될 수 있지 않을까?
지금부터는 서로 다른 activation function
에 대해 알아볼 것이다.
$$h(x) = \frac {1} {exp(-x) + 1}$$
Sigmoid
Function은 다음과 같이 수식으로 표현된다.
실수 space에서 ${0, 1}$의 구간으로 mapping해주는 특징이 있다.
python
을 이용하여 step function을 구현할 수 있다.
$x$가 양수면 1을 출력하고, 그 외에는 0을 출력하면 된다.
def step_function(x):
if x > 0:
return 1
else:
return 0
위와 같이 간단하게 구현할 수 있으나, 위의 함수는 $x$가 실수일 경우에만 작동한다.
즉, $x$가 ndarray와 같은 numpy array일 때도 작동할 수 있도록 코드를 수정하면 다음과 같다.
def step_function(x):
return (x > 0).astype(int)
step_function(np.array([1, 2, 3]))
- $x>0$을 처리하면 boolean list로 [True or False, ... ,] 의 형태로 구현이 된다
- 여기서
astype
을 이용하여int
로 type을 바꾸면 1또는 0으로 변환되어 기존의 목적을 달성할 수 있다.
필자가 생각한 코드는 아래와 같다. ndarray
의 모든 원소에 접근하는 거라면, np.where
도 써볼만하다.
def step_function(x):
return np.where(x > 0, 1, 0)
step_function(np.array([1, 2, 3]))
이를 python
을 이용하여 아래와 같이 시각화할 수 있다.
import numpy as np
import matplotlib.pyplot as plt
def step_function(x):
return np.where(x > 0, 1, 0)
x = np.arange(-5, 5, 0.1)
y = step_function(x)
plt.plot(x, y)
plt.ylim(-0.1, 1.1)
plt.show()
0을 경계로 출력이 0에서 1로 급격하게 바뀌어서 모양이 계단같다고 하여 이를 step function이라 한다.
sigmoid function을 python으로 다음과 같이 구현가능하다.
def sigmoid(x):
return 1 / (1 + np.exp(-x))
np.exp()
를 사용하여 ndarray를 반환했기에 $x$가 ndarray 여도 올바른 결과를 출력한다- 이는 numpy의 broadcasting 기능 덕분이다
Broadcasting
이란 numpy의 array와 scalar value의 연산을, numpy array의 각 원소와 scalar value의 연산으로 바꿔 처리하는 것이다.
sigmoid(np.array([-1.0, 1.0, 2.0]))
# array([0.26894142, 0.73105858, 0.88079708])
이제 sigmoid function을 graph로 시각화해보자.
import numpy as np
import matplotlib.pyplot as plt
def sigmoid(x):
return 1 / (1 + np.exp(-x))
x = np.arange(-5, 5, 0.1)
y = sigmoid(x)
plt.plot(x, y)
plt.ylim(-0.1, 1.1)
plt.show()
여기서 두 그래프를 비교해보자.
1) Difference
sigmoid
는 부드러운 곡선이며, 입력에 따라 출력이 연속적으로 변한다.
step function
은 0을 경계로 출력이 갑자기 바뀌어버린다.
즉,perceptron
에서는 0 또는 1이 흘렀다면,neural network
에서는 0과 1사이의 연속적인 실수가 흐른다.
2) Similarity
큰 관점에서 보면 비슷한 모양을 하고 있다. sigmoid
가 x축을 기준으로 압축을 많이 할수록 비슷해진다.
둘 다 입력이 작을 수록 0에 가깝고, 커지면 1에 가까워진다.
또한 입력이 아무리 작거나 커도 출력은 0과 1 사이이다.
위에서 설명한 sigmoid
와 step function
은 모두 non-linear function이다.
Neural Network에서는activation function
으로 반드시Non-Linear
function을 사용해야 한다.
그 이유는 Linear Function을 사용할 경우, Deep Neural Network처럼 Layer를 깊게 쌓았을 때 그 의미가 사라지기 때문이다.
즉, Layer를 아무리 깊게 쌓아도 Hidden Layer가 없는 network 하나로도 똑같은 기능을 수행할 수 있다는 것이다.
즉, Multi-layer effect를 사용하고 싶다면 반드시 activation function을 non-linear
하게 설정해야 한다.
Sigmoid보다 최근에 더 많이 사용하는 함수가 있다.
이를 ReLU
(Rectified Linear Unit)이라 한다.
수식으로는 아래와 같이 나타낼 수 있다.
$$h(x) = \begin{cases} x \: (x > 0) \\ 0 \:(x \leq 0) \end{cases}$$
or
$$h(x) = \text{max}(0, x)$$
python
으로 다음과 같이 구현가능하다.
import numpy as np
import matplotlib.pyplot as plt
def relu(x):
return np.maximum(x, 0)
x = np.arange(-5, 5, 0.1)
y = relu(x)
plt.plot(x, y)
plt.ylim(-0.1, 5.1)
plt.show()
-np.max()
: 단일 array내 최대값을 찾는 함수
-np.maximum()
: 서로 다른 array 사이에서 각 element별 최대값을 가져오는 함수
3.3) 다차원 배열의 계산
2D ndarray
b = np.array([[1, 2], [3, 4], [5, 6]])
print(b)
print(np.ndim(b))
print(np.shape(b))
# [[1 2]
# [3 4]
# [5 6]]
# 2
# (3, 2)
Matrix Multiplication
: RR, RC, CR, CC (4-way)RC
: row vector & colum vector 내적RR
: 왼쪽 row vector의 각 element를 오른쪽 row vector각각에 분배하여 합CC
: 오른쪽 column vector의 각 element를 왼쪽 column vector각각에 분배하여 합CR
: column vector(=row vector)의 개수만큼 각각 rank 1 matrix를 만들어서 합
np.dot()
과 @
과 np.inner()
간의 차이를 알고 싶다면 다음의 포스트를 살펴보자.
-a, b
가 모두 1D: np.dot()을 사용
-a, b
중 하나라도 2D 존재: @을 사용 (Matrix Multiplication shape 맞춰야 함)
Matrix Multiplication @ Neural Networks
$$\mathbf{W}^T \mathbf{x} = \mathbf{y}$$
위와 같이 식을 작성해야 $\mathbf{x}, \mathbf{y}$를 column vector로 처리할 수 있다.
$\mathbf{x}\mathbf{W} = \mathbf{y}$ 이렇게 처리하면 둘다 row vector이다.
Weight Matrix
$\mathbf{W}$- Size = $M \times N$ (Input Node $M$, Output Node $N$)
- \Node $x_i$에서 Node $y_i$로 갈 때 곱해지는 matrix의 원소는 $\mathbf{W}_{x_i y_i}$이다
- index count의 방향은 2D 좌표계 기준 -y, +x 방향을 count x, y 방향으로 설정한다.
따라서, 저 위의 weight를 보고 weight matrix $\mathbf{W}$는 다음과 같이 작성할 수 있다.
그렇기에 기존의 코드인 row vector대로라면, 아래와 같다.
X = np.array([[1, 2,]])
print(X.shape)
W = np.array([[1, 3, 5], [2, 4, 6]])
print(W.shape)
y = np.dot(X, W)
print(y)
# (1, 2)
# (2, 3)
# [[ 5 11 17]]
하지만 column vector를 고려한다면 아래와 같다.
X = np.array([[1, 2]]).T
print(X.shape)
W = np.array([[1, 3, 5], [2, 4, 6]])
print(W.shape)
y = np.dot(W.T, X)
print(y)
# (2, 1)
# (2, 3)
# [[5]
# [11]
# [17]]
3.4) 3층 신경망 구현하기
지금부터 설명하는 Notation은 기존의 책과 사뭇 다르다.
그 이유는 필자는 column vector를 기준으로 하여 연산을 진행하기 때문이다.
$\mathbf{W}_{ij}^{(k)}$
- $i$: 앞 neuron index
- $j$: 뒤 neuron index
- $k$: $k$번째 Layer
$\mathbf{a}_{i}^{k}$
- $i$: i번째 Neuron on $k^{\text{th}}$ layer
$\mathbf{b}_{i}^{k}$
- $i$: i번째 Neuron에 더해지는 bias on $k^{\text{th}}$ layer
$$a_1^{(1)} = \mathbf{W}_{11}^{(1)}x_1 + \mathbf{W}_{12}^{(1)}x_2 + b_1^{(1)}$$
여기서 행렬을 사용하면 간단하게 표현할 수 있다.
$$\mathbf{A}^{(1)} = (\mathbf{W}^{(1)})^T\mathbf{X} + \mathbf{B}^{(1)} $$
- $\mathbf{A}^{(1)} = \left [ a_1^{(1)}, a_2^{(1)}, a_3^{(1)}\right ]^T$
- $\mathbf{X} = \left [x_1, x_2 \right ]^T$
- $\mathbf{B}^{(1)} = \left [ b_1^{(1)}, b_2^{(1)}, b_3^{(1)}\right ]^T$
이에 대한 구현은 다음과 같다.
X = np.array([1.0, 0.5]).T
W1 = np.array([[0.1, 0.3, 0.5], [0.2, 0.4, 0.6]])
B1 = np.array([0.1, 0.2, 0.3]).T
print(W1.shape) # (2, 3)
print(X.shape) # (2,)
print(B1.shape) # (3,)
A1 = np.dot(W1.T, X) + B1
print(A1)
이어서 1번째 Layer에서의 activation function의 처리를 살펴보자.
Z1 = sigmoid(A1)
print(A1)
print(Z1)
# [0.3 0.7 1.1]
# [0.57444252 0.66818777 0.75026011]
이어서 1층에서 2층으로 가는 과정과 그 구현을 살펴보자.
$Z1$이 2nd Layer의 입력이 된다는 점을 제외하면 이전의 구현과 동일하다.
W2 = np.array([[0.1, 0.4], [0.2, 0.5], [0.3, 0.6]])
B2 = np.array([0.1, 0.2]).T
print(Z1.shape) # (3,)
print(W2.shape) # (3, 2)
print(B2.shape) # (2,)
A2 = np.dot(W2.T, Z1) + B2
Z2 = sigmoid(A2)
마지막으로 2층에서 Output Layer으로의 신호 전달이다.
activation function만 linear function으로, 지금까지와 다르다.
def identity_function(x):
return x
W3 = np.array([[0.1, 0.3], [0.2, 0.4]])
B3 = np.array([0.1, 0.2]).T
A3 = np.dot(W3.T,Z2) + B3
Y = identity_function(A3) # 혹은 Y = A3
이제 3개의 layer로 이루어진 Neural Network에 대한 설명은 끝났다.
지금까지의 구현을 코드로 정리하면 다음과 같다.
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]).T
network["W2"] = np.array([[0.1, 0.4], [0.2, 0.5], [0.3, 0.6]])
network["b2"] = np.array([0.1, 0.2]).T
network["W3"] = np.array([[0.1, 0.3], [0.2, 0.4]])
network["b3"] = np.array([0.1, 0.2]).T
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(W1.T, x) + b1
z1 = sigmoid(a1)
a2 = np.dot(W2.T, z1) + b2
z2 = sigmoid(a2)
a3 = np.dot(W3.T, z2) + b3
y = identity_function(a3)
return y
network = init_network()
x = np.array([1.0, 0.5]).T
y = forward(network, x)
print(y)
# [0.31682708 0.69627909]
init_network
: weight, bias를 초기화하고, newtork에 dictionary로 저장forward
: input 신호를 output으로 출력하는 처리과정을 모두 구현
3.5) 출력층 설계하기
Hidden Layer과 Output Layer Activation은 주로 다음과 같은 상황에서 사용된다.
Hidden Layer Activation
: 사용하는 연산에 따라 결정 (MLP, CNN, RNN 등)Output Layer Activation
: 해결하는 문제(Task)에 따라 결정 (Regression, Classification)
identity function
: input signal을 그대로 output signal로 사용- output layer의 $k$번째 node는 오직 $k$번째 input node에만 영향을 받음
$$y_k = a_k$$
softmax function
: $e^x$ (exponential function)을 사용하여 각 class에 $\left [0, 1 \right]$ 사이의 weight 부여- output layer의 $k$번째 node는 모든 input node에 영향을 받음
$$y_k = \frac {\text{exp}(a_k)} {\sum_{i=1}^{n}\text{exp}(a_i)}$$
- $n$: output layer의 node 개수
- $y_k$: k번째 node의 output
- $a_k$: k번째 input signal
a = np.array([0.3, 2.9, 4.0])
exp_a = np.exp(a)
print(exp_a) # [ 1.34985881 18.17414537 54.59815003]
sum_exp_a = np.sum(exp_a)
print(sum_exp_a) # 74.1221542101633
y = exp_a / sum_exp_a
print(y) # [0.01821127 0.24519181 0.73659691]
이제 위의 process를 바탕으로 softmax function을 구현해보자.
def softmax(a):
return np.exp(a) / np.sum(np.exp(a))
softmax function
을 사용할 때는, exponential function인 지수함수를 사용하기 때문에 매우 큰 값을 내받는 경우가 있다.
이때, 이러한 큰 값들끼리 나눗셈을 할 경우 overflow
문제가 발생한다.
이러한 문제를 해결하기 위해 아래와 같은 변형을 이용한다.
softmax
의 특징 중 하나는, 지수함수 계산시 지수 안에 어떠한 정수를 더하거나 빼도 결과는 바뀌지 않는다는 것이다.
따라서 지수 안에 기존의 input $a_i$에 input의 maximum을 빼서 최종 지수 안에 넣음으로써,overflow
를 방지할 수 있다.
$$y_k=\frac{\exp(a_k)}{\sum_{i=1}^{n}\exp(a_i)}=\frac{C\exp(a_k)}{C\sum_{i=1}^{n}\exp(a_i)} \ =\frac{\exp(a_k+\log C)}{\sum_{i=1}^{n}\exp(a_i+\log C)} \ =\frac{\exp(a_k+C')}{\sum_{i=1}^{n}\exp(a_i+C')}$$
overflow 문제를 실제 코드로 구현하면 다음과 같다.
a = np.array([1010, 1000, 990])
y = softmax(a)
print(y) # [nan nan nan]
c = np.max(a)
print(a - c) # [ 0 -10 -20]
y = softmax(a - c)
print(y) # [9.99954600e-01 4.53978686e-05 2.06106005e-09]
이를 해결하는 softmax는 python
으로 다음과 같이 구현가능하다.
def softmax(a):
array = np.exp(a - np.max(a))
return array / np.sum(array)
a = np.array([0.3, 2.9, 4.0])
y = softmax(a)
print(y) # [0.01821127 0.24519181 0.73659691]
print(np.sum(y)) # 1.0
softmax
함수의 총합은 1이고, 각 index는 0에서 1사이의 값을 가지기 때문에 이를 probability
로 해석할 수 있다.
즉, Multi-Class Classification task에서는 $y$의 index=2
에 있는 원소의 value가 73.7%로 가장 높기 때문에, 결국 2번째 class에 속하는 값이다라고 해석할 수 있다.
주의할 점은,softmax function
을 사용해도 각 원소의 대소관계는 변하지 않는다는 것이다.
$e^x$ function이 단조 증가 함수이므로, $a$에서의 maximum 원소가 $y$에서의 maximum element와 동일하다.
이때, 일반적으로 가장 큰 value를 내는 원소에 해당하는 class로 classification이 이루어진다.
그리고 softmax
를 적용해도 value의 대소관계는 바뀌지 않기에, classification task
의 경우 computational cost를 줄이기 위해 output layer activation에서 softmax를 주로 생략하곤 한다.
여기서softmax
를 생략한다고 말하는 것은:
-training
: output layer activation에 softmax 사용
-inference
: output layer activation에 softmax 생략
Multi-class Classification
: Class의 개수Binary Classification
: 2개Regression
: 1개
3.6) 손글씨 숫자 인식
해당 section에서는 backpropagation
을 이용한 training을 생략하고, inference만 구현한다.
이를 forward propagation
이라고도 한다.
training
: 학습 데이터를 사용하여 weight parameter를 학습inference
: trained weight을 이용하여 input data에 대해 작업 수행
MNIST Dataset은 사람이 쓴 손글씨 숫자 이미지 집합으로, 0부터 9까지의 숫자 이미지로 구성되어 있다.
Train data가 60,000장, Test data가 10,000장으로 준비되어 있으며, Train data로 모델을 학습하고, 학습한 모델을 Test data를 통해 성능을 평가한다.
각 이미지는 $28 \times 28$의 size이며, 각 pixel은 0부터 255까지의 값을 가진다.
아래의 코드는 MNIST Dataset을 다운받아 numpy
배열로 변환해주는 코드이다.
import sys, os
sys.path.append(os.pardir) # 부모 디렉터리의 파일을 가져올 수 있도록 설정
from dataset.mnist import load_mnist
(x_train, t_train), (x_test, t_test) = load_mnist(flatten=True, normalize=False)
# 각 데이터의 형상 출력
print(x_train.shape) # (60000, 784)
print(t_train.shape) # (60000,)
print(x_test.shape) # (10000, 784)
print(t_test.shape) # (10000,)
- x_train: train data
- t_train: train data label
- x_test: test data
- t_test: test data label
아래는 load_mnist()
함수의 parameter에 대한 설명이다.
normalize
는 $[0, 255]$ 범위의 배열을 $[0, 1]$ 사의의 값으로 normalize 하는 지의 여부이고,flatten
은 False로 $1 \times 28 \times 28$을 할지, True로 $1 \times 784$로 할 지 결정한다.one_hot_label
은 label을 저장할 때 one-hot encoded 형태로 저장할 지, 숫자(0~9) 형태로 저장할지를 정한다.
그럼 이제, MNIST Dataset을 가져왔으니 시각화해서 확인을 해보자.
import sys, os
sys.path.append(os.pardir) # 부모 디렉터리의 파일을 가져올 수 있도록 설정
import numpy as np
from dataset.mnist import load_mnist
from PIL import Image
def img_show(img):
pil_img = Image.fromarray(np.uint8(img))
pil_img.show()
(x_train, t_train), (x_test, t_test) = load_mnist(flatten=True, normalize=False)
img = x_train[0]
label = t_train[0]
print(label) # 5
print(img.shape) # (784,)
img = img.reshape(28, 28)
print(img.shape) # (28, 28)
img_show(img)
이제 MNIST Dataset을 이용하여 Inference를 수행하는 Neural Network를 구현해보자.
- Input Layer: 784개의 Node ($28 \times 28 = 784$)
- Output Layer: 10개의 Node ($10$개의 Class: 0 ~ 9)
- Hidden Layer: 1st는 50개, 2nd는 100개의 Node
pickle
은 프로그램 실행 중에 특정 객체를 파일로 저장하는 기능이다.
즉, 저장한pickle
파일을 로드하면 실행 당시의 객체를 즉시 복원할 수 있다.
해당 pickle 파일은 아래의 공식 github에서 찾을 수 있다.
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"].T, network["b2"].T, network["b3"].T
a1 = np.dot(W1.T, x) + b1
z1 = sigmoid(a1)
a2 = np.dot(W2.T, z1) + b2
z2 = sigmoid(a2)
a3 = np.dot(W3.T, z2) + b3
y = softmax(a3)
return y
그렇다면 이제 실제 trained neural network의 classification 성능을 측정하기 위해 아래의 코드를 작성하자.
x, t = get_data()
network = init_network()
accuracy_cnt = 0
for i in range(len(x)):
y = predict(network, x[i]) # x[i] shape: (784,)
p = np.argmax(y)
if p == t[i]:
accuracy_cnt += 1
print(f"Accuracy: {float(accuracy_cnt) / len(x)}")
Accuracy: 0.9352
이는 학습된 Neural Network의 분류 성능이 93.52%라는 것이다.
Preprocessing
: Input Data에 특정 변환을 가하는 것Normalization
: Data를 특정 범위로 변환하는 처리
위의 Neural Network는 Image Data에 대한 preprocessing으로 Normalization을 수행한 것이다.
이전의 코드에서 사용한 가중치들의 shape을 살펴보자.
x, _ = get_data()
network = init_network()
W1, W2, W3 = network["W1"], network["W2"], network["W3"]
print(x.shape) # (10000, 784)
print(W1.shape) # (784, 50)
print(W2.shape) # (50, 100)
print(W3.shape) # (100, 10)
이때 최종적으로 $y$는 $(10000, 10)$의 shape을 갖고, 그건 하나로 묶인 input data의 단위를batch
라고 하는데,batch
단위로 데이터를 처리하기 때문이다.
batch 처리까지 반영한 코드는 아래와 같다.
x, t = get_data()
network = init_network()
batch_size = 100 # 배치 크기
accuracy_cnt = 0
for i in range(0, len(x), batch_size):
# for loop 안은 하나의 batch에 대한 처리
x_batch = x[i : i + batch_size] # (100, 784)
y_batch = np.array(
[predict(network, x[j]) for j in range(i, i + batch_size)]
) # (100, 10)
p = np.argmax(y_batch, axis=1) # (100,)
accuracy_cnt += np.sum(p == t[i : i + batch_size]) # True: 1, False: 0
print(f"Accuracy: {float(accuracy_cnt) / len(x)}")
for loop안은 하나의 batch에 대한 처리로 해석하자.
이처럼 데이터를 batch로 묶어서 처리하면 효율적이고 빠르게 처리할 수 있다.