캐글에 있는 데이터로 피마 인디언 당뇨병 데이터 세트를 이용해 당뇨병 여부를 판단하는 머신러닝 예측 모델을 수립하고, 평가 지표를 이용해서 성능을 측정하며 튜닝할 것이다. 데이터는 https://www.kaggle.com/uciml/pima-indians-diabetes-database에 들어가서 다운받을 수 있다. 

 

데이터를 다운받은 다음 성능 지표들을 임포트하고, 정규화(StandardScaler) , 로지스틱 회귀 모델을 사용할 수 있는 LogisticRegression 클래스를 임포트한다. (Numpy, Pandas, Matplotlib.pyplot은 지난 포스팅과 동일하므로 생략)

 

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('diabetes.csv')
print(diabetes_data['Outcome'].value_counts())
diabetes_data.head(3)


일단 예측해야 하는 레이블 값이 'Outcome' 이라는 칼럼에 들어있다. 이 칼럼을 .value_counts() 메서드를 통해서 각 레이블 값의 분포를 확인한다. 이후에 info()를 통해서 null값이 있는지, 각 칼럼은 어떤 데이터 타입으로 되어있는지 확인한다. 

 

diabetes_data.info()

확인한 결과 전부 숫자형으로 되어있고, null 값은 딱히 없다는 것을 확인할 수 있다. 

 

 

get_clf_eval이라는 분류기 성능을 측정하는 함수를 정의한다. 이 함수는 정확도, 재현율, 정밀도, f1 Score, ROC_AUC Score을 측정하여 출력하는 함수이다.

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_score(y_test, pred)
    print('오차 행렬')
    print(confusion)
    print('정확도 : {0:.4f}, 정밀도 : {1:.4f}, 재현율 : {2:.4f},  \
    F1 : {3:.4f}, AUC : {4:.4f}'.format(accuracy, precision, recall, f1, roc_auc))
    
    
    
#피처 데이터 세트 X, 레이블 데이터 세트 y를 추출.
#맨 끝이 Outcome 칼럼으로 레이블 값임. 칼럼 위치 -1을 이용해 춫 ㄹ 
X = diabetes_data.iloc[:, :-1]
y = diabetes_data.iloc[:, -1]

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

#로지스틱 회귀로 학습
lr_clf = LogisticRegression()
lr_clf.fit(X_train, y_train)
pred = lr_clf.predict(X_test)
pred_proba = lr_clf.predict_proba(X_test)

get_clf_eval(y_test, pred, pred_proba)

 


위의 성능 지표에 대해서 잘 모른다면, 

 

https://dsbook.tistory.com/141?category=761052

(오차행렬, 정확도, 정밀도, 재현율)

https://dsbook.tistory.com/142?category=761052

(F1 Score, ROC&AUC)

여기를 확인하면 된다. 

 

 

함수를 정의하고 실행한 결과 재현율의 성능이 약 60%로 다른 평가 지표보다 낮다. 이런 경우, 정밀도 재현율 곡선을 보고 임계값을 조절하여 지표를 조절할 수 있다. 그러기 위해서 정밀도 재현율 곡선을 시각화하는 함수(precision_recall_curve_plot)를 정의한다. 

def precision_recall_curve_plot(y_test, pred_proba_c1):
    precisions, recalls, thresholds = precision_recall_curve(y_test, pred_proba_c1)
    
    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')
    
    start, end = plt.xlim()
    plt.xticks(np.round(np.arange(start, end , 0.1), 2))
    
    plt.xlabel('Threshold value'); plt.ylabel('Precision and Recall value')
    plt.legend(); plt.grid()
    plt.show()

 

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

 

 

 

함수를 정의한 후 시각화한 결과 임계값이 0.47인 부분에서 정밀도와 재현율이 비슷했다. 하지만, 두 수치 모두 0.7정도이다. 너무 낮은 수치이기 때문에, 이런 경우 데이터 값에 어떤 문제가 있는지 확인해 볼 필요가 있다. 데이터의 수치를 세부적으로 보기 위해 .describe() 메서드를 사용한다. Glucose 피처는 혈중 포도당 수치인데, 최소값(min)이 0으로 되어있는 것을 볼 수 있다. 이것은 말이 안되는 데이터이므로, 이와 비슷한 값들이 더 존재하는지 다른 값들도 확인한다. 

diabetes_data.describe()

 

 

'BloodPressure', 'SkinThickness', 'Insuline' ,'BMI'가 Glucose 피처처럼 0이 되면 안되는 값들이 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))

 

 

 

보면, SkinThickness와 Insuline을 제외한 피처에서 0의 비율은 5%를 넘지 않는 것에 비해, 나머지 두 피처는 29.56%, 48.70%를 차지하고 있다. 데이터가 많은 경우 칼럼을 삭제하는 방법도 있지만, 위의 데이터는 크지 않은 편이기 때문에, 평균값으로 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)

#로지스틱 회귀로 학습 
lr_clf = LogisticRegression()
lr_clf.fit(X_train, y_train)
pred = lr_clf.predict(X_test)
pred_proba = lr_clf.predict_proba(X_test)[: , 1]
get_clf_eval(y_test, pred, pred_proba)

다시 성능 측정을 한 결과 각 지표마다 전부 성능이 올라간 것을 확인할 수 있다. 

 

 

 

임계값을 조금씩 옮기면서 각 지표들이 어디에서 가장 높은 수치를 나타내는지 확인하는 함수를 정의하고 실행한다. 임계값은 0.3부터 0.5까지 비슷한 간격으로 8개로 정했다.

from sklearn.preprocessing import Binarizer
def get_eval_by_threshold(y_test, pred_proba_c1, thresholds):
    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)
        
        
thresholds = [0.3,0.33,0.36,0.39,0.42,0.45,0.48,0.50]
pred_proba = lr_clf.predict_proba(X_test)
get_eval_by_threshold(y_test, pred_proba[:, 1].reshape(-1, 1), thresholds)
        

가장 고르게 높은 수치들을 나타낸 임계값은 0.48인 것을 확인할 수 있다. 

 

이제 임계값을 0.48로 해서 성능측정을 해야하는데, 사이킷런의 .predict() 메서드는 사용자가 임의로 임계값을 지정할수 없도록 만들어 놓았다. 따라서 Binarizer 클래스를 통해서 임계값을 0.48로 지정해주고, 로지스틱 회귀 모델로 학습하여 확률을 나타낸 pred_proba의, 데이터를 이용해서 분류를 수행한다. 지표들을 확인해보면, 정밀도는 조금 떨어졌지만, 그 이상으로 재현율이 올라간 것을 확인할 수 있다. 

 

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

#위에서 구한 lr_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])

 

728x90
반응형
  • 네이버 블러그 공유하기
  • 네이버 밴드에 공유하기
  • 페이스북 공유하기
  • 라이프코리아트위터 공유하기
  • shared
  • 카카오스토리 공유하기