🔥알림🔥
① 테디노트 유튜브 - 구경하러 가기!
② LangChain 한국어 튜토리얼 바로가기 👀
③ 랭체인 노트 무료 전자책(wikidocs) 바로가기 🙌
④ RAG 비법노트 LangChain 강의오픈 바로가기 🙌
⑤ 서울대 PyTorch 딥러닝 강의 바로가기 🙌

30 분 소요

이번 포스트의 주제는 “PyCaret을 활용한 시계열 데이터 예측 모형 생성” 입니다. 이 글에서는 시계열 데이터 예측 모형을 구축하는 과정을 세심하게 탐구하게 될 것입니다. 특히, PyCaret 라이브러리를 활용하여 기계 학습 프로세스를 효과적으로 자동화하는 방법을 중점적으로 다룹니다.


먼저, 단변량(Univariate) 시계열 예측외생 변수(Exogenous Variables) 에 대한 이해로 출발합니다. 이후에는 모듈 로딩과 데이터셋을 탐색적 분석(Exploratory Data Analysis) 하는 과정을 거치게 됩니다. 👀


PyCaret의 setup() 를 사용하여 시계열 데이터 전처리 전략 을 설정하고, 베이스라인 모델을 생성하여 단일 변수를 예측📊 해볼 것입니다. 그 다음으로, 외생 변수를 활용한 단일 변수 예측에 대한 개선된 모델을 살펴보겠습니다.


또한, 이러한 모델들을 평가하고, 제한적인 외생 변수를 사용한 단일 변수 예측에 대한 간소화된 모델을 구축해보겠습니다.


외생변수(Exogenous Variables)를 활용하여 미래 값을 예측하는 과정까지 살펴보게 될 것입니다. 이 과정에서 어떻게 미래의 외생 변수 값을 얻을 수 있는지, 그리고 이를 활용하여 목표 변수에 대한 미래를 예측하는 방법에 대한 설명이 이어질 것입니다.


이 포스트를 통해 PyCaret을 활용한 시계열 예측 모델 생성의 기본적인 과정을 이해하고, AutoML을 통한 모델링 자동화에 대한 이해를 높이실 수 있을 것을 기대합니다🎉


[참고]

  • 본 튜토리얼은 PyCaret 의 영문 공식 튜토리얼 을 참고하여 작성하였습니다
  • 링크: https://pycaret.gitbook.io/docs/get-started/tutorials#tutorials-time_series-forecasting

목차 - 단변량(Univariate) 시계열 예측과 외생 변수(Exogenous Variables)

본 튜토리얼에서는 외생 변수를 사용한 모델링을 다룹니다. 튜토리얼의 진행방식은 아래의 목록 순서와 같습니다.

  1. 시계열을 생성하는 과정에 대한 실질적인 통찰력을 얻기 위해 데이터셋에 대한 EDA를 수행합니다.

  2. 벤치마킹을 위한 기본 모델 (외생 변수 없는 단변량 모델)을 구축합니다.

  3. 가능한 최고의 성능을 확인하기 위해 모든 외생 변수를 가진 단변량 모델을 구축합니다.

  4. 외생 변수가 있는 모델을 평가하고 잠재적인 문제점을 논의합니다.

  5. 위에서 확인된 문제점을 해결합니다.

  6. 최적의 모델로 미래 예측을 수행합니다.

  7. 자동 시계열 모델링 (AutoML)로 작업 흐름을 복제합니다.

✔️ 모듈 로드 / 데이터셋

import os

# 오직 중요한 로깅만 활성화하기 (선택적)
os.environ["PYCARET_CUSTOM_LOGGING_LEVEL"] = "CRITICAL"
# 설치된 라이브러리 확인
def what_is_installed():
    from pycaret import show_versions
    show_versions()

try:
    what_is_installed()
except ModuleNotFoundError:
    !pip install pycaret
    what_is_installed()

System:
    python: 3.9.17 (main, Jul  5 2023, 15:35:09)  [Clang 14.0.6 ]
executable: /Users/teddy/miniconda/envs/pycaret3/bin/python
   machine: macOS-13.4.1-arm64-arm-64bit

PyCaret required dependencies:
                 pip: 23.1.2
          setuptools: 67.8.0
             pycaret: 3.0.4
             IPython: 8.14.0
          ipywidgets: 8.0.7
                tqdm: 4.65.0
               numpy: 1.23.5
              pandas: 1.5.3
              jinja2: 3.1.2
               scipy: 1.11.1
              joblib: 1.3.1
             sklearn: 1.2.2
                pyod: 1.1.0
            imblearn: 0.11.0
   category_encoders: 2.6.1
            lightgbm: 3.3.5
               numba: 0.57.1
            requests: 2.31.0
          matplotlib: 3.7.2
          scikitplot: 0.3.7
         yellowbrick: 1.5
              plotly: 5.15.0
    plotly-resampler: Not installed
             kaleido: 0.2.1
           schemdraw: 0.15
         statsmodels: 0.14.0
              sktime: 0.20.0
               tbats: 1.1.3
            pmdarima: 2.0.3
              psutil: 5.9.5
          markupsafe: 2.1.3
             pickle5: Not installed
         cloudpickle: 2.2.1
         deprecation: 2.1.0
              xxhash: 3.2.0
           wurlitzer: 3.0.3

PyCaret optional dependencies:
                shap: 0.42.1
           interpret: 0.4.2
                umap: Not installed
    pandas_profiling: Not installed
  explainerdashboard: Not installed
             autoviz: Not installed
           fairlearn: Not installed
          deepchecks: Not installed
             xgboost: Not installed
            catboost: Not installed
              kmodes: Not installed
             mlxtend: Not installed
       statsforecast: Not installed
        tune_sklearn: Not installed
                 ray: Not installed
            hyperopt: Not installed
              optuna: Not installed
               skopt: Not installed
              mlflow: Not installed
              gradio: Not installed
             fastapi: Not installed
             uvicorn: Not installed
              m2cgen: Not installed
           evidently: Not installed
               fugue: Not installed
           streamlit: 1.24.1
             prophet: 1.1.4
# 필요한 모듈 import
import numpy as np
import pandas as pd
from pycaret.datasets import get_data
from pycaret.time_series import TSForecastingExperiment  # 시계열 데이터 예측 모듈

# 노트북을 위한 global fugure 설정
global_fig_settings = {
    # 'renderer': 'notebook',
    "renderer": "png",  # 'notebook' 으로 설정시 interactive 시각화 가능
    "width": 1000,
    "height": 600,
}

샘플 데이터셋을 로드 합니다. pycaret 내장 데이터셋을 불러오도록 하겠습니다.

# 데이터셋 로드
data = get_data("airquality")
Date Time CO(GT) PT08.S1(CO) NMHC(GT) C6H6(GT) PT08.S2(NMHC) NOx(GT) PT08.S3(NOx) NO2(GT) PT08.S4(NO2) PT08.S5(O3) T RH AH
0 2004-03-10 18:00:00 2.6 1360 150 11.9 1046 166 1056 113 1692 1268 13.6 48.9 0.7578
1 2004-03-10 19:00:00 2.0 1292 112 9.4 955 103 1174 92 1559 972 13.3 47.7 0.7255
2 2004-03-10 20:00:00 2.2 1402 88 9.0 939 131 1140 114 1555 1074 11.9 54.0 0.7502
3 2004-03-10 21:00:00 2.2 1376 80 9.2 948 172 1092 122 1584 1203 11.0 60.0 0.7867
4 2004-03-10 22:00:00 1.6 1272 51 6.5 836 131 1205 116 1490 1110 11.2 59.6 0.7888
# 데이터셋에서 Date 와 Time 을 병합하여 datetime 생성합니다
data.insert(0, "datetime", pd.to_datetime(data["Date"] + " " + data["Time"]))
data.drop(columns=["Date", "Time"], inplace=True)
data.head()
datetime CO(GT) PT08.S1(CO) NMHC(GT) C6H6(GT) PT08.S2(NMHC) NOx(GT) PT08.S3(NOx) NO2(GT) PT08.S4(NO2) PT08.S5(O3) T RH AH
0 2004-03-10 18:00:00 2.6 1360 150 11.9 1046 166 1056 113 1692 1268 13.6 48.9 0.7578
1 2004-03-10 19:00:00 2.0 1292 112 9.4 955 103 1174 92 1559 972 13.3 47.7 0.7255
2 2004-03-10 20:00:00 2.2 1402 88 9.0 939 131 1140 114 1555 1074 11.9 54.0 0.7502
3 2004-03-10 21:00:00 2.2 1376 80 9.2 948 172 1092 122 1584 1203 11.0 60.0 0.7867
4 2004-03-10 22:00:00 1.6 1272 51 6.5 836 131 1205 116 1490 1110 11.2 59.6 0.7888
# Target Y 정의
target = "CO(GT)"
  • Air Quality 데이터셋: https://archive.ics.uci.edu/ml/datasets/air+quality

이 데이터셋에는 -200으로 태그된 결측값이 있습니다. 이 값들을 제거해야합니다(그것들을 NaN 대체) 및 pycaret이 적절하게 대치를 처리하게 하겠습니다.

# -200 데이터 조회
data[data[target] == -200].head()
datetime CO(GT) PT08.S1(CO) NMHC(GT) C6H6(GT) PT08.S2(NMHC) NOx(GT) PT08.S3(NOx) NO2(GT) PT08.S4(NO2) PT08.S5(O3) T RH AH
10 2004-03-11 04:00:00 -200.0 1011 14 1.3 527 21 1818 34 1197 445 10.1 60.5 0.7465
34 2004-03-12 04:00:00 -200.0 831 10 1.1 506 21 1893 32 1134 384 6.1 65.9 0.6248
39 2004-03-12 09:00:00 -200.0 1545 -200 22.1 1353 -200 767 -200 2058 1588 9.2 56.2 0.6561
58 2004-03-13 04:00:00 -200.0 1147 56 6.2 821 109 1132 83 1412 992 7.0 71.1 0.7158
82 2004-03-14 04:00:00 -200.0 1130 56 5.2 773 70 1130 82 1452 1051 12.1 61.1 0.8603
# -200 데이터 처리 후 결과 확인
data.replace(-200, np.nan, inplace=True)
data[data[target] == -200]
datetime CO(GT) PT08.S1(CO) NMHC(GT) C6H6(GT) PT08.S2(NMHC) NOx(GT) PT08.S3(NOx) NO2(GT) PT08.S4(NO2) PT08.S5(O3) T RH AH

이제, 우리의 EDA와 모델링으로 넘어가 봅시다.

👀 탐색적 분석(Exploratory Data Analysis)

# EDA 실험 만들기
eda = TSForecastingExperiment()
# 5개 행 조회
data.head()
datetime CO(GT) PT08.S1(CO) NMHC(GT) C6H6(GT) PT08.S2(NMHC) NOx(GT) PT08.S3(NOx) NO2(GT) PT08.S4(NO2) PT08.S5(O3) T RH AH
0 2004-03-10 18:00:00 2.6 1360.0 150.0 11.9 1046.0 166.0 1056.0 113.0 1692.0 1268.0 13.6 48.9 0.7578
1 2004-03-10 19:00:00 2.0 1292.0 112.0 9.4 955.0 103.0 1174.0 92.0 1559.0 972.0 13.3 47.7 0.7255
2 2004-03-10 20:00:00 2.2 1402.0 88.0 9.0 939.0 131.0 1140.0 114.0 1555.0 1074.0 11.9 54.0 0.7502
3 2004-03-10 21:00:00 2.2 1376.0 80.0 9.2 948.0 172.0 1092.0 122.0 1584.0 1203.0 11.0 60.0 0.7867
4 2004-03-10 22:00:00 1.6 1272.0 51.0 6.5 836.0 131.0 1205.0 116.0 1490.0 1110.0 11.2 59.6 0.7888

setup()

  • 링크: https://pycaret.readthedocs.io/en/latest/api/time_series.html#pycaret.time_series.setup

numeric_imputation_target / numeric_imputation_Exogenous 결측치 처리 방법

  • 'drift' : drift/트렌드 값은 sktime.PolynomialTrendForecaster(degree=1)에 의해 계산됩니다. 먼저, transform()의 X는 ffill로 채워진 후 bfill로 채워집니다. 그 후 PolynomialTrendForecaster(degree=1)가 채워진 X에 맞춰져(fitted) 결측치가 있던 인덱스에서 예측 값이 조회됩니다.

  • 'linear' : 선형 보간, pd.Series.interpolate() 사용. 이 방법은 외삽(extrapolation)할 수 없으므로, 항상 transform()에 제공된 데이터에 맞춰집니다.

  • 'nearest' : 가장 가까운 값을 사용, pd.Series.interpolate() 사용.

  • 'constant' : 모든 NaN에 대해 같은 상수 값 (인수 value에서 주어짐).

  • 'mean' : fit 데이터의 pd.Series.mean().

  • 'median' : fit 데이터의 pd.Series.median().

  • 'backfill' or 'bfill' : pd.Series.fillna()에서 적용.

  • 'pad' or 'ffill' : pd.Series.fillna()에서 적용.

  • 'random' : fit 데이터의 pd.Series.min()과 .max() 사이의 랜덤 값. 만약 pd.Series의 dtype이 int라면, 샘플은 균등 이산(uniform discrete)입니다. 만약 pd.Series의 dtype이 float라면, 샘플은 균등 연속(uniform continuous)입니다.

# Setup 함수에서 데이터 전처리에 대한 설정을 지정합니다.
_ = eda.setup(
    data=data,  # 데이터프레임 전달
    target=target,  # 예측 컬럼
    index="datetime",  # Time 컬럼
    # Forecast Horizon: 예측할 미래 기간을 입력합니다.
    fh=48,
    # 'drift', 'linear', 'nearest', 'mean', 'median', 'backfill', 'bfill', 'pad', 'ffill', 'random'
    numeric_imputation_target="drift",
    # 'drift', 'linear', 'nearest', 'mean', 'median', 'backfill', 'bfill', 'pad', 'ffill', 'random'
    numeric_imputation_exogenous="drift",
    fig_kwargs=global_fig_settings,  # 플롯에 대한 기본 설정을 지정합니다
    session_id=42,
)
  Description Value
0 session_id 42
1 Target CO(GT)
2 Approach Univariate
3 Exogenous Variables Present
4 Original data shape (9357, 13)
5 Transformed data shape (9357, 13)
6 Transformed train set shape (9309, 13)
7 Transformed test set shape (48, 13)
8 Rows with missing values 91.2%
9 Fold Generator ExpandingWindowSplitter
10 Fold Number 3
11 Enforce Prediction Interval False
12 Splits used for hyperparameters all
13 User Defined Seasonal Period(s) None
14 Ignore Seasonality Test False
15 Seasonality Detection Algo auto
16 Max Period to Consider 60
17 Seasonal Period(s) Tested [24, 48, 23, 25, 47, 49]
18 Significant Seasonal Period(s) [24, 48, 23, 25, 47, 49]
19 Significant Seasonal Period(s) without Harmonics [48, 23, 25, 47, 49]
20 Remove Harmonics False
21 Harmonics Order Method harmonic_max
22 Num Seasonalities to Use 1
23 All Seasonalities to Use [24]
24 Primary Seasonality 24
25 Seasonality Present True
26 Seasonality Type mul
27 Target Strictly Positive True
28 Target White Noise No
29 Recommended d 1
30 Recommended Seasonal D 0
31 Preprocess True
32 Numerical Imputation (Target) drift
33 Transformation (Target) None
34 Scaling (Target) None
35 Feature Engineering (Target) - Reduced Regression False
36 Numerical Imputation (Exogenous) drift
37 Transformation (Exogenous) None
38 Scaling (Exogenous) None
39 CPU Jobs -1
40 Use GPU False
41 Log Experiment False
42 Experiment Name ts-default-name
43 USI d449

진행하기에 앞서, 여기에서 몇 가지 유용한 정보를 관찰할 수 있습니다.

  1. 데이터는 시간당 데이터셋이므로, 24시간 주기가 테스트되었습니다. 이 시간 주기에서 계절성이 감지되었습니다.

  2. 데이터의 성격 때문에 모델링하면서 데이터를 차분하는 것이 권장됩니다(d=1). 우리는 이를 EDA 과정에서 더 평가해 볼 것입니다.

# 시계열 데이터 시각화
eda.plot_model(
    fig_kwargs={
        "renderer": "png",  # 'notebook' 으로 설정시 interactive 플롯을 생성합니다.
        "width": 1000,
        "height": 1200,
    }
)

# 시계열 데이터 시각화 with 세부설정
eda.plot_model(
    plot="ts",
    fig_kwargs={
        "height": 1200,
        # resampler_kwargs를 사용하여, plotly-resampler 객체의 생성자를 구성할 수 있습니다.
        "resampler_kwargs": {
            "default_n_shown_samples": 1500,
            # show_dash kwargs는 show_dash (render) 메서드의 kwargs를 보류합니다.
            "show_dash": {"mode": "inline", "port": 8055},
        },
    },
    # display_format='plotly-dash', # interactive 시각화 출력을 원한다면 주석을 해제 합니다.
    # display_format="plotly-widget",  # interactive 시각화 출력을 원한다면 주석을 해제 합니다.
)

확대 축소된 그래프에서 세부 사항을 보기 힘들 수도 있지만, pycaret는 인터랙티브 plotly 플롯을 사용하여 확대하는 기능을 제공합니다. 확대하면, 대략 19:00와 8:00에 피크가 있는 명확한 24시간 주기 를 확인할 수 있습니다.

또한 NMHC(GT)는 모든 값이 누락된 것으로 보입니다. 우리는 차후에 개발할 다변량 모델에서 이 변수를 제거할 예정입니다.

다음으로, 설정에서는 데이터를 차분하는 것이 좋다고 권장하였습니다. 이것이 어떻게 보이는지, 그리고 필요한지 살펴보겠습니다. 차분된 데이터 외에도, 우리는 ACF, PACF 그리고 Periodogram 과 같은 일부 진단을 그려보겠습니다.

Periodogram 은 시계열에서 스펙트럼 밀도를 주파수 함수로 그리는 그래프입니다. 이 경우, 주파수는 0에서 0.5까지의 범위를 가집니다(주파수를 측정하는데 필요한 최소 점은 2개로, 이는 최대 주파수 0.5에 해당합니다). 여러 주파수에서의 크기는 시계열에 대한 중요한 특성을 파악하는 데 사용될 수 있습니다. 이에 대해서는 아래에서 살펴보겠습니다.

# 결측치가 많아 제거합니다.
exclude = ["NMHC(GT)"]
# 기본적으로 원본 데이터를 첫 번째 차분(order d = 1)과 함께 그래프로 그립니다.
eda.plot_model(
    plot="diff",
    fig_kwargs={
        "width": 1500,
        "height": 900,
        # plotly-widget을 사용할 때는 show_dash kwargs를 전달할 필요가 없습니다.
        "resampler_kwargs": {"default_n_shown_samples": 1500},
    },
    data_kwargs={"acf": True, "pacf": True, "periodogram": True},
    # display_format='plotly-dash', # interactive 시각화 출력을 원한다면 주석을 해제 합니다.
    # display_format='plotly-widget', # interactive 시각화 출력을 원한다면 주석을 해제 합니다.
)