준비하는 대학생

[기계학습] 평가지표 본문

Programming/Machine learning

[기계학습] 평가지표

Bangii 2023. 6. 19. 16:20

정확도

정확도란?

  • 실제 데이터에서 예측 데이터가 얼마나 같은지 판단하는 지표

$$ 정확도 = \frac{예측\ 결과가\ 동일한\ 데이터\ 건수}{전체\ 예측\ 데이터\ 건수} $$

정확도 지표의 이진분류 모델 성능 왜곡

import numpy as np
from sklearn.metrics import accuracy_score, confusion_matrix, precision_score, recall_score

# 예를 들어, 데이터셋에 클래스 1이 5개, 클래스 0이 95개 있는 경우
y_true = [0] * 95 + [1] * 5
y_pred = [0] * 100  # 모델이 항상 0으로만 예측

# 정확도 계산
accuracy = accuracy_score(y_true, y_pred)

print(f'정확도: {accuracy}')
정확도: 0.95

위 코드를 실행하면, 정확도는 매우 높게 나오지만 클래스 1에 대한 분류를 하지 못하는 것을 알 수 있습니다. 이처럼 0으로만 예측했을 때 정확도가 95%로 수준 높은 모델들과 비슷한 결과가 나온다는 것이라고 할 수 없습니다.

따라서 이러한 한계점을 극복하기 위해 정확도 외 여러 평가지표를 사용하여 모델의 성능을 평가해야합니다.

오차행렬(혼동행렬, confusion matrix)

오차행렬이란?

  • 학습된 분류모델이 예측을 수행하면 얼마나 햇갈리고 있는지 알려주는 지표
  • 이진분류의 예측 오류가 각각 얼마인지 어떤 유형에서 오류가 발생하고 있는지를 보여주는 지표

TN,FP,FN,TP

실제 값 \ 예측 값 Negative(0) Positive(1)
Negative(0) TN (True Negative) FP (False Positive)
Positive(1) FN (False Negative) TP (True Positive)

앞의 True/False는 예측값과 실제값의 일치 유무를 뒤에 Negative/Positive 는 예측값에 따라 달라집니다.

  • TN : 예측값을 Negative로 예측하였고, 실제값과 일치한 경우
  • FP : 예측값을 Positive로 예측하였고, 실제값과 다른 경우
  • FN : 예측값을 Negative로 예측하였고, 실제값과 다른 경우
  • TP : 예측값을 Positive로 예측하였고, 실제값과 일치한 경우

오차행렬 출력

위 예제에 대해 오차행렬을 출력해 보자

from sklearn.metrics import confusion_matrix

# 혼동 행렬 계산
conf_matrix = confusion_matrix(y_true, y_pred)

print(f'혼동 행렬:\\n {conf_matrix}')
혼동 행렬:
 [[95  0]
 [ 5  0]]

출력된 결과를 보면 TN = 95, FT = 0, FN = 5, TT = 0 인 결과를 확인할 수 있습니다. 이 값들을 조합해 모델의 성능을 평가할수 있는 여러 지표인 정확도, 정밀도, 재현율 등을 알 수 있습니다.

앞서 설명했던 정확도도 오차행렬 값들을 이용해 구할 수 있습니다. (단순히 예측값이 실제값과 일치한 비율)d

$$ 정확도 = \frac{(TP + NP)}{(TP+NP+FP+FN)} $$

위에서 보았듯이 정확도는 결과가 한쪽으로 치우쳐져 있는 불균형한 데이터에 성능을 평가하는데 신뢰도가 떨어질 수 있다는 것을 알았습니다. 다음으로 불균형한 데이터에서 좀 더 신뢰성있는 평가지표에 대해 알아보도록 합시다.

정밀도 와 재현율

  • Positive 데이터 세트의 예측 성능에 초점을 맞춘 평가지표

$$ 정밀도 = \frac{TP}{(FP+TP)},\ 재현율 = \frac{TP}{(FN+TP)} $$

  • 정밀도 : 예측을 Positive(1)로 한 것중에 실제 값과 일치한 비율
  • 재현율 : 실제 값이 Posive(1)인 것등 중에 예측값과 일치한 비율

재현율이 중요한 경우

  • 실제 Postive 데이터를 Negative로 예측하는 경우 리스크가 큰 경우
  • 암 환자의 양성 유무를 판단하는 경우 양성인데 음성으로 예측하면 큰 위험을 초래하기 때문에 이 경우 재현율이 중요하다.

정밀도가 중요한 경우

  • 실제 Negative 데이터를 Positive로 예측하는 경우 리스크가 큰 경우
  • 스팸 문자를 분류하는 경우, 중요한 문자가 왔는데 해당 문자를 스팸메일로 분류하여 보지 못하는 경우 문제가 생길 수 있다. 이 경우 정밀도가 중요하다.

재현율과 정밀도 모두 TP가 커질수록 값이 증가하지만, 정밀도의 경우 FP의 값이 작아질수록(Positive로 잘못 예측한 경우가 작을 수록), 재현율의 경우 FN의 값이 작아질수록(Negative로 잘못 예측한 경우가 작을수록) 지표의 값이 커지게 된다.

가장 좋은 경우는 재현율과 정밀도가 모두 높은 경우이지만 이 특성을 생각하여 상황에 따라 더 중점적으로 보는 평가지표가 달라질 수 있다. 반면에 하나의 지표만 높고, 다른 지표는 낮은 경우에는 좋은 모델이라고 할 수 없습니다.

위 예제의 정밀도와 재현율

from sklearn.metrics import precision_score, recall_score

# 정밀도, 재현율 계산
precision = precision_score(y_true, y_pred, zero_division=0)
recall = recall_score(y_true, y_pred)

print(f'정밀도: {precision}')
print(f'재현율: {recall}')
정밀도: 0.0
재현율: 0.0

위 예제에서는 클래스가 1일때 맞춘 값들이 나오지 않기 때문에 정밀도 재현율 모두 0이 나오게 됩니다. 따라서 정확도로만 보았을 때 높은 성능을 보였던 모델이 정밀도와 재현율을 통해 이를 보완할 수 있습니다.

정밀도, 재현율 트레이드 오프

앞서 설명한 대로 정밀도와 재현율이 중요한 경우가 존재합니다. 이경우 분류 결정의 임계값을 조정해 정밀도나 재현율의 수치를 높일 수 있습니다. 하지만 정밀도와 재현율은 서로 보완하는 평가지표이기 때문에 어느 한 지표를 높이게 되면 다른 지표는 떨어질 수 있습니다. 이를 조정하는 것을 정밀도 재현율 트레이드 오프(Trade-off)라고 합니다.

대부분 sklearn 의 분류모델은 예측데이터가 어느 클래스로 분류할지에 대해 각 클래스에 들어갈 확률을 계산합니다. 이후 계산된 확률중에서 가장 큰 값에 해당하는 클래스로 예측하게 됩니다. 예를들어 0의 확률이 78%이고 1의 확률이 22%이변 22%보다 높은 78%의 확률을 가진 0으로 예측합니다. 일반적으로 이진분류인 경우 임곗값을 0.5(50%)로 정하고 이 기준 값보다 큰 경우 Positive, 작은 경우 Negative로 결정합니다.

sklearn 에서는 이 확률 값을 보여주는 predict_proba()를 제공합니다.

# 이진 데이터 분류 모델 
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, confusion_matrix, precision_score, recall_score
# load_breast_cancer: 유방암 데이터셋 로드
from sklearn.datasets import load_breast_cancer

# 데이터셋 로드
X, y = load_breast_cancer(return_X_y=True)
lg = LogisticRegression()

X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)
lg.fit(X_train, y_train)
pred_proba = lg.predict_proba(X_test)[:5]
pred = lg.predict(X_test)[:5]
print(f'예측 확률:\\n {pred_proba}')
print(f'예측:\\n {pred}')
예측 확률:
 [[9.94243819e-01 5.75618074e-03]
 [2.87434295e-02 9.71256570e-01]
 [2.26496762e-03 9.97735032e-01]
 [1.99399709e-01 8.00600291e-01]
 [1.07192749e-04 9.99892807e-01]]
예측:
 [0 1 1 1 1]

이 sklearn에서는 예측 확률이 0.5(임계값)을 만족하는 배열의 인덱스를 최종 예측 클래스로 결정합니다. 이러한 임계값을 다른 값으로 설정하여 예측하기 위해 sklearn 의 Binarizer 클래스를 이용해 확인해 보도록 하겠습니다.

Binarizer는 지정된 임계값을 매개변수로 갖고, 해당 매개변수보다 작거나 같으면 0으로 크면 1의 값으로 반환합니다.

from sklearn.preprocessing import Binarizer

custom_threshold = 0.5
# Binarizer 객체 생성
binarizer = Binarizer(threshold=custom_threshold)
# fit_transform 메서드로 예측 확률 계산
custom_predict = binarizer.fit_transform(pred_proba[:, 1].reshape(-1, 1))

print(f'혼동 행렬:\\n {confusion_matrix(y_test, custom_predict)}')
print(f'정밀도: {precision_score(y_test, custom_predict)}')
print(f'재현율: {recall_score(y_test, custom_predict)}')
혼동 행렬:
 [[52  1]
 [ 5 85]]
정밀도: 0.9883720930232558
재현율: 0.9444444444444444

이제 임계값을 변경했을 때 평가지표를 확인해보겠습니다.

from sklearn.preprocessing import Binarizer
# 임계값 변경 0.5 -> 0.2
custom_threshold = 0.2
# Binarizer 객체 생성
binarizer = Binarizer(threshold=custom_threshold)
# fit_transform 메서드로 예측 확률 계산
custom_predict = binarizer.fit_transform(pred_proba[:, 1].reshape(-1, 1))

print(f'혼동 행렬:\\n {confusion_matrix(y_test, custom_predict)}')
print(f'정밀도: {precision_score(y_test, custom_predict)}')
print(f'재현율: {recall_score(y_test, custom_predict)}')
혼동 행렬:
 [[51  2]
 [ 1 89]]
정밀도: 0.978021978021978
재현율: 0.9888888888888889

임계값을 낮추니 재현율이 올라가고 정밀도가 떨어졌습니다. 임계값을 낮추는 경우 원래 예측 결과보다 1로 예측하는 범위가 더 커지기 때문에 예측값을 1로 예측하는 경우가 더 늘어납니다. 따라서 TP,FP의 값이 증가하면서 정밀도의 값은 떨어지면서 재현율은 올라가는 것을 알 수 있습니다.

임계값의 변화에 따라 정밀도와 재현율이 어떻게 변하는지 그래프를 통해 확인하겠습니다.

from sklearn.preprocessing import Binarizer
from matplotlib import pyplot as plt
# custom_threshold = 0
precision_scores = []
recall_scores = []
for custom_threshold in np.arange(0,10)/10:
    # Binarizer 객체 생성
    binarizer = Binarizer(threshold=custom_threshold)

    # fit_transform 메서드로 예측 확률 계산
    custom_predict = binarizer.fit_transform(pred_proba[:, 1].reshape(-1, 1))
    precision_scores.append(precision_score(y_test, custom_predict))
    recall_scores.append(recall_score(y_test, custom_predict))

plt.plot(np.arange(0,10)/10,precision_scores, label='precision')
plt.plot(np.arange(0,10)/10,recall_scores, label='recall')
plt.legend()
plt.show()

위 그래프를 통해 재현율과 정밀도가 임계값에 따라 변하는 정도를 확인할 수 있습니다. 만약 재현율을 높이면서 어느정도의 정밀도를 갖게 하기 위해서는 0.3 정도의 임계값이 적절해 보입니다.

정밀도와 재현율의 맹정

위 그래프를 보면 Positive 임계값이 변경함에 따라 정밀도와 재현율의 수치가 변경됩니다. 두 수치를 서로 상호 보완할 수 있는 수준에서 임계값을 조정해야하며 단순히 하나의 지표를 높이기 위해 임계값을 변경하면 안됩니다.

단순히 각 지표의 숫자만 높이는 방법은 위 크래프에서 알 수 있듯이 임계값을 아주 작거나 큰 숫자로 하면 된다.

  • 임계값이 매우 낮은 경우, 대부분 Positive로 예측하기 때문에 TP의 값이 높고, FN의 값이 매우 작게 되므로 높은 재현율이 나오게 된다.
  • 임계값이 매우 높은 경우, 대부분 Negative로 예측하고 아주 높은 확률을 갖는 Positive만을 positive로 예측하기 때문에 FP의 값은 매우 작고 TP의 값만 존재할 수 있기 때문에 1에 가까운 정밀도이 나온다.

F1스코어

정밀도와 재현율을 결합한 지표, 즉 정밀도와 재현율을 종합하여 평가해 한쪽으로 치우치지 않는 수치를 나타낼 때 상대적으로 높은 값을 가집니다.

$$ F1 = \frac{2}{\frac{1}{recall}+\frac{1}{precision}}=2\frac{precision*recall}{precision+recall} $$

from sklearn.preprocessing import Binarizer
from sklearn.metrics import f1_score
from matplotlib import pyplot as plt

# custom_threshold = 0
precision_scores = []
recall_scores = []
f1_scores = []
for custom_threshold in np.arange(0,10)/10:
    # Binarizer 객체 생성
    binarizer = Binarizer(threshold=custom_threshold)

    # fit_transform 메서드로 예측 확률 계산
    custom_predict = binarizer.fit_transform(pred_proba[:, 1].reshape(-1, 1))
    precision_scores.append(precision_score(y_test, custom_predict))
    recall_scores.append(recall_score(y_test, custom_predict))
    f1_scores.append(f1_score(y_test, custom_predict))

plt.plot(np.arange(0,10)/10,precision_scores, label='precision')
plt.plot(np.arange(0,10)/10,recall_scores, label='recall')
plt.plot(np.arange(0,10)/10,f1_scores, label='f1')
plt.legend()
plt.show()

위 예제 코드에서 f1 socre 지표를 추가하여 비교하였습니다. 이 경우 약 0.3정도일때 가장 높은 f1 점수를 갖게 됩니다. 이처럼 f1의 값도 확인하며 정밀도와 예측률의 적정 비율을 갖고 있음을 확인하고 특정 성능이 더 우수한 임계값을 찾을 수 있습니다.

ROC곡선과 AUC

  • ROC : FPR(False Positive Rate)가 변할 때 TPR(True Positive Rate)가 어떻게 변하는지 나타내는 곡선
    • TPR = TP/(FN+TP) : 재현율(민감도) - 양성으로 정확히 예측되는 확률
    • TNR = TN/(FP+TN) : 특이성 - 음성으로 정확히 예측되는 확률
    • FPR = FP/(FP+TN) = 1 - TNR = 1 - 특이성

ROC 곡선을 찾기 위해선 FPR을 0~1로 변할 때 TPR의 값을 알아야 합니다. 이 떄 FPR의 값을 변경하기 위해선 위에서 했던 임계값을 조정하여 FPR의 값을 조절할 수 있습니다. 임계값이 0이면 모든 값을 Positive로 예측하기 깨문에 TNR이 0이 되고 FPR은 1이 됩니다. 임계값이 1인 경우엔 모든 값을 Negative로 예측하기 때문에 이때 TNR은 1이 되고 FPR은 0이 됩니다.

sklearn에서는 ROC곡선을 쉽게 찾을 수 있는 roc_curve() 메소드를 제공합니다. 해당 메소드를 사용해 위 예제의 ROC 곡선을 구해보겠습니다.

# roc curve
from sklearn.metrics import roc_curve

# roc_curve 함수로 FPR, TPR 계산
fprs, tprs, thresholds = roc_curve(y_test, pred_proba[:, 1])
thr_idx = np.arange(1, thresholds.shape[0])
print(f'샘플 추출을 위한 임계값 인덱스: {thr_idx}')
print(f'샘플용 임계값: {np.round(thresholds[thr_idx], 2)}')

# 추출한 임계값에 따른 FPR, TPR 값
print(f'샘플 임계값별 FPR: {np.round(fprs[thr_idx], 3)}')
print(f'샘플 임계값별 TPR: {np.round(tprs[thr_idx], 3)}')

# roc graph
plt.plot(fprs, tprs, label='ROC')
plt.show()
샘플 추출을 위한 임계값 인덱스: [1 2 3 4 5 6 7 8 9]
샘플용 임계값: [1.   0.9  0.87 0.35 0.27 0.21 0.05 0.03 0.  ]
샘플 임계값별 FPR: [0.    0.    0.019 0.019 0.038 0.038 0.208 0.208 1.   ]
샘플 임계값별 TPR: [0.011 0.8   0.8   0.978 0.978 0.989 0.989 1.    1.   ]

AUC의 값은 ROC 곡선 밑의 면적을 구한 으로 일반적으로 1에 가까울수록 좋은 수치입니다. AUC 수치가 커지려면 FPR이 작은 상태에서 TPR 수치가 크게 나와야 넓은 면적을 갖고 높은 성능을 갖고 있다고 평가할 수 있습니다.

# auc score
from sklearn.metrics import roc_auc_score
roc_auc = roc_auc_score(y_test, pred_proba[:, 1])
print(f'ROC AUC 값: {roc_auc}')
ROC AUC 값: 0.9939203354297694

 결론

앞서 살펴본 바와 같이, 정확도(Accuracy), 정밀도(Precision), 재현율(Recall), F1 스코어(F1 Score), ROC 곡선(ROC Curve) 및 AUC(Area Under the Curve)는 머신러닝 모델의 성능을 평가하는 데 중요한 지표들입니다. 하지만 이들 각각의 지표가 측정하는 것은 서로 다르기 때문에, 문제의 특성과 데이터의 특성에 따라 적절한 지표를 선택하는 것이 중요합니다. 예를 들어, 불균형한 데이터셋을 다루는 경우 정확도만으로는 모델의 성능을 제대로 판단하기 어려우므로, 정밀도, 재현율, F1 스코어와 같은 지표를 함께 고려하는 것이 좋습니다. 또한, ROC 곡선과 AUC는 모델의 성능을 다양한 임계값에서 평가하는 데 유용하며, AUC는 모델이 양성과 음성을 구별하는 능력을 종합적으로 나타내는 지표로 활용됩니다. 따라서, 모델의 성능을 정확히 이해하고 평가하려면 이러한 지표들을 적절히 활용하는 것이 중요합니다. 이를 통해 더욱 신뢰성 있는 머신러닝 모델을 구축할 수 있을 것입니다.

Comments