1. 오차행렬 (Confusion Matrix)
오차행렬
- 오차행렬은 이진 분류의 예측 오류가 얼마인지와 더불어 어떠한 유형의 예측 오류가 발생하고 있는지를 함께 나타내는 지표
- x축(label_test): 실제 클래스(Actual Class) = N/P
- y축(prediction): 예측 클래스(Predicted Class) = N/P
- T/F: 실제로는 예측값이 맞으면(True) 틀리면(False)
- 암기: TN(기준) -> TP 대각선 아래 & FN -> FP 대각선 위 (X자)
confusion_matrix( prediction, y_test)
from sklearn.metrics import confusion_matrix
# 앞절의 예측 결과인 fakepred와 실제 결과인 y_test의 confusion matrix 출력
confusion_matrix(fakepred, y_test)
# array([[405, 45],
# [ 0, 0]])
pd.crosstab( prediction, y_test)도 동일한 오차행렬 생성
다중분류 오차행렬 (MultiClassification Confusion Matrix)
Binary Classification Confusion Matrix
Multi-Classification Confusion Matrix
TP: 예측 Class와 실제 Class가 해당 Label로 같은 값
FP: 실제 Class는 맞지만 예측 Class가 해당 Label과 다른 값
FN: 예측 Class는 아니지만 실제 Class는 해당 Label과 값은 값
정밀도 Precision = $\frac {TP} {TP + FP}$: 세로축 상의 비율
재현율 Recall = $\frac {TP} {TP + FN}$: 가로축 상의 비율
Macro Precision / Recall
Macro-Precision = $ \frac {Pre_{1} + Pre_{1} + ...... + Pre_{n}} {n}$
Macro-Recall = $ \frac {Re_{1} + Re_{1} + ...... + Re_{n}} {n}$
평균들의 평균 개념이며 매크로 평균(Macro-average)이라고 불리는 방법입니다.
Macro 는 사이즈가 크건 작건 하나의 precision 으로 묶어서, micro 는 TP/FP 가 많으므로 영향을 많이 미칩니다.
전체 성능을 평가하기 위해 모든 클래스에 동일한 가중치를 부여합니다.
Sample에 대해 동일한 weight를 주는 것이 아닌, Class 별로 똑같은 weight을 부여합니다.
Micro Precision / Recall
Micro-Precision = $\frac {TP_{1} + ...... + TP_{n}} {(FP_{1} + ...... + FP_{n}) + (TP_{1} + ...... + TP_{n})}$
Micro-Recall = $\frac {TP_{1} + ...... + TP_{n}} {(FN_{1} + ...... + FN_{n}) + (TP_{1} + ...... + TP_{n})}$
전체의 값들의 평균 개념이며 마이크로 평균(Micro-average)이라고 불리는 방법
각 샘플에 동일한 가중치를 적용하려는 경우 유용함
구현
TPS = []
FNS = []
FPS = []
macro_precisions = []
macro_recall = []
for i in range(cm.shape[0]):
TP = cm[i][i]
FP = 0
FN = 0
for j in range(cm.shape[0]):
if i == j:
continue
FP += cm[j][i]
FN += cm[i][j]
FNS.append(FN)
FPS.append(FP)
TPS.append(TP)
macro_precisions.append((TP) / (TP + FP))
macro_recall.append((TP) / (TP + FN))
print(f"macro_precision 값: {np.mean(macro_precisions)}")
print(f"macro_recall 값: {np.mean(macro_recall)}")
print(f"micro_precision 값: {sum(TPS) / (sum(TPS)+sum(FPS))}")
print(f"micro_recall 값: {sum(TPS) / (sum(TPS)+sum(FNS))}")
Method
parameter값에 average='macro' or average='micro' 사용
from sklearn.metrics import precision_score, recall_score
print(precision_score(y_test, pred, average="macro"))
print(recall_score(y_test, pred, average="macro"))
print()
print(precision_score(y_test, pred, average="micro"))
print(recall_score(y_test, pred, average="micro"))
classification_report(y_test, pred, labels, target_names, output_dict)
각 Class별 precision, recall, F1 score을 쉽게 확인 가능 + macro / micro / weighted average까지 알 수 있음
output_dict의 파라미터를 True로 설정하면 딕셔너리 형태로 출력되어 원하는 값을 쉽게 얻을 수 있음
labels: 주어진 Class(Label)값들을 넣기
target_names: 파라미터에 Class 이름을 할당할 수 있음
# classification_report를 변수에 저장하고 DataFrame으로 변환하여 출력하세요.
clf_report = classification_report(
y_test, pred, labels=classes, target_names=classes, output_dict=True
)
clf_report_df = pd.DataFrame(clf_report)
clf_report_df
2. 정밀도와 재현율
정밀도(precision)와 재현율(recall)
- $정확도 = \frac {TN+TP} {TN+TP+FN+FP}$
- $정밀도 = \frac {TP} {TP+FP}$ = Precision = Positive Predictive Value : 세로축
- 예측을 Positive로 한 대상 중에 예측과 실제값이 Positive로 일치한 데이터의 비율을 뜻함
- 미처 잡아내지 못하는 것이 있더라도 더 정확한 예측이 필요할 때 사용
- 정밀도를 올리는 방법: TP를 높이거나 FP를 낮추면 됨
- precision_score() 제공
- $재현율 = \frac {TP} {TP+FN}$ = Recall = TPR(True Positive Rate) = Sensitivity : 가로축
- 실제 값이 Positive인 대상 중 예측과 실제 값이 Positive로 일치한 데이터의 비율을 뜻함
- 잘못 걸러내는 비율이 높더라도 놓치는 것이 없도록 하는 경우에 사용
- 재현율를 올리는 방법: TP를 높이거나 FN를 낮추면 됨
- recall_score() 제공
- 위양성률 = FPR(False Positive Rate) = $\frac {FP} {FP + TN}$
precision_score( prediction, y_test), recall_score( prediction, y_test)
from sklearn.metrics import accuracy_score, precision_score, recall_score
print(f'정밀도: {precision_score(fakepred, y_test)}')
print(f'재현율: {recall_score(fakepred, y_test)}')
# 정밀도: 0.0
# 재현율: 0.0
from sklearn.metrics import (
accuracy_score,
precision_score,
recall_score,
confusion_matrix,
)
def get_clf_eval(y_test, pred):
confusion = confusion_matrix(y_test, pred)
accuracy = accuracy_score(y_test, pred)
precision = precision_score(y_test, pred)
recall = recall_score(y_test, pred)
print("오차 행렬")
print(confusion)
print(f"정확도: {accuracy:.4f}, 정밀도: {precision:.4f}, 재현율: {recall:.4f}")
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
import warnings
warnings.filterwarnings('ignore')
# 원본 데이터를 재로딩, 데이터 가공, 학습데이터/테스트 데이터 분할.
titanic_df = pd.read_csv('./titanic_train.csv')
y_titanic_df = titanic_df['Survived']
X_titanic_df= titanic_df.drop('Survived', axis=1)
X_titanic_df = transform_features(X_titanic_df)
X_train, X_test, y_train, y_test = train_test_split(X_titanic_df, y_titanic_df, \
test_size=0.20, random_state=11)
lr_clf = LogisticRegression(solver='liblinear')
lr_clf.fit(X_train , y_train)
pred = lr_clf.predict(X_test)
get_clf_eval(y_test , pred)
# 오차 행렬
# [[108 10]
# [ 14 47]]
# 정확도: 0.8659, 정밀도: 0.8246, 재현율: 0.7705
pd.crosstab( prediction, y_test)도 동일한 오차행렬 생성
3. 정밀도와 재현율의 Trade-off
정밀도(precision)와 재현율(recall)의 상대적 중요도
업무에 따라 정밀도 / 재현율의 중요도가 다르다.
정밀도 또는 재현율이 특별히 강조돼야 할 경우 분류의 결정 임곗값(Threshold)을 조정해 수치를 높일 수 있다.
일반적으로 이진 분류에서는 임곗값을 0.5 (50%)로 정하고 임곗값보다 확률이 크면 Positive(1), 작으면 Negative(0)로 결정한다.
그러나 정밀도와 재현율은 상호 보완적인 평가 지표이므로 어느 한 쪽을 높이면 다른 하나의 수치는 떨어지는데 이를 정밀도 / 재현율의 Trade-off라 한다.
- 정밀도가 상대적으로 더 중요한 경우 (FP가 더 중요한 경우)
- 미처 잡아내지 못하는 것이 있더라도 더 정확한 예측이 필요할 때 사용 ( FP을 낮춘다)
- 실제 Negative 음성인 데이터 예측을 Positive 양성으로 잘못 판단하게 되면 업무상 큰 영향이 발생하는 경우
- 스팸 메일
- 재현율이 상대적으로 더 중요한 경우 (FN이 더 중요한 경우)
- 잘못 걸러내는 비율이 높더라도 놓치는 것이 없도록 하는 경우에 사용 (FN을 낮춘다)
- 실제 Positive 양성인 데이터 예측을 Negative로 잘못 판단하게 되면 업무상 큰 영향이 발생하는 경우
- 암 진단, 금융사기 판별
- 불균형한 레이블 클래스를 가지는 Binary Classification model에서는 많은 데이터 중에서 중점적으로 찾아야 하는 매우 적은 수의 결괏값에 Positive를 설정해 1값을 부여하고, 그렇지 않은 경우는 Negative로 0 값을 일반적으로 부여함
정밀도(precision)와 재현율(recall)의 Trade-off
분류 결정 임계값
- Positive 예측값을 결정하는 확률의 기준
- 임계값을 낮출수록 True 값이 많아짐
분류 결정 임계값에 따른 Positive 예측 확률 변화
- 정밀도: $\frac {TP} {TP+FP}$
- 재현율: $\frac {TP} {TP+FN}$
- 분류 결정 임계값이 낮아질 수록 Positive(양성)으로 예측할 확률이 높아짐
- FN이 줄어들고, FP 증가하여 정밀도 감소, 재현율 증가
- FN: 예측을 Negative로 했는데 틀린(False) 경우
- 예측을 Negative로 하는 횟수 자체가 줄어듦: case가 감소 -> FN 감소
- 예측을 Positive로 하는 횟수 자체가 증가 -> 틀릴 확률 또한 증가 -> FP 증가
- predict_proba(X_test)
- Scikit-Learn Estimator 객체의 predict_proba() method는 분류 결정 예측 확률을 반환
- 이를 이용하여 임의로 분류 결정 임계값을 조정하면서 예측 확률을 변경할 수 있음
- predict_proba()의 반환 값 array에서 보통 Positive Column(Label이 1)의 예측 확률이 보통 사용됨
- predict(X_test) : 예측 결과 Class값
- predict_proba(X_test): 예측 결과 확률
pred
# array([1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0,
# 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1,
# 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
# 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0,
# 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0,
# 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0,
# 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0,
# 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0,
# 0, 0, 1])
pred_proba
# array([[0.44935227, 0.55064773],
# [0.86335512, 0.13664488],
# [0.86429645, 0.13570355],
# [0.84968519, 0.15031481],
# [0.82343411, 0.17656589],
# [0.84231224, 0.15768776],
# [0.8709549 , 0.1290451 ],
# [0.27228603, 0.72771397],
# [0.78185129, 0.21814871],
# [0.33185993, 0.66814007],
# [0.86178763, 0.13821237],
# [0.87058099, 0.12941901],
# [0.86425952, 0.13574048],
# [0.87065945, 0.12934055],
# [0.56033546, 0.43966454],
# [0.85003024, 0.14996976],
# [0.88954174, 0.11045826],
# [0.74250732, 0.25749268],
# [0.71120225, 0.28879775],
# [0.23776274, 0.76223726],
# [0.75684106, 0.24315894],
# [0.62428169, 0.37571831],
# [0.84655248, 0.15344752],
# [0.82711258, 0.17288742],
# [0.8682563 , 0.1317437 ],
# [0.77003828, 0.22996172],
# [0.82946349, 0.17053651],
# [0.9033613 , 0.0966387 ],
# [0.73372048, 0.26627952],
# [0.68847388, 0.31152612],
# [0.07646869, 0.92353131],
# [0.2253212 , 0.7746788 ],
# [0.87161941, 0.12838059],
# [0.24075416, 0.75924584],
# [0.62711734, 0.37288266],
# [0.77003828, 0.22996172],
# [0.90554276, 0.09445724],
# [0.40602576, 0.59397424],
# [0.93043586, 0.06956414],
# [0.87650522, 0.12349478],
# [0.69797422, 0.30202578],
# [0.89664594, 0.10335406],
# [0.21993378, 0.78006622],
# [0.3156571 , 0.6843429 ],
# [0.37942229, 0.62057771],
# [0.37932892, 0.62067108],
# [0.0716128 , 0.9283872 ],
# [0.5577757 , 0.4422243 ],
# [0.07914487, 0.92085513],
# [0.86803083, 0.13196917],
# [0.5079006 , 0.4920994 ],
# [0.87065945, 0.12934055],
# [0.85576406, 0.14423594],
# [0.3487013 , 0.6512987 ],
# [0.71558417, 0.28441583],
# [0.78853202, 0.21146798],
# [0.7461921 , 0.2538079 ],
# [0.86429002, 0.13570998],
# [0.84079004, 0.15920996],
# [0.59838063, 0.40161937],
# [0.73532081, 0.26467919],
# [0.88705598, 0.11294402],
# [0.54552801, 0.45447199],
# [0.55326341, 0.44673659],
# [0.62583526, 0.37416474],
# [0.88363279, 0.11636721],
# [0.35181256, 0.64818744],
# [0.39903352, 0.60096648],
# [0.08300813, 0.91699187],
# [0.85072524, 0.14927476],
# [0.86778821, 0.13221179],
# [0.83070926, 0.16929074],
# [0.87649043, 0.12350957],
# [0.05959914, 0.94040086],
# [0.78735759, 0.21264241],
# [0.87065945, 0.12934055],
# [0.716541 , 0.283459 ],
# [0.79159804, 0.20840196],
# [0.20303096, 0.79696904],
# [0.86429002, 0.13570998],
# [0.2400505 , 0.7599495 ],
# [0.37123581, 0.62876419],
# [0.08369625, 0.91630375],
# [0.84018612, 0.15981388],
# [0.07766718, 0.92233282],
# [0.08973248, 0.91026752],
# [0.84723078, 0.15276922],
# [0.86241532, 0.13758468],
# [0.16539728, 0.83460272],
# [0.87065945, 0.12934055],
# [0.87065945, 0.12934055],
# [0.77003828, 0.22996172],
# [0.75416741, 0.24583259],
# [0.87065945, 0.12934055],
# [0.37932892, 0.62067108],
# [0.8988389 , 0.1011611 ],
# [0.07361402, 0.92638598],
# [0.87897227, 0.12102773],
# [0.60197828, 0.39802172],
# [0.06738996, 0.93261004],
# [0.47948278, 0.52051722],
# [0.90469272, 0.09530728],
# [0.05673721, 0.94326279],
# [0.88180789, 0.11819211],
# [0.45587967, 0.54412033],
# [0.86133438, 0.13866562],
# [0.84974931, 0.15025069],
# [0.85072699, 0.14927301],
# [0.5550275 , 0.4449725 ],
# [0.88426898, 0.11573102],
# [0.84747418, 0.15252582],
# [0.87269564, 0.12730436],
# [0.67538691, 0.32461309],
# [0.48275249, 0.51724751],
# [0.8682563 , 0.1317437 ],
# [0.91597192, 0.08402808],
# [0.84194204, 0.15805796],
# [0.78872838, 0.21127162],
# [0.11141752, 0.88858248],
# [0.90534856, 0.09465144],
# [0.87071645, 0.12928355],
# [0.8690544 , 0.1309456 ],
# [0.91525793, 0.08474207],
# [0.58196819, 0.41803181],
# [0.98025012, 0.01974988],
# [0.87071645, 0.12928355],
# [0.8721902 , 0.1278098 ],
# [0.71194639, 0.28805361],
# [0.34348896, 0.65651104],
# [0.70226697, 0.29773303],
# [0.06738996, 0.93261004],
# [0.59805546, 0.40194454],
# [0.32885342, 0.67114658],
# [0.48644747, 0.51355253],
# [0.42864815, 0.57135185],
# [0.56346556, 0.43653444],
# [0.25853143, 0.74146857],
# [0.77643225, 0.22356775],
# [0.87632449, 0.12367551],
# [0.15009273, 0.84990727],
# [0.13434695, 0.86565305],
# [0.85072699, 0.14927301],
# [0.86772104, 0.13227896],
# [0.89628756, 0.10371244],
# [0.8861334 , 0.1138666 ],
# [0.34797629, 0.65202371],
# [0.89917049, 0.10082951],
# [0.7299734 , 0.2700266 ],
# [0.12221446, 0.87778554],
# [0.81719691, 0.18280309],
# [0.61865109, 0.38134891],
# [0.37370299, 0.62629701],
# [0.38348342, 0.61651658],
# [0.86463299, 0.13536701],
# [0.25161297, 0.74838703],
# [0.1038833 , 0.8961167 ],
# [0.57648059, 0.42351941],
# [0.8547685 , 0.1452315 ],
# [0.31415124, 0.68584876],
# [0.33907973, 0.66092027],
# [0.8434772 , 0.1565228 ],
# [0.23261126, 0.76738874],
# [0.88859274, 0.11140726],
# [0.35220567, 0.64779433],
# [0.58554859, 0.41445141],
# [0.36143288, 0.63856712],
# [0.13634058, 0.86365942],
# [0.67797003, 0.32202997],
# [0.88600085, 0.11399915],
# [0.13946115, 0.86053885],
# [0.8709549 , 0.1290451 ],
# [0.20616021, 0.79383979],
# [0.76719903, 0.23280097],
# [0.77437245, 0.22562755],
# [0.50324031, 0.49675969],
# [0.91079839, 0.08920161],
# [0.84970739, 0.15029261],
# [0.54874088, 0.45125912],
# [0.48192067, 0.51807933]])
분류 결정 임계값에 따른 정밀도, 재현율 곡선
- precision_reacall_curve(y_test, pred_proba_class) 함수를 통해 임계값에 따른 정밀도, 재현율의 변화값을 제공
Precision/Recall Trade-off
- predict_proba() method 확인
pred_proba = lr_clf.predict_proba(X_test)
pred = lr_clf.predict(X_test)
print(f"pred_proba() 결과 Shape : {pred_proba.shape}")
print(f"pred_proba array에서 앞 3개만 sample로 추출\n:{pred_proba[:3]}")
# 예측 확률 array와 예측 결과값 array를 concatenate 하여 예측 확률과 결과값을 한눈에 확인
# reshape(-1,1)을 통해 pred를 1차원에서 2차원으로 변환
pred_proba_result = np.concatenate([pred_proba, pred.reshape(-1, 1)], axis=1)
print(f"두 개의 class 중에서 더 큰 확률을 class 값으로 예측 \n {pred_proba_result[:3]}")
# pred_proba() 결과 Shape : (179, 2)
# pred_proba array에서 앞 3개만 sample로 추출
# :[[0.44935227 0.55064773]
# [0.86335512 0.13664488]
# [0.86429645 0.13570355]]
# 두 개의 class 중에서 더 큰 확률을 class 값으로 예측
# [[0.44935227 0.55064773 1. ]
# [0.86335512 0.13664488 0. ]
# [0.86429645 0.13570355 0. ]]
- Binarizer 활용
- 생성자인 임계값을 조정하여 데이터 전처리를 수행할 수 있음
- 들어가는 parameter가 2차원이 되어야 하므로 fit시킬 때 2차원의 데이터를 입력(reshape(-1,1)이용)
from sklearn.preprocessing import Binarizer
X = [[1, -1, 2], [2, 0, 0], [0, 1.1, 1.2]]
# threshold 기준값보다 같거나 작으면 0을, 크면 1을 반환
binarizer = Binarizer(threshold=1.1)
print(binarizer.fit_transform(X))
# [[0. 0. 1.]
# [1. 0. 0.]
# [0. 0. 1.]]
- 분류 결정 임계값 0.5 기반에서 Binarizer를 이용하여 예측값 반환: predict method 흉내내기
from sklearn.preprocessing import Binarizer
# Binarizer의 threshold 설정값, 분류 결정 임계값임
custom_threshold = 0.5
# predict_proba() 변환값의 두 번째 column. 즉 Positive 클래스 column 하나만 추가하여 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)
# 오차 행렬
# [[108 10]
# [ 14 47]]
# 정확도: 0.8659, 정밀도: 0.8246, 재현율: 0.7705
- 분류 결정 임계값 0.4 기반에서 Binarizer를 이용하여 예측값 반환
# Binarizer의 threshold 설정값을 0.4로 설정. 즉 분류 결정 임곗값을 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)
# 오차 행렬
# [[97 21]
# [11 50]]
# 정확도: 0.8212, 정밀도: 0.7042, 재현율: 0.8197
- 여러개의 분류 결정 임곗값을 변경하면서 Binarizer를 이용하여 예측값 반환
# 테스트를 수행할 모든 임곗값을 리스트 객체로 저장.
thresholds = [0.4, 0.45, 0.50, 0.55, 0.60]
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 )
# 임곗값: 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
precision_recall_curve( y_test, predict_proba_c1)
- precision_recall_curve()를 이용하여 임계값에 따른 정밀도-재현율 값 추출
- precision_reacall_curve(y_test, pred_proba_class1)
- 임계값(threshold)가 달라짐에 따라 predict_proba_class1에 적용되는 predict method가 달라진다(Label 값이 다르게 부여)
- 그에 따라 달라진 결과값 pred로 인해 (pred, y_test)를 인자로 받는 precision, recall값이 달라짐
- 반환 값: precision, recall, thresholds (각각의 값들을 전부 ndarray로 반환)
from sklearn.metrics import precision_recall_curve
# Label 값이 1일 때의 예측 확률을 추출
pred_proba_class1 = lr_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)
print("반환된 precisions 배열의 Shape:", precisions.shape)
print("반환된 recalls 배열의 Shape:", recalls.shape)
print("thresholds 5 sample:", thresholds[:5])
print("precisions 5 sample:", precisions[:5])
print("recalls 5 sample:", recalls[:5])
# 반환된 임계값 배열 로우가 165건이므로 샘플로 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))
# 반환된 분류 결정 임곗값 배열의 Shape: (165,)
# 반환된 precisions 배열의 Shape: (166,)
# 반환된 recalls 배열의 Shape: (166,)
# thresholds 5 sample: [0.01974988 0.06956414 0.08402808 0.08474207 0.08920161]
# precisions 5 sample: [0.34078212 0.34269663 0.34463277 0.34659091 0.34857143]
# recalls 5 sample: [1. 1. 1. 1. 1.]
# 샘플 추출을 위한 임계값 배열의 index 10개: [ 0 15 30 45 60 75 90 105 120 135 150]
# 샘플용 10개의 임곗값: [0.02 0.11 0.13 0.14 0.16 0.24 0.32 0.45 0.62 0.73 0.87]
# 샘플 임계값별 정밀도: [0.341 0.372 0.401 0.44 0.505 0.598 0.688 0.774 0.915 0.968 0.938]
# 샘플 임계값별 재현율: [1. 1. 0.967 0.902 0.902 0.902 0.869 0.787 0.705 0.492 0.246]
- 임계값의 변경에 따른 Precision-Recall 변화 곡선을 그림
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
%matplotlib inline
def precision_recall_curve_plot(y_test , pred_proba_c1):
# threshold ndarray와 이 threshold에 따른 정밀도, 재현율 ndarray 추출.
precisions, recalls, thresholds = precision_recall_curve( y_test, pred_proba_c1)
# X축을 threshold값으로, Y축은 정밀도, 재현율 값으로 각각 Plot 수행. 정밀도는 점선으로 표시
plt.figure(figsize=(8,6))
threshold_boundary = thresholds.shape[0]
plt.plot(thresholds, precisions[0:threshold_boundary], linestyle='--', label='precision')
plt.plot(thresholds, recalls[0:threshold_boundary],label='recall')
# threshold 값 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, lr_clf.predict_proba(X_test)[:, 1] )
PR Curve Analysis
- Baseline 기준 PR Curve 성능 평가
- Baseline = $\frac {P} {N+P}$
- N: 전체 데이터의 수
- P: Positive Label의 수
- Baseline 아래의 면적을 Poor, 위의 면적을 Good으로 평가
- PR Curve 언제 사용할까?
- Data의 Class 분포도가 심하게 차이날 때 사용
- Accuracy에서 설명했듯, 실제 암 보유자와 아닌 사람의 분포가 심하게 차이가 난다면 ROC에서도 유의미한 결과를 얻기 어렵다
- 왜냐하면 암이 없는게 정답이고 실제로 대부분의 환자들이 암이 없을 경우에 의사가 모든 환자에게 암이 없다고 했을 경우, FPR은 낮은데 Recall은 높은 아주 훌륭한 모델처럼 보일 수 있기 떄문이다
- PR Curve가 계단식으로 생긴 이유
- 특정 임계값을 기준으로 값을 변화시켜가며 그래프를 그리기 때문
- Precision / Recall 에 영향을 줄 수 있는 threshold가 있다면 그걸 기준으로 PR curve를 그릴 수 있다.
4. F1 Score과 ROC-AUC 이해
정밀도와 재현율의 맹점
- 정밀도를 100%로 만드는 법: FP = 0으로 만든다
- 확실한 기준이 되는 경우만 Positive로 예측하고 나머지는 모두 Negative로 예측
- 정밀도 = $\frac {TP} {FP+TP}$
- 전체 환자 1000명 중 확실한 Positive 징후만 가진 환자는 단 1명이고 이 한 명만 Positive로 예측하고 나머지는 모두 Negative로 예측하더라도 FP는 0, TP는 1이 되므로 정밀도는 1/(1+0)으로 100%가 됨
- 재현율을 100%로 만드는 법: FN = 0으로 만든다
- 모든 데이터를 Positive로 예측하면 됨
- 재현율 = $\frac {TP} {FN+TP}$
- 전체 환자 1000명을 다 Positive로 예측하는 것. 이 중 실제 양성인 사람이 30명 정도라도 TN이 수치에 포함되지 않고 FN은 아예 0이므로 30/(30+0)으로 100%가 됨
F1 Score
- F1 Score은 정밀도와 재현율을 결합한 지표
- F1 Score는 정밀도와 재현율이 어느 한쪽으로 치우치지 않는 수치를 나타낼 때 상대적으로 높은 값을 가짐
$F1 = \frac{2}{\frac{1}{recall} + \frac{1}{precision}} = 2 \times \frac{precision \times recall}{precision + recall}$
합 분의 곱 * 2
- 만일 A 예측 모델의 경우 정밀도가 0.9, 재현율이 0.1로 극단적인 차이가 나고, B예측 모델은 정밀도가 0.5, 재현율이 0.5로 정밀도와 재현율이 큰 차이가 없다면 A예측 모델의 F1 Score는 0.18이고, B예측 모델의 F1 Score는 0.5로 B 모델이 A 모델에 비해 매우 우수한 F1 Score를 가지게 됨
- 사이킷런은 F1 Score를 위해 f1_score(y_test, pred) 함수를 제공
y_label이 encoding하지 않은 범주형 데이터일 때 F1 Score
f1_score(y_test, y_pred, pos_label='positive_label') 이용
from sklearn.metrics import f1_score
f1 = f1_score(y_test, pred)
print(f"F1 Score: {f1:.4f}")
# F1 Score: 0.7966
def get_clf_eval(y_test, pred):
confusion = confusion_matrix(y_test, pred)
accuracy = accuracy_score(y_test, pred)
precision = precision_score(y_test, pred)
recall = recall_score(y_test, pred)
# F1 스코어 추가
f1 = f1_score(y_test, pred)
print("오차 행렬")
print(confusion)
# f1 score print 추가
print(
"정확도: {0:.4f}, 정밀도: {1:.4f}, 재현율: {2:.4f}, F1:{3:.4f}".format(
accuracy, precision, recall, f1
)
)
thresholds = [0.4, 0.45, 0.50, 0.55, 0.60]
pred_proba = lr_clf.predict_proba(X_test)
get_eval_by_threshold(y_test, pred_proba[:, 1].reshape(-1, 1), thresholds)
ROC 곡선과 AUC
- ROC 곡선(Receiver Operation Characteristic Curve)과 이에 기반한 AUC 스코어는 이진 분류의 예측 성능 측정에서 중요하게 사용되는 지표
- 일반적으로 의학 분야에서 많이 사용되지만, ML의 이진 분류 모델의 예측 성능을 판단하는 중요한 평가 지표이기도 함
- ROC 곡선은 FPR이 변할 때 TPR(Recall)이 어떻게 변하는지를 나타내는 곡선 (FPR을 X축으로, TPR을 Y축으로 잡으면 FPR의 변화에 따른 TPR의 변화가 곡선 형태로 나타남
- 분류의 성능 지표로 사용되는 것은 ROC 곡선 면적에 기반한 AUC 값으로 결정
- AUC(Area Under Curve)값은 ROC 곡선 밑의 면적을 구한 것으로서 일반적으로 1에 가까울수록 좋은 수치
- TPR(True Positive Rate): 재현율
- $\frac {TP} {TP+FN}$
- 재현율은 민감도로도 불림
- FPR(False Positive Rate): 실제 Negative를 잘못 예측한 비율
- 실제는 Negative인데 Positive로 잘못 예측한 비율
- $\frac {FP} {TN+FP}$
- 임계값을 1로 하면 FPR = 0, 임계값을 0으로 하면 FPR = 1
- 임계값이 증가할수록 FPR, TPR 모두 감소
- 초반에 임계값이 증가하면서 TPR 감소 속도 << FPR 감소 속도
from sklearn.metrics import roc_curve
# Label 값이 1일 때의 예측확률을 추출
pred_proba_class1 = lr_clf.predict_proba(X_test)[:, 1]
fprs, tprs, thresholds = roc_curve(y_test, pred_proba_class1)
# 반환된 임계값 배열에서 샘플로 데이터를 추출하되, 임계값을 5 step으로 추출
# thresholds[0]은 max(예측확률)+1로 임의설정됨. 이를 제외하기 위해 np.arange는 1부터 시작
thr_index = np.arange(1, thresholds.shape[0], 5)
print("샘플 추출을 위한 임계값 배열의 index:", thr_index)
print("샘플 index로 추출한 임계값:", np.round(thresholds[thr_index], 2))
# 5step 단위로 추출된 임계값에 따른 FPR, TPR 값
print("샘플 임계값별 FPR: ", np.round(fprs[thr_index], 3))
print("샘플 임계값별 TPR: ", np.round(tprs[thr_index], 3))
# 샘플 추출을 위한 임계값 배열의 index: [ 1 6 11 16 21 26 31 36 41 46]
# 샘플 index로 추출한 임계값: [0.94 0.73 0.62 0.52 0.44 0.28 0.15 0.14 0.13 0.12]
# 샘플 임계값별 FPR: [0. 0.008 0.025 0.076 0.127 0.254 0.576 0.61 0.746 0.847]
# 샘플 임계값별 TPR: [0.016 0.492 0.705 0.738 0.803 0.885 0.902 0.951 0.967 1. ]
def roc_curve_plot(y_test, pred_proba_c1):
# 임곗값에 따른 FPR, TPR 값을 반환 받음.
fprs, tprs, thresholds = roc_curve(y_test, pred_proba_c1)
# ROC Curve를 plot 곡선으로 그림.
plt.plot(fprs, tprs, label="ROC")
# 가운데 대각선 직선을 그림.
plt.plot([0, 1], [0, 1], "k--", label="Random")
# FPR X 축의 Scale을 0.1 단위로 변경, X,Y 축명 설정등
start, end = plt.xlim()
plt.xticks(np.round(np.arange(start, end, 0.1), 2))
plt.xlim(0, 1)
plt.ylim(0, 1)
plt.xlabel("FPR( 1 - Sensitivity )")
plt.ylabel("TPR( Recall )")
plt.legend()
plt.show()
roc_curve_plot(y_test, lr_clf.predict_proba(X_test)[:, 1])
ROC 곡선과 AUC Score
roc_curve(y_test, predict_proba_c1)
- 임계값에 따른 ROC 곡선 데이터를 roc_curve(y_true, y_score) 함수로 제공
- y_true(y_test): 실제 Class 값 array (array shape = [데이터 건수])
- y_score(predict_proba_c1): predict_proba()의 반환값 중 Positive Column의 예측 확률
- Binary 분류 시 shape = [n_samples]
- 반환 값: fpr, tpr, thresholds (각각의 값들을 전부 array로 반환)
roc_auc_score(y_test, predict_proba_c1)
- AUC 스코어를 roc_auc_score(y_true, y_score) 함수로 제공
- y_true(y_test): 실제 Class 값 array (array shape = [데이터 건수])
- y_score(predict_proba_c1): predict_proba()의 반환값 중 Positive Column의 예측 확률
- Binary 분류 시 shape = [n_samples]
- 반환 값: AUC Score 값
from sklearn.metrics import roc_auc_score
pred_proba = lr_clf.predict_proba(X_test)[:, 1]
roc_score = roc_auc_score(y_test, pred_proba)
print("ROC AUC 값: {0:.4f}".format(roc_score))
# ROC AUC 값: 0.8987
- accuracy(y_test, pred)
- confusion_matrix(y_test, pred)
- precision_score(y_test, pred)
- recall_score(y_test, pred)
- f1_score(y_test, pred)
- roc_auc_score(y_test, predict_proba_c1)
def get_clf_eval(y_test, pred=None, pred_proba=None):
confusion = confusion_matrix(y_test, pred)
accuracy = accuracy_score(y_test, pred)
precision = precision_score(y_test, pred)
recall = recall_score(y_test, pred)
f1 = f1_score(y_test, pred)
# ROC-AUC 추가
roc_auc = roc_auc_score(y_test, pred_proba)
print("오차 행렬")
print(confusion)
# ROC-AUC print 추가
print(
"정확도: {0:.4f}, 정밀도: {1:.4f}, 재현율: {2:.4f},\
F1: {3:.4f}, AUC:{4:.4f}".format(
accuracy, precision, recall, f1, roc_auc
)
)