[밑바닥부터 시작하는 딥러닝 1] 3. MNIST 손글씨 숫자 인식
mnist.py github
https://github.com/oreilly-japan/deep-learning-from-scratch/blob/master/dataset/mnist.py
위 파일을 다운받아 dataset 폴더에 넣는다.
(책에는 안 나와있다......)
MNIST 데이터셋은 0부터 9까지 숫자 이미지로 구성된다.
훈련 이미지가 60000장, 테스트 이미지가 10000장 준비되어 있다.
# 데이터 불러오기
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)
load_mnist 함수는 읽은 MNIST 데이터를
(훈련 이미지, 훈련 레이블), (테스트 이미지, 테스트 레이블) 형식으로 반환한다.
인수로는 normalize, flatten, one_hot_label 세 가지를 설정할 수 있다.
normalize는 입력 이미지의 픽셀값 0~255를 0.0~1.0값으로 정규화할 건지 (False여서 정규화하지 않는다.)
flatten은 입력 이미지를 평탄한 1차원 배열로 만들지 (True로 784개로 이루어진 1차원 배열로 저장한다.)
one_hot_lable은 레이블을 원-핫 인코딩할 건지 정하는 인수이다.
예를 들면 정답만 1이고 나머지가 0인 배열을 말한다.
# MNIST 데이터셋의 첫 번째 훈련 이미지를 출력해보자
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)
from PIL import Image
import numpy as np
def img_show(img):
pil_img = Image.fromarray(np.uint8(img))
pil_img.show()
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)
PIL 모듈로 이미지를 표시할 수 있다.
flatten으로 평탄하게 했으므로 reshape() 메서드로 원래 shape로 돌아간다.
또한 넘파이로 저장된 이미지 데이터를 PIL용 데이터 객체로 변환해야 하며,
이는 Image.fromarray() 가 수행한다.
# 신경망의 추론 처리
이 신경망은 입력층 뉴런을 784개, 출력층 뉴런을 10개로 구성한다.
입력층 뉴런이 784개인 이유는 이미지 크기가 28*28=784이기 때문이고,
출력층 뉴런이 10개인 이유는 이 문제가 0에서 9까지의 숫자를 구분하는 문제이기 때문이다.
은닉층은 총 두 개로,
첫 번째 은닉층에는 50개의 뉴런을, 두 번째 은닉층에는 100개의 뉴런을 배치한다.
이는 임의로 지정한 값이다.
sample_weight.pkl github
https://github.com/oreilly-japan/deep-learning-from-scratch/blob/master/ch03/sample_weight.pkl
위 파일을 사용한다.
import sys, os
sys.path.append(os.pardir) # 부모 디렉터리의 파일을 가져올 수 있도록 설정
from dataset.mnist import load_mnist
import pickle
import numpy as np
def sigmoid(x):
return 1 / (1 + np.exp(-x))
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
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
init_network()에서는 sample_weight.pkl에 저장된 '학습된 가중치 매개변수'를 읽는다.
이 파일에는 가중치와 절편 매개변수가 딕셔너리 변수로 저장되어 있다.
# 신경망에 의한 추론과 정확도
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
가장 먼저 MNIST 데이터셋을 얻고 네트워크를 생성한다.
이어서 for 문을 돌며 x에 저장된 이미지 데이터를 1장씩 꺼내 predict() 함수로 분류한다.
여러 장씩 꺼내는 방식도 있는데, 이때의 입력 데이터를 배치(batch)라고 한다.
predict() 함수는 각 레이블의 확률을 넘파이 배열로 반환한다.
그런 다음 argmax() 함수로 이 배열에서 값이 가장 큰(확률이 가장 높은) 원소의 인덱스를 구한다.
마지막으로 신경망이 예측한 답변과 정답 레이블을 비교하여 맞힌 숫자(accuracy_cnt)를 세고,
이를 전체 이미지 숫자로 나눠 정확도를 구한다.
이 예제에서 normalize를 True로 설정했는데,
이처럼 데이터를 특정 범위로 변환하는 처리를 [정규화]라고 하고,
신경망의 입력 데이터에 특정 변환을 가하는 것을 [전처리]라고 한다.
각 층의 가중치 형상을 출력해보자.
x, _ = get_data()
network = init_network()
W1, W2, W3 = network['W1'], network['W2'], network['W3']
print(x.shape) # (10000, 784)
print(x[0].shape) # (784,)
print(W1.shape) # (784, 50)
print(W2.shape) # (50, 100)
print(W3.shape) # (100, 10)
원소 수가 일치하는 차원이 있음을 확인할 수 있다.
# 배치 처리
이번에는 배치 처리를 구현해본다.
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
range(start, end, step) 함수는 start에서 end-1까지 step 간격으로 증가하는 정수를 반환하는 반복자를 돌려준다.
이를 바탕으로 x[i:i+batch_size]에서 입력 데이터를 묶는다.
x[0:100], x[100, 200], ...와 같이 100장씩 묶어 꺼내게 된다.
argmax() 함수는 최댓값의 인덱스를 가져온다.
axis=1로 지정하였는데, 이는 100*10의 배열 중 1번째 차원을 구성하는 각 원소에서 최댓값의 인덱스를 찾도록 한 것이다.
마지막으로 배치 단위로 분류한 결과를 실제 답과 비교한다.
이를 위해 == 연산자를 사용해 넘파이 배열끼리 비교하여 True/False로 구성된 bool 배열을 만들고, True의 개수를 센다.