Underfitting: Pár szó a modellkomplexitásról
Amikor készítünk egy fényképet a nyári vakációnkról, a valóság egy számunkra fontos szeletét akarjuk megőrizni, adott esetben az a célunk, hogy mások, további magyarázat nélkül is értelmezni tudják a látottakat.
Valójában a fénykép elkészültekor egy modellt építettünk.
kapcsolat: (24) Zoltán Mészáros | LinkedIn
Az 1900-as évek elején az Adriai tenger partján készült alkotás tökéletes példa.
A valóságból kiragadt elemek és azok fontossága egyértelmű.
- Előtérben állnak a személyek, akik részt vettek a családi eseményen. Felismerhetőek, dominálnak a fényképen.
- Háttérben kevésbé előkelő pozícióban talán a család barátai, távolabbi hozzátartozók, személyzet.
- Látjuk a hullámzó tengervizet ami keretezi a fényképet kijelölve a helyszínt, ugyanakkor már homályban hagyva a pontos lokációt. Valószínűleg a fényképész számára értékesebb volt a közösség, a család együttlétének megörökítésé ebben az adott pillanatban.
- Hiányoznak a színek. Ha a technológia lehetőséget adott volna ennek a rétegnek a rögzítésére, valószínűleg akkor is elhanyagolható lenne a kép mondanivalója szempontjából.
- Feltűnőek a ruhák, a századelő középosztálybeli viselete, mely bár alapvető adottsága volt a kornak és így a képnek is, de közvetett módon segít nekünk a korszak megjelölésében.
Mindezt 120 év távlatából ösztönösen meg tudjuk állapítani.
Pontosan ilyen egy jól hangolt matematikai modell. Ha vesszük idősorunkat, és meg akarjuk ragadni annak a valóságnak szerkezetét amit ábrázolni próbálunk, olyan érzékenyre kell állítani az azt leíró egyenletet, ami képes megragadni a valóságot. Össze kell válogatnunk a leglényegesebb elemeket.
Mint minden az adattudományban ez nyers adatok függvénye. Egy komplex adatsort mely magas variánciával, szezonális trendekkel, kisze-kusza ciklusokkal rendelkezik csak egy ennek megfelelő modell képes visszaadni. Pont mint a példának vett fotónk szereplői, a helyszín, a történeti keret egysége.
Nézzünk az előbbi képre ha “általánosítunk” kicsit rajta, és elmosódnak a részletek.
Ez a példa valószínűleg nem állta volna ki az idő próbáját. Egyszerűen nem használható. A kép lényegi információtartama elvész, csak tippelni tudnánk arra mi szerepel a képen, mikor készült, különösen ha az eredetinek nem lennénk tudatában. 1900-as évek Adriája? Vagy a nyolcvanas évek Balatonfürede? Elvesztettük a kontextust.
Gondoljunk erre a második képre úgy, mint modellillesztésnél az egyik fő adatelemzői hibára amit elkövethetünk.
Ez pedig az alulillesztés (underfitting) problémaköre, mely akkor jelentkezik ha a valóságot leíró adatokra fittelt modell nem elég komplex.
A továbbiakban a metaforát kibontjuk és megnézzük annak matematikai hátterét.
01. Elemzési csomagok betöltése
Töltsük be az elemzés támogató python könyvtárakat. A korábbi bejegyzésekhez képest újdonság az sklearn csomag LinearRegression funkciójának importálása. Az sklearn mint ML programcsomag talán a legszélesebb körben használt eszköz. Még a legbonyolultabb elemzések is sokszor egy egyszerűbb lineáris modellből indulnak ki, mint összehasonlítási alap. Tegyünk mi is így. További újdonság, hogy az ExponentialSmoothing megvalósításához szükséges eszközöket ezúttal a statsmodel könyvtárból vesszük (amiből a Darts csomag is építkezik).
# adatbetöltés, fomrázás, ábrázolás
import pandas as pd
import math
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
# használt hibametrika
from sklearn.metrics import mean_absolute_error
# Exp. Smoothing toolkit
from statsmodels.tsa.holtwinters import SimpleExpSmoothing
# Lineáris regressziós modell
from sklearn.linear_model import LinearRegression
# hibaüzenetek kikapcs
import warnings
warnings.filterwarnings("ignore")
02. “Baseline” modell: Egyszerű átlagolása az idősornak
Készítsünk egy függvényt ami egy Excel fájlból betölti az értékesítési adatainkat, melyből keresleti predikciót szeretnék készíteni. Számoljuk ki a teljes idősor átlagát, adjunk meg egy hibametrikát (MAE — mean absolut error) mellyel értékelni tudjuk az illesztés minőségét, és lehetőséget ad az összehasonlításra. Az eredményeket ábrázoljuk egy grafikonon.
def run_analysis():
# Adatok betöltése
df = pd.read_excel("raw_data_store_01.xlsx")
df["nr"] = range(1, len(df) + 1)
df = df[["date", "nr", "demand"]]
# Kereslet átlagának kiszámítása
average_demand = df["demand"].mean()
df["fitted_demand"] = average_demand
# MAE kiszámolása
mae = mean_absolute_error(df["demand"], df["fitted_demand"])
# Grafikon megjelenítése
plt.figure(figsize=(10, 5))
plt.plot(df["nr"], df["demand"], label="Actual Demand", color="grey")
plt.axhline(y=average_demand, color='blue', linestyle='--', label="Average Demand")
plt.title(f"Demand Forecast Using Simple Average - MAE: {mae:.2f}")
plt.xlabel("nr")
plt.ylabel("Demand")
plt.legend()
plt.grid(True)
plt.show()
run_analysis()
Az ábra X tengelye idősorunk, szürke vonallal látható a tényadataink, szaggatott kék vonallal pedig az átlag, mint alap predikciós stratégia. Ebben az estben egy vonalat illesztettünk az adathalmazra, predikciónk is ennek megfelelőlen változatlan maradna a következő periódusra is.
Egy átlag természetesen nem fog túl jó illeszkedést biztosítani, haladjunk egy kicsit tovább, és döntsük meg ezt a vonalat.
03. Lineáris trend illesztése
Lineáris trend alkalmazása esetén már képesek vagyunk visszaadni egy hosszú, akár több éven keresztül húzódó fő tulajdonságot. Ezt a vásárló igénybe jelentkező csökkenést sikerül már ezzel a komplexitási szinten is kifejezni.
Ahogy a MAE értéken is látszik 32 000-ről 31 000 egységre javultunk.
def run_analysis():
# Adatok betöltése
df = pd.read_excel("raw_data_store_01.xlsx")
df["nr"] = range(1, len(df) + 1)
df = df[["date", "nr", "demand"]]
# Lineáris regresszió
X = df[["nr"]]
y = df["demand"]
model = LinearRegression()
model.fit(X, y)
df["fitted_demand"] = model.predict(X)
# MAE kiszámolása
mae = mean_absolute_error(y, df["fitted_demand"])
# Grafikon megjelenítése
plt.figure(figsize=(10, 5))
plt.plot(df["nr"], df["demand"], label="Actual Demand", color="grey")
plt.plot(df["nr"], df["fitted_demand"], label="Fitted Demand", color = "blue", linestyle='--')
plt.title(f"Demand Forecast - MAE: {mae:.2f}")
plt.xlabel("nr")
plt.ylabel("Demand")
plt.legend()
plt.grid(False)
plt.show()
run_analysis()
Haladjunk tovább az összetettségben, használjuk az Exponential Smoothing technikát.
04. Exponential Smoothing
A három modellünk közül a legkomplexebb modellben 0.19 alpha értékekkel közelítjük az adatsor vonulását. (múltbeli adatokra való emlékezés mértéke)
def run_analysis_2(alpha = 0.19):
# Adatok betöltése
df = pd.read_excel("raw_data_store_01.xlsx")
df["nr"] = range(1, len(df) + 1)
df = df[["date", "nr", "demand"]]
# Egyszerű exponenciális simítás
model = SimpleExpSmoothing(df["demand"]).fit(smoothing_level=alpha, optimized=False)
df["fitted_demand"] = model.fittedvalues
# MAE kiszámolása
mae = mean_absolute_error(df["demand"], df["fitted_demand"])
# Grafikon megjelenítése
plt.figure(figsize=(10, 5))
plt.plot(df["nr"], df["demand"], label="Actual Demand", color="grey")
plt.plot(df["nr"], df["fitted_demand"], label="Fitted Demand", color = "blue", linestyle='--')
plt.title(f"Demand Forecast - MAE: {mae:.2f}")
plt.xlabel("nr")
plt.ylabel("Demand")
plt.legend()
plt.grid(False)
plt.show()
# A függvény meghívása, ami végrehajtja az elemzést és megjeleníti az eredményeket
run_analysis_2()
A legkomplexebb modellünk szépen simítja az adatsort, adaptálódik a lokális átlagértékekhez és az enyhe, ciklikusnak tűnő mozgásokat is követi. Úgy tűnik a tényvilág adatkomplexitásához ez a típus áll a legközelebb jelenlegi eszköztárunkból.
Összehasonlítva a MAE értékeket a következőket látjuk:
“Baseline” átlag: 32 105
Linear trend: 30 312
Exp. Smoothing: 28 824
Ebből máris kitűnik, hogy az adataink természetéhez jobban alkalmazkodó ES modell 5%-kal ad jobb eredményt mint a lineáris verzió, illetve több mint 10%-kal teljesíti felül az átlagra szavazó változatot. Üzleti oldalról vizsgálva: ha a túl- illetve alulkészletünkből fakadó költségeket azonosnak vesszük, el tudjuk képzelni az éves budgetben a forkasztálás minőségének javításával milyen ugrásszerű költségcsökkenést érhetünk el!
A modell komplexitásának vizsgálata elvezet egy, az ML világába is fontos alapvető kérdéshez, és technikához:
Hogyan tudom meghatározni modellem mennyire stabil? Hogyan fog teljesíteni az eddig nem látott új adatokon? Hogyan tudok “kilépni” a modell világból? A válasz a tanuló és teszt adathalmazra bontás.
05. Tanulás és tesztelés
Gondoljunk egy iskolai szituációra. Hogy tudom értékelni egy tanuló teljesítményét? Dolgozatot íratunk, jellemzően olyan példákkal, amivel nem találkozott a diák megelőzőleg.
Ismerjük a megoldás menetét, (a modell megfelelő komplexitású) tehát képesek vagyunk egy általános igazságot felismerni. Valószínűleg, pontosan azzal a feladattal még nem találkoztunk, ami a dolgozatban szerepel, mégis jó megoldást tudunk adni.
Megelőlegezve egy későbbi témát: túltanulás esetén a helyzet fordított. Ebben az esetben úgy tanítottak minket, hogy képtelenek lennénk egy új, ismeretlen feladatot megoldani akkor, ha csak egy-két szám is más lenne a tesztben, mint a tanulópéldákban.
Biztosítanunk kell, hogy az adatsorunk két elkülönülő részre legyen bontva. A teszt halmaz lehetőséget ad a modell felépítésére és optimalizálására, a tréning szetten pedig ellenőrizni tudjuk annak valódi pontosságát új adatokon. Emlékezzünk: a modell ezt a teszt szettet soha nem “látta” korábban.
Mivel szembesülhetünk eredményül?
- Egy alulfittelt modell adhat jó eredményt a tanuló halmazon, de gyengét a teszthalmazon.
- Ha a modell a tréning halmazon nem szerepel jól, valószínűleg a teszt halmazon sem fog.
Készítsük el a tréning/teszt módszer szerint a kódolást:
- Töltsük be az adatsort az eddigiek szerint.
- Vágjuk a betöltött adatsort teszt és tréning részre (a példában 50%-nál vágok, a bemutatás érdekében, idősorok esetén jellemzően az utolsó periódust vágjuk le és hagyjuk teszt adathalmazban)
- Tanítsuk be a modellt a tanító halmazon.
- Végezzünk el a predikciót a teszt halmazon, úgy hogy összeillesszük a teszt és tanuló halmaz adatsorát
- Számoljuk ki a mindkét halmazra a hiba mértékét, majd ábrázoljuk mindezt együtt egy grafikonon.
Fontos: A modell fittelésénél figyeljünk arra, hogy a modell ne kapjon véletlenül sem információt a “jövőből”. Például ha paraméter optimalizálást bármilyen módon is, de végez a modell a tesztadatokon hibás eredményt fogunk kapni. Ez a Data Lakage jelensége.
def run_analysis_2(alpha = 0.19):
# Adatok betöltése a load_data függvény segítségével
df = pd.read_excel("raw_data_store_01.xlsx")
df["nr"] = range(1, len(df) + 1)
df = df[["date", "nr", "demand"]]
# Kiválasztjuk az első felét az adatoknak a tanításhoz
half_point = len(df) // 2
train_df = df.iloc[:half_point]
test_df = df.iloc[half_point:]
# Egyszerű exponenciális simítás a tanító adatokon
model = SimpleExpSmoothing(train_df["demand"]).fit(smoothing_level=alpha, optimized=False)
# Predikció az egész adathalmazon
df["fitted_demand"] = model.predict(start=0, end=len(df) - 1)
# MAE kiszámolása a tanító adathalmazon
train_mae = mean_absolute_error(train_df["demand"], df.loc[:half_point - 1, "fitted_demand"])
# MAE kiszámolása a teszt adathalmazon
test_mae = mean_absolute_error(test_df["demand"], df.loc[half_point:, "fitted_demand"])
# Grafikon megjelenítése
plt.figure(figsize=(10, 5))
plt.plot(df["nr"], df["demand"], label="Actual Demand", color="grey")
plt.plot(df["nr"], df["fitted_demand"], label="Fitted Demand", color="blue", linestyle='--')
plt.axvline(x=half_point, color='red', label='Training/Test Split', linestyle='--') # Piros vonal a felosztási pontnál
plt.title(f"Demand Forecast - Training MAE: {train_mae:.2f}, Test MAE: {test_mae:.2f}")
plt.xlabel("nr")
plt.ylabel("Demand")
plt.legend()
plt.grid(True)
plt.show()
# függvény indítása
run_analysis_2()
Képzeletben álljunk a múltat és jövőt elválasztó jelenpontra (piros szaggatott vonal) és tekintsünk rá a két halmazunkra. Az adatsor eleje a tanuló halmaz a múlt, még a piros vonal másik oldala a jövő, amit még a modell nem ismer.
A grafikon piros vonalán végeztünk tehát egy metszést az adathalmazon és keletkezett 50% tanulóadatunk. Ezen a halmazon futtatjuk a ES modellt, mely látszik a kék szaggatott vonal mozgásából, így próbálja lekövetni a változásokat 0.19 alpha értéken. A múltbeli adatok enyhe felülsúlyozása kitűnik: az időszak kezdetén egy lokális erősebb trend figyelhető meg, ami miatt a mi kék prediktív szaggatott vonalunk is magasabbra kerül.
A piros vonaltól jobbra találhatóak teszt adataink. Mivel a továbbra sem számolunk trenddel és szezonalitással (csak szintekkel, lásd korábbi bejegyzésem), a predikciónk egy konstans lesz. A hibametrikákat értelmezve a két időszak MAE értéke nagyon közeli.
Alkalmazzuk a gondolatmenetet a lineáris trendre is.
def run_analysis_2(alpha = 0.19):
# Adatok betöltése a load_data függvény segítségével
df = pd.read_excel("raw_data_store_01.xlsx")
df["nr"] = range(1, len(df) + 1)
df = df[["date", "nr", "demand"]]
# Kiválasztjuk az első felét az adatoknak a tanításhoz
half_point = len(df) // 2
train_df = df.iloc[:half_point]
test_df = df.iloc[half_point:]
# Egyszerű exponenciális simítás a tanító adatokon
model = SimpleExpSmoothing(train_df["demand"]).fit(smoothing_level=alpha, optimized=False)
# Predikció az egész adathalmazon
df["fitted_demand"] = model.predict(start=0, end=len(df) - 1)
# MAE kiszámolása a tanító adathalmazon
train_mae = mean_absolute_error(train_df["demand"], df.loc[:half_point - 1, "fitted_demand"])
# MAE kiszámolása a teszt adathalmazon
test_mae = mean_absolute_error(test_df["demand"], df.loc[half_point:, "fitted_demand"])
# Grafikon megjelenítése
plt.figure(figsize=(10, 5))
plt.plot(df["nr"], df["demand"], label="Actual Demand", color="grey")
plt.plot(df["nr"], df["fitted_demand"], label="Fitted Demand", color="blue", linestyle='--')
plt.axvline(x=half_point, color='red', label='Training/Test Split', linestyle='--') # Piros vonal a felosztási pontnál
plt.title(f"Demand Forecast - Training MAE: {train_mae:.2f}, Test MAE: {test_mae:.2f}")
plt.xlabel("nr")
plt.ylabel("Demand")
plt.legend()
plt.grid(True)
plt.show()
# Függvény indítása
run_analysis_2()
Ahogy látszik a lineáris illesztés az új, nem látott adatokon nagyon rosszul teljesít. Továbbvisszük a lineáris trendet, ami a tesztperiódusban már értelmét veszti. Ez látszik is a két adathalmaz MAE értékének arányán: majdnem 50%-kal rosszabb tesztértéket kapunk.
Emlékezzünk: Amikor a teljes adathalmazra végeztünk illesztést, 30 000-es MAE értéket kaptunk, ami az SE eredményét közelítő eredmény. Mégis, amint elvégeztük az elemzést a teszt halmazon, látszik hogy a használatot erős fenntartással kellene kezelnünk.
06. Underfitting javítása
A modell érzékenységének javítására két módszerünk van. Egyrészt, ahogy a bejegyzésből is látszik választhatunk egy komplexebb működésű modellt. Másik lehetőség, hogy keresünk további változókat, melyek magyarázatot adnak bizonyos időszakok eltérő viselkedésére. Ilyen lehet például a hétvégi értékesítési adatok különbözősége, promóció. A nehézség abban áll, hogy statisztikai módszertanokat használva ez külön modell megépítését jelentené, hogy két vagy több modell együtt már érzékeny legyen a változásokra.
Ez a gyakorlatban nehezen kezelhető, viszont itt lép képbe a gépi tanulás eszközei, melyekkel már nem csak felismerni lesz képes a modellünk egy mintázatot, de képes lesz értelmezni is a történéseket. Ebben az irányban teszünk lépéseket a következőkben.
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