개념
시계열(Time Series)이 다른 이유
표준 머신러닝은 i.i.d., 즉 독립 동일 분포(independent and identically distributed)를 가정합니다. 각 표본(sample)이 같은 분포(distribution)에서 서로 독립적으로 뽑힌다고 보는 것입니다. 시계열은 이 두 조건을 모두 위반합니다.
- 서로 독립이지 않습니다. 오늘의 주가는 어제의 주가에 의존하고, 이번 주 매출은 지난주 매출과 상관관계가 있습니다.
- 동일한 분포가 아닙니다. 분포 자체가 시간에 따라 변합니다. 12월의 매출은 3월의 매출과 다른 모습을 보입니다.
이러한 위반은 결코 사소하지 않습니다. 특성(feature)을 만드는 방식, 모델을 평가하는 방식, 그리고 잘 작동하는 알고리즘(algorithm)까지 모두 바꿔놓기 때문입니다.
flowchart LR
subgraph IID["Standard ML (i.i.d.)"]
direction TB
S1[Sample 1] ~~~ S2[Sample 2]
S2 ~~~ S3[Sample 3]
end
subgraph TS["Time Series (not i.i.d.)"]
direction LR
T1[t=1] --> T2[t=2]
T2 --> T3[t=3]
T3 --> T4[t=4]
end
style S1 fill:#dfd
style S2 fill:#dfd
style S3 fill:#dfd
style T1 fill:#ffd
style T2 fill:#ffd
style T3 fill:#ffd
style T4 fill:#ffd
표준 머신러닝에서는 표본의 순서를 바꿔도 거의 문제가 되지 않습니다. 그러나 시계열에서는 순서가 곧 전부입니다. 무작위로 섞는 셔플링(shuffling)은 데이터에 담긴 신호(signal)를 그대로 파괴해 버립니다.
시계열(Time Series)의 구성요소
모든 시계열은 다음 세 가지 요소의 조합으로 볼 수 있습니다.
flowchart TD
A[관측된 시계열] --> B[추세(Trend)]
A --> C[계절성(Seasonality)]
A --> D[잔차/잡음(Residual/Noise)]
B --> E[장기 방향: 상승, 하락, 평평함]
C --> F[반복 패턴: 일별, 주별, 연별]
D --> G[추세와 계절성을 제거한 뒤의 무작위 변동]
- 추세(Trend): 장기적인 방향성을 뜻합니다. 매출이 매년 10%씩 성장한다거나, 지구 평균 기온이 꾸준히 상승하는 현상이 대표적인 예입니다.
- 계절성(Seasonality): 고정된 주기로 반복되는 패턴을 가리킵니다. 12월의 소매 매출 급증이나, 7월의 에어컨 사용량 정점 같은 것들입니다.
- 잔차(Residual): 추세와 계절성을 모두 걷어내고 남는 부분입니다. 이 잔차가 백색 잡음(white noise)처럼 보인다면, 분해(decomposition)가 신호를 잘 포착해낸 것입니다.
정상성(Stationarity)
시계열의 통계적 속성, 즉 평균(mean), 분산(variance), 자기상관(autocorrelation)이 시간이 흘러도 변하지 않으면 그 시계열을 정상적(stationary)이라고 부릅니다. 대부분의 예측 방법(forecasting method)은 정상성(stationarity)을 가정합니다.
왜 중요한가? 비정상 시계열(non-stationary series)에서는 평균이 시간에 따라 표류(drift)합니다. 1월 데이터로 학습한 모델이 2월의 다른 평균을 예측해야 하는 상황이 되므로, 체계적으로 빗나갈 수밖에 없습니다.
어떻게 확인하나? 일정한 창(window) 단위로 롤링 평균(rolling mean)과 롤링 표준편차(rolling standard deviation)를 계산해 봅니다. 그 값이 시간에 따라 표류한다면, 그 시계열은 비정상입니다.
어떻게 바로잡나? 차분(differencing)을 사용합니다. 원래의 값(raw value) 대신, 연속된 값 사이의 변화량을 모델링(modeling)하는 방식입니다.
diff[t] = value[t] - value[t-1]
한 번의 차분만으로 정상성을 얻지 못하면 한 번 더 적용합니다. 대부분의 현실 시계열은 두 번 이내의 차분으로 충분합니다.
예시는 다음과 같습니다.
원본 시계열: [100, 102, 106, 112, 120]
1차 차분: [2, 4, 6, 8] (아직 상승 추세)
2차 차분: [2, 2, 2] (상수, 정상적)
원본 시계열에는 2차 추세(quadratic trend)가 있었습니다. 1차 차분이 이를 선형 추세(linear trend)로 바꾸었고, 2차 차분에 이르러 비로소 평평해졌습니다. 실무에서는 두 번을 초과해서 차분해야 하는 경우가 드뭅니다.
증강 디키-풀러 검정(Augmented Dickey-Fuller, ADF test)은 정상성을 판단하는 표준적인 통계 검정입니다. 귀무가설(null hypothesis)은 "이 시계열은 비정상이다"이며, p-value가 0.05보다 낮으면 귀무가설을 기각하고 정상이라고 결론지을 수 있습니다. 이 차시에서는 ADF를 직접 처음부터 구현하지는 않습니다. 점근 분포표(asymptotic distribution table)가 필요해 범위를 벗어나기 때문입니다. 대신 코드에서는 롤링 통계(rolling statistics) 방식을 활용해 실용적으로 시각적인 확인을 합니다.
자기상관(Autocorrelation)
자기상관(autocorrelation)은 시간 t에서의 값이 k 시점 과거인 t-k 시점의 값과 얼마나 상관관계(correlation)를 갖는지를 측정합니다. 자기상관 함수(Autocorrelation Function, ACF)는 각 지연(lag) k에 대한 상관관계를 그려서 보여줍니다.
ACF가 알려주는 것은 다음과 같습니다.
- 시계열이 얼마나 오랫동안 기억을 유지하는지를 보여줍니다. 지연 5 이후 ACF가 0에 가까워진다면, 5단계보다 더 오래된 값은 비교적 덜 중요합니다.
- 계절성이 존재하는지 알려줍니다. 월 단위 데이터에서 지연 12에 뾰족한 값(spike)이 보인다면 연간 계절성(yearly seasonality)이 있다는 신호입니다.
- 지연 특성(lag feature)을 몇 개나 만들어야 할지 가늠하게 해줍니다. ACF 값이 무시할 수 있을 만큼 작아지는 지점까지의 지연을 사용합니다.
부분 자기상관 함수(Partial Autocorrelation Function, PACF)는 간접적인 상관관계를 제거해 줍니다. 오늘과 3일 전의 값이 서로 상관이 있는 이유가 단지 두 값이 모두 어제의 값과 상관되어 있기 때문이라면, 지연 3에서 PACF는 0에 가깝게 나오는 반면 ACF는 여전히 0이 아닐 수 있습니다.
지연 특성(Lag Feature): 시계열을 지도학습(Supervised Learning)으로 바꾸기
표준 머신러닝 모델은 특성 행렬(feature matrix) X와 타깃(target) y를 필요로 합니다. 그런데 시계열은 값 하나로 이루어진 단일 열(column)만을 제공합니다. 이 둘을 이어주는 다리 역할을 하는 것이 바로 지연 특성(lag feature)입니다.
시계열 [10, 12, 14, 13, 15]에서 지연 1과 지연 2 특성을 만들어 보면 다음과 같이 됩니다.
| lag_2 | lag_1 | target |
|---|
| 10 | 12 | 14 |
| 12 | 14 | 13 |
| 14 | 13 | 15 |
이렇게 만들고 나면 평범한 표준 회귀 문제(standard regression problem)가 됩니다. 선형 회귀(linear regression), 랜덤 포레스트(random forest), 그래디언트 부스팅(gradient boosting)을 비롯한 어떤 머신러닝 모델이라도 지연값을 가지고 타깃을 예측할 수 있습니다.
추가로 만들어 볼 수 있는 특성은 다음과 같습니다.
- 롤링 통계(Rolling statistics): 최근 k개 값의 평균, 표준편차, 최솟값, 최댓값
- 달력 특성(Calendar features): 요일, 월, 휴일 여부, 주말 여부
- 차분값(Differenced values): 직전 시점 대비 변화량
- 확장 통계(Expanding statistics): 누적 평균, 누적합
- 비율 특성(Ratio features): 현재 값을 롤링 평균으로 나눈 비율
- 상호작용 특성(Interaction features):
lag_1 * day_of_week처럼 두 특성을 곱한 값
지연을 몇 개나 쓰는 게 좋을까요? 이때 ACF를 길잡이로 활용합니다. ACF가 지연 10까지 유의미하다면 최소 10개의 지연을 씁니다. 주간 계절성(weekly seasonality)이 있으면 지연 7, 가능하다면 지연 14까지도 함께 포함합니다. 지연이 많아지면 그만큼 모델이 활용할 이력은 풍부해지지만, 특성의 수가 늘어나 과적합(overfitting) 위험도 같이 커집니다.
타깃 정렬 함정(Target alignment trap). 지연 특성을 만들 때 타깃은 시점 t의 값이어야 하고, 모든 특성은 시점 t-1 이전의 값만 사용해야 합니다. 실수로 시점 t의 값을 특성에 포함하게 되면, 완벽해 보이는 예측기가 만들어지지만 정작 실제 운영 환경에서는 전혀 쓸 수 없는 모델이 되고 맙니다. 시계열 특성 엔지니어링(feature engineering)에서 가장 자주 마주치는 버그입니다.
워크 포워드 검증(Walk-Forward Validation)
이번 차시에서 가장 중요한 개념입니다. 표준적인 k-겹 교차검증(k-fold cross-validation)은 표본을 무작위로 학습용과 테스트용으로 배정합니다. 시계열에서는 이것이 곧 미래 정보의 누수(future leakage)로 이어집니다.
flowchart TD
subgraph WRONG["Random Split (잘못됨)"]
direction LR
W1[Jan] --> W2[Mar]
W2 --> W3[Feb]
W3 --> W4[May]
W4 --> W5[Apr]
style W1 fill:#fdd
style W3 fill:#fdd
style W5 fill:#fdd
style W2 fill:#dfd
style W4 fill:#dfd
end
subgraph RIGHT["Walk-Forward (올바름)"]
direction LR
R1["Train: Jan-Mar"] --> R2["Test: Apr"]
R3["Train: Jan-Apr"] --> R4["Test: May"]
R5["Train: Jan-May"] --> R6["Test: Jun"]
style R1 fill:#dfd
style R2 fill:#fdd
style R3 fill:#dfd
style R4 fill:#fdd
style R5 fill:#dfd
style R6 fill:#fdd
end
워크 포워드 검증(walk-forward validation)은 다음의 흐름으로 진행합니다.
- 시점 t까지의 데이터로 학습합니다.
- 시점 t+1을 예측하거나, 다중 스텝(multi-step) 예측이라면 t+1부터 t+k까지를 예측합니다.
- 창(window)을 한 칸 앞으로 이동시킵니다.
- 위 과정을 반복합니다.
각 테스트 폴드(test fold)는 모든 학습 데이터보다 뒤에 위치한 데이터만 포함하므로 미래 누수가 발생할 여지가 없습니다. 이렇게 평가하면 실제 운영 환경에서 모델이 어떻게 작동할지를 훨씬 정직하게 추정할 수 있습니다.
확장 창(Expanding window)은 모든 과거 데이터를 학습에 사용해 학습 창의 크기가 점점 커집니다. 슬라이딩 창(Sliding window)은 고정된 크기의 학습 창이 앞으로 이동해 가는 방식입니다. 오래된 데이터도 여전히 의미가 있다고 본다면 확장 창을, 세상이 빠르게 변해 오래된 데이터가 오히려 모델에 해롭다고 본다면 슬라이딩 창을 사용합니다.
ARIMA 직관
ARIMA는 고전적인 시계열 모델입니다. 세 가지 구성요소로 이루어져 있습니다.
- AR(Autoregressive, 자기회귀): 과거의 값으로부터 예측합니다. AR(p)는 마지막 p개의 값을 사용합니다.
- I(Integrated, 누적/차분): 정상성을 확보하기 위해 차분을 적용합니다. I(d)는 d회 차분합니다.
- MA(Moving Average, 이동평균): 과거의 예측 오차(forecast error)를 사용해 예측합니다. MA(q)는 마지막 q개의 오차를 사용합니다.
ARIMA(p, d, q)는 이 세 가지를 결합한 모델입니다. p, d, q 값은 ACF/PACF 분석을 통해 고르거나 자동 탐색(auto-ARIMA)으로 선택합니다.
이 차시에서는 ARIMA를 처음부터 구현하지 않습니다. 수치 최적화(numerical optimization)가 필요해 범위를 벗어나기 때문입니다. 핵심은 각 구성요소가 무슨 역할을 하는지 이해해서, ARIMA의 결과를 해석하고 언제 사용해야 할지 판단할 수 있게 되는 것입니다.
무엇을 언제 쓸까
| 접근 방식 | 적합한 경우 | 계절성 처리 | 외부 특성 처리 |
|---|
| 지연 특성(Lag features) + 머신러닝 | 외부 특성이 많은 표 형식(tabular) 문제 | 달력 특성(calendar feature)으로 처리 | 가능 |
| ARIMA | 단일 단변량 시계열(univariate series), 단기 예측 | SARIMA 변형으로 처리 | 제한적, ARIMAX 활용 |
| 지수 평활(Exponential smoothing) | 단순한 추세 + 계절성 | 가능, Holt-Winters | 불가 |
| Prophet | 비즈니스 예측, 휴일이 중요한 경우 | 푸리에 항(Fourier terms)으로 가능 | 제한적 |
| 신경망(Neural networks, LSTM, Transformer) | 긴 시퀀스, 시계열이 다수인 경우 | 학습을 통해 처리 | 가능 |
대부분의 실무 문제에서는 지연 특성과 그래디언트 부스팅의 조합이 가장 강력한 출발점이 됩니다. 외부 특성을 자연스럽게 다룰 수 있고, 정상성을 반드시 요구하지 않으며, 디버깅(debugging)도 비교적 쉽기 때문입니다.
예측 기간(Forecasting Horizon)과 전략
단일 스텝 예측(single-step forecasting)은 한 시점만 앞을 내다보고 예측합니다. 다중 스텝 예측(multi-step forecasting)은 여러 시점을 함께 예측합니다. 다중 스텝 예측의 전략은 세 가지가 있습니다.
재귀적 방식(Recursive/iterated): 한 시점을 예측한 뒤, 그 예측값을 다음 시점의 입력으로 다시 사용합니다. 단순하지만 오차가 점점 누적된다는 단점이 있습니다. 각 예측이 이전 예측을 입력으로 받기 때문에, 작은 실수가 복합적으로 커질 수 있습니다.
직접 방식(Direct): 예측 기간(horizon)마다 별도의 모델을 학습합니다. 1번 모델은 t+1을, 5번 모델은 t+5를 예측하는 식입니다. 오차가 누적되지는 않지만, 모델마다 학습 표본이 줄어들고 모델들 사이에 정보를 공유할 수 없다는 한계가 있습니다.
다중 출력 방식(Multi-output): 모든 예측 기간을 하나의 모델이 동시에 출력하도록 학습합니다. 여러 예측 기간 사이에 정보를 공유할 수 있지만, 다중 출력을 지원하는 모델이나 사용자 정의 손실 함수(custom loss function)가 필요합니다.
대부분의 실무에서는 1~5 시점의 짧은 예측 기간은 재귀적 방식으로 시작하고, 더 긴 예측 기간은 직접 방식으로 시작합니다.
시계열(Time Series)에서 흔한 실수
| 실수 | 왜 발생하는가 | 해결 방법 |
|---|
| 무작위 학습/테스트 분할 | 표준 머신러닝의 습관 | 워크 포워드 검증이나 시간 기반 분할(temporal split) 사용 |
| 미래 특성(future feature) 사용 | 시점 t의 특성을 실수로 포함 | 모든 특성의 시간 정렬(temporal alignment)을 점검 |
| 계절성에 과적합 | 모델이 달력 패턴을 외워버림 | 테스트 세트에 한 번의 전체 계절 주기(full seasonal cycle)를 남겨둠 |
| 스케일 변화 무시 | 매출은 두 배가 됐지만 패턴은 그대로 | 절댓값 대신 백분율 변화(percentage change)를 모델링 |
| 너무 많은 지연 특성 | "이력이 많을수록 좋다"는 믿음 | ACF로 관련 있는 지연만 선별 |
| 차분 미적용 | "모델이 알아서 처리할 것"이라는 가정 | 트리 모델은 추세를 다룰 수 있지만, 선형 모델은 정상성이 필요 |
만들어보기
code/time_series.py는 핵심 구성 요소(building block)들을 처음부터 직접 구현합니다.
지연 특성 생성기(Lag Feature Creator)
def make_lag_features(series, n_lags):
n = len(series)
X = np.full((n, n_lags), np.nan)
for lag in range(1, n_lags + 1):
X[lag:, lag - 1] = series[:-lag]
valid = ~np.isnan(X).any(axis=1)
return X[valid], series[valid]
1차원 시계열을 특성 행렬로 변환하는 함수입니다. 각 행(row)은 최근 n_lags개의 값을 특성으로 가지고, 현재 시점의 값을 타깃으로 가집니다.
워크 포워드 교차검증(Walk-Forward Cross-Validation)
def walk_forward_split(n_samples, n_splits=5, min_train=50):
assert min_train < n_samples, "min_train must be less than n_samples"
step = max(1, (n_samples - min_train) // n_splits)
for i in range(n_splits):
train_end = min_train + i * step
test_end = min(train_end + step, n_samples)
if train_end >= n_samples:
break
yield slice(0, train_end), slice(train_end, test_end)
각 분할에서 학습 데이터가 반드시 테스트 데이터보다 시간상 앞에 오도록 보장합니다. 학습 창(training window)은 폴드(fold)가 진행될수록 점점 확장됩니다.
단순 자기회귀 모델(Simple Autoregressive Model)
순수한 AR 모델은 결국 지연 특성에 대해 수행하는 선형 회귀와 같습니다.
class SimpleAR:
def __init__(self, n_lags=5):
self.n_lags = n_lags
self.weights = None
self.bias = None
def fit(self, series):
X, y = make_lag_features(series, self.n_lags)
X_b = np.column_stack([np.ones(len(X)), X])
theta = np.linalg.lstsq(X_b, y, rcond=None)[0]
self.bias = theta[0]
self.weights = theta[1:]
return self
Lesson 02에서 다룬 선형 회귀와 개념적으로 동일하지만, 같은 변수의 시간 지연 버전(time-lagged version)에 적용한다는 점만 다릅니다.
정상성 확인(Stationarity Check)
코드에서는 롤링 통계를 계산해 정상성을 시각적이면서도 수치적으로 평가합니다. 롤링 평균이 표류하거나 롤링 표준편차가 변동한다면 비정상으로 판단하고, 차분을 적용한 뒤 다시 확인합니다.
또한 코드는 시계열의 앞 절반과 뒤 절반도 비교합니다. 두 구간의 평균 차이가 표준편차의 절반보다 크거나, 분산의 비율이 2배를 넘으면 해당 시계열을 비정상으로 표시합니다.
자기상관(Autocorrelation)
def autocorrelation(series, max_lag=20):
n = len(series)
mean = series.mean()
var = series.var()
acf = np.zeros(max_lag + 1)
for k in range(max_lag + 1):
cov = np.mean((series[:n-k] - mean) * (series[k:] - mean))
acf[k] = cov / var if var > 0 else 0
return acf
사용하기
sklearn에서는 지연 특성을 어떤 회귀기(regressor)와도 곧바로 함께 사용할 수 있습니다.
from sklearn.linear_model import Ridge
from sklearn.ensemble import GradientBoostingRegressor
X, y = make_lag_features(series, n_lags=10)
for train_idx, test_idx in walk_forward_split(len(X)):
model = Ridge(alpha=1.0)
model.fit(X[train_idx], y[train_idx])
predictions = model.predict(X[test_idx])
ARIMA를 사용할 때는 statsmodels를 활용합니다.
from statsmodels.tsa.arima.model import ARIMA
model = ARIMA(train_series, order=(5, 1, 2))
fitted = model.fit()
forecast = fitted.forecast(steps=30)
sklearn TimeSeriesSplit
sklearn은 워크 포워드 검증을 구현한 TimeSeriesSplit을 기본으로 제공합니다.
from sklearn.model_selection import TimeSeriesSplit
tscv = TimeSeriesSplit(n_splits=5)
for train_index, test_index in tscv.split(X):
X_train, X_test = X[train_index], X[test_index]
y_train, y_test = y[train_index], y[test_index]
model.fit(X_train, y_train)
score = model.score(X_test, y_test)
cross_val_score와도 함께 쓸 수 있습니다.
from sklearn.model_selection import cross_val_score
scores = cross_val_score(model, X, y, cv=TimeSeriesSplit(n_splits=5))
print(f"평균 점수: {scores.mean():.4f} +/- {scores.std():.4f}")
평가 지표(Evaluation Metrics)
시계열 예측에는 회귀 문제에서 쓰이는 평가 지표를 그대로 활용하되, 시간 맥락을 함께 고려합니다.
- MAE(Mean Absolute Error, 평균 절대 오차):
|y_true - y_pred|의 평균입니다. 원래 단위 그대로 해석할 수 있어 직관적입니다. "평균적으로 예측이 3.2도 정도 빗나간다"처럼 말할 수 있습니다.
- RMSE(Root Mean Squared Error, 평균 제곱근 오차): MSE의 제곱근입니다. MAE보다 큰 오차에 더 큰 벌점을 부여합니다. 작은 오차 여러 개보다 큰 오차 하나가 더 치명적인 상황에서 사용합니다.
- MAPE(Mean Absolute Percentage Error, 평균 절대 백분율 오차):
|error / true_value| * 100의 평균입니다. 스케일 독립적(scale-independent)이라 서로 다른 시계열을 비교할 때 편리합니다. 다만 참값이 0이면 정의되지 않는다는 한계가 있습니다.
- 나이브 기준선 비교(Naive baseline comparison): 항상 단순한 기준선과 비교합니다. 계절성 나이브 기준선(seasonal naive baseline)은 한 주기(period) 전의 값을 그대로 예측값으로 사용합니다. 만든 모델이 나이브 기준선조차 이기지 못한다면 어딘가 잘못된 것입니다.
롤링 특성(Rolling Features)
코드는 지연 특성에 롤링 통계를 더하는 방법도 함께 보여줍니다. 7일, 14일 창에서 계산한 평균, 표준편차, 최솟값, 최댓값은 최근의 추세와 변동성에 대한 정보를 모델에 전달합니다. 롤링 평균이 점차 상승한다면 상승 추세를 시사하고, 롤링 표준편차가 커지고 있다면 변동성이 커지고 있음을 시사합니다. 이런 패턴은 트리 기반 모델(tree-based model)은 잘 학습하지만, 선형 모델만으로는 포착하기 어렵습니다.
산출물 만들기
이 차시의 최종 산출물은 다음과 같습니다.
outputs/prompt-time-series-advisor.md: 시계열 문제를 구조화(framing)하기 위한 프롬프트(prompt)
code/time_series.py: 지연 특성, 워크 포워드 검증, AR 모델, 정상성 확인 기능을 담은 구현
반드시 이겨야 하는 기준선(Baselines You Must Beat)
모델을 만들기에 앞서 기준선(baseline)을 먼저 세웁니다.
- 마지막 값 기준선(Last value, persistence). 내일의 값이 오늘의 값과 같다고 예측합니다. 많은 시계열에서 이 단순한 예측을 이기는 것이 의외로 매우 어렵습니다.
- 계절성 나이브(Seasonal naive). 오늘의 값이 지난주 같은 요일이나 작년 같은 시기의 값과 같다고 예측합니다. 이 기준선을 이기지 못한다면, 계절성을 넘어서는 유용한 패턴을 모델이 학습하지 못했다는 뜻입니다.
- 이동평균(Moving average). 최근 k개 값의 평균을 예측값으로 사용합니다. 잡음(noise)을 부드럽게 평활화해 주지만, 급격한 변화는 포착하지 못합니다.
만일 화려해 보이는 머신러닝 모델이 계절성 나이브 기준선에조차 진다면, 어딘가에 버그가 있을 가능성이 높습니다. 흔한 원인으로는 특성에 미래 정보가 누수된 경우, 평가 방법 자체가 잘못된 경우, 혹은 그 시계열이 실제로는 무작위에 가까워 본질적으로 예측이 불가능한 경우 등이 있습니다.
실전 팁
-
시각화부터 시작합니다. 모델링에 들어가기 전에 원본 시계열을 그려봅니다. 추세, 계절성, 이상치(outlier), 구조적 단절(structural break)을 눈으로 찾아냅니다. 30초간의 시각적 점검이 한 시간 동안의 자동 분석보다 더 많은 것을 알려주는 경우가 많습니다.
-
먼저 차분하고 그다음 모델링합니다. 명확한 추세가 보인다면 지연 특성을 만들기 전에 차분을 먼저 적용합니다. 트리 기반 모델은 추세를 어느 정도 다룰 수 있지만 선형 모델은 그렇지 못하며, 차분 자체가 모델에 해를 끼치는 경우는 거의 없습니다.
-
테스트 세트에 최소 한 번의 전체 계절 주기를 남겨둡니다. 주간 계절성이 있다면 테스트 세트에 최소 한 주의 데이터가 있어야 하고, 월간 계절성이 있다면 최소 한 달의 데이터가 필요합니다. 그렇지 않으면 모델이 계절 패턴을 제대로 포착했는지 평가할 수 없습니다.
-
운영 환경에서도 모니터링을 이어갑니다. 시간이 흐르며 세상이 변하면 모델의 성능도 점차 저하됩니다. 예측 오차를 롤링 기준으로 꾸준히 추적하고, 오차가 커지기 시작하면 최근 데이터로 모델을 다시 학습시킵니다.
-
국면 변화(regime change)를 경계합니다. 팬데믹 이전 데이터로 학습한 모델은 팬데믹 이후의 행동 양상을 제대로 예측하지 못할 수 있습니다. 이미 알려진 국면 변화 지표를 특성으로 추가하거나, 오래된 데이터를 잊는 슬라이딩 창 방식을 사용하는 것이 좋습니다.
-
왜도(skew)가 큰 시계열은 로그 변환을 고려합니다. 매출, 가격, 개수와 같은 값은 흔히 오른쪽으로 치우친(right-skewed) 분포를 보입니다. 로그를 취하면 분산이 안정되고, 곱셈 형태의 패턴이 덧셈 형태로 바뀌어 선형 모델이 다루기 한결 쉬워집니다. 로그 공간(log space)에서 예측한 다음, 지수 함수로 되돌려 원래 단위로 변환합니다.
연습문제
-
정상성 실험(Stationarity experiment). 선형 추세를 가진 시계열을 하나 생성합니다. 롤링 통계로 정상성을 확인한 뒤, 1차 차분을 적용하고 다시 정상성을 확인합니다. 2차 추세를 정상화하려면 몇 번의 차분이 필요할까요?
-
지연 선택(Lag selection). 주기가 7인 계절성 시계열에서 ACF를 계산해 봅니다. 어떤 지연에서 자기상관이 가장 높게 나타나나요? 1부터 7까지의 연속된 지연을 모두 쓰는 대신, 가장 자기상관이 높은 지연만 골라 특성을 만들어 봅니다. 정확도는 더 좋아지나요?
-
워크 포워드 검증과 무작위 분할 비교. 지연 특성으로 릿지 회귀(Ridge regression)를 학습합니다. 무작위 80/20 분할과 워크 포워드 검증으로 각각 평가해 봅니다. 무작위 분할은 성능을 얼마나 과대평가하는지 확인합니다.
-
특성 엔지니어링(Feature engineering). 지연 특성에 창 크기 7의 롤링 평균, 창 크기 7의 롤링 표준편차, 그리고 요일 특성을 추가해 봅니다. 워크 포워드 검증으로 추가 특성을 넣었을 때와 빼고 학습했을 때를 비교합니다.
-
다중 스텝 예측(Multi-step forecasting). AR 모델을 1 시점 앞이 아니라 5 시점 앞을 예측하도록 수정합니다. 재귀적 전략과 직접 전략을 비교해 봅니다. 어느 쪽이 더 정확한가요?
핵심 용어
| 용어 | 흔한 설명 | 실제 의미 |
|---|
| 정상성(Stationarity) | "통계가 시간에 따라 변하지 않음" | 평균, 분산, 자기상관 구조가 시간에 걸쳐 일정하게 유지되는 시계열 |
| 차분(Differencing) | "연속한 값을 빼기" | 추세를 제거하고 정상성을 얻기 위해 y[t] - y[t-1]을 계산하는 작업 |
| 자기상관(Autocorrelation, ACF) | "시계열이 자기 자신과 얼마나 닮았는가" | 시계열과 그 시계열을 지연시킨 사본 사이의 상관관계 |
| 부분 자기상관(Partial Autocorrelation, PACF) | "직접적인 상관만" | 더 짧은 지연들의 효과를 제거한 뒤, 지연 k에서 측정한 자기상관 |
| 지연 특성(Lag feature) | "과거 값을 입력으로" | y[t-1], y[t-2], ..., y[t-k]를 특성으로 사용해 y[t]를 예측하는 기법 |
| 워크 포워드 검증(Walk-forward validation) | "시간 순서를 지키는 교차검증" | 학습 데이터가 항상 테스트 데이터보다 시간상 앞에 오도록 하는 평가 방식 |
| ARIMA | "고전적인 시계열 모델" | AutoRegressive Integrated Moving Average. 과거 값(AR), 차분(I), 과거 오차(MA)를 결합한 모델 |
| 계절성(Seasonality) | "반복되는 달력 패턴" | 일·주·연 단위 등 달력 주기와 연결된, 규칙적이고 예측 가능한 주기 |
| 추세(Trend) | "장기적인 방향" | 시계열의 수준이 시간에 걸쳐 지속적으로 증가하거나 감소하는 흐름 |
| 확장 창(Expanding window) | "모든 과거 이력을 사용" | 폴드가 진행될수록 학습 세트가 점점 커지는 워크 포워드 방식 |
| 슬라이딩 창(Sliding window) | "고정 길이의 이력만" | 일정한 길이의 학습 창이 앞으로 이동해 가는 워크 포워드 방식 |
더 읽을거리