MNIST 데이터셋 분류


1. MNIST

MNIST는 고등학생과 미국 인구조사국 직원들이 손으로 쓴 70,000개의 작은 숫자 이미지를 모은 데이터셋입니다.
각 이미지에는 어떤 숫자를 나타내는지 레이블 되어 있습니다. 이 데이터셋은 학습용으로 아주 많이 사용됩니다.

사이킷런에서 제공하는 여러 헬퍼 함수를 사용해 MNIST 데이터셋을 내려받을 수 있습니다.

Code

from sklearn.datasets import fetch_mldata
X, y = mnist["data"], mnist["target"]
print(X.shape)
print(y.shape)

Output

이미지가 70,000개가 있고 각 이미지에는 784개의 특성이 있습니다. 이미지가 28 × 28 픽셀이기 때문입니다.
개개의 특성은 단순히 0(흰색) 부터 255 (검은색) 까지의 픽셀 강도를 나타냅니다.



데이터셋에서 이미지 하나를 확인해보도록 하겠습니다. 샘플의 특성 벡터를 추출해서 28 × 28 배열로 크기를 바꾸고 맷플롯립의 imshow() 함수를 활용합니다.

Code

import matplotlib
import matplotlib.pyplot as plt

some_digit = X[36000]
some_digit_image = some_digit.reshape(28, 28)

plt.imshow(some_digit_image, cmap=matplotlib.cm.binary,
interpolation="nearest")
plt.axis("off") plt.show() print(y[36000])

Output


>> 5.0

그림 상으로 숫자 '5' 처럼 보이는데, 실제 레이블을 확인해보니 5.0으로 나타납니다.



데이터를 분석하기 전 테스트 세트를 만들고 시작하도록 하겠습니다. MNIST 데이터셋의 경우 이미 훈련 세트(앞쪽 60,000개)와 테스트 세트(뒤쪽 10,000)로 나누어져 있습니다.

Code

import numpy as np
X_train, X_test, y_train, y_test = X[:60000], X[60000:], y[:60000], y[60000:]
shuffle_index = np.random.permutation(60000)
X_train, y_train = X_train[shuffle_index], y_train[shuffle_index]

훈련 세트를 섞어 모든 교차 검증 폴드가 비슷해지도록 만들었습니다. 특정 학습 알고리즘은 훈련 샘플의 순서에 민감하게 반응해서 많은 비슷한 샘플이 연이어 나타나면 성능이 나빠집니다. 데이터셋을 섞으면 이러한 문제를 방지할 수 있습니다.



2. 이진 데이터 훈련

문제를 단순화해서 하나의 숫자만 식별해보도록 하겠습니다. 이 감지기는 '5'와 '5가 아님' 두개의 클래스를 구분할 수 있는 이진 분류기(Binary Classifier) 의 예입니다.

Code

from sklearn.linear_model import SGDClassifier

y_train_5 = (y_train == 5)
y_test_5 = (y_test == 5)

sgd_clf = SGDClassifier(max_iter=5, random_state=42)
sgd_clf.fit(X_train, y_train_5)

print(sgd_clf.predict([some_digit]))

Output

>> [True]

분류기가 해당 이미지가 5를 나타낸다고 추측했습니다.



3. 성능 측정

3.1 교차 검증을 사용한 정확도 측정

교차 검증은 모델을 평가하는 좋은 방법 중 하나입니다. 사이킷런에서 교차 검증 함수를 제공하지만, 교차 검증 과정을 더 많이 제어해야 하는 경우도 있습니다.
이때는 교차 검증 기능을 직접 구현하면 됩니다. 아래 코드는 사이킷런의 cross_val_score() 함수와 거의 같은 작업을 수행하고 동일한 결과를 출력합니다.

StratifiedKFold는 클래스별 비율이 유지되도록 폴드를 만들기 위해 계층적 샘플링을 수행합니다. 매 반복에서 분류기 객체를 복제하여 훈련 폴드로 훈련시키고 테스트 폴드로 예측을 만듭니다. 이후 올바른 예측 수를 세어 정확한 예측의 비율을 출력합니다.

Code

from sklearn.linear_model import SGDClassifier
from sklearn.model_selection import StratifiedKFold
skfolds = StratifiedKFold(n_splits=3, random_state=42)

for train_index, test_index in skfolds.split(X_train, y_train_5):
clone_clf = clone(sgd_clf)
X_train_folds = X_train[train_index]
y_train_folds = y_train_5[train_index]
X_test_fold = X_train[test_index]
y_test_fold = y_train_5[test_index]

clone_clf.fit(X_train_folds, y_train_folds)
y_pred = clone_clf.predict(X_test_fold)
n_correct = sum(y_pred == y_test_fold)
print(n_correct / len(y_pred))

Output



사이킷런의 cross_val_score() 함수를 사용하여 폴드가 3개인 K-겹 교차 검증을 사용해 SGDClassifier 모델을 평가해보겠습니다. K-겹 교차 검증은 훈련 세트를 K개의 폴드로 나누고, 각 폴드에 대해 예측을 만들고 평가하기 위해 나머지 폴드로 훈련시킨 모델을 사용합니다.

Code

from sklearn.model_selection import cross_val_score

print(cross_val_score(sgd_clf, X_train, y_train_5, cv=3, scoring="accuracy"))

Output

모든 교차 검증 폴드에 대해 정확도(Accuracy)가 95% 이상임을 확인할 수 있습니다.



그렇다면, 모든 이미지를 '5가 아님' 클래스로 분류하는 더미 분류기를 만들어 성능을 비교해보도록 하겠습니다.

Code

from sklearn.base import BaseEstimator
class Never5Classifier(BaseEstimator):
def fit(self, X, y=None):
pass

def predict(self, X):
return np.zeros((len(X), 1), dtype=bool)


never_5_clf = Never5Classifier()
print(cross_val_score(never_5_clf, X_train, y_train_5, cv=3, scoring="accuracy"))

Output

정확도가 90% 이상 나오는 것을 확인할 수 있습니다만, 숫자 '5'를 분류할 때보다 성능이 떨어지는 것을 확인할 수 있었습니다.
이 예제는 정확도를 분류기의 성능 지표로 선호하지 않는 이유를 보여줍니다. 특히 불균형한 데이터셋을 다룰 때 더욱 그렇습니다.



References

  • 오렐리앙 제롱, '핸즈온 머신러닝', 한빛미디어, 2018


+ Recent posts