ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • chatper3. 분류
    공부/Hands-On Machine Learning 2023. 1. 6. 13:03
    728x90

    3.1 MNIST

    70,000의 작은 숫자 이미지를 모은 MNIST 데이터셋

    어떤 숫자를 나타내는지 레이블되어있음

    사이킷런에서 제공하는 여러 행의 함수를 사용해 잘 알려진 데이터셋을 내려받을 수 있음

    from sklearn.datasets import fetch_openml
    mnist=fetch_openml('mnist_784',version=1,as_frame=False)
    #사이킷런 0.22버전에서 as_frame 매개변수 추가
    #as_frame=True로 설정하면 판다스 데이터프레임을 반환
    mnist.keys()

    #데이터셋을 설명하는 DESCR
    #샘플이 하나의 행,특성이 하나의 열로 구성된 배열을 가진 data
    #레이블 배열을 담은 target

    X,y=mnist['data'],mnist['target']
    print(X.shape,y.shape)

    ->이미지가 70,000개 있고 각 이미지에는 784개의 특성이 있음(이미지가 28*28 픽셀이라서)

    특성은 단순히 0(흰색)~255(검은색)까지 픽셀 강도를 나타냄

    %matplotlib inline
    import matplotlib as mpl
    import matplotlib.pyplot as plt
    
    some_digit = X[0]
    some_digit_image=some_digit.reshape(28,28)
    
    plt.imshow(some_digit_image, cmap='binary')
    plt.axis('off')
    plt.show()

    보기엔 5같아보이는데 label도 5일 것인가?!?!

    y[0]

    5였따

    import numpy as np
    #레이블은 앞서 본 '5'처럼 문자열로 되어있음
    #대부분 머신러닝 알고리즘은 숫자를 기대하므로 y를 정수로 변환
    y=y.astype(np.uint8)
    #데이터 조사 전 훈련 데이터, 테스트 데이터로 나눔
    X_train,X_test, y_train, y_test=X[:60000],X[60000:],y[:60000],y[60000:]

    3.2 이진 분류기 훈련

    문제를 단순화하여 숫자5만 식별해보겠음

    ->'5' 와 '5가 아님' 두 개의 클래스를 구분할 수 있는 이진 분류기(binary classifier)의 한 예

    분류 작업을 위해 타깃 벡터를 만듦

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

    사이킷런의 SGDClassifier클래스를 사용해 확률적 경사 하강법(SGD)분류기로 시작해보기

    해당 분류기는 매우 큰 데이터셋을 효율적으로 처리하는 장점

    SGD가 한번에 하나씩 훈련샘플을 독립적으로 처리하기 때문

    from sklearn.linear_model import SGDClassifier
    
    #SGDClassifier은 훈련하는 데 무작위성을 사용함
    #결과를 재현하고 싶다면 random_state 매개변수를 지정해줘야함
    sgd_clf=SGDClassifier(random_state=42)
    sgd_clf.fit(X_train,y_train)
    sgd_clf.predict([some_digit])

    책과는 다르게 내 모델은 some_digit(아까 보았던 5)을 3으로 예측하였다->근데 왜 False가 안 나오고 저렇게 나온담?

    3.3 성능측정

    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, shuffle=True)
    
    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))

    from sklearn.model_selection import cross_val_score
    cross_val_score(sgd_clf,X_train,y_train_5,cv=3,scoring='accuracy')

    모든 교차 검증 폴드에 대해 정확도가 90%이상

     

    모든 이미지를 '5아님'클래스로 분류하는 더미 분류기를 만들어 비교

    from sklearn.base import BaseEstimator
    
    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')

    정확도 90%이상!

    이미지의 10%가 5이기때문에 무조건 5아님으로 예측하면 정확히 맞출 확률이 90%

     

    앞의 예제들은 정확도를 분류기의 성능 측정 지표로 선호하지 않는 이유를 보여줌

    특히 불균형한 데이터셋을 다룰 때(어떤 클래스가 다른 클래스보다 월등히 많은 경우) 더욱 그럼

    3.3.2 오차 행렬

    분류기의성능을 평가하는 더 좋은 방법은 오차 행렬(confusion matrix)을 조사하는 것

    오차행렬을 만들려면 실제 타깃과 비교할 수 있도록 먼저 예측값을 만들어야함

    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() 함수는 k-겹 교차 검증을 수행하지만 평가점수를 반환하지 않고 각 테스트 폴드에서 얻은 예측 반환

    ->훈련 세트의 모든 샘플에 대해 깨끗한 예측을 얻게됨

     

    이제 confusion_matrix()함수를 사용해 오차 행렬으 ㄹ만들 준비가 되었음

    타깃 클래스(y_train_5)와 예측클래스(y_train_pred)를 넣고 호출

    from sklearn.metrics import confusion_matrix
    confusion_matrix(y_train_5, y_train_pred)

    53892 : 실제로 5가 아니고 실험 결과도 5가 아닌 경우

    687: 실제로 5가 아닌데 실험결과가 5인 경우

    1891 : 실제로 5인데 실험결과가 5가 아닌 경우

    3530 : 실제로 5인데 실험결과도 5인 경우

    3.3.3 정밀도와 재현율

    from sklearn.metrics import precision_score, recall_score
    
    print('정밀도 :',precision_score(y_train_5, y_train_pred))
    print('재현율 :',recall_score(y_train_5, y_train_pred))

    5로 판별된 이미지 중 84%가 정확, 전체 숫자 5에서 65%만 감지

    F1점수 = 정밀도와 재현율의 조화 평균(hamonic  mean)

    from sklearn.metrics import f1_score
    f1_score(y_train_5, y_train_pred)

    정밀도와 재현율이 비슷한 분류기에서는 F1점수가 높음, 그렇다고 항상 좋은 건 아님

    예시1)

    어린이에게 안전한 동영상을 걸러내는 분류기를 훈련시킨다고 가정

    재현율은 높으나 나쁜 동영상이 몇개 노출되는 것보다 좋은 동영상이 많이 제외되더라도(낮은 재현율) 안전한 것들만 노출시키는(높은 정밀도) 분류기를 선호할 것

    예시2)

    감시 카메라를 통해 좀도둑을 잡아내는 분류기를 훈련시킨다고 가정

    분류기의 재현율이 99%라면 정확도가 30%만 되도 괜찮을 것

    경비원이 잘못된 호출을 받더라도 거의 모든 좀도둑을 잡을 것

     

    하지만 정밀도, 재현율 둘 다를 얻을 수 없음->정밀도/재현율 트레이드오프 라고 함

    3.3.4 정밀도/재현율 트레이드오프

    SGDClassifier가 분류를 어떻게 결정하는지 살펴보며 트레이드오프를 이해해보겠움

    이 분류기는 결정 함수(decision function)를 사용하여 각 샘플의 점수를 계산

    점수가 임곗값보다 크면 샘플을 양성 클래스에 할당 그렇지 않으면 음성 클래스에 할당

     

    사이킷런에서 임곗값을 직접 지정할 수는 없지만 사용한 점수는 확인가능

    분류기의 predict()메서드 대신 decision_function()메서드를 호출하면 각 샘플의 점수를 얻을 수 있음

    이 점수를 기반으로 원하는 임계값을 정해 예측가능

    y_scores=sgd_clf.decision_function([some_digit])
    y_scores

    파란부분이 임곗값

    threshold=0
    y_some_digit_pred=y_scores>threshold
    y_some_digit_pred

    threshold=1824
    y_some_digit_pred=y_scores>threshold
    y_some_digit_pred

    임곗값을 높이면 재현율이 줄어듦을 볼 수 있음

     

    적절한 임곗값을 어떻게 정할 수 있을까

    이를 위해서는 먼저 cross_val_predict()함수를 이용해 훈련 세트에 있는 모든 샘플의 점수를 구해야함

    이번에는 예측 결과가 아니라 결정 점수를 반환받도록 지정

    y_scores=cross_val_predict(sgd_clf, X_train, y_train_5, cv=3, method="decision_function")
    from sklearn.metrics import precision_recall_curve
    precisions, recalls, thresholds=precision_recall_curve(y_train_5, y_scores)
    def plot_precision_recall_vs_threshold(precisions, recalls, thresholds):
      plt.plot(thresholds, precisions[:-1],'b--',label='precision')
      plt.plot(thresholds, recalls[:-1],'g-',label='recall')
      plt.legend(loc="center right", fontsize=10)
      plt.grid(True)
    
    recall_90_precision = recalls[np.argmax(precisions>=0.9)]
    threshold_90_precision=thresholds[np.argmax(precisions>=0.9)]
    
    plot_precision_recall_vs_threshold(precisions, recalls, thresholds)
    plt.plot([threshold_90_precision, threshold_90_precision],[0,0.9],'r:')
    plt.plot([-50000, threshold_90_precision], [0.9,0.9],'r:')
    plt.plot([-50000, threshold_90_precision],[recall_90_precision, recall_90_precision],'r:')
    plt.plot([threshold_90_precision],[0.9],'ro')
    plt.plot([threshold_90_precision],[recall_90_precision],'ro')
    plt.show()

    좋은 정밀도/재현율 트레이드오프를 선택하는 다른 방법은 재현율에 대한 정밀도 곡선을 그리는 것

    def plot_precision_vs_recall(precisions, recalls):
      plt.plot(recalls,precisions, 'b-')
      plt.xlabel('Recall', fontsize=10)
      plt.ylabel('Precision', fontsize=10)
      plt.grid(True)
    
    plot_precision_vs_recall(precisions, recalls)
    recall_90_precision = recalls[np.argmax(precisions>=0.9)]
    plt.plot([recall_90_precision, recall_90_precision],[0,0.9],'r:')
    plt.show()

    재현율 70~80%근처에서 정밀도가 급격하게 줄어듦

    이 하강점 직전을 정밀도/재현율 트레이드오프로 선택하는 것이 좋음

     

    정밀도 90%를 달성하는 것이 목표라고 가정

    y_train_pred_90=y_scores>=threshold_90_precision
    
    print('정밀도:',precision_score(y_train_5,y_train_pred_90))
    print('재현율:',recall_score(y_train_5, y_train_pred_90))

    3.3.5 ROC곡선

    수신기 조작 특성(receiver operating characteristic,ROC)곡선도 이진 분류에서 널리 사용하는 도구

    거짓양성비율(FPR)에 대한 진짜양성비율(TPR,재현율)의 곡선

    거짓양성비율(FPR)은 양성으로 잘못 분류된 음성 샘플의 비율

    ->1-음성으로 정확하게 분류한 음성샘플의 비율 진짜 음성비율(TNR)=xmrdleh

    =>ROC곡선은 민감도(재현율)에 대한 1-특이도 그래프

    roc_curve()함수를 이용해 여러 임곗값에서 TPR과 FPR을 계산

    def plot_roc_curve(fpr, tpr, label=None):
      plt.plot(fpr,tpr, linewidth=2, label=label)
      plt.legend(loc='center right',fontsize=10)
      plt.grid(True)
      plt.axis([0,1,0,1])
      plt.plot([0,1,0,1],'k--')
      
    plot_roc_curve(fpr,tpr)
    plt.show()

    재현율이 높을수록 분류기가 만드는 거짓 양성이 늘어남

    점선은 완전한 랜덤 분류기의 ROC곡선

    좋은 분류기는 이 점선에서 최대한 멀리 떨어져있어야함(왼쪽 위 모서리)

    곡선 아래의 면적(area under rhe curve,AUC)을 측정하면 분류기들을 비교할 수 있음

    완벽한 분류기는 ROC, AUC가 1이고 완전한 랜덤 분류기는 0.5

    from sklearn.metrics import roc_auc_score
    roc_auc_score(y_train_5,y_scores)

    더보기

    ROC곡선이 정밀도/재현율(PR) 곡선과 비슷해서 어디에 사용해야할 지 궁금

    일반적인 법칙은 양성 클래스가 드물거나 거짓 음성보다 거짓 양성이 더 중요할때 PR곡선을 사용하고

    그렇지 않으면 ROC곡선을 사용

    예를 들어 조금 전의 ROC곡선을 보면 매우 좋은 분류기라고 생각할 수 있음

    하지만 이는 음성(5아님)에 비해 양성(5)이 매우 적기 때문

    이와 다르게 PR곡선은 분류기의 성능 개선 여지가 얼마나 되는지(오른쪽 위 모서리에 가까워질 수 있는지)잘 보여줌

    RandomForestClassifier을 훈련시켜 SGDClassifier의 ROC곡선과 ROC AUC점수를 비교

    먼저 훈련 세트의 샘플에 대한 점수를 얻어야함

    하지만 작동 방식의 차이 때문에 RandomForestClassfier에는 decision_function()이 없음

    대신 predict_proba()메서드가 있음

    사이킷런 분류기는 일반적으로 이 두 매서드 중 하나 또는 둘 모두를 가짐

    predict_proba()메서드는 샘플이 행, 클래스가 열이고 샘플이 주어진 클래스에 속할 확률을 담은 배열을 반환

    from sklearn.ensemble import RandomForestClassifier
    
    forest_clf=RandomForestClassifier(random_state=42)
    y_probas_forest=cross_val_predict(forest_clf, X_train, y_train_5, cv=3, method='predict_proba')
    y_scores_forest=y_probas_forest[:,1]  #양성 클래스에 대한 확률을 점수로 사용
    fpr_forest, tpr_forest, thresholds_forest=roc_curve(y_train_5, y_scores_forest)
    plt.plot(fpr, tpr, "b:",label="SGD")
    plot_roc_curve(fpr_forest, tpr_forest, "RandomForest")
    plt.legend(loc='lower right')
    plt.show()

    이미지에서 볼 수 있듯이 RandomForestClassifier의 ROC곡선이 왼쪽 위 모서리에 더 가까워 SGDClassifier보다 좋아보임

    roc_auc_score(y_train_5, y_scores_forest)

    정밀도와 재현율 점수를 계산하면 99%의 정밀도가 나옴

    3.4 다중분류

    이진 분류가 두 개의 클래스를 구별하는 반면 다중 분류기(multiclass classifier)는 둘 이상의 클래스를 구별할 수 있음

    일부 알고리즘은 여러 개의 클래스를 직접 처리할 수 있는 반면(SGD분류기, 랜덤포레스트 분류기, 나이브베이즈 분류 등),

    다른 알고릴즘은 이진 분류만 가능(로지스틱 회귀, 서포트 벡터 머신 분류기 등)

    하지만 이진 분류기를 여러 개 사용해 다중 클래스를 분류하는 기법도 많음

    기법1)

    특정 숫자 하나만 구분하는 숫자별 이진 분류기 10개를 훈련시켜 클래스가 10개인 숫자 이미지 분류 시스템을 만들 수 있음 이미지를 분류할 때 각 분류기의 결정 점수 중에서 가장 높은 것을 클래스로 선택하면 됨

    ->OvR(one-versus-the-rest)전략이라고 함((OvA=one-versus-all)이라고도 함)

    기법2)

    0과1구별, 0과2구별, 1과2구별 등과 같이 각 숫자의 조합마다 이진 분류기를 훈련시키는 것

    ->OvO(one-versus-one)전략이라고 함

    클래스가 N개라면 분류기는 N(N-1)/2개가 필요

    이미지 하나를 분류하려면 N(N-1)/2개 분류기를 모두 통과시켜서 가장 많이 양성으로 분류된 클래스 선택

    장점은 각 분류기의 훈련에 전체 훈련세트 중 구별할 두 클래스에 해당하는 샘플만 필요하다는 것

     

    일부 알고리즘(서포트벡터머신같은)은 훈련 세트의 크기에 민감해서 큰 훈련세트에서 몇 개의 분류기를 훈련시키는 것보다 작은 훈련 세트에서 많은 분류기를 훈련시키는 쪽이 빠르므로 OvO를 선호

    하지만 대부분의 이진 분류 알고리즘은 OvR을 선호

     

    다중 클래스 분류 작업에 이진 분류 알고리즘을 선택하면 사이킷런이 알고리즘에 따라 자동으로 OvR/OvO실행

    sklearn.svm.SVC클래스를 사용해 서포트 벡터 머신 분류기를 테스트

    from sklearn.svm import SVC
    svm_clf=SVC()
    svm_clf.fit(X_train,y_train)
    svm_clf.predict([some_digit])

    해당 코드는 5를 구별한 타깃 클래스(y_train_5)대신 0~9까지의 원래 타깃 클래스(y_train)을 사용해 SVC를 훈련시킨 후 예측 하나를 만듦

    내부에선 사이킷런이 OvO전략을 사용해 45개의 이진 분류기를 훈련시키고 각각의 결정 점수를 얻어 점수가 가장 높은 클래스를 선택

    decision_function()메서드를 호출하면 (1개가 아니라) 샘플당 10개의 점수를 반환

    some_digit_scores=svm_clf.decision_function([some_digit])
    some_digit_scores

    파란 밑줄그은 곳이 클래스가 5에 해당하는 값

    np.argmax(some_digit_scores)

    svm_clf.classes_

    svm_clf.classes_[5]

    사이킷런에서 OvO/OvR을 사용하도록 강제하려면 OneVsOneClassifier/OneVsRestClassifier을 사용

    간단하게 이진 분류기 인스턴스를 만들어 객체를 생성할 때 전달

    아래 코드는 SVC기반으로 OvR전략을 사용하는 다중 분류기를 만듦

    from sklearn.multiclass import OneVsRestClassifier
    ovr_clf=OneVsRestClassifier(SVC())
    ovr_clf.fit(X_train,y_train)
    ovr_clf.predict([some_digit])

    len(ovr_clf.estimators_)

    SGDClassifier(또는 RandomForestClassifier)를 훈련시키는 것도 간단

    sgd_clf.fit(X_train,y_train)
    sgd_clf.predict([some_digit])

    이 경우 SGD분류기는 직접 샘플을 다중 클래스로 분류할 수 있기 때문에 별도로 사이킷런의 OvR/OvO를 적용할 필요가 없음

    decision_function()메서드는 클래스마다 하나의 값을 반환

    sgd_clf.decision_function([some_digit])

    파란 밑줄이 그어진 값이 가장 큼->3으로 분류

    클래스 3에 대한 강한 확신을 보이고 있음

    대부분의 점수가 큰 음수인 반면 클래스 3의 점수는 1823.7임

    -1385.8의 점수를 얻은 클래스 5가 다음으로 의심

    이제 분류기를 평가해볼 시간

    분류기 평가에는 일반적으로 교차 검증을 사용

    corss_val_score()함수를 사용해 SGDClassifier의 정확도를 평가

    cross_val_score(sgd_clf, X_train, y_train,cv=3,scoring='accuracy')

    모든 테스트 폴드에서 85%이상을 얻었음

    랜덤 분류기를 사용했다면 10%정도의 정확도를 얻었을 것이므로 이 점수가 아주 나쁘지는 않지만 성능을 더 높일 여지가 있음

    예를 들어 입력의 스케일을 조정하면 정확도를 89%이상 높일 수 있음

    from sklearn.preprocessing import StandardScaler
    scaler=StandardScaler()
    X_train_scaled=scaler.fit_transform(X_train.astype(np.float64))
    cross_val_score(sgd_clf,X_train_scaled,y_train,cv=3,scoring='accuracy')

    3.5 에러분석

    오차 행렬을 분석하기 위해 corss_val_predict()함수를 사용해 예측을 만들고 이전 처럼 confusion_matrix()함수 호출

    y_train_pred=cross_val_predict(sgd_clf,X_train_scaled,y_train,cv=3)
    conf_mx=confusion_matrix(y_train, y_train_pred)
    conf_mx

    plt.matshow(conf_mx, cmap=plt.cm.gray)
    plt.show()

    숫자로 나와서 보기 불편하니 위의 코드로 이미지화

    이 오차 행렬은 대부분의 이미지가 올바르게 분류되었음을 나타내는 주대각선에 있으므로 매우 좋아보임

    숫자 5는 다른 숫자보다 어두워보이는 이뉴는 데이터셋에 숫자 5의 이미지가 적거나 분류기가 숫자 5를 다른 숫자만큼 잘 분류하지 못한다는 뜻

    글래프의 에러부분에 초점

    먼저 오차행렬의 각 값을 대응되는 클래스의 이미지 개수로 나누어 에러 비율을 비교

    row_sums=conf_mx.sum(axis=1,keepdims=True)
    norm_conf_mx=conf_mx/row_sums

    주대각선만 0으로 채워서 그래프를 그림

    np.fill_diagonal(norm_conf_mx,0)
    plt.matshow(norm_conf_mx, cmap=plt.cm.gray)
    plt.show()

    여기서 분류기가 만든 에러를 확실하게 볼 수 있음

    행은 실제 클래스를 나타내고 열은 예측한 클래스를 나타냄

    클래스 8의 열이 상당히 밝으므로 많은 이미지가 8로 잘못 분류되었음을 암시

    하지만 클래스 8의 행은 그리 나쁘지 않으므로 실제 8이 적절히 8로 분류되었다는 것을 말해줌

    오차행렬은 반드시 대칭인 것은 아님

    3과 5가 서로 많이 혼동하고 있는 것도 볼 수 있음

    위 그래프를 살펴보면 8로 잘못 분류되는 것을 줄이도록 개선할 필요가 있음

    예를 들어 8처럼 보이는 숫자의 훈련 데이터를 더 많이 모아서 실제 8과 구분하도록 분류기를 학습시킬 수 있음

    또는 분류기에 도움 될 만한 특성을 더 찾아볼 수 있음

     

    개개의 에러를 분석해보면 분류기가 무슨일을 하고, 왜 잘못되었는지에 대한 통찰을 얻을 수 있지만 더 어렵고 오래걸림 

    예를 들어 3와 5의 샘플을 그려보겠음

    def plot_digits(instances, images_per_row=10, **options):
      size=28
      images_per_row=min(len(instances),images_per_row)
      images=[instance.reshape(size,size) for instance in instances]
      n_rows=(len(instances)-1)//images_per_row+1
      row_images=[]
      n_empty=n_rows*images_per_row-len(instances)
      images.append(np.zeros((size,size*n_empty)))
      for row in range(n_rows):
        rimages=images[row*images_per_row : (row+1)*images_per_row]
        row_images.append(np.concatenate(rimages,axis=1))
      image=np.concatenate(row_images,axis=0)
      plt.imshow(image,cmap=mpl.cm.binary,**options)
      plt.axis('off')
    cl_a,cl_b=3,5
    X_aa=X_train[(y_train==cl_a)&(y_train_pred==cl_a)]
    X_ab=X_train[(y_train==cl_a)&(y_train_pred==cl_b)]
    X_ba=X_train[(y_train==cl_b)&(y_train_pred==cl_a)]
    X_bb=X_train[(y_train==cl_b)&(y_train_pred==cl_b)]
    
    plt.figure(figsize=(8,8))
    plt.subplot(221); plot_digits(X_aa[:25],images_per_row=5)
    plt.subplot(222); plot_digits(X_ab[:25],images_per_row=5)
    plt.subplot(223); plot_digits(X_ba[:25],images_per_row=5)
    plt.subplot(224); plot_digits(X_bb[:25],images_per_row=5)
    plt.show()

    왼쪽의 5*5 블록 두 개는 3으로 분류된 이미지이고 오른쪽 5*5 블록 두개는 5로 분류된 이미지

    분류기가 잘못 분류한 숫자 중 일부는 정말 잘못 쓰여 있어서(왼쪽 아래 블록과 오른쪽 위 블록) 사람도 분류하기 어려울 것

    그러나 대부분의 잘못 분류된 이미지는 확실히 에러인 것 같고 분류기가 실수한 이유를 이해하기 어려움

    원인은 선현 모델인 SGDClassifier를 사용했기 떄문

    선형 분류기는 클래스마다 픽셀에 가중치를 할당하고 새로운 이미지에 대해 단순히 픽셀 강도의 가중치 합을 클래스의 점수로 계산->따라서 3과 5는 몇 개의 픽셀만 다르기 때문에 모델이 쉽게 혼동

    3과5의 주요 차이는 위쪽 선과 아래쪽 호를 이어주는 작은 직선의 위치

    숫자 3을 쓸 때 연결부위가 조금 왼쪽으로 치우치면 분류기가 5로 분류하고 반대도 마찬가지

    다른말로 하면 분류기는 이미지의 위치나 회전방향에 매우 민감

    3과 5의 에러를 줄이는 한 가지 방법은 이미지를 중앙에 위치시키고 회전되어 있지않도록 전처리하는 것

    3.6 다중 레이블 분류

    지금까지는 각 샘플이 하나의 클래스에만 할당

    하지만 분류기가 샘플마다 여러개의 클래스를 출력해야할때도 있음

    얼굴 인식 분류기를 한번 생각해보면 같은 사진에 여러 사람이 등장한다면 인식된 사람마다 하나의 꼬리표를 붙여야함

    분류기가 앨리스,밥,찰리 세 얼굴을 인식하도록 훈련되었다고 가정해봄

    분류기가 앨리스와 찰리가 같이 있는 사진을 본다면 [1,0,1]을 출력해야함

    이 처럼 여러 개의 이진 꼬리표를 출력하는 분류 시스템을 다중 레이블 분류 시스템이라고 함

    학습을 위해 조금 더 간단한 예를 살펴보겠음

    from sklearn.neighbors import KNeighborsClassifier
    
    y_train_large=(y_train>=7)
    y_train_odd=(y_train%2==1)
    y_multilabel=np.c_[y_train_large, y_train_odd]
    
    knn_clf=KNeighborsClassifier()
    knn_clf.fit(X_train, y_multilabel)
    knn_clf.predict([some_digit])

    y_train_large는 큰 값(7,8,9)인지 나타내고 y_train_odd는 홀수 인지 나타냄

    그다음 KNeighborsClassifier인스턴스를 만들고 다중 타깃 배열을 사용하여 훈련

    some_digit는 5이기 때문에 크지않고[False] 홀수[True]임

     

    다중 레이블 분류기를 평가하는 방법은 많음

    적절한 지표는 프로젝트에 따라 다름

    예를 들어 각 레이블의 F1점수를 구하고 간단하게 평균 점수를 계산

    y_train_knn_pred=cross_val_predict(knn_clf, X_train, y_multilabel,cv=3)
    f1_score(y_multilabel, y_train_knn_pred, average='macro')

    실제로는 아닐 수 있지만 이 코드는 모든 레이블의 가중치가 같다고 가정

    특히 앨리스 사진이 밥이나 찰리 사진보다 훨신 많다면 앨리스 사진에 대한 분류기의 점수에 더 높은 가중치를 둘 것

    간단한 방법은 레이블에 클래스의 지지도(support, 타깃 레이블에 속한 샘플 수)를 가중치 두는 것

    이렇게 하려면 위 코드에서 average='weighted'라고 설정하면 됨

    3.7 다중 출력 분류

    이미지에서 잡음을 제거하는 시스템을 만들어보겠음

    잡음이 많은 숫자 이미지를 입력으로 받고 깨끗한 숫자 이미지를 MNIST 이미지처럼 픽셀의 강도를 담은 배열로 출력

    분류기의 출력이 다중 레이블이고 각 레이블은 값을 여러개 가짐(0~255)

    MNIST 이미지에서 추출한 훈련 세트와 테스트 세트에 넘파이의 randint()함수를 사용하여 픽셀 강도에 잡음을 추가

    타깃 이미지는 원본 이미지가 될 것

    def plot_digit(data):
      image=data.reshape(28,28)
      plt.imshow(image, cmap=mpl.cm.binary)
      plt.axis('off')
    noise=np.random.randint(0,100,(len(X_train),784))
    X_train_mod=X_train+noise
    noise=np.random.randint(0,100,(len(X_test),784))
    X_test_mod=X_test+noise
    y_train_mod=X_train
    y_test_mod=X_test
    some_index=0
    plt.subplot(121);plot_digit(X_test_mod[some_index])
    plt.subplot(122);plot_digit(y_test_mod[some_index])
    plt.show()

    knn_clf.fit(X_train_mod, y_train_mod)
    clean_digit=knn_clf.predict([X_test_mod[some_index]])
    plot_digit(clean_digit)

    타깃과 매우 유사!

     

    댓글

Designed by Tistory.