🔥알림🔥
테디노트 유튜브 -
구경하러 가기!
[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 시각화 출력을 원한다면 주석을 해제 합니다.
)