1. 데이터 로드
학습 데이터를 2015년 1월 1일부터 시작하도록 설정합니다.
시간이 지나며 따라 기상 조건에 영향을 미치는 요소들이 변화할 수 있습니다.
최근 데이터를 중심으로 학습 기간을 설정함으로써, 현재와 더 밀접한 환경 변화를 반영할 수 있으며,
이는 예측의 정확도를 높이는 데 기여할 수 있습니다.
또한, 오래된 데이터는 때로 현재 상황과는 무관한 노이즈를 포함할 수 있습니다.
최신 데이터를 선택함으로써 이러한 노이즈의 영향을 줄이고, 데이터의 품질을 높일 수 있습니다.
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
train = pd.read_csv('preprocessing_train.csv', parse_dates=['일시'])
train = train.set_index('일시')
# 학습/ 검증 데이터 분할
train_data = train[train.index < '2022-01-01']
validation_data = train[train.index >= '2022-01-01']
# 학습 기간 변경
train_data = train_data[train_data.index >= '2015-01-01']
train_data.index.freq = 'D'
validation_data.index.freq = 'D'
display(train_data.tail())
display(validation_data.tail())
2. 데이터 전처리 (1)
from scipy.stats import boxcox
from sklearn.preprocessing import StandardScaler
# 1. 강수량 이상값 처리
train_data['강수량'] = np.where(train_data['강수량']>=60, 60, train_data['강수량'])
validation_data['강수량'] = np.where(validation_data['강수량']>=60, 60, validation_data['강수량'])
# 2. 값이 0인 데이터에 작은 상수를 더하는 함수
def positive_shift(series, shift_value=1e-3):
return series + shift_value if np.any(series <= 0) else series
# 극단적인 분포를 감소시키기 위해 '강수량' 값에 Box-Cox 변환 적용
train_data['강수량'], lamb = boxcox(positive_shift(train_data['강수량']))
# lmbda 파라미터에 값을 입력할 경우 람다 파라미터를 반환하지 않음
validation_data['강수량'] = boxcox(positive_shift(validation_data['강수량']), lmbda=lamb)
# 3. 표준화 적용 - 학습
scaler = StandardScaler()
scaler.fit(train_data)
# 학습 데이터 변환
scaled_train = scaler.transform(train_data)
scaled_train = pd.DataFrame(scaled_train)
# 검증 데이터 변환
scaled_val = scaler.transform(validation_data)
scaled_val = pd.DataFrame(scaled_val)
scaled_train.index = train_data.index
scaled_train.columns = train_data.columns
scaled_val.index = validation_data.index
scaled_val.columns = validation_data.columns
scaled_train.head()
※ 코드 설명
이전 스테이지에서 boxcox() 함수를 적용할 때, 강수량 데이터에 Box-Cox 변환을 적용했는데, 이 과정에서 람다 값은 반환되지 않았습니다.
반면, 이번 스테이지에서는 강수량 에 Box-Cox 변환을 적용하면서 변환된 람다 값(lambda)을 변수에 저장하고 검증 데이터셋의 강수량에도 동일하게 적용했습니다.
통상적으로, 검증 데이터셋에 대한 데이터 누수를 방지하기 위해 훈련 데이터셋에서 도출된 파라미터를 검증 데이터셋에 적용합니다.
현재 코드의 경우, 훈련 데이터셋에서 계산된 람다 값을 검증 데이터셋에도 적용함으로써 두 데이터셋 간의 일관된 변환 처리를 보장합니다.
이번 스테이지에서는 검증 데이터셋의 실제 값과 모델에 의해 예측된 값을 비교하여 모델의 성능을 평가하는데,
훈련 데이터셋과 검증 데이터셋이 동일한 방식으로 처리되었기 때문에, 예측 결과와 실제 값의 차이 비교가 보다 정확하고 신뢰할 수 있습니다.
3. 데이터 전처리 (2)
from sklearn.decomposition import PCA
pca_1 = ['일교차', '일조합', '일사합', '일조율']
pca_2 = ['최고기온', '최저기온']
# 다중공선성을 가지는 변수 PCA 변환 적용
# pca_1 사용
sun_pca = PCA(n_components=0.7)
sun_pca.fit(scaled_train[pca_1])
scaled_train['pca_sun'] = sun_pca.transform(scaled_train[pca_1])
scaled_val['pca_sun'] = sun_pca.transform(scaled_val[pca_1])
scaled_train.drop(pca_1, axis = 1, inplace = True)
scaled_val.drop(pca_1, axis = 1, inplace = True)
# pca_2 사용
temperature_pca = PCA(n_components=0.7)
temperature_pca.fit(scaled_train[pca_2])
scaled_train['pca_temperature'] = temperature_pca.transform(scaled_train[pca_2])
scaled_val['pca_temperature'] = temperature_pca.transform(scaled_val[pca_2])
scaled_train.drop(pca_2, axis = 1, inplace = True)
scaled_val.drop(pca_2, axis = 1, inplace = True)
sns.heatmap(scaled_train.corr(), annot = True)
plt.show()
display(scaled_train.head())
4. VAR 모델 최적 Lag 찾기
시계열 데이터 분석에 있어서, VAR(Vector Autoregression, 벡터 자기회귀) 모델은 여러 시계열 변수들이 서로에게 어떤 영향을 미치는지 분석하는 데 사용됩니다.
VAR 모델은 각 변수가 그 이전의 여러 값들로부터 영향을 받는다고 가정합니다.
이때, ‘몇 시점 전의 데이터를 사용할 것인가’를 결정하는 것이 Lag(지연)의 개념입니다.
두 개의 변수를 포함하는 데이터셋이 있을 때, 첫 번째 변수에 중점을 두고 있다고 가정하면,
Lag가 1일 때 첫 번째 변수에 대한 VAR 모델의 식은 다음과 같습니다:
위 수식에서 각 항목은 다음과 같습니다.
- y_{1,t}: 이 항목은 관심 대상인 첫 번째 변수의 시점 t에서의 값입니다. 이는 예측하고자 하는 종속 변수의 현재 값입니다.
- c_1 : 첫 번째 변수에 대한 상수항 혹은 절편입니다. 모든 시계열 데이터의 평균 수준을 조정하는 데 사용됩니다.
- a_{11,1} : 이 계수는 첫 번째 변수의 과거 값(시점 t-1)이 현재 값(시점 t)에 미치는 영향의 정도를 나타냅니다.
이는 자기회귀 계수로, 변수가 자기 자신의 과거 값에 어떻게 반응하는지를 보여줍니다. - y_{1,t-1} : 첫 번째 변수의 시점 t-1에서의 값입니다.
- a_{12,1} : 이 계수는 두 번째 변수의 과거 값(시점 t-1)이 첫 번째 변수의 현재 값(시점 t)에 미치는 영향을 나타냅니다.
즉, 교차 회귀 계수로, 다른 변수의 과거 값이 첫 번째 변수에 어떻게 영향을 미치는지를 나타냅니다. - y_{2,t-1} : 두 번째 변수의 시점 t-1에서의 값입니다. 첫 번째 변수의 현재 값에 영향을 미치는 또 다른 요소로 고려됩니다.
- e_{1,t} : 오차항으로, 시점 t에서 예측값과 실제 관측값 간의 차이를 나타냅니다. 모델이 설명할 수 없는 임의의 변동성을 포함합니다.
위 수식에서 a_{12,1} 은 다음을 의미합니다:
첫 번째 숫자 1은 현재 예측하고자 하는 변수를 나타냅니다.
두 번째 숫자 2는 두번째 변수의 영향력을 나타냅니다.
마지막 숫자 1은 지연(Lag)의 정도를 나타내며, 1시점 전의 데이터를 나타냅니다.
즉, a_{12,1}는 첫 번째 변수에 대해 예측하고자 할 때, 두 번째 변수의 한 시점 전 데이터가 첫 번째 변수의 현재 값에 미치는 영향의 계수입니다.
그리고 y_{2,t-1} 는 조금 더 간단합니다.
시점 t-1에서의 두 번째 변수의 값을 의미합니다.
그리고 Lag를 선택하는 것은 매우 중요한데, 너무 적은 Lag를 선택하면 모델이 데이터의 중요한 패턴을 포착하지 못할 수 있고, 너무 많은 Lag를 선택하면 모델이 과적합될 위험이 있습니다.
이를 위해 AIC (Akaike Information Criterion, 아카이케 정보 기준) 값을 사용합니다.
AIC는 모델의 복잡도와 데이터에 대한 적합도를 동시에 고려하는 지표로, 일반적으로 낮은 AIC 값을 가지는 모델이 데이터를 더 잘 설명한다고 여겨집니다.
본 코드는 0부터 10까지의 Lag 값을 시험하여 각 Lag 값에 대한 AIC 값을 계산합니다.
그리고 가장 낮은 AIC 값을 가지는 Lag 값을 최적의 Lag로 선택합니다.
from statsmodels.tsa.api import VAR
best_lag = 0
best_aic = np.inf
model = VAR(scaled_train)
for i in range(0, 11):
result = model.fit(i)
temp_aic = result.aic
if temp_aic <= best_aic:
best_aic = temp_aic
best_lag = i
print(f'최적 Lag는 {best_lag} 입니다.')
print(f'Lag가 {best_lag} 일 때 AIC 값은 {best_aic} 입니다.' )
최적 Lag는 7 입니다.
Lag가 7 일 때 AIC 값은 -9.976095501411052 입니다.
※ 결과 해석
코드 실행 결과, 최적의 Lag는 7로 확인됩니다.
이는 VAR 모델이 7시점 전의 데이터를 기반으로 현재의 데이터를 가장 잘 예측한다는 것을 의미합니다.
또한, 해당 Lag에서의 AIC 값은 약 -9.972로, 이 값이 다른 Lag 값들에 비해 가장 낮습니다.
이를 통해 모델의 복잡도를 고려한 가장 적절한 Lag를 선택할 수 있습니다.
5. VAR 모델을 활용한 시계열 데이터 예측
# 선택된 Lag 값
selected_lag_order = 7
# VAR 모델 생성 및 훈련
model = VAR(scaled_train)
result = model.fit(selected_lag_order)
# 예측 기간 설정 (scaled_val의 기간을 사용)
n_forecast = len(scaled_val)
# VAR 모델을 사용하여 예측 수행
forecast = result.forecast(y=scaled_train.values, steps=n_forecast)
# 예측 결과를 데이터프레임으로 변환
forecast_df = pd.DataFrame(forecast, columns=scaled_train.columns)
# 모든 변수에 대한 예측 결과 시각화
fig, axes = plt.subplots(nrows=3, ncols=2, dpi=120, figsize=(10,10))
for i, (col, ax) in enumerate(zip(scaled_train.columns, axes.flatten())):
# 실제 값
ax.plot(scaled_val.index, scaled_val[col], label='실제 ' + col, marker='o')
# 예측 값
ax.plot(scaled_val.index, forecast_df[col], label='예측 ' + col, linestyle='--', marker='x')
ax.set_title(col)
ax.legend()
plt.tight_layout()
plt.show()
※ 코드설명
이 단계에서는 앞서 선택된 최적의 Lag 값(7)을 사용하여 VAR 모델을 구축하고 예측을 수행합니다.
먼저, VAR(scaled_train) 을 사용하여 모델을 생성하고, fit() 메서드에 선택된 Lag 값을 사용하여 모델을 학습시킵니다.
fit() 메서드에 입력된 selected_lag_order는 VAR 모델이 현재 값을 예측하기 위해 참고할 이전 데이터 포인트의 수를 나타냅니다.
즉, selected_lag_order = 7 이므로 모델이 각 예측에 앞서 7일 동안의 데이터를 사용한다는 의미입니다.
이는 모델이 얼마나 과거 정보를 참고할지 결정하는 파라미터입니다.
모델 학습 후, forecast() 메서드를 사용하여 scaled_train 데이터를 기반으로 미래 값을 예측합니다.
- y 매개변수는 모델이 학습된 마지막 데이터 포인트로, 예측의 시작점으로 사용됩니다.
- steps 매개변수는 예측할 기간의 길이를 나타냅니다.
- n_forecasted 변수에 저장된 값은 검증 데이터셋(scaled_val) 길이와 같으므로, 모델은 scaled_val 데이터셋이 포함하는 기간만큼 예측을 수행합니다.
마지막으로, 예측 결과와 실제 값을 시각화하여 비교합니다.
실제 값은 실행 데이터(y_true), 예측 값은 선차 값(y_pred)로 구분하여 표시됩니다.
※ 결과 해석
각 그래프는 시계열 데이터의 한 변수를 나타냅니다.
파란색 점선은 실제 관측값을, 주황색 선은 VAR 모델에 의해 예측된 값을 나타냅니다.
각 그래프는 해당 변수에 대한 시간에 따른 변화와 예측 모델의 성능을 보여줍니다.
출력되는 이미지를 보고 실제 값과 예측 값 사이에 얼마나 큰 차이가 있는지를 볼 수 있습니다.
대체로 각 변수에서는 실제 값과 예측 값 사이에 상당한 차이가 있습니다.
6. 최적의 변수 조합 탐색
from itertools import combinations
# 최적의 변수 조합을 찾기 위한 함수
def find_best_var_combination(data, max_vars=10):
best_aic = np.inf
best_vars = None
# 가능한 모든 변수 조합에 대해서 AIC를 계산
for i in range(1, max_vars+1):
for combination in combinations(data.columns, i):
# VAR 모델의 가정에 위배되는 조합 무시
if len(combination) == 1:
continue
# 선택된 변수로 VAR 모델 적합
model = VAR(data[list(combination)])
result = model.fit(maxlags=selected_lag_order) # 7
# AIC가 더 낮은 모델을 찾으면 업데이트
if result.aic < best_aic:
best_aic = result.aic
best_vars = combination
return best_vars, best_aic
# 최적의 변수 조합 찾기
best_vars, best_aic = find_best_var_combination(scaled_train, len(scaled_train.columns))
print(f'Best variable combination: {best_vars}')
print(f'Best AIC: {best_aic}')
※ 코드 설명
코드는 주어진 데이터셋에서 AIC를 최소화하는 최적의 변수 조합을 찾기 위한 함수를 정의하고 실행합니다.
VAR 모델은 여러 시계열 변수들 간의 상호관계를 모델링하는 데 사용됩니다.
이에, 모든 변수가 동일한 영향을 갖는 것이 아닌, 특정 변수 조합이 모델의 성능을 높일 수 있습니다.
정의한 함수 find_best_var_combination 은 주어진 데이터셋에서 1개의 변수를 선택하는 조합을 제외하고,
최대 max_vars개의 변수를 포함하는 조합까지 고려합니다.
이후 각 조합에 대해 AIC 값을 계산하고, 가장 낮은 AIC 값을 가진 조합을 찾은 후 그 조합과 함께 반환합니다.
정의한 함수 find_best_var_combination 은 다음 단계를 수행합니다.
- 함수는 max_vars 매개변수로 정의된 최대 변수 수까지의 모든 가능한 변수 조합을 생성합니다.
VAR 모델은 두 개 이상의 상호 의존적인 시계열 변수 간의 관계를 모델링하는 데 사용되기 때문에,
하나의 변수만을 포함하는 조합은 무시됩니다. - 가능한 각 조합에 대해 VAR 모델을 정의하고 학습합니다.
이때 Lag는 이전에 설정된 7로 설정되며, 각 변수의 과거 7개 값을 고려합니다. - 각 모델의 AIC 값을 계산하고, 이전에 발견된 것보다 낮은 AIC 값을 가진 조합이 있으면 해당 조합과 그 AIC 값을 최적으로 갱신합니다.
- 모든 조합을 평가한 후, 가장 낮은 AIC 값을 가진 변수 조합과 그 AIC 값을 반환합니다.
Best variable combination: ('강수량', '평균습도', '평균풍속', '평균기온', 'pca_temperature')
Best AIC: -10.229080557098811
※ 결과 해석
poa_sun 변수는 최적의 조합에서 제외되었습니다.
이는 poa_sun 변수가 다른 변수들과 함께 사용되었을 때 모델의 AIC를 개선하지 못했기 때문이며,
poa_sun 이 포함된 모델보다 제외되었을 때 더 좋은 모델 적합도를 보였음을 의미합니다.
다음 스텝에서는 poa_sun 변수를 제거한 후 VAR 모델을 학습하고 시각화를 수행할 예정입니다.
7. pca_sun 변수 제거 및 VAR 모델 예측 시각화
del_sun_train = scaled_train.drop('pca_sun', axis=1)
del_sun_val = scaled_val.drop('pca_sun', axis=1)
def train_and_forecast(train_data, val_data, lag_order):
# VAR 모델 생성 및 훈련
model = VAR(train_data)
result = model.fit(lag_order)
# 예측 기간 설정
n_forecast = len(val_data)
# 예측 수행
forecast = result.forecast(y=train_data.values, steps=n_forecast)
forecast_df = pd.DataFrame(forecast, columns=train_data.columns)
# 모든 변수에 대한 예측 결과 시각화
fig, axes = plt.subplots(nrows=3, ncols=2, dpi=120, figsize=(10,10))
for i, (col, ax) in enumerate(zip(val_data.columns, axes.flatten())):
ax.plot(val_data.index, val_data[col], label='실제 ' + col, marker='o')
ax.plot(val_data.index, forecast_df[col], label='예측 ' + col, linestyle='--', marker='x')
ax.set_title(col)
ax.legend()
plt.tight_layout()
plt.show()
return forecast_df, model
# 함수 사용
forecast_df, forecast_model = train_and_forecast(del_sun_train, del_sun_val, selected_lag_order) # 7
forecast_df.head()
※ 코드 설명
코드에서는 poa_sun 변수를 제거하고, 스텝 5에서 사용했던 VAR 모델을 통해 예측 및 시각화하는 과정을 함수로 구현하였습니다.
del_sun_train과 del_sun_val은 기존 학습 및 검증 데이터에서 poa_sun 변수를 제외한 데이터입니다. poa_sun 변수를 제거하는 이유는 이 변수가 AIC를 통한 모델 평가에서 성능 향상에 기여하지 못했기 때문입니다.
함수 train_and_forecast() 는 VAR 모델의 생성, 학습, 예측 및 결과 시각화의 전 과정을 포함하고 있으며, 이를 함수화함으로써 동일한 작업을 반복할 때의 코드 작성 시간을 단축시킬 수 있습니다.
또한, 함수화함을 통해 노트북 파일(.ipynb)에서뿐만 아니라, .py 파일로 저장하여 다른 프로젝트나 스크립트에서도 재사용이 가능하도록 합니다.
.py 파일로 코드를 저장하는 방식은 서비스 배포나 유저 보수를 보다 효율적으로 할 수 있게 해줍니다.
예를 들어, 배포된 서비스의 코드이나 라이브러리를 업데이트해야 할 때, .ipynb는 웹 기반 인터페이스가 필요하여 직접 수정해야 하지만, .py 파일만을 수정하여 배포하면 됩니다.
또한, 코드의 재사용이 자주 필요한 데이터 전처리, 모델링, 시각화 등의 작업에 함수화는 장점을 가집니다. 반복적인 정의해 놓은 함수는 필요할 때 쉽게 호출할 수 있으며, 이를 통해 빠르게 작업을 진행하고 결과를 확인할 수 있습니다.
이런 접근 방식은 코드의 가독성과 관리를 개선시키는 동시에, 잠재적인 오류를 더 쉽게 찾아내고 수정하는 데 도움을 줍니다.
※ 결과 해석
대부분의 변수에서 실제 값과 예측 값 사이에 상당한 차이가 보이며, 예측이 실제 값을 정확하게 반영하지 못하고 있습니다.
모델의 성능을 개선하기 위한 추가적인 조치가 필요합니다.
8. 정상성 검정을 통한 시계열 데이터 분석
from statsmodels.tsa.stattools import adfuller
def check_stationarity(dataframe):
normal = []
abnormal = []
for col in dataframe.columns:
result = adfuller(dataframe[col])
if result[1] <= 0.05:
normal.append(col)
else:
abnormal.append(col)
return normal, abnormal
normal_cols, abnormal_cols = check_stationarity(del_sun_train)
print(f'정상시계열을 가지는 데이터는 {normal_cols} 입니다.')
print(f'비정상시계열을 가지는 데이터는 {abnormal_cols} 입니다.')
ax = del_sun_train.plot(subplots = True, figsize = (15,10))
plt.show()
※ 코드 설명
이번 코드에서는 직접 정의한 함수 check_stationarity() 를 사용하여 각 변수의 정상성을 점검합니다.
VAR 모델은 시계열 모델이므로 정상성을 갖추어야 잘 작동할 수 있습니다.
이에, 비정상 시계열을 발견하면, 차분(differencing) 이나 변환(transformation) 과 같은 방법을 통해 정상 시계열로 변환할 수 있습니다.
또한, 선 그래프로 그림으로써 추세나 계절성 을 확인할 수 있습니다.
이번 스테이지에서는 평균기온 에 대해 ADF 테스트를 수행했을 때는 정상 시계열로 나타났지만, 선 그래프를 관찰해보면 결과 차이를 확인하였습니다.
ADF 테스트는 정상성 여부만을 판별하기 때문에, 이러한 패턴이 있을 수 있음을 시각적으로 확인하는 것은 중요합니다.
정상시계열을 가지는 데이터는 ['강수량', '평균습도', '평균풍속', '평균기온', 'pca_temperature'] 입니다.
비정상시계열을 가지는 데이터는 [] 입니다.
※ 결과 해석
ADF 테스트 결과에 따르면 강수량, 평균풍속, 평균습도, 평균기온, poa_temperature 변수들은 정상 시계열이나, 반면에 비정상 시계열을 나타내는 변수는 없습니다.
그러나 그래프를 살펴보면, 일부 변수에서 계절성이 뚜렷하게 나타나고 있는 것을 볼 수 있습니다.
특히 평균기온, poa_temperature 변수에서는 시간이 따른 주기적인 패턴 이 명확하게 보이며, 이는 계절 변화에 따른 영향을 반영하는 것으로 추정됩니다.
이러한 계절성은 ADF 테스트에서는 직접적으로 고려되지 않을 수 있기 때문에, 모델을 설계할 때 이를 반영하는 추가적인 처리가 필요할 변수 입니다.
9. 차분을 적용한 시계열 데이터의 정상성 검정
※ 비정상 시계열 데이터 변환
비정상 시계열 데이터는 시간에 따른 통계적 특성이 변화 하므로, 이를 분석하기 전에 정상 시계열로 변환 하는 것이 중요합니다.
이를 위한 일반적인 방법 중 하나는 차분(differencing) 을 적용하는 것입니다.
차분은 연속된 관측치 간의 차이를 계산하여 시계열 데이터의 추세나 계절성을 제거 합니다.
from statsmodels.tsa.stattools import adfuller
# 차분 적용
diff_train = del_sun_train.copy().diff().dropna()
diff_val = del_sun_val.copy().diff().dropna()
normal_cols, abnormal_cols = check_stationarity(diff_train)
print(f'정상시계열을 가지는 데이터는 {normal_cols} 입니다.')
print(f'비정상시계열을 가지는 데이터는 {abnormal_cols} 입니다.')
diff_train.plot(subplots = True, figsize = (15,10))
plt.show()
※ 코드 설명
이 코드는 원본 데이터에 차분을 적용 하고, ADF 검정을 사용하여 각 변수의 시계열 데이터가 정상성을 만족하는지 다시 확인 합니다. diff() 메서드를 사용하여 데이터프레임의 열에 대해 차분을 적용 하고, dropna() 를 사용해 결측치를 제거합니다.
차분 적용 후 check_stationarity() 함수를 사용하여 각 변수의 시계열 데이터에 대해 ADF 검정을 수행 하고
그 결과를 그래프로 확인 하여 계절성을 가진 그래프가 어떻게 변화했는지 확인할 수 있습니다.
정상시계열을 가지는 데이터는 ['강수량', '평균습도', '평균풍속', '평균기온', 'pca_temperature'] 입니다.
비정상시계열을 가지는 데이터는 [] 입니다.
※ 결과 해석
시계열 데이터에 차분을 적용하기 전에는 일부 변수에서 추세나 계절성과 같은 비정상적인 패턴 이 나타났습니다.
차분은 이러한 비정상성을 제거하는 기법 으로, 적용 후 데이터가 보다 정상적인 형태 로 변환된 것을 확인할 수 있습니다.
이 과정에서 변수들의 시계열 변동이 평활화되고 안정화되는 것 을 관찰할 수 있습니다.
특히, 차분을 통해 계절성이 사라졌다는 것 은 이 방법이 계절적 변동성을 효과적으로 제거했음을 의미 합니다.
따라서 차분을 거친 데이터는 시계열 분석이나 예측 모델링에 사용하기 적합한 상태로 변환 되었다고 볼 수 있습니다.
10. 차분 적용 시계열 데이터에 대한 VAR 모델 최적 Lag 찾기
이번에는 로그 가능도(log-likelihood) 값을 이용하여 VAR 모델의 최적 lag(지연 시간)을 찾습니다.
VAR 모델은 여러 시계열 데이터 간의 관계를 모델링할 때 사용되며, 각 변수의 과거 값들이 현재 값에 어떤 영향을 미치는지 추정하는데, 각각의 모델 적합도를 로그 가능도 값으로 평가하여, 그 중 가장 높은 로그 가능도 값을 찾는 lag를 찾습니다.
로그 가능도는 주어진 모델이 관측된 데이터를 얼마나 잘 설명하는지를 나타내는 척도입니다.
로그 가능도 값이 클수록 모델이 데이터를 더 잘 설명하고 있다고 볼 수 있습니다.
가능도는 모델 파라미터가 주어졌을 때 관측된 데이터가 나타날 확률이며, 로그 가능도는 이 확률의 로그를 취한 값입니다.
로그를 사용하는 이유는 곱셈이 모두 덧셈이 되므로 계산하기 수월하며, 연산량이 줄어드는 효과가 있습니다.
이전 스텝에서 사용한 AIC는 모델의 복잡도와 데이터에 대한 적합도를 동시에 고려하는 지표입니다.
AIC는 과적합을 방지하고, 모델의 일반화 능력을 높이는 데 도움을 줍니다.
그러나 AIC를 이용한 방법은 때때로 과소적합을 유발할 수도 있습니다.
모델이 너무 단순화되어 데이터의 중요한 정보를 놓치는 경우가 그 예입니다.
이에 비해 로그 가능도는 모델의 복잡도를 고려하지 않고 순수하게 데이터를 얼마나 잘 설명하는지에만 초점을 맞춥니다.
이는 특히 데이터셋이 크고, 변수 간의 관계가 복잡한 경우, 모델의 적합성을 더 정확하게 평가할 수 있는 장점이 있습니다.
그렇기 때문에 AIC에 의존하기보다는 로그 가능도 값을 직접 이용하여 최적의 lag를 선택하는 방법을 사용할 수 있습니다.
from tqdm import tqdm
best_lag = 0
best_loglik = -np.inf # 로그 가능도의 최대값을 찾기 위해 초기값을 음의 무한대로 설정
model = VAR(diff_train)
for i in tqdm(range(0, 180, 10)):
result = model.fit(i)
temp_loglik = result.llf # Log-Likelihood 값 저장
if temp_loglik >= best_loglik:
best_loglik = temp_loglik
best_lag = i
print(f'가능도가 가장 높은 최적 Lag는 {best_lag} 입니다.')
print(f'Lag가 {best_lag} 일 때 Log-Likelihood 값은 {best_loglik} 입니다.')
가능도가 가장 높은 최적 Lag는 170 입니다.
Lag가 170 일 때 Log-Likelihood 값은 -1721.7514151567575 입니다.
※ 결과 해석
제시된 코드 실행 결과, 로그 가능도가 가장 높은 최적의 lag 값은 170이 도출되었습니다.
이는 해당 VAR 모델이 데이터를 설명하는 데 있어서 170시점의 데이터를 고려하는 것이 가장 적합하다고 추정됨을 의미합니다.
그러나 중요한 점은, 로그 가능도를 기반으로 한 lag 값은 모델의 복잡도를 전혀 고려하지 않았기 때문에 이를 명심하여야 합니다.
최적 lag 수치가 설정 범위의 마지막 값인 170에서 나왔다면, 이는 실제적인 최적값이 더 높을 가능성을 배제하지 않습니다.
따라서 과소적합이라고 판단할 경우 더 높은 Lag 값을, 과대적합이라고 판단할 경우 더 낮은 Lag 값을 사용할 수 있을 것입니다.
11. 차분 적용 시계열 데이터에 대한 VAR 모델 예측 수행
diff_forecast_df, diff_forecast_model = train_and_forecast(diff_train, diff_val, 170)
diff_forecast_df.head()
※ 결과 해석
차분 전의 lag 값을 7로 설정했을 때 모델이 거의 일정한 값을 예측하는 경향을 보였으나,
차분 후의 lag를 170으로 설정하였을 때의 그래프는 관측치를 훨씬 더 잘 추적하고 있음을 나타냅니다.
이는 차분이 시계열 데이터에서 비정상성을 제거하고 예측 모델의 적합도를 높이는 데 도움이 되었다는 것을 의미합니다.
차분을 한 후의 예측이 실제 관측치와 더 일치하도록 개선되었다는 것은 모델이 데이터의 패턴을 더 잘 이해하고 있으며,
과거의 많은 시점을 고려하는 것이 유의미한 정보를 제공했다는 것을 시사합니다.
다음 단계에서는 이 차분된 데이터를 원래의 시계열로 복원하여 그래프로 그리고 결과를 확인할 계획입니다.
이는 모델이 실제 값에 얼마나 가깝게 예측했는지를 더 명확하게 평가하기 위한 단계로,
차분을 통해 제거된 추세나 계절성을 다시 데이터에 반영하게 됩니다.
12. 예측 결과 역차분 및 시각화
# 차분 이전 데이터의 마지막 데이터를 가져옵니다.
last_obs = del_sun_train.iloc[-1]
# 차분된 예측값을 원래 스케일로 되돌립니다. 마지막 관측값에 예측된 차분값을 누적합니다.
undiff_forecast = last_obs + diff_forecast_df.cumsum()
# 원본 검증 데이터셋과 비교를 위해 인덱스 재설정
undiff_forecast.index = diff_val.index
# 결과 시각화
fig, axes = plt.subplots(nrows=3, ncols=2, dpi=120, figsize=(10,10))
for i, (col, ax) in enumerate(zip(del_sun_val.columns, axes.flatten())):
# 실제 값
ax.plot(del_sun_val.index, del_sun_val[col], label='실제 ' + col, marker='o')
# 예측 값
ax.plot(undiff_forecast.index, undiff_forecast[col], label='역차분 예측 ' + col, linestyle='--', marker='x')
ax.set_title(col)
ax.legend()
plt.tight_layout()
plt.show()
※ 코드 설명
이 코드는 차분된 시계열 데이터의 예측 결과를 차분 이전의 스케일로 되돌리는 작업을 수행합니다.
예측이 완료된 후에는, 이러한 변환을 역으로 수행하여 차분 이전의 데이터로 스케일을 복원하는 과정이 필요합니다.
코드에서는 차분된 예측 결과 데이터프레임 diff_forecast_df를 차분 이전 데이터셋 del_sun_train의 마지막 관측값 last_obs를 기준으로 역차분합니다.
이를 위해 예측된 차분값에 cumsum() 함수를 사용하여 누적합을 계산하고, 이는 last_obs를 더하여 차분 이전 스케일의 예측값을 얻습니다.
그런 다음, 원본 검증 데이터셋 del_sun_val과 시간 인덱스를 일치시켜 각 변수별로 실제 관측값과 예측값을 비교하고 시각화합니다. 이 과정을 통해 모델의 예측 성능을 시각적으로 평가할 수 있습니다.
차분된 시계열은 일반적으로는 차분 시점을 기준으로 하지 전에 관측값을 사용합니다.
차분된 값을 원래 값으로 복원하는 것은 차분 이전의 원본 데이터 상태를 복원하는 데 더하여 새로운 데이터 상태를 복원합니다.
코드에서 마지막 관측값을 사용하는 이유는 VAR 모델과 같은 시계열 모델이 예측을 수행할 때 마지막 관측값을 기준으로 미래 값을 추정하기 때문입니다.
차분(diff)은 시계열 데이터에서 각 시점별 변화량을 계산하는 과정입니다.
예측된 차분값은 이전 데이터를 기반으로 한 미래 시점의 변화량을 나타내며, 이를 누적하여 현재 값에 더함으로써 미래 시점의 데이터를 추정합니다.
따라서, 예측된 차분값을 현재 데이터 상태에 적용하기 위해서는 차분 계산의 시작점이 되는 마지막 관측값이 필요합니다.
이 마지막 관측값은 모델이 예측을 수행하는 시점에서의 데이터 상태를 반영하며,
예측된 차분값을 이에 더함으로써 미래의 데이터를 추정할 수 있습니다.
이 코드의 한계점은 스케일링된 데이터를 원래의 단위로 역변환하는 과정이 있습니다.
데이터에 StandardScaler를 적용한 후, 주기 패턴을 제거한 뒤 PCA(주성분 분석)를 수행했습니다.
PCA 과정에서 일부 변수는 개별적인 스케일링 전의 원본 값과 달라졌고,
이로 인해 StandardScaler.inverse_transform() 메서드를 직접 사용해도 스케일링된 데이터를 원래 단위로 되돌릴 수 없습니다.
마지막 스텝에서는 이를 고려하여 2023-01-01부터 2023-12-24까지의 기간을 예측하는 전체 코드에 원래의 단위로 역변환을 수행하는 방법이 추가됩니다.
※ 결과 해석
비록 원래의 단위로 확인하진 못했지만, 역차분된 예측결과를 봤을 때 다음과 같습니다.
강수량, 평균풍속의 경우 검증 기간과 비교했을 때 제대로된 예측을 하지 못하는 것으로 보이나,
평균기온, 평균습도, poa_temperature의 경우 실제 값과 비슷한 예측을 한 것으로 확인됩니다.
실제 예측하여야 하는 평균기온의 예측 값이 어느정도 비슷하므로 이 값을 그대로 예측에 사용할 수 있습니다.
마지막 스텝에서는 2022년 데이터도 활용하여 2023년의 평균기온을 예측하겠습니다.
13. 시계열 예측을 위한 VAR 모델 적용 프로세스
- 강수량 변수에 BoxCox 변환을 적용하여 분포를 변화시켜주세요.
- 최고기온, 최저기온 변수를 활용하여 PCA 변환을 수행하고, 이를 pca_temperature 변수에 저장해주세요.
- 표준화가 완료된 train_data_scaled 데이터프레임에 차분을 적용해주세요.
- 학습된 VAR 모델 final_result 를 사용해서 예측값을 획득하고 이를 forecast_diff 변수에 저장해주세요.
- inverse_transform() 메서드를 사용하여 표준화 이전의 범위로 되돌리고, forecast_df_unscaled 변수에 저장해주세요.
import pandas as pd
import numpy as np
from scipy.stats import boxcox
from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import PCA
from statsmodels.tsa.api import VAR
# 데이터 읽어오기
train_data = pd.read_csv('preprocessing_train.csv', parse_dates=['일시'])
submission_data = pd.read_csv('sample_submission.csv')
# 일자 인덱스 설정 및 빈도 추가
train_data = train_data.set_index('일시')
train_data.index.freq = 'D'
# 학습 기간 변경
train_data = train_data[train_data.index >= '2015-01-01']
# 이상값 처리
train_data['강수량'] = np.where(train_data['강수량'] >= 60, 60, train_data['강수량'])
# 1. Box-Cox 변환 - 0을 포함한 변수에 작은 값을 더함
train_data['강수량'], lamb = boxcox(train_data['강수량'] + 1e-3)
# 불필요한 컬럼 제거
train_data.drop(['일교차', '일조합', '일사합', '일조율'], axis=1, inplace=True)
# PCA를 수행할 변수에 스케일링 진행
scaler_temperature = StandardScaler()
train_data[['최고기온', '최저기온']] = scaler_temperature.fit_transform(train_data[['최고기온', '최저기온']])
# 2. PCA 변환 및 변수제거
pca_temperature = PCA(n_components=1)
train_data['pca_temperature'] = pca_temperature.fit_transform(train_data[['최고기온', '최저기온']])
train_data.drop(['최고기온', '최저기온'], axis=1, inplace=True)
# 전체 변수에 대한 스케일링
scaler = StandardScaler()
train_data_scaled = scaler.fit_transform(train_data)
train_data_scaled = pd.DataFrame(train_data_scaled, index=train_data.index, columns=train_data.columns)
# 3. 차분 수행
train_data_diff = train_data_scaled.diff().dropna()
# VAR 모델 학습
final_var_model = VAR(train_data_diff)
final_result = final_var_model.fit(170) # 이전 스텝에서 로그 가능도를 통해 얻은 최적의 Lag 170를 사용
# 4. 예측 수행
forecast_diff = final_result.forecast(y=train_data_diff.values, steps=len(submission_data))
# 예측된 차분값 복원
forecast_df_diff = pd.DataFrame(forecast_diff, index=submission_data.index, columns=train_data_diff.columns)
forecast_df_restored = forecast_df_diff.cumsum() + train_data_scaled.iloc[-1]
# 5. 전체 예측 결과에 대한 스케일링 복원
forecast_df_unscaled = scaler.inverse_transform(forecast_df_restored)
forecast_df_unscaled = pd.DataFrame(forecast_df_unscaled, index=forecast_df_restored.index, columns=forecast_df_restored.columns)
# '평균기온' 예측값 추출
submission_data['평균기온'] = forecast_df_unscaled['평균기온']
display(submission_data.head())
14. 제출
submission_data.to_csv('submission.csv', index=False)
'딥러닝 > 딥러닝: 실전 프로젝트 학습' 카테고리의 다른 글
회귀 : 커머스 제품 판매량 예측 2 (0) | 2025.06.12 |
---|---|
회귀 : 커머스 제품 판매량 예측 1 (3) | 2025.06.12 |
시계열 : 서울시 평균기온 예측 프로젝트 5(전처리) (1) | 2025.02.07 |
시계열 : 서울시 평균기온 예측 프로젝트 4(결측값 처리) (0) | 2025.02.07 |
시계열 : 서울시 평균기온 예측 프로젝트 3(ARIMA 모델을 사용한 단변량 예측) (0) | 2025.02.04 |