machine learning, image

MNIST Handwritten Digit Recognition in Keras(Tensorflow)

qkqhxla1 2018. 12. 30. 22:01

파이썬 3 기반입니다.


https://nextjournal.com/gkoehler/digit-recognition-with-keras


에서 읽은 글을 다시 제가 정리했습니다. 위 글도 좋긴 한데 굳이 쓸때없는 pyplot를 이용한 그래프 그리기와, 변수명 등을 너무 대충 지어서 가독성이 떨어지기에 공부하면서 다시 정리했습니다. (예로 : (X_train, y_train), (X_test, y_test) = mnist.load_data()) 새로 정리한 글에서는 신경 네트워크 만드는 부분과 데이터 넣는법, 만드는 법을 중점적으로 적겠습니다. tensorflow에도 keras가 들어왔기에 tensorflow의 keras를 쓰겠습니다.


목적은 다른 머신러닝을 돌리기 위한 적당한 코드 이해입니다. 


여기에서는 mnist의 숫자 데이터를 이용해서 간단하게 숫자를 인식하는 뉴럴 네트워크를 만들어볼겁니다.

mnist의 데이터는 70000개의 데이터가 있습니다. 크기는 28x28이며, 그중 60000개는 훈련용 데이터로 쓸거고, 10000개는 검증용으로 쓸겁니다.(원문에는 테스트용으로 쓴다고 그러는데 검증용입니다.) tensorflow를 보다보면 트레이닝, 검증, 테스트용 이렇게 3가지가 나오는데 다르게 구분해야 하는것 같습니다.

트레이닝, 검증, 테스트용 데이터의 다른점 : 링크

요약 : 트레이닝 데이터 : 트레이닝용. 성능을 더 향상시키기 위해 사용.

검증 데이터 : 트레이닝이 원활하게 되고 있는지 검증용.

테스트용 : 가장 마지막에 돌려보는것.


케라스는 유저 친화적으로 설계된 하이레벨 api입니다. 그냥 한번 써보니까 이것저것 인자 조작하기가 다른거보다 쉽다고 느꼈습니다.


환경 설정은 넘어가고 mnist데이터를 가져오겠습니다. 

import numpy as np
import tensorflow as tf
from tensorflow import keras
import os

(train_data, train_label), (test_data, test_label) = keras.datasets.mnist.load_data()

# let's print the shape before we reshape and normalize
print("train_data shape", train_data.shape)
print("train_label shape", train_label.shape)
print("test_data shape", test_data.shape)
print("test_label shape", test_label.shape)

for line in train_data[0]:
    for c in line:
        if c == 0:
            print('0', end='')
        else:
            print('1', end='')
    print()

print(train_label[0])

shape로 numpy의 배열 모양을 출력해보면 알겠지만 train_data는 (60000, 28, 28) 입니다. 60000x28x28의 3차원 배열이며, 60000개의 28x28의 숫자가 있다고 생각하시면 됩니다. train_label안에는 해당 인덱스에 train_data가 나타내는 숫자가 어떤건지 적혀져있습니다.


첫번째 숫자를 예로 출력해보면 아래와 같습니다. 숫자 5 입니다. 위 코드의 출력하는 부분에서 값이 0이면 배경이고, 아니면 숫자 부분이기에 저렇게 간단하게 출력하였습니다.


테스트는 이와 같고 numpy의 포맷을 알아둬야 하는 이유는 저같은 초보들은 다음에 tensorflow로 머신러닝을 할때 타입을 동일하게 넣어줘야 하기 때문입니다. 아마 많이쓰는 현직자분들은 어떻게 다른 포맷으로도 할수 있겠죠..


숫자 부분의 값을 1로 전부 퉁쳤는데, print를 바꿔보면 알겠지만 1~255사이의 값입니다. 이것으로 얼마나 검은 부분인지 표현합니다. 0에 가까울수록 흰색, 255에 가까울수록 검정색입니다. 이미지가 grayscale이라서 전부 흰색 아니면 검정색밖에 표현이 안되어 있습니다.

# building the input vector from the 28x28 pixels
train_data = train_data.reshape(60000, 784)
test_data = test_data.reshape(10000, 784)
train_data = train_data.astype('float32')
test_data = test_data.astype('float32')

# normalizing the data to help with the training
train_data /= 255
test_data /= 255

그리고 이미지를 변환시켜줍니다. 주석에 적혀있듯이 6000,28,28이었던 이미지를 60000,784로 변환시켜줍니다. 구글링하다 봤는데 트레이닝시 28x28이렇게 2차원 배열로 넣어주면 안되고, 1차원으로 죽 길게 늘여서, 즉 28*28사이즈인 784로 변환시켜서 넣어줘야 한다고 합니다.

그리고 데이터를 노멀라이징 시켜줍니다. np array를 255로 나누면 각각의 원소가 255로 나눠진 값이 들어갑니다. 노멀라이징 시켜주면 더 빨라지고 뭐가 좋다좋다고 구글에서 하는데.. 그냥 그런가보다 했습니다.(255로 나누면 소수점이 될 것이므로 앞에서 astype으로 float로 변환해줬습니다.)


그리고 이제 마지막으로 label들을 모델에 넣기위한 포맷으로 만들어줘야 합니다. 위에서 숫자 5의 레이블은 그냥 5입니다. 이게 아니라 [0, 0, 0, 0, 0, 1, 0, 0, 0, 0] 요런 포맷으로 변환시켜줘야합니다. 숫자가 10개니 10개의 원소중 5라는 표시를 하기 위해 index 5를 1로 만들어줍니다.

# one-hot encoding using keras' numpy-related utilities
n_classes = 10
print("Shape before one-hot encoding: ", train_label.shape)
train_label = keras.utils.to_categorical(train_label, n_classes)
test_label = keras.utils.to_categorical(test_label, n_classes)
print("Shape after one-hot encoding: ", train_label.shape)

keras에 to_categorical이라는게 있는데 이 함수에 그냥 넣으면 됩니다. print를 보면

Shape before one-hot encoding:  (60000,)

Shape after one-hot encoding:  (60000, 10)

로 그냥 1차원 60000개에서 2차원인 60000x10으로 변했음을 알수있습니다. 

이제 네트워크를 만들 차례입니다. 위 사진과 같은 네트워크를 만들겁니다. 입력값은 28*28인 784가 들어올테고, 레이어 1에서 512개의 노드가 있고 이것들이 상호작용해서 결과값을 레이어 2로 보내고.. 최종적으로 0~9중 하나가 나오게 됩니다. 

# building a linear stack of layers with the sequential model
model = keras.Sequential()
model.add(keras.layers.Dense(512, input_shape=(784,)))
model.add(keras.layers.Activation('relu'))
model.add(keras.layers.Dropout(0.2))

model.add(keras.layers.Dense(512))
model.add(keras.layers.Activation('relu'))
model.add(keras.layers.Dropout(0.2))

model.add(keras.layers.Dense(10))
model.add(keras.layers.Activation('softmax'))

model.compile(optimizer=tf.train.AdamOptimizer(),
              loss='categorical_crossentropy',
              metrics=['accuracy'])

이와 같이 만들수 있고 대충 코드만 보면 어떤건지 이해는 할수 있습니다. Activation이 뭔가 찾아봤는데 퍼셉트론이란 거랍니다. 아직 허접이기에 그냥 바꿀일없는 고정변수값으로 생각하면 될것같습니다.. 

Dropout은 과적합을 막기 위해 사용된답니다. 과적합이란 똑같은 데이터로 학습을 엄청나게 오래했을때 내 데이터에 대해서는 좋은 결과값이 나오지만 새 데이터에 대해서는 좋은 결과가 못나오는, 즉 내 데이터에만 적합하게 만들어진 그런걸 뜻한답니다. Dropout도 그냥 고정 변수값으로 생각했습니다.


그후 모델을 컴파일 해야하는데 AdamOptimizer라는 옵티마이저를 사용했습니다. 구글링하면서 다른 예시를 봐도 다 이 옵티마이저를 사용하기에 그냥 최적화 잘하는 건가보다.. 하고 사용합니다. loss=는 loss function인것 같은데 이것도 그냥 그런가보다 하고 넘어갔습니다.


이제 모델 준비가 되었으니 트레이닝을 시키고 모델을 저장하겠습니다.

# training the model and saving metrics in history
model.fit(train_data, train_label,
          batch_size=128, epochs=20,
          verbose=2,
          validation_data=(test_data, test_label))

# saving the model
save_dir = "./results"
model_name = 'keras_mnist.h5'
model_path = os.path.join(save_dir, model_name)
model.save(model_path)
print('Saved trained model at %s ' % model_path)

fit이라는 함수로 계속 맞는 방향으로 트레이닝을 시킵니다. batch_size는 한번에 트레이닝시킬 데이터 수로서, 128이나 256을 많이 쓴다고 합니다. epoch는 영어 뜻을 찾아보면 세대라는 뜻인데, 한 세대 다음 세대 그 뜻입니다. 한번 트레이닝을 시키고, 그것을 기반으로 다음 세대에서는 조금 더 좋아지고.. 뭐 그러겠죠. 

처음에 그러면 한 epoch당 하나의 batch_size만큼 트레이닝되는건가?? 그럼 총 batch_size*epochs의 값인 128*20개의 데이터만 쓰는건가?했는데 아닙니다. 1 epoch당 트레이닝 데이터 전체가 돌아가는데, 이 트레이닝 데이터를 나눠서 돌리는 값이 batch_size입니다. 

테스트 데이터 1000개가 있고 batch_size 가 500이면, 1 epoch는 batch_size가 2번 돌아야 됩니다.

https://stats.stackexchange.com/questions/153531/what-is-batch-size-in-neural-network


맨 위에서 말한 검증 데이터에는 test_data와 test_label을 넣어줍니다. verbose도 딱히 뭔가 바꿀일이 없어서 그냥 고정변수처럼 생각했습니다. 코드를 돌리면 해당 모델이 위의 경로에 저장됩니다.


아래는 트레이닝 전체 코드입니다.

# imports for array-handling and plotting
import numpy as np
import tensorflow as tf
from tensorflow import keras
import os

(train_data, train_label), (test_data, test_label) = keras.datasets.mnist.load_data()

# let's print the shape before we reshape and normalize
print("train_data shape", train_data.shape)
print("train_label shape", train_label.shape)
print("test_data shape", test_data.shape)
print("test_label shape", test_label.shape)

# building the input vector from the 28x28 pixels
train_data = train_data.reshape(60000, 784)
test_data = test_data.reshape(10000, 784)
train_data = train_data.astype('float32')
test_data = test_data.astype('float32')

# normalizing the data to help with the training
train_data /= 255
test_data /= 255

# print the final input shape ready for training
print("Train matrix shape", train_data.shape)
print("Test matrix shape", test_data.shape)

# one-hot encoding using keras' numpy-related utilities
n_classes = 10
print("Shape before one-hot encoding: ", train_label.shape)
train_label = keras.utils.to_categorical(train_label, n_classes)
test_label = keras.utils.to_categorical(test_label, n_classes)
print("Shape after one-hot encoding: ", train_label.shape)

# model = keras.models.load_model("./results/keras_mnist.h5")
# model.compile(optimizer=tf.train.AdamOptimizer(),
#               loss='categorical_crossentropy',
#               metrics=['accuracy'])
# loss_and_metrics = model.evaluate(X_test, Y_test, verbose=2)
#
# print("Test Loss", loss_and_metrics[0])
# print("Test Accuracy", loss_and_metrics[1])
# exit(0)

# building a linear stack of layers with the sequential model
model = keras.Sequential()
model.add(keras.layers.Dense(512, input_shape=(784,)))
model.add(keras.layers.Activation('relu'))
model.add(keras.layers.Dropout(0.2))

model.add(keras.layers.Dense(512))
model.add(keras.layers.Activation('relu'))
model.add(keras.layers.Dropout(0.2))

model.add(keras.layers.Dense(10))
model.add(keras.layers.Activation('softmax'))

model.compile(optimizer=tf.train.AdamOptimizer(),
              loss='categorical_crossentropy',
              metrics=['accuracy'])

# training the model and saving metrics in history
model.fit(train_data, train_label,
          batch_size=128, epochs=20,
          verbose=2,
          validation_data=(test_data, test_label))

# saving the model
save_dir = "./results"
model_name = 'keras_mnist.h5'
model_path = os.path.join(save_dir, model_name)
model.save(model_path)
print('Saved trained model at %s ' % model_path)

모델을 저장했으니 가져와서 쓰려면 keras.models.load_model(path)로 가져와서 다시 compile후 쓰면 됩니다.


https://www.tensorflow.org/tutorials/keras/save_and_restore_models를 보니 현재는 tensorflow의 옵티마이저를 저장할수 없어서 매번 다시 가져와서 컴파일해야 한다고 합니다.