모델 평가 지표
| topics | 100-데이터분석 & AI 101 머신러닝 |
| types | 이론 학습 레퍼런스 |
| tags |
모델 평가 지표
왜 필요한가
모델을 학습시켰다고 끝이 아니다. 그 모델이 실제로 얼마나 잘 동작하는지 객관적으로 평가해야 한다.
평가 지표가 없다면?
- 모델이 좋은지 나쁜지 판단할 수 없다
- 여러 모델 중 어떤 게 나은지 비교할 수 없다
- 어떤 부분을 개선해야 할지 알 수 없다
핵심: 문제 유형(분류/회귀)과 비즈니스 목표에 맞는 평가 지표를 선택해야 한다.
분류 평가 지표
Precision-Recall Curve
**Precision(정밀도)**과 **Recall(재현율)**의 트레이드오프를 시각화한 그래프다.
언제 사용할까?
- 클래스 불균형이 심한 경우 (예: 사기 거래 탐지, 희귀 질병 진단)
- Positive 클래스가 중요한 경우
그래프 해석
Precision
^
| ____
| / \___
| / \____
| / \___
|/ \___
+-------------------------> Recall
- 오른쪽 위에 가까울수록 좋은 모델
- **곡선 아래 면적(AUC-PR)**이 클수록 좋음
왜 Accuracy가 아니라 PR Curve를 쓸까?
클래스 불균형이 심하면 Accuracy는 의미가 없다.
예시: 사기 거래 탐지
- 정상: 9,900건 (99%)
- 사기: 100건 (1%)
모든 거래를 "정상"으로 예측하면?
→ Accuracy = 99% (좋아 보임)
→ 하지만 사기를 하나도 못 잡음!
이럴 때 Precision과 Recall이 중요하다.
ROC Curve
ROC (Receiver Operating Characteristic) 곡선은 **TPR(True Positive Rate)**과 **FPR(False Positive Rate)**의 관계를 나타낸다.
정의
- TPR (True Positive Rate) = Recall = TP / (TP + FN)
- FPR (False Positive Rate) = FP / (FP + TN)
언제 사용할까?
- 클래스가 비교적 균형잡힌 경우
- Positive와 Negative 둘 다 중요한 경우
그래프 해석
TPR
^
| /----
| /
| /
| /
|/___________> FPR
- 왼쪽 위 모서리에 가까울수록 좋은 모델
- **대각선(y=x)**은 랜덤 추측 수준
- AUC-ROC (곡선 아래 면적): 0.5~1.0 사이 값, 1.0에 가까울수록 좋음
PR Curve vs ROC Curve
| 구분 | Precision-Recall Curve | ROC Curve |
|---|---|---|
| 적합한 상황 | 클래스 불균형이 심한 경우 | 클래스가 균형잡힌 경우 |
| 중점 | Positive 클래스 성능 | 전체 클래스 균형 |
| 민감도 | Positive 클래스 변화에 민감 | 전체적인 성능 파악 |
회귀 평가 지표
Pearson Correlation (pearsonr)
피어슨 상관계수는 두 변수 간의 선형 관계를 측정한다.
정의
$r = \frac{\sum(x_i - \bar{x})(y_i - \bar{y})}{\sqrt{\sum(x_i - \bar{x})^2 \sum(y_i - \bar{y})^2}}$
값의 범위: -1 ~ 1
- r = 1: 완벽한 양의 상관관계
- r = 0: 상관관계 없음
- r = -1: 완벽한 음의 상관관계
언제 사용할까?
패턴이 얼마나 유사한가를 측정할 때 사용한다.
예측값의 절대 크기는 틀려도, **경향성(트렌드)**이 맞으면 높은 점수를 받는다.
예시
from scipy.stats import pearsonr
# <span id="실제값"></span>실제값
y_true = [1, 2, 3, 4, 5]
# <span id="예측값-1-크기도-맞고-패턴도-맞음"></span>예측값 1: 크기도 맞고 패턴도 맞음
y_pred1 = [1.1, 2.0, 3.1, 3.9, 5.0]
r1, _ = pearsonr(y_true, y_pred1)
# <span id="r1-0999-거의-완벽"></span>r1 ≈ 0.999 (거의 완벽)
# <span id="예측값-2-크기는-틀렸지만-패턴은-맞음"></span>예측값 2: 크기는 틀렸지만 패턴은 맞음
y_pred2 = [10, 20, 30, 40, 50]
r2, _ = pearsonr(y_true, y_pred2)
# <span id="r2-10-완벽한-상관관계"></span>r2 = 1.0 (완벽한 상관관계)
# <span id="예측값-3-패턴이-반대"></span>예측값 3: 패턴이 반대
y_pred3 = [5, 4, 3, 2, 1]
r3, _ = pearsonr(y_true, y_pred3)
# <span id="r3--10-완벽한-음의-상관관계"></span>r3 = -1.0 (완벽한 음의 상관관계)
RMSE vs Pearson
| 지표 | 무엇을 측정하나 | 언제 사용하나 |
|---|---|---|
| RMSE | 예측값과 실제값의 절대적인 차이 | 정확한 값이 중요할 때 (예: 가격 예측) |
| Pearson | 예측값과 실제값의 경향성/패턴 유사도 | 트렌드가 중요할 때 (예: 주식 방향 예측) |
앙상블 평가
앙상블이란?
여러 모델의 예측을 결합하여 최종 예측을 만드는 기법이다.
왜 앙상블을 쓸까?
- 단일 모델보다 안정적이다
- 과적합을 줄인다
- 일반적으로 성능이 향상된다
슬라이스별 앙상블
데이터를 슬라이스(구간)별로 나누어 앙상블하는 방법이다.
예시: Cutoff별 앙상블
# <span id="full-데이터로-학습한-모델"></span>FULL 데이터로 학습한 모델
model_full = train_model(X_full, y_full)
pred_full = model_full.predict(X_test)
# <span id="최근-50개-데이터로-학습한-모델"></span>최근 50개 데이터로 학습한 모델
model_50 = train_model(X[-50:], y[-50:])
pred_50 = model_50.predict(X_test)
# <span id="앙상블"></span>앙상블
pred_ensemble = (pred_full + pred_50) / 2
앙상블 방법 비교
1. 단순 평균 (Simple Average)
모든 모델의 예측을 동일한 가중치로 평균 낸다.
# <span id="3개-모델의-예측"></span>3개 모델의 예측
pred1 = model1.predict(X_test)
pred2 = model2.predict(X_test)
pred3 = model3.predict(X_test)
# <span id="단순-평균"></span>단순 평균
pred_avg = (pred1 + pred2 + pred3) / 3
장점
- 간단하고 직관적
- 빠르게 구현 가능
- 과적합 위험 적음
단점
- 성능 차이를 고려하지 않음
- 나쁜 모델도 같은 비중
2. 가중 평균 (Weighted Average)
각 모델의 성능에 따라 다른 가중치를 부여한다.
# <span id="각-모델의-검증-점수"></span>각 모델의 검증 점수
score1 = 0.85
score2 = 0.90
score3 = 0.88
# <span id="점수에-비례한-가중치"></span>점수에 비례한 가중치
total = score1 + score2 + score3
w1 = score1 / total
w2 = score2 / total
w3 = score3 / total
# <span id="가중-평균"></span>가중 평균
pred_weighted = w1 * pred1 + w2 * pred2 + w3 * pred3
장점
- 좋은 모델에 더 많은 가중치
- 일반적으로 단순 평균보다 성능 좋음
단점
- 가중치 최적화가 필요
- 검증 데이터에 과적합 위험
앙상블 평가 예시
pearsonr로 앙상블 비교
from scipy.stats import pearsonr
# <span id="각-방법별-예측"></span>각 방법별 예측
pred_simple = (pred1 + pred2 + pred3) / 3
pred_weighted = 0.3 * pred1 + 0.5 * pred2 + 0.2 * pred3
# <span id="pearson-상관계수로-평가"></span>pearson 상관계수로 평가
r_simple, _ = pearsonr(y_test, pred_simple)
r_weighted, _ = pearsonr(y_test, pred_weighted)
print(f"단순 평균 pearson: {r_simple:.4f}")
print(f"가중 평균 pearson: {r_weighted:.4f}")
# <span id="더-높은-상관계수를-가진-방법-선택"></span>더 높은 상관계수를 가진 방법 선택
if r_weighted > r_simple:
final_pred = pred_weighted
else:
final_pred = pred_simple
평가 지표 선택 가이드
분류 문제
| 상황 | 추천 지표 |
|---|---|
| 클래스 균형 | Accuracy, ROC-AUC |
| 클래스 불균형 | Precision-Recall, F1-Score |
| FP 비용이 높음 | Precision 중시 |
| FN 비용이 높음 | Recall 중시 |
회귀 문제
| 상황 | 추천 지표 |
|---|---|
| 절대값 정확도 중요 | RMSE, MAE |
| 트렌드/패턴 중요 | Pearson, Spearman |
| 이상치에 민감 | RMSE |
| 이상치에 둔감 | MAE |
코드 예시
분류 평가
from sklearn.metrics import (
precision_recall_curve,
roc_curve,
auc,
classification_report
)
import matplotlib.pyplot as plt
# <span id="precision-recall-curve"></span>Precision-Recall Curve
precision, recall, _ = precision_recall_curve(y_test, y_pred_proba)
pr_auc = auc(recall, precision)
plt.plot(recall, precision, label=f'PR AUC = {pr_auc:.3f}')
plt.xlabel('Recall')
plt.ylabel('Precision')
plt.title('Precision-Recall Curve')
plt.legend()
plt.show()
# <span id="roc-curve"></span>ROC Curve
fpr, tpr, _ = roc_curve(y_test, y_pred_proba)
roc_auc = auc(fpr, tpr)
plt.plot(fpr, tpr, label=f'ROC AUC = {roc_auc:.3f}')
plt.plot([0, 1], [0, 1], 'k--', label='Random')
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('ROC Curve')
plt.legend()
plt.show()
회귀 평가 + 앙상블
from scipy.stats import pearsonr
from sklearn.metrics import mean_squared_error
import numpy as np
# <span id="여러-모델-예측"></span>여러 모델 예측
predictions = [
model1.predict(X_test),
model2.predict(X_test),
model3.predict(X_test)
]
# <span id="단순-평균"></span>단순 평균
pred_simple = np.mean(predictions, axis=0)
# <span id="가중-평균-검증-점수-기반"></span>가중 평균 (검증 점수 기반)
weights = [0.3, 0.5, 0.2]
pred_weighted = np.average(predictions, axis=0, weights=weights)
# <span id="평가"></span>평가
for name, pred in [('Simple', pred_simple), ('Weighted', pred_weighted)]:
rmse = np.sqrt(mean_squared_error(y_test, pred))
r, _ = pearsonr(y_test, pred)
print(f"\n{name} 앙상블:")
print(f" RMSE: {rmse:.4f}")
print(f" Pearson: {r:.4f}")