1. 사용 데이터

 -사이킷런에서 제공하는 MNIST 데이터셋(손글씨 데이터셋)

# 사이킷런에서 기본적으로 제공하는 MNIST 데이터셋 불러오기
from sklearn.datasets import fetch_openml
mnist=fetch_openml('mnist_784',version=1,as_frame=False)
mnist.keys()

### 결과 ###
dict_keys(['data', 'target', 'frame', 'categories', 'feature_names', 'target_names', 'DESCR', 'details', 'url'])

 

 -MNIST 데이터셋 배열 확인

X,y=mnist['data'],mnist['target']
X.shape     # (70000, 784)
y.shape     # (70000,)

# 이미지가 70000개이고 각 이미지는 784개의 특성을 가짐(28*28 픽셀의 이미지이므로 28*28=784개의 특성을 가짐)
# 각 특성은 0(흰색)~255(검은색)까지의 픽셀 강도

 

 -가장 첫번째 데이터 출력해보기

 -가로 28개, 세로 28개의 픽셀로 이루어짐

import matplotlib as mpl
import matplotlib.pyplot as plt
import numpy as np

# 가장 첫번째 데이터 불러와서 28*28의 배열로 재배열
some_digit=X[0]
some_digit_image=some_digit.reshape(28,28)

# imshow()함수를 통해 이미지 확인
plt.imshow(some_digit_image, cmap='binary')
plt.axis('off')
plt.show()

 -이미지 여러개 확인해보기

import matplotlib as mpl

# 그림 그리기 기능 함수
def plot_digit(data):
    image=data.reshape(28,28)
    plt.imshow(image,cmap=mpl.cm.binary,interpolation='nearest')
    plt.axis('off')

def plot_digits(instances, images_per_row, **options):
    size=28
    images_per_row=min(len(instances),images_per_row)
    n_rows=(len(instances)-1) // images_per_row +1

    # 필요하면 그리드의 끝을 채우기 위해 빈 이미지 추가
    n_empty=n_rows*images_per_row-len(instances)
    padded_instances=np.concatenate([instances, np.zeros((n_empty, size*size))],axis=0)

    # 배열의 크기를 바꿔 28*28 이미지를 담은 그리드로 구성
    image_grid=padded_instances.reshape((n_rows, images_per_row, size, size))

    # 축 0(이미지 그리드의 수직축)과 2(이미지의 수직축)를 합치고 축 1과 3(그리드와 이미지의 수평축)을 합침
    # transpose()를 통해 결합하려는 축을 옆으로 이동한 다음 합침
    big_image=image_grid.transpose(0, 2, 1, 3).reshape(n_rows*size,images_per_row*size)

    # 하나의 큰 이미지 출력
    plt.imshow(big_image, cmap=mpl.cm.binary, **options)
    plt.axis('off')

# 이미지 샘플 100개 출력
plt.figure(figsize=(9,9))
example_images=X[:100]
plot_digits(example_images,10)
plt.show()

 -레이블 확인

y[0]

### 결과 ###
'5'

 -레이블이 문자형으로 되어있고 머신러닝 알고리즘은 대부분 숫자형의 레이블을 계산하므로 숫자형으로 변환이 필요

y=y.astype(np.uint8)

# train 데이터와 test 데이터 분리(train 6만개, test 1만개)
X_train,X_test,y_train,y_test=X[:60000],X[60000:],y[:60000],y[60000:]

 

 

 

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)

# 앞에서 살펴봤던 가장 첫번째 데이터를 예측기에 넣어본 결과 True(5)로 예측
sgd_clf.predict([some_digit])

### 결과 ###
array([ True])

 

 

3. 교차 검증을 통한 성능 측정

from sklearn.model_selection import StratifiedKFold
from sklearn.base import clone

# StratifiedKFold는 클래스별 비율이 유지되도록 계층적 샘플링 수행
# 3개의 서브셋으로 나눔
skfolds=StratifiedKFold(n_splits=3,random_state=42,shuffle=True)

# X_train과 y_train_5를 3개의 서브셋으로 나누고 각 서브셋의 인덱스를 받아 해당 인덱스의 데이터만으로 훈련과 검증을 반복(총 세번 반복)
# clone은 훈련되지 않은 새로운 분류기를 생성해줌
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]        # X_train 데이터에서 train으로 분류된 데이터
     y_train_folds=y_train_5[train_index]     # y_train 데이터에서 train으로 분류된 데이터
     X_test_fold=X_train[test_index]           # X_train 데이터에서 test로 분류된 데이터
     y_test_fold=y_train_5[test_index]        # y_train 데이터에서 test로 분류된 데이터

     clone_clf.fit(X_train_folds,y_train_folds)     # 훈련되지 않은 새로운 분류기에 train데이터 훈련
     y_pred=clone_clf.predict(X_test_fold)        # test 데이터 예측
     n_correct=sum(y_pred==y_test_fold)       # 정확하게 예측한 데이터 개수
     print(n_correct/len(y_pred))                     # (정확하게 예측한 데이터 개수 / 전체 데이터 개수)로 정확도 계산

### 결과 ###
0.9669
0.91625
0.96785

 -세 번의 교차 검증 결과, 정확하게 예측한 데이터 개수의 비율이 0.9 이상으로 정확도가 90% 이상임

 

4. 오차 행렬을 통한 성능 측정

 -오차 행렬 구하기

# cross_val_predict는 cross_val_score가 평가 점수를 반환하는 것과 다르게 각 테스트셋의 예측값을 반환(훈련에 쓰이지 않은 값들의 예측값)
from sklearn.model_selection import cross_val_predict

# 앞에서 쓴 확률적 경사하강법 모델에 X_train으로 학습시키는 과정을 3번 반복
y_train_pred=cross_val_predict(sgd_clf,X_train,y_train_5,cv=3)

# 실제 y_train_5값과 예측된 y_train_pred값을 비교하여 오차행렬 생성
from sklearn.metrics import confusion_matrix
confusion_matrix(y_train_5,y_train_pred)

### 결과 ###
array([[53892,   867],
          [1891,   3530]])

# True Negative(TN, 실제: 음성, 예측: 음성) : 53892     False Positive(FP, 실제: 음성, 예측: 양성)  : 687
# False Negative(FN, 실제: 양성, 예측: 음성): 1891      True Positive(TP, 실제: 양성, 예측: 양성)   : 3530

 

 -정밀도, 재현율 구하기

from sklearn.metrics import precision_score,recall_score
# 정밀도: TP/(TP+FP)
precision_score(y_train_5,y_train_pred) # 3530 / (3530+687)

# 재현율: TP/(TP+FN)
recall_score(y_train_5, y_train_pred)   # 3530 / (3530+1891)

# F-1 점수: 정밀도와 재현율의 조화 평균, 2 / (1/정밀도)+(1/재현율) = 2*(정밀도*재현율) / (정밀도+재현율)
from sklearn.metrics import f1_score
f1_score(y_train_5,y_train_pred)

 -정밀도와 재현율은 같이 높일 수 없고 한쪽이 높아지면 다른 한 쪽은 낮아지는 트레이드오프 관계이므로 적절한 선에서 타협을 봐야함

 -f1-score는 정밀도와 재현율의 조화 평균으로 f1-score가 클수록 정밀도와 재현율이 비슷함

 -예시 1) 어린이에게 안전한 동영상만 컬러내는 분류기 훈련 가정

  →재현율이 높고 정밀도가 낮음: 실제 양성 중 양성으로 예측한 비율이 높음

                                                      →예측한 것 중에 나쁜 동영상이 있을수 있음

  →재현율이 낮고 정밀도가 높음: 양성으로 예측한 것 중 실제 양성인 비율이  높음

                                                     →예측한 것에 좋은 동영상의 비율이 높으므로 안전한 동영상만 있을 확률이 더 높음

 

 -예시 2) 감시 카메라로 좀도둑을 잡는 분류기 훈련 가정

  →재현율이 높고 정밀도가 낮음: 실제 양성 중 양성으로 예측한 비율이 높음

                                                      →실제 도둑을 도둑으로 예측할 확률이 높아 도둑을 잡을 확률이 높아지지만 경비원이 오인 출동할 확률도 있음

  →재현율이 낮고 정밀도가 높음: 양성으로 예측한 것 중 실제 양성인 비율이  높음

                                                     →도둑이라고 예측했다면 진짜 도둑일 확률이 높이만 애초에 도둑이라고 예측할 비율은 예상할 수 없음

 

 -데이터의 점수에 대한 정밀도와 재현율 그래프 그리기

# 그래프 그리기 전에 먼저 모든 데이터의 점수 받아오기
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,threshods):
    plt.plot(thresholds,precisions[:-1],'b--',label='정밀도')   # x축에 임계값, y축에 정밀도, 색깔은 blue, 선 종류는 긴 점선
    plt.plot(thresholds,recalls[:-1],'g',label='재현율')        # x축에 임계값, y축에 재현율, 색깔은 green, 선 종류는 실선

plot_precision_recall_vs_threshold(precisions,recalls,thresholds)
plt.show()
# 재현율은 임계값이 올라감에 따라 무조건 줄어들지만 정밀도는 가끔 낮아질 때가 있어 울퉁불퉁한 그래프가 그려짐

# 재현율과 정밀도에 관한 그래프
# 정밀도가 급격하게 줄어드는 지점(이 그래프에서는 재현율이 80%인 지점 쯤) 직전에서 정밀도와 재현율을 결정하는 것이 좋음(이 그래프에서는 재현율이 60%인 지점 쯤)
plt.plot(recalls[:-1],precisions[:-1])

 -정밀도와 재현율 사이에서 임계값 찾기

 -정밀도와 재현율 중 한 점수에 대해 목표를 잡고 그 목표에 대한 임계값 구하기

# 정밀도 90% 달성이 목표라고 했을 때 정밀도가 최소 90%가 되는 가장 낮은 임계값 찾기
# np.argmax()는 최대값의 첫번째 인덱스 반환(여기서는 True의 첫번째 인덱스 반환)
threshold_90_precision=thresholds[np.argmax(precisions>=0.90)]

# 훈련 세트에 대한 예측
y_train_pred_90=(y_scores>=threshold_90_precision)
# 예측에 대한 정밀도와 재현율
precision_score(y_train_5,y_train_pred_90)  # 0.9000345901072293
recall_score(y_train_5,y_train_pred_90)     # 0.4799852425751706
# 재현율이 너무 낮으므로 정밀도가 높아도 유용하지 않은 분류기임

 -정밀도가 90% 이상인 점을 찾아 정밀도는 90%가 넘도록 나왔지만 재현율은 0.48정도로 낮음

 -유용하지 않은 분류기

 

 

5. ROC 곡선과 AUC 점수

 -ROC 곡선은 재현율(진짜 양성 비율)과 FPR(거짓 양성 비율, FP / (FP+TN), 1-특이도)의 곡선

from sklearn.metrics import roc_curve
fpr, tpr, thresholds=roc_curve(y_train_5, y_scores)

def plot_roc_curve(fpr, tpr, label=None):
    plt.plot(fpr, tpr, linewidth=2, label=label)
    plt.plot([0, 1],[0, 1], 'k--')

plot_roc_curve(fpr, tpr)
plt.show()

 -점선은 완전 랜덤 분류기의 성능으로 가장 안좋은 상황

 -좋은 분류기일수록 점선에서 떨어져 왼쪽 위의 모서리로 가까워짐

 

 -AUC 점수는 곡선 아래의 면적(Area Under Curve)으로 분류기의 성능을 비교하기 위해 계산

 -완전 랜덤일 때(점선일 때) 0.5, 가장 좋을 때(왼쪽 위의 모서리가 꼭짓점으로 있을 때) 1

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

### 결과 ###
0.9604938554008616

+ Recent posts