소개
이 실습에서는 scikit-learn 을 사용하여 파이썬에서 시그모이드 보정을 활용한 3 클래스 분류 방법을 보여줍니다. 시그모이드 보정이 예측 확률을 어떻게 변경하고 모델 성능을 개선하는 데 어떻게 활용될 수 있는지 보여줍니다.
VM 팁
VM 시작이 완료되면 왼쪽 상단 모서리를 클릭하여 Notebook 탭으로 전환하여 Jupyter Notebook을 연습에 사용할 수 있습니다.
때때로 Jupyter Notebook 이 완전히 로드되기까지 몇 초 정도 기다려야 할 수 있습니다. Jupyter Notebook 의 제한으로 인해 작업 검증은 자동화될 수 없습니다.
학습 중 문제가 발생하면 Labby 에게 문의하십시오. 세션 후 피드백을 제공하면 문제를 신속하게 해결해 드리겠습니다.
데이터
2,000 개의 샘플, 2 개의 특징, 3 개의 목표 클래스를 가진 분류 데이터셋을 생성합니다. 다음과 같이 데이터를 분할합니다.
- train: 분류기를 학습하는 데 사용하는 600 개의 샘플
- valid: 예측 확률을 보정하는 데 사용하는 400 개의 샘플
- test: 1,000 개의 샘플
import numpy as np
from sklearn.datasets import make_blobs
np.random.seed(0)
X, y = make_blobs(
n_samples=2000, n_features=2, centers=3, random_state=42, cluster_std=5.0
)
X_train, y_train = X[:600], y[:600]
X_valid, y_valid = X[600:1000], y[600:1000]
X_train_valid, y_train_valid = X[:1000], y[:1000]
X_test, y_test = X[1000:], y[1000:]
학습 및 보정
연결된 학습 및 검증 데이터 (1,000 개 샘플) 로 25 개의 기본 추정자 (트리) 를 사용하여 랜덤 포레스트 분류기를 학습합니다. 이것이 보정되지 않은 분류기입니다.
from sklearn.ensemble import RandomForestClassifier
clf = RandomForestClassifier(n_estimators=25)
clf.fit(X_train_valid, y_train_valid)
보정된 분류기를 학습하기 위해 동일한 랜덤 포레스트 분류기로 시작하지만, 학습 데이터 하위 집합 (600 개 샘플) 만 사용하여 학습한 다음, method='sigmoid'를 사용하여 2 단계 과정에서 검증 데이터 하위 집합 (400 개 샘플) 을 사용하여 보정합니다.
from sklearn.calibration import CalibratedClassifierCV
clf = RandomForestClassifier(n_estimators=25)
clf.fit(X_train, y_train)
cal_clf = CalibratedClassifierCV(clf, method="sigmoid", cv="prefit")
cal_clf.fit(X_valid, y_valid)
예측 확률 비교
테스트 샘플의 예측 확률 변화를 보여주는 화살표가 있는 2-단순형을 플롯합니다.
import matplotlib.pyplot as plt
plt.figure(figsize=(10, 10))
colors = ["r", "g", "b"]
clf_probs = clf.predict_proba(X_test)
cal_clf_probs = cal_clf.predict_proba(X_test)
## 화살표 플롯
for i in range(clf_probs.shape[0]):
plt.arrow(
clf_probs[i, 0],
clf_probs[i, 1],
cal_clf_probs[i, 0] - clf_probs[i, 0],
cal_clf_probs[i, 1] - clf_probs[i, 1],
color=colors[y_test[i]],
head_width=1e-2,
)
## 각 정점에서 완벽한 예측 플롯
plt.plot([1.0], [0.0], "ro", ms=20, label="Class 1")
plt.plot([0.0], [1.0], "go", ms=20, label="Class 2")
plt.plot([0.0], [0.0], "bo", ms=20, label="Class 3")
## 단순형의 경계 플롯
plt.plot([0.0, 1.0, 0.0, 0.0], [0.0, 0.0, 1.0, 0.0], "k", label="Simplex")
## 단순형 주변 6 개의 점과 단순형 내부 중간점에 대한 주석
plt.annotate(
r"($\frac{1}{3}$, $\frac{1}{3}$, $\frac{1}{3}$)",
xy=(1.0 / 3, 1.0 / 3),
xytext=(1.0 / 3, 0.23),
xycoords="data",
arrowprops=dict(facecolor="black", shrink=0.05),
horizontalalignment="center",
verticalalignment="center",
)
plt.plot([1.0 / 3], [1.0 / 3], "ko", ms=5)
plt.annotate(
r"($\frac{1}{2}$, $0$, $\frac{1}{2}$)",
xy=(0.5, 0.0),
xytext=(0.5, 0.1),
xycoords="data",
arrowprops=dict(facecolor="black", shrink=0.05),
horizontalalignment="center",
verticalalignment="center",
)
plt.annotate(
r"($0$, $\frac{1}{2}$, $\frac{1}{2}$)",
xy=(0.0, 0.5),
xytext=(0.1, 0.5),
xycoords="data",
arrowprops=dict(facecolor="black", shrink=0.05),
horizontalalignment="center",
verticalalignment="center",
)
plt.annotate(
r"($\frac{1}{2}$, $\frac{1}{2}$, $0$)",
xy=(0.5, 0.5),
xytext=(0.6, 0.6),
xycoords="data",
arrowprops=dict(facecolor="black", shrink=0.05),
horizontalalignment="center",
verticalalignment="center",
)
plt.annotate(
r"($0$, $0$, $1$)",
xy=(0, 0),
xytext=(0.1, 0.1),
xycoords="data",
arrowprops=dict(facecolor="black", shrink=0.05),
horizontalalignment="center",
verticalalignment="center",
)
plt.annotate(
r"($1$, $0$, $0$)",
xy=(1, 0),
xytext=(1, 0.1),
xycoords="data",
arrowprops=dict(facecolor="black", shrink=0.05),
horizontalalignment="center",
verticalalignment="center",
)
plt.annotate(
r"($0$, $1$, $0$)",
xy=(0, 1),
xytext=(0.1, 1),
xycoords="data",
arrowprops=dict(facecolor="black", shrink=0.05),
horizontalalignment="center",
verticalalignment="center",
)
## 그리드 추가
plt.grid(False)
for x in [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]:
plt.plot([0, x], [x, 0], "k", alpha=0.2)
plt.plot([0, 0 + (1 - x) / 2], [x, x + (1 - x) / 2], "k", alpha=0.2)
plt.plot([x, x + (1 - x) / 2], [0, 0 + (1 - x) / 2], "k", alpha=0.2)
plt.title("sigmoid 보정 후 테스트 샘플 예측 확률 변화")
plt.xlabel("확률 클래스 1")
plt.ylabel("확률 클래스 2")
plt.xlim(-0.05, 1.05)
plt.ylim(-0.05, 1.05)
_ = plt.legend(loc="best")
로그 손실 비교
1,000 개의 테스트 샘플 예측에 대한 보정되지 않은 분류기와 보정된 분류기의 로그 손실을 비교합니다.
from sklearn.metrics import log_loss
score = log_loss(y_test, clf_probs)
cal_score = log_loss(y_test, cal_clf_probs)
print("로그 손실:")
print(f" * 보정되지 않은 분류기: {score:.3f}")
print(f" * 보정된 분류기: {cal_score:.3f}")
그리드 생성 및 플롯
2-단순형에서 보정되지 않은 확률의 가능한 그리드를 생성하고, 해당 확률을 보정하여 각각의 화살표를 플롯합니다. 화살표는 가장 높은 보정되지 않은 확률에 따라 색상이 지정됩니다. 이는 학습된 보정 맵을 보여줍니다.
plt.figure(figsize=(10, 10))
## 확률 값의 그리드 생성
p1d = np.linspace(0, 1, 20)
p0, p1 = np.meshgrid(p1d, p1d)
p2 = 1 - p0 - p1
p = np.c_[p0.ravel(), p1.ravel(), p2.ravel()]
p = p[p[:, 2] >= 0]
## 세 클래스별 보정기를 사용하여 보정된 확률 계산
calibrated_classifier = cal_clf.calibrated_classifiers_[0]
prediction = np.vstack(
[
calibrator.predict(this_p)
for calibrator, this_p in zip(calibrated_classifier.calibrators, p.T)
]
).T
## 보정된 예측값을 단순형 내부에 유지하도록 재정규화합니다.
## 이와 같은 재정규화 단계는 CalibratedClassifierCV 의 predict 메서드에서 다중 클래스 문제에 대해 내부적으로 수행됩니다.
prediction /= prediction.sum(axis=1)[:, None]
## 보정기가 유도하는 예측 확률의 변화를 플롯
for i in range(prediction.shape[0]):
plt.arrow(
p[i, 0],
p[i, 1],
prediction[i, 0] - p[i, 0],
prediction[i, 1] - p[i, 1],
head_width=1e-2,
color=colors[np.argmax(p[i])],
)
## 단위 단순형의 경계 플롯
plt.plot([0.0, 1.0, 0.0, 0.0], [0.0, 0.0, 1.0, 0.0], "k", label="Simplex")
plt.grid(False)
for x in [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]:
plt.plot([0, x], [x, 0], "k", alpha=0.2)
plt.plot([0, 0 + (1 - x) / 2], [x, x + (1 - x) / 2], "k", alpha=0.2)
plt.plot([x, x + (1 - x) / 2], [0, 0 + (1 - x) / 2], "k", alpha=0.2)
plt.title("학습된 sigmoid 보정 맵")
plt.xlabel("확률 클래스 1")
plt.ylabel("확률 클래스 2")
plt.xlim(-0.05, 1.05)
plt.ylim(-0.05, 1.05)
plt.show()
요약
이 실험에서는 scikit-learn 을 사용하여 파이썬에서 3 클래스 분류에 시그모이드 보정을 사용하는 방법을 보여주었습니다. 예측 확률에 대한 보정의 영향과 모델 성능 향상에 어떻게 활용될 수 있는지 보여주었습니다.