배운 내용
- DataFrame.describe()
- plt.hist()
- Binarizer
피마 인디언 당뇨병 데이터 세트는 북아메리카 피마 지역 원주민의 Type-2 당뇨병 결과 데이터입니다. 당뇨병의 원인으로는 서구화된 식습관과 유전을 꼽습니다. 피마 지역은 고립된 지역에서 인디언 고유의 혈통이 오랫동안 지속된 곳이었습니다.
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('/content/drive/MyDrive/military/diabetes.csv')
print(diabetes_data['Outcome'].value_counts())
diabetes_data.head(3)
'''
0 500
1 268
Name: Outcome, dtype: int64
결과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
'''
피마 인디언 당뇨병 데이터 세트는 다음 피처로 구성되어 있습니다.
- Pregnancies: 임신 횟수
- Glucose: 포도당 부하 검사 수치
- BloodPressure: 혈압(mm Hg)
- SkinThickness: 팔 삼두근 뒤쪽의 피하지방 측정값(mm)
- Insulin: 혈청 인슐린(mu U/ml)
- BMI: 체질량지수(체중(kg)/(키(m))^2))
- DiabetesPedigreeFunction: 당뇨 내력 가중치 값
- Age: 나이
- Outcome: 클래스 결정값(0 또는 1)
피처 타입은 모두 숫자형이고, 임신 횟수, 나이와 같은 숫자형 피처와 당뇨 검사 수치 피처로 구성된 특징으로 볼 때, 별도의 피처 인코딩은 필요하지 않아 보입니다.
데이터 세트를 피처 데이터 세트와 클래스 데이터 세트로 나눈 후 각각의 세트를 학습 데이터 세트와 테스트 데이터 세트로 분리하겠습니다. 로지스틱 회귀를 이용해 예측을 수행하고 앞 예제에서 사용한 유틸리티 함수인 get_clf_eval(), get_eval_by_threshold(), precision_recall_curve_plot()을 이용해 성능 평가 지표를 출력하고 재현율 곡선을 시각화해보겠습니다.
# 피처 데이터 세트 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)[:, 1]
get_clf_eval(y_test, pred, pred_proba)
'''
오차 행렬
[[88 12]
[23 31]]
정확도: 0.7727, 정밀도: 0.7209, 재현율: 0.5741, F1:0.6392, AUC:0.7919
'''
앞서 diabetes_data['Outcome']에서 0이 500개, 1이 268개 였으므로, Negative가 전체 데이터의 65%여서 정확도 보다는 재현율 성능에 초점을 맞춰보겠습니다. 먼저 정밀도 재현율 곡선을 보고 임곗값별 정밀도와 재현율 값의 변화를 확인하겠습니다.
pred_proba_c1 = lr_clf.predict_proba(X_test)[:, 1]
precision_recall_curve_plot(y_test, pred_proba_c1)
'''
결과2
'''
임곗값이 0.42일 때 정밀도와 재현율이 어느 정도 균형을 맞출 것 같습니다. 하지만, 두 개의 지표 모두 0.7이 안되는 수치로 인위적으로 조작이 필요해 보입니다. 조작하기 전 다시 데이터 값을 점검하겠습니다.
DataFrame.describe()
diabetes_data.describe()
'''결과3'''
데이터 값을 보면 min에 값이 0으로 된 피처가 상당히 많습니다. 예를 들어 Glucose 피처는 포도당 수치인데 min 값이 0인 것은 말이 안됩니다. Glucose 피처의 히스토그램을 확인해보면 0 값이 일정 수준 존재하는 것을 알 수 있습니다.
plt.hist()
bins는 막대 영역(bin)을 얼마나 채우는지 결정해주는 변수이다. 전체 범위를 해당 (입력값+1)의 범위로 나눈 값을 너비로 한다. 아래 예에서는 범위를 11로 나눈다.
plt.hist(diabetes_data['Glucose'], bins=10)
'''
(array([ 5., 0., 4., 32., 156., 211., 163., 95., 56., 46.]),
array([ 0. , 19.9, 39.8, 59.7, 79.6, 99.5, 119.4, 139.3, 159.2,
179.1, 199. ]),
<a list of 10 Patch objects>)
결과4
'''
min()값이 0으로 된 피처에 대해 0값이 전체 데이터의 몇%의 비율로 존재하는지 확인해보겠습니다. 확인할 피처는 'Glucose', 'BloodPressure', 'SkinThickness', 'Insulin', 'BMI'입니다.(Pregnancies는 출산 횟수를 의미하므로 제외합니다.)
zero_count = databetes_data[diabetes_data[feature] ==0]은 DataFrame이 반환되므로 column을 선택해 Series로 변환해줘야한다.
# 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))
'''
Glucose 0건수는 5, 퍼센트는 0.65%
BloodPressure 0건수는 35, 퍼센트는 4.56%
SkinThickness 0건수는 227, 퍼센트는 29.56%
Insulin 0건수는 374, 퍼센트는 48.70%
BMI 0건수는 11, 퍼센트는 1.43%
'''
이들 데이터들 중 SkinThickness와 Insulin은 29%, 48%로 0값이 대단히 많습니다. 이들 데이터를 일괄적으로 삭제할 경우 학습에 효과적이지 않을 수 있어, 위 feature들을 모두 평균값으로 대체하겠습니다.
로지스틱 회귀의 경우 일반적으로 숫자 데이터에 스케일링을 하는 것이 좋습니다. 피처 스케일링 후 성능 평가 지표를 확인해보겠습니다.
# zero_features 리스트 내부에 저장된 개별 피처들에 대해서 0값을 평균값으로 대체
mean_zero_features = diabetes_data[zero_features].mean()
diabetes_data[zero_features]=diabetes_data[zero_features].replace(0, mean_zero_features)
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)
# Positive의 예측확률만 추출
pred_proba = lr_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:0.8433
'''
데이터 변환과 스케일링을 통해 성능 수치가 일정 수준 개선됐습니다. 하이퍼 파라미터 튜닝은 로지스틱 회귀를 본격적으로 배운 후 시도해보겠습니다. 재현율은 여전히 낮은 수치여서 Threshold를 변화시키며 성능 수치가 어느 정도 개선되는지 확인해보겠습니다.
임곗값을 0.3에서 0.5까지 0.03씩 변화시키면서 재현율과 다른 평가 지표의 값을 출력합니다.
thresholds = [0.3, 0.33, 0.36, 0.39, 0.42, 0.45, 0.48, 0.50]
get_eval_by_threshold(y_test, pred_proba.reshape(-1, 1), thresholds)
'''
임계값: 0.3
오차 행렬
[[67 33]
[11 43]]
정확도: 0.7143, 정밀도: 0.5658, 재현율: 0.7963, F1:0.6615, AUC:0.8433
임계값: 0.33
오차 행렬
[[72 28]
[12 42]]
정확도: 0.7403, 정밀도: 0.6000, 재현율: 0.7778, F1:0.6774, AUC:0.8433
임계값: 0.36
오차 행렬
[[76 24]
[15 39]]
정확도: 0.7468, 정밀도: 0.6190, 재현율: 0.7222, F1:0.6667, AUC:0.8433
임계값: 0.39
오차 행렬
[[78 22]
[16 38]]
정확도: 0.7532, 정밀도: 0.6333, 재현율: 0.7037, F1:0.6667, AUC:0.8433
임계값: 0.42
오차 행렬
[[84 16]
[18 36]]
정확도: 0.7792, 정밀도: 0.6923, 재현율: 0.6667, F1:0.6792, AUC:0.8433
임계값: 0.45
오차 행렬
[[85 15]
[18 36]]
정확도: 0.7857, 정밀도: 0.7059, 재현율: 0.6667, F1:0.6857, AUC:0.8433
임계값: 0.48
오차 행렬
[[88 12]
[19 35]]
정확도: 0.7987, 정밀도: 0.7447, 재현율: 0.6481, F1:0.6931, AUC:0.8433
임계값: 0.5
오차 행렬
[[90 10]
[21 33]]
정확도: 0.7987, 정밀도: 0.7674, 재현율: 0.6111, F1:0.6804, AUC:0.8433
'''
전반적으로 봤을 때 임계값이 0.48에서 성능이 가장 좋아보입니다. 임계값을 0.48로 낮춘 상태에서 다시 예측을 해보겠습니다. 사이킷런의 predict()는 임곗값을 설정하는 기능을 제공하지 않아서 Binarizer 클래스에 predict_proba()로 추출한 예측 결과 확률을 입력해서 임곗값에 따라 예측 클래스 값을 구해보도록 하겠습니다.
# 임곗값을 0.48로 설정한 Binarizer 생성
binarizer = Binarizer(threshold=0.48)
# 위에서 구한 lr_clf의 predict_proba() 예측 확률 array에서 1에 해당하는 칼럼값을 Binarizer 변환.
pred_th_048 = binarizer.fit_transform(pred_proba.reshape(-1, 1))
get_clf_eval(y_test, pred_th_048, pred_proba)
'''
오차 행렬
[[88 12]
[19 35]]
정확도: 0.7987, 정밀도: 0.7447, 재현율: 0.6481, F1:0.6931, AUC:0.8433
'''
결과가 올바르게 나왔음을 확인할 수 있습니다.
출처: 파이썬 머신러닝 완벽 가이드 (권철민)
'파이썬 머신 러닝 완벽 가이드' 카테고리의 다른 글
분류 실습 - 캐글 산탄데르 고객 만족 예측 (0) | 2021.11.14 |
---|---|
분류 (0) | 2021.11.06 |
평가 (0) | 2021.10.27 |
타이타닉 생존자 예측 (0) | 2021.10.20 |
사이킷런 scikit-learn (0) | 2021.10.15 |