6. model_selection 모듈

  -학습용 데이터와 테스트 데이터로 분리

  -교차 검증 분할 및 평가

  -Estimator의 하이퍼 파라미터 튜닝을 위한 다양한 함수와 클래스 제공

  -train_test_split(): 학습/테스트 세트 분리

from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split
from sklearn.datasets import load_diabetes

# diabetes 데이터 세트 불러오기
diabetes = load_diabetes()

# diabetes 데이터 세트를 train데이터와 test 데이터로 쪼개기
# train_test_split의 인자는 X(feature 데이터), y(target 데이터), test_size(테스트 데이터 비율)
# 4개의 값을 반환(feature 데이터 훈련용 / 테스트용, target 데이터 훈련용 / 테스트용)
X_train, x_test, y_train, y_test = train_test_split(diabetes.data, diabetes.target, test_size = 0.3)

# 선형회귀모델 적용해보고 점수 확인하기
model = LinearRegression()
model.fit(X_train, y_train)

print("학습 데이터 점수: {}".format(model.score(X_train, y_train)))
print("평가 데이터 점수: {}".format(model.score(X_test, y_test)))

# 출력결과
학습 데이터 점수: 0.5097761792435763
평가 데이터 점수: 0.5120316690456863
# 모델에 의해 예측된 X_test에 대한 y_test값(y_pred)과 원래의 y_test값 비교 시각화
import matplotlib.pyplot as plt

y_pred = model.predict(X_test)
plt.figure(figsize = (8,4))
plt.scatter(y_test, y_pred)
plt.plot([30, 350], [30, 350], '--r')
plt.tight_layout()

 

    → x축이 실제 정답, y축이 예측된 값

    → 빨간선에 가까울수록 잘 맞춘 것이고, 빨간선에 가까운 점이 적으므로 점수도 0.5정도밖에 나오지 않음

     다른 모델을 사용하던지, 전처리 과정의 조정으로 점수를 점점 개선시켜야 함

 

  -cross_val_score(): 교차 검증

    → 교차검증은 위 그림과 같이 데이터를 여러 개로 쪼개어 한 부분을 검증용으로, 나머지 부분을 테스트용으로 사용하여 각각의 점수를 계산

from sklearn.model_selection import cross_val_score, cross_validate
import numpy as np

# 인자는 LinearRegression를 담은 model, feature 데이터 세트, target 데이터 세트
# 마지막 인자는 교차 검증을 위해 데이터를 몇 개의 세트로 분리할 것인지 지정
# 교차 검증은 분리한 데이터 세트 개수만큼 진행
scores = cross_val_score(model, diabetes.data, diabetes.target, cv = 5)

# 데이터를 5개로 나누어 각 세트에 대해 한번씩 교차검증을 진행하여 총 5번 진행하고 각각의 점수 출력
print("교차 검증 정확도: {}".format(scores))

# 모든 점수 리스트의 평균과 표준편차 출력
print("교차 검증 정확도의 평균: {} +/- {}".format(np.mean(scores), np.std(scores)))

# 출력 결과
교차 검증 정확도: [0.42955615 0.52259939 0.48268054 0.42649776 0.55024834]
교차 검증 정확도의 평균: 0.48231643590864215 +/- 0.04926857751190378

 

  -GridSearchCV: 교차 검증을 반복하며 최적의 하이퍼 파라미터 찾기

from sklearn.model_selection import GridSearchCV
from sklearn.linear_model import Ridge
import pandas as pd

# Ridge 모델에서 최적의 점수를 도출하는 alpha값의 후보
alpha = [0.001, 0.01, 0.1, 1, 10, 100, 1000]

# gridsearch를 통해 찾을 하이퍼파라미터 값과 후보 하이퍼파라미터 값을 딕셔너리 형태로 지정
param_grid = dict(alpha = alpha)

# GridSearch를 통해 최적 하이퍼 파라미터를 모델(Ridge), 하이퍼파라미터 후보, 교차검증 횟수
gs = GridSearchCV(estimator = Ridge(), param_grid = param_grid, cv = 10)

# fit하는 과정은 일반 모델과 동일
result = gs.fit(diabetes.data, diabetes.target)

print("최적 점수: {}".format(result.best_score_))
print("최적 파라미터: {}".format(result.best_params_))
print(gs.best_estimator_)
pd.DataFrame(result.cv_results_)

# 출력 결과
최적 점수: 0.46332219117960366
최적 파라미터: {'alpha': 0.1}
Ridge(alpha=0.1)

     result.cv_results_에 대한 출력결과로, 각 하이퍼파라미터에 대한 10번의 교차 검증의 결과가 전부 표로 정리되어 출력

 

  -multiprocessing을 이용한 GridSearchCV

import multiprocessing
from sklearn.datasets import load_iris
from sklearn.linear_model import LogisticRegression

# 새롭게 diabetes가 아닌 iris 데이터로 연습
iris = load_iris()

# 모델도 새롭게 LinearRegression 모델을 사용하고 그에 맞는 하이퍼파라미터 후보 지정
param_grid = [{'penalty': ['l1', 'l2'],
               'C': [1.5, 2.0, 2.5, 3.0, 3.5]}]

# multiprocessing을 통해 cpu 개수만큼 job을 생성해서 모델을 실행
gs = GridSearchCV(estimator = LogisticRegression(), param_grid = param_grid,
                             scoring = 'accuracy', cv = 10, n_jobs = multiprocessing.cpu_count())

# 이후 동일
result = gs.fit(iris.data, iris.target)

print("최적 점수: {}".format(result.best_score_))
print("최적 파라미터: {}".format(result.best_params_))
print(gs.best_estimator_)
pd.DataFrame(result.cv_results_)

# 출력 결과
최적 점수: 0.9800000000000001
최적 파라미터: {'C': 2.5, 'penalty': 'l2'}
LogisticRegression(C=2.5)

    → 모든 penalty값과 C값의 조합에서 교차 검증이 진행되므로 (C값 후보 5개 × penalty값 후보 2개 = 총 10개)의 경우에 10번씩의 교차검증 과정이 진행

 

 

7. preprocessing 모듈

  -데이터 전처리 모듈

  -데이터의 특징 스케일링(feature scaling): 데이터 값의 범위를 조정, 표준화와 정규화 방법이 있음

  • 표준화 방법(Standaradization): \( x_{i}^{'}=\frac{x_i - mean(x)}{stdev(x)} \)
  • 정규화 방법(Normalization): \( x_{i}^{'}=\frac{x_i - min(x)}{max(x) - min(x)} \)
  • scikit-learn에서는 개별 벡터 크기를 맞추는 형태로 정규화하므로 minmax를 사용한 방법과 차이는 있지만 결국 같은 방법이긴함

 

  -StandardScaler: 표준화 클래스

iris = load_iris()
iris_df = pd.DataFrame(data = iris.data, columns = iris.feature_names)
iris_df.describe()

from sklearn.preprocessing import StandardScaler

scaler = StandardScaler()

# fit을 통해 표준화에 필요한 정보를 가져오고, transform을 통해 표준화 동작 실행
# fit_transform의 return값은 넘파이 배열
iris_scaled = scaler.fit_transform(iris_df)
iris_df_scaled = pd.DataFrame(data = iris_scaled, columns = iris.feature_names)
iris_df_scaled.describe()

    → StandardScaler를 실행하기 전과 비교하면 평균은 0에 가까운 값으로, 표준편차는 1에 가까운 값으로 표준화됨

import multiprocessing
from sklearn.datasets import load_iris
from sklearn.linear_model import LogisticRegression

X_train, X_test, y_train, y_test = train_test_split(iris_df_scaled, iris.target, test_size = 0.3)

model = LogisticRegression()
model.fit(X_train, y_train)

print("훈련 데이터 점수: {}".format(model.score(X_train, y_train)))
print("평가 데이터 점수: {}".format(model.score(X_test, y_test)))

# 출력 결과
훈련 데이터 점수: 0.9904761904761905
평가 데이터 점수: 0.9111111111111111

    → 표준화된 데이터에 대해 모델에 적용시키면 평가 데이터 점수에서도 높은 점수를 받을 수 있음

 

  -MinMaxScaler: 정규화 클래스

from sklearn.preprocessing import MinMaxScaler

scaler = MinMaxScaler()
iris_scaled = scaler.fit_transform(iris_df)
iris_df_scaled = pd.DataFrame(data = iris_scaled, columns = iris.feature_names)
iris_df_scaled.describe()

    → MinMaxScaler를 실행하기 전과 비교해서 최소값은 0, 최대값은 1에 맞춰 정규화됨

X_train, X_test, y_train, y_test = train_test_split(iris_df_scaled, iris.target, test_size = 0.3)

model = LogisticRegression()
model.fit(X_train, y_train)

print("훈련 데이터 점수: {}".format(model.score(X_train, y_train)))
print("평가 데이터 점수: {}".format(model.score(X_test, y_test)))

# 출력 결과
훈련 데이터 점수: 0.9333333333333333
평가 데이터 점수: 0.9111111111111111

    → 정규화된 데이터에 대해서도 모델에 적용시키면 높은 점수를 받을 수 있음

 

 

8. 성능 평가 지표

 -정확도(Accuracy)

  • 정확도는 전체 예측 데이터 건수 중(실제 데이터와 비교하여) 예측 결과가 동일한 데이터 건수로 계산
  • scikit-learn에서는 accuracy_score 함수 제공
from sklearn.datasets import make_classification
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score

# n_samples: 샘플 개수
# n_features: feature 데이터 개수
# n_informative: 의미있는 변수 개수
# n_redundant: 노이즈
# n_cluster_per_class: 클래스 당 군집 개수
X, y = make_classification(n_samples = 1000, n_features = 2, n_informative = 2, n_redundant = 0, n_clusters_per_class = 1)

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.3)

model = LogisticRegression()
model.fit(X_train, y_train)

print("훈련 데이터 점수: {}".format(model.score(X_train, y_train)))
print("평가 데이터 점수: {}".format(model.score(X_test, y_test)))

predict = model.predict(X_test)
print('정확도: {}'.format(accuracy_score(y_test, predict)))

# 출력 결과
훈련 데이터 점수: 0.9114285714285715
평가 데이터 점수: 0.9266666666666666
정확도: 0.9266666666666666

    → 정확도는 항상 평가 데이터 점수와 같게 나옴

 

 - 오차 행렬(Confusion Matrix)

  • True Negative(TN): 예측값을 Negative로 예측, 실제 값도 Negative
  • False Positive(FP): 예측값을 Positive로 예측, 실제 값은 Negative
  • False Negative(FN): 예측값을 Negative로 예측, 실제 값은 Positive
  • True Positive(TP): 예측값을 Positive로 예측, 실제 값도 Positive
from sklearn.metrics import confusion_matrix

# 실제값에 y_test를, 예측값에 X_test를 model에 넣어 예측한 predict를 넣고 오차 행렬 출력
confmat = confusion_matrix(y_true = y_test, y_pred = predict)
print(confmat)

# 출력 결과
[[137  11]
 [ 11 141]]
# 오차 행렬 시각화
fig, ax = plt.subplots(figsize = (2.5, 2.5))

# matshow()로 히트맵 그리기
# cmap(color map)은 Blue로 설정, alpha(투명도)는 0.3
ax.matshow(confmat, cmap = plt.cm.Blues, alpha = 0.3)

# 오차 행렬(confmap)의 행(shape[0]), 열(shape[1]) 개수만큼씩 반복하여
# 행렬의 각 칸마다 text 삽입(1행 1열에 오차 행렬의 1행 1열의 값을 문자로 삽입...)
# 수직(vertical alignment), 수평(horizontal alignment) 정렬은 모두 center로 설정
for i in range(confmat.shape[0]):
    for j in range(confmat.shape[1]):
        ax.text(x = j , y = i, s = confmat[i, j], va = 'center', ha = 'center')

# X축 label은 Predicted label(예측한 값이 무엇인지 나타냄)
# Y축 label은 True label(실제 값이 무엇인지 나타냄)
# tight_layout()을 사용하여 figure와 subplot간 여백을 기본값으로 설정
plt.xlabel('Predicted label')
plt.ylabel('True label')
plt.tight_layout()
plt.show()

    → 1사분면: 1로 예측, 실제 0 (FP)

    → 2사분면: 0로 예측, 실제 0 (TN)

    → 3사분면: 0로 예측, 실제 1 (FN)

    → 4사분면: 1로 예측, 실제 1 (TP)

 

 -정밀도와 재현율

  • 정밀도 = TP / (FP + TP), Negative인 것을 Positive로 잘못 판단할 때 큰 문제가 있는 경우 중요
  • 재현율 = TP / (FN + TP), Positive인 것을 Negative로 잘못 판단할 때 큰 문제가 있는 경우 중요
  • 정확도 = (TN + TP) / (TN + FP + FN + TP)
  • 오류율 = (FN + FP) / (TN + FP + FN + TP)
from sklearn.metrics import precision_score, recall_score

# 정밀도
precision = precision_score(y_test, predict)
# 재현율
recall = recall_score(y_test, predict)

print("정밀도: {}".format(precision))
print("재현율: {}".format(recall))

# 출력 결과
정밀도: 0.9276315789473685
재현율: 0.9276315789473685

 

 - F1 Score

  • 정밀도와 재현율을 결합한 지표
  • 정밀도와 재현율이 어느 한쪽으로 치우치지 않을 때 높은 값(재현율과 정밀도는 trade off 관계이며, 둘 다 어느정도 높은 값을 가지도록 하고 싶을 때, F1 Score 사용)

$$ F1=2 \times \frac { precision \times recall} { precision + recall } $$

from sklearn.metrics import f1_score

# f1 score
f1 = f1_score(y_test, predict)
print("F1 score: {}".format(f1))

# 출력 결과
F1 score: 0.9276315789473685

 

 - ROC 곡선과 AUC

  • ROC 곡선은 FPR(False Positive Rate)이 변할 때, TPR(True Positive Rate)이 어떻게 변하는지 나타내는 곡선
    • TPR: TP / (FN + TP), 재현율, 민감도(Sensitivity), 실제값이 양성인 것이 정확히 예측되어야 하는 수준
    • TNR: TN / (FP + TN), 특이도, 실제값이 음성인 것이 정확히 예측되어야 하는 수준
    • FPR: FP / (FP + TN), 1 - TNR

      → FPR을 0부터 1까지 변화시키며 TPR의 변화를 그린 곡선

      → 임계값(threshold)의 활용: 분류 결정 임계값은 양성 예측값을 결정하는 확률의 기준

      → 임계값을 1로 지정하면 양성 예측 기준이 1이 되어, 양성일 확률이 1보다 커야 양성으로 예측하는데, 1보다 클 수 없으므로 무조건 음성으로 예측함, 이때, FPR 공식에서 분자 FP는 0이 되므로 FPR은 0이 됨

      → 반대로, 임계값을 0으로 지정하면, 무조건 양성으로 예측하여 TN이 0이 되고 FPR = FP / FP = 1이 됨

      → 위의 방법으로 FPR을 0부터 1까지 변화시키며 TPR 값의 변화를 관찰

from sklearn.metrics import roc_curve

# model에 X_test값을 넣었을 때, 각 값에 대해 양성으로 예측할 확률
pred_proba_class1 = model.predict_proba(X_test)[:, 1]
# roc_curve의 인자는 실제값(y_test), 양성 예측확률(predict_proba의 두번째 열)
fprs, tprs, thresholds = roc_curve(y_test, pred_proba_class1)

# FPR과 TPR 간의 그래프(ROC 곡선)
plt.plot(fprs, tprs, label = 'ROC')
# (0, 0)에서 (1, 1)을 잇는 직선으로, 완전 랜덤한 추측이 이루어졌을 때(가장 안좋은 상황일 때)를 나타냄
plt.plot([0, 1], [0, 1], '--k', label = 'Random')
# xlim()이 0부터 1이므로, start와 end는 각각 0, 1
start, end = plt.xlim()
# x축값은 0, 1에서부터 0.1씩 커지며 소수점 두번째까지 반올림
plt.xticks(np.round(np.arange(start, end, 0.1), 2))
plt.xlim(0, 1)
plt.ylim(0, 1)
plt.xlabel('FPR(1-TNR)')
plt.ylabel('TPR(Recall)')
plt.legend()

     → 각 임계값에 대해 그 임계값 이상의 양성예측 확률을 가진 것만 양성으로 예측했을 때의 오차행렬을 계산

     → 그 오차행렬로부터 FPR(FP / (FP + TN)), TPR(TP / (FN + TP))을 계산

# model.predict_proba(X_test)의 출력값
# 0열은 음성일 확률, 1열은 양성일 확률
# 1열의 양성일 확률과 임계값을 비교하여 임계값 이상의 것만 양성으로 판단
# 이렇게 판단한 값과 실제값(y_test)를 비교하여 오차행렬을 만들면 그에 따른 FPR, TPR 계산 가능
array([[1.01487800e-02, 9.89851220e-01],
       [4.37551716e-02, 9.56244828e-01],
       [9.97233002e-01, 2.76699767e-03],
       [5.02728650e-03, 9.94972714e-01],
       [1.39106181e-02, 9.86089382e-01],
       [9.85028531e-01, 1.49714687e-02],
       [2.63580374e-02, 9.73641963e-01],
       [9.94944361e-01, 5.05563867e-03],
       [9.72061703e-03, 9.90279383e-01],
       [2.28855348e-03, 9.97711447e-01],
       [9.99983728e-01, 1.62719028e-05],
       [9.92155567e-01, 7.84443295e-03],
       [2.27501886e-02, 9.77249811e-01],
       [9.98637794e-01, 1.36220602e-03],
       [2.54748755e-02, 9.74525124e-01],
       [9.93339766e-01, 6.66023394e-03],
       [3.90060776e-02, 9.60993922e-01],
       [7.02834321e-01, 2.97165679e-01],
       [9.99899997e-01, 1.00002794e-04],
       [3.66162097e-03, 9.96338379e-01],
       [9.94623675e-01, 5.37632451e-03],
       [2.93344676e-03, 9.97066553e-01],
       [2.68465302e-02, 9.73153470e-01],
       [1.49182896e-05, 9.99985082e-01],
       [3.45351748e-02, 9.65464825e-01],
...
       [3.20673218e-02, 9.67932678e-01],
       [9.94737158e-01, 5.26284187e-03],
       [7.22599615e-01, 2.77400385e-01],
       [3.29609357e-03, 9.96703906e-01],
       [1.18750772e-01, 8.81249228e-01]])

     → 완전 랜덤이라면: 임계값이 몇이든 반은 맞고 반은 틀림

     → 잘 예측했다면: 양성으로 예측 확률이 0.5이상인 것에 대해 전부 양성이고 실제도 양성이라면, 임계값이 0.5가 될 때까지 양성을 잘 맞춘 비율인 TPR이 급격히 증가할 것이고, 그 이후에는 0.5미만인 것도 양성으로 예측하여 실제 음성인 것을 양성으로 예측한 비율인 FPR이 증가하기만 하여 아래와 같은 그래프가 나옴

    → 점선에 가까울수록 성능이 안좋음

  • AUC(Area Under Curve): ROC 곡선 밑의 면적(1에 가까울수록 좋은 값이며, 최소 0.5는 랜덤한 데이터라고 보면 됨)
from sklearn.metrics import roc_auc_score

# AUC 점수 간단하게 계산하기
roc_auc = roc_auc_score(y_test, predict)
print("ROC_AUC_Score: {}".format(roc_auc))

# 출력 결과
ROC_AUC_Score: 0.926653627311522

+ Recent posts