Tüm rehberler
Rehber Yapay Zeka Zaman Serisi

Zaman Serisi Tahmin —
Prophet & TFT.

ARIMA'dan Prophet'e, NeuralProphet'ten Temporal Fusion Transformer'a. ACF/PACF analizi, STL ayrıştırma, Bayesian örnekleme, anomali tespiti ve nixtla StatsForecast ile çok model yönetimi.

00 Zaman Serisi Nedir

Zaman serisi, belirli aralıklarla ölçülen gözlemler dizisidir. Temel bileşenler: trend, mevsimsellik ve gürültü.

Bir zaman serisi üç ana bileşene ayrılır: trend, uzun dönemli yükseliş veya düşüşü temsil eder; elektrik tüketimindeki yıllık artış veya nüfus büyümesi buna örnektir. Mevsimsellik (seasonality), sabit ve bilinen periyotlarla tekrarlayan dalgalanmalardır; haftalık alışveriş örüntüleri veya yaz aylarındaki turizm artışı gibi. Gürültü (noise) ise modellenemeyen rastgele varyasyondur.

Additif ayrışma modeli Y(t) = T(t) + S(t) + R(t) şeklinde yazılır. Mevsimsellik büyüklüğü trendle orantılı olarak artıyorsa çarpımsal model Y(t) = T(t) × S(t) × R(t) daha uygundur. Bu farkı anlamak model seçiminde kritiktir: log dönüşümü çarpımsal seriyi additife çevirir.

Durağanlık (stationarity), serinin istatistiksel özelliklerinin (ortalama, varyans, otokorelasyon) zaman içinde değişmemesi anlamına gelir. ADF (Augmented Dickey-Fuller) testi p < 0.05 ise seri durağandır. KPSS testi ise tersini test eder: p > 0.05 durağanlığa işaret eder. İki test birlikte kullanılmalıdır.

decompose.py
import pandas as pd
import numpy as np
from statsmodels.tsa.seasonal import seasonal_decompose, STL
from statsmodels.tsa.stattools import adfuller, kpss

# ── Örnek veri yükle ─────────────────────────────────────────
df = pd.read_csv("energy.csv", parse_dates=["timestamp"],
                  index_col="timestamp", freq="h")
series = df["consumption_kwh"]

# ── Additif ayrışma ──────────────────────────────────────────
result = seasonal_decompose(series, model="additive", period=24)
result.plot()  # trend, seasonal, residual grafikleri

# ── ADF durağanlık testi ─────────────────────────────────────
adf_stat, p_val, _, _, crit, _ = adfuller(series.dropna())
if p_val < 0.05:
    print(f"Seri DURAĞAN (ADF p={p_val:.4f})")
else:
    diff1 = series.diff().dropna()
    _, p2, *_ = adfuller(diff1)
    print(f"1. fark sonrası: p={p2:.4f}")

# ── Çarpımsal vs additif karar ───────────────────────────────
rolling_std = series.rolling(30 * 24).std()
rolling_mean = series.rolling(30 * 24).mean()
corr = rolling_std.corr(rolling_mean)
model_type = "multiplicative" if corr > 0.7 else "additive"
print(f"Önerilen model: {model_type} (korelasyon={corr:.2f})")
BileşenTanımÖrnekModelleme
TrendUzun dönemli yönelimYıllık büyümePolinom, spline
MevsimsellikSabit periyodik tekrarHaftalık satışlarFourier, dummy
DöngüsellikDeğişken periyotEkonomik konjonktürZor, LSTM
GürültüAçıklanamayan varyansAnlık dalgalanmaModellenmez

01 Keşifsel Analiz — ACF/PACF & STL

ACF ve PACF grafikleri ARIMA parametrelerini belirler; STL ayrıştırma güçlü mevsimsellik tespiti sağlar.

ACF (Autocorrelation Function), bir serinin kendi gecikmeli versiyonuyla korelasyonunu ölçer. Lag-k ACF değeri, t zamanındaki değer ile t-k zamanındaki değer arasındaki lineer ilişkiyi gösterir. MA(q) modeli seçiminde kullanılır: ACF lag-q'dan sonra kesiliyorsa MA(q) uygundur.

PACF (Partial Autocorrelation Function) ise aradaki lagların etkisi kontrol edildikten sonra kalan direkt korelasyonu ölçer. AR(p) seçiminde kullanılır: PACF lag-p'den sonra kesiliyorsa AR(p) uygundur. Güven bantları (±1.96/√n) dışına çıkan laglar istatistiksel olarak anlamlıdır.

STL (Seasonal and Trend decomposition using Loess), klasik ayrışmadan üstündür: birden fazla mevsimselliği destekler, sağlama (robust) hale getirilebilir, eksik veriyle çalışabilir. Özellikle günlük + haftalık gibi çift mevsimsellik için MSTL (Multiple STL) kullanılır.

acf_pacf_stl.py
from statsmodels.graphics.tsaplots import plot_acf, plot_pacf
from statsmodels.tsa.seasonal import STL, MSTL
import matplotlib.pyplot as plt

# ── ACF / PACF ───────────────────────────────────────────────
fig, axes = plt.subplots(2, 1, figsize=(12, 8))
plot_acf(series, lags=72, ax=axes[0], title="ACF (72 lag)")
plot_pacf(series, lags=72, ax=axes[1], method="ywm",
          title="PACF (72 lag)")
plt.tight_layout()

# ── STL ayrıştırma (tek mevsim, periyot=24) ──────────────────
stl = STL(series, period=24, robust=True)
res = stl.fit()
# res.trend, res.seasonal, res.resid

# ── MSTL (çift mevsim: günlük 24 + haftalık 24*7) ────────────
mstl = MSTL(series, periods=[24, 24 * 7])
mres = mstl.fit()

# ── Mevsimsel güç testi ──────────────────────────────────────
def seasonal_strength(trend, seasonal, resid):
    """Fs = max(0, 1 - Var(R) / Var(S+R))"""
    sr = seasonal + resid
    return max(0, 1 - np.var(resid) / np.var(sr))

fs = seasonal_strength(res.trend, res.seasonal, res.resid)
print(f"Mevsimsel güç: {fs:.3f}")  # > 0.6 güçlü mevsimsellik

# ── Fark alınmış seri için ACF ───────────────────────────────
series_diff = series.diff(24).diff(1).dropna()
fig2, axes2 = plt.subplots(2, 1, figsize=(12, 6))
plot_acf(series_diff, lags=48, ax=axes2[0])
plot_pacf(series_diff, lags=48, ax=axes2[1])
KURAL

ACF üstel azalıyor + PACF lag-p'de kesiyor → AR(p). ACF lag-q'da kesiyor + PACF üstel azalıyor → MA(q). İkisi de üstel azalıyor → ARMA(p,q). Pratik: auto_arima ile otomatik seçim çoğu durumda yeterlidir.

02 ARIMA ve SARIMA — İstatistiksel Baseline

ARIMA(p,d,q) zaman serisi tahminin temel modeli; SARIMA mevsimsel eklenti ile gerçek dünya verilerine uyarlanır.

ARIMA üç bileşenden oluşur: AR(p) — geçmiş p değerin ağırlıklı ortalaması; I(d) — d kez fark alarak durağanlaştırma; MA(q) — geçmiş q hata teriminin ağırlıklı ortalaması. SARIMA(p,d,q)(P,D,Q)m, aynı yapıyı mevsimsel lag m üzerinde tekrarlar. Örneğin aylık veri için m=12, saatlik veri için m=24.

Model karşılaştırma kriterleri: AIC (Akaike Information Criterion) ve BIC (Bayesian Information Criterion) — daha düşük değer daha iyi. BIC parametre sayısını daha ağır cezalandırır. pmdarima.auto_arima stepwise arama ile en iyi parametreleri otomatik bulur.

sarima.py
import pmdarima as pm
from statsmodels.tsa.statespace.sarimax import SARIMAX
from sklearn.metrics import mean_absolute_percentage_error
import warnings; warnings.filterwarnings("ignore")

# ── Train/test split ─────────────────────────────────────────
n_test = 7 * 24  # son 1 hafta test
train, test = series[:-n_test], series[-n_test:]

# ── auto_arima ile otomatik parametre seçimi ─────────────────
auto_model = pm.auto_arima(
    train,
    seasonal=True, m=24,      # günlük mevsimsellik
    stepwise=True,
    information_criterion="aic",
    max_p=3, max_q=3,
    max_P=2, max_Q=2,
    d=None, D=None,            # otomatik fark derecesi
    trace=True, error_action="ignore"
)
print(auto_model.summary())
print(f"En iyi order: {auto_model.order}, seasonal: {auto_model.seasonal_order}")

# ── Manuel SARIMAX (kovaryatlarla) ───────────────────────────
exog_train = train_df[["temperature", "is_holiday"]].values
exog_test  = test_df [["temperature", "is_holiday"]].values

sarima = SARIMAX(
    train,
    exog=exog_train,
    order=(1, 1, 1),
    seasonal_order=(1, 1, 1, 24),
    enforce_stationarity=False
)
sarima_fit = sarima.fit(disp=False)

# ── Tahmin ve değerlendirme ───────────────────────────────────
forecast = sarima_fit.forecast(steps=n_test, exog=exog_test)
mape = mean_absolute_percentage_error(test, forecast)
print(f"SARIMA MAPE: {mape:.3%}")

# ── Residual diagnostics ─────────────────────────────────────
sarima_fit.plot_diagnostics(figsize=(14, 8))
# Ljung-Box testi: p > 0.05 → artıklar beyaz gürültü (iyi)
SINIR

SARIMA'nın zayıf noktaları: büyük periyotlarda (m=24×7=168) hesaplama maliyeti patlar; çoklu mevsimselliği aynı anda modelleyemez; non-lineer ilişkileri yakalayamaz. Bu durumlarda Prophet veya derin öğrenme modelleri tercih edilir.

03 Facebook Prophet — Model Yapısı ve Python API

Prophet, additif regresyon modeli olarak trend + mevsimsellik + tatil etkisini ayrı ayrı modeller ve Bayesian çıkarımla belirsizlik aralığı verir.

Prophet'in temel denklemi: y(t) = g(t) + s(t) + h(t) + ε_t. g(t) trend — lineer büyüme (changepoint tespiti ile) veya lojistik büyüme (kapasite sınırı varsa). s(t) mevsimsellik — Fourier serisi ile modellenir; yıllık için varsayılan N=10 terim, haftalık için N=3. h(t) tatil efektleri — kullanıcı tanımlı özel günler. Changepoint'ler, trendin ani yön değiştirdiği noktalardır; otomatik tespiti veya manuel tanımlama mümkündür.

prophet_basic.py
from prophet import Prophet
import pandas as pd

# ── Prophet formatı: ds (datetime) + y (hedef) ───────────────
df_prophet = df.reset_index().rename(
    columns={"timestamp": "ds", "consumption_kwh": "y"}
)

# ── Temel model ──────────────────────────────────────────────
m = Prophet(
    changepoint_prior_scale=0.05,  # trend esnekliği
    seasonality_prior_scale=10.0,  # mevsimsellik gücü
    holidays_prior_scale=10.0,
    seasonality_mode="additive",    # ya da "multiplicative"
    yearly_seasonality=True,
    weekly_seasonality=True,
    daily_seasonality=True,
    interval_width=0.95,           # %95 belirsizlik bandı
)

# ── Özel mevsimsellik ekle ───────────────────────────────────
m.add_seasonality(
    name="hourly", period=1, fourier_order=5
)

# ── Tatil günleri ────────────────────────────────────────────
tr_holidays = pd.DataFrame({
    "holiday": ["cumhuriyet", "ramazan_bayrami", "kurban_bayrami"],
    "ds": pd.to_datetime(["2024-10-29", "2024-04-10", "2024-06-16"]),
    "lower_window": [0, -1, -1],   # tatil öncesi etki
    "upper_window": [1,  2,  3],   # tatil sonrası etki
})
m.add_country_holidays(country_name="TR")

# ── Kovaryat (regressor) ekleme ──────────────────────────────
m.add_regressor("temperature", standardize=True)

m.fit(df_prophet[:-168])  # son 1 hafta test için ayır

# ── Gelecek DataFrame ────────────────────────────────────────
future = m.make_future_dataframe(periods=168, freq="h")
future["temperature"] = future_temp_values  # regressor doldur

forecast = m.predict(future)
# forecast: yhat, yhat_lower, yhat_upper, trend, weekly, daily...
m.plot(forecast)
m.plot_components(forecast)  # trend + mevsimsellik ayrı grafik

04 Prophet ile Tahmin — Hiperparametre Ayarı & Bayesian Sampling

Prophet'in Stan tabanlı Bayesian motoru, belirsizlik aralıkları üretir ve MCMC örneklemesi ile daha doğru posterior dağılım verir.

Prophet varsayılan olarak MAP (Maximum A Posteriori) tahmini kullanır — hızlıdır ama belirsizlik aralıkları yaklaşık olur. mcmc_samples=300 ile tam Bayesian örnekleme yapılabilir; bu daha gerçekçi aralıklar üretir ama eğitim süresi 10-50 kat artar. Üretimde MAP tercih edilir.

Kritik hiperparametreler: changepoint_prior_scale (0.001–0.5, varsayılan 0.05) — düşük değer düz trend, yüksek değer aşırı esneklik; seasonality_prior_scale (0.01–10) — mevsimsellik genişliği; changepoint_range (varsayılan 0.8) — verinin ilk %80'inde changepoint arar. Cross-validation ile grid search yapılabilir.

prophet_tuning.py
from prophet.diagnostics import cross_validation, performance_metrics
from prophet.plot import plot_cross_validation_metric
import itertools, numpy as np

# ── Cross-validation ─────────────────────────────────────────
# initial: ilk eğitim penceresi | period: yeni cut her N günde
# horizon: tahmin ufku
df_cv = cross_validation(
    m,
    initial="365 days",
    period="30 days",
    horizon="7 days",
    parallel="processes"
)
df_perf = performance_metrics(df_cv)
print(df_perf[["horizon", "mae", "mape", "coverage"]].tail(10))

# ── Hiperparametre grid search ───────────────────────────────
param_grid = {
    "changepoint_prior_scale": [0.001, 0.01, 0.1, 0.5],
    "seasonality_prior_scale": [0.01, 0.1, 1.0, 10.0],
}
all_params = [
    dict(zip(param_grid.keys(), v))
    for v in itertools.product(*param_grid.values())
]
results = []
for params in all_params:
    m_gs = Prophet(**params).fit(df_prophet[:-168])
    df_gs = cross_validation(m_gs, horizon="7 days",
                              period="30 days", initial="180 days",
                              parallel="processes")
    mape = performance_metrics(df_gs)["mape"].mean()
    results.append({**params, "mape": mape})

best = sorted(results, key=lambda x: x["mape"])[0]
print(f"En iyi: {best}")

# ── MCMC örnekleme (daha doğru belirsizlik) ──────────────────
m_mcmc = Prophet(
    mcmc_samples=300,
    changepoint_prior_scale=best["changepoint_prior_scale"],
    seasonality_prior_scale=best["seasonality_prior_scale"],
)
m_mcmc.fit(df_prophet[:-168])
forecast_mcmc = m_mcmc.predict(future)
# Daha geniş ve güvenilir yhat_lower/yhat_upper

05 NeuralProphet — Derin Öğrenme + Prophet Hibrit

NeuralProphet, Prophet'in additif çerçevesini PyTorch tabanlı AR-Net ve çok adımlı öğrenme ile güçlendirir.

NeuralProphet, Prophet'in yorumlanabilirliğini korurken derin öğrenme kapasitesini ekler. Temel fark: trend ve mevsimsellik aynı şekilde modellenir, ancak AR-Net (Autoregressive Neural Network) geçmiş değerlerin non-lineer kombinasyonunu öğrenir. Çoklu zaman serisi (multi-time-series) desteği, global model eğitimine olanak tanır — binlerce ürün için tek model.

neuralprophet.py
from neuralprophet import NeuralProphet

# ── Model tanımı ─────────────────────────────────────────────
m_np = NeuralProphet(
    n_forecasts=24,        # 24 saat ileriye tahmin
    n_lags=48,             # AR: 48 gecikmeli değer
    yearly_seasonality=True,
    weekly_seasonality=True,
    daily_seasonality=True,
    learning_rate=0.003,
    epochs=50,
    batch_size=64,
    ar_layers=[32, 16],    # AR-Net katman boyutları
    seasonality_mode="additive",
)

# ── Lagged regressor (geçmiş değeri kullan) ──────────────────
m_np.add_lagged_regressor("temperature")

# ── Future regressor (gelecek değeri biliniyor) ───────────────
m_np.add_future_regressor("is_holiday")

# ── Eğitim ───────────────────────────────────────────────────
metrics = m_np.fit(
    df_prophet,
    validation_df=df_prophet[-7 * 24:],
    freq="h",
    progress="bar"
)

# ── Tahmin ───────────────────────────────────────────────────
future_np = m_np.make_future_dataframe(df_prophet, periods=24, n_historic_predictions=True)
forecast_np = m_np.predict(future_np)

# ── Çoklu seri (global model) ────────────────────────────────
# df_multi: ID sütunu farklı serileri ayırt eder
df_multi["ID"] = df_multi["sensor_id"]
m_global = NeuralProphet(n_forecasts=12, n_lags=24)
m_global.fit(df_multi, freq="h")

06 Temporal Fusion Transformer (TFT) — Mimari ve PyTorch Lightning

TFT, dikkat mekanizmasıyla hangi geçmiş zaman adımının önemli olduğunu yorumlanabilir biçimde öğrenir ve çoklu ufuk tahmininde SOTA performans verir.

TFT (Lim et al., 2021), üç temel bileşeni birleştirir: Variable Selection Networks — hangi özelliklerin önemli olduğunu dinamik olarak seçer; LSTM Encoder-Decoder — yerel ve uzun dönemli bağımlılıkları yakalar; Multi-head Attention — geçmiş zaman adımları arasındaki uzun dönemli ilişkileri modeller. Quantile loss ile P10, P50, P90 tahminleri üretir.

PyTorch Forecasting kütüphanesi TFT'yi hazır sunar ve PyTorch Lightning üzerine kuruludur. Veri formatı: TimeSeriesDataSet nesnesi, statik kategorik, statik sayısal, geçmiş ve gelecek kovaryatları birbirinden ayırt eder.

tft.py
from pytorch_forecasting import TimeSeriesDataSet, TemporalFusionTransformer
from pytorch_forecasting.metrics import QuantileLoss
from pytorch_lightning import Trainer
from pytorch_lightning.callbacks import EarlyStopping
import torch

# ── Veri hazırlama ───────────────────────────────────────────
max_encoder_length = 168   # 1 hafta geçmiş
max_prediction_length = 24  # 1 gün ilerisi

training = TimeSeriesDataSet(
    df[df["time_idx"] < df["time_idx"].max() - max_prediction_length],
    time_idx="time_idx",
    target="consumption_kwh",
    group_ids=["meter_id"],
    min_encoder_length=max_encoder_length // 2,
    max_encoder_length=max_encoder_length,
    min_prediction_length=1,
    max_prediction_length=max_prediction_length,
    static_categoricals=["meter_type"],
    static_reals=["building_area"],
    time_varying_known_reals=["time_idx", "hour_of_day",
                               "day_of_week", "temperature"],
    time_varying_unknown_reals=["consumption_kwh"],
    add_relative_time_idx=True,
    add_target_scales=True,
    add_encoder_length=True,
)

# ── DataLoader ───────────────────────────────────────────────
train_dl = training.to_dataloader(train=True, batch_size=64, num_workers=4)
val_dl   = validation.to_dataloader(train=False, batch_size=64)

# ── TFT modeli ───────────────────────────────────────────────
tft = TemporalFusionTransformer.from_dataset(
    training,
    learning_rate=0.03,
    lstm_layers=2,
    hidden_size=32,
    attention_head_size=4,
    dropout=0.1,
    hidden_continuous_size=16,
    output_size=7,             # 7 quantile
    loss=QuantileLoss(),
    log_interval=10,
    reduce_on_plateau_patience=4,
)
print(f"Parametre sayısı: {tft.size()/1e3:.1f}k")

# ── Eğitim ───────────────────────────────────────────────────
trainer = Trainer(
    max_epochs=30,
    accelerator="auto",
    gradient_clip_val=0.1,
    callbacks=[EarlyStopping(monitor="val_loss", patience=5)],
)
trainer.fit(tft, train_dl, val_dl)

# ── Tahmin ve yorumlama ──────────────────────────────────────
predictions, x = tft.predict(val_dl, return_x=True, mode="raw")
interpretation = tft.interpret_output(predictions, reduction="sum")
tft.plot_interpretation(interpretation)  # hangi değişken önemli

07 Anomali Tespiti — Isolation Forest & Prophet Bayesian

Zaman serisi anomalileri nokta anomalisi, bağlamsal anomali ve toplu anomali olarak sınıflandırılır; Isolation Forest ve Prophet residualleri bunu tespit eder.

Isolation Forest, veriyi rastgele bölme ağaçlarıyla izole eder: normal noktalar daha derin dallarda, anomaliler daha sığ dallarda izole edilir. Anomaly score, ortalama izolasyon derinliğinin tersine orantılıdır. Zaman serisi için sliding window ile özellik çıkarımı yapılır.

Prophet Bayesian anomali tespiti: model tahmininin belirsizlik aralığı dışına çıkan gözlemler anomali adayıdır. interval_width=0.99 ile %99 güven bandı kullanılır; bu bandın dışındaki noktalar işaretlenir. Residual (gerçek - tahmin) z-skoru ile kombine edilebilir.

anomaly.py
from sklearn.ensemble import IsolationForest
from prophet import Prophet
import numpy as np, pandas as pd

# ── Yöntem 1: Isolation Forest ───────────────────────────────
def make_features(s: pd.Series, window=24) -> pd.DataFrame:
    return pd.DataFrame({
        "value": s,
        "rolling_mean": s.rolling(window).mean(),
        "rolling_std":  s.rolling(window).std(),
        "diff1":         s.diff(1),
        "diff24":        s.diff(24),
    }).dropna()

feats = make_features(series)
iso = IsolationForest(
    n_estimators=200, contamination=0.01,
    random_state=42
)
preds = iso.fit_predict(feats)  # -1 = anomali, 1 = normal
anomaly_mask_iso = preds == -1

# ── Yöntem 2: Prophet residual analizi ──────────────────────
m_anom = Prophet(
    interval_width=0.99,
    changepoint_prior_scale=0.001  # çok esnek trend istemiyoruz
)
m_anom.fit(df_prophet)
forecast_anom = m_anom.predict(df_prophet[["ds"]])

forecast_anom["y"] = df_prophet["y"].values
forecast_anom["anomaly"] = (
    (forecast_anom["y"] < forecast_anom["yhat_lower"]) |
    (forecast_anom["y"] > forecast_anom["yhat_upper"])
)

# ── Yöntem 3: z-score hibrit ─────────────────────────────────
residual = forecast_anom["y"] - forecast_anom["yhat"]
z_score = (residual - residual.mean()) / residual.std()
anomaly_mask_z = z_score.abs() > 3.5

# ── Ensemble: ikisi de anomali derse yüksek güven ─────────────
high_confidence = anomaly_mask_z & forecast_anom["anomaly"]
print(f"Tespit edilen anomali: {high_confidence.sum()}")

08 Özellik Mühendisliği — Lag, Rolling, Fourier

Makine öğrenmesi tabanlı zaman serisi modellerinde ham seriden anlamlı özellikler türetmek model başarısını doğrudan belirler.

Lag özellikler, geçmiş değerleri doğrudan özellik olarak kullanır. Enerji tahmini için t-1, t-24, t-48, t-168 (geçen hafta aynı saat) güçlü sinyaller taşır. Rolling istatistikler (kayan ortalama, standart sapma, min/max) pencere içindeki dinamiği yakalar. Fourier özellikleri mevsimselliği sinüs/kosinüs dalgaları ile kodlar — tree-based modeller için idealdir çünkü bu modeller doğal periyodikliği öğrenemez.

feature_engineering.py
import pandas as pd
import numpy as np

def build_features(df: pd.DataFrame, target: str, freq_h=1) -> pd.DataFrame:
    s = df[target]
    out = df.copy()

    # ── Lag özellikleri ──────────────────────────────────────────
    for lag in [1, 2, 3, 6, 12, 24, 48, 168]:
        out[f"lag_{lag}"] = s.shift(lag)

    # ── Rolling istatistikler ────────────────────────────────────
    for win in [6, 12, 24, 168]:
        out[f"roll_mean_{win}"] = s.shift(1).rolling(win).mean()
        out[f"roll_std_{win}"]  = s.shift(1).rolling(win).std()
        out[f"roll_max_{win}"]  = s.shift(1).rolling(win).max()

    # ── Fourier özellikleri (mevsimsellik) ───────────────────────
    t = np.arange(len(df))
    for period, n_terms in [(24, 4), (168, 4), (8766, 6)]:
        for k in range(1, n_terms + 1):
            out[f"sin_{period}_{k}"] = np.sin(2 * np.pi * k * t / period)
            out[f"cos_{period}_{k}"] = np.cos(2 * np.pi * k * t / period)

    # ── Takvim özellikleri ───────────────────────────────────────
    out["hour"]       = df.index.hour
    out["dow"]        = df.index.dayofweek
    out["month"]      = df.index.month
    out["is_weekend"] = df.index.dayofweek >= 5

    # ── Exponentially weighted mean ─────────────────────────────
    out["ewm_24"] = s.shift(1).ewm(span=24).mean()

    return out.dropna()

# ── LightGBM ile kullan ─────────────────────────────────────
import lightgbm as lgb
from sklearn.model_selection import TimeSeriesSplit

X = build_features(df, "consumption_kwh")
y = X.pop("consumption_kwh")

tscv = TimeSeriesSplit(n_splits=5)
model = lgb.LGBMRegressor(n_estimators=500, learning_rate=0.05)
for fold, (tr, va) in enumerate(tscv.split(X)):
    model.fit(X.iloc[tr], y.iloc[tr],
              eval_set=[(X.iloc[va], y.iloc[va])],
              callbacks=[lgb.early_stopping(50, verbose=False)])
    print(f"Fold {fold}: best_iter={model.best_iteration_}")

09 Üretim Pipeline — nixtla StatsForecast ile Çok Model Yönetimi

nixtla ekosistemi binlerce zaman serisini paralel ve verimli şekilde yönetir; StatsForecast, MLForecast ve NeuralForecast tek API altında toplanır.

Üretim ortamında genellikle yüzlerce veya binlerce farklı seri (ürün, bölge, sensör) için aynı anda tahmin üretmek gerekir. StatsForecast, ARIMA, ETS, CES ve Theta modellerini Numba ile hızlandırılmış C kodunda çalıştırır; seri başına milisaniye düzeyinde tahmin sağlar. MLForecast, LightGBM/XGBoost/LinearRegression'ı global model olarak çalıştırır. NeuralForecast, NBEATS, NHITS ve TFT'yi PyTorch ile sunar.

nixtla_pipeline.py
from statsforecast import StatsForecast
from statsforecast.models import (
    AutoARIMA, AutoETS, AutoCES, AutoTheta,
    MSTL, SeasonalNaive
)
from mlforecast import MLForecast
from mlforecast.utils import PredictionIntervals
import lightgbm as lgb
import pandas as pd

# ── Nixtla uzun format: unique_id | ds | y ────────────────────
df_long = df_multi.rename(columns={
    "sensor_id": "unique_id",
    "timestamp": "ds",
    "value": "y"
})

# ── StatsForecast: çoklu model paralel ───────────────────────
sf = StatsForecast(
    models=[
        AutoARIMA(season_length=24),
        AutoETS(season_length=24),
        MSTL(season_length=[24, 168]),
        SeasonalNaive(season_length=24),
    ],
    freq="h",
    n_jobs=-1              # tüm CPU çekirdekleri
)
sf.fit(df_long)
sf_forecast = sf.predict(h=24, level=[80, 95])

# ── MLForecast: global LightGBM + tahmin aralığı ─────────────
mlf = MLForecast(
    models=[lgb.LGBMRegressor(n_estimators=300)],
    freq="h",
    lags=[1, 24, 48, 168],
    lag_transforms={
        24: [("rolling_mean", 24), ("rolling_std", 24)],
    },
    date_features=["hour", "dayofweek"],
    target_transforms=[Differences([1])],
)
mlf.fit(df_long, prediction_intervals=PredictionIntervals(n_windows=3))
ml_forecast = mlf.predict(h=24, level=[80, 95])

# ── Model seçimi: son 4 hafta holdout üzerinde ───────────────
cv_results = sf.cross_validation(
    df=df_long,
    h=24,
    step_size=12,
    n_windows=4,
)
# Her seri için en iyi modeli seç
best_models = (
    cv_results
    .groupby(["unique_id", "model"])
    .apply(lambda x: ((x["y"] - x["yhat"]).abs()).mean())
    .reset_index(name="mae")
    .sort_values("mae")
    .groupby("unique_id")
    .first()
)
print(best_models.value_counts("model"))  # hangi model kaç seride kazandı
ÜRETIM

StatsForecast 1000 seriyi, seri başına 1 yıllık saatlik veriyle yaklaşık 2-5 dakikada tamamlar (paralel). MLForecast global modeli tek seferde eğitir — yeni seriler için fine-tuning gerekmez. NeuralForecast GPU'da çalışır ve özellikle uzun ufuk tahminlerinde istatistiksel modelleri geçer.