본문 바로가기
머신러닝/Logistic Regression

Precision(정밀도)과 Recall(재현율)의 Trade off

by 미생22 2024. 5. 7.
728x90

precision과 recall을 강제로 조정하는것은 강제로 threshold를 조정하는 것인데, 이것이 모델 성능의 향상을 말하지 않는다는 의견이 많습니다. 어차피 내가 얻은 데이터에서 작은 threshold의 움직임은 변화가 거의없고 또 threshold를 극단적으로 바꾸면 이게 의미가 없거든요. 그래도 방법은 소개해야할 것 같아서 강의에서 말하고 있습니다.

 

wine 데이터를 다시 불러옵니다.

import pandas as pd

red_url = 'https://raw.githubusercontent.com/PinkWink/ML_tutorial/master/dataset/winequality-red.csv'
white_url = 'https://raw.githubusercontent.com/PinkWink/ML_tutorial/master/dataset/winequality-white.csv'

red_wine = pd.read_csv(red_url, sep=';')
white_wine = pd.read_csv(white_url, sep=';')

red_wine['color'] = 1.
white_wine['color'] = 0.

wine = pd.concat([red_wine, white_wine])
wine['taste'] = [1. if grade>5 else 0. for grade in wine['quality']]

X = wine.drop(['taste', 'quality'], axis=1)
y = wine['taste']

 

데이터를 분리하겠습니다.

from sklearn.model_selection import train_test_split

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=13)

 

간단한 logistic regression을 사용해보겠습니다.

 

from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score

lr = LogisticRegression(solver='liblinear', random_state=13)
lr.fit(X_train, y_train)

y_pred_tr = lr.predict(X_train)
y_pred_test = lr.predict(X_test)

print('Train Acc.:', accuracy_score(y_train, y_pred_tr))
print('Test Acc.:', accuracy_score(y_test, y_pred_test))

 

Classification report

여기서 잠시 classification report에 대해 알아보겠습니다.

classification report라고,classification report(분류 보고서)는 머신러닝 분류 모델의 성능을 평가하기 위해 사용되는 지표들을 종합적으로 제공하는 보고서입니다. 일반적으로 scikit-learn 라이브러리에서 제공되는 함수를 통해 생성됩니다.

classification report에는 다음과 같은 주요 지표들이 포함됩니다:

정확도(Accuracy): 전체 예측 중에서 올바르게 분류된 샘플의 비율입니다.
정밀도(Precision): 양성 클래스로 예측된 샘플 중에서 실제로 양성 클래스인 샘플의 비율입니다. 즉, 양성 예측의 정확도를 나타냅니다.
재현율(Recall): 실제 양성 클래스 중에서 양성 클래스로 올바르게 예측된 샘플의 비율입니다. 즉, 실제 양성 샘플을 얼마나 잘 찾았는지를 나타냅니다.
F1 점수(F1 Score): 정밀도와 재현율의 조화 평균으로 계산되는 지표입니다. 두 지표가 어느 한 쪽으로 치우치지 않는 모델일수록 높은 값을 갖습니다.
지원( Support): 각 클래스의 샘플 수를 나타냅니다.
classification report는 다중 클래스 분류 문제에서 각 클래스마다 위의 지표들을 제공하며, 각 지표는 해당 클래스를 양성 클래스로 간주한 결과를 보여줍니다. 이러한 정보를 통해 모델의 분류 성능을 종합적으로 평가할 수 있습니다.
from sklearn.metrics import classification_report

print(classification_report(y_test, y_pred_test))

wine 데이터의 5등급 이상 이하로 0,1을 나누었었습니다.
1.0행의 precision은 1이라고 예측한 것 중 1일 확률
1.0행의 recall은 실제 1중에서 1을 맞춘 확률
support는 각 데이터의 개수입니다.
micro avg는 0.0일때와 1.0일때의 평균입니다.
weighted avg는 가중평균이죠. 개수를 고려해준겁니다.

 

Recall(sensitivity) 랑 Precision 등 계산하는 방법이었죠. 아래 그림을 통해 다시 상기해봅시다.

 

 

Confusion matrix

confusion_matrix() 함수는 분류 모델의 성능을 평가하기 위해 사용되는 행렬로, 실제 클래스와 예측된 클래스 간의 오차를 보여줍니다. 이 행렬은 scikit-learn 라이브러리에서 제공됩니다.
분류 모델이 예측한 결과와 실제 결과를 비교하여 생성되며, 각 클래스에 대한 예측 결과가 실제 클래스와 어떻게 관련되는지를 나타냅니다.

실제 클래스와 예측된 클래스 간의 오차를 나타내는 행렬입니다. 행렬의 각 행은 실제 클래스를 나타내고, 각 열은 예측된 클래스를 나타냅니다. 각 셀은 해당 클래스에 대한 예측 결과를 나타냅니다.
예를 들어, 이진 분류 문제의 경우 confusion_matrix() 함수는 2x2 행렬을 반환합니다. 각 셀은 다음을 나타냅니다:
- 좌상단 셀: 실제 Negative 클래스인 샘플 중에서 모델이 Negative 클래스로 예측한 샘플 수 (True Negative, TN)
- 우상단 셀: 실제 Negative 클래스인 샘플 중에서 모델이 Positive 클래스로 잘못 예측한 샘플 수 (False Positive, FP)
- 좌하단 셀: 실제 Positive 클래스인 샘플 중에서 모델이 Negative 클래스로 잘못 예측한 샘플 수 (False Negative, FN)
- 우하단 셀: 실제 Positive 클래스인 샘플 중에서 모델이 Positive 클래스로 예측한 샘플 수 (True Positive, TP)
이러한 오차 행렬을 통해 모델의 성능을 종합적으로 평가할 수 있습니다.
from sklearn.metrics import confusion_matrix

print(confusion_matrix(y_test, y_pred_test))

이건 전체 0중에서 275개를 0이라고 했고, 202개를 1이라고 했다는 겁니다.
전체 1중에서 691개를 1이라고 잘 맞추고, 132개를 0이라고 못맞췄다는 겁니다.

 

precision recall curve

import matplotlib.pyplot as plt
from sklearn.metrics import precision_recall_curve

plt.figure(figsize=(10,8))
pred = lr.predict_proba(X_test)[:,1] #1일때의 확률

precisions, recalls, thresholds = precision_recall_curve(y_test, pred)
plt.plot(thresholds, precisions[:len(thresholds)], label='precisions') #threshold에 대한 precision을 thresholds 크기만큼 그려라
plt.plot(thresholds, recalls[:len(thresholds)], label='recalls')
plt.grid()
plt.legend()
plt.show()

predict_proba(X_test)[:,1]인 것은 1일때의 확률만 가져와야 하기 때문입니다.

precision을 그냥쓰면 x축 대비 precision이 커서 에러가 나므로 threshold에 대한 precision을 thresholds 크기만큼 그리라고 [:,len(thresholds)]를 붙여줘야합니다.

precisions, recalls, thresholds를 각각 보면 

네 이렇게 생겼습니다.

 

현재 thresholds가 변해가는 것에 대해서 precision과 recall의 상황입니다. 
preidct함수까지만 쓰면 threshold를 0.5로 본겁니다.

 

pred_proba = lr.predict_proba(X_test)
pred_proba

각 행이 1300개의 X_test 데이터들에 대한 0일확률과 1일확률입니다.

predict_proba() 함수는 주어진 입력 데이터에 대해 각 클래스에 속할 확률을 반환합니다. 이 함수는 일반적으로 다중 클래스 분류 문제에서 사용됩니다.입력 데이터가 여러 개의 샘플로 구성되어 있는 경우, 각 샘플에 대해 각 클래스에 속할 확률을 계산하여 반환합니다. 즉, 함수의 반환값은 행이 입력 데이터의 샘플 수와 같고, 열이 클래스의 수와 같은 2차원 배열이 됩니다.예를 들어, 100개의 샘플이 주어진 경우, predict_proba() 함수는 각 샘플에 대해 클래스에 속할 확률을 계산하여 100xK 크기의 배열을 반환합니다. 여기서 K는 클래스의 수를 나타냅니다.따라서 predict_proba() 함수의 행이 여러 개인 이유는 입력 데이터에 대해 각 샘플마다 클래스에 속할 확률을 계산하기 위함입니다.

 

여기서 결과적으로 0인지 1인지를 오른쪽 열에 붙여보겠습니다.

y_pred_test

이 친구를

y_pred_test.reshape(-1, 1)

이렇게 바꾸고 합칠겁니다.

import numpy as np

np.concatenate([pred_proba, y_pred_test.reshape(-1,1)], axis=1)

numpy.concatenate() 함수는 주어진 배열들을 하나로 합치는 함수입니다. 이 함수는 주로 배열을 연결하거나 결합할 때 사용됩니다.
axis : 배열을 결합할 축을 지정합니다. 기본값은 0이며, 이는 첫 번째 축(행)을 따라 배열들이 결합됨을 의미합니다. 1로 설정하면 두 번째 축(열)을 따라 배열들이 결합됩니다.

 

이렇게 할 수 있다는걸 잠깐 알아둡니다...ㅎㅎ  

 

threshold 바꿔보기 - Binarizer

threshold를 0.5가 아닌, 사용자 지정을 받아 바꿔주면서 0과 1을 바꿔보겠습니다.
그걸 가능하게 해주는게 Binarizer입니다.

from sklearn.preprocessing import Binarizer

binarizer = Binarizer(threshold=0.6).fit(pred_proba) #주의할 점이 fit시키는 대상이 lr.pred_proba(X_test)인 pred_proba입니다.

여기서 주의할 점이 fit시키는 대상이 'lr.pred_proba(X_test)'인 pred_proba입니다.

pred_proba를 fit시키는겁니다.

 

pred_bin = binarizer.transform(pred_proba) #pred_proba 변수를 이용해서 transform 시킵니다.
pred_bin

pred_proba 안에 threshold=0.5가 적용되어 있으므로 binarizer.transform 변수를 이용해서 threshold=0.6으로 변환시킵니다.

결과가 이렇게 나옵니다. 우리는 또 1일 확률이 중요하므로, [:,1]을 붙여줍니다.

pred_bin = binarizer.transform(pred_proba)[:,1]
pred_bin

 

극단적으로 threshold=1로 바꾸면 

from sklearn.preprocessing import Binarizer

binarizer = Binarizer(threshold=1).fit(pred_proba)
pred_bin = binarizer.transform(pred_proba)[:,1]
pred_bin

이런식으로 바뀌기도 합니다.
다시 classification report를 뽑아보겠습니다.
이전에 print(classification_report(y_test, lr.predict(X_test)))와 비교하기 위해 얘도 확인해보겠습니다.

 

print(classification_report(y_test, lr.predict(X_test)))

 

print(classification_report(y_test, pred_bin))

728x90