본문 바로가기
컴퓨터공학/AI

기계 학습 3. 분류(Classification)

by Jinger 2023. 9. 20.

서론

    기계 학습에서 Classification(분류)은 데이터를 미리 정의된 클래스 또는 카테고리로 분류하는 작업을 의미한다. Classification은 데이터 분석, 패턴 인식, 예측 및 의사 결정 등 다양한 분야에서 중요한 역할을 하며, 기계 학습의 주요 응용 분야 중 하나이다.


MNIST

MNIST

    MNIST(엠니스트) 데이터셋은 70,000개의 손으로 쓴 작은 숫자 이미지로 구성된 데이터이다. 이 데이터셋은 미국 인구조사국의 고등학생과 직원들에 의해 손으로 쓰인 숫자 이미지로, 각 이미지는 해당하는 숫자로 레이블링 되어 있다. Scikit-Learn에서 로드한 데이터셋은 일반적으로 다음과 같은 딕셔너리 구조를 가지고 있으며, 이 안에는 다음과 같은 키(key)들이 포함되어 있다.

from sklearn.datasets import fetch_openml
mnist = fetch_openml('mnist_784', version=1)
mnist.keys()
# 결과 dict_key(['data', 'target', 'feature_name', 'DESCR', 'details', 'categories', 'url'])
# DESCR 키: 데이터셋에 대한 설명
# data 키: 각 인스턴스 당 하나의 특성(feature) 열을 가진 배열
# target 키: 레이블을 포함하는 배열

     이 데이터셋에서는 총 70,000개의 이미지가 있으며, 각 이미지는 784개의 특성으로 구성되어 있다. 이는 각 이미지가 28x28 픽셀이며, 각 특성은 단순히 하나의 픽셀 강도를 나타내며 0(흰색)에서 255(검은색)까지의 값을 갖는다. 아래 코드로 확인이 가능하다.

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

       데이터셋에서 한 숫자 이미지를 확인해보자. 이미지에 대한 레이블은 문자열로 표시되어 있으므로, 레이블을 정수로 변환해주어야 한다. 데이터를 자세히 살펴보기 전에 항상 테스트 세트를 만들고 따로 보관하는 것이 좋다.

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()

y[0]
y = y.astype(np.uint8)

    또한 MNIST 데이터셋은 이미 훈련 세트(첫 번째 60,000개 이미지)와 테스트 세트(마지막 10,000개 이미지)로 잘 분할되어 있다. 즉, 훈련 세트는 이미 잘 섞여 있으므로 이로 인해 교차 검증 폴드가 유사하게 구성되어 모든 폴드에서 일부 숫자가 빠지지 않도록 보장된다. 그렇기에 아래와 같이 간단하게 훈련 세트와 테스트 세트를 나눠도 된다.

X_train, X_test, y_train, y_test = X[:60000], X[60000:], y[:60000], y[60000:]

Training a Binary Classifier

     이진 분류기(Binary Classifier)는 머신 러닝에서 주어진 입력 데이터를 두 개의 이산적인 클래스로 분류하는 모델 또는 알고리즘을 말한다. MNIST는 바이너리 분류기에 적절하지 않은 예시이지만, 문제를 단순화하여 하나의 숫자만 식별하는 문제로 살펴보자. 예를 들어 5와 5가 아닌 것을 구분할 수 있는 5-감지기(5-detector)를 만들어보자. 아래와 같이 5와 5가 아닌 것을 구분하는 작업을 위해 목표 벡터를 생성하여 이진 분류 작업을 수행하도록 설정한다.

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

     또한, 확률적 경사 하강법(Stochastic Gradient Descent, SGD) 분류기를 사용한다. SGD 분류기는 대용량 데이터셋을 효율적으로 처리할 수 있는 장점이 있다. 이는 SGD가 훈련 인스턴스를 독립적으로 하나씩 다루기 때문에 가능한 것이며, 이러한 특성으로 SGD는 온라인 학습(online learning)에 적합하다.

from sklearn.linear_model import SDGClassifier

sgd_clf = SGDClassifier(random_state=42)
sgd_clf.fit(X_train, y_train_5)

Performance Measures

교차 검증(Cross-Validation)을 사용하여 정확도 측정하기

    `cross_val_score()` 함수를 사용하여 SGDClassifier 모델을 평가해 보자. 이 함수는 세 개의 폴드(fold)로 K-폴드 교차 검증(K-fold cross-validation)을 수행하여 모델의 성능을 측정한다.

from sklearn.model_selectio import cross_val_score
cross_val_score(sgd_clf, X_train, y_train_5, cv=3, scoring="accuracy")
# 결과값 array([0.96355, 0.93795, 0.95615])

    윗 코들의 모든 교차 검증 폴드에서 93% 이상의 정확도를 얻었다. 과연 이는 좋은 결과일까? 교차 검증 결과 93% 이상의 정확도를 얻었으므로 처음에는 좋은 결과로 보일 수 있다. 그러나 위의 예시와 상반대되는 "not-5" 클래스에 속하는 모든 이미지를 예측하는 매우 단순한 분류기를 고려해 보자.

from sklean.base import BaseEstimator

class Never5Classifier(BaseEstimator):
	def fit(self, X, y=None):
    	return self
    def predict(self, X):
    	return np.zero((len(X), 1), dtype=bool)
        
never_5_clf = Never5classifier()
cross_val_score(never_5_clf, X_train, y_train_5, cv=3, scoring="accuracy")
# 결과값 array([0.91125, 0.90855, 0.90915])

     이 분류기도 정확도가 90% 이상을 보인다. 이는 단순히 5의 이미지가 전체 이미지(0~9) 중 약 10% 뿐이기 때문에, 모든 이미지를 항상 "5가 아님"으로 예측하면 약 90%의 정확도를 얻을 수 있기 때문이다. 즉, 이 결과는 분류기 성능을 측정하는 선호되지 않는 방법 중 하나인 정확도가 어떤 경우에는 적절하지 않을 수 있음을 보여준다. 특히 데이터셋이 불균형한 경우(즉, 어떤 클래스가 다른 클래스보다 훨씬 더 빈번한 경우)에는 더욱 이러한 경향이 보인다. 정확도는 클래스 불균형 문제에 영향을 받을 수 있으며, 이러한 경우 다른 성능 지표를 사용하는 것이 더 적절할 수 있다.

Confusion Matrix(오차 행렬, 혼동 행렬)

    혼동 행렬(Confusion Matrix)은 분류 모델의 성능을 평가하는 데 사용되는 중요한 도구이다. 이 행렬은 다음과 같은 네 가지 항목을 기반으로 분류 모델의 예측 결과를 보여준다.

  • True Positives (TP): 모델이 양성(Positive) 클래스로 정확하게 예측한 샘플 수. 즉, 실제 양성인 샘플을 양성으로 올바르게 예측한 경우이다.
  • False Positives (FP): 모델이 양성 클래스로 잘못 예측한 샘플 수. 즉, 실제 음성인 샘플을 양성으로 잘못 예측한 경우이다.
  • False Negatives (FN): 모델이 음성 클래스로 잘못 예측한 샘플 수. 즉, 실제 양성인 샘플을 음성으로 잘못 예측한 경우이다.
  • True Negatives (TN): 모델이 음성 클래스로 정확하게 예측한 샘플 수. 즉, 실제 음성인 샘플을 음성으로 올바르게 예측한 경우이다.

   혼동 행렬을 통해 분류 모델의 성능을 다양한 측면에서 평가할 수 있다. 이를 통해 다음과 같은 지표들을 계산할 수 있다.

  • 정확도(Accuracy): 전체 예측 중 올바르게 분류된 비율로, (TP + TN) / (TP + FP + FN + TN)으로 계산된다.
  • 정밀도(Precision): 양성으로 예측한 것 중에서 실제로 양성인 비율로, TP / (TP + FP)로 계산됩니다. 양성 클래스로 예측한 것 중에서 얼마나 정확한지를 측정한다.
  • 재현율(Recall): 실제 양성 중에서 양성으로 올바르게 예측된 비율로, TP / (TP + FN)로 계산됩니다. 실제 양성 중에서 얼마나 많은 것을 감지했는지를 측정한다.
  • F1 점수(F1 Score): 정밀도와 재현율의 조화 평균으로, 2 * (정밀도 * 재현율) / (정밀도 + 재현율)로 계산된다. 정밀도와 재현율의 균형을 측정하는 지표로, 불균형한 클래스 분포에서 유용하다.

    혼동 행렬을 분석함으로써 분류 모델이 어떤 유형의 오류를 만들고 있는지 이해할 수 있으며, 모델의 성능을 조정하거나 개선하는 데 도움이 됩니다.
   이제 앞서 언급한 숫자 5를 확인하는 이진 분류기로 직접확인을 해보자.

from sklearn.metrics import confusion_matrix
confusion_matrix(y_train_5, y_train_pred)
# 결과값 array([[53057, 1522], [1325, 4096]])

     결과값을 통해 TN은 53057, FP은 1522, FN은 1325, TP는 4096 임을 확인할 수 있다. 만약 FP와 FN이 0인 경우 완벽한 분류기(perfect classifier)라 한다. 이런 경우 학습 데이터의 과적합(overfitting)을 의심해야 한다. 설명을 쉽게하기 위해 결과를 다음과 같이 나왔다고 하자.

더보기
from sklearn.metrics import precision_scorem, recall_score, f1_socre
precision_score(y_train_5, y_train_pred)	# == 4096 / (4096 + 1522), 정밀도
recall_score(y_train_5, y_train_pred)	# == 4096 / (4096 + 1325), 재현율
f1_score(y_train_5, y_train_pred)	# f1-score

    이 표에서 정확도는 8/11, 정밀도는 3/4, 재현율은 3/5, F1 점수는 18/27 임을 알 수 있다. 즉, 이 예시를 통해 정확도보다 정밀도 혹은 재현율이 더욱 가치 있는 지표임을 확인할 수 있다. 특히, F1 점수(F1 Score)는 정밀도(Precision)와 재현율(Recall) 사이의 균형을 측정하는 지표로, 이 두 가지 지표를 조화 평균으로 결합한 것이다. F1 점수는 두 가지 상충하는 요소인 정밀도와 재현율 사이에서 균형을 찾을 때 유용하다.
    그러나 모든 상황에서 정밀도와 재현율을 동등하게 중요하게 생각하는 것은 항상 적절하지 않다. 어떤 상황에서는 정밀도를 주로 중요하게 생각하고, 다른 상황에서는 재현율을 중요하게 생각할 수 있다.
    예를 들어, 아이들을 위한 안전한 비디오를 감지하는 분류기를 훈련한다고 가정해보자. 이 경우, 아마도 정밀도가 낮더라도(즉, 좋은 비디오 중에서도 거부하는 경우가 많더라도) 안전한 비디오만 유지하는 분류기를 선호할 것이다. 그러나 정밀도가 높아도(안전하지 않은 비디오를 거의 허용하지 않더라도) 안전하지 않은 비디오가 제품에 표시되면 안 되므로 많은 좋은 비디오를 거부하는 분류기는 유용하지 않다.
    또 다른 예로, 감시 이미지에서 물건 훔치는 사람을 감지하는 분류기를 훈련한다고 가정해자. 이 경우, 정밀도가 낮더라도(즉, 가짜 경보가 몇 번 나타나더라도) 거의 모든 물건 훔치는 사람을 잡아내는 것이 중요할 수 있다.
    물론 정밀도와 재현율 두 수치 모두 높으면 좋지만 불행하게도, 정밀도와 재현율은 상충 관계(trade-off)에 있으며 한쪽을 높이면 다른 쪽은 낮아진다. 이를 "정밀도/재현율 트레이드오프"(precision/recall trade-off)라고 한다.

precision/recall trade-off

    윗 그림을 살펴보며 정밀도/재현율의 트레이드오프를 살펴보자. 만약 정밀도를 100%(3/3)으로 챙기면 재현율은 50%(3/6)의 결과를, 재현율을 100%(6/6) 챙긴다면 정밀도는 75%(6/8)의 결과를 가지게 된다. 이를 통해 어느 한쪽이 높아지면 다른 한쪽이 낮아지는 것을 확인할 수 있다. 그렇기에 적절한 임계값(threshold)을 찾아야한다.
    Scikit-Learn은 직접 임계값을 설정할 수는 없지만, 예측을 만들 때 사용하는 결정 점수에 액세스 할 수 있다. `decision_function()` 메서드를 호출하여 각 인스턴스에 대한 점수를 얻은 다음 이 점수를 기반으로 원하는 임계값을 사용하여 예측을 만들 수 있다.

더보기
y_scores = sgd_clf.decision_function([some_digit])
y_scores

threshold = 0
y_some_digit_pred = (y_scores>threshold)

threshold = 8000
y_some_digit_pred = (y_scores>threshold)
y_some_digit_pred

    적절한 임계값을 선택하는 방법 중 하나는 다음 단계를 따른다.

  1. `cross_val_predict()` 함수를 사용하여 훈련 세트의 모든 인스턴스의 점수를 얻으며, 이번에는 예측 대신 결정 점수(decision scores)를 반환하도록 지정한다.
  2. 이러한 점수를 사용하여 `precision_recall_curve()` 함수를 사용하여 모든 가능한 임계값에 대한 정밀도와 재현율을 계산한다.
  3. 이러한 값을 Matplotlib을 사용하여 임계값의 함수로 나타낸다.
# 1
y_scores = cross_val_predict(sgd_clf, X_train, y_train_5, cv=3, method="decision_function")

# 2
from sklearn.metrics import precision_recall_cureve

precisions, recalls, thresholds = precision_recall_curve(y_train_5, y_scores)

# 3
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")
    [...]	# 임계값 하이라이트, 그리드 추가

plot_precision_recall_vs_threshold(precisions, recalls, thresholds)
plt.show()

    또 다른 방법은 정밀도를 직접 재현율에 대한 그래프로 표시하는 것이다. 예를 들어, 정밀도를 90%로 설정하려고 결정했다고 가정해 보자. 첫 번째 그래프를 찾아보고 최소한 90%의 정밀도를 얻기 위해 사용해야 하는 임계값을 찾을 수 있다. 보다 정확한 결과를 얻으려면 정밀도가 적어도 90%인 가장 낮은 임계값을 검색할 수 있다.

threshold_90_precision = thresholds[np.argmax(precisions >= 0.90)] # ~7816
y_train_pred_90 = (y_scores >= threshold_90_precision)

precision_score(y_train_5, y_train_pred_90)

recall_score(y_train_5, y_train_pred_90)

The ROC(Receiver Operating Characteristic) Curve

    ROC(Receiver Operating Characteristic) 곡선은 이진 분류기와 함께 사용되는 일반적인 도구 중 하나이다. ROC 곡선은 진짜 양성 비율(TPR, True Positive Rate, 민감도로도 불림)거짓 양성 비율(FPR, False Positive Rate)에 대해 그린다. FPR은 음성 클래스로 잘못 분류된 샘플의 비율을 나타낸다. 다시 말하면, 민감도(TPR)가 높을수록 거짓 양성(FPR)도 증가한다. 점선은 완전히 무작위로 분류하는 분류기의 ROC 곡선을 나타내며, 좋은 분류기는 이 점선에서 최대한 멀리 떨어진 곳에 위치한다(왼쪽 상단 모서리 쪽으로).
    분류기를 비교하는 한 가지 방법은 ROC 곡선 아래 영역(AUC, area under the curve)을 측정하는 것입니다. 완벽한 분류기의 ROC AUC는 1이며, 완전히 무작위인 분류기의 ROC AUC는 0.5이다. 즉, 1에 가까울수록 좋은 모델이다.
    양성 클래스가 드물거나 거짓 양성보다 거짓 음성에 더 많은 관심이 있는 경우 PR 곡선을 선호한다. 그렇지 않은 대부분의 경우 ROC 곡선을 사용한다. 예를 들어, 5-감지 분류기의 경우 ROC 곡선은 분류기가 매우 우수해 보이지만 이는 양성(5)과 음성(5가 아닌 것)의 비율이 불균형하기 때문이다. 그에 반해 PR 곡선을 사용하면 분류기에 개선의 여지가 있다는 것을 더 명확하게 파악할 수 있다(곡선이 오른쪽 상단 코너에 더 가까워질 수 있음).

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, linewith=2, label=label)
    plt.plot([0,1],[0,1],'k--')
    [...]	# 축과 그리드 추가
  
plot_roc_curve(fpr, tpr)
plt.show()
더보기

    이제 RandomForestClassifier를 훈련하고 그것의 ROC 곡선과 ROC AUC 점수를 SGDClassifier의 것과 비교하는 예제를 살펴보자.

from sklearn.ensemble import RandomForestClassifier

forest_clf = RandomForestCalssifier(random_state=42)
y_probas_forest = cross_val_predict(forext_clf, X_train, y_train_5, cv=5, method="predict_proba")
y_scores_forest = y_probas_forest[:.1]
fpr_forest, tpr_forest, thresholds_forest = roc_cureve(y_train_5,y_scores_forest)

plt.plot(fpr, tpr, "b:", label="SGD")
plot_roc_curve(fpr_forest, tpr_forest, "Random Forest")
plt.legend(loc="lower right")
plt.show()

roc_auc_score(y_train_5, y_scores_forest)

Multiclass Classification

    다중 클래스 분류(Multiclass Classification)는 이진 분류자와 달리 개 이상의 클래스를 구별할 수 있는 분류자를 의미한다. 일부 알고리즘(Logistic Regression classifiers, Random Forest classifiers, naive Bayes classifiers)은 본질적으로 다중 클래스를 다룰 수 있다. 그러나 다른 알고리즘(SGD Classifiers or Support Vector Machine classifiers)은 엄격한 이진 분류자이다. 그러나 이러한 이진 분류자를 사용하여 다중 클래스 분류를 수행하는 다양한 전략이 있다.

One-Versus-the-Rest(OvR) 전략(또는 one-versus-all)

   0부터 9까지 10개의 클래스로 이미지를 분류하는 경우, 각 숫자(0-9, 클래스)에 대한 이진 분류자를 모두 학습한다. 즉, 0을 감지하는 분류자, 1을 감지하는 분류자, 2를 감지하는 분류자 등을 모두 학습한다. 이미지를 분류할 때, 해당 이미지에 대한 각 분류자의 결정 점수를 얻고, 가장 높은 점수를 가진 분류자의 클래스를 선택한다. 즉, 한 이미지에 모든 분류자를 대입하여 나온 값 중 가장 큰 값을 해당 클래스로 판단한다.

One-Versus-One(OvO) 전략

    각 숫자(클래스) 쌍(0과 1, 0과 2, 1과 2 등)에 대한 이진 분류자를 학습한다. 따라서 N개의 클래스가 있다면 N × (N - 1) / 2 개의 분류자를 학습해야 한다. 이 경우 이미지를 분류할 때, 해당 이미지를 모든 45개 분류자를 통해 실행하고 가장 많은 결과가 나온 클래스를 선택한다.
     Scikit-Learn은 이진 분류 알고리즘을 다중 클래스 분류 작업에 자동으로 적용하고 알고리즘에 따라 OvR 또는 OvO를 실행한다. 알고리즘에 따라 성능 및 계산 비용을 고려하여 OvO 또는 OvR 중 하나를 선택한다. 예를 들어, SVC(Support Vector Machine) 분류자는 대부분의 경우 OvR 전략을 사용하며, 작은 훈련 세트에서는 OvO 전략을 사용한다.

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

    Scikit-Learn을 사용하면 이진 분류자를 다중 클래스 분류 작업에 쉽게 적용할 수 있으며, 모든 클래스에 대한 결정 점수를 반환하고 가장 높은 점수를 가진 클래스를 선택할 수 있다. 예컨대, `decision_function()` 메서드를 사용하면 각 인스턴스에 대해 10개의 점수(클래스당 1개씩)를 얻을 수 있다.

some_digit_scores = svm_clf.decision_function([some_digit])
some_digit_scores

np.argmax(some_digit_scores)

svm_clf.classes_

svm_clf.classes_[5]

    SGD 분류자를 훈련할 때도 Scikit-Learn은 내부적으로 OvR 전략을 사용한다. 특정 문제에서 입력 데이터의 스케일링을 수행하면 정확도를 높일 수 있다.

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

sgd_clf.decision_function([some_digit])

cress_val_score(sgd_clf, X_train, y_train, cv=3, scoring="accuracy")

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

Error Analysis

    에러 분석(Error Analysis)은 모델의 성능을 더 잘 이해하고 향상하기 위한 중요한 단계 중 하나이다. 먼저, 혼동 행렬(Confusion Matrix)을 보면 각 클래스(레이블)에 대한 모델의 예측 결과와 실제 클래스 간의 관계를 보자.

from sklearn.metrics import ConfusionMatrixDisplay

y_train_pred = cross_val_predict(sgd_clf, X_train_scaled, y_train, cv=3)
ConfusionMatrixDisplay.from_predictions(y_train, y_train_pred)
plt.show()

ConfusionMatrixDisplay.from_predictions(y_train, y_train_pred, normalize="true", values_format=".0%")

plt.show()

    해당 그림은 각 클래스에 대한 모델의 예측 결과와 실제 클래스 간의 관계이기 때문에 에러가 시각적으로 보기 힘들다. 그렇기에 에러를 더 잘 드러나게 만들어야 한다. 아래 그림을 보면 백분율을 통해 에러 비율을 살펴볼 수 있다. 하지만 주의해야 할 점은 이 백분율이 전체 에러 중에서 해당 에러가 차지하는 비율임을 이해해야 한다. 즉, 한 카테고리의 에러 중에서 어떤 카테고리로 잘못 분류되었는지를 나타낸다. 아래 결과를 보면 8 혹은 3과 5에서 에러가 생기는 것을 확인할 수 있다.

sample_weight = (y_train_pred != y_train)
ConfusionMatrixDisplay.from_predictions(y_train, y_train_pred,
	sample_weight=sample_weight,
	normalize="true", values_format=".0%")
plt.show()

    개별 에러를 분석하는 것도 모델 동작 및 실패 원인을 이해하는 데 도움이 된다. 특정 클래스(3과 5)의 예시를 그려보면서 모델이 왜 잘못 분류했는지 파악할 수 있다. 3과 5의 경우, 모델은 선형 모델인 SGD 분류자를 사용했기 때문에 두 클래스의 차이를 이해하지 못하고 혼동한다. 3과 5의 주요 차이는 상단 선과 하단 아크를 연결하는 작은 선의 위치이다. 모델은 각 픽셀에 대해 클래스 별 가중치를 할당하고 새로운 이미지를 볼 때 이 가중치 된 픽셀 강도를 합산하여 각 클래스에 대한 점수를 얻는다. 따라서 3과 5는 몇 개의 픽셀만 다를 뿐이므로 이 모델은 쉽게 혼동한다. 이 모델은 이미지의 이동과 회전에 민감하다. 그래서 3과 5의 혼동을 줄이려면 이미지를 중앙에 정렬하고 회전을 조절하는 이미지 전처리가 도움이 될 것이다. 이러한 전처리는 다른 에러도 감소시킬 가능성이 높다.


Multilabel Classification

    다중 레이블(Multilabel) 분류는 각 인스턴스가 하나 이상의 클래스에 속할 수 있는 경우를 다룬다. 일반적인 분류 작업에서는 각 인스턴스가 하나의 클래스에만 속하도록 한다. 그러나 때로는 분류기가 각 인스턴스에 대해 여러 클래스를 출력하도록 하려는 경우도 있다. 예를 들어 하나의 이미지에서 여러 사람을 인식해야 하는 경우이다.
    다중 레이블 분류를 수행하기 위해 MNIST 데이터셋을 활용하고 KNeighborsClassifier를 훈련시키는 예제를 살펴보자. 아래 KNeighborsClassifier는 MNIST 데이터셋 중에서 숫자가 7 이상인지, 또한 해당 숫자가 홀수인 숫자인지를 구분하는 다중 레이블 분류이다.

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_knn_pred = cross_val_predict(knn_clf, X_train, y_multilabel, cv=3)
f1_score(y_multilabel, y_train_knn_pred, average="macro")

    다중 레이블 분류기를 평가하는 방법은 프로젝트의 요구 사항에 따라 다양하다. 하나의 접근 방법은 각 개별 레이블에 대한 F1 점수(또는 이전에 논의한 이진 분류자 메트릭 중 하나)를 측정하고 이 점수를 평균화하는 것이다. 다중 레이블 분류를 네이티브로 지원하지 않는 분류기(SVC 등)를 사용하려는 경우, 각 레이블 당 하나의 모델을 훈련시키는 전략을 사용할 수 있다. 그러나 이 전략은 레이블 간의 종속성을 캡처하기 어려울 수 있다. 이 문제를 해결하기 위해 모델을 체인 형태로 구성할 수 있다. 각 모델이 예측을 수행할 때, 해당 모델 앞에 있는 모든 모델의 예측 결과를 포함하여 입력 피처를 사용한다. 이를 ChainClassifier라고 한다.

from sklearn.multioutput import ClassifierChain

chain_clf = ClassifierChain(SVC(), cv=3, random_state=42)
chain_clf.fit(X_train[:2000], y_multilabel[:2000])

chain_clf.predict([some_digit])

Multioutput Classification

   다중 출력(Multioutput) 분류는 다중 출력-다중 클래스 분류(Multioutput-Multiclass classification)의 일반화된 형태로, 각 레이블이 다중 클래스(즉, 두 개 이상의 가능한 값)를 가질 수 있는 경우를 나타낸다. 예를 들어, 이미지에서 노이즈를 제거하는 시스템을 생각해 보자. 이 시스템은 노이즈가 있는 숫자 이미지를 입력으로 받아서 (희망적으로) 깨끗한 숫자 이미지를 출력으로 내보내는데, 출력은 픽셀 강도의 배열로 나타난다. 이때 분류자의 출력은 다중 레이블(각각의 픽셀에 대한 하나의 레이블)이며, 각 레이블은 여러 값(픽셀 강도는 0에서 255까지의 범위를 가짐)을 가질 수 있다. 분류와 회귀 사이의 경계가 가끔 모호할 수 있음에 주목해야 한다. 픽셀 강도를 예측하는 것은 분류보다는 회귀에 더 가깝다고 볼 수 있다. MNIST 이미지를 가져와 픽셀 강도에 노이즈를 추가하여 훈련 세트와 테스트 세트를 생성한 다음, 분류기를 훈련시키고 이미지를 정리하는 예제를 살펴보자.

noise = np.random.randint(0, 100, len(X_train), 784))
X_train_mod = X_train + noise
noise = np.random.randitn(0, 100, len(X_train), 784))
X_test_mod = X_test + noise
y_train_mod = X_train
y_test_mod = X_test

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

주섬주섬

  • Scikit-Learn은 인기 있는 데이터셋을 다운로드하기 위한 많은 도우미 함수를 제공하며, MNIST 데이터셋도 그중 하나이다.
  • 딕셔너리는 파이썬에 존재하는 시퀀스 중 하나로 키(key)와 값(value)이 매핑하여 저장된 자료 구조를 말한다.

참고

   윗 코드들과 데이터의 출처이다.

 

GitHub - ageron/handson-ml2: A series of Jupyter notebooks that walk you through the fundamentals of Machine Learning and Deep L

A series of Jupyter notebooks that walk you through the fundamentals of Machine Learning and Deep Learning in Python using Scikit-Learn, Keras and TensorFlow 2. - GitHub - ageron/handson-ml2: A ser...

github.com

 

반응형

댓글