파이썬 머신러닝 완벽 가이드

분류하려는 업무 특성상 정밀도 또는 재현율이 특별히 강조돼야할 경우 분류의 결정 임계값(Thershold)을 조정해 정밀도 또는 재현율을 높일 수 있다.
정밀도/재현율의 트레이드오프(Trade-off)
하지만 정밀도와 재현율은 상호보완적인 평가 지표이기 때문에 어느 한 쪽을 강제로 높이면 다른 하나의 수치는 떨어지기 쉽다.
사이킷런은 개별 데이터별로 예측 확률을 반환하는 메서드인 predict_proba()를 제공
입력 파라미터
반환값
각 열은 개별 클래스의 예측 확률, 이진 분류에서 첫 번쨰 칼럼은 0 Negative의 확률, 두 번째 칼럼은 1 Positive의 확률
타이타닉 예제에서의 predict_proba() 메서드를 수행한 뒤 반환 값을 확인하고 predict() 메서드와 비교해보자
pred_proba = Ir_clf.predict_proba(X_test)
pred = Ir_clf.predict(X_test)
print('pred_proba()결과 shape:{0}'.format(pred_proba.shape))
print('pred_proba array에서 앞 3개만 샘플로 추출 \n:', pred_proba[:3])
#예측 확률 array와 예측 결과값 array를 병합하여(concatenate) 예측 확률과 결괏값을 한눈에 확인
pred_proba_result = np.concatenate([pred_proba, pred.reshape(-1,1)], axis=1)
print('두 개의 class 중에서 더 큰 확률을 클래스 값으로 예측 \n', pred_proba_result[:3])
[output]
pred_proba()결과 shape:(179, 2)
pred_proba array에서 앞 3개만 샘플로 추출
: [[0.44935227 0.55064773]
[0.86335512 0.13664488]
[0.86429645 0.13570355]]
두 개의 class 중에서 더 큰 확률을 클래스 값으로 예측
[[0.44935227 0.55064773 1. ]
[0.86335512 0.13664488 0. ]
[0.86429645 0.13570355 0. ]]
반환 결과인 ndarray는 0과 1에 대한 확률을 나타내므로 첫 번째 칼럼값과 두 번째 칼럼값을 더하면 1이 된다.
또한 predict() 메서드 결과 비교에서도 나타나듯이, 두 개의 칼럼 중에서 더 큰 확률 값으로 predict()가 최종 예측하고 있다.
사이킷런의 predict()는 predict_proba() 메서드가 반환하는 확률 값을 가진 ndarray에서 정해진 임계값을 만족하는 ndarray의 칼럼 위치를 최종 예측 클래스로 결정한다.
이러한 로직을 사이킷런의 Binarizer 클래스를 이용해 코드로 구현하여 정밀도/재현율 트레이드 오프 방식을 이해해보자
먼저 Binarizer 클래스의 사용법부터 간단히 알아보자
from sklearn.preprocessing import Binarizer
X= [[ 1, -1, 2],
[ 2, 0, 0],
[0, 1.1, 1.2]]
# X의 개별 원소들이 threshold 값보다 같거나 작으면 0, 크면 1을 반환
binarizer = Binarizer(threshold=1.1)
print(binarizer.fit_transform(X))
[output]
[[0. 0. 1.]
[1. 0. 0.]
[0. 0. 1.]]
이제 이 Binarizer를 이용해 사이킷런 predict()의 의사(pseudo)코드를 만들어보자
from sklearn.preprocessing import Binarizer
#Binarizer의 threshold 설정값. 분류 결정 임계값임
custom_threshold = 0.5
#predict_proba() 반환값의 두 번째 칼럼, 즉 Positive 클래스 칼럼 하나만 추출해 Binarizer를 적용
pred_proba_1 = pred_proba[:, 1].reshape(-1, 1)
binarizer = Binarizer(threshold=custom_threshold).fit(pred_proba_1)
custom_predict = binarizer.transform(pred_proba_1)
get_clf_eval(y_test, custom_predict)
[output]
오차 행렬
[[108 10]
[ 14 47]]
정확도: 0.8659, 정밀도:0.8246, 재현율:0.7705
이 의사코드는 앞에서 predict()로 계산된 지표 값과 정확히 같다. 즉, predict()가 predict_proba()에 기반함을 알 수 있다.
#Binarizer의 threshold 설정값을 0.5에서 0.4로 낮춤.
custom_threshold = 0.4
pred_proba_1 = pred_proba[:, 1].reshape(-1, 1)
binarizer = Binarizer(threshold=custom_threshold).fit(pred_proba_1)
custom_predict = binarizer.transform(pred_proba_1)
get_clf_eval(y_test, custom_predict)
[output]
오차 행렬
[[97 21]
[11 50]]
정확도: 0.8212, 정밀도:0.7042, 재현율:0.8197
임계값을 낮추니 정밀도는 떨어지고 재현율이 올라간 것을 확인
그 이유는 임계값은 Positive 예측값을 결정하는 확률의 기준이기 때문에 이를 낮추면 Positive 예측을 할 확률에 대한 범위가 더 넓어진다.
그러므로 양성 예측을 많이하게 되어 실제 양성을 음성으로 예측하는 횟수가 상대적으로 줄어들게된다.
이번에는 임계값을 0.4부터 0.6까지 0.05씩 증가시키며 평가 지표를 조사해보자
이를 위해 get_eval_by_threshold() 생성
#테스트를 수행할 모든 임곗값을 리스트 객체로 저장.
thresholds = [0.4, 0.45, 0.5, 0.55, 0.6]
def get_eval_by_threshold(y_test, pred_proba_c1, thresholds):
#thresholds list 객체 내의 값을 차례로 iteration 하면서 Evaluation 수행.
for custom_threshold in thresholds:
binarizer = Binarizer(threshold=custom_threshold).fit(pred_proba_c1)
custom_predict = binarizer.transform(pred_proba_c1)
print('임곗값:', custom_threshold)
get_clf_eval(y_test, custom_predict)
get_eval_by_threshold(y_test, pred_proba[:,1].reshape(-1,1), thresholds)
[output]
임곗값: 0.4
오차 행렬
[[97 21]
[11 50]]
정확도: 0.8212, 정밀도:0.7042, 재현율:0.8197
임곗값: 0.45
오차 행렬
[[105 13]
[ 13 48]]
정확도: 0.8547, 정밀도:0.7869, 재현율:0.7869
임곗값: 0.5
오차 행렬
[[108 10]
[ 14 47]]
정확도: 0.8659, 정밀도:0.8246, 재현율:0.7705
임곗값: 0.55
오차 행렬
[[111 7]
[ 16 45]]
정확도: 0.8715, 정밀도:0.8654, 재현율:0.7377
임곗값: 0.6
오차 행렬
[[113 5]
[ 17 44]]
정확도: 0.8771, 정밀도:0.8980, 재현율:0.7213
임곗값이 0.45일 경우에 디폴트 0.5인 경우와 비교해서 정확도는 동일하고 정밀도는 약간 떨어졌으나 재현율이 오른 것을 확인할 수 있다.
사이킷런은 이와 유사한 precision_recall_curve() API를 제공
입력 파라미터:
반환값:
이 메서드를 이용해 타이타닉 예측 모델의 임곗값별 정밀도와 재현율을 구해보자
from sklearn.metrics import precision_recall_curve
#레이블 값이 1일때 예측확률을 추출
pred_proba_class1 = Ir_clf.predict_proba(X_test)[:,1]
# 실제값 데이터 세트와 레이블 값이 1일 때의 예측확률을 precision_recall_curve 인자로 입력
precisions, recalls, thresholds = precision_recall_curve(y_test, pred_proba_class1)
print('반환된 분류 결정 임곗값 배열의 shape:', thresholds.shape)
#반환된 임계값 배열 로우가 147건이므로 샘플로 10건만 추출하되, 임곗값을 15 step으로 추출
thr_index = np.arange(0,thresholds.shape[0],15)
print('샘플 추출을 위한 임계값 배열의 index 10개:', thr_index)
print('샘플용 10개의 임곗값:', np.round(thresholds[thr_index],2))
#15 step 단위로 추출된 임계값에 따른 정밀도와 재현율 값
print('샘플 임계값별 정밀도:', np.round(precisions[thr_index],3))
print('샘플 임계값별 재현율:', np.round(recalls[thr_index],3))
[output]
반환된 분류 결정 임곗값 배열의 shape: (147,)
샘플 추출을 위한 임계값 배열의 index 10개: [ 0 15 30 45 60 75 90 105 120 135]
샘플용 10개의 임곗값: [0.12 0.13 0.15 0.17 0.26 0.38 0.49 0.63 0.76 0.9 ]
샘플 임계값별 정밀도: [0.379 0.424 0.455 0.519 0.618 0.676 0.797 0.93 0.964 1. ]
샘플 임계값별 재현율: [1. 0.967 0.902 0.902 0.902 0.82 0.77 0.656 0.443 0.213]
추출된 임곗값 샘플 10개에 해당하는 정밀도 값과 재현율 값을 살펴보면 임곗값이 증가할수록 정밀도 값은 동시에 높아지나 재현율 값은 낮아짐을 알 수 있다.
precision_recall_curve() API는 정밀도와 재현율의 임곗값에 따른 값 변화를 곡선 형태의 그래프로 시각화 하는 데 이용할 수 있다.
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
%matplotlib inline
def precision_recall_curve_plot(y_test, pred_proba_c1):
# thredshold ndarray와 이 threshold에 따른 정밀도, 재현율 ndarray 추출
precision, recalls, thresholds = precision_recall_curve(y_test, pred_proba_c1)
#X축을 thredshold 값으로, Y축은 정밀도, 재현율 값으로 각각 plot 수행. 정밀도는 점선으로 표시
plt.figure(figsize=(8,6))
threshold_boundary = thresholds.shape[0]
plt.plot(thresholds, precision[0:threshold_boundary], linestyle='--', label='precision')
plt.plot(thresholds, recalls[0:threshold_boundary], label ='recall')
#thredshold 값 X축 Scale을 0.1단위로 변경
start, end = plt.xlim()
plt.xticks(np.round(np.arange(start, end, 0.1),2))
#X축, y축, label과 legend, 그리고 grid 설정
plt.xlabel('Threshold value'); plt.ylabel('Precision and Recall value')
plt.legend();plt.grid()
plt.show()
precision_recall_curve_plot(y_test, Ir_clf.predict_proba(X_test)[:,1])
[output]

[정밀도가 100%가 되는 방법]
확실한 기준이 되는 경우만 Positive로 예측하고 나머지는 모두 Negative로 예측한다.
[재현율이 100%가 되는 방법]
모든 암 환자를 Positive로 예측하면 된다.
이처럼 정밀도와 재현율 성능 수치도 어느 한 쪽만 참조하면 극단적인 수치 조작이 가능하다.
따라서 정밀도 또는 재현율 중 하나만 스코어가 좋고 다른 하나는 스코어가 나쁜 분류는 성능이 좋지 않다고 할 수 있다.