[2주차] 평가 (정확도 / 이진 분류 / 정밀도 / 재현율 / F1 / ROC AUC)
머신러닝의 과정 : 데이터 가공/변환 ≫ 모델 학습/예측 ≫ 평가
성능 평가 지표(Evaluation Metric)
- 머신러닝 모델은 여러 가지 방법으로 예측 성능을 평가할 수 있음
- 모델이 분류인지, 회귀인지에 따라 나뉨
- 회귀의 경우, 실제값과 예측값의 오차 평균값에 기반
- 분류의 경우, 정확도만 가지고 판단했을 시 잘못된 평가 결과에 빠질 수 있음 (특히 이진 분류)
분류의 성능 평가 지표
- 정확도 (Accuracy)
- 오차행렬(Confusion Matrix)
- 정밀도(Precision)
- 재현율(Recall)
- F1 스코어
- ROC AUC
01 정확도 (Accuracy)
정확도란? 실제 데이터에서 예측 데이터가 얼마나 같은지를 판단하는 지표
예측 결과가 동일한 데이터 건수 / 전체 예측 데이터 건수로 계산한다. 직관적이지만, 0과 1로 나뉘는 이진 분류에서는 데이터에 구성에 따라 머신러닝 모델의 성능을 왜곡할 수 있다. 어떻게 왜곡하는지 살펴보자.
타이타닉 생존자 예제에서, 탑승객이 남자인 경우보다 여자인 경우에 생존 확률이 높았다. 무조건 성별이 여자(0)인 경우 생존(1), 남자(1)인 경우 사망(0)으로 예측을 해 보자. 실제 결과와 다르지만, 수치상 정확도는 비슷하다. 단순한 알고리즘도 높은 정확도를 나타내는 상황이 발생할 수 있다. ML모델의 성능을 위해, 이런 경우를 방지해야 한다.
매우 단순한 Classifier을 생성한 실습
fit() 메서드는 아무것도 수행하지 않으며, predict() 메서드는 단순히 Sex 피처가 1이면 0, 그렇지 않으면 1로 예측한다. 사이킷런은 BaseEstimator를 상속받으면 개발자가 Estimator를 커스텀할 수 있다.
1. MyDummyClassifier 생성
import sklearn
import numpy as np
from sklearn.base import BaseEstimator
class MyDummyClassifier(BaseEstimator):
# fit( ) 메소드는 아무것도 학습하지 않음.
def fit(self, X , y=None):
pass
# predict( ) 메소드는 단순히 Sex feature가 1 이면 0 , 그렇지 않으면 1 로 예측함.
def predict(self, X):
pred = np.zeros( ( X.shape[0], 1 ))
for i in range (X.shape[0]) :
if X['Sex'].iloc[i] == 1:
pred[i] = 0
else :
pred[i] = 1
return pred
2. 데이터 전처리 함수 정의
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
# 앞에서 설정한 데이터 전처리 함수 호출
def transform_features(df):
df = fillna(df)
df = drop_features(df)
df = format_features(df)
return df
3. 학습/예측/평가
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
# 원본 데이터를 재로딩, 데이터 가공, 학습 데이터/테스트 데이터 분할.
titanic_df = pd.read_csv('./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.2, random_state=0)
# 위에서 생성한 Dummy Classifier를 이용해 학습/예측/평가 수행.
myclf = MyDummyClassifier()
myclf.fit(X_train, y_train)
mypredictions = myclf.predict(X_test)
print('Dummy Classifier의 정확도는: {0:.4f}'.format(accuracy_score(y_test, mypredictions)))
4. 결과
Dummy Classifier의 정확도는: 0.7877
이렇게 단순한 알고리즘으로 예측을 하였음에도 높은 정확도가 나타난 것을 확인했다. 정확도를 평가 지표로 사용할 때에는 매우 신중해야 한다.
특히 불균형한 레이블 값 분포에서 적합하지 않다. 예를 들어서, 100개 중 90개의 데이터 값이 0, 10개가 1이라고 해 보자. 무조건 0이라고 예측 결과를 반환해도, 정확도가 90%인 모델이 되는 것이다. 이러한 예시를 다음 MNIST 예제를 통해 학습해 보자.
MNIST 데이터 세트를 이용한 실습
MNIST 데이터는 0부터 9까지의 숫자 이미지의 픽셀 정보를 가지고 있고, 이를 이용해 숫자를 예측하는 데에 사용된다. 사이킷런은 load_digits() API를 통해 MNIST 데이터 세트를 제공한다.
레이블 값이 7인 것만 True, 나머지는 모두 False로 변환해 이진 분류 문제로 바꿔 보고, 불균형한 데이터 세트에서 정확도 예측의 맹점을 살펴보자.
1. MyFakeclassifire 정의, 데이터 준비
from sklearn.datasets import load_digits
from sklearn.model_selection import train_test_split
from sklearn.base import BaseEstimator
from sklearn.metrics import accuracy_score
import numpy as np
import pandas as pd
class MyFakeClassifier(BaseEstimator):
def fit(self,X,y):
pass
# 입력값으로 들어오는 X 데이터 셋의 크기만큼 모두 0값으로 만들어서 반환
def predict(self,X):
return np.zeros( (len(X), 1) , dtype=bool)
# 사이킷런의 내장 데이터 셋인 load_digits( )를 이용하여 MNIST 데이터 로딩
digits = load_digits()
digits.target == 7
#array([False, False, False, ..., False, False, False])
# 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)
코드 부가 설명
astype(): Numpy 라이브러리에서 배열의 데이터 타입 변경할 때 사용
digits.target == 7: digits.target 배열에서 숫자 7이면 True, 아니면 False
astype(int): True를 1로, False를 0으로 변환
y: 7이면 타겟 레이블이 1인 이진 분류 레이블 배열
2. 데이터 분포도 확인 (불균형한 데이터 세트 생성)
# 불균형한 레이블 데이터 분포도 확인.
print('레이블 테스트 세트 크기 :', y_test.shape)
print('테스트 세트 레이블 0 과 1의 분포도')
print(pd.Series(y_test).value_counts())
출력
레이블 테스트 세트 크기 : (450,)
테스트 세트 레이블 0 과 1의 분포도
0 405
1 45
dtype: int64
3. 학습/예측/평가
# Dummy Classifier로 학습/예측/정확도 평가
fakeclf = MyFakeClassifier()
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
단순한 예측에서도 90%라는 높은 정확도를 보이므로, 불균형한 레이블 데이터 세트에서 정확도를 성능 평가 수치로 사용하면 안 됨을 알 수 있다.
02 오차행렬
오차행렬 (confusion matrix, 혼동행렬) : 학습된 분류 모델의 예측 오류가 어떤 유형으로, 얼마나 발생하는지 함께 나타내는 지표
| 예측값 | |||
| Negative(0) | Positive(1) | ||
| 실제값 | Negative(0) | TN (True Negative) |
FP (False Positive) |
| Positive(1) | FN (False Negative) |
TP (True Positive) |
|
T/F는 예측이 맞음/틀림을, N/P는 예측값을 나타낸다.
표에서 실제 값에 따라 색을 다르게 나타내 보았다. FP는 실제로는 Negative, FN은 Positive라는 점을 주의하자.
오차행렬 실습
confusion_matrix(실제값, 예측값) : 오차행렬을 구하기 위해 사이킷런이 제공. 배열 형태로 출력됨.
앞에서 사용한 예제 자료를 이어서 사용해 보자.
from sklearn.metrics import confusion_matrix
# 앞절의 예측 결과인 fakepred와 실제 결과인 y_test의 Confusion Matrix출력
confusion_matrix(y_test , fakepred)
출력
array([[405, 0],
[ 45, 0]], dtype=int64)
| TN : 405개 | FP : 0개 |
| FN : 45개 | TP : 0개 |
앞의 MyFakeClassifier는 무조건 Negative로 예측해서 다음과 같은 결과가 나왔다. TP, TN, FP, TN 값은 Classifier 성능의 여러 면모를 판단할 수 있는 기반 정보를 제공한다. 이 값을 조합해서 정확도, 정밀도, 재현율 값을 알 수 있다.
정확도 = 예측 결과와 실제 값이 동일한 건수/전체 데이터 수 = (TN + TP)/(TN + FP + FN + TP)
불균형한 레이블 클래스를 가지는 이진 분류 모델에서는 중점적으로 찾아야 하는 매우 적은 수의 결괏값에 Positive를 설정해 1값을 부여하고, 그렇지 않은 경우에는 Negative 로 0값을 부여하는 경우가 많다.
예시
사기 행위 예측 모델 - 사기 행위가 Positive 로 1, 정상 행위가 Negative 로 0
암 검진 예측 모델 - 암이 양성일 경우 Positive 로 1, 음성일 경우 Negative로 0
정확도의 맹점
- 불균형한 레이블 데이터 세트를 가진 이진 분류 모델의 성능 왜곡 가능성
- 중점적으로 찾아야 하는 매우 적은 수의 결괏값에 Positive 설정
- Negative가 많아지고, Negative만으로 예측 정확도가 높아지는 경향 발생
- Positive에 대한 예측 정확도를 판단하지 못한 채 Negative에 대한 예측 정확도만으로도 분류의 정확도가 높게 나타남
- 정밀도와 재현율로 보완할 수 있음
03 정밀도와 재현율
정밀도와 재현율은 Positive 데이터 세트의 예측 성능에 초점을 맞춘 평가 지표
| 정밀도 | 재현율 |
| TP / (FP + TP) 예측이 Positive인 대상 중 예측과 실제값이 Positive 로 일치한 데이터 비율 TP↑, FP↓에 초점 실제 Negative 인 데이터 예측을 Positive로 잘못 판단 시 큰 영향 발생하는 경우 사용 precision_score(실제값, 예측값) |
TP / (FN + TP) 실제값이 Positive인 대상 중 예측과 실제값이 Positive로 일치한 데이터 비율 TP↑, FN↓에초점 실제 Positive 인 데이터 예측을 Negative로 잘못 판단 시 큰 영향 발생하는 경우 사용 recall_score(실제값, 예측값) |
정밀도/재현율 실습 - 타이타닉
평가 함수 get_clf_eval 정의
로지스틱 회귀 모델로 학습/예측/평가
정밀도/재현율 트레이드오프
정밀도/재현율의 트레이드오프 : 상호 보완적인 평가 지표이기 때문에 어느 한 쪽을 강제로 높이면 다른 하나의 수치는 떨어지기 쉬움
둘 중 하나를 특별히 강조해야 할 경우, 분류의 결정 임곗값(Threshold)을 조정해 수치를 조절한다. 일반적으로 이진 분류에서는 임곗값을 0.5(50%)로 둔다.
사이킷런의 분류 알고리즘 : 개별 레이블별로 결정 확률을 구함 - 예측 데이터가 특정 레이블에 속하는지를 계산 - 예측 확률이 큰 레이블값으로 예측하게 됨
예를 들어, 이진 분류 모델에서 특정 데이터가 0이 될 확률이 10%, 1이 될 확률이 90%면 최종 예측은 90%확률을 가진 1임.
트레이드오프 실습
predict_proba(테스트 피처 데이터 세트)
- 사이킷런이 제공하는 개별 데이터별로 예측 확률을 반환하는 메서드
- 테스트 피처 데이터 세트를 파라미터로 입력해주면, 테스트 피처 레코드의 개별 클래스 예측 확률을 ndarray m x m 형태로 반환
- predict() 메서드와 유사하지만 반환 결과가 예측 결과 클래스 값(더 큰 확률 값으로 최종 예측)이 아닌 예측 확률 결과
- 첫 번째 칼럼은 0 Negative의 확률, 두 번째 칼럼은 1 Positive의 확률
#predict_proba와 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.44935228 0.55064772]
[0.86335513 0.13664487]
[0.86429645 0.13570355]]
두개의 class 중에서 더 큰 확률을 클래스 값으로 예측
[[0.44935228 0.55064772 1. ]
[0.86335513 0.13664487 0. ]
[0.86429645 0.13570355 0. ]]
Binarizer 클래스
객체 = Binarizer(threshold) 객체 생성
객체.fit_transform(ndarray) : 입력된 ndarray의 값을 threshold 값보다 작으면 0, 크면 1로 변환해 반환
from sklearn.preprocessing import Binarizer
X = [[ 1, -1, 2],
[ 2, 0, 0],
[ 0, 1.1, 1.2]]
# threshold 기준값보다 같거나 작으면 0을, 크면 1을 반환
binarizer = Binarizer(threshold=1.1)
print(binarizer.fit_transform(X))
출력
[[0. 0. 1.]
[1. 0. 0.]
[0. 0. 1.]]
predict() 의사 코드
Binarizer를 이용해 사이킷런 predict()의 의사 코드를 만듦
앞 예제의 LogisticRegression 객체의 predict_proba() 메서드로 구한 각 클래스별 예측 확률값인 pred_proba 객체 변수에 분류 결정 임곗값을 0.5로 지정한 Binarizer 클래스를 적용해 최종 예측값을 구함
from sklearn.preprocessing import Binarizer
#Binarizer의 threshold 설정값. 분류 결정 임곗값임.
custom_threshold = 0.5
# predict_proba( ) 반환값의 두번째 컬럼 , 즉 Positive 클래스 컬럼 하나만 추출하여 Binarizer를 적용
pred_proba_1 = pred_proba[:,1].reshape(-1,1)
binarizer = Binarizer(threshold=custom_threshold).fit(pred_proba_1)
custom_predict = binarizer.transform(pred_proba_1)
get_clf_eval(y_test, custom_predict)
출력
오차 행렬
[[108 10]
[ 14 47]]
정확도: 0.8659, 정밀도: 0.8246, 재현율: 0.7705
분류 결정 임곗값 조정
# 테스트를 수행할 모든 임곗값을 리스트 객체로 저장.
thresholds = [0.4, 0.45, 0.50, 0.55, 0.60]
def get_eval_by_threshold(y_test , pred_proba_c1, thresholds):
# thresholds list객체내의 값을 차례로 iteration하면서 Evaluation 수행.
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)
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
임곗값: 0.45
오차 행렬
[[105 13]
[ 13 48]]
정확도: 0.8547, 정밀도: 0.7869, 재현율: 0.7869
임곗값: 0.5
오차 행렬
[[108 10]
[ 14 47]]
정확도: 0.8659, 정밀도: 0.8246, 재현율: 0.7705
임곗값: 0.55
오차 행렬
[[111 7]
[ 16 45]]
정확도: 0.8715, 정밀도: 0.8654, 재현율: 0.7377
임곗값: 0.6
오차 행렬
[[113 5]
[ 17 44]]
정확도: 0.8771, 정밀도: 0.8980, 재현율: 0.7213
분류 결정 임곗값을 낮추니, 정밀도는 감소하고 재현율은 증가함
분류 결정 임곗값은 Positive 예측값을 결정하는 확률의 기준이 됨. 임곗값 값을 낮출수록 True 값이 많아져서, Positive로 예측을 너그럽게 함. 결과적으로 재현율이 증가함.
precision_recall_curve()
precision_recall_curve(실제 클래스값 배열, Positive 칼럼의 예측 확률 배열) : 임곗값을 담은 넘파이 ndarray와 이 임곗값에 해당하는 정밀도 및 재현율 값을 담은 넘파이 ndarray를 반환
from sklearn.metrics import precision_recall_curve
# 레이블 값이 1일때의 예측 확률을 추출
pred_proba_class1 = lr_clf.predict_proba(X_test)[:, 1]
# 실제값 데이터 셋과 레이블 값이 1일 때의 예측 확률을 precision_recall_curve 인자로 입력
precisions, recalls, thresholds = precision_recall_curve(y_test, pred_proba_class1 )
print('반환된 분류 결정 임곗값 배열의 Shape:', thresholds.shape)
print('반환된 precisions 배열의 Shape:', precisions.shape)
print('반환된 recalls 배열의 Shape:', recalls.shape)
print("thresholds 5 sample:", thresholds[:5])
print("precisions 5 sample:", precisions[:5])
print("recalls 5 sample:", recalls[:5])
#반환된 임계값 배열 로우가 147건이므로 샘플로 10건만 추출하되, 임곗값을 15 Step으로 추출.
thr_index = np.arange(0, thresholds.shape[0], 15)
print('샘플 추출을 위한 임계값 배열의 index 10개:', thr_index)
print('샘플용 10개의 임곗값: ', np.round(thresholds[thr_index], 2))
# 15 step 단위로 추출된 임계값에 따른 정밀도와 재현율 값
print('샘플 임계값별 정밀도: ', np.round(precisions[thr_index], 3))
print('샘플 임계값별 재현율: ', np.round(recalls[thr_index], 3))
출력
반환된 분류 결정 임곗값 배열의 Shape: (147,)
반환된 precisions 배열의 Shape: (148,)
반환된 recalls 배열의 Shape: (148,)
thresholds 5 sample: [0.11573101 0.11636721 0.11819211 0.12102773 0.12349478]
precisions 5 sample: [0.37888199 0.375 0.37735849 0.37974684 0.38216561]
recalls 5 sample: [1. 0.98360656 0.98360656 0.98360656 0.98360656]
샘플 추출을 위한 임계값 배열의 index 10개: [ 0 15 30 45 60 75 90 105 120 135]
샘플용 10개의 임곗값: [0.12 0.13 0.15 0.17 0.26 0.38 0.49 0.63 0.76 0.9 ]
샘플 임계값별 정밀도: [0.379 0.424 0.455 0.519 0.618 0.676 0.797 0.93 0.964 1. ]
샘플 임계값별 재현율: [1. 0.967 0.902 0.902 0.902 0.82 0.77 0.656 0.443 0.213]
시각화
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
%matplotlib inline
def precision_recall_curve_plot(y_test , pred_proba_c1):
# threshold ndarray와 이 threshold에 따른 정밀도, 재현율 ndarray 추출.
precisions, recalls, thresholds = precision_recall_curve( y_test, pred_proba_c1)
# X축을 threshold값으로, Y축은 정밀도, 재현율 값으로 각각 Plot 수행. 정밀도는 점선으로 표시
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('Threshold value'); plt.ylabel('Precision and Recall value')
plt.legend(); plt.grid()
plt.show()
precision_recall_curve_plot( y_test, lr_clf.predict_proba(X_test)[:, 1] )
출력

정밀도와 재현율의 맹점
- 임곗값에 따라 정밀도와 재현율을 조정 가능
- 정밀도가 100%가 되는 방법 : 확실한 기준이 되는 경우만 Positive로 예측, 나머지는 모두 Negative로 예측
- 재현율이 100%가 되는 방법 : 모든 경우를 Positive로 예측
- 하나만 올리는 극단적 수치 조작이 가능하나, 성능이 나쁜 분류임
- 두 수치를 상호 보완할 수 있는 수준에서 적용해야 함
04 F1 스코어
F1 스코어
- 정밀도와 재현율을 결합한 지표
- 정밀도와 재현율이 어느 한 쪽으로 치우치지 않는 수치를 나타낼 때 높은 값을 가짐
- f1_score(실제값, 예측값)

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 = f1_score(y_test,pred)
print('오차 행렬')
print(confusion)
# f1 score print 추가
print('정확도: {0:.4f}, 정밀도: {1:.4f}, 재현율: {2:.4f}, F1:{3:.4f}'.format(accuracy, precision, recall, f1))
thresholds = [0.4 , 0.45 , 0.50 , 0.55 , 0.60]
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
위의 예제에서 F1 스코어는 임곗값이 0.6일 때 가장 좋은 값이지만, 재현율이 크게 감소하고 있으니 주의해야 함
05 ROC 곡선과 AUC
ROC 곡선 (Receiver Operation Characteristic Curve)
- 이진 분류의 예측 성능 측정에서 중요하게 사용되는 지표
- 수신자 판단 곡선
- X축 FPR(False Positive Rate)이 변할 때 Y축 TPR(True Positive Rate)이 어떻게 변하는지를 나타내는 곡선
- 분류 결정 임곗값을 변경하면서 FPR을 0부터 1까지 변경함
- roc_curve(실제 클래스 값 array, predict_proba()의 반환 값 array에서 Positive 칼럼의 예측 확률) : fpr, tpr, thresholds를 반환
TPR(True Positive Rate) : 민감도, 실제값 Positive가 정확히 예측돼야 하는 수준 (질병이 있는 사람은 양성 판정)
TNR(True Negative Rate) : 특이성, 실제값 Negative가 정확히 예측돼야 하는 수준 (질병이 없는 사람은 음성 판정)
TNR = TN / ( FP + TN )이고 FPR = FP / (FP + TN) 이므로, FPR = 1 - TNR로 나타낼 수 있다.
FPR을 0으로 만드려면 임곗값으로 1을 지정하고, 1로 만들려면 임곗값을 0으로 지정하면 된다
AUC(Area Under Curve)
- ROC 곡선 면적에 기반한 AUC 값으로 분류의 성능 평가
- ROC 곡선 밑의 면적으로 1에 가까울수록 좋은 수치
- 수치가 커지려면 FPR이 작은 상태에서 큰 TPR을 얻어야 함
- 가운데 직선에서 멀어지고 왼쪽 상단 모서리 쪽으로 가파르게 곡선이 이동할수록 직사각형에 가까운 곡선이 되어 면적이 1에 가까워지는 좋은 성능 수치를 얻게 됨
- 보통의 분류는 0.5 이상의 AUC 값을 가짐
ROC-AUC 실습 –타이타닉
ROC 곡선 시각화
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]
샘플 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. ]
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 - Specificity )'); plt.ylabel('TPR( Recall )')
plt.legend()
plt.show()
roc_curve_plot(y_test, lr_clf.predict_proba(X_test)[:, 1] )
출력

사용법
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 완성
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))