4. F1 score

F1 score는 정밀도와 재현율을 결합한 지표이다. F1 score는 정밀도와 재현율이 어느 한쪽으로 치우치지 않는 수치를 나타낼 때 상대적으로 높은 값을 가진다.

 

이전에 학습한 로지스틱 회귀 기반 타이타닉 생존자 모델을 이용하여 F1 score를 구해보았다.

from sklearn.metrics import f1_score
f1 = f1_score(y_test, pred)
print('F1 스코어:{0:.4f}'.format(f1))

threshold를 변화시키면서 F1 score를 구해볼 것이고, 이전에 get_clf_eval() 함수에 F1 score를 구하는 로직을 추가하였다. 그리고 이전에 get_eval_by_threshold() 함수를 이용해 임계값 0.4 ~ 0.6별로 평가 지표를 구할것이다.

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 score 추가
    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)

F1 score는 threshold가 0.6일 때 가장 좋은 값을 보인다. 하지만 임계값이 0.6인 경우에는 재현율이 크게 감소하니 주의해야 한다.

 

 

5. ROC 곡선과 AUC

ROC 곡선은 FPR(False Positive Rate)이 변할 때 TPR(True Positive Rate)이 어떻게 변하는지를 나타내는 곡선이다. FPR을 x축으로, TPR을 y축으로 잡으면 FPR의 변화에 따른 TPR의 변화가 곡선 형태로 나타남

 

분류의 성능 지표로 사용되는 것은 ROC 곡선 면적에 기반한 AUC 값으로 결정한다. AUC(Area Under Curve)값은 ROC 곡선 밑의 면적을 구한 것으로 일반적으로 1에 가까울수록 좋은 수치이다.

→ROC 곡선이 가운데 직선에 가까울수록 면적이 작기 때문에 성능이 떨어지는 것이며, 멀어질수록 면적이 크기 때문에 성능이 뛰어난 것

 

-TPR은 True Positive Rate의 약자이며, 이는 재현율을 나타낸다.(실제 Positive를 맞게 예측한 비율)

따라서 TPR은 TP / (FN+TP)이다. TPR, 즉 재현율은 민감도로도 불림

-FPR은 실제 Negative를 잘못 예측한 비율을 나타낸다. 

즉, 실제는 Negative인데 Positive로 잘못 예측한 비율이다. 

따라서 FPR은 FP / (FP+TN)이다.

 

ROC 곡선은 FPR을 0부터 1까지 변경하면서 TPR의 변화 값을 구해 그리는데, Threshold(분류 결정 임계값)을 변경함으로서 수행할 수 있음

●Threshold를 1로 하면 FPR은 0임

→임계값을 1로 하면 Positive 예측 기준이 매우 높기 때문에 아예 Positive로 예측하지 않아서 FP 값이 0이 되므로 FPR은 0이 됨

●Threshold를 0으로 하면 FPR은 1임

→임계값을 0으로 하면 Positive 예측 기준이 매우 낮기 때문에 전부 Positive로 예측을 해서 아예 Negative 예측이 없음. 그래서 TN 값이 0이 되므로 FPR은 1이 됨

 

<ROC 곡선 및 AUC 스코어 API>

 

이전에 정밀도와 재현율에서 학습한 LogisticRegression 객체의 predict_proba() 결과를 다시 이용해 ROC 곡선과 AUC 값을 구해보자

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], 2))
print('샘플 임계값별 TPR:', np.round(tprs[thr_index], 2))

# roc curve 시각화
def roc_curve_plot(y_test, pred_proba_c1):
    # 임계값에 따른 FPR, TPR 값을 반환받음
    fprs, tprs, thresholds = roc_curve(y_test, pred_proba_c1)
    # ROC 곡선 그리기
    plt.plot(fprs, tprs, label='ROC')
    # 가운데 랜덤 수준의 대각선 직선 그리기(AUC=0.5)
    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()
    
roc_curve_plot(y_test, pred_proba[:, 1])

# AUC 값 구하기
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))

 


오차행렬, 정확도, 정밀도, 재현율, F1 스코어, ROC AUC 값을 한번에 출력할 수 있는 함수 작성

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인 분류

 

●다중 분류:클래스 변수의 상태 공간이 크기가 3이상인 분류

각 클래스를 긍정으로 간주하여 평가 지표를 계산한 뒤, 이들의 산술 평균(macro average), 가중 평균(micro average, weighted average)으로 평가한다


1. 정확도

실제 데이터에서 예측 데이터가 얼마나 같은지를 판단하는 지표

 

●정확도는 직관적으로 모델 예측 성능을 나타내는 평가 지표이다. 하지만 이진 분류의 경우 데이터의 구성에 따라 ML 모델의 성능을 왜곡할 수 있기 때문에 정확도 수치 하나만 가지고 성능을 평가하지 않는다.

●특히 정확도는 불균형한 레이블 값 분포에서 ML 모델의 성능을 판단할 경우, 적합한 평가 지표가 아니다.

●정확도 = (TN+TP) / (TN+FP+FN+TP)

 

유명한 MNIST 데이터 세트를 변환해 불균형한 데이터 세트로 만든 뒤에 정확도 지표 적용 시 어떤 문제가 발생할 수 있는지 살펴보자. 

MNIST 데이터 세트는 0부터 9까지의 숫자 이미지의 픽셀 정보를 가지고 있으며, 이를 기반으로 숫자 Digit를 예측하는데 사용된다. 원래 MNIST 데이터 세트는 레이블 값이 0부터 9까지 있는 다중 분류를 위한 것이다. 이것을 레이블 값이 7인 것만 True, 나머지 값은 모두 False로 변환해 이진 분류 문제로 바꿔 전체 데이터의 10%만 True, 나머지 90%는 False인 불균형한 데이터 세트로 변형한다.

from sklearn.datasets import load_digits
from sklearn.model_selection import train_test_split
from sklearn.base import BaseEstimator # 사이킷런은 BaseEstimator를 상속받으면 맞춤형 형태의 Estimator를 개발자가 생성할 수 있음
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 번호가 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())

# MyFakeClassifier로 학습/예측/정확도 평가
fakeclf = MyFakeClassifier()
fakeclf.fit(X_train, y_train)
fakepred = fakeclf.predict(X_test) # 위 코드를 보면 반환값으로 다 0으로 반환→모든 예측을 0으로 하도록 반환
# 실제값과 모든 예측을 0으로 반환한 예측값의 정확도
print('모든 예측을 0으로 하여도 정확도는:{0:.3f}'.format(accuracy_score(y_test, fakepred)))

 

 

2. 오차행렬(혼동행렬)

일반적으로 불균형한 레이블을 가지는 이진 분류 모델에서는 많은 데이터 중에서 중점적으로 찾아야 하는 매우 적은 수의 결과값에 Postive를 설정해 1값을 부여하고, 그렇지 않은 경우는 Negative로 0값을 부여하는 경우가 많다. 

예를 들어 사기 행위 예측 모델에서는 사기 행위가 Positive 양성으로 1, 정상 행위가 Negative 음성으로 0 값이 결정 값으로 할당되거나 암 검진 예측 모델에서는 암이 양성일 경우 Positive 양성으로 1, 암이 음성일 경우 Negative 음성으로 0값이 할당되는 경우가 일반적이다.

 

불균형한 이진 분류 데이터 세트에서는 Positive 데이터 건수가 매우 작기 때문에 데이터에 기반한 ML 알고리즘은 Positive보다는 Negative로 예측 정확도가 높아지는 경향이 발생한다(학습 목적식은 정확도를 최대화한다). 10,000건의 데이터 세트에서 9,900건이 Negative이고 100건이 Positive라면 Negative로 예측하는 경향이 더 강해져서 TN은 매우 커지고 TP는 매우 작아진다. 또한 Negative로 예측할 때 정확도가 높기 때문에 FN이 매우 작고, Positive로 예측하는 경우가 작기 때문에 FP 역시 매우 작아진다.

결과적으로 정확도 지표는 불균형한 레이블을 가지는 데이터 세트에서 Positive에 대한 예측 정확도를 판단하지 못한 채 Negative에 대한 예측 정확도만으로도 분류의 정확도가 매우 높게 나타나는 수치적인 판단 오류를 일으키게 된다.

 

정확도는 분류 모델의 성능을 측정할 수 있는 하나의 요소일 뿐이다. 앞에서도 말했듯 불균형한 데이터 세트에서 정확도만으로는 모델 신뢰도가 떨어질 수 있는 사례가 많기 때문에 불균형한 데이터 세트에서 정확도보다 더 선호되는 평가 지표인 정밀도(Precision)와 재현율(Recall)을 더 선호하는 측면이 있다.

 

 

3. 정밀도와 재현율

정밀도와 재현율은 Positive 데이터 세트의 예측 성능에 좀 더 초점을 맞춘 평가 지표이다.

 

●정밀도는 예측을 Positive로 한 대상 중에 예측과 실제 값이 Positive로 일치한 데이터의 비율

정밀도 = TP / (FP+TP)

●재현율은 실제 값이 Positive인 대상 중에 예측과 실제 값이 Positive로 일치한 데이터의 비율

재현율 = TP / (FN+TP)

 

정밀도와 재현율 지표의 중요성은 비즈니스의 특성에 따라 달라질 수 있다. 

1) 재현율이 상대적으로 더 중요한 지표인 경우는 실제 Positive 양성인 데이터 예측을 Negative로 잘못 판단하게 되면 업무상 큰 영향이 발생하는 경우: 암 진단, 금융사기 판별

2) 정밀도가 상대적으로 더 중요한 지표인 경우는 실제 Negative 음성인 데이터 예측을 Positive 양성으로 잘못 판단하게 되면 업무상 큰 영향이 발생하는 경우: 스팸 메일 판별

→일반적으로 재현율이 정밀도보다 상대적으로 중요한 업무가 많다. 즉, 보통은 위음성비용(FN)이 위양성비용(FP)보다 훨씬 크다.(예시- 암 진단: 위양성 비용(정상인→암환자) vs 위음성 비용(암환자→정상인))

 

재현율과 정밀도의 공식을 살펴보면 재현율과 정밀도 모두 TP를 높이는 데 동일하게 초점을 맞추지만, 재현율은 FN(위음성비용)을 낮추는데, 정밀도는 FP(위양성비용)를 낮추는데 초점을 맞춤

from sklearn.metrics import *

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 pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression

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=11)

lr_clf = LogisticRegression(solver='liblinear')

lr_clf.fit(X_train, y_train)
pred = lr_clf.predict(X_test)
get_clf_eval(y_test, pred)


<정밀도/재현율 트레이드오프>

분류하려는 업무의 특성상 정밀도 또는 재현율이 특별히 강조돼야 할 경우 분류의 결정 임계값(Threshold)을 조정해 정밀도 또는 재현율의 수치를 높일 수 있다. 하지만 정밀도와 재현율은 상호 보완적인 평가 지표이기 때문에 어느 한 쪽을 강제로 높이면 다른 하나의 수치는 떨어지기 쉽다. 이를 정밀도/재현율의 트레이드오프(Trade-off)라고 부른다.

 

사이킷런의 분류 알고리즘은 예측 데이터가 특정 레이블에 속하는지를 계산하기 위해 먼저 개별 레이블별로 결정 확률을 구한다. 그리고 예측 확률이 큰 레이블값으로 예측하게 된다. 예를 들어 특정 데이터가 0이 될 확률이 10%, 1이 될 확률이 90%로 예측됐다면 최종 예측은 1로 예측한다. 일반적으로 분류에서는 분류 결정 임계값을 0.5, 즉 50%로 정하고 이 기준값보다 확률이 크면 Positive, 작으면 Negative로 결정한다.

 

개별 레이블별로 결정 확률을 반환하는 predict_proba() 메서드를 이용한다.

predict() 메서드와 유사하지만 단지 반환 결과가 예측 결과 클래스값이 아닌 예측 확률 결과이다.

이진 분류에서 predict_proba()를 수행해 반환되는 ndarray는 첫 번째 컬럼이 클래스 값 0에 대한 예측 확률, 두 번째 컬럼이 클래스 값 1에 대한 예측 확률이다.

# 위의 타이타닉 예제 이어서 실행

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를 병합해 예측 확률과 예측 결과값을 한눈에 확인
pred_proba_result = np.concatenate([pred_proba, pred.reshape(-1,1)], axis=1)
print('예측 확률이 큰 레이블값으로 예측\n', pred_proba_result[:3])

지금부터 분류 결정 임계값을 조절해 정밀도와 재현율의 성능 수치를 상호 보완적으로 조정해보자!

사이킷런의 Binarizer 클래스에서 threshold를 조절해 진행함. threshold보다 같거나 작으면 0값으로, 크면 1값으로 반환

 

● threshold=0.5

# 분류 결정 임계값을 0.5로 설정했을 때
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)

일반적으로 분류에서는 분류 경정 임계값을 0.5, 즉 50%로 정하고 이 기준값보다 확률이 크면 Positive, 작으면 Negative로 결정하기 때문에 threshold를 0.5로 설정했으므로 앞에서 타이타닉 데이터로 학습된 로지스틱 회귀 Classifier 객체에서 호출된 predict()로 계산된 지표 값과 정확히 같다.

 

 

● threshold=0.4

# 분류 결정 임계값을 0.4로 설정했을 때
custom_threshold = 0.4
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)

임계값을 낮추니 재현율 값이 올라가고 정밀도가 떨어졌다. 왜냐하면 분류 결정 임계값은 Postive 예측값을 결정하는 확률의 기준으로 확률이 0.5가 아닌 0.4부터 Positive로 예측을 더 너그럽게 하기 때문에 임계값을 낮출수록 True인 1 값이 많아지기 때문이다.

Positive 예측값이 많아지면, 양성 예측을 많이 하다 보니 실제 양성을 음성으로 예측하는 횟수인 FN이 상대적으로 줄어들기 때문에 재현율 값이 높아지고, 양성 예측을 많이 하다 보니 FP가 커지기 때문에 정밀도 값이 낮아짐(공식 생각)

임계값이 낮아지면서 재현율이 0.770에서 0.820으로 좋아졌고, 정밀도는 0.825에서 0.704로 나빠졌다. 그리고 정확도도 0.866에서 0.821로 나빠졌다.

 

● threshold=0.4 ~ 0.6 (in steps of 0.05)

# 분류 결정 임계값을 0.4 ~ 0.6(in stpes of 0.05)로 설정했을 때

# 테스트를 수행할 모든 임계값을 리스트 객체로 저장
thresholds = [0.4, 0.45, 0.5, 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)
        print('-'*60)
        get_clf_eval(y_test, custom_predict)
        print('='*60)
    
get_eval_by_threshold(y_test, pred_proba[:,1].reshape(-1,1), thresholds)

임계값이 0.45일 경우에 디폴트 0.5인 경우와 비교해서 정확도가 약간 감소했고 정밀도도 약간 감소했으나 재현율이 올랐다. 만약 재현율을 향상시키면서 다른 수치를 어느 정도 감소시켜야 하는 경우라면 임계값은 0.45가 가장 적당할 것이다.

 

 

★precision_reacall_curve()★

사이킷런은 threshold 변화에 따른 정밀도와 재현율을 구할 수 있게 해주는 precision_recall_curve() API를 제공한다.

- 입력 파라미터

  • y_true : 실제 클래스값 배열(배열크기 = [데이터 건수])
  • probas_pred : Positive 컬럼의 예측 확률 배열(배열크기 = [데이터 건수])

- 반환값

  • 정밀도 : 임계값별 정밀도 값을 배열로 반환
  • 재현율 : 임계값별 재현율 값을 배열로 반환
  • thresholds : 임계값(일반적으로 0.11 ~ 0.95 정도의 임계값)
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])

print('-'*60)

# 반환된 임계값 배열 크기가 165건이므로 샘플로 11건만 추출하되, 임계값을 16 step으로 추출
thr_index = np.arange(0, thresholds.shape[0], 16)
print('샘플 추출을 위한 임계값 배열의 index 11개:', thr_index)
print('샘플용 11개의 임계값:', np.round(thresholds[thr_index], 2))

# 16 step 단위로 추출된 임계값에 따른 정밀도와 재현율 값
print('샘플 임계값별 정밀도:', np.round(precisions[thr_index], 2))
print('샘플 임계값별 재현율:', np.round(recalls[thr_index], 2))

결과를 보면 threshold가 증가할수록 정밀도는 높아지나 재현율은 낮아지는 것을 알 수 있다. 

precision_recall_curve() API는 threshold에 따른 정밀도와 재현율의 변화를 시각화하는데 이용할 수 있다.

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])

그래프에서 보다시피 정밀도와 재현율에 trade-off가 존재하며 로지스틱 회귀 기반의 타이타닉 생존자 예측 모델의 경우 약 0.45 정도의 threshold에서 정밀도와 재현율이 비슷해지는 것을 볼 수 있다.

 

→threshold에 따른 정밀도와 재현율은 업무 환경에 맞게 상호 보완할 수 있는 수준에서 적용돼야 한다.

이처럼 정밀도와 재현율 성능 수치도 어느 한 쪽만 참조하면 극단적인 수치 조작이 가능하다. 따라서 정밀도와 재현율 중 하나만 스코어가 좋고 다른 하나는 스코어가 나쁜 분류는 성능이 좋지 않은 분류로 간주할 수 있다.

⇛그래서 정밀도와 재현율의 수치가 적절하게 조합돼 분류의 종합적인 성능 평가에 사용될 수 있는 평가 지표인

'F1 score'가 있다!! 

타이타닉 생존자 예측.ipynb
0.06MB

데이터 전처리

범주형 변수를 처리하는 데이터 인코딩, 서로 다른 변수의 값 범위를 일정한 수준으로 맞추는 작업인 스케일링에 대해 알아본다. 더 다양하고 구체적인 데이터 전처리는 '데이터 전처리' 카테고리를 참고한다.

 

데이터 인코딩

사이킷런의 머신러닝 알고리즘은 문자열 값을 입력값으로 허용하지 않는다. 그래서 모든 문자열 값은 인코딩돼서 숫자 형으로 변환해야 한다. 여기서 문자열 피처는 범주형 변수, 코드화된 범주형 변수를 의미한다.

 

1) 레이블 인코딩(Label encoding)

from sklearn.preprocessing import LabelEncoder

items = ['TV', '냉장고', '전자레인지', '컴퓨터', '선풍기', '선풍기', '믹서', '믹서']

# LabelEncoder를 객체로 생성한 후, fit()과 transform()으로 레이블 인코딩 수행
encoder = LabelEncoder()
encoder.fit(items)
labels = encoder.transform(items)
print('인코딩 변환값:', labels)

# 문자열 값이 어떤 숫자 값으로 인코딩됐는지 확인 -> LabelEncoder 객체의 classes_
print('인코딩 클래스:', encoder.classes_)
# TV:0, 냉장고:1, 믹서:2, 선풍기:3, 전자레인지:4, 컴퓨터:5

# 인코딩된 값을 다시 디코딩 -> LabelEncoder 객체의 inverse_transform()
print('디코딩 원본값:', encoder.inverse_transform([4, 5, 2, 0, 1, 1, 3, 3]))

레이블 인코딩은 간단하게 문자열 값을 숫자형 값으로 변환하지만 숫자 값의 대소관계가 작용하기 때문에 몇몇 ML 알고리즘에서 예측 성능이 떨어지는 경우가 발생할 수 있다. 

예를들어 냉장고가 1, 믹서가 2로 변환되면, 1보다 2가 더 큰  값이므로 특정 ML 알고리즘에서 가중치가 더 부여되거나 더 중요하게 인식할 가능성이 발생한다. 하지만 냉장고와 믹서의 숫자 변환 값은 단순 코드이지 숫자 값에 따른 순서나 중요도로 인식돼서는 안된다.

이러한 특성 때문에 레이블 인코딩은 선형회귀와 같은 ML 알고리즘에는 적용X, 트리 계열의 ML 알고리즘은 숫자의 이러한 특성을 반영하지 않으므로 레이블 인코딩도 별문제가 없음

 

→레이블 인코딩의 이러한 문제점을 해결하기 위한 인코딩 방식이 "원-핫 인코딩"

 

2) 원-핫 인코딩(One-Hot Encoding)

원-핫 인코딩은 피처 값의 유형에 따라 새로운 피처를 추가해 고유 값에 해당하는 컬럼에만 1을 표시하고 나머지 컬럼에는 0을 표시하는 방식

●주의점: 입력값으로 2차원 데이터가 필요, OneHotEncoder를 이용해 변환한 값이 희소행렬(sparse matrix) 형태이므로 이를 다시 toarray() 메서드를 이용해 밀집행렬(dense matrix)로 변환해야 함

from sklearn.preprocessing import OneHotEncoder
import numpy as np

items = ['TV', '냉장고', '전자레인지', '컴퓨터', '선풍기', '선풍기', '믹서', '믹서']

# 2차원 ndarray로 변환
items = np.array(items).reshape(-1,1)

# OneHotEncoder를 객체로 생성한 후, fit()과 transform()으로 원-핫 인코딩 수행
oh_encoder = OneHotEncoder()
oh_encoder.fit(items)
oh_labels = oh_encoder.transform(items)

# OneHotEncoder로 변환한 결과는 희소행렬이므로 toarray()를 이용해 밀집행렬로 변환
print('원-핫 인코딩 데이터')
print(oh_labels.toarray())
print('원-핫 인코딩 데이터 차원')
print(oh_labels.shape)

판다스에는 원-핫 인코딩을 더 쉽게 지원하는 get_dummies()가 있음

import pandas as pd

df = pd.DataFrame({'items':['TV', '냉장고', '전자레인지', '컴퓨터', '선풍기', '선풍기', '믹서', '믹서']})
pd.get_dummies(df)

 

 

스케일링

스케일링은 서로 다른 피처의 값 범위를 일정한 수준으로 맞추는 작업으로 대표적인 방법인 표준화, 정규화가 있음

●표준화(Standardization)

표준화는 데이터의 피처 각각이 평균이 0이고 분산이 1인 가우시안 정규분포를 가진 값으로 변환하는 것

●정규화(Normalization)

정규화는 서로 다른 피처의 크기를 통일하기 위해 크기를 모두 최소 0 ~ 최대 1의 값으로 변환하는 것

 

사이킷런에서 제공하는 스케일링 클래스인 StandardScaler, MinMaxScaler를 알아보자!

 

1) StandardScaler

표준화를 쉽게 지원하는 클래스로, 개별 피처를 평균이 0이고, 분산이 1인 가우시안 정규분포를 가진 값으로 변환

(Scaler 클래스의 fit(), transform()은 2차원 이상 데이터만 가능하다!!)

from sklearn.datasets import load_iris
import pandas as pd

# 붓꽃 데이터 세트를 로딩
iris = load_iris()
iris_df = pd.DataFrame(iris.data, columns=iris.feature_names)

print('feature들의 평균값')
print(iris_df.mean())
print('\nfeature들의 분산값')
print(iris_df.var())

from sklearn.preprocessing import StandardScaler

# StandardScaler 객체 생성
scaler = StandardScaler()
# StandardScaler로 데이터 세트 변환. fit()과 transform() 호출
scaler.fit(iris_df)
iris_scaled = scaler.transform(iris_df)

# transform()시 스케일 변환된 데이터 세트가 ndarray로 반환돼 이를 DataFrame으로 변환
iris_df_scaled = pd.DataFrame(iris_scaled, columns=iris.feature_names)
print('feature들의 평균값')
print(iris_df_scaled.mean())
print('\nfeature들의 분산값')
print(iris_df_scaled.var())

2) MinMaxScaler

정규화를 쉽게 지원하는 클래스로, 서로 다른 피처의 크기를 통일하기 위해 데이터값을 0과 1 사이의 범위 값으로 변환(음수 값이 있으면 -1에서 1값으로 변환)

(Scaler 클래스의 fit(), transform()은 2차원 이상 데이터만 가능하다!!)

from sklearn.preprocessing import MinMaxScaler

# MinMaxScaler 객체 생성
scaler = MinMaxScaler()
# MinMaxScaler로 데이터 세트 변환. fit()과 transform() 호출
scaler.fit(iris_df)
iris_scaled = scaler.transform(iris_df)

# transform()시 스케일 변환된 데이터 세트가 ndarray로 반환돼 이를 DataFrame으로 변환
iris_df_scaled = pd.DataFrame(iris_scaled, columns=iris.feature_names)
print('feature들의 최솟값')
print(iris_df_scaled.min())
print('\nfeautre들의 최댓값')
print(iris_df_scaled.max())

참고로 MinMaxScaler 객체에서 feature_range 인자를 활용해 데이터값의 범위를 조정할 수 있음

 

 

★학습 데이터와 테스트 데이터의 스케일링 변환 시 유의점★

fit()은 데이터 변환을 위한 기준 정보를 기억하고 transform()은 이렇게 설정된 정보를 이용해 데이터를 변환함

 

그런데 학습 데이터 세트와 테스트 데이터 세트에 이 fit()과 transfrom()을 적용할 때 주의가 필요하다. 

다른 전처리 객체도 마찬가지임!!!(SimpleImputer, KNNImputer 등등)

 

Scaler 객체를 이용해 학습 데이터 세트로 fit()과 transform()을 적용하면 테스트 데이터 세트로는 다시 fit()을 수행하지 않고 학습 데이터 세트로 fit()을 수행한 결과를 이용해 transform() 변환을 적용해야 한다. 

즉, 학습 데이터로 fit()이 적용된 스케일링 기준 정보를 그대로 테스트 데이터에 적용해야 하며, 그렇지 않고 테스트 데이터로 다시 새로운 스케일링 기준 정보를 만들게 되면 학습 데이터와 테스트 데이터의 스케일링 기준 정보가 서로 달라지기 때문에 올바른 예측 결과를 도출하지 못한다.

 

아래 예시를 통해 테스트 데이터에 fit()을 적용할 때 어떠한 문제가 발생하는지 알아보자~

from sklearn.preprocessing import MinMaxScaler
import numpy as np

# 학습 데이터는 0부터 10까지, 테스트 데이터는 0부터 5까지 값을 가지는 데이터 세트로 생성
# Scaler 클래스의 fit(), transform()은 2차원 이상 데이터만 가능하므로 reshape(-1,1)로 차원 변경
train_array = np.arange(0,11).reshape(-1,1)
test_array = np.arange(0,6).reshape(-1,1)
# MinMaxScaler를 이용해 학습 데이터 변환
scaler = MinMaxScaler() # MinMaxScaler 객체에 별도의 feature_range 파라미터 값을 지정하지 않으면 0~1 값으로 변환
scaler.fit(train_array)
train_scaled = scaler.transform(train_array)

print('원본 train_array 데이터:', np.round(train_array.reshape(-1), 2))
print('Scale된 train_array 데이터:', np.round(train_scaled.reshape(-1), 2))

# MinMaxScaler를 이용해 테스트 데이터 변환
scaler.fit(test_array)
test_scaled = scaler.transform(test_array)

print('원본 test_array 데이터:', np.round(test_array.reshape(-1), 2))
print('Scale된 test_array 데이터:', np.round(test_scaled.reshape(-1), 2))

출력 결과를 확인하면 학습 데이터와 테스트 데이터의 스케일링이 맞지 않음을 알 수 있다. 테스트 데이터의 경우 최솟값 0, 최댓값 5이므로 1/5로 스케일링된다. 따라서 원본값 1은 0.2로 원본값 5는 1로 변환이 된다. 앞서 학습 데이터는 스케일링 변환으로 원본값 2가 0.2로 원본값 10이 1로 변환됐다. 

→머신러닝 모델은 학습 데이터를 기반으로 학습되기 때문에 반드시 테스트 데이터는 학습 데이터의 스케일링 기준에 따라야 하며, 테스트 데이터의 1 값은 학습 데이터와 동일하게 0.1 값으로 변환돼야 한다. 따라서 테스트 데이터에 다시 fit()을 적용해서는 안되며 학습 데이터로 이미 fit()이 적용된 Scaler 객체를 이용해 transform()으로 변환해야 한다.

 

다음은 맞는 방법인 테스트 데이터에 fit()을 호출하지 않고 학습 데이터로 fit()을 수행한 MinMaxScaler 객체의 transform()을 이용해 데이터를 변환해보자

from sklearn.preprocessing import MinMaxScaler

scaler = MinMaxScaler()
scaler.fit(train_array)
train_scaled = scaler.transform(train_array)
test_scaled = scaler.transform(test_array)

print('원본 train_array 데이터:', np.round(train_array.reshape(-1), 2))
print('Scale된 train_array 데이터:', np.round(train_scaled.reshape(-1), 2))
print('\n원본 test_array 데이터:', np.round(test_array.reshape(-1), 2))
print('Scale된 test_array 데이터:', np.round(test_scaled.reshape(-1), 2))

스케일링 변환 시 유의할 점을 요약하면

첫째, 가능하다면 전체 데이터의 스케일링 변환을 적용한 뒤 학습과 테스트 데이터로 분리

둘째, 1이 여의치 않다면 테스트 데이터 변환 시에는 fit()을 적용하지 않고 학습 데이터로 이미 fit()된 Scaler 객체를 이용해 transform()으로 변환

 

https://www.inflearn.com/questions/751488/%EC%A0%84%EC%B2%98%EB%A6%AC%EC%99%80-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EB%B6%84%ED%95%A0

 

전처리와 데이터 분할 - 인프런 | 질문 & 답변

선생님, 안녕하세요. 스케일링 파트를 보다가 의문점이 생겨서 문의 드렸습니다. 가능하다면 전체 데이터의 스케일링 변환을 적용한 뒤 학습/테스트 데이터로 분리 하라고 하셨는데 스케일링

www.inflearn.com


로그변환

●log변환은 왜곡된 분포를 가진 피처를 비교적 정규분포에 가깝게 변환 → 변수 치우침을 해결하는 대표적인 처리 방법

●log변환은 로그를 취하는 순간 그 수는 진수가 되어버리니 값이 작아지기 때문에 큰 수치를 같은 비율의 작은 수치로 변환하기 때문에 데이터 분포도의 왜곡을 상당 수준 개선

●넘파이의 log1p() 함수를 이용하여 적용이 가능

 

로그변환과 스케일링의 차이

딱 정해서 말씀드리면, 개별 feature, 또는 target의 분포도가 skew가 심하면 log 변환을 합니다. Standard Scaler는 일반적으로 선형기반의 머신러닝의 입력 (전체) 데이터들에 대해서 다 적용합니다.  보통 scaling은 전체 feature들에(카테고리성 피처 제외) 다 적용합니다.

Skew가 심하면 로그 변환, 전체 데이터의 스케일링을 표준 정규 분포 형태로 맞추고 싶다면 Standard Scaler를 적용합니다(선형 기반의 머신러닝의 입력에서 MinMax나 Standard scaler를 선호는 합니다)

 

Reference) https://www.inflearn.com/questions/171024/%EC%8A%A4%EC%BC%80%EC%9D%BC%EB%A7%81%EA%B3%BC-%EB%A1%9C%EA%B7%B8%EB%B3%80%ED%99%98%EC%9D%98-%EC%B0%A8%EC%9D%B4

지도학습이란?
지도학습은 명확한 정답이 주어진 데이터를 먼저 학습한 뒤 미지의 정답을 예측하는 방식

이때 학습을 위해 주어진 데이터 세트를 학습 데이터 세트, 머신러닝 모델의 예측 성능을 평가하기 위해 별도로 주어진 데이터 세트를 테스트 데이터 세트로 지칭함

하이퍼 파라미터란?
하이퍼 파라미터는 머신러닝 알고리즘별로 최적의 학습을 위해 직접 입력하는 파라미터들을 통칭하며, 하이퍼 파라미터를 통해 머신러닝 알고리즘의 성능을 튜닝할 수 있음


붓꽃 품종 예측하기
추후에 배우게 될 의사결정트리(Decision Tree)를 활용해 붓꽃의 품종을 분류하는 모델을 만들어 머신러닝의 흐름을 파악해보자!

from sklearn.datasets import load_iris
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
# 붓꽃 데이터 세트 로딩
iris = load_iris()

# 피처와 레이블
iris_data = iris.data
iris_label = iris.target

학습 데이터로 학습된 모델이 얼마나 뛰어난 성능을 가지는지 평가하기 위해 학습/테스트 데이터 분리함

# 학습/테스트 데이터 분리
x_train, x_test, y_train, y_test = train_test_split(iris_data, iris_label,
                                                   test_size=0.2, random_state=11)
# DecisionTreeClassifier 객체 생성
dt_clf = DecisionTreeClassifier(random_state=11)

# 학습
dt_clf.fit(x_train, y_train)

# 예측
pred = dt_clf.predict(x_test)

# 평가
print('예측 정확도:{0:.4f}'.format(accuracy_score(y_test, pred)))
예측 정확도:0.9333

사이킷런 프레임워크 익히기(지도학습:분류, 회귀)
●사이킷런은 ML 모델 학습을 위해 fit(), 학습된 모델의 예측을 위해 predict() 메서드 제공
●분류 알고리즘은 Classifier, 회귀 알고리즘은 Regressor로 지칭 → 두 개를 합쳐서 Estimator라고 부름
●cross_val_score와 같은 평가함수, GridSearchCV와 같은 하이퍼 파라미터 튜닝을 지원하는 클래스는 Estimator를 인자로 받음

사이킷런의 주요 모듈


Model Selection 모듈 소개
1. 학습/테스트 데이터 세트 분리 - train_test_split()
sklearn.model_selection의 train_test_split()함수

●첫 번째 파라미터로 피처 데이터 세트 입력
●두 번째 파라미터로 레이블 데이터 세트 입력
●test_size: 전체 데이터에서 테스트 데이터 세트 크기를 얼마로 샘플링할 것인가를 결정함. 디폴트는 0.25, 즉 25%임
●train_size: 전체 데이터에서 학습용 데이터 세트 크기를 얼마로 샘플링할 것인가를 결정함. test_size parameter를 통상적으로 사용하기 때문에 train_size는 잘 사용되지 않음
●shuffle: 데이터를 분리하기 전에 데이터를 미리 섞을지를 결정함, 디폴트는 True임
●random_state: 호출할 때마다 동일한 학습/테스트용 데이터 세트를 생성하기 위해 주어지는 난수값임, train_test_split()은 호출 시 무작위로 데이터를 분리하므로 random_state를 지정하지 않으면 수행할 때마다 다른 학습/테스트 용 데이터를 생성함

하지만 이 방법 역시 과적합(Overfitting)이라는 약점이 존재함
여기서 과적합이란 모델이 학습 데이터에만 과도하게 최적화되어, 실제 예측을 다른 데이터로 수행할 경우에는 예측 성능이 과도하게 떨어지는 경우를 말함, 즉 고정된 학습 데이터로 학습하고 고정된 테스트 데이터를 통해 모델의 성능을 확인하고 수정하는 과정을 반복하면, 결국 내가 만든 모델은 테스트 데이터에만 잘 동작하는 모델이 된다. 이 경우에는 테스트 데이터에 과적합되어 다른 실제 데이터를 가지고 예측을 수행하면 엉망인 결과가 나와버림
→ 이를 해결하기 위해 교차검증을 사용

2. 교차검증
간략하게 설명하자면 본고사를 치르기 전에 모의고사를 여러 번 보는 것임. 즉, 본고사가 테스트 데이터 세트에 대해 평가하는 거라면 모의고사는 교차검증에서 많은 학습과 검증 세트에서 알고리즘 학습과 평가를 수행하는 것

(원칙적으로 train_test_split으로 학습과 테스트 데이터 세트로 분리 후 학습 데이터를 학습과 검증 데이터 세트로 나눠 1차 평가)

→교차검증은 별도의 여러 세트로 구성된 학습 데이터 세트와 검증 데이터 세트에서 학습과 평가를 수행하는 것
⇛ML모델의 성능 평가는 교차 검증 기반으로 1차 평가를 한 뒤 최종적으로 테스트 데이터 세트에 적용해 평가하는 프로세스이다.

 

(iris데이터는 데이터 세트가 적기 때문에 교차검증이 어떻게 적용되는지를 살펴보기 위해서 학습 데이터에서 검증 데이터 세트를 안나누고 전체 데이터 세트로 교차검증을 수행했다. GridSearchCV는 원칙대로 적용)

1) K 폴드 교차검증
K 폴드 교차검증은 가장 보편적으로 사용되는 교차검증 기법으로, K개의 데이터 폴드 세트를 만들어서 K번만큼 각 폴드 세트에 학습과 검증 평가를 반복적으로 수행하는 방법
→K개의 예측 평가를 구했으면 이를 평균해서 K 폴드 평가 결과로 반영

from sklearn.datasets import load_iris
from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import accuracy_score
from sklearn.model_selection import KFold
import numpy as np

iris = load_iris()
features = iris.data
label = iris.target

dt_clf = DecisionTreeClassifier(random_state=156)

# 5개의 폴드 세트로 분리하는 KFold 객체와 폴드 세트별 정확도를 담을 리스트 객체 생성
kfold = KFold(n_splits=5)
cv_accuracy = []

n_iter = 0 # for loop시 몇 번 반복했는지 확인하기 위해 n_iter 객체 생성
# KFold 객체의 split()를 호출하면 학습용, 검증용 테스트 데이터로 분할할 수 있는 인덱스를 array로 반환
for train_index, test_index in kfold.split(features):
    # kfold.split()으로 반환된 인덱스를 이용해 학습용, 검증용 테스트 데이터 추출
    x_train, x_test = features[train_index], features[test_index]
    y_train, y_test = label[train_index], label[test_index]
    # 학습 및 예측
    dt_clf.fit(x_train, y_train)
    pred = dt_clf.predict(x_test)
    # 평가(정확도)
    accuracy = np.round(accuracy_score(y_test, pred), 4)
    # 폴드 세트별 정확도를 담을 리스트 객체에 정확도 추가
    cv_accuracy.append(accuracy)
    
    n_iter += 1
    train_size = x_train.shape[0]
    test_size = x_test.shape[0]
    print('\n#{0} 교차검증 정확도:{1}, 학습 데이터 크기:{2}, 검증 데이터 크기:{3}'.format(n_iter, accuracy, train_size, test_size))
    # split()이 어떤 값을 실제로 반환하는지 확인해 보기 위해 검증 데이터 세트의 인덱스도 추출해봄
    print('#{0} 검증 세트 인덱스:{1}'.format(n_iter, test_index))

# 개별 iteration별 정확도를 합하여 평균 정확도 계산
print('\n## 평균 검증 정확도:', np.mean(cv_accuracy))


2) Stratified K 폴드
Stratified K 폴드는 불균형한 분포도를 가진 레이블 데이터 집합을 위한 K 폴드 방식
→Stratified K 폴드는 원본 데이터의 레이블 분포를 먼저 고려한 뒤 이 분포와 동일하게 학습과 검증용 테스트 데이터를 분배

StratifiedKFold를 사용하는 방법은 KFold를 사용하는 방법과 비슷하다. 단 하나 큰 차이는 StratifiedKFold는 레이블 데이터 분포도에 따라 학습/검증용 테스트 데이터를 나누기 때문에 split() 메서드에 인자로 피처 데이터 세트뿐만 아니라 레이블 데이터 세트도 반드시 필요함

from sklearn.datasets import load_iris
from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import accuracy_score
from sklearn.model_selection import StratifiedKFold
import numpy as np

iris = load_iris()
features = iris.data
label = iris.target

dt_clf = DecisionTreeClassifier(random_state=156)

# 3개의 폴드 세트로 분리하는 StratifiedKFold 객체와 폴드 세트별 정확도를 담을 리스트 객체 생성
skfold = StratifiedKFold(n_splits=3)
cv_accuracy = []

n_iter = 0 # for loop시 몇 번 반복했는지 확인하기 위해 n_iter 객체 생성
# StratifiedKFold 객체의 split()를 호출하면 학습용, 검증용 테스트 데이터로 분할할 수 있는 인덱스를 array로 반환
# StratifiedKFold의 split() 호출시 반드시 레이블 데이터 세트도 추가 입력 필요
for train_index, test_index in skfold.split(features, label):
    # skfold.split()으로 반환된 인덱스를 이용해 학습용, 검증용 테스트 데이터 추출
    x_train, x_test = features[train_index], features[test_index]
    y_train, y_test = label[train_index], label[test_index]
    # 학습 및 예측
    dt_clf.fit(x_train, y_train)
    pred = dt_clf.predict(x_test)
    # 평가(정확도)
    accuracy = np.round(accuracy_score(y_test, pred), 4)
    # 폴드 세트별 정확도를 담을 리스트 객체에 정확도 추가
    cv_accuracy.append(accuracy)
    
    n_iter += 1
    train_size = x_train.shape[0]
    test_size = x_test.shape[0]
    print('\n#{0} 교차검증 정확도:{1}, 학습 데이터 크기:{2}, 검증 데이터 크기:{3}'.format(n_iter, accuracy, train_size, test_size))
    # split()이 어떤 값을 실제로 반환하는지 확인해 보기 위해 검증 데이터 세트의 인덱스도 추출해봄
    print('#{0} 검증 세트 인덱스:{1}'.format(n_iter, test_index))

# 개별 iteration별 정확도를 합하여 평균 정확도 계산
print('\n## 평균 검증 정확도:', np.round(np.mean(cv_accuracy), 4))

그럼 KFold와 StratifiedKFold는 언제 써야 할까?

  • KFold
    • 회귀 문제(회귀의 결정값은 이산값 형태의 레이블이 아니라 연속된 숫자값이기 때문에 결정값 별로 분포를 정하는 의미가 없기 때문에 Stratified K 폴드가 지원되지 않음)에서의 교차 검증
  • StratifiedKFold
    • 레이블 데이터가 왜곡됐을 경우 반드시
    • 분류 문제에서의 교차 검증


사이킷런은 교차검증을 좀 더 편리하게 수행할 수 있게 해주는 API를 제공함 → cross_val_score()

3) cross_val_score()

cross_val_score(estimator, X, y=None, scoring=None, cv=None, n_jobs=1, verbose=0, fit_params=None, pre_dispatch='2*n_jobs') → 이 중 estimator, X, y, scoring, cv가 주요 파라미터임
●estimator: 사이킷런의 분류 알고리즘 클래스인 Classifier 또는 회귀 알고리즘 클래스인 Regressor를 의미
●X: 피처 데이터 세트
●y: 레이블 데이터 세트
●scoring: 예측 성능 평가 지표
●cv: 교차검증 폴드 수

cross_val_score()는 분류 문제인 경우 Stratified K 폴드 방식으로 작동하고, 회귀 문제인 경우 K 폴드 방식으로 작동

from sklearn.datasets import load_iris
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import cross_val_score
import numpy as np

iris = load_iris()
features = iris.data
label = iris.target

dt_clf = DecisionTreeClassifier(random_state=156)

# 성능 지표는 정확도(accuracy), 교차검증 세트는 3개
scores = cross_val_score(dt_clf, features, label, scoring='accuracy', cv=3)
print('교차검증별 정확도:', np.round(scores, 4))
print('평균 검증 정확도:', np.round(np.mean(scores), 4))


4) 교차검증과 최적의 하이퍼파라미터 튜닝을 한번에 - GridSearchCV
사이킷런은 GridSearchCV를 이용해 분류와 회귀 알고리즘에 사용되는 하이퍼 파라미터를 순차적으로 입력하면서 편리하게 최적의 하이퍼파라미터를 도출할 수 있는 방안을 제공(최적의 하이퍼파라미터를 교차검증을 통해 추출)

<GridSearchCV의 주요 파라미터>
●estimator: 사이킷런의 분류 알고리즘 클래스인 Classifier 또는 회귀 알고리즘 클래스인 Regressor를 의미
●param_grid: key + 리스트 값을 가지는 딕셔너리가 주어짐. estimator의 튜닝을 위해 하이퍼파라미터명과 사용될 여러 하이퍼파라미터 값을 지정
●scoring: 예측 성능 평가 지표
●cv: 교차검증 폴드 수
●refit: 디폴트가 True이며 True로 생성시 최적의 하이퍼파라미터를 찾은 뒤, 입력된 estimator 객체를 해당 하이퍼파라미터로 재학습시킴

from sklearn.datasets import load_iris
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import train_test_split, GridSearchCV

# 데이터를 로딩하고 학습 데이터와 테스트 데이터 분리
iris = load_iris()
features = iris.data
label = iris.target
x_train, x_test, y_train, y_test = train_test_split(features, label, test_size=0.2, random_state=121)

dtree = DecisionTreeClassifier()

# 파라미터를 딕셔너리 형태로 설정
parameters = {'max_depth':[1,2,3], 'min_samples_split':[2,3]}

grid_dtree = GridSearchCV(dtree, param_grid=parameters, scoring='accuracy', cv=3) # 디폴트로 refit=True
grid_dtree.fit(x_train, y_train)

# cv=3을 돌면서, 하이퍼파라미터 조합을 다 적용시켜서 그중 최적의 하이퍼파라미터 값
print('GridSearchCV 최적 파라미터:', grid_dtree.best_params_)
# cv=3을 돌면서, 하이퍼파라미터 조합을 다 적용시켜서 그중 최적의 하이퍼파라미터 값의 평가 결과 값
print('GridSearchCV 최고 정확도:', grid_dtree.best_score_)

from sklearn.metrics import accuracy_score
# GridSearchCV의 refit=True로 최적의 하이퍼파라미터로 학습된 estimator 반환
estimator = grid_dtree.best_estimator_

# 최적의 하이퍼파라미터로 학습된 estimator를 이용해 테스트 데이터 세트에 대해 예측 및 평가
pred = estimator.predict(x_test)
print('테스트 데이터 세트 정확도:{0:.4f}'.format(accuracy_score(y_test, pred)))

검증 세트의 최고 정확도 0.975보다 테스트 데이터 세트 정확도가 0.9667로 약간 낮게 나왔다. (검증 세트보다 약간 낮게 나오는게 일반적)→ 정확도 차이가 0.975보다 엄청 작게 나온게 아니므로 과적합이 없다고 판단

 

 

일반적인 머신러닝 모델 적용 방법은 학습 데이터를 GridSearchCV를 이용해 최적 하이퍼파라미터 튜닝을 수행한 뒤에 별도의 테스트 세트에서 이를 평가한다.

('데이터 전처리' 카테고리에서 '데이터 전처리 정리 및 실습'  파트에서 발생한 오류)

# 특징 선택과 모델 하이퍼 파라미터 튜닝
from sklearn.ensemble import RandomForestClassifier as RFC
from xgboost import XGBClassifier as XGB
from lightgbm import LGBMClassifier as LGBM
from sklearn.feature_selection import *
from sklearn.model_selection import ParameterGrid
from sklearn.metrics import f1_score

# 모델 파라미터 그리드 설계
model_parameter_grid = dict()
model_parameter_grid[RFC] = ParameterGrid({'max_depth':[2, 3, 4],
                                          'n_estimators':[50, 100]})
model_parameter_grid[XGB] = ParameterGrid({'max_depth':[2, 3, 4],
                                          'n_estimators':[50, 100],
                                          'learning_rate':[0.05, 0.1, 0.15, 0.2]})
model_parameter_grid[LGBM] = ParameterGrid({'max_depth':[2, 3, 4],
                                           'n_estimators':[50, 100],
                                           'learning_rate':[0.05, 0.1, 0.15, 0.2]})
# 튜닝 시작
best_score = 0
for k in range(30, 5, -1):
    s_x_train = x_train[pvals.iloc[:k].index]
    s_x_test = x_test[pvals.iloc[:k].index]
    for M in model_parameter_grid.keys():
        for P in model_parameter_grid[M]:
            model = M(**P).fit(s_x_train, y_train)
            pred = model.predict(s_x_test)
            score = f1_score(y_test, pred)
            if score > best_score:
                best_score = score
                best_feature = s_x_train.columns
                best_model = M
                best_parameter = P

print(best_score)
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Input In [78], in <cell line: 3>()
      6 for M in model_parameter_grid.keys():
      7     for P in model_parameter_grid[M]:
----> 8         model = M(**P).fit(s_x_train, y_train)
      9         pred = model.predict(s_x_test)
     10         score = f1_score(y_test, pred)

File ~\AppData\Roaming\Python\Python39\site-packages\xgboost\core.py:575, in _deprecate_positional_args.<locals>.inner_f(*args, **kwargs)
    573 for k, arg in zip(sig.parameters, args):
    574     kwargs[k] = arg
--> 575 return f(**kwargs)

File ~\AppData\Roaming\Python\Python39\site-packages\xgboost\sklearn.py:1357, in XGBClassifier.fit(self, X, y, sample_weight, base_margin, eval_set, eval_metric, early_stopping_rounds, verbose, xgb_model, sample_weight_eval_set, base_margin_eval_set, feature_weights, callbacks)
   1352     expected_classes = np.arange(self.n_classes_)
   1353 if (
   1354     self.classes_.shape != expected_classes.shape
   1355     or not (self.classes_ == expected_classes).all()
   1356 ):
-> 1357     raise ValueError(
   1358         f"Invalid classes inferred from unique values of `y`.  "
   1359         f"Expected: {expected_classes}, got {self.classes_}"
   1360     )
   1362 params = self.get_xgb_params()
   1364 if callable(self.objective):

ValueError: Invalid classes inferred from unique values of `y`.  Expected: [0 1], got [-1  1]

 

클래스가 0부터 시작해야 하기 때문에 발생합니다(버전 1.3.2부터 필요함)

이를 해결하는 쉬운 방법은 sklearn.preprocessing 라이브러리의 LabelEncoder를 사용하는 것입니다.

 

솔루션

from sklearn.preprocessing import LabelEncoder
le = LabelEncoder()
y_train = le.fit_transform(y_train)

y_train의 레이블을 새로 인코딩 한 뒤 학습을 하니 해결이 가능했다.

 

이후 모델 평가 시 주의사항

le.inverse_transform으로 인코딩된 값을 다시 디코딩해서 새로 들어오는 데이터 즉, 테스트 데이터의 레이블 값(y_test)의 원본 형태인 -1과 1로 맞추어야 score를 구할 수 있음

# 튜닝 시작
best_score = 0
for k in range(30, 5, -1):
    s_x_train = x_train[pvals.iloc[:k].index]
    s_x_test = x_test[pvals.iloc[:k].index]
    for M in model_parameter_grid.keys():
        for P in model_parameter_grid[M]:
            model = M(**P).fit(s_x_train, y_train)
            pred = model.predict(s_x_test)
            pred = le.inverse_transform(pred)
            score = f1_score(y_test, pred)
            if score > best_score:
                best_score = score
                best_feature = s_x_train.columns
                best_model = M
                best_parameter = P

print(best_score)
print('\n')
print(best_feature)
print('\n')
print(best_model)
print('\n')
print(best_parameter)

 

출처:https://stackoverflow.com/questions/71996617/invalid-classes-inferred-from-unique-values-of-y-expected-0-1-2-3-4-5-got

넘파이
▷ndarray: N차원 배열 객체


▷ndarray 생성
넘파이 모듈의 array() 함수로 생성하며 인자로 주로 파이썬 list를 입력한다.


▷ndarray 형태와 차원

ndarray의 shape는 ndarray.shape 속성으로, 차원은 ndarray.ndim 속성으로 알 수 있다.
ndarray 배열의 shape 변수는 ndarray의 크기, 즉 행과 열의 수를 튜플 형태로 가지고 있으며 이를 통해 ndim을 사용 하지 않고 바로 ndarray 배열의 차원을 알 수 있다.(shape안의 숫자 개수를 통해 차원을 바로 알 수 있음, 리스트의 형태를 보고도 차원을 바로 알 수 있음)


▷ndarray 데이터 타입
- ndarray내의 데이터값은 숫자 값, 문자열 값, 불 값 등이 모두 가능하다.
- ndarray내의 데이터 타입은 그 연산의 특성상 같은 데이터 타입만 가능하다. 즉, 한개의 ndarray 객체에 int와 float이 함께 있을 수 없다.
- 만약 다른 데이터 유형이 섞여 있는 리스트를 ndarray로 변경하면 데이터 크기가 더 큰 데이터 타입으로 형 변환을 일괄 적용한다.
- ndarray내의 데이터 타입은 ndarray.dtype으로 확인한다.


▷ndarray 데이터 타입 변환
- ndarray.astype()을 이용하여 ndarray 내 데이터값의 타입을 변환하고, 변경을 원하는 타입을 astype()에 인자로 입력한다.
- 대용량 데이터를 다룰 시 메모리 절약을 위해서 형변환을 한다.
→파이썬 기반의 머신러닝 알고리즘은 대부분 메모리로 데이터를 전체 로딩한 다음 이를 기반으로 알고리즘을 적용하기 때문에 대용량의 데이터를 로딩할 때는 수행속도가 느려지거나 메모리 부족으로 오류가 발생할 수 있다.
→가령 int형으로 충분한 경우인데, 데이터 타입이 float라면 int형으로 바꿔서 메모리를 절약할 수 있다.


▷ndarray를 편리하게 생성하기-arange, zeros, ones
특정 크기와 차원을 가진 ndarray를 연속값이나 0또는 1로 초기화 생성해야 할 경우 arange(), zeros(), ones()를 이용해 쉽게 ndarray를 생성할 수 있다. 주로 테스트용으로 데이터를 만들거나 데이터를 일괄적으로 초기화해야 할 경우에 사용된다.

●arange()는 array를 range()로 표현한 것이다. default 함수 인자는 stop값이며, 0부터 stop값-1까지의 연속 숫자 값으로 구성된 1차원 ndarray를 만들어 준다. 여기서는 stop값만 부여했으나 range와 유사하게 start값과 step을 활용할 수도 있다.
●zeros()와 ones()는 함수 인자로 튜플 형태의 shape값을 입력하면 모든 값을 0이나 1로 채운 해당 shape를 가진 ndarray를 반환한다. 그리고 dtype을 정해주지 않으면 default로 float64 형의 데이터로 ndarray를 채운다.


▷ndarray의 차원과 크기를 변경하는 reshape()
●reshape()는 ndarray를 특정 차원 및 형태로 변환한다. 변환 형태를 함수 인자로 부여하면 된다.

●reshape(-1,5)와 같이 인자에 -1을 부여하면 -1에 해당하는 axis의 크기는 가변적이되 -1이 아닌 인자값(여기서는 5)에 해당하는 axis 크기는 인자값으로 고정하여 ndarray의 shape를 변환한다.

●reshape()는 reshape(-1,1), reshape(-1,)과 같은 형식으로 변환이 요구되는 경우가 많다. 주로 머신러닝 API의 인자로 1차원 ndarray를 명확하게 2차원 ndarray로 변환하여 입력하기를 원하거나, 또는 반대의 경우가 있을 경우 reshape()를 이용하여 ndarray의 형태를 변환시켜 주는데 사용된다.


★ndarray의 axis 축 이해
강의 'ndarray의 axis 축 이해' 참고(axis는 엄청 헷갈리기 때문에 이론이 생각안나면 다음 강의 참고)


▷ndarray의 데이터 세트 선택하기-인덱싱(Indexing)

1. 단일 값 추출
- 1차원 ndarray
●ndarray는 axis를 기준으로 0부터 시작하는 위치 인덱스값을 가지고 있다. 해당 인덱스 값을 []에 명시하여 단일 값을 추출한다. 마이너스가 인덱스로 사용되면 맨 뒤에서부터 위치를 지정한다.

- 2차원 ndarray
●1차원과 다차원 ndarray에서의 데이터 접근의 차이는 콤마(,)로 분리된 인덱스를 통해 접근하는 것이다. 즉, 2차원의 경우 콤마로 분리된 로우와 컬럼 위치의 인덱스를 통해 접근한다.

2. 슬라이싱(Slicing)
- 1차원 ndarray
●슬라이싱은 :을 이용하여 연속된 값을 선택한다.

- 2차원 ndarray
●2차원 ndarray에서의 슬라이싱도 1차원 ndarray에서의 슬라이싱과 유사하며, 단지 콤마(,)로 로우와 컬럼 인덱스를 지칭하는 부분만 다르다.

3. 팬시 인덱싱
- 1차원 ndarray
●팬시 인덱싱은 리스트나 ndarray로 인덱스 집합을 지정하면 해당 위치의 인덱스에 해당하는 ndarray를 반환하는 인덱싱 방식이다.

- 2차원 ndarray
●위의 인덱싱과 마찬가지로 콤마(,)로 로우와 컬럼 인덱스를 지칭하는 부분만 다르다.

4. 불린 인덱싱
●불린 인덱싱은 조건 필터링과 추출을 동시에 할 수 있기 때문에 매우 자주 사용되는 인덱싱 방식이다.


▷배열의 정렬-sort()와 argsort()
-넘파이 sort() 유형
1. np.sort(ndarray): 인자로 들어온 원 행렬은 그대로 유지한 채 원 행렬의 정렬된 행렬을 반환
2. ndarray.sort(): 원 행렬 자체를 정렬한 형태로 반환하며 반환 값은 None
np.sort()나 ndarray.sort() 모두 기본적으로 오름차순으로 행렬 내 원소를 정렬한다. 내림차순으로 정렬하기 위해서는 [::-1]을 적용한다. np.sort()[::-1]과 같이 사용하면 된다.
⊙배열이 2차원 이상일 경우에 axis 축 값 설정을 통해 로우 방향, 또는 컬럼 방향으로 정렬을 수행할 수 있다.

-argsort()
원본 행렬 정렬 시 정렬된 행렬의 원래 인덱스를 필요로 할 때 np.argsort()를 이용한다. np.argsort()는 정렬 행렬의 원본 행렬 인덱스를 ndarray 형으로 반환한다.
argsort()는 넘파이의 데이터 추출에서 많이 사용되므로 주의 깊게 봐야한다.


▷선형대수 연산-행렬 내적(행렬 곱)
행렬 내적의 특성으로 왼쪽 행렬의 열 개수와 오른쪽 행렬의 행 개수가 동일해야 내적 연산이 가능하다.


▷선형대수 연산-전치 행렬
원 행렬에서 행과 열 위치를 교환한 원소로 구성한 행렬을 그 행렬의 전치 행렬이라고 한다.

numpy.ipynb
0.02MB

판다스
▷판다스의 주요 구성 요소-DataFrame, Series, Index


▷DataFrame과 리스트, 딕셔너리, 넘파이 ndarray 상호 변환


▷DataFrame의 컬럼 데이터 세트 생성과 수정
→ [ ]연산자를 이용해 쉽게 할 수 있다.
●DataFrame[ ] 내에 새로운 컬럼명을 입력하고 값을 할당해주기만 하면 된다.
ex1 ) titanic_df['Age_0'] = 0 을 하면 새로운 컬럼명 'Age_0'에 모든 데이터값이 0으로 할당된다.
ex2 ) titanic_df['Age_by_10'] = titanic_df['Age'] * 10 을 하면 새로운 컬럼명 'Age_by_10'에 기존 컬럼 'Age'에 10을 곱한 값들이 할당된다.
ex3 ) titanic_df['Family_No'] = titanic_df['SibSp'] + titanic_df['Parch'] + 1 도 마찬가지다.
●DataFrame 내의 기존 컬럼 값도 쉽게 일괄적으로 업데이트할 수 있다.
ex1 ) titanic_df['Age_by_10'] = titanic_df['Age_by_10'] + 100


▷(TIP). 파라미터 inplace
변수 갱신 여부를 설정하는 파라미터
(본인은 변수를 다시 정의하는 것보다 inplace를 사용하는게 편하더라)


▷DataFrame 데이터 삭제
DataFrame의 drop()
DataFrame.drop(labels=None, axis=0, index=None, columns=None, level=None, inplace=False, errors='raise')
●axis: DataFrame의 로우를 삭제할 때는 axis=0, 컬럼을 삭제할 때는 axis=1로 설정(2차원 행렬에서 axis0은 로우 방향, axis1은 컬럼 방향: 'numpy ndarray axis 축 이해' 참고)
●로우를 삭제할 때는 labels에 index를 입력, 컬럼을 삭제할 때는 labels에 컬럼명을 입력
●여러개의 로우나 컬럼들의 삭제는 drop의 인자로 삭제 로우나 컬럼들을 리스트로 입력
●원본 DataFrame은 유지하고 드롭된 DataFrame을 새롭게 객체 변수로 받고 싶다면 inplace=False로 설정(디폴트 값이 False임)
ex ) titanic_drop_df = titanic_df.drop('Age_0', axis=1, inplace=False)
●원본 DataFrame에 드롭된 결과를 적용할 경우에는 inplace=True를 적용
ex ) titanic_df.drop('Age_0', axis=1, inplace=True)


▷판다스 Index 객체
●판다스의 Index 객체는 RDBMS의 PK(Primary Key)와 유사하게 DataFrame, Series의 레코드를 고유하게 식별하는 객체(하지만 판다스 Index는 별도의 컬럼값이 아니다)
●DataFrame/Series 객체는 Index 객체를 포함하지만 DataFrame/Series 객체에 연산 함수를 적용할 때 Index는 연산에서 제외된다. Index는 오직 식별용으로만 사용된다.
●DataFrame/Series에서 Index 객체만 추출하려면 DataFrame.index 또는 Series.index 속성을 통해 가능
●판다스 Index는 반드시 숫자형 값이 아니어도 되며, 고유한 값을 유지 할 수 있다면 문자형/Datetime도 상관 없음
●DataFrame 및 Series에 reset_index() 메서드를 수행하면 새롭게 인덱스를 연속 숫자 형으로 할당하며 기존 인덱스는 'index'라는 새로운 컬럼명으로 추가된다.
# reset_index(drop=False, inplace=False)
(TIP)'index'가 새로운 컬럼명으로 추가 안하려면 파라미터 drop=True를 사용함


▷DataFrame 인덱싱 및 슬라이싱
1. [ ]
데이터 프레임의 컬럼 선택:df[col_name] or df[col_name_list]
ex) df[’col1’] →Series 반환 / df[[’col1’]] →데이터 프레임 반환
즉, column name => Series / column name list => Data Frame
컬럼 필터링 용도로만 사용!!

2. loc[ ], iloc[ ]
→위치 기반 인덱싱:iloc / 명칭(레이블) 기반 기반 인덱싱:loc

iloc[0,1] = 3 / loc[0, 'PassengerID'] = 1
iloc[0,1] = 3 / loc[0, 'PassengerID'] = error발생

3. 불린 인덱싱(마스킹 검색)
조건식에 따른 필터링을 제공(조건이 여러개면 각 조건마다 괄호()를 씌움)
df[부울리스트] / df.loc[부울 리스트] : True인 요소의 위치에 대응되는 행만 가져옴
ex ) titanic_boolean = titanic_df[titanic_df['Age'] > 60] / titanic_boolean = titanic_df.loc[titanic_df['Age'] > 60]
→둘 중 입맛에 맞는거 사용
# 불린 인덱싱은 [ ], loc[ ]에서 공통으로 지원한다. iloc[ ]는 불린 인덱싱이 지원되지 않는다.
# and 조건: & / or 조건: | / not 조건: ~


▷DataFrame과 Series의 정렬-sort_values()
●DataFrame의 sort_values() 메소드는 by 인자로 정렬하고자 하는 컬럼값을 입력 받아서 해당 컬럼값으로 DataFrame을 정렬, 오름차순 정렬이 기본이며 ascending=True로 설정됨. 내림차순 정렬 시 ascending=False로 설정

마찬가지로, 여러 개의 컬럼으로 정렬하는 경우도 by 인자로 정렬하고자 하는 컬럼값들을 입력

●Series의 경우 컬럼 한 개에 대한 값이므로 by 인자를 사용할 필요가 없고, 오름차순/내림차순 정렬 기준만 설정하면 된다.
→Series.sort_values()


▷Aggregation 함수 적용
●sum(), max(), min(), count() 등은 DataFrame/Series에서 집합(Aggregation)연산을 수행
●DataFrame의 경우 DataFrame에서 바로 aggregation을 호출할 경우 모든 컬럼에 해당 aggregation을 적용
ex ) titanic_df.count()
●특정 컬럼에 aggregation을 적용하려면 컬럼 필터링 후 aggregation 적용
ex ) titanic_df[['Age','Fare']].mean()


▷groupby()적용
●DataFrame을 groupby 대상 컬럼을 기준으로 그룹화하는 함수
●사용 구조:df.groupby(groupby 대상 컬럼)[적용 기준 컬럼].집계함수
●주요 입력
- by: groupby 대상 컬럼
- as_index: groupby 대상 컬럼을 인덱스로 사용할 것인지 여부(default:True)

1. groupby 대상 컬럼을 제외한 모든 컬럼에 해당 aggregation 함수 적용
df.groupby('Pclass').count()
2. groupby에 특정 컬럼만 aggregation 함수 적용
df.groupby('Pclass')[['PassengerID', 'Survived']].count()
3. 서로 다른 aggregation 함수 적용
df.groupby('Pclass')['Age'].agg([max, min])
4. 여러 개의 컬럼이 서로 다른 aggregation 함수 적용(딕셔너리 형태로 aggregation이 적용될 컬럼들과 aggregation 함수를 입력)
agg_format = {'Age': 'max', 'SibSp': 'sum', 'Fare': 'mean'}
df.groupby('Pclass').agg(agg_format)

(TIP). groupby 대상 컬럼이 여러개이면 리스트에 여러개의 컬럼들을 넣고 입력(어떤 경우에 리스트로 만들고 안만들고 헷갈리니 컬럼이 한개일때도 리스트에 컬럼을 넣고 입력->groupby가 아닌 메소드일때도!!)
위에는 안한 경우임


▷결측 데이터 처리하기
●isna()로 결측 데이터 여부 확인
→isna().sum() 을 사용해 결측 데이터의 개수 확인
●fillna()로 결측 데이터 대체


▷nunique()
유니크한 값의 개수 반환
ex) df['Survived'].nunique()


▷replace()
원본 값을 특정값으로 대체
●한 개의 값만 대체할 경우
df['Sex'].replace('male', 'Man')
●여러개의 값을 대체할 경우 딕셔너리 사용
df['Sex'].replace({'male': 'Man', 'female': 'Woman'})
# 파라미터 inplace 활용



▷apply lambda 식으로 데이터 가공
●lambda 식 이해
lambda 식은 파이썬에서 함수형 프로그래밍을 지원하기 위해 만들어졌으며, 일반적으로 파이썬에서 함수를 만들 때 여러 줄의 코드를 찍지만 lambda식을 사용하면 한줄의 코드로 함수를 만들수 있다.

●Pandas는 apply lambda 식을 결합해 DataFrame이나 Series의 레코드별(행별)로 데이터를 가공하는 기능을 제공한다. 판다스의 경우 컬럼에 일괄적으로 데이터 가공을 하는 것이 속도 면에서 더 빠르나 복잡한 데이터 가공이 필요할 경우 어쩔 수 없이 apply lambda를 이용한다.

lambda 식을 이용할 때 여러 개의 값을 입력 인자로 사용해야 할 경우 map()함수를 결합해서 사용한다.

a = [1,2,3]
squares = map(lambda x : x**2, a)
list(squares)

lambda 식은 if, else만 지원하고 elif는 지원하지 않는다. if 절의 경우 if 식보다 반환 값을 먼저 기술해야 한다. 이는 lambda 식 ':' 기호의 오른편에 반환 값이 있어야 하기 때문이다.

elif를 이용하기 위해서는 else 절을 ()로 내포해 () 내에서 다시 if else 적용해 사용한다.

titanic_df['Age_cat'] = titanic_df['Age'].apply(lambda x : 'Child' if x<=15 else ('Adult' if x <= 60 else 
                                                                                  'Elderly'))

하지만 else if가 많이 나와야 하는 경우 이렇게 else를 계속 내포해서 쓰기 부담스럽다. 이 경우 아예 별도의 함수를 만들어 반환값으로 지정하는 것이 더 나을 수 있다.

def get_category(age):
    if age <= 5:
        cat = 'Baby'
    elif age <= 12:
        cat = 'Child'
    elif age <= 18:
        cat = 'Teenager'
    elif age <= 25:
        cat = 'Student'
    elif age <= 35:
        cat = 'Young Adult'
    elif age <= 60:
        cat = 'Adult'
    else:
        cat = 'Elderly'

    return cat

# lambda 식에 위에서 생성한 get_category( ) 함수를 반환값으로 지정. 
titanic_df['Age_cat'] = titanic_df['Age'].apply(lambda x : get_category(x))
titanic_df[['Age', 'Age_cat']].head()
pandas.ipynb
1.15MB

실제 프로세스에서는 데이터 탐색과 전처리 사이에 피드백 루프가 존재한다.

즉, 탐색을 한 번에 다하고 전처리를 한 번에 다하는 것이 아니다.

그리고 전처리에도 다양한 하이퍼 파라미터가 있기에 모델의 하이퍼 파라미터와 같이 튜닝해야 한다.

 

현실 지도학습 모델 개발 프로세스 (1) 필수 전처리

일반적으로 필수 전처리는 특별한 튜닝이 필요하지 않으므로 순차적으로 진행한다.

필수 전처리

현실 지도학습 모델 개발 프로세스 (2) 성능 향상을 위한 전처리

일반적으로 성능 향상을 위한 전처리는 튜닝도 같이 수행해야 한다.

성능 향상을 위한 전처리

특징 선택 단계에서 차원을 축소하므로 신규 특징 추가에서 특징을 다수 추가하는 것이 최종적으로 성능 향상에 도움이 될 가능성이 높고 이전 스텝으로 돌아가는 피드백 루프를 줄일 수 있다.(파생변수 생성)

 

이상치 확인 단계와 특징 간 상관관계 확인 단계는 많이 스킵하는 경우도 있다. 왜냐하면 특징 간 상관관계 확인 단계는  상관성이 있을 때 영향을 받는 모델이 있고 영향을 받지 않는 모델이 있다. 즉, 어떤 모델을 선택하느냐에 따라서 상관성을 무시하는 경우도 존재한다. 그리고 이상치 확인 단계는 이상치를 제거하는 것이 유리한 상황일 수도 있고 그렇지 않은 상황이 있을 수 있기 때문이다.(도메인 지식에 의해 이상치가 필요한 분석인 경우)

 

왜도 확인 단계도 마찬가지로 스킵하는 경우가 종종 있다.

 

별이 붙은 작업은 어떻게 튜닝하느냐에 따라서 성능 차이가 발생할 수 있다. 그래서 별이 붙은 단계는 튜닝을 하는 것이 중요하다.

성능 향상을 위한 전처리

스케일링을 먼저 한 이유는 재샘플링은 거의 다 거리 기반의 방법론이기 때문에 실제로는 스케일링을 먼저 해주고 재샘플링을 하는 것이 더 좋은 효과를 보일 수 밖에 없다. 

 

모델 목록 정의 즉, 모델 파라미터 튜닝은 튜닝 작업 중 제일 중요하다.

 

상황에 따라서 더 많은 프로세스가 포함될 수 있다.

 

 

파라미터 그리드 설계(실제로 튜닝하는 범위)

EDA(탐색)를 제대로 하지 않으면 파라미터 그리드에 포함된 파라미터 개수가 수십~수백만개도 된다.

다시 말해, EDA를 해서 어떤 튜닝 작업을 해야할지 정하는 것이 굉장히 중요하다.

 


(Chapter 22) 01. 최종실습예제 연습.ipynb
0.07MB

+ Recent posts