2.4 분류 예측의 불확실성 추정

2.3.8 신경망(딥러닝) | 목차 | 2.5 요약  및 정리

아직까지 이야기하지 않았는데, scikit-learn에서 많이 사용하는 인터페이스 중 하나는 분류기에 예측의 불확실성을 추정할 수 있는 기능입니다. 어떤 테스트 포인트에 대해 분류기가 예측한 클래스가 무엇인지 뿐만아니라 정확한 클래스임을 얼마나 확신하는지가 중요할 때가 많습니다. 실제 애플리케이션에서는 오류의 종류에 따라 전혀 다른 결과를 만듭니다. 암을 진료하는 의료 애플리케이션을 생각해보겠습니다. 거짓 양성false positive 예측은 환자에게 추가 진료를 요구하겠지만 거짓 음성false negative 예측은 심각한 질병을 치료하지 못하게 만들 수 있습니다. 이 주제는 6장에서 더 자세히 살펴보겠습니다.

scikit-learn 분류기에서 불확실성을 추정할 수 있는 함수가 두 개 있습니다. decision_function과 predict_proba입니다. 대부분의(전체는 아니고) 분류 클래스는 적어도 둘 중 하나를 제공하고 두 함수를 모두 제공하는 경우도 많습니다. 인위적으로 만든 2차원 데이터셋을 사용해 GradientBoostingClassifier 분류기의 decision_function과 predict_proba 메서드가 어떤 역할을 하는지 살펴보겠습니다.

In[107]:

from sklearn.ensemble import GradientBoostingClassifier
from sklearn.datasets import make_circles
X, y = make_circles(noise=0.25, factor=0.5, random_state=1)

# 예제를 위해 클래스의 이름을 "blue"와 "red"로 바꿉니다.
y_named = np.array(["blue", "red"])[y]

# 여러 배열을 한꺼번에 train_test_split에 넣을 수 있습니다.
# 훈련 세트와 테스트 세트로 나뉘는 방식은 모두 같습니다.
X_train, X_test, y_train_named, y_test_named, y_train, y_test = \
    train_test_split(X, y_named, y, random_state=0)

# 그래디언트 부스팅 모델을 만듭니다.
gbrt = GradientBoostingClassifier(random_state=0)
gbrt.fit(X_train, y_train_named)

2.4.1 결정 함수

이진 분류에서 decision_function 반환값의 크기는 (n_samples,)이며 각 샘플이 하나의 실수 값을 반환합니다.

In[108]:

print("X_test.shape: {}".format(X_test.shape))
print("결정 함수 결과 형태: {}".format(gbrt.decision_function(X_test).shape))

Out[108]:

X_test.shape: (25, 2)
결정 함수 결과 형태: (25,)

이 값은 모델이 데이터 포인트가 양성 클래스인 클래스 1에 속한다고 믿는 정도입니다. 양수 값은 양성 클래스를 의미하며 음수 값은 음성 (즉 다른) 클래스를 의미합니다.

In[109]:

# 결정 함수 결과 중 앞부분 일부를 확인합니다.
print("결정 함수:\n{}".format(gbrt.decision_function(X_test)[:6]))

Out[109]:

결정 함수:
[ 4.136 -1.683 -3.951 -3.626 4.29 3.662]

결정 함수의 부호만 보고 예측 결과를 알 수 있습니다.

In[110]:

print("임계치와 결정 함수 결과 비교:\n{}".format(gbrt.decision_function(X_test) > 0))
print("예측:\n{}".format(gbrt.predict(X_test)))

Out[110]:

임계치와 결정 함수 결과 비교:
[ True False False False True True False True True True False True
  True False True False False False True True True True True False
  False]
예측:
['red' 'blue' 'blue' 'blue' 'red' 'red' 'blue' 'red' 'red' 'red' 'blue'
 'red' 'red' 'blue' 'red' 'blue' 'blue' 'blue' 'red' 'red' 'red' 'red'
 'red' 'blue' 'blue']

이진 분류에서 음성 클래스는 항상 classes_ 속성의 첫 번째 원소이고 양성 클래스는 classes_의 두 번째 원소입니다. 그래서 predict 함수의 결과를 완전히 재현하려면 classes_ 속성을 사용하면 됩니다.

In[111]:

# 불리언 값을 0과 1로 변환합니다.
greater_zero = (gbrt.decision_function(X_test) > 0).astype(int)
# classes_에 인덱스로 사용합니다.
pred = gbrt.classes_[greater_zero]
# pred와 gbrt.predict의 결과를 비교합니다.
print("pred는 예측 결과와 같다: {}".format(
    np.all(pred == gbrt.predict(X_test))))

Out[111]:

pred는 예측 결과와 같다: True

decision_function 값의 범위는 데이터와 모델 파라미터에 따라 달라집니다.

In[112]:

decision_function = gbrt.decision_function(X_test)
print("결정 함수 최솟값: {:.2f} 최댓값: {:.2f}".format(
    np.min(decision_function), np.max(decision_function)))

Out[112]:

결정 함수 최솟값: -7.69 최댓값: 4.29

decision_function의 출력 범위가 임의의 값이라 이해하긴 어렵습니다.

다음 예에서 2차원 평면의 모든 점에 대해 decision_function의 값을 색으로 표현하여 앞서 본 결정 경계와 함께 그래프로 나타내보겠습니다. 훈련 데이터는 원 모양이고 테스트 데이터는 삼각형입니다(그림 2-55).

In[113]:

fig, axes = plt.subplots(1, 2, figsize=(13, 5))
mglearn.tools.plot_2d_separator(gbrt, X, ax=axes[0], alpha=.4,
                                fill=True, cm=mglearn.cm2)
scores_image = mglearn.tools.plot_2d_scores(gbrt, X, ax=axes[1],
                                            alpha=.4, cm=mglearn.ReBl)

for ax in axes:
    # 훈련 포인트와 테스트 포인트를 그리기
    mglearn.discrete_scatter(X_test[:, 0], X_test[:, 1], y_test,
                             markers='^', ax=ax)
    mglearn.discrete_scatter(X_train[:, 0], X_train[:, 1], y_train,
                             markers='o', ax=ax)
    ax.set_xlabel("특성 0")
    ax.set_ylabel("특성 1")
cbar = plt.colorbar(scores_image, ax=axes.tolist())
axes[0].legend(["테스트 클래스 0", "테스트 클래스 1", "훈련 클래스 0",
                "훈련 클래스 1"], ncol=4, loc=(.1, 1.1))

2-55

그림 2-55 2차원 예제 데이터셋을 사용해 만든 그래디언트 부스팅 모델의 결정 경계(좌)와 결정 함수(우)

예측한 결과뿐만 아니라 분류기가 얼마나 확신하는지를 알면 추가 정보를 얻게 됩니다. 그러나 결정 함수 그래프에서 두 클래스 사이의 경계를 구분하기는 어렵습니다.

2.4.2 예측 확률

predict_proba의 출력은 각 클래스에 대한 확률이고 decision_function의 출력보다 이해하기 더 쉽습니다. 이 값의 크기는 이진 분류에서는 항상 (n_samples, 2)입니다.

In[114]:

print("확률 값의 형태: {}".format(gbrt.predict_proba(X_test).shape))

Out[114]:

확률 값의 형태: (25, 2)

각 행의 첫 번째 원소는 첫 번째 클래스의 예측 확률이고 두 번째 원소는 두 번째 클래스의 예측 확률입니다. 확률이기 때문에 predict_proba의 출력은 항상 0과 1 사이의 값이며 두 클래스에 대한 확률의 합은 항상 1입니다.

In[115]:

# predict_proba 결과 중 앞부분 일부를 확인합니다.
print("예측 확률:\n{}".format(gbrt.predict_proba(X_test[:6])))

Out[115]:

예측 확률:
[[ 0.016 0.984]
 [ 0.843 0.157]
 [ 0.981 0.019]
 [ 0.974 0.026]
 [ 0.014 0.986]
 [ 0.025 0.975]]

두 클래스의 확률 합은 1이므로 두 클래스 중 하나는 50% 이상의 확신을 가질 것이 틀림없습니다. 그리고 바로 그 클래스가 예측값이 됩니다. 1

앞의 출력값을 보면 분류기가 대부분의 포인트에서 비교적 강하게 확신하고 있습니다. 데이터에 있는 불확실성이 얼마나 이 값에 잘 반영되는지는 모델과 매개변수 설정에 달렸습니다. 과대적합된 모델은 혹 잘못된 예측이더라도 예측의 확신이 강한 편입니다. 일반적으로 복잡도가 낮은 모델은 예측에 불확실성이 더 많습니다. 이런 불확실성과 모델의 정확도가 동등하면 이 모델이 보정calibration되었다고 합니다. 즉 보정된 모델에서 70% 확신을 가진 예측은 70%의 정확도를 낼 것입니다.

다음 예에서 앞에서와 같은 데이터셋을 사용해 결정 경계와 클래스 1의 확률을 그려보겠습니다(그림 2-56).

In[116]:

fig, axes = plt.subplots(1, 2, figsize=(13, 5))

mglearn.tools.plot_2d_separator(
    gbrt, X, ax=axes[0], alpha=.4, fill=True, cm=mglearn.cm2)
scores_image = mglearn.tools.plot_2d_scores(
    gbrt, X, ax=axes[1], alpha=.5, cm=mglearn.ReBl, function='predict_proba')

for ax in axes:
    # 훈련 포인트와 테스트 포인트를 그리기
    mglearn.discrete_scatter(X_test[:, 0], X_test[:, 1], y_test,
                             markers='^', ax=ax)
    mglearn.discrete_scatter(X_train[:, 0], X_train[:, 1], y_train,
                             markers='o', ax=ax)
    ax.set_xlabel("특성 0")
    ax.set_ylabel("특성 1")
cbar = plt.colorbar(scores_image, ax=axes.tolist())
axes[0].legend(["테스트 클래스 0", "테스트 클래스 1", "훈련 클래스 0",
                "훈련 클래스 1"], ncol=4, loc=(.1, 1.1))

2-56

그림 2-56 [그림 2-55]의 그래디언트 부스팅 모델의 결정 경계(좌)와 예측 확률(우)

이 그래프의 경계는 훨씬 잘 나타나 있으며 불확실성이 있는 작은 영역들도 잘 확인할 수 있습니다.

scikit-learn 웹사이트(http://bit.ly/2cqCYx6)에 많은 모델의 불확실성 추정이 어떤지 잘 비교해놓았습니다. 이 그래프를 [그림 2-57]에 옮겨봤습니다. 웹사이트에 있는 예제도 한번 살펴보길 권합니다.

2-57

그림 2-57 인위적으로 만든 데이터셋을 사용한 scikit-learn의 여러 분류기의 비교(이미지 출처: http://scikit-learn.org)

2.4.3 다중 분류에서의 불확실성

이제까지 이진 분류에서 불확실성 추정에 관해 이야기했습니다. 하지만 decision_function과 predict_proba 메서드는 다중 분류에도 사용할 수 있습니다. 클래스가 세 개인 iris 데이터셋에 적용해보겠습니다.

In[117]:

from sklearn.datasets import load_iris

iris = load_iris()
X_train, X_test, y_train, y_test = train_test_split(
    iris.data, iris.target, random_state=42)

gbrt = GradientBoostingClassifier(learning_rate=0.01, random_state=0)
gbrt.fit(X_train, y_train)

In[118]:

print("결정 함수의 결과 형태: {}".format(gbrt.decision_function(X_test).shape))
# decision function 결과 중 앞부분 일부를 확인합니다.
print("결정 함수 결과:\n{}".format(gbrt.decision_function(X_test)[:6, :]))

Out[118]:

결정 함수의 결과 형태: (38, 3)
결정 함수 결과:
[[-0.529 1.466 -0.504]
 [ 1.512 -0.496 -0.503]
 [-0.524 -0.468 1.52 ]
 [-0.529 1.466 -0.504]
 [-0.531 1.282 0.215]
 [ 1.512 -0.496 -0.503]]

다중 분류에서는 decision_function의 결괏값의 크기는 (n_samples, n_classes)입니다. 각 열은 각 클래스에 대한 확신 점수를 담고 있습니다. 수치가 크면 그 클래스일 가능성이 크고 수치가 작으면 그 클래스일 가능성이 낮습니다. 데이터 포인트마다 점수들에서 가장 큰 값을 찾아 예측 결과를 재현할 수 있습니다.

In[119]:

print("가장 큰 결정 함수의 인덱스:\n{}".format(
    np.argmax(gbrt.decision_function(X_test), axis=1)))
print("예측:\n{}".format(gbrt.predict(X_test)))

Out[119]:

가장 큰 결정 함수의 인덱스:
[1 0 2 1 1 0 1 2 1 1 2 0 0 0 0 1 2 1 1 2 0 2 0 2 2 2 2 2 0 0 0 0 1 0 0 2 1 0]
예측:
[1 0 2 1 1 0 1 2 1 1 2 0 0 0 0 1 2 1 1 2 0 2 0 2 2 2 2 2 0 0 0 0 1 0 0 2 1 0]

predict_proba의 출력값 크기는 (n_samples, n_classes)로, 앞서 본 것과 같습니다. 마찬가지로 각 데이터 포인트에서 클래스 확률의 합은 1입니다.

In[120]:

# predict_proba 결과 중 앞부분 일부를 확인합니다.
print("예측 확률:\n{}".format(gbrt.predict_proba(X_test)[:6]))
# 행 방향으로 확률을 더하면 1이 됩니다.
print("합: {}".format(gbrt.predict_proba(X_test)[:6].sum(axis=1)))

Out[120]:

예측 확률:
[[ 0.107 0.784 0.109]
 [ 0.789 0.106 0.105]
 [ 0.102 0.108 0.789]
 [ 0.107 0.784 0.109]
 [ 0.108 0.663 0.228]
 [ 0.789 0.106 0.105]]
합: [ 1. 1. 1. 1. 1. 1.]

predict_proba의 결과에 argmax 함수를 적용해서 예측을 재현할 수 있습니다.

In[121]:

print("가장 큰 예측 확률의 인덱스:\n{}".format(
    np.argmax(gbrt.predict_proba(X_test), axis=1)))
print("예측:\n{}".format(gbrt.predict(X_test)))

Out[121]:

가장 큰 예측 확률의 인덱스:
[1 0 2 1 1 0 1 2 1 1 2 0 0 0 0 1 2 1 1 2 0 2 0 2 2 2 2 2 0 0 0 0 1 0 0 2 1 0]
예측:
[1 0 2 1 1 0 1 2 1 1 2 0 0 0 0 1 2 1 1 2 0 2 0 2 2 2 2 2 0 0 0 0 1 0 0 2 1 0]

정리하면 predict_proba와 decision_function의 결괏값 크기는 항상 (n_samples, n_classes)입니다. 이진 분류의 decision_function은 조금 다릅니다. 이진 분류에서는 decision_function은 열이 하나뿐이며 양성 클래스인 classes_[1]에 대응하는 값을 가지고 있습니다. 이는 아마도 오래전부터 내려온 관례 때문입니다.

열이 n_classes개일 때는 열을 가로질러서 argmax 함수를 적용해 예측 결과를 재현할 수 있습니다. 하지만 주의할 것은 클래스가 문자열이거나 또는 정수형을 사용하지만 연속적이지 않고 0부터 시작하지 않을 수 있습니다. 2 predict의 결과와 decision_function이나 predict_proba의 결과를 비교하려면 분류기의 classes_ 속성을 사용해 클래스의 실제 이름을 얻어야 합니다.

In[122]:

logreg = LogisticRegression()

# iris 데이터셋의 타깃을 클래스 이름으로 나타내기
named_target = iris.target_names[y_train]
logreg.fit(X_train, named_target)
print("훈련 데이터에 있는 클래스 종류: {}".format(logreg.classes_))
print("예측: {}".format(logreg.predict(X_test)[:10]))
argmax_dec_func = np.argmax(logreg.decision_function(X_test), axis=1)
print("가장 큰 결정 함수의 인덱스: {}".format(argmax_dec_func[:10]))
print("인덱스를 classses_에 연결: {}".format(
       logreg.classes_[argmax_dec_func][:10]))

Out[122]:

훈련 데이터에 있는 클래스 종류: ['setosa' 'versicolor' 'virginica']
예측: ['versicolor' 'setosa' 'virginica' 'versicolor' 'versicolor'
 'setosa' 'versicolor' 'virginica' 'versicolor' 'versicolor']
가장 큰 결정 함수의 인덱스: [1 0 2 1 1 0 1 2 1 1]
인덱스를 classses_에 연결: ['versicolor' 'setosa' 'virginica' 'versicolor'
 'versicolor' 'setosa' 'versicolor' 'virginica' 'versicolor' 'versicolor']


  1. 확률값은 실수이므로 두 클래스가 정확히 0.500이 될 가능성은 거의 없습니다. 그러나 만약 그런 값이 나온다면 예측은 랜덤하게 둘 중 하나를 선택합니다.
  2. 옮긴이_ 클래스가 숫자일 경우 predict_proba와 descision_function의 배열 인덱스와 같을 수 있습니다. 이런 경우 argmax의 값이 그대로 예측 클래스가 됩니다. 하지만 클래스의 숫자가 “1” 처럼 문자열일 수 있고 연속된 숫자가 아닐 수 있으므로 항상 classes_의 값을 사용하는 것이 버그를 미연에 방지하는 길입니다.

2.3.8 신경망(딥러닝) | 목차 | 2.5 요약  및 정리

이 글은 한빛미디어에서 출간한  “파이썬 라이브러리를 활용한 머신러닝“의 1장과 2장입니다. 이 책의 저작권은 한빛미디어(주)에 있으므로 무단 복제 및 무단 전제를 금합니다.

답글 남기기

아래 항목을 채우거나 오른쪽 아이콘 중 하나를 클릭하여 로그 인 하세요:

WordPress.com 로고

WordPress.com의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

Twitter 사진

Twitter의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

Facebook 사진

Facebook의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

Google+ photo

Google+의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

%s에 연결하는 중