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
'Data > 머신러닝' 카테고리의 다른 글
[파이썬머신러닝완벽가이드]GBM(Gradient Boosting Machine) (1) | 2023.07.02 |
---|---|
[파이썬머신러닝완벽가이드]04.분류(1) (0) | 2023.05.20 |
[파이썬머신러닝완벽가이드]03.평가(1) (0) | 2023.05.06 |
[파이썬머신러닝완벽가이드]02.사이킷런으로 시작하는 머신러닝(2) (0) | 2023.04.28 |
[파이썬머신러닝완벽가이드]02.사이킷런으로 시작하는 머신러닝(1) (0) | 2023.04.20 |