콘텐츠로 이동

분포가 다른 데이터 간 모델 평가 방법

1. 문제 배경

  • 가구별 전력 소비량 분포가 이질적일 때 단순 MSE/MAE 집계는 고부하 가구에 지배당함
  • 본 프로젝트에서 Apt89(MSE=3.58) vs Apt30(MSE=0.09) — 약 87배 차이 발생
  • MAPE는 저소비 구간(≈0)에서 폭발 (Apt60: 342%, Apt75: 237%)

2. 평가 방법론

2-1. MASE (Mean Absolute Scaled Error) ★ 핵심 대안

  • naive forecast(lag-1) 대비 상대 오차로 정규화 → 스케일 무관
  • 값이 1이면 naive와 동등, <1이면 naive보다 우수
  • 시계열 예측 논문 표준 지표 (Hyndman & Koehler, 2006)
import numpy as np

def mase(y_true: np.ndarray, y_pred: np.ndarray) -> float:
    """
    MASE: Mean Absolute Scaled Error
    y_true, y_pred shape: (T,) — 단일 시계열
    분모: in-sample naive(lag-1) MAE
    """
    mae = np.mean(np.abs(y_true - y_pred))
    naive_mae = np.mean(np.abs(y_true[1:] - y_true[:-1]))
    return mae / (naive_mae + 1e-8)


def mase_per_household(
    y_true: np.ndarray,  # (N_samples, pred_len)
    y_pred: np.ndarray,
) -> float:
    """배치 단위 MASE — 전체 샘플을 이어붙여 naive MAE 계산"""
    y_flat = y_true.flatten()
    p_flat = y_pred.flatten()
    mae = np.mean(np.abs(y_flat - p_flat))
    naive_mae = np.mean(np.abs(y_flat[1:] - y_flat[:-1]))
    return float(mae / (naive_mae + 1e-8))

2-2. nMAE / nRMSE (Normalized MAE / RMSE)

  • 가구별 range 또는 mean으로 정규화
  • range 정규화: 모든 가구를 0~1 스케일로 통일
def nmae(y_true: np.ndarray, y_pred: np.ndarray, mode: str = "range") -> float:
    """
    nMAE: Normalized MAE
    mode: "range" → MAE / (max - min)
          "mean"  → MAE / mean(y_true)
    """
    mae = np.mean(np.abs(y_true - y_pred))
    if mode == "range":
        denom = y_true.max() - y_true.min()
    else:
        denom = np.mean(np.abs(y_true))
    return float(mae / (denom + 1e-8))

2-3. 순위 기반 평가 (Win Rate + 상대 개선율)

  • 분포 가정 없이 baseline 대비 가구별 승/패 집계
  • 상대 개선율 Δ%로 방향성 파악
def relative_improvement(
    baseline_mse: dict[str, float],  # {"Apt6": 0.7753, ...}
    model_mse: dict[str, float],     # {"Apt6": 0.7286, ...}
) -> dict:
    results = {}
    for apt in baseline_mse:
        if apt not in model_mse:
            continue
        delta = (model_mse[apt] - baseline_mse[apt]) / baseline_mse[apt] * 100
        results[apt] = {
            "baseline": baseline_mse[apt],
            "model": model_mse[apt],
            "delta_pct": round(delta, 2),   # 음수 = 개선
            "win": model_mse[apt] < baseline_mse[apt],
        }
    win_rate = sum(v["win"] for v in results.values()) / len(results)
    return {"per_household": results, "win_rate": win_rate}

# 사용 예 (DLinear vs GWN N=50, 5가구)
baseline = {"Apt6": 0.7753, "Apt15": 0.1545, "Apt30": 0.0875, "Apt51": 0.6751, "Apt88": 0.9111}
model    = {"Apt6": 0.7286, "Apt15": 0.1634, "Apt30": 0.0806, "Apt51": 0.6109, "Apt88": 0.9410}
# → win_rate = 3/5 = 0.6

2-4. 계층화(Stratification) 평가

  • 소비량 기준으로 그룹 분류 후 그룹 내 별도 집계
  • 이상치 가구(Apt89 등)가 다른 그룹 결과를 오염하지 않도록 격리
def stratified_eval(
    per_household_mse: dict[str, float],
    consumption_mean: dict[str, float],  # 가구별 평균 소비량(kW)
    thresholds: tuple = (0.3, 0.7),      # 저/중/고 분위 기준 (kW)
) -> dict[str, dict]:
    """
    소비량 기준 3-tier 계층 분류 후 그룹별 MSE 집계
    """
    groups = {"low": [], "mid": [], "high": []}
    for apt, mse in per_household_mse.items():
        c = consumption_mean.get(apt, 0)
        if c < thresholds[0]:
            groups["low"].append(mse)
        elif c < thresholds[1]:
            groups["mid"].append(mse)
        else:
            groups["high"].append(mse)

    return {
        g: {"mean_mse": float(np.mean(v)), "n": len(v)}
        for g, v in groups.items() if v
    }

2-5. 통계적 유의성 검정

  • 단일 평균 비교 대신 가구별 paired 오차를 검정
  • Wilcoxon signed-rank test: 정규분포 가정 없음, 소표본에 적합
  • Diebold-Mariano test: 시계열 예측 비교 표준 (예측 오차 자기상관 처리)
from scipy.stats import wilcoxon

def wilcoxon_test(
    baseline_errors: list[float],  # 가구별 MAE (baseline)
    model_errors: list[float],     # 가구별 MAE (model)
) -> dict:
    """
    H0: 두 모델의 예측 오차 분포가 동일
    p < 0.05 → 유의미한 차이
    """
    stat, p = wilcoxon(baseline_errors, model_errors, alternative="greater")
    return {"statistic": stat, "p_value": p, "significant": p < 0.05}

# 사용 예
baseline_mae = [0.6255, 0.3097, 0.2206, 0.5408, 0.6721]  # DLinear 5HH
model_mae    = [0.6359, 0.2940, 0.2233, 0.5095, 0.7027]  # GWN N=50 5HH
# N=5라 검정력 낮음 — 참고용으로만 사용

3. 본 프로젝트 적용 권고

현재 문제 권고 해결책
MSE가 고부하 가구(Apt89 등)에 지배당함 MASE 또는 nMAE 도입
MAPE가 저소비 구간에서 폭발 MASE로 대체, MAPE는 각주 참고용으로 격하
DLinear vs GWN 단순 평균 비교 가구별 Win Rate + Δ% 병기
N=5 가구, 단일 seed의 통계 불안정 Wilcoxon test로 유의성 검증 (참고용)
GWN PAPE vs DLinear PAPE 정의 불일치 정의 통일 후 PAPE 비교, 또는 MASE로 대체

우선순위: MASE 도입 → Win Rate 집계 → 계층화 분석 순으로 적용