파이썬 머신러닝 완벽 가이드 (권철민 저)을 요약정리했습니다.
성능 평가
머신러닝의 파이프라인을 살펴보면
데이터 가공 -> 데이터 변환 -> 모델 학습 -> 모델 예측 -> 평가 순으로 구성된다.
일반적으로 회귀는 실제 값과 예측값의 오차 평균
값으로 성능 평가를 한다.
분류에 사용되는 성능 지표는 다음과 같다. 자세히 한 번 알아보자.
- 정확도(Accuracy)
- 오차 행렬 (Confusion Matrix)
- 정밀도 ( Precision )
- 재현율 ( Recall )
- F1 스코어
- ROC AUC
1. 정확도(Accuracy)
정확도는 실제 데이터에서 예측 데이터가 얼마나 같은지 판단하는 지표이다.
$정확도(Accuracy) = \frac {예측 결과가 동일한 데이터 건수} {전체 예측 데이터 건수}$
정확도는 직관적으로 모델 예측 성능을 나타내는 평가 지표지만 이진 분로의 경우 데이터의 구성에 따라 ML 모델의 성능을 왜곡할 수 있기 때문에 정확도 수치 하나만 가지고 성능 평가를 하지 않는다.
쉬운 예로 타이타닉 생존자 예측 수행 결과를 보면 ML 알고리즘을 적용한 후 예측 정확도가 보통 80%인데 탑승객이 남자인 경우보다 여자인 경우에 생존확률이 높았기 때문에 별 다른 알고리즘 적용 없이 성별이 여자인 경우 생존, 남자인 경우 사망으로 예측하여도 예측 정확도는 이와 비슷한 수치가 나올 수 있다.
예를들어 내부 데이터의 90%가 0으로 되어있는 데이터를 이진분류 한다고 해보자.
# digits번호가 7번이면 True이고 이를 astype(int)로 1로 변환, 7번이 아니면 False이고 0으로 변환.
y = (digits.target == 7).astype(int)
X_train, X_test, y_train, y_test = train_test_split( digits.data, y, random_state=11)
# 불균형한 레이블 데이터 분포도 확인.
print('레이블 테스트 세트 크기 :', y_test.shape)
print('테스트 세트 레이블 0 과 1의 분포도')
print(pd.Series(y_test).value_counts())
# Dummy Classifier로 학습/예측/정확도 평가
fakeclf = MyFakeClassifier() # 예측을 무조건 0으로 하는 분류기
fakeclf.fit(X_train , y_train)
fakepred = fakeclf.predict(X_test)
print('모든 예측을 0으로 하여도 정확도는:{:.3f}'.format(accuracy_score(y_test , fakepred)))
레이블 테스트 세트 크기 : (450,)
테스트 세트 레이블 0 과 1의 분포도
0 405
1 45
dtype: int64
모든 예측을 0으로 하여도 정확도는:0.900
단순히 예측 결과를 0으로만 하여도 정확도는 90%라는 높은 수치가 나온다. 이처럼 정확도 평가 지표는 불균형한 레이블 데이터 셋에서 성능 수치로 사용돼서는 안된다.
아무런 튜닝과정없이 무조건 특정 값으로만 예측해도 정확도가 높을 수 있는 것이 평가 지표의 약점 이다
2. 오차 행렬
이진 분류에서 성능 지표로 잘 활용되는 오차행렬은 학습된 분류 모델이 예측을 수행하면서 어떤 예측 오류가 발생하고 있는지를 함께 나타내는 지표이다
- TN은 예측값을 Negative 값 0으로 예측했고 실제 값 역시 Negative 값 0
- FP는 예측값을 Positive 값 1으로 예측했는데 실제 값은 Negative 값 0
- FN은 예측값을 Negative 값 0으로 예측했고 실제 값은 Positive 값 1
- TP는 예측값을 Positive 값 1으로 예측했고 실제 값도 Positive 값 1
사이킷 런의 confusion_matrix()를 이용하여 위의 MyFakeClassifier의 예측 성능 지표를 오차 행렬로 표현해 보자.
from sklearn.metrics import confusion_matrix
confusion_matrix(y_test,fakepred)
TN은 전체 450건 데이터중 무조건 0으로 예측해서 True가 된 결과 405 건,
FP는 0인 데이터를 1로 예측한 건수가 없으므로 0, FN은 레이블이 1인 데이터를 45개 N으로 예측해서 F가 된 결과 45건, TP는 1인 데이터를 1로로 예측한 건수가 없으므로 0 건이다.
정확도는 오차 행렬 상에서 다음과 같이 정의 된다.정확도 = 예측 결과와 실제 값이 동일한 건수/ 전체 데이터수 (TN + TP)/(TN + FP + FN + TP)
일반적으로 불균형한 레이블 클래스를 가지는 이진 분류 모델에서는 많은 데이터 중에서 중점적으로 찾아야하는 매우 적은 수의 결괏값에 Positive를 선정해 1 값을 부여하고, 그렇지 않은 경우는 Negative로 0값을 부여하는 경우가 많다.
예를 들어 사기 행위 예측 모델에서는 사기 행위가 Positive 양성으로 1, 정상 행위가 Negative 음성으로 0 값이 결정 값으로 할당 되거나
암 검진 예측 모델에서는 암이 양성일 경우 Positive 양성으로 1, 암이 음성일 경우 Negative 음성으로 0 값이 할당 되는 경우가 일반적이다.
3. 정밀도와 재현율
- 정밀도 : 예측을 Positive로 한 대상 중에 실제 값이 Positive인 데이터의 비율, 양성 예측도
- 재현율 : 실제 값이 Positive인 대상 중에 예측과 실제 값이 Positive로 일치한 데이터의 비율, 민감도
정밀도 = $\frac{TP}{(FP + TP)}$, 재현율 = $\frac{TP}{(FN + TP)}$
로지스틱 회귀 모델로 타이타닉 생존자를 예측하고, 오차행렬, 정확도, 정밀도, 재현율을 출력해보자.
from sklearn.preprocessing import LabelEncoder
# Null 처리 함수
def fillna(df):
df['Age'].fillna(df['Age'].mean(),inplace=True)
df['Cabin'].fillna('N',inplace=True)
df['Embarked'].fillna('N',inplace=True)
df['Fare'].fillna(0,inplace=True)
return df
# 머신러닝 알고리즘에 불필요한 속성 제거
def drop_features(df):
df.drop(['PassengerId','Name','Ticket'],axis=1,inplace=True)
return df
# 레이블 인코딩 수행.
def format_features(df):
df['Cabin'] = df['Cabin'].str[:1]
features = ['Cabin','Sex','Embarked']
for feature in features:
le = LabelEncoder()
le = le.fit(df[feature])
df[feature] = le.transform(df[feature])
return df
# 앞에서 설정한 Data Preprocessing 함수 호출
def transform_features(df):
df = fillna(df)
df = drop_features(df)
df = format_features(df)
return df
#오차행렬, 정확도, 정밀도, 재현율을 한꺼번에 계산하는 함수 생성
from sklearn.metrics import accuracy_score, precision_score , recall_score , confusion_matrix
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)
print('오차 행렬')
print(confusion)
print('정확도: {0:.4f}, 정밀도: {1:.4f}, 재현율: {2:.4f}'.format(accuracy , precision ,recall))
import numpy as np
import pandas as pd
import warnings
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
warnings.filterwarnings(action='ignore')
# 원본 데이터를 재로딩, 데이터 가공, 학습데이터/테스트 데이터 분할.
titanic_df = pd.read_csv('./excel/titanic_train.csv')
y_titanic_df = titanic_df['Survived']
X_titanic_df= titanic_df.drop('Survived', axis=1)
X_titanic_df = transform_features(X_titanic_df)
X_train, X_test, y_train, y_test = train_test_split(X_titanic_df, y_titanic_df, \
test_size=0.20, random_state=11)
lr_clf = LogisticRegression()
lr_clf.fit(X_train , y_train)
pred = lr_clf.predict(X_test)
get_clf_eval(y_test , pred)
오차 행렬
[[104 14]
[ 13 48]]
정확도: 0.8492, 정밀도: 0.7742, 재현율: 0.7869
정밀도-재현율 Trad-off
분석 목적에 맞게 정밀도 또는 재현율이 특히 강조돼야 할 경우 분류 결정 임곗값을 조정해 정밀도 또는 재현율의 수치를 높일 수 있다.
하지만 정밀도와 재현율은 상호 보완적인 평가 지표이기 때문에 어느 한쪽을 강제로 높이면 다른 하나의 수치는 떨어지기 마련이다. 이를 정밀도-재현율의 트레이드 오프(Trad-off) 라고 부른다.
사이킷런의 predict_prova()메서드를 통해 테스트 데이터의 개별 클래스 예측 확률을 알 수 있다.
predict_prova 와 predict() 메서드 결과 비교
pred_proba = lr_clf.predict_proba(X_test)
pred = lr_clf.predict(X_test)
print('pred_proba()결과 Shape : {0}'.format(pred_proba.shape))
print('pred_proba array에서 앞 3개만 샘플로 추출 \n:', pred_proba[:3])
# 예측 확률 array 와 예측 결과값 array 를 concatenate 하여 예측 확률과 결과값을 한눈에 확인
pred_proba_result = np.concatenate([pred_proba , pred.reshape(-1,1)],axis=1)
print('두개의 class 중에서 더 큰 확률을 클래스 값으로 예측 \n',pred_proba_result[:3])
pred_proba()결과 Shape : (179, 2)
pred_proba array에서 앞 3개만 샘플로 추출
: [[0.46184057 0.53815943]
[0.87866995 0.12133005]
[0.87716959 0.12283041]]
두개의 class 중에서 더 큰 확률을 클래스 값으로 예측
[[0.46184057 0.53815943 1. ]
[0.87866995 0.12133005 0. ]
[0.87716959 0.12283041 0. ]]
반환 결과인 ndarray는 0과 1에 대한 확률을 나타내므로 첫 번째 칼럼 값과 두 번째 칼럼 값을 더하면 1이 된다. 그리고 맨 마지막 줄의 predict() 메서드의 결과 비교에서도 나타나듯이, 두 개의 칼럼 중에서 더 큰 확률 값으로 predict() 메서드가 최종 예측하고 있다.
F1 스코어
F1스코어는 정밀도와 재현율을 결합한 지표이다. F1스코어는 정밀도와 재현율이 어느 한쪽으로치우치지 않는 수치를 나타낼 때 상대적으로 높은 값을 가진다.
$2 * { {Precision * Recall}\over{Precision + Recall} }$
사이킷런의 F1_score() API를 이용해 F1 스코어를 구해 보자.
from sklearn.metrics import f1_score
f1 = f1_score(y_test , pred)
print('F1 스코어: {0:.4f}'.format(f1))
F1 스코어: 0.7805
ROC 곡선과 AUC
ROC 곡선과 AUC 스코어는 이진 분류의 예측 성능 측정에서 중요하게 사용되는 지표이다.
가운데 직선은 RCO 곡선의 최저 값이고, 왼쪽 하단과 오른쪽 상단 대각선으로 이은 직선은 동전을 무작위로 던져 앞/뒤를 맞추는 랜덤 수준의 이진 분류의 ROC 직선이다. ROC 곡선이 가운데 직선에 가까울수록 성능이 떨어지는 것이다.
사이킷런의 roc_curve() 를 이용해 타이타닉 생존자 예측 모델의 FPT, TPR, 임곗값을 구해보자.
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('샘플 추출을 위한 임곗값 배열의 index:', thr_index)
print('샘플 index로 추출한 임곗값: ', np.round(thresholds[thr_index], 2))
# 5 step 단위로 추출된 임계값에 따른 FPR, TPR 값
print('샘플 임곗값별 FPR: ', np.round(fprs[thr_index], 3))
print('샘플 임곗값별 TPR: ', np.round(tprs[thr_index], 3))
샘플 추출을 위한 임곗값 배열의 index: [ 1 6 11 16 21 26 31 36 41 46 51]
샘플 index로 추출한 임곗값: [0.97 0.65 0.63 0.56 0.45 0.4 0.35 0.15 0.13 0.11 0.11]
샘플 임곗값별 FPR: [0. 0.017 0.034 0.076 0.127 0.169 0.203 0.466 0.585 0.686 0.797]
샘플 임곗값별 TPR: [0.033 0.639 0.721 0.754 0.803 0.836 0.885 0.902 0.934 0.967 0.984]
ROC 커브 시각화
import matplotlib.pyplot as plt
def roc_curve_plot(y_test , pred_proba_c1):
# 임곗값에 따른 FPR, TPR 값을 반환 받음.
fprs , tprs , thresholds = roc_curve(y_test ,pred_proba_c1)
# ROC Curve를 plot 곡선으로 그림.
plt.plot(fprs , tprs, label='ROC')
# 가운데 대각선 직선을 그림.
plt.plot([0, 1], [0, 1], 'k--', label='Random')
# FPR X 축의 Scale을 0.1 단위로 변경, X,Y 축명 설정등
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()
plt.show()
roc_curve_plot(y_test, lr_clf.predict_proba(X_test)[:, 1] )
일반적으로 ROC 곡선 자체는 FPR과 TPR의 변화 값을 보는 데 이용하며 분류의 성능 지표로 사용되는것은 ROC 곡선 면적에 기반한 AUC 값으로 결정한다.
AUC(Area Under Curve)값은 ROC 곡선 밑의 면적을 구한것으로서 일반적으로 1에 가까울수록 좋은 수치이다.
'머신러닝' 카테고리의 다른 글
앙상블 학습 개요 (0) | 2020.08.20 |
---|---|
결정 트리 (0) | 2020.08.20 |
numpy, pandas 기초 (0) | 2020.08.20 |
타이타닉 생존자 예측 -사이킷런 (0) | 2020.08.19 |
데이터 전처리 (0) | 2020.08.19 |