Discover us

About us

Projects

Blog

Events

Members

Development Blog

GDGoC CAU 개발자와 디자이너의 작업 과정과
결과물을 공유하는 공간입니다.

어떻게 프로젝트를 시작하게 되었고,
진행하면서 느낀 개발자와 디자이너의
생생한 스토리를 직접 확인해보세요!

Development

ML ch3 - classification

  • #DS/ML/DL
  • Jeongyoon Shin
  • 2023. 9. 18.

ML ch3 - classification

ch3 - 분류

Classification

mnist dataset 사용하여 숫자 클래스를 분류하는 내용을 다루어 볼 것이다.
import numpy as np
#mnist dataset 불러오기from sklearn.datasets import fetch_openmlmnist = fetch_openml(‘mnist_784’, version=1)mnist.keys()
  • mnist dataset은 70000개의 숫자를 손으로 쓴 이미지
  • target col에 각 이미지의 label이 저장되어 있음
#그 중 data : 하나의 행이 하나의 샘플 - 784가지 특성 담겨있음 (까만색, 흰색 채워져있는거), target : 샘플의 labelX, y= mnist[“data”], mnist[“target”]X.shape
import matplotlib as mplimport matplotlib.pyplot as plt
some_digit = X.to_numpy()[0] #X[0] 로 작성하니 오류가 난다. to_numpy함수 사용으로 해결some_digit_image = some_digit.reshape(28,28)plt.imshow(some_digit_image, cmap = “binary”)plt.axis(“off”)plt.show()
notion image
clf1
y[0]
#y의 label을 정수형으로 변환해 줄 것이다y = y.astype(np.uint8)
#X와 y의 train, test set 나누기X_train , X_test, y_train, y_test = X[:60000], X[60000:], y[:60000], y[60000:]

이진 분류기 훈련

5이다, 5가 아니다 두 클래스로 분류해주는 알고리즘 만들기 (5-detector)
y_train_5 = (y_train == 5) #y가 그냥 label이었다면 - 5이면 true로, 5가 아니면 false로 판단하게 바꿈y_test_5 = (y_test == 5)
  • 확률적 경사 하강법 (Stochastic Gradient Descent - SGD) 분류기를 사용할 것
from sklearn.linear_model import SGDClassifiersgd_clf = SGDClassifier(random_state = 42)sgd_clf.fit(X_train, y_train_5)sgd_clf.predict([X.to_numpy()[0]])

성능 측정

  1. 정확도 측정
from sklearn.model_selection import cross_val_scorecross_val_score(sgd_clf, X_train, y_train_5, cv =3, scoring=“accuracy”)# train set (X, y)를 가지고 3개로 나눠 두묶음으로 sgd_clf 훈련 후 한 묶음으로 MSE 구하는 식
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”)#5가 아니라고 무조건 분류하는 분류기도 정확도가 90% 이상 #0~9까지의 이미지가 있는데 그럼 그 중 5는 10%에 불과, 따라서 5가 아니라고 무조건 분류하는 분류기도 정확도가 90% 이상임 - 정확도는 적합하지 않은 성능 측정 지표
  • F1 점수로 평가
#오차행렬 이용#만약 이진분류 아니라면 i행 i열 제외한 나머지가 오차인 것들from sklearn.model_selection import cross_val_predicty_train_pred = cross_val_predict(sgd_clf, X_train, y_train_5, cv=3)
  • k개로 나누고 훈련 묶음에서 훈련하고 테스트 폴드에서 얻은 예측(T/F)을 반환하는 함수
  • SGD로 분류해서 예측한 값을 y_train_pred에 저장
  • 70000개 - 3묶음으로 나누면 첫묶음 테스트로 하고 예측한값 + 두번째 + 세번째 합친것
  1. 오차행렬
  • 대각성분 - 제대로 예측
  • 1행 2열 - 거짓인데 진실로 예측 , 2행 1열 - 진실인데 거짓으로 예측
  1. 정밀도와 재현율
  • 정밀도(precision) - 5라고 분류한 것 중에서 진짜 5의 비율
  • 재현율(recall) - 진짜 5중에서 5라고 예측한 비율
  • F1점수 - 정밀도와 재현율의 조화평균 = 2 / (1/정밀도)+(1/재현율)
  • F1 점수 높으면 분류기 성능 좋게 평가되지만, 평가시 어느 하나에 가중치 두어야할 때는 주의해야 한다.
#y_train_pred의 오차행렬 출력from sklearn.metrics import confusion_matrixconfusion_matrix(y_train_5, y_train_pred)
y_train_perfect_predictions = y_train_5
#제대로 분류되어있으면 대각성분 제외 0 가짐confusion_matrix(y_train_5, y_train_perfect_predictions)
#정밀도 5라고 분류한것 중 진짜 5인 수의 비율from sklearn.metrics import precision_score, recall_scoreprecision_score(y_train_5, y_train_pred)
#정밀도 직접 계산3530 / (3530 + 687)
recall_score(y_train_5, y_train_pred)
#재현율 직접 계산3530 / (3530 + 1891)
#정밀도, 재현율의 조화평균인 F1점수 코드from sklearn.metrics import f1_scoref1_score(y_train_5, y_train_pred)
3530 / (3530 + (1891 + 687)/2)#F1 점수 직접 계산
y_scores = sgd_clf.decision_function([some_digit])y_scores
  • 결정함수 - SGD분류기는 결정함수를 이용해 샘플점수를 계산하고 임계값(threshold)을 기준으로 클래스에 샘플을 할당하는 방식이다.
  • 샘플의 점수 : 양성 or 음성 클래스 할당에 필요한 점수 비슷한 점수면 같은 클래스일 확률이 높다.
  • 따라서 적절한 임계값 설정 과정은 아래 코드 참고
#임계값을 0으로 정할 경우 모든 데이터 양의 클래스로(5라고) 판별threshold = 0y_some_digit_pred = (y_scores > threshold)y_some_digit_pred
#임계값을 너무 크게 잡으면 음의 클래스로 판별할 가능성 높아진다threshold = 200000y_some_digit_pred = (y_scores > threshold)y_some_digit_pred
#cv로 각 샘플의 예측값 대신 결정점수를 반환받아 저장y_scores = cross_val_predict(sgd_clf, X_train, y_train_5, cv=3, method=“decision_function”)
#60000개의 train set 모두의 예측된 결정점수 받음y_scores.shape
#반환받은 결정점수를 이용해 모든 임계값에 대한 정밀도, 재현율을 계산할 것#input = train set의 실제y값(T/F), 샘플의 결정점수#output = 각 임계값에 따른 정밀도, 재현율from sklearn.metrics import precision_recall_curveprecisions, recalls, thresholds = precision_recall_curve(y_train_5, y_scores)
  • precision_recall_curve 설명
확률이나 결정점수를 input하면 제일 큰값부터 threshold로 부여한다. 그에 따라 정해지는 precision과 recall을 저장한다
그 다음 값을 threshold로 사용해 앞의 과정 반복해서 세개의 array로 반환해준다
#threshold에 따른 정밀도, 재현율 plot하기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=16) plt.legend(loc=“upper left”, fontsize=16) plt.axis([-50000,50000,0,1])plt.figure(figsize=(8, 4))plot_precision_recall_vs_threshold(precisions, recalls, thresholds)plt.show()
notion image
clf2
#재현율에 따른 정밀도 곡선def plot_precision_vs_recall(precisions, recalls): plt.plot(recalls, precisions, “b-”, linewidth=2) plt.xlabel(“recall”, fontsize=16) plt.ylabel(“precision”, fontsize=16) plt.axis([0, 1, 0, 1])plt.figure(figsize=(8, 6))plot_precision_vs_recall(precisions, recalls)plt.show()#재현율 80% 근처에서 정밀도 급강 - 그 전 지점 트레이드오프로 선택?
notion image
clf3
  • 예를 들어 정밀도 90% 지점에서 트레이드오프라고 지정하면 이를 만족하는 threshold를 찾는다.
threshold_90_precision = thresholds[np.argmax(precisions >= 0.90)]threshold_90_precision
  • 따라서 바로 T/F 판별대신 임의로 임계값을 설정해서 분류해 준다
y_train_pred_90 = (y_scores >= threshold_90_precision)
  • 정밀도를 확인해 보면 90% 만족하는 것으로 나온다
precision_score(y_train_5, y_train_pred_90)recall_score(y_train_5, y_train_pred_90)

roc_curve

  • high precision, but low recall 이면 유용하지 않을것
  • 정밀도 조정에서 재현율 - 정밀도 곡선처럼 재현율 조정을 위해 사용되는 다른 곡선
  • 위양성비율에 대한 진양성비율(재현율) 곡선
  • roc_curve 함수
  • input = 훈련용 라벨데이터, y의 결정점수
  • output = 거짓양성비율, 진짜양성비율, 임계값
from sklearn.metrics import roc_curvefpr, 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–’) plt.axis([0, 1, 0, 1]) plt.xlabel(‘False Positive Rate’, fontsize=16) plt.ylabel(‘True Positive Rate’, fontsize=16)plt.figure(figsize=(8, 6))plot_roc_curve(fpr, tpr)plt.show()
notion image
clf4
  • 재현율이 높아질 수록 분류기의 거짓양성이 늘어난다. ROC곡선은 좋은 분류기일수록 점선에서 멀리 떨어져 있어야 한다. (점선 : 랜덤분류기의 ROC곡선)
  • 따라서 곡선 아래 면적을 측정해서 커질수록 좋은 분류기 (완벽한 분류기 = 1, 랜덤 분류기 = 0.5)
  • 정밀도/재현율 곡선 <-> ROC 곡선 사용하는 경우
  • 양성클래스가 크게 적지 않은 경우 ROC곡선 -> 어느 경우든 AUC 점수가 좋으면 유용하지 않음 (반대의 경우) 거짓양성을 가려내는 것이 더 중요하면 정밀도/재현율 곡선
from sklearn.metrics import roc_auc_scoreroc_auc_score(y_train_5, y_scores)
# 랜덤포레스트와 SGD 비교 - ROC 곡선 이용# 랜덤포레스트는 결정점수 대신 확률을 점수 대신으로 이용한다.from sklearn.ensemble import RandomForestClassifierforest_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)
plt.figure(figsize=(8, 6))plt.plot(fpr, tpr, “b:”, linewidth=2, label=“SGD”)plot_roc_curve(fpr_forest, tpr_forest, “RandomForest”)plt.legend(loc=“lower right”, fontsize=16)plt.show()
notion image
clf5
roc_auc_score(y_train_5, y_scores_forest)#랜덤포레스트의 분류기가 AUC값이 더 커 좋은 분류기라고 비교할 수 있다.

다중 분류

  • 이진분류 - 5와 5아님 으로 구별한다
  • 다중분류 - 둘 이상의 클래스 즉, 1, 2, 3 … 여러개 클래스를 구별
  • 분류방법 : 이진분류기를 여러개 사용, 다중 클래스 분류에 이진분류 알고리즘 선택하면 사이킷런이 그에 따라 자동으로 OvR 또는 OvO를 실행한다
  • OvR :
  • OvO :
#서포트벡터머신 분류기 불러오기#SVC함수 fit input = X_train, y_train(label : 정수값)#predict 는 label의 정수값을 리턴한다.from sklearn.svm import SVCsvm_clf = SVC(gamma=“auto”, random_state = 42)svm_clf.fit(X_train[:1000], y_train[:1000])svm_clf.predict([some_digit])
#샘플 하나 당 10개의 결정점수 반환 - 각 클래스 분류 기준이 될 점수#그 중 가장 높은 점수를 가진 클래스에 할당한다.some_digit_scores = svm_clf.decision_function([some_digit])some_digit_scoresnp.argmax(some_digit_scores)svm_clf.classes_svm_clf.classes_[5]
#SVC에서 OvR기법 사용하는 다중 분류기를 만들고 싶을 때from sklearn.multiclass import OneVsRestClassifierovr_clf = OneVsRestClassifier(SVC(gamma=“auto”, random_state = 42))ovr_clf.fit(X_train[:1000], y_train[:1000])ovr_clf.predict([some_digit])
#OvO기법 사용하도록 강제하는 경우from sklearn.multiclass import OneVsOneClassifierovo_clf = OneVsOneClassifier(SGDClassifier(max_iter=5, random_state=42))ovo_clf.fit(X_train, y_train)ovo_clf.predict([some_digit])len(ovo_clf.estimators_)
  • SGD분류기는 그 자체로 다중 클래스 분류 가능하므로 그냥 실행하면 된다.
  • SGD에서도 decision_function()는 클래스마다 결정점수값 반환해준다.
sgd_clf.fit(X_train, y_train)sgd_clf.predict([some_digit])sgd_clf.decision_function([some_digit])
#분류기 정확도 측정해보기cross_val_score(sgd_clf, X_train, y_train, cv = 3, scoring = “accuracy”)
#정확도가 더 높아질 수 있도록 X_train의 스케일 조정한다.#너무 오래걸려 중단…from sklearn.preprocessing import StandardScalerscaler = 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”)
  • 위의 과정을 통해 사용하기 괜찮은 다중분류 모델을 찾았다고 가정하면 앞으로 생길 수 있는 에러의 종류들을 분석한다.
#오차행렬 측정#y_train_pred 에 sgd 다중분류를 실행했을 때 예측값들을 저장#이러한 y_train_pred의 오차행렬을 출력 (10x10 행렬)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
def plot_confusion_matrix(matrix): ""“컬러 오차 행렬을 원할 경우”"" fig = plt.figure(figsize=(8,8)) ax = fig.add_subplot(111) cax = ax.matshow(matrix) fig.colorbar(cax)
plt.matshow(conf_mx, cmap=plt.cm.gray)plt.show()#대각선부분이 잘 분류되었다는 것을 나타내는 지표 - 밝을수록 데이터 수가 많다는 뜻#대각선이 어두우면 데이터의 양이 적거나 잘 분류되지 않았다는 것이라 생각할 수 있다.
notion image
clf6
  • 각 클래스마다의 에러 비율로 비교해보자 (오차행렬 값 / 클래스당 이미지 수)
  • 실제 클래스의 값 = row , 예측된 클래스 값 = col
  • 8열의 색이 대체적으로 밝음 - 8 이외의 다른 이미지가 8로 잘못 분류 되었다
  • 8행8열은 어두우므로 실제 8은 8로 분류되었다
  • 따라서 8로 잘못 분류되는 경우를 줄이는 방향으로 조정 - 8처럼 보이는 다른 숫자 데이터로 학습시키거나 새 특성을 찾아 추가하는 방식
  • 3, 5도 서로 잘못 분류되기도 하는 것 확인.
row_sums = conf_mx.sum(axis=1, keepdims=True)norm_conf_mx = conf_mx / row_sums
np.fill_diagonal(norm_conf_mx, 0)plt.matshow(norm_conf_mx, cmap=plt.cm.gray)plt.show()
notion image
clf7
#숫자그림 위한 추가 함수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 = matplotlib.cm.binary,** options) plt.axis(“off”)
notion image
clf8
#훈련데이터중 label이 각각 3이나 5, 예측된 클래스가 3이나 5로 분류된 데이터들을 각각 경우에 따라 출력#label, predict# X_11 = (3,3) X_12 = (3,5) X_21 = (5,3) X_22 = (5,5)cl_a, cl_b = 3, 5X_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()

다중레이블 분류

  • 다중레이블 분류 - 한 분류기가 샘플마다 여러개의 클래스 출력
  • ex) 숫자 이미지에서 1. 큰값(7,8,9)인지 출력, 2. 홀수인지 판단 - 판단은 이진분류에 해당
  • 다중타깃 배열을 사용한다.
from sklearn.neighbors import KNeighborsClassifier#타깃 레이블 정하기 (T/F)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])
#분류기 평가하기 - 모든 레이블에서의 F1 점수 평균으로 평가y_train_knn_pred = cross_val_predict(knn_clf, X_train, y_multilabel, cv=3, n_jobs=-1)f1_score(y_multilabel, y_train_knn_pred, average=“macro”)

다중 출력 분류

# 다중 출력 분류 - 다중레이블 분류 + 이진분류가 아닌 다중분류# 이미지 픽셀 강도에 잡음 추가해주는 코드noise = np.random.randint(0, 100, (len(X_train), 784))X_train_mod = X_train + noisenoise = np.random.randint(0, 100, (len(X_test), 784))X_test_mod = X_test + noisey_train_mod = X_trainy_test_mod = X_test
def plot_digit(data): image = data.reshape(28,28) plt.imsow(image, cmap = mpl.cm.binary, interpolation = “nearest”) plt.axis(“off”)
some_index = 5500plt.subplot(121); plot_digit(X_test_mod[some_index])plt.subplot(122); plot_digit(y_test_mod[some_index])plt.show()
notion image
clf9
knn_clf.fit(X_train_mod, y_train_mod)clean_digit = knn_clf.predict([X_test_mod[some_index]])plot_digit(clean_digit)
notion image
clf10