본문 바로가기
로봇/인공지능, AI

[핸즈온 머신러닝 3판] 3.분류

by 33곰탱 2024. 9. 20.

1부 3장

오늘 정리할 부분은 3장 분류부분이다. 2장에서 머신러닝을 어떻게 하는지 간단하게 배웠으므로 오늘부터는 실전!!

3장 분류

  • 3.1 MNIST.
  • 3.2 이진 분류기 훈련
  • 3.3 성능 측정
  • 3.4 다중 분류
  • 3.5 오류 분석
  • 3.6 다중 레이블 분류
  • 3.7 다중 출력 분류

3.1 MNIST

이번 장에서는 고등학생과 미국 인구 조사국 직원들이 손으로 쓴 70000개의 작은 숫자 이미지인 MNIST 데이터를 사용한다. (매우 유명한 데이터셋이다.)

 

아래와 같은 코드를 통해 OpenML.org에서 MNIST 데이터를 불러올 수 있다.

from sklearn.datasets import fetch_openml

# 사이킷런 1.2에서 추가된 parser 매개변수 기본값이 1.4 버전에서 'liac-arff'에서 'auto'로 바뀝니다.
# 'auto'일 경우 희소한 ARFF 포맷일 때는 'liac-arff', 그렇지 않을 때는 'pandas'가 됩니다.
# 이에 대한 경고를 피하려면 parser='auto'로 지정하세요.
mnist = fetch_openml('mnist_784', as_frame=False)

 

sklearn.datasets 패키지에는 데이터 처리에 유용한 여러 함수가 포함되어 있다. 그중 가장 자주 사용되는 함수들은 아래 3개라고 한다.

 

  • fetch_* 인터넷에서 실전 데이터를 다운로드할 때 사용
  • load_*: 사이킷런에 내장된 소규모 데이터를 로드할 때 사용하며, 인터넷 연결이 필요 없음
  • make_*: 테스트나 연습을 위해 가짜 데이터를 생성할 때 사용

이 함수들은 보통 넘파이 배열 형식의 데이터와 레이블이 포함된 (X, y) 튜플을 반환한다고 한다.

 

또한 sklearn.utils.Bunch 객체로 반환되는 경우도 있는데, 이 객체는 딕셔너리와 유사하게 작동하며 다음과 같은 속성을 가진다.

  • DESCR: 데이터셋 설명
  • data: 입력 데이터, 일반적으로 2D 넘파이 배열
  • target: 레이블, 일반적으로 1D 넘파이 배열
# 추가 코드 - 조금 내용이 깁니다
print(mnist.DESCR)

잘 안보이니 확대해서 확인해보세용

 

그런데, fetch_openml() 함수는 기본적으로 데이터를 판다스 데이터프레임으로 반환하기 때문에, MNIST와 같은 이미지 데이터셋의 경우, 데이터프레임과 잘 맞지 않기 때문에 as_frame=False로 설정하여 넘파이 배열로 데이터를 받아 줘야 한다.

 

X.shape()로 확인 해보면 (70000, 784)의 결과와 y.shape()로 확인 해보면 (70000,)의 결과가 나온다.

 

이는 이미지가 70000개 있고 각 이미지에 784개의 특성이 있는 것으로 확인할 수 있는데 왜냐하면 이미지가 28*28의 픽셀로 이루어져 있기 때문이다..!  여기서 각각의 특성은 0 (흰색) ~ 255 (검은색) 까지의 픽셀 강도를 나타낸다.

 

X는 이미지 데이터라고 생각하면 되고 각 이미지가 어떤 숫자를 나타내는지에 대한 정답이 y에 저장 된다.

 

한번 이미지를 확인해보자!  1차원 배열이었던 784를 28 * 28의 2차원 배열로 바꿔주어 이미지를 출력해야한다.

import matplotlib.pyplot as plt

def plot_digit(image_data):
    image = image_data.reshape(28, 28)
    plt.imshow(image, cmap="binary")
    plt.axis("off")

some_digit = X[0]
plot_digit(some_digit)
save_fig("some_digit_plot")  # 추가 코드
plt.show()

이 그림은 숫자 5로 보인다.. 역시 y[0]을 통해 확인하면 5임을 확인할 수 있다.

 

이제 MNIST 데이터셋을 살펴보았는데 더 자세히 조사하기 전 테스트 세트를 만들고 따로 떼어놓아햐 한다.

 

그런데 fetch_openml()이 반환한 MNIST 데이터셋은 이미 훈련 세트와 테스트 세트(뒤쪽 10000개 이미지)로 나뉘어져 있다고 한다.

 

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

3.2 이진 분류기 훈련

문제를 단순화해서 숫자 5만 식별해보자. 이는 '5'와 '5 아님' 두 개의 클래스를 구분할 수 있는 이진 분류기이다.

 

먼저 타깃 벡터를 만들어주고

y_train_5 = (y_train == '5')  # 5는 True고, 다른 숫자는 모두 False
y_test_5 = (y_test == '5')

 

확률적 경사 하강법(SGD) 분류기로 훈련을 시켜보자. 여기서 some_digit은 아까 사용한 X[0] 데이터이다.

 

SGD는 확률적으로 선택된 하나의 데이터 포인트를 사용해 가중치를 업데이트하는 경사 하강법 알고리즘이라고 하는데 나중에 배운다고 한다.

from sklearn.linear_model import SGDClassifier

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

sgd_clf.predict([some_digit])

 

이 분류기는 이 이미지가 5(True)를 나타낸다고 추측했다! 정답을 맞췄군 굿굿

3.3 성능 측정

자 그럼 모델의 성능을 평가해 보자. 이번 장에서 가장 중요한거라고 생각함!

3.3.1 교차 검증을 사용한 정확도 측정

2장에서 배운 것처럼 교차 검증은 모델을 평가하는데 좋은 방법이다.

 

cross_val_score() 함수로 폴드가 3개인 k-fold 교차 검증을 사용해 위에서 사용한 SGD 모델을 평가해 보자

(훈련세트를 3개의 폴드로 나누고,  그 중 하나의 폴드를 검증 데이터로 사용하여 모델을 학습하고 평가하는 방법)

 

from sklearn.model_selection import cross_val_score

cross_val_score(sgd_clf, X_train, y_train_5, cv=3, scoring="accuracy")

정확도가 95% 이상이다..? 무려 95%이상 맞출 수 있다고 한다.

 

https://colab.research.google.com/github/rickiepark/handson-ml3/blob/main/03_classification.ipynb#scrollTo=7wflRzpeYIo_

 

03_classification.ipynb

Run, share, and edit Python notebooks

colab.research.google.com

코랩으로 들어가면 StratifiedKFold를 이용한 수동 교차 검증 코드도 확인할 수 있다. 

 

다음으로 '5 아님'으로 분류하는 더미 분류기를 만들어보자.

더미 분류기가 뭔가 해서 찾아 봤다.

 

더미 분류기(Dummy Classifier)는 사이킷런에서 제공하는 베이스라인 모델로, 매우 단순한 방식으로 예측을 수행하는 분류기입니다. 더미 분류기는 학습을 통해 데이터의 특성을 파악하지 않으며, 특정 규칙에 기반해 예측을 합니다. 이는 머신러닝 모델의 성능을 비교하기 위한 기준점(베이스라인)을 제공하는 역할을 합니다

from sklearn.dummy import DummyClassifier

dummy_clf = DummyClassifier()
dummy_clf.fit(X_train, y_train_5)
print(any(dummy_clf.predict(X_train)))

이 역시 90% 이상으로 나왔다. 

 

  • 여기서 중요한 점은, MNIST 데이터셋에서 숫자 '5'가 아닌 경우가 약 90%를 차지하기 때문에, 단순히 무작정 '5가 아니다'라고만 예측해도 정확도가 높게 나오는 상황이다.
  • 따라서 이 90%의 정확도는 모델이 데이터를 제대로 학습해서 나온 결과가 아니라, 불균형한 데이터셋에 기초한 잘못된 평가임을 보여주는 결과이다..!

 

3.3.2 오차 행렬

이 문제를 해결하기 위해서는 오차 행렬(confusion matrix)을 사용해, 각 클래스별로 모델이 얼마나 잘 예측했는지를 보는 것이 더 유용하다고 한다. 

 

오차 행렬은 모델이 예측한 결과와 실제 결과를 비교할 수 있도록 만든 표로. 클래스 A/B로 분류할 때, 각 샘플이 맞게 예측되었는지, 아니면 잘못된 클래스로 예측되었는지를 알 수 있다.

 

오차 행렬을 만들려면 실제 타깃과 비교할 수 있도록 예측값을 만들어야 한다. 테스트 세트로 예측을 만들 수 있지만 그러면 안되고 

cross_val_predict() 함수로 교차 검증을 수행하면서 훈련 세트의 각 샘플에 대해 예측을 만들면 된다.

from sklearn.model_selection import cross_val_predict

y_train_pred = cross_val_predict(sgd_clf, X_train, y_train_5, cv=3)

y_train_pred

 

이제 confusion_matrix() 함수를 사용해 오차 행렬을 만들어 보자

from sklearn.metrics import confusion_matrix

cm = confusion_matrix(y_train_5, y_train_pred)
cm

오차 행렬이 만들어 졌다. 오차 행렬의 실제 클래스를 나타내고 열은 예측한 클래스를 나타낸다! 

 

첫번 째 행은 '5 아님' 이미지에 대한 것으로 53892개를 '5 아님'으로 정확하게 분류했고(TP) 687개를 '5'라고 잘못 분류했다.(FP)

 

두번 째 행은 '5' 이미지에 대한 것으로 1891개를 '5 아님'으로 잘못 분류했고(FN) 3530개를 정확히 '5'라고 분류한 것임을 알 수 있다.

 

만약 완벽한 분류기라면 진짜 양성과 진짜 음성만 가지고 있을 거라서 오차 행렬의 주대각선 (왼쪽 위에서 오른쪽 아래로)가 0이 아닌 값이 된다고 생각하면 된다.

예시

오차 행렬의 구성:

  • True Positive (TP): 실제로 긍정(positive)이고, 모델도 긍정으로 예측한 경우
  • True Negative (TN): 실제로 부정(negative)이고, 모델도 부정으로 예측한 경우
  • False Positive (FP): 실제는 부정인데, 모델이 긍정으로 예측한 경우 (1종 오류)
  • False Negative (FN): 실제는 긍정인데, 모델이 부정으로 예측한 경우 (2종 오류)

 

오차 행렬이 많은 정보를 제공해주지만 가끔 더 요약된 지표가 필요할 때도 있다. 이 때 볼만한 것은 양성 예측의 정확도이다. 이를 정밀도(precision)라고 한다.

 

\[ \text{정밀도(precision)} = \frac{TP}{TP + FP} \]

정밀도는 모델이 '긍정'으로 예측한 것 중에서 실제로 긍정인 비율을 나타낸다. 즉, False Positive를 얼마나 줄였는지 평가하는 지표라고 생각하면 된다.

 

또한 정밀도는 재현율이라는 또 다른 지표와 같이 사용하는 것이 일반적이라고 한다.

 

\[ \text{재현율(precision)} = \frac{TP}{TP + FN} \]

재현율은 실제로 긍정인 것 중에서 모델이 긍정으로 예측한 비율로, False Negative를 얼마나 줄였는지 평가한다.

 

재현율은 실제 긍정인 것들 중에서 모델이 놓치지 않고 잡아낸 비율을 나타낸다!

 

3.3.3 정밀도와 재현율

우리의 친구 사이킷런은 정밀도와 재현율을 포함해 분류기의 지표를 계산하는 여러 함수를 제공하고 있다..!

아래는 그 코드이다.

from sklearn.metrics import precision_score, recall_score

precision_score(y_train_5, y_train_pred)  # == 3530 / (687 + 3530)

# 추가 코드 – TP / (FP + TP) 식으로 정밀도를 계산합니다
# cm[1, 1] / (cm[0, 1] + cm[1, 1])

recall_score(y_train_5, y_train_pred)  # == 3530 / (1891 + 3530)

# 추가 코드 – TP / (FN + TP) 식으로 정밀도를 계산합니다
# cm[1, 1] / (cm[1, 0] + cm[1, 1])

전과는 달리 성능이 좀 떨어진 모습을 확인할 수 있다. 전체 숫자 5에서 65.11~%만 감지했다..

 

정밀도와 재현율을 F1 점수(F1 Score)라고 하는 하나의 숫자로 만들면 편리해진다고 한다. F1점수는 정밀도와 재현율의 조화 평균인데 조화 평균이란 여러 값의 역수들의 평균을 취한 후, 그 값의 역수를 구하여 계산하는데 즉, 일반적인 산술 평균과는 다른 방식으로 계산되며, 작은 값에 더 큰 영향을 주는 특징이 있다고 한다.

 

\[F_1 = 2 \times \frac{\text{정밀도} \times \text{재현율}}{\text{정밀도} + \text{재현율}}\]

 

이 역시 사이킷런에 있다.. (대박대박)

 

from sklearn.metrics import f1_score

f1_score(y_train_5, y_train_pred)

# 추가 코드 – f1 점수를 계산합니다
# cm[1, 1] / (cm[1, 1] + (cm[1, 0] + cm[0, 1]) / 2)

 

정밀도와 재현율의 관계: F1 점수는 정밀도와 재현율 간의 균형을 평가하는 지표이지만, 모든 상황에서 이 균형이 이상적인 것은 아니고, 경우에 따라 정밀도가 더 중요한 경우가 있고, 재현율이 더 중요한 경우도 있다고 한다.

 

밑은 그 예시 이다.

 

  • 재현율이 중요한 상황: 예를 들어, 어린이를 대상으로 한 안전한 동영상 분류기에서는 재현율이 더 중요한 역할을 합니다. 안전한 동영상을 놓치는 일이 없도록, 가능한 모든 안전한 동영상을 식별하는 것이 중요합니다. 이런 상황에서는 높은 재현율이 필요합니다.
  • 정밀도가 중요한 상황: 반면에, 감시 카메라에서 범죄자를 잡아내는 시스템에서는 정밀도가 중요할 수 있습니다. 잘못된 경고(거짓 양성)를 너무 많이 발생시키지 않는 것이 목표일 때, 정밀도가 더 중요하게 작용합니다.

 

 

정밀도와 재현율은 보통 트레이드오프 관계에 있다고 한다. 즉, 정밀도를 높이면 재현율이 낮아지고, 재현율을 높이면 정밀도가 낮아지는 경향이 있어서, 사용자는 자신이 원하는 목표에 따라 두 지표 중 어느 것을 더 중시할지를 결정해야 한다고 한다.

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

바로 위에서 모델 성능에서 정밀도재현율은 서로 트레이드오프 관계에 있다고 말했다. 이를 이해하기 위해 SGDClassifier임계값을 조정하는 과정을 살펴보도록 하자.

 

모델은 각 샘플에 대해 결정 함수를 통해 점수를 계산하고, 이 점수를 기준으로 임계값을 설정하여 양성 클래스(1) 또는 음성 클래스(0)로 분류합니다. 임계값이 낮을수록 더 많은 샘플이 양성 클래스로 분류되고, 임계값이 높을수록 더 적은 샘플이 양성 클래스로 분류됩니다.

 

 

사이킷런에서 임곗값을 직접 지정할 수는 없다고 한다. 하지만 예측에 사용한 점수는 확인할 수 있는데 결정 함수(decision function)라는 점수(확률과 유사한 값)를 계산하여, 임계값을 넘는 경우 양성(1), 그렇지 않은 경우 음성(0)으로 분류하게 할 수 있다.

 

아래 코드는 결정 함수를 통해서 점수를 얻고 임계값을 설정하는 모습이다.

y_scores = sgd_clf.decision_function([some_digit])
y_scores

threshold = 0
y_some_digit_pred = (y_scores > threshold)
y_some_digit_pred

2164 > 0 => True

threshold = 3000
y_some_digit_pred = (y_scores > threshold)
y_some_digit_pred

2164 < 3000

 

그렇다면 적절한 임곗값을 어떻게 정할 수 있을까?

 

먼저 cross_value_predict()함수를 사용해 모든 샘플의 점수를 구해야 한다고 한다. 하지만 결과가 아니라 결정 점수를 반환하도록 지정해줘야 한다!

 

y_scores = cross_val_predict(sgd_clf, X_train, y_train_5, cv=3,
                             method="decision_function")

 

기본적으로 cross_val_predict()는 예측 결과(True/False)를 반환하지만, method="decision_function"을 사용하면 각 샘플에 대해 결정 함수 점수를 반환한다.

 

이 점수를 통해 precision_recall_curve() 함수를 사용하여 가능한 모든 임곗값에 대해 정밀도와 재현율을 계산할 수 있다.

from sklearn.metrics import precision_recall_curve

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

thresholds, precisions

 

임곗값이 3000일 때 그래프를 그려보자

plt.figure(figsize=(8, 4))  # 추가 코드
plt.plot(thresholds, precisions[:-1], "b--", label="Precision", linewidth=2)
plt.plot(thresholds, recalls[:-1], "g-", label="Recall", linewidth=2)
plt.vlines(threshold, 0, 1.0, "k", "dotted", label="threshold")

# 추가 코드 – 그림 3–5를 그리고 저장합니다
idx = (thresholds >= threshold).argmax()  # 첫 번째 index ≥ threshold
plt.plot(thresholds[idx], precisions[idx], "bo")
plt.plot(thresholds[idx], recalls[idx], "go")
plt.axis([-50000, 50000, 0, 1])
plt.grid()
plt.xlabel("Threshold")
plt.legend(loc="center right")
save_fig("precision_recall_vs_threshold_plot")

plt.show()

 

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

import matplotlib.patches as patches  # 추가 코드 – 구부러진 화살표를 그리기 위해서

plt.figure(figsize=(6, 5))  # 추가 코드

plt.plot(recalls, precisions, linewidth=2, label="Precision/Recall curve")

# extra code – just beautifies and saves Figure 3–6
plt.plot([recalls[idx], recalls[idx]], [0., precisions[idx]], "k:")
plt.plot([0.0, recalls[idx]], [precisions[idx], precisions[idx]], "k:")
plt.plot([recalls[idx]], [precisions[idx]], "ko",
         label="Point at threshold 3,000")
plt.gca().add_patch(patches.FancyArrowPatch(
    (0.79, 0.60), (0.61, 0.78),
    connectionstyle="arc3,rad=.2",
    arrowstyle="Simple, tail_width=1.5, head_width=8, head_length=10",
    color="#444444"))
plt.text(0.56, 0.62, "Higher\nthreshold", color="#333333")
plt.xlabel("Recall")
plt.ylabel("Precision")
plt.axis([0, 1, 0, 1])
plt.grid()
plt.legend(loc="lower left")
save_fig("precision_vs_recall_plot")

plt.show()

재현율 80% 근처에서 정밀도가 급격하게 줄어들기 시작한다. 책에서는 이 하강점 직전을 정밀도/재현율 트레이드오프로 선택하는 것이 좋다고 한다. 예를 들면 재현율이 60% 정도인 지점이라고 말한다..!

 

만약 정밀도 90%를 달성하는 것이 목표라고 가정하면, 그래프에서 사용할 임곗값을 찾을 수 있지만 정확하지 않다.

 

이때 다른 방법은 정밀도가 최소 90%가 되는 가장 낮은 임곗값을 찾는 것 이다. 이를 위해 넘파이 배열의 argmax() 메서드를 사용할 수 있는데, 이 메서드는 최댓값의 첫 번째 인덱스를 반환한다고 한다.

 

idx_for_90_precision = (precisions >= 0.90).argmax()
threshold_for_90_precision = thresholds[idx_for_90_precision]
threshold_for_90_precision

90 넘는 최소 임곗값

y_train_pred_90 = (y_scores >= threshold_for_90_precision)

precision_score(y_train_5, y_train_pred_90)

정밀도

recall_at_90_precision = recall_score(y_train_5, y_train_pred_90)
recall_at_90_precision

재현율

3.3.5 ROC 곡선

ROC 곡선은 수신기 조작 특성 곡선이라고도 불리는데, 이진 분류에서 많이 사용되는 도구이다.

정밀도/재현율 곡선과 매우 비슷하지만, ROC 곡선은 정밀도에 대한 재현율 곡선이 아닌 거짓 양성 비율에 대한 재현율의 곡선이다.

 

정밀도는 모델이 '긍정'으로 예측한 것 중에서 실제로 긍정인 비율였다면 거짓 양성 비율'긍정'으로 잘못 분류된 음성 샘플의 비율이다.  

 

 

  • True Positive Rate (TPR): 재현율과 동일하게 실제 양성인 샘플 중에서 모델이 올바르게 양성으로 예측한 비율 \[ TPR = \frac{TP}{TP + FN} \]
  • False Positive Rate (FPR): 실제 음성인 샘플 중에서 모델이 잘못 양성으로 예측한 비율 \[ FPR = \frac{FP}{FP + TN} \]

사이킷런의 roc_curve 함수를 사용하여 ROC 곡선을 그릴 수 있다.

from sklearn.metrics import roc_curve

fpr, tpr, thresholds = roc_curve(y_train_5, y_scores)

idx_for_threshold_at_90 = (thresholds <= threshold_for_90_precision).argmax()
tpr_90, fpr_90 = tpr[idx_for_threshold_at_90], fpr[idx_for_threshold_at_90]

plt.figure(figsize=(6, 5))  # 추가 코드
plt.plot(fpr, tpr, linewidth=2, label="ROC curve")
plt.plot([0, 1], [0, 1], 'k:', label="Random classifier's ROC curve")
plt.plot([fpr_90], [tpr_90], "ko", label="Threshold for 90% precision")

# 추가 코드 – 그림 3–7을 그리고 저장합니다
plt.gca().add_patch(patches.FancyArrowPatch(
    (0.20, 0.89), (0.07, 0.70),
    connectionstyle="arc3,rad=.4",
    arrowstyle="Simple, tail_width=1.5, head_width=8, head_length=10",
    color="#444444"))
plt.text(0.12, 0.71, "Higher\nthreshold", color="#333333")
plt.xlabel('False Positive Rate (Fall-Out)')
plt.ylabel('True Positive Rate (Recall)')
plt.grid()
plt.axis([0, 1, 0, 1])
plt.legend(loc="lower right", fontsize=13)
save_fig("roc_curve_plot")

plt.show()

 

여기에서도 볼 수있듯이 트레이드오프가 있는데, 재현율(TPR)이 높을 수록 거짓 양성 비율(FPR)이 높아지는 것을 알 수 있다. 점선은 완전한 랜덤 분류기의 ROC 곡선으로 좋은 분류기는 이 점선에서 최대한 멀리 떨어져 있어야 한다고 한다!

 

여기서 곡선 아래의 면적(AUC)을 측정해 분류기들을 비교할 수 있다. 완벽할 수록 AUC는 1에 가까워진다.

이 역시 사이킷런에 있다....!!!!

from sklearn.metrics import roc_auc_score

roc_auc_score(y_train_5, y_scores)

이제 랜덤포레스트SGD의 PR(정밀도/재현율) 곡선과 F1 점수를 비교해보자 

 

precision_recall_curve() 함수는 모델이 반환한 점수에 대해 다양한 임계값을 기준으로 정밀도 재현율값을 계산하는 데 사용되지만 랜덤포레스트분류기에서는 작동 방식 때문에 결정함수를 제공하지 않는다고 한다... 그래도 각 샘플에 대한 클래스 확률을 반환하는 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_probas_forest[:2]

랜덤포레스트분류기는 첫 번째 이미지를 89%의 확률로 양성, 두 번째 이미지를 99% 확률로 음성이라고 예측한다.

 

두 번째 열에 양수 클래스에 대한 추정 확률이 포함되어 있으므로 이를 precision_recall_curve() 함수에 전달하면 PR 곡선을 그릴 수 있게 된다!

y_scores_forest = y_probas_forest[:, 1]
precisions_forest, recalls_forest, thresholds_forest = precision_recall_curve(
    y_train_5, y_scores_forest)
    
plt.figure(figsize=(6, 5))  # 추가 코드

plt.plot(recalls_forest, precisions_forest, "b-", linewidth=2,
         label="Random Forest")
plt.plot(recalls, precisions, "--", linewidth=2, label="SGD")

# 추가 코드 – 그림 3–8을 그리고 저장합니다
plt.xlabel("Recall")
plt.ylabel("Precision")
plt.axis([0, 1, 0, 1])
plt.grid()
plt.legend(loc="lower left")
save_fig("pr_curve_comparison_plot")

plt.show()

 

y_train_pred_forest = y_probas_forest[:, 1] >= 0.5  # 양성 확률 ≥ 50%
f1_score(y_train_5, y_train_pred_forest)

F1 점수

roc_auc_score(y_train_5, y_scores_forest)

AUC 점수

 

랜덤포레스트분류기의 PR 곡선과 F1, ROC AUC 점수가 더 뛰어난 것을 확인할 수 있다..!!

3.4 다중 분류

이전에 사용한 SGDClassifier 분류기는 이진 분류기였다. 이진 분류기는 두 개의 클래스를 구별하는 반면RandomForestClassifier의 경우는 다중 분류기로 둘 이상의 클래스를 구별할 수 있었다.. (몰랐지만 이제 알았다)

 

그렇다면 둘 이상의 클래스를 구별하려고 할 때 다중 분류기를 꼭 써야할까? 그렇지는 않다.

이진 분류기를 여러개 사용하여 다중 클래스를 분류하는 기법도 있다. 책에서 2가지를 소개해준다.

 

  • OvR 전략 (One-vs-Rest):
    • 각 클래스와 나머지 클래스를 구분하는 이진 분류기를 학습시켜 전체 시스템을 구성하는 방법.
    • 예를 들어 특정 숫자 하나만 구분하는 숫자별 이진 분류기 10개(0부터 9까지)를 훈련시켜 클래스가 10개인 숫자 이미지 분류 시스템을 만들 수 있고, 이미지를 분류할 때 각 분류기의 결정 점수 중에서 가장 높은 것을 클래스로 선택하는 방법. 이를 OvR(One-versus-the-Rest)라고 한다다.
  • OvO 전략 (One-vs-One):
    • 클래스 간의 모든 조합에 대해 이진 분류기를 학습시키는 방법 (0과1, 0과2, 1과2 구별등..). N개의 클래스가 있을 경우, N*(N-1)/2개의 분류기를 학습시켜야 합니다. 예를 들어 MNIST 문제처럼 10개의 숫자를 분류할 때, 45개의 이진 분류기를 학습해야 한다.
    • 장점은 각 분류기의 훈련에 전체 훈련 세트중 구별할 두 클래스에 해당하는 샘플만 있으면 된다는 점! 

 대부분의 이진 분류 알고리즘에서는 OvR을 선호한다. 하지만 일부 알고리즘은 (SVM같은) 훈련 세트의 크기에 민감해서 큰 훈련 세트에서 몇 개의 분류기를 훈련 시키는 것보다는 작은 훈련 세트에서 많은 분류기를 훈련시키는 쪽이 빠르므로 OvO를 선호한다고 한다.

 

사이킷런에 있는 다양한 모델들이 있지만 다중 클래스 분류 작업에 이진 분류 알고리즘을 선택하면 사이킷런이 알고리즘에 따라 알아서 OvR 또는 OvO를 선택한다고 한다. SVM을 사용해서 테스트 해보자..!

 

from sklearn.svm import SVC

svm_clf = SVC(random_state=42)
svm_clf.fit(X_train[:2000], y_train[:2000])  # y_train_5가 아니고 y_train을 사용합니다.

svm_clf.predict([some_digit])

이 코드는 5를 구별한 타깃 클래스(y_train_5) 대신 0에서 9까지의 원래 타깃 클래스(y_train)를 사용해 SVM을 훈련시킨다. (2개보다 많은) 10개의 클래스가 있기 때문에 사이킷런은 OvO 전략을 사용해 45개의 이진 분류기를 훈련한다. (신기신기)

 

또한 5를 맞추기도 했다!! (some_digit 은 X[0]이었던것을 기억하자)

 

이 코드는 실제로 클래스 쌍마다 하나씩 45번의 예측을 수행하여 가장 많은 쌍에서 승리한 클래스를 선택한 것으로 decision_function() 메서드를 호출하면 샘플마다 총 10개의 점수(클래스마다 하나씩)를 반환하는 것을 볼 수 있다. 여기서 각 클래스는 동률 문제를 해결하기 위해 분류기 점수를 기반으로 각 쌍에서 이긴 횟수에 약간의 조정 값(최대 ±0.33)을 더하거나 뺀 점수를 얻게 된다고 한다.. (오!!) 코드로 확인 해보자.

 

some_digit_scores = svm_clf.decision_function([some_digit])
some_digit_scores.round(2)

class_id = some_digit_scores.argmax()
class_id

클래스에서 5가 9.3으로 가장 높은 것을 확인할 수 있다!

 

분류기가 훈련될 때 classes_ 속성에 타깃 클래스의 리스트를 값으로 정렬하여 저장한다고 한다. 무슨말인가 하면 코드로 봐보자

svm_clf.classes_

여기서는 인덱스 5번의 값이 5임을 알 수 있다

svm_clf.classes_[class_id]

클래스 5? 인덱스 5? 헷갈리면 그냥 위의 코드를 사용해서 뭔지 알아내자..^^

 

사이킷런에서 OvO나 OvR을 사용하도록 강제하려면 OneVsOneClassifierOneVsRestClassifier를 사용한다. 간단하게 이진 분류기 인스턴스를 만들어 객체를 생성할 때 전달하면 되는데 심지어 이진 분류기일 필요도 없다고 한다. 예를 들어 다음 코드를 봐보자 SVM 기반으로 OvR을 강제로 사용하게 한다. 

from sklearn.multiclass import OneVsRestClassifier

ovr_clf = OneVsRestClassifier(SVC(random_state=42))
ovr_clf.fit(X_train[:2000], y_train[:2000])

ovr_clf.predict([some_digit])

len(ovr_clf.estimators_)

OvR을 사용해서 10개의 분류기 개수가 만들어 진것을 확인할 수 있다.

 

또 다른 코드를 보자 밑의 코드는 SGDClassifier 코드이다.

sgd_clf = SGDClassifier(random_state=42)
sgd_clf.fit(X_train, y_train)
sgd_clf.predict([some_digit])

sgd_clf.decision_function([some_digit]).round()

decision_function() 메서드는 클래스마다 하나의 값을 반환한다. SGD 분류기가 각 클래스에 부여한 점수를 확인해보면 클래스 3번 값이 1824로 가장 높고 클래스 5번 값이 -1386으로 그 다음이다..

 

물론 이 분류기를 두 개 이상의 이미지에서 평가해보자. 각 클래스마다 거의 같은 개수의 이미지가 있기 때문에 정확도 지표가 좋다고 한다. 이전처럼 cross_val_score() 함수를 사용하면 될듯 하다.

 

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

cross_val_score() 함수를 사용해 모델을 평가한 결과, 85% 이상의 정확도를 기록했다. 성능은 나쁘지 않으나, 추가적으로 입력 데이터의 스케일링을 적용하면 정확도를 89% 이상으로 높일 수 있다고 한다.

from sklearn.preprocessing import StandardScaler

scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train.astype("float64"))
cross_val_score(sgd_clf, X_train_scaled, y_train, cv=3, scoring="accuracy")

3.5 오류 분석

가능성이 높은 모델을 하나 찾았다고 가정하고, 이 모델의 성능을 높일 방법을 찾아보자 이중 한가지 방법은 생성된 오류의 종류를 분석하는 것이다..! 

 

먼저 오차 행렬을 살펴보기 위해 cross_val_predict() 함수를 사용해 예측을 만들고 confusion_matrix() 함수를 호출한다. 그다음 앞에서 했던 것처럼 confusion_matrix() 함수에 레이블과 예측을 전달할 수 있게된다! 하지만 클래스가 2개가 아니라 10개라서 오차 행렬에 상당히 많은 숫자가 포함되므로 읽기 어려울 것 이다..

따라서 이를 방지하고자 오차 행렬을 컬러 그래프로 나타내면 분석하기가 훨씬 쉬워진다. 그래프를 그리려면 다음과 같
이 ConfusionMatrixDisplay.from_predictions() 함수를 사용하면 된다!!

from sklearn.metrics import ConfusionMatrixDisplay

y_train_pred = cross_val_predict(sgd_clf, X_train_scaled, y_train, cv=3)
plt.rc('font', size=9)  # 추가 코드 - 폰트 크기를 줄입니다
ConfusionMatrixDisplay.from_predictions(y_train, y_train_pred)
plt.show()

 

아래의 코드는 위 코드에 정규화를 진행하는 코드이다.

plt.rc('font', size=10)  # 추가 코드
ConfusionMatrixDisplay.from_predictions(y_train, y_train_pred,
                                        normalize="true", values_format=".0%")
plt.show()

 

 

 

오른쪽 이미지를 확인하면 5 이미지의 82%만이 올바르게 분류되었다는 것을 알 수 있다. 모델이 5 이미지에서 가장 많이 범한 오류는 8로 잘못 분류한 것인데 전체 5의 10%에서 이러한 오류가 발생했다. 하지만 8은 2%만이 5로 잘못 분류된 것을 보면 오차 행렬은 일반적으로 대칭이 아니란 것을 알 수 있다!

 

주의 깊게 살펴보면 많은 숫자가 8로 잘못 분류되었지만 이 그래프를 보자마자 오류에 대해 알기는 어렵다. 따라서 오류를 더 눈에 띄게 만들고 싶다면 올바른 예측에 대한 가중치를 0으로 설정해보자. 

 

# 추가 코드 – 그림 3–10을 생성하고 저장합니다
fig, axs = plt.subplots(nrows=1, ncols=2, figsize=(9, 4))
plt.rc('font', size=10)
ConfusionMatrixDisplay.from_predictions(y_train, y_train_pred, ax=axs[0],
                                        sample_weight=sample_weight,
                                        normalize="true", values_format=".0%")
axs[0].set_title("Errors normalized by row")
ConfusionMatrixDisplay.from_predictions(y_train, y_train_pred, ax=axs[1],
                                        sample_weight=sample_weight,
                                        normalize="pred", values_format=".0%")
axs[1].set_title("Errors normalized by column")
save_fig("confusion_matrix_plot_2")
plt.show()
plt.rc('font', size=14)  # 폰트 크기를 다시 키웁니다

 

 

이제 분류기가 어떤 종류의 오류를 범하는지 훨씬 더 명확하게 확인할 수 있다!! 클래스 8의 열이 매우 밝은 것으로 보아 많은 이미지가 8로 잘못 분류되었음을 알 수 있는데, 이 그래프에서 8로 잘못 분류하는게 모든 클래스에서 가장 많이 발생하는 분류 오류인 것 같다. 하지만 여기서 주의할 것이 있는데 7번 행, 9번 열의 36%는 모든 7 이미지 중 36%가 9로 잘못 분류되었다는 뜻이 아닌 7 이미지에서 발생한 오류 중 36%가 9로 잘못 분류되었다는 의미를 갖기 때문에 주의해서 받아 들이자..!!

오차 행렬을 행 단위가 아닌 열 단위로 정규화할 수도 있다. normalize="pred"로 지정하면 오른쪽과 그래프와 같은 결과를 얻을 수 있다. 예를 들어 오른쪽 그래프에서는 잘못 분류된 7의 56%가 실제로는 9라는 것을 알 수 있다.!

오차 행렬을 분석하면 분류기의 성능 향상 방안에 관해 생각해 볼수 있다. 여기서는 8로 잘못 분류되는 것이 많기 때문에 이를 줄이도록 개선할 필요가 있다는 생각을 할 수 있을 것이다! 이를 위해서 8처럼 보이는 숫자의 훈련 데이터를 더 많이 모아서 실제 8과 구분하도록 분류기를 학습시키거나, 또는 분류기에 도움될 만한 특성을 더 찾아봐야 한다고 한다. 예를 들어 동심원의 수를 세는 알고리즘을 작성하거나(8은 2개, 6은 1개, 5는 0개) 동심원과 같은 패턴이 드러나도록 (Scikit-Image, Pillow, OpenCV 등을 사용해서) 이미지를 전처리 해볼 수 있다.

 

오차 행렬 스타일로 3과 5의 샘플을 봐보자.

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

# 추가 코드 – 그림 3–11을 생성하고 저장합니다
size = 5
pad = 0.2
plt.figure(figsize=(size, size))
for images, (label_col, label_row) in [(X_ba, (0, 0)), (X_bb, (1, 0)),
                                       (X_aa, (0, 1)), (X_ab, (1, 1))]:
    for idx, image_data in enumerate(images[:size*size]):
        x = idx % size + label_col * (size + pad)
        y = idx // size + label_row * (size + pad)
        plt.imshow(image_data.reshape(28, 28), cmap="binary",
                   extent=(x, x + 1, y, y + 1))
plt.xticks([size / 2, size + pad + size / 2], [str(cl_a), str(cl_b)])
plt.yticks([size / 2, size + pad + size / 2], [str(cl_b), str(cl_a)])
plt.plot([size + pad / 2, size + pad / 2], [0, 2 * size + pad], "k:")
plt.plot([0, 2 * size + pad], [size + pad / 2, size + pad / 2], "k:")
plt.axis([0, 2 * size + pad, 0, 2 * size + pad])
plt.xlabel("Predicted label")
plt.ylabel("True label")
save_fig("error_analysis_digits_plot")
plt.show()

분류기가 잘못 분류한 숫자의 일부는 정말 잘못 쓰여 있어서 (3을 5처럼 적어놨네;;;;) 사람도 분류하기 어려울 수도 있겠다... 하지만 대부분의 잘못 분류된 이미지는 확실한 오류인 것 같다..

 

아래는 책의 일부를 가져왔다.

 

사람의 뇌가 환상적인 패턴 인식 시스템이라는 것을 기억하세요. 시각 시스템에서는 어떤 정보가 인식되기 전에 매우 많은 전처리를 수행합니다. 그래서 간단해 보여도 진짜로 간단한 일이 아닐 수 있습니다. 이 예제는 선형 모델인 SGDClassifier를 사용한다는 점을 기억해둡시다. 선형 분류기는 클래스마다 픽셀에 가중치를 할당하고 새로운 이미지에 대해 단순히 픽셀 강도의 가중치 합을클래스의 점수로 계산합니다. 그러므로 몇 개의 픽셀만 다른 3과 5를 모델이 쉽게 혼동하게 됩니다.

3과 5의 주요 차이는 위쪽 선과 아래쪽 호를 이어주는 작은 직선의 위치입니다. 숫자 3을 쓸 때 연결 부위가 조금 왼쪽으로 치우치면 분류기가 5로 분류하고 그 반대도 마찬가지입니다. 다른 말로 하면 분류기는 이미지의 위치나 회전 방향에 매우 민감합니다. 3과 5의 오류를 줄이는 한 가지 방법은 이미지를 중앙에 위치시키고 회전되어 있지 않도록 전처리하는 것입니다. 하지만 각 이미지에 대해 정확한 회전을 예측해야 하므로 쉽지 않을 수 있습니다. 이보다 훨씬 간단한 접근 방식은 훈련 이미지를 약간 이동시키거나 회전된 변형 이미지로 훈련 집합을 보강하는 것입니다. 이렇게 하면 모델이 이러한 변형에 더 잘 견디도록 학습하게 됩니다. 이를 데이터 증식(Data augmentation)이라고 한다고 한다. (14장에서 다룬다고 하네요.)

3.6 다중 레이블 분류

지금까지는 각 샘플이 하나의 클래스에만 할당되었었다. 하지만 분류기가 샘플마다 여러 개의 클래스를 출력해야 할 때도 있다. 얼굴 인식 분류기를 생각해보자. 만약 사진에 여러 사람이 등장한다면 인식된 사람마다 하나씩 표를 붙여야 할것이다..!

 

만약 분류기가 앨리스, 밥, 찰리 세 얼굴을 인식하도록 훈련되었다고 가정했을 때, 분류기가 앨리스와 찰리가 있는 사진을 본다면 [1, 0, 1]을 출력해야 할 것이다. ('앨리스 있음, 밥 없음, 찰리 있음'). 이처럼 여러 개의 이진 꼬리표를 출력하는 분류 시스템을 다중 레이블 분류(Multilabel classification) 시스템이라고 한다!!!

여기서는 조금 더 간단한 예시를 살펴본다.

import numpy as np
from sklearn.neighbors import KNeighborsClassifier

y_train_large = (y_train >= '7')
y_train_odd = (y_train.astype('int8') % 2 == 1)
y_multilabel = np.c_[y_train_large, y_train_odd]

knn_clf = KNeighborsClassifier()
knn_clf.fit(X_train, y_multilabel)

 

이 코드는 각 숫자 이미지에 두 개의 타깃 레이블이 담긴 y_multilabel 배열을 만드는데, 첫 번째는 숫자가 큰 값(7, 8, 9)인지 나타내고 두 번째는 홀수인지 나타낸다.

 

아래의의 코드는 KNeighborsClassifier 인스턴스를 만들고 다중 타깃 배열을 사용하여 훈련시키는 과정이다. (KNeighborsClassifier는 다중 레이블 분류를 지원하지만 모든 분류기가 그런 것은 아니라고 한다). 이제 예측을 만들어 보자!

(인스턴스가 뭔가해서 찾아 봤는데 KNeighborsClassifier()는 클래스이고, knn_clf는 그 클래스의 인스턴스라고 한다.)

knn_clf.predict([some_digit])


숫자 5는 7보다 크지 않고(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")


여기서서 이 코드는 모든 레이블의 가중치가 같다고 가정한 것이다. 만약 앨리스 사진이 밥이나 찰리 사진보다 훨씬 많다면 앨리스 사진에 대한 분류기의 점수에 더 높은 가중치를 둬야 한다..! 간단한 방법은 레이블에 클래스의 지지도(타깃 레이블에 속한 샘플 수)를 가중치로 주는 것이라고 한다. 이렇게 하려면 f1_score() 함수를 호출할 때 average="weighted"로 설정해주자

 

여기서 지지도

  • 각 클래스에 속한 샘플 수가 다를 때, 모든 클래스에 대해 동일하게 평가하는 것이 아닌, 샘플 수에 비례해 중요도를 부여하는 방식이다.
  • 예를 들어, 클래스 A에 속한 샘플이 1,000개이고 클래스 B에 속한 샘플이 100개라면, 가중치를 부여할 때 클래스 A의 영향력이 더 크도록 반영할 수 있다. 이렇게 하면 데이터 분포가 불균형한 상황에서도 모델 성능을 보다 정확히 평가할 수 있다.


SVC와 같이 다중 레이블 분류를 기본적으로 지원하지 않는 분류기를 사용할 때, 각각의 모델을 체인(chain)으로 연결해 학습하는 전략을 사용할 수 있다고 하는데. 좋은 소식은 사이킷런에 ClassifierChain 클래스가 있다고 한다!!!! 이를 활용하면 각 레이블의 예측을 순차적으로 학습시키는 체인 방식으로 구현할 수 있으며, 훈련 세트에서 정확도를 높일 수 있다고 하는데 체인이 뭔지 GPT에게 물어봤다.

 

체인(Chain)은 머신러닝이나 프로그래밍에서 연결된 일련의 작업이나 단계를 의미합니다. 각 단계가 앞선 단계의 출력을 받아 다음 단계의 입력으로 사용하는 방식입니다.

 

아하

 

 

아래의 코드를 통해서

from sklearn.multioutput import ClassifierChain

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


이제 ClassifierChain을 사용해 예측을 만들 수 있어진다 야호

chain_clf.predict([some_digit])

 

3.7 다중 출력 분류

마지막으로 알아볼 분류 작업은 다중 출력 다중 클래스 분류(ultioutput-muliclass classification) 또는다중 출력 분류(multioutput classification)이다. 이는 다중 레이블 분류에서 한 레이블이 다중 클래스가 될 수 있도록 일반화한 것입니다(즉, 값을 두 개 이상 가질 수 있습니다).

 

다중 레이블 분류와 뭐가 다르지..?

 

다중 레이블 분류

  • 하나의 입력에 대해 여러 개의 같은 유형의 레이블을 예측하는 문제입니다.
  • 예를 들어, 하나의 사진이 여러 카테고리에 동시에 속할 수 있을 때 사용됩니다.
    • 예: 사진에 개와 고양이가 같이 찍혀 있다면, "개"와 "고양이"라는 두 가지 레이블이 모두 해당됩니다.

다중 출력 분류

  • 하나의 입력에 대해 여러 개의 다른 유형의 레이블을 예측하는 문제입니다.
  • 예를 들어, 하나의 입력에 대해 여러 가지 다른 정보를 동시에 예측할 때 사용됩니다.
    • 예: 한 사람의 사진을 보고, 그 사람의 감정(기쁨, 슬픔)과 옷 색상(빨강, 파랑)을 동시에 예측해야 한다면, 이건 다중 출력 문제입니다. 감정과 옷 색상은 서로 다른 유형의 정보이기 때문입니다.

핵심 차이:

  • 다중 레이블 분류: 같은 종류의 레이블을 여러 개 예측 (예: 여러 개의 장르, 여러 개의 동물 등).
  • 다중 출력 분류: 다른 종류의 정보를 동시에 예측 (예: 감정 + 옷 색상처럼 서로 다른 정보를 한 번에).

책에서는 이를 설명하기 위해서 이미지에서 잡음을 제거하는 시스템을 만들어보기로 한다.

 

시스템은 잡음이 많은 숫자 이미지를 입력으로 받아 깨끗한 숫자 이미지를 MNIST 이미지처럼 픽셀의 강도를 담은 배열로 출력한다. 분류기의 출력이 다중 레이블(픽셀당 한 레이블)이고 각 레이블은 값을 여러 개(0부터 255까지 픽셀 강도) 가진다.. 그러므로 이 예는 다중 출력 분류 시스템이라고 말한다.

 

약간 이해가 안가서 GPT에게 물어봤다

 

왜 이 경우는 다중 출력인가?

이 예에서는 숫자 이미지의 각 픽셀이 독립적인 출력이 됩니다. 이미지의 각 픽셀은 0에서 255까지의 강도 값을 가질 수 있으므로, 각 픽셀에 대해 예측을 해야 합니다. 즉, 하나의 이미지에 대해 **수많은 출력값(픽셀별 강도)**을 예측하는 것이고, 이 출력들이 서로 독립적입니다.

  • 각 픽셀은 하나의 레이블이지만, 이 픽셀 값들이 모두 서로 독립적이므로, 다중 출력 분류 문제로 볼 수 있습니다.

결론:

  • 다중 레이블 분류는 여러 레이블이 하나의 카테고리 내에서 동시에 예측되는 경우(예: 한 이미지에 "개"와 "고양이"가 동시에 포함된 경우)입니다.
  • 다중 출력 분류는 각 출력이 서로 다른 예측을 필요로 하는 경우입니다. 이 예시에서는 이미지의 각 픽셀이 하나의 독립적인 출력이기 때문에 다중 출력 분류에 해당합니다.

 

먼저 MNIST 이미지에서 추출한 훈련 세트와 테스트 세트에 넘파이의 randint() 함수를 사용하여 픽셀 강도에 잡음을 추가 해주자. 그러면 타깃 이미지는 원본 이미지가 될 것이다.

 

np.random.seed(42)  # 동일하게 재현되게 하려고 지정합니다
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

# 추가 코드 – 그림 3–12을 생성하고 저장합니다
plt.subplot(121); plot_digit(X_test_mod[0])
plt.subplot(122); plot_digit(y_test_mod[0])
save_fig("noisy_digit_example_plot")
plt.show()

 

테스트 세트에서 이미지를 하나 선택하자 여기선 

왼쪽이 잡음이 섞인 입력 이미지이고 오른쪽이 깨끗한 타깃 이미지입니다. 분류기를 훈련시켜 이 이미지를 깨끗하게 만들어보는 것이 목표이다.

 

knn_clf = KNeighborsClassifier()
knn_clf.fit(X_train_mod, y_train_mod)
clean_digit = knn_clf.predict([X_test_mod[0]])
plot_digit(clean_digit)
save_fig("cleaned_digit_example_plot")  # 추가 코드 – 그림 3–13을 저장합니다
plt.show()

 

 

깨끗해진 모습을 확인할 수 있다!!!

 

 

이로써 3장이 끝났다 장수가 적어 금방 할줄 알았는데 생각보다 오래걸려서 힘들었다..

머신러닝 어렵다 어려워 허헣