콘텐츠로 이동
Data Prep
상세

INEEJI 시계열 예측 프로젝트

분석 기간: 2025-05 ~ 2025-06 분야: 시계열 예측, 앙상블 모델링 결과: 예측 대회 상위 성적

개요

INEEJI 주최 시계열 예측 대회 참가 프로젝트. 다양한 전처리 기법과 앙상블 모델을 적용하여 시계열 데이터의 미래 값을 예측했다.

데이터 구조

학습 데이터

파일 설명 크기
data_train.csv 학습용 시계열 데이터 158KB
data_test.csv 테스트용 데이터 41KB
metadata.xlsx 변수 설명 17KB

주요 변수

  • target: 예측 대상 시계열 값
  • timestamp: 시간 인덱스
  • features: 외생 변수들 (기상, 계절 등)

분석 방법론

1. 전처리 파이프라인

┌─────────────────────────────────────────────────────────────────┐
│                     전처리 파이프라인                            │
│                                                                 │
│  결측치 처리 → 이상치 탐지 → 피처 생성 → 정규화 → 분해           │
│                                                                 │
│  ┌────────┐   ┌────────┐   ┌────────┐   ┌────────┐   ┌────────┐│
│  │보간법  │──▶│IQR     │──▶│Lag     │──▶│MinMax  │──▶│CEEMDAN ││
│  │(선형)  │   │기반    │   │Rolling │   │Scaler  │   │분해    ││
│  └────────┘   └────────┘   └────────┘   └────────┘   └────────┘│
└─────────────────────────────────────────────────────────────────┘

2. CEEMDAN 분해

Complete Ensemble Empirical Mode Decomposition with Adaptive Noise:

from PyEMD import CEEMDAN
import numpy as np

def decompose_signal(signal: np.ndarray) -> tuple:
    """CEEMDAN으로 시계열 분해"""

    ceemdan = CEEMDAN()
    ceemdan.ceemdan(signal)

    imfs = ceemdan.get_imfs_and_residue()

    # IMF (Intrinsic Mode Functions) + Residue
    # 각 IMF는 다른 주기의 진동 성분

    return imfs

def reconstruct_from_imfs(imfs: np.ndarray, selected: list = None) -> np.ndarray:
    """선택된 IMF로 신호 재구성"""

    if selected is None:
        selected = list(range(len(imfs)))

    return np.sum(imfs[selected], axis=0)

CEEMDAN의 장점: - 비선형, 비정상 시계열에 효과적 - Mode mixing 문제 완화 - 각 IMF를 별도로 예측 가능

3. 피처 엔지니어링

import pandas as pd
import numpy as np

def create_time_features(df: pd.DataFrame, date_col: str) -> pd.DataFrame:
    """시간 기반 피처 생성"""

    df = df.copy()
    df['date'] = pd.to_datetime(df[date_col])

    # 기본 시간 피처
    df['hour'] = df['date'].dt.hour
    df['dayofweek'] = df['date'].dt.dayofweek
    df['month'] = df['date'].dt.month
    df['quarter'] = df['date'].dt.quarter
    df['dayofyear'] = df['date'].dt.dayofyear

    # 주기 인코딩 (순환 특성 보존)
    df['hour_sin'] = np.sin(2 * np.pi * df['hour'] / 24)
    df['hour_cos'] = np.cos(2 * np.pi * df['hour'] / 24)
    df['dow_sin'] = np.sin(2 * np.pi * df['dayofweek'] / 7)
    df['dow_cos'] = np.cos(2 * np.pi * df['dayofweek'] / 7)

    return df

def create_lag_features(df: pd.DataFrame, target: str, lags: list) -> pd.DataFrame:
    """지연 피처 생성"""

    df = df.copy()

    for lag in lags:
        df[f'{target}_lag_{lag}'] = df[target].shift(lag)

    # 롤링 통계
    for window in [7, 14, 30]:
        df[f'{target}_rolling_mean_{window}'] = df[target].rolling(window).mean()
        df[f'{target}_rolling_std_{window}'] = df[target].rolling(window).std()

    # 차분
    df[f'{target}_diff_1'] = df[target].diff(1)
    df[f'{target}_diff_7'] = df[target].diff(7)

    return df

모델링

1. 단일 모델 비교

모델 RMSE MAE 특징
SVR 0.142 0.098 비선형 패턴 캡처
LightGBM 0.156 0.108 빠른 학습, 피처 중요도
XGBoost 0.161 0.112 안정적 성능
LSTM 0.178 0.124 장기 의존성

2. 앙상블 전략

from sklearn.svm import SVR
from sklearn.ensemble import BaggingRegressor
import lightgbm as lgb

def create_ensemble_model():
    """Bagging SVR + LightGBM 앙상블"""

    # Bagging SVR
    base_svr = SVR(kernel='rbf', C=10, gamma='scale')
    bagging_svr = BaggingRegressor(
        estimator=base_svr,
        n_estimators=10,
        max_samples=0.8,
        random_state=42
    )

    # LightGBM
    lgbm = lgb.LGBMRegressor(
        n_estimators=500,
        learning_rate=0.05,
        num_leaves=31,
        random_state=42
    )

    return bagging_svr, lgbm

def voting_ensemble(models: list, X: np.ndarray, weights: list = None) -> np.ndarray:
    """가중 평균 앙상블"""

    if weights is None:
        weights = [1/len(models)] * len(models)

    predictions = []
    for model, weight in zip(models, weights):
        pred = model.predict(X)
        predictions.append(pred * weight)

    return np.sum(predictions, axis=0)

3. CEEMDAN + Bagging SVR

핵심 아이디어: 각 IMF를 개별 모델로 예측 후 합산

def ceemdan_bagging_svr_pipeline(train_series, test_horizon):
    """CEEMDAN 분해 + Bagging SVR 파이프라인"""

    # 1. 시계열 분해
    imfs = decompose_signal(train_series.values)

    predictions_per_imf = []

    for i, imf in enumerate(imfs):
        # 2. 각 IMF에 대해 피처 생성
        df = pd.DataFrame({'target': imf})
        df = create_lag_features(df, 'target', lags=[1, 2, 3, 7, 14])
        df = df.dropna()

        X = df.drop('target', axis=1)
        y = df['target']

        # 3. Bagging SVR 학습
        model = BaggingRegressor(
            estimator=SVR(kernel='rbf', C=10),
            n_estimators=10,
            random_state=42
        )
        model.fit(X, y)

        # 4. 예측 (재귀적)
        imf_predictions = recursive_forecast(model, X.iloc[-1:], test_horizon)
        predictions_per_imf.append(imf_predictions)

    # 5. IMF 예측 합산
    final_predictions = np.sum(predictions_per_imf, axis=0)

    return final_predictions

def recursive_forecast(model, last_features, horizon):
    """재귀적 다단계 예측"""

    predictions = []
    current_features = last_features.copy()

    for _ in range(horizon):
        pred = model.predict(current_features)[0]
        predictions.append(pred)

        # 피처 업데이트 (lag 시프트)
        current_features = update_lag_features(current_features, pred)

    return np.array(predictions)

결과

최종 성능

제출 버전 모델 Public Score Private Score
v1 SVR 단일 0.158 0.162
v2 Bagging SVR 0.148 0.151
v3 CEEMDAN + Bagging SVR 0.139 0.143
v4 (최종) Voting (SVR + LGBM) 0.132 0.138

피처 중요도 (LightGBM)

피처 중요도
────────────────────────────────────────────────
target_lag_1      ████████████████████████ 0.28
target_lag_7      ██████████████████ 0.21
rolling_mean_7    ███████████████ 0.18
hour_sin          ████████████ 0.14
target_diff_1     █████████ 0.11
dow_sin           ██████ 0.08
────────────────────────────────────────────────

학습 포인트

성공 요인

  1. CEEMDAN 분해: 복잡한 시계열을 단순한 성분으로 분리
  2. Bagging: SVR의 과적합 방지
  3. 앙상블: SVR + LGBM 조합으로 편향-분산 균형
  4. 순환 인코딩: 시간 피처의 순환 특성 보존

개선 가능점

  1. 자동 하이퍼파라미터 튜닝: Optuna 활용
  2. 딥러닝 추가: Transformer 기반 모델
  3. 확률적 예측: 점추정 → 구간추정
  4. 외생 변수 활용: 더 많은 외부 데이터

코드

전체 파이프라인

# INEEJI_Test_EomGyuHyeon_modeling.py

import pandas as pd
import numpy as np
from sklearn.svm import SVR
from sklearn.ensemble import BaggingRegressor
import lightgbm as lgb
from PyEMD import CEEMDAN

def main():
    # 1. 데이터 로드
    train = pd.read_csv('train_ready.csv')
    test = pd.read_csv('test_ready.csv')

    # 2. 피처 엔지니어링
    train = create_time_features(train, 'timestamp')
    train = create_lag_features(train, 'target', [1, 2, 3, 7, 14, 21])
    train = train.dropna()

    # 3. 학습/검증 분리
    train_size = int(len(train) * 0.8)
    X_train, X_val = train.iloc[:train_size], train.iloc[train_size:]

    # 4. 모델 학습
    features = [c for c in train.columns if c not in ['target', 'timestamp', 'date']]

    # Bagging SVR
    bagging_svr = BaggingRegressor(
        estimator=SVR(kernel='rbf', C=10, gamma='scale'),
        n_estimators=10,
        random_state=42
    )
    bagging_svr.fit(X_train[features], X_train['target'])

    # LightGBM
    lgbm = lgb.LGBMRegressor(n_estimators=500, learning_rate=0.05)
    lgbm.fit(X_train[features], X_train['target'])

    # 5. 앙상블 예측
    pred_svr = bagging_svr.predict(test[features])
    pred_lgbm = lgbm.predict(test[features])

    final_pred = 0.6 * pred_svr + 0.4 * pred_lgbm

    # 6. 제출 파일 생성
    submission = pd.DataFrame({
        'id': test['id'],
        'target': final_pred
    })
    submission.to_csv('submission.csv', index=False)

if __name__ == '__main__':
    main()

파일 구조

ineeji/
├── data_train.csv           # 학습 데이터
├── data_test.csv            # 테스트 데이터
├── metadata.xlsx            # 변수 설명
├── train_ready.csv          # 전처리된 학습 데이터
├── test_ready.csv           # 전처리된 테스트 데이터
├── INEEJI_Test_EomGyuHyeon.ipynb    # 분석 노트북
├── INEEJI_Test_EomGyuHyeon_modeling.py  # 모델링 코드
├── INEEJI_Report_EomGyuHyeon.pdf    # 보고서
├── submission*.csv          # 제출 파일들
└── 엄규현_PT_발표자료.pdf    # 발표 자료

참고

  • 원본 데이터: iCloud/03_Projects/ineeji/
  • 주요 라이브러리: PyEMD, scikit-learn, LightGBM
  • 참고 논문: "Complete Ensemble EMD with Adaptive Noise" (Torres et al., 2011)

마지막 업데이트: 2026-03-04