MNIST는 딥러닝에서 자주 사용되는 대규모 데이터 Set으로, 그 안에는 손으로 그린 0부터 9까지의 숫자 이미지가 포함되어 있다. MNIST는 아래와 같은 특징을 갖는다.
이처럼 처음에는 MNIST가 단지 손으로 그린 숫자를 인식할 수 있는 모델을 만들기 위해 사용하는 데이터 Set이다. 하지만 현재의 MNIST는 일종의 형식처럼 간주되어, 다양한 변형 MNIST 데이터 Set이 존재하게 되었다.
이번 포스팅에서 사용할 Fashion MNIST 역시 대표적인 변형 MNIST 중 하나이다. 즉, Fashion MNIST는 숫자 이미지 대신 패션 아이템을 취급한다는 점만 제외하면, MNIST의 특징을 그대로 가지고 있는 것이다.
그러면 지금부터 Fashion MNIST에 어떠한 데이터가 포함되어 있는지 확인해보자. Fashion MNIST는 매우 유명한 데이터 Set이기 때문에, 이미 많은 딥러닝 라이브러리에서 이 데이터를 불러올 수 있는 도구를 제공하고 있다.
① Tensorflow의 Keras 패키지를 이용하여 Fashion MNIST 데이터를 다운로드한다.
load_data()
메서드는 훈련 Set과 테스트 Set에서의 입력과 타깃을 구분하여 반환한다.from tensorflow import keras
(train_input, train_target), (test_input, test_target) = keras.datasets.fashion_mnist.load_data()
② 좌측 폴더 아이콘을 클릭하여, 다운로드 된 파일을 확인할 수 있다.
③ 훈련 Set과 테스트 Set에서의 입력과 타깃의 크기를 확인해보자.
print(train_input.shape, train_target.shape)
print(test_input.shape, test_target.shape)
④ 어떤 이미지가 포함되어 있는지 확인해보기 위해, 10개의 Sample을 그림으로 그려보자.
import matplotlib.pyplot as plt
fig, axs = plt.subplots(1, 10, figsize=(10, 10))
for i in range(10):
axs[i].imshow(train_input[i], cmap='gray_r')
axs[i].axis('off')
plt.show()
⑤ 10개의 Sample에 대한 타깃 값도 확인해보자.
print([train_target[i] for i in range(10)])
⑥ Fashion MNIST의 이미지도 마찬가지로, 매핑되는 숫자 레이블과 쌍을 이루고 있다.
⑦ Fashion MNIST의 각 레이블에는 정확히 6000개의 Sample이 포함되어 있다.
import numpy as np
print(np.unique(train_target, return_counts=True))
Artificial Neural Network(ANN, 인공 신경망)는 생물학적 뉴런의 동작 방식을 모방하여 설계된, 딥러닝 분야의 기계 학습 과정이다. 뉴런은 수상 돌기를 통해 받아들인 외부 신호를 세포체에 모아둔다. 이 때, 축적된 신호가 역치 이상에 도달할 경우, 뉴런은 축삭 돌기를 통해 해당 신호를 다른 뉴런에게 전달한다.
ANN도 주어지는 입력 데이터를 한 곳으로 모아 Weighted-Sum을 계산하고, 이에 대한 결과를 출력한다. 특히 분류 문제일 경우, 출력 값이 확률이기 때문에, 활성 함수를 통해 0과 1 사이의 값으로 변환된다. 마치 이러한 동작 방식이 생물학적 뉴런과 비슷하다고 하여, 인공 신경망이라 불리게 되었다.
입력 값에 대한 Weighted-Sum을 계산하고 계산 결과를 활성 함수로 변환하는 과정에서, 이전에 배운 Logistic Regression을 떠올릴 수 있다. 놀라운 사실은, 실제로도 인공 신경망의 기본 원리는 SGD를 적용한 Logistic Regression과 거의 유사하다는 것이다. 따라서, ANN을 더 명확하게 이해하기 위해, 먼저는 SGD Logistic Regression 모델에 대해 간단히 복습해보자.
훈련 Set의 데이터가 6만개나 포함되어 있으므로, 한번에 훈련시키기보다는 SGD를 이용하여 Sample을 하나씩 훈련하는 방법이 더 좋을 것 같다. 그러면 지금부터 Fashion MNIST의 이미지를 분류하는 SGD Logistic Regression 모델을 만들어보도록 하자.
① 이미지 데이터를 사용할 때에는 0에서 255 사이의 픽셀 값을 0과 1 사이의 값으로 정규화하는 것이 일반적이다.
train_scaled = train_input / 255
② 데이터의 특성은 반드시 1차원으로 표현되어야 하므로, train_scaled 배열의 차원을 변환해야 한다.
train_scaled = train_scaled.reshape(-1, 28 * 28)
③ 변환 결과를 확인한다.
print(train_scaled.shape) # (60000, 784) 출력
④ SGDClassifier 모델을 만든 후, 교차 검증을 수행하여 성능을 평가해보자.
from sklearn.model_selection import cross_validate
from sklearn.linear_model import SGDClassifier
sc = SGDClassifier(loss='log_loss', max_iter=5, random_state=42)
scores = cross_validate(sc, train_scaled, train_target, n_jobs=-1)
print(np.mean(scores['test_score']))
비록 엄청 좋은 성능은 아니지만, SGDClassifier 모델이 대체적으로 분류를 잘 수행하였다. SGDClassifier 모델이 분류를 수행할 수 있었던 이유는 아래와 같은 형태의 방정식을 학습했기 때문이다.
z_티셔츠 = w1 * (픽셀 1) + w2 * (픽셀 2) + ... + w3 * (픽셀 3) + c
z_바지 = w1' * (픽셀 1) + w2' * (픽셀 2) + ... + w784 * (픽셀 784) + c
...
즉, 픽셀(입력)에 대한 가중치와 상수항(절편)을 다르게 조절함으로써, 분류를 수행할 수 있었던 것이다. 물론, Softmax Function을 이용하면, 각 클래스에 대한 확률도 얻을 수 있을 것이다.
ANN 모델도 Logistic 회귀 모델과 비슷한 구조를 가지고 있다.
① Input Layer(입력층)
② Neuron
③ Dense Layer(밀집층)
④ Output Layer(출력층)
SGD Logistic Regression은 ANN 모델을 이해하는 데에 도움을 줄 수 있지만, 결코 ANN 모델을 만들기에 적합한 알고리즘은 아니다. 그러므로, SGDClassifer보다 더 강력한 ANN 모델을 지원하는 딥러닝 라이브러리를 사용해야 한다.
딥러닝 라이브러리 중 가장 인기가 높은 것은 단연 구글의 Tensorflow이다. Tensorflow에는 저수준의 API와 고수준의 API가 존재하는데, 저수준 API는 세밀한 제어와 최적화를, 고수준 API는 간편한 구축과 관리를 목표로 하고 있다. (저수준 API는 초심자의 영역이 아니므로, 고수준 API에 대해서만 설명하기로 한다.)
Tensorflow에서 공식적으로 사용하는 고수준 API가 바로 Keras이다. 딥러닝 라이브러리는 머신 러닝 라이브러리와 달리, GPU를 사용하여 모델을 훈련시킨다. GPU는 벡터와 행렬 연산에 최적화되어 있기 때문에, GPU를 활용하면 ANN 모델을 매우 빠르게 훈련할 수 있게 된다. 그러나 Keras는 직접적으로 GPU 연산을 수행할 수 없기 때문에, 별도의 Backend 라이브러리를 필요로 하는데 이 때, 사용되는 라이브러리가 바로 Tensorflow이다.
예전에는 Muti-Backend Keras라고 해서, Tensorflow가 아닌 다른 Backend 라이브러리를 사용하기도 하였다. 그러나 현재는 Tensorflow에 Keras API가 내장되면서, 사실상 Tensorflow만을 Backend 라이브러리로 사용하고 있다. 이러한 이유에서, Tensorflow와 Keras는 마치 동의어처럼 사용되기도 한다.
① 훈련 Set과 테스트 Set, 검증 Set을 만든다.
from tensorflow import keras
from sklearn.model_selection import train_test_split
(train_input, train_target), (test_input, test_target) = keras.datasets.fashion_mnist.load_data()
train_scaled = train_input / 255
train_scaled = train_scaled.reshape(-1, 28 * 28)
train_scaled, val_scaled, train_target, val_target = train_test_split(train_scaled, train_target, test_size=0.2, random_state=42)
※ 교차 검증을 수행하지 않고 검증 Set을 떼어내는 이유
일반적으로 ANN 모델의 데이터 Set은 매우 크기 때문에, 교차 검증을 수행하지 않는다. 그 이유는 아래와 같다.
- 검증 Set을 따로 떼어내더라도, 검증 점수가 충분히 안정적이다.
- 교차 검증의 실행 시간은 매우 길기 때문에, 큰 데이터 Set에 사용하기에 부적합하다.
② 먼저 ANN의 밀집층부터 만들어보기로 하자.
dense = keras.layers.Dense(10, activation='softmax', input_shape=(784,))
③ 위에서 만든 밀집층을 갖는 ANN 모델을 만든다.
model = keras.Sequential(dense)
④ Keras 모델은 훈련하기 전에 몇 가지 사전 설정을 진행해야 한다.
compile()
메서드를 사용한다.model.compile(loss='sparse_categorical_crossentropy', metrics='accuracy')
※ sparse_categorical_crossentropy
Categorical Crossentropy는, Keras에서 Cross-Entropy Function(다중 분류 손실 함수)을 부르는 이름이다. 또한 이름 앞에 붙은 Sparse는 One-Hot Encoding을 의미한다.
예를 들어 Fashion MNIST에서 z1의 값을 출력하는 상황을 생각해보자. 활성 함수의 출력 값은 [z1, z2, ... , z10]일 것이므로, z1의 값을 얻으려면 [1, 0, ... , 0] 배열과 원소별 곱셈을 수행해야 할 것이다. 이 때 사용되는 [1, 0, ... , 0] 배열을 만드는 작업을 One-Hot Encoding이라 하는데, 배열에 유효한 값(1)이 희소하다는 의미로 Sparse라는 단어가 사용된 것이다.
원래대로라면, 각 타깃 데이터마다 One-Hot Encoding을 적용해주어야 하지만, Sparse Categorical Crossentropy를 사용함으로써, Fashion MNIST의 [1, 2, ... , 10]과 같은 정수형 타깃 값을 그대로 사용할 수 있게 되는 것이다.
⑤ Keras 모델을 훈련할 때에도 동일하게 fit()
메서드를 사용한다.
model.fit(train_scaled, train_target, epochs=5)
⑥ 이제 검증 Set을 이용해 완성된 모델의 성능을 평가해보자.
evaluate()
이다.model.evaluate(val_scaled, val_target)
동작 방식은 SGD Logistic Regression과 크게 다르지 않지만, 성능은 조금 더 향상되었다. 사실 Logistic Regression 모델은 기본적으로 특성의 개수가 많아질 경우, 성능이 저하될 수 있다는 단점이 있다. 그러나, ANN은 특성 간의 복잡한 패턴을 학습하기에 유리하도록 설계되었기 때문에, 오히려 특성 데이터가 많아질수록 더 강력한 성능을 지니게 된다.