Paraméter optimalizálás — Keresztvalidáció és GridSearch technika idősorok esetén

Meszaros Zoltan Gabor
8 min readApr 24, 2024

--

Az idősorokban rejlő szabályrendszert (trend, szezonalitás, ciklusok, véletlen tényezők) egy matematikai modellel szeretnék megragadni. Ez a modell statisztikai keretrendszerében vizsgálódva nem képes oly módon tanulni, mint egy gépi tanulást megvalósító algoritmus. A gépi tanulás itt nagyon is emberi folyamat marad, tehát a szabályrendszer paramétereit nekünk kell megtalálni. Ez legalább annyira kreatív, mint matematikailag alátámasztható folyamat. Felismerünk egy mintát, amit képesek vagyunk extrapolálni, akár “kézzel” meghúzni az előrejelzést. Ugyanakkor a paraméterek megadása nagyon is egzakt kell hogy maradjon, amit ha elhibázunk végletesen rossz előrejelzést adhatunk. Nézzünk egy lehetséges példát a rossz paraméter választásra:

smoothing trend paraméter = 0.7, egy rossz megoldás

A béta értéket, tehát a trendet szabályzó paramétert állítsuk 0.7-re. Ezzel a megoldással a közelmúlt változásait extrém értékben felülsúlyozzuk. A kódsort futtatva azonnal látható, hogy a trend tökéletesen ellentétes irányt fog felvenni, mint az elvárt.

A megoldás egy, maximum két paraméter esetében lehet a szimpla próbálkozás. De mi a helyzet egy komplexebb adatsort leíró modell esetében, ahol 5–6 paraméter kombinációja adja a legjobb eredményt? Ehhez kísérleteinket automatizálnunk kell. Ugorjunk egy kicsit előre és az ES modell minden, statsmodels csomagban elérhető paraméterét élesítsük.

Alkalmazott modell megismerése

ES technika paraméterei a kódban kifejezve a következők. Fontos, mielőtt bármit állítanánk ismerjük meg annak működését, a várható hatásokat.

# Modell létrehozása
model = sm.tsa.ExponentialSmoothing(
df["passengers"],
trend='add',
seasonal='mul',
seasonal_periods=12,
damped_trend=False # Csillapított trend engedélyezése
)

# Modell illesztése
fitted_model = model.fit(
smoothing_level=0.187,
smoothing_trend=0.7,
smoothing_seasonal=0.108,
damping_slope=0.9,
remove_bias = True, # ha trend add és season mul
optimized=False
)

trend
Ez a paraméter meghatározza, hogy milyen típusú trendkomponens legyen a modellben.

seasonal
A seasonal paraméter meghatározza a szezonális komponens típusát. Lehet multiplikatív vagy additív.

seasonal_periods
Ez a paraméter állítja be, hogy hány időperiódusonként ismétlődik a szezonális minta.

damped_trend
A damped_trend engedélyezése vagy tiltása határozza meg, hogy a trendkomponens az idő előrehaladtával csillapodjon-e vagy sem. Ebben az esetben a False érték azt jelenti, hogy a trend nem csillapodik, hanem konzisztensen tovább nő vagy csökken.

smoothing_level (α)
A simítási szint, ami meghatározza az adatok aktuális értékének súlyát az előző előrejelzés frissítésekor.

smoothing_trend (β)
A simítási trend, ami szabályozza a trendkomponenst.

smoothing_seasonal (γ)
A simítási szezonális paraméter határozza meg, milyen mértékben frissül a szezonális komponens a korábbi szezonok alapján.

damping_slope (φ)
Ez a paraméter szabályozza, hogy mennyire csillapodjon a trend, ha a damped_trend engedélyezve van. Itt a 0.9 érték azt jelenti, hogy a trend csökkenő mértékben változik, ami enyhén csillapított trendet eredményez, bár a damped_trend=False esetében ez a paraméter nem aktív.

remove_bias
A torzítás eltávolítása (remove_bias=True) azt jelenti, hogy a modell megkísérli korrigálni a rendszeres hibákat az illeszkedés során. (additív trend és multiplikatív szezonalitás esetén hasznos)

Fogaljuk össze egy grafikonon azokat a tényezőket amiket változtathatunk a modellépítés és későbbi finomhangolás érdekében. Ezek gyűjteménye és értelmezési tartománya lesz a hangoló alkalmazásunk egyik fő összetevője, a paraméter háló, vagy parameter grid.

változtatható modell és fittelési paraméterek

Most hogy elméletben megalkottunk egy könyvtárat, nézzük meg hogyan illeszkednek az optimalizáló gép további elemei:

paraméter optimalizálás iteratív folyamata
  1. Adatok beolvasása
  2. Statisztikai modell induló paramétereinek inicializálása
  3. Paraméter háló (lehetséges értékek) beállítása
  4. Paraméterkombinációk vezérlése
  5. Keresztvalidáció — a kombinációk vizsgálata tanuló és teszt halmazokon
  6. Kiértékelés átlagolás módszertannal
  7. For ciklus futtatása amíg a legkedvezőbb érték előáll
  8. Kilépés a for ciklusból, minden kombináció tesztelése után; legjobb metrika és az azt előállító paraméter kombináció mentése
  9. Modell illesztése új paraméterekkel, predikció elkészítése

Vizsgáljuk meg a főbb elemeket különös tekintettel a ciklusban működő elemekre.

Keresztvalidáció

Az első eszköz ami szükséges egy automatizált kísérletsorozat elvégzésére a keresztvalidáció technikája. Alapesetben egyszerű validáció során adatainkat két részre osztjuk, egy tanuló és egy teszt halmazra. A tanuló halmazon végezzük a modell hangolását, a teszt halmazon pedig megtörténik a kiértékelés. Megfelelő mennyiségű adathalmaz esetén, a teszt halmazt tovább oszthatjuk két részre. Ennek a tovább osztott halmaznak az egyik része fog szolgálni a betanított modell hiperparamétereinek finomhangolására, a fennmaradó pedig a végső tesztelésre. Hátránya a technikának, hogy csak a szerencsén múlik milyen a két halmaz összetétele. Ez a módszer félreviheti az elemzést és túlilleszthetjük a modellt.
Ennek a megoldásnak az eggyel hatékonyabb változata, amikor a két halmaz véletlenszerűen kerül kiválasztásra. Ekkor az adatok sokkal heterogénebbek, egyenletesebben oszlanak el a vizsgálat alá vont adatpontok.

forrás: https://algotrading101.com/learn/wp-content/uploads/2020/06/training-validation-test-data-set.png

Ennél még hatékonyabb technika, a keresztvalidáció. Alapgondolata, hogy vegyünk több kísérletet, ahol a teszt és tréning halmazra bontást ismételjük. Az ábrán egy hat-hajtásos keresztvalidáció történik. Hat kísérletet és kiértékelést végzünk. Minden kísérletben a teljes halmaz egyhatoda validációs/kiértékelő csoport, míg a fennmaradó nagyobb halmaz a modell betanítására szolgál. Amint ez megtörténik, kiértékeljük a modell eredményét a validációs halmazokon, majd vesszük a választott hibametrikánk átlagát. Ezzel megkaptuk a modell teljesítményét.

forrás: dataskool.hu

További információt szerezhetünk, ha minden iterációban a teszt és validációs halmaz eredményét összehasonlítjuk. Ideális esetben ezeknek közel kell esniük egymáshoz.

Idősorok esetén keresztvalidáció alkalmazásakor óvatosan kell eljárni. Nem boríthatjuk fel az időbeli sorrendet, tehát a tanuló halmaz indexeinek mindig kisebbnek kell lenniük, mint a validációsnak. Ezt mutatja be a következő ábra:

forrás: https://thierrymoudiki.github.io/images/2021-11-07/2021-11-07-image1.png

Ezt a funkciót az sklearn gépi tanulási csomag, TimeSeriesSplit osztályával fogjuk elérni, mely képes ezt, az idősorokra vonatkozó megszorítást biztosítani.

Hibametrika

A tesztelési technikán túl szükségünk lesz egy mérési célra (KPI), vagyis egy hibamaterikára amivel a különböző paraméterkombinációk összehasonlíthatóvá vállnak. Ez a hibametrika fog működni a paraméterkombináció keresztvalidálásakor, illetve eggyel magasabb szinten, mikor kiválasztjuk a legjobb átlagos eredményt adó kombinációt is.

A példában mean_absolut_error mutatót fogjuk használni az optimalizáció során.

Bővebben a metrikákról egy korábbi bejegyzésemben:

https://medium.com/%40meszaros.zoltan.gabor/key-performance-index-sarah-bob-jack-%C3%A9s-emma-t%C3%B6rt%C3%A9nete-4837e26a8e04

Kombinációk keresése — Grid és Random Search

A gépi tanulás világából vett technika jól használható statisztikai idősorok esetében is. Mindkét módszer jól működik, de különböző megközelítéseket alkalmaznak a legjobb paraméterkombinációk megtalálására, és más-más helyzetekben lehet előnyös az alkalmazásuk.

A Grid Search, vagy rácskeresés egy alapos brute force jellegű módszer, amely minden lehetséges kombinációt kipróbál a megadott hiperparaméter-értéktartományokon belül. Egy előre definiált “rácsot” hoz létre a paraméterértékek minden lehetséges kombinációjával, és kiszámítja a modell teljesítményét minden egyes kombináció esetében.

Ha a két paraméterrel dolgozunk, egy 2D-ben kifeszített rácsot tudunk elképzelni. Három paraméter esetén már ennek megfelelő dimenziószámban kell megtalálni az optimumot. Ennél magasabb paraméterszám esetén tovább kell növelnünk a dimenziókat, amit matematikailag az sklearn GridSearch funkciója képes megvalósítani. A lenti ábrán egy döntési fa 3 dimenziós paraméterrácsa látszik:

forrás: https://www.researchgate.net/publication/354735559/figure/fig2/AS:1081038038142986@1634750961638/3D-visualization-of-grid-search-result-over-the-investigated-space-of-hyperparameters-n.png

Előnyök:

  • Alapos: minden lehetséges kombinációt értékel.
  • Egyszerű implementáció és használat.
  • Garantáltan megtalálja a rácsban definiált legjobb kombinációt.

Hátrányok:

  • Időigényes és számítási kapacitást igényel, különösen nagy paramétertartományok és/vagy sok paraméter esetén.
  • Nem skálázódik jól nagy adatkészletekkel vagy sok hiperparaméterrel.

A Random Search, vagy véletlenszerű keresés, véletlenszerűen választ ki kombinációkat a paraméterek lehetséges értéktartományából egy előre meghatározott számú iterációra. Nem vizsgálja meg minden lehetséges kombinációt, hanem véletlenszerű mintavételezést alkalmaz.

Előnyök:

  • Gyorsabb, mint a Grid Search, különösen nagy paramétertartományok és sok hiperparaméter esetén.
  • Jobban skálázódik nagy adatkészletekre és bonyolultabb modellekre.
  • Meglepően jó eredményeket hozhat, mivel nem korlátozódik egy szabályos rácsra, és így kiszélesítheti a keresési területet.

Hátrányok:

  • Nincs garantált fedettség, mint a Grid Search-nél, így lehetséges, hogy nem találja meg az optimális kombinációt.
  • Az eredmények kiszámíthatatlanabbak lehetnek, mivel nagyban függenek a véletlen mintavételezéstől.

Nézzük a résztechnikákat elmélet és gyakorlat együtt:

ES + ParameterGrid

Mivel a sklearn és a statsmodel ExponentialSmoothing modellje külön csomag, az egyszerű ParameterGrid osztály kerül meghívásra. most a TimeSeriesSplit biztosítja a keresztvalidációs eljárást idősorok esetére. A kód, az elméleti bevezetőben meghatározottak szerint működik. Fontos: A túlillesztés elkerülése érdekében a lehetséges paraméter változókat tudatosan kontrolláljuk. Pl.: trend esetén nem érdemes 0.5 fölé engedni beállítási lehetőséget, mivel ilyenkor a régmúlt adatainak már szinte semmi hatása nem lesz a predikcióra.

import numpy as np
import pandas as pd
import statsmodels.api as sm
import matplotlib.pyplot as plt
from sklearn.model_selection import ParameterGrid, TimeSeriesSplit

def cross_validate_es_with_mae(data, param_grid, n_splits=5):
best_score = float('inf')
best_params = None

# TimeSeriesSplit inicializálása
tscv = TimeSeriesSplit(n_splits=n_splits)

for params in ParameterGrid(param_grid):
mae_scores = []

for train_index, test_index in tscv.split(data):
train, test = data.iloc[train_index], data.iloc[test_index]

# Modell létrehozása a kiválasztott paraméterekkel
model = sm.tsa.ExponentialSmoothing(
train['passengers'],
trend=params['trend'],
seasonal=params['seasonal'],
seasonal_periods=params['seasonal_periods'],
damped_trend=params['damped_trend']
).fit(
smoothing_level=params['smoothing_level'],
smoothing_trend=params['smoothing_trend'],
smoothing_seasonal=params['smoothing_seasonal'],
damping_slope=params['damping_slope'],
optimized=False
)

# Predikciók készítése a teszthalmazra
predictions = model.forecast(len(test))

# MAE számítása a teszthalmazra
mae = np.mean(np.abs(predictions - test['passengers']))
mae_scores.append(mae)

# Átlagos MAE számítása a különböző felosztásokon
avg_mae = np.mean(mae_scores)

# Legjobb paraméterek és pontszám frissítése, ha szükséges
if avg_mae < best_score:
best_score = avg_mae
best_params = params

return best_params, best_score

# Paraméter rács és adatok megadása
param_grid = {
'smoothing_level': np.arange(0.1, 0.6, 0.1),
'smoothing_trend': np.arange(0.1, 0.6, 0.1),
'smoothing_seasonal': np.arange(0.1, 0.6, 0.1),
'damping_slope': np.arange(0.1, 0.6, 0.1),
'trend': ['add', 'mul'],
'seasonal': ['add', 'mul'],
'seasonal_periods': [12],
'damped_trend': [True, False]
}

# Optimalizálás és eredmények az MAE alapján
best_params, best_mae = cross_validate_es_with_mae(df, param_grid, n_splits=5)
print("Best Params:", best_params)
print("Best MAE:", best_mae)

Futtatás eredménye, a javasolt paraméterekkel, és az ezzel elérhető legjobb MAE átlaggal.

Best Params: {'damped_trend': False, 'damping_slope': 0.1, 'seasonal': 'mul', 'seasonal_periods': 12, 'smoothing_level': 0.30000000000000004, 'smoothing_seasonal': 0.30000000000000004, 'smoothing_trend': 0.30000000000000004, 'trend': 'add'}
Best MAE: 17.039069809793027

ES + Random Search

Mivel az sklearn nem tartalmazza a statsamodel ES modelljét, randomizált kereséshez komoly átalakításra van szükség. Az alábbi kód a fentiek AI által átalakított verziója:

import numpy as np
import pandas as pd
from sklearn.base import BaseEstimator, RegressorMixin
from sklearn.model_selection import TimeSeriesSplit, RandomizedSearchCV
from sklearn.metrics import make_scorer, mean_absolute_error
import statsmodels.api as sm
from scipy.stats import uniform

class ExponentialSmoothingAdapter(BaseEstimator, RegressorMixin):
def __init__(self, trend=None, seasonal=None, seasonal_periods=None, damped_trend=False,
smoothing_level=None, smoothing_trend=None, smoothing_seasonal=None, damping_slope=None):
self.trend = trend
self.seasonal = seasonal
self.seasonal_periods = seasonal_periods
self.damped_trend = damped_trend
self.smoothing_level = smoothing_level
self.smoothing_trend = smoothing_trend
self.smoothing_seasonal = smoothing_seasonal
self.damping_slope = damping_slope
self.model_ = None

def fit(self, X, y):
self.model_ = sm.tsa.ExponentialSmoothing(
y,
trend=self.trend,
seasonal=self.seasonal,
seasonal_periods=self.seasonal_periods,
damped_trend=self.damped_trend
).fit(
smoothing_level=self.smoothing_level,
smoothing_trend=self.smoothing_trend,
smoothing_seasonal=self.smoothing_seasonal,
damping_slope=self.damping_slope,
optimized=False
)
return self

def predict(self, X):
return self.model_.forecast(len(X))

# Paraméter rács
param_dist = {
'smoothing_level': uniform(0.1, 0.5),
'smoothing_trend': uniform(0.1, 0.5),
'smoothing_seasonal': uniform(0.1, 0.5),
'damping_slope': uniform(0.1, 0.5),
'trend': ['add', 'mul'],
'seasonal': ['add', 'mul'],
'seasonal_periods': [12],
'damped_trend': [True, False]
}

# Adatok és RandomizedSearchCV
data = df
X = np.arange(len(data))
y = data['passengers']

tscv = TimeSeriesSplit(n_splits=5)
random_search = RandomizedSearchCV(
estimator=ExponentialSmoothingAdapter(),
param_distributions=param_dist,
n_iter=100, # A véletlenszerű kiválasztások száma
cv=tscv,
scoring='neg_mean_absolute_error',
n_jobs=-1,
verbose=1,
random_state=30 # Véletlenszám-generátor állapota a reprodukálhatóság érdekében
)

random_search.fit(X, y)
print("Best Params:", random_search.best_params_)
print("Best Score:", -random_search.best_score_) # A negatív MAE-t pozitívra konvertáljuk

És az eredmény.

Fitting 5 folds for each of 100 candidates, totalling 500 fits
Best Params: {'damped_trend': False, 'damping_slope': 0.33889487606768864, 'seasonal': 'mul', 'seasonal_periods': 12, 'smoothing_level': 0.29834624219235306, 'smoothing_seasonal': 0.13490199168699377, 'smoothing_trend': 0.28913128423438483, 'trend': 'mul'}
Best Score: 19.647149009454225

Végezetül futtassuk a modellt, a javasolt paraméterek alapján. Az illesztett és predikált görbe futása egyértelműen jelzi hogy jó irányba haladunk.

Zárszó

A paraméter optimalizáció különösen az ML modellek egyik legfontosabb, de előadások során kicsit elhanyagolt része. Látható, hogy komolyabb átalakításokkal, de statisztikai modellek esetén is használható a logika és az azt megvalósító sklearn ökoszisztéma, bár kétségtelen egy ML modell esetén kivitelezése egyszerűbb.

Boldog optimalizálást mindenkinek :)

Kapcsolat

🚀Ha értékesnek találod a leírtakat, és szükségét látod egy hasonló elemzés elkészítésének, vagy adatelemzői munkakörbe keresel hosszútávon szakembert, kérlek keress meg alábbi Linkedin profilon:
(24) Zoltán Mészáros | LinkedIn

--

--