ML & DL/파이썬 머신러닝 실전 가이드

[Python ML Guide] Section 3.2(평가 Evaluation): 오차행렬(Confusion Matrix), 정밀도(Precision), 재현율(Recall)

Jae. 2023. 8. 26. 06:29
728x90

 https://www.inflearn.com/course/%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EB%A8%B8%EC%8B%A0%EB%9F%AC%EB%8B%9D-%EC%99%84%EB%B2%BD%EA%B0%80%EC%9D%B4%EB%93%9C

 

[개정판] 파이썬 머신러닝 완벽 가이드 - 인프런 | 강의

이론 위주의 머신러닝 강좌에서 탈피하여 머신러닝의 핵심 개념을 쉽게 이해함과 동시에 실전 머신러닝 애플리케이션 구현 능력을 갖출 수 있도록 만들어 드립니다., [사진]상세한 설명과 풍부

www.inflearn.com

 

 


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

 

728x90