본문 바로가기
Data/머신러닝

[파이썬머신러닝완벽가이드]03.평가(2)

by mozi2 2023. 5. 13.
반응형
X = diabetes_data.iloc[:,:-1]
y = diabetes_data.iloc[:,-1]

#standardScaler 클래스를 이용해 피처 데이터 세트에 일괄적으로 스케일링 적용
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)

X_train, X_test, y_train, y_test = train_test_split(X_scaled, y, test_size=0.2, random_state=156, stratify=y)

#로지스틱 회귀로 학습, 예측 및 평가 수행
Ir_clf = LogisticRegression()
Ir_clf.fit(X_train, y_train)
pred = Ir_clf.predict(X_test)
pred_proba = Ir_clf.predict_proba(X_test)[:,1]

get_clf_eval(y_test, pred, pred_proba)​
diabetes_data.info()

1. F1 스코어

 : 정밀도와 재현율을 결합한 지표

: 정밀도와 재현율이 어느 한쪽으로 치우치지 않을 때, 상대적으로 높은 값을 갖을 수 있다. 

 

from sklearn.metrics import f1_score

f1 = f1_score(y_test, pred)

print('F1 스코어 : {0:.4f}'.format(f1))
F1 스코어 : 0.7966

 : 타이타닉 생존자 예측에서임계값을 변화시키면서 F1 스코어를 포함한 평가 지표를 구해보자

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_score(y_test, pred)
    print('오차행렬')
    print(confusion)
    print('정확도: {0:.4f}, 정밀도: {1:.4f}, 재현율: {2:.4f}, F1: {3:.4f}'
          .format(accuracy, precision, recall, f1))
    
thresholds = [0.4, 0.45, 0.5, 0.55, 0.6]
pred_proba = lr_clf.predict_proba(X_test)
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, F1: 0.7576
임곗값: 0.45
오차행렬
[[105  13]
 [ 13  48]]
정확도: 0.8547, 정밀도: 0.7869, 재현율: 0.7869, F1: 0.7869
임곗값: 0.5
오차행렬
[[108  10]
 [ 14  47]]
정확도: 0.8659, 정밀도: 0.8246, 재현율: 0.7705, F1: 0.7966
임곗값: 0.55
오차행렬
[[111   7]
 [ 16  45]]
정확도: 0.8715, 정밀도: 0.8654, 재현율: 0.7377, F1: 0.7965
임곗값: 0.6
오차행렬
[[113   5]
 [ 17  44]]
정확도: 0.8771, 정밀도: 0.8980, 재현율: 0.7213, F1: 0.8000

2. ROC 곡선과 AUC 

: FPR(False Positive Rate)이 변할 때 TPR(True Positive Rate)이 어떻게 변하는지를 나타내는 곡선으로, FPR을 X 축으로, TPR을 Y 축으로 잡으면 FPR의 변화에 따른 TPR의 변화가 곡선 형태로 나타난다. TPR은 재현율을 나타내고 이에 대응하는 지표인 TNR은 특이성을 나타낸다.

FPR = \frac{FP}{(FP + TN)} = 1 - TNR = 1 - 특이성 

ROC 곡선이 가운데 직선에 가까울수록 성능이 떨어지는 것이며, 멀어질수록 성능이 뛰어난 것이다. 사이킷런은 ROC 곡선을 구하기 위해 roc_curve() API를 제공하며, 사용법은 precision_recall_curve()와 유사하다.

  • 입력파라미터
    • y_true: 실제 클래스 값 array(array shape = [데이터 건수])
    • y_score: predict_proba()의 반환 값 array에서 Positive 칼럼의 예측 확률이 보통 사용
  • 반환값
    • fpr: fpr 값을 array로 반환
    • tpr: tpr 값을 array로 반환
    • thresholds: thresholds 값 array
from sklearn.metrics import roc_curve

# 레이블 값이 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(f'샘플 추출을 위한 임곗값 배열의 index: {thr_index}')
print(f'샘플 index로 추출한 임곗값: {np.round(thresholds[thr_index], 2)}')

# 5 step 단위로 추출된 임곗값에 따른 FPR, TPR 값
print(f'샘플 임곗값별 FPR: {np.round(fprs[thr_index], 3)}')
print(f'샘플 임곗값별 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.   ]

 : 위의 결과로 임곗값이 1에서부터 점점 작아지면서 FPR이 점점 커지고 TPR은 가파르게 커짐을 알 수 있다.

def roc_curve_plot(y_test, pred_proba_c1):
    fprs, tprs, thresholds = roc_curve(y_test, pred_proba_c1)
    plt.plot(fprs, tprs, label='ROC')
    # 가운데 대각선 직선을 그림
    plt.plot([0, 1], [0, 1], 'k--', label='Random')
    
    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()

roc_curve_plot(y_test, pred_proba[:, 1])

: 일반적으로 ROC 곡선 자체는 FPR과 TPR의 변화 값을 보는 데 이용하며 분류의 성능 지표로 사용되는 것은 ROC 곡선 면적에 기반한 AUC 값으로 결정한다. AUC(Area Under Curve) 값은 곡선 밑의 면적을 구한 것으로 일반적으로 1에 가까울수록 좋은 수치이다.

: AUC 수치가 커지려면 FPR이 작은 상태에서 얼마나 큰 TPR을 얻을 수 있는지가 키이다.

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

 : get_clf_eval() 함수에 roc_auc_score()를 이용해 ROC AUC값을 측정하는 로직을 추가하는데 ROC AUC는 예측 확률값을 기반으로 계산되므로 get_clf_eval()함수의 인자로 받을 수 있도록 get_clf_eval(y_test, pred=None, pred_proba=None)으로 함수명을 변경해준다.

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

 

2. 피마 인디언 당뇨병 예측 

: 피마 인디언 당뇨병 데이트 세트를 이용해 당뇨병 여부를 판단하는 머신러닝 예측 모델을 수립해본다

: 피마 인디언 당뇨병 데이터 세트의 피처 구성이다.

  • Pregnancies: 임신횟수
  • Glucose: 포도당 부하 검사 수치
  • BloodPressure: 혈압(mm Hg)
  • SkinThickness: 팔 삼두근 뒤쪽의 피하지방 측정값(mm)
  • Insulin: 혈청 인슐린
  • BMI: 체질량 지수
  • DiabetesPedigreeFunction: 당뇨 내력 가중치 값
  • Age: 나이
  • Outcome: 클래스 결정값 (0 또는 1)
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline

from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, precision_score, recall_score, roc_auc_score
from sklearn.metrics import f1_score, confusion_matrix, precision_recall_curve, roc_curve
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression

diabetes_data = pd.read_csv('C:\/diabetes.csv')
print(diabetes_data['Outcome'].value_counts())
diabetes_data.head(3)

: Negative 0 이 Positive 1보다 상대적으로 많은 것을 확인할 수 있다.

 : feature 타입과 Null 개수를 살펴보자 

diabetes_data.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 768 entries, 0 to 767
Data columns (total 9 columns):
 #   Column                    Non-Null Count  Dtype  
---  ------                    --------------  -----  
 0   Pregnancies               768 non-null    int64  
 1   Glucose                   768 non-null    int64  
 2   BloodPressure             768 non-null    int64  
 3   SkinThickness             768 non-null    int64  
 4   Insulin                   768 non-null    int64  
 5   BMI                       768 non-null    float64
 6   DiabetesPedigreeFunction  768 non-null    float64
 7   Age                       768 non-null    int64  
 8   Outcome                   768 non-null    int64  
dtypes: float64(2), int64(7)
memory usage: 54.1 KB

: null 값은 없으며 피처의 타입은 모두 숫자형이다. 그러므로 별도의 인코딩은 필요하지 않다. 이제 로지스틱 회귀를 이용해 예측 모델을 생성해보자.

: 앞 예제에서 사용한 유틸리티 함수인 get_clf_eval(), get_eval_by_threshold(), precision_recall_curve_plot을 이용해 성능 평가 지표를 출력하고 재현율 곡선을 시각화해본다. 

#로지스틱 회귀로 학습, 예측 및 평가 수행 
Ir_clf = LogisticRegression(solver='liblinear')
Ir_clf.fit(X_train, y_train)
pred = Ir_clf.predict(X_test)
pred_proba = Ir_clf.predict_proba(X_test)[:,1]

get_clf_eval(y_test, pred, pred_proba)
오차 행렬
[[108  10]
 [ 14  47]]
정확도:0.8659, 정밀도:0.8246, 재현율:0.7705.\F1:0.7966, AUC:[4:.4f]

전체 데이터의 65%가 Negative이므로 정확도보다는 재현율 성능에 좀 더 초점을 맞춰보자

먼저 정밀도 재현율 곡선을 보고 임곗값별 정밀도와 재현율 값의 변화를 확인해보자

pred_proba_c1 = Ir_clf.predict_proba(X_test)[:,1]
precision_recall_curve_plot(y_test, pred_proba_c1)

재현율 곡선을 보면 임곗값을 0.42 정도로 낮추면 정밀도와 재현율이 어느정도 균형을 맞출 것 같다. 하지만 두 개의 지표 모두 0.7이 안되는 수치로 값이 낮다. 

임곗값을 다시 조작하기 전에 데이터 값을 점검해보자

diabetes_data.describe()

이 데이터를 보면 min()값이 0으로 말이 되지 않는 수치로 나와있다. 

plt.hist(diabetes_data['Glucose'], bins=100)
plt.show()

Glucose 피처 히스토그램을 보면 0값이 일정 수준 존재하는 것을 알 수 있다.

min()값이 0으로 돼있는 피처에 대해 0 값의 건수 및 전체 데이터 건수 대비 몇 퍼센트의 비율로 존재하는지 확인해보자

#0값을 검사할 피처명 리스트
zero_features = ['Glucose', 'BloodPressure', 'SkinThickness', 'Insulin', 'BMI']

#전체 데이터 건수
total_count = diabetes_data['Glucose'].count()

# 피처별로 반복하면서 데이터 값이 0인 데이터 건수를 추출하고, 퍼센트 계산
for feature in zero_features:
    zero_count = diabetes_data[diabetes_data[feature]==0][feature].count()
    print('{0}0 건수는 {1}, 퍼센트는{2:.2f}%'.format(feature, zero_count, 100*zero_count/total_count))
Glucose0 건수는 5, 퍼센트는0.65%
BloodPressure0 건수는 35, 퍼센트는4.56%
SkinThickness0 건수는 227, 퍼센트는29.56%
Insulin0 건수는 374, 퍼센트는48.70%
BMI0 건수는 11, 퍼센트는1.43%

전체 데이터 건수가 많지 않기 때문에 이들 데이터를 일괄적으로 삭제할 경우에는 학습을 효과적으로 수행하기 어려울 것같다. 위 피처의 0 값을 평균값으로 대체해보자

#zero_features 리스트 내부에 저장된 개별 피처들에 대해서 0값을 평균 값으로 대체
mean_zero_features = diabetes_data[zero_features].mean()
diabetes_data[zero_features]=diabetes_data[zero_features].replace(0,mean_zero_features)

0 값을 평균값으로 대체한 데이터 세트에 피처 스케일링을 적용해 변환해보자. 
로지스틱 회귀의 경우 숫자 데이터에 스케일링을 적용하는 것이 좋다. 
이후에 다시 학습/테스트 데이터 세트를 나누고 로지스틱 회귀를 적용해 성능 평가 지표를 확인해보자 

X = diabetes_data.iloc[:,:-1]
y = diabetes_data.iloc[:,-1]

#standardScaler 클래스를 이용해 피처 데이터 세트에 일괄적으로 스케일링 적용
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)

X_train, X_test, y_train, y_test = train_test_split(X_scaled, y, test_size=0.2, random_state=156, stratify=y)

#로지스틱 회귀로 학습, 예측 및 평가 수행
Ir_clf = LogisticRegression()
Ir_clf.fit(X_train, y_train)
pred = Ir_clf.predict(X_test)
pred_proba = Ir_clf.predict_proba(X_test)[:,1]

get_clf_eval(y_test, pred, pred_proba)
오차 행렬
[[90 10]
 [21 33]]
정확도:0.7987, 정밀도:0.7674, 재현율:0.6111.\F1:0.6804, AUC:[4:.4f]

데이터 변환과 스케일링을 통해 성능 수치가 일정 수준 개선됐지만 재현율 수치는 개선이 더 필요해보인다.

분류 결정 임곗값을 0.3에서 0.5까지 0.03 씩 변화시키면서 성능 수치가 어느 정도 개선되는지 확인해보자

thresholds = [0.3, 0.33, 0.36, 0.39, 0.42, 0.45, 0.48, 0.50]
pred_proba = Ir_clf.predict_proba(X_test)
get_eval_by_threshold(y_test, pred_proba[:,1].reshape(-1,1), thresholds)
임곗값: 0.3
정확도: 0.7143, 정밀도: 0.5658, 재현율: 0.7963
임곗값: 0.33
정확도: 0.7403, 정밀도: 0.6000, 재현율: 0.7778
임곗값: 0.36
정확도: 0.7468, 정밀도: 0.6190, 재현율: 0.7222
임곗값: 0.39
정확도: 0.7532, 정밀도: 0.6333, 재현율: 0.7037
임곗값: 0.42
정확도: 0.7792, 정밀도: 0.6923, 재현율: 0.6667
임곗값: 0.45
정확도: 0.7857, 정밀도: 0.7059, 재현율: 0.6667
임곗값: 0.48
정확도: 0.7987, 정밀도: 0.7447, 재현율: 0.6481
임곗값: 0.5
정확도: 0.7987, 정밀도: 0.7674, 재현율: 0.6111

임곗값 0.48이 전체적인 성능 평가 지표를 유지하면서 재현율을 약간 향상시키는 좋은 임곗값으로 보인다.

앞에서 학습된 로지스틱 회귀 모델을 이용해 임곗값 0.48로 낮춘 상태에서 다시 예측을 해본다. 

#임곗값을 0.48로 설정한 Binarizer 생성
binarizer = Binarizer(threshold=0.48)

#위에서 구한 Ir_clf.predict_proba() 예측 확률 array 에서 1에 해당하는 칼럼값을 Binarizer 변환
pred_th_048 = binarizer.fit_transform(pred_proba[:,1].reshape(-1,1))

get_clf_eval(y_test, pred_th_048, pred_proba[:,1])

[output]
오차 행렬
[[88 12]
 [19 35]]
정확도:0.7987, 정밀도:0.7447, 재현율:0.6481, F1:0.6931, AUC:0.8433

728x90
반응형