#!/usr/bin/env python
# coding: utf-8
# # Chapter 3. 분류
# ---
# ## 3.1 MNIST
# In[57]:
from sklearn.datasets import fetch_mldata
mnist = fetch_mldata('MNIST original')
mnist
# ◈ 사이킷런에서 읽어 들인 데이터셋들은 일반적으로 비슷한 딕셔너리 구조를 가짐
# + 데이터셋을 설명하는 DESCR 키
# + 샘플이 하나의 행, 특성이 하나의 열로 구성된 배열을 가진 data 키
# + 레이블 배열을 담고 있는 target 키
# In[58]:
X, y = mnist["data"], mnist["target"]
print(type(mnist))
print(mnist.keys())
print(type(X), type(X))
print(X.shape,y.shape)
# In[59]:
get_ipython().run_line_magic('matplotlib', 'inline')
import matplotlib
import matplotlib.pyplot as plt
import numpy as np
import random
target = []
plt.figure(figsize=(8,8))
for i in range(100):
num = random.randrange(0, 70000)
target.append(int(y[num]))
some_digit = X[num]
some_digit_image = some_digit.reshape(28, 28)
plt.subplot(10,10,i+1)
plt.imshow(some_digit_image,
cmap = matplotlib.cm.binary,
# cmap = matplotlib.cm.Blues,
interpolation="nearest",)
plt.axis("off")
plt.show()
target = np.array(target)
target = target.reshape(-1, 10)
target
# **※ interpolation** 매개변수
# https://matplotlib.org/gallery/images_contours_and_fields/interpolation_methods.html
# ---
# + MNIST 데이터셋은 이미 훈련 세트(앞쪽 60,000개 이미지)와 테스트 세트(뒤쪽 10,000개 이미지)로 나누어 놓음
# In[60]:
X_train, X_test, y_train, y_test = X[:60000], X[60000:], y[:60000], y[60000:]
# ---
# + 훈련 세트를 섞어서 모든 교차 검증 폴드가 비슷해지도록 만듦 (하나의 폴드라도 특정 숫자가 누락되면 안 됨)
# + 어떤 학습 알고리즘은 훈련 샘플의 순서에 민감해서 많은 비슷한 샘플이 연이어 나타나면 성능이 나빠짐
# In[61]:
shuffle_index = np.random.permutation(60000)
X_train, y_train = X_train[shuffle_index], y_train[shuffle_index]
# In[62]:
np.random.permutation(10)
# ---
# ## 3.2 이진 분류기 훈련
# + **이진 분류기**binary classifier (간단한 5-감지기) 타깃 벡터
# In[63]:
y_train_5 = (y_train == 5) # 5는 True고, 다른 숫자는 모두 False
y_test_5 = (y_test == 5)
# ◈ **확률적 경사 하강법**Stochastic Gradient Descent(SGD) 분류기
# + 매우 큰 데이터셋을 효율적으로 처리 (한 번에 하나씩 훈련 샘플을 독립적으로 처리하기 때문)
# + **온라인 학습**에 잘 들어맞음
# In[64]:
from sklearn.linear_model import SGDClassifier
sgd_clf = SGDClassifier(max_iter=5, random_state=42)
sgd_clf.fit(X_train, y_train_5)
# In[65]:
some_digit_1, some_digit_2 = X[36000], X[44000]
print(y[36000], y[44000])
# In[66]:
sgd_clf.predict([some_digit_1, some_digit_2])
# ---
# ## 3.3 성능 측정
# ### 3.3.1 교차 검증을 사용한 정확도 측정
# ◈ K-겹 교차 검증
# In[67]:
from sklearn.model_selection import cross_val_score
cross_val_score(sgd_clf, X_train, y_train_5, cv=3, scoring="accuracy")
# ---
# ◈ 교차 검증 기능을 직접 구현
# + 가끔 사이킷런이 제공하는 기능보다 교차 검증 과정을 더 많이 제어해야 할 필요가 있음
# + cross_val_score() 함수와 거의 같은 작업을 수행하고 동일한 결과를 출력
# In[68]:
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])
# print(y_test_fold)
# print(train_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** 클래스
# https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.StratifiedKFold.html
# ---
# ◈ 모든 이미지를 무조건 5가 아니라고 예측하는 더미 분류기
# In[69]:
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()
cross_val_score(never_5_clf, X_train, y_train_5, cv=3, scoring="accuracy")
# In[70]:
np.zeros((3, 1), dtype=bool)
# + 정확도를 분류기의 성능 측정 지표로 선호하지 않는 이유를 보여줌
# + 특히 **불균형한 데이터셋**을 다룰 때 더욱 선호되지 않음(어떤 클래스가 다른 것보다 월등히 많은 경우)
# ### 3.3.2 오차 행렬confusion matrix
# + e.g. 숫자 5의 이미지를 3으로 잘못 분류한 횟수 → 오차 행렬의 5행 3열
# In[71]:
from sklearn.model_selection import cross_val_predict
y_train_pred = cross_val_predict(sgd_clf, X_train, y_train_5, cv=3)
print(y_train_pred, y_train_pred.shape)
# In[72]:
from sklearn.metrics import confusion_matrix
confusion_matrix(y_train_5, y_train_pred)
# + 행 : **실제 클래스** , 열 : **예측한 클래스**
# + 첫 번째 행 : '5 아님'(**음성 클래스**negative class)
# + 54,104개를 '5 아님'으로 정확하게 분류 → **진짜 음성**true negative
# + 나머지 475개는 '5'라고 잘못 분류 → **거짓 양성**false positive
# + 두 번째 행 : '5'이미지(**양성 클래스**positive class)
# + 1,966개를 '5 아님'으로 잘못 분류 → **거짓 음성**false negative
# + 나머지 3,455개는 '5'라고 정확하게 분류 → **진짜 양성**true positive
# In[73]:
y_train_perfect_predictions = y_train_5
confusion_matrix(y_train_5, y_train_perfect_predictions)
# ---
# ◈ **정밀도**precidion : 양성 예측의 정확도
# ![Equation3-1](./images/Equation3-1.png)
# **
식 3-1 정밀도**
# + TP : 진짜 양성의 수 , FP : 거짓 양성의 수
# ◈ **재현율**recall=**민감도**sensitivity=**진짜 양성 비율**true positive rate(TPR) : 분류기가 정확하게 감지한 양성 샘플의 비율
# ![Equation3-2](./images/Equation3-2.png)
# **식 3-2 재현율**
# + FN : 거짓 음성의 수
# ---
# ![Figure3-2](./images/Figure3-2.png)
# **그림 3-2 오차 행렬**
# ### 3.3.3 정밀도와 재현율
# In[79]:
from sklearn.metrics import precision_score, recall_score
precision_score(y_train_5, y_train_pred), recall_score(y_train_5, y_train_pred)
# + 5로 판별된 이미지 중 69%만 정확
# + 전체 숫자 5에서 83%만 감지
# ---
# ◈ **F1 score** : 정밀도와 재현율의 **조화 평균**harmonic mean
# + 정밀도와 재현율을 하나의 숫자로 만들어 두 분류기를 비교할 때 편리함
# ![Equation3-3](./images/Equation3-3.png)
# **식 3-3 F1 점수**
# In[80]:
from sklearn.metrics import f1_score
f1_score(y_train_5, y_train_pred)
# + 정밀도와 재현율이 비슷한 분류기에서는 F1 점수가 높음
# + 상황에 따라 정밀도가 중요할 수도 있고 재현율이 중요할 수도 있음
# + 안전한 동영상 걸러내기 → 재현율이 낮더라도 높은 정밀도가 필요
# + 감시 카메라 → 정밀도가 낮더라도 높은 재현율이 필요
#
# ▣ 정밀도를 올리면 재현율이 줄고 그 반대도 마찬가지 → **정밀도/재현율 트레이드오프**
# ### 3.3.4 정밀도/재현율 트레이드오프
# + SGDClassifier는 **결정 함수**decision function를 사용하여 각 샘플의 점수를 계산함
# + 이 점수가 임곗값보다 크면 샘플을 양성 클래스에 할당하고 그렇지 않으면 음성 클래스에 할당함
#
# ![Figure3-3](./images/Figure3-3.png)
# **그림 3-3 결정 임곗값과 정밀도/재현율 트레이드오프**
# In[186]:
some_digit = X[34887]
y_scores = sgd_clf.decision_function([some_digit])
y_scores
# In[187]:
threshold = 0
y_some_digit_pred = (y_scores > threshold)
y_some_digit_pred
# In[188]:
threshold = 20000
y_some_digit_pred = (y_scores > threshold)
y_some_digit_pred
# In[273]:
y_scores = cross_val_predict(sgd_clf, X_train, y_train_5,
cv=3, method="decision_function")
y_scores, y_scores.shape
# In[274]:
(y_train_pred == (y_scores > 0)).all()
# In[201]:
from sklearn.metrics import precision_recall_curve
precisions, recalls, thresholds = precision_recall_curve(y_train_5, y_scores)
precisions.shape, recalls.shape, thresholds.shape
# + 가능한 모든 임곗값에 대해 정밀도와 재현율을 계산
# ---
# ◈ **결정 임곗값에 대한 정밀도와 재현율**
# In[244]:
def plot_precision_recall_vs_threshold(precisions, recalls, thresholds):
plt.plot(thresholds, precisions[:-1], "b--", label="precision", linewidth=2)
plt.plot(thresholds, recalls[:-1], "g-", label="Recall", linewidth=2)
plt.xlabel("Threshold", fontsize=20)
plt.legend(loc="center left", fontsize=20)
plt.xlim([-700000, 700000])
plt.ylim([0, 1])
plt.figure(figsize=(10, 5))
plot_precision_recall_vs_threshold(precisions, recalls, thresholds)
plt.show()
# ---
# ◈ **재현율에 대한 정밀도(PR 곡선)**
# In[268]:
def plot_precision_vs_recall(precisions, recalls):
plt.plot(recalls, precisions, "b-", linewidth=2)
plt.xlabel("Recall", fontsize=20)
plt.ylabel("Precision", fontsize=20)
plt.axis([0, 1, 0, 1])
plt.figure(figsize=(8, 5))
plot_precision_vs_recall(precisions, recalls)
plt.show()
# In[275]:
y_train_pred_70000 = (y_scores > 70000)
precision_score(y_train_5, y_train_pred_70000), recall_score(y_train_5, y_train_pred_70000)
# ### 3.3.5 ROC 곡선
# ◈ **수신기 조작 특성**receiver operating characteristic(ROC) 곡선
# + 이진 분류에서 널리 사용
# + **거짓 양성 비율**false positive rate(FPR)에 대한 **진짜 양성 비율**true positive rate(TPR, 재현율의 다른 이름)의 곡선
# ※ **FPR**false positive rate : 양성으로 잘못 분류된 음성 샘플의 비율
# ※ **TNR**true negative rate : 음성으로 정확하게 분류한 음성 샘플의 비율(=**특이도**specificity)
# ※ **FPR = 1 - TNR**
# + 따라서 ROC 곡선은 **민감도**(재현율)에 대한 **1 - 특이도** 그래프
# In[277]:
from sklearn.metrics import roc_curve
fpr, tpr, thresholds = roc_curve(y_train_5, y_scores)
# In[288]:
def plot_roc_curve(fpr, tpr, label=None):
plt.plot(fpr, tpr, linewidth=2, label=label)
plt.plot([0, 1], [0, 1], 'k--')
plt.axis([0, 1, 0, 1])
plt.xlabel('FPR', fontsize=20)
plt.ylabel('TPR', fontsize=20)
plt.figure(figsize=(8, 5))
plot_roc_curve(fpr, tpr)
plt.show()
# + 검은색 점선은 완전한 랜덤 분류기의 ROC 곡선을 뜻함
# + 좋은 분류기는 이 점선으로부터 최대한 멀리 떨어져 있어야 함(왼쪽 위 모서리)
# ◈ **곡선 아래의 면적**area under the curve(AUC)
# + 완벽한 분류기는 ROC의 AUC가 1
# + 완전한 랜덤 분류기는 ROC의 AUC가 0.5
# In[289]:
from sklearn.metrics import roc_auc_score
roc_auc_score(y_train_5, y_scores)
# + 일반적으로 양성 클래스가 드물거나 거짓 음성보다 거짓 양성이 더 중요할 때 PR 곡선을 사용하고 그렇지 않으면 ROC 곡선을 사용
# In[362]:
from sklearn.ensemble import RandomForestClassifier
forest_clf = RandomForestClassifier(n_estimators=10, 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)
# In[363]:
y_probas_forest.shape
# In[364]:
plt.figure(figsize=(8, 5))
plt.plot(fpr, tpr, "b:", linewidth=2, label="SGD")
plot_roc_curve(fpr_forest, tpr_forest, "Random Forest")
plt.legend(loc="lower right", fontsize=20)
plt.show()
# In[365]:
roc_auc_score(y_train_5, y_scores), roc_auc_score(y_train_5, y_scores_forest)
# In[366]:
y_train_pred_forest = cross_val_predict(forest_clf, X_train, y_train_5, cv=3)
y_train_pred_forest, y_train_pred_forest.shape
# In[367]:
precision_score(y_train_5, y_train_pred_forest), recall_score(y_train_5, y_train_pred_forest)