데이터 분석 Data Analytics/프로그래머스 데이터분석 데브코스 2기

[TIL] 데이터분석 데브코스 50일차 (2) - 검증/교차검증/성능평가/성능 평가 metric

상급닌자연습생 2024. 4. 27. 00:06

 

검증(Validation)

: 모델의 학습이 잘 진행되었는지(=일반화 능력이 좋은지)를 판단하는 평가 과정 ► 학습의 종료 시점을 판가름할 수 있음

 

📌 일반화 능력 : 내가 학습한 데이터가 아닌 새로운 다른 데이터로도 모델이 잘 작동하는 능력

 

 

 

 

 

교차 검증 (Cross Validation; CV)

: 다음의 문제를 회피(혹은 감수)하면서도 검증의 원래 의미를 살리는 평가 방법으로,

전체 데이터를 여러 개의 하위 데이터로 나누고 이 하위 데이터 세트들의 조합은 서로 다른 방법으로 훈련&검증에 사용해서 모델의 일반화 능력을 충분히 측정한다.

► 각 조합의 결과 개수 = 하위 데이터 셋들의 조합 수

※ 머신러닝 모델 학습 과정에서 발생할 수 있는 문제

- 너무 쉬운 데이터로의 편향
- 전체적인 데이터 양의 부족

 

장점 :

  • 일반화 능력 추정
  • 데이터 활용 최대화
  • 과정합 방지

단점 : 

  • 시간 복잡도가 큼

교차 검증

 

 

 

 

 

 

 

교차 검증 방법

학습 데이터와 검증 데이터를 구성하는 방법에 따라 다르다.

 

1. K-Fold CV

: 전체 데이터 셋을 총 K개의 덩어리(=fold)로 나누고, 각 덩어리를 순차적으로 검증 데이터로 사용하는 방법

► 모든 데이터가 학습 & 평가로 사용됨

 

프로세스

  1. 데이터 셋을 K개의 덩어리(fold)로 나눔 → K개의 조합이 생성됨
  2. 하나의 조합 당 1개 fold만 평가 데이터, 나머지 K-1개 fold는 훈련 데이터로 사용
  3. 총 K번의 학습 & 평가 과정이 반복됨

[출처] https://www.mltut.com/k-fold-cross-validation-in-machine-learning-how-does-k-fold-work/

 

 

 

2. Stratified Cross-Validation(계층적 교차 검증)

: K-Fold CV + 각 폴드에서 클래스의 비율을 원본 데이터셋의 클래스 비율과 유사하게 유지하면서 검증하는 방법

► K-Fold CV의 장점을 갖고 가면서 클래스 사이 불균형이 있는 경우의 편향까지 고려

 

프로세스

  1. 클래스별로 데이터를 분할
  2. 각 클래스를 K개의 fold로 나눔
  3. 각 클래스에 존재하는 K개의 fold를 하나씩 조합 → K개의 조합이 생성됨
  4. 하나의 조합 당 1개 fold만 평가 데이터, 나머지 K-1개 fold는 훈련 데이터로 사용
  5. 총 K번의 학습 & 평가 과정이 반복됨

 

[출처] https://stats.stackexchange.com/questions/49540/understanding-stratified-cross-validation

각 클래스에 존재하는 K개의 fold를 하나씩 조합

 

 

 

 

3. LOOCV (Leave-One-Out Cross Validation)

한번에 하나의 데이터 포인트만 평가 데이터로, 나머지는 학습 데이터로 사용하는 방법

► 매우 정확하지만, 데이터 크기가 크다면 많은 시간 소요

 

프로세스

  1. 데이터 셋을 전체 데이터 수(=K개)만큼의 덩어리(fold)로 나눔 → K(=전체 데이터 수)개의 조합이 생성됨
  2. 하나의 조합 당 1개 fold만 평가 데이터, 나머지 K-1개(=전체 데이터 수 - 1) fold는 훈련 데이터로 사용
  3. 총 K번의 학습 & 평가 과정이 반복됨

 

 

 

 

 

 

 

선형 분류 교차 검증 실습

1단계. 데이터 생성

import numpy as np

seed = 1234
np.random.seed(seed)

# 데이터 생성
num_sample_per_class = 250

X_class1 = np.random.normal(2, 2, (num_sample_per_class, 2))
X_class2 = np.random.normal(-2, 2, (num_sample_per_class, 2))


X_ = np.vstack([X_class1, X_class2]) # feature 두 개를 묶어주고 (250, 2) + (250, 2) -> (500, 2)
X = np.hstack([np.ones((num_sample_per_class * 2, 1)), X_]) # 1개의 bias를 추가 (500, 2) -> (500, 3)
y = np.array([1] * num_sample_per_class + [0] * num_sample_per_class)  # 클래스 레이블 생성

 

 

 

2단계. 클래스별 데이터 시각화

import matplotlib.pyplot as plt

# 클래스별 데이터 시각화
plt.figure(figsize=(8, 6))
plt.scatter(X_class1[:, 0], X_class1[:, 1], alpha=0.5, label='Class 1')
plt.scatter(X_class2[:, 0], X_class2[:, 1], alpha=0.5, label='Class 2')

# 그래프 설정
plt.title('2D dummy data for linear classification')
plt.xlabel('Feature 1')
plt.ylabel('Feature 2')
plt.legend()

# 그래프 출력
plt.show()

 

 

 

 

 

3단계. Logistic Regression 모델 생성 및 학습

from sklearn.linear_model import LogisticRegression

logistic_reg = LogisticRegression()
logistic_reg.fit(X, y)

 

 

 

 

 

4단계. 선형 분류 결과 확인

plt.figure(figsize=(8, 6))
plt.scatter(X_class1[:, 0], X_class1[:, 1], alpha=0.5, label='Class 1')
plt.scatter(X_class2[:, 0], X_class2[:, 1], alpha=0.5, label='Class 2')

w0 = logistic_reg.intercept_[0]
w1, w2 = logistic_reg.coef_[0][1:]

feauter1_value = np.array([X[:, 1].min(), X[:, 1].max()])
feature2_value = -(w0 + w1 * feauter1_value) / w2

plt.plot(feauter1_value, feature2_value, color='red', linestyle='--', label='logistic regression')

plt.title('2D dummy data for linear classification with classification line ')
plt.xlabel('Feature 1')
plt.ylabel('Feature 2')
plt.margins(0.2)
plt.legend()

plt.show()

 

 

 

 

 

5단계. 성능 평가 - (0) Random Split으로 분할 후 정확도를 구하는 일반적인 방법

`train_test_split()` : 학습 및 평가 데이터 분할 함수

  • `arrays` : 분할시킬 데이터셋
  • `test_size`: 테스트 데이터셋의 비율 or 갯수 (디폴트값 = `0.25`)
  • `train_size` : 학습 데이터셋의 비율 or 갯수 (디폴트값 = `test_size`의 나머지)
  • `random_state` : 데이터 분할시 이루어지는 셔플을 위한 시드 값 설정(매번 동일한 훈련 및 평가 데이터 생성)
  • 리턴값
    • `X_train`, `X_test`, `Y_train`, `Y_test` : `arrays`에 데이터와 레이블(정답)을 둘 다 넣었을 경우의 반환값(데이터와 레이블의 순서쌍 유지됨)
    • `X_train`, `X_test` : `arrays`에 레이블 없이 데이터만 넣었을 경우의 반환값
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score


X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=seed)
logistic_reg.fit(X_train, y_train)
y_pred = logistic_reg.predict(X_test)
accuracy_random_split = accuracy_score(y_test, y_pred)
print(f'Random Split 결과 정확도 : {accuracy_random_split*100:.2f} %')

 

 

 

 

 

5단계. 성능 평가 - (1) K-Fold CV

`cross_val_score()` : 교차 검증을 위한 함수

  • `estimator` : 사용한 분류(Classifier) 혹은 회귀(Regressor) 알고리즘
  • `X` : feature 데이터 셋
  • `Y` : 레이블(정답) 데이터 셋
  • `scoring` : 예측 성능 평가 지표
  • `cv` : 교차 검증 덩어리(fold) 갯수
  • 리턴값 : `scoring` 파라미터로 지정된 성능 평가값을 arrray 형태로 반환
from sklearn.model_selection import cross_val_score

k_fold_score = np.mean(cross_val_score(logistic_reg, X, y, cv=5))
print(f'K-Fold CV 결과 정확도 : {k_fold_score*100:.2f} %')

 

►사실 일반적인 방법과 큰 성능 차이는 보이지 않는다.

즉, 교차 검증으로 성능이 좋아지는 것이 아니라, 성능을 평가하는 과정 자체가 좋다는 것을 알아두자!

 

 

 

5단계. 성능 평가 - (2) Stratified CV

계층적 교자 검증을 사용하기 위해서는 `from sklearn.model_selection import StratifiedKFold`로  필요한 라이브러리를 불러온 후,

`cross_val_score()` 함수 내 `cv=` 파라미터만 `StratifiedKFold(폴드 갯수)`로 변경해주면 된다.

from sklearn.model_selection import cross_val_score, StratifiedKFold

stratified_cv_score = np.mean(cross_val_score(logistic_reg, X, y, cv=StratifiedKFold(5)))
print(f'Stratified CV 결과 정확도 : {stratified_cv_score*100:.2f} %')

 

 

 

5단계. 성능 평가 - (3) LOOCV

LOOCV를 사용하기 위해서는 `from sklearn.model_selection import LeaveOneOut`로  필요한 라이브러리를 불러온 후,

`cross_val_score()` 함수 내 `cv=` 파라미터만 `LeaveOneOut()`로 변경해주면 된다.

from sklearn.model_selection import cross_val_score, LeaveOneOut

loocv_score = np.mean(cross_val_score(logistic_reg, X, y, cv=LeaveOneOut()))
print(f'LOOCV 결과 정확도 : {loocv_score*100:.2f} %')

 

 

 

[정리]

클래스 내 불균형이 심해보이면 Stratified CV를 사용, 그렇지 않으면 K-Fold CV를 사용하는 것이 일반적이다.
교차검증을 하면서 다른 모델들과 비교하고 가장 좋은 성능을 보이는 모델을 찾아낸다.
이후, 해당 모델의 구조를 활용해서 실제 학습을 진행한다.

 

 

 

 

 

 

 

 

 

 


성능 평가

: 머신러닝 모델의 성능을 객관적으로 측정하고 비교하는 과정에서 사용됨

 

 

 

Metric (메트릭)

: 성능 평가에 사용되는 지표

 

 

 

Metric 종류

지도 학습

  • 분류 문제
    • 정확도 (Accuracy)
    • 혼동 행렬 (Confusion Matrix)
    • 정밀도 (Precision)
    • 재현율 (Recall)
    • F1 score
  • 회귀 문제
    • 평균 제곱 오차 (Mean Squared Error; MSE)

 

비지도 학습

  • 군집화
    • SSE(Sum of Squared Error)
    • 실루엣 계수 (Silhouette Coefficient)
  • 이상치 탐지
    • 분류 문제 metric 활용

 

 

 

 

 


분류 문제 Metric

Confusion Matrix(혼동 행렬)

: 실제 정답(True Label)과 예측값(Predicted Label)을 비교해서 모델의 성능을 시각적으로 표현한 행렬

 

📌 구성 요소 `[맞았는지(T)틀렸는지(F) | 예측(P/N)]` : 

  • 진짜 양성 (True Positive, TP) : 양성으로 예측했는데 정답도 양성일 경우
  • 거짓 양성 (False Positive, FP) : 양성으로 예측했는데 정답은 음성일 경우
  • 진짜 음성 (True Negative, TN) : 음성으로 예측했는데 정답도 음성일 경우
  • 거짓 음성 (False Negative, FN) : 음성으로 예측했는데 정답은 양성일 경우

 

► 혼동 행렬로 알 수 있는 다른 metric : 

 

 

 

 

ROC(Receiver Operating Characteristic) curve

: 양성과 음성을 나누는 임계값의 변화에 따른 성능(←혼동 행렬로부터 알 수 있음)시각화한 그래프

(임계값에 따라서 Confusion Matrix가 달라지며, 이에 따라 Accuracy / Precision / Recall / F1 score도 바뀐다.0

 

► 머신러닝 모델이 양성을 최대한 많이 & 잘 찾아내면서 noise(잡음)에 의한 거짓 탐지를 최소화하는지를 바탕으로 적절한 임계치를 찾는 도구

📌 threshold(임계값) : 음성(N)과 양성(P)을 나누어주는 경곗값

좋은 분류기 : 왼쪽 상단에 포인트가 존재하게 하는 임계치를 선택하는 것이 좋다 (= FPR은 작아야하고, TPR은 커야함)
나쁜 분류기 : y=x 그래프에 수렴

 

  • X축(가로) : 거짓 양성률(False Positive Rate; FPR)
    ► 전체 음성 중에서 실제로는 음성인데 양성이라고 잘못 예측한 비율

  • Y축(세로) : 진짜 양성률(True Positive Rate; TPR)
    ► 실제 양성중에 예측을 양성이라고 찾은 비율 = Recall(재현율)

 

가운데가 가장 좋은 그래프

 

 

 

 

 

AUC(Area Under the Curve) 점수

: 분류기의 전반적인 성능(=음성과 양성을 잘 구분할 수 있는가?)을 알 수 있는 척도로, ROC curve의 아랫부분 면적

 

  • 최댓값 : 1
  • 최솟값 : 0.5

 

왼쪽이 가장 좋은 분류기 [출처] http://www.navan.name/roc/

 

 

 

 

 

 

 

선형 분류 성능 평가 Metric 실습

※ 1단계(데이터 생성) ~ 4단계(모델 학습 결과)까지는 위의 '선형 분류 교차 검증 실습' 과정과 동일하다.

 

 

5단계. 성능 평가 (1) - Confusino Matrix

from sklearn.metrics import confusion_matrix

y_pred = logistic_reg.predict(X_test)
cm = confusion_matrix(y_test, y_pred)

# 혼동 행렬 시각화
plt.figure(figsize=(6, 6))

plt.imshow(cm, interpolation='nearest', cmap=plt.cm.Blues)
plt.colorbar()
tick_marks = np.arange(2)
plt.xticks(tick_marks, ['Predicted Negative', 'Predicted Positive'])
plt.yticks(tick_marks, ['Actual Negative', 'Actual Positive'])
plt.xlabel('Predicted Label')
plt.ylabel('True Label')
plt.title('Confusion Matrix with Labels')

labels = ['TN', 'FP', 'FN', 'TP']
label_values = [cm[0, 0], cm[0, 1], cm[1, 0], cm[1, 1]]
label_colors = ['white', 'black', 'black', 'white']
indices = [(0, 0), (0, 1), (1, 0), (1, 1)]

for label, value, color, (i, j) in zip(labels, label_values, label_colors, indices):
    plt.text(j, i, f'{label}\n{value}', ha='center', va='center', color=color)

plt.show()

 

 

5단계. 성능 평가 (2) - ROC curve, AUC

`predict_proba` : 예측 확률을 구할 수 있음

from sklearn.metrics import roc_curve

# 각 데이터가 양성일 확률, 양성만 취할 예정이므로 1 위치의 값만 가져옴
y_pred_proba = logistic_reg.predict_proba(X_test)[:, 1]

fpr, tpr, thresholds = roc_curve(y_test, y_pred_proba)
# ROC 커브 시각화
plt.plot(fpr, tpr, color='orange', label=f'ROC curve')
plt.plot([0, 1], [0, 1], color='navy', linestyle='--') # y=x 직선
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('Receiver Operating Characteristic (ROC) Curve')
plt.legend(loc="lower right")
plt.show()

 

 

최적의 임계값(threshold) 위치를 찾아보자.

`np.argmin()` : 괄호 안의 값(f(x))을 최솟값으로 만들기 위한 값(x)을 구하는 함수

# 최적 threshold 위치 찾기
# tpr은 크고 fpr은 작아야하므로 아래와 같은 코드로 위치를 선정
closest_leftupper_conner = np.argmin(np.abs(tpr - 1) + fpr)
optimal_threshold = thresholds[closest_leftupper_conner]
print(f'가장 좌측 상단에 위치한 포인트의 threshold 값 : {optimal_threshold}')

 

 

# Optimal Point를 포함하는 ROC 커브 시각화
plt.plot(fpr, tpr, color='orange', label=f'ROC curve')
plt.plot([0, 1], [0, 1], color='navy', linestyle='--') # y=x 직선
plt.scatter(fpr[closest_leftupper_conner],
            tpr[closest_leftupper_conner],
            color='red', label=f'Optimal Threshold = {optimal_threshold:.2f}')
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('Receiver Operating Characteristic (ROC) Curve')
plt.legend(loc="lower right")
plt.show()

 

from sklearn.metrics import auc

roc_auc = auc(fpr, tpr)
print(f'ROC 커브로부터 구한 AUC : {roc_auc}')

 

 

 

 

 

 

 

 

 


회귀 문제 Metric

R2 score (결정계수)

: 모델이 데이터의 변동성을 얼마나 잘 설명하는지를 나타내는 통계적 지표

데이터 포인트와 회귀선 얼만큼 퍼져있는지(=변동성)를 평가

► 모델이 설명해야 하는 전체 변동량 중에 얼만큼 현재 모델이 설명하고 있는지

 

 

※ 데이터가 복잡해지면 설명력은 증가하지만 과적합의 위험에 빠질 수 있다.

즉, R2 score이 클수록 설명력은 좋지만 과적합 상태로 빠지는 것을 주의해야 한다.

 

 

 

 

 

 

 

 

회귀 문제 성능 평가 Metric 실습

1단계. 데이터 생성

# 데이터 생성 변수
w0 = 2.3
w1 = 1
num_data = 100
noise = np.random.normal(0, 3, num_data)

# 데이터 생성
X = np.linspace(0, 5, num_data)
y = w0 + w1 * (X ** 2) + noise

# 시각화
plt.figure(figsize=(8, 6))
plt.scatter(X, y)
plt.xlabel('X')
plt.ylabel('y')
plt.margins(0.2)
plt.show()

 

 

 

2단계. 선형 회귀 모델 생성 및 학습

from sklearn.linear_model import LinearRegression

X = X.reshape(-1, 1) # 학습을 위한 차원 변환 진행

linear_reg = LinearRegression()
linear_reg.fit(X, y)

 

 

 

 

 

3단계. 회귀 결과 확인

# 회귀선 시각화
y_pred = linear_reg.predict(X)

plt.figure(figsize=(8, 6))

plt.scatter(X, y, alpha=0.5, label='Data')
plt.plot(X, y_pred, color='red', label=f'Linear Regression')

plt.title('Regression Line On Data')
plt.xlabel('X')
plt.ylabel('y')
plt.legend()
plt.show()

 

 

 

 

 

4단계. 성능평가 - R2 score 확인

from sklearn.metrics import r2_score
r2 = r2_score(y, y_pred)
print(f'R² 값 : {r2:.4f}')

# 시각화
plt.figure(figsize=(8, 6))
plt.scatter(X, y, alpha=0.5, label='Data')
plt.plot(X, y_pred, color='red', label=f'Linear Regression (R² = {r2:.2f})')

y_mean = np.mean(y)
plt.axhline(y=y_mean, color='green', linestyle='--', label=f'Mean (y = {y_mean:.2f})')

plt.title('Regression Line with R² and Data Mean')
plt.xlabel('X')
plt.ylabel('y')
plt.legend()
plt.show()

초록색 가로선은 평균선