핸즈온머신러닝&딥러닝

MNIST 활용, 분류

threegopark 2021. 5. 18. 13:53
728x90

1. mnist 데이터 불러오기

 

from sklearn.datasets import fetch_openml
import numpy as np

mnist = fetch_openml('mnist_784', version=1)
mnist.keys()

  • mnist 데이터는 dictionary 구조로 이루어져있다.
  • DESCR : 데이터셋 설명
  • data : 샘플이 하나의 행, 특성이 하나의 열
  • target : 레이블 배열

 

X, y = mnist["data"], mnist["target"]

X.shape

y.shape

  • 70,000 개의 이미지
  • 각 이미지에는 784개의 특성 (28 * 28 = 784 픽셀)
  • 각 픽셀은 0 ~ 255까지의 픽셀 강도를 나타냄

 

import matplotlib as mpl
import matplotlib.pyplot as plt

#첫 번째 데이터는 숫자 5임
some_digit = X[0]
some_digit_image = some_digit.reshape(28,28)

plt.imshow(some_digit_image, cmap="binary")
plt.axis("off")
plt.show()

 

#numpy.uint8 object type
y[0]

문자열 --> 숫자로 변경 (대부분의 알고리즘은 숫자를 기대하므로)

y = y.astype(np.uint8)

 

테스트와 훈련 데이터로 분리한다. (70,000개이므로 훈련 6 : 테스트 1 정도로 분리한다)

X_train, X_test, Y_train, Y_test = X[:60000], X[60000:], y[:60000], y[60000:]
  • mnist 데이터는 이미 섞여있어서 모든 교차 검증 폴드를 비슷하게 만든다.
  • 만약 섞이지 않은 데이터를 사용한다면 꼭 섞어야함. 어떤 알고리즘은 훈련 샘플의 순서에 매우 민감하기때문에 동일한 샘플이 연달아서 나오게 된다면 성능이 저하될 수 있다.

 

2. 이진 분류기 훈련

 

간단히 숫자 5만 식별하는 이진 분류기 생성을 위한 타깃 벡터 생성 (5)

#5는 True, 나머지는 False
y_train_5 = (y_train == 5)
y_test_5 = (y_test == 5)

 

SGD (확률적 경사 하강법) 분류기 (매우 큰 데이터셋 효율적으로 처리 + 온라인 학습에 적절)

from sklearn.linear_model import SGDClassifier

sgd_clf = SGDClassifier(random_state = 42)
sgd_clf.fit(X_train, y_train_5)
  • SGDClassifier 는 훈련하는 데 무작위성을 사용 (확률적)
  • 결과 재현을 위해 random_state 난수 적용
#some_digit는 X[0] = 5 임
sgd_clf.predict([some_digit])

5라고 정확하게 예측함

 

 

3. 성능 측정

 

3.1 교차 검증

  • cross_val_score() 함수 혹은 사이킷런에서 직접구현 가능
from sklearn.model_selection import StratifiedKFold
from sklearn.base import clone

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))
  • StratifiedKFold : 클래스별 비율이 유지되도록 폴드를 만들기 위해 계층적 샘플링 수행
  • 매 반복에서 분류기 객체를 복제하여 훈련 폴드로 훈련, 테스트  폴드로 예측
  • 올바른 예측의 수를 세어 정확한 예측의 비율 출력

(+설명 및 코드)

a_a = np.array([[1, 2, 3, 4], 
                [3, 4, 5, 6], 
                [1, 2, 3, 4], 
                [3, 2, 4, 1], 
                [1, 3, 4, 2], 
                [2, 1, 4, 3]])
b_b = np.array([1,1,2,2,3,3])

skf = StratifiedKFold(n_splits=2)

for train_index, test_index in skf.split(a_a, b_b):
    print("TRAIN:", train_index, "TEST:", test_index)
    X_train, X_test = a_a[train_index], a_a[test_index]
    y_train, y_test = b_b[train_index], b_b[test_index]
    print("train_data")
    print(X_train, '\n', y_train)
    print("test_data")
    print(X_test, '\n', y_test)

클래스별로 샘플의 비율을 동일하게 하기 위하여 총 2번 (n_splits = 2) 나눴고 그 결과 총 2가지의 train & test index가 생성되었다. (   1. Train : [1, 3, 5] & Test : [0, 2, 4],  2. Train : [0, 2, 4] & Test : [1, 3, 5]  )

 

1의 split을 살펴보자.

a_a의 1, 3, 5번째 인덱스에 해당하는 [3,4,5,6] [3,2,4,1] [2,1,4,3] 과 b_b의 0, 2, 4번째 인덱스에 해당하는 [1, 2, 3] 으로 먼저 분류되었고 마찬가지로 2의 split도 진행되어 결국 클래스별 비율이 동일하게 샘플을 추출한 것을 알 수 있다.

 

여기서 주의할 점은 n_splits = 변수 하이퍼파라미터를 적절히 잘 지정해야하는데, 만약 각 배열의 크기를 고려하지 않고 지정하게 된다면 오류가 뜬다.

( 6 * 4 , 1 * 6 이므로 6의 공약수로 지정해야한다. n_splits=2 이라면 b_b에 동일한 숫자가 2개씩 존재해야 바람직하다. 즉, 2,2 3,3 4,4 --- 이런식)

 

 

위와 같은 방법으로 사이킷런에서는 cross_val_score() 함수를 지원한다. 훈련 세트를 3개 (cv=3)으로 나누는 동일한 교차 검증 세트를 만들어보면

from sklearn.model_selection import cross_val_score

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

각 폴드 별 정확도가 95% 이상임을 알 수 있다.

 

 

 

모든 이미지를 5 아님 클래스로 분류하는 더미 분류기

from sklearn.base import BaseEstimator
#해당 클래스를 상속하면 *args or **kwargs 가 없어도 자동으로 입력받을 수 있음

class Never5Classifier(BaseEstimator):
    def fit(self, X, y=None):
    	return self
    def predict(self, X):
    	return np.zeros((len(X),1), dtype=bool)
        
        
 never_5_clf = Never5Classifier()
 cross_val_score(never_5_clf, X_train, y_train_5, cv=3, scoring="accuracy")

 

  • Never5Classifier 클래스에 의해 입력되는 X_train내부의 데이터가 모두 [False] 로 변환된다.

  • 앞서 만들어놓은 y_train_5와 변환된 X_train을 비교 (cross_val_score) 하여 5가 아닌 값(y_train_5에서 false인 값)의 확률을 계산한다.

앞서 만든 y_train_5

 

 

3.2 오차 행렬

 

  • 분류기의 성능 평가
  • 클래스 A의 샘플이 클래스 B로 분류된 횟수를 세는 것
  • 예를 들어, 숫자 5의 이미지를 3으로 잘못 분류한 횟수를 알고 싶다면 오차 행렬의 5행 3열을 확인
  • 오차 행렬을 위해 실제 타깃과 비교할 수 있도록 먼저 예측값을 만들어야 함
from sklearn.model_selection import cross_val_predict

y_train_pred = cross_val_predict(sgd_clf, X_train, y_train_5, cv=3)
  • cross_val_predict() : cross_val_score()처럼 k-겹 교차 검증 수행 but 각 테스트 폴드에서 얻은 예측 반환 (모든 샘플에 대한 예측)
  • confusion_matrix() 로 오차 행렬 생성
from sklearn.metrics import confusion_matrix
confusion_matrix(y_train_5, y_train_pred)

  • 행 : 실제 클래스
  • 열 : 예측 클래스
  • 1행1열 : TN(True Negative), 1행2열 : FP(False Positive) ...
from sklearn.metrics import precision_score, recall_score
from sklearn.metrics import f1_score

precision_score(y_train_5, y_train_pred)
recall_score(y_train_5, y_train_pred)
f1_score(y_train_5, y_train_pred)

(재현율 & 정밀도 & f1 점수)