Machine Learning (7) - 분류 / 피마 인디언 당뇨병 예측
평가
피마 인디언 당뇨병 예측
고립된 지역에서 혈통이 지속돼 왔으나 20세기 후반에 서구화된 식습관으로 많은 당뇨환자가 발생했다.
따라서 당뇨의 원인중 하나인 유전적 특성을 배제하고 후천적 환경요인(식습관)과 당뇨병의 연관성에 대해 연구가 가능했다.
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression # 회귀모델을 이진분류용으로 사용
df = pd.read_csv('diabetes.csv')
df.sample(3)
Pregnancies | Glucose | BloodPressure | SkinThickness | Insulin | BMI | DiabetesPedigreeFunction | Age | Outcome | |
---|---|---|---|---|---|---|---|---|---|
604 | 4 | 183 | 0 | 0 | 0 | 28.4 | 0.212 | 36 | 1 |
699 | 4 | 118 | 70 | 0 | 0 | 44.5 | 0.904 | 26 | 0 |
107 | 4 | 144 | 58 | 28 | 140 | 29.5 | 0.287 | 37 | 0 |
데이터 확인
df.info() # null 없음, 모두 숫자값
<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
# 이진분류 레이블 확인
df.Outcome.value_counts()
0 500
1 268
Name: Outcome, dtype: int64
정밀도, 재현율 곡선 그리기
# X = df.iloc[:, :-1]
# y = df.iloc[;, -1]
X = df.drop(columns=['Outcome'])
y = df['Outcome']
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=156, stratify=y) # stratify: stratified KFold처럼 레이블 비율을 비슷하게 나눔
# 로지스틱 회귀로 학습, 예측, 평가
lr_clf = LogisticRegression(solver='liblinear')
lr_clf.fit(X_train, y_train)
pred = lr_clf.predict(X_test)
pred_proba = lr_clf.predict_proba(X_test)[:, 1] # 1이 될 확룰
# 최종 완성된 평가 함수
def get_clf_eval(y_test, pred, pred_proba_1):
from sklearn.metrics import accuracy_score, precision_score, recall_score, confusion_matrix, f1_score, roc_auc_score
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)
auc = roc_auc_score(y_test, pred_proba_1)
print('==오차 행렬==')
print(confusion)
print(f"정확도: {accuracy:.4f}, 정밀도: {precision:.4f}, 재현율: {recall:.4f}, F1: {f1:.4f}, AUC: {auc:.4f}")
# 그래프로 만들기
def precision_recall_curve_plot(y_test, pred_proba_1):
from sklearn.metrics import precision_recall_curve
import matplotlib.pyplot as plt
# ndarray 추출
precisions, recalls, thresholds = precision_recall_curve(y_test, pred_proba_1)
# x축은 threshold, y축은 precision, recall으로 plot 수행. precision은 점선
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')
# threshold 값 x축의 scale을 0.1 단위로 변경
start, end = plt.xlim() # 시작값과 끝값 리던
plt.xticks(np.round(np.arange(start, end, 0.1), 2))
# x축, y축 label과 legend, grid설정
plt.xlabel('임곗값')
plt.ylabel('정밀도와 재현율')
plt.legend()
plt.grid()
plt.show()
get_clf_eval(y_test, pred, pred_proba)
==오차 행렬==
[[87 13]
[22 32]]
정확도: 0.7727, 정밀도: 0.7111, 재현율: 0.5926, F1: 0.6465, AUC: 0.8083
- 낮은 재현율을 좀 더 올려보자!
precision_recall_curve_plot(y_test, pred_proba)
임곗값 0.42정도에 서로 만난다.
하지만 그 지점에서의 정밀도와 재현율이 너무 낮다.
전처리가 제대로 되었는지 확인한다.
전처리 확인
df.describe()
Pregnancies | Glucose | BloodPressure | SkinThickness | Insulin | BMI | DiabetesPedigreeFunction | Age | Outcome | |
---|---|---|---|---|---|---|---|---|---|
count | 768.000000 | 768.000000 | 768.000000 | 768.000000 | 768.000000 | 768.000000 | 768.000000 | 768.000000 | 768.000000 |
mean | 3.845052 | 120.894531 | 69.105469 | 20.536458 | 79.799479 | 31.992578 | 0.471876 | 33.240885 | 0.348958 |
std | 3.369578 | 31.972618 | 19.355807 | 15.952218 | 115.244002 | 7.884160 | 0.331329 | 11.760232 | 0.476951 |
min | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.078000 | 21.000000 | 0.000000 |
25% | 1.000000 | 99.000000 | 62.000000 | 0.000000 | 0.000000 | 27.300000 | 0.243750 | 24.000000 | 0.000000 |
50% | 3.000000 | 117.000000 | 72.000000 | 23.000000 | 30.500000 | 32.000000 | 0.372500 | 29.000000 | 0.000000 |
75% | 6.000000 | 140.250000 | 80.000000 | 32.000000 | 127.250000 | 36.600000 | 0.626250 | 41.000000 | 1.000000 |
max | 17.000000 | 199.000000 | 122.000000 | 99.000000 | 846.000000 | 67.100000 | 2.420000 | 81.000000 | 1.000000 |
min() 값이 0으로 돼 있는 피처가 상당히 많다.(특히 glucose와 bloodpressure 등 0이 나올 수 없는 데이터들이 있다.)
plt.hist(df.Glucose, bins=100)
plt.show()
0값이 5개 존재!
df.columns
Index(['Pregnancies', 'Glucose', 'BloodPressure', 'SkinThickness', 'Insulin',
'BMI', 'DiabetesPedigreeFunction', 'Age', 'Outcome'],
dtype='object')
# 0값을 검사할 피처명 리스트
zero_features = ['Glucose', 'BloodPressure', 'SkinThickness', 'Insulin', 'BMI']
# 전체 데이터 건수
total_count = df.Glucose.count()
# feature별로 반복하면서 데이터 값이 0인 데이터 건수를 추출, 퍼센트 계산
for feature in zero_features:
zero_count = df[df[feature] == 0][feature].count()
print(f"{feature} 0 건수: {zero_count}건, 퍼센트: {zero_count/total_count*100:.2f}%")
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%
# zero_features 리스트 내부에 저장된 개별 피처들에 대해서 0값을 평균 값으로 대체
mean_zero_features = df[zero_features].mean()
df[zero_features] = df[zero_features].replace(0, mean_zero_features)
# feature별로 반복하면서 데이터 값이 0인 데이터 건수를 추출, 퍼센트 계산
for feature in zero_features:
zero_count = df[df[feature] == 0][feature].count()
print(f"{feature} 0 건수: {zero_count}건, 퍼센트: {zero_count/total_count*100:.2f}%")
Glucose 0 건수: 0건, 퍼센트: 0.00%
BloodPressure 0 건수: 0건, 퍼센트: 0.00%
SkinThickness 0 건수: 0건, 퍼센트: 0.00%
Insulin 0 건수: 0건, 퍼센트: 0.00%
BMI 0 건수: 0건, 퍼센트: 0.00%
X = df.drop(columns=['Outcome'])
y = df['Outcome']
# StanderdScaler 적용 - 평균0, 분산1(가우시안 정규분포)
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(solver='liblinear')
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)
# ==오차 행렬==
# [[87 13]
# [22 32]]
# 정확도: 0.7727, 정밀도: 0.7111, 재현율: 0.5926, F1: 0.6465, AUC: 0.8083
==오차 행렬==
[[90 10]
[21 33]]
정확도: 0.7987, 정밀도: 0.7674, 재현율: 0.6111, F1: 0.6804, AUC: 0.8433
임곗값을 조절
thresholds = [0.3, 0.33, 0.36, 0.39, 0.42, 0.45, 0.48, 0.5] # 재현율을 높이기 위해 0.5보다 낮은 수치로만 설정
def get_eval_by_threshold(y_test, pred_proba_1, thresholds):
from sklearn.preprocessing import Binarizer
for custom_threshold in thresholds:
custom_predict = Binarizer(threshold=custom_threshold).fit_transform(pred_proba_1.reshape(-1, 1))
print('==임곗값:', custom_threshold)
get_clf_eval(y_test, custom_predict, pred_proba_1)
get_eval_by_threshold(y_test, pred_proba, thresholds)
==임곗값: 0.3
==오차 행렬==
[[65 35]
[11 43]]
정확도: 0.7013, 정밀도: 0.5513, 재현율: 0.7963, F1: 0.6515, AUC: 0.8433
==임곗값: 0.33
==오차 행렬==
[[71 29]
[11 43]]
정확도: 0.7403, 정밀도: 0.5972, 재현율: 0.7963, F1: 0.6825, 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
==오차 행렬==
[[88 12]
[19 35]]
정확도: 0.7987, 정밀도: 0.7447, 재현율: 0.6481, F1: 0.6931, AUC: 0.8433
Reference
- 이 포스트는 SeSAC 인공지능 자연어처리, 컴퓨터비전 기술을 활용한 응용 SW 개발자 양성 과정 - 심선조 강사님의 강의를 정리한 내용입니다.
댓글남기기