🔥알림🔥
① 테디노트 유튜브 -
구경하러 가기!
② LangChain 한국어 튜토리얼
바로가기 👀
③ 랭체인 노트 무료 전자책(wikidocs)
바로가기 🙌
④ RAG 비법노트 LangChain 강의오픈
바로가기 🙌
⑤ 서울대 PyTorch 딥러닝 강의
바로가기 🙌
[AutoML] PyCaret을 활용한 시계열 데이터 예측 모형 생성
이번 포스트의 주제는 “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)
본 튜토리얼에서는 외생 변수를 사용한 모델링을 다룹니다. 튜토리얼의 진행방식은 아래의 목록 순서와 같습니다.
-
시계열을 생성하는 과정에 대한 실질적인 통찰력을 얻기 위해 데이터셋에 대한 EDA를 수행합니다.
-
벤치마킹을 위한 기본 모델 (외생 변수 없는 단변량 모델)을 구축합니다.
-
가능한 최고의 성능을 확인하기 위해 모든 외생 변수를 가진 단변량 모델을 구축합니다.
-
외생 변수가 있는 모델을 평가하고 잠재적인 문제점을 논의합니다.
-
위에서 확인된 문제점을 해결합니다.
-
최적의 모델로 미래 예측을 수행합니다.
-
자동 시계열 모델링 (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 |
진행하기에 앞서, 여기에서 몇 가지 유용한 정보를 관찰할 수 있습니다.
-
데이터는 시간당 데이터셋이므로, 24시간 주기가 테스트되었습니다. 이 시간 주기에서 계절성이 감지되었습니다.
-
데이터의 성격 때문에 모델링하면서 데이터를 차분하는 것이 권장됩니다(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 시각화 출력을 원한다면 주석을 해제 합니다.
)
위의 그래프들은 몇 가지 흥미로운 특성을 보여줍니다.
원본 데이터
차분이 필요한가요?
-
데이터셋은 상당히 높은 유의 수준을 보여주는 확장된 자기상관성(ACF - row 1, col 2)을 보여줍니다. 이는 차분이 필요하다는 것을 나타냅니다.
-
같은 관측 결과는 PACF 플롯을 보면서도 파악할 수 있습니다. (row 1, col 3) 이는 lag1에서 매우 높은 상관관계를 보여줍니다. (즉, 이전 시점)
-
이 특성은 주기도표 (row 1, col 4)에서도 확인할 수 있습니다. 이는
~f = 0
에서 피크를 보여줍니다.~f = 0
에서 피크가 나타나는 것은 시간 주기가 매우 크다는 것을 의미하며, 이는 ‘랜덤 워크’ 유형의 행동을 차분으로 모델링할 수 있다는 것을 시사합니다.)
[참고]
- 피리오도그램 (Periodogram)
피리오도그램은 시계열 데이터의 주파수 도메인 특성을 분석하는 데 사용되는 일종의 플롯이며, 주로 시계열 데이터의 스펙트럼 분석에 사용됩니다. 이는 시계열의 주기성을 이해하는 데 도움이 되며, 시간 도메인에서는 눈으로 보이지 않는 특성을 빛으로 드러내줍니다.
피리오도그램은 시계열의 파워 스펙트럼 밀도 (Power Spectrum Density, PSD)를 추정합니다. PSD는 시계열에서 각 주파수 성분의 ‘강도’를 나타냅니다. 높은 PSD 값을 가진 주파수는 해당 시계열에 더 강한 주기적 특성을 나타냅니다.
- ~f = 0 의 의미
피리오도그램에서의 ~f = 0
은 주파수가 0인 지점, 즉 시계열 데이터의 DC 성분(직류 성분)을 의미합니다. 시계열 데이터의 평균값을 나타내며, 이는 시계열 데이터의 전반적인 수준 혹은 추세를 의미합니다.
예를 들어, 신호에 DC 성분이 있다는 것은 해당 신호가 0을 중심으로 진동하지 않고, 어떤 양의 값 혹은 음의 값으로 이동한 상태를 나타냅니다. 이는 신호의 전반적인 ‘수준’ 혹은 ‘오프셋’을 나타내는 값이라고 할 수 있습니다.
따라서, 주파수가 0인 곳에서의 값은 주파수 성분이 없이 단순히 상수 값으로 이루어진 신호 성분을 나타냅니다. 이런 성분은 종종 시계열 데이터의 ‘백그라운드 노이즈’나 ‘기본 수준’ 등을 나타내는 데 사용됩니다.
[예시]
-
피리오도그램 (Periodogram) 에 대해 생각해보면, 음악에 사용되는 그래픽 이퀄라이저를 떠올릴 수 있습니다. 이퀄라이저는 음악의 다양한 주파수 성분을 분석해서 그 강도를 시각적으로 보여주죠. 예를 들어, 베이스 사운드(저주파)가 강하면 그 부분의 미터가 더 높게 표시되고, 트레블 사운드(고주파)가 약하면 그 부분의 미터가 낮게 표시됩니다. 이것이 바로 피리오도그램이 시계열 데이터에 대해 하는 일입니다. 주기적인 패턴이 강하게 나타나는 주파수를 보여줍니다.
-
~f = 0
의 의미에 대해서는, 라디오의 볼륨 조절을 생각해보면 좋습니다. 라디오의 볼륨을 조절하면 모든 음악, 즉 모든 주파수 성분의 강도가 일괄적으로 증가하거나 감소합니다. 이것은 피리오도그램에서 ~f = 0, 즉 주파수가 0인 지점에서의 값을 조절하는 것과 유사합니다. 주파수가 0인 성분은 시계열 데이터의 전반적인 ‘볼륨’ 또는 ‘수준’을 조절하는 역할을 합니다. 따라서, 이 값을 높이면 시계열 데이터 전체의 강도가 증가하고, 낮추면 감소하게 됩니다. 이것이 바로 ~f = 0의 의미입니다.
계절성을 모델링해야 할까요?
-
ACF는 또한 lag 24에서 피크를 나타내어 24의 계절성을 보여줍니다. (이것은 설정 단계에서도 확인할 수 있었습니다)
-
이는 주기도표에서도 확인할 수 있습니다. (기본 f = 0.0417에서 피크를 보여주며, 그것의 고유 주파수는 0.0834, 0.1249, …). f = 0.0417는 T = 1/0.0417 = 24, 즉 24시간의 계절 주기를 나타냅니다.
차분 순서 = 1
-
차분 순서 = 1은 확장된 자기상관성의 많은 부분이 처리되었음을 보여줍니다. (row 2, col 2의 크기가 이전보다 훨씬 작습니다.) 이는 주기도표 (row 2, col 4)에서도 확인할 수 있습니다. 여기서 ~ f = 0에서의 크기가 이전보다 훨씬 작습니다.
-
ACF는 여전히 lag = 24에서 피크를 보여주며, 주기도표도 이를 나타냅니다. 이는 우리가 이를 적절히 모델링하기 위해 여전히 처리해야 함을 나타냅니다. 다음으로 계절성을 포함한 차분을 플로팅함으로써 이를 처리해봅시다. 이번에는 분석에 더 세밀한 제어를 원하기 때문에 lag들을 명확히 지정하겠습니다.
참고: lag = 1은 차분 순서 = 1, 즉 첫 번째 차분을 의미하며, lag = [1, 24]는 첫 번째 차분이 취해진 다음 계절 차분 24가 취해진다는 것을 의미합니다.
# 차분에 대한 시각화
eda.plot_model(
plot="diff",
fig_kwargs={
"width": 1500,
"height": 900,
"resampler_kwargs": {
"default_n_shown_samples": 1500,
"show_dash": {"mode": "inline", "port": 8056},
},
},
data_kwargs={
"lags_list": [1, [1, 24]],
"acf": True,
"pacf": True,
"periodogram": True,
},
# display_format='plotly-dash', # interactive 시각화 출력을 원한다면 주석을 해제 합니다.
# display_format="plotly-widget", # interactive 시각화 출력을 원한다면 주석을 해제 합니다.
)
우리는 마지막 플롯의 반복이 되는 첫 번째와 두 번째 행보다는 3번째 행에 주목할 것입니다. 3번째 행은 24의 계절 차분이 뒤따르는 1차 차분을 표현합니다. ACF(행 3, 열 2)에서 볼 수 있듯이, 모든 확장 자기상관이 처리되었습니다. 우리는 여전히 lag = 24에서 피크를 보지만(이제는 음수), 페리오도그램을 주의 깊게 검토하면, 누출이 매우 낮은 스펙트럼 밀도를 가지고 있다는 것을 알 수 있습니다. 따라서, 첫 번째 차분과 24의 계절 차분을 취하면 이 시계열을 상당히 합리적으로 모델링할 수 있다는 결론을 내릴 수 있습니다.
참고: 면밀히 살펴보면 AR 및 MA 구성 요소를 알아낼 수 있습니다. 예를 들어, PACF 플롯(행 3, 열 3)은 lag = 2에서 그리고 그 이후에서 음의 스파이크(중요)를 보여줌으로써 적어도 2차 AR 구성 요소를 나타냅니다. 단순성을 위해 이번 연습에서는 그것을 사용하지 않겠지만, 더 완전한 평가를 위해 반드시 탐색해야 합니다.
다음으로, 외생 변수가 타겟에 미치는 영향과 그 사이의 상관 관계를 조사해 보겠습니다. 이는 Cross Correlation Plots을 이용하여 달성할 수 있습니다.
# Cross Correlation Plot 시각화
eda.plot_model(plot="ccf", fig_kwargs={"height": 1000, "width": 1500})
첫 번째 플롯(행 1, 열 1)은 타겟의 자기상관성을 보여주며(ACF와 같음), 나머지 플롯들은 타겟과 외생 변수의 지연된 값들 사이의 상관성을 보여줍니다. 예를 들어, 행 1, 열 2는 CO 농도가 lag 0과 lag 24에서 PT08.S1(CO)
와 매우 상관관계가 있음을 보여줍니다.
NOx(GT)
, C6H6(GT)
, PT08.S2(NMHC)
등 다른 변수들에서도 비슷한 패턴을 볼 수 있습니다. 이 변수들은 CO 농도를 모델링할 때 유용할 수 있습니다.
또 하나의 관찰 결과로, 절대 습도 AH
는 CO 농도와 매우 강한 상관관계를 보이지 않는 것으로 보입니다. 이 변수는 모델링에 중요하지 않을 수 있습니다.
# AH 컬럼 제외목록에 추가
exclude.append("AH")
exclude
['NMHC(GT)', 'AH']
이제 모델링 단계로 넘어가 보겠습니다.
exclude = ["NMHC(GT)", "AH"]
data.drop(columns=exclude, inplace=True)
data.head()
datetime | CO(GT) | PT08.S1(CO) | C6H6(GT) | PT08.S2(NMHC) | NOx(GT) | PT08.S3(NOx) | NO2(GT) | PT08.S4(NO2) | PT08.S5(O3) | T | RH | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 2004-03-10 18:00:00 | 2.6 | 1360.0 | 11.9 | 1046.0 | 166.0 | 1056.0 | 113.0 | 1692.0 | 1268.0 | 13.6 | 48.9 |
1 | 2004-03-10 19:00:00 | 2.0 | 1292.0 | 9.4 | 955.0 | 103.0 | 1174.0 | 92.0 | 1559.0 | 972.0 | 13.3 | 47.7 |
2 | 2004-03-10 20:00:00 | 2.2 | 1402.0 | 9.0 | 939.0 | 131.0 | 1140.0 | 114.0 | 1555.0 | 1074.0 | 11.9 | 54.0 |
3 | 2004-03-10 21:00:00 | 2.2 | 1376.0 | 9.2 | 948.0 | 172.0 | 1092.0 | 122.0 | 1584.0 | 1203.0 | 11.0 | 60.0 |
4 | 2004-03-10 22:00:00 | 1.6 | 1272.0 | 6.5 | 836.0 | 131.0 | 1205.0 | 116.0 | 1490.0 | 1110.0 | 11.2 | 59.6 |
🔆 (베이스라인) 외생 변수 없이 단일 변수 예측하기
# 시계열 실험(Experiment) 객체를 생성합니다.
exp_uni = TSForecastingExperiment()
# 설정 값 지정
exp_uni.setup(
data=data[target],
fh=48, # (예측 time-step) forecast horizon 설정
numeric_imputation_target="linear",
numeric_imputation_exogenous="linear",
fig_kwargs=global_fig_settings,
session_id=42,
)
Description | Value | |
---|---|---|
0 | session_id | 42 |
1 | Target | CO(GT) |
2 | Approach | Univariate |
3 | Exogenous Variables | Not Present |
4 | Original data shape | (9357, 1) |
5 | Transformed data shape | (9357, 1) |
6 | Transformed train set shape | (9309, 1) |
7 | Transformed test set shape | (48, 1) |
8 | Rows with missing values | 18.0% |
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, 25, 23, 47, 49, 12, 36, 60] |
18 | Significant Seasonal Period(s) | [24, 48, 25, 23, 47, 49, 12, 36, 60] |
19 | Significant Seasonal Period(s) without Harmonics | [48, 25, 23, 47, 49, 60, 36] |
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) | linear |
33 | Transformation (Target) | None |
34 | Scaling (Target) | None |
35 | Feature Engineering (Target) - Reduced Regression | False |
36 | CPU Jobs | -1 |
37 | Use GPU | False |
38 | Log Experiment | False |
39 | Experiment Name | ts-default-name |
40 | USI | ac57 |
# 'arima' 모델을 생성합니다.
model = exp_uni.create_model(
"arima", # ARIMA, SARIMA, SARIMAX 등을 커버. 모두 동일하게 arima 로 지정
order=(0, 1, 0), # (p, d, q)
seasonal_order=(0, 1, 0, 24), # (p, d, q, s)
)
cutoff | MASE | RMSSE | MAE | RMSE | MAPE | SMAPE | R2 | |
---|---|---|---|---|---|---|---|---|
0 | 9164.0000 | 1.1705 | 1.1515 | 1.0340 | 1.4835 | 0.5751 | 0.9044 | -1.3625 |
1 | 9212.0000 | 2.1365 | 1.6565 | 1.8839 | 2.1316 | 1.5146 | 0.7456 | -2.8068 |
2 | 9260.0000 | 1.0963 | 0.8704 | 0.9652 | 1.1183 | 1.1796 | 1.2402 | -5.1529 |
Mean | nan | 1.4677 | 1.2261 | 1.2944 | 1.5778 | 1.0898 | 0.9634 | -3.1074 |
SD | nan | 0.4738 | 0.3252 | 0.4178 | 0.4190 | 0.3888 | 0.2062 | 1.5620 |
exp_uni.plot_model(
model,
fig_kwargs={
# 'notebook' 으로 설정시 interactive 플롯을 생성합니다.
"renderer": "png",
"width": 1500,
"height": 500,
},
)
예측을 확대해 보면, 모델이 데이터셋의 일부 추세(피크)를 포착할 수 있는 것을 알 수 있지만, 모든 것을 포착하지는 못합니다. 베이스라인 모델의 성능은 CV 폴드 전체의 평균 MASE가 1.46 라는 것을 보여주며, 이는 그다지 좋지 않은 성과입니다.
1보다 큰 값은 모델이 단순한 1단계 예측조차도 못 따라가고 있다는 것을 의미합니다. 이 모델은 더 개선이 필요합니다. 외생 변수를 추가하여 모델 성능을 향상시킬 수 있는지 살펴보겠습니다.
🔆 (개선된 모델) 외생 변수를 사용한 단일 변수 예측하기
# 외생변수를 추가한 실험 생성
exp_exo = TSForecastingExperiment()
# setup 시 외생변수를 포함한 dataframe 을 입력
exp_exo.setup(
data=data,
target=target,
index="datetime",
fh=48,
numeric_imputation_target="linear",
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, 11) |
5 | Transformed data shape | (9357, 11) |
6 | Transformed train set shape | (9309, 11) |
7 | Transformed test set shape | (48, 11) |
8 | Rows with missing values | 25.8% |
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, 25, 23, 47, 49, 12, 36, 60] |
18 | Significant Seasonal Period(s) | [24, 48, 25, 23, 47, 49, 12, 36, 60] |
19 | Significant Seasonal Period(s) without Harmonics | [48, 25, 23, 47, 49, 60, 36] |
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) | linear |
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 | c7d3 |
# SARIMAX 모델 생성
model_exo = exp_exo.create_model(
"arima",
order=(0, 1, 0),
seasonal_order=(0, 1, 0, 24),
)
cutoff | MASE | RMSSE | MAE | RMSE | MAPE | SMAPE | R2 | |
---|---|---|---|---|---|---|---|---|
0 | 2005-03-27 14:00 | 0.2063 | 0.1858 | 0.1822 | 0.2394 | 0.1056 | 0.1138 | 0.9385 |
1 | 2005-03-29 14:00 | 0.1907 | 0.1667 | 0.1681 | 0.2145 | 0.1019 | 0.0966 | 0.9615 |
2 | 2005-03-31 14:00 | 0.3687 | 0.2967 | 0.3246 | 0.3813 | 0.3682 | 0.4894 | 0.2849 |
Mean | NaT | 0.2552 | 0.2164 | 0.2250 | 0.2784 | 0.1919 | 0.2333 | 0.7283 |
SD | NaT | 0.0805 | 0.0573 | 0.0707 | 0.0735 | 0.1246 | 0.1813 | 0.3137 |
# Actual vs Prediction 시각화
exp_exo.plot_model(model_exo)
# 시계열 Decomposition - seasonal period
exp_exo.plot_model(plot="decomp", data_kwargs={"seasonal_period": 24})
# 시계열 Decomposition
exp_exo.plot_model(plot="decomp", data_kwargs={"type": "multiplicative"})
# Cross Correlation Plot 시각화
exp_exo.plot_model(
plot="ccf",
fig_kwargs={
"width": 1500,
"height": 700,
},
)
❗️ 모델 평가하기
나쁘지 않습니다. MASE
를 상당히 향상시키는 데 성공했으며, 이는 단변량 모델보다 훨씬 좋고, 단순 모델에 비해 큰 개선입니다. 이 개선에 만족해야 합니다.
모델을 최종화하기 위해 전체 데이터셋에 대해 훈련시키고, 실제 미래 예측을 할 수 있도록 하겠습니다.
# Finalize Model : 전체 데이터를 활용한 재학습
final_model_exo = exp_exo.finalize_model(model_exo)
# 예측을 위한 wrapper 함수
def safe_predict(exp, model):
try:
exp.predict_model(model)
except ValueError as exception:
print(exception)
exo_vars = exp.exogenous_variables
# 외생변수(Exogenous Variable) 가 설정이 안되었기 때문에 에러 발생
print(
f"{len(exo_vars)} exogenous variables (X) needed in order to make future predictions:\n{exo_vars}"
)
safe_predict(exp_exo, final_model_exo)
Model was trained with exogenous variables but you have not passed any for predictions. Please pass exogenous variables to make predictions. 10 exogenous variables (X) needed in order to make future predictions: ['PT08.S1(CO)', 'C6H6(GT)', 'PT08.S2(NMHC)', 'NOx(GT)', 'PT08.S3(NOx)', 'NO2(GT)', 'PT08.S4(NO2)', 'PT08.S5(O3)', 'T', 'RH']
이 접근 방식은 부작용이 없지 않습니다. 문제는 우리가 10개의 외생 변수를 가지고 있다는 것입니다.
따라서 CO 농도의 알려지지 않은 미래 값을 얻으려면, 이러한 외생 변수들의 미래 값을 얻어야 합니다. 이는 일반적으로 일부 예측 과정을 통해 얻어집니다.
하지만 각 예측에는 오차가 있으며, 특히 외생 변수가 많을 때 이러한 오차는 복리로 증폭될 수 있습니다.
예측 성능을 저하시키지 않으면서 이 외생 변수들을 유용한 소수의 변수로 줄일 수 있는지 살펴보겠습니다.
🔆 (간소한 모델) 제한적인 외생 변수를 사용한 단일 변수 예측
CCF 분석에서 우리는 많은 외생 변수들이 CO 농도와 매우 유사한 상관 구조를 보인다는 것을 발견했습니다.
예를 들어, 24시간 전(lag = 24
)의 PT08.S1(CO)
, NOx(GT)
, C6H6(GT)
, PT08.S2(NMHC)
값은 CO 농도와 높은 양의 상관 관계를 보입니다. 이들 중에서도 NOx(GT)
가 lag 24에서 가장 높은 양의 상관 관계를 가지는 변수입니다.
마찬가지로, 24시간 전(lag = 24
)의 PT08.S3(NOx)
값은 CO 농도와 가장 높은 음의 상관 관계를 보입니다. 우리는 이 변수를 유지하기로 결정했습니다.
마지막으로, 일일 사이클에서 12시간 전에 발생한 사건도 현재 값을 영향을 줄 수 있습니다(예: 지난 밤의 값은 다음 날에 영향을 줄 수 있고 그 반대도 가능합니다). Lag = 12에서 CO 농도와 가장 높은 상관 관계를 가지는 변수는 RH
입니다. 이 변수도 유지하기로 결정했습니다.
# 시계열 실험 생성
exp_slim = TSForecastingExperiment()
# 사용할 외생변수 & 타겟 정의
keep = [target, "datetime", "NOx(GT)", "PT08.S3(NOx)", "RH"]
data_slim = data[keep]
# setup
exp_slim.setup(
data=data_slim,
target=target,
index="datetime",
fh=48,
numeric_imputation_target="drift",
numeric_imputation_exogenous="linear",
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, 4) |
5 | Transformed data shape | (9357, 4) |
6 | Transformed train set shape | (9309, 4) |
7 | Transformed test set shape | (48, 4) |
8 | Rows with missing values | 25.8% |
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) | linear |
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 | c037 |
# 동일한 SARIMAX 모델을 생성
model_slim = exp_slim.create_model(
"arima", order=(0, 1, 0), seasonal_order=(0, 1, 0, 24)
)
cutoff | MASE | RMSSE | MAE | RMSE | MAPE | SMAPE | R2 | |
---|---|---|---|---|---|---|---|---|
0 | 2005-03-27 14:00 | 0.2425 | 0.2653 | 0.2001 | 0.3286 | 0.1305 | 0.1299 | 0.8808 |
1 | 2005-03-29 14:00 | 0.3796 | 0.3707 | 0.3128 | 0.4587 | 0.3020 | 0.2191 | 0.8237 |
2 | 2005-03-31 14:00 | 0.3069 | 0.3331 | 0.2526 | 0.4117 | 0.2805 | 0.3921 | 0.2508 |
Mean | NaT | 0.3096 | 0.3231 | 0.2552 | 0.3997 | 0.2377 | 0.2470 | 0.6518 |
SD | NaT | 0.0560 | 0.0436 | 0.0460 | 0.0538 | 0.0763 | 0.1089 | 0.2845 |
# 시각화
exp_slim.plot_model(
model_slim,
)
나쁘지 않습니다. MASE는 약간 증가했지만, 우리는 외생 변수를 크게 줄일 수 있었습니다. 이것은 우리가 ‘진짜’ 알려지지 않은 미래 예측을 할 때 도움이 될 것입니다. 왜냐하면 CO 농도에 대한 예측을 하기 위해 이러한 외생 변수의 ‘알려지지 않은’ 미래 값이 필요하기 때문입니다.
🔆 모델 최종화(Finalize) 하기
-
실제 미래 예측을 할 수 있도록 슬림 모델을 전체 데이터셋에 대해 훈련시킵니다.
-
배포를 위해 모델을 pickle 파일로 저장합니다.
# 전체 데이터셋으로 재학습
final_slim_model = exp_slim.finalize_model(model_slim)
# 모델 저장 (모델, 저장할 모델명)
_ = exp_slim.save_model(final_slim_model, "final_slim_model")
Transformation Pipeline and Model Successfully Saved
# 여전히 외생변수가 누락되었기 때문에 에러 발생
safe_predict(exp_slim, final_slim_model)
Model was trained with exogenous variables but you have not passed any for predictions. Please pass exogenous variables to make predictions. 3 exogenous variables (X) needed in order to make future predictions: ['NOx(GT)', 'PT08.S3(NOx)', 'RH']
따라서 우리는 여전히 3개의 외생 변수에 대한 미래 값을 필요로 합니다. 다음 파트에서 예측 기법을 사용하여 이를 얻을 것입니다.
🔥 외생변수(Exogenous Variables) 에 대한 값 얻기
이제 우리의 모델을 구축했으니, 미래 예측을 진행해 보겠습니다.
# 사용할 외생변수 정의(데모를 위해 3개만 추려냅니다)
exog_vars = ["NOx(GT)", "PT08.S3(NOx)", "RH"]
data = data[["datetime"] + exog_vars]
data.head()
datetime | NOx(GT) | PT08.S3(NOx) | RH | |
---|---|---|---|---|
0 | 2004-03-10 18:00:00 | 166.0 | 1056.0 | 48.9 |
1 | 2004-03-10 19:00:00 | 103.0 | 1174.0 | 47.7 |
2 | 2004-03-10 20:00:00 | 131.0 | 1140.0 | 54.0 |
3 | 2004-03-10 21:00:00 | 172.0 | 1092.0 | 60.0 |
4 | 2004-03-10 22:00:00 | 131.0 | 1205.0 | 59.6 |
📊 미래 예측하기
예측을 통해 미래의 외생 변수 값을 얻기
# STEP 1: 외생변수 각각에 대한 시계열 예측 수행
exog_exps = []
exog_models = []
for exog_var in exog_vars:
# 외생변수에 대한 예측을 도출하기 위하여 시계열 실험 생성
exog_exp = TSForecastingExperiment()
# setup
exog_exp.setup(
data=data[["datetime", exog_var]],
target=exog_var,
index="datetime",
fh=48,
numeric_imputation_target="ffill",
numeric_imputation_exogenous="ffill",
fig_kwargs=global_fig_settings,
session_id=42,
verbose=False,
)
# 사용자는 미래 외생 변수를 모델링하는 방법을 사용자 정의할 수 있습니다.
# 즉, 모델링 시간이 늘어나는 대신 더 나은 모델을 얻을 수 있는 추가 단계와 모델을 추가할 수 있습니다.
best = exog_exp.compare_models(
sort="mase",
include=[
"arima",
"ets",
"exp_smooth",
"theta",
"lightgbm_cds_dt",
],
verbose=False,
)
final_exog_model = exog_exp.finalize_model(best)
exog_exps.append(exog_exp)
exog_models.append(final_exog_model)
# STEP 2: 외생 변수에 대한 미래 예측 얻기
future_exog = [
exog_exp.predict_model(exog_model)
for exog_exp, exog_model in zip(exog_exps, exog_models)
]
# 예측값 concat
future_exog = pd.concat(future_exog, axis=1)
future_exog.columns = exog_vars
# 예측으로 도출한 외생변수 집합 확인
future_exog
NOx(GT) | PT08.S3(NOx) | RH | |
---|---|---|---|
2005-04-04 15:00 | 262.1091 | 656.1044 | 14.5065 |
2005-04-04 16:00 | 297.1918 | 638.6659 | 15.6333 |
2005-04-04 17:00 | 325.5872 | 596.7227 | 17.5673 |
2005-04-04 18:00 | 259.7444 | 556.0672 | 22.7364 |
2005-04-04 19:00 | 425.2039 | 534.2108 | 28.3385 |
2005-04-04 20:00 | 403.5829 | 553.1982 | 33.7580 |
2005-04-04 21:00 | 275.5622 | 611.5993 | 37.0870 |
2005-04-04 22:00 | 266.8754 | 652.8857 | 39.7600 |
2005-04-04 23:00 | 198.3008 | 662.6246 | 41.3155 |
2005-04-05 00:00 | 140.6534 | 680.4221 | 42.4415 |
2005-04-05 01:00 | 89.7790 | 730.9550 | 44.4348 |
2005-04-05 02:00 | 85.5490 | 799.7945 | 46.8401 |
2005-04-05 03:00 | 75.8562 | 861.3735 | 48.6809 |
2005-04-05 04:00 | 63.6113 | 890.8526 | 49.7074 |
2005-04-05 05:00 | 86.7398 | 878.5921 | 50.1808 |
2005-04-05 06:00 | 190.1795 | 796.5090 | 50.4606 |
2005-04-05 07:00 | 600.8788 | 663.6406 | 48.9828 |
2005-04-05 08:00 | 591.7944 | 561.3295 | 46.7280 |
2005-04-05 09:00 | 527.8903 | 565.2039 | 41.8545 |
2005-04-05 10:00 | 476.1367 | 599.8656 | 37.9259 |
2005-04-05 11:00 | 356.5084 | 622.0729 | 35.3302 |
2005-04-05 12:00 | 295.9845 | 630.0078 | 34.0590 |
2005-04-05 13:00 | 237.5478 | 632.9998 | 35.7544 |
2005-04-05 14:00 | 267.1838 | 653.6938 | 36.6735 |
2005-04-05 15:00 | 263.9894 | 655.8095 | 35.8249 |
2005-04-05 16:00 | 298.8190 | 638.3788 | 35.1843 |
2005-04-05 17:00 | 327.0035 | 596.4545 | 35.7987 |
2005-04-05 18:00 | 260.9848 | 555.8173 | 38.7220 |
2005-04-05 19:00 | 426.2978 | 533.9706 | 41.7648 |
2005-04-05 20:00 | 404.5546 | 552.9496 | 44.0184 |
2005-04-05 21:00 | 276.4319 | 611.3244 | 47.1953 |
2005-04-05 22:00 | 267.6602 | 652.5922 | 49.4721 |
2005-04-05 23:00 | 199.0148 | 662.3268 | 51.0524 |
2005-04-06 00:00 | 141.3084 | 680.1162 | 53.1152 |
2005-04-06 01:00 | 90.3847 | 730.6264 | 54.2431 |
2005-04-06 02:00 | 86.1137 | 799.4349 | 55.4766 |
2005-04-06 03:00 | 76.3867 | 860.9863 | 56.8362 |
2005-04-06 04:00 | 64.1132 | 890.4521 | 58.0996 |
2005-04-06 05:00 | 87.2179 | 878.1971 | 59.2240 |
2005-04-06 06:00 | 190.6379 | 796.1510 | 59.8861 |
2005-04-06 07:00 | 601.3206 | 663.3422 | 59.1575 |
2005-04-06 08:00 | 592.2224 | 561.0771 | 57.2327 |
2005-04-06 09:00 | 528.3069 | 564.9498 | 52.8706 |
2005-04-06 10:00 | 476.5436 | 599.5958 | 46.1781 |
2005-04-06 11:00 | 356.9073 | 621.7932 | 41.6050 |
2005-04-06 12:00 | 296.3769 | 629.7245 | 38.9799 |
2005-04-06 13:00 | 237.9346 | 632.7152 | 37.8304 |
2005-04-06 14:00 | 267.5660 | 653.3999 | 37.2366 |
모델을 로드하고 목표 변수에 대한 미래 예측하기
# 미래 예측용 시계열 실험 생성
exp_future = TSForecastingExperiment()
# 이전에 저장한 모델 로드
final_slim_model = exp_future.load_model("final_slim_model")
Transformation Pipeline and Model Successfully Loaded
# 예측용 Forecasting Horizon 개수 확인
final_slim_model.fh
ForecastingHorizon([ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48], dtype='int64', is_relative=True)
# 예측으로 도출한 외생변수 확인
future_exog.tail(10)
NOx(GT) | PT08.S3(NOx) | RH | |
---|---|---|---|
2005-04-06 05:00 | 87.2179 | 878.1971 | 59.2240 |
2005-04-06 06:00 | 190.6379 | 796.1510 | 59.8861 |
2005-04-06 07:00 | 601.3206 | 663.3422 | 59.1575 |
2005-04-06 08:00 | 592.2224 | 561.0771 | 57.2327 |
2005-04-06 09:00 | 528.3069 | 564.9498 | 52.8706 |
2005-04-06 10:00 | 476.5436 | 599.5958 | 46.1781 |
2005-04-06 11:00 | 356.9073 | 621.7932 | 41.6050 |
2005-04-06 12:00 | 296.3769 | 629.7245 | 38.9799 |
2005-04-06 13:00 | 237.9346 | 632.7152 | 37.8304 |
2005-04-06 14:00 | 267.5660 | 653.3999 | 37.2366 |
# 모델의 Forecasting Horizon 과 예측으로 생성한 외생변수의 개수가 동일해야 함
assert len(future_exog) == len(final_slim_model.fh)
# 예측
future_preds = exp_future.predict_model(
final_slim_model, # 모델 입력
X=future_exog, # 외생변수 입력
)
# 결과 시각화
future_preds.plot()
이제 우리의 데이터셋을 수동으로 모델링하는 과정을 자동화하여 재현해 보겠습니다.
🔥 (전체 자동화) AutoML을 사용한 자동화된 시계열 모델링
① 데이터 전처리
# 데이터 로드
data = get_data("airquality", verbose=False)
# 간단한 데이터 전처리 (demo 용)
# 추후 더 정확한 예측을 위하여 정교한 전처리가 요구될 수 있습니다.
data = data.iloc[-720:]
data.insert(0, "datetime", pd.to_datetime(data["Date"] + " " + data["Time"]))
data.drop(columns=["Date", "Time"], inplace=True)
data.replace(-200, np.nan, inplace=True)
data.set_index("datetime", inplace=True)
# 타겟 정의
target = "CO(GT)"
# 사용할 외생변수 정의
exog_vars = ["NOx(GT)", "PT08.S3(NOx)", "RH"]
# 최종 사용 Feature 정의
include = [target] + exog_vars
data = data[include]
data.head()
CO(GT) | NOx(GT) | PT08.S3(NOx) | RH | |
---|---|---|---|---|
datetime | ||||
2005-03-05 15:00:00 | 1.5 | 180.0 | 820.0 | 28.3 |
2005-03-05 16:00:00 | 1.8 | 255.0 | 751.0 | 29.7 |
2005-03-05 17:00:00 | 2.0 | 251.0 | 721.0 | 38.7 |
2005-03-05 18:00:00 | 1.9 | 258.0 | 695.0 | 56.3 |
2005-03-05 19:00:00 | 2.5 | 344.0 | 654.0 | 57.9 |
② 하이퍼 파라미터 정의
FH = 48 # 예측 Forecasting Horizon
metric = "mase" # 평가지표
# 예측에 제외할 모델명 입력
exclude_models = [
"auto_arima",
"bats",
"tbats",
"lar_cds_dt",
"par_cds_dt",
]
③ 모델 생성
exp_auto = TSForecastingExperiment()
exp_auto.setup(
data=data,
target=target,
fh=FH,
# enforce_exogenous=False --> 모델이 지원하는 경우 다변량 예측을 사용하고, 그렇지 않은 경우 단일 변수 예측을 사용합니다.
enforce_exogenous=False,
numeric_imputation_target="drift",
numeric_imputation_exogenous="linear",
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 | (720, 4) |
5 | Transformed data shape | (720, 4) |
6 | Transformed train set shape | (672, 4) |
7 | Transformed test set shape | (48, 4) |
8 | Rows with missing values | 3.8% |
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, 25, 23, 47, 49, 12, 36] |
18 | Significant Seasonal Period(s) | [24, 48, 25, 23, 47, 49, 12, 36] |
19 | Significant Seasonal Period(s) without Harmonics | [48, 25, 23, 47, 49, 36] |
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) | linear |
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 | 3ae4 |
# 사용 가능한 모델 확인하기
exp_auto.models()
Name | Reference | Turbo | |
---|---|---|---|
ID | |||
naive | Naive Forecaster | sktime.forecasting.naive.NaiveForecaster | True |
grand_means | Grand Means Forecaster | sktime.forecasting.naive.NaiveForecaster | True |
snaive | Seasonal Naive Forecaster | sktime.forecasting.naive.NaiveForecaster | True |
polytrend | Polynomial Trend Forecaster | sktime.forecasting.trend.PolynomialTrendForeca... | True |
arima | ARIMA | sktime.forecasting.arima.ARIMA | True |
auto_arima | Auto ARIMA | sktime.forecasting.arima.AutoARIMA | True |
exp_smooth | Exponential Smoothing | sktime.forecasting.exp_smoothing.ExponentialSm... | True |
ets | ETS | sktime.forecasting.ets.AutoETS | True |
theta | Theta Forecaster | sktime.forecasting.theta.ThetaForecaster | True |
stlf | STLF | sktime.forecasting.trend.STLForecaster | True |
croston | Croston | sktime.forecasting.croston.Croston | True |
bats | BATS | sktime.forecasting.bats.BATS | False |
tbats | TBATS | sktime.forecasting.tbats.TBATS | False |
prophet | Prophet | pycaret.containers.models.time_series.ProphetP... | False |
lr_cds_dt | Linear w/ Cond. Deseasonalize & Detrending | pycaret.containers.models.time_series.BaseCdsD... | True |
en_cds_dt | Elastic Net w/ Cond. Deseasonalize & Detrending | pycaret.containers.models.time_series.BaseCdsD... | True |
ridge_cds_dt | Ridge w/ Cond. Deseasonalize & Detrending | pycaret.containers.models.time_series.BaseCdsD... | True |
lasso_cds_dt | Lasso w/ Cond. Deseasonalize & Detrending | pycaret.containers.models.time_series.BaseCdsD... | True |
llar_cds_dt | Lasso Least Angular Regressor w/ Cond. Deseaso... | pycaret.containers.models.time_series.BaseCdsD... | True |
br_cds_dt | Bayesian Ridge w/ Cond. Deseasonalize & Detren... | pycaret.containers.models.time_series.BaseCdsD... | True |
huber_cds_dt | Huber w/ Cond. Deseasonalize & Detrending | pycaret.containers.models.time_series.BaseCdsD... | True |
omp_cds_dt | Orthogonal Matching Pursuit w/ Cond. Deseasona... | pycaret.containers.models.time_series.BaseCdsD... | True |
knn_cds_dt | K Neighbors w/ Cond. Deseasonalize & Detrending | pycaret.containers.models.time_series.BaseCdsD... | True |
dt_cds_dt | Decision Tree w/ Cond. Deseasonalize & Detrending | pycaret.containers.models.time_series.BaseCdsD... | True |
rf_cds_dt | Random Forest w/ Cond. Deseasonalize & Detrending | pycaret.containers.models.time_series.BaseCdsD... | True |
et_cds_dt | Extra Trees w/ Cond. Deseasonalize & Detrending | pycaret.containers.models.time_series.BaseCdsD... | True |
gbr_cds_dt | Gradient Boosting w/ Cond. Deseasonalize & Det... | pycaret.containers.models.time_series.BaseCdsD... | True |
ada_cds_dt | AdaBoost w/ Cond. Deseasonalize & Detrending | pycaret.containers.models.time_series.BaseCdsD... | True |
lightgbm_cds_dt | Light Gradient Boosting w/ Cond. Deseasonalize... | pycaret.containers.models.time_series.BaseCdsD... | True |
# Prophet과 같은 느린 모델을 포함(turbo=False) 하되, 특정 모델을 제외합니다.
best = exp_auto.compare_models(sort=metric, turbo=False, exclude=exclude_models)
Model | MASE | RMSSE | MAE | RMSE | MAPE | SMAPE | R2 | TT (Sec) | |
---|---|---|---|---|---|---|---|---|---|
arima | ARIMA | 0.2887 | 0.3406 | 0.2167 | 0.3690 | 0.2012 | 0.1809 | 0.7031 | 0.4167 |
br_cds_dt | Bayesian Ridge w/ Cond. Deseasonalize & Detrending | 0.8021 | 0.7953 | 0.6025 | 0.8618 | 0.4299 | 0.3924 | -0.7004 | 0.2267 |
ridge_cds_dt | Ridge w/ Cond. Deseasonalize & Detrending | 0.8061 | 0.7971 | 0.6055 | 0.8638 | 0.4334 | 0.4000 | -0.7038 | 0.2300 |
lr_cds_dt | Linear w/ Cond. Deseasonalize & Detrending | 0.8064 | 0.7973 | 0.6057 | 0.8640 | 0.4337 | 0.4004 | -0.7041 | 0.2333 |
huber_cds_dt | Huber w/ Cond. Deseasonalize & Detrending | 0.9400 | 0.8940 | 0.7055 | 0.9686 | 0.5424 | 0.4850 | -1.1793 | 0.2367 |
en_cds_dt | Elastic Net w/ Cond. Deseasonalize & Detrending | 0.9624 | 0.9278 | 0.7233 | 1.0050 | 0.5227 | 0.3703 | -1.6815 | 0.2533 |
lasso_cds_dt | Lasso w/ Cond. Deseasonalize & Detrending | 0.9693 | 0.9331 | 0.7284 | 1.0107 | 0.5267 | 0.3722 | -1.7354 | 0.2267 |
llar_cds_dt | Lasso Least Angular Regressor w/ Cond. Deseasonalize & Detrending | 0.9693 | 0.9331 | 0.7284 | 1.0107 | 0.5267 | 0.3722 | -1.7355 | 0.2267 |
theta | Theta Forecaster | 0.9707 | 0.8809 | 0.7316 | 0.9578 | 0.4972 | 0.4405 | -0.2475 | 0.0267 |
omp_cds_dt | Orthogonal Matching Pursuit w/ Cond. Deseasonalize & Detrending | 0.9752 | 0.9343 | 0.7327 | 1.0119 | 0.5327 | 0.3746 | -1.7656 | 0.2267 |
snaive | Seasonal Naive Forecaster | 0.9778 | 0.9772 | 0.7338 | 1.0583 | 0.5202 | 0.3890 | -1.8920 | 0.0333 |
lightgbm_cds_dt | Light Gradient Boosting w/ Cond. Deseasonalize & Detrending | 0.9891 | 0.8615 | 0.7455 | 0.9356 | 0.5699 | 0.5207 | -0.4090 | 0.2767 |
gbr_cds_dt | Gradient Boosting w/ Cond. Deseasonalize & Detrending | 1.0026 | 0.8826 | 0.7543 | 0.9570 | 0.5706 | 0.5661 | -0.8815 | 0.3267 |
et_cds_dt | Extra Trees w/ Cond. Deseasonalize & Detrending | 1.0364 | 0.9264 | 0.7798 | 1.0043 | 0.5830 | 0.5254 | -1.1930 | 0.5367 |
naive | Naive Forecaster | 1.0547 | 0.9179 | 0.7958 | 0.9984 | 0.6104 | 0.5194 | -0.4216 | 0.0333 |
croston | Croston | 1.0578 | 0.9004 | 0.7952 | 0.9774 | 0.7624 | 0.4971 | -0.5989 | 0.0167 |
knn_cds_dt | K Neighbors w/ Cond. Deseasonalize & Detrending | 1.0619 | 0.9917 | 0.7980 | 1.0742 | 0.6746 | 0.4617 | -2.0527 | 0.2700 |
stlf | STLF | 1.0854 | 0.9925 | 0.8152 | 1.0757 | 0.6703 | 0.4958 | -1.5120 | 0.0233 |
rf_cds_dt | Random Forest w/ Cond. Deseasonalize & Detrending | 1.0966 | 0.9671 | 0.8262 | 1.0491 | 0.6247 | 0.5842 | -1.2369 | 0.5600 |
dt_cds_dt | Decision Tree w/ Cond. Deseasonalize & Detrending | 1.1184 | 0.9986 | 0.8406 | 1.0818 | 0.7151 | 0.5494 | -1.9197 | 0.2633 |
grand_means | Grand Means Forecaster | 1.2577 | 0.9942 | 0.9447 | 1.0773 | 0.9801 | 0.5670 | -1.6413 | 0.0233 |
ada_cds_dt | AdaBoost w/ Cond. Deseasonalize & Detrending | 1.3131 | 1.0980 | 0.9856 | 1.1884 | 0.8663 | 0.5345 | -3.0193 | 0.2833 |
polytrend | Polynomial Trend Forecaster | 1.4086 | 1.0881 | 1.0587 | 1.1793 | 1.1216 | 0.6099 | -2.1636 | 0.0200 |
ets | ETS | 1.8521 | 1.6281 | 1.3886 | 1.7632 | 1.1426 | 0.6031 | -5.5128 | 0.1900 |
exp_smooth | Exponential Smoothing | 1.8891 | 1.6610 | 1.4166 | 1.7991 | 1.1620 | 0.6101 | -5.6581 | 0.0633 |
# best 모델로 시각화
exp_auto.plot_model(best)
# 전체 데이터셋으로 재학습
final_auto_model = exp_auto.finalize_model(best)
④ 최종 예측
def safe_predict(exp, model):
"""예측용 wrapper 함수."""
try:
future_preds = exp.predict_model(model)
except ValueError as exception:
print(exception)
exo_vars = exp.exogenous_variables
print(f"미래 예측을 위해 {len(exo_vars)}개의 외생 변수 (X)가 필요합니다:\n{exo_vars}")
exog_exps = []
exog_models = []
for exog_var in exog_vars:
exog_exp = TSForecastingExperiment()
exog_exp.setup(
data=data[exog_var],
fh=FH,
numeric_imputation_target="drift",
numeric_imputation_exogenous="linear",
fig_kwargs=global_fig_settings,
session_id=42,
verbose=False,
)
# 사용자는 미래 외생 변수를 모델링하는 방법을 사용자 정의할 수 있습니다.
# 예를 들어, 더 많은 단계와 모델을 추가하여 더 나은 모델을 얻을 수 있지만,
# 모델링 시간이 늘어날 수 있습니다.
best = exog_exp.compare_models(
sort=metric,
include=[
"arima",
"ets",
"exp_smooth",
"theta",
"lightgbm_cds_dt",
],
verbose=False,
)
final_exog_model = exog_exp.finalize_model(best)
exog_exps.append(exog_exp)
exog_models.append(final_exog_model)
# 단계 2: 외생 변수에 대한 미래 예측 얻기 ----
future_exog = [
exog_exp.predict_model(exog_model)
for exog_exp, exog_model in zip(exog_exps, exog_models)
]
future_exog = pd.concat(future_exog, axis=1)
future_exog.columns = exog_vars
future_preds = exp.predict_model(model, X=future_exog)
return future_preds
future_preds = safe_predict(exp_auto, final_auto_model)
future_preds.plot()
Model was trained with exogenous variables but you have not passed any for predictions. Please pass exogenous variables to make predictions. 미래 예측을 위해 3개의 외생 변수 (X)가 필요합니다: ['NOx(GT)', 'PT08.S3(NOx)', 'RH']
댓글남기기